From dafaf854c5f4b4f4c4f4b14e237532b07523a97d Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 26 May 2020 13:09:40 +0930 Subject: [PATCH] bitcoin/tx_parts: infrastructure for partial bitcoin txs. `struct tx_parts` is just a txid and a bunch of inputs and outputs, some of which may be NULL. This is both a nod towards a future where we (or our peer) can combine HTLCs or (in an eltoo world) commitments, although for the moment all our tx_parts will be complete. It also matches our plan to split `bitcoin_tx` into two types: this `struct tx_parts` where we don't know input amounts etc, and `psbt` where we do. Signed-off-by: Rusty Russell --- bitcoin/Makefile | 2 + bitcoin/tx_parts.c | 396 +++++++++++++++++++++++++++++++++++++++++ bitcoin/tx_parts.h | 29 +++ tools/generate-wire.py | 1 + 4 files changed, 428 insertions(+) create mode 100644 bitcoin/tx_parts.c create mode 100644 bitcoin/tx_parts.h diff --git a/bitcoin/Makefile b/bitcoin/Makefile index 9d2635947..9b204bc73 100644 --- a/bitcoin/Makefile +++ b/bitcoin/Makefile @@ -15,6 +15,7 @@ BITCOIN_SRC := \ bitcoin/short_channel_id.c \ bitcoin/signature.c \ bitcoin/tx.c \ + bitcoin/tx_parts.c \ bitcoin/varint.c BITCOIN_OBJS := $(BITCOIN_SRC:.c=.o) @@ -35,6 +36,7 @@ BITCOIN_HEADERS := bitcoin/address.h \ bitcoin/short_channel_id.h \ bitcoin/signature.h \ bitcoin/tx.h \ + bitcoin/tx_parts.h \ bitcoin/varint.h check-source: $(BITCOIN_SRC:%=check-src-include-order/%) \ diff --git a/bitcoin/tx_parts.c b/bitcoin/tx_parts.c new file mode 100644 index 000000000..e788a1514 --- /dev/null +++ b/bitcoin/tx_parts.c @@ -0,0 +1,396 @@ +#include +#include +#include +#include + +/* This destructor makes it behave like a native tal tree (a little!) */ +static void destroy_wally_tx_input(struct wally_tx_input *in) +{ + wally_tx_input_free(in); +} + +static struct wally_tx_input *clone_input(const tal_t *ctx, + const struct wally_tx_input *src) +{ + struct wally_tx_input *in; + int ret; + + if (is_elements(chainparams)) { + ret = wally_tx_elements_input_init_alloc + (src->txhash, sizeof(src->txhash), + src->index, src->sequence, + src->script, src->script_len, + src->witness, + src->blinding_nonce, sizeof(src->blinding_nonce), + src->entropy, sizeof(src->entropy), + src->issuance_amount, src->issuance_amount_len, + src->inflation_keys, src->inflation_keys_len, + src->issuance_amount_rangeproof, + src->issuance_amount_rangeproof_len, + src->inflation_keys_rangeproof, + src->inflation_keys_rangeproof_len, + src->pegin_witness, + &in); + } else { + ret = wally_tx_input_init_alloc(src->txhash, sizeof(src->txhash), + src->index, src->sequence, + src->script, src->script_len, + src->witness, &in); + } + assert(ret == WALLY_OK); + + tal_add_destructor(in, destroy_wally_tx_input); + return tal_steal(ctx, in); +} + +static void destroy_wally_tx_output(struct wally_tx_output *out) +{ + wally_tx_output_free(out); +} + +static struct wally_tx_output *clone_output(const tal_t *ctx, + const struct wally_tx_output *src) +{ + struct wally_tx_output *out; + int ret; + + if (is_elements(chainparams)) { + ret = wally_tx_elements_output_init_alloc + (src->script, src->script_len, + src->asset, src->asset_len, + src->value, src->value_len, + src->nonce, src->nonce_len, + src->surjectionproof, src->surjectionproof_len, + src->rangeproof, src->rangeproof_len, + &out); + } else { + ret = wally_tx_output_init_alloc(src->satoshi, + src->script, src->script_len, + &out); + } + assert(ret == WALLY_OK); + + tal_add_destructor(out, destroy_wally_tx_output); + return tal_steal(ctx, out); +} + +struct tx_parts *tx_parts_from_wally_tx(const tal_t *ctx, + const struct wally_tx *wtx, + int input, int output) +{ + struct tx_parts *txp = tal(ctx, struct tx_parts); + + wally_txid(wtx, &txp->txid); + txp->inputs = tal_arrz(txp, struct wally_tx_input *, wtx->num_inputs); + txp->outputs = tal_arrz(txp, struct wally_tx_output *, wtx->num_outputs); + + for (size_t i = 0; i < wtx->num_inputs; i++) { + if (input != -1 && input != i) + continue; + txp->inputs[i] = clone_input(txp->inputs, &wtx->inputs[i]); + } + + for (size_t i = 0; i < wtx->num_outputs; i++) { + if (output != -1 && output != i) + continue; + txp->outputs[i] = clone_output(txp->outputs, &wtx->outputs[i]); + } + + return txp; +} + +/* FIXME: If libwally exposed their linearization code, we could use it */ +static struct wally_tx_witness_stack * +fromwire_wally_tx_witness_stack(const tal_t *ctx, + const u8 **cursor, + size_t *max) +{ + struct wally_tx_witness_stack *ws; + size_t num; + int ret; + + num = fromwire_u32(cursor, max); + if (num == 0) + return NULL; + + ret = wally_tx_witness_stack_init_alloc(num, &ws); + if (ret != WALLY_OK) { + fromwire_fail(cursor, max); + return NULL; + } + + for (size_t i = 0; i < num; i++) { + u8 *w = fromwire_tal_arrn(tmpctx, + cursor, max, + fromwire_u32(cursor, max)); + ret = wally_tx_witness_stack_add(ws, w, tal_bytelen(w)); + if (ret != WALLY_OK) { + wally_tx_witness_stack_free(ws); + fromwire_fail(cursor, max); + return NULL; + } + } + + return ws; +} + +static void towire_wally_tx_witness_stack(u8 **pptr, + const struct wally_tx_witness_stack *ws) +{ + if (!ws) { + towire_u32(pptr, 0); + return; + } + + towire_u32(pptr, ws->num_items); + for (size_t i = 0; i < ws->num_items; i++) { + towire_u32(pptr, ws->items[i].witness_len); + towire_u8_array(pptr, + ws->items[i].witness, + ws->items[i].witness_len); + } +} + +static struct wally_tx_input *fromwire_wally_tx_input(const tal_t *ctx, + const u8 **cursor, + size_t *max) +{ + struct wally_tx_input *in; + struct bitcoin_txid txid; + u32 index, sequence; + u8 *script; + struct wally_tx_witness_stack *ws; + int ret; + + fromwire_bitcoin_txid(cursor, max, &txid); + index = fromwire_u32(cursor, max); + sequence = fromwire_u32(cursor, max); + script = fromwire_tal_arrn(tmpctx, + cursor, max, fromwire_u32(cursor, max)); + /* libwally doesn't like non-NULL ptrs with zero lengths. */ + if (tal_bytelen(script) == 0) + script = tal_free(script); + ws = fromwire_wally_tx_witness_stack(tmpctx, cursor, max); + + if (is_elements(chainparams)) { + u8 *blinding_nonce, *entropy, *issuance_amount, + *inflation_keys, *issuance_amount_rangeproof, + *inflation_keys_rangeproof; + struct wally_tx_witness_stack *pegin_witness; + + blinding_nonce = fromwire_tal_arrn(tmpctx, + cursor, max, + fromwire_u32(cursor, max)); + entropy = fromwire_tal_arrn(tmpctx, + cursor, max, + fromwire_u32(cursor, max)); + issuance_amount = fromwire_tal_arrn(tmpctx, + cursor, max, + fromwire_u32(cursor, max)); + inflation_keys = fromwire_tal_arrn(tmpctx, + cursor, max, + fromwire_u32(cursor, max)); + issuance_amount_rangeproof = fromwire_tal_arrn(tmpctx, + cursor, max, + fromwire_u32(cursor, max)); + inflation_keys_rangeproof = fromwire_tal_arrn(tmpctx, + cursor, max, + fromwire_u32(cursor, max)); + pegin_witness = fromwire_wally_tx_witness_stack(tmpctx, + cursor, max); + ret = wally_tx_elements_input_init_alloc + (txid.shad.sha.u.u8, sizeof(txid.shad.sha.u.u8), + index, sequence, + script, tal_bytelen(script), + ws, + blinding_nonce, tal_bytelen(blinding_nonce), + entropy, tal_bytelen(entropy), + issuance_amount, tal_bytelen(issuance_amount), + inflation_keys, tal_bytelen(inflation_keys), + issuance_amount_rangeproof, + tal_bytelen(issuance_amount_rangeproof), + inflation_keys_rangeproof, + tal_bytelen(inflation_keys_rangeproof), + pegin_witness, + &in); + } else { + ret = wally_tx_input_init_alloc(txid.shad.sha.u.u8, + sizeof(txid.shad.sha.u.u8), + index, sequence, + script, tal_bytelen(script), + ws, &in); + } + if (ret != WALLY_OK) { + fromwire_fail(cursor, max); + return NULL; + } + + tal_add_destructor(in, destroy_wally_tx_input); + return tal_steal(ctx, in); +} + +static struct wally_tx_output *fromwire_wally_tx_output(const tal_t *ctx, + const u8 **cursor, + size_t *max) +{ + struct wally_tx_output *out; + unsigned char *script; + int ret; + + script = fromwire_tal_arrn(tmpctx, + cursor, max, fromwire_u32(cursor, max)); + + if (is_elements(chainparams)) { + u8 *asset, *value, *nonce, *surjectionproof, *rangeproof; + + asset = fromwire_tal_arrn(tmpctx, + cursor, max, + fromwire_u32(cursor, max)); + value = fromwire_tal_arrn(tmpctx, + cursor, max, + fromwire_u32(cursor, max)); + nonce = fromwire_tal_arrn(tmpctx, + cursor, max, + fromwire_u32(cursor, max)); + surjectionproof = fromwire_tal_arrn(tmpctx, + cursor, max, + fromwire_u32(cursor, max)); + rangeproof = fromwire_tal_arrn(tmpctx, + cursor, max, + fromwire_u32(cursor, max)); + ret = wally_tx_elements_output_init_alloc + (script, tal_bytelen(script), + asset, tal_bytelen(asset), + value, tal_bytelen(value), + nonce, tal_bytelen(nonce), + surjectionproof, tal_bytelen(surjectionproof), + rangeproof, tal_bytelen(rangeproof), + &out); + } else { + u64 satoshi; + satoshi = fromwire_u64(cursor, max); + ret = wally_tx_output_init_alloc(satoshi, + script, tal_bytelen(script), + &out); + } + if (ret != WALLY_OK) { + fromwire_fail(cursor, max); + return NULL; + } + + tal_add_destructor(out, destroy_wally_tx_output); + return tal_steal(ctx, out); +} + +static void towire_wally_tx_input(u8 **pptr, const struct wally_tx_input *in) +{ + /* Just like a bitcoin_txid */ + towire_u8_array(pptr, in->txhash, sizeof(in->txhash)); + towire_u32(pptr, in->index); + towire_u32(pptr, in->sequence); + towire_u32(pptr, in->script_len); + towire_u8_array(pptr, in->script, in->script_len); + towire_wally_tx_witness_stack(pptr, in->witness); + + if (is_elements(chainparams)) { + towire_u8_array(pptr, in->blinding_nonce, + sizeof(in->blinding_nonce)); + towire_u8_array(pptr, in->entropy, sizeof(in->entropy)); + towire_u32(pptr, in->issuance_amount_len); + towire_u8_array(pptr, in->issuance_amount, + in->issuance_amount_len); + towire_u32(pptr, in->inflation_keys_len); + towire_u8_array(pptr, in->inflation_keys, + in->inflation_keys_len); + towire_u32(pptr, in->issuance_amount_rangeproof_len); + towire_u8_array(pptr, in->issuance_amount_rangeproof, + in->issuance_amount_rangeproof_len); + towire_u32(pptr, in->inflation_keys_rangeproof_len); + towire_u8_array(pptr, in->inflation_keys_rangeproof, + in->inflation_keys_rangeproof_len); + towire_wally_tx_witness_stack(pptr, in->pegin_witness); + } +} + +static void towire_wally_tx_output(u8 **pptr, const struct wally_tx_output *out) +{ + towire_u32(pptr, out->script_len); + towire_u8_array(pptr, out->script, out->script_len); + + if (is_elements(chainparams)) { + towire_u32(pptr, out->asset_len); + towire_u8_array(pptr, out->asset, out->asset_len); + towire_u32(pptr, out->value_len); + towire_u8_array(pptr, out->value, out->value_len); + towire_u32(pptr, out->nonce_len); + towire_u8_array(pptr, out->nonce, out->nonce_len); + towire_u32(pptr, out->surjectionproof_len); + towire_u8_array(pptr, out->surjectionproof, + out->surjectionproof_len); + towire_u32(pptr, out->rangeproof_len); + towire_u8_array(pptr, out->rangeproof, out->rangeproof_len); + } else { + towire_u64(pptr, out->satoshi); + } +} + +/* Wire marshalling and unmarshalling */ +struct tx_parts *fromwire_tx_parts(const tal_t *ctx, + const u8 **cursor, size_t *max) +{ + struct tx_parts *txp = tal(ctx, struct tx_parts); + u32 num_inputs, num_outputs; + + fromwire_bitcoin_txid(cursor, max, &txp->txid); + num_inputs = fromwire_u32(cursor, max); + txp->inputs = tal_arr(txp, struct wally_tx_input *, num_inputs); + for (size_t i = 0; i < num_inputs; i++) { + if (fromwire_bool(cursor, max)) { + txp->inputs[i] = fromwire_wally_tx_input(txp->inputs, + cursor, max); + } else { + txp->inputs[i] = NULL; + } + } + + num_outputs = fromwire_u32(cursor, max); + txp->outputs = tal_arr(txp, struct wally_tx_output *, num_outputs); + for (size_t i = 0; i < num_outputs; i++) { + if (fromwire_bool(cursor, max)) { + txp->outputs[i] = fromwire_wally_tx_output(txp->outputs, + cursor, max); + } else { + txp->outputs[i] = NULL; + } + } + + if (*cursor == NULL) + return tal_free(txp); + + return txp; +} + +void towire_tx_parts(u8 **pptr, const struct tx_parts *txp) +{ + towire_bitcoin_txid(pptr, &txp->txid); + + towire_u32(pptr, tal_count(txp->inputs)); + for (size_t i = 0; i < tal_count(txp->inputs); i++) { + if (txp->inputs[i]) { + towire_bool(pptr, true); + towire_wally_tx_input(pptr, txp->inputs[i]); + } else { + towire_bool(pptr, false); + } + } + + towire_u32(pptr, tal_count(txp->outputs)); + for (size_t i = 0; i < tal_count(txp->outputs); i++) { + if (txp->outputs[i]) { + towire_bool(pptr, true); + towire_wally_tx_output(pptr, txp->outputs[i]); + } else { + towire_bool(pptr, false); + } + } +} diff --git a/bitcoin/tx_parts.h b/bitcoin/tx_parts.h new file mode 100644 index 000000000..dbbc031d0 --- /dev/null +++ b/bitcoin/tx_parts.h @@ -0,0 +1,29 @@ +/* This represents a specific part of a transaction, without including + * all the metadata (which we might not know, if we didn't make the + * transction ourselves). */ +#ifndef LIGHTNING_BITCOIN_TX_PARTS_H +#define LIGHTNING_BITCOIN_TX_PARTS_H +#include "config.h" +#include +#include + +struct tx_parts { + /* The txid of this transacation */ + struct bitcoin_txid txid; + /* A subset of inputs: NULL means it's not included. */ + struct wally_tx_input **inputs; + /* A subset of outputs: NULL means it's not included. */ + struct wally_tx_output **outputs; +}; + +/* Initialize this from a wally_tx: input/output == -1 for all, + * otherwise the input/output number to include. */ +struct tx_parts *tx_parts_from_wally_tx(const tal_t *ctx, + const struct wally_tx *wtx, + int input, int output); + +/* Wire marshalling and unmarshalling */ +struct tx_parts *fromwire_tx_parts(const tal_t *ctx, + const u8 **cursor, size_t *max); +void towire_tx_parts(u8 **pptr, const struct tx_parts *tx_parts); +#endif /* LIGHTNING_BITCOIN_TX_PARTS_H */ diff --git a/tools/generate-wire.py b/tools/generate-wire.py index 253d75669..f07a1800a 100755 --- a/tools/generate-wire.py +++ b/tools/generate-wire.py @@ -234,6 +234,7 @@ class Type(FieldSet): 'feature_set', 'onionmsg_path', 'route_hop', + 'tx_parts', ] # Some BOLT types are re-typed based on their field name