Skip to content

Commit e8c0864

Browse files
committed
Add tests for BIP86
We also fix a bug about withdrawing bitcoi. There are a few gross things that need to fixed at this point which is the handle_memleak stuff.
1 parent 7d8cf0c commit e8c0864

File tree

19 files changed

+556
-197
lines changed

19 files changed

+556
-197
lines changed

bitcoin/tx.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,7 @@ size_t bitcoin_tx_input_witness_weight(enum utxotype utxotype)
940940
/* In practice, these predate anchors, so: */
941941
return 1 + 1 + bitcoin_tx_input_sig_weight();
942942
case UTXO_P2TR:
943+
case UTXO_P2TR_BIP86:
943944
return 1 + 64;
944945
}
945946
abort();

bitcoin/tx.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ enum utxotype {
5757
UTXO_P2WSH_FROM_CLOSE = 3,
5858
/* "p2tr" addresses. */
5959
UTXO_P2TR = 4,
60+
/* "bip86" addresses (P2TR with BIP86 derivation). */
61+
UTXO_P2TR_BIP86 = 5,
6062
};
6163

6264
struct bitcoin_tx_output *new_tx_output(const tal_t *ctx,

common/hsm_secret.c

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,8 @@ static struct hsm_secret *extract_plain_secret(const tal_t *ctx,
189189

190190
hsms->type = HSM_SECRET_PLAIN;
191191
hsms->mnemonic = NULL;
192-
memcpy(&hsms->secret, hsm_secret, sizeof(hsms->secret));
192+
hsms->secret_len = HSM_SECRET_PLAIN_SIZE;
193+
hsms->secret_data = tal_dup_arr(hsms, u8, hsm_secret, HSM_SECRET_PLAIN_SIZE, 0);
193194

194195
*err = HSM_SECRET_OK;
195196
return hsms;
@@ -215,22 +216,28 @@ static struct hsm_secret *extract_encrypted_secret(const tal_t *ctx,
215216
return tal_free(hsms);
216217
}
217218

218-
/* Clear secret data first in case of partial decryption */
219-
memset(&hsms->secret, 0, sizeof(hsms->secret));
219+
/* Prepare for legacy encrypted secret (decrypted to 32 bytes) */
220+
hsms->secret_len = HSM_SECRET_PLAIN_SIZE;
221+
hsms->secret_data = tal_arr(hsms, u8, HSM_SECRET_PLAIN_SIZE);
222+
memset(hsms->secret_data, 0, HSM_SECRET_PLAIN_SIZE);
220223

221224
/* Attempt decryption */
222-
decrypt_success = decrypt_hsm_secret(encryption_key, hsm_secret, &hsms->secret);
225+
struct secret temp_secret;
226+
decrypt_success = decrypt_hsm_secret(encryption_key, hsm_secret, &temp_secret);
223227

224228
/* Clear encryption key immediately after use */
225229
destroy_secret(encryption_key);
226230

227231
if (!decrypt_success) {
228232
/* Clear any partial decryption data */
229-
memset(&hsms->secret, 0, sizeof(hsms->secret));
233+
memset(hsms->secret_data, 0, HSM_SECRET_PLAIN_SIZE);
230234
*err = HSM_SECRET_ERR_WRONG_PASSPHRASE;
231235
return tal_free(hsms);
232236
}
233237

238+
/* Copy decrypted secret */
239+
memcpy(hsms->secret_data, temp_secret.data, HSM_SECRET_PLAIN_SIZE);
240+
234241
hsms->type = HSM_SECRET_ENCRYPTED;
235242
hsms->mnemonic = NULL;
236243

@@ -298,11 +305,9 @@ static struct hsm_secret *extract_mnemonic_secret(const tal_t *ctx,
298305
return tal_free(hsms);
299306
}
300307

301-
/* Store the full 64-byte BIP32 seed for BIP86 derivation */
302-
hsms->bip32_seed = tal_dup_arr(hsms, u8, bip32_seed, bip32_seed_len, 0);
303-
304-
/* We also keep the first 32 bytes for legacy compatibility */
305-
memcpy(hsms->secret.data, bip32_seed, sizeof(hsms->secret.data));
308+
/* Store the full BIP32 seed for mnemonic-based secrets */
309+
hsms->secret_len = HSM_SECRET_MNEMONIC_SIZE; /* Should be 64 bytes */
310+
hsms->secret_data = tal_dup_arr(hsms, u8, bip32_seed, HSM_SECRET_MNEMONIC_SIZE, 0);
306311

307312
*err = HSM_SECRET_OK;
308313
return hsms;

common/hsm_secret.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#define ENCRYPTED_HSM_SECRET_LEN (HS_HEADER_LEN + HS_CIPHERTEXT_LEN)
1515
#define PASSPHRASE_HASH_LEN 32
1616
#define HSM_SECRET_PLAIN_SIZE 32
17+
#define HSM_SECRET_MNEMONIC_SIZE 64
1718

1819
enum hsm_secret_type {
1920
HSM_SECRET_PLAIN = 0, /* Legacy 32-byte format */
@@ -40,8 +41,8 @@ enum hsm_secret_error {
4041
* Represents the content of the hsm_secret file, either a raw seed or a mnemonic.
4142
*/
4243
struct hsm_secret {
43-
struct secret secret; /* 32-byte secret (legacy compatibility) */
44-
u8 *bip32_seed; /* 64-byte BIP32 seed (NULL for legacy) */
44+
u8 *secret_data; /* Variable length: 32 bytes (legacy) or 64 bytes (mnemonic) */
45+
size_t secret_len; /* Length of secret_data: 32 or 64 bytes */
4546
char *mnemonic; /* NULL if not derived from mnemonic */
4647
enum hsm_secret_type type;
4748
};

common/utxo.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ const char *utxotype_to_str(enum utxotype utxotype)
4747
return "p2wsh_from_close";
4848
case UTXO_P2TR:
4949
return "p2tr";
50+
case UTXO_P2TR_BIP86:
51+
return "p2tr_bip86";
5052
}
5153
abort();
5254
}

contrib/msggen/msggen/schema.json

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25994,12 +25994,13 @@
2599425994
"addresstype": {
2599525995
"type": "string",
2599625996
"description": [
25997-
"It specifies the type of address wanted; currently *bech32* (e.g. `tb1qu9j4lg5f9rgjyfhvfd905vw46eg39czmktxqgg` on bitcoin testnet or `bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej` on bitcoin mainnet), or *p2tr* taproot addresses. The special value *all* generates all known address types for the same underlying key."
25997+
"It specifies the type of address wanted; currently *bech32* (e.g. `tb1qu9j4lg5f9rgjyfhvfd905vw46eg39czmktxqgg` on bitcoin testnet or `bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej` on bitcoin mainnet), *p2tr* taproot addresses, or *bip86* for BIP86-derived taproot addresses. The special value *all* generates all known address types for the same underlying key."
2599825998
],
2599925999
"default": "*bech32* address",
2600026000
"enum": [
2600126001
"bech32",
2600226002
"p2tr",
26003+
"bip86",
2600326004
"all"
2600426005
]
2600526006
}
@@ -26013,7 +26014,7 @@
2601326014
"added": "v23.08",
2601426015
"type": "string",
2601526016
"description": [
26016-
"The taproot address."
26017+
"The taproot address (returned for both 'p2tr' and 'bip86' addresstype)."
2601726018
]
2601826019
},
2601926020
"bech32": {
@@ -26061,6 +26062,18 @@
2606126062
"response": {
2606226063
"p2tr": "bcrt1p2gppccw6ywewmg74qqxxmqfdpjds3rpr0mf22y9tm9xcc0muggwsea9nkf"
2606326064
}
26065+
},
26066+
{
26067+
"request": {
26068+
"id": "example:newaddr#3",
26069+
"method": "newaddr",
26070+
"params": {
26071+
"addresstype": "bip86"
26072+
}
26073+
},
26074+
"response": {
26075+
"p2tr": "bcrt1p2gppccw6ywewmg74qqxxmqfdpjds3rpr0mf22y9tm9xcc0muggwsea9nkf"
26076+
}
2606426077
}
2606526078
]
2606626079
},

doc/schemas/newaddr.json

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
"addresstype": {
1818
"type": "string",
1919
"description": [
20-
"It specifies the type of address wanted; currently *bech32* (e.g. `tb1qu9j4lg5f9rgjyfhvfd905vw46eg39czmktxqgg` on bitcoin testnet or `bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej` on bitcoin mainnet), or *p2tr* taproot addresses. The special value *all* generates all known address types for the same underlying key."
20+
"It specifies the type of address wanted; currently *bech32* (e.g. `tb1qu9j4lg5f9rgjyfhvfd905vw46eg39czmktxqgg` on bitcoin testnet or `bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej` on bitcoin mainnet), *p2tr* taproot addresses, or *bip86* for BIP86-derived taproot addresses. The special value *all* generates all known address types for the same underlying key."
2121
],
2222
"default": "*bech32* address",
2323
"enum": [
2424
"bech32",
2525
"p2tr",
26+
"bip86",
2627
"all"
2728
]
2829
}
@@ -36,7 +37,7 @@
3637
"added": "v23.08",
3738
"type": "string",
3839
"description": [
39-
"The taproot address."
40+
"The taproot address (returned for both 'p2tr' and 'bip86' addresstype)."
4041
]
4142
},
4243
"bech32": {
@@ -84,6 +85,18 @@
8485
"response": {
8586
"p2tr": "bcrt1p2gppccw6ywewmg74qqxxmqfdpjds3rpr0mf22y9tm9xcc0muggwsea9nkf"
8687
}
88+
},
89+
{
90+
"request": {
91+
"id": "example:newaddr#3",
92+
"method": "newaddr",
93+
"params": {
94+
"addresstype": "bip86"
95+
}
96+
},
97+
"response": {
98+
"p2tr": "bcrt1p2gppccw6ywewmg74qqxxmqfdpjds3rpr0mf22y9tm9xcc0muggwsea9nkf"
99+
}
87100
}
88101
]
89102
}

hsmd/hsmd.c

Lines changed: 34 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <common/hsm_secret.h>
1919
#include <common/memleak.h>
2020
#include <common/status.h>
21+
#include <hsmd/libhsmd.h>
2122
#include <common/status_wiregen.h>
2223
#include <common/subdaemon.h>
2324
#include <errno.h>
@@ -40,7 +41,7 @@
4041
#define REQ_FD 3
4142

4243
/* Temporary storage for the secret until we pass it to `hsmd_init` */
43-
struct hsm_secret hsm_secret = { .bip32_seed = NULL, .mnemonic = NULL };
44+
struct hsm_secret hsm_secret = { .secret_data = NULL, .secret_len = 0, .mnemonic = NULL, .type = HSM_SECRET_INVALID };
4445

4546
/*~ We keep track of clients, but there's not much to keep. */
4647
struct client {
@@ -369,11 +370,9 @@ static void create_hsm(int fd, const char *passphrase)
369370
}
370371
status_debug("HSM: Derived BIP32 seed from mnemonic");
371372

372-
/* Store the full 64-byte BIP32 seed for BIP86 derivation */
373-
hsm_secret.bip32_seed = tal_dup_arr(tmpctx, u8, bip32_seed, bip32_seed_len, 0);
374-
375-
/* Also store first 32 bytes for legacy compatibility */
376-
memcpy(&hsm_secret.secret, bip32_seed, sizeof(hsm_secret.secret));
373+
/* Store the full BIP32 seed */
374+
hsm_secret.secret_data = tal_dup_arr(tmpctx, u8, bip32_seed, bip32_seed_len, 0);
375+
hsm_secret.secret_len = bip32_seed_len;
377376

378377
/* Set the type based on whether passphrase was used */
379378
hsm_secret.type = passphrase ? HSM_SECRET_MNEMONIC_WITH_PASS : HSM_SECRET_MNEMONIC_NO_PASS;
@@ -478,8 +477,19 @@ static void load_hsm(const char *passphrase)
478477
"Failed to load hsm_secret: %s", hsm_secret_error_str(err));
479478
}
480479

481-
/* Copy the extracted secret to our global hsm_secret */
482-
hsm_secret = *hsms;
480+
/* Deep copy the extracted secret to our global hsm_secret */
481+
hsm_secret = *hsms; /* Copy the basic fields */
482+
483+
/* Deep copy the secret data if it exists */
484+
if (hsms->secret_data) {
485+
hsm_secret.secret_data = tal_dup_arr(NULL, u8, hsms->secret_data, hsms->secret_len, 0);
486+
hsm_secret.secret_len = hsms->secret_len;
487+
}
488+
489+
/* Deep copy the mnemonic if it exists */
490+
if (hsms->mnemonic) {
491+
hsm_secret.mnemonic = tal_strdup(NULL, hsms->mnemonic);
492+
}
483493
}
484494

485495
/*~ We have a pre-init call in developer mode, to set dev flags */
@@ -558,7 +568,9 @@ static struct io_plan *init_hsm(struct io_conn *conn,
558568
hsm_passphrase = (const char *)tlvs->hsm_passphrase;
559569

560570
/*~ Don't swap this. */
561-
sodium_mlock(hsm_secret.secret.data, sizeof(hsm_secret.secret.data));
571+
if (hsm_secret.secret_data && hsm_secret.secret_len > 0) {
572+
sodium_mlock(hsm_secret.secret_data, hsm_secret.secret_len);
573+
}
562574

563575
if (!developer) {
564576
assert(!dev_force_privkey);
@@ -578,7 +590,7 @@ static struct io_plan *init_hsm(struct io_conn *conn,
578590

579591
/* This was tallocated off NULL, and memleak complains if we don't free it */
580592
tal_free(tlvs);
581-
return req_reply(conn, c, hsmd_init(hsm_secret.secret, hsmd_mutual_version,
593+
return req_reply(conn, c, hsmd_init(hsm_secret.secret_data, hsm_secret.secret_len, hsmd_mutual_version,
582594
bip32_key_version));
583595
}
584596

@@ -662,6 +674,11 @@ static struct io_plan *handle_memleak(struct io_conn *conn,
662674

663675
memleak_ptr(memtable, dev_force_privkey);
664676
memleak_ptr(memtable, dev_force_bip32_seed);
677+
678+
/* Question for Rusty/ Reviewer: Is this a suss pattern? */
679+
memleak_ptr(memtable, hsm_secret.secret_data);
680+
memleak_ptr(memtable, hsm_secret.mnemonic);
681+
memleak_ptr(memtable, get_secretstuff_bip32_seed());
665682

666683
found_leak = dump_memleak(memtable, memleak_status_broken, NULL);
667684
reply = towire_hsmd_dev_memleak_reply(NULL, found_leak);
@@ -706,46 +723,17 @@ void hsmd_status_failed(enum status_failreason reason, const char *fmt, ...)
706723
status_send_fatal(take(towire_status_fail(NULL, reason, str)));
707724
}
708725

709-
710-
/* Helper function to derive the BIP86 base key (m/86'/0'/0') */
711-
static void derive_bip86_base_key(struct ext_key *bip86_base)
712-
{
713-
/* Check if we have the full BIP32 seed available */
714-
if (!hsm_secret.bip32_seed) {
715-
status_failed(STATUS_FAIL_INTERNAL_ERROR,
716-
"BIP86 derivation requires full BIP32 seed (not available in legacy format)");
717-
}
718-
719-
/* First create the master key from the seed */
720-
struct ext_key master_key;
721-
if (bip32_key_from_seed(hsm_secret.bip32_seed, 64, BIP32_VER_MAIN_PRIVATE, 0, &master_key) != WALLY_OK) {
722-
status_failed(STATUS_FAIL_INTERNAL_ERROR,
723-
"Failed to create master key from BIP32 seed");
724-
}
725-
726-
/* Set up the BIP86 base path: m/86'/0'/0' */
727-
u32 base_path[3];
728-
base_path[0] = 86 | 0x80000000; /* 86' */
729-
base_path[1] = 0x80000000; /* 0' */
730-
base_path[2] = 0x80000000; /* 0' */
731-
732-
/* Derive the BIP86 base key */
733-
if (bip32_key_from_parent_path(&master_key, base_path, 3, BIP32_FLAG_KEY_PRIVATE, bip86_base) != WALLY_OK) {
734-
status_failed(STATUS_FAIL_INTERNAL_ERROR,
735-
"Failed to derive BIP86 base key");
736-
}
737-
}
738-
739726
/* Handle BIP86 key derivation request */
740727
static struct io_plan *handle_derive_bip86_key(struct io_conn *conn,
741728
struct client *c,
742729
const u8 *msg_in)
743730
{
744731
u8 *reply;
745-
struct pubkey pubkey;
732+
u32 index;
733+
bool is_change;
746734

747-
/* Validate the incoming message format */
748-
if (!fromwire_hsmd_derive_bip86_key(msg_in, NULL, NULL))
735+
/* Extract parameters from the wire message */
736+
if (!fromwire_hsmd_derive_bip86_key(msg_in, &index, &is_change))
749737
return bad_req(conn, c, msg_in);
750738

751739
/* Check if we have a mnemonic-based HSM secret */
@@ -755,57 +743,15 @@ static struct io_plan *handle_derive_bip86_key(struct io_conn *conn,
755743
"BIP86 derivation requires mnemonic-based HSM secret");
756744
}
757745

758-
/* Derive the BIP86 base key using the helper function */
746+
/* Derive only the BIP86 base key (m/86'/0'/0') */
759747
struct ext_key bip86_base;
760748
derive_bip86_base_key(&bip86_base);
761749

762-
/* Return the BIP86 base key as a pubkey */
763-
if (!secp256k1_ec_pubkey_parse(secp256k1_ctx, &pubkey.pubkey,
764-
bip86_base.pub_key, sizeof(bip86_base.pub_key))) {
765-
return bad_req_fmt(conn, c, msg_in,
766-
"Failed to parse BIP86 base pubkey");
767-
}
768-
769-
reply = towire_hsmd_derive_bip86_key_reply(NULL, &pubkey);
750+
/* Return the full BIP86 base extended key */
751+
reply = towire_hsmd_derive_bip86_key_reply(NULL, &bip86_base);
770752
return req_reply(conn, c, take(reply));
771753
}
772754

773-
/*~ Get the BIP86 keys for this given index: if privkey is NULL, we
774-
* don't fill it in. This derives the full path: m/86'/0'/0'/0/index */
775-
static void bip86_key(struct privkey *privkey, struct pubkey *pubkey,
776-
u32 index)
777-
{
778-
struct privkey unused_priv;
779-
780-
if (privkey == NULL)
781-
privkey = &unused_priv;
782-
783-
if (index >= BIP32_INITIAL_HARDENED_CHILD)
784-
status_failed(STATUS_FAIL_MASTER_IO, "Index %u too great", index);
785-
786-
/* Derive the BIP86 base key using the helper function */
787-
struct ext_key bip86_base;
788-
derive_bip86_base_key(&bip86_base);
789-
790-
/* Now derive the specific index: m/86'/0'/0'/0/index */
791-
u32 final_path[2];
792-
final_path[0] = 0; /* change (0 for receive) */
793-
final_path[1] = index; /* address_index */
794-
795-
struct ext_key final_key;
796-
if (bip32_key_from_parent_path(&bip86_base, final_path, 2, BIP32_FLAG_KEY_PRIVATE, &final_key) != WALLY_OK) {
797-
status_failed(STATUS_FAIL_INTERNAL_ERROR,
798-
"BIP86 derivation of index %u failed", index);
799-
}
800-
801-
/* Convert to our format */
802-
memcpy(privkey->secret.data, final_key.priv_key+1, 32);
803-
if (!secp256k1_ec_pubkey_create(secp256k1_ctx, &pubkey->pubkey,
804-
privkey->secret.data))
805-
status_failed(STATUS_FAIL_INTERNAL_ERROR,
806-
"BIP86 pubkey %u create failed", index);
807-
}
808-
809755
/* Handle BIP86 pubkey check request */
810756
static struct io_plan *handle_check_bip86_pubkey(struct io_conn *conn,
811757
struct client *c,

hsmd/hsmd_wire.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ msgtype,hsmd_derive_bip86_key,54
201201
msgdata,hsmd_derive_bip86_key,index,u32,
202202
msgdata,hsmd_derive_bip86_key,is_change,bool,
203203
msgtype,hsmd_derive_bip86_key_reply,154
204-
msgdata,hsmd_derive_bip86_key_reply,pubkey,pubkey,
204+
msgdata,hsmd_derive_bip86_key_reply,bip86_base,ext_key,
205205

206206
# Give me ECDH(node-id-secret,point)
207207
msgtype,hsmd_ecdh_req,1

0 commit comments

Comments
 (0)