Support p2tr deposit addresses

Changelog-Added: JSON-RPC: newaddr: p2tr option to create taproot addresses.
Changelog-Changed: Wallet: we now use taproot change addresses.
This commit is contained in:
Greg Sanders 2023-07-11 05:29:41 +09:30 committed by Rusty Russell
parent 55cad79d82
commit 4b70736d13
39 changed files with 504 additions and 320 deletions

View File

@ -273,7 +273,8 @@
"NewaddrAddresstype": {
"all": 2,
"bech32": 0,
"p2sh-segwit": 1
"p2sh-segwit": 1,
"p2tr": 3
},
"PayStatus": {
"complete": 0,
@ -1263,7 +1264,8 @@
},
"NewaddrResponse": {
"NewAddr.bech32": 1,
"NewAddr.p2sh-segwit": 2
"NewAddr.p2sh-segwit": 2,
"NewAddr.p2tr": 3
},
"PayRequest": {
"Pay.amount_msat": 13,
@ -4643,6 +4645,10 @@
"added": "pre-v0.10.1",
"deprecated": "v23.02"
},
"NewAddr.p2tr": {
"added": "v23.08",
"deprecated": false
},
"Pay": {
"added": "pre-v0.10.1",
"deprecated": null

View File

@ -972,7 +972,7 @@ struct amount_sat change_fee(u32 feerate_perkw, size_t total_weight)
struct amount_sat 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(&fee,

View File

@ -330,11 +330,13 @@ size_t bitcoin_tx_2of2_input_witness_weight(void);
struct amount_sat change_fee(u32 feerate_perkw, size_t total_weight);
/**
* 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.
* @total_weight: current weight of tx.
*
* 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 change_fee()).

View File

@ -349,9 +349,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);

View File

@ -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);

Binary file not shown.

BIN
cln-grpc/src/convert.rs generated

Binary file not shown.

BIN
cln-rpc/src/model.rs generated

Binary file not shown.

View File

@ -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++;
}

View File

@ -139,9 +139,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;

View File

@ -3,9 +3,8 @@
#include <common/psbt_keypath.h>
#include <common/utils.h>
#include <wally_bip32.h>
#include <wally_psbt.h>
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);
}

View File

@ -3,20 +3,23 @@
#include "config.h"
#include <ccan/short_types/short_types.h>
#include <wally_psbt.h>
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 */

View File

@ -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);

File diff suppressed because one or more lines are too long

View File

@ -613,6 +613,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
})

View File

@ -17,8 +17,8 @@ The funding transaction needs to be confirmed before funds can be used.
*addresstype* specifies the type of address wanted; currently *bech32*
(e.g. `tb1qu9j4lg5f9rgjyfhvfd905vw46eg39czmktxqgg` on bitcoin testnet
or `bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej` on
bitcoin mainnet). The special value *all* generates all known address types
for the same underlying key.
bitcoin mainnet), or *p2tr* taproot addresses. The special value *all*
generates all known address types for the same underlying key.
If no *addresstype* is specified the address generated is a *bech32* address.
@ -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.08)*
- **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: <https://github.com/ElementsProject/lightning>
[comment]: # ( SHA256STAMP:90d550bc2290dd2ab6ee67e377679fe45230a14ba6f4608fda8e51bb6670cc07)
[comment]: # ( SHA256STAMP:f93771e450afe0fc20b2ff9763ba7654d4caf17c35cf45186f2cb9146a67503f)

View File

@ -8,6 +8,7 @@
"type": "string",
"enum": [
"bech32",
"p2tr",
"all"
]
}

View File

@ -4,6 +4,11 @@
"additionalProperties": false,
"required": [],
"properties": {
"p2tr": {
"added": "v23.08",
"type": "string",
"description": "The taproot address"
},
"bech32": {
"type": "string",
"description": "The bech32 (native segwit) address"

View File

@ -489,7 +489,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_anchors) {
@ -507,6 +507,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",

View File

@ -290,7 +290,7 @@ static struct bitcoin_tx *spend_anchor(const tal_t *ctx,
psbt_input_set_wit_utxo(psbt, 1,
scriptpubkey_p2wsh(tmpctx, adet->anchor_wscript),
AMOUNT_SAT(330));
psbt_input_add_pubkey(psbt, 1, &channel->local_funding_pubkey);
psbt_input_add_pubkey(psbt, 1, &channel->local_funding_pubkey, false);
if (!amount_sat_add(&change, utxos[0]->amount, AMOUNT_SAT(330))
|| !amount_sat_sub(&change, change, fee)) {

View File

@ -398,6 +398,10 @@ struct channel *new_channel(struct peer *peer, u64 dbid,
struct channel *channel = tal(peer->ld, struct channel);
struct amount_msat htlc_min, htlc_max;
bool anysegwit = !chainparams->is_elements && feature_negotiated(peer->ld->our_features,
peer->their_features,
OPT_SHUTDOWN_ANYSEGWIT);
assert(dbid != 0);
channel->peer = peer;
channel->dbid = dbid;
@ -477,13 +481,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 (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;
@ -534,9 +543,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 (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))

View File

@ -674,6 +674,11 @@ static struct command_result *json_close(struct command *cmd,
index_val = (u32) channel->final_key_idx;
final_index = &index_val;
/* Don't send a scriptpubkey peer won't accept */
anysegwit = !chainparams->is_elements && feature_negotiated(cmd->ld->our_features,
channel->peer->their_features,
OPT_SHUTDOWN_ANYSEGWIT);
/* If we've set a local shutdown script for this peer, and it's not the
* default upfront script, try to close to a different channel.
* Error is an operator error */
@ -682,8 +687,13 @@ static struct command_result *json_close(struct command *cmd,
tal_count(close_to_script),
channel->shutdown_scriptpubkey[LOCAL],
tal_count(channel->shutdown_scriptpubkey[LOCAL]))) {
u8 *default_close_to = p2wpkh_for_keyidx(tmpctx, cmd->ld,
channel->final_key_idx);
u8 *default_close_to = NULL;
if (anysegwit)
default_close_to = p2tr_for_keyidx(tmpctx, cmd->ld,
channel->final_key_idx);
else
default_close_to = p2wpkh_for_keyidx(tmpctx, cmd->ld,
channel->final_key_idx);
if (!memeq(default_close_to, tal_count(default_close_to),
channel->shutdown_scriptpubkey[LOCAL],
tal_count(channel->shutdown_scriptpubkey[LOCAL]))) {
@ -722,10 +732,6 @@ static struct command_result *json_close(struct command *cmd,
} else
close_script_set = false;
/* Don't send a scriptpubkey peer won't accept */
anysegwit = feature_negotiated(cmd->ld->our_features,
channel->peer->their_features,
OPT_SHUTDOWN_ANYSEGWIT);
if (!valid_shutdown_scriptpubkey(channel->shutdown_scriptpubkey[LOCAL],
anysegwit, false)) {
/* Explicit check for future segwits. */

View File

@ -1287,7 +1287,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. */

View File

@ -691,9 +691,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);

View File

@ -217,6 +217,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)

View File

@ -100,6 +100,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);

View File

@ -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,

View File

@ -1,6 +1,7 @@
#include "config.h"
#include <bitcoin/chainparams.h>
#include <bitcoin/psbt.h>
#include <bitcoin/script.h>
#include <ccan/array_size/array_size.h>
#include <ccan/json_out/json_out.h>
#include <ccan/tal/str/str.h>
@ -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)

View File

@ -1,5 +1,6 @@
#include "config.h"
#include <bitcoin/psbt.h>
#include <bitcoin/script.h>
#include <ccan/array_size/array_size.h>
#include <common/addr.h>
#include <common/json_param.h>
@ -573,7 +574,8 @@ static struct command_result *newaddr_sweep_done(struct command *cmd,
struct listfunds_info *info)
{
struct out_req *req;
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);
info->txp = tal(info, struct txprepare);
info->txp->is_upgrade = true;
@ -627,6 +629,7 @@ static struct command_result *json_upgradewallet(struct command *cmd,
newaddr_sweep_done,
forward_error,
info);
json_add_string(req->js, "addresstype", "all");
return send_outreq(cmd->plugin, req);
}

View File

@ -395,13 +395,13 @@ def test_bookkeeping_missed_chans_leases(node_factory, bitcoind):
# l1 events
exp_events = [('channel_open', open_amt * 1000 + lease_fee, 0),
('onchain_fee', 1224000, 0),
('onchain_fee', 1320000, 0),
('lease_fee', 0, lease_fee),
('journal_entry', 0, invoice_msat)]
_check_events(l1, channel_id, exp_events)
exp_events = [('channel_open', open_amt * 1000, 0),
('onchain_fee', 796000, 0),
('onchain_fee', 892000, 0),
('lease_fee', lease_fee, 0),
('journal_entry', invoice_msat, 0)]
_check_events(l2, channel_id, exp_events)
@ -461,7 +461,7 @@ def test_bookkeeping_missed_chans_pushed(node_factory, bitcoind):
# 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)
@ -534,7 +534,7 @@ def test_bookkeeping_missed_chans_pay_after(node_factory, bitcoind):
# 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)

View File

@ -1338,7 +1338,7 @@ def test_penalty_htlc_tx_fulfill(node_factory, bitcoind, chainparams, anchors):
]}
] + [
{'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)
@ -3203,7 +3203,7 @@ def test_shutdown(node_factory):
@pytest.mark.developer("needs to set upfront_shutdown_script")
def test_option_upfront_shutdown_script(node_factory, bitcoind, executor):
def test_option_upfront_shutdown_script(node_factory, bitcoind, executor, chainparams):
l1 = node_factory.get_node(start=False, allow_warning=True)
# Insist on upfront script we're not going to match.
# '0014' + l1.rpc.call('dev-listaddrs', [10])['addresses'][-1]['bech32_redeemscript']
@ -3256,8 +3256,11 @@ def test_option_upfront_shutdown_script(node_factory, bitcoind, executor):
# Now, if we specify upfront and it's OK, all good.
l1.stop()
# We need to prepend the segwit version (0) and push opcode (14).
l1.daemon.env["DEV_OPENINGD_UPFRONT_SHUTDOWN_SCRIPT"] = '0014' + addr['bech32_redeemscript']
if not chainparams['elements']:
l1.daemon.env["DEV_OPENINGD_UPFRONT_SHUTDOWN_SCRIPT"] = bitcoind.rpc.getaddressinfo(addr['p2tr'])['scriptPubKey']
else:
# We need to prepend the segwit version (0) and push opcode (14).
l1.daemon.env["DEV_OPENINGD_UPFRONT_SHUTDOWN_SCRIPT"] = '0014' + addr['bech32_redeemscript']
l1.start()
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
@ -3573,8 +3576,8 @@ def test_close_feerate_range(node_factory, bitcoind, chainparams):
l1.rpc.close(l2.info['id'], feerange=['253perkw', 'normal'])
if not chainparams['elements']:
l1_range = [139, 4140]
l2_range = [1035, 1000000]
l1_range = [151, 4500]
l2_range = [1125, 1000000]
else:
# That fee output is a little chunky.
l1_range = [221, 6577]

View File

@ -636,7 +636,7 @@ def test_withdraw_misc(node_factory, bitcoind, chainparams):
{'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)

View File

@ -316,7 +316,10 @@ def test_txprepare(node_factory, bitcoind, chainparams):
assert o['scriptPubKey']['type'] == 'witness_v0_keyhash'
assert scriptpubkey_addr(o['scriptPubKey']) == addr
else:
assert o['scriptPubKey']['type'] in ['witness_v0_keyhash', 'fee']
if chainparams['elements']:
o['scriptPubKey']['type'] in ['witness_v0_keyhash', 'fee']
else:
assert o['scriptPubKey']['type'] in ['witness_v1_taproot', 'fee']
# Now prepare one with no change.
prep2 = l1.rpc.txprepare([{addr: 'all'}])
@ -438,7 +441,10 @@ def test_txprepare(node_factory, bitcoind, chainparams):
assert decode['vout'][outnum2]['scriptPubKey']['type'] == 'witness_v0_keyhash'
assert scriptpubkey_addr(decode['vout'][outnum2]['scriptPubKey']) == addr
assert decode['vout'][changenum]['scriptPubKey']['type'] == 'witness_v0_keyhash'
if chainparams['elements']:
assert decode['vout'][changenum]['scriptPubKey']['type'] == 'witness_v0_keyhash'
else:
assert decode['vout'][changenum]['scriptPubKey']['type'] == 'witness_v1_taproot'
def test_reserveinputs(node_factory, bitcoind, chainparams):
@ -1237,45 +1243,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]
descriptors = subprocess.check_output(cmd_line).decode("utf8").split("\n")
# If we switch wallet, we can't generate address: do so now.
mine_to_addr = bitcoind.rpc.getnewaddress()
# Deprecated or empty line
descriptors = [desc for desc in descriptors if not (desc.startswith("sh(wpkh(") or desc == '')]
# 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)
withdraw_addr = None
index_offset = 2 # index starts handing out addrs at 2
# 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]
}])
# 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!')
# 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
# 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):
@ -1545,6 +1545,44 @@ def test_withdraw_bech32m(node_factory, bitcoind):
assert set([output['scriptPubKey']['address'] for output in outputs]).issuperset([addr.lower() for addr in addrs])
@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

View File

@ -98,7 +98,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):
@ -409,7 +410,8 @@ def basic_fee(feerate, anchor_expected):
def closing_fee(feerate, num_outputs):
assert num_outputs == 1 or num_outputs == 2
weight = 428 + 124 * num_outputs
# Assumes p2tr outputs
weight = 428 + (8 + 1 + 1 + 1 + 32) * 4 * num_outputs
return (weight * feerate) // 1000

View File

@ -558,7 +558,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);
@ -573,6 +573,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;

View File

@ -1365,9 +1365,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"
@ -1461,9 +1461,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 = ?"

View File

@ -381,15 +381,24 @@ 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;
psbt_append_output(psbt, b32script, change);
/* 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);
} else {
change_outnum = -1;
}

View File

@ -734,6 +734,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;
@ -761,6 +763,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;
}

View File

@ -27,11 +27,19 @@
#include <wally_psbt.h>
#include <wire/wire_sync.h>
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,21 @@ 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;
redeemscript = NULL;
/* out buffer is 73 + strlen(human readable part),
* see common/bech32.h*/
out = tal_arr(ctx, char, 73 + strlen(hrp));
ok = segwit_addr_encode(out, hrp, /* witver */ 1, x_key, 32);
if (!ok)
out = tal_free(out);
}
if (out_redeemscript)
@ -78,14 +103,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,
@ -98,11 +115,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;
}
@ -116,8 +135,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),
@ -132,15 +152,19 @@ 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 (cmd->ld->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.");
}
@ -148,6 +172,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 (cmd->ld->deprecated_apis && (*addrtype & ADDR_P2SH_SEGWIT))
json_add_string(response, "p2sh-segwit", p2sh);
return command_success(cmd, response);
@ -196,19 +222,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);
@ -219,6 +254,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);
@ -666,7 +702,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);
}