diff --git a/common/hsm_version.h b/common/hsm_version.h index 6407f262f..cf085cffc 100644 --- a/common/hsm_version.h +++ b/common/hsm_version.h @@ -11,6 +11,7 @@ * v3: edd3d288fc88a5470adc2f99abcbfe4d4af29fae0c7a80b4226f28810a815524 * v3 without v1: 3f813898f7de490e9126ab817e1c9a29af79c0413d5e37068acedce3ea7b5429 * v4: 41a730986c51b930e2d8d12b3169d24966c2004e08d424bdda310edbbde5ba70 + * v4 with check_pubkey: 48b3992745aa3c6ab6ce5cdaee9082cb7d70017f523d322015e9710bf49fd193 */ #define HSM_MIN_VERSION 3 #define HSM_MAX_VERSION 4 diff --git a/hsmd/hsmd.c b/hsmd/hsmd.c index f4459f3f6..0f1a37325 100644 --- a/hsmd/hsmd.c +++ b/hsmd/hsmd.c @@ -680,6 +680,7 @@ static struct io_plan *handle_client(struct io_conn *conn, struct client *c) case WIRE_HSMD_SIGN_LOCAL_HTLC_TX: case WIRE_HSMD_SIGN_REMOTE_HTLC_TO_US: case WIRE_HSMD_SIGN_DELAYED_PAYMENT_TO_US: + case WIRE_HSMD_CHECK_PUBKEY: /* Hand off to libhsmd for processing */ return req_reply(conn, c, take(hsmd_handle_client_message( @@ -712,6 +713,7 @@ static struct io_plan *handle_client(struct io_conn *conn, struct client *c) case WIRE_HSMD_SIGN_BOLT12_REPLY: case WIRE_HSMD_PREAPPROVE_INVOICE_REPLY: case WIRE_HSMD_PREAPPROVE_KEYSEND_REPLY: + case WIRE_HSMD_CHECK_PUBKEY_REPLY: return bad_req_fmt(conn, c, c->msg_in, "Received an incoming message of type %s, " "which is not a request", diff --git a/hsmd/hsmd_wire.csv b/hsmd/hsmd_wire.csv index ac6fa86e8..8a2ef9ecd 100644 --- a/hsmd/hsmd_wire.csv +++ b/hsmd/hsmd_wire.csv @@ -323,3 +323,12 @@ msgdata,hsmd_derive_secret,info,u8,len # Reply with the derived secret msgtype,hsmd_derive_secret_reply,127 msgdata,hsmd_derive_secret_reply,secret,secret, + +# Sanity check this pubkey derivation is correct (unhardened only) +msgtype,hsmd_check_pubkey,28 +msgdata,hsmd_check_pubkey,index,u32, +msgdata,hsmd_check_pubkey,pubkey,pubkey, + +# Reply +msgtype,hsmd_check_pubkey_reply,128 +msgdata,hsmd_check_pubkey_reply,ok,bool, diff --git a/hsmd/libhsmd.c b/hsmd/libhsmd.c index 0a362f1b5..0ba4c87ec 100644 --- a/hsmd/libhsmd.c +++ b/hsmd/libhsmd.c @@ -1,5 +1,6 @@ #include "config.h" #include +#include #include #include #include @@ -122,6 +123,7 @@ bool hsmd_check_client_capabilities(struct hsmd_client *client, case WIRE_HSMD_PREAPPROVE_INVOICE: case WIRE_HSMD_PREAPPROVE_KEYSEND: case WIRE_HSMD_DERIVE_SECRET: + case WIRE_HSMD_CHECK_PUBKEY: return (client->capabilities & HSM_CAP_MASTER) != 0; /*~ These are messages sent by the HSM so we should never receive them. */ @@ -154,6 +156,7 @@ bool hsmd_check_client_capabilities(struct hsmd_client *client, case WIRE_HSMD_PREAPPROVE_INVOICE_REPLY: case WIRE_HSMD_PREAPPROVE_KEYSEND_REPLY: case WIRE_HSMD_DERIVE_SECRET_REPLY: + case WIRE_HSMD_CHECK_PUBKEY_REPLY: break; } return false; @@ -533,6 +536,33 @@ static u8 *handle_sign_to_us_tx(struct hsmd_client *c, const u8 *msg_in, return towire_hsmd_sign_tx_reply(NULL, &sig); } +/* This will check lightningd's key derivation: hopefully any errors in + * this process are independent of errors in lightningd! */ +static u8 *handle_check_pubkey(struct hsmd_client *c, const u8 *msg_in) +{ + u32 index; + struct pubkey their_pubkey, our_pubkey; + struct privkey our_privkey; + + if (!fromwire_hsmd_check_pubkey(msg_in, &index, &their_pubkey)) + return hsmd_status_malformed_request(c, msg_in); + + /* We abort if lightningd asks for a stupid index. */ + bitcoin_key(&our_privkey, &our_pubkey, index); + if (!pubkey_eq(&our_pubkey, &their_pubkey)) { + hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR, + "BIP32 derivation index %u differed:" + " they got %s, we got %s", + index, + type_to_string(tmpctx, struct pubkey, + &their_pubkey), + type_to_string(tmpctx, struct pubkey, + &our_pubkey)); + } + + return towire_hsmd_check_pubkey_reply(NULL, true); +} + /*~ lightningd asks us to sign a message. I tweeted the spec * in https://twitter.com/rusty_twit/status/1182102005914800128: * @@ -1650,6 +1680,8 @@ u8 *hsmd_handle_client_message(const tal_t *ctx, struct hsmd_client *client, return handle_sign_delayed_payment_to_us(client, msg); case WIRE_HSMD_DERIVE_SECRET: return handle_derive_secret(client, msg); + case WIRE_HSMD_CHECK_PUBKEY: + return handle_check_pubkey(client, msg); case WIRE_HSMD_DEV_MEMLEAK: case WIRE_HSMD_ECDH_RESP: @@ -1679,6 +1711,7 @@ u8 *hsmd_handle_client_message(const tal_t *ctx, struct hsmd_client *client, case WIRE_HSMD_SIGN_BOLT12_REPLY: case WIRE_HSMD_PREAPPROVE_INVOICE_REPLY: case WIRE_HSMD_PREAPPROVE_KEYSEND_REPLY: + case WIRE_HSMD_CHECK_PUBKEY_REPLY: break; } return hsmd_status_bad_request(client, msg, "Unknown request"); @@ -1692,6 +1725,7 @@ u8 *hsmd_init(struct secret hsm_secret, u32 salt = 0; struct ext_key master_extkey, child_extkey; struct node_id node_id; + static const u32 capabilities[] = { WIRE_HSMD_CHECK_PUBKEY }; /*~ Don't swap this. */ sodium_mlock(secretstuff.hsm_secret.data, @@ -1822,6 +1856,10 @@ u8 *hsmd_init(struct secret hsm_secret, * incompatibility detection) with alternate implementations. */ return take(towire_hsmd_init_reply_v4( - NULL, 4, NULL, &node_id, &secretstuff.bip32, + NULL, 4, + /* Capabilities arg needs to be a tal array */ + tal_dup_arr(tmpctx, u32, capabilities, + ARRAY_SIZE(capabilities), 0), + &node_id, &secretstuff.bip32, &bolt12)); } diff --git a/tests/test_misc.py b/tests/test_misc.py index 7e414251d..02c7e17ee 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -3096,3 +3096,9 @@ def test_checkmessage_pubkey_not_found(node_factory): check_result = l1.rpc.checkmessage(msg, zbase, pubkey=pubkey) assert check_result["pubkey"] == pubkey assert check_result["verified"] is True + + +def test_hsm_capabilities(node_factory): + l1 = node_factory.get_node() + # This appears before the start message, so it'll already be present. + assert l1.daemon.is_in_log(r"hsmd: capability \+WIRE_HSMD_CHECK_PUBKEY")