mirror of
https://github.com/aljazceru/lightning.git
synced 2025-12-23 17:14:22 +01:00
libhsmd: Migrate handle_sign_withdrawal_tx
This commit is contained in:
committed by
Rusty Russell
parent
e7007a7f36
commit
9aa4b5198d
161
hsmd/hsmd.c
161
hsmd/hsmd.c
@@ -500,37 +500,6 @@ static void populate_secretstuff(void)
|
|||||||
"Can't derive bolt12 keypair");
|
"Can't derive bolt12 keypair");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*~ 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 encrypts the content of the secretstuff and stores it in hsm_secret,
|
||||||
* this is called instead of create_hsm() if `lightningd` is started with
|
* this is called instead of create_hsm() if `lightningd` is started with
|
||||||
* --encrypted-hsm.
|
* --encrypted-hsm.
|
||||||
@@ -1263,132 +1232,6 @@ static struct io_plan *pass_client_hsmfd(struct io_conn *conn,
|
|||||||
send_pending_client_fd, c);
|
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` or `option_anchor_outputs` 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find our inputs by the pubkey associated with the inputs, and
|
|
||||||
* add a partial sig for each */
|
|
||||||
static void sign_our_inputs(struct utxo **utxos, struct wally_psbt *psbt)
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < tal_count(utxos); i++) {
|
|
||||||
struct utxo *utxo = utxos[i];
|
|
||||||
for (size_t j = 0; j < psbt->num_inputs; j++) {
|
|
||||||
struct privkey privkey;
|
|
||||||
struct pubkey pubkey;
|
|
||||||
|
|
||||||
if (!wally_tx_input_spends(&psbt->tx->inputs[j],
|
|
||||||
&utxo->txid, utxo->outnum))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
hsm_key_for_utxo(&privkey, &pubkey, utxo);
|
|
||||||
|
|
||||||
/* This line is basically the entire reason we have
|
|
||||||
* to iterate through to match the psbt input
|
|
||||||
* to the UTXO -- otherwise we would just
|
|
||||||
* call wally_psbt_sign for every utxo privkey
|
|
||||||
* and be done with it. We can't do that though
|
|
||||||
* because any UTXO that's derived from channel_info
|
|
||||||
* 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);
|
|
||||||
|
|
||||||
/* It's actually a P2WSH in this case. */
|
|
||||||
if (utxo->close_info && utxo->close_info->option_anchor_outputs) {
|
|
||||||
const u8 *wscript = anchor_to_remote_redeem(tmpctx, &pubkey);
|
|
||||||
psbt_input_set_witscript(psbt, j, wscript);
|
|
||||||
psbt_input_set_wit_utxo(psbt, j,
|
|
||||||
scriptpubkey_p2wsh(psbt, wscript),
|
|
||||||
utxo->amount);
|
|
||||||
}
|
|
||||||
tal_wally_start();
|
|
||||||
if (wally_psbt_sign(psbt, privkey.secret.data,
|
|
||||||
sizeof(privkey.secret.data),
|
|
||||||
EC_FLAG_GRIND_R) != WALLY_OK)
|
|
||||||
status_broken("Received wally_err attempting to "
|
|
||||||
"sign utxo with key %s. PSBT: %s",
|
|
||||||
type_to_string(tmpctx, struct pubkey,
|
|
||||||
&pubkey),
|
|
||||||
type_to_string(tmpctx, struct wally_psbt,
|
|
||||||
psbt));
|
|
||||||
tal_wally_end(psbt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*~ 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,
|
|
||||||
struct client *c,
|
|
||||||
const u8 *msg_in)
|
|
||||||
{
|
|
||||||
struct utxo **utxos;
|
|
||||||
struct wally_psbt *psbt;
|
|
||||||
|
|
||||||
if (!fromwire_hsmd_sign_withdrawal(tmpctx, msg_in,
|
|
||||||
&utxos, &psbt))
|
|
||||||
return bad_req(conn, c, msg_in);
|
|
||||||
|
|
||||||
sign_our_inputs(utxos, psbt);
|
|
||||||
|
|
||||||
return req_reply(conn, c,
|
|
||||||
take(towire_hsmd_sign_withdrawal_reply(NULL, psbt)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEVELOPER
|
#if DEVELOPER
|
||||||
static struct io_plan *handle_memleak(struct io_conn *conn,
|
static struct io_plan *handle_memleak(struct io_conn *conn,
|
||||||
struct client *c,
|
struct client *c,
|
||||||
@@ -1485,9 +1328,6 @@ static struct io_plan *handle_client(struct io_conn *conn, struct client *c)
|
|||||||
case WIRE_HSMD_CLIENT_HSMFD:
|
case WIRE_HSMD_CLIENT_HSMFD:
|
||||||
return pass_client_hsmfd(conn, c, c->msg_in);
|
return pass_client_hsmfd(conn, c, c->msg_in);
|
||||||
|
|
||||||
case WIRE_HSMD_SIGN_WITHDRAWAL:
|
|
||||||
return handle_sign_withdrawal_tx(conn, c, c->msg_in);
|
|
||||||
|
|
||||||
case WIRE_HSMD_SIGN_COMMITMENT_TX:
|
case WIRE_HSMD_SIGN_COMMITMENT_TX:
|
||||||
return handle_sign_commitment_tx(conn, c, c->msg_in);
|
return handle_sign_commitment_tx(conn, c, c->msg_in);
|
||||||
|
|
||||||
@@ -1513,6 +1353,7 @@ static struct io_plan *handle_client(struct io_conn *conn, struct client *c)
|
|||||||
return handle_sign_mutual_close_tx(conn, c, c->msg_in);
|
return handle_sign_mutual_close_tx(conn, c, c->msg_in);
|
||||||
|
|
||||||
case WIRE_HSMD_GET_PER_COMMITMENT_POINT:
|
case WIRE_HSMD_GET_PER_COMMITMENT_POINT:
|
||||||
|
case WIRE_HSMD_SIGN_WITHDRAWAL:
|
||||||
case WIRE_HSMD_GET_CHANNEL_BASEPOINTS:
|
case WIRE_HSMD_GET_CHANNEL_BASEPOINTS:
|
||||||
case WIRE_HSMD_SIGN_INVOICE:
|
case WIRE_HSMD_SIGN_INVOICE:
|
||||||
case WIRE_HSMD_SIGN_MESSAGE:
|
case WIRE_HSMD_SIGN_MESSAGE:
|
||||||
|
|||||||
120
hsmd/libhsmd.c
120
hsmd/libhsmd.c
@@ -3,6 +3,7 @@
|
|||||||
#include <common/bolt12_merkle.h>
|
#include <common/bolt12_merkle.h>
|
||||||
#include <common/hash_u5.h>
|
#include <common/hash_u5.h>
|
||||||
#include <common/key_derive.h>
|
#include <common/key_derive.h>
|
||||||
|
#include <common/type_to_string.h>
|
||||||
#include <hsmd/capabilities.h>
|
#include <hsmd/capabilities.h>
|
||||||
#include <hsmd/libhsmd.h>
|
#include <hsmd/libhsmd.h>
|
||||||
#include <wire/peer_wire.h>
|
#include <wire/peer_wire.h>
|
||||||
@@ -300,6 +301,106 @@ static void hsm_unilateral_close_privkey(struct privkey *dst,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*~ 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)
|
||||||
|
hsmd_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)
|
||||||
|
hsmd_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))
|
||||||
|
hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR,
|
||||||
|
"BIP32 pubkey %u create failed", index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 */
|
||||||
|
hsmd_status_debug("Unilateral close output, deriving secrets");
|
||||||
|
hsm_unilateral_close_privkey(privkey, utxo->close_info);
|
||||||
|
pubkey_from_privkey(privkey, pubkey);
|
||||||
|
hsmd_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find our inputs by the pubkey associated with the inputs, and
|
||||||
|
* add a partial sig for each */
|
||||||
|
static void sign_our_inputs(struct utxo **utxos, struct wally_psbt *psbt)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < tal_count(utxos); i++) {
|
||||||
|
struct utxo *utxo = utxos[i];
|
||||||
|
for (size_t j = 0; j < psbt->num_inputs; j++) {
|
||||||
|
struct privkey privkey;
|
||||||
|
struct pubkey pubkey;
|
||||||
|
|
||||||
|
if (!wally_tx_input_spends(&psbt->tx->inputs[j],
|
||||||
|
&utxo->txid, utxo->outnum))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
hsm_key_for_utxo(&privkey, &pubkey, utxo);
|
||||||
|
|
||||||
|
/* This line is basically the entire reason we have
|
||||||
|
* to iterate through to match the psbt input
|
||||||
|
* to the UTXO -- otherwise we would just
|
||||||
|
* call wally_psbt_sign for every utxo privkey
|
||||||
|
* and be done with it. We can't do that though
|
||||||
|
* because any UTXO that's derived from channel_info
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
/* It's actually a P2WSH in this case. */
|
||||||
|
if (utxo->close_info && utxo->close_info->option_anchor_outputs) {
|
||||||
|
const u8 *wscript = anchor_to_remote_redeem(tmpctx, &pubkey);
|
||||||
|
psbt_input_set_witscript(psbt, j, wscript);
|
||||||
|
psbt_input_set_wit_utxo(psbt, j,
|
||||||
|
scriptpubkey_p2wsh(psbt, wscript),
|
||||||
|
utxo->amount);
|
||||||
|
}
|
||||||
|
tal_wally_start();
|
||||||
|
if (wally_psbt_sign(psbt, privkey.secret.data,
|
||||||
|
sizeof(privkey.secret.data),
|
||||||
|
EC_FLAG_GRIND_R) != WALLY_OK)
|
||||||
|
hsmd_status_broken(
|
||||||
|
"Received wally_err attempting to "
|
||||||
|
"sign utxo with key %s. PSBT: %s",
|
||||||
|
type_to_string(tmpctx, struct pubkey,
|
||||||
|
&pubkey),
|
||||||
|
type_to_string(tmpctx, struct wally_psbt,
|
||||||
|
psbt));
|
||||||
|
tal_wally_end(psbt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*~ lightningd asks us to sign a message. I tweeted the spec
|
/*~ lightningd asks us to sign a message. I tweeted the spec
|
||||||
* in https://twitter.com/rusty_twit/status/1182102005914800128:
|
* in https://twitter.com/rusty_twit/status/1182102005914800128:
|
||||||
*
|
*
|
||||||
@@ -761,6 +862,22 @@ static u8 *handle_get_per_commitment_point(struct hsmd_client *c, const u8 *msg_
|
|||||||
NULL, &per_commitment_point, old_secret);
|
NULL, &per_commitment_point, old_secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*~ 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 u8 *handle_sign_withdrawal_tx(struct hsmd_client *c, const u8 *msg_in)
|
||||||
|
{
|
||||||
|
struct utxo **utxos;
|
||||||
|
struct wally_psbt *psbt;
|
||||||
|
|
||||||
|
if (!fromwire_hsmd_sign_withdrawal(tmpctx, msg_in,
|
||||||
|
&utxos, &psbt))
|
||||||
|
return hsmd_status_malformed_request(c, msg_in);
|
||||||
|
|
||||||
|
sign_our_inputs(utxos, psbt);
|
||||||
|
|
||||||
|
return towire_hsmd_sign_withdrawal_reply(NULL, psbt);
|
||||||
|
}
|
||||||
|
|
||||||
u8 *hsmd_handle_client_message(const tal_t *ctx, struct hsmd_client *client,
|
u8 *hsmd_handle_client_message(const tal_t *ctx, struct hsmd_client *client,
|
||||||
const u8 *msg)
|
const u8 *msg)
|
||||||
{
|
{
|
||||||
@@ -787,7 +904,6 @@ u8 *hsmd_handle_client_message(const tal_t *ctx, struct hsmd_client *client,
|
|||||||
switch (t) {
|
switch (t) {
|
||||||
case WIRE_HSMD_INIT:
|
case WIRE_HSMD_INIT:
|
||||||
case WIRE_HSMD_CLIENT_HSMFD:
|
case WIRE_HSMD_CLIENT_HSMFD:
|
||||||
case WIRE_HSMD_SIGN_WITHDRAWAL:
|
|
||||||
case WIRE_HSMD_SIGN_COMMITMENT_TX:
|
case WIRE_HSMD_SIGN_COMMITMENT_TX:
|
||||||
case WIRE_HSMD_SIGN_DELAYED_PAYMENT_TO_US:
|
case WIRE_HSMD_SIGN_DELAYED_PAYMENT_TO_US:
|
||||||
case WIRE_HSMD_SIGN_REMOTE_HTLC_TO_US:
|
case WIRE_HSMD_SIGN_REMOTE_HTLC_TO_US:
|
||||||
@@ -821,6 +937,8 @@ u8 *hsmd_handle_client_message(const tal_t *ctx, struct hsmd_client *client,
|
|||||||
return handle_channel_update_sig(client, msg);
|
return handle_channel_update_sig(client, msg);
|
||||||
case WIRE_HSMD_GET_PER_COMMITMENT_POINT:
|
case WIRE_HSMD_GET_PER_COMMITMENT_POINT:
|
||||||
return handle_get_per_commitment_point(client, msg);
|
return handle_get_per_commitment_point(client, msg);
|
||||||
|
case WIRE_HSMD_SIGN_WITHDRAWAL:
|
||||||
|
return handle_sign_withdrawal_tx(client, msg);
|
||||||
|
|
||||||
case WIRE_HSMD_DEV_MEMLEAK:
|
case WIRE_HSMD_DEV_MEMLEAK:
|
||||||
case WIRE_HSMD_ECDH_RESP:
|
case WIRE_HSMD_ECDH_RESP:
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ void hsmd_status_fmt(enum log_level level,
|
|||||||
|
|
||||||
#define hsmd_status_debug(...) \
|
#define hsmd_status_debug(...) \
|
||||||
hsmd_status_fmt(LOG_DBG, NULL, __VA_ARGS__)
|
hsmd_status_fmt(LOG_DBG, NULL, __VA_ARGS__)
|
||||||
|
#define hsmd_status_broken(...) \
|
||||||
|
hsmd_status_fmt(LOG_BROKEN, NULL, __VA_ARGS__)
|
||||||
|
|
||||||
void hsmd_status_failed(enum status_failreason code,
|
void hsmd_status_failed(enum status_failreason code,
|
||||||
const char *fmt, ...) PRINTF_FMT(2,3);
|
const char *fmt, ...) PRINTF_FMT(2,3);
|
||||||
|
|||||||
Reference in New Issue
Block a user