Exemple #1
0
def donation_address(reusable_donation_pubkey=None): #pragma: no cover
    #Donation code currently disabled, so not tested.
    if not reusable_donation_pubkey:
        reusable_donation_pubkey = ('02be838257fbfddabaea03afbb9f16e852'
                                    '9dfe2de921260a5c46036d97b5eacf2a')
    sign_k = binascii.hexlify(os.urandom(32))
    c = btc.sha256(btc.multiply(sign_k, reusable_donation_pubkey, True))
    sender_pubkey = btc.add_pubkeys(
        [reusable_donation_pubkey, btc.privtopub(c + '01', True)], True)
    sender_address = btc.pubtoaddr(sender_pubkey, get_p2pk_vbyte())
    log.debug('sending coins to ' + sender_address)
    return sender_address, sign_k
Exemple #2
0
    def verify_unsigned_tx(self, txd):
        tx_utxo_set = set(ins['outpoint']['hash'] + ':' + str(
                ins['outpoint']['index']) for ins in txd['ins'])
        # complete authentication: check the tx input uses the authing pubkey
        input_utxo_data = jm_single().bc_interface.query_utxo_set(
                list(tx_utxo_set))

        if None in input_utxo_data:
            return False, 'some utxos already spent or not confirmed yet'
        input_addresses = [u['address'] for u in input_utxo_data]
        if btc.pubtoaddr(
                self.i_utxo_pubkey, get_p2pk_vbyte()) not in input_addresses:
            return False, "authenticating bitcoin address is not contained"

        my_utxo_set = set(self.utxos.keys())
        if not tx_utxo_set.issuperset(my_utxo_set):
            return False, 'my utxos are not contained'

        my_total_in = sum([va['value'] for va in self.utxos.values()])
        self.real_cjfee = calc_cj_fee(
                self.ordertype, self.cjfee, self.cj_amount)
        expected_change_value = (
            my_total_in - self.cj_amount - self.txfee + self.real_cjfee)
        log.debug('potentially earned = {}'.format(
                self.real_cjfee - self.txfee))
        log.debug('mycjaddr, mychange = {}, {}'.format(
                self.cj_addr, self.change_addr))

        times_seen_cj_addr = 0
        times_seen_change_addr = 0
        for outs in txd['outs']:
            addr = btc.script_to_address(outs['script'], get_p2pk_vbyte())
            if addr == self.cj_addr:
                times_seen_cj_addr += 1
                if outs['value'] != self.cj_amount:
                    return False, 'Wrong cj_amount. I expect ' + str(
                            self.cj_amount)
            if addr == self.change_addr:
                times_seen_change_addr += 1
                if outs['value'] != expected_change_value:
                    return False, 'wrong change, i expect ' + str(
                            expected_change_value)
        if times_seen_cj_addr != 1 or times_seen_change_addr != 1:
            fmt = ('cj or change addr not in tx '
                   'outputs once, #cjaddr={}, #chaddr={}').format
            return False, (fmt(times_seen_cj_addr, times_seen_change_addr))
        return True, None
Exemple #3
0
    def verify_unsigned_tx(self, txd):
        tx_utxo_set = set(ins['outpoint']['hash'] + ':' +
                          str(ins['outpoint']['index']) for ins in txd['ins'])
        # complete authentication: check the tx input uses the authing pubkey
        input_utxo_data = jm_single().bc_interface.query_utxo_set(
            list(tx_utxo_set))

        if None in input_utxo_data:
            return False, 'some utxos already spent or not confirmed yet'
        input_addresses = [u['address'] for u in input_utxo_data]
        if btc.pubtoaddr(self.i_utxo_pubkey,
                         get_p2pk_vbyte()) not in input_addresses:
            return False, "authenticating bitcoin address is not contained"

        my_utxo_set = set(self.utxos.keys())
        if not tx_utxo_set.issuperset(my_utxo_set):
            return False, 'my utxos are not contained'

        my_total_in = sum([va['value'] for va in self.utxos.values()])
        self.real_cjfee = calc_cj_fee(self.ordertype, self.cjfee,
                                      self.cj_amount)
        expected_change_value = (my_total_in - self.cj_amount - self.txfee +
                                 self.real_cjfee)
        log.debug('potentially earned = {}'.format(self.real_cjfee -
                                                   self.txfee))
        log.debug('mycjaddr, mychange = {}, {}'.format(self.cj_addr,
                                                       self.change_addr))

        times_seen_cj_addr = 0
        times_seen_change_addr = 0
        for outs in txd['outs']:
            addr = btc.script_to_address(outs['script'], get_p2pk_vbyte())
            if addr == self.cj_addr:
                times_seen_cj_addr += 1
                if outs['value'] != self.cj_amount:
                    return False, 'Wrong cj_amount. I expect ' + str(
                        self.cj_amount)
            if addr == self.change_addr:
                times_seen_change_addr += 1
                if outs['value'] != expected_change_value:
                    return False, 'wrong change, i expect ' + str(
                        expected_change_value)
        if times_seen_cj_addr != 1 or times_seen_change_addr != 1:
            fmt = ('cj or change addr not in tx '
                   'outputs once, #cjaddr={}, #chaddr={}').format
            return False, (fmt(times_seen_cj_addr, times_seen_change_addr))
        return True, None
Exemple #4
0
def donation_address(cjtx):
    from bitcoin.main import multiply, G, deterministic_generate_k, add_pubkeys
    reusable_donation_pubkey = ('02be838257fbfddabaea03afbb9f16e852'
                                '9dfe2de921260a5c46036d97b5eacf2a')

    donation_utxo_data = cjtx.input_utxos.iteritems().next()
    global donation_utxo
    donation_utxo = donation_utxo_data[0]
    privkey = cjtx.wallet.get_key_from_addr(donation_utxo_data[1]['address'])
    # tx without our inputs and outputs
    tx = btc.mktx(cjtx.utxo_tx, cjtx.outputs)
    msghash = btc.bin_txhash(tx, btc.SIGHASH_ALL)
    # generate unpredictable k
    global sign_k
    sign_k = deterministic_generate_k(msghash, privkey)
    c = btc.sha256(multiply(reusable_donation_pubkey, sign_k))
    sender_pubkey = add_pubkeys(reusable_donation_pubkey, multiply(G, c))
    sender_address = btc.pubtoaddr(sender_pubkey, get_p2pk_vbyte())
    log.debug('sending coins to ' + sender_address)
    return sender_address
Exemple #5
0
def donation_address(cjtx):
    from bitcoin.main import multiply, G, deterministic_generate_k, add_pubkeys
    reusable_donation_pubkey = ('02be838257fbfddabaea03afbb9f16e852'
                                '9dfe2de921260a5c46036d97b5eacf2a')

    donation_utxo_data = cjtx.input_utxos.iteritems().next()
    global donation_utxo
    donation_utxo = donation_utxo_data[0]
    privkey = cjtx.wallet.get_key_from_addr(donation_utxo_data[1]['address'])
    # tx without our inputs and outputs
    tx = btc.mktx(cjtx.utxo_tx, cjtx.outputs)
    msghash = btc.bin_txhash(tx, btc.SIGHASH_ALL)
    # generate unpredictable k
    global sign_k
    sign_k = deterministic_generate_k(msghash, privkey)
    c = btc.sha256(multiply(reusable_donation_pubkey, sign_k))
    sender_pubkey = add_pubkeys(
            reusable_donation_pubkey, multiply(
                    G, c))
    sender_address = btc.pubtoaddr(sender_pubkey, get_p2pk_vbyte())
    log.debug('sending coins to ' + sender_address)
    return sender_address
Exemple #6
0
    def recv_txio(self, nick, utxo_list, cj_pub, change_addr):
        if nick not in self.nonrespondants:
            log.debug(('recv_txio => nick={} not in '
                       'nonrespondants {}').format(nick, self.nonrespondants))
            return
        self.utxos[nick] = utxo_list
        log.debug("Trying to retrieve utxos for: " + nick)
        utxo_data = jm_single().bc_interface.query_utxo_set(self.utxos[nick])
        log.debug("Got this utxo data: " + pprint.pformat(utxo_data))
        if None in utxo_data:
            log.debug(('ERROR outputs unconfirmed or already spent. '
                       'utxo_data={}').format(pprint.pformat(utxo_data)))
            # when internal reviewing of makers is created, add it here to
            # immediately quit; currently, the timeout thread suffices.
            return

        total_input = sum([d['value'] for d in utxo_data])
        real_cjfee = calc_cj_fee(self.active_orders[nick]['ordertype'],
                       self.active_orders[nick]['cjfee'], self.cj_amount)
        change_amount = (total_input - self.cj_amount -
            self.active_orders[nick]['txfee'] + real_cjfee)

        # certain malicious and/or incompetent liquidity providers send
        # inputs totalling less than the coinjoin amount! this leads to
        # a change output of zero satoshis, so the invalid transaction
        # fails harmlessly; let's fail earlier, with a clear message.
        if change_amount < jm_single().DUST_THRESHOLD:
            fmt = ('ERROR counterparty requires sub-dust change. nick={}'
                   'totalin={:d} cjamount={:d} change={:d}').format
            log.debug(fmt(nick, total_input, self.cj_amount, change_amount))
            return              # timeout marks this maker as nonresponsive

        self.outputs.append({'address': change_addr, 'value': change_amount})
        fmt = ('fee breakdown for {} totalin={:d} '
               'cjamount={:d} txfee={:d} realcjfee={:d}').format
        log.debug(fmt(nick, total_input, self.cj_amount,
            self.active_orders[nick]['txfee'], real_cjfee))
        cj_addr = btc.pubtoaddr(cj_pub, get_p2pk_vbyte())
        self.outputs.append({'address': cj_addr, 'value': self.cj_amount})
        self.cjfee_total += real_cjfee
        self.maker_txfee_contributions += self.active_orders[nick]['txfee']
        self.nonrespondants.remove(nick)
        if len(self.nonrespondants) > 0:
            log.debug('nonrespondants = ' + str(self.nonrespondants))
            return
        log.debug('got all parts, enough to build a tx')
        self.nonrespondants = list(self.active_orders.keys())

        my_total_in = sum([va['value'] for u, va in
                           self.input_utxos.iteritems()])
        if self.my_change_addr:
            #Estimate fee per choice of next/3/6 blocks targetting.
            estimated_fee = estimate_tx_fee(len(sum(
                self.utxos.values(),[])), len(self.outputs)+2)
            log.debug("Based on initial guess: "+str(
                self.total_txfee)+", we estimated a fee of: "+str(estimated_fee))
            #reset total
            self.total_txfee = estimated_fee
        my_txfee = max(self.total_txfee - self.maker_txfee_contributions, 0)
        my_change_value = (
            my_total_in - self.cj_amount - self.cjfee_total - my_txfee)
        #Since we could not predict the maker's inputs, we may end up needing
        #too much such that the change value is negative or small. Note that 
        #we have tried to avoid this based on over-estimating the needed amount
        #in SendPayment.create_tx(), but it is still a possibility if one maker
        #uses a *lot* of inputs.
        if self.my_change_addr and my_change_value <= 0:
            raise ValueError("Calculated transaction fee of: "+str(
                self.total_txfee)+" is too large for our inputs;Please try again.")
        elif self.my_change_addr and my_change_value <= jm_single().DUST_THRESHOLD:
            log.debug("Dynamically calculated change lower than dust: "+str(
                my_change_value)+"; dropping.")
            self.my_change_addr = None
            my_change_value = 0
        log.debug('fee breakdown for me totalin=%d my_txfee=%d makers_txfee=%d cjfee_total=%d => changevalue=%d'
                  % (my_total_in, my_txfee, self.maker_txfee_contributions,            
                  self.cjfee_total, my_change_value))
        if self.my_change_addr is None:
            if my_change_value != 0 and abs(my_change_value) != 1:
                # seems you wont always get exactly zero because of integer
                # rounding so 1 satoshi extra or fewer being spent as miner
                # fees is acceptable
                log.debug(('WARNING CHANGE NOT BEING '
                           'USED\nCHANGEVALUE = {}').format(my_change_value))
        else:
            self.outputs.append({'address': self.my_change_addr,
                                 'value': my_change_value})
        self.utxo_tx = [dict([('output', u)])
                        for u in sum(self.utxos.values(), [])]
        self.outputs.append({'address': self.coinjoin_address(),
                             'value': self.cj_amount})
        random.shuffle(self.utxo_tx)
        random.shuffle(self.outputs)
        tx = btc.mktx(self.utxo_tx, self.outputs)
        log.debug('obtained tx\n' + pprint.pformat(btc.deserialize(tx)))
        #Re-calculate a sensible timeout wait based on the throttling
        #settings and the tx size.
        #Calculation: Let tx size be S; tx undergoes two b64 expansions, 1.8*S
        #So we're sending N*1.8*S over the wire, and the
        #maximum bytes/sec = B, means we need (1.8*N*S/B) seconds,
        #and need to add some leeway for network delays, we just add the
        #contents of jm_single().maker_timeout_sec (the user configured value)
        self.maker_timeout_sec = (len(tx) * 1.8 * len(
            self.active_orders.keys()))/(B_PER_SEC) + jm_single().maker_timeout_sec
        log.debug("Based on transaction size: " + str(
            len(tx)) + ", calculated time to wait for replies: " + str(
            self.maker_timeout_sec))
        self.all_responded = True
        with self.timeout_lock:
            self.timeout_lock.notify()
        self.msgchan.send_tx(self.active_orders.keys(), tx)

        self.latest_tx = btc.deserialize(tx)
        for index, ins in enumerate(self.latest_tx['ins']):
            utxo = ins['outpoint']['hash'] + ':' + str(
                    ins['outpoint']['index'])
            if utxo not in self.input_utxos.keys():
                continue
            # placeholders required
            ins['script'] = 'deadbeef'
Exemple #7
0
    def recv_txio(self, nick, utxo_list, cj_pub, change_addr):
        if nick not in self.nonrespondants:
            log.debug(('recv_txio => nick={} not in '
                       'nonrespondants {}').format(nick, self.nonrespondants))
            return
        self.utxos[nick] = utxo_list
        log.debug("Trying to retrieve utxos for: " + nick)
        utxo_data = jm_single().bc_interface.query_utxo_set(self.utxos[nick])
        log.debug("Got this utxo data: " + pprint.pformat(utxo_data))
        if None in utxo_data:
            log.debug(('ERROR outputs unconfirmed or already spent. '
                       'utxo_data={}').format(pprint.pformat(utxo_data)))
            # when internal reviewing of makers is created, add it here to
            # immediately quit; currently, the timeout thread suffices.
            return

        total_input = sum([d['value'] for d in utxo_data])
        real_cjfee = calc_cj_fee(self.active_orders[nick]['ordertype'],
                                 self.active_orders[nick]['cjfee'],
                                 self.cj_amount)
        change_amount = (total_input - self.cj_amount -
                         self.active_orders[nick]['txfee'] + real_cjfee)

        # certain malicious and/or incompetent liquidity providers send
        # inputs totalling less than the coinjoin amount! this leads to
        # a change output of zero satoshis, so the invalid transaction
        # fails harmlessly; let's fail earlier, with a clear message.
        if change_amount < jm_single().DUST_THRESHOLD:
            fmt = ('ERROR counterparty requires sub-dust change. nick={}'
                   'totalin={:d} cjamount={:d} change={:d}').format
            log.debug(fmt(nick, total_input, self.cj_amount, change_amount))
            return  # timeout marks this maker as nonresponsive

        self.outputs.append({'address': change_addr, 'value': change_amount})
        fmt = ('fee breakdown for {} totalin={:d} '
               'cjamount={:d} txfee={:d} realcjfee={:d}').format
        log.debug(
            fmt(nick, total_input, self.cj_amount,
                self.active_orders[nick]['txfee'], real_cjfee))
        cj_addr = btc.pubtoaddr(cj_pub, get_p2pk_vbyte())
        self.outputs.append({'address': cj_addr, 'value': self.cj_amount})
        self.cjfee_total += real_cjfee
        self.maker_txfee_contributions += self.active_orders[nick]['txfee']
        self.nonrespondants.remove(nick)
        if len(self.nonrespondants) > 0:
            log.debug('nonrespondants = ' + str(self.nonrespondants))
            return
        log.debug('got all parts, enough to build a tx')
        self.nonrespondants = list(self.active_orders.keys())

        my_total_in = sum(
            [va['value'] for u, va in self.input_utxos.iteritems()])
        if self.my_change_addr:
            #Estimate fee per choice of next/3/6 blocks targetting.
            estimated_fee = estimate_tx_fee(len(sum(self.utxos.values(), [])),
                                            len(self.outputs) + 2)
            log.debug("Based on initial guess: " + str(self.total_txfee) +
                      ", we estimated a fee of: " + str(estimated_fee))
            #reset total
            self.total_txfee = estimated_fee
        my_txfee = max(self.total_txfee - self.maker_txfee_contributions, 0)
        my_change_value = (my_total_in - self.cj_amount - self.cjfee_total -
                           my_txfee)
        #Since we could not predict the maker's inputs, we may end up needing
        #too much such that the change value is negative or small. Note that
        #we have tried to avoid this based on over-estimating the needed amount
        #in SendPayment.create_tx(), but it is still a possibility if one maker
        #uses a *lot* of inputs.
        if self.my_change_addr and my_change_value <= 0:
            raise ValueError("Calculated transaction fee of: " +
                             str(self.total_txfee) +
                             " is too large for our inputs;Please try again.")
        elif self.my_change_addr and my_change_value <= jm_single(
        ).DUST_THRESHOLD:
            log.debug("Dynamically calculated change lower than dust: " +
                      str(my_change_value) + "; dropping.")
            self.my_change_addr = None
            my_change_value = 0
        log.debug(
            'fee breakdown for me totalin=%d my_txfee=%d makers_txfee=%d cjfee_total=%d => changevalue=%d'
            % (my_total_in, my_txfee, self.maker_txfee_contributions,
               self.cjfee_total, my_change_value))
        if self.my_change_addr is None:
            if my_change_value != 0 and abs(my_change_value) != 1:
                # seems you wont always get exactly zero because of integer
                # rounding so 1 satoshi extra or fewer being spent as miner
                # fees is acceptable
                log.debug(('WARNING CHANGE NOT BEING '
                           'USED\nCHANGEVALUE = {}').format(my_change_value))
        else:
            self.outputs.append({
                'address': self.my_change_addr,
                'value': my_change_value
            })
        self.utxo_tx = [
            dict([('output', u)]) for u in sum(self.utxos.values(), [])
        ]
        self.outputs.append({
            'address': self.coinjoin_address(),
            'value': self.cj_amount
        })
        random.shuffle(self.utxo_tx)
        random.shuffle(self.outputs)
        tx = btc.mktx(self.utxo_tx, self.outputs)
        log.debug('obtained tx\n' + pprint.pformat(btc.deserialize(tx)))
        #Re-calculate a sensible timeout wait based on the throttling
        #settings and the tx size.
        #Calculation: Let tx size be S; tx undergoes two b64 expansions, 1.8*S
        #So we're sending N*1.8*S over the wire, and the
        #maximum bytes/sec = B, means we need (1.8*N*S/B) seconds,
        #and need to add some leeway for network delays, we just add the
        #contents of jm_single().maker_timeout_sec (the user configured value)
        self.maker_timeout_sec = (len(tx) * 1.8 * len(self.active_orders.keys(
        ))) / (B_PER_SEC) + jm_single().maker_timeout_sec
        log.debug("Based on transaction size: " + str(len(tx)) +
                  ", calculated time to wait for replies: " +
                  str(self.maker_timeout_sec))
        self.all_responded = True
        with self.timeout_lock:
            self.timeout_lock.notify()
        self.msgchan.send_tx(self.active_orders.keys(), tx)

        self.latest_tx = btc.deserialize(tx)
        for index, ins in enumerate(self.latest_tx['ins']):
            utxo = ins['outpoint']['hash'] + ':' + str(
                ins['outpoint']['index'])
            if utxo not in self.input_utxos.keys():
                continue
            # placeholders required
            ins['script'] = 'deadbeef'