def test_bip143_tv(): # p2sh-p2wpkh case: rawtx_hex = "0100000001db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a54770100000000feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac92040000" inp_spk_hex = "a9144733f37cf4db86fbc2efed2500b4f4e49f31202387" value = 10 redeemScript = "001479091972186c449eb1ded22b78e40d009bdf0089" privkey_hex = "eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf01" pubkey_hex = "03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873" tx = btc.CMutableTransaction.deserialize(btc.x(rawtx_hex)) btc.sign(tx, 0, btc.x(privkey_hex), amount=btc.coins_to_satoshi(10), native=False) expectedsignedtx = "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009bdf0089feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac02473044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb012103ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a2687392040000" assert btc.b2x(tx.serialize()) == expectedsignedtx
def test_sign_standard_txs(addrtype): # liberally copied from python-bitcoinlib tests, # in particular see: # https://github.com/petertodd/python-bitcoinlib/pull/227 # Create the (in)famous correct brainwallet secret key. priv = hashlib.sha256(b'correct horse battery staple').digest() + b"\x01" pub = btc.privkey_to_pubkey(priv) # Create an address from that private key. # (note that the input utxo is fake so we are really only creating # a destination here). scriptPubKey = btc.CScript([btc.OP_0, btc.Hash160(pub)]) address = btc.P2WPKHCoinAddress.from_scriptPubKey(scriptPubKey) # Create a dummy outpoint; use same 32 bytes for convenience txid = priv[:32] vout = 2 amount = btc.coins_to_satoshi(float('0.12345')) # Calculate an amount for the upcoming new UTXO. Set a high fee to bypass # bitcoind minfee setting. amount_less_fee = int(amount - btc.coins_to_satoshi(0.01)) # Create a destination to send the coins. destination_address = address target_scriptPubKey = scriptPubKey # Create the unsigned transaction. txin = btc.CTxIn(btc.COutPoint(txid[::-1], vout)) txout = btc.CTxOut(amount_less_fee, target_scriptPubKey) tx = btc.CMutableTransaction([txin], [txout]) # Calculate the signature hash for the transaction. This is then signed by the # private key that controls the UTXO being spent here at this txin_index. if addrtype == "p2wpkh": sig, msg = btc.sign(tx, 0, priv, amount=amount, native="p2wpkh") elif addrtype == "p2sh-p2wpkh": sig, msg = btc.sign(tx, 0, priv, amount=amount, native=False) elif addrtype == "p2pkh": sig, msg = btc.sign(tx, 0, priv) else: assert False if not sig: print(msg) raise print("created signature: ", bintohex(sig)) print("serialized transaction: {}".format(bintohex(tx.serialize()))) print("deserialized transaction: {}\n".format( btc.human_readable_transaction(tx)))
def sign(utxo, priv, destaddrs, utxo_address_type): """Sign a tx sending the amount amt, from utxo utxo, equally to each of addresses in list destaddrs, after fees; the purpose is to create multiple utxos. utxo_address_type must be one of p2sh-p2wpkh/p2wpkh/p2pkh. """ results = validate_utxo_data([(utxo, priv)], retrieve=True, utxo_address_type=utxo_address_type) if not results: return False assert results[0][0] == utxo amt = results[0][1] ins = [utxo] estfee = estimate_tx_fee(1, len(destaddrs), txtype=utxo_address_type) outs = [] share = int((amt - estfee) / len(destaddrs)) fee = amt - share*len(destaddrs) assert fee >= estfee log.info("Using fee: " + str(fee)) for i, addr in enumerate(destaddrs): outs.append({'address': addr, 'value': share}) tx = btc.make_shuffled_tx(ins, outs, version=2, locktime=compute_tx_locktime()) amtforsign = amt if utxo_address_type != "p2pkh" else None rawpriv, _ = BTCEngine.wif_to_privkey(priv) if utxo_address_type == "p2wpkh": native = utxo_address_type else: native = False success, msg = btc.sign(tx, 0, rawpriv, amount=amtforsign, native=native) assert success, msg return tx
def make_tx_add_notify(): wallet_dict = make_wallets(1, [[1, 0, 0, 0, 0]], mean_amt=4, sdev_amt=0)[0] amount = 250000000 txfee = 10000 wallet = wallet_dict['wallet'] sync_wallet(wallet) inputs = wallet.select_utxos(0, amount) ins = inputs.keys() input_value = sum([i['value'] for i in inputs.values()]) output_addr = wallet.get_new_addr(1, 0) change_addr = wallet.get_new_addr(0, 1) outs = [{ 'value': amount, 'address': output_addr }, { 'value': input_value - amount - txfee, 'address': change_addr }] tx = bitcoin.mktx(ins, outs) de_tx = bitcoin.deserialize(tx) for index, ins in enumerate(de_tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index']) addr = inputs[utxo]['address'] priv = wallet.get_key_from_addr(addr) tx = bitcoin.sign(tx, index, priv) unconfirm_called[0] = confirm_called[0] = False timeout_unconfirm_called[0] = timeout_confirm_called[0] = False jm_single().bc_interface.add_tx_notify(bitcoin.deserialize(tx), unconfirm_callback, confirm_callback, output_addr, timeout_callback) return tx
def sign_transaction(cls, tx, index, privkey_locktime, amount, hashcode=btc.SIGHASH_ALL, **kwargs): assert amount is not None priv, locktime = privkey_locktime pub = cls.privkey_to_pubkey(priv) redeem_script = cls.pubkey_to_script_code((pub, locktime)) return btc.sign(tx, index, priv, amount=amount, native=redeem_script)
def sign(utxo, priv, destaddrs, segwit=True): """Sign a tx sending the amount amt, from utxo utxo, equally to each of addresses in list destaddrs, after fees; the purpose is to create a large number of utxos. If segwit=True the (single) utxo is assumed to be of type segwit p2sh/p2wpkh. """ results = validate_utxo_data([(utxo, priv)], retrieve=True, segwit=segwit) if not results: return False assert results[0][0] == utxo amt = results[0][1] ins = [utxo] # TODO extend to other utxo types txtype = 'p2sh-p2wpkh' if segwit else 'p2pkh' estfee = estimate_tx_fee(1, len(destaddrs), txtype=txtype) outs = [] share = int((amt - estfee) / len(destaddrs)) fee = amt - share * len(destaddrs) assert fee >= estfee log.info("Using fee: " + str(fee)) for i, addr in enumerate(destaddrs): outs.append({'address': addr, 'value': share}) unsigned_tx = btc.mktx(ins, outs) amtforsign = amt if segwit else None return btc.sign(unsigned_tx, 0, btc.from_wif_privkey(priv, vbyte=get_p2pk_vbyte()), amount=amtforsign)
def create_single_acp_pair(utxo_in, priv, addr_out, amount, bump, segwit=False): """Given a utxo and a signing key for it, and its amout in satoshis, sign a "transaction" consisting of only 1 input and one output, signed with single|acp sighash flags so it can be grafted into a bigger transaction. Also provide a destination address and a 'bump' value (so the creator can claim more output in the final transaction. Note that, for safety, bump *should* be positive if the recipient is untrusted, since otherwise they can waste your money by simply broadcasting this transaction without adding any inputs of their own. Returns the serialized 1 in, 1 out, and signed transaction. """ assert bump >= 0, "Output of single|acp pair must be bigger than input for safety." out = {"address": addr_out, "value": amount + bump} tx = btc.mktx([utxo_in], [out]) amt = amount if segwit else None return btc.sign(tx, 0, priv, hashcode=btc.SIGHASH_SINGLE | btc.SIGHASH_ANYONECANPAY, amount=amt)
def test_spend_p2wpkh(setup_tx_creation): #make 3 p2wpkh outputs from 3 privs privs = [struct.pack(b'B', x) * 32 + b'\x01' for x in range(1, 4)] pubs = [bitcoin.privkey_to_pubkey(priv) for priv in privs] scriptPubKeys = [bitcoin.pubkey_to_p2wpkh_script(pub) for pub in pubs] addresses = [str(bitcoin.CCoinAddress.from_scriptPubKey( spk)) for spk in scriptPubKeys] #pay into it wallet_service = make_wallets(1, [[3, 0, 0, 0, 0]], 3)[0]['wallet'] wallet_service.sync_wallet(fast=True) amount = 35000000 p2wpkh_ins = [] for i, addr in enumerate(addresses): ins_full = wallet_service.select_utxos(0, amount) txid = make_sign_and_push(ins_full, wallet_service, amount, output_addr=addr) assert txid p2wpkh_ins.append((txid, 0)) txhex = jm_single().bc_interface.get_transaction(txid) #wait for mining jm_single().bc_interface.tick_forward_chain(1) #random output address output_addr = wallet_service.get_internal_addr(1) amount2 = amount*3 - 50000 outs = [{'value': amount2, 'address': output_addr}] tx = bitcoin.mktx(p2wpkh_ins, outs) for i, priv in enumerate(privs): # sign each of 3 inputs; note that bitcoin.sign # automatically validates each signature it creates. sig, msg = bitcoin.sign(tx, i, priv, amount=amount, native="p2wpkh") if not sig: assert False, msg txid = jm_single().bc_interface.pushtx(tx.serialize()) assert txid
def sign_transaction(cls, tx, index, privkey, *args, **kwargs): hashcode = kwargs.get('hashcode') or btc.SIGHASH_ALL return btc.sign(tx, index, privkey, hashcode=hashcode, amount=None, native=False)
def create_coinjoin_proposal(bobdata, alicedata, verbose=True, incentive=0): """A very crude/static implementation of a coinjoin for SNICKER. **VERY DELIBERATELY STUPIDLY SIMPLE VERSION!** We assume only one utxo for each side (this will certainly change, Alice side, for flexibility). Two outputs equal size are created with 1 change for Alice also (Bob's utxo is completely satisfied by 1 output). The data for each side is utxo, and amount; alice must provide privkey for partial sign. All scriptpubkeys assumed p2sh/p2wpkh for now. Bob's destination is tweaked and included as a destination which he will verify. What is returned is (tweak, partially signed tx) which is enough information for Bob to complete. """ fee = estimate_tx_fee(2, 3, 'p2sh-p2wpkh') bob_utxo, bob_pubkey, amount = bobdata alice_utxo, alice_privkey, alice_amount, alice_destination, change = alicedata ins = [bob_utxo, alice_utxo] random.shuffle(ins) tweak, dest_pt, bob_destination = create_recipient_address(bob_pubkey, segwit=True) print('using amount, alice_amount,incentive, fee: ' + ','.join([str(x) for x in [amount, alice_amount, incentive, fee]])) coinjoin_amount = amount + incentive change_amount = alice_amount - coinjoin_amount - incentive - fee outs = [{ "address": alice_destination, "value": coinjoin_amount }, { "address": bob_destination, "value": coinjoin_amount }, { "address": change, "value": change_amount }] random.shuffle(outs) unsigned_tx = btc.mktx(ins, outs) if verbose: print('here is proposed transaction:\n', pformat(btc.deserialize(unsigned_tx))) print('destination for Bob: ', bob_destination) print('destination for Alice: ', alice_destination) print('destination for Alice change: ', change) if not raw_input("Is this acceptable? (y/n):") == "y": return (None, None) #Alice signs her input; assuming segwit here for now partially_signed_tx = btc.sign(unsigned_tx, 1, alice_privkey, amount=alice_amount) #return the material to be sent to Bob return (tweak, partially_signed_tx)
def sign_transaction(cls, tx, index, privkey, amount, hashcode=btc.SIGHASH_ALL, **kwargs): assert amount is not None return btc.sign(tx, index, privkey, hashcode=hashcode, amount=amount, native="p2wpkh")
def test_verify_tx_input(setup_tx_creation, signall, mktxlist): priv = "aa" * 32 + "01" addr = bitcoin.privkey_to_address(priv, magicbyte=get_p2pk_vbyte()) wallet = make_wallets(1, [[2, 0, 0, 0, 0]], 1)[0]['wallet'] sync_wallet(wallet) insfull = wallet.select_utxos(0, 110000000) print(insfull) if not mktxlist: outs = [{"address": addr, "value": 1000000}] ins = insfull.keys() tx = bitcoin.mktx(ins, outs) else: out1 = addr + ":1000000" ins0, ins1 = insfull.keys() print("INS0 is: " + str(ins0)) print("INS1 is: " + str(ins1)) tx = bitcoin.mktx(ins0, ins1, out1) desertx = bitcoin.deserialize(tx) print(desertx) if signall: privdict = {} for index, ins in enumerate(desertx['ins']): utxo = ins['outpoint']['hash'] + ':' + str( ins['outpoint']['index']) ad = insfull[utxo]['address'] priv = wallet.get_key_from_addr(ad) privdict[utxo] = priv tx = bitcoin.signall(tx, privdict) else: for index, ins in enumerate(desertx['ins']): utxo = ins['outpoint']['hash'] + ':' + str( ins['outpoint']['index']) ad = insfull[utxo]['address'] priv = wallet.get_key_from_addr(ad) if index % 2: tx = binascii.unhexlify(tx) tx = bitcoin.sign(tx, index, priv) if index % 2: tx = binascii.hexlify(tx) desertx2 = bitcoin.deserialize(tx) print(desertx2) sig, pub = bitcoin.deserialize_script(desertx2['ins'][0]['script']) print(sig, pub) pubscript = bitcoin.address_to_script( bitcoin.pubkey_to_address(pub, magicbyte=get_p2pk_vbyte())) sig = binascii.unhexlify(sig) pub = binascii.unhexlify(pub) sig_good = bitcoin.verify_tx_input(tx, 0, pubscript, sig, pub) assert sig_good
def cli_broadcast(wallet_name, partial_tx_hex): """Given a partially signed transaction retrieved by running this script with the -r flag, and assuming that the utxo with which the transaction was made is in a Joinmarket wallet, this function will complete the signing and then broadcast the transaction. This function is useful if the *receiver*'s wallet is Joinmarket; if it is Core then the workflow is just `signrawtransaction` then `sendrawtransaction`; should be similar for Electrum although haven't tried. """ wallet = cli_get_wallet(wallet_name) tx = btc.deserialize(partial_tx_hex) num_sigs = 0 for index, ins in enumerate(tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index']) #is the utxo in our utxos? in_wallet_utxos = wallet.get_utxos_by_mixdepth(False) for m, um in in_wallet_utxos.iteritems(): for k, v in um.iteritems(): if k == utxo: print("Found utxo in mixdepth: ", m) if isinstance(wallet, SegwitWallet): amount = v['value'] else: amount = None signed_tx = btc.sign(partial_tx_hex, index, wallet.get_key_from_addr( v['address']), amount=amount) num_sigs += 1 if num_sigs != 1: print("Something wrong, expected to get 1 sig, got: ", num_sigs) return #should be fully signed; broadcast? print("Signed tx in hex:") print(signed_tx) print("In decoded form:") print(pformat(btc.deserialize(signed_tx))) if not raw_input("Broadcast to network? (y/n): ") == "y": print("You chose not to broadcast, quitting.") return txid = btc.txhash(signed_tx) print('txid = ' + txid) pushed = jm_single().bc_interface.pushtx(signed_tx) if not pushed: print("Broadcast failed.") else: print("Broadcast was successful.")
def test_spend_freeze_script(setup_tx_creation): ensure_bip65_activated() wallet_service = make_wallets(1, [[3, 0, 0, 0, 0]], 3)[0]['wallet'] wallet_service.sync_wallet(fast=True) mediantime = jm_single().bc_interface.rpc("getblockchaininfo", [])["mediantime"] timeoffset_success_tests = [(2, False), (-60 * 60 * 24 * 30, True), (60 * 60 * 24 * 30, False)] for timeoffset, required_success in timeoffset_success_tests: #generate keypair priv = b"\xaa" * 32 + b"\x01" pub = bitcoin.privkey_to_pubkey(priv) addr_locktime = mediantime + timeoffset redeem_script = bitcoin.mk_freeze_script(pub, addr_locktime) script_pub_key = bitcoin.redeem_script_to_p2wsh_script(redeem_script) # cannot convert to address within wallet service, as not known # to wallet; use engine directly: addr = wallet_service._ENGINE.script_to_address(script_pub_key) #fund frozen funds address amount = 100000000 funding_ins_full = wallet_service.select_utxos(0, amount) funding_txid = make_sign_and_push(funding_ins_full, wallet_service, amount, output_addr=addr) assert funding_txid #spend frozen funds frozen_in = (funding_txid, 0) output_addr = wallet_service.get_internal_addr(1) miner_fee = 5000 outs = [{'value': amount - miner_fee, 'address': output_addr}] tx = bitcoin.mktx([frozen_in], outs, locktime=addr_locktime + 1) i = 0 sig, success = bitcoin.sign(tx, i, priv, amount=amount, native=redeem_script) assert success push_success = jm_single().bc_interface.pushtx(tx.serialize()) assert push_success == required_success
def make_sign_and_push(ins_full, wallet_service, amount, output_addr=None, change_addr=None, hashcode=btc.SIGHASH_ALL, estimate_fee=False): """Utility function for easily building transactions from wallets. """ assert isinstance(wallet_service, WalletService) total = sum(x['value'] for x in ins_full.values()) ins = ins_full.keys() #random output address and change addr output_addr = wallet_service.get_new_addr( 1, BaseWallet.ADDRESS_TYPE_INTERNAL) if not output_addr else output_addr change_addr = wallet_service.get_new_addr( 0, BaseWallet.ADDRESS_TYPE_INTERNAL) if not change_addr else change_addr fee_est = estimate_tx_fee(len(ins), 2) if estimate_fee else 10000 outs = [{ 'value': amount, 'address': output_addr }, { 'value': total - amount - fee_est, 'address': change_addr }] tx = btc.mktx(ins, outs) de_tx = btc.deserialize(tx) for index, ins in enumerate(de_tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index']) addr = ins_full[utxo]['address'] priv = wallet_service.get_key_from_addr(addr) if index % 2: priv = binascii.unhexlify(priv) tx = btc.sign(tx, index, priv, hashcode=hashcode) #pushtx returns False on any error print(btc.deserialize(tx)) push_succeed = jm_single().bc_interface.pushtx(tx) if push_succeed: return btc.txhash(tx) else: return False
def test_on_sig(createcmtdata, dummyaddr, signmethod, schedule): #plan: create a new transaction with known inputs and dummy outputs; #then, create a signature with various inputs, pass in in b64 to on_sig. #in order for it to verify, the DummyBlockchainInterface will have to #return the right values in query_utxo_set #create 2 privkey + utxos that are to be ours privs = [x * 32 + "\x01" for x in [chr(y) for y in range(1, 6)]] utxos = [str(x) * 64 + ":1" for x in range(5)] fake_query_results = [{ 'value': 200000000, 'utxo': utxos[x], 'address': bitcoin.privkey_to_address(privs[x], False, magicbyte=0x6f), 'script': bitcoin.mk_pubkey_script( bitcoin.privkey_to_address(privs[x], False, magicbyte=0x6f)), 'confirms': 20 } for x in range(5)] dbci = DummyBlockchainInterface() dbci.insert_fake_query_results(fake_query_results) jm_single().bc_interface = dbci #make a transaction with all the fake results above, and some outputs outs = [{ 'value': 100000000, 'address': dummyaddr }, { 'value': 899990000, 'address': dummyaddr }] tx = bitcoin.mktx(utxos, outs) de_tx = bitcoin.deserialize(tx) #prepare the Taker with the right intermediate data taker = get_taker(schedule=schedule, sign_method=signmethod) taker.nonrespondants = ["cp1", "cp2", "cp3"] taker.latest_tx = de_tx #my inputs are the first 2 utxos taker.input_utxos = { utxos[0]: { 'address': bitcoin.privkey_to_address(privs[0], False, magicbyte=0x6f), 'value': 200000000 }, utxos[1]: { 'address': bitcoin.privkey_to_address(privs[1], False, magicbyte=0x6f), 'value': 200000000 } } taker.utxos = { None: utxos[:2], "cp1": [utxos[2]], "cp2": [utxos[3]], "cp3": [utxos[4]] } for i in range(2): # placeholders required for my inputs taker.latest_tx['ins'][i]['script'] = 'deadbeef' #to prepare for my signing, need to mark cjaddr: taker.my_cj_addr = dummyaddr #make signatures for the last 3 fake utxos, considered as "not ours": tx3 = bitcoin.sign(tx, 2, privs[2]) sig3 = b64encode( bitcoin.deserialize(tx3)['ins'][2]['script'].decode('hex')) taker.on_sig("cp1", sig3) tx4 = bitcoin.sign(tx, 3, privs[3]) sig4 = b64encode( bitcoin.deserialize(tx4)['ins'][3]['script'].decode('hex')) taker.on_sig("cp2", sig4) tx5 = bitcoin.sign(tx, 4, privs[4]) #Before completing with the final signature, which will trigger our own #signing, try with an injected failure of query utxo set, which should #prevent this signature being accepted. dbci.setQUSFail(True) sig5 = b64encode( bitcoin.deserialize(tx5)['ins'][4]['script'].decode('hex')) taker.on_sig("cp3", sig5) #allow it to succeed, and try again dbci.setQUSFail(False) #this should succeed and trigger the we-sign code taker.on_sig("cp3", sig5)
def make_sign_and_push(ins_sw, wallet, amount, other_ins=None, output_addr=None, change_addr=None, hashcode=btc.SIGHASH_ALL): """A more complicated version of the function in test_tx_creation; will merge to this one once finished. ins_sw have this structure: {"txid:n":(amount, priv, index), "txid2:n2":(amount2, priv2, index2), ..} if other_ins is not None, it has the same format, these inputs are assumed to be plain p2pkh. All of these inputs in these two sets will be consumed. They are ordered according to the "index" fields (to allow testing of specific ordering) It's assumed that they contain sufficient coins to satisy the required output specified in "amount", plus some extra for fees and a change output. The output_addr and change_addr, if None, are taken from the wallet and are ordinary p2pkh outputs. All amounts are in satoshis and only converted to btc for grab_coins """ #total value of all inputs print ins_sw print other_ins total = sum([x[0] for x in ins_sw.values()]) total += sum([x[0] for x in other_ins.values()]) #construct the other inputs ins1 = other_ins ins1.update(ins_sw) ins1 = sorted(ins1.keys(), key=lambda k: ins1[k][2]) #random output address and change addr output_addr = wallet.get_new_addr(1, 1) if not output_addr else output_addr change_addr = wallet.get_new_addr(1, 0) if not change_addr else change_addr outs = [{ 'value': amount, 'address': output_addr }, { 'value': total - amount - 10000, 'address': change_addr }] tx = btc.mktx(ins1, outs) de_tx = btc.deserialize(tx) for index, ins in enumerate(de_tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index']) temp_ins = ins_sw if utxo in ins_sw.keys() else other_ins amt, priv, n = temp_ins[utxo] temp_amt = amt if utxo in ins_sw.keys() else None #for better test code coverage print "signing tx index: " + str(index) + ", priv: " + priv if index % 2: priv = binascii.unhexlify(priv) ms = "other" if not temp_amt else "amount: " + str(temp_amt) print ms tx = btc.sign(tx, index, priv, hashcode=hashcode, amount=temp_amt) print pformat(btc.deserialize(tx)) txid = jm_single().bc_interface.pushtx(tx) time.sleep(3) received = jm_single().bc_interface.get_received_by_addr( [output_addr], None)['data'][0]['balance'] #check coins were transferred as expected assert received == amount #pushtx returns False on any error return txid
def test_on_sig(setup_taker, dummyaddr, schedule): #plan: create a new transaction with known inputs and dummy outputs; #then, create a signature with various inputs, pass in in b64 to on_sig. #in order for it to verify, the DummyBlockchainInterface will have to #return the right values in query_utxo_set utxos = [(struct.pack(b"B", x) * 32, 1) for x in range(5)] #create 2 privkey + utxos that are to be ours privs = [x*32 + b"\x01" for x in [struct.pack(b'B', y) for y in range(1,6)]] scripts = [BTC_P2PKH.key_to_script(privs[x]) for x in range(5)] addrs = [BTC_P2PKH.privkey_to_address(privs[x]) for x in range(5)] fake_query_results = [{'value': 200000000, 'utxo': utxos[x], 'address': addrs[x], 'script': scripts[x], 'confirms': 20} for x in range(5)] dbci = DummyBlockchainInterface() dbci.insert_fake_query_results(fake_query_results) jm_single().bc_interface = dbci #make a transaction with all the fake results above, and some outputs outs = [{'value': 100000000, 'address': dummyaddr}, {'value': 899990000, 'address': dummyaddr}] tx = bitcoin.mktx(utxos, outs) # since tx will be updated as it is signed, unlike in real life # (where maker signing operation doesn't happen here), we'll create # a second copy without the signatures: tx2 = bitcoin.mktx(utxos, outs) #prepare the Taker with the right intermediate data taker = get_taker(schedule=schedule) taker.nonrespondants=["cp1", "cp2", "cp3"] taker.latest_tx = tx #my inputs are the first 2 utxos taker.input_utxos = {utxos[0]: {'address': addrs[0], 'script': scripts[0], 'value': 200000000}, utxos[1]: {'address': addrs[1], 'script': scripts[1], 'value': 200000000}} taker.utxos = {None: utxos[:2], "cp1": [utxos[2]], "cp2": [utxos[3]], "cp3":[utxos[4]]} for i in range(2): # placeholders required for my inputs taker.latest_tx.vin[i].scriptSig = bitcoin.CScript(hextobin('deadbeef')) tx2.vin[i].scriptSig = bitcoin.CScript(hextobin('deadbeef')) #to prepare for my signing, need to mark cjaddr: taker.my_cj_addr = dummyaddr #make signatures for the last 3 fake utxos, considered as "not ours": sig, msg = bitcoin.sign(tx2, 2, privs[2]) assert sig, "Failed to sign: " + msg sig3 = b64encode(tx2.vin[2].scriptSig) taker.on_sig("cp1", sig3) #try sending the same sig again; should be ignored taker.on_sig("cp1", sig3) sig, msg = bitcoin.sign(tx2, 3, privs[3]) assert sig, "Failed to sign: " + msg sig4 = b64encode(tx2.vin[3].scriptSig) #try sending junk instead of cp2's correct sig assert not taker.on_sig("cp2", str("junk")), "incorrectly accepted junk signature" taker.on_sig("cp2", sig4) sig, msg = bitcoin.sign(tx2, 4, privs[4]) assert sig, "Failed to sign: " + msg #Before completing with the final signature, which will trigger our own #signing, try with an injected failure of query utxo set, which should #prevent this signature being accepted. dbci.setQUSFail(True) sig5 = b64encode(tx2.vin[4].scriptSig) assert not taker.on_sig("cp3", sig5), "incorrectly accepted sig5" #allow it to succeed, and try again dbci.setQUSFail(False) #this should succeed and trigger the we-sign code taker.on_sig("cp3", sig5)
def graft_onto_single_acp(wallet, txhex, amount, destaddr): """Given a serialized txhex which is checked to be of form single|acp (one in, one out), a destination address and an amount to spend, grafts in this in-out pair (at index zero) to our own transaction spending amount amount to destination destaddr, and uses a user-specified transaction fee (normal joinmarket configuration), and sanity checks that the bump value is not greater than user specified bump option. Returned: serialized txhex of fully signed transaction. """ d = btc.deserialize(txhex) if len(d['ins']) != 1 or len(d['outs']) != 1: return (False, "Proposed tx should have 1 in 1 out, has: " + ','.join([str(len(d[x])) for x in ['ins', 'outs']])) #most important part: check provider hasn't bumped more than options.bump: other_utxo_in = d['ins'][0]['outpoint']['hash'] + ":" + str( d['ins'][0]['outpoint']['index']) res = jm_single().bc_interface.query_utxo_set(other_utxo_in) assert len(res) == 1 if not res[0]: return (False, "Utxo provided by counterparty not found.") excess = d['outs'][0]['value'] - res[0]["value"] if not excess <= options.bump: return (False, "Counterparty claims too much excess value: " + str(excess)) #Last sanity check - ensure that it's single|acp, else we're wasting our time try: if 'txinwitness' in d['ins'][0]: sig, pub = d['ins'][0]['txinwitness'] else: sig, pub = btc.deserialize_script(d['ins'][0]['script']) assert sig[-2:] == "83" except Exception as e: return ( False, "The transaction's signature does not parse as signed with " "SIGHASH_SINGLE|SIGHASH_ANYONECANPAY, for p2pkh or p2sh-p2wpkh, or " "is otherwise invalid, and so is not valid for this function.\n" + repr(e)) #source inputs for our own chosen spending amount: try: input_utxos = wallet.select_utxos(options.mixdepth, amount) except Exception as e: return (False, "Unable to select sufficient coins from mixdepth: " + str(options.mixdepth)) total_selected = sum([x['value'] for x in input_utxos.values()]) fee = estimate_tx_fee(len(input_utxos) + 1, 3, txtype='p2sh-p2wpkh') change_amount = total_selected - amount - excess - fee changeaddr = wallet.get_new_addr(options.mixdepth, 1) #Build new transaction and, graft in signature ins = [other_utxo_in] + input_utxos.keys() outs = [ d['outs'][0], { 'address': destaddr, 'value': amount }, { 'address': changeaddr, 'value': change_amount } ] fulltx = btc.mktx(ins, outs) df = btc.deserialize(fulltx) #put back in original signature df['ins'][0]['script'] = d['ins'][0]['script'] if 'txinwitness' in d['ins'][0]: df['ins'][0]['txinwitness'] = d['ins'][0]['txinwitness'] fulltx = btc.serialize(df) for i, iu in enumerate(input_utxos): priv, inamt = get_privkey_amount_from_utxo(wallet, iu) print("Signing index: ", i + 1, " with privkey: ", priv, " and amount: ", inamt, " for utxo: ", iu) fulltx = btc.sign(fulltx, i + 1, priv, amount=inamt) return (True, fulltx)