From 455ec5b0188e1fc76f38c9b7aad7f4e24b421eb4 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Fri, 6 Mar 2026 09:09:55 +1300 Subject: [PATCH] tx: pre-allocate the full number of witnesses when deserializing Prevents quadratic resizing for deserializing non-standard txs with many witnesses. Note in the transaction.c case, analyze_tx has already run and validated that the number of witnesses is sane. --- src/internal.h | 6 ++++++ src/pullpush.c | 3 ++- src/transaction.c | 28 ++++++++++++++++++++-------- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/internal.h b/src/internal.h index bc07f5163..70a93d013 100644 --- a/src/internal.h +++ b/src/internal.h @@ -132,6 +132,12 @@ const struct wally_map_item *map_find_equal_integer(const struct wally_map *lhs, /* Clamp initial witness stack allocation sizing */ #define MAX_WITNESS_ITEMS_ALLOC 100u /* Non-Taproot standardness limit */ +/* Allows allocating a larger witness for e.g deserializing */ +struct wally_tx_witness_stack; +int tx_witness_stack_init_alloc(size_t allocation_len, + size_t max_allocation_len, + struct wally_tx_witness_stack **output); + /* Absolute maximum number of inputs and outputs for BTC. * Liquid numbers are smaller; we use the upper limit */ #define TX_MAX_INPUTS (TX_MAX_INPUTS_ALLOC * 10) diff --git a/src/pullpush.c b/src/pullpush.c index 0244373ba..d21c475e0 100644 --- a/src/pullpush.c +++ b/src/pullpush.c @@ -260,7 +260,8 @@ int pull_witness(const unsigned char **cursor, size_t *max, /* Not enough bytes remaining for num_witnesses empty witnesses */ return WALLY_EINVAL; } - ret = wally_tx_witness_stack_init_alloc(num_witnesses, witness_out); + ret = tx_witness_stack_init_alloc(num_witnesses, num_witnesses, + witness_out); for (i = 0; ret == WALLY_OK && i < num_witnesses; ++i) { const unsigned char *wit; diff --git a/src/transaction.c b/src/transaction.c index 0c3702100..ad3c42234 100644 --- a/src/transaction.c +++ b/src/transaction.c @@ -138,7 +138,8 @@ int wally_tx_witness_stack_clone_alloc(const struct wally_tx_witness_stack *stac if (!stack) return WALLY_EINVAL; - ret = wally_tx_witness_stack_init_alloc(stack->items_allocation_len, output); + ret = tx_witness_stack_init_alloc(stack->items_allocation_len, + stack->items_allocation_len, output); for (i = 0; ret == WALLY_OK && i < stack->num_items; ++i) { ret = wally_tx_witness_stack_set(*output, i, stack->items[i].witness, @@ -151,15 +152,16 @@ int wally_tx_witness_stack_clone_alloc(const struct wally_tx_witness_stack *stac return ret; } -int wally_tx_witness_stack_init_alloc(size_t allocation_len, - struct wally_tx_witness_stack **output) +int tx_witness_stack_init_alloc(size_t allocation_len, + size_t max_allocation_len, + struct wally_tx_witness_stack **output) { OUTPUT_CHECK; OUTPUT_ALLOC(struct wally_tx_witness_stack); if (allocation_len) { - if (allocation_len > MAX_WITNESS_ITEMS_ALLOC) - allocation_len = MAX_WITNESS_ITEMS_ALLOC; + if (allocation_len > max_allocation_len) + allocation_len = max_allocation_len; (*output)->items = wally_calloc(allocation_len * sizeof(struct wally_tx_witness_item)); if (!(*output)->items) { wally_free(*output); @@ -172,6 +174,16 @@ int wally_tx_witness_stack_init_alloc(size_t allocation_len, return WALLY_OK; } +int wally_tx_witness_stack_init_alloc(size_t allocation_len, + struct wally_tx_witness_stack **output) +{ + /* The public interface is limited to pre-allocating enough + * witness items for a standard tx, and will be slow if adding more + */ + return tx_witness_stack_init_alloc(allocation_len, + MAX_WITNESS_ITEMS_ALLOC, output); +} + static int tx_witness_stack_free(struct wally_tx_witness_stack *stack, bool free_parent) { @@ -2371,7 +2383,7 @@ static int witness_stack_from_bytes(const unsigned char *bytes, struct wally_tx_ const unsigned char *p = bytes; p += varint_from_bytes(p, &num_witnesses); if (num_witnesses) { - ret = wally_tx_witness_stack_init_alloc(num_witnesses, witness); + ret = tx_witness_stack_init_alloc(num_witnesses, num_witnesses, witness); if (ret != WALLY_OK) goto cleanup; @@ -2485,8 +2497,8 @@ static int tx_from_bytes(const unsigned char *bytes, size_t bytes_len, p += varint_from_bytes(p, &num_witnesses); if (!num_witnesses) continue; - ret = wally_tx_witness_stack_init_alloc(num_witnesses, - &(*output)->inputs[i].witness); + ret = tx_witness_stack_init_alloc(num_witnesses, num_witnesses, + &(*output)->inputs[i].witness); if (ret != WALLY_OK) goto fail;