diff --git a/contrib/remote_hsmd/NOTES.md b/contrib/remote_hsmd/NOTES.md index fc9814777f17..5c04002eb3cf 100644 --- a/contrib/remote_hsmd/NOTES.md +++ b/contrib/remote_hsmd/NOTES.md @@ -86,7 +86,6 @@ Some popular tests: export THETEST=tests/test_connection.py::test_fee_limits export THETEST=tests/test_closing.py::test_option_upfront_shutdown_script - rust-lightning-signer ---------------------------------------------------------------- @@ -94,8 +93,6 @@ rust-lightning-signer Signing Formats ----------------------------------------------------------------- - ``` rust-lightning c-lightning rust-lightning-signer p2pkh P2PKH @@ -107,6 +104,11 @@ p2shwsh ``` +Failing tests after removing seed from hsmd: ``` - + export THETEST=tests/test_misc.py::test_blockchaintrack + export THETEST=tests/test_misc.py::test_new_node_is_mainnet + export THETEST=tests/test_misc.py::test_getsharedsecret + export THETEST=tests/test_wallet.py::test_hsm_secret_encryption + export THETEST=tests/test_wallet.py::test_hsmtool_secret_decryption ``` diff --git a/contrib/remote_hsmd/dump.cc b/contrib/remote_hsmd/dump.cc index a83c000d43d3..2386c0764e40 100644 --- a/contrib/remote_hsmd/dump.cc +++ b/contrib/remote_hsmd/dump.cc @@ -12,6 +12,7 @@ extern "C" { #include #include #include +#include } #include "contrib/remote_hsmd/dump.h" @@ -75,6 +76,16 @@ string dump_pubkey(const struct pubkey *kp) return dump_hex(kp->pubkey.data, sizeof(kp->pubkey.data)); } +string dump_ext_pubkey(const struct ext_key *xp) +{ + char *out; + int rv = bip32_key_to_base58(xp, BIP32_FLAG_KEY_PUBLIC, &out); + assert(rv == WALLY_OK); + string retval(out); + wally_free_string(out); + return retval; +} + string dump_witnesses(const u8 ***wp) { ostringstream ostrm; diff --git a/contrib/remote_hsmd/dump.h b/contrib/remote_hsmd/dump.h index 3a9b6236c3aa..030f341d1f94 100644 --- a/contrib/remote_hsmd/dump.h +++ b/contrib/remote_hsmd/dump.h @@ -9,6 +9,7 @@ std::string dump_secp256k1_ecdsa_recoverable_signature(const secp256k1_ecdsa_rec std::string dump_secret(const struct secret *sp); std::string dump_node_id(const struct node_id *pp); std::string dump_pubkey(const struct pubkey *kp); +std::string dump_ext_pubkey(const struct ext_key *xp); std::string dump_witnesses(const u8 ***wp); std::string dump_unilateral_close_info(const struct unilateral_close_info *ip); std::string dump_utxo(const struct utxo *in); diff --git a/contrib/remote_hsmd/hsmd.c b/contrib/remote_hsmd/hsmd.c index 518d6e18c0e9..d9545f91e357 100644 --- a/contrib/remote_hsmd/hsmd.c +++ b/contrib/remote_hsmd/hsmd.c @@ -61,12 +61,10 @@ * stream from lightningd. */ #define REQ_FD 3 -/*~ Nobody will ever find it here! hsm_secret is our root secret, the bip32 - * tree is derived from that, and cached here. */ +/* This used to be secretstuff, now has no secrets ... */ static struct { - struct secret hsm_secret; - struct ext_key bip32; -} secretstuff; + struct ext_key bip32; /* Only has public part */ +} pubstuff; /* Version codes for BIP32 extended keys in libwally-core. * It's not suitable to add this struct into client struct, @@ -80,16 +78,6 @@ static struct privkey *dev_force_privkey; static struct secret *dev_force_bip32_seed; #endif -/* FIXME - REMOVE THIS WHEN WE'VE COMPLETED THE PROXY IMPL */ -enum proxy_impl { - PROXY_IMPL_NONE = 0, /* proxy unimplemented */ - PROXY_IMPL_MARSHALED = 1, /* marshaled, sent, result unused */ - PROXY_IMPL_COMPLETE = 2, /* marshaled, sent, result used */ - PROXY_IMPL_IGNORE = 3 /* skip for now */ -}; -static int g_proxy_impl; -static enum hsm_wire_type g_proxy_last; - /* FIXME - REMOVE THIS WHEN NO LONGER NEEDED */ #if 0 static void print_hex(char const *tag, void const *vptr, size_t sz) @@ -329,368 +317,33 @@ static struct io_plan *req_reply(struct io_conn *conn, return io_write_wire(conn, msg_out, client_read_next, c); } -/*~ This returns the secret and/or public key for this node. */ -static void node_key(struct privkey *node_privkey, struct pubkey *node_id) -{ - u32 salt = 0; - struct privkey unused_s; - struct pubkey unused_k; - - /* If caller specifies NULL, they don't want the results. */ - if (node_privkey == NULL) - node_privkey = &unused_s; - else if (node_id == NULL) - node_id = &unused_k; - - /*~ So, there is apparently a 1 in 2^127 chance that a random value is - * not a valid private key, so this never actually loops. */ - do { - /*~ ccan/crypto/hkdf_sha256 implements RFC5869 "Hardened Key - * Derivation Functions". That means that if a derived key - * leaks somehow, the other keys are not compromised. */ - hkdf_sha256(node_privkey, sizeof(*node_privkey), - &salt, sizeof(salt), - &secretstuff.hsm_secret, - sizeof(secretstuff.hsm_secret), - "nodeid", 6); - salt++; - } while (!secp256k1_ec_pubkey_create(secp256k1_ctx, &node_id->pubkey, - node_privkey->secret.data)); - -#if DEVELOPER - /* In DEVELOPER mode, we can override with --dev-force-privkey */ - if (dev_force_privkey) { - *node_privkey = *dev_force_privkey; - if (!secp256k1_ec_pubkey_create(secp256k1_ctx, &node_id->pubkey, - node_privkey->secret.data)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Failed to derive pubkey for dev_force_privkey"); - } -#endif -} - -/*~ This secret is the basis for all per-channel secrets: the per-channel seeds - * will be generated by mixing in the dbid and the peer node_id. */ -static void hsm_channel_secret_base(struct secret *channel_seed_base) -{ - hkdf_sha256(channel_seed_base, sizeof(struct secret), NULL, 0, - &secretstuff.hsm_secret, sizeof(secretstuff.hsm_secret), - /*~ Initially, we didn't support multiple channels per - * peer at all: a channel had to be completely forgotten - * before another could exist. That was slightly relaxed, - * but the phrase "peer seed" is wired into the seed - * generation here, so we need to keep it that way for - * existing clients, rather than using "channel seed". */ - "peer seed", strlen("peer seed")); -} - -/*~ This gets the seed for this particular channel. */ -static void get_channel_seed(const struct node_id *peer_id, u64 dbid, - struct secret *channel_seed) -{ - struct secret channel_base; - u8 input[sizeof(peer_id->k) + sizeof(dbid)]; - /*~ Again, "per-peer" should be "per-channel", but Hysterical Raisins */ - const char *info = "per-peer seed"; - - /*~ We use the DER encoding of the pubkey, because it's platform - * independent. Since the dbid is unique, however, it's completely - * unnecessary, but again, existing users can't be broken. */ - /* FIXME: lnd has a nicer BIP32 method for deriving secrets which we - * should migrate to. */ - hsm_channel_secret_base(&channel_base); - memcpy(input, peer_id->k, sizeof(peer_id->k)); - BUILD_ASSERT(sizeof(peer_id->k) == PUBKEY_CMPR_LEN); - /*~ For all that talk about platform-independence, note that this - * field is endian-dependent! But let's face it, little-endian won. - * In related news, we don't support EBCDIC or middle-endian. */ - memcpy(input + PUBKEY_CMPR_LEN, &dbid, sizeof(dbid)); - - hkdf_sha256(channel_seed, sizeof(*channel_seed), - input, sizeof(input), - &channel_base, sizeof(channel_base), - info, strlen(info)); -} - -/*~ Called at startup to derive the bip32 field. */ -static void populate_secretstuff(void) -{ - u8 bip32_seed[BIP32_ENTROPY_LEN_256]; - u32 salt = 0; - struct ext_key master_extkey, child_extkey; - - assert(bip32_key_version.bip32_pubkey_version == BIP32_VER_MAIN_PUBLIC - || bip32_key_version.bip32_pubkey_version == BIP32_VER_TEST_PUBLIC); - - assert(bip32_key_version.bip32_privkey_version == BIP32_VER_MAIN_PRIVATE - || bip32_key_version.bip32_privkey_version == BIP32_VER_TEST_PRIVATE); - - /* Fill in the BIP32 tree for bitcoin addresses. */ - /* In libwally-core, the version BIP32_VER_TEST_PRIVATE is for testnet/regtest, - * and BIP32_VER_MAIN_PRIVATE is for mainnet. For litecoin, we also set it like - * bitcoin else.*/ - do { - hkdf_sha256(bip32_seed, sizeof(bip32_seed), - &salt, sizeof(salt), - &secretstuff.hsm_secret, - sizeof(secretstuff.hsm_secret), - "bip32 seed", strlen("bip32 seed")); - salt++; - } while (bip32_key_from_seed(bip32_seed, sizeof(bip32_seed), - bip32_key_version.bip32_privkey_version, - 0, &master_extkey) != WALLY_OK); - -#if DEVELOPER - /* In DEVELOPER mode, we can override with --dev-force-bip32-seed */ - if (dev_force_bip32_seed) { - if (bip32_key_from_seed(dev_force_bip32_seed->data, - sizeof(dev_force_bip32_seed->data), - bip32_key_version.bip32_privkey_version, - 0, &master_extkey) != WALLY_OK) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Can't derive bip32 master key"); - } -#endif /* DEVELOPER */ - - /* BIP 32: - * - * The default wallet layout - * - * An HDW is organized as several 'accounts'. Accounts are numbered, - * the default account ("") being number 0. Clients are not required - * to support more than one account - if not, they only use the - * default account. - * - * Each account is composed of two keypair chains: an internal and an - * external one. The external keychain is used to generate new public - * addresses, while the internal keychain is used for all other - * operations (change addresses, generation addresses, ..., anything - * that doesn't need to be communicated). Clients that do not support - * separate keychains for these should use the external one for - * everything. - * - * - m/iH/0/k corresponds to the k'th keypair of the external chain of - * account number i of the HDW derived from master m. - */ - /* Hence child 0, then child 0 again to get extkey to derive from. */ - if (bip32_key_from_parent(&master_extkey, 0, BIP32_FLAG_KEY_PRIVATE, - &child_extkey) != WALLY_OK) - /*~ status_failed() is a helper which exits and sends lightningd - * a message about what happened. For hsmd, that's fatal to - * lightningd. */ - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Can't derive child bip32 key"); - - if (bip32_key_from_parent(&child_extkey, 0, BIP32_FLAG_KEY_PRIVATE, - &secretstuff.bip32) != WALLY_OK) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Can't derive private bip32 key"); -} - -/*~ Get the keys for this given BIP32 index: if privkey is NULL, we - * don't fill it in. */ -static void bitcoin_key(struct privkey *privkey, struct pubkey *pubkey, - u32 index) -{ - struct ext_key ext; - struct privkey unused_priv; - - if (privkey == NULL) - privkey = &unused_priv; - - if (index >= BIP32_INITIAL_HARDENED_CHILD) - status_failed(STATUS_FAIL_MASTER_IO, - "Index %u too great", index); - - /*~ This uses libwally, which doesn't dovetail directly with - * libsecp256k1 even though it, too, uses it internally. */ - if (bip32_key_from_parent(&secretstuff.bip32, index, - BIP32_FLAG_KEY_PRIVATE, &ext) != WALLY_OK) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "BIP32 of %u failed", index); - - /* libwally says: The private key with prefix byte 0; remove it - * for libsecp256k1. */ - memcpy(privkey->secret.data, ext.priv_key+1, 32); - if (!secp256k1_ec_pubkey_create(secp256k1_ctx, &pubkey->pubkey, - privkey->secret.data)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "BIP32 pubkey %u create failed", index); -} - -/*~ This encrypts the content of the secretstuff and stores it in hsm_secret, - * this is called instead of create_hsm() if `lightningd` is started with - * --encrypted-hsm. +/* The c-lightning testing framework imbues the hsm_secret with a + * file created before hsmd starts. For now we use the secret from + * the testing framework rather than generating in the remote signer. + * + * Returns true if test seed fetched. If false is returned test seed not + * present, use random instead. */ -static void create_encrypted_hsm(int fd, const struct secret *encryption_key) -{ - crypto_secretstream_xchacha20poly1305_state crypto_state; - u8 header[crypto_secretstream_xchacha20poly1305_HEADERBYTES]; - /* The cipher size is static with xchacha20poly1305 */ - u8 cipher[sizeof(struct secret) + crypto_secretstream_xchacha20poly1305_ABYTES]; - - crypto_secretstream_xchacha20poly1305_init_push(&crypto_state, header, - encryption_key->data); - crypto_secretstream_xchacha20poly1305_push(&crypto_state, cipher, - NULL, - secretstuff.hsm_secret.data, - sizeof(secretstuff.hsm_secret.data), - /* Additional data and tag */ - NULL, 0, 0); - if (!write_all(fd, header, sizeof(header))) { - unlink_noerr("hsm_secret"); - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Writing header of encrypted secret: %s", strerror(errno)); - } - if (!write_all(fd, cipher, sizeof(cipher))) { - unlink_noerr("hsm_secret"); - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Writing encrypted secret: %s", strerror(errno)); - } -} - -static void create_hsm(int fd) -{ - /*~ ccan/read_write_all has a more convenient return than write() where - * we'd have to check the return value == the length we gave: write() - * can return short on normal files if we run out of disk space. */ - if (!write_all(fd, &secretstuff.hsm_secret, sizeof(secretstuff.hsm_secret))) { - /* ccan/noerr contains useful routines like this, which don't - * clobber errno, so we can use it in our error report. */ - unlink_noerr("hsm_secret"); - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "writing: %s", strerror(errno)); - } -} - -/*~ We store our root secret in a "hsm_secret" file (like all of c-lightning, - * we run in the user's .lightning directory). */ -static void maybe_create_new_hsm(const struct secret *encryption_key, - bool random_hsm) -{ - /*~ Note that this is opened for write-only, even though the permissions - * are set to read-only. That's perfectly valid! */ - int fd = open("hsm_secret", O_CREAT|O_EXCL|O_WRONLY, 0400); - if (fd < 0) { - /* If this is not the first time we've run, it will exist. */ - if (errno == EEXIST) - return; - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "creating: %s", strerror(errno)); - } - - /*~ This is libsodium's cryptographic randomness routine: we assume - * it's doing a good job. */ - if (random_hsm) - randombytes_buf(&secretstuff.hsm_secret, sizeof(secretstuff.hsm_secret)); - - /*~ If an encryption_key was provided, store an encrypted seed. */ - if (encryption_key) - create_encrypted_hsm(fd, encryption_key); - /*~ Otherwise store the seed in clear.. */ - else - create_hsm(fd); - /*~ fsync (mostly!) ensures that the file has reached the disk. */ - if (fsync(fd) != 0) { - unlink_noerr("hsm_secret"); - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "fsync: %s", strerror(errno)); - } - /*~ This should never fail if fsync succeeded. But paranoia good, and - * bugs exist. */ - if (close(fd) != 0) { - unlink_noerr("hsm_secret"); - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "closing: %s", strerror(errno)); - } - /*~ We actually need to sync the *directory itself* to make sure the - * file exists! You're only allowed to open directories read-only in - * modern Unix though. */ - fd = open(".", O_RDONLY); - if (fd < 0) { - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "opening: %s", strerror(errno)); - } - if (fsync(fd) != 0) { - unlink_noerr("hsm_secret"); - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "fsyncdir: %s", strerror(errno)); - } - close(fd); - /*~ status_unusual() is good for things which are interesting and - * definitely won't spam the logs. Only status_broken() is higher; - * status_info() is lower, then status_debug() and finally - * status_io(). */ - status_unusual("HSM: created new hsm_secret file"); -} - -/*~ We always load the HSM file, even if we just created it above. This - * both unifies the code paths, and provides a nice sanity check that the - * file contents are as they will be for future invocations. */ -static void load_hsm(const struct secret *encryption_key) +static bool read_test_seed(struct secret *hsm_secret) { struct stat st; int fd = open("hsm_secret", O_RDONLY); if (fd < 0) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "opening: %s", strerror(errno)); + return false; if (stat("hsm_secret", &st) != 0) status_failed(STATUS_FAIL_INTERNAL_ERROR, "stating: %s", strerror(errno)); /* If the seed is stored in clear. */ - if (st.st_size <= 32) { - if (!read_all(fd, &secretstuff.hsm_secret, sizeof(secretstuff.hsm_secret))) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "reading: %s", strerror(errno)); - /* If an encryption key was passed with a not yet encrypted hsm_secret, - * remove the old one and create an encrypted one. */ - if (encryption_key) { - if (close(fd) != 0) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "closing: %s", strerror(errno)); - if (remove("hsm_secret") != 0) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "removing clear hsm_secret: %s", strerror(errno)); - maybe_create_new_hsm(encryption_key, false); - fd = open("hsm_secret", O_RDONLY); - if (fd < 0) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "opening: %s", strerror(errno)); - } - } - /*~ If an encryption key was passed and the `hsm_secret` is stored - * encrypted, recover the seed from the cipher. */ - if (encryption_key && st.st_size > 32) { - crypto_secretstream_xchacha20poly1305_state crypto_state; - u8 header[crypto_secretstream_xchacha20poly1305_HEADERBYTES]; - /* The cipher size is static with xchacha20poly1305 */ - u8 cipher[sizeof(struct secret) + crypto_secretstream_xchacha20poly1305_ABYTES]; - - if (!read_all(fd, &header, crypto_secretstream_xchacha20poly1305_HEADERBYTES)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Reading xchacha20 header: %s", strerror(errno)); - if (!read_all(fd, cipher, sizeof(cipher))) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Reading encrypted secret: %s", strerror(errno)); - if (crypto_secretstream_xchacha20poly1305_init_pull(&crypto_state, header, - encryption_key->data) != 0) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Initializing the crypto state: %s", strerror(errno)); - if (crypto_secretstream_xchacha20poly1305_pull(&crypto_state, - secretstuff.hsm_secret.data, - NULL, 0, cipher, sizeof(cipher), - NULL, 0) != 0) { - /* Exit but don't throw a backtrace when the user made a mistake in typing - * its password. Instead exit and `lightningd` will be able to give - * an error message. */ - exit(1); - } - } - /* else { handled in hsm_control } */ - close(fd); + if (st.st_size > 32) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "hsm_secret not in clear"); - populate_secretstuff(); + if (!read_all(fd, hsm_secret, sizeof(*hsm_secret))) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "reading: %s", strerror(errno)); + close(fd); + return true; } /*~ This is the response to lightningd's HSM_INIT request, which is the first @@ -700,7 +353,6 @@ static struct io_plan *init_hsm(struct io_conn *conn, const u8 *msg_in) { struct node_id node_id; - struct pubkey key; struct privkey *privkey; struct secret *seed; struct secrets *secrets; @@ -718,15 +370,6 @@ static struct io_plan *init_hsm(struct io_conn *conn, &hsm_encryption_key, &privkey, &seed, &secrets, &shaseed)) return bad_req(conn, c, msg_in); - /*~ The memory is actually copied in towire(), so lock the `hsm_secret` - * encryption key (new) memory again here. */ - if (hsm_encryption_key && sodium_mlock(hsm_encryption_key, - sizeof(hsm_encryption_key)) != 0) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Could not lock memory for hsm_secret encryption key."); - /*~ Don't swap this. */ - sodium_mlock(secretstuff.hsm_secret.data, sizeof(secretstuff.hsm_secret.data)); - #if DEVELOPER dev_force_privkey = privkey; dev_force_bip32_seed = seed; @@ -737,14 +380,27 @@ static struct io_plan *init_hsm(struct io_conn *conn, /* Once we have read the init message we know which params the master * will use */ c->chainparams = chainparams; - maybe_create_new_hsm(hsm_encryption_key, true); - load_hsm(hsm_encryption_key); + + /* Fail fast if these are set. */ + assert(hsm_encryption_key == NULL); + assert(privkey == NULL); + assert(seed == NULL); + assert(secrets == NULL); + assert(shaseed == NULL); + + /* The c-lightning testing framework imbues the hsm_secret + * with a file created before hsmd starts. To allow running + * the c-lightning test suite we use the secret from the + * testing framework rather than generating in the remote + * signer for now. The seed is NOT otherwise retained. + */ + struct secret hsm_secret; + if (!read_test_seed(&hsm_secret)) + randombytes_buf(&hsm_secret, sizeof(hsm_secret)); proxy_stat rv = proxy_init_hsm(&bip32_key_version, chainparams, - hsm_encryption_key, privkey, seed, - secrets, shaseed, - &secretstuff.hsm_secret, - &node_id); + &hsm_secret, &node_id, + &pubstuff.bip32); if (PROXY_PERMANENT(rv)) { status_failed(STATUS_FAIL_INTERNAL_ERROR, "proxy_%s failed: %s", __FUNCTION__, @@ -757,25 +413,9 @@ static struct io_plan *init_hsm(struct io_conn *conn, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); } - g_proxy_impl = PROXY_IMPL_IGNORE; - /*~ We don't need the hsm_secret encryption key anymore. - * Note that sodium_munlock() also zeroes the memory. */ - if (hsm_encryption_key) { - sodium_munlock(hsm_encryption_key, sizeof(*hsm_encryption_key)); - tal_free(hsm_encryption_key); - } - - /*~ We tell lightning our node id and (public) bip32 seed. */ - node_key(NULL, &key); - node_id_from_pubkey(&node_id, &key); - - /*~ Note: marshalling a bip32 tree only marshals the public side, - * not the secrets! So we're not actually handing them out here! - */ return req_reply(conn, c, - take(towire_hsm_init_reply(NULL, &node_id, - &secretstuff.bip32))); + take(towire_hsm_init_reply(NULL, &node_id, &pubstuff.bip32))); } /*~ The client has asked us to extract the shared secret from an EC Diffie @@ -801,7 +441,6 @@ static struct io_plan *handle_ecdh(struct io_conn *conn, return bad_req_fmt(conn, c, msg_in, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); - g_proxy_impl = PROXY_IMPL_COMPLETE; /*~ In the normal case, we return the shared secret, and then read * the next msg. */ @@ -872,7 +511,6 @@ static struct io_plan *handle_cannouncement_sig(struct io_conn *conn, return bad_req_fmt(conn, c, msg_in, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); - g_proxy_impl = PROXY_IMPL_COMPLETE; reply = towire_hsm_cannouncement_sig_reply(NULL, &node_sig, &bitcoin_sig); @@ -924,7 +562,6 @@ static struct io_plan *handle_channel_update_sig(struct io_conn *conn, return bad_req_fmt(conn, c, msg_in, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); - g_proxy_impl = PROXY_IMPL_COMPLETE; cu = towire_channel_update_option_channel_htlc_max(tmpctx, &sig, &chain_hash, &scid, timestamp, message_flags, channel_flags, @@ -962,7 +599,6 @@ static struct io_plan *handle_get_channel_basepoints(struct io_conn *conn, return bad_req_fmt(conn, c, msg_in, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); - g_proxy_impl = PROXY_IMPL_COMPLETE; return req_reply(conn, c, take(towire_hsm_get_channel_basepoints_reply(NULL, @@ -1021,7 +657,6 @@ static struct io_plan *handle_sign_commitment_tx(struct io_conn *conn, return bad_req_fmt(conn, c, msg_in, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); - g_proxy_impl = PROXY_IMPL_COMPLETE; return req_reply(conn, c, take(towire_hsm_sign_commitment_tx_reply(NULL, &sig))); @@ -1083,7 +718,6 @@ static struct io_plan *handle_sign_remote_commitment_tx(struct io_conn *conn, return bad_req_fmt(conn, c, msg_in, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); - g_proxy_impl = PROXY_IMPL_COMPLETE; status_debug("%s:%d %s: signature: %s", __FILE__, __LINE__, __FUNCTION__, @@ -1130,7 +764,6 @@ static struct io_plan *handle_sign_remote_htlc_tx(struct io_conn *conn, return bad_req_fmt(conn, c, msg_in, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); - g_proxy_impl = PROXY_IMPL_COMPLETE; return req_reply(conn, c, take(towire_hsm_sign_tx_reply(NULL, &sig))); } @@ -1171,7 +804,6 @@ static struct io_plan *handle_sign_delayed_payment_to_us(struct io_conn *conn, return bad_req_fmt(conn, c, msg_in, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); - g_proxy_impl = PROXY_IMPL_COMPLETE; return req_reply(conn, c, take(towire_hsm_sign_tx_reply(NULL, &sig))); } @@ -1217,7 +849,6 @@ static struct io_plan *handle_sign_remote_htlc_to_us(struct io_conn *conn, return bad_req_fmt(conn, c, msg_in, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); - g_proxy_impl = PROXY_IMPL_COMPLETE; return req_reply(conn, c, take(towire_hsm_sign_tx_reply(NULL, &sig))); } @@ -1257,7 +888,6 @@ static struct io_plan *handle_sign_penalty_to_us(struct io_conn *conn, return bad_req_fmt(conn, c, msg_in, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); - g_proxy_impl = PROXY_IMPL_COMPLETE; return req_reply(conn, c, take(towire_hsm_sign_tx_reply(NULL, &sig))); } @@ -1296,7 +926,6 @@ static struct io_plan *handle_sign_local_htlc_tx(struct io_conn *conn, return bad_req_fmt(conn, c, msg_in, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); - g_proxy_impl = PROXY_IMPL_COMPLETE; return req_reply(conn, c, take(towire_hsm_sign_tx_reply(NULL, &sig))); } @@ -1328,7 +957,6 @@ static struct io_plan *handle_get_per_commitment_point(struct io_conn *conn, return bad_req_fmt(conn, c, msg_in, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); - g_proxy_impl = PROXY_IMPL_COMPLETE; /*~ hsm_client_wire.csv marks the secret field here optional, so it only * gets included if the parameter is non-NULL. We violate 80 columns @@ -1364,7 +992,6 @@ static struct io_plan *handle_check_future_secret(struct io_conn *conn, return bad_req_fmt(conn, c, msg_in, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); - g_proxy_impl = PROXY_IMPL_COMPLETE; return req_reply(conn, c, take(towire_hsm_check_future_secret_reply(NULL, @@ -1402,7 +1029,6 @@ static struct io_plan *handle_sign_mutual_close_tx(struct io_conn *conn, return bad_req_fmt(conn, c, msg_in, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); - g_proxy_impl = PROXY_IMPL_COMPLETE; return req_reply(conn, c, take(towire_hsm_sign_tx_reply(NULL, &sig))); } @@ -1470,7 +1096,6 @@ static struct io_plan *pass_client_hsmfd(struct io_conn *conn, return bad_req_fmt(conn, c, msg_in, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); - g_proxy_impl = PROXY_IMPL_COMPLETE; /*~ We stash this in a global, because we need to get both the fd and * the client pointer to the callback. The other way would be to @@ -1480,154 +1105,6 @@ static struct io_plan *pass_client_hsmfd(struct io_conn *conn, send_pending_client_fd, c); } -/*~ For almost every wallet tx we use the BIP32 seed, but not for onchain - * unilateral closes from a peer: they (may) have an output to us using a - * public key based on the channel basepoints. It's a bit spammy to spend - * those immediately just to make the wallet simpler, and we didn't appreciate - * the problem when we designed the protocol for commitment transaction keys. - * - * So we store just enough about the channel it came from (which may be - * long-gone) to regenerate the keys here. That has the added advantage that - * the secrets themselves stay within the HSM. */ -static void hsm_unilateral_close_privkey(struct privkey *dst, - struct unilateral_close_info *info) -{ - struct secret channel_seed; - struct basepoints basepoints; - struct secrets secrets; - - get_channel_seed(&info->peer_id, info->channel_id, &channel_seed); - derive_basepoints(&channel_seed, NULL, &basepoints, &secrets, NULL); - - /* BOLT #3: - * - * If `option_static_remotekey` is negotiated the `remotepubkey` - * is simply the remote node's `payment_basepoint`, otherwise it is - * calculated as above using the remote node's `payment_basepoint`. - */ - /* In our UTXO representation, this is indicated by a NULL - * commitment_point. */ - if (!info->commitment_point) - dst->secret = secrets.payment_basepoint_secret; - else if (!derive_simple_privkey(&secrets.payment_basepoint_secret, - &basepoints.payment, - info->commitment_point, - dst)) { - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Deriving unilateral_close_privkey"); - } -} - -/* This gets the bitcoin private key needed to spend from our wallet. */ -static void hsm_key_for_utxo(struct privkey *privkey, struct pubkey *pubkey, - const struct utxo *utxo) -{ - if (utxo->close_info != NULL) { - /* This is a their_unilateral_close/to-us output, so - * we need to derive the secret the long way */ - status_debug("Unilateral close output, deriving secrets"); - hsm_unilateral_close_privkey(privkey, utxo->close_info); - pubkey_from_privkey(privkey, pubkey); - status_debug("Derived public key %s from unilateral close", - type_to_string(tmpctx, struct pubkey, pubkey)); - } else { - /* Simple case: just get derive via HD-derivation */ - bitcoin_key(privkey, pubkey, utxo->keyindex); - } -} - -/* This completes the tx by filling in the input scripts with signatures. */ -static void sign_all_inputs(struct bitcoin_tx *tx, struct utxo **utxos) -{ - /*~ Deep in my mind there's a continuous battle: should arrays be - * named as singular or plural? Is consistency the sign of a weak - * mind? - * - * ZmnSCPxj answers thusly: One must make peace with the fact, that - * the array itself is singular, yet its contents are plural. Do you - * name the array, or do you name its contents? Is the array itself - * the thing and the whole of the thing, or is it its contents that - * define what it is? - * - *... I'm not sure that helps! */ - assert(tx->wtx->num_inputs == tal_count(utxos)); - for (size_t i = 0; i < tal_count(utxos); i++) { - struct pubkey inkey; - struct privkey inprivkey; - const struct utxo *in = utxos[i]; - u8 *subscript, *wscript, *script; - struct bitcoin_signature sig; - - /* Figure out keys to spend this. */ - hsm_key_for_utxo(&inprivkey, &inkey, in); - - /* It's either a p2wpkh or p2sh (we support that so people from - * the last bitcoin era can put funds into the wallet) */ - wscript = p2wpkh_scriptcode(tmpctx, &inkey); - if (in->is_p2sh) { - /* For P2SH-wrapped Segwit, the (implied) redeemScript - * is defined in BIP141 */ - subscript = bitcoin_redeem_p2sh_p2wpkh(tmpctx, &inkey); - script = bitcoin_scriptsig_p2sh_p2wpkh(tx, &inkey); - bitcoin_tx_input_set_script(tx, i, script); - } else { - /* Pure segwit uses an empty inputScript; NULL has - * tal_count() == 0, so it works great here. */ - subscript = NULL; - bitcoin_tx_input_set_script(tx, i, NULL); - } - /* This is the core crypto magic. */ - sign_tx_input(tx, i, subscript, wscript, &inprivkey, &inkey, - SIGHASH_ALL, &sig); - - /* The witness is [sig] [key] */ - bitcoin_tx_input_set_witness( - tx, i, take(bitcoin_witness_p2wpkh(tx, &sig, &inkey))); - } -} - -/*~ lightningd asks us to sign the transaction to fund a channel; it feeds us - * the set of inputs and the local and remote pubkeys, and we sign it. */ -static struct io_plan *handle_sign_funding_tx(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - struct amount_sat satoshi_out, change_out; - u32 change_keyindex; - struct pubkey local_pubkey, remote_pubkey; - struct utxo **utxos; - struct bitcoin_tx *tx; - u16 outnum; - struct pubkey *changekey; - - /* FIXME: Check fee is "reasonable" */ - if (!fromwire_hsm_sign_funding(tmpctx, msg_in, - &satoshi_out, &change_out, - &change_keyindex, &local_pubkey, - &remote_pubkey, &utxos)) - return bad_req(conn, c, msg_in); - - if (amount_sat_greater(change_out, AMOUNT_SAT(0))) { - changekey = tal(tmpctx, struct pubkey); - bitcoin_key(NULL, changekey, change_keyindex); - } else - changekey = NULL; - - tx = funding_tx(tmpctx, c->chainparams, &outnum, - /*~ For simplicity, our generated code is not const - * correct. The C rules around const and - * pointer-to-pointer are a bit weird, so we use - * ccan/cast which ensures the type is correct and - * we're not casting something random */ - cast_const2(const struct utxo **, utxos), - satoshi_out, &local_pubkey, &remote_pubkey, - change_out, changekey, - NULL); - - sign_all_inputs(tx, utxos); - return req_reply(conn, c, take(towire_hsm_sign_funding_reply(NULL, tx))); -} - /*~ lightningd asks us to sign a withdrawal; same as above but in theory * we can do more to check the previous case is valid. */ static struct io_plan *handle_sign_withdrawal_tx(struct io_conn *conn, @@ -1647,18 +1124,10 @@ static struct io_plan *handle_sign_withdrawal_tx(struct io_conn *conn, &outputs, &utxos, &nlocktime)) return bad_req(conn, c, msg_in); - if (!bip32_pubkey(&secretstuff.bip32, &changekey, change_keyindex)) + if (!bip32_pubkey(&pubstuff.bip32, &changekey, change_keyindex)) return bad_req_fmt(conn, c, msg_in, "Failed to get key %u", change_keyindex); - // FIXME - The next few lines are for debug, remove them. - struct bitcoin_tx *tx2 = - withdraw_tx(tmpctx, c->chainparams, - cast_const2(const struct utxo **, utxos), outputs, - &changekey, change_out, NULL, NULL, nlocktime); - sign_all_inputs(tx2, utxos); - print_tx("REF", tx2); - tx = withdraw_tx(tmpctx, c->chainparams, cast_const2(const struct utxo **, utxos), outputs, &changekey, change_out, NULL, NULL, nlocktime); @@ -1678,7 +1147,6 @@ static struct io_plan *handle_sign_withdrawal_tx(struct io_conn *conn, proxy_last_message()); /* Sign w/ the remote lightning-signer. */ - g_proxy_impl = PROXY_IMPL_COMPLETE; assert(tal_count(wits) == tal_count(utxos)); for (size_t ii = 0; ii < tal_count(wits); ++ii) { struct pubkey inkey; @@ -1704,7 +1172,6 @@ static struct io_plan *handle_sign_withdrawal_tx(struct io_conn *conn, tal_count(wits[ii][1]), 0); bitcoin_tx_input_set_witness(tx, ii, take(witness)); } - print_tx("RLS", tx); return req_reply(conn, c, take(towire_hsm_sign_withdrawal_reply(NULL, tx))); @@ -1738,7 +1205,6 @@ static struct io_plan *handle_sign_invoice(struct io_conn *conn, return bad_req_fmt(conn, c, msg_in, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); - g_proxy_impl = PROXY_IMPL_COMPLETE; return req_reply(conn, c, take(towire_hsm_sign_invoice_reply(NULL, &rsig))); @@ -1785,7 +1251,6 @@ static struct io_plan *handle_sign_node_announcement(struct io_conn *conn, return bad_req_fmt(conn, c, msg_in, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); - g_proxy_impl = PROXY_IMPL_COMPLETE; reply = towire_hsm_node_announcement_sig_reply(NULL, &sig); return req_reply(conn, c, take(reply)); @@ -1818,7 +1283,6 @@ static struct io_plan *handle_sign_message(struct io_conn *conn, return bad_req_fmt(conn, c, msg_in, "proxy_%s error: %s", __FUNCTION__, proxy_last_message()); - g_proxy_impl = PROXY_IMPL_COMPLETE; return req_reply(conn, c, take(towire_hsm_sign_message_reply(NULL, &rsig))); @@ -1833,9 +1297,6 @@ static struct io_plan *handle_memleak(struct io_conn *conn, bool found_leak; u8 *reply; - /* Ignore this routine for proxy implementation purposes */ - g_proxy_impl = PROXY_IMPL_IGNORE; - memtable = memleak_enter_allocations(tmpctx, msg_in, msg_in); /* Now delete clients and anything they point to. */ @@ -1935,17 +1396,15 @@ static bool check_client_capabilities(struct client *client, } /*~ This is the core of the HSM daemon: handling requests. */ -static struct io_plan *handle_client_(struct io_conn *conn, struct client *c) +static struct io_plan *handle_client(struct io_conn *conn, struct client *c) { enum hsm_wire_type t = fromwire_peektype(c->msg_in); - g_proxy_last = t; status_debug("Client: Received message %d from client", t); /* Before we do anything else, is this client allowed to do * what he asks for? */ if (!check_client_capabilities(c, t)) { - g_proxy_impl = PROXY_IMPL_IGNORE; return bad_req_fmt(conn, c, c->msg_in, "does not have capability to run %d", t); } @@ -1971,7 +1430,9 @@ static struct io_plan *handle_client_(struct io_conn *conn, struct client *c) return handle_channel_update_sig(conn, c, c->msg_in); case WIRE_HSM_SIGN_FUNDING: - return handle_sign_funding_tx(conn, c, c->msg_in); + /* FIXME: Never called by integration tests. */ + /* return handle_sign_funding_tx(conn, c, c->msg_in); */ + assert(false); case WIRE_HSM_NODE_ANNOUNCEMENT_SIG_REQ: return handle_sign_node_announcement(conn, c, c->msg_in); @@ -2043,34 +1504,6 @@ static struct io_plan *handle_client_(struct io_conn *conn, struct client *c) return bad_req_fmt(conn, c, c->msg_in, "Unknown request"); } -/* FIXME - This wrapper is only here to figure out which handlers aren't - * proxied yet, remove it when we're done and rename handle_client_ to - * handle_client ... - */ -static struct io_plan *handle_client(struct io_conn *conn, struct client *c) -{ - g_proxy_impl = PROXY_IMPL_NONE; /* presume unimplemented */ - struct io_plan *rv = handle_client_(conn, c); - switch (g_proxy_impl) { - case PROXY_IMPL_NONE: - fprintf(stderr, "PROXY_IMPL_NONE %s\n", - hsm_wire_type_name(g_proxy_last)); - assert(false); - break; - case PROXY_IMPL_MARSHALED: - /* - fprintf(stderr, "PROXY_IMPL_MARSHALED %s\n", - hsm_wire_type_name(g_proxy_last)); - */ - break; - case PROXY_IMPL_COMPLETE: - case PROXY_IMPL_IGNORE: - /* quiet on purpose here */ - break; - } - return rv; -} - static void master_gone(struct io_conn *unused UNUSED, struct client *c UNUSED) { daemon_shutdown(); diff --git a/contrib/remote_hsmd/proxy.cc b/contrib/remote_hsmd/proxy.cc index 6ec1ae8f89f8..9165f8179981 100644 --- a/contrib/remote_hsmd/proxy.cc +++ b/contrib/remote_hsmd/proxy.cc @@ -22,6 +22,7 @@ extern "C" { #include #include #include +#include } #include "contrib/remote_hsmd/remotesigner.pb.h" @@ -214,6 +215,12 @@ void unmarshal_pubkey(PubKey const &pk, struct pubkey *o_pp) assert(ok); } +void unmarshal_ext_pubkey(ExtPubKey const &xpk, struct ext_key *o_xp) +{ + int rv = bip32_key_from_base58(xpk.encoded().data(), o_xp); + assert(rv == WALLY_OK); +} + void unmarshal_bitcoin_signature(BitcoinSignature const &bs, struct bitcoin_signature *o_sig) { @@ -293,42 +300,74 @@ void proxy_setup() proxy_stat proxy_init_hsm(struct bip32_key_version *bip32_key_version, struct chainparams const *chainparams, - struct secret *hsm_encryption_key, - struct privkey *privkey, - struct secret *seed, - struct secrets *secrets, - struct sha256 *shaseed, struct secret *hsm_secret, - struct node_id *o_node_id) + struct node_id *o_node_id, + struct ext_key *o_ext_pubkey) { - status_debug("%s:%d %s", __FILE__, __LINE__, __FUNCTION__); - - last_message = ""; - InitRequest req; - - auto cp = req.mutable_chainparams(); - cp->set_network_name(chainparams->network_name); + status_debug( + "%s:%d %s hsm_secret=%s", + __FILE__, __LINE__, __FUNCTION__, + dump_secret(hsm_secret).c_str() + ); - /* FIXME - Sending the secret instead of generating on the remote. */ - marshal_secret(hsm_secret, req.mutable_hsm_secret()); + /* First we make the Init call to create the Node. */ + { + last_message = ""; + InitRequest req; + + auto cp = req.mutable_chainparams(); + cp->set_network_name(chainparams->network_name); + + /* FIXME - Sending the secret instead of generating on + * the remote. */ + marshal_secret(hsm_secret, req.mutable_hsm_secret()); + + ClientContext context; + InitReply rsp; + Status status = stub->Init(&context, req, &rsp); + if (status.ok()) { + unmarshal_node_id(rsp.node_id(), o_node_id); + unmarshal_node_id(rsp.node_id(), &self_id); + status_debug("%s:%d %s node_id=%s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(o_node_id).c_str()); + last_message = "success"; + // Fall-through to the next part. */ + } else { + status_unusual("%s:%d %s: %s", + __FILE__, __LINE__, __FUNCTION__, + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } + } - ClientContext context; - InitReply rsp; - Status status = stub->Init(&context, req, &rsp); - if (status.ok()) { - unmarshal_node_id(rsp.node_id(), o_node_id); - unmarshal_node_id(rsp.node_id(), &self_id); - status_debug("%s:%d %s node_id=%s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(o_node_id).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: %s", - __FILE__, __LINE__, __FUNCTION__, - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); + /* Next we make the GetExtPubKey call to fetch the XPUB. */ + { + last_message = ""; + GetExtPubKeyRequest req; + + marshal_node_id(&self_id, req.mutable_node_id()); + req.set_derivation_path("m"); + + ClientContext context; + GetExtPubKeyReply rsp; + Status status = stub->GetExtPubKey(&context, req, &rsp); + if (status.ok()) { + unmarshal_ext_pubkey(rsp.xpub(), o_ext_pubkey); + status_debug("%s:%d %s node_id=%s ext_pubkey=%s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_ext_pubkey(o_ext_pubkey).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: %s", + __FILE__, __LINE__, __FUNCTION__, + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } } } diff --git a/contrib/remote_hsmd/proxy.h b/contrib/remote_hsmd/proxy.h index 84b5f014331b..58b066b94769 100644 --- a/contrib/remote_hsmd/proxy.h +++ b/contrib/remote_hsmd/proxy.h @@ -30,13 +30,9 @@ void proxy_setup(void); proxy_stat proxy_init_hsm( struct bip32_key_version *bip32_key_version, struct chainparams const *chainparams, - struct secret *hsm_encryption_key, - struct privkey *privkey, - struct secret *seed, - struct secrets *secrets, - struct sha256 *shaseed, struct secret *hsm_secret, - struct node_id *o_node_id); + struct node_id *o_node_id, + struct ext_key *o_ext_pub_key); proxy_stat proxy_handle_ecdh( const struct pubkey *point, diff --git a/tests/test_misc.py b/tests/test_misc.py index e82935ff0e03..db9000522ddf 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1023,6 +1023,7 @@ def test_daemon_option(node_factory): @flaky @unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1") +@unittest.skipIf(os.getenv('SUBDAEMON', 'xxx') == 'hsmd:remote_hsmd', "remote_hsmd doesn't have repeatable random seeding") def test_blockchaintrack(node_factory, bitcoind): """Check that we track the blockchain correctly across reorgs """ @@ -2000,6 +2001,7 @@ def test_regtest_upgrade(node_factory): @unittest.skipIf(VALGRIND, "valgrind files can't be written since we rmdir") @unittest.skipIf(TEST_NETWORK != "regtest", "needs bitcoin mainnet") +@unittest.skipIf(os.getenv('SUBDAEMON', 'xxx') == 'hsmd:remote_hsmd', "remote_hsmd doesn't create hsm_secret file") def test_new_node_is_mainnet(node_factory): """Test that an empty directory causes us to be on mainnet""" l1 = node_factory.get_node(start=False, may_fail=True) @@ -2159,6 +2161,7 @@ def test_sendcustommsg(node_factory): @unittest.skipIf(not DEVELOPER, "needs --dev-force-privkey") +@unittest.skipIf(os.getenv('SUBDAEMON', 'xxx') == 'hsmd:remote_hsmd', "remote_hsmd doesn't support dev-force-privkey") def test_getsharedsecret(node_factory): """ Test getsharedsecret command. diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 55b6fa7d3912..6117be84637b 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -593,6 +593,7 @@ def test_transaction_annotations(node_factory, bitcoind): @unittest.skipIf(VALGRIND, "It does not play well with prompt and key derivation.") +@unittest.skipIf(os.getenv('SUBDAEMON', 'xxx') == 'hsmd:remote_hsmd', "remote_hsmd doesn't support hsm_secret file") def test_hsm_secret_encryption(node_factory): l1 = node_factory.get_node(may_fail=True) # May fail when started without key password = "reckful\n" @@ -633,6 +634,7 @@ def test_hsm_secret_encryption(node_factory): @unittest.skipIf(VALGRIND, "It does not play well with prompt and key derivation.") +@unittest.skipIf(os.getenv('SUBDAEMON', 'xxx') == 'hsmd:remote_hsmd', "remote_hsmd doesn't support hsm_secret file") def test_hsmtool_secret_decryption(node_factory): l1 = node_factory.get_node() password = "reckless\n"