Skip to content

Commit faf5ab8

Browse files
committed
tx: implement bip341 signature hashing for elements, reimplement for btc
Taproot signing is extremely inefficent without several levels of caching (e.g. pre-computing tagged hashes, and caching sub-hashes when signing). This is especially true for Elements which adds much more data to the signature hash. Wally's initial hash generation relied on creating the entire preimage and then hashing it in one call. Since segwit and particularly taproot, this approach has not aged well, and is not compatible with efficient tagged hashing. For bip341 Elements signing, this change implements a new approach which hashes the data piecemeal rather than concatinating it first. This uses much less cpu, memory and stack, and eliminates the need to pre-compute the final allocation length of the preimage before generating it, leading to less, more readable code. Additionally, we implement an on-demand cache for preimage data when signing. By passing in a wally_map to hold the cached data, callers can greatly decrease the time required to sign. We replace the existing BTC taproot implementation with this new one, and fix several bugs that were found as a result: - The sub-length calculation for tapscripts was incorrect - The sighash mask for taproot was incorrect - ANYONECANPAY required all input data when only the given inputs data was used.
1 parent 57b513d commit faf5ab8

File tree

14 files changed

+900
-386
lines changed

14 files changed

+900
-386
lines changed

include/wally.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1897,6 +1897,12 @@ inline int tx_get_hash_prevouts(const TX& tx, size_t index, size_t num_inputs, B
18971897
return detail::check_ret(__FUNCTION__, ret);
18981898
}
18991899

1900+
template <class TX, class SCRIPTS, class ASSETS, class VALUES, class TAPLEAF_SCRIPT, class ANNEX, class GENESIS_BLOCKHASH, class CACHE, class BYTES_OUT>
1901+
inline int tx_get_input_signature_hash(const TX& tx, size_t index, const SCRIPTS& scripts, const ASSETS& assets, const VALUES& values, const TAPLEAF_SCRIPT& tapleaf_script, uint32_t key_version, uint32_t codesep_position, const ANNEX& annex, const GENESIS_BLOCKHASH& genesis_blockhash, uint32_t sighash, uint32_t flags, const CACHE& cache, BYTES_OUT& bytes_out) {
1902+
int ret = ::wally_tx_get_input_signature_hash(detail::get_p(tx), index, detail::get_p(scripts), detail::get_p(assets), detail::get_p(values), tapleaf_script.data(), tapleaf_script.size(), key_version, codesep_position, annex.data(), annex.size(), genesis_blockhash.data(), genesis_blockhash.size(), sighash, flags, detail::get_p(cache), bytes_out.data(), bytes_out.size());
1903+
return detail::check_ret(__FUNCTION__, ret);
1904+
}
1905+
19001906
template <class TX>
19011907
inline int tx_get_length(const TX& tx, uint32_t flags, size_t* written) {
19021908
int ret = ::wally_tx_get_length(detail::get_p(tx), flags, written);

include/wally_transaction.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ extern "C" {
5151
#define WALLY_SIGHASH_MASK 0x1f /* Mask for determining ALL/NONE/SINGLE */
5252
#define WALLY_SIGHASH_TR_IN_MASK 0xc0 /* Taproot mask for determining input hash type */
5353

54+
/*** tx-sighash-type Transaction signature hash flags */
55+
#define WALLY_SIGTYPE_PRE_SW 0x1 /* Pre-segwit signature hash */
56+
#define WALLY_SIGTYPE_SW_V0 0x2 /* Segwit v0 signature hash */
57+
#define WALLY_SIGTYPE_SW_V1 0x3 /* Segwit v1 (taproot) signature hash */
58+
#define WALLY_SIGTYPE_MASK 0xf /* Mask for signature hash in signature hash flags */
59+
5460
#define WALLY_TX_ASSET_CT_EMPTY_PREFIX 0x00
5561
#define WALLY_TX_ASSET_CT_EXPLICIT_PREFIX 0x01
5662
#define WALLY_TX_ASSET_CT_VALUE_PREFIX_A 0x08
@@ -862,6 +868,53 @@ WALLY_CORE_API int wally_tx_get_signature_hash(
862868
unsigned char *bytes_out,
863869
size_t len);
864870

871+
/**
872+
* Get the hash of the preimage for signing a transaction input.
873+
*
874+
* :param tx: The transaction to generate the signature hash from.
875+
* :param index: The input index of the input being signed for.
876+
* :param scripts: The scriptpubkeys of each input in the transaction.
877+
* :param assets: The asset commitments of each input in the transaction,
878+
*| or NULL for non-Elements transactions.
879+
* :param values: The satoshi values(BTC) or value commitments(Elements) of
880+
*| each input in the transaction. BTC values must be uint64/host endiannes.
881+
* :param tapleaf_script: The taptree leaf script to sign with.
882+
* :param tapleaf_script_len: Length of ``tapleaf_script`` in bytes.
883+
* :param key_version: Version of pubkey in tapscript. Must be set to 0x00 or 0x01.
884+
* :param codesep_position: BIP342 codeseparator position or ``WALLY_NO_CODESEPARATOR`` if none.
885+
* :param annex: BIP341 annex, or NULL if none.
886+
* :param annex_len: Length of ``annex`` in bytes.
887+
* :param genesis_blockhash: The genesis blockhash of the chain to sign for,
888+
*| or NULL for non-Elements transactions.
889+
* :param genesis_blockhash_len: Length of ``genesis_blockhash`` in bytes. Must be `SHA256_LEN` or 0.
890+
* :param sighash: ``WALLY_SIGHASH_`` flags specifying the sighash type.
891+
* :param cache: An opaque cache for faster generation, or NULL to disable
892+
*| caching. Must be empty on the first call to this function for a given
893+
*| transaction, and only used for signing the inputs of the same ``tx``.
894+
* :param flags: :ref:`tx-sighash-type` controlling signature hash generation.
895+
* :param bytes_out: Destination for the resulting signature hash.
896+
* FIXED_SIZED_OUTPUT(len, bytes_out, SHA256_LEN)
897+
*/
898+
WALLY_CORE_API int wally_tx_get_input_signature_hash(
899+
const struct wally_tx *tx,
900+
size_t index,
901+
const struct wally_map *scripts,
902+
const struct wally_map *assets,
903+
const struct wally_map *values,
904+
const unsigned char *tapleaf_script,
905+
size_t tapleaf_script_len,
906+
uint32_t key_version,
907+
uint32_t codesep_position,
908+
const unsigned char *annex,
909+
size_t annex_len,
910+
const unsigned char *genesis_blockhash,
911+
size_t genesis_blockhash_len,
912+
uint32_t sighash,
913+
uint32_t flags,
914+
struct wally_map *cache,
915+
unsigned char *bytes_out,
916+
size_t len);
917+
865918
/**
866919
* Determine if a transaction is a coinbase transaction.
867920
*

src/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ libwallycore_la_SOURCES = \
167167
sign.c \
168168
symmetric.c \
169169
transaction.c \
170+
tx_io.c \
170171
wif.c \
171172
wordlist.c \
172173
ccan/ccan/base64/base64.c \

src/amalgamation/combined.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
#include "src/sign.c"
7676
#include "src/symmetric.c"
7777
#include "src/transaction.c"
78+
#include "src/tx_io.c"
7879
#include "src/wif.c"
7980
#include "src/wordlist.c"
8081

src/swig_java/swig.i

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,7 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) {
10151015
%returns_array_(wally_tx_get_btc_signature_hash, 8, 9, SHA256_LEN);
10161016
%returns_array_(wally_tx_get_btc_taproot_signature_hash, 14, 15, SHA256_LEN);
10171017
%returns_array_(wally_tx_get_elements_signature_hash, 9, 10, SHA256_LEN);
1018+
%returns_array_(wally_tx_get_input_signature_hash, 17, 18, SHA256_LEN);
10181019
%returns_size_t(wally_tx_get_elements_weight_discount);
10191020
%returns_array_(wally_tx_get_hash_prevouts, 4, 5, SHA256_LEN);
10201021
%returns_array_(wally_tx_get_input_blinding_nonce, 3, 4, SHA256_LEN);

src/swig_python/python_extra.py_in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ tx_get_btc_signature_hash = _wrap_bin(tx_get_btc_signature_hash, SHA256_LEN)
236236
tx_get_btc_taproot_signature_hash = _wrap_bin(tx_get_btc_taproot_signature_hash, SHA256_LEN)
237237
tx_get_hash_prevouts = _wrap_bin(tx_get_hash_prevouts, SHA256_LEN)
238238
tx_get_input_script = _wrap_bin(tx_get_input_script, tx_get_input_script_len)
239+
tx_get_input_signature_hash = _wrap_bin(tx_get_input_signature_hash, SHA256_LEN)
239240
tx_get_input_txhash = _wrap_bin(tx_get_input_txhash, WALLY_TXHASH_LEN)
240241
tx_get_input_witness = _wrap_bin(tx_get_input_witness, tx_get_input_witness_len)
241242
tx_get_output_script = _wrap_bin(tx_get_output_script, tx_get_output_script_len)

src/test/util.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,7 @@ class wally_psbt(Structure):
691691
('wally_tx_get_elements_signature_hash', c_int, [POINTER(wally_tx), c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_uint32, c_void_p, c_size_t]),
692692
('wally_tx_get_elements_weight_discount', c_int, [POINTER(wally_tx), c_uint32, c_size_t_p]),
693693
('wally_tx_get_hash_prevouts', c_int, [POINTER(wally_tx), c_size_t, c_size_t, c_void_p, c_size_t]),
694+
('wally_tx_get_input_signature_hash', c_int, [POINTER(wally_tx), c_size_t, POINTER(wally_map), POINTER(wally_map), POINTER(wally_map), c_void_p, c_size_t, c_uint32, c_uint32, c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_uint32, POINTER(wally_map), c_void_p, c_size_t]),
694695
('wally_tx_get_length', c_int, [POINTER(wally_tx), c_uint32, c_size_t_p]),
695696
('wally_tx_get_signature_hash', c_int, [POINTER(wally_tx), c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_uint64, c_uint32, c_uint32, c_uint32, c_void_p, c_size_t]),
696697
('wally_tx_get_total_output_satoshi', c_int, [POINTER(wally_tx), c_uint64_p]),

0 commit comments

Comments
 (0)