diff --git a/.msggen.json b/.msggen.json index 677fa967a318..3c6a89995011 100644 --- a/.msggen.json +++ b/.msggen.json @@ -272,7 +272,8 @@ "NewaddrAddresstype": { "all": 2, "bech32": 0, - "p2sh-segwit": 1 + "p2sh-segwit": 1, + "p2tr": 3 }, "PayStatus": { "complete": 0, @@ -1257,7 +1258,9 @@ }, "NewaddrResponse": { "NewAddr.bech32": 1, - "NewAddr.p2sh-segwit": 2 + "NewAddr.p2sh-segwit": 2, + "NewAddr.p2tr": 3, + "NewAddr.script": 4 }, "PayRequest": { "Pay.amount_msat": 13, @@ -4608,6 +4611,10 @@ "added": "pre-v0.10.1", "deprecated": "v23.02" }, + "NewAddr.p2tr": { + "added": "v23.05", + "deprecated": false + }, "Pay": { "added": "pre-v0.10.1", "deprecated": null diff --git a/bitcoin/tx.c b/bitcoin/tx.c index c48d255faee8..b6a56c266f27 100644 --- a/bitcoin/tx.c +++ b/bitcoin/tx.c @@ -972,7 +972,7 @@ struct amount_sat change_amount(struct amount_sat excess, u32 feerate_perkw, struct amount_sat change_fee; /* Must be able to pay for its own additional weight */ - outweight = bitcoin_tx_output_weight(BITCOIN_SCRIPTPUBKEY_P2WPKH_LEN); + outweight = bitcoin_tx_output_weight(chainparams->is_elements ? BITCOIN_SCRIPTPUBKEY_P2WPKH_LEN : BITCOIN_SCRIPTPUBKEY_P2TR_LEN); /* Rounding can cause off by one errors, so we do this */ if (!amount_sat_sub(&change_fee, diff --git a/bitcoin/tx.h b/bitcoin/tx.h index 8bb62c50e1fb..434a93eb3054 100644 --- a/bitcoin/tx.h +++ b/bitcoin/tx.h @@ -320,10 +320,12 @@ size_t bitcoin_tx_simple_input_weight(bool p2sh); size_t bitcoin_tx_2of2_input_witness_weight(void); /** - * change_amount - Is it worth making a P2WPKH change output at this feerate? + * change_amount - Is it worth making a change output at this feerate? * @excess: input amount we have above the tx fee and other outputs. * @feerate_perkw: feerate. * + * Change script is P2TR for Bitcoin, P2WPKH for Elements + * * If it's not worth (or possible) to make change, returns AMOUNT_SAT(0). * Otherwise returns the amount of the change output to add (@excess minus * the additional fee for the change output itself). diff --git a/channeld/full_channel.c b/channeld/full_channel.c index 02ba567f6c04..ac782349b1f7 100644 --- a/channeld/full_channel.c +++ b/channeld/full_channel.c @@ -338,9 +338,9 @@ struct bitcoin_tx **channel_txs(const tal_t *ctx, /* Set the remote/local pubkeys on the commitment tx psbt */ psbt_input_add_pubkey(txs[0]->psbt, 0, - &channel->funding_pubkey[side]); + &channel->funding_pubkey[side], false /* is_taproot */); psbt_input_add_pubkey(txs[0]->psbt, 0, - &channel->funding_pubkey[!side]); + &channel->funding_pubkey[!side], false /* is_taproot */); add_htlcs(&txs, *htlcmap, channel, &keyset, side); diff --git a/channeld/watchtower.c b/channeld/watchtower.c index 00f34d30fd66..ed038cf22cc1 100644 --- a/channeld/watchtower.c +++ b/channeld/watchtower.c @@ -81,7 +81,7 @@ penalty_tx_create(const tal_t *ctx, bitcoin_tx_add_output(tx, final_scriptpubkey, NULL, to_them_sats); assert((final_index == NULL) == (final_ext_key == NULL)); if (final_index) - psbt_add_keypath_to_last_output(tx, *final_index, final_ext_key); + psbt_add_keypath_to_last_output(tx, *final_index, final_ext_key, is_p2tr(final_scriptpubkey, NULL)); /* Worst-case sig is 73 bytes */ weight = bitcoin_tx_weight(tx) + 1 + 3 + 73 + 0 + tal_count(wscript); diff --git a/cln-grpc/proto/node.proto b/cln-grpc/proto/node.proto index 7c9def50fa7d..1a597d5f01b4 100644 --- a/cln-grpc/proto/node.proto +++ b/cln-grpc/proto/node.proto @@ -947,12 +947,14 @@ message NewaddrRequest { // NewAddr.addresstype enum NewaddrAddresstype { BECH32 = 0; + P2TR = 3; ALL = 2; } optional NewaddrAddresstype addresstype = 1; } message NewaddrResponse { + optional string p2tr = 3; optional string bech32 = 1; optional string p2sh_segwit = 2; } diff --git a/cln-grpc/src/convert.rs b/cln-grpc/src/convert.rs index dbd1b54bf535..a5462c700dfe 100644 --- a/cln-grpc/src/convert.rs +++ b/cln-grpc/src/convert.rs @@ -793,6 +793,7 @@ impl From for pb::WaitsendpayResponse { impl From for pb::NewaddrResponse { fn from(c: responses::NewaddrResponse) -> Self { Self { + p2tr: c.p2tr, // Rule #2 for type string? bech32: c.bech32, // Rule #2 for type string? #[allow(deprecated)] p2sh_segwit: c.p2sh_segwit, // Rule #2 for type string? diff --git a/cln-rpc/src/model.rs b/cln-rpc/src/model.rs index 2e4a466ceebe..ccb7912db9b9 100644 --- a/cln-rpc/src/model.rs +++ b/cln-rpc/src/model.rs @@ -785,6 +785,8 @@ pub mod requests { pub enum NewaddrAddresstype { #[serde(rename = "bech32")] BECH32, + #[serde(rename = "p2tr")] + P2TR, #[serde(rename = "all")] ALL, } @@ -794,7 +796,8 @@ pub mod requests { fn try_from(c: i32) -> Result { match c { 0 => Ok(NewaddrAddresstype::BECH32), - 1 => Ok(NewaddrAddresstype::ALL), + 1 => Ok(NewaddrAddresstype::P2TR), + 2 => Ok(NewaddrAddresstype::ALL), o => Err(anyhow::anyhow!("Unknown variant {} for enum NewaddrAddresstype", o)), } } @@ -3030,6 +3033,8 @@ pub mod responses { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NewaddrResponse { + #[serde(skip_serializing_if = "Option::is_none")] + pub p2tr: Option, #[serde(skip_serializing_if = "Option::is_none")] pub bech32: Option, #[deprecated] diff --git a/common/close_tx.c b/common/close_tx.c index 48e54d1e9c02..6a54e81985ff 100644 --- a/common/close_tx.c +++ b/common/close_tx.c @@ -52,7 +52,7 @@ struct bitcoin_tx *create_close_tx(const tal_t *ctx, assert((local_wallet_index == NULL) == (local_wallet_ext_key == NULL)); if (local_wallet_index) psbt_add_keypath_to_last_output( - tx, *local_wallet_index, local_wallet_ext_key); + tx, *local_wallet_index, local_wallet_ext_key, is_p2tr(script, NULL)); num_outputs++; } diff --git a/common/initial_channel.c b/common/initial_channel.c index cae1ad140201..6d6c35f30c4f 100644 --- a/common/initial_channel.c +++ b/common/initial_channel.c @@ -138,9 +138,9 @@ struct bitcoin_tx *initial_channel_tx(const tal_t *ctx, if (init_tx) { psbt_input_add_pubkey(init_tx->psbt, 0, - &channel->funding_pubkey[side]); + &channel->funding_pubkey[side], false /* is_taproot */); psbt_input_add_pubkey(init_tx->psbt, 0, - &channel->funding_pubkey[!side]); + &channel->funding_pubkey[!side], false /* is_taproot */); } return init_tx; diff --git a/common/psbt_keypath.c b/common/psbt_keypath.c index f163614e4926..b9fe0559e5b1 100644 --- a/common/psbt_keypath.c +++ b/common/psbt_keypath.c @@ -3,9 +3,8 @@ #include #include #include -#include -void psbt_set_keypath(u32 index, const struct ext_key *ext, struct wally_map *map_in) { +void psbt_output_set_keypath(u32 index, const struct ext_key *ext, bool is_taproot, struct wally_psbt_output *output) { u8 fingerprint[BIP32_KEY_FINGERPRINT_LEN]; if (bip32_key_get_fingerprint( (struct ext_key *) ext, fingerprint, sizeof(fingerprint)) != WALLY_OK) @@ -14,20 +13,30 @@ void psbt_set_keypath(u32 index, const struct ext_key *ext, struct wally_map *ma u32 path[1]; path[0] = index; - if (wally_map_keypath_add(map_in, - ext->pub_key, sizeof(ext->pub_key), - fingerprint, sizeof(fingerprint), - path, 1) != WALLY_OK) - abort(); + if (is_taproot) { + if (wally_psbt_output_taproot_keypath_add(output, + ext->pub_key + 1, sizeof(ext->pub_key) - 1, + NULL, 0, + fingerprint, sizeof(fingerprint), + path, 1) != WALLY_OK) + abort(); + } else { + if (wally_psbt_output_keypath_add(output, + ext->pub_key, sizeof(ext->pub_key), + fingerprint, sizeof(fingerprint), + path, 1) != WALLY_OK) + abort(); + } + } void psbt_add_keypath_to_last_output(struct bitcoin_tx *tx, u32 key_index, - const struct ext_key *ext) { + const struct ext_key *ext, + bool is_taproot) { size_t outndx = tx->psbt->num_outputs - 1; - struct wally_map *map_in = &tx->psbt->outputs[outndx].keypaths; tal_wally_start(); - psbt_set_keypath(key_index, ext, map_in); + psbt_output_set_keypath(key_index, ext, is_taproot, &tx->psbt->outputs[outndx]); tal_wally_end(tx->psbt); } diff --git a/common/psbt_keypath.h b/common/psbt_keypath.h index f19d85761cbd..878d8bd74921 100644 --- a/common/psbt_keypath.h +++ b/common/psbt_keypath.h @@ -3,20 +3,23 @@ #include "config.h" #include +#include struct bitcoin_tx; struct ext_key; struct wally_map; -/* psbt_set_keypath - Set the keypath of a PSBT output. +/* psbt_output_set_keypath- Set the keypath of a PSBT output. * * @index - child index of the wallet key * @ext - extended public key of the immediate parent of the wallet key - * @map_in - wally keypaths map + * @is_taproot - PSBT output has taproot script + * @output - PSBT output to set */ -void psbt_set_keypath(u32 index, +void psbt_output_set_keypath(u32 index, const struct ext_key *ext, - struct wally_map *map_in); + bool is_taproot, + struct wally_psbt_output *output); /* psbt_add_keypath_to_last_output - augment the last output with the * given wallet keypath @@ -24,9 +27,11 @@ void psbt_set_keypath(u32 index, * @tx - transaction to modify * @index - child index of the wallet key * @ext - extended public key of the immediate parent of the wallet key + * @is_taproot - if the output is taproot */ void psbt_add_keypath_to_last_output(struct bitcoin_tx *tx, u32 index, - const struct ext_key *ext); + const struct ext_key *ext, + bool is_taproot); #endif /* LIGHTNING_COMMON_PSBT_KEYPATH_H */ diff --git a/common/psbt_open.c b/common/psbt_open.c index 80cbe7fb4b3d..1bc250cc7fff 100644 --- a/common/psbt_open.c +++ b/common/psbt_open.c @@ -75,6 +75,9 @@ static const u8 *linearize_input(const tal_t *ctx, wally_psbt_input_set_final_scriptsig(&psbt->inputs[0], NULL, 0); wally_psbt_input_set_witness_script(&psbt->inputs[0], NULL, 0); wally_psbt_input_set_redeem_script(&psbt->inputs[0], NULL, 0); + wally_psbt_input_set_taproot_signature(&psbt->inputs[0], NULL, 0); + psbt->inputs[0].taproot_leaf_hashes.num_items = 0; + psbt->inputs[0].taproot_leaf_paths.num_items = 0; psbt->inputs[0].keypaths.num_items = 0; psbt->inputs[0].signatures.num_items = 0; @@ -104,6 +107,8 @@ static const u8 *linearize_output(const tal_t *ctx, /* We don't care if the keypaths change */ psbt->outputs[0].keypaths.num_items = 0; + psbt->outputs[0].taproot_leaf_hashes.num_items = 0; + psbt->outputs[0].taproot_leaf_paths.num_items = 0; /* And you can add scripts, no problem */ wally_psbt_output_set_witness_script(&psbt->outputs[0], NULL, 0); wally_psbt_output_set_redeem_script(&psbt->outputs[0], NULL, 0); diff --git a/contrib/pyln-testing/pyln/testing/grpc2py.py b/contrib/pyln-testing/pyln/testing/grpc2py.py index 1193bd9e0017..589535b7e102 100644 --- a/contrib/pyln-testing/pyln/testing/grpc2py.py +++ b/contrib/pyln-testing/pyln/testing/grpc2py.py @@ -617,6 +617,7 @@ def waitsendpay2py(m): def newaddr2py(m): return remove_default({ + "p2tr": m.p2tr, # PrimitiveField in generate_composite "bech32": m.bech32, # PrimitiveField in generate_composite "p2sh_segwit": m.p2sh_segwit, # PrimitiveField in generate_composite }) diff --git a/doc/lightning-newaddr.7.md b/doc/lightning-newaddr.7.md index 902e326b71d3..7a27b58849f8 100644 --- a/doc/lightning-newaddr.7.md +++ b/doc/lightning-newaddr.7.md @@ -30,6 +30,7 @@ RETURN VALUE [comment]: # (GENERATE-FROM-SCHEMA-START) On success, an object is returned, containing: +- **p2tr** (string, optional): The taproot address *(added v23.05)* - **bech32** (string, optional): The bech32 (native segwit) address - **p2sh-segwit** (string, optional): The p2sh-wrapped address **deprecated, removal in v23.11** @@ -56,4 +57,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:90d550bc2290dd2ab6ee67e377679fe45230a14ba6f4608fda8e51bb6670cc07) +[comment]: # ( SHA256STAMP:409fe64b6c30c0c61b8d0e6b326985a8115cd5a655c5e4ab0ad23d88f9c79ba0) diff --git a/doc/schemas/newaddr.request.json b/doc/schemas/newaddr.request.json index add617e4b317..b0eadf2c7d98 100644 --- a/doc/schemas/newaddr.request.json +++ b/doc/schemas/newaddr.request.json @@ -8,6 +8,7 @@ "type": "string", "enum": [ "bech32", + "p2tr", "all" ] } diff --git a/doc/schemas/newaddr.schema.json b/doc/schemas/newaddr.schema.json index 7f0212760c86..1a2a9c4e1895 100644 --- a/doc/schemas/newaddr.schema.json +++ b/doc/schemas/newaddr.schema.json @@ -4,6 +4,11 @@ "additionalProperties": false, "required": [], "properties": { + "p2tr": { + "added": "v23.05", + "type": "string", + "description": "The taproot address" + }, "bech32": { "type": "string", "description": "The bech32 (native segwit) address" diff --git a/hsmd/libhsmd.c b/hsmd/libhsmd.c index f516e8a31516..1f9fcb8a0303 100644 --- a/hsmd/libhsmd.c +++ b/hsmd/libhsmd.c @@ -485,7 +485,7 @@ static void sign_our_inputs(struct utxo **utxos, struct wally_psbt *psbt) * requires the HSM to find the pubkey, and we * skip doing that until now as a bit of a reduction * of complexity in the calling code */ - psbt_input_add_pubkey(psbt, j, &pubkey); + psbt_input_add_pubkey(psbt, j, &pubkey, utxo->scriptPubkey && is_p2tr(utxo->scriptPubkey, NULL)); /* It's actually a P2WSH in this case. */ if (utxo->close_info && utxo->close_info->option_anchor_outputs) { @@ -503,6 +503,8 @@ static void sign_our_inputs(struct utxo **utxos, struct wally_psbt *psbt) sizeof(privkey.secret.data), EC_FLAG_GRIND_R) != WALLY_OK) { tal_wally_end(psbt); + /* Converting to v0 for log consumption */ + psbt_set_version(psbt, 0); hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR, "Received wally_err attempting to " "sign utxo with key %s. PSBT: %s", diff --git a/lightningd/channel.c b/lightningd/channel.c index 6c02905e42a0..44e43b94184c 100644 --- a/lightningd/channel.c +++ b/lightningd/channel.c @@ -466,13 +466,18 @@ struct channel *new_channel(struct peer *peer, u64 dbid, channel->shutdown_wrong_funding = tal_steal(channel, shutdown_wrong_funding); channel->closing_feerate_range = NULL; - if (local_shutdown_scriptpubkey) + if (local_shutdown_scriptpubkey) { channel->shutdown_scriptpubkey[LOCAL] = tal_steal(channel, local_shutdown_scriptpubkey); - else + } else if (!chainparams->is_elements && channel_type_has(type, OPT_SHUTDOWN_ANYSEGWIT)) { + channel->shutdown_scriptpubkey[LOCAL] + = p2tr_for_keyidx(channel, channel->peer->ld, + channel->final_key_idx); + } else { channel->shutdown_scriptpubkey[LOCAL] = p2wpkh_for_keyidx(channel, channel->peer->ld, - channel->final_key_idx); + channel->final_key_idx); + } channel->last_was_revoke = last_was_revoke; channel->last_sent_commit = tal_steal(channel, last_sent_commit); channel->first_blocknum = first_blocknum; @@ -523,9 +528,17 @@ struct channel *new_channel(struct peer *peer, u64 dbid, channel->state_change_cause = reason; /* Make sure we see any spends using this key */ - txfilter_add_scriptpubkey(peer->ld->owned_txfilter, - take(p2wpkh_for_keyidx(NULL, peer->ld, - channel->final_key_idx))); + if (!local_shutdown_scriptpubkey) { + if (!chainparams->is_elements && channel_type_has(type, OPT_SHUTDOWN_ANYSEGWIT)) { + txfilter_add_scriptpubkey(peer->ld->owned_txfilter, + take(p2tr_for_keyidx(NULL, peer->ld, + channel->final_key_idx))); + } else { + txfilter_add_scriptpubkey(peer->ld->owned_txfilter, + take(p2wpkh_for_keyidx(NULL, peer->ld, + channel->final_key_idx))); + } + } /* scid is NULL when opening a new channel so we don't * need to set error in that case as well */ if (is_stub_scid(scid)) diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c index faebf7b33314..416c193b81f2 100644 --- a/lightningd/dual_open_control.c +++ b/lightningd/dual_open_control.c @@ -1277,7 +1277,7 @@ wallet_commit_channel(struct lightningd *ld, = tal_steal(channel, our_upfront_shutdown_script); else channel->shutdown_scriptpubkey[LOCAL] - = p2wpkh_for_keyidx(channel, channel->peer->ld, + = p2tr_for_keyidx(channel, channel->peer->ld, channel->final_key_idx); /* Can't have gotten their alias for this channel yet. */ diff --git a/lightningd/onchain_control.c b/lightningd/onchain_control.c index 37c0b3dd4dd5..846008151a59 100644 --- a/lightningd/onchain_control.c +++ b/lightningd/onchain_control.c @@ -710,9 +710,10 @@ static struct bitcoin_tx *onchaind_tx_unsigned(const tal_t *ctx, bitcoin_tx_add_input(tx, &info->out, info->to_self_delay, NULL, info->out_sats, NULL, info->wscript); + /* FIXME should this be p2tr now? */ bitcoin_tx_add_output( tx, scriptpubkey_p2wpkh(tmpctx, &final_key), NULL, info->out_sats); - psbt_add_keypath_to_last_output(tx, channel->final_key_idx, &final_wallet_ext_key); + psbt_add_keypath_to_last_output(tx, channel->final_key_idx, &final_wallet_ext_key, false /* is_taproot */); /* Worst-case sig is 73 bytes */ weight = bitcoin_tx_weight(tx) + 1 + 3 + 73 + 0 + tal_count(info->wscript); diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index 4994b3f3aa71..0edbd45c8d34 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -218,6 +218,15 @@ u8 *p2wpkh_for_keyidx(const tal_t *ctx, struct lightningd *ld, u64 keyidx) return scriptpubkey_p2wpkh(ctx, &shutdownkey); } +u8 *p2tr_for_keyidx(const tal_t *ctx, struct lightningd *ld, u64 keyidx) +{ + struct pubkey shutdownkey; + + bip32_pubkey(ld, &shutdownkey, keyidx); + + return scriptpubkey_p2tr(ctx, &shutdownkey); +} + static void sign_last_tx(struct channel *channel, struct bitcoin_tx *last_tx, struct bitcoin_signature *last_sig) diff --git a/lightningd/peer_control.h b/lightningd/peer_control.h index 5f3044c38470..6440435d0325 100644 --- a/lightningd/peer_control.h +++ b/lightningd/peer_control.h @@ -99,6 +99,7 @@ void channel_errmsg(struct channel *channel, const u8 *err_for_them); u8 *p2wpkh_for_keyidx(const tal_t *ctx, struct lightningd *ld, u64 keyidx); +u8 *p2tr_for_keyidx(const tal_t *ctx, struct lightningd *ld, u64 keyidx); /* We've loaded peers from database, set them going. */ void setup_peers(struct lightningd *ld); diff --git a/openingd/dualopend.c b/openingd/dualopend.c index 49ce872d1ee1..b2b0ac7b975f 100644 --- a/openingd/dualopend.c +++ b/openingd/dualopend.c @@ -969,8 +969,8 @@ static char *check_balances(const tal_t *ctx, static bool is_segwit_output(struct wally_tx_output *output) { - const u8 *wit_prog = wally_tx_output_get_script(tmpctx, output); - return is_p2wsh(wit_prog, NULL) || is_p2wpkh(wit_prog, NULL); + const u8 *script = wally_tx_output_get_script(tmpctx, output); + return is_known_segwit_scripttype(script); } static void set_remote_upfront_shutdown(struct state *state, diff --git a/plugins/spender/multifundchannel.c b/plugins/spender/multifundchannel.c index 38c836f531b9..4581b87bd27f 100644 --- a/plugins/spender/multifundchannel.c +++ b/plugins/spender/multifundchannel.c @@ -1219,17 +1219,17 @@ after_newaddr(struct command *cmd, { const jsmntok_t *field; - field = json_get_member(buf, result, "bech32"); + field = json_get_member(buf, result, chainparams->is_elements ? "bech32" : "p2tr"); if (!field) plugin_err(cmd->plugin, - "No bech32 field in newaddr result: %.*s", + "No p2tr field in newaddr result: %.*s", json_tok_full_len(result), json_tok_full(buf, result)); if (json_to_address_scriptpubkey(mfc, chainparams, buf, field, &mfc->change_scriptpubkey) != ADDRESS_PARSE_SUCCESS) plugin_err(cmd->plugin, - "Unparseable bech32 field in newaddr result: %.*s", + "Unparseable p2tr field in newaddr result: %.*s", json_tok_full_len(result), json_tok_full(buf, result)); @@ -1244,7 +1244,7 @@ acquire_change_address(struct multifundchannel_command *mfc) "newaddr", &after_newaddr, &mfc_forward_error, mfc); - json_add_string(req->js, "addresstype", "bech32"); + json_add_string(req->js, "addresstype", chainparams->is_elements ? "bech32" : "p2tr"); return send_outreq(mfc->cmd->plugin, req); } @@ -1260,7 +1260,7 @@ handle_mfc_change(struct multifundchannel_command *mfc) * costs. */ change_weight = bitcoin_tx_output_weight( - BITCOIN_SCRIPTPUBKEY_P2WPKH_LEN); + chainparams->is_elements ? BITCOIN_SCRIPTPUBKEY_P2WPKH_LEN : BITCOIN_SCRIPTPUBKEY_P2TR_LEN); /* To avoid 'off-by-one' errors due to rounding down * (which we do in `amount_tx_fee`), we find the total calculated @@ -1504,16 +1504,6 @@ perform_fundpsbt(struct multifundchannel_command *mfc, u32 feerate) tal_fmt(tmpctx, "%zu", startweight)); } - /* If we've got v2 opens, we need to use a min weight of 110. */ - /* BOLT-f53ca2301232db780843e894f55d95d512f297f9 #3: - * The minimum witness weight for an input is 110. - */ - if (dest_count(mfc, OPEN_CHANNEL) > 0) { - json_add_string(req->js, "min_witness_weight", - tal_fmt(tmpctx, "%u", 110)); - } - - return send_outreq(mfc->cmd->plugin, req); } diff --git a/plugins/spender/multiwithdraw.c b/plugins/spender/multiwithdraw.c index d0f86407705b..d68c0c7185ac 100644 --- a/plugins/spender/multiwithdraw.c +++ b/plugins/spender/multiwithdraw.c @@ -1,6 +1,7 @@ #include "config.h" #include #include +#include #include #include #include @@ -511,7 +512,7 @@ mw_get_change_addr(struct multiwithdraw_command *mw) req = jsonrpc_request_start(mw->cmd->plugin, mw->cmd, "newaddr", &mw_after_newaddr, &mw_forward_error, mw); - json_add_string(req->js, "addresstype", "bech32"); + json_add_string(req->js, "addresstype", chainparams->is_elements ? "bech32" : "p2tr"); return send_outreq(mw->cmd->plugin, req); } @@ -524,7 +525,7 @@ mw_after_newaddr(struct command *cmd, const jsmntok_t *bech32tok; const u8 *script; - bech32tok = json_get_member(buf, result, "bech32"); + bech32tok = json_get_member(buf, result, chainparams->is_elements ? "bech32" : "p2tr"); if (!bech32tok || json_to_address_scriptpubkey(mw, chainparams, buf, bech32tok, &script) != ADDRESS_PARSE_SUCCESS) diff --git a/plugins/txprepare.c b/plugins/txprepare.c index a80376813342..759d1fddf531 100644 --- a/plugins/txprepare.c +++ b/plugins/txprepare.c @@ -1,5 +1,6 @@ #include "config.h" #include +#include #include #include #include @@ -246,7 +247,8 @@ static struct command_result *newaddr_done(struct command *cmd, struct txprepare *txp) { size_t num = tal_count(txp->outputs), pos; - const jsmntok_t *addr = json_get_member(buf, result, "bech32"); + const jsmntok_t *addr = json_get_member(buf, result, chainparams->is_elements ? "bech32" : "p2tr"); + assert(addr); /* Insert change in random position in outputs */ tal_resize(&txp->outputs, num+1); @@ -354,6 +356,7 @@ static struct command_result *psbt_created(struct command *cmd, * but probably won't happen. */ forward_error, txp); + json_add_string(req->js, "addresstype", "all"); return send_outreq(cmd->plugin, req); } diff --git a/tests/test_bookkeeper.py b/tests/test_bookkeeper.py index 4e7681752112..f3e180875ec3 100644 --- a/tests/test_bookkeeper.py +++ b/tests/test_bookkeeper.py @@ -465,7 +465,7 @@ def _check_events(node, channel_id, exp_events): # l1 events exp_events = [('channel_open', open_amt * 1000, 0), - ('onchain_fee', 4567000, 0), + ('onchain_fee', 4927000, 0), ('pushed', 0, push_amt), ('journal_entry', 0, invoice_msat)] _check_events(l1, channel_id, exp_events) @@ -538,7 +538,7 @@ def _check_events(node, channel_id, exp_events): # l1 events exp_events = [('channel_open', open_amt * 1000, 0), - ('onchain_fee', 4567000, 0), + ('onchain_fee', 4927000, 0), ('invoice', 0, invoice_msat)] _check_events(l1, channel_id, exp_events) diff --git a/tests/test_closing.py b/tests/test_closing.py index 022daf2fc656..b8dbfd4e35d2 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -1337,7 +1337,7 @@ def test_penalty_htlc_tx_fulfill(node_factory, bitcoind, chainparams): ]} ] + [ {'blockheight': 108, 'accounts': [ - {'balance_msat': '995433000msat', 'account_id': 'wallet'}, + {'balance_msat': '995073000msat', 'account_id': 'wallet'}, {'balance_msat': '500000000msat', 'account_id': first_channel_id(l1, l2)}, {'balance_msat': '499994999msat', 'account_id': channel_id}]} ] * 2 # duplicated; we stop and restart l2 twice (both at block 108) diff --git a/tests/test_misc.py b/tests/test_misc.py index c4b0ff076478..84606bf9ac0b 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -637,7 +637,7 @@ def dont_spend_outputs(n, txid): {'type': 'chain_mvt', 'credit_msat': 2000000000, 'debit_msat': 0, 'tags': ['deposit']}, {'type': 'chain_mvt', 'credit_msat': 2000000000, 'debit_msat': 0, 'tags': ['deposit']}, {'type': 'chain_mvt', 'credit_msat': 2000000000, 'debit_msat': 0, 'tags': ['deposit']}, - {'type': 'chain_mvt', 'credit_msat': 11957603000, 'debit_msat': 0, 'tags': ['deposit']}, + {'type': 'chain_mvt', 'credit_msat': 11956163000, 'debit_msat': 0, 'tags': ['deposit']}, ] check_coin_moves(l1, 'external', external_moves, chainparams) diff --git a/tests/test_wallet.py b/tests/test_wallet.py index de0e0ace9c45..ad3d9cd61463 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -1309,45 +1309,39 @@ def test_hsmtool_secret_decryption(node_factory): def test_hsmtool_dump_descriptors(node_factory, bitcoind): l1 = node_factory.get_node() l1.fundwallet(10**6) - # Get a tpub descriptor of lightningd's wallet hsm_path = os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "hsm_secret") cmd_line = ["tools/hsmtool", "dumponchaindescriptors", hsm_path, "testnet"] - out = subprocess.check_output(cmd_line).decode("utf8").split("\n") - descriptor = [l for l in out if l.startswith("wpkh(tpub")][0] - - # If we switch wallet, we can't generate address: do so now. - mine_to_addr = bitcoind.rpc.getnewaddress() - - # Import the descriptor to bitcoind - try: - bitcoind.rpc.importmulti([{ - "desc": descriptor, - # No need to rescan, we'll transact afterward - "timestamp": "now", - # The default - "range": [0, 99] - }]) - except JSONRPCError: - # Oh look, a new API! - # Need watch-only wallet, since descriptor has no privkeys. - bitcoind.rpc.createwallet("lightningd-ro", True) - - # FIXME: No way to access non-default wallet in python-bitcoinlib - bitcoind.rpc.unloadwallet("lightningd-tests", True) - bitcoind.rpc.importdescriptors([{ - "desc": descriptor, - # No need to rescan, we'll transact afterward - "timestamp": "now", - # The default - "range": [0, 99] - }]) - - # Funds sent to lightningd can be retrieved by bitcoind - addr = l1.rpc.newaddr()["bech32"] - txid = l1.rpc.withdraw(addr, 10**3)["txid"] - bitcoind.generate_block(1, txid, mine_to_addr) - assert len(bitcoind.rpc.listunspent(1, 1, [addr])) == 1 + descriptors = subprocess.check_output(cmd_line).decode("utf8").split("\n") + + # Deprecated or empty line + descriptors = [desc for desc in descriptors if not (desc.startswith("sh(wpkh(") or desc == '')] + + withdraw_addr = None + index_offset = 2 # index starts handing out addrs at 2 + + # Generate twenty addresses for all known descriptors + cln_addrs = [l1.rpc.newaddr('all') for _ in range(20)] + for descriptor in descriptors: + for i, cln_addr in enumerate(cln_addrs): + computed_addr = bitcoind.rpc.deriveaddresses(descriptor, [i + index_offset, i + index_offset])[0] + if descriptor.startswith("wpkh"): + assert cln_addr["bech32"] == computed_addr + withdraw_addr = cln_addr["bech32"] + elif descriptor.startswith("tr"): + assert cln_addr["p2tr"] == computed_addr + withdraw_addr = cln_addr["p2tr"] + else: + raise Exception('Unexpected descriptor!') + + # For last address per type: + # Funds sent to lightningd can be retrieved by bitcoind + txid = l1.rpc.withdraw(withdraw_addr, 10**3)["txid"] + bitcoind.generate_block(1, txid, bitcoind.rpc.getnewaddress()) + l1.daemon.wait_for_log('Owning output .* txid {} CONFIRMED'.format(txid)) + actual_index = len(cln_addrs) - 1 + index_offset + res = bitcoind.rpc.scantxoutset("start", [{"desc": descriptor, "range": [actual_index, actual_index]}]) + assert res["total_amount"] == Decimal('0.00001000') def test_hsmtool_generatehsm(node_factory): @@ -1613,6 +1607,43 @@ def test_withdraw_bech32m(node_factory, bitcoind): l1.rpc.multiwithdraw(args)["txid"] +@unittest.skipIf(TEST_NETWORK != 'regtest', "Elements-based schnorr is not yet supported") +def test_p2tr_deposit_withdrawal(node_factory, bitcoind): + + # Don't get any funds from previous runs. + l1 = node_factory.get_node(random_hsm=True) + + # Can fetch p2tr addresses through 'all' or specifically + deposit_addrs = [l1.rpc.newaddr('all')] * 3 + withdrawal_addr = l1.rpc.newaddr('p2tr') + + # Add some funds to withdraw + for addr_type in ['p2tr', 'bech32']: + for i in range(3): + l1.bitcoin.rpc.sendtoaddress(deposit_addrs[i][addr_type], 1) + + bitcoind.generate_block(1) + + wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 6) + for i in range(3): + assert l1.rpc.listfunds()['outputs'][i]['address'] == deposit_addrs[i]['p2tr'] + assert l1.rpc.listfunds()['outputs'][i + 3]['address'] == deposit_addrs[i]['bech32'] + l1.rpc.withdraw(withdrawal_addr['p2tr'], 100000000 * 5) + wait_for(lambda: len(bitcoind.rpc.getrawmempool()) == 1) + raw_tx = bitcoind.rpc.getrawtransaction(bitcoind.rpc.getrawmempool()[0], 1) + assert len(raw_tx['vin']) == 6 + assert len(raw_tx['vout']) == 2 + # Change goes to p2tr + for output in raw_tx['vout']: + assert output["scriptPubKey"]["type"] == "witness_v1_taproot" + bitcoind.generate_block(1) + wait_for(lambda: len(l1.rpc.listtransactions()['transactions']) == 7) + + # Only self-send + change is left + wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 2) + + # make sure tap derivation is embedded in PSBT output + @unittest.skipIf(TEST_NETWORK != 'regtest', "Address is network specific") def test_upgradewallet(node_factory, bitcoind): # Make sure bitcoind doesn't think it's going backwards diff --git a/tests/utils.py b/tests/utils.py index c93f6b024ec5..3adec4b17552 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -117,7 +117,8 @@ def check_balance_snaps(n, expected_bals): snaps = n.rpc.listsnapshots()['balance_snapshots'] for snap, exp in zip(snaps, expected_bals): assert snap['blockheight'] == exp['blockheight'] - assert _dictify(snap) == _dictify(exp) + if _dictify(snap) != _dictify(exp): + raise Exception('Unexpected balance snap: {} vs {}'.format(_dictify(snap), _dictify(exp))) def check_coin_moves(n, account_id, expected_moves, chainparams): diff --git a/tools/hsmtool.c b/tools/hsmtool.c index 447fdd253ebd..d3a5d2f5043e 100644 --- a/tools/hsmtool.c +++ b/tools/hsmtool.c @@ -557,7 +557,7 @@ static int dumponchaindescriptors(const char *hsm_secret_path, const char *old_p if (bip32_key_to_base58(&master_extkey, BIP32_FLAG_KEY_PUBLIC, &enc_xpub) != WALLY_OK) errx(ERROR_LIBWALLY, "Can't encode xpub"); - /* Now we format the descriptor strings (we only ever create P2WPKH and + /* Now we format the descriptor strings (we only ever create P2TR, P2WPKH, and * P2SH-P2WPKH outputs). */ descriptor = tal_fmt(NULL, "wpkh(%s/0/0/*)", enc_xpub); @@ -572,6 +572,12 @@ static int dumponchaindescriptors(const char *hsm_secret_path, const char *old_p printf("%s#%s\n", descriptor, checksum.csum); tal_free(descriptor); + descriptor = tal_fmt(NULL, "tr(%s/0/0/*)", enc_xpub); + if (!descriptor_checksum(descriptor, strlen(descriptor), &checksum)) + errx(ERROR_LIBWALLY, "Can't derive descriptor checksum for tr"); + printf("%s#%s\n", descriptor, checksum.csum); + tal_free(descriptor); + wally_free_string(enc_xpub); return 0; diff --git a/wallet/db.c b/wallet/db.c index bf89d9fbff0d..07367fc46410 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -1355,9 +1355,9 @@ migrate_inflight_last_tx_to_psbt(struct lightningd *ld, struct db *db) &remote_funding_pubkey, &last_sig)) abort(); psbt_input_add_pubkey(last_tx->psbt, 0, - &local_funding_pubkey); + &local_funding_pubkey, false /* is_taproot */); psbt_input_add_pubkey(last_tx->psbt, 0, - &remote_funding_pubkey); + &remote_funding_pubkey, false /* is_taproot */); update_stmt = db_prepare_v2(db, SQL("UPDATE channel_funding_inflights" @@ -1451,9 +1451,9 @@ void migrate_last_tx_to_psbt(struct lightningd *ld, struct db *db) &remote_funding_pubkey, &last_sig)) abort(); psbt_input_add_pubkey(last_tx->psbt, 0, - &local_funding_pubkey); + &local_funding_pubkey, false /* is_taproot */); psbt_input_add_pubkey(last_tx->psbt, 0, - &remote_funding_pubkey); + &remote_funding_pubkey, false /* is_taproot */); update_stmt = db_prepare_v2(db, SQL("UPDATE channels" " SET last_tx = ?" diff --git a/wallet/reservation.c b/wallet/reservation.c index 4d3b470dfb98..61f1b335c526 100644 --- a/wallet/reservation.c +++ b/wallet/reservation.c @@ -385,8 +385,17 @@ static struct command_result *finish_psbt(struct command *cmd, "Failed to generate change address." " Keys exhausted."); - bip32_pubkey(cmd->ld, &pubkey, keyidx); - b32script = scriptpubkey_p2wpkh(tmpctx, &pubkey); + if (chainparams->is_elements) { + bip32_pubkey(cmd->ld, &pubkey, keyidx); + b32script = scriptpubkey_p2wpkh(tmpctx, &pubkey); + } else { + b32script = p2tr_for_keyidx(tmpctx, cmd->ld, keyidx); + } + if (!b32script) { + return command_fail(cmd, LIGHTNINGD, + "Failed to generate change address." + " Keys generation failure"); + } txfilter_add_scriptpubkey(cmd->ld->owned_txfilter, b32script); change_outnum = psbt->num_outputs; @@ -395,7 +404,7 @@ static struct command_result *finish_psbt(struct command *cmd, excess = AMOUNT_SAT(0); /* Add additional weight of output */ weight += bitcoin_tx_output_weight( - BITCOIN_SCRIPTPUBKEY_P2WPKH_LEN); + chainparams->is_elements ? BITCOIN_SCRIPTPUBKEY_P2WPKH_LEN : BITCOIN_SCRIPTPUBKEY_P2TR_LEN); } fee_calc: diff --git a/wallet/wallet.c b/wallet/wallet.c index 12dccbb0be8a..9fddc651cfac 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -683,6 +683,8 @@ bool wallet_can_spend(struct wallet *w, const u8 *script, *output_is_p2sh = true; else if (is_p2wpkh(script, NULL)) *output_is_p2sh = false; + else if (is_p2tr(script, NULL)) + *output_is_p2sh = false; else return false; @@ -710,6 +712,18 @@ bool wallet_can_spend(struct wallet *w, const u8 *script, return true; } tal_free(s); + /* Try taproot output now */ + s = scriptpubkey_p2tr_derkey(w, ext.pub_key); + if (scripteq(s, script)) { + /* If we found a used key in the keyscan_gap we should + * remember that. */ + if (i > bip32_max_index) + db_set_intvar(w->db, "bip32_max_index", i); + tal_free(s); + *index = i; + return true; + } + tal_free(s); } return false; } diff --git a/wallet/walletrpc.c b/wallet/walletrpc.c index a04613bda949..a17d59c26c35 100644 --- a/wallet/walletrpc.c +++ b/wallet/walletrpc.c @@ -27,11 +27,19 @@ #include #include +enum addrtype { + /* Deprecated! */ + ADDR_P2SH_SEGWIT = 1, + ADDR_BECH32 = 2, + ADDR_P2TR = 4, + ADDR_ALL = (ADDR_P2SH_SEGWIT + ADDR_BECH32 + ADDR_P2TR) +}; + /* May return NULL if encoding error occurs. */ static char * encode_pubkey_to_addr(const tal_t *ctx, const struct pubkey *pubkey, - bool is_p2sh_p2wpkh, + enum addrtype addrtype, /* Output: redeemscript to use to redeem outputs * paying to the address. * May be NULL if redeemscript is do not care. */ @@ -44,14 +52,16 @@ encode_pubkey_to_addr(const tal_t *ctx, u8 *redeemscript; bool ok; - if (is_p2sh_p2wpkh) { + assert(addrtype != ADDR_ALL); + + if (addrtype == ADDR_P2SH_SEGWIT) { redeemscript = bitcoin_redeem_p2sh_p2wpkh(ctx, pubkey); sha256(&h, redeemscript, tal_count(redeemscript)); ripemd160(&h160, h.u.u8, sizeof(h)); out = p2sh_to_base58(ctx, chainparams, &h160); - } else { + } else if (addrtype == ADDR_BECH32) { hrp = chainparams->onchain_hrp; /* out buffer is 73 + strlen(human readable part), @@ -68,6 +78,27 @@ encode_pubkey_to_addr(const tal_t *ctx, ok = segwit_addr_encode(out, hrp, 0, h160.u.u8, sizeof(h160)); if (!ok) out = tal_free(out); + } else { + assert(addrtype == ADDR_P2TR); + u8 *p2tr_spk = scriptpubkey_p2tr(ctx, pubkey); + u8 *x_key = p2tr_spk + 2; + hrp = chainparams->onchain_hrp; + + /* out buffer is 73 + strlen(human readable part), + * see common/bech32.h*/ + out = tal_arr(ctx, char, 73 + strlen(hrp)); + + /* I am uncertain why this is so for direct SegWit + * outputs, but this is how listaddrs worked prior to + * this code being refactored. */ + redeemscript = tal_dup_arr(ctx, u8, + (u8 *)x_key, 32, + 0); + + /* Would be better to use wally_scriptpubkey_to_address, but lacks signet support */ + ok = segwit_addr_encode(out, hrp, /* witver */ 1, x_key, 32); + if (!ok) + out = tal_free(out); } if (out_redeemscript) @@ -78,14 +109,6 @@ encode_pubkey_to_addr(const tal_t *ctx, return out; } -enum addrtype { - /* Deprecated! */ - ADDR_P2SH_SEGWIT = 1, - ADDR_BECH32 = 2, - ADDR_ALL = (ADDR_P2SH_SEGWIT + ADDR_BECH32) -}; - -/* Extract bool indicating "bech32" */ static struct command_result *param_newaddr(struct command *cmd, const char *name, const char *buffer, @@ -97,11 +120,13 @@ static struct command_result *param_newaddr(struct command *cmd, **addrtype = ADDR_P2SH_SEGWIT; else if (json_tok_streq(buffer, tok, "bech32")) **addrtype = ADDR_BECH32; + else if (!chainparams->is_elements && json_tok_streq(buffer, tok, "p2tr")) + **addrtype = ADDR_P2TR; else if (json_tok_streq(buffer, tok, "all")) **addrtype = ADDR_ALL; else return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "'%s' should be 'bech32', or 'all', not '%.*s'", + "'%s' should be 'p2tr', 'bech32', or 'all', not '%.*s'", name, tok->end - tok->start, buffer + tok->start); return NULL; } @@ -115,8 +140,9 @@ static struct command_result *json_newaddr(struct command *cmd, struct pubkey pubkey; enum addrtype *addrtype; s64 keyidx; - char *p2sh, *bech32; + char *p2sh, *bech32, *p2tr; u8 *b32script; + u8 *p2tr_script; if (!param(cmd, buffer, params, p_opt_def("addresstype", param_newaddr, &addrtype, ADDR_BECH32), @@ -131,15 +157,20 @@ static struct command_result *json_newaddr(struct command *cmd, bip32_pubkey(cmd->ld, &pubkey, keyidx); b32script = scriptpubkey_p2wpkh(tmpctx, &pubkey); + p2tr_script = scriptpubkey_p2tr(tmpctx, &pubkey); if (*addrtype & ADDR_BECH32) txfilter_add_scriptpubkey(cmd->ld->owned_txfilter, b32script); + if (*addrtype & ADDR_P2TR) + txfilter_add_scriptpubkey(cmd->ld->owned_txfilter, p2tr_script); if (deprecated_apis && (*addrtype & ADDR_P2SH_SEGWIT)) txfilter_add_scriptpubkey(cmd->ld->owned_txfilter, scriptpubkey_p2sh(tmpctx, b32script)); - p2sh = encode_pubkey_to_addr(cmd, &pubkey, true, NULL); - bech32 = encode_pubkey_to_addr(cmd, &pubkey, false, NULL); - if (!p2sh || !bech32) { + p2sh = encode_pubkey_to_addr(cmd, &pubkey, ADDR_P2SH_SEGWIT, NULL); + bech32 = encode_pubkey_to_addr(cmd, &pubkey, ADDR_BECH32, NULL); + p2tr = encode_pubkey_to_addr(cmd, &pubkey, ADDR_P2TR, NULL); + + if (!p2sh || !bech32 || !p2tr) { return command_fail(cmd, LIGHTNINGD, "p2wpkh address encoding failure."); } @@ -147,6 +178,8 @@ static struct command_result *json_newaddr(struct command *cmd, response = json_stream_success(cmd); if (*addrtype & ADDR_BECH32) json_add_string(response, "bech32", bech32); + if (*addrtype & ADDR_P2TR) + json_add_string(response, "p2tr", p2tr); if (deprecated_apis && (*addrtype & ADDR_P2SH_SEGWIT)) json_add_string(response, "p2sh-segwit", p2sh); return command_success(cmd, response); @@ -195,19 +228,28 @@ static struct command_result *json_listaddrs(struct command *cmd, u8 *redeemscript_p2sh; char *out_p2sh = encode_pubkey_to_addr(cmd, &pubkey, - true, + ADDR_P2SH_SEGWIT, &redeemscript_p2sh); // bech32 : p2wpkh u8 *redeemscript_p2wpkh; char *out_p2wpkh = encode_pubkey_to_addr(cmd, &pubkey, - false, + ADDR_BECH32, &redeemscript_p2wpkh); if (!out_p2wpkh) { abort(); } + // p2tr + char *out_p2tr = encode_pubkey_to_addr(cmd, + &pubkey, + ADDR_P2TR, + /* out_redeemscript */ NULL); + if (!out_p2tr) { + abort(); + } + // outputs json_object_start(response, NULL); json_add_u64(response, "keyidx", keyidx); @@ -218,6 +260,7 @@ static struct command_result *json_listaddrs(struct command *cmd, json_add_string(response, "bech32", out_p2wpkh); json_add_hex_talarr(response, "bech32_redeemscript", redeemscript_p2wpkh); + json_add_string(response, "p2tr", out_p2tr); json_object_end(response); } json_array_end(response); @@ -693,7 +736,8 @@ static void match_psbt_outputs_to_wallet(struct wally_psbt *psbt, abort(); } - psbt_set_keypath(index, &ext, &psbt->outputs[outndx].keypaths); + psbt_output_set_keypath(index, &ext, is_p2tr(script, NULL), + &psbt->outputs[outndx]); } tal_wally_end(psbt); }