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(binascii.hexlify(priv).decode('ascii')) for priv in privs ] scriptPubKeys = [bitcoin.pubkey_to_p2wpkh_script(pub) for pub in pubs] addresses = [bitcoin.pubkey_to_p2wpkh_address(pub) for pub in pubs] #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 addr in 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") #wait for mining time.sleep(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) sigs = [] for i, priv in enumerate(privs): # sign each of 3 inputs tx = bitcoin.p2wpkh_sign(tx, i, binascii.hexlify(priv), amount, native=True) # check that verify_tx_input correctly validates; # to do this, we need to extract the signature and get the scriptCode # of this pubkey scriptCode = bitcoin.pubkey_to_p2pkh_script(pubs[i]) witness = bitcoin.deserialize(tx)['ins'][i]['txinwitness'] assert len(witness) == 2 assert witness[1] == pubs[i] sig = witness[0] assert bitcoin.verify_tx_input(tx, i, scriptPubKeys[i], sig, pubs[i], scriptCode=scriptCode, amount=amount) txid = jm_single().bc_interface.pushtx(tx) assert txid
def test_create_and_sign_psbt_with_legacy(setup_psbt_wallet): """ The purpose of this test is to check that we can create and then partially sign a PSBT where we own one input and the other input is of legacy p2pkh type. """ wallet_service = make_wallets(1, [[1, 0, 0, 0, 0]], 1)[0]['wallet'] wallet_service.sync_wallet(fast=True) utxos = wallet_service.select_utxos(0, bitcoin.coins_to_satoshi(0.5)) assert len(utxos) == 1 # create a legacy address and make a payment into it legacy_addr = bitcoin.CCoinAddress.from_scriptPubKey( bitcoin.pubkey_to_p2pkh_script(bitcoin.privkey_to_pubkey(b"\x01" * 33))) tx = direct_send(wallet_service, bitcoin.coins_to_satoshi(0.3), 0, str(legacy_addr), accept_callback=dummy_accept_callback, info_callback=dummy_info_callback, return_transaction=True) assert tx # this time we will have one utxo worth <~ 0.7 my_utxos = wallet_service.select_utxos(0, bitcoin.coins_to_satoshi(0.5)) assert len(my_utxos) == 1 # find the outpoint for the legacy address we're spending n = -1 for i, t in enumerate(tx.vout): if bitcoin.CCoinAddress.from_scriptPubKey( t.scriptPubKey) == legacy_addr: n = i assert n > -1 utxos = copy.deepcopy(my_utxos) utxos[(tx.GetTxid()[::-1], n)] = { "script": legacy_addr.to_scriptPubKey(), "value": bitcoin.coins_to_satoshi(0.3) } outs = [{ "value": bitcoin.coins_to_satoshi(0.998), "address": wallet_service.get_addr(0, 0, 0) }] tx2 = bitcoin.mktx(list(utxos.keys()), outs) spent_outs = wallet_service.witness_utxos_to_psbt_utxos(my_utxos) spent_outs.append(tx) new_psbt = wallet_service.create_psbt_from_tx(tx2, spent_outs, force_witness_utxo=False) signed_psbt_and_signresult, err = wallet_service.sign_psbt( new_psbt.serialize(), with_sign_result=True) assert err is None signresult, signed_psbt = signed_psbt_and_signresult assert signresult.num_inputs_signed == 1 assert signresult.num_inputs_final == 1 assert not signresult.is_final
def pubkey_to_script_code(cls, pubkey): """ As per BIP143, the scriptCode for the p2wpkh case is "76a914+hash160(pub)+"88ac" as per the scriptPubKey of the p2pkh case. """ return btc.pubkey_to_p2pkh_script(pubkey, require_compressed=True)
def pubkey_to_script(cls, pubkey): # this call does not enforce compressed: return btc.pubkey_to_p2pkh_script(pubkey)
def main(): # sets up grpc connection to lnd channel = get_secure_channel() # note that the 'admin' macaroon already has the required # permissions for the walletkit request, so we don't need # that third macaroon. macaroon, signer_macaroon = get_macaroons(["admin", "signer"]) # the main stub allows access to the default rpc commands: stub = lnrpc.LightningStub(channel) # the signer stub allows us to access the rpc for signing # transactions on our coins: stub_signer = signrpc.SignerStub(channel) # we also need a stub for the walletkit rpc to extract # public keys for addresses holding coins: stub_walletkit = walletrpc.WalletKitStub(channel) # Here we start the process to sign a custom tx. # 1. List unspent coins, get most recent ones (just an example). # 2. Get the pubkeys of those addresses. # 3. Get the next unused address in the wallet as destination. # 4. Build a transaction, (in future: optionally taking extra # inputs and outputs from elsewhere). # 5. Use signOutputRaw rpc to sign the new transaction. # 6. Use the walletkit PublishTransaction to publish. # Just an example of retrieving basic info, not necessary: # Retrieve and display the wallet balance response = stub.WalletBalance(ln.WalletBalanceRequest(), metadata=[('macaroon', macaroon)]) print("Current on-chain wallet balance: ", response.total_balance) inputs = get_our_coins(stub, macaroon) + get_other_coins() for inp in inputs: # Attach auxiliary data needed to the inputs, for signing. # Get the public key of an address inp["pubkey"] = stub_walletkit.KeyForAddress( walletkit.KeyForAddressRequest(addr_in=inp["utxo"].address), metadata=[('macaroon', macaroon)]).raw_key_bytes # this data (known as scriptCode in BIP143 parlance) # is the pubkeyhash script for this p2wpkh, as is needed # to construct the signature hash. # **NOTE** This code currently works with bech32 only. # TODO update to allow p2sh-p2wpkh in wallet coins, also. inp["script"] = btc.pubkey_to_p2pkh_script(inp["pubkey"]) # We need an output address for the transaction, this is taken from the # standard wallet 'new address' request (type 0 is bech32 p2wpkh): request = ln.NewAddressRequest(type=0, ) response = stub.NewAddress(request, metadata=[('macaroon', macaroon)]) output_address = response.address print("Generated new address: ", output_address) # Build the raw unsigned transaction tx_ins = [] output_amt = 0 for inp in inputs: tx_ins.append(inp["utxo"].outpoint.txid_str + ":" + str(inp["utxo"].outpoint.output_index)) output_amt += inp["utxo"].amount_sat fee_est = estimate_tx_fee(2, 1, "p2wpkh", 6, stub, macaroon) output = {"address": output_address, "value": output_amt - fee_est} tx_unsigned = btc.mktx(tx_ins, [output], version=2) print(btc.deserialize(tx_unsigned)) # use SignOutputRaw to sign each input (currently, they are all ours). raw_sigs = {} for i, inp in enumerate(inputs): # KeyDescriptors must contain at least one of the pubkey and the HD path, # here we use the latter: kd = signer.KeyDescriptor(raw_key_bytes=inp["pubkey"]) # specify the utxo information for this input into a TxOut: sdout = signer.TxOut(value=inp["utxo"].amount_sat, pk_script=unhexlify(inp["utxo"].pk_script)) # we must pass a list of SignDescriptors; we could batch all into # one grpc call if we preferred. The witnessscript field is # constructed above as the "script" field in the input dict. sds = [ signer.SignDescriptor(key_desc=kd, input_index=i, output=sdout, witness_script=inp["script"], sighash=1) ] req = signer.SignReq(raw_tx_bytes=unhexlify(tx_unsigned), sign_descs=sds) # here we make the actual signing request to lnd over grpc: response = stub_signer.SignOutputRaw(req, metadata=[('macaroon', signer_macaroon)]) # note that btcwallet's sign function does not return the sighash byte, # it must be added manually: raw_sigs[i] = response.raw_sigs[0] + sighash_all_bytes # insert the signatures into the relevant inputs in the deserialized tx tx_unsigned_deser = btc.deserialize(tx_unsigned) for i in range(len(inputs)): tx_unsigned_deser["ins"][i]["txinwitness"] = [ btc.safe_hexlify(raw_sigs[i]), btc.safe_hexlify(inputs[i]["pubkey"]) ] print("Signed transaction: \n", tx_unsigned_deser) hextx = btc.serialize(tx_unsigned_deser) print("Serialized: ", hextx) print("You can broadcast this externally e.g. via Bitcoin Core")
def pubkey_to_script(cls, pubkey): return btc.pubkey_to_p2pkh_script(pubkey)
def test_is_snicker_tx(our_input_val, their_input_val, network_fee, script_type, net_transfer): our_input = (bytes([1]) * 32, 0) their_input = (bytes([2]) * 32, 1) assert our_input_val - their_input_val - network_fee > 0 total_input_amount = our_input_val + their_input_val total_output_amount = total_input_amount - network_fee receiver_output_amount = their_input_val + net_transfer proposer_output_amount = total_output_amount - receiver_output_amount # all keys are just made up; only the script type will be checked privs = [bytes([i]) * 32 + bytes([1]) for i in range(1, 4)] pubs = [btc.privkey_to_pubkey(x) for x in privs] if script_type == "p2wpkh": spks = [btc.pubkey_to_p2wpkh_script(x) for x in pubs] elif script_type == "p2sh-p2wpkh": spks = [btc.pubkey_to_p2sh_p2wpkh_script(x) for x in pubs] else: assert False tweaked_addr, our_addr, change_addr = [ str(btc.CCoinAddress.from_scriptPubKey(x)) for x in spks ] # now we must construct the three outputs with correct output amounts. outputs = [{"address": tweaked_addr, "value": receiver_output_amount}] outputs.append({"address": our_addr, "value": receiver_output_amount}) outputs.append({ "address": change_addr, "value": total_output_amount - 2 * receiver_output_amount }) assert all([x["value"] > 0 for x in outputs]) # make_shuffled_tx mutates ordering (yuck), work with copies only: outputs1 = copy.deepcopy(outputs) # version and locktime as currently specified in the BIP # for 0/1 version SNICKER. (Note the locktime is partly because # of expected delays). tx = btc.make_shuffled_tx([our_input, their_input], outputs1, version=2, locktime=0) assert btc.is_snicker_tx(tx) # construct variants which will be invalid. # mixed script types in outputs wrong_tweaked_spk = btc.pubkey_to_p2pkh_script(pubs[1]) wrong_tweaked_addr = str( btc.CCoinAddress.from_scriptPubKey(wrong_tweaked_spk)) outputs2 = copy.deepcopy(outputs) outputs2[0] = { "address": wrong_tweaked_addr, "value": receiver_output_amount } tx2 = btc.make_shuffled_tx([our_input, their_input], outputs2, version=2, locktime=0) assert not btc.is_snicker_tx(tx2) # nonequal output amounts outputs3 = copy.deepcopy(outputs) outputs3[1] = {"address": our_addr, "value": receiver_output_amount - 1} tx3 = btc.make_shuffled_tx([our_input, their_input], outputs3, version=2, locktime=0) assert not btc.is_snicker_tx(tx3) # too few outputs outputs4 = copy.deepcopy(outputs) outputs4 = outputs4[:2] tx4 = btc.make_shuffled_tx([our_input, their_input], outputs4, version=2, locktime=0) assert not btc.is_snicker_tx(tx4) # too many outputs outputs5 = copy.deepcopy(outputs) outputs5.append({"address": change_addr, "value": 200000}) tx5 = btc.make_shuffled_tx([our_input, their_input], outputs5, version=2, locktime=0) assert not btc.is_snicker_tx(tx5) # wrong nVersion tx6 = btc.make_shuffled_tx([our_input, their_input], outputs, version=1, locktime=0) assert not btc.is_snicker_tx(tx6) # wrong nLockTime tx7 = btc.make_shuffled_tx([our_input, their_input], outputs, version=2, locktime=1) assert not btc.is_snicker_tx(tx7)