From 2f58b462befeb5206476081713f8736bc10fe0de Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Wed, 12 Feb 2025 13:08:39 +1300 Subject: [PATCH 01/22] tests: verify x-only pubkey conversion in schnorr signing test --- src/test/test_sign.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/test/test_sign.py b/src/test/test_sign.py index aadd38de2..bdd63921b 100755 --- a/src/test/test_sign.py +++ b/src/test/test_sign.py @@ -406,7 +406,7 @@ def add_by_final_vbf(a_hex, b_hex): def test_bip340_sigs(self): num_tests_run = 0 - sig_out, sig_out_len = make_cbuffer('00' * EC_SIGNATURE_LEN) + out, out_len = make_cbuffer('00' * EC_SIGNATURE_LEN) for case in self.get_bip340_sign_cases(): priv_key, priv_key_len = make_cbuffer(case['priv_key']) @@ -416,13 +416,12 @@ def test_bip340_sigs(self): sig, sig_len = make_cbuffer(case['sig']) is_valid = case['is_valid'] - if priv_key_len == EC_PRIV_KEY_LEN: num_tests_run += 1 flags = FLAG_SCHNORR - ret = self.sign(priv_key, msg, flags, sig_out, None, aux_rand) + ret = self.sign(priv_key, msg, flags, out, None, aux_rand) self.assertEqual(ret, WALLY_OK) - self.assertEqual(sig, sig_out) + self.assertEqual(sig, out) ret = wally_ec_sig_from_bytes_len(priv_key, priv_key_len, msg, msg_len, FLAG_SCHNORR) @@ -433,7 +432,10 @@ def test_bip340_sigs(self): msg, msg_len, FLAG_SCHNORR) self.assertEqual(ret, (WALLY_EINVAL, 0)) - # TODO support wally_ec_public_key_from_private_key with bip340 keys to check priv_key -> pub_key + ret = wally_ec_public_key_from_private_key(priv_key, priv_key_len, out, EC_PUBLIC_KEY_LEN) + self.assertEqual(ret, WALLY_OK) + # pub_key is x-only, so ignore the leading byte to compare + self.assertEqual(out[1:EC_PUBLIC_KEY_LEN].hex(), case['pub_key'].lower()) ret = wally_ec_sig_verify(pub_key, pub_key_len, msg, msg_len, FLAG_SCHNORR, sig, sig_len) self.assertEqual(ret == WALLY_OK, is_valid) From 07b6bfa18768ce70f47e3a1ddfbea83a4441b846 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Wed, 12 Feb 2025 13:45:33 +1300 Subject: [PATCH 02/22] tests: add missing bip341 key tweak tests for invalid parameters --- src/test/test_transaction.py | 41 ++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/test/test_transaction.py b/src/test/test_transaction.py index 85338caef..3a427115e 100644 --- a/src/test/test_transaction.py +++ b/src/test/test_transaction.py @@ -405,7 +405,7 @@ def test_hash_prevouts(self): self.assertEqual(WALLY_EINVAL, wally_get_hash_prevouts(*args)) def test_bip341_tweak(self): - """Tests for computing the bip341 signature hash""" + """Tests for computing a public/private bip341 key tweak""" pubkey_cases = [] mc = lambda h: (None, 0) if h is None else make_cbuffer(h) @@ -423,6 +423,26 @@ def test_bip341_tweak(self): self.assertEqual(ret, WALLY_OK) self.assertEqual(expected, h(bytes_out[1:out_len])) + # Invalid args + ((k, k_len), (m, m_len), _) = pubkey_cases[1] + self.assertTrue(m_len != 0) # We require a test case with merkle data + invalid_args = [ + [None, k_len, m, m_len, 0, bytes_out, out_len], # NULL pubkey + [k, 0, m, m_len, 0, bytes_out, out_len], # Empty pubkey + [k, 65, m, m_len, 0, bytes_out, out_len], # Uncompressed pubkey + [k, 15, m, m_len, 0, bytes_out, out_len], # Invalid pubkey length + [k, k_len, None, m_len, 0, bytes_out, out_len], # NULL merkle + [k, k_len, m, 0, 0, bytes_out, out_len], # Empty merkle + [k, k_len, m, 15, 0, bytes_out, out_len], # Invalid merkle length + [k, k_len, m, m_len, 3, bytes_out, out_len], # Unknown flags + [k, k_len, m, m_len, 0, None, out_len], # NULL output + [k, k_len, m, m_len, 0, bytes_out, 0], # Empty output + [k, k_len, m, m_len, 0, bytes_out, 15], # Invalid output length + ] + for args in invalid_args: + ret = wally_ec_public_key_bip341_tweak(*args) + self.assertEqual(ret, WALLY_EINVAL) + privkey_cases = [] mc = lambda h: (None, 0) if h is None else make_cbuffer(h) for i in range(len(JSON['keyPathSpending'][0]['inputSpending'])): @@ -439,7 +459,24 @@ def test_bip341_tweak(self): self.assertEqual(ret, WALLY_OK) self.assertEqual(expected, h(bytes_out[:out_len])) - # FIXME: Add invalid arguments cases for pub/priv keys + # Invalid args + ((k, k_len), (m, m_len), _) = privkey_cases[1] + self.assertTrue(m_len != 0) # We require a test case with merkle data + invalid_args = [ + [None, k_len, m, m_len, 0, bytes_out, out_len], # NULL private key + [k, 0, m, m_len, 0, bytes_out, out_len], # Empty private key + [k, 15, m, m_len, 0, bytes_out, out_len], # Invalid private key length + [k, k_len, None, m_len, 0, bytes_out, out_len], # NULL merkle + [k, k_len, m, 0, 0, bytes_out, out_len], # Empty merkle + [k, k_len, m, 15, 0, bytes_out, out_len], # Invalid merkle length + [k, k_len, m, m_len, 3, bytes_out, out_len], # Unknown flags + [k, k_len, m, m_len, 0, None, out_len], # NULL output + [k, k_len, m, m_len, 0, bytes_out, 0], # Empty output + [k, k_len, m, m_len, 0, bytes_out, 15], # Invalid output length + ] + for args in invalid_args: + ret = wally_ec_private_key_bip341_tweak(*args) + self.assertEqual(ret, WALLY_EINVAL) def test_get_taproot_signature_hash(self): """Tests for computing the taproot signature hash""" From 8ffd7b484fbbad3e55b9e0522c290a26432ebfd8 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Wed, 12 Feb 2025 13:50:30 +1300 Subject: [PATCH 03/22] tests: verify that elements bip341 key tweaks are different from btc --- src/test/test_transaction.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/test_transaction.py b/src/test/test_transaction.py index 3a427115e..73e533107 100644 --- a/src/test/test_transaction.py +++ b/src/test/test_transaction.py @@ -406,6 +406,7 @@ def test_hash_prevouts(self): def test_bip341_tweak(self): """Tests for computing a public/private bip341 key tweak""" + _, is_elements_build = wally_is_elements_build() pubkey_cases = [] mc = lambda h: (None, 0) if h is None else make_cbuffer(h) @@ -422,6 +423,14 @@ def test_bip341_tweak(self): ret = wally_ec_public_key_bip341_tweak(*args) self.assertEqual(ret, WALLY_OK) self.assertEqual(expected, h(bytes_out[1:out_len])) + if is_elements_build: + # Verify that Elements tweaking produces a different tweak. + # We don't have test vectors; the actual values are tested + # by taproot signing tests elsewhere. + args[4] = 0x10 # EC_FLAG_ELEMENTS + ret = wally_ec_public_key_bip341_tweak(*args) + self.assertEqual(ret, WALLY_OK) + self.assertNotEqual(expected, h(bytes_out[1:out_len])) # Invalid args ((k, k_len), (m, m_len), _) = pubkey_cases[1] @@ -458,6 +467,12 @@ def test_bip341_tweak(self): ret = wally_ec_private_key_bip341_tweak(*args) self.assertEqual(ret, WALLY_OK) self.assertEqual(expected, h(bytes_out[:out_len])) + if is_elements_build: + # Verify that Elements tweaking produces a different tweak, as above + args[4] = 0x10 # EC_FLAG_ELEMENTS + ret = wally_ec_private_key_bip341_tweak(*args) + self.assertEqual(ret, WALLY_OK) + self.assertNotEqual(expected, h(bytes_out[:out_len])) # Invalid args ((k, k_len), (m, m_len), _) = privkey_cases[1] From 97fb96c7aa706891b5a7d92654c6bff9a2eddeb3 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sun, 16 Feb 2025 20:14:23 +1300 Subject: [PATCH 04/22] tests: update/fix btc taproot signature hash test --- src/test/test_transaction.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/test_transaction.py b/src/test/test_transaction.py index 73e533107..b59e2fa9d 100644 --- a/src/test/test_transaction.py +++ b/src/test/test_transaction.py @@ -493,8 +493,8 @@ def test_bip341_tweak(self): ret = wally_ec_private_key_bip341_tweak(*args) self.assertEqual(ret, WALLY_EINVAL) - def test_get_taproot_signature_hash(self): - """Tests for computing the taproot signature hash""" + def test_get_btc_taproot_signature_hash(self): + """Tests for computing the BTC taproot signature hash""" keyspend_case = JSON['keyPathSpending'][0] input_spending = keyspend_case['inputSpending'] @@ -506,7 +506,7 @@ def test_get_taproot_signature_hash(self): values = (c_uint64 * num_utxos)() num_values = num_utxos # Bad/Faked data for invalid parameter checks - empty_scripts = pointer(wally_map()) + empty_map = pointer(wally_map()) non_tr_scripts = pointer(wally_map()) wally_map_init_alloc(num_utxos, None, non_tr_scripts) fake_script, fake_script_len = make_cbuffer('00') @@ -556,12 +556,12 @@ def test_get_taproot_signature_hash(self): [(0, None)], # NULL tx [(1, 50)], # Invalid index [(2, None)], # NULL scripts - [(2, empty_scripts)], # Missing script(s) + [(2, empty_map)], # Missing script(s) + [(2, non_tr_scripts)], # Non-taproot script (for the input being signed) [(3, None)], # NULL values [(4, 0)], # Missing values [(4, 1)], # Too few values [(5, fake_script)], # Zero-length tapleaf script - [(5, non_tr_scripts)], # Non-taproot input script [(6, fake_script_len)], # NULL tapleaf script [(7, 2)], # Invalid key version (only 0/1 are allowed) [(9, fake_annex)], # Zero length annex From 57b513dd5407c15dc9c8dc181c4ac446f4be72be Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Wed, 19 Feb 2025 23:15:09 +1300 Subject: [PATCH 05/22] tx: remove uneeded helper, ensure written is always initialized --- src/transaction.c | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/transaction.c b/src/transaction.c index 066569df2..f7d89cb3e 100644 --- a/src/transaction.c +++ b/src/transaction.c @@ -132,21 +132,12 @@ static bool is_valid_elements_tx_input_pegin(const struct wally_tx_input *input) return is_valid_elements_tx_input(input) && (input->features & WALLY_TX_IS_PEGIN); } -static bool is_null_bytes(const unsigned char *bytes, size_t bytes_len) -{ - size_t i; - for (i = 0; i < bytes_len; ++i) - if (bytes[i]) - return false; - return true; -} - static bool is_coinbase_bytes(const unsigned char *bytes, size_t bytes_len, uint32_t index) { - return index == 0xffffffff && is_null_bytes(bytes, bytes_len); + return index == 0xffffffff && mem_is_zero(bytes, bytes_len); } -static bool is_valid_coinbase_input(const struct wally_tx_input *input) +static bool is_coinbase_input(const struct wally_tx_input *input) { return input && is_coinbase_bytes(input->txhash, sizeof(input->txhash), input->index); } @@ -3252,32 +3243,35 @@ int wally_tx_from_hex(const char *hex, uint32_t flags, int wally_tx_is_elements(const struct wally_tx *tx, size_t *written) { + if (written) + *written = 0; if (!tx || !written) return WALLY_EINVAL; *written = is_valid_elements_tx(tx); - return WALLY_OK; } int wally_tx_elements_input_is_pegin(const struct wally_tx_input *input, size_t *written) { + if (written) + *written = 0; if (!input || !written) return WALLY_EINVAL; *written = is_valid_elements_tx_input_pegin(input); - return WALLY_OK; } int wally_tx_is_coinbase(const struct wally_tx *tx, size_t *written) { + if (written) + *written = 0; if (!tx || !written) return WALLY_EINVAL; - *written = tx->num_inputs == 1 && is_valid_coinbase_input(tx->inputs); - + *written = tx->num_inputs == 1 && is_coinbase_input(tx->inputs); return WALLY_OK; } From ab9af261e00181f6c9929e4260a91089b1c1ea28 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Thu, 30 Jan 2025 13:57:05 +1300 Subject: [PATCH 06/22] tx: implement bip341 signature hashing for elements, reimplement for btc Taproot signing is extremely inefficient 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 concatenating 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. --- include/wally.hpp | 6 + include/wally_transaction.h | 63 +++ src/Makefile.am | 1 + src/amalgamation/combined.c | 1 + src/swig_java/swig.i | 1 + src/swig_python/python_extra.py_in | 1 + src/test/util.py | 1 + src/transaction.c | 462 +++--------------- src/tx_io.c | 737 +++++++++++++++++++++++++++++ src/tx_io.h | 16 + src/wasm_package/src/const.js | 4 + src/wasm_package/src/functions.js | 1 + src/wasm_package/src/index.d.ts | 1 + tools/wasm_exports.sh | 1 + 14 files changed, 910 insertions(+), 386 deletions(-) create mode 100644 src/tx_io.c create mode 100644 src/tx_io.h diff --git a/include/wally.hpp b/include/wally.hpp index 0e845b137..4d5bece2f 100644 --- a/include/wally.hpp +++ b/include/wally.hpp @@ -1897,6 +1897,12 @@ inline int tx_get_hash_prevouts(const TX& tx, size_t index, size_t num_inputs, B return detail::check_ret(__FUNCTION__, ret); } +template +inline int tx_get_input_signature_hash(const TX& tx, size_t index, const SCRIPTS& scripts, const ASSETS& assets, const VALUES& values, const SCRIPT& 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) { + 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), script.data(), 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()); + return detail::check_ret(__FUNCTION__, ret); +} + template inline int tx_get_length(const TX& tx, uint32_t flags, size_t* written) { int ret = ::wally_tx_get_length(detail::get_p(tx), flags, written); diff --git a/include/wally_transaction.h b/include/wally_transaction.h index 4b45b77dd..ae238db84 100644 --- a/include/wally_transaction.h +++ b/include/wally_transaction.h @@ -51,6 +51,12 @@ extern "C" { #define WALLY_SIGHASH_MASK 0x1f /* Mask for determining ALL/NONE/SINGLE */ #define WALLY_SIGHASH_TR_IN_MASK 0xc0 /* Taproot mask for determining input hash type */ +/*** tx-sighash-type Transaction signature hash flags */ +#define WALLY_SIGTYPE_PRE_SW 0x1 /* Pre-segwit signature hash */ +#define WALLY_SIGTYPE_SW_V0 0x2 /* Segwit v0 signature hash */ +#define WALLY_SIGTYPE_SW_V1 0x3 /* Segwit v1 (taproot) signature hash */ +#define WALLY_SIGTYPE_MASK 0xf /* Mask for signature hash in signature hash flags */ + #define WALLY_TX_ASSET_CT_EMPTY_PREFIX 0x00 #define WALLY_TX_ASSET_CT_EXPLICIT_PREFIX 0x01 #define WALLY_TX_ASSET_CT_VALUE_PREFIX_A 0x08 @@ -862,6 +868,63 @@ WALLY_CORE_API int wally_tx_get_signature_hash( unsigned char *bytes_out, size_t len); +/** + * Get the hash of the preimage for signing a transaction input. + * + * :param tx: The transaction to generate the signature hash from. + * :param index: The input index of the input being signed for. + * :param scripts: The scriptpubkeys of each input in the transaction, indexed + *| by their 0-based input index. For non-taproot signing, only the + *| scriptpubkey of ``index`` is required. + * :param assets: The asset commitments of each input in the transaction, + *| or NULL for non-Elements transactions. Ignored for non-taproot signing. + * :param values: The satoshi values(BTC) or value commitments(Elements) of + *| each input in the transaction. BTC values must be stored as bytes with + *| uint64/host endiannes. For non-taproot signing, only the value + *| of ``index`` is required. + * :param script: For segwit v0 signing, the scriptcode of the input to sign + *| for. For taproot, the leaf script to sign with if any. Ignored for + *| pre-segwit signing. + * :param script_len: Length of ``script`` in bytes. + * :param key_version: Version of pubkey in tapscript. Must be set + *| to `0x00` or `0x01` for taproot script-path signing. + * :param codesep_position: BIP342 codeseparator position + *| or ``WALLY_NO_CODESEPARATOR`` if none. Only used for taproot signing. + * :param annex: BIP341 annex, or NULL if none. + * :param annex_len: Length of ``annex`` in bytes. Only used for taproot signing. + * :param genesis_blockhash: The genesis blockhash of the chain to sign for, + *| or NULL for non-Elements transactions. Only used for taproot signing. + * :param genesis_blockhash_len: Length of ``genesis_blockhash`` in bytes. Must + *| be `SHA256_LEN` or 0. + * :param sighash: ``WALLY_SIGHASH_`` flags specifying the sighash flags + *| to sign with. + * :param flags: :ref:`tx-sighash-type` controlling signature hash generation. + * :param cache: An opaque cache for faster generation, or NULL to disable + *| caching. Must be empty on the first call to this function for a given + *| transaction, and only used for signing the inputs of the same ``tx``. + * :param bytes_out: Destination for the resulting signature hash. + * FIXED_SIZED_OUTPUT(len, bytes_out, SHA256_LEN) + */ +WALLY_CORE_API int wally_tx_get_input_signature_hash( + const struct wally_tx *tx, + size_t index, + const struct wally_map *scripts, + const struct wally_map *assets, + const struct wally_map *values, + const unsigned char *script, + size_t script_len, + uint32_t key_version, + uint32_t codesep_position, + const unsigned char *annex, + size_t annex_len, + const unsigned char *genesis_blockhash, + size_t genesis_blockhash_len, + uint32_t sighash, + uint32_t flags, + struct wally_map *cache, + unsigned char *bytes_out, + size_t len); + /** * Determine if a transaction is a coinbase transaction. * diff --git a/src/Makefile.am b/src/Makefile.am index 5c0084353..22c545a9c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -167,6 +167,7 @@ libwallycore_la_SOURCES = \ sign.c \ symmetric.c \ transaction.c \ + tx_io.c \ wif.c \ wordlist.c \ ccan/ccan/base64/base64.c \ diff --git a/src/amalgamation/combined.c b/src/amalgamation/combined.c index 206544232..3f1f7659d 100644 --- a/src/amalgamation/combined.c +++ b/src/amalgamation/combined.c @@ -75,6 +75,7 @@ #include "src/sign.c" #include "src/symmetric.c" #include "src/transaction.c" +#include "src/tx_io.c" #include "src/wif.c" #include "src/wordlist.c" diff --git a/src/swig_java/swig.i b/src/swig_java/swig.i index ac1e77e74..92cd29aee 100644 --- a/src/swig_java/swig.i +++ b/src/swig_java/swig.i @@ -1015,6 +1015,7 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %returns_array_(wally_tx_get_btc_signature_hash, 8, 9, SHA256_LEN); %returns_array_(wally_tx_get_btc_taproot_signature_hash, 14, 15, SHA256_LEN); %returns_array_(wally_tx_get_elements_signature_hash, 9, 10, SHA256_LEN); +%returns_array_(wally_tx_get_input_signature_hash, 17, 18, SHA256_LEN); %returns_size_t(wally_tx_get_elements_weight_discount); %returns_array_(wally_tx_get_hash_prevouts, 4, 5, SHA256_LEN); %returns_array_(wally_tx_get_input_blinding_nonce, 3, 4, SHA256_LEN); diff --git a/src/swig_python/python_extra.py_in b/src/swig_python/python_extra.py_in index 08db803cb..bd433b43a 100644 --- a/src/swig_python/python_extra.py_in +++ b/src/swig_python/python_extra.py_in @@ -236,6 +236,7 @@ tx_get_btc_signature_hash = _wrap_bin(tx_get_btc_signature_hash, SHA256_LEN) tx_get_btc_taproot_signature_hash = _wrap_bin(tx_get_btc_taproot_signature_hash, SHA256_LEN) tx_get_hash_prevouts = _wrap_bin(tx_get_hash_prevouts, SHA256_LEN) tx_get_input_script = _wrap_bin(tx_get_input_script, tx_get_input_script_len) +tx_get_input_signature_hash = _wrap_bin(tx_get_input_signature_hash, SHA256_LEN) tx_get_input_txhash = _wrap_bin(tx_get_input_txhash, WALLY_TXHASH_LEN) tx_get_input_witness = _wrap_bin(tx_get_input_witness, tx_get_input_witness_len) tx_get_output_script = _wrap_bin(tx_get_output_script, tx_get_output_script_len) diff --git a/src/test/util.py b/src/test/util.py index 6cdbadaf3..316b05867 100755 --- a/src/test/util.py +++ b/src/test/util.py @@ -691,6 +691,7 @@ class wally_psbt(Structure): ('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]), ('wally_tx_get_elements_weight_discount', c_int, [POINTER(wally_tx), c_uint32, c_size_t_p]), ('wally_tx_get_hash_prevouts', c_int, [POINTER(wally_tx), c_size_t, c_size_t, c_void_p, c_size_t]), + ('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]), ('wally_tx_get_length', c_int, [POINTER(wally_tx), c_uint32, c_size_t_p]), ('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]), ('wally_tx_get_total_output_satoshi', c_int, [POINTER(wally_tx), c_uint64_p]), diff --git a/src/transaction.c b/src/transaction.c index f7d89cb3e..5aeb8ac31 100644 --- a/src/transaction.c +++ b/src/transaction.c @@ -10,6 +10,7 @@ #include #include "pullpush.h" +#include "script.h" #include "script_int.h" #define WALLY_TX_ALL_FLAGS \ @@ -33,17 +34,6 @@ static const unsigned char DUMMY_SIG[EC_SIGNATURE_DER_MAX_LEN + 1]; /* +1 for si #define MAX_INVALID_SATOSHI ((uint64_t) -1) -#define EXT_FLAG_BIP342 0x1 /* Indicates BIP342 tapscript message extension */ - -#ifdef BUILD_ELEMENTS -#define TAPLEAF(is_elements) (is_elements) ? "TapLeaf/elements" : "TapLeaf" -#define TAPSIGHASH(is_elements) (is_elements) ? "TapSighash/elements" : "TapSighash" -#else -#define TAPLEAF(is_elements) "TapLeaf" -#define TAPSIGHASH(is_elements) "TapSighash" -#endif - - /* Extra options when serializing for hashing */ struct tx_serialize_opts { @@ -56,17 +46,6 @@ struct tx_serialize_opts bool bip143; /* Serialize for BIP143 hash */ const unsigned char *value; /* Confidential value of the input we are signing */ size_t value_len; /* length of 'value' in bytes */ - bool bip341; /* Serialize for BIP341 taproot hash */ - unsigned char ext_flag; /* 1 = serialize for BIP342 tapscript hash */ - const uint64_t *prev_satoshis; /* Output amounts for BIP341/342 sha_amounts */ - size_t num_prev_satoshis; /* Number of items in prev_satoshis */ - const struct wally_map *scripts; /* Output scripts for BIP341/342 sha_scriptpubkeys */ - const unsigned char *tapleaf_script; /* Executed BIP342 tapscript */ - size_t tapleaf_script_len; /* Length of executed tapscript */ - uint32_t key_version; /* BIP342 key version */ - uint32_t codesep_position; /* BIP342 codeseparator position */ - const unsigned char *annex; /* Annex data to be put under sighash */ - size_t annex_len; /* Length of sighash data, including 0x50 prefix */ }; static const unsigned char EMPTY_OUTPUT[9] = { @@ -1657,25 +1636,38 @@ int wally_tx_get_witness_count(const struct wally_tx *tx, size_t *written) return WALLY_OK; } -static int get_txout_commitments_size(const struct wally_tx_output *output, - size_t *written) +static size_t txout_get_serialized_len(const struct wally_tx_output *output, + bool is_elements, size_t *witness_size_out) { -#ifdef BUILD_ELEMENTS - size_t c_n; + size_t n; + const bool output_is_elements = output->features & WALLY_TX_IS_ELEMENTS; - if (!(*written = confidential_asset_length_from_bytes(output->asset))) - return WALLY_EINVAL; - if (!(c_n = confidential_value_length_from_bytes(output->value))) - return WALLY_EINVAL; - *written += c_n; - if (!(c_n = confidential_nonce_length_from_bytes(output->nonce))) - return WALLY_EINVAL; - *written += c_n; + if (witness_size_out) + *witness_size_out = 0; + if (is_elements != output_is_elements) + return 0; + if (!is_elements) + n = sizeof(output->satoshi); + else { +#ifndef BUILD_ELEMENTS + return 0; #else - (void)output; - *written = 0; -#endif - return WALLY_OK; + size_t sub_n; + if (!(n = confidential_asset_length_from_bytes(output->asset))) + return 0; + if (!(sub_n = confidential_value_length_from_bytes(output->value))) + return 0; + n += sub_n; + if (!(sub_n = confidential_nonce_length_from_bytes(output->nonce))) + return 0; + n += sub_n; + if (witness_size_out) { + *witness_size_out = varbuff_get_length(output->rangeproof_len) + + varbuff_get_length(output->surjectionproof_len); + } +#endif /* BUILD_ELEMENTS */ + } + return n + varbuff_get_length(output->script_len); } static int get_txin_issuance_size(const struct wally_tx_input *input, @@ -1704,17 +1696,6 @@ static int get_txin_issuance_size(const struct wally_tx_input *input, return WALLY_OK; } -/* Get the (exact) BIP341 serialized tx size as per BIP341/342/118 */ -static size_t get_btc_bip341_size(const struct tx_serialize_opts *opts) -{ - const bool sh_anyonecanpay = opts->tx_sighash & WALLY_SIGHASH_ANYONECANPAY; - const bool sh_none = (opts->tx_sighash & WALLY_SIGHASH_MASK) == WALLY_SIGHASH_NONE; - /* Note the leading 1 is for the sighash epoch byte */ - return 1 + 174 - (sh_anyonecanpay ? 49 : 0) - (sh_none ? SHA256_LEN : 0) + - (opts->annex_len ? SHA256_LEN : 0) + - (opts->ext_flag == EXT_FLAG_BIP342 ? SHA256_LEN + 1 + 4 : 0); -} - /* We compute the size of the witness separately so we can compute vsize * without iterating the transaction twice with different flags. */ @@ -1726,7 +1707,7 @@ static int tx_get_lengths(const struct wally_tx *tx, size_t n, i, j; const unsigned char sighash = opts ? opts->sighash : 0; const bool sh_anyonecanpay = sighash & WALLY_SIGHASH_ANYONECANPAY; - const bool sh_rangeproof = sighash & WALLY_SIGHASH_RANGEPROOF; + const bool sh_rangeproof = is_elements && (sighash & WALLY_SIGHASH_RANGEPROOF); const bool sh_none = (sighash & WALLY_SIGHASH_MASK) == WALLY_SIGHASH_NONE; const bool sh_single = (sighash & WALLY_SIGHASH_MASK) == WALLY_SIGHASH_SINGLE; @@ -1741,12 +1722,6 @@ static int tx_get_lengths(const struct wally_tx *tx, if (flags & WALLY_TX_FLAG_USE_WITNESS) return WALLY_ERROR; /* Segwit tx hashing uses bip143 opts member */ - if (opts->bip341) { - *base_size = get_btc_bip341_size(opts); - *witness_size = 0; - return WALLY_OK; - } - if (opts->bip143) { size_t issuance_size, amount_size = sizeof(uint64_t); @@ -1757,7 +1732,7 @@ static int tx_get_lengths(const struct wally_tx *tx, varbuff_get_length(opts->script_len) + /* script */ sizeof(uint32_t) + /* input sequence */ SHA256_LEN + /* hash outputs */ - ((is_elements && sh_rangeproof) ? SHA256_LEN : 0) + /* rangeproof */ + (sh_rangeproof ? SHA256_LEN : 0) + /* rangeproof */ sizeof(uint32_t) + /* nlocktime */ sizeof(uint32_t); /* tx sighash */ @@ -1827,21 +1802,11 @@ static int tx_get_lengths(const struct wally_tx *tx, if (sh_single && i != opts->index) n += sizeof(EMPTY_OUTPUT); else { - if (is_elements && (output->features & WALLY_TX_IS_ELEMENTS)) { - size_t commit_size; - if (get_txout_commitments_size(output, &commit_size) != WALLY_OK) - return WALLY_EINVAL; - n += commit_size; - } else - n += sizeof(output->satoshi); - n += varbuff_get_length(output->script_len); - -#ifdef BUILD_ELEMENTS - if (is_elements && sh_rangeproof) { - n += varbuff_get_length(output->rangeproof_len) + - varbuff_get_length(output->surjectionproof_len); - } -#endif /* BUILD_ELEMENTS */ + size_t wit_size = 0, *wit_p = sh_rangeproof ? &wit_size : NULL; + size_t txout_len = txout_get_serialized_len(output, is_elements, wit_p); + if (!txout_len) + return WALLY_EINVAL; /* Error getting txout length */ + n += txout_len + wit_size; } } } @@ -2083,11 +2048,11 @@ int wally_tx_get_hash_prevouts(const struct wally_tx *tx, return hash_prevouts(buff_p, inputs_size, bytes_out, len, inputs_size > sizeof(buff)); } -static inline int tx_to_bip143_bytes(const struct wally_tx *tx, - const struct tx_serialize_opts *opts, - uint32_t flags, - unsigned char *bytes_out, size_t len, - size_t *written) +static int tx_to_bip143_bytes(const struct wally_tx *tx, + const struct tx_serialize_opts *opts, + uint32_t flags, + unsigned char *bytes_out, size_t len, + size_t *written) { unsigned char buff[TX_STACK_SIZE / 2], *buff_p = buff; size_t i, inputs_size, outputs_size, rangeproof_size = 0, issuances_size = 0, buff_len = sizeof(buff); @@ -2117,47 +2082,21 @@ static inline int tx_to_bip143_bytes(const struct wally_tx *tx, outputs_size = 0; else if (sh_single) { const struct wally_tx_output *output = tx->outputs + opts->index; - if (!is_elements) - outputs_size = sizeof(uint64_t) + - varbuff_get_length(output->script_len); -#ifdef BUILD_ELEMENTS - else { - if ((ret = get_txout_commitments_size(output, &outputs_size)) != WALLY_OK) - goto error; - outputs_size += varbuff_get_length(output->script_len); - - if (sh_rangeproof) { - rangeproof_size = varbuff_get_length(output->rangeproof_len) + - varbuff_get_length(output->surjectionproof_len); - } - } -#else - else - return WALLY_EINVAL; -#endif + size_t wit_size = 0, *wit_p = sh_rangeproof ? &wit_size : NULL; + outputs_size = txout_get_serialized_len(output, is_elements, wit_p); + if (!outputs_size) + goto error; /* Error getting txout length */ + rangeproof_size += wit_size; } else { outputs_size = 0; for (i = 0; i < tx->num_outputs; ++i) { const struct wally_tx_output *output = tx->outputs + i; - if (!is_elements) - outputs_size += sizeof(uint64_t); -#ifdef BUILD_ELEMENTS - else { - size_t commit_size; - if ((ret = get_txout_commitments_size(output, &commit_size)) != WALLY_OK) - goto error; - outputs_size += commit_size; - - if (sh_rangeproof) { - rangeproof_size += varbuff_get_length(output->rangeproof_len) + - varbuff_get_length(output->surjectionproof_len); - } - } -#else - else - return WALLY_EINVAL; -#endif - outputs_size += varbuff_get_length(output->script_len); + size_t wit_size = 0, *wit_p = sh_rangeproof ? &wit_size : NULL; + size_t n = txout_get_serialized_len(output, is_elements, wit_p); + if (!n) + goto error; /* Error getting txout length */ + outputs_size += n; + rangeproof_size += wit_size; } } @@ -2174,7 +2113,7 @@ static inline int tx_to_bip143_bytes(const struct wally_tx *tx, issuances_size += 1; } } -#endif +#endif /* BUILD_ELEMENTS */ if (inputs_size > buff_len || outputs_size > buff_len || rangeproof_size > buff_len || issuances_size > buff_len) { @@ -2216,7 +2155,7 @@ static inline int tx_to_bip143_bytes(const struct wally_tx *tx, #ifdef BUILD_ELEMENTS if (is_elements) { - /* Issuance */ + /* sha_issuances */ if (sh_anyonecanpay) memset(p, 0, SHA256_LEN); else { @@ -2336,234 +2275,6 @@ static inline int tx_to_bip143_bytes(const struct wally_tx *tx, return ret; } -static bool tr_is_input_hash_type(uint32_t sighash, uint32_t hash_type) -{ - return (sighash & WALLY_SIGHASH_TR_IN_MASK) == hash_type; -} - -static inline int tx_to_bip341_bytes(const struct wally_tx *tx, - const struct tx_serialize_opts *opts, - uint32_t flags, - unsigned char *bytes_out, size_t len, - size_t *written) -{ - unsigned char buff[TX_STACK_SIZE / 2], *buff_p = buff; - size_t i, is_elements, sub_size, buff_len = sizeof(buff); - const bool has_annex = opts->annex != NULL; - const bool sh_none = (opts->sighash & WALLY_SIGHASH_MASK) == WALLY_SIGHASH_NONE; - const bool sh_single = (opts->sighash & WALLY_SIGHASH_MASK) == WALLY_SIGHASH_SINGLE; - const bool sh_anyonecanpay = tr_is_input_hash_type(opts->sighash, WALLY_SIGHASH_ANYONECANPAY); - const bool sh_anyprevout = tr_is_input_hash_type(opts->sighash, WALLY_SIGHASH_ANYPREVOUT); - const bool sh_anyprevout_anyscript = tr_is_input_hash_type(opts->sighash, WALLY_SIGHASH_ANYPREVOUTANYSCRIPT); - unsigned char *p = bytes_out, *tmp_p; - int ret = WALLY_OK; - - /* Note we assume tx_to_bytes has already validated all inputs */ - (void)flags; - (void)len; - (void)is_elements; - -#ifdef BUILD_ELEMENTS - if ((ret = wally_tx_is_elements(tx, &is_elements)) != WALLY_OK || is_elements) - return WALLY_EINVAL; -#endif - - if (opts->key_version > 0x1) - return WALLY_EINVAL; /* Non-BIP341/342/118 key version */ - - switch (opts->sighash) { - case WALLY_SIGHASH_DEFAULT: - case WALLY_SIGHASH_ALL: - case WALLY_SIGHASH_NONE: - case WALLY_SIGHASH_SINGLE: - case WALLY_SIGHASH_ALL | WALLY_SIGHASH_ANYONECANPAY: - case WALLY_SIGHASH_NONE | WALLY_SIGHASH_ANYONECANPAY: - case WALLY_SIGHASH_SINGLE | WALLY_SIGHASH_ANYONECANPAY: - break; /* Always valid */ - case WALLY_SIGHASH_ALL | WALLY_SIGHASH_ANYPREVOUT: - case WALLY_SIGHASH_NONE | WALLY_SIGHASH_ANYPREVOUT: - case WALLY_SIGHASH_SINGLE | WALLY_SIGHASH_ANYPREVOUT: - case WALLY_SIGHASH_ALL | WALLY_SIGHASH_ANYPREVOUT | WALLY_SIGHASH_ANYONECANPAY: - case WALLY_SIGHASH_NONE | WALLY_SIGHASH_ANYPREVOUT | WALLY_SIGHASH_ANYONECANPAY: - case WALLY_SIGHASH_SINGLE | WALLY_SIGHASH_ANYPREVOUT | WALLY_SIGHASH_ANYONECANPAY: - if (opts->key_version != 1) - return WALLY_EINVAL; /* Only valid for key_version 1 */ - break; - default: - return WALLY_EINVAL; /* Unknown sighash type */ - } - - if (sh_single && opts->index >= tx->num_outputs) - return WALLY_EINVAL; /* no corresponding output for SIGHASH_SINGLE */ - - p += uint8_to_le_bytes(0, p); /* sighash epoch: 0x00 */ - p += uint8_to_le_bytes(opts->sighash, p); /* hash_type (1) */ - p += uint32_to_le_bytes(tx->version, p); /* nVersion (4) */ - p += uint32_to_le_bytes(tx->locktime, p); /* nLockTime (4) */ - - /* Compute the sizes needed for prevouts/scripts/output sub-hashes */ - sub_size = tx->num_inputs * (WALLY_TXHASH_LEN + sizeof(uint32_t)); - buff_len = sub_size > buff_len ? sub_size : buff_len; - - sub_size = 0; - for (i = 0; i < tx->num_inputs; ++i) { - const struct wally_map_item *script = wally_map_get_integer(opts->scripts, i); - if (!script || !script->value || !script->value_len) { - ret = WALLY_EINVAL; /* Missing script */ - goto error; - } - sub_size += varbuff_get_length(script->value_len); - } - buff_len = sub_size > buff_len ? sub_size : buff_len; - - if (sh_none) - sub_size = 0; - else if (sh_single) { - sub_size = sizeof(uint64_t); - sub_size += varbuff_get_length(tx->outputs[opts->index].script_len); - } else { - sub_size = 0; - for (i = 0; i < tx->num_outputs; ++i) { - sub_size += sizeof(uint64_t); - sub_size += varbuff_get_length(tx->outputs[i].script_len); - } - } - buff_len = sub_size > buff_len ? sub_size : buff_len; - - buff_len = opts->tapleaf_script_len > buff_len ? opts->tapleaf_script_len : buff_len; - - /* Allocate a larger buffer if needed to hold our sub-hashes data */ - if (buff_len > sizeof(buff) && !(buff_p = wally_malloc(buff_len))) - return WALLY_ENOMEM; - - if (!sh_anyonecanpay && !sh_anyprevout) { - /* sha_prevouts */ - tmp_p = buff_p; - for (i = 0; i < tx->num_inputs; ++i) { - memcpy(tmp_p, tx->inputs[i].txhash, WALLY_TXHASH_LEN); - uint32_to_le_bytes(tx->inputs[i].index, tmp_p + WALLY_TXHASH_LEN); - tmp_p += WALLY_TXHASH_LEN + sizeof(uint32_t); - } - if ((ret = wally_sha256(buff_p, tmp_p - buff_p, p, SHA256_LEN)) != WALLY_OK) - goto error; - p += SHA256_LEN; - - /* sha_amounts */ - tmp_p = buff_p; - for (i = 0; i < tx->num_inputs; ++i) - tmp_p += uint64_to_le_bytes(opts->prev_satoshis[i], tmp_p); - if ((ret = wally_sha256(buff_p, tmp_p - buff_p, p, SHA256_LEN)) != WALLY_OK) - goto error; - p += SHA256_LEN; - - /* sha_scriptpubkeys */ - tmp_p = buff_p; - for (i = 0; i < tx->num_inputs; ++i) { - const struct wally_map_item *script = wally_map_get_integer(opts->scripts, i); - tmp_p += varbuff_to_bytes(script->value, script->value_len, tmp_p); - } - if ((ret = wally_sha256(buff_p, tmp_p - buff_p, p, SHA256_LEN)) != WALLY_OK) - goto error; - p += SHA256_LEN; - - /* sha_sequences */ - tmp_p = buff_p; - for (i = 0; i < tx->num_inputs; ++i) - tmp_p += uint32_to_le_bytes(tx->inputs[i].sequence, tmp_p); - - if ((ret = wally_sha256(buff_p, tmp_p - buff_p, p, SHA256_LEN)) != WALLY_OK) - goto error; - p += SHA256_LEN; - } - - if (!sh_none && !sh_single) { - /* sha_outputs */ - tmp_p = buff_p; - for (i = 0; i < tx->num_outputs; ++i) { - tmp_p += uint64_to_le_bytes(tx->outputs[i].satoshi, tmp_p); - tmp_p += varbuff_to_bytes(tx->outputs[i].script, - tx->outputs[i].script_len, tmp_p); - } - if ((ret = wally_sha256(buff_p, tmp_p - buff_p, p, SHA256_LEN)) != WALLY_OK) - goto error; - p += SHA256_LEN; - } - - /* Data about this input: */ - p += uint8_to_le_bytes(opts->ext_flag * 2 + has_annex, p); /* spend_type (1) */ - - if (sh_anyonecanpay || sh_anyprevout) { - const struct wally_map_item *script = wally_map_get_integer(opts->scripts, opts->index); - if (!script || !script->value || script->value_len != WALLY_SCRIPTPUBKEY_P2TR_LEN || - script->value[0] != OP_1 || script->value[1] != 32u) { - ret = WALLY_EINVAL; /* Not a v1 segwit taproot script */ - goto error; - } - if (sh_anyonecanpay) { - /* outpoint (36) */ - memcpy(p, tx->inputs[opts->index].txhash, WALLY_TXHASH_LEN); - p += WALLY_TXHASH_LEN; - p += uint32_to_le_bytes(tx->inputs[opts->index].index, p); - } - p += uint64_to_le_bytes(opts->satoshi, p); /* amount (8) */ - p += varbuff_to_bytes(script->value, script->value_len, p); /* scriptPubKey (35) */ - p += uint32_to_le_bytes(tx->inputs[opts->index].sequence, p); /* nSequence (4) */ - } else if (sh_anyprevout_anyscript) { - p += uint32_to_le_bytes(tx->inputs[opts->index].sequence, p); /* nSequence (4) */ - } else { - p += uint32_to_le_bytes(opts->index, p); /* input_index (4) */ - } - - if (has_annex) { - /* sha_annex */ - tmp_p = buff_p + varbuff_to_bytes(opts->annex, opts->annex_len, buff_p); - if ((ret = wally_sha256(buff_p, tmp_p - buff_p, p, SHA256_LEN)) != WALLY_OK) - goto error; - p += SHA256_LEN; - } - - /* Data about this output: */ - if (sh_single) { - /* sha_single_output */ - const struct wally_tx_output *txout = &tx->outputs[opts->index]; - tmp_p = buff_p + uint64_to_le_bytes(txout->satoshi, buff_p); - tmp_p += varbuff_to_bytes(txout->script, txout->script_len, tmp_p); - if ((ret = wally_sha256(buff_p, tmp_p - buff_p, p, SHA256_LEN)) != WALLY_OK) - goto error; - p += SHA256_LEN; - } - - /* Tapscript Extensions: */ - if (opts->ext_flag == EXT_FLAG_BIP342) { - if (!sh_anyprevout_anyscript) { - /* tapleaf_hash (32): hash_TapLeaf(v || compact_size(size of s) || s) */ - buff_p[0] = 0xC0; /* leaf_version */ - tmp_p = buff_p + 1; - tmp_p += varbuff_to_bytes(opts->tapleaf_script, opts->tapleaf_script_len, tmp_p); - ret = wally_bip340_tagged_hash(buff_p, tmp_p - buff_p, - TAPLEAF(is_elements), p, SHA256_LEN); - if (ret != WALLY_OK) - goto error; - p += SHA256_LEN; - } - - p += uint8_to_le_bytes(opts->key_version & 0xff, p); /* key_version (1) */ - p += uint32_to_le_bytes(opts->codesep_position, p); /* codesep_pos (4) */ - } else if (opts->ext_flag) { - ret = WALLY_ERROR; /* Unknown extension flag */ - goto error; - } - - *written = p - bytes_out; - -error: - if (buff_p != buff) - clear_and_free(buff_p, buff_len); - else - wally_clear(buff, sizeof(buff)); - return ret; -} - static int tx_to_bytes(const struct wally_tx *tx, const struct tx_serialize_opts *opts, uint32_t flags, @@ -2616,9 +2327,6 @@ static int tx_to_bytes(const struct wally_tx *tx, if (opts && opts->bip143) return tx_to_bip143_bytes(tx, opts, flags, bytes_out, len, written); - if (opts && opts->bip341) - return tx_to_bip341_bytes(tx, opts, flags, bytes_out, len, written); - if (flags & WALLY_TX_FLAG_USE_WITNESS) { if (wally_tx_get_witness_count(tx, &witness_count) != WALLY_OK) return WALLY_EINVAL; @@ -3285,16 +2993,14 @@ static int tx_get_signature_hash(const struct wally_tx *tx, uint32_t sighash, uint32_t tx_sighash, uint32_t flags, unsigned char *bytes_out, size_t len) { - unsigned char buff[TX_STACK_SIZE], *buff_p = buff, ext_flag = 0; + unsigned char buff[TX_STACK_SIZE], *buff_p = buff; size_t n, n2; size_t is_elements = 0; const bool is_bip143 = (flags & WALLY_TX_FLAG_USE_WITNESS) ? true : false; - const bool is_bip341 = false; int ret; const struct tx_serialize_opts opts = { sighash, tx_sighash, index, script, script_len, satoshi, - is_bip143, value, value_len, is_bip341, ext_flag, NULL, 0, NULL, - NULL, 0, 0, 0, NULL, 0 + is_bip143, value, value_len }; if (!is_valid_tx(tx) || BYTES_INVALID(script, script_len) || @@ -3376,47 +3082,30 @@ int wally_tx_get_btc_taproot_signature_hash( uint32_t sighash, uint32_t flags, unsigned char *bytes_out, size_t len) { - unsigned char buff[256]; /* Largest possible hash preimage is 244 bytes */ - const bool is_bip143 = false, is_bip341 = true; - const struct tx_serialize_opts opts = { - sighash, sighash, index, NULL, 0, - values && index < num_values ? values[index] : 0, - is_bip143, NULL, 0, is_bip341, tapleaf_script ? EXT_FLAG_BIP342 : 0, - values, num_values, scripts, tapleaf_script, tapleaf_script_len, - key_version, codesep_position, annex, annex_len - }; - size_t is_elements, n, n2; + struct wally_map values_map; int ret; - (void)is_elements; - - if (!values || !num_values || index >= num_values || - BYTES_INVALID(tapleaf_script, tapleaf_script_len) || - BYTES_INVALID(annex, annex_len) || (annex && *annex != 0x50) || - flags || !bytes_out || len != SHA256_LEN) - return WALLY_EINVAL; - -#ifdef BUILD_ELEMENTS - if (wally_tx_is_elements(tx, &is_elements) != WALLY_OK || is_elements) + if (flags) return WALLY_EINVAL; -#endif - - if ((ret = tx_get_length(tx, &opts, 0, &n, false)) != WALLY_OK) + ret = wally_map_init(num_values, NULL, &values_map); + if (ret != WALLY_OK) return ret; - - if (n > sizeof(buff)) - return WALLY_ERROR; /* Will never happen unless buff size is reduced */ - - ret = tx_to_bytes(tx, &opts, 0, buff, sizeof(buff), &n2, false); - if (ret == WALLY_OK) { - if (n != n2) - ret = WALLY_ERROR; /* tx_get_length/tx_to_bytes mismatch, should not happen! */ - else - ret = wally_bip340_tagged_hash(buff, n, TAPSIGHASH(false), bytes_out, len); - } - wally_clear(buff, n); + /* Convert values array into a non-owning map of input values */ + for (size_t i = 0; i < num_values; ++i) { + values_map.items[i].key_len = i; + values_map.items[i].value = (unsigned char*)(values + i); + values_map.items[i].value_len = sizeof(values[0]); + } + values_map.num_items = num_values; + ret = wally_tx_get_input_signature_hash(tx, index, scripts, NULL, &values_map, + tapleaf_script, tapleaf_script_len, + key_version, codesep_position, + annex, annex_len, NULL, 0, sighash, + WALLY_SIGTYPE_SW_V1, NULL, bytes_out, len); + wally_free(values_map.items); /* No need to clear the value pointers */ return ret; } +#ifndef WALLY_ABI_NO_ELEMENTS int wally_tx_get_elements_signature_hash(const struct wally_tx *tx, size_t index, const unsigned char *script, size_t script_len, @@ -3536,6 +3225,7 @@ int wally_tx_elements_issuance_calculate_reissuance_token(const unsigned char *e buff, sizeof(buff), bytes_out, len); } +#endif /* WALLY_ABI_NO_ELEMENTS */ int wally_tx_get_total_output_satoshi(const struct wally_tx *tx, uint64_t *value_out) { diff --git a/src/tx_io.c b/src/tx_io.c new file mode 100644 index 000000000..3e2404cd1 --- /dev/null +++ b/src/tx_io.c @@ -0,0 +1,737 @@ +#include "internal.h" +#include +#include "pullpush.h" +#include "script.h" +#include "script_int.h" +#include "tx_io.h" +#include + +#define SIGTYPE_ALL (WALLY_SIGTYPE_PRE_SW | WALLY_SIGTYPE_SW_V0 | WALLY_SIGTYPE_SW_V1) + +/* Cache keys for data that is constant while signing a given tx. + * We also cache other data keyed by their binary value directly. + */ +#define TXIO_UNCACHED 0 /* Signals that data should not be cached */ +#define TXIO_SHA_TAPSIGHASH_CTX 1 /* Initial sha256_ctx for taproot bip340 hashing */ +#define TXIO_SHA_OUTPOINT_FLAGS 2 /* Taproot cached data ... */ +#define TXIO_SHA_PREVOUTS 3 +#define TXIO_SHA_AMOUNTS 4 +#define TXIO_SHA_ASSET_AMOUNTS 5 +#define TXIO_SHA_SCRIPTPUBKEYS 6 +#define TXIO_SHA_SEQUENCES 7 +#define TXIO_SHA_ISSUANCES 8 +#define TXIO_SHA_ISSUANCE_RANGEPROOFS 9 +#define TXIO_SHA_OUTPUTS 10 +#define TXIO_SHA_OUTPUT_WITNESSES 11 /* ... end of taproot cached data */ + +/* SHA256(TapSighash) */ +static const unsigned char TAPSIGHASH_SHA256[SHA256_LEN] = { + 0xf4, 0x0a, 0x48, 0xdf, 0x4b, 0x2a, 0x70, 0xc8, 0xb4, 0x92, 0x4b, 0xf2, 0x65, 0x46, 0x61, 0xed, + 0x3d, 0x95, 0xfd, 0x66, 0xa3, 0x13, 0xeb, 0x87, 0x23, 0x75, 0x97, 0xc6, 0x28, 0xe4, 0xa0, 0x31 +}; +/* SHA256(TapLeaf) */ +static const unsigned char TAPLEAF_SHA256[SHA256_LEN] = { + 0xae, 0xea, 0x8f, 0xdc, 0x42, 0x08, 0x98, 0x31, 0x05, 0x73, 0x4b, 0x58, 0x08, 0x1d, 0x1e, 0x26, + 0x38, 0xd3, 0x5f, 0x1c, 0xb5, 0x40, 0x08, 0xd4, 0xd3, 0x57, 0xca, 0x03, 0xbe, 0x78, 0xe9, 0xee +}; + +#ifdef BUILD_ELEMENTS +/* SHA256(TapSighash/elements) */ +static const unsigned char TAPSIGHASH_SHA256_ELEMENTS[SHA256_LEN] = { + 0xe3, 0x43, 0x16, 0x49, 0xdc, 0xb6, 0x48, 0x53, 0x3d, 0x8e, 0x36, 0x4a, 0xff, 0xd6, 0x06, 0xcb, + 0x7d, 0xe9, 0x78, 0xd6, 0x0c, 0xd0, 0x12, 0x2d, 0x1e, 0x55, 0x17, 0x48, 0x75, 0xca, 0xba, 0x08 +}; +/* SHA256(TapLeaf/elements) */ +static const unsigned char TAPLEAF_SHA256_ELEMENTS[SHA256_LEN] = { + 0x69, 0xff, 0xb5, 0x5a, 0xb8, 0xc8, 0x1c, 0x21, 0xf5, 0x8b, 0x2a, 0xdc, 0xb0, 0x83, 0x5a, 0x08, + 0x60, 0x8a, 0xf5, 0x9d, 0x04, 0x2f, 0x03, 0x37, 0x64, 0x33, 0x9c, 0xd8, 0xe6, 0xba, 0x33, 0xe7 +}; +#define TAPSIGHASH_SHA256(is_elements) (is_elements ? TAPSIGHASH_SHA256_ELEMENTS : TAPSIGHASH_SHA256) +#define TAPLEAF_SHA256(is_elements) (is_elements ? TAPLEAF_SHA256_ELEMENTS : TAPLEAF_SHA256) +#else +#define TAPSIGHASH_SHA256(is_elements) TAPSIGHASH_SHA256 +#define TAPLEAF_SHA256(is_elements) TAPLEAF_SHA256 +#endif /* BUILD_ELEMENTS */ + +static bool script_len_ok(size_t len) { return len != 0; } +static bool asset_len_ok(size_t len) { return len == WALLY_TX_ASSET_CT_ASSET_LEN; } +static bool satoshi_len_ok(size_t len) { return len == sizeof(uint64_t); } +static bool value_len_ok(size_t len) +{ + return len == WALLY_TX_ASSET_CT_VALUE_LEN || + len == WALLY_TX_ASSET_CT_VALUE_UNBLIND_LEN; +} + +/* Ensure 'm' is integer-indexed with num_items valid items */ +static bool map_has_all(const struct wally_map *m, size_t num_items, + bool (*len_fn)(size_t)) +{ + if (!m || m->num_items != num_items) + return false; + for (size_t i = 0; i < num_items; ++i) { + const struct wally_map_item *item = m->items + i; + if (item->key || item->key_len != i || + !item->value || !len_fn(item->value_len)) + return false; + } + return true; +} + +/* Ensure 'm' is integer-indexed containing a valid item for 'index' */ +static bool map_has_one(const struct wally_map *m, size_t index, + bool (*len_fn)(size_t)) +{ + if (!m || !m->num_items) + return false; + for (size_t i = 0; i < m->num_items; ++i) { + const struct wally_map_item *item = m->items + i; + if (item->key || !item->value || !len_fn(item->value_len)) + return false; + if (index == item->key_len) + return true; + } + return false; +} + +static inline void hash_u8(struct sha256_ctx *ctx, uint8_t v) +{ + sha256_u8(ctx, v); +} + +static inline void hash_le32(struct sha256_ctx *ctx, uint32_t v) +{ + sha256_le32(ctx, v); +} + +static inline void hash_le64(struct sha256_ctx *ctx, uint64_t v) +{ + sha256_le64(ctx, v); +} + +static void hash_map_le64(struct sha256_ctx *ctx, + const struct wally_map *m, size_t index) +{ + const struct wally_map_item *item = wally_map_get_integer(m, index); + uint64_t v; + memcpy(&v, item->value, item->value_len); + hash_le64(ctx, v); +} + +static inline void hash_bytes(struct sha256_ctx *ctx, + const unsigned char *bytes, size_t bytes_len) +{ + sha256_update(ctx, bytes, bytes_len); +} + +static void hash_varbuff(struct sha256_ctx *ctx, + const unsigned char *bytes, size_t bytes_len) +{ + unsigned char varbuff_len[9]; + size_t n = varint_to_bytes(bytes_len, varbuff_len); + sha256_update(ctx, varbuff_len, n); + sha256_update(ctx, bytes, bytes_len); +} + +static void hash_map_varbuff(struct sha256_ctx *ctx, + const struct wally_map *m, size_t index) +{ + const struct wally_map_item *item = wally_map_get_integer(m, index); + hash_varbuff(ctx, item->value, item->value_len); +} + +static bool txio_hash_cached_item(cursor_io *io, uint32_t key) +{ + const struct wally_map_item *item; + item = io->cache ? wally_map_get_integer(io->cache, key) : NULL; + if (!item) + return false; + hash_bytes(&io->ctx, item->value, item->value_len); + return true; +} + +static void txio_hash_sha256_ctx(cursor_io *io, struct sha256_ctx *ctx, int key) +{ + struct sha256 hash; + sha256_done(ctx, &hash); + hash_bytes(&io->ctx, hash.u.u8, sizeof(hash)); + if (io->cache && key != TXIO_UNCACHED) + wally_map_add_integer(io->cache, key, hash.u.u8, sizeof(hash)); +} + +static void txio_done(cursor_io *io) +{ + struct sha256 hash; + sha256_done(&io->ctx, &hash); + push_bytes(&io->cursor, &io->max, hash.u.u8, sizeof(hash)); +} + +/* Initialize a sha256 context for bip340 tagged hashing. + * 'hash' must be SHA256(tag), e.g. 'TapSighash', 'TapLeaf' etc. + */ +static void tagged_hash_init(struct sha256_ctx *ctx, + const unsigned char *hash, size_t hash_len) +{ + sha256_init(ctx); + hash_bytes(ctx, hash, hash_len); + hash_bytes(ctx, hash, hash_len); +} + +#ifdef BUILD_ELEMENTS +static void hash_commmitment(struct sha256_ctx *ctx, + const unsigned char *bytes, size_t bytes_len) +{ + if (!bytes_len) + hash_u8(ctx, 0); + else + hash_bytes(ctx, bytes, bytes_len); +} + +static void hash_map_commmitment(struct sha256_ctx *ctx, + const struct wally_map *m, size_t index) +{ + const struct wally_map_item *item = wally_map_get_integer(m, index); + hash_commmitment(ctx, item->value, item->value_len); +} + +static void hash_issuance_rangeproofs(struct sha256_ctx *ctx, + const struct wally_tx_input *txin) +{ + if (!(txin->features & WALLY_TX_IS_ISSUANCE)) { + hash_u8(ctx, 0); + hash_u8(ctx, 0); + return; + } + hash_varbuff(ctx, txin->issuance_amount_rangeproof, txin->issuance_amount_rangeproof_len); + hash_varbuff(ctx, txin->inflation_keys_rangeproof, txin->inflation_keys_rangeproof_len); +} + +static void hash_output_elements(struct sha256_ctx *ctx, + const struct wally_tx_output *txout) +{ + hash_commmitment(ctx, txout->asset, txout->asset_len); + hash_commmitment(ctx, txout->value, txout->value_len); + hash_commmitment(ctx, txout->nonce, txout->nonce_len); + hash_varbuff(ctx, txout->script, txout->script_len); +} + +static void hash_output_witness(struct sha256_ctx *ctx, + const struct wally_tx_output *txout) +{ + hash_varbuff(ctx, txout->surjectionproof, txout->surjectionproof_len); + hash_varbuff(ctx, txout->rangeproof, txout->rangeproof_len); +} + +static void txio_hash_sha_outpoint_flags(cursor_io *io, const struct wally_tx *tx) +{ + if (!txio_hash_cached_item(io, TXIO_SHA_OUTPOINT_FLAGS)) { + struct sha256_ctx ctx; + sha256_init(&ctx); + for (size_t i = 0; i < tx->num_inputs; ++i) { + const struct wally_tx_input *txin = tx->inputs + i; + uint8_t v = 0; + if (txin->features & WALLY_TX_IS_ISSUANCE) + v = WALLY_TX_ISSUANCE_FLAG >> 24; + else if (txin->features & WALLY_TX_IS_PEGIN) + v = WALLY_TX_PEGIN_FLAG >> 24; + hash_u8(&ctx, v); + } + txio_hash_sha256_ctx(io, &ctx, TXIO_SHA_OUTPOINT_FLAGS); + } +} + +static void txio_hash_sha_asset_amounts(cursor_io *io, + const struct wally_map *values, + const struct wally_map *assets) +{ + if (!txio_hash_cached_item(io, TXIO_SHA_ASSET_AMOUNTS)) { + struct sha256_ctx ctx; + sha256_init(&ctx); + for (size_t i = 0; i < values->num_items; ++i) { + hash_commmitment(&ctx, assets->items[i].value, assets->items[i].value_len); + hash_commmitment(&ctx, values->items[i].value, values->items[i].value_len); + } + txio_hash_sha256_ctx(io, &ctx, TXIO_SHA_ASSET_AMOUNTS); + } +} + +static void txio_hash_sha_issuances(cursor_io *io, const struct wally_tx *tx) +{ + if (!txio_hash_cached_item(io, TXIO_SHA_ISSUANCES)) { + struct sha256_ctx ctx; + sha256_init(&ctx); + for (size_t i = 0; i < tx->num_inputs; ++i) { + const struct wally_tx_input *txin = tx->inputs + i; + if (!(txin->features & WALLY_TX_IS_ISSUANCE)) { + hash_u8(&ctx, 0); + continue; + } + hash_bytes(&ctx, txin->blinding_nonce, sizeof(txin->blinding_nonce)); + hash_bytes(&ctx, txin->entropy, sizeof(txin->entropy)); + hash_commmitment(&ctx, txin->issuance_amount, txin->issuance_amount_len); + hash_commmitment(&ctx, txin->inflation_keys, txin->inflation_keys_len); + } + txio_hash_sha256_ctx(io, &ctx, TXIO_SHA_ISSUANCES); + } +} + +static void txio_hash_sha_issuance_rangeproofs(cursor_io *io, const struct wally_tx *tx) +{ + if (!txio_hash_cached_item(io, TXIO_SHA_ISSUANCE_RANGEPROOFS)) { + struct sha256_ctx ctx; + sha256_init(&ctx); + for (size_t i = 0; i < tx->num_inputs; ++i) + hash_issuance_rangeproofs(&ctx, tx->inputs + i); + txio_hash_sha256_ctx(io, &ctx, TXIO_SHA_ISSUANCE_RANGEPROOFS); + } +} + +static void txio_hash_sha_outputs_elements(cursor_io *io, const struct wally_tx *tx) +{ + if (!txio_hash_cached_item(io, TXIO_SHA_OUTPUTS)) { + struct sha256_ctx ctx; + sha256_init(&ctx); + for (size_t i = 0; i < tx->num_outputs; ++i) + hash_output_elements(&ctx, tx->outputs + i); + txio_hash_sha256_ctx(io, &ctx, TXIO_SHA_OUTPUTS); + } +} + +static void txio_hash_sha_output_witnesses(cursor_io *io, const struct wally_tx *tx) +{ + if (!txio_hash_cached_item(io, TXIO_SHA_OUTPUT_WITNESSES)) { + struct sha256_ctx ctx; + sha256_init(&ctx); + for (size_t i = 0; i < tx->num_outputs; ++i) + hash_output_witness(&ctx, tx->outputs + i); + txio_hash_sha256_ctx(io, &ctx, TXIO_SHA_OUTPUT_WITNESSES); + } +} + +static void txio_hash_outpoint_flag(cursor_io *io, const struct wally_tx_input *txin) +{ + unsigned char outpoint_flag = 0; + if (txin->features & WALLY_TX_IS_ISSUANCE) + outpoint_flag |= WALLY_TX_ISSUANCE_FLAG >> 24; + if (txin->features & WALLY_TX_IS_PEGIN) + outpoint_flag |= WALLY_TX_PEGIN_FLAG >> 24; + hash_u8(&io->ctx, outpoint_flag); +} + +static void txio_hash_input_elements(cursor_io *io, + const struct wally_tx *tx, size_t index, + const struct wally_map *scripts, + const struct wally_map *assets, + const struct wally_map *values) +{ + const struct wally_tx_input *txin = tx->inputs + index; + + hash_map_commmitment(&io->ctx, assets, index); + hash_map_commmitment(&io->ctx, values, index); + hash_map_varbuff(&io->ctx, scripts, index); + hash_le32(&io->ctx, txin->sequence); + + if (!(txin->features & WALLY_TX_IS_ISSUANCE)) + hash_u8(&io->ctx, 0); + else { + /* asset_issuance */ + hash_bytes(&io->ctx, txin->blinding_nonce, sizeof(txin->blinding_nonce)); + hash_bytes(&io->ctx, txin->entropy, sizeof(txin->entropy)); + hash_commmitment(&io->ctx, txin->issuance_amount, txin->issuance_amount_len); + hash_commmitment(&io->ctx, txin->inflation_keys, txin->inflation_keys_len); + { + /* sha_single_issuance_rangeproofs */ + struct sha256_ctx ctx; + sha256_init(&ctx); + hash_issuance_rangeproofs(&ctx, txin); + txio_hash_sha256_ctx(io, &ctx, TXIO_UNCACHED); + } + } +} + +static void txio_hash_sha_single_output_elements(cursor_io *io, + const struct wally_tx_output *txout) +{ + struct sha256_ctx ctx; + sha256_init(&ctx); + hash_output_elements(&ctx, txout); + txio_hash_sha256_ctx(io, &ctx, TXIO_UNCACHED); +} + +static void txio_hash_sha_single_output_witness(cursor_io *io, + const struct wally_tx_output *txout) +{ + struct sha256_ctx ctx; + sha256_init(&ctx); + hash_output_witness(&ctx, txout); + txio_hash_sha256_ctx(io, &ctx, TXIO_UNCACHED); +} +#endif /* BUILD_ELEMENTS */ + +static void txio_hash_sha_prevouts(cursor_io *io, const struct wally_tx *tx) +{ + if (!txio_hash_cached_item(io, TXIO_SHA_PREVOUTS)) { + struct sha256_ctx ctx; + sha256_init(&ctx); + for (size_t i = 0; i < tx->num_inputs; ++i) { + hash_bytes(&ctx, tx->inputs[i].txhash, WALLY_TXHASH_LEN); + hash_le32(&ctx, tx->inputs[i].index); + } + txio_hash_sha256_ctx(io, &ctx, TXIO_SHA_PREVOUTS); + } +} + +static void txio_hash_sha_amounts(cursor_io *io, const struct wally_map *values) +{ + if (!txio_hash_cached_item(io, TXIO_SHA_AMOUNTS)) { + struct sha256_ctx ctx; + sha256_init(&ctx); + for (size_t i = 0; i < values->num_items; ++i) { + uint64_t v; + memcpy(&v, values->items[i].value, values->items[i].value_len); + hash_le64(&ctx, v); + } + txio_hash_sha256_ctx(io, &ctx, TXIO_SHA_AMOUNTS); + } +} + +static void txio_hash_sha_scriptpubkeys(cursor_io *io, const struct wally_map *scripts) +{ + if (!txio_hash_cached_item(io, TXIO_SHA_SCRIPTPUBKEYS)) { + struct sha256_ctx ctx; + sha256_init(&ctx); + for (size_t i = 0; i < scripts->num_items; ++i) + hash_varbuff(&ctx, scripts->items[i].value, scripts->items[i].value_len); + txio_hash_sha256_ctx(io, &ctx, TXIO_SHA_SCRIPTPUBKEYS); + } +} + +static void txio_hash_sha_sequences(cursor_io *io, const struct wally_tx *tx) +{ + if (!txio_hash_cached_item(io, TXIO_SHA_SEQUENCES)) { + struct sha256_ctx ctx; + sha256_init(&ctx); + for (size_t i = 0; i < tx->num_inputs; ++i) + hash_le32(&ctx, tx->inputs[i].sequence); + txio_hash_sha256_ctx(io, &ctx, TXIO_SHA_SEQUENCES); + } +} + +static void txio_hash_sha_outputs(cursor_io *io, const struct wally_tx *tx) +{ + if (!txio_hash_cached_item(io, TXIO_SHA_OUTPUTS)) { + struct sha256_ctx ctx; + sha256_init(&ctx); + for (size_t i = 0; i < tx->num_outputs; ++i) { + hash_le64(&ctx, tx->outputs[i].satoshi); + hash_varbuff(&ctx, tx->outputs[i].script, tx->outputs[i].script_len); + } + txio_hash_sha256_ctx(io, &ctx, TXIO_SHA_OUTPUTS); + } +} + +static void txio_hash_outpoint(cursor_io *io, const struct wally_tx_input *txin) +{ + hash_bytes(&io->ctx, txin->txhash, sizeof(txin->txhash)); + hash_le32(&io->ctx, txin->index); +} + +static void txio_hash_input(cursor_io *io, + const struct wally_tx *tx, size_t index, + const struct wally_map *scripts, + const struct wally_map *values) +{ + hash_map_le64(&io->ctx, values, index); + hash_map_varbuff(&io->ctx, scripts, index); + hash_le32(&io->ctx, tx->inputs[index].sequence); +} + +static void txio_hash_sha_single_output(cursor_io *io, const struct wally_tx_output *txout) +{ + struct sha256_ctx ctx; + sha256_init(&ctx); + hash_le64(&ctx, txout->satoshi); + hash_varbuff(&ctx, txout->script, txout->script_len); + txio_hash_sha256_ctx(io, &ctx, TXIO_UNCACHED); +} + +static void txio_hash_annex(cursor_io *io, + const unsigned char *annex, size_t annex_len) +{ + const struct wally_map_item *item; + item = io->cache ? wally_map_get(io->cache, annex, annex_len) : NULL; + if (item) + hash_bytes(&io->ctx, item->value, item->value_len); + else { + struct sha256_ctx ctx; + sha256_init(&ctx); + hash_varbuff(&ctx, annex, annex_len); + struct sha256 hash; + sha256_done(&ctx, &hash); + hash_bytes(&io->ctx, hash.u.u8, sizeof(hash)); + if (io->cache) + wally_map_add(io->cache, annex, annex_len, hash.u.u8, sizeof(hash)); + } +} + +static void txio_hash_tapleaf_hash(cursor_io *io, + const unsigned char *tapleaf_script, size_t tapleaf_script_len, + bool is_elements) +{ + const struct wally_map_item *item; + item = io->cache ? wally_map_get(io->cache, tapleaf_script, tapleaf_script_len) : NULL; + if (item) { + hash_bytes(&io->ctx, item->value, item->value_len); + } else { + struct sha256_ctx ctx; + struct sha256 hash; + tagged_hash_init(&ctx, TAPLEAF_SHA256(is_elements), SHA256_LEN); + hash_u8(&ctx, 0xc0); /* leaf_version */ + hash_varbuff(&ctx, tapleaf_script, tapleaf_script_len); + sha256_done(&ctx, &hash); + hash_bytes(&io->ctx, hash.u.u8, sizeof(hash)); + if (io->cache) + wally_map_add(io->cache, tapleaf_script, tapleaf_script_len, hash.u.u8, sizeof(hash)); + } +} + +/* BIP 341 */ +static void txio_bip341_init(cursor_io *io, + const unsigned char *genesis_blockhash, size_t genesis_blockhash_len) +{ + const struct wally_map_item *item; + item = io->cache ? wally_map_get_integer(io->cache, TXIO_SHA_TAPSIGHASH_CTX) : NULL; + if (item) { + /* Note we hash the intial sha256_ctx itself here and so memcpy it */ + memcpy(&io->ctx, item->value, item->value_len); + return; + } + + tagged_hash_init(&io->ctx, TAPSIGHASH_SHA256(genesis_blockhash != NULL), SHA256_LEN); + if (genesis_blockhash) { + hash_bytes(&io->ctx, genesis_blockhash, genesis_blockhash_len); + hash_bytes(&io->ctx, genesis_blockhash, genesis_blockhash_len); + } + if (io->cache) + wally_map_add_integer(io->cache, TXIO_SHA_TAPSIGHASH_CTX, + (const unsigned char*)&io->ctx, sizeof(io->ctx)); +} + +static inline uint32_t tr_get_output_sighash_type(uint32_t sighash) +{ + if (sighash == WALLY_SIGHASH_DEFAULT) + return WALLY_SIGHASH_ALL; + return sighash & 0x3; +} + +static inline bool bip341_is_input_hash_type(uint32_t sighash, uint32_t hash_type) +{ + return (sighash & WALLY_SIGHASH_TR_IN_MASK) == hash_type; +} + +static int bip341_signature_hash( + const struct wally_tx *tx, size_t index, + const struct wally_map *scripts, + const struct wally_map *assets, + const struct wally_map *values, + const unsigned char *tapleaf_script, size_t tapleaf_script_len, + uint32_t key_version, + uint32_t codesep_position, + const unsigned char *annex, size_t annex_len, + const unsigned char *genesis_blockhash, size_t genesis_blockhash_len, + uint32_t sighash, + struct wally_map *cache, + unsigned char *bytes_out, size_t len) +{ + const struct wally_tx_input *txin = tx ? tx->inputs + index : NULL; + const struct wally_tx_output *txout = tx ? tx->outputs + index : NULL; + size_t is_elements = 0; + const uint32_t output_type = tr_get_output_sighash_type(sighash); + const bool sh_anyonecanpay = sighash & WALLY_SIGHASH_ANYONECANPAY; + const bool sh_anyprevout = bip341_is_input_hash_type(sighash, WALLY_SIGHASH_ANYPREVOUT); + const bool sh_anyprevout_anyscript = bip341_is_input_hash_type(sighash, WALLY_SIGHASH_ANYPREVOUTANYSCRIPT); + cursor_io io; + int ret = WALLY_OK; + + if (!tx || index >= tx->num_inputs || + !values || + BYTES_INVALID(tapleaf_script, tapleaf_script_len) || + key_version > 1 || + codesep_position != WALLY_NO_CODESEPARATOR || /* TODO: Add support */ + BYTES_INVALID(annex, annex_len) || (annex && *annex != 0x50) || + BYTES_INVALID_N(genesis_blockhash, genesis_blockhash_len, SHA256_LEN) || + !bytes_out || len != SHA256_LEN) + return WALLY_EINVAL; + +#ifdef BUILD_ELEMENTS + if ((ret = wally_tx_is_elements(tx, &is_elements)) != WALLY_OK) + return ret; +#endif + if (is_elements) { + if (!genesis_blockhash) + return WALLY_EINVAL; + } else { + genesis_blockhash = NULL; + genesis_blockhash_len = 0; + } + + { + /* Validate input scripts/values/assets: + * For ACP/APO, we must have the items at 'index', and look them up. + * Otherwise we need all values, and iterate them. + */ + const struct wally_map_item *item; + bool (*value_len_fn)(size_t) = is_elements ? value_len_ok : satoshi_len_ok; + if (!sh_anyonecanpay && !sh_anyprevout) { + if (!map_has_all(scripts, tx->num_inputs, script_len_ok) || + !map_has_all(values, tx->num_inputs, value_len_fn) || + (is_elements && !map_has_all(assets, tx->num_inputs, asset_len_ok))) + return WALLY_EINVAL; + } else { + if (!map_has_one(scripts, index, script_len_ok) || + !map_has_one(values, index, value_len_fn) || + (is_elements && !map_has_one(assets, index, asset_len_ok))) + return WALLY_EINVAL; + } + item = wally_map_get_integer(scripts, index); + if (!scriptpubkey_is_p2tr(item->value, item->value_len)) + return WALLY_EINVAL; + } + + switch (sighash) { + case WALLY_SIGHASH_DEFAULT: + case WALLY_SIGHASH_ALL: + case WALLY_SIGHASH_NONE: + case WALLY_SIGHASH_SINGLE: + case WALLY_SIGHASH_ALL | WALLY_SIGHASH_ANYONECANPAY: + case WALLY_SIGHASH_NONE | WALLY_SIGHASH_ANYONECANPAY: + case WALLY_SIGHASH_SINGLE | WALLY_SIGHASH_ANYONECANPAY: + break; /* Always valid */ + case WALLY_SIGHASH_ALL | WALLY_SIGHASH_ANYPREVOUT: + case WALLY_SIGHASH_NONE | WALLY_SIGHASH_ANYPREVOUT: + case WALLY_SIGHASH_SINGLE | WALLY_SIGHASH_ANYPREVOUT: + case WALLY_SIGHASH_ALL | WALLY_SIGHASH_ANYPREVOUT | WALLY_SIGHASH_ANYONECANPAY: + case WALLY_SIGHASH_NONE | WALLY_SIGHASH_ANYPREVOUT | WALLY_SIGHASH_ANYONECANPAY: + case WALLY_SIGHASH_SINGLE | WALLY_SIGHASH_ANYPREVOUT | WALLY_SIGHASH_ANYONECANPAY: + if (key_version != 1) + return WALLY_EINVAL; /* Only valid for key_version 1 */ + if (is_elements) + return WALLY_ERROR; /* Elements: unsure of Activation status/no ELIP */ + break; + default: + return WALLY_EINVAL; /* Unknown sighash type */ + } + + /* Init */ + io.cache = cache; + io.cursor = bytes_out; + io.max = len; + txio_bip341_init(&io, genesis_blockhash, genesis_blockhash_len); + if (!is_elements) + hash_u8(&io.ctx, 0); /* sighash epoch */ + /* Tx data */ + hash_u8(&io.ctx, sighash); /* hash_type */ + hash_le32(&io.ctx, tx->version); + hash_le32(&io.ctx, tx->locktime); +#ifdef BUILD_ELEMENTS + if (is_elements & !sh_anyonecanpay) + txio_hash_sha_outpoint_flags(&io, tx); +#endif + if (!sh_anyonecanpay && !sh_anyprevout) { + txio_hash_sha_prevouts(&io, tx); +#ifdef BUILD_ELEMENTS + if (is_elements) + txio_hash_sha_asset_amounts(&io, values, assets); + else +#endif + txio_hash_sha_amounts(&io, values); + txio_hash_sha_scriptpubkeys(&io, scripts); + txio_hash_sha_sequences(&io, tx); +#ifdef BUILD_ELEMENTS + if (is_elements) { + txio_hash_sha_issuances(&io, tx); + txio_hash_sha_issuance_rangeproofs(&io, tx); + } +#endif + } + if (output_type == WALLY_SIGHASH_ALL) { +#ifdef BUILD_ELEMENTS + if (is_elements) { + txio_hash_sha_outputs_elements(&io, tx); + txio_hash_sha_output_witnesses(&io, tx); + } else +#endif + txio_hash_sha_outputs(&io, tx); + } + /* Input data */ + hash_u8(&io.ctx, (tapleaf_script ? 1 : 0) * 2 + (annex ? 1 : 0)); /* spend_type */ + if (sh_anyonecanpay || sh_anyprevout) { + if (sh_anyonecanpay) { +#ifdef BUILD_ELEMENTS + if (is_elements) + txio_hash_outpoint_flag(&io, txin); +#endif + txio_hash_outpoint(&io, txin); + } +#ifdef BUILD_ELEMENTS + if (is_elements) + txio_hash_input_elements(&io, tx, index, scripts, assets, values); + else +#endif + txio_hash_input(&io, tx, index, scripts, values); + } else if (sh_anyprevout_anyscript) { + hash_le32(&io.ctx, tx->inputs[index].sequence); /* nSequence */ + } else { + hash_le32(&io.ctx, index); /* input_index */ + } + if (annex) { + txio_hash_annex(&io, annex, annex_len); + } + /* Output data */ + if (output_type == WALLY_SIGHASH_SINGLE) { +#ifdef BUILD_ELEMENTS + if (is_elements) { + txio_hash_sha_single_output_elements(&io, txout); + txio_hash_sha_single_output_witness(&io, txout); + } else +#endif + txio_hash_sha_single_output(&io, txout); + } + /* Tapscript Extensions */ + if (tapleaf_script) { + if (!sh_anyprevout_anyscript) + txio_hash_tapleaf_hash(&io, tapleaf_script, tapleaf_script_len, is_elements); + hash_u8(&io.ctx, key_version & 0xff); + hash_le32(&io.ctx, codesep_position); + } + txio_done(&io); + if (io.max) + ret = WALLY_ERROR; /* Wrote the wrong number of bytes: should not happen! */ + return ret; +} + +int wally_tx_get_input_signature_hash( + const struct wally_tx *tx, size_t index, + const struct wally_map *scripts, + const struct wally_map *assets, + const struct wally_map *values, + const unsigned char *script, size_t script_len, + uint32_t key_version, + uint32_t codesep_position, + const unsigned char *annex, size_t annex_len, + const unsigned char *genesis_blockhash, size_t genesis_blockhash_len, + uint32_t sighash, + uint32_t flags, + struct wally_map *cache, + unsigned char *bytes_out, size_t len) +{ + if (!flags || (flags & ~SIGTYPE_ALL)) + return WALLY_EINVAL; + if (flags & WALLY_SIGTYPE_SW_V1) + return bip341_signature_hash(tx, index, scripts, assets, values, + script, script_len, + key_version, codesep_position, + annex, annex_len, + genesis_blockhash, genesis_blockhash_len, + sighash, cache, bytes_out, len); + return WALLY_ERROR; /* FIXME: Support segwit/pre-segwit hashing */ +} diff --git a/src/tx_io.h b/src/tx_io.h new file mode 100644 index 000000000..c23a9773c --- /dev/null +++ b/src/tx_io.h @@ -0,0 +1,16 @@ +#ifndef LIBWALLY_CORE_TX_IO_H +#define LIBWALLY_CORE_TX_IO_H 1 + +#include +#include "ccan/ccan/crypto/sha256/sha256.h" + +/* A cursor for pushing/pulling tx bytes for hashing */ +typedef struct cursor_io +{ + struct sha256_ctx ctx; + struct wally_map *cache; + unsigned char *cursor; + size_t max; +} cursor_io; + +#endif /* LIBWALLY_CORE_TX_IO_H */ diff --git a/src/wasm_package/src/const.js b/src/wasm_package/src/const.js index 9c48139a3..7b87a32ba 100755 --- a/src/wasm_package/src/const.js +++ b/src/wasm_package/src/const.js @@ -215,6 +215,10 @@ export const WALLY_SIGHASH_NONE = 0x02; export const WALLY_SIGHASH_RANGEPROOF = 0x40 ; /* Liquid/Elements only */ export const WALLY_SIGHASH_SINGLE = 0x03; export const WALLY_SIGHASH_TR_IN_MASK = 0xc0; /* Taproot mask for determining input hash type */ +export const WALLY_SIGTYPE_MASK = 0xf; /* Mask for signature hash in signature hash flags */ +export const WALLY_SIGTYPE_PRE_SW = 0x1; /* Pre-segwit signature hash */ +export const WALLY_SIGTYPE_SW_V0 = 0x2; /* Segwit v0 signature hash */ +export const WALLY_SIGTYPE_SW_V1 = 0x3; /* Segwit v1 (taproot) signature hash */ export const WALLY_TXHASH_LEN = 32; /** Size of a transaction hash in bytes */ export const WALLY_TX_ASSET_CT_ASSET_LEN = 33; /* version byte + 32 bytes */ export const WALLY_TX_ASSET_CT_ASSET_PREFIX_A = 0x0a; diff --git a/src/wasm_package/src/functions.js b/src/wasm_package/src/functions.js index fef08da9a..66d16306f 100644 --- a/src/wasm_package/src/functions.js +++ b/src/wasm_package/src/functions.js @@ -672,6 +672,7 @@ export const tx_get_input_issuance_amount_len = wrap('wally_tx_get_input_issuanc export const tx_get_input_issuance_amount_rangeproof_len = wrap('wally_tx_get_input_issuance_amount_rangeproof_len', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]); export const tx_get_input_script_len = wrap('wally_tx_get_input_script_len', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]); export const tx_get_input_sequence = wrap('wally_tx_get_input_sequence', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]); +export const tx_get_input_signature_hash = wrap('wally_tx_get_input_signature_hash', [T.OpaqueRef, T.Int32, T.OpaqueRef, T.OpaqueRef, T.OpaqueRef, T.Bytes, T.Int32, T.Int32, T.Bytes, T.Bytes, T.Int32, T.Int32, T.OpaqueRef, T.DestPtrSized(T.Bytes, C.SHA256_LEN)]); export const tx_get_input_txhash = wrap('wally_tx_get_input_txhash', [T.OpaqueRef, T.Int32, T.DestPtrSized(T.Bytes, C.WALLY_TXHASH_LEN)]); export const tx_get_input_witness_len = wrap('wally_tx_get_input_witness_len', [T.OpaqueRef, T.Int32, T.Int32, T.DestPtr(T.Int32)]); export const tx_get_input_witness_num_items = wrap('wally_tx_get_input_witness_num_items', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]); diff --git a/src/wasm_package/src/index.d.ts b/src/wasm_package/src/index.d.ts index 39e378f5e..067e47057 100644 --- a/src/wasm_package/src/index.d.ts +++ b/src/wasm_package/src/index.d.ts @@ -632,6 +632,7 @@ export function tx_get_input_issuance_amount_len(tx_in: Ref_wally_tx, index: num export function tx_get_input_issuance_amount_rangeproof_len(tx_in: Ref_wally_tx, index: number): number; export function tx_get_input_script_len(tx_in: Ref_wally_tx, index: number): number; export function tx_get_input_sequence(tx_in: Ref_wally_tx, index: number): number; +export function tx_get_input_signature_hash(tx: Ref_wally_tx, index: number, scripts: Ref_wally_map, assets: Ref_wally_map, values: Ref_wally_map, script: Buffer|Uint8Array, key_version: number, codesep_position: number, annex: Buffer|Uint8Array, genesis_blockhash: Buffer|Uint8Array, sighash: number, flags: number, cache: Ref_wally_map): Buffer; export function tx_get_input_txhash(tx_in: Ref_wally_tx, index: number): Buffer; export function tx_get_input_witness_len(tx_in: Ref_wally_tx, index: number, wit_index: number): number; export function tx_get_input_witness_num_items(tx_in: Ref_wally_tx, index: number): number; diff --git a/tools/wasm_exports.sh b/tools/wasm_exports.sh index f4f9b295d..b8ca67deb 100644 --- a/tools/wasm_exports.sh +++ b/tools/wasm_exports.sh @@ -409,6 +409,7 @@ EXPORTED_FUNCTIONS="['_malloc','_free','_bip32_key_free' \ ,'_wally_tx_get_input_script' \ ,'_wally_tx_get_input_script_len' \ ,'_wally_tx_get_input_sequence' \ +,'_wally_tx_get_input_signature_hash' \ ,'_wally_tx_get_input_txhash' \ ,'_wally_tx_get_input_witness' \ ,'_wally_tx_get_input_witness_len' \ From d35276a9c7e79799c946afef3a2759a53d5e043e Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sun, 16 Feb 2025 20:25:56 +1300 Subject: [PATCH 07/22] tests: add tests for elements taproot signature hash generation --- src/data/bip341_vectors.json | 44 +++++++++++++ src/test/test_transaction.py | 119 ++++++++++++++++++++++++++++++++++- 2 files changed, 161 insertions(+), 2 deletions(-) diff --git a/src/data/bip341_vectors.json b/src/data/bip341_vectors.json index 11261b00b..a58ccb209 100644 --- a/src/data/bip341_vectors.json +++ b/src/data/bip341_vectors.json @@ -447,6 +447,50 @@ "auxiliary": { "fullySignedTx": "020000000001097de20cbff686da83a54981d2b9bab3586f4ca7e48f57f5b55963115f3b334e9c010000000000000000d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd990000000000fffffffff8e1f583384333689228c5d28eac13366be082dc57441760d957275419a41842000000006b4830450221008f3b8f8f0537c420654d2283673a761b7ee2ea3c130753103e08ce79201cf32a022079e7ab904a1980ef1c5890b648c8783f4d10103dd62f740d13daa79e298d50c201210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798fffffffff0689180aa63b30cb162a73c6d2a38b7eeda2a83ece74310fda0843ad604853b0100000000feffffffaa5202bdf6d8ccd2ee0f0202afbbb7461d9264a25e5bfd3c5a52ee1239e0ba6c0000000000feffffff956149bdc66faa968eb2be2d2faa29718acbfe3941215893a2a3446d32acd050000000000000000000e664b9773b88c09c32cb70a2a3e4da0ced63b7ba3b22f848531bbb1d5d5f4c94010000000000000000e9aa6b8e6c9de67619e6a3924ae25696bb7b694bb677a632a74ef7eadfd4eabf0000000000ffffffffa778eb6a263dc090464cd125c466b5a99667720b1c110468831d058aa1b82af10100000000ffffffff0200ca9a3b000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac807840cb0000000020ac9a87f5594be208f8532db38cff670c450ed2fea8fcdefcc9a663f78bab962b0141ed7c1647cb97379e76892be0cacff57ec4a7102aa24296ca39af7541246d8ff14d38958d4cc1e2e478e4d4a764bbfd835b16d4e314b72937b29833060b87276c030141052aedffc554b41f52b521071793a6b88d6dbca9dba94cf34c83696de0c1ec35ca9c5ed4ab28059bd606a4f3a657eec0bb96661d42921b5f50a95ad33675b54f83000141ff45f742a876139946a149ab4d9185574b98dc919d2eb6754f8abaa59d18b025637a3aa043b91817739554f4ed2026cf8022dbd83e351ce1fabc272841d2510a010140b4010dd48a617db09926f729e79c33ae0b4e94b79f04a1ae93ede6315eb3669de185a17d2b0ac9ee09fd4c64b678a0b61a0a86fa888a273c8511be83bfd6810f0247304402202b795e4de72646d76eab3f0ab27dfa30b810e856ff3a46c9a702df53bb0d8cc302203ccc4d822edab5f35caddb10af1be93583526ccfbade4b4ead350781e2f8adcd012102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f90141a3785919a2ce3c4ce26f298c3d51619bc474ae24014bcdd31328cd8cfbab2eff3395fa0a16fe5f486d12f22a9cedded5ae74feb4bbe5351346508c5405bcfee0020141ea0c6ba90763c2d3a296ad82ba45881abb4f426b3f87af162dd24d5109edc1cdd11915095ba47c3a9963dc1e6c432939872bc49212fe34c632cd3ab9fed429c4820141bbc9584a11074e83bc8c6759ec55401f0ae7b03ef290c3139814f545b58a9f8127258000874f44bc46db7646322107d4d86aec8e73b8719a61fff761d75b5dd9810065cd1d" } + }, { + "description": "Elements 2 input p2tr spend. Note this JSON is a different format from the previous example", + "given": { + "genesisBlockhash": "00902a6b70c2ca83b5d9c815d96a0e2f4202179316970d14ea1847dae5b1ca21", + "rawUnsignedTx": "020000000102d7e48759db4d6f3d3f38440162c487fcc95205b6500d8772a5b8c32e870de2280000000000fdffffff882358478a81a3259677612047587d24da92494ccfd4487dd3e27715bbb18bf60000000000fdffffff050abe40ef1f47c667beb32dd0488751358860d1433697e4787e142f066be1095f2d085d699b82683473a96932a016a46ebcce0c9eb442c9b5d42daf37e46d49ec90d603c868ed1a2a7dcf8405961581f13d091269a1710160392bf89d616ff06ae78cc117a914501d2552872ba1c5389433354a3e61d583eaf5ad870ac844723343c588eb47feef3c1bc1254b996613542085a3b679f9629cb7e06759083e1153bf03577a00445538d8b5ab25b295254efd5131a66d4a727d8da3ce37fd0347e524f0e7e781508d56be3df67dba0c7ec2494d9e8e4ac9c8d09c1c485a02d1225120ac73968281b866aacacc3f339549c3c8301d203e2d40cc33df78c17fd80389970a00adaa4b0390856e6570baed216bf61ca6bfe752ec81843655aa29f61c3559e50895ad406892a57f44dca170cb236a62bb874761250912430c0b8a3870373951760349f40321421e15d81278a86415548031b732409e8e938a85ed16dda97e0710f317a9146deab1ecba96723fabe6191924250a71b52321be870ad88338c4d86b698b3f8a90f2da77bf7ae70203bda44edf2d9bccbdb52d727efb09103056d5e5810a391118aa6db30e80a8812b95cf1436e46c069624676599cd7703b9b33c4e7e6ff5fc0ededc37a5a1f9db9b132745fdce0c1ce53871bd6e85e878225120c486c03619768c5a72a960a2246a7b750fbbbd27d35ed4d6d74a31ea856ea77d0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000000000122000000000000000000000000000006302000317307c438dde0f189eae26a2bf1bf48150d837313897607ef9cf7845a08ad2db0e31aa62d20b368c836dd8c76754691b852224dab5d4c9a9d68d967827d17300ee3fbbbe3887e295b821cd137d207b2069c2731c180ab5850ba49e3fd4969250fd4e10603300000000000000018b473501c52011ae3d0e20ea30e58000568d75d929e0322e7b48f8b5b1a51e4e1b816c73ad3e5b828a3e241d648603faab3f2bfe584ce269e01fa6a44662031dfd65d71c290c31c8b3768ed278a9f2dc39ac7b947f39fc07b128448873f00e34f465ac7c55632dcb2a8b3d45d903f1c6280b6e145dc40fb3130abc868184433dd35995a3293b3a7b8b83611b499e81587724f0c70e5a009a052f081793f661e4a2361c50491deeb03e19519ddf23c82454b4d422a7a9c5d79fd5e825f2554663668cb3d2b1fc1e72b48a057e7a4a4484b197318c2f13ab007c2648778ceb28f60ef1a756646a35e5c1efd5752641393570a5e03d9caca1569ffe38a3ae403ad8420b49104bac49d9e55482d0cf694483b20aa1a68904dc1f1550280583633e00387ec8df96aa163183255d099461a822234138688aeda7d95b4862877ae78b437fb81073909fb4457d281d0dcc467a23fbb3d31fdbcfb41777f903247366053cc247eeafd4e25b7764e2dca8e0266035c117b0c46dc7186a9b75e94b5c9c2baa4f66bdedd9866201ca82dfe6827568b057218768514c0d9782e687e98632f4c1d423aa16ca5bacbb2e211770ebc1d90cc7d15fff85fa667c3f8cac3daeb2dbcc6430a7cfa4ddd22e3f11a13ed97d77352d73a710b2948a7b359f8d7e6bc0586d784be01c12ffcfcab3cd6108e4c9c89d10e54e310621d6a2c9c6c21a5c71aca534945d5a50d3eaf6cc7d20596b1dfcef2021384d3ef46ed4c767bd3b491a80d9e8fedd66a78a3d37d18a7aa38ef8c68d124453f382020acc487f2ed1e3ea22de95b89b2882460f069c94449fd946938969a59eeaefc0a12b1701a22bee25be6d0a9ff8a380c1ac832a95bbf46235d516aa51ca98c8cc1942b5337ddee2fd3442aea1a0ebe433e029eac0098f701341e6052b1e46271656dc37e392b1bdcb1cb6f51c9e0e1eff425dd5ce487b0f07bf9b70b44139877c5f212ba55308c606b7e432b5ed1eda148d1548f0c6472eb93898d21ea70fa324bbe2ace95a11f5bc595223f1969e267afa6ca85a49312c4ed62bde5dc7d084598c9e3c599a4f2f8de95c2d594690a9b0a431fac27da409374a5b53d38accb05280e8f2b1df51870137132c12b1e805b7531d83b6aeff523cfb871e7abdbf54e0edf1e5ff2183bd3e9645322add1c82563f22a27307e5763bbd8ff2a26da86b6d3e62633240af7762797da73ae96398a758dca993b526da6de2842093f5f1c17c79e20e16b66de0a339a435f1cefed76d0ac98b870565c59940af0bec7e4df19807648a01cbce181404f34218c79401de4113df9f3e205418bbc845d138efea581f823d1c32af90713f035a4a5fe82e1b0f6645b4245f9c96dc12158cc0b9dcf58ca18d3c840804c949d26276bc65a241e311ecab2cdfcac3769324ad185c4f8e1390a46768d4c2c30372e006e6d536620e848f183a84b1bb9452e264b63c2af050c9dcca9bc3fb8589cd9703ff86f8ad014eb25da947085b802c2011ffb45855dbc69645f135614fcf78754db9a0b26be4fb5d8a6bde73b5e6c5ddcaf6b3070b1df1f6706d3812e12b0e1a5043ec3551c414c4dcadac7756a453e4f62d252893217b5b141402b18e966c169c126af8e296e408643084db6dc2876cbdd35b62c7ca446740223c4ef0ce9a20cdd523a135b29e2728e8b8f15e2d1ad16df332e60c5e38892ea28eaeebfb2fd9c9563cf1b4b65ca0feeeed3acf6ebb85dbc1f7bce884dd761e1680dbe55254388bae03f66cee8bc6ec8d7d7305f8861e4797aa9e8e098b559fc1112be542a282a38d448ce4dc3e1715a794f204a207dd47b311654d5599ace348b0d4b85806d0c0366e2dcc3abca5e1394230517d24025287be4c2bff6f1083ab46c21d145f947401b0fdbdb578d048ca9cf247517f3668ac71af5097c39e5b08edfd7933437c2f3ea039ad75d64b5adc7ec3d8ff233219b37bcd0b3bc3a1ba829d069db5e3fb60145a9e548817cb4b404d5a91a2fd9043555dd14baf31f9cce2504ba96430fc90090ca5e36db7c4af3be71ca44f7952f1353e5044da224d9627d4275e56c1ad4b93aad1a4a27df4f680b8044751c1e149543063ec912d3c1831364077c38f38bc999c017aab2a842c93416b7683e16cc4ae3d58b69bf6f4a07fab9dc2d63a44b65d890bbeda585826a77bcdd3133153fe0e83ec660323603a98eb97e19f07d4b40aaab5f58b30dfc29aeae63ee9eeafce594fbb0c3adeafe3e176c3eb042695a9f612350c1782cd9378dc3c1d85d63acf4fc58069fde087f4e50d9eca866a620d00ea3c024da328ff7eeb580c25969eab04a611f450fee3a99fb32b58ed63f28fc737e0569a67acac6ee5010f30f23c0e8a88893f629ccc60d576b92faa818f17b2eef7d2d6fa9b2503a710b784303b513a96756815f30e8d45b1795b8d1f5a686ccf9b3ac4708bd60fafa703669214914e5b65c6121e6f20f51cdfe34f05e4988a5b2340166615c32398e9ebf6909dafb12eb0bbd9c856f0fe84b3014f9b43aa9b4e9f6655ece31ac6f4444aef628ce43d4c2e27ba3888f11b51f3a515ba0fc8b45a2202c29bfe63c270c6efc73beba1cbf9fab52d85831893072093222f15f55ebf1620285481a89c1cf33264904d38ae2b8ce50b749d6719b22970ce8d96f61f119888772f5a4e1ed56c4735bedfc6bf7f4cd8dccbe3e7a39e2050551b1583083b2c5547c98ada570b9a95d14fed0c4f3b0d45b6f9f29d940d6623837b568c22035a2cbfde4d4b4898fefc4f390d5ed53b7857c150d4ecdbd41303f5949f2b396f76d02842b476755f2e595721630d7faf3e5f869a5903b5e70750cd1a199df822668a2567b1caccecdffe6c485855424c7271fbf3402d758d49f663d999556b43494393e675f4b4eb372ffb6d0225680cf6859b7741201d4a7e0c03a5ee10d073d78ddb72d3cad8ba064d5a7ff85a7d9dc76d5ce373cb7fcbe56b70fb130792e400e4ac5129f1dc02cb4b65a30354d57280c8bacf7d6859e9fea59d57cba2c0692f12aa596e09b00bf1c240a838c19a7815f4526aef8a96ce1ec745928d094cfd9049da6022833d71b72f0618813e71ecbfab66f50d254fde6e31557af61b2d677abc421ba41a6cafacf021a1a970a3f6411014c7087b4ce29fda27b25ae9ba30fb18cc26e6744eac902213c99821729821df94377288f464321e82a2fa7530b549afd687eb45a40a2d85f5fb475cd1daf501ca6511abb82d5854e91dd8b6176973537eee0f48ea6217f60b615498c0f2362992603199d23a988417e393d6de5ca281fc1aa75fe6744808f16eb39b95d6aebf26c2798dddc4dbd5762584207d633513c8e6f8c22cf63f1df26caf77f2dd88078b3d3dd7b88a65a45ebada6c4eef0f277cd609547d9910fd3a40188673740e85cf43c58abe64f66eb81f64fc2a2d58f9cff6d3cd0f9ac2a56942ade0d6ff847ae1ebb1c053d27a456a56badaaca62f38316e455ffabf02676da4b8bbd3b602216671a149a83238f385c65b085955c00160cd02c4a0262a39ea79909b2254ce36b4bd0d424fc801972c53fd862d644d16c96927622a67c5a8db7ed6e4befffb5e5af9557f2d0f3076047f14b06d7aa23406f2f9fa341a382082b65765ac10f5473d6c82f82c13cf230093bbc131daa3dc4580fd907c91eb518440fba44783c9c2d54c2835faba54aeafbcf7a57cc453c16844493154f85c19863b4e40557bc9957290191f74856b3dd663e5fc292acef35b72bde20cc6188499322e5d52d78d482cdbd8dde025fc8c93f6c3e7ee664394c74e6a80045c6e12423f0ee4dedbf0da58504343265bc56cc149abbdd5889d466e0bae9e510756f7d18b43427e12279fffcf433a6978e04c230d577955e4c24a6ef83669a685b61b96e8379bcdedce62333dce27a228af93148b673a792d8a2aab8e4de8ae50dabf6e3246aa5219577114c4d4544b9e44626be0d88730796bde1c1050440857e5b3a0f17a03f2476cc2590e20d055be994c3f34c0035ee5c4ab1bc630a52d3dc5bc240d5bd10a5a77f4541b5a0c4762b6657f52d7b33e9e682ac14af6d8e477eacb73451489b8931eff7bb8d3bcd056ea424455be97eb6b6c2ae8e75c8834730589f4a7e85b48bfb1dfdb8fef0555c076171f400bab26c51ccb839592ea65c7875f8f2a1dca7fe50f295f2e6f0b214cd0cec44024275d1a3eb211e5a5648e8d66f792ca94a77edf43ff939858ca2ca67987f4d3a3db71bbbbae1818716ab033dd8864cc6afdd3f6d5153acd3a1957bf841246dc5cfb304c6d93c6f1ff8e7a60516f35ee83649a63a88c076a301dd9c674627af8bbd89035ca8eeaf55dce2b427aa10c662cbae3140dfd4437ddbd3eff6798980ad32fb6ffc195d3117d765505f20d8987854d135679470d45c8cfecdc20e493509488d2dfd442e6277626e3acee945719d60c2689a59ff1c6349b57cd4a7fe66ab67eab11a3664ec67855b68046bed43df2ff494118a0a35bb7cefe2617b376bbc802500962bc3ba84e163b060866ec1a10fc78179d463a6eaa03b62eb1f4f7981775c35a5d419ca995cacaa85665ec2ec673ce9ef9e763b73217d45177baa7c519dd6fa9be812bac335832aa2c7ca732d5666416abf8c9d670ea2f24cc6f8c8820d8bfac9bb89e469bd7c25f1ea5fce4bdfa1bd294de54d61a0295313e54c1bf7dfac89e7d14b7b547dc9df5019883edc0a8d51d2675016c8614386ef3b30e8f0e6f8c465513e3cc0a4716a2174a8002b8b82e67f6a8bd9938a77cba18d3a42f2751e8e9d71d874c5b05fcbc33222710e418257d7c94a6a693d042c50bb2e47d78424cf95e9f2488dfd7d04ea0cec7785b27dec3fb64ca72549368116cf162fb6cb5af176757ef84dbc625ec53b211b41aa7446461d3fb78e49c08ea86eac1e396ad0e9764fb4ec44e613d6c9b92aaa8c317b4b17eb6f7915d501b92b2ef7d919b459d24d903f861aed3c1d69d49e6319c38de0b365dbea48a4ec2966da5d23b5207dfeba866661baa9f221a426346100d393eab9816bfb2850f1c51dc9c70bd43382115b23ecbb0399a6aec4907fdc5a908eced5c74d1ba490d37e65af40247f769680a7449f71a7b67a033b2048a02f87a2dda0bb66e11dd8d548aaf6e97abe971134d5bd4099da1d081500421b10e57499814b9f28433c5626d3f98a8578be88f0197c9e22db9262acd651ff75a54c2373b763dfea6780f1ecdcba1bc3c51a1ad9b85f500bebad48fdd38b41a54745adf0a5cb013e9e4a6da555000963ac79311485e221d79ad399bfb093d9d5de34030ebea9cba83da0b17afc28f3f1ae1ce3abc02282985f0df462701362574083c357d1611d1564b639253421af452341680111bfa747d64d7f57ebc4c682edabcf5b5f594405ba7210b925ace286fd91805dfe5b4d03c531d7df277810099737a9a24d4337908b5ec5188f571772a9bdd19e95c3a293cd26bf06aa400f2a79956c6b6f618e8f36b9765421a7c21fe5354cac9c6338a2be6d648b7adefe8c9f9217ff85100d67c6e11df953e2f78dc2547cd4b71f19f0d7e37a2425286c701ec7874e8612d8187a04f66fddf2738c077c22093624e19a15f364962348c1df9d57f7f013de1e018776649eba2075324b2f45f6a15088c17137bf7d62f663f0918f3da9d5ce42d5a05dac8573d056180bf5d25cfca186348c7c9d4bffb6b51f9b25ba978c38aa4da2ffb6165519b5470fda5d6ff972464c41532a368ec602abc6d691304d693eda048404c2c7bf7fb52a9cd5b1272dff486f23e94eff24eb58adf73780eb7d58e2c31d50ccfc2ef95b42612b83a066e868034706726ec65bac8b130bec693b812937082d68673451cdcc406d0f2b263020003fbd947fa1b1c1605c1d190f3d3e02d23683988f2d456975df01033883113898b17a9211633c2d4a92c1843af2a9f1b7edce71c3e3fd3e1003e7cd75f8678b1459259856941c65ef21d0249b3eace33006d73f19967dd5c2186ce0528160a3df3fd4e106033000000000000000186d73201c5236992ef55ce958c3edafdfbedb2a17ba129571ac56a7ac3505e7a561c92248c554283e01441f0bf6ee173c99eb6bbc7c4e6a10984d59041ea5301b9a3e7b5d1a6783427d6a9d3dfc0b0f125296e020fa98e08f0c05c5368b6e17c4b9e6d027aaf4ebd55506d15f040cf8ec4a47057b09c07b5ea7ad75a7e79f155cd035dca9f693961d1fa7a4bf8969e6c2a395ca3037d9bd5b33002d945c0cf1d9c62b0a664ff1ed28a7c1a25845a01493585c2452f2afaaba8cf239f7a1b87acc76f562e16de34f12ce9a2c6388fdad75e248b54bc6d80aa359eb8afbf64536d8d5f8eec633f8dd1cb6916f21922bc86df05834424f4ab4cd8779bbd53a9a8439b4de61d396cfa3d27d9741231ac7bd95a7a57f78f597dfbf534376502abd5cfcd08ee303f6816044df810706265f1f446335032f9d9286becc502ccd18ddabe4225d9a6d5c387f50a981ea706e14895897ca181346f14a7cc1c590450c1a459dcc2834eefe952441a518dd0792fbd4b5ce0ba17241bcabd1262f904e794f37325324ef9acb38e4956e5d25c1fd96f5fdf5f3dd071f6d44c696b6691284ebc68506f8c3263efd50b44fa50752c11f6d72b40fc5a988e81855c7be4571d54974f448c1f4aa497b185ec371fb0c2d210b7ff875a4a951fe780ebefde9b9d25ec1ad04e0a5c39acec4c4d8ea393d9793f5ef03f9f35ebe413229863875b7cc99e63bba8df0f200cbb914dc0287e563bcbab5317a19396274ad35f6749570e10a14a813d63aef04feafdee4551275d1f47e9440dedd9a48473e4b2f125309bcdedaee5f76365c7b936ff61de4b7ed402787b308c40d02a37ab6c1a481868c6b05d1e7d5297a37789a15d4c9824c895c635b3912cf459179fae84904e1f276f248806ca5b75b51ec27f58d5c370dbd6732c4434c97f0f05b31afb90704ddcf207a80d3e9d67d758f886b025ba7cbfb547503c122a3c311c3ae3376db1aabbe5e6c737cbe9645c3a463cbb20924e119177690234249ff2a862dbcfa1e7bd2a503901c6cd68924d233a435665b020c51c6733913e6fba3ca515e241ef9ab7987ed9d3122e730b8e7925d8648b2d9a3fd5df35d3e202ea1bf934bfc86120e37a20dac3cb1cd31b578c6d880057d93242fb7eff06ee8609ba7ae708c5f6d14cb8d27d6ab2e515d44eb5ca4eb04c7723145f9c20cb83694911c990daa423691b7b0fd2ba40bc2078725515e4a9ade5d6e706ed27f0700a848673655a41621c1dc0ef51ffc4a3ea309e63089a18c2812a1dbb43fde4a28847f4c21614eb654c87a1ae9ecc77a84b6126bc7a7f51314d01e7c979a1d30045f2a49b763ac7e93e80ba891c38e78c74ef8ceaf5ec1dc5378054a5636e10e9b326f5d88b44c58d1f7a482d35961f7995d89f23339f839a19017b88e6185886c8a9572cc2d21803d9082b0286b2f97ed88a2b3708a43cf2475122e9c95111a00c56ace7f607fd2cd294eb72a61ff5435177f57b39521c00ab332470553c2b2994caf891011cdf002ddd8a5188d0a5235b3bd9418d3ed5a3511d7e89110bac4c812f3ad50ea4e8c8c06ae7a19d5e6f51eaefe2cfe389f480e615f0ad12b8abbd999cefe81839f0876ee19924223d00f97492ab4a10eb7363eeeb9d261ddda0a507fa2ae5a6cc1a8deac23143290771ac9433ddb32e4563284f99ad6236d60b0db104a44c68b078f5deaa1c030914f5fe5f17431de34c0938517cd6f2128e3ecada06d1ca0f3c853f0157ea3c276042d4c668240a6cfd5235531c8e66c881583c7531612e5d672edc65b675e2aa550c511a4903cf43e454ce7efd93c6fe7fd7b153cf608823ee8783f83cbb3f1cfc23400dab55835ad3f18e1c9b5d6f78b3938ac0bc9fefe2273e7a82320d4e8c22c76979b366cc7257260539af1806d18de9422d0f12c3d4c6de6a5a20540d3eabbbce86b61e303475f681fb0f7b1f298bd922010b60645f08205869d473825a6e015ea2966f1542be053c53b84c13a95b7f006211822da08b661981d8acda443797d7630383758de098d746797666f1c81b19e517bad2ea6b938b00ea47a9d841b16d2887bf060d49d8a463afdd2ea61a24281c1fbc31efbcd05c764570967f81d4943d246356117229c611e9ad522539b28c930288cabe7ddc1cdf3bec0500b4e022982e5be0627879f8547a189b20e90092f10263cce9a11963cedc36fb2ea92836daacb71e39befa205f7a253e800784089dd6474fc36a2f8b12d230220c6d0dfacb13061651536cf725a057f5c7ca8cacaaa258deb2114a4c79a097ed34fbd47fbad54bc5330c5fff4bd76d5e77064272e34f3f13a87dcb6d60afb18678d12e6813a5307c3a4cfba49a59e7216f7168000a32cff1f5d98bb035aba353e91c9955d90993c9f6a2f967747c49e095b859dabbde023ad43989285ea7cd2297b62b73d3b89c9e79a62d67c11749bd0c7cc0158b097eea0a7cfe67680de87721b65158f51b8ec72a45363ce9d3efa028838e9ca0ee762d98a7b1838f152669fe233666943d5f51aedcfcdedf90826f17e71c7f3ab89a3f2a04ee459662a04e027e7dc8a9d251837831cf0a33bfd14db96dedb1ed4c858efa94b662689d58a8d315190308862667995155c8cb1a95d0490e6bbe8c807f76719a10e2395df1bebb6a844d19dc4d86c98b1327c1e14e2d466753515d3e7a3cd127057142a3af9e8340e3008828f0e1219c3668f52f6e4dd8a155b01e8b10744e716075aec04a49b438599fb30fecfd508cbdce837b73cf879d23722f07f2bd22b718349f3347d1db94eaf42e36c6b576c59adaef497d1406cd9621295397c58b44aa236bf724dbca317ef0c25fecd89c8501d66280f69b9c7d4029e0d0de3e031b4298ed1eb30e5bf67a90863953d10bcdeac2fa79a3e4fc1f560b9c4840911c981bdc378a0c2b38084f4bfe762fe17158c1e1ed8fb8079557048bf0b90409cf10be6ced7f99ccc8027bcadf3a592d252e38726ac4caaffbce70a2b2ca933eb83b9b4b767662e5602fac6a6c4aa8931ce1a5b42bbc78897e1ca416c30c54d6604532e25e76378e1d3e687ca17b2c8ac407801150b1b7b87597e8179cab00dbbe043437566bd44a9b3ce024ece11d56299bcbf536248b840e3ac9377114e1eda07019d797e97a7cad9ae0894038576bfc4222eadfe22216dedf354c1abf1bf14d341f2c542c0d78b4388b47fdc754bb1604dd47c7d39c50896e7e5facc4a003afec4d9a7d1664a39786bffa770b264e2f81ff4bd69429e14ecce868e5ef9c918d1de809ff88deeda5dfd2ab3c23e0618b0e945bf52504698bee195ed4617b2165d504dd36819d63d83eb8939fce034d7eea169cdfccaf740b3108245c1b543b808f331db3cef40e5167ba3f86aa4129eef869c71fa61c08fba948975172fa9ccc779bfaf4fdd4fdb09e6e7893fdcd1303dc69fc2fc47ba29e3cb7ccd1fb6a96c2af00f8f47acb5af41d6f321090934ef9f5da7c31aafa4e7aefca60e08ba23975490ed5d84b4e28e082238656a1ac6d7c003318f123d5e5431523622baef2afdfa833b581851f9997e6f0677928aa88b0fd06290f057ff079a213b732d6f98a4643cc3d5f5cd3c1b4814642527df40e92f0f967052f66b30fdb823b30cb8a23054b25214853866031033f0f0786f823788069076afab7f79664f1239f7cec40b70068e93ac98e2880238c019f093b1cfebe58bffcfa83da7fd6f6efe5bb4597e49ae0c57c3f2cfd9893281075664fea4876253871979f1805341eef192b0b7646df0dfac2710c55eaaa54e8fa0edbd6812a06c9feed2bebb538473e0fd751b2d50068e828c19e60fc920d3d09ac25e77d2ede88598d207b0e08017f5517c1ba52e2f03c33045abc40af91ee1c3b2033e9dfc066662799f19a2f1409e2351cf3083be5c0a64b97289e7d03c58bb21efc4333b3665cc7652d661aaed1518387ea0c0130fbf50cd01479316ab748f676c1e62b6197b98109f57cf6078ecdce2f0511227e01b5ce521ecaceb9873aaa832efbed393b416d278b33060ee08c7fa2c8eb71a2a3a03daf1104411000ab76ce93e10eb346d89e9f44215f14e819e63c91a1f56c6ae3799abe7d5cdaaeb79a3d97372e515210aab266a11fd017513c07647654d96cb016c5f93a4667ff93fcc9ba841df18a2d1f911b6ccf253db20d793ad73700de60125d36d2fd1490853ca3810c77562593bbf3fcaac0351b2f3bc1d5f1e09158c756bb008d9b1030b933d52a0d9520e8f33efc1b9c742ca0e580f836f33558cd5e4009d90ff18becd5461d37b8abc4ce357a47e91c2ca5ad46cb355d63cae6244e716a2a9d3c408b2561bc3c3c02b5b49ca1cb61d656d8cfd4c92718ce9363b34d916d22f2f9e0f6a7c2c2412dc399f49663d50a2e207cdcbd2bb70c927184f4d97d6b9e46ee303c60846c714d13070f9e5e7a68803f23a658a68c62a107c6a46d1393d28d2afbfeb6fd8b088f8a8489f64bd4a41d05b8999c0ac4243d1abd4aa137236f90354ecfa2433d5ebb90a04875c774ea8a04d74b0cdf170757a54780969c48de7962b669822d88d6ee88f408e3d3314fe35d9bcdb37e37d3e0ddb723ff0038af0a823e986d37482fc77f0876b6e22cc0671d5b3abd2d757e42e42bd4419d58a950d8c9de71e18b8f635a6ddd581486f14b1bf1da34cea5155809b304f1698b999d9f786e8510c90715024a9049d7e59e818abd7c307f86d60f3f17b5094fbeac1473154314dfbe7f8a79897abbb9bef9af0b53e5a86c88c0b3a60e9f265b965a5f4099776dbbfe11bb71aaa9789b3da19825df9f76430ebc49a6ce41f23e6db8dd73ddf305be76b72679c26036954df5e846ed5960dee98efb9d65d0e06e293c69803de5bf658c9e3143851da374475b88f6dbeda23076ce08b0b1f4ddbd5e94c6f527b0d8454997504c74010219d2e1ed79a6fd04da1ffe4ad19e6721f551e9c3276b4e1c006abdd876f4cffe1179f916833a77e3f71ebd6b563f8824df786d5f98678e4f287aba551d67ffcb30332f3dd6758053e6b8dc753bf602e5036d925518af274b41f4173890a0fb6a08cc479ba94848a3190019bc2cc2553764ec53ecfddbf7a792f0da5ea352ab1644482efb0f840543c72a2fb67ab06b704ef3a4911ae44fe0d550a57e39f16c4ad035e24aba5d22ba0b7231b036d52f3d650ae5cbd68f43303ed0c651b3bfb7d06fd8cabca0d33405a840eeba5a94a9d1d74deba1e5ff587f1b273e139c13a97a49e30e34ad0e7296024ad1aa8a6105ef5329abb5806c94b16759a609ce470edfd7b5c5a9628542aee0678071e28b2afcd037e54fc92a96a898b8f0905bdfc394f5ffbc2404133452746f8d9c438e86dbfd35c25da1bd80f0b9aa0126790ccd42bcea371040a6e1f543c6ce5446c95222dc483525427beb00d4d33e09257f703875ceeed702c68b315a560762277686228b7f2567fb9d6e306851b5fbdf228f47ef30233705e334c3edde1285807377c5af8c3ffa4987380e17ded5ce19fc396955a7d870bb9e5b45e2370eee8533263fd39c3513903e519a0b6d4c8bd75eeb397be473fcabc94b35bb3c87a8f8461aaa83cc0e343ff540f4e197f63c717a65035dff43e28da68749d2bee45783b2ca64afeb40c0f31ea7e85977e1cb6ca8b5223699b414768bb0fe628e2e3bf02e5d07a0bdee45cc7b6c0ef4642d8fb97ad9f77ed3ecc981db5f3a02cea95440970a21d2fa2fc90c093e541b70d3d1bec11f0a9436894599adcc5f1f8ddb9e179a29747b6b273c5b0942d0c37f424f84a1b2dd18af3fe3018f1ec65c99e632c7592a52b4c9396ce976ddf17f328ea0672716f2ec6d48a147edc03031d9851dab24ce7fee630eccea7088c0c642630200034d786c4c70316169588c04e4f7206a52eb01e07e910109a9b8dd0030a2e643896c59a1dab25086034dfe6984a64fa650a95576b7accbfbb1bb62664e7ad6e696ce2398b080b928f5fa06a4361ad870cafa5b2cf7cbe08ce032e9bea1d14dee10fd4e10603300000000000000010a681d008ddb1b1238cf5a46e76199f5139662813dddec6a57b4c91a1ba271087e42beeb6067080dc5d0b5c9205d65b8a6aaf250a4f4137c1fc33cce0e6e77b3cc923f0308de162b3830821fd8bf4bd4208e0123abb8c3c84ede176b905a1cfaa33fa4c02be83acc6ee5393708e5ab7eda2eac5fec183194e76cab1743a89bbe0c1dce8f7d66d9dec6eb6147c7ed42ac72266408e2b2f6fddc1dcb28ce7bc7412e7298f853325f3d665ae167a165afbb3c9ac0b0385ba8c292a3ed9cdefa8f9da87b2d4dc089adce1b53e5ff9aa888def60f14f081c0e9f465f5133814366323f99cd0fa97ab1baa4e26508e735f57e78f552283ad7e9f7eba6758c5f367e846d035802ae121e65cb6748750ec0465f8a8e4ea3f70f1da9896ad8735a48877af78961f940e8e9fa83002b2ffeb586f10e3ae00453a0259fab91970cc540acc8dac003a5c9bb2297c77d7c7d60afd9063664eace8c50ac5e4724e48dd090007557163fae9b79e64a030a81d94d1a2f6b8a1c8abec00db2ad240382d409ee7994716cfecad4574b9e8db72e8a63c08ec25606533f2832a8f12680c0c5ae28e841600ddff8694621347432b5f15fdff47af05de9cd4bb251af222ef39e3fbaa46faa31dfb02564f511d4e2007728295eea7901594f235154d745f42855f753dc981b2ec0074515a8e515114af3d199980e35b2649d5342a33cc74527db826b8256e74a5a3a81204ffd3c205ae31603519ff741b2774767414114c342c5033b8e2ba6a6644e74e40551b739851746fadaf3296f014dbfb6df72bd751f06c999c87ed34db3315d2e0607f173b6d0044dcb8cc8cc60dadb7d91898b9dde8474b7a393a11acfc4eaa788a8448bdf8126f064916c22233aab3942d6297e8c2a1e5231a20f272d0e0b1f9ea3a308ed96f88ff66f18bb8794721f5b6d690817787dccd39a7b2d205b0b145e319d92ef3ca0e8bba47debc29a8f4f77c5c0fc790522bd73abd526f9c157a452084ce77499e7be6f1553e51e3302600b52b070a2aef03ff62f07948ace88273073f08b7a852e7cc172f5b38bb4426bfa1718f137acd2ad75c15d059360aeaccccc3a7852004a39e792312db8d858b911ed064cedcc3d676b512ada00f1c534666757222919251637bf7f6653b18933155f5c5d2cf955725e56b0632dc5fb018a7311d954a92236672432b2512ab22307f28f4aff1ffbb9f2ed3aebdb57b7275a0d429737f0493ce071ae4463f624d69fdafa32c66e8d964d44cf23875b1e2396694afac493da162891d15f7d32fd502a7c08b2275b5e4f14aba32444a6ff40cdb375809cf3cebaa94a0dfd4ba288ac62242606a176370849b7638584d64d9ff365b5344e77f6ec60c706a2a87e7f3fefe6f73504297fbb41b2f1962d2c6d1a12fd76f9a1a598923396b3dfbea1a991a4ce6141e54cec960831251b676e54074335c8fbf9855322f2a44933ddbd5bea43e5a92b45edece83c86ab6d9d474cb13a1f326a658e61b5275ca8016dc0a209644885339fe5a6c0e3432d0ad9206adcd830be0511af156311600247d61e6e6f71a3c26374fc442f9e6e6c2becf5752ab986630f15d3b0324e015cfe05fe7de67ca5a822dc589ea2f8d284b3d7357e79de258466583d81e2aa117378d221bf004236528309d9ece0cfcd34ae0ffc473156aa8d8081289449d8d8de7d6ceec005ab79e637579eb79c1acc53f1ce58c992313433dae79312c712521c2a65a432453671b8fb387f87144fd57f4612e387b77f96e4734396a32d4d8911334bcc83e350a922772b447e12fa54c8f7cf94f7365ac28a5e0ef2c497282a197be05398b23a1b42f8275e36a2cee583fc0b22382ca1904b43d63169fcb58fd22c54651539effee7852f60ecdfc29baf5f8a7778e05bccf375d48a62f567c586bc580bbd0036ea2508c00731e160f5a3bb6b530367ff24f9c2d714cc04983b9c5378b33098b7274a8270e37a90e19e19ff4ceb3af40546424e8a08ae24afc8254324772b5c4825f0ec230201760151f971f69668a3d1bb591075d054935b685c088e05c6e6391199695b90daf349969b89c46f908d3c6f49b8edbe980a26a1e4164a56fea5456329d41da0b8ecdb74658ee55cead4ce17721ab5127b25af2b9a8a4cb9e02cb38503c1ddacc29668b4c5d9d99a74049b730d6791e486c7d510a7f4ed1e8cea648aa40465bb22aa4c5a6d448f0c3236617ee2a1dc18f75c4703c667655d88f8b4f6470a8bb0b212e8b5976e9a4484204db6090f6fb93afd273d0f143b3655b0b45376b573b3d453babb29eacef27e42751b79a986b4f05cfec8c9c73ff8d396e53a0e0d6a8156551e0f854984ea3609fd227d9b1930ee8e09a5a6b12c43b63e5ac8c9106c752622d549075693697d277400f6a3203f1e8b3c3fc368a013a1b6814bf41b05c7409dbaa95d8829ed6a5cbffadc128cade32961d1739e7ce48d66a9430f5b94001d39e0de8f026fcb3850d36aeebfcd6b1b02f43bc8b7753bcde20a318bc4d42554e133d32b7a7e9dd6057b0c17c1fa34ef0419a84ad0680c420ac39cf8c1f47ae55017c1d13845dc1ceadb8a0ef421a1d1f392f028b90e3e7d0aeff2777b2d6c57413441d9f2d0062c9d99e44bd26720317e20332456e726b9946f25d1dcf98396588686509a06b5068f2a70f9ff7f27e88261a762e0a8bac83b103494d40aceae4879a687e453b055af0f5cb3aa02547563921987a34bdea18c5a139dc41da0c3a61641d95be6c91c13c774d784a7e48ec0cbd2e212950191658767c07843c56fbaa12cf3388318bf03c3b2bdc782c9d6c57bf491246ff7a1bb02dcc7452a52eefd30014d6cf599fa0d3c55b660edf0effcd8bc18627bb58d704795b69f91215b9853dc2a0955bd9287da6e9a2263e0145c223aa941ce85192ba5d5ccaeb868160f5f71b497ff626d5b420b49b015905de5538437afada46136e9346e8cb266d7c9188f8fe1e75c39f2749b0d80c7ebef1406c80f33c362df1cfd52d7d1de42f714e9117207103f966f83a516e45cd79cd71238e294ef2de8eafe6104512fee9e2d9427ba658d3b8e0c02f60dbd66ceb3cb8b688dbe7cb531e7c0dbd0ac8018651520c968033e8c74de267859c1ecd3052712556e918257dcbf8337c666f563a79cd95c2d48cd83005ae10cea542044d25ef5656c42ece886a12596df42ce208b35ac86f25bb4819f79135196efee8a46ce7db683eef6a0680554964cca84407dbc35c68e88fe28df14c3addfa01786146f65803ecb31deea8aeef5a5f194a77e246c33911bd2b9ee1290a788c455bd4f71e405dd78a06c26013b4666717152a72d46796c07074e9b728946b18fe9f63c99fca30fd0c30c03c4f632aeaf06f1a75fa256d3f409b9d6890b9a0247de292e6eaaa1c41fbe955e33c4f3d4bc0b4a6f0171420f36ec93c70496e6d0a2c8de6a8717a324f94fc39e69d3bcc6d3b61d1e7331163c84b4da8eec4cf92dfb01285d64feb59ca35861ce6ba5871829a0f2e956f34b98c8503cabd2906da05c942e0140efd5669f891ca6abaa01e435d0de0d1afa9e2969a899118d70037ae811c992b33111ab422e0aa1a2cb3ec24ab57da300439cc9537ad37f540c2d6aaeb30169424b4484d5859953817e9139f0c254b683dd73b2cd770db2f7646ac91749f36c6dc7a6a0e22d01306bf3b81c867bdbe63ddb9e715cd3e1d933fe609422316c54745c938c61c48cfeb9b1986b28a0ab29d00aa03a5a5ebea9a2066c74cce8754779c98b6c9363a2e0d3aca909f05decc44c6136eb99eece52632bfe0490eea1bffddcf9e582c00e89c4f60b7a6d2c9ac3aa1dae8da2d86a3805c105bcf00a6e69689395e1d4d663240cfbd68a7ecce9ee04515d169629a4597eab8c00b3a03d6c68a5fffff6e73d4ea4600855b8ea0c8723dc9a3677711c3bbf068099e8d037f0bb71a0914305d88386d8cfb70cea5d31048da3f859f9a7a96b338fde70fd2217428daa93c73565dabfd9ca12fa8af18a89a1d95b1d9617d1812e5ceca0cf5410c610ce9e118e1796101bfe10cea9bf8aeebd876cc8f9ffb1fe41319a0d99e932c702662d93ce47c77c26b68b349f5f5c59bf4e691f16b8130a8f4eaa94802d85f77c8ad35d6a7bec2ec8597e4224ff497a90b70634cf0df2e03c3b21795e88a7a9f035946c556777944d8a231643b509b655a68449fd629b14d045cd3aa76903f10e69cbd6117ab4f0848c615e49cbb8d1af3d46552743cf0b67d2a1aca8ed4591e780e45c71449f6560cae7ca7cd771d2b556a20006da80fb0d2c91a28e7785e855c07b33892f9a5ba447c33af8c21c3add8531b94ca2266dba2859dc84430c5372fb968e4aa673df876e1dbf58d8dc5d70dac0fc58b7e13e4286a2e0ceb68c3f43e26e131b767c928df49958eb0a87bfb68c438a4a017f30996a4c6638f7f72f77d20ee5257d50fb4f12ce7cc8d256c7cbfc59fe52b2a784ca28d3a1960be4d15002530e27155fe641d6b03755a2dc0a16398c40088ab4e019a52ba79852091ccf3277f028812e4ad1ab7accbaaad51386457b1a13a4bd69dcbaba24f8479d8918ddb7e0e9d3f728a7981481efb4c1fe6dbd591d2da7fc9aab77cbdfb89326f33db3e4beee16710a936c5596a17c8da21c45f99fb2a735f47fab719009ac98a27787642d2b8cddae7cd280c414bad9db1c7e097a78b227dfd5695c7e8e1852f68c26ba55672ca198c229466ed479b0b54c5d7314bcbd51cbc6f6151ad652dc38650b5eedd0a28933c2afe7a1febc351823fe9a987cbf13e0d980129a38d7a4c8d6086315283db94032d84e833da53d72f14ee0b51f1e36b5d71c47d04356d1f075247fb600ac215206fd07d1b30bff1bd9b21bada456dd11a9ca6819b017311102a6562f9afdbcbee105fcace44e69c32c685e10997b10129e0cd1ebb6248296ee4ff8340529c78db64af42b7109b3e3b5577f3a5d8ea0f6b0eeb7eb8ab09caaa5dd79619709043ef5a92451f634e6f58625bd0cc1f8daf143471f7ed649f035149cabcef070da398eca0928a63dd4320e4f4f91b1385992dc036a8b1879221a0214b9c6db46d3a023707ca6f8749213b0c84963ca1b415929c3ec60f293deec9b694b691c826e5cc999582ed50c21fc94acc227b4f945e3cea5d92bdfcbb7822dca08a2e1b138e290b912cc4b5b1e14eecee115cd840d66aee031a12d87a2579a7fe139c21bc1b69a2e7d82e5920696ff37ad6db0284637e46f725b9a3147098a545bfe881e191bb4bfe532ca8973db2c525ff475615f179c3fc215e5750055f24d80f69474a1505eb1f45a87303959539322252bac4c0061ffa7b7dc5b11dcd35497f325c986650cf4bc2efab4ab2acce3be9f82f71d3b3b5a5e61c0c6fe8e788e23aa18a4ceaf35b3c42e270f2dd2a5f7e549bfca25d01ffa5b695d66b7fea8d9a8e19de471bc5d485c53c41a000796077440eecd94d902815917b7908cc49df5e4abdb7bd6d2c9b7730247c32cd658ffd87fc0d70bdac018811807ffa349e5a1e60fe265fda521e6e6096e2ddee666f86caa4ef2bed6e2ec9f57ce13ae3e5405b5493111e2af4996b73dce964498dd305b1123a898f771fce00772ded3f37f460e3521691b23441a23784eae2dafe57c8d3d8f5de081996b92bf188d639f2d50711539ed6651fbb3a737f352fe96f6a9306e559d0790e878acdc2e1623576558fc252efcefac922fb726f393b141f7d9a576ce2a06966b545477e5147fdaccb71e5ea44c830deb31a87f13385072385c332c1bb519140be7eda33e0442d8e311a94d30a08f3d60f24e812e2865e8947d692c921b68b7400c758fe43dbf8c37ea859278061544db0aa0755108e6f90cf5c4f2819c96302000327232096d1a82ecf61cbcefe3f6bb4b2cc570beab4cd6461a9d99d60e79d53137c6075e0dc4b055ad55aa8bce73a5eb67c780b3bb548c76957055d20ee484652dd0d58b06a9c5bc0453a6006553d7250b9c9378640113ec0529fe9a84aee08c0fd4e1060330000000000000001a17bd801fbecd6d44aa5c7dc813891fb97ad58d073e0f93b72396185b875617ea296d3feb204c8eae268302f29ef06c3f202a1efcaac3ff2f1097413b604b69b12973e197127108008cc904c93a533c3f1e057a21ceabf89bf3e63d38aa64511d918adf0fdd4896ee71bb9faf59c114bc44a2d1e9aa120bbd144f48e3fffde961e0e6db917ecaf6d677d49c58e63079c704b196b4c61690258dcc054f7f874b0f52b263753a611a4655acc1c98ce634efa94c66bb93f1d9434b68902be611796ec818027a6d43765dbda57dcc34f1aa5adabbb6865ace3421ab2d51eb62f5c732677e6bdd7aa7b8fb0789562fdf70770847b4b667c84a7cfd156aae89ab76d7035af3c588c4905fe0735d093d7e15d1ea576d21ad6a97590fc5313fd6451f4c627456c4cb8b49ab9ceee34ebf787abd148058bd47c890431aa513bd84f8accaaa59db7c8e412913959405a0d56ca54f364a68fb13ebb95fd10720b8bcf649817d23b9c151fcde394b63b25704c5f5f54f3486606b40fb0d1ee430b35949b87985c19f48498dcf4d52d7c61ebc2bf19846b6b0519fdc7758fb836bac55c686bc7d78850812753a51fb4da8eb8aa81cf676c43355ff5682796514935e0fc114a534f6cbd5198caaa04fef34c4e3105e6c80b7b0c7a3f9c78201ad7ae84eea0a7a8822c7966ddb0b63e67365eb42579da8120f31676619d5eedb33c075583f3dfd4de8fec0ff9f1ce0d1b0c64a9a14a848d6b8eb44371ded67dffb41137e9fb72210ff4574c345bd16e650d2e5c9d05847f0ccaceae3db51ca74227ededc05c203e7f89ce8a5e0d417c90542ac2df63e2c606c73020e33e3cbdbfda2fb75399722abdd43cfb4846d639bdb84d865055eae7e239c0cb071dde8c739ba2b9ae9887f94c4c94ce671de676883e3402e61d74c27622ec49b26747c6729f3ac7c28222470fb8af9bdfa664ae3f15b950a9fe62179ef1548448a739f4054b8e6d4a2d7092810314355fb5f419c8bd7a2c2f0a9cb822242e719c684755ef1bcce13328f6be317b9e8a1a86b5d3de65f288e5013b8eaf839de472b202c66db5dc9c974bba5b154a58a2f67f9fe33ad646b1c40c9cb0ef8c1715ec18a24acee922bf4f0c0fabc3e43edaf858ee24b23ad1a2c01801b0eb8a54b5b14776f80b9588d75e8102b49280e9a7e6b73a205599c1bde71ec85112bad7ed46981e2a7a5d5a7b67aa22e56b8cc2e1b67a155bc6767d35e7d95dab119ccbddd00058ba365b86511206a51fa56f4fcc838ad5a1f91d31826306acb351dc36fdf5e58026c65b160a3159abacf241896ad2a4bf1db73555c2a688b901c373b3ff986b19d1b42ee6f945e939cbac8eb5dc55d9ad247e954eb67b129d8a9625a2eac628b67cdca3acbf8333a27304e583957ab7ad11fb88db233b0372c5c7cc9a598b44bfd87ed64f6b80fe3564d9e24aa87cdbf5da3b01bf15e9ba1477286bcaee5ddd908682d24dc05e79b10ad6d9c27560c43f71d2d8bdafbb709c3561db25997ea5ae2c08f71148f50879cec08dead14638f415dd564d7c1d05455883a3a84c07d81770be34059e290fdd1a854e3d45de6b408dfd3ada2a05795ddc8a63c7a55a53fccf6986476ead6e703fd24262523f3069f56a48ef751c442ac901fd79b0af6676299dbfaf60b0a541f69e7482e9c8290e481ee98e14b2ac4de5c9eeb0601dbe3d19fe6539a84f405f9d5a432975a6c52aa397752b39ed7db65775c67a68a534c33fa61c22f9ad793ef0230fba4efd1a611a9776e39cf506f9b33b509dfa71242444106fb6b0c221125ccf82065aa06c6ead9e21eb0956ccfb406d71de0e9a53012bc52abc210294db90672f7d64eaa95eb122d5e477a79813a2db5e3982216b86dda6a2d11290db2b1af4fe693ab98b1647a7d934b192b254fff35ea441d965642b5c15d4af3c2996a627e101b379d477fc679632d74444e8d02f66f6b803d3bec62bcfcd738d0bae5a899e0ab85a124cbc6c0ac14956add647a0fe98a0ff007fbde6cfd5aa476ce0efe263cb742f3514ee1f859b15d9e45e8fc3bc6e905eb91817ddb1ccdbd7ed08099991139e43faa20bfd9577166d6099f28e1b69a5aaebe07f60ff642ed772a3430fec167ce63b71ae391b7924daa992e3137cc1d34c68b554a20bfbc50632356a5454246057f9f2a703fa7cbfd519e5a054d4bad914a5cd97a15cafbcb87a1c8f78119c6b8c08f06466c1dc863a5761b0c05f6535701689ec2e6f69893e84643ad41976fc9986ec505ef42aeaf0583807f3b2d713840e3636806962ad4a06460558a1bb04aed5c87a94ef1b9bb551a3a77df28ddc8a673ee2d5dd9f665703ae9619e5384b5bee10b3ca10be7e24d231b8de242d7f6fa759780d825e053c902d814a407408c46086b66c1e3c9844ecad6cb1df93b1f6730f03126eacf34fa5f8d2c2473802ec07f9c6a8f56d125ac59860240a57a5fe9d17903ce3c01d1e0a88798749a920092d44c57707e5c7f2431957424f3505a88f9fdb34b1ddbcb3ce25c82fbd22ae07eb5baeb5cf440fa94ce8e1059dae595fcf93fbf491fbaf032db776c62d41cb589a22e542c8db63962faf43221fd1fcbc47740f0252692a854e1436d159c53027d1c2e4a8a81523b999bddcbf56d6f5d94118c2a2d8c7edbe89132bfc50b1f95c45f24b9641a0eff4210fe5b1e1636b485b7e252084724e0a4a7b40547ab6a44500f8ebbf8a7ab1aeeca93373a479b70db6667ccbb4598d35c0891a3ca7535a9090c0894df776dcec823c8a5af8481ce2bacb857cfeebd8b08d738f00f93912f804561ea32d86928cf622dd250aa1e3751ce48389b80c425a53aec185efdb11e1196e473144a5a26b76cb31aece2f85fb4ad70c66e0dda82acd66373a28769de943705170dfe9eba298270ed6a280c8ee36f6135393d88b01396b78911f6b682bd99f23221cfec36aced43b4d4c52b872d29cd5ec4879fb43020fb3bf7ef7635d797021db7f3fb8ee334f2b30fee5ee5a666f3b154930418e090ffa39affeeb086a83baa760c47f3a3398389aa77698e829ac153c45ec9acc3d19d5b17a71e9fe7622d8133281083565bd71a32c5978cb7e91bb6afd83359d4217f9c50de6ea829099ea1fc011989e88c648c6a79f28f1733a11f7c1a7962cbaaf73710951e67279c79245063d1a0cc905781445c5bdc90979744287aa5b80f367dc17654bf3b5796cac1b027c72cdcf79b12b894e48f2d15c0c9880d64f4799af9a711001690d7c9e45cc5f1b6a9f2cd07390692f54dc6fe7e8584601f10ff5c33c4a4e72165ec5c4818109b89244b46814921bb231cffc5a281f98e4332835e1715038a1dbecd230f06f48119753da38784f5ffc5d7e7d89aa1bdbf47fbe3bb04e1b83da4199f6b0f4bc8e5dc58b7c961e0912fe9cb0184860eb5da860ae08e67f4dd1544269bbacddf2f17757e0d86f0d0dce2390aa2a9ff98dd4a65052d2bcb5a5ae103e6303ab8c92694e49df9b158b2b0e3938d2927c2be9fd2eeeb152d24d13cd4bb5af104fe856017d315a19348e8ec417ea0e7d0a650d845a562daad993db81dbc7138ff2c86172e3d2749f69f61d532fbd99f92897668f4ebd2d49d590cc2486c83cc9b8911286b6e610e430d3bb2c6125244e2aefe7cf124455ecb159be1df64066f36317ea8b4146836f71d351613c4e9d3c9f5f0c9f5cf99010697ea550d21bd74fcb9f9582c6b958122672fdb90183e446022710cdf9df0cef3e15f6f29025fcffc2ef15484df2b001cb919be46fd6821cd4f9c259859b600e61bb020338c9ce1ba362930165eec3340aadced8be80bb41db674f1215926e2104930a36a0873405d3d19dbd34f4383c5a96b4a363fa90f25da756768dbf289214db61632aaa0f0b4c39100c209f9059f8c840133d1412a7ee508f85f9286a7709a40b8bd1da7fae5906f5090e81a090a8ef689a639ac14b9f7e1182db7d6939c5cab757b2e6b5e384ece941c552f6f32dd5fc9d66752c1c1808531f31e039841f9cb610082b49fde69268aacd2c93db1af3e1ef1c2be6d124585979ee53bb39c4296f34356cb04fd3605a436dd39b6268e5213cff6f8429dd9eddb4602a6353e14b625f554d3472ea3afdd0353ebab6d80dc5ac2fa077d75a7d9ee224dd8a97a35abc1dcb4209fd4df8c8137a7e25bc46c6d961fe480da57638e9c82d5b98c0c4638aa18f2dddf26d52c6da811bf40739f604068f5375f21c14723d081a482b83d6e258dbffc12ac16cb53fba4934f3128f6b2444a7fa6582df34045564fdf17150977a4820a9c842ffc49cb4b112f17d6caef48a6b1fd8c15e61dcce152caef791c1b537f6bb2796bfc3961f3d5b1c487ab00e31399278fecac5ee64501079a2eb4b5c36175becdc2f0eb4f7ede7fa6591b4e508303137675dd8b61951f7585807e4b00d0c71ab87a7a34f314cc1208a306702a771110ac51c16e8ba7f68c5328f3dcddeb569351e8d69034a6b2c7c9d2cb42f9bcc6c67dad7d980c9c350d46c681f0c1498a2b9497cf30c5e8d6f1602107c52b8d07538579e2a5a5a8519c17d0466a6f5dd6a7e9c7267968e556d5728859122309dea1617b9f4d3c04c2d78388c7c00c38d01d17d619ebecc72e1e7be03ae1528b0a985ffe7f59ca0dd8c967bf13569b63e1fb093191986109522e0a942c560f14367d9f47672bbcdddd11a759042c0194e8e8924f5ba95cc2281ddf430fbe7873a0022d23b353a87f025562b86e3ab7fe5598e4349b62671ed0171763d75c972b0f7c1b6dcdf661ccf06d3b9f811021f7359cf472b20b49254e89945fbf7d86cc5e3863c415d61126de01401e5e3e1d268f3a2dd53edafb71cec77f0358bd1633194cfec66fab07c9f7232425dde93a97833a512967e50a21e515e053415ef42953c0fbfaecce64f723fd7c2ac67e5f2ce642455ba5de2fb14f6f4892d6fc6a173e2faab450e1a6db158a78ede3a1f9f4682275be675e82119a2139082d3f56afc000c3b72a644ae6d45389b1afa4eafa9037ecd3d52bbe469c714b58298f15850b2f8a31c7f03990dfccdfce485a12ef5179ab36c5774fcbc1c745b2062ea8a8550ea6fa8c9f97393137aa4b44fb3df98cbd4028c041cba5d82b1bb04f68f286853470e1e4c25136c29e2441292d0d50e100fd00406d3144304215e4390403c524fe017f4f9b2ad509f9b1d230be16b47d1813700b5a44cc5766510792a13191b2c033ee560f8cb369ee8525b5dff14a651130d5f3f36aaa67124cb3a2f9024783d4795611421167c9ca8d3783efd303591f5da1cee431d7eb425804325eed8c7e8241423669e6d0991d6db640c88d52c12177424bd50f56f2103b1dfea4648943b98c66152c12639dea44ffd06aac91ffd6398a8998ffa6cdb686cf2258399c0aa96cdda90a7d3561392ce061c6f2f7961ab8d6ec457aa08b3c60fabd338639e006a77f59b4d7a06c9d2125c92bddfa0b748da6f5d58f7e837f4f40968cbb9f55435cf086d20928834c314d84121c1dc9aaf40df8c26314dfa0a6ee2a64404a617b5f28fd84c9529025db4d14b4f2a8fd913cb341a8a1e75af6c344045d70acc3f9eb329e7454469ce2d5802a018e1924851267a8cb1477bccb8cc85f07cdd729fc15384b26b0e681821360266743f72a252ab0f4ff3b1bb81b4ea73fff7105df82c7c197594ccd192e0e07e562361f01d66b1d64a39bfb08b9d2ce909439879323d86096852b4ec291c6639bd37c16dc602bc56932ae88110deb95bbb2c0de30c5a890ca546587d482118d80ba07e4e099021aca2071f9988c955cd4c87c938cfd0b1d65f6a58401a1020b564864076146061f88f431849cb3e6fa76a98ec9fb5b80472677d292d9d2ef3b7168eb08a0000", + "utxosSpent": [ + { + "scriptPubKey": "5120900d1d75269396d4220c4529527dbcb746a6093c7209cea2d76a87c8ab9447fc", + "valueCommitment": "083696f3aefdf18c044cab5ef189c575d79a096db1c6bc17f152cdf1fd3cb136f3", + "assetCommitment": "0aacb400cb406593951f7f7e61b63f10db73ee63b861c31fd266cce84f145f87f2" + }, + { + "scriptPubKey": "5120b850370392dafc3e7d02db13b447c927e98b6439681fea582401e570904cd6a5", + "valueCommitment": "097dfabf3388968a388633a058784a5bff414a53801cf32109be34b92123d3096e", + "assetCommitment": "0ae983aaefe54f21571238e95a93a8c965ed737bc217ea480485be1e1969adc279" + } + ] + }, + "inputSpending": [ + { + "given": { + "txinIndex": 0, + "internalPrivkey": "fbfe896b002b1e98fc9f8aae4479760d3fd9f145bdaa9b6da59cb069cdc7539b", + "merkleRoot": null, + "hashType": 0 + }, + "intermediary": { + "internalPubkey": "03f658082d7f5ec466d61220aed1429391d7bedf8f03428c9d7e4062d80e37345a", + "sigHash": "2c478ce6d5637e0ea8be37a53090e0955b6c501773fccf6738520cfcc5442150" + } + }, + { + "given": { + "txinIndex": 1, + "internalPrivkey": "8547f4b1ccc5888353bb4781ab9a728fe0be2fdc30aa0db137d234723917b9b2", + "merkleRoot": null, + "hashType": 131 + }, + "intermediary": { + "internalPubkey": "03efb5fcad2eae616088aee84f5bf5ecc765cff8d7bcef65ec217eee817f0d1e91", + "sigHash": "00df8570ffea8d57bac49f979b64c6f0ddff24977608e7e8f9047a5ede03af96" + } + } + ] } ] } diff --git a/src/test/test_transaction.py b/src/test/test_transaction.py index b59e2fa9d..c9a4be4e5 100644 --- a/src/test/test_transaction.py +++ b/src/test/test_transaction.py @@ -10,6 +10,9 @@ TX_FAKE_HEX = utf8('010000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000') TX_HEX = utf8('0100000001be66e10da854e7aea9338c1f91cd489768d1d6d7189f586d7a3613f2a24d5396000000008b483045022100da43201760bda697222002f56266bf65023fef2094519e13077f777baed553b102205ce35d05eabda58cd50a67977a65706347cc25ef43153e309ff210a134722e9e0141042daa93315eebbe2cb9b5c3505df4c6fb6caca8b756786098567550d4820c09db988fe9997d049d687292f815ccd6e7fb5c1b1a91137999818d17c73d0f80aef9ffffffff0123ce0100000000001976a9142bc89c2702e0e618db7d59eb5ce2f0f147b4075488ac00000000') TX_WITNESS_HEX = utf8('020000000001012f94ddd965758445be2dfac132c5e75c517edf5ea04b745a953d0bc04c32829901000000006aedc98002a8c500000000000022002009246bbe3beb48cf1f6f2954f90d648eb04d68570b797e104fead9e6c3c87fd40544020000000000160014c221cdfc1b867d82f19d761d4e09f3b6216d8a8304004830450221008aaa56e4f0efa1f7b7ed690944ac1b59f046a59306fcd1d09924936bd500046d02202b22e13a2ad7e16a0390d726c56dfc9f07647f7abcfac651e35e5dc9d830fc8a01483045022100e096ad0acdc9e8261d1cdad973f7f234ee84a6ee68e0b89ff0c1370896e63fe102202ec36d7554d1feac8bc297279f89830da98953664b73d38767e81ee0763b9988014752210390134e68561872313ba59e56700732483f4a43c2de24559cb8c7039f25f7faf821039eb59b267a78f1020f27a83dc5e3b1e4157e4a517774040a196e9f43f08ad17d52ae89a3b720') +SIGTYPE_PRE_SW = 1 +SIGTYPE_SW_V0 = 2 +SIGTYPE_SW_V1 = 3 # Test vectors from: # https://github.com/bitcoin/bips/blob/master/bip-0341/wallet-test-vectors.json @@ -19,9 +22,11 @@ class TransactionTests(unittest.TestCase): - def tx_deserialize_hex(self, hex_): + def tx_deserialize_hex(self, hex_, is_elements=False): tx_p = pointer(wally_tx()) - self.assertEqual(WALLY_OK, wally_tx_from_hex(hex_, 0x0, tx_p)) + USE_ELEMENTS = 2 # WALLY_TX_FLAG_USE_ELEMENTS + flags = USE_ELEMENTS if is_elements else 0 + self.assertEqual(WALLY_OK, wally_tx_from_hex(hex_, flags, tx_p)) return tx_p[0] def tx_serialize_hex(self, tx): @@ -581,6 +586,116 @@ def test_get_btc_taproot_signature_hash(self): ret = wally_tx_get_btc_taproot_signature_hash(*args) self.assertEqual(ret, WALLY_EINVAL) + def test_get_elements_taproot_signature_hash(self): + """Tests for computing the Elements taproot signature hash""" + _, is_elements_build = wally_is_elements_build() + if not is_elements_build: + self.skipTest('Elements support is disabled') + + keyspend_case = JSON['keyPathSpending'][1] + input_spending = keyspend_case['inputSpending'] + utxos = keyspend_case['given']['utxosSpent'] + genesis = bytes.fromhex(keyspend_case['given']['genesisBlockhash']) + genesis, genesis_len = make_cbuffer(bytes(reversed(genesis)).hex()) + num_utxos = len(utxos) + + def make_map(n): + m = pointer(wally_map()) + wally_map_init_alloc(n, None, m) + return m + + scripts, values, assets = [make_map(num_utxos) for i in range(3)] + # Bad/Faked data for invalid parameter checks + empty_map = pointer(wally_map()) + non_tr_scripts = pointer(wally_map()) + wally_map_init_alloc(num_utxos, None, non_tr_scripts) + fake_script, fake_script_len = make_cbuffer('00') + fake_annex, fake_annex_len = make_cbuffer('5000') + bad_annex, bad_annex_len = make_cbuffer('00') + + for i, utxo in enumerate(utxos): + for k, m in [ + ('scriptPubKey', scripts), + ('assetCommitment', assets), + ('valueCommitment', values) + ]: + v, v_len = make_cbuffer(utxo[k]) + wally_map_add_integer(m, i, v, v_len) + + wally_map_add_integer(non_tr_scripts, i, fake_script, fake_script_len) + + tx = self.tx_deserialize_hex(keyspend_case['given']['rawUnsignedTx'], True) + bytes_out, out_len = make_cbuffer('00'*32) + + for input_index in range(len(input_spending)): + sighash = input_spending[input_index]['given']['hashType'] + index = input_spending[input_index]['given']['txinIndex'] + expected = utf8(input_spending[input_index]['intermediary']['sigHash']) + + # Unused in these tests + tapleaf_script = None + tapleaf_script_len = 0 + key_version = 0 + codesep_pos = 0xFFFFFFFF + flags = SIGTYPE_SW_V1 + annex = None + annex_len = 0 + + args = [tx, index, scripts, assets, values, tapleaf_script, tapleaf_script_len, + key_version, codesep_pos, annex, annex_len, genesis, genesis_len, + sighash, flags, None, bytes_out, out_len] + + self.assertEqual(wally_tx_get_input_signature_hash(*args), WALLY_OK) + self.assertEqual(out_len, 32) + self.assertEqual(expected, h(bytes_out[:out_len])) + + # Test that signing with a provided tapleaf script/annex works + args[5] = fake_script + args[6] = fake_script_len + self.assertEqual(wally_tx_get_input_signature_hash(*args), WALLY_OK) + args[9] = fake_annex + args[10] = fake_annex_len + self.assertEqual(wally_tx_get_input_signature_hash(*args), WALLY_OK) + + # Invalid args + invalid_cases = [ + [(0, None)], # NULL tx + [(1, 50)], # Invalid index + [(2, None)], # NULL scripts + [(2, empty_map)], # Missing script(s) + [(2, non_tr_scripts)], # Non-taproot script (for the input being signed) + [(3, None)], # NULL assets + [(3, empty_map)], # Missing asset(s) + [(3, scripts)], # Invalid asset(s) + [(4, None)], # NULL values + [(4, empty_map)], # Missing values(s) + [(4, scripts)], # Invalid values(s) + [(5, fake_script)], # Zero-length tapleaf script + [(6, fake_script_len)], # NULL tapleaf script + [(7, 2)], # Invalid key version (only 0/1 are allowed) + [(8, 1)], # Code separator position given (TODO: Implement) + [(9, fake_annex)], # Zero length annex + [(10, fake_annex_len)], # NULL annex + [(9, bad_annex), (10, bad_annex_len)], # Missing 0x50 annex prefix + [(11, None)], # NULL genesis blockhash + [(12, 0)], # Empty genesis blockhash + [(12, 16)], # Invalid genesis blockhash len + [(13, 0xffffffff)], # Invalid sighash + [(14, 0xff)], # Unknown flag(s) + [(16, None)], # NULL output + [(17, 0)], # Zero length output + [(17, 33)], # Incorrect length output + ] + for case in invalid_cases: + args = [tx, index, scripts, assets, values, tapleaf_script, tapleaf_script_len, + key_version, codesep_pos, annex, annex_len, genesis, genesis_len, + sighash, flags, None, bytes_out, out_len] + + for i, arg in case: + args[i] = arg + ret = wally_tx_get_input_signature_hash(*args) + self.assertEqual(ret, WALLY_EINVAL) + if __name__ == '__main__': unittest.main() From ea028b4d27783dcfaf8720818a45b074c8b5bdad Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sun, 16 Feb 2025 20:27:28 +1300 Subject: [PATCH 08/22] psbt: add support for elements p2tr signing Use the new signature hash function for both btc and elements. For signing via PSBT input instead of via the PSBT, this requires that EC_FLAG_ELEMENTS is passed in flags, in order to correctly use the Elements-specific tagged hashes. This is because the input alone does not know if it belongs to a PSET. --- include/wally_psbt.h | 7 +- src/psbt.c | 179 +++++++++++++++++++++++++------------------ 2 files changed, 109 insertions(+), 77 deletions(-) diff --git a/include/wally_psbt.h b/include/wally_psbt.h index 02ed4fd00..20d054c67 100644 --- a/include/wally_psbt.h +++ b/include/wally_psbt.h @@ -2614,7 +2614,9 @@ WALLY_CORE_API int wally_psbt_sign( * * :param psbt: PSBT to sign. Directly modifies this PSBT. * :param hdkey: The parent extended key to derive signing keys from. - * :param flags: Flags controlling signing. Must be 0 or EC_FLAG_GRIND_R. + * :param flags: Flags controlling signing. Must be 0 or `EC_FLAG_GRIND_R`. + *| Note that unlike `wally_psbt_sign_input_bip32`, `EC_FLAG_ELEMENTS` + *| is determined automatically and should not included in ``flags``. * * .. note:: See https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#simple-signer-algorithm *| for a description of the signing algorithm. @@ -2633,7 +2635,8 @@ WALLY_CORE_API int wally_psbt_sign_bip32( * :param txhash: The signature hash to sign, from `wally_psbt_get_input_signature_hash`. * :param txhash_len: Size of ``txhash`` in bytes. Must be `WALLY_TXHASH_LEN`. * :param hdkey: The derived extended key to sign with. - * :param flags: Flags controlling signing. Must be 0 or EC_FLAG_GRIND_R. + * :param flags: Flags controlling signing. Must be 0 or `EC_FLAG_GRIND_R`, + *| logical or-d with `EC_FLAG_ELEMENTS` if ``psbt`` is a PSET. */ WALLY_CORE_API int wally_psbt_sign_input_bip32( struct wally_psbt *psbt, diff --git a/src/psbt.c b/src/psbt.c index da6d23698..da0ded9a5 100644 --- a/src/psbt.c +++ b/src/psbt.c @@ -4422,47 +4422,63 @@ int wally_psbt_get_input_scriptcode(const struct wally_psbt *psbt, size_t index, return ret; } -/* Get the input scripts and values for taproot signing. - * Creates a non-value-owning map, avoiding allocating/copying the scripts. +static void append_signing_data(struct wally_map *m, size_t index, + unsigned char* bytes, size_t len) +{ + if (bytes && len) { + m->items[m->num_items].key = NULL; + m->items[m->num_items].key_len = index; + m->items[m->num_items].value = bytes; + m->items[m->num_items].value_len = len; + ++m->num_items; + } +} + +/* Get input scripts, assets (if applicable) and values for tx signing. + * Creates non-owning maps, avoiding allocations/copying. */ -static int get_scripts_and_values(const struct wally_psbt *psbt, - struct wally_map *scripts, - uint64_t **values) +static int get_signing_data(const struct wally_psbt *psbt, + struct wally_map *scripts, + struct wally_map *assets, + struct wally_map *values) { - size_t num_inputs = psbt->num_inputs, i; - int ret = WALLY_OK; + int ret; - wally_clear(scripts, sizeof(scripts)); + memset(scripts, 0, sizeof(*scripts)); + memset(values, 0, sizeof(*values)); + if (assets) + memset(assets, 0, sizeof(*assets)); - if (!(*values = wally_malloc(num_inputs * sizeof(uint64_t)))) - return WALLY_ENOMEM; - if (!(scripts->items = wally_calloc(num_inputs * sizeof(struct wally_map_item)))) { - ret = WALLY_ENOMEM; - goto fail; - } - scripts->items_allocation_len = num_inputs; + ret = wally_map_init(psbt->num_inputs, NULL, scripts); + if (ret == WALLY_OK) + ret = wally_map_init(psbt->num_inputs, NULL, values); + if (ret == WALLY_OK && assets) + ret = wally_map_init(psbt->num_inputs, NULL, assets); - for (i = 0; i < num_inputs && ret == WALLY_OK; ++i) { + /* We add all the data we have and let the signing code + * validate that it is sufficient, since the required data + * depends on things like the sighash type being signed with. + */ + for (size_t i = 0; i < psbt->num_inputs && ret == WALLY_OK; ++i) { const struct wally_psbt_input *p = psbt->inputs + i; const struct wally_tx_output *utxo = utxo_from_input(psbt, p); - if (!utxo || !utxo->script) - ret = WALLY_EINVAL; - else { - (*values)[i] = utxo->satoshi; /* FIXME: Support for Elements */ - /* Add the script to the map without allocating/copying */ - scripts->items[i].key_len = i; - scripts->items[i].value = utxo->script; - scripts->items[i].value_len = utxo->script_len; + if (utxo) { + /* Add items to maps without allocating/copying */ + append_signing_data(scripts, i, utxo->script, utxo->script_len); + if (assets) { + append_signing_data(assets, i, utxo->asset, utxo->asset_len); + append_signing_data(values, i, utxo->value, utxo->value_len); + } else { + append_signing_data(values, i, (unsigned char*)&utxo->satoshi, + sizeof(utxo->satoshi)); + } } } - if (ret == WALLY_OK) - scripts->num_items = num_inputs; - else { + if (ret != WALLY_OK) { wally_free(scripts->items); /* No need to clear the value pointers */ - wally_clear(scripts, sizeof(scripts)); -fail: - wally_free(*values); - *values = NULL; + wally_free(values->items); + if (assets) + wally_free(assets->items); } return ret; } @@ -4473,19 +4489,22 @@ int wally_psbt_get_input_signature_hash(struct wally_psbt *psbt, size_t index, uint32_t flags, unsigned char *bytes_out, size_t len) { - struct wally_map scripts; const struct wally_psbt_input *inp = psbt_get_input(psbt, index); - const bool is_taproot = is_taproot_input(psbt, inp); - uint64_t satoshi, *values = NULL; - uint32_t sighash, sig_flags; + const struct wally_tx_output *utxo = utxo_from_input(psbt, inp); size_t is_pset; + uint32_t sighash, sig_flags; + const bool is_taproot = is_taproot_input(psbt, inp); int ret; - if (!inp || !tx || flags) + if (!tx || !inp || !utxo || flags) return WALLY_EINVAL; if ((ret = wally_psbt_is_elements(psbt, &is_pset)) != WALLY_OK) return ret; +#ifndef BUILD_ELEMENTS + if (is_pset) + return WALLY_EINVAL; /* Unsupported */ +#endif sighash = inp->sighash; if (!sighash) @@ -4493,41 +4512,44 @@ int wally_psbt_get_input_signature_hash(struct wally_psbt *psbt, size_t index, else if (sighash & 0xffffff00) return WALLY_EINVAL; + if (is_taproot) { + struct wally_map scripts, assets, values; + struct wally_map *assets_p = is_pset ? &assets : NULL; + +#ifdef BUILD_ELEMENTS + if (is_pset && mem_is_zero(psbt->genesis_blockhash, sizeof(psbt->genesis_blockhash))) + return WALLY_EINVAL; /* Genesis blockhash is required for taproot */ +#endif + ret = get_signing_data(psbt, &scripts, assets_p, &values); + if (ret == WALLY_OK) + ret = wally_tx_get_input_signature_hash(tx, index, + &scripts, assets_p, &values, + NULL, 0, 0, WALLY_NO_CODESEPARATOR, + NULL, 0, + psbt->genesis_blockhash, sizeof(psbt->genesis_blockhash), + sighash, WALLY_SIGTYPE_SW_V1, + NULL, bytes_out, len); + + wally_free(scripts.items); /* No need to clear the value pointers */ + wally_free(values.items); + if (assets_p) + wally_free(assets_p->items); + return ret; + } + sig_flags = inp->witness_utxo ? WALLY_TX_FLAG_USE_WITNESS : 0; - if (is_pset) { - const struct wally_tx_output *utxo = utxo_from_input(psbt, inp); - if (!utxo) - return WALLY_EINVAL; /* Prevout is required */ #ifdef BUILD_ELEMENTS + if (is_pset) return wally_tx_get_elements_signature_hash(tx, index, script, script_len, utxo->value, utxo->value_len, sighash, sig_flags, bytes_out, len); -#else - return WALLY_EINVAL; /* Unsupported */ -#endif /* BUILD_ELEMENTS */ - } - - if (!is_taproot) { - satoshi = inp->witness_utxo ? inp->witness_utxo->satoshi : 0; - return wally_tx_get_btc_signature_hash(tx, index, script, script_len, - satoshi, sighash, sig_flags, - bytes_out, len); - } - - /* Taproot */ - if ((ret = get_scripts_and_values(psbt, &scripts, &values)) == WALLY_OK) { - ret = wally_tx_get_btc_taproot_signature_hash(tx, index, &scripts, - values, psbt->num_inputs, - NULL, 0, 0, 0xFFFFFFFF, - NULL, 0, sighash, 0, - bytes_out, len); - wally_free(values); - wally_free(scripts.items); /* No need to clear the value pointers */ - } - return ret; +#endif + return wally_tx_get_btc_signature_hash(tx, index, script, script_len, + utxo->satoshi, sighash, sig_flags, + bytes_out, len); } int wally_psbt_sign_input_bip32(struct wally_psbt *psbt, @@ -4545,7 +4567,7 @@ int wally_psbt_sign_input_bip32(struct wally_psbt *psbt, int ret; if (!inp || !hdkey || hdkey->priv_key[0] != BIP32_FLAG_KEY_PRIVATE || - (flags & ~EC_FLAGS_ALL)) + (flags & ~(EC_FLAG_GRIND_R|EC_FLAG_ELEMENTS))) return WALLY_EINVAL; /* Find the public key this signature is for */ @@ -4561,24 +4583,25 @@ int wally_psbt_sign_input_bip32(struct wally_psbt *psbt, if (ret != WALLY_OK || !pubkey_idx) return WALLY_EINVAL; /* Signing pubkey key not found */ - /* Copy signing key so we can tweak it if needed */ - memcpy(signing_key, hdkey->priv_key + 1, EC_PRIVATE_KEY_LEN); - - if (is_taproot) { + if (!is_taproot) { + /* ECDSA: Use untweaked private key. Only grinding flag is relevant */ + memcpy(signing_key, hdkey->priv_key + 1, EC_PRIVATE_KEY_LEN); + flags = EC_FLAG_ECDSA | (flags & EC_FLAG_GRIND_R); + } else { /* Schnorr BIP340: Tweak the private key */ const struct wally_map_item *p = wally_map_get_integer(&inp->psbt_fields, PSBT_IN_TAP_MERKLE_ROOT); const unsigned char *merkle_root = p ? p->value : NULL; const size_t merkle_root_len = p ? p->value_len : 0; - ret = wally_ec_private_key_bip341_tweak(signing_key, sizeof(signing_key), + + ret = wally_ec_private_key_bip341_tweak(hdkey->priv_key + 1, EC_PRIVATE_KEY_LEN, merkle_root, merkle_root_len, - 0, signing_key, sizeof(signing_key)); + flags & EC_FLAG_ELEMENTS, + signing_key, sizeof(signing_key)); if (ret != WALLY_OK) goto done; - flags = EC_FLAG_SCHNORR; - } else { - /* ECDSA: Only grinding flag is relevant */ - flags = EC_FLAG_ECDSA | (flags & EC_FLAG_GRIND_R); + /* Only Elements flag is relevant */ + flags = EC_FLAG_SCHNORR | (flags & EC_FLAG_ELEMENTS); } sighash = inp->sighash; @@ -4626,12 +4649,18 @@ int wally_psbt_sign_bip32(struct wally_psbt *psbt, struct wally_tx *tx; if (!hdkey || hdkey->priv_key[0] != BIP32_FLAG_KEY_PRIVATE || - (flags & ~EC_FLAGS_ALL)) + (flags & ~EC_FLAG_GRIND_R)) return WALLY_EINVAL; if ((ret = psbt_build_tx(psbt, &tx, &is_pset, false)) != WALLY_OK) return ret; +#ifdef BUILD_ELEMENTS + if (is_pset) { + flags |= EC_FLAG_ELEMENTS; + } +#endif + /* Go through each of the inputs */ for (i = 0; ret == WALLY_OK && i < psbt->num_inputs; ++i) { unsigned char txhash[WALLY_TXHASH_LEN]; From 0a45c2ac88799dbac3762462bbeacddb26d82673 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sun, 16 Feb 2025 21:08:46 +1300 Subject: [PATCH 09/22] tests: test elements p2tr psbt signing This test case was generated from Elements with extra processing to work around Elements bugs and lack of ELIP-0101 support there. --- src/data/psbt.json | 14 +++++++++++++- src/test/test_psbt.py | 21 +++++++++++++++------ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/data/psbt.json b/src/data/psbt.json index b148c986a..b852ad74c 100644 --- a/src/data/psbt.json +++ b/src/data/psbt.json @@ -1102,12 +1102,24 @@ "result": "cHNidP8BAH0CAAAAAcxHwxyy9FpFa1AuYMltf+VRZ0Wb+xs7irBhENL3ZNDfAAAAAAD9////AqCGAQAAAAAAIlEg9bpXXraKrTowBOttg43mWTzNvrpNtwi247wAU/AVyx4hR/QFAAAAABYAFMtFjSqHFoz6HBMyvhQ92LQA8Ym8ZgAAAAABAIkCAAAAAYxqZIhs1zyGR1MCUaBZyQq4ug9tsyD8T3DZhrptncZQAAAAAAD9////AgDh9QUAAAAAIlEg57VlYkU0rn3hXaeIjADwG8K29OFka+lH9bfT0HQMm31bEBAkAQAAACJRIC+KLodP0myMhi9pydVeg4+NxhveBSpGNR29rfNGH3MPZQAAAAEBKwDh9QUAAAAAIlEg57VlYkU0rn3hXaeIjADwG8K29OFka+lH9bfT0HQMm30BAwQDAAAAARNBYqqZYa86n3RkuA32q8P5JOCzUjsif6fnfsUvgUbKvDI7+tac/nfHnggY1zJr0H0lyJ1RiENCkCEaCJBQpeheOwMhFiViK8ln+8oH7t8l+OupgyTlBVIgvrFwmImfEK24VhhDCQCYBc59AAAAAAAM/AlsaWdodG5pbmcEAgABACICA8qv05+H/b7q27AMU7Mq9crdV1eXrQRdyd+ftgOf/cF6CMtFjSoDAAAAAA==" }, { - "comment": "Taproot keyspend with no associated pubkey", + "comment": "PSBT Taproot keyspend with no associated pubkey", "privkeys": [ "cVd4tUYMjuWJS2te21dhjdBpTwvkxwdfjP9AC81PdM9BQwW7oaTo" ], "psbt": "cHNidP8BAH0CAAAAAd0d8Rfl45hnbKrHUO9vcDujNeb6VPuwyxDEVOwATVHyAQAAAAD9////AqCGAQAAAAAAIlEgeBUu8Q1HKJXIb76zd36Sl+izmyC/+w4AOsb6XLhNXUohR/QFAAAAABYAFFQykddI6WJF1YsFfGREBm/XYEGAZgAAAAABAIkCAAAAAfJC3TPe4ugqhUQW5CfoullkiEdvTl8MKBoWjZNHAFQSAAAAAAD9////AlsQECQBAAAAIlEgsO71o3RalUR0nhP9S0yNS4uncZHorNTFL2DmAdCyvHQA4fUFAAAAACJRIGApzC84K4A36zenkm3RSaYX9skR38q3j4G6qeZ0ffepZQAAAAEBKwDh9QUAAAAAIlEgYCnMLzgrgDfrN6eSbdFJphf2yRHfyrePgbqp5nR996kADPwJbGlnaHRuaW5nBAIAAQAiAgKm3O8QgEIUeJ+0Nman44ppKjC1A4tbrMC1YukyTtRdgAhUMpHXAwAAAAA=", "result": "cHNidP8BAH0CAAAAAd0d8Rfl45hnbKrHUO9vcDujNeb6VPuwyxDEVOwATVHyAQAAAAD9////AqCGAQAAAAAAIlEgeBUu8Q1HKJXIb76zd36Sl+izmyC/+w4AOsb6XLhNXUohR/QFAAAAABYAFFQykddI6WJF1YsFfGREBm/XYEGAZgAAAAABAIkCAAAAAfJC3TPe4ugqhUQW5CfoullkiEdvTl8MKBoWjZNHAFQSAAAAAAD9////AlsQECQBAAAAIlEgsO71o3RalUR0nhP9S0yNS4uncZHorNTFL2DmAdCyvHQA4fUFAAAAACJRIGApzC84K4A36zenkm3RSaYX9skR38q3j4G6qeZ0ffepZQAAAAEBKwDh9QUAAAAAIlEgYCnMLzgrgDfrN6eSbdFJphf2yRHfyrePgbqp5nR996kADPwJbGlnaHRuaW5nBAIAAQAiAgKm3O8QgEIUeJ+0Nman44ppKjC1A4tbrMC1YukyTtRdgAhUMpHXAwAAAAA=" + }, + { + "comment": "PSETv2 regtest p2tr keyspend, DEFAULT + SINGLE|ANYONECANPAY inputs", + "is_pset": true, + "master_xpriv": "tprv8gTfWnFCND72oJZfZTokBBXcS1FzQhrtd5wNFu3FgBE76yErH49cev2Zn3Wws3o6ZwKZVZaQP1UWKVNotpPg8U6tCgGrjMfaRQJvV1Vdbi7", + "privkeys": [ + "cW2Ybem38XTuRQ4PWDY7DJ1drUzBKVFmP3wSD2SV11HftBdP6aj5", + "cS3nM3rBgAMrSU6uMm44rAmGYvWyauhvKgBSx72ta78NEHfa8Jr1" + ], + "master_fully_signs": true, + "psbt": "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", + "result": "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" } ], "invalid_signer": [ diff --git a/src/test/test_psbt.py b/src/test/test_psbt.py index 74a2eacc3..9f3b72dfe 100644 --- a/src/test/test_psbt.py +++ b/src/test/test_psbt.py @@ -176,24 +176,33 @@ def do_sign(self, case): if expected and case.get('master_xpriv', None): # Test signing with the master extended private key. # Note we cannot check for equality with the explicit private keys - # since the PSBTs contain multiple keys from the same master, - # and only some of them are given as explicit private keys. + # in all cases, since the PSBTs contain multiple keys from the same + # master, and some test cases only give a subset as explicit private keys. key_out = POINTER(ext_key)() ret = bip32_key_from_base58_alloc(case['master_xpriv'], byref(key_out)) self.assertEqual(ret, WALLY_OK) psbt = self.parse_base64(case['psbt']) ret = wally_psbt_sign_bip32(psbt, key_out, 0x4) - b64_out = self.roundtrip(psbt) - self.assertNotEqual(b64_out, case['psbt']) # Inputs have been signed + # If all of the explicit private keys resulting from the master xpriv + # are present, we can verify the fully signed result matches exactly + can_match = case.get('all_privkeys_present', False) + b64_out = self.roundtrip(psbt, expected if can_match else None) + if not can_match: + # Check that the result changed at least, i.e. some inputs were signed + self.assertNotEqual(b64_out, case['psbt']) bip32_key_free(key_out) def test_signer_role(self): """Test the PSBT signer role""" + _, is_elements_build = wally_is_elements_build() + for case in JSON['signer']: - self.do_sign(case) + if is_elements_build or not case.get('is_pset', False): + self.do_sign(case) for case in JSON['invalid_signer']: - self.do_sign(case) + if is_elements_build or not case.get('is_pset', False): + self.do_sign(case) def test_finalizer_role(self): """Test the PSBT finalizer role""" From 0ee5421a9c4b9abd9fd1c7a285700104a79507ef Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Wed, 19 Feb 2025 23:34:04 +1300 Subject: [PATCH 10/22] descriptor: disable elements address generation until it is implemented --- src/descriptor.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/descriptor.c b/src/descriptor.c index 1756653b5..1cae78926 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -2881,6 +2881,13 @@ int wally_descriptor_to_addresses(const struct wally_descriptor *descriptor, if (!(p = wally_malloc(descriptor->script_len))) return WALLY_ENOMEM; + if (descriptor->features & WALLY_MS_IS_ELEMENTS) { + /* Disable Elements address generation until: + * - It is reconciled with Elements-core, and + * - We support blinded addresses + */ + return WALLY_ERROR; + } memcpy(&ctx, descriptor, sizeof(ctx)); ctx.variant = variant; if (ctx.max_path_elems && From d76e782718c727ea326640909fd6826a0a728820 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Thu, 20 Feb 2025 00:29:55 +1300 Subject: [PATCH 11/22] psbt: add support for caching when generating signature hashes/signing Having the cache as part of the PSBT is not ideal, but it allows us to support caching without having to modify a bunch of psbt calls in an incompatible fashion. Also includes a drive-by fix to add genesis_blockhash to the tests ctypes wrapper, as it was missed previously. --- include/wally.hpp | 11 +++++++++++ include/wally_psbt.h | 31 +++++++++++++++++++++++++++++++ src/psbt.c | 22 +++++++++++++++++++++- src/swig_java/swig.i | 2 ++ src/test/util.py | 6 +++++- src/tx_io.h | 3 +++ src/wasm_package/src/functions.js | 2 ++ src/wasm_package/src/index.d.ts | 2 ++ tools/wasm_exports.sh | 2 ++ 9 files changed, 79 insertions(+), 2 deletions(-) diff --git a/include/wally.hpp b/include/wally.hpp index 4d5bece2f..036e64d24 100644 --- a/include/wally.hpp +++ b/include/wally.hpp @@ -1641,6 +1641,17 @@ inline int psbt_sign_input_bip32(const PSBT& psbt, size_t index, size_t subindex return detail::check_ret(__FUNCTION__, ret); } +inline int psbt_signing_cache_disable(struct wally_psbt* psbt) { + int ret = ::wally_psbt_signing_cache_disable(psbt); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int psbt_signing_cache_enable(const PSBT& psbt, uint32_t flags) { + int ret = ::wally_psbt_signing_cache_enable(detail::get_p(psbt), flags); + return detail::check_ret(__FUNCTION__, ret); +} + template inline int psbt_to_base64(const PSBT& psbt, uint32_t flags, char** output) { int ret = ::wally_psbt_to_base64(detail::get_p(psbt), flags, output); diff --git a/include/wally_psbt.h b/include/wally_psbt.h index 20d054c67..2e9da1832 100644 --- a/include/wally_psbt.h +++ b/include/wally_psbt.h @@ -141,6 +141,7 @@ struct wally_psbt { uint32_t pset_modifiable_flags; unsigned char genesis_blockhash[SHA256_LEN]; /* All zeros if not present */ #endif /* WALLY_ABI_NO_ELEMENTS */ + struct wally_map *signing_cache; }; #endif /* SWIG */ @@ -2592,6 +2593,36 @@ WALLY_CORE_API int wally_psbt_blind_alloc( struct wally_map **output); #endif /* WALLY_ABI_NO_ELEMENTS */ +/** + * Enable caching of intermediate data when signing a PSBT. + * + * This function should be called just before signing a PSBT or the first + * input being signed, or before computing a signature hash for the PSBT. + * If the PSBT is modified in a way that would affect the signatures produced, + * this function should be called again to ensure that old cached data is + * purged before signing again. + * + * :param psbt: PSBT to enable the signing cache for. Directly modifies this PSBT. + * :param flags: Flags controlling the signing cache. Must be 0. + * + * .. note:: The signing cache is local to the given PSBT and is not + *| serialized with it. + */ +WALLY_CORE_API int wally_psbt_signing_cache_enable( + struct wally_psbt *psbt, + uint32_t flags); + +/** + * Disable caching of intermediate data when signing a PSBT. + * + * This function can be called at any time to ensure that the PSBT signing + * cache data is not reused when signing again. + * + * :param psbt: PSBT to disable the signing cache for. Directly modifies this PSBT. + */ +WALLY_CORE_API int wally_psbt_signing_cache_disable( + struct wally_psbt *psbt); + /** * Sign PSBT inputs corresponding to a given private key. * diff --git a/src/psbt.c b/src/psbt.c index da0ded9a5..4d7c82ae5 100644 --- a/src/psbt.c +++ b/src/psbt.c @@ -10,6 +10,7 @@ #include "script_int.h" #include "script.h" #include "pullpush.h" +#include "tx_io.h" /* TODO: * - When setting utxo in an input via the psbt (in the SWIG @@ -1308,6 +1309,7 @@ int wally_psbt_free(struct wally_psbt *psbt) #ifdef BUILD_ELEMENTS wally_map_clear(&psbt->global_scalars); #endif /* BUILD_ELEMENTS */ + wally_psbt_signing_cache_disable(psbt); clear_and_free(psbt, sizeof(*psbt)); } return WALLY_OK; @@ -4528,7 +4530,7 @@ int wally_psbt_get_input_signature_hash(struct wally_psbt *psbt, size_t index, NULL, 0, psbt->genesis_blockhash, sizeof(psbt->genesis_blockhash), sighash, WALLY_SIGTYPE_SW_V1, - NULL, bytes_out, len); + psbt->signing_cache, bytes_out, len); wally_free(scripts.items); /* No need to clear the value pointers */ wally_free(values.items); @@ -4720,6 +4722,24 @@ int wally_psbt_sign(struct wally_psbt *psbt, return ret; } +int wally_psbt_signing_cache_enable(struct wally_psbt *psbt, uint32_t flags) +{ + if (!psbt || flags) + return WALLY_EINVAL; + wally_psbt_signing_cache_disable(psbt); + return wally_map_init_alloc(TXIO_CACHE_INITIAL_SIZE, NULL, + &psbt->signing_cache); +} + +int wally_psbt_signing_cache_disable(struct wally_psbt *psbt) +{ + if (!psbt) + return WALLY_EINVAL; + wally_map_free(psbt->signing_cache); + psbt->signing_cache = NULL; + return WALLY_OK; +} + static const struct wally_map_item *get_sig(const struct wally_psbt_input *input, size_t i, size_t n) { diff --git a/src/swig_java/swig.i b/src/swig_java/swig.i index 92cd29aee..03ff33716 100644 --- a/src/swig_java/swig.i +++ b/src/swig_java/swig.i @@ -955,6 +955,8 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %returns_void__(wally_psbt_sign); %returns_void__(wally_psbt_sign_bip32); %returns_void__(wally_psbt_sign_input_bip32); +%returns_void__(wally_psbt_signing_cache_enable); +%returns_void__(wally_psbt_signing_cache_disable); %returns_string(wally_psbt_to_base64); %returns_size_t(wally_psbt_to_bytes); %returns_array_(wally_ripemd160, 3, 4, RIPEMD160_LEN); diff --git a/src/test/util.py b/src/test/util.py index 316b05867..071c25d70 100755 --- a/src/test/util.py +++ b/src/test/util.py @@ -193,7 +193,9 @@ class wally_psbt(Structure): ('has_fallback_locktime', c_uint32), ('tx_modifiable_flags', c_uint32), ('global_scalars', wally_map), - ('pset_modifiable_flags', c_uint32)] + ('pset_modifiable_flags', c_uint32), + ('genesis_blockhash', c_ubyte * 32), + ('signing_cache', POINTER(wally_map))] for f in ( # Internal functions @@ -629,6 +631,8 @@ class wally_psbt(Structure): ('wally_psbt_sign', c_int, [POINTER(wally_psbt), c_void_p, c_size_t, c_uint32]), ('wally_psbt_sign_bip32', c_int, [POINTER(wally_psbt), POINTER(ext_key), c_uint32]), ('wally_psbt_sign_input_bip32', c_int, [POINTER(wally_psbt), c_size_t, c_size_t, c_void_p, c_size_t, POINTER(ext_key), c_uint32]), + ('wally_psbt_signing_cache_disable', c_int, [POINTER(wally_psbt)]), + ('wally_psbt_signing_cache_enable', c_int, [POINTER(wally_psbt), c_uint32]), ('wally_psbt_to_base64', c_int, [POINTER(wally_psbt), c_uint32, c_char_p_p]), ('wally_psbt_to_bytes', c_int, [POINTER(wally_psbt), c_uint32, c_void_p, c_size_t, c_size_t_p]), ('wally_ripemd160', c_int, [c_void_p, c_size_t, c_void_p, c_size_t]), diff --git a/src/tx_io.h b/src/tx_io.h index c23a9773c..da888168c 100644 --- a/src/tx_io.h +++ b/src/tx_io.h @@ -4,6 +4,9 @@ #include #include "ccan/ccan/crypto/sha256/sha256.h" +/* Suggested initial size of a signing cache to avoid re-allocations */ +#define TXIO_CACHE_INITIAL_SIZE 16 + /* A cursor for pushing/pulling tx bytes for hashing */ typedef struct cursor_io { diff --git a/src/wasm_package/src/functions.js b/src/wasm_package/src/functions.js index 66d16306f..adf23cfad 100644 --- a/src/wasm_package/src/functions.js +++ b/src/wasm_package/src/functions.js @@ -602,6 +602,8 @@ export const psbt_set_version = wrap('wally_psbt_set_version', [T.OpaqueRef, T.I export const psbt_sign = wrap('wally_psbt_sign', [T.OpaqueRef, T.Bytes, T.Int32]); export const psbt_sign_bip32 = wrap('wally_psbt_sign_bip32', [T.OpaqueRef, T.OpaqueRef, T.Int32]); export const psbt_sign_input_bip32 = wrap('wally_psbt_sign_input_bip32', [T.OpaqueRef, T.Int32, T.Int32, T.Bytes, T.OpaqueRef, T.Int32]); +export const psbt_signing_cache_disable = wrap('wally_psbt_signing_cache_disable', [T.OpaqueRef]); +export const psbt_signing_cache_enable = wrap('wally_psbt_signing_cache_enable', [T.OpaqueRef, T.Int32]); export const psbt_to_base64 = wrap('wally_psbt_to_base64', [T.OpaqueRef, T.Int32, T.DestPtrPtr(T.String)]); export const ripemd160 = wrap('wally_ripemd160', [T.Bytes, T.DestPtrSized(T.Bytes, C.RIPEMD160_LEN)]); export const s2c_commitment_verify = wrap('wally_s2c_commitment_verify', [T.Bytes, T.Bytes, T.Bytes, T.Int32]); diff --git a/src/wasm_package/src/index.d.ts b/src/wasm_package/src/index.d.ts index 067e47057..eeb6fb648 100644 --- a/src/wasm_package/src/index.d.ts +++ b/src/wasm_package/src/index.d.ts @@ -562,6 +562,8 @@ export function psbt_set_version(psbt: Ref_wally_psbt, flags: number, version: n export function psbt_sign(psbt: Ref_wally_psbt, key: Buffer|Uint8Array, flags: number): void; export function psbt_sign_bip32(psbt: Ref_wally_psbt, hdkey: Ref_ext_key, flags: number): void; export function psbt_sign_input_bip32(psbt: Ref_wally_psbt, index: number, subindex: number, txhash: Buffer|Uint8Array, hdkey: Ref_ext_key, flags: number): void; +export function psbt_signing_cache_disable(psbt: Ref_wally_psbt): void; +export function psbt_signing_cache_enable(psbt: Ref_wally_psbt, flags: number): void; export function psbt_to_base64(psbt: Ref_wally_psbt, flags: number): string; export function ripemd160(bytes: Buffer|Uint8Array): Buffer; export function s2c_commitment_verify(sig: Buffer|Uint8Array, s2c_data: Buffer|Uint8Array, s2c_opening: Buffer|Uint8Array, flags: number): void; diff --git a/tools/wasm_exports.sh b/tools/wasm_exports.sh index b8ca67deb..e727d81ee 100644 --- a/tools/wasm_exports.sh +++ b/tools/wasm_exports.sh @@ -362,6 +362,8 @@ EXPORTED_FUNCTIONS="['_malloc','_free','_bip32_key_free' \ ,'_wally_psbt_sign' \ ,'_wally_psbt_sign_bip32' \ ,'_wally_psbt_sign_input_bip32' \ +,'_wally_psbt_signing_cache_disable' \ +,'_wally_psbt_signing_cache_enable' \ ,'_wally_psbt_to_base64' \ ,'_wally_psbt_to_bytes' \ ,'_wally_ripemd160' \ From 046a4bc2d763ca8e6387fd73ba83a565086c4765 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Thu, 20 Feb 2025 00:36:17 +1300 Subject: [PATCH 12/22] tests: enable the signing cache for psbt signing tests --- src/test/test_psbt.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/test_psbt.py b/src/test/test_psbt.py index 9f3b72dfe..a488c7656 100644 --- a/src/test/test_psbt.py +++ b/src/test/test_psbt.py @@ -167,6 +167,7 @@ def do_sign(self, case): expected_ret = WALLY_OK if expected else WALLY_EINVAL priv_key, priv_key_len = make_cbuffer('00'*32) psbt = self.parse_base64(case['psbt']) + wally_psbt_signing_cache_enable(psbt, 0) # Enable signing cache for wif in case['privkeys']: self.assertEqual(WALLY_OK, wally_wif_to_bytes(wif, 0xEF, 0, priv_key, priv_key_len)) self.assertEqual(expected_ret, wally_psbt_sign(psbt, priv_key, priv_key_len, FLAG_GRIND_R)) @@ -182,6 +183,7 @@ def do_sign(self, case): ret = bip32_key_from_base58_alloc(case['master_xpriv'], byref(key_out)) self.assertEqual(ret, WALLY_OK) psbt = self.parse_base64(case['psbt']) + wally_psbt_signing_cache_enable(psbt, 0) # Enable signing cache ret = wally_psbt_sign_bip32(psbt, key_out, 0x4) # If all of the explicit private keys resulting from the master xpriv # are present, we can verify the fully signed result matches exactly From 9e49e1ebb93b9d67f791f03aa0c370f094e1bfd2 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Thu, 20 Feb 2025 20:48:43 +1300 Subject: [PATCH 13/22] tests: remove redundant helper function, fix formatting --- src/test/test_transaction.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/test/test_transaction.py b/src/test/test_transaction.py index c9a4be4e5..bf7fc1198 100644 --- a/src/test/test_transaction.py +++ b/src/test/test_transaction.py @@ -319,29 +319,23 @@ def test_get_signature_hash(self): (tx, 0, script, script_len, 1, 1, 16, out, out_len), # Invalid flags (tx, 0, script, script_len, 1, 1, 0, None, out_len), # Empty bytes (tx, 0, script, script_len, 1, 1, 0, out, 31), # Short len - ]: + ]: self.assertEqual(WALLY_EINVAL, wally_tx_get_btc_signature_hash(*args)) - def sha256d(hex_): - bin_input, bin_input_len = make_cbuffer(hex_) - buf, buf_len = make_cbuffer('00'*32) - self.assertEqual(WALLY_OK, wally_sha256d(bin_input, bin_input_len, buf, buf_len)) - return h(buf) - script, script_len = make_cbuffer('00') out, out_len = make_cbuffer('00'*32) for args, expected in [ ((tx, 0, script, script_len, 1, 1, 0, out, out_len), utf8('1bcf681d585c3cbbc64b30a69e60b721fc0aacc57132dfbe43af6df8f4797a80')), - ((tx, 1, script, script_len, 1, 1, 0, out, out_len), - utf8('01'+'00'*31)), - ((tx, 0, script, script_len, 1, 0, 0, out, out_len), - utf8('882630e74173c928fc18236b99e25ffd15643faabc65c010e9ca27b8db29278a')), - ((tx, 0, script, script_len, 1, 1, 1, out, out_len), - utf8('5dad88b42332e3559950b325bba69eedb64b9330e55585fc1098964572f9c45d')), - ((tx, 0, script, script_len, 0, 1, 1, out, out_len), - utf8('bb30f5feed35b2591eedd8e778d507236a756e8c2eff8cf72ef0afa83abdea31')), - ]: + ((tx, 1, script, script_len, 1, 1, 0, out, out_len), + utf8('01'+'00'*31)), + ((tx, 0, script, script_len, 1, 0, 0, out, out_len), + utf8('882630e74173c928fc18236b99e25ffd15643faabc65c010e9ca27b8db29278a')), + ((tx, 0, script, script_len, 1, 1, 1, out, out_len), + utf8('5dad88b42332e3559950b325bba69eedb64b9330e55585fc1098964572f9c45d')), + ((tx, 0, script, script_len, 0, 1, 1, out, out_len), + utf8('bb30f5feed35b2591eedd8e778d507236a756e8c2eff8cf72ef0afa83abdea31')), + ]: self.assertEqual(WALLY_OK, wally_tx_get_btc_signature_hash(*args)) self.assertEqual(expected, h(out[:out_len])) From 024c520b0499eb71a3cfcfaf1305671393dde008 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sat, 22 Feb 2025 23:43:27 +1300 Subject: [PATCH 14/22] tests: allow skipping some expensive tests with WALLY_SKIP_EXPENSIVE_TESTS=1 Note we leave these tests enabled for the CI, this is just for faster development when hacking on other parts of wally. --- src/test/test_bip38.py | 7 +++++++ src/test/test_pbkdf2.py | 4 ++++ src/test/test_scrypt.py | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/src/test/test_bip38.py b/src/test/test_bip38.py index 68718b0ed..f113fbf00 100755 --- a/src/test/test_bip38.py +++ b/src/test/test_bip38.py @@ -1,3 +1,4 @@ +import os import unittest from util import * @@ -65,6 +66,9 @@ def to_priv(self, bip38, passwd, flags): def test_bip38(self): + if os.getenv('WALLY_SKIP_EXPENSIVE_TESTS', None): + self.skipTest('Skipping expensive bip38 test') + for case in cases: priv_key, passwd, flags, expected = case passwd = utf8(passwd) if type(passwd) is not bytes else passwd @@ -81,6 +85,9 @@ def test_bip38(self): def test_bip38_invalid(self): + if os.getenv('WALLY_SKIP_EXPENSIVE_TESTS', None): + self.skipTest('Skipping expensive bip38 test') + priv_key = 'CBF4B9F70470856BB4F40F80B87EDB90865997FFEE6DF315AB166D713AF433A5' passwd = utf8('TestingInvalidFlags') K_RES1 = 0x10 # BIP38_FLAG_RESERVED1 diff --git a/src/test/test_pbkdf2.py b/src/test/test_pbkdf2.py index 8dd084fb9..88b8445f7 100755 --- a/src/test/test_pbkdf2.py +++ b/src/test/test_pbkdf2.py @@ -1,3 +1,4 @@ +import os import unittest from util import * @@ -29,6 +30,9 @@ def setUp(self): def test_pbkdf2_hmac_sha(self): + if os.getenv('WALLY_SKIP_EXPENSIVE_TESTS', None): + self.skipTest('Skipping expensive pbkdf2 test') + # Some test vectors are nuts (e.g. 2097152 cost), so only run the # first few. set these to -1 to run the whole suite (only needed # when refactoring the impl) diff --git a/src/test/test_scrypt.py b/src/test/test_scrypt.py index 8036f5c25..136ad30f4 100755 --- a/src/test/test_scrypt.py +++ b/src/test/test_scrypt.py @@ -1,3 +1,4 @@ +import os import unittest from util import * @@ -37,6 +38,9 @@ class ScryptTests(unittest.TestCase): def test_scrypt(self): + if os.getenv('WALLY_SKIP_EXPENSIVE_TESTS', None): + self.skipTest('Skipping expensive scrypt test') + # Invalid arguments pwd, salt, cost, block, p, l, _ = cases[0] pwd, salt = utf8(pwd), utf8(salt) From dd75dada11127b708fd45baecc03ce94052d87c4 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sat, 22 Feb 2025 23:43:34 +1300 Subject: [PATCH 15/22] ci: also enable bounds checks in sanitizer builds --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 514974634..c5c4f1bef 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -35,7 +35,7 @@ test_asan_ubsan_gcc: - ga script: - ./tools/cleanup.sh && ./tools/autogen.sh - - CC=gcc CFLAGS="-O2 -fsanitize=address -fsanitize=undefined -fsanitize=alignment -fsanitize-address-use-after-scope -fno-sanitize-recover=all" ./configure --enable-export-all --enable-swig-python --enable-swig-java $CONFIGURE_ARGS --enable-shared --disable-static --disable-clear-tests --disable-asm + - CC=gcc CFLAGS="-O2 -fsanitize=address -fsanitize=bounds -fsanitize=undefined -fsanitize=alignment -fsanitize-address-use-after-scope -fno-sanitize-recover=all" ./configure --enable-export-all --enable-swig-python --enable-swig-java $CONFIGURE_ARGS --enable-shared --disable-static --disable-clear-tests --disable-asm - sed -i 's/^PYTHON = /PYTHON = LD_PRELOAD=\/usr\/lib\/gcc\/x86_64-linux-gnu\/10\/libasan.so /g' src/Makefile - sed -i 's/^JAVA = /JAVA = LD_PRELOAD=\/usr\/lib\/gcc\/x86_64-linux-gnu\/10\/libasan.so /g' src/Makefile - make -j $(($(grep ^processor /proc/cpuinfo | wc -l) / 2)) From 13af779d7283925878a65962a195a6b11b24b918 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Mon, 24 Feb 2025 19:51:55 +1300 Subject: [PATCH 16/22] ci: add the python tests to the valgrind CI Don't bother checking for leaks, since we don't clean up in these tests. --- .gitlab-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c5c4f1bef..c62ce94b6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,7 +23,9 @@ test_with_valgrind: - make check -j $(($(grep ^processor /proc/cpuinfo | wc -l) / 2)) - for t in $(ls src/.libs/test_* | egrep -v '_clear|xml|json' | tr '\n' ' '); do LD_LIBRARY_PATH=./src/.libs/ valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose --xml=yes --xml-file=$t.xml $t; done - for t in $(ls src/.libs/test_* | egrep -v '_clear|xml|json' | tr '\n' ' '); do valgrind-codequality --input-file $t.xml --output-file $t.json; done - - jq '[.[]|.[]]' -s ./src/.libs/test_*.json > valgrind.json + - for t in $(ls src/test/test_*.py | tr '\n' ' '); do WALLY_SKIP_EXPENSIVE_TESTS=1 PYTHONMALLOC=malloc PYTHONDEVMODE=1 MALLOC_CHECK_=3 valgrind --tool=memcheck --leak-check=no --verbose --xml=yes --xml-file=$t.xml python $t; done + - for t in $(ls src/test/test_*.py | tr '\n' ' '); do valgrind-codequality --input-file $t.xml --output-file $t.json; done + - jq '[.[]|.[]]' -s ./src/.libs/test_*.json src/test/test_*.json > valgrind.json test_asan_ubsan_gcc: stage: test From 5d39eb7c7759ae83bb979f0082022b0b4cb4a964 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Mon, 24 Feb 2025 22:01:50 +1300 Subject: [PATCH 17/22] tests: skip elements tests when elements is not enabled For valgrind runs we run all tests including Elements ones, even if Elements support is disabled. Make the tests friendly to that use-case. --- src/test/test_confidential_addr.py | 6 ++++++ src/test/test_elements.py | 22 +++++++++++++++++++--- src/test/test_pegin.py | 12 +++++++++--- src/test/test_pegout.py | 9 ++++++--- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/test/test_confidential_addr.py b/src/test/test_confidential_addr.py index 5ef517be3..851699c78 100755 --- a/src/test/test_confidential_addr.py +++ b/src/test/test_confidential_addr.py @@ -51,6 +51,8 @@ class CATests(unittest.TestCase): def test_master_blinding_key(self): + if not wally_is_elements_build()[1]: + self.skipTest('Elements support not enabled') # from Trezor firmware code class Slip21Node: @@ -102,6 +104,8 @@ def key(self): def test_confidential_addr(self): """Tests for confidential addresses""" + if not wally_is_elements_build()[1]: + self.skipTest('Elements support not enabled') # The (Liquid) address that is to be blinded addr = 'Q7qcjTLsYGoMA7TjUp97R6E6AM5VKqBik6' @@ -128,6 +132,8 @@ def test_confidential_addr(self): def test_confidential_addr_segwit(self): """Tests for confidential segwit addresses""" + if not wally_is_elements_build()[1]: + self.skipTest('Elements support not enabled') for addr, conf_key, conf_addr in segwit_valid_cases: conf_key, conf_key_len = make_cbuffer(conf_key) diff --git a/src/test/test_elements.py b/src/test/test_elements.py index 198f84699..d29f4b435 100755 --- a/src/test/test_elements.py +++ b/src/test/test_elements.py @@ -28,6 +28,9 @@ class ElementsTests(unittest.TestCase): def test_asset_unblind(self): + if not wally_is_elements_build()[1]: + self.skipTest('Elements support not enabled') + asset_out, _ = make_cbuffer('00' * 32) abf_out, _ = make_cbuffer('00' * 32) vbf_out, _ = make_cbuffer('00' * 32) @@ -48,6 +51,9 @@ def test_asset_unblind(self): (WALLY_OK, 80000000, UNBLINDED_ASSET, UNBLINDED_ABF, UNBLINDED_VBF)) def test_asset_unblind_with_nonce(self): + if not wally_is_elements_build()[1]: + self.skipTest('Elements support not enabled') + out_nonce_hash, _ = make_cbuffer('00'*32) ret = wally_ecdh_nonce_hash(UNBLIND_SENDER_PK, UNBLIND_SENDER_PK_LEN, UNBLIND_OUR_SK, UNBLIND_OUR_SK_LEN, @@ -82,6 +88,9 @@ def test_asset_unblind_with_nonce(self): (WALLY_OK, 80000000, UNBLINDED_ASSET, UNBLINDED_ABF, UNBLINDED_VBF)) def test_asset_generator_from_bytes(self): + if not wally_is_elements_build()[1]: + self.skipTest('Elements support not enabled') + generator, generator_len = make_cbuffer('00' * 33) # Blind the unblinded asset with its blinding factor @@ -111,6 +120,9 @@ def test_asset_generator_from_bytes(self): self.assertEqual((ret, generator), (WALLY_OK, expected)) def test_blinding(self): + if not wally_is_elements_build()[1]: + self.skipTest('Elements support not enabled') + value = 80000000 # asset_value_commitment @@ -209,6 +221,9 @@ def test_blinding(self): self.assertEqual(wally_ec_scalar_verify(offset, offset_len), WALLY_OK) def test_deterministic_blinding_factors(self): + if not wally_is_elements_build()[1]: + self.skipTest('Elements support not enabled') + # Test vector from: # https://github.com/Blockstream/Jade/blob/master/test_data/liquid_txn_ledger_compare.json with open(root_dir + 'src/data/liquid_txn_ledger_compare.json', 'r') as f: @@ -242,6 +257,9 @@ def test_deterministic_blinding_factors(self): self.assertEqual(h(out[:o_len]), utf8(expected)) def test_elements_tx_weights(self): + if not wally_is_elements_build()[1]: + self.skipTest('Elements support not enabled') + # Test the elements weight discount with open(root_dir + 'src/data/elip200_vectors.json', 'r') as f: JSON = json.load(f) @@ -255,6 +273,4 @@ def test_elements_tx_weights(self): if __name__ == '__main__': - _, val = wally_is_elements_build() - if val != 0: - unittest.main() + unittest.main() diff --git a/src/test/test_pegin.py b/src/test/test_pegin.py index cea9c0b41..4077c942c 100644 --- a/src/test/test_pegin.py +++ b/src/test/test_pegin.py @@ -40,6 +40,8 @@ def get_pegin_address(self): def test_liquidv1_pegin_address(self): """ uses same strategy as elementsd 0.18 to generate the mainchain address and claim script """ + if not wally_is_elements_build()[1]: + self.skipTest('Elements support not enabled') pk, pk_len = make_cbuffer('02daec8c199e33d5626032c8af7d1da274571089393d64ac7a44e02e7935d10cbf') expected_claim_script, _ = make_cbuffer('0014ec3b20acfc151fd117f76598acdc5be08af160e6') @@ -66,6 +68,9 @@ def test_liquidv1_pegin_address(self): def test_pegin_tx(self): # pegin tx from createrawpegin + if not wally_is_elements_build()[1]: + self.skipTest('Elements support not enabled') + pegin_tx = '0200000001018c78b1b1e379bb79cff9c96ef92742ceb7cfe3c144cd7b4a2c472234758b94340000004000ffffffff020125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000005f5d386001600149ce668ef355bb7bbb4dc532d6253b1dc620c864d0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000000000d7a000000000000000000060800e1f505000000002025b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a2006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f16001447dc2241f735ba3e39cbbc30883bc79fbe79b9308a020000000194e7d81a9ac87b725ec10e6c59918c52d39f61e4fa2fa08c7fa2d078b38c468500000000171600146b84062093cd6d4f7aef0f7315201f8b00d975aefeffffff0200e1f5050000000017a91472c44f957fc011d97e3406667dca5b1c930c402687188542060100000017a9145db12bf4a1fbce5b1bb232277d221abe271db81f873b00000097000000207c390a46698af24ef305b49f33ccba92e8b800677baf2a64d9ca059302f657101c69b46c7b062fbef3bfdd959518aae4875bf4bd88e97380020fea9d8535388bf2d38b5dffff7f2000000000020000000266bc307341d2cfa75b79b17a74ae1e3e00b5746e8d04bd311ecbe11bf1b739bb8c78b1b1e379bb79cff9c96ef92742ceb7cfe3c144cd7b4a2c472234758b9434010500000000' # pegin tx from claimpegin (signed) @@ -173,6 +178,9 @@ def test_pegin_tx(self): def test_contract_script(self): + if not wally_is_elements_build()[1]: + self.skipTest('Elements support not enabled') + expected, expected_len = make_cbuffer('745c87635b21039e7dc52351b81d97dde2b369f692d89b5cf938534ad503094bc89f830473796321030508eb92dccb704ac7511eb55369f2259485b50f56671ff5bf1dfd8cdf5c6c662102140052a92c55c5a0c2f960b438bba966974cdfa2c872a4702645ff6028f6b0f2210209aa6d8ab038fd00088355324c9c3fea336b2cf650d951b23c2b20ad47386daa2102328940da1f59bc214757a8fdedd86887fc48a952e0fa19c1f1959ffa826c88b42102188281e1055fb81f642a7f2ca994a8f6abaa8bbcc2720aff35c0fd263188ef7c2103ae97faefcdba436269cc36c31db7956ade6c1977b174b87174173ea92c112d332103f4a2d090f03684a65f74f5ee031d6d9bf5fd02d4e643693701f86d4e4f721ae82102d5ee27530bc9075c310e53b308a127a3ed7a90c6039355d00d2d1ea72874add72103960c1740e6ac39c15fc8fd048789fdd480b68331a49e6e557fcbec192d0b3a252103e33ae6c4b978523ff81e8f2fb2e2c0174f9483de86c186e228753715fa2228392103003d2490f282d7628a2a8efa08366f317efa6473579bb5c34b4c409e36e7b2df2102da66e69bd08a68d4c8fabefd797786bb6de16d553acab4ee85e3aceda8e48d8c2103d46bd2ba127f1666650de1e0d85f438978c28399a9ac866ea84db30cc77446c3210257fdfffaf0a360f7ee1d0c2588d931a5b51302ab5a100cd9fa54ebe1d63adbdb5f6702c00fb275522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb5368ae') # https://blockstream.info/liquid/tx/871f1611bbf723412e95dc40b6d7ec0a8ffc917b045f1bb31fd1bb81d9fcb456 @@ -186,6 +194,4 @@ def test_contract_script(self): if __name__ == '__main__': - _, val = wally_is_elements_build() - if val != 0: - unittest.main() + unittest.main() diff --git a/src/test/test_pegout.py b/src/test/test_pegout.py index c1b651e01..9b7587aaa 100644 --- a/src/test/test_pegout.py +++ b/src/test/test_pegout.py @@ -83,12 +83,17 @@ def generate_pegout_script(self): return pegout_script, pegout_script_len def test_pegout(self): + if not wally_is_elements_build()[1]: + self.skipTest('Elements support not enabled') + op_return_data = '6a2006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f1976a91420f2d8c7514c601984fffee90f988f33bd87f96f88ac2103c58ebf2840c9321e42e1859a387d42cc78241048f81ce9c911bd57b240139e9741013996e9eca65e06b3deda77fdc19b3476cd83af3ae8f543647a52b097558c33878752c52536c493ea00d446159009ce484795287aca1de8aaa52d6064b5960caa' buf, buf_len = make_cbuffer('00'*int(len(op_return_data)/2)) ret, written = wally_hex_to_bytes(utf8(op_return_data), buf, buf_len) self.assertEqual(buf, self.generate_pegout_script()[0]) def test_pegout_tx(self): + if not wally_is_elements_build()[1]: + self.skipTest('Elements support not enabled') tx_hex = "02000000010111b13a9bc2833fcfddb53086fffb3cf7ff1c13948c876d9bd15df872f5fdefca0000000017160014355347fd5b11a57cddd5e1576fb38280a0627cf7fdffffff030125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000174876e80000a06a2006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f1976a91420f2d8c7514c601984fffee90f988f33bd87f96f88ac2103c58ebf2840c9321e42e1859a387d42cc78241048f81ce9c911bd57b240139e9741013996e9eca65e06b3deda77fdc19b3476cd83af3ae8f543647a52b097558c33878752c52536c493ea00d446159009ce484795287aca1de8aaa52d6064b5960caa0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010007751ecdd907a20017a9140fbb8e55216381f7c4e7124eaa9070d8e8dc92c7870125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000000000105e0000000000000000024730440220029550293885a772c04c2a462b0afc7d9dd03d0286d37e1273d60a64b333875e0220626baf165c2ba70d5ce202bf6d2212a0bab83b457188a52db67d5ce396601d5001210324a1cbd388173f0c72616a0c8fe363daf9c016a157b14aa1dabf6d19b85df95c00000000000000" asset, asset_len = make_cbuffer('5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225') txhash, txhash_len = make_cbuffer('caeffdf572f85dd19b6d878c94131cfff73cfbff8630b5ddcf3f83c29b3ab111') @@ -125,6 +130,4 @@ def test_pegout_tx(self): if __name__ == '__main__': - _, val = wally_is_elements_build() - if val != 0: - unittest.main() + unittest.main() From be25cb792d9559f780b75a5fc406e8feaf7496c9 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sat, 22 Feb 2025 23:46:16 +1300 Subject: [PATCH 18/22] tx: disallow invalid satoshi amounts for btc signature hash generation --- src/tx_io.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/tx_io.c b/src/tx_io.c index 3e2404cd1..8923e5355 100644 --- a/src/tx_io.c +++ b/src/tx_io.c @@ -6,6 +6,8 @@ #include "tx_io.h" #include +#define WALLY_SATOSHI_MAX ((uint64_t)WALLY_BTC_MAX * WALLY_SATOSHI_PER_BTC) + #define SIGTYPE_ALL (WALLY_SIGTYPE_PRE_SW | WALLY_SIGTYPE_SW_V0 | WALLY_SIGTYPE_SW_V1) /* Cache keys for data that is constant while signing a given tx. @@ -62,6 +64,14 @@ static bool value_len_ok(size_t len) len == WALLY_TX_ASSET_CT_VALUE_UNBLIND_LEN; } +static uint64_t satoshi_from_item(const struct wally_map_item *item) +{ + uint64_t v; + /* Map values must be in cpu byte order, but may not be aligned */ + memcpy(&v, item->value, item->value_len); + return v; +} + /* Ensure 'm' is integer-indexed with num_items valid items */ static bool map_has_all(const struct wally_map *m, size_t num_items, bool (*len_fn)(size_t)) @@ -87,8 +97,12 @@ static bool map_has_one(const struct wally_map *m, size_t index, const struct wally_map_item *item = m->items + i; if (item->key || !item->value || !len_fn(item->value_len)) return false; - if (index == item->key_len) + if (index == item->key_len) { + if (len_fn == satoshi_len_ok && + satoshi_from_item(item) > WALLY_SATOSHI_MAX) + return false; /* Invalid BTC amount */ return true; + } } return false; } @@ -111,10 +125,7 @@ static inline void hash_le64(struct sha256_ctx *ctx, uint64_t v) static void hash_map_le64(struct sha256_ctx *ctx, const struct wally_map *m, size_t index) { - const struct wally_map_item *item = wally_map_get_integer(m, index); - uint64_t v; - memcpy(&v, item->value, item->value_len); - hash_le64(ctx, v); + hash_le64(ctx, satoshi_from_item(wally_map_get_integer(m, index))); } static inline void hash_bytes(struct sha256_ctx *ctx, @@ -385,11 +396,8 @@ static void txio_hash_sha_amounts(cursor_io *io, const struct wally_map *values) if (!txio_hash_cached_item(io, TXIO_SHA_AMOUNTS)) { struct sha256_ctx ctx; sha256_init(&ctx); - for (size_t i = 0; i < values->num_items; ++i) { - uint64_t v; - memcpy(&v, values->items[i].value, values->items[i].value_len); - hash_le64(&ctx, v); - } + for (size_t i = 0; i < values->num_items; ++i) + hash_le64(&ctx, satoshi_from_item(values->items + i)); txio_hash_sha256_ctx(io, &ctx, TXIO_SHA_AMOUNTS); } } From 4f978734a9ca6a471a57aa9cb8f64d45750d148b Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sat, 22 Feb 2025 23:47:01 +1300 Subject: [PATCH 19/22] tx: add segwit v0 support to the caching signature hash helpers Segwit uses SHA-256D instead of taproot's SHA-256 for commitments. Support this by using a flag on cache keys indicating a double hash is required. Update the various functions that hash portions of the tx data to support the segwit v0 variant (in many cases the same data, just double hashed). Note that Elements taproot hashes some data in a different order, which makes this change more painful than it should be. It also makes it impossible to cleanly/automatically cache taproot data when generating segwit data and vice-versa, meaning mixed segwit v0 and taproot transactions are less efficient than they could potentially be otherwise. --- src/tx_io.c | 194 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 126 insertions(+), 68 deletions(-) diff --git a/src/tx_io.c b/src/tx_io.c index 8923e5355..0eaaf9c08 100644 --- a/src/tx_io.c +++ b/src/tx_io.c @@ -14,8 +14,11 @@ * We also cache other data keyed by their binary value directly. */ #define TXIO_UNCACHED 0 /* Signals that data should not be cached */ +#define TXIO_SHA256_D 0x80000000 /* Data should be double hashed */ +#define TXIO_UNCACHED_D (TXIO_UNCACHED | TXIO_SHA256_D) #define TXIO_SHA_TAPSIGHASH_CTX 1 /* Initial sha256_ctx for taproot bip340 hashing */ -#define TXIO_SHA_OUTPOINT_FLAGS 2 /* Taproot cached data ... */ +/* Taproot cached data ... */ +#define TXIO_SHA_OUTPOINT_FLAGS 2 #define TXIO_SHA_PREVOUTS 3 #define TXIO_SHA_AMOUNTS 4 #define TXIO_SHA_ASSET_AMOUNTS 5 @@ -24,7 +27,15 @@ #define TXIO_SHA_ISSUANCES 8 #define TXIO_SHA_ISSUANCE_RANGEPROOFS 9 #define TXIO_SHA_OUTPUTS 10 -#define TXIO_SHA_OUTPUT_WITNESSES 11 /* ... end of taproot cached data */ +#define TXIO_SHA_OUTPUT_WITNESSES 11 +/* ... end of taproot cached data */ +/* Segwit v0 data */ +#define TXIO_SHA_PREVOUTS_D (TXIO_SHA_PREVOUTS | TXIO_SHA256_D) +#define TXIO_SHA_SEQUENCES_D (TXIO_SHA_SEQUENCES | TXIO_SHA256_D) +#define TXIO_SHA_ISSUANCES_D (TXIO_SHA_ISSUANCES | TXIO_SHA256_D) +#define TXIO_SHA_OUTPUTS_D (TXIO_SHA_OUTPUTS | TXIO_SHA256_D) +#define TXIO_SHA_OUTPUT_WITNESSES_D (TXIO_SHA_OUTPUTS | TXIO_SHA256_D) +/* ... end of segwit cached data */ /* SHA256(TapSighash) */ static const unsigned char TAPSIGHASH_SHA256[SHA256_LEN] = { @@ -164,16 +175,29 @@ static void txio_hash_sha256_ctx(cursor_io *io, struct sha256_ctx *ctx, int key) { struct sha256 hash; sha256_done(ctx, &hash); + if (key & TXIO_SHA256_D) { + struct sha256 hash2; + sha256(&hash2, hash.u.u8, sizeof(hash)); + memcpy(hash.u.u8, hash2.u.u8, sizeof(hash)); + } hash_bytes(&io->ctx, hash.u.u8, sizeof(hash)); - if (io->cache && key != TXIO_UNCACHED) + if (io->cache && (key & ~TXIO_SHA256_D) != TXIO_UNCACHED) wally_map_add_integer(io->cache, key, hash.u.u8, sizeof(hash)); } -static void txio_done(cursor_io *io) +static int txio_done(cursor_io *io, uint32_t flags) { struct sha256 hash; sha256_done(&io->ctx, &hash); - push_bytes(&io->cursor, &io->max, hash.u.u8, sizeof(hash)); + if (flags & TXIO_SHA256_D) { + struct sha256 hash2; + sha256(&hash2, hash.u.u8, sizeof(hash)); + push_bytes(&io->cursor, &io->max, hash2.u.u8, sizeof(hash2)); + } else + push_bytes(&io->cursor, &io->max, hash.u.u8, sizeof(hash)); + if (io->max) + return WALLY_ERROR; /* Wrote the wrong number of bytes: should not happen! */ + return WALLY_OK; } /* Initialize a sha256 context for bip340 tagged hashing. @@ -226,10 +250,15 @@ static void hash_output_elements(struct sha256_ctx *ctx, } static void hash_output_witness(struct sha256_ctx *ctx, - const struct wally_tx_output *txout) + const struct wally_tx_output *txout, + uint32_t key) { - hash_varbuff(ctx, txout->surjectionproof, txout->surjectionproof_len); + /* Elements taproot hashing reverses the order, d'oh */ + if (!(key & TXIO_SHA256_D)) + hash_varbuff(ctx, txout->surjectionproof, txout->surjectionproof_len); hash_varbuff(ctx, txout->rangeproof, txout->rangeproof_len); + if (key & TXIO_SHA256_D) + hash_varbuff(ctx, txout->surjectionproof, txout->surjectionproof_len); } static void txio_hash_sha_outpoint_flags(cursor_io *io, const struct wally_tx *tx) @@ -265,9 +294,9 @@ static void txio_hash_sha_asset_amounts(cursor_io *io, } } -static void txio_hash_sha_issuances(cursor_io *io, const struct wally_tx *tx) +static void txio_hash_sha_issuances(cursor_io *io, const struct wally_tx *tx, uint32_t key) { - if (!txio_hash_cached_item(io, TXIO_SHA_ISSUANCES)) { + if (!txio_hash_cached_item(io, key)) { struct sha256_ctx ctx; sha256_init(&ctx); for (size_t i = 0; i < tx->num_inputs; ++i) { @@ -281,7 +310,7 @@ static void txio_hash_sha_issuances(cursor_io *io, const struct wally_tx *tx) hash_commmitment(&ctx, txin->issuance_amount, txin->issuance_amount_len); hash_commmitment(&ctx, txin->inflation_keys, txin->inflation_keys_len); } - txio_hash_sha256_ctx(io, &ctx, TXIO_SHA_ISSUANCES); + txio_hash_sha256_ctx(io, &ctx, key); } } @@ -296,25 +325,25 @@ static void txio_hash_sha_issuance_rangeproofs(cursor_io *io, const struct wally } } -static void txio_hash_sha_outputs_elements(cursor_io *io, const struct wally_tx *tx) +static void txio_hash_sha_outputs_elements(cursor_io *io, const struct wally_tx *tx, uint32_t key) { - if (!txio_hash_cached_item(io, TXIO_SHA_OUTPUTS)) { + if (!txio_hash_cached_item(io, key)) { struct sha256_ctx ctx; sha256_init(&ctx); for (size_t i = 0; i < tx->num_outputs; ++i) hash_output_elements(&ctx, tx->outputs + i); - txio_hash_sha256_ctx(io, &ctx, TXIO_SHA_OUTPUTS); + txio_hash_sha256_ctx(io, &ctx, key); } } -static void txio_hash_sha_output_witnesses(cursor_io *io, const struct wally_tx *tx) +static void txio_hash_sha_output_witnesses(cursor_io *io, const struct wally_tx *tx, uint32_t key) { - if (!txio_hash_cached_item(io, TXIO_SHA_OUTPUT_WITNESSES)) { + if (!txio_hash_cached_item(io, key)) { struct sha256_ctx ctx; sha256_init(&ctx); for (size_t i = 0; i < tx->num_outputs; ++i) - hash_output_witness(&ctx, tx->outputs + i); - txio_hash_sha256_ctx(io, &ctx, TXIO_SHA_OUTPUT_WITNESSES); + hash_output_witness(&ctx, tx->outputs + i, key); + txio_hash_sha256_ctx(io, &ctx, key); } } @@ -332,24 +361,33 @@ static void txio_hash_input_elements(cursor_io *io, const struct wally_tx *tx, size_t index, const struct wally_map *scripts, const struct wally_map *assets, - const struct wally_map *values) + const struct wally_map *values, + const unsigned char *scriptcode, size_t scriptcode_len, + uint32_t hash_type) { const struct wally_tx_input *txin = tx->inputs + index; - hash_map_commmitment(&io->ctx, assets, index); - hash_map_commmitment(&io->ctx, values, index); - hash_map_varbuff(&io->ctx, scripts, index); + if (hash_type == WALLY_SIGTYPE_SW_V0) { + hash_varbuff(&io->ctx, scriptcode, scriptcode_len); + hash_map_commmitment(&io->ctx, values, index); + } else { + /* Elements taproot hashing reverses the order, d'oh */ + hash_map_commmitment(&io->ctx, assets, index); + hash_map_commmitment(&io->ctx, values, index); + hash_map_varbuff(&io->ctx, scripts, index); + } hash_le32(&io->ctx, txin->sequence); - if (!(txin->features & WALLY_TX_IS_ISSUANCE)) - hash_u8(&io->ctx, 0); - else { + if (!(txin->features & WALLY_TX_IS_ISSUANCE)) { + if (hash_type != WALLY_SIGTYPE_SW_V0) + hash_u8(&io->ctx, 0); + } else { /* asset_issuance */ hash_bytes(&io->ctx, txin->blinding_nonce, sizeof(txin->blinding_nonce)); hash_bytes(&io->ctx, txin->entropy, sizeof(txin->entropy)); hash_commmitment(&io->ctx, txin->issuance_amount, txin->issuance_amount_len); hash_commmitment(&io->ctx, txin->inflation_keys, txin->inflation_keys_len); - { + if (hash_type != WALLY_SIGTYPE_SW_V0) { /* sha_single_issuance_rangeproofs */ struct sha256_ctx ctx; sha256_init(&ctx); @@ -360,34 +398,37 @@ static void txio_hash_input_elements(cursor_io *io, } static void txio_hash_sha_single_output_elements(cursor_io *io, - const struct wally_tx_output *txout) + const struct wally_tx_output *txout, + uint32_t key) { struct sha256_ctx ctx; sha256_init(&ctx); hash_output_elements(&ctx, txout); - txio_hash_sha256_ctx(io, &ctx, TXIO_UNCACHED); + txio_hash_sha256_ctx(io, &ctx, key); } static void txio_hash_sha_single_output_witness(cursor_io *io, - const struct wally_tx_output *txout) + const struct wally_tx_output *txout, + uint32_t key) { struct sha256_ctx ctx; sha256_init(&ctx); - hash_output_witness(&ctx, txout); - txio_hash_sha256_ctx(io, &ctx, TXIO_UNCACHED); + hash_output_witness(&ctx, txout, key); + txio_hash_sha256_ctx(io, &ctx, key); } #endif /* BUILD_ELEMENTS */ -static void txio_hash_sha_prevouts(cursor_io *io, const struct wally_tx *tx) +static void txio_hash_sha_prevouts(cursor_io *io, const struct wally_tx *tx, + uint32_t key) { - if (!txio_hash_cached_item(io, TXIO_SHA_PREVOUTS)) { + if (!txio_hash_cached_item(io, key)) { struct sha256_ctx ctx; sha256_init(&ctx); for (size_t i = 0; i < tx->num_inputs; ++i) { hash_bytes(&ctx, tx->inputs[i].txhash, WALLY_TXHASH_LEN); hash_le32(&ctx, tx->inputs[i].index); } - txio_hash_sha256_ctx(io, &ctx, TXIO_SHA_PREVOUTS); + txio_hash_sha256_ctx(io, &ctx, key); } } @@ -413,27 +454,29 @@ static void txio_hash_sha_scriptpubkeys(cursor_io *io, const struct wally_map *s } } -static void txio_hash_sha_sequences(cursor_io *io, const struct wally_tx *tx) +static void txio_hash_sha_sequences(cursor_io *io, const struct wally_tx *tx, + uint32_t key) { - if (!txio_hash_cached_item(io, TXIO_SHA_SEQUENCES)) { + if (!txio_hash_cached_item(io, key)) { struct sha256_ctx ctx; sha256_init(&ctx); for (size_t i = 0; i < tx->num_inputs; ++i) hash_le32(&ctx, tx->inputs[i].sequence); - txio_hash_sha256_ctx(io, &ctx, TXIO_SHA_SEQUENCES); + txio_hash_sha256_ctx(io, &ctx, key); } } -static void txio_hash_sha_outputs(cursor_io *io, const struct wally_tx *tx) +static void txio_hash_sha_outputs(cursor_io *io, const struct wally_tx *tx, + uint32_t key) { - if (!txio_hash_cached_item(io, TXIO_SHA_OUTPUTS)) { + if (!txio_hash_cached_item(io, key)) { struct sha256_ctx ctx; sha256_init(&ctx); for (size_t i = 0; i < tx->num_outputs; ++i) { hash_le64(&ctx, tx->outputs[i].satoshi); hash_varbuff(&ctx, tx->outputs[i].script, tx->outputs[i].script_len); } - txio_hash_sha256_ctx(io, &ctx, TXIO_SHA_OUTPUTS); + txio_hash_sha256_ctx(io, &ctx, key); } } @@ -446,20 +489,30 @@ static void txio_hash_outpoint(cursor_io *io, const struct wally_tx_input *txin) static void txio_hash_input(cursor_io *io, const struct wally_tx *tx, size_t index, const struct wally_map *scripts, - const struct wally_map *values) + const struct wally_map *values, + const unsigned char *scriptcode, size_t scriptcode_len, + uint32_t hash_type) { - hash_map_le64(&io->ctx, values, index); - hash_map_varbuff(&io->ctx, scripts, index); + if (hash_type == WALLY_SIGTYPE_SW_V0) { + hash_varbuff(&io->ctx, scriptcode, scriptcode_len); + hash_map_le64(&io->ctx, values, index); + } else { + /* Elements taproot hashing reverses the order, d'oh */ + hash_map_le64(&io->ctx, values, index); + hash_map_varbuff(&io->ctx, scripts, index); + } hash_le32(&io->ctx, tx->inputs[index].sequence); } -static void txio_hash_sha_single_output(cursor_io *io, const struct wally_tx_output *txout) +static void txio_hash_sha_single_output(cursor_io *io, + const struct wally_tx_output *txout, + uint32_t key) { struct sha256_ctx ctx; sha256_init(&ctx); hash_le64(&ctx, txout->satoshi); hash_varbuff(&ctx, txout->script, txout->script_len); - txio_hash_sha256_ctx(io, &ctx, TXIO_UNCACHED); + txio_hash_sha256_ctx(io, &ctx, key); } static void txio_hash_annex(cursor_io *io, @@ -548,17 +601,16 @@ static int bip341_signature_hash( const unsigned char *genesis_blockhash, size_t genesis_blockhash_len, uint32_t sighash, struct wally_map *cache, + bool is_elements, unsigned char *bytes_out, size_t len) { const struct wally_tx_input *txin = tx ? tx->inputs + index : NULL; const struct wally_tx_output *txout = tx ? tx->outputs + index : NULL; - size_t is_elements = 0; const uint32_t output_type = tr_get_output_sighash_type(sighash); const bool sh_anyonecanpay = sighash & WALLY_SIGHASH_ANYONECANPAY; const bool sh_anyprevout = bip341_is_input_hash_type(sighash, WALLY_SIGHASH_ANYPREVOUT); const bool sh_anyprevout_anyscript = bip341_is_input_hash_type(sighash, WALLY_SIGHASH_ANYPREVOUTANYSCRIPT); cursor_io io; - int ret = WALLY_OK; if (!tx || index >= tx->num_inputs || !values || @@ -570,10 +622,6 @@ static int bip341_signature_hash( !bytes_out || len != SHA256_LEN) return WALLY_EINVAL; -#ifdef BUILD_ELEMENTS - if ((ret = wally_tx_is_elements(tx, &is_elements)) != WALLY_OK) - return ret; -#endif if (is_elements) { if (!genesis_blockhash) return WALLY_EINVAL; @@ -645,7 +693,7 @@ static int bip341_signature_hash( txio_hash_sha_outpoint_flags(&io, tx); #endif if (!sh_anyonecanpay && !sh_anyprevout) { - txio_hash_sha_prevouts(&io, tx); + txio_hash_sha_prevouts(&io, tx, TXIO_SHA_PREVOUTS); #ifdef BUILD_ELEMENTS if (is_elements) txio_hash_sha_asset_amounts(&io, values, assets); @@ -653,10 +701,10 @@ static int bip341_signature_hash( #endif txio_hash_sha_amounts(&io, values); txio_hash_sha_scriptpubkeys(&io, scripts); - txio_hash_sha_sequences(&io, tx); + txio_hash_sha_sequences(&io, tx, TXIO_SHA_SEQUENCES); #ifdef BUILD_ELEMENTS if (is_elements) { - txio_hash_sha_issuances(&io, tx); + txio_hash_sha_issuances(&io, tx, TXIO_SHA_ISSUANCES); txio_hash_sha_issuance_rangeproofs(&io, tx); } #endif @@ -664,11 +712,11 @@ static int bip341_signature_hash( if (output_type == WALLY_SIGHASH_ALL) { #ifdef BUILD_ELEMENTS if (is_elements) { - txio_hash_sha_outputs_elements(&io, tx); - txio_hash_sha_output_witnesses(&io, tx); + txio_hash_sha_outputs_elements(&io, tx, TXIO_SHA_OUTPUTS); + txio_hash_sha_output_witnesses(&io, tx, TXIO_SHA_OUTPUT_WITNESSES); } else #endif - txio_hash_sha_outputs(&io, tx); + txio_hash_sha_outputs(&io, tx, TXIO_SHA_OUTPUTS); } /* Input data */ hash_u8(&io.ctx, (tapleaf_script ? 1 : 0) * 2 + (annex ? 1 : 0)); /* spend_type */ @@ -682,10 +730,11 @@ static int bip341_signature_hash( } #ifdef BUILD_ELEMENTS if (is_elements) - txio_hash_input_elements(&io, tx, index, scripts, assets, values); + txio_hash_input_elements(&io, tx, index, scripts, assets, values, + NULL, 0, WALLY_SIGTYPE_SW_V1); else #endif - txio_hash_input(&io, tx, index, scripts, values); + txio_hash_input(&io, tx, index, scripts, values, NULL, 0, WALLY_SIGTYPE_SW_V1); } else if (sh_anyprevout_anyscript) { hash_le32(&io.ctx, tx->inputs[index].sequence); /* nSequence */ } else { @@ -698,11 +747,11 @@ static int bip341_signature_hash( if (output_type == WALLY_SIGHASH_SINGLE) { #ifdef BUILD_ELEMENTS if (is_elements) { - txio_hash_sha_single_output_elements(&io, txout); - txio_hash_sha_single_output_witness(&io, txout); + txio_hash_sha_single_output_elements(&io, txout, TXIO_UNCACHED); + txio_hash_sha_single_output_witness(&io, txout, TXIO_UNCACHED); } else #endif - txio_hash_sha_single_output(&io, txout); + txio_hash_sha_single_output(&io, txout, TXIO_UNCACHED); } /* Tapscript Extensions */ if (tapleaf_script) { @@ -711,10 +760,7 @@ static int bip341_signature_hash( hash_u8(&io.ctx, key_version & 0xff); hash_le32(&io.ctx, codesep_position); } - txio_done(&io); - if (io.max) - ret = WALLY_ERROR; /* Wrote the wrong number of bytes: should not happen! */ - return ret; + return txio_done(&io, 0); } int wally_tx_get_input_signature_hash( @@ -732,14 +778,26 @@ int wally_tx_get_input_signature_hash( struct wally_map *cache, unsigned char *bytes_out, size_t len) { + size_t is_elements = 0; + uint32_t sighash_type = flags & WALLY_SIGTYPE_MASK; + int ret = WALLY_EINVAL; + if (!flags || (flags & ~SIGTYPE_ALL)) return WALLY_EINVAL; - if (flags & WALLY_SIGTYPE_SW_V1) + +#ifdef BUILD_ELEMENTS + if ((ret = wally_tx_is_elements(tx, &is_elements)) != WALLY_OK) + return ret; +#endif + + /* FIXME: Support segwit/pre-segwit hashing */ + if (sighash_type == WALLY_SIGTYPE_SW_V1) return bip341_signature_hash(tx, index, scripts, assets, values, script, script_len, key_version, codesep_position, annex, annex_len, genesis_blockhash, genesis_blockhash_len, - sighash, cache, bytes_out, len); - return WALLY_ERROR; /* FIXME: Support segwit/pre-segwit hashing */ + sighash, cache, is_elements, + bytes_out, len); + return ret; } From be8e7ef2b5a67d3ee8b55606b181e783d21baaa0 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sat, 22 Feb 2025 23:47:29 +1300 Subject: [PATCH 20/22] tx: implement (caching) segwit v0/bip143 signature hashing Share parameter checking code between bip143/bip431, and reject all-zero genesis hashes in the signing code itself. --- src/tx_io.c | 188 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 152 insertions(+), 36 deletions(-) diff --git a/src/tx_io.c b/src/tx_io.c index 0eaaf9c08..b4eb4db71 100644 --- a/src/tx_io.c +++ b/src/tx_io.c @@ -37,6 +37,8 @@ #define TXIO_SHA_OUTPUT_WITNESSES_D (TXIO_SHA_OUTPUTS | TXIO_SHA256_D) /* ... end of segwit cached data */ +static const unsigned char zero_hash[SHA256_LEN]; + /* SHA256(TapSighash) */ static const unsigned char TAPSIGHASH_SHA256[SHA256_LEN] = { 0xf4, 0x0a, 0x48, 0xdf, 0x4b, 0x2a, 0x70, 0xc8, 0xb4, 0x92, 0x4b, 0xf2, 0x65, 0x46, 0x61, 0xed, @@ -555,6 +557,107 @@ static void txio_hash_tapleaf_hash(cursor_io *io, } } +/* BIP 143 */ +static int bip143_signature_hash( + const struct wally_tx *tx, size_t index, + const struct wally_map *values, + const unsigned char *scriptcode, size_t scriptcode_len, + uint32_t sighash, + struct wally_map *cache, + bool is_elements, + unsigned char *bytes_out, size_t len) +{ + const struct wally_tx_input *txin = tx ? tx->inputs + index : NULL; + const struct wally_tx_output *txout = tx ? tx->outputs + index : NULL; + const bool sh_anyonecanpay = sighash & WALLY_SIGHASH_ANYONECANPAY; +#ifdef BUILD_ELEMENTS + const bool sh_rangeproof = sighash & WALLY_SIGHASH_RANGEPROOF; +#endif + const bool sh_none = (sighash & WALLY_SIGHASH_MASK) == WALLY_SIGHASH_NONE; + const bool sh_single = (sighash & WALLY_SIGHASH_MASK) == WALLY_SIGHASH_SINGLE; + cursor_io io; + + /* Note that scriptcode can be empty, so we don't check it here */ + if (!tx || !values || BYTES_INVALID(scriptcode, scriptcode_len) || + sighash & 0xffffff00) + return WALLY_EINVAL; + + { + /* Validate input values: We must have the value at 'index'. */ + bool (*value_len_fn)(size_t) = is_elements ? value_len_ok : satoshi_len_ok; + if (!map_has_one(values, index, value_len_fn)) + return WALLY_EINVAL; + } + + /* Init */ + io.cache = cache; + io.cursor = bytes_out; + io.max = len; + sha256_init(&io.ctx); + /* Tx data */ + hash_le32(&io.ctx, tx->version); + if (sh_anyonecanpay) + hash_bytes(&io.ctx, zero_hash, sizeof(zero_hash)); + else + txio_hash_sha_prevouts(&io, tx, TXIO_SHA_PREVOUTS_D); + if (sh_anyonecanpay || sh_single || sh_none) + hash_bytes(&io.ctx, zero_hash, sizeof(zero_hash)); + else + txio_hash_sha_sequences(&io, tx, TXIO_SHA_SEQUENCES_D); +#ifdef BUILD_ELEMENTS + if (is_elements) { + if (sh_anyonecanpay) + hash_bytes(&io.ctx, zero_hash, sizeof(zero_hash)); + else + txio_hash_sha_issuances(&io, tx, TXIO_SHA_ISSUANCES_D); + } +#endif + /* Input data */ + txio_hash_outpoint(&io, txin); +#ifdef BUILD_ELEMENTS + if (is_elements) + txio_hash_input_elements(&io, tx, index, NULL, NULL, values, + scriptcode, scriptcode_len, WALLY_SIGTYPE_SW_V0); + else +#endif + txio_hash_input(&io, tx, index, NULL, values, + scriptcode, scriptcode_len, WALLY_SIGTYPE_SW_V0); + + /* Output data */ + if (sh_none || (sh_single && index >= tx->num_outputs)) + hash_bytes(&io.ctx, zero_hash, sizeof(zero_hash)); + else if (sh_single) { +#ifdef BUILD_ELEMENTS + if (is_elements) + txio_hash_sha_single_output_elements(&io, txout, TXIO_UNCACHED_D); + else +#endif + txio_hash_sha_single_output(&io, txout, TXIO_UNCACHED_D); + } else { +#ifdef BUILD_ELEMENTS + if (is_elements) + txio_hash_sha_outputs_elements(&io, tx, TXIO_SHA_OUTPUTS_D); + else +#endif + txio_hash_sha_outputs(&io, tx, TXIO_SHA_OUTPUTS_D); + } + +#ifdef BUILD_ELEMENTS + if (sh_rangeproof) { + if (sh_none || (sh_single && index >= tx->num_outputs)) + hash_bytes(&io.ctx, zero_hash, sizeof(zero_hash)); + else if (sh_single) + txio_hash_sha_single_output_witness(&io, txout, TXIO_UNCACHED_D); + else + txio_hash_sha_output_witnesses(&io, tx, TXIO_SHA_OUTPUT_WITNESSES_D); + } +#endif + + hash_le32(&io.ctx, tx->locktime); + hash_le32(&io.ctx, sighash); + return txio_done(&io, TXIO_SHA256_D); +} + /* BIP 341 */ static void txio_bip341_init(cursor_io *io, const unsigned char *genesis_blockhash, size_t genesis_blockhash_len) @@ -612,18 +715,12 @@ static int bip341_signature_hash( const bool sh_anyprevout_anyscript = bip341_is_input_hash_type(sighash, WALLY_SIGHASH_ANYPREVOUTANYSCRIPT); cursor_io io; - if (!tx || index >= tx->num_inputs || - !values || - BYTES_INVALID(tapleaf_script, tapleaf_script_len) || - key_version > 1 || - codesep_position != WALLY_NO_CODESEPARATOR || /* TODO: Add support */ - BYTES_INVALID(annex, annex_len) || (annex && *annex != 0x50) || - BYTES_INVALID_N(genesis_blockhash, genesis_blockhash_len, SHA256_LEN) || - !bytes_out || len != SHA256_LEN) - return WALLY_EINVAL; + if (index >= tx->num_inputs || (annex && *annex != 0x50)) + return WALLY_EINVAL; if (is_elements) { - if (!genesis_blockhash) + if (!genesis_blockhash || + mem_is_zero(genesis_blockhash, genesis_blockhash_len)) return WALLY_EINVAL; } else { genesis_blockhash = NULL; @@ -653,30 +750,6 @@ static int bip341_signature_hash( return WALLY_EINVAL; } - switch (sighash) { - case WALLY_SIGHASH_DEFAULT: - case WALLY_SIGHASH_ALL: - case WALLY_SIGHASH_NONE: - case WALLY_SIGHASH_SINGLE: - case WALLY_SIGHASH_ALL | WALLY_SIGHASH_ANYONECANPAY: - case WALLY_SIGHASH_NONE | WALLY_SIGHASH_ANYONECANPAY: - case WALLY_SIGHASH_SINGLE | WALLY_SIGHASH_ANYONECANPAY: - break; /* Always valid */ - case WALLY_SIGHASH_ALL | WALLY_SIGHASH_ANYPREVOUT: - case WALLY_SIGHASH_NONE | WALLY_SIGHASH_ANYPREVOUT: - case WALLY_SIGHASH_SINGLE | WALLY_SIGHASH_ANYPREVOUT: - case WALLY_SIGHASH_ALL | WALLY_SIGHASH_ANYPREVOUT | WALLY_SIGHASH_ANYONECANPAY: - case WALLY_SIGHASH_NONE | WALLY_SIGHASH_ANYPREVOUT | WALLY_SIGHASH_ANYONECANPAY: - case WALLY_SIGHASH_SINGLE | WALLY_SIGHASH_ANYPREVOUT | WALLY_SIGHASH_ANYONECANPAY: - if (key_version != 1) - return WALLY_EINVAL; /* Only valid for key_version 1 */ - if (is_elements) - return WALLY_ERROR; /* Elements: unsure of Activation status/no ELIP */ - break; - default: - return WALLY_EINVAL; /* Unknown sighash type */ - } - /* Init */ io.cache = cache; io.cursor = bytes_out; @@ -782,7 +855,12 @@ int wally_tx_get_input_signature_hash( uint32_t sighash_type = flags & WALLY_SIGTYPE_MASK; int ret = WALLY_EINVAL; - if (!flags || (flags & ~SIGTYPE_ALL)) + if (!tx || !tx->num_inputs || !tx->num_outputs || !values || + BYTES_INVALID(script, script_len) || key_version > 1 || + codesep_position != WALLY_NO_CODESEPARATOR || /* TODO: Add support */ + BYTES_INVALID(annex, annex_len) || + BYTES_INVALID_N(genesis_blockhash, genesis_blockhash_len, SHA256_LEN) || + !flags || (flags & ~SIGTYPE_ALL) || !bytes_out || len != SHA256_LEN) return WALLY_EINVAL; #ifdef BUILD_ELEMENTS @@ -790,7 +868,45 @@ int wally_tx_get_input_signature_hash( return ret; #endif - /* FIXME: Support segwit/pre-segwit hashing */ + switch (sighash) { + case WALLY_SIGHASH_DEFAULT: +#if 0 + /* TODO: The previous impl allows a sighash of 0 for + * pre-segwit/segwit v0 txs. We should probably disallow this. + */ + if (sighash_type != WALLY_SIGTYPE_SW_V1) + return WALLY_EINVAL; /* Only valid for taproot */ + break; +#endif + case WALLY_SIGHASH_ALL: + case WALLY_SIGHASH_NONE: + case WALLY_SIGHASH_SINGLE: + case WALLY_SIGHASH_ALL | WALLY_SIGHASH_ANYONECANPAY: + case WALLY_SIGHASH_NONE | WALLY_SIGHASH_ANYONECANPAY: + case WALLY_SIGHASH_SINGLE | WALLY_SIGHASH_ANYONECANPAY: + break; /* Always valid */ + case WALLY_SIGHASH_ALL | WALLY_SIGHASH_ANYPREVOUT: + case WALLY_SIGHASH_NONE | WALLY_SIGHASH_ANYPREVOUT: + case WALLY_SIGHASH_SINGLE | WALLY_SIGHASH_ANYPREVOUT: + case WALLY_SIGHASH_ALL | WALLY_SIGHASH_ANYPREVOUT | WALLY_SIGHASH_ANYONECANPAY: + case WALLY_SIGHASH_NONE | WALLY_SIGHASH_ANYPREVOUT | WALLY_SIGHASH_ANYONECANPAY: + case WALLY_SIGHASH_SINGLE | WALLY_SIGHASH_ANYPREVOUT | WALLY_SIGHASH_ANYONECANPAY: + if (sighash_type != WALLY_SIGTYPE_SW_V1 || key_version != 1) + return WALLY_EINVAL; /* Only valid for taproot key version 1 */ + if (is_elements) { + /* Activation status unclear and no ELIP: disallow for now */ + return WALLY_ERROR; + } + break; + default: + return WALLY_EINVAL; /* Unknown sighash type */ + } + + /* FIXME: Support pre-segwit hashing */ + if (sighash_type == WALLY_SIGTYPE_SW_V0) + return bip143_signature_hash(tx, index, values, script, script_len, + sighash, cache, is_elements, + bytes_out, len); if (sighash_type == WALLY_SIGTYPE_SW_V1) return bip341_signature_hash(tx, index, scripts, assets, values, script, script_len, From 1976fbf95cc6ca941d629d9e8b09db1034691deb Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sat, 22 Feb 2025 23:48:43 +1300 Subject: [PATCH 21/22] tx: migrate bip143 signature hashing to the new impl Note the indentation of the existing (no pre-segwit-only) impl is not changed, since this makes the diff smaller, and it too will be migrated in a future commit. --- src/transaction.c | 313 ++++++---------------------------------------- 1 file changed, 35 insertions(+), 278 deletions(-) diff --git a/src/transaction.c b/src/transaction.c index 5aeb8ac31..4356bc274 100644 --- a/src/transaction.c +++ b/src/transaction.c @@ -43,7 +43,6 @@ struct tx_serialize_opts const unsigned char *script; /* scriptPubkey spent by the input we are signing */ size_t script_len; /* length of 'script' in bytes */ uint64_t satoshi; /* Amount of the input we are signing */ - bool bip143; /* Serialize for BIP143 hash */ const unsigned char *value; /* Confidential value of the input we are signing */ size_t value_len; /* length of 'value' in bytes */ }; @@ -1720,37 +1719,7 @@ static int tx_get_lengths(const struct wally_tx *tx, if (opts) { if (flags & WALLY_TX_FLAG_USE_WITNESS) - return WALLY_ERROR; /* Segwit tx hashing uses bip143 opts member */ - - if (opts->bip143) { - size_t issuance_size, amount_size = sizeof(uint64_t); - - *base_size = sizeof(uint32_t) + /* version */ - SHA256_LEN + /* hash prevouts */ - SHA256_LEN + /* hash sequence */ - WALLY_TXHASH_LEN + sizeof(uint32_t) + /* outpoint + index */ - varbuff_get_length(opts->script_len) + /* script */ - sizeof(uint32_t) + /* input sequence */ - SHA256_LEN + /* hash outputs */ - (sh_rangeproof ? SHA256_LEN : 0) + /* rangeproof */ - sizeof(uint32_t) + /* nlocktime */ - sizeof(uint32_t); /* tx sighash */ - - if (is_elements) { - /* Amount, possibly blinded */ - if (!(amount_size = confidential_value_length_from_bytes(opts->value))) - return WALLY_EINVAL; - amount_size += SHA256_LEN; /* TODO: Comment what this represents */ - } - *base_size += amount_size; - - if (get_txin_issuance_size(tx->inputs + opts->index, - &issuance_size, NULL) != WALLY_OK) - return WALLY_EINVAL; - *base_size += issuance_size; - *witness_size = 0; - return WALLY_OK; - } + return WALLY_ERROR; /* Segwit tx hashing done elsewhere */ } if ((flags & ~WALLY_TX_ALL_FLAGS) || @@ -2048,233 +2017,6 @@ int wally_tx_get_hash_prevouts(const struct wally_tx *tx, return hash_prevouts(buff_p, inputs_size, bytes_out, len, inputs_size > sizeof(buff)); } -static int tx_to_bip143_bytes(const struct wally_tx *tx, - const struct tx_serialize_opts *opts, - uint32_t flags, - unsigned char *bytes_out, size_t len, - size_t *written) -{ - unsigned char buff[TX_STACK_SIZE / 2], *buff_p = buff; - size_t i, inputs_size, outputs_size, rangeproof_size = 0, issuances_size = 0, buff_len = sizeof(buff); - size_t is_elements = 0; - const unsigned char sighash = opts->sighash; - const bool sh_anyonecanpay = sighash & WALLY_SIGHASH_ANYONECANPAY; - const bool sh_rangeproof = sighash & WALLY_SIGHASH_RANGEPROOF; - const bool sh_none = (sighash & WALLY_SIGHASH_MASK) == WALLY_SIGHASH_NONE; - const bool sh_single = (sighash & WALLY_SIGHASH_MASK) == WALLY_SIGHASH_SINGLE; - unsigned char *p = bytes_out, *output_p; - int ret = WALLY_OK; - - (void)flags; - (void)len; - (void)sh_rangeproof; - -#ifdef BUILD_ELEMENTS - if ((ret = wally_tx_is_elements(tx, &is_elements)) != WALLY_OK) - return ret; -#endif - - /* Note we assume tx_to_bytes has already validated all inputs */ - p += uint32_to_le_bytes(tx->version, p); - - inputs_size = tx->num_inputs * (WALLY_TXHASH_LEN + sizeof(uint32_t)); - if (sh_none || (sh_single && opts->index >= tx->num_outputs)) - outputs_size = 0; - else if (sh_single) { - const struct wally_tx_output *output = tx->outputs + opts->index; - size_t wit_size = 0, *wit_p = sh_rangeproof ? &wit_size : NULL; - outputs_size = txout_get_serialized_len(output, is_elements, wit_p); - if (!outputs_size) - goto error; /* Error getting txout length */ - rangeproof_size += wit_size; - } else { - outputs_size = 0; - for (i = 0; i < tx->num_outputs; ++i) { - const struct wally_tx_output *output = tx->outputs + i; - size_t wit_size = 0, *wit_p = sh_rangeproof ? &wit_size : NULL; - size_t n = txout_get_serialized_len(output, is_elements, wit_p); - if (!n) - goto error; /* Error getting txout length */ - outputs_size += n; - rangeproof_size += wit_size; - } - } - -#ifdef BUILD_ELEMENTS - if (is_elements && !sh_anyonecanpay) { - for (i = 0; i < tx->num_inputs; ++i) { - if (tx->inputs[i].features & WALLY_TX_IS_ISSUANCE) { - size_t issuance_size; - if (get_txin_issuance_size(tx->inputs + i, - &issuance_size, NULL) != WALLY_OK) - return WALLY_EINVAL; - issuances_size += issuance_size; - } else - issuances_size += 1; - } - } -#endif /* BUILD_ELEMENTS */ - - if (inputs_size > buff_len || outputs_size > buff_len || - rangeproof_size > buff_len || issuances_size > buff_len) { - buff_len = inputs_size > outputs_size ? inputs_size : outputs_size; - buff_len = buff_len > rangeproof_size ? buff_len : rangeproof_size; - buff_len = buff_len > issuances_size ? buff_len : issuances_size; - buff_p = wally_malloc(buff_len); - if (buff_p == NULL) - return WALLY_ENOMEM; - } - - /* Inputs */ - if (sh_anyonecanpay) - memset(p, 0, SHA256_LEN); - else { - for (i = 0; i < tx->num_inputs; ++i) { - unsigned char *tmp_p = buff_p + i * (WALLY_TXHASH_LEN + sizeof(uint32_t)); - memcpy(tmp_p, tx->inputs[i].txhash, WALLY_TXHASH_LEN); - uint32_to_le_bytes(tx->inputs[i].index, tmp_p + WALLY_TXHASH_LEN); - } - - if ((ret = wally_sha256d(buff_p, inputs_size, p, SHA256_LEN)) != WALLY_OK) - goto error; - } - p += SHA256_LEN; - - /* Sequences */ - if (sh_anyonecanpay || sh_single || sh_none) - memset(p, 0, SHA256_LEN); - else { - for (i = 0; i < tx->num_inputs; ++i) - uint32_to_le_bytes(tx->inputs[i].sequence, buff_p + i * sizeof(uint32_t)); - - ret = wally_sha256d(buff_p, tx->num_inputs * sizeof(uint32_t), p, SHA256_LEN); - if (ret != WALLY_OK) - goto error; - } - p += SHA256_LEN; - -#ifdef BUILD_ELEMENTS - if (is_elements) { - /* sha_issuances */ - if (sh_anyonecanpay) - memset(p, 0, SHA256_LEN); - else { - unsigned char *tmp_p = buff_p; - for (i = 0; i < tx->num_inputs; ++i) { - if (tx->inputs[i].features & WALLY_TX_IS_ISSUANCE) { - memcpy(tmp_p, tx->inputs[i].blinding_nonce, SHA256_LEN); - tmp_p += SHA256_LEN; - memcpy(tmp_p, tx->inputs[i].entropy, SHA256_LEN); - tmp_p += SHA256_LEN; - tmp_p += confidential_value_to_bytes(tx->inputs[i].issuance_amount, - tx->inputs[i].issuance_amount_len, tmp_p); - tmp_p += confidential_value_to_bytes(tx->inputs[i].inflation_keys, - tx->inputs[i].inflation_keys_len, tmp_p); - } - else - *tmp_p++ = 0; - } - - if ((ret = wally_sha256d(buff_p, issuances_size, p, SHA256_LEN)) != WALLY_OK) - goto error; - } - p += SHA256_LEN; - } -#endif /* BUILD_ELEMENTS */ - - /* Input details */ - memcpy(p, tx->inputs[opts->index].txhash, WALLY_TXHASH_LEN); - p += WALLY_TXHASH_LEN; - p += uint32_to_le_bytes(tx->inputs[opts->index].index, p); - p += varbuff_to_bytes(opts->script, opts->script_len, p); - if (!is_elements) - p += uint64_to_le_bytes(opts->satoshi, p); -#ifdef BUILD_ELEMENTS - else - p += confidential_value_to_bytes(opts->value, opts->value_len, p); -#endif - p += uint32_to_le_bytes(tx->inputs[opts->index].sequence, p); - -#ifdef BUILD_ELEMENTS - if (is_elements && (tx->inputs[opts->index].features & WALLY_TX_IS_ISSUANCE)) { - memcpy(p, tx->inputs[opts->index].blinding_nonce, SHA256_LEN); - p += SHA256_LEN; - memcpy(p, tx->inputs[opts->index].entropy, SHA256_LEN); - p += SHA256_LEN; - p += confidential_value_to_bytes(tx->inputs[opts->index].issuance_amount, - tx->inputs[opts->index].issuance_amount_len, p); - p += confidential_value_to_bytes(tx->inputs[opts->index].inflation_keys, - tx->inputs[opts->index].inflation_keys_len, p); - } -#endif - - /* Outputs */ - if (sh_none || (sh_single && opts->index >= tx->num_outputs)) - memset(p, 0, SHA256_LEN); - else { - output_p = buff_p; - for (i = 0; i < tx->num_outputs; ++i) { - if (sh_single && i != opts->index) - continue; - if (!is_elements) - output_p += uint64_to_le_bytes(tx->outputs[i].satoshi, output_p); -#ifdef BUILD_ELEMENTS - else { - output_p += confidential_value_to_bytes(tx->outputs[i].asset, tx->outputs[i].asset_len, - output_p); - output_p += confidential_value_to_bytes(tx->outputs[i].value, tx->outputs[i].value_len, - output_p); - output_p += confidential_value_to_bytes(tx->outputs[i].nonce, tx->outputs[i].nonce_len, - output_p); - } -#endif - output_p += varbuff_to_bytes(tx->outputs[i].script, - tx->outputs[i].script_len, output_p); - } - - ret = wally_sha256d(buff_p, outputs_size, p, SHA256_LEN); - if (ret != WALLY_OK) - goto error; - } - p += SHA256_LEN; - - /* rangeproof */ -#ifdef BUILD_ELEMENTS - if (is_elements && sh_rangeproof) { - if (sh_none || (sh_single && opts->index >= tx->num_outputs)) - memset(p, 0, SHA256_LEN); - else { - output_p = buff_p; - for (i = 0; i < tx->num_outputs; ++i) { - if (sh_single && i != opts->index) - continue; - output_p += varbuff_to_bytes(tx->outputs[i].rangeproof, - tx->outputs[i].rangeproof_len, output_p); - output_p += varbuff_to_bytes(tx->outputs[i].surjectionproof, - tx->outputs[i].surjectionproof_len, output_p); - } - ret = wally_sha256d(buff_p, rangeproof_size, p, SHA256_LEN); - if (ret != WALLY_OK) - goto error; - } - p += SHA256_LEN; - } -#endif - - /* nlocktime and sighash*/ - p += uint32_to_le_bytes(tx->locktime, p); - p += uint32_to_le_bytes(opts->tx_sighash, p); - - *written = p - bytes_out; - -error: - if (buff_p != buff) - clear_and_free(buff_p, buff_len); - else - wally_clear(buff, sizeof(buff)); - return ret; -} - static int tx_to_bytes(const struct wally_tx *tx, const struct tx_serialize_opts *opts, uint32_t flags, @@ -2324,9 +2066,6 @@ static int tx_to_bytes(const struct wally_tx *tx, return WALLY_OK; } - if (opts && opts->bip143) - return tx_to_bip143_bytes(tx, opts, flags, bytes_out, len, written); - if (flags & WALLY_TX_FLAG_USE_WITNESS) { if (wally_tx_get_witness_count(tx, &witness_count) != WALLY_OK) return WALLY_EINVAL; @@ -2993,14 +2732,41 @@ static int tx_get_signature_hash(const struct wally_tx *tx, uint32_t sighash, uint32_t tx_sighash, uint32_t flags, unsigned char *bytes_out, size_t len) { + size_t is_elements = 0; + +#ifdef BUILD_ELEMENTS + if (wally_tx_is_elements(tx, &is_elements) != WALLY_OK) + return WALLY_EINVAL; +#endif + + if (extra || extra_len || extra_offset) + return WALLY_ERROR; /* Not implemented, not planned */ + + if (flags & WALLY_TX_FLAG_USE_WITNESS) { + struct wally_map_item value_item; + struct wally_map values = { &value_item, 1, 1, NULL }; + values.items[0].key = NULL; + values.items[0].key_len = index; + if (is_elements) { + value_item.value = (unsigned char*)value; + value_item.value_len = value_len; + } else { + value_item.value = (unsigned char*)&satoshi; + value_item.value_len = sizeof(uint64_t); + } + return wally_tx_get_input_signature_hash(tx, index, NULL, NULL, + &values, script, script_len, + 0, WALLY_NO_CODESEPARATOR, + NULL, 0, NULL, 0, + sighash, WALLY_SIGTYPE_SW_V0, + NULL, bytes_out, len); + } else { unsigned char buff[TX_STACK_SIZE], *buff_p = buff; size_t n, n2; - size_t is_elements = 0; - const bool is_bip143 = (flags & WALLY_TX_FLAG_USE_WITNESS) ? true : false; int ret; const struct tx_serialize_opts opts = { sighash, tx_sighash, index, script, script_len, satoshi, - is_bip143, value, value_len + value, value_len }; if (!is_valid_tx(tx) || BYTES_INVALID(script, script_len) || @@ -3009,23 +2775,13 @@ static int tx_get_signature_hash(const struct wally_tx *tx, (flags & ~WALLY_TX_ALL_FLAGS) || !bytes_out || len < SHA256_LEN) return WALLY_EINVAL; - if (extra || extra_len || extra_offset) - return WALLY_ERROR; /* FIXME: Not implemented yet */ - if (index >= tx->num_inputs || (index >= tx->num_outputs && (sighash & WALLY_SIGHASH_MASK) == WALLY_SIGHASH_SINGLE)) { - if (!(flags & WALLY_TX_FLAG_USE_WITNESS)) { - memset(bytes_out, 0, SHA256_LEN); - bytes_out[0] = 0x1; - return WALLY_OK; - } + memset(bytes_out, 0, SHA256_LEN); + bytes_out[0] = 0x1; + return WALLY_OK; } -#ifdef BUILD_ELEMENTS - if ((ret = wally_tx_is_elements(tx, &is_elements)) != WALLY_OK) - goto fail; -#endif - if ((ret = tx_get_length(tx, &opts, 0, &n, is_elements != 0)) != WALLY_OK) goto fail; @@ -3049,6 +2805,7 @@ static int tx_get_signature_hash(const struct wally_tx *tx, wally_clear(buff, sizeof(buff)); return ret; } +} int wally_tx_get_signature_hash(const struct wally_tx *tx, size_t index, From fd74e27147973fb08b16694d9a9e7be446672ad9 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Mon, 24 Feb 2025 07:25:07 +1300 Subject: [PATCH 22/22] psbt: migrate bip143 signature hashing to the new impl --- src/psbt.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/psbt.c b/src/psbt.c index 4d7c82ae5..4d3262ad0 100644 --- a/src/psbt.c +++ b/src/psbt.c @@ -4494,8 +4494,10 @@ int wally_psbt_get_input_signature_hash(struct wally_psbt *psbt, size_t index, const struct wally_psbt_input *inp = psbt_get_input(psbt, index); const struct wally_tx_output *utxo = utxo_from_input(psbt, inp); size_t is_pset; - uint32_t sighash, sig_flags; + uint32_t sighash; const bool is_taproot = is_taproot_input(psbt, inp); + /* FIXME: Determine segwitness in a smarter way (e.g. prevout script */ + const bool is_segwit = inp->witness_utxo != NULL; int ret; if (!tx || !inp || !utxo || flags) @@ -4514,22 +4516,24 @@ int wally_psbt_get_input_signature_hash(struct wally_psbt *psbt, size_t index, else if (sighash & 0xffffff00) return WALLY_EINVAL; - if (is_taproot) { + if (is_taproot || is_segwit) { struct wally_map scripts, assets, values; struct wally_map *assets_p = is_pset ? &assets : NULL; -#ifdef BUILD_ELEMENTS - if (is_pset && mem_is_zero(psbt->genesis_blockhash, sizeof(psbt->genesis_blockhash))) - return WALLY_EINVAL; /* Genesis blockhash is required for taproot */ -#endif + if (is_taproot) { + /* FIXME: Support script path spends */ + script = NULL; + script_len = 0; + } ret = get_signing_data(psbt, &scripts, assets_p, &values); if (ret == WALLY_OK) ret = wally_tx_get_input_signature_hash(tx, index, &scripts, assets_p, &values, - NULL, 0, 0, WALLY_NO_CODESEPARATOR, - NULL, 0, + script, script_len, + 0, WALLY_NO_CODESEPARATOR, NULL, 0, psbt->genesis_blockhash, sizeof(psbt->genesis_blockhash), - sighash, WALLY_SIGTYPE_SW_V1, + sighash, + is_taproot ? WALLY_SIGTYPE_SW_V1 : WALLY_SIGTYPE_SW_V0, psbt->signing_cache, bytes_out, len); wally_free(scripts.items); /* No need to clear the value pointers */ @@ -4539,18 +4543,16 @@ int wally_psbt_get_input_signature_hash(struct wally_psbt *psbt, size_t index, return ret; } - sig_flags = inp->witness_utxo ? WALLY_TX_FLAG_USE_WITNESS : 0; - #ifdef BUILD_ELEMENTS if (is_pset) return wally_tx_get_elements_signature_hash(tx, index, script, script_len, utxo->value, utxo->value_len, - sighash, sig_flags, bytes_out, + sighash, 0, bytes_out, len); #endif return wally_tx_get_btc_signature_hash(tx, index, script, script_len, - utxo->satoshi, sighash, sig_flags, + utxo->satoshi, sighash, 0, bytes_out, len); }