diff --git a/_cmake/config.h.in b/_cmake/config.h.in index fd045ba27..0a8f3b2a7 100644 --- a/_cmake/config.h.in +++ b/_cmake/config.h.in @@ -20,7 +20,7 @@ #cmakedefine HAVE_INLINE_ASM @HAVE_INLINE_ASM@ /* Define to 1 if you have the header file. */ -#cmakedefine HAVE_MBEDTLS_SHA256_H_ @HAVE_MBEDTLS_SHA256_H@ +#cmakedefine HAVE_MBEDTLS_SHA256_H @HAVE_MBEDTLS_SHA256_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_MBEDTLS_SHA512_H @HAVE_MBEDTLS_SHA512_H@ diff --git a/configure.ac b/configure.ac index c46255cda..84823b45f 100644 --- a/configure.ac +++ b/configure.ac @@ -83,7 +83,7 @@ AC_ARG_ENABLE(minimal, AS_HELP_STRING([--enable-minimal],[enable minimal size/memory footprint build (default: no)]), [minimal=$enableval], [minimal=no]) AC_ARG_ENABLE(mbed-tls, - AS_HELP_STRING([--enable-mbed-tls],[enable minimal size/memory footprint build (default: no)]), + AS_HELP_STRING([--enable-mbed-tls],[use mbed-tls hashing functions where available (default: no)]), [mbedtls=$enableval], [mbedtls=no]) AC_ARG_ENABLE(secp256k1-tests, AS_HELP_STRING([--enable-secp256k1-tests],[enable secp256k1 tests (default: no)]), diff --git a/include/wally_script.h b/include/wally_script.h index 563ff97ce..d6cc05d53 100644 --- a/include/wally_script.h +++ b/include/wally_script.h @@ -27,7 +27,8 @@ extern "C" { #define WALLY_SCRIPTPUBKEY_P2WSH_LEN 34 /** OP_0 [SHA256] */ #define WALLY_SCRIPTPUBKEY_P2TR_LEN 34 /** OP_1 [X-ONLY-PUBKEY] */ -#define WALLY_LEAF_VERSION_TAPSCRIPT 0xc0 /** BIP-342 tapscript leaf version */ +#define WALLY_LEAF_VERSION_TAPSCRIPT 0xc0 /** BIP-342 tapscript leaf version */ +#define WALLY_LEAF_VERSION_TAPSCRIPT_ELEMENTS 0xc4 /** Elements tapscript leaf version */ #define WALLY_SCRIPTPUBKEY_OP_RETURN_MAX_LEN 83 /** OP_RETURN [80 bytes of data] */ diff --git a/include/wally_transaction.h b/include/wally_transaction.h index 03749216b..e18b0484e 100644 --- a/include/wally_transaction.h +++ b/include/wally_transaction.h @@ -894,8 +894,10 @@ WALLY_CORE_API int wally_tx_get_signature_hash( *| 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. + *| for. For taproot, the leaf script to sign with if any. Its leaf version + *| is taken to be ``WALLY_LEAF_VERSION_TAPSCRIPT``, or + *| ``WALLY_LEAF_VERSION_TAPSCRIPT_ELEMENTS`` for Elements transactions. + *| Ignored for pre-segwit signing. * :param script_len: Length of ``script`` in bytes. * :param key_version: For taproot signing, the version of the pubkey *| in ``script`` when signing with a script path. Currently must be ``1`` diff --git a/src/Makefile.am b/src/Makefile.am index 150f7a545..ab7da2699 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -224,12 +224,13 @@ SUBDIRS = secp256k1 endif TESTS = +TESTS_CFLAGS = -I$(top_srcdir) -I$(top_srcdir)/include -I$(srcdir)/ccan -I$(top_srcdir)/src/secp256k1/include $(AM_CFLAGS) noinst_PROGRAMS = if RUN_TESTS TESTS += test_bech32 noinst_PROGRAMS += test_bech32 test_bech32_SOURCES = ctest/test_bech32.c -test_bech32_CFLAGS = -I$(top_srcdir)/include $(AM_CFLAGS) +test_bech32_CFLAGS = $(TESTS_CFLAGS) test_bech32_LDADD = $(lib_LTLIBRARIES) @CTEST_EXTRA_STATIC@ if PYTHON_MANYLINUX test_bech32_LDADD += $(PYTHON_LIBS) @@ -237,7 +238,7 @@ endif TESTS += test_psbt noinst_PROGRAMS += test_psbt test_psbt_SOURCES = ctest/test_psbt.c ccan/ccan/str/hex/hex.c -test_psbt_CFLAGS = -I$(top_srcdir)/include $(AM_CFLAGS) -I$(srcdir)/ccan +test_psbt_CFLAGS = $(TESTS_CFLAGS) test_psbt_LDADD = $(lib_LTLIBRARIES) @CTEST_EXTRA_STATIC@ if PYTHON_MANYLINUX test_psbt_LDADD += $(PYTHON_LIBS) @@ -245,7 +246,7 @@ endif TESTS += test_psbt_limits noinst_PROGRAMS += test_psbt_limits test_psbt_limits_SOURCES = ctest/test_psbt_limits.c ccan/ccan/str/hex/hex.c -test_psbt_limits_CFLAGS = -I$(top_srcdir)/include $(AM_CFLAGS) -I$(srcdir)/ccan +test_psbt_limits_CFLAGS = $(TESTS_CFLAGS) test_psbt_limits_LDADD = $(lib_LTLIBRARIES) @CTEST_EXTRA_STATIC@ if PYTHON_MANYLINUX test_psbt_limits_LDADD += $(PYTHON_LIBS) @@ -254,7 +255,7 @@ if USE_PTHREAD TESTS += test_clear noinst_PROGRAMS += test_clear test_clear_SOURCES = ctest/test_clear.c -test_clear_CFLAGS = -I$(top_srcdir)/include -I$(top_srcdir) -I$(top_srcdir)/src/secp256k1/include $(PTHREAD_CFLAGS) $(AM_CFLAGS) $(NOOPT_CFLAGS) $(NOBUILTIN_CFLAGS) +test_clear_CFLAGS = $(TESTS_CFLAGS) $(PTHREAD_CFLAGS) $(AM_CFLAGS) $(NOOPT_CFLAGS) $(NOBUILTIN_CFLAGS) test_clear_LIBS = $(PTHREAD_LIBS) test_clear_LDADD = $(lib_LTLIBRARIES) @CTEST_EXTRA_STATIC@ if PYTHON_MANYLINUX @@ -264,15 +265,23 @@ endif TESTS += test_coinselection noinst_PROGRAMS += test_coinselection test_coinselection_SOURCES = ctest/test_coinselection.c -test_coinselection_CFLAGS = -I$(top_srcdir)/include $(AM_CFLAGS) +test_coinselection_CFLAGS = $(TESTS_CFLAGS) test_coinselection_LDADD = $(lib_LTLIBRARIES) @CTEST_EXTRA_STATIC@ if PYTHON_MANYLINUX test_coinselection_LDADD += $(PYTHON_LIBS) endif +TESTS += test_script +noinst_PROGRAMS += test_script +test_script_SOURCES = ctest/test_script.c +test_script_CFLAGS = $(TESTS_CFLAGS) +test_script_LDADD = $(lib_LTLIBRARIES) @CTEST_EXTRA_STATIC@ +if PYTHON_MANYLINUX +test_script_LDADD += $(PYTHON_LIBS) +endif TESTS += test_tx noinst_PROGRAMS += test_tx test_tx_SOURCES = ctest/test_tx.c -test_tx_CFLAGS = -I$(top_srcdir)/include $(AM_CFLAGS) +test_tx_CFLAGS = $(TESTS_CFLAGS) test_tx_LDADD = $(lib_LTLIBRARIES) @CTEST_EXTRA_STATIC@ if PYTHON_MANYLINUX test_tx_LDADD += $(PYTHON_LIBS) @@ -280,7 +289,7 @@ endif TESTS += test_descriptor noinst_PROGRAMS += test_descriptor test_descriptor_SOURCES = ctest/test_descriptor.c -test_descriptor_CFLAGS = -I$(top_srcdir)/include $(AM_CFLAGS) +test_descriptor_CFLAGS = $(TESTS_CFLAGS) test_descriptor_LDADD = $(lib_LTLIBRARIES) @CTEST_EXTRA_STATIC@ if PYTHON_MANYLINUX test_descriptor_LDADD += $(PYTHON_LIBS) @@ -289,7 +298,7 @@ if BUILD_ELEMENTS TESTS += test_elements_tx noinst_PROGRAMS += test_elements_tx test_elements_tx_SOURCES = ctest/test_elements_tx.c -test_elements_tx_CFLAGS = -I$(top_srcdir)/include $(AM_CFLAGS) +test_elements_tx_CFLAGS = $(TESTS_CFLAGS) test_elements_tx_LDADD = $(lib_LTLIBRARIES) @CTEST_EXTRA_STATIC@ if PYTHON_MANYLINUX test_elements_tx_LDADD += $(PYTHON_LIBS) diff --git a/src/base_58.c b/src/base_58.c index b94354583..264277edc 100644 --- a/src/base_58.c +++ b/src/base_58.c @@ -92,6 +92,8 @@ static int base58_decode(const char *base58, size_t base58_len, /* Take 6 bits to store each 58 bit number, rounded up to the next byte, * then round that up to a uint32_t word boundary. */ + if (base58_len > (SIZE_MAX - 7) / 6) + return WALLY_EINVAL; /* Too long: bn_words would overflow */ bn_words = ((base58_len * 6 + 7) / 8 + 3) / 4; /* Allocate our bignum buffer if it won't fit on the stack */ @@ -201,6 +203,8 @@ int wally_base58_from_bytes(const unsigned char *bytes, size_t bytes_len, return WALLY_OK; /* All 0's */ } + if (bytes_len - zeros > SIZE_MAX / 138) + goto cleanup; /* Too long: bn_bytes would overflow */ bn_bytes = (bytes_len - zeros) * 138 / 100 + 1; /* log(256)/log(58) rounded up */ /* Allocate our bignum buffer if it won't fit on the stack */ diff --git a/src/base_64.c b/src/base_64.c index 27a5bc069..447a33b1b 100644 --- a/src/base_64.c +++ b/src/base_64.c @@ -12,6 +12,8 @@ int wally_base64_from_bytes(const unsigned char *bytes, size_t bytes_len, if (!bytes || !bytes_len || flags || !output) return WALLY_EINVAL; + if (bytes_len > (SIZE_MAX - 1) / 4 * 3) + return WALLY_EINVAL; /* Encoded length would overflow */ encoded_len = base64_encoded_length(bytes_len) + 1; /* +1 for NUL */ if ((encoded = wally_malloc(encoded_len)) == NULL) diff --git a/src/bech32.c b/src/bech32.c index 8600cea04..4b1a5dce1 100644 --- a/src/bech32.c +++ b/src/bech32.c @@ -177,7 +177,6 @@ static int segwit_addr_encode(char *output, const char *hrp, size_t hrp_len, uin ++datalen; return bech32_encode(output, hrp, hrp_len, data, datalen, 90, witver != 0); fail: - wally_clear_2(data, sizeof(data), (void *)witprog, witprog_len); return 0; } @@ -209,24 +208,14 @@ int wally_addr_segwit_from_bytes(const unsigned char *bytes, size_t bytes_len, char **output) { char result[90 + 1]; /* Max encoded length plus NUL */ - size_t push_size; - int ret; size_t witver; size_t addr_family_len = addr_family ? strlen(addr_family) : 0; if (output) *output = 0; - if (!addr_family || flags || !bytes || !bytes_len || !output) - return WALLY_EINVAL; - - if (!script_is_op_n(bytes[0], true, &witver)) - return WALLY_EINVAL; - - ret = script_get_push_size_from_bytes(bytes + 1, bytes_len - 1, &push_size); - if (ret != WALLY_OK) - return WALLY_EINVAL; - else if (witver == 0 && push_size != HASH160_LEN && push_size != SHA256_LEN) + if (!scriptpubkey_is_witness_program(bytes, bytes_len, &witver) || + !addr_family || flags || !output) return WALLY_EINVAL; result[0] = '\0'; diff --git a/src/ctest/_CMakeLists.txt b/src/ctest/_CMakeLists.txt index 2b0a9e8d6..1b9cc5e71 100644 --- a/src/ctest/_CMakeLists.txt +++ b/src/ctest/_CMakeLists.txt @@ -36,3 +36,14 @@ add_executable(test_psbt_limits test_psbt_limits.c) target_include_directories(test_psbt_limits PRIVATE ${CMAKE_BINARY_DIR}) target_link_libraries(test_psbt_limits PRIVATE wallycore) add_test(test_psbt_limits test_psbt_limits) + +# TODO: Requires static linking or exposing scriptint_from_bytes w/WALLY_INTERNAL_API +#add_executable(test_script test_script.c) +#target_include_directories(test_script PRIVATE ${CMAKE_BINARY_DIR} $) +#target_link_libraries(test_script PRIVATE wallycore) +#add_test(test_script test_script) + +add_executable(test_tx test_tx.c) +target_include_directories(test_tx PRIVATE ${CMAKE_BINARY_DIR}) +target_link_libraries(test_tx PRIVATE wallycore) +add_test(test_tx test_tx) diff --git a/src/ctest/test_descriptor.c b/src/ctest/test_descriptor.c index 6f480d223..9fb00c83f 100644 --- a/src/ctest/test_descriptor.c +++ b/src/ctest/test_descriptor.c @@ -5,6 +5,7 @@ #include #include #include +#include #include /* @@ -1293,6 +1294,22 @@ static const descriptor_test g_descriptor_cases[] = { "descriptor errchk - xprivkey - unmatch network5", "wpkh(mainnet_xpriv)", WALLY_NETWORK_LIQUID_REGTEST, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor errchk - wif - zero uncompressed wif", + "pkh(5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAbuatmU)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor errchk - wif - zero compressed wif", + "pkh(KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73Nd2Mcv1)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor errchk - wif - too-large uncompressed wif", + "pkh(5Km2kuu7vtFDPpxywn4u3NLpbr5jKpTB3jsuDU2KYEqetwr388P)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor errchk - wif - too-large compressed wif", + "pkh(L5oLkpV3aqBjhki6LmvChTCV6odsp4SXM6FfU2Gppt5kFqRzExJJ)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, NULL, "", VARS_STD },{ "descriptor errchk - addr - empty addr", "addr()", @@ -2132,8 +2149,105 @@ static const taproot_descriptor_test g_taproot_cases[] = { /* k1,k2,k3 inside multi_a() (as x-only) */ { TR_K3("8bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048", "a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7", "b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284" ) }, { TR_K_NONE }, { TR_K_NONE }, { TR_K_NONE }, { TR_K_NONE } - }} - /* FIXME: Elements test cases */ + }}, +#ifdef BUILD_ELEMENTS + /* Elements taptrees use the "/elements" tagged hashes and the Elements + * tapscript leaf version 0xc4 (not 0xc0). Expected values are computed + * independently of wally following Elements' taproot_construct(). + */ + {{ + "eltr - single leaf pk", + "eltr(x_only,pk(key_1))", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, + "512009d43e325715a7bf0c323f770430e6b2e365ca6a87eb9b86d3c695ef4f068300", + "", VARS_STD + }, + 3, + "b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e", + "3796c4fa9bdb7a19f38edcce3e908cc7a364c70e21d15b3f1c45dde8e45963b7", + { + /* scripts */ + "208bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac", + "", "", "", "", + }, { + /* hashes */ + "3796c4fa9bdb7a19f38edcce3e908cc7a364c70e21d15b3f1c45dde8e45963b7", + "", "", "", "", + }, { + /* control blocks: 0xc4 | odd output key parity */ + "c5b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e", + "", "", "", "", + }, { + /* leaf_keys */ + { TR_K1("8bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048") }, + { TR_K_NONE }, { TR_K_NONE }, { TR_K_NONE }, { TR_K_NONE } + }}, + {{ + "eltr - tr() as elements - single leaf pk", + "tr(x_only,pk(key_1))", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, WALLY_MINISCRIPT_AS_ELEMENTS, + "512009d43e325715a7bf0c323f770430e6b2e365ca6a87eb9b86d3c695ef4f068300", + "", VARS_STD + }, + 3, + "b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e", + "3796c4fa9bdb7a19f38edcce3e908cc7a364c70e21d15b3f1c45dde8e45963b7", + { + /* scripts */ + "208bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac", + "", "", "", "", + }, { + /* hashes */ + "3796c4fa9bdb7a19f38edcce3e908cc7a364c70e21d15b3f1c45dde8e45963b7", + "", "", "", "", + }, { + /* control blocks */ + "c5b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e", + "", "", "", "", + }, { + /* leaf_keys */ + { TR_K1("8bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048") }, + { TR_K_NONE }, { TR_K_NONE }, { TR_K_NONE }, { TR_K_NONE } + }}, + {{ + "eltr - 3 leaf right balanced", + "eltr(x_only,{pk(key_1),{pk(key_2),pk(key_3)}})", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, + "5120d08a01f83338d5af74fa32802da45efa9836b64fd0e491fc56673081e567f1cb", + "", VARS_STD + }, + 5, + "b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e", + "7aca839053aa7171103ecf85b7a1f249f52f1707e7086f36ef131a655a5a687d", + { + /* scripts */ + "208bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac", /* [k1] CHECKSIG */ + "20a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac", /* [k2] CHECKSIG */ + "20b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac", /* [k3] CHECKSIG */ + "", "", + }, { + /* hashes */ + "3796c4fa9bdb7a19f38edcce3e908cc7a364c70e21d15b3f1c45dde8e45963b7", /* k1 */ + "ab10947edf94833e249d1841b3a2732618135e500df66a93584232dcbf7a211b", /* k2 */ + "c334a00c7dda9e85ebb762ca8a4c457dcfd00c2c2a77a09621625ef55fe0437c", /* k3 */ + "", "", + }, { + /* control blocks: 0xc4 | even output key parity */ + /* /LHS: [internal_key] + branch(k2,k3) */ + "c4b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0eee914a8c1458861503e47da32d83caaec61e9108a7daf5982276750cc0f35278", + /* /RHS/LHS: [internal_key] + k3 + k1 */ + "c4b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0ec334a00c7dda9e85ebb762ca8a4c457dcfd00c2c2a77a09621625ef55fe0437c3796c4fa9bdb7a19f38edcce3e908cc7a364c70e21d15b3f1c45dde8e45963b7", + /* /RHS/RHS: [internal_key] + k2 + k1 */ + "c4b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0eab10947edf94833e249d1841b3a2732618135e500df66a93584232dcbf7a211b3796c4fa9bdb7a19f38edcce3e908cc7a364c70e21d15b3f1c45dde8e45963b7", + "", "", + }, { + /* leaf_keys */ + { TR_K1("8bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048") }, /* k1 xo */ + { TR_K1("a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7") }, /* k2 xo */ + { TR_K1("b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284") }, /* k3 xo */ + { TR_K_NONE }, { TR_K_NONE } + }}, +#endif /* BUILD_ELEMENTS */ }; @@ -2576,6 +2690,63 @@ static const struct address_test { WALLY_NETWORK_LIQUID, 0, 0, 0, ADDR("lq1pq26fndnz8ef6umlz6e2755sm6j5jwxv3tdt2295mr4mx6ux0uf8vcc2tuvwx7k7g9kvhhpux07vqpm3qjj8uwdj94650265ustv0xy8z8wfacw9e5a5t") + }, { + /* Taptree leaves use the Elements tapscript leaf version 0xc4 */ + "address - slip77 eltr script tree", + "ct(slip77(slip77_key),eltr(x_only,{pk(key_1),{pk(key_2),pk(key_3)}}))", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("lq1pqvrmexgzdqfplvx5lmtytu2jqwyqklahrva5pr87ujeq0c6s7el285y2q8urxwx44a605v5q9kj9a75cx6myl58yj879veess8jk0uwtedks5lxz9v7l") + }, + /* ct() descriptors are Elements without the el prefix, and blinded */ + { + "address - ELIP 150 Valid Descriptor 1 (no el prefix)", + "ct(mainnet_xpub,pkh(key_4))", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("VTpvZZYdbhbyVF3Wa99eMjgXhfvu4LS26dR2FwMfNXq7FDX73HZEsZr3VvgH9EDgQnYK7sP6ACKSuMGw") + }, { + "address - ELIP 150 Valid Descriptor 2 (no el prefix)", + "ct(mainnet_xpub,wpkh(key_4))", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("lq1qqg5s7xj7upzl7h4q2k2wj4vq63nvaktn0egqu09nqcr6d44p4evaqknpl78t02k2xqgdh9ltmfmpy9ssk7qfvghdsfr4mvr9c") + }, { + "address - ELIP 150 Valid Descriptor 3 (no el prefix)", + "ct(mainnet_xpub,sh(wpkh(key_4)))", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("VJL8znN4XjXEUKzDaYsqdzRASGLY2KHxC4N6g5b5QvrNjXfeKp83Ci9AW2a8QzbZjpEffoy4PEywpLAZ") + }, { + "address - ELIP 150 Valid Descriptor 4 (no el prefix)", + "ct(mainnet_xpub,tr(key_4))", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("lq1pq0nsl8du3gsuk7r90sgm78259mmv6mt9d4yvj30zr3u052ufs5meuc2tuvwx7k7g9kvhhpux07vqpm3qjj8uwdj94650265ustv0xy8zrdxdfgp8g9pl") + }, { + "address - ELIP 150 View Descriptor 2 (no el prefix)", + "ct(c25deb86fa11e49d651d7eae27c220ef930fbd86ea023eebfa73e54875647963,wpkh(021a8fb6bd5a653b021b98a2a785725b8ddacfe3687bc043aa7f4d25d3a48d40b5))", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("lq1qq265u4g3k3m3qpyxjwpdrtnm293wuxgvs9xzmzcs2ck0mv5rx23w4d7xfsednsmmxrszfe7s9rs0c6cvf3dfytxax3utlmm46") + }, { + "address - ELIP 150 Non-View Descriptor 2 (no el prefix)", + "ct(0286fc9a38e765d955e9b0bcc18fa9ae81b0c893e2dd1ef5542a9c73780a086b90,wpkh(021a8fb6bd5a653b021b98a2a785725b8ddacfe3687bc043aa7f4d25d3a48d40b5))", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("lq1qq265u4g3k3m3qpyxjwpdrtnm293wuxgvs9xzmzcs2ck0mv5rx23w4d7xfsednsmmxrszfe7s9rs0c6cvf3dfytxax3utlmm46") + }, { + "address - slip77 (ELIP 150 Valid Descriptor 6, no el prefix)", + "ct(slip77(slip77_key),wpkh(key_4))", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("lq1qqdx5wnttttzulcs6ujlg9pfts6mp3r4sdwg5ekdej566n5wxzk88vknpl78t02k2xqgdh9ltmfmpy9ssk7qfvr33xa22hpw23") + }, { + "address - slip77 (ELIP 150 Valid Descriptor 8, no el prefix)", + "ct(slip77(slip77_key),tr(key_4))", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("lq1pq26fndnz8ef6umlz6e2755sm6j5jwxv3tdt2295mr4mx6ux0uf8vcc2tuvwx7k7g9kvhhpux07vqpm3qjj8uwdj94650265ustv0xy8z8wfacw9e5a5t") }, #endif /* BUILD_ELEMENTS */ /* @@ -3134,6 +3305,43 @@ static bool check_taptree_depth_limit(void) return tests_ok; } +/* Verify that substituted values that would overflow the length of the + * canonicalized descriptor are rejected. The value lengths given are larger + * than the actual value: they must be rejected before any value is read. + */ +static bool check_canonical_length_overflow(void) +{ + static const struct { + const char *descriptor; + size_t value_len; + } cases[] = { + /* Multiple substitutions overflow */ + { "a,a", SIZE_MAX / 2 + 1 }, + /* A single substitution leaves no room for the checksum */ + { "a", SIZE_MAX - 5 }, + /* Text following a substitution overflows */ + { "a()", SIZE_MAX - 11 }, + }; + unsigned char key[1] = { 'a' }, value[1] = { '0' }; + struct wally_map_item item = { key, sizeof(key), value, 0 }; + struct wally_map vars = { &item, 1, 1, NULL }; + bool tests_ok = true; + size_t i; + + for (i = 0; i < NUM_ELEMS(cases); ++i) { + struct wally_descriptor *descriptor = NULL; + int ret; + + item.value_len = cases[i].value_len; + ret = wally_descriptor_parse(cases[i].descriptor, &vars, + WALLY_NETWORK_NONE, 0, &descriptor); + if (!check_ret("canonical length overflow", ret, WALLY_EINVAL)) + tests_ok = false; + wally_descriptor_free(descriptor); + } + return tests_ok; +} + int main(void) { bool tests_ok = true; @@ -3165,6 +3373,11 @@ int main(void) tests_ok = false; } + if (!check_canonical_length_overflow()) { + printf("canonical length overflow test failed!\n"); + tests_ok = false; + } + wally_cleanup(0); return tests_ok ? 0 : 1; } diff --git a/src/ctest/test_script.c b/src/ctest/test_script.c new file mode 100644 index 000000000..ac926f853 --- /dev/null +++ b/src/ctest/test_script.c @@ -0,0 +1,112 @@ +#include "config.h" + +#include +#include "script_int.h" +#include +#include +#include +#include + +#define NUM_ELEMS(a) (sizeof(a) / sizeof(a[0])) + +#define B_1(b1) { b1, 0, 0, 0, 0, 0 } +#define B_2(b1, b2) { b1, b2, 0, 0, 0, 0 } +#define B_3(b1, b2, b3) { b1, b2, b3, 0, 0, 0 } +#define B_4(b1, b2, b3, b4) { b1, b2, b3, b4, 0, 0 } +#define B_5(b1, b2, b3, b4, b5) { b1, b2, b3, b4, b5, 0 } +#define B_6(b1, b2, b3, b4, b5, b6) { b1, b2, b3, b4, b5, b6 } + +#ifdef WALLY_EXPORT_ALL +struct scriptint_test { + int64_t expected; + unsigned char bytes[6]; + bool is_valid; + const char* description; +} scriptint_cases[] = { + /* valid encodings, but rejected by scriptint_from_bytes */ + { 0, B_1(0x00), false, "Must use OP_0 instead" }, + { 1, B_2(0x01, 0x01), false, "Must use OP_1 instead" }, + { 16, B_2(0x01, 0x10), false, "Must use OP_16 instead" }, + { -1, B_2(0x01, 0x81), false, "Negative disallowed (-1)"}, + { -127, B_2(0x01, 0xff), false, "Negative disallowed (-127)" }, + { -128, B_3(0x02, 0x80, 0x80), false, "Negative disallowed (-128)" }, + { -255, B_3(0x02, 0xff, 0x80), false, "Negative disallowed (-255)" }, + { -32767, B_3(0x02, 0xff, 0xff), false, "Negative disallowed (-32767)" }, + { -2147483647, B_5(0x04, 0xff, 0xff, 0xff, 0xff), false, "Negative disallowed (-2147483647)" }, + /* valid */ + { 17, B_2(0x01, 0x11), true, "Minimum allowed" }, + { 127, B_2(0x01, 0x7f), true, "Max 1-byte without padding" }, + { 128, B_3(0x02, 0x80, 0x00), true, "Requires trailing 0" }, + { 255, B_3(0x02, 0xff, 0x00), true, "255" }, + { 32767, B_3(0x02, 0xff, 0x7f), true, "Max 2-byte without padding" }, + { 32768, B_4(0x03, 0x00, 0x80, 0x00), true, "Requires trailing 0" }, + { 8388607, B_4(0x03, 0xff, 0xff, 0x7f), true, "Max 3-byte without padding" }, + { 2147483647, B_5(0x04, 0xff, 0xff, 0xff, 0x7f), true, "Max allowed value 0xffffffff" }, + /* invalid encodings */ + { 0, B_2(0x01, 0x00), false, "Non-minimal 0" }, + { 0, B_2(0x01, 0x80), false, "Negative 0" }, + { 1, B_3(0x02, 0x01, 0x00), false, "Zero padded positive int" }, + { -1, B_3(0x02, 0x01, 0x80), false, "Zero padding on negative int" }, + { 127, B_3(0x02, 0x7f, 0x00), false, "Trailing 0" }, + { -127, B_3(0x02, 0x7f, 0x80), false, "Trailing sign byte" }, + { 0, B_4(0x03, 0x00, 0x00, 0x00), false, "Negative Case: Multi-byte redundant zeros" }, + { 0, B_6(0x05, 0x01, 0x02, 0x03, 0x04, + 0x05), false, ">4 bytes" } +}; + +static int do_scriptint_from_bytes(const unsigned char *bytes, size_t len, + int64_t *value_out) +{ + unsigned char *b = NULL; + if (bytes && len) { + /* Clone source bytes to allow valgrind read checks */ + b = wally_malloc(len); + if (!b) + return WALLY_ENOMEM; + memcpy(b, bytes, len); + } + int ret = scriptint_from_bytes(b, len, value_out); + wally_free(b); + return ret; +} + +static bool test_scriptint_parse(void) +{ + unsigned char bytes[2] = { 0x01, 0x11 }; + int64_t v; + + if (do_scriptint_from_bytes(NULL, 1, &v) != WALLY_EINVAL || /* NULL bytes */ + do_scriptint_from_bytes(bytes, 0, &v) != WALLY_EINVAL || /* Zero bytes */ + do_scriptint_from_bytes(bytes, 1, &v) != WALLY_EINVAL || /* Short bytes */ + do_scriptint_from_bytes(bytes, 2, NULL) != WALLY_EINVAL) /* NULL value */ + return false; + + for (size_t i = 0; i < NUM_ELEMS(scriptint_cases); ++i) { + const struct scriptint_test* test = scriptint_cases + i; + const size_t len = test->bytes[0] + 1; + int ret = do_scriptint_from_bytes(test->bytes, len, &v); + if (!test->is_valid) { + if (ret != WALLY_EINVAL) + return false; + } else { + if (ret != WALLY_OK || v != test->expected) + return false; + } + } + + return true; +} +#endif + +int main(void) +{ + bool tests_ok = true; + +#define RUN(t) if (!t()) { printf(#t "() test failed!\n"); tests_ok = false; } + +#ifdef WALLY_EXPORT_ALL + RUN(test_scriptint_parse); +#endif + + return tests_ok ? 0 : 1; +} diff --git a/src/descriptor.c b/src/descriptor.c index 331d767eb..d1ba30f42 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -450,6 +450,19 @@ static bool is_policy_start_char(char c) { return c == '@'; } static bool is_policy_id_char(char c) { return c >= '0' && c <= '9'; } static bool is_elements_policy_id_char(char c) { return c == 'B' || is_policy_id_char(c); } +/* Add n to the canonicalized descriptor length in *len, leaving room for + * the checksum separator, checksum and NUL terminator. + * Returns false if the resulting length would overflow. + */ +static bool canonical_len_add(size_t *len, size_t n) +{ + const size_t max_len = SIZE_MAX - (1 + DESCRIPTOR_CHECKSUM_LENGTH + 1); + if (n > max_len - *len) + return false; + *len += n; + return true; +} + static int canonicalize_impl(const char *descriptor, const struct wally_map *vars_in, uint32_t flags, char **output, size_t *num_substitutions) @@ -482,10 +495,11 @@ static int canonicalize_impl(const char *descriptor, /* First, find the length of the canonicalized descriptor */ while (*p && *p != '#') { - while (*p && *p != '#' && !is_id_start(*p)) { - ++required_len; + start = p; + while (*p && *p != '#' && !is_id_start(*p)) ++p; - } + if (!canonical_len_add(&required_len, p - start)) + return WALLY_EINVAL; /* Too long */ if (!is_id_start(*p)) break; start = p++; @@ -495,16 +509,20 @@ static int canonicalize_impl(const char *descriptor, const bool starts_with_digit = *start >= '0' && *start <= '9'; const size_t lookup_len = p - start; if (!vars_in || lookup_len > VAR_MAX_NAME_LEN || starts_with_digit) { - required_len += lookup_len; /* Too long/wrong format for an identifier */ + /* Too long/wrong format for an identifier */ + if (!canonical_len_add(&required_len, lookup_len)) + return WALLY_EINVAL; /* Too long */ } else { /* Lookup the potential identifier */ const struct wally_map_item *item; item = wally_map_get(vars_in, (unsigned char*)start, lookup_len); if (!item) { - required_len += lookup_len; + if (!canonical_len_add(&required_len, lookup_len)) + return WALLY_EINVAL; /* Too long */ continue; } - required_len += item->value_len; + if (!canonical_len_add(&required_len, item->value_len)) + return WALLY_EINVAL; /* Too long */ ++*num_substitutions; if (flags & WALLY_MINISCRIPT_POLICY_TEMPLATE) { int key_index = (int)(item - vars_in->items); @@ -525,7 +543,8 @@ static int canonicalize_impl(const char *descriptor, */ if (*p++ != '/') return WALLY_EINVAL; - ++required_len; + if (!canonical_len_add(&required_len, 1)) + return WALLY_EINVAL; /* Too long */ if (*p == '<') { found_policy_multi = true; continue; @@ -535,10 +554,12 @@ static int canonicalize_impl(const char *descriptor, if (*p == '*') { found_policy_multi = true; ++p; - required_len += strlen("<0;1>/*"); + if (!canonical_len_add(&required_len, strlen("<0;1>/*"))) + return WALLY_EINVAL; /* Too long */ } else { found_policy_single = true; - required_len += 1; + if (!canonical_len_add(&required_len, 1)) + return WALLY_EINVAL; /* Too long */ } } } @@ -1755,11 +1776,12 @@ static int leaf_tapleaf_hash(ms_ctx *ctx, ms_node *leaf, if (ret == WALLY_OK) { if (written > buf_len) ret = WALLY_ERROR; /* Should not happen! */ - else - ret = bip341_tapleaf_hash(WALLY_LEAF_VERSION_TAPSCRIPT, - buf, written, - ms_ctx_is_elements(ctx), + else { + const bool is_elements = ms_ctx_is_elements(ctx); + ret = bip341_tapleaf_hash(TAPSCRIPT_LEAF_VERSION(is_elements), + buf, written, is_elements, hash_out, hash_out_len); + } } wally_free(buf); return ret; @@ -2824,21 +2846,23 @@ static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags, privkey, sizeof(privkey), &privkey_len); if (ret == WALLY_OK && privkey_len && privkey_len <= EC_PRIVATE_KEY_LEN + 2) { if (ctx->addr_ver && ctx->addr_ver->version_wif != privkey[0]) - return WALLY_EINVAL; - if (privkey_len == EC_PRIVATE_KEY_LEN + 1) { + ret = WALLY_EINVAL; + else if (privkey_len == EC_PRIVATE_KEY_LEN + 1) { if (flags & WALLY_MINISCRIPT_TAPSCRIPT) - return WALLY_EINVAL; /* Tapscript only allows x-only keys */ + ret = WALLY_EINVAL; /* Tapscript only allows x-only keys */ node->flags |= WALLY_MS_IS_UNCOMPRESSED; ctx->features |= WALLY_MS_IS_UNCOMPRESSED; } else if (privkey_len != EC_PRIVATE_KEY_LEN + 2 || privkey[EC_PRIVATE_KEY_LEN + 1] != 1) - return WALLY_EINVAL; /* Unknown WIF format */ + ret = WALLY_EINVAL; /* Unknown WIF format */ - node->flags |= (flags & WALLY_MINISCRIPT_TAPSCRIPT) ? WALLY_MS_IS_X_ONLY : 0; - ret = wally_ec_private_key_verify(&privkey[1], EC_PRIVATE_KEY_LEN); + if (ret == WALLY_OK) { + node->flags |= (flags & WALLY_MINISCRIPT_TAPSCRIPT) ? WALLY_MS_IS_X_ONLY : 0; + ret = wally_ec_private_key_verify(&privkey[1], EC_PRIVATE_KEY_LEN); + } if (ret == WALLY_OK && !clone_bytes((unsigned char **)&node->data, &privkey[1], EC_PRIVATE_KEY_LEN)) - ret = WALLY_EINVAL; - else { + ret = WALLY_ENOMEM; + else if (ret == WALLY_OK) { node->data_len = EC_PRIVATE_KEY_LEN; node->kind = KIND_PRIVATE_KEY; ctx->features |= (WALLY_MS_IS_PRIVATE | WALLY_MS_IS_RAW); @@ -3134,8 +3158,12 @@ static int analyze_miniscript(ms_ctx *ctx, const char *str, size_t str_len, /* Not a pure descriptor */ ctx->features &= ~WALLY_MS_IS_DESCRIPTOR; } - if (builtin_is_elements(str + offset, i - offset)) + if (builtin_is_elements(str + offset, i - offset) || + node->kind == KIND_DESCRIPTOR_CT) { + /* ct() is Elements-only: treat its descriptor as Elements + * without an el prefix, e.g. wpkh() as elwpkh() */ ctx->features |= WALLY_MS_IS_ELEMENTS; + } if (node->kind == KIND_DESCRIPTOR_TR) ctx->features |= WALLY_MS_IS_TAPROOT; offset = i + 1; @@ -4298,7 +4326,8 @@ int wally_descriptor_get_taproot_control_block( if (ret == WALLY_OK) { /* Leaf version is ORed with the output key parity, per BIP-341 */ - bytes_out[0] = WALLY_LEAF_VERSION_TAPSCRIPT | (tweaked[0] & 1); + bytes_out[0] = TAPSCRIPT_LEAF_VERSION(ms_ctx_is_elements(&ctx)); + bytes_out[0] |= (tweaked[0] & 1); /* Followed by the (untweaked) x-only internal key */ memcpy(bytes_out + 1, p2tr + 2, sizeof(p2tr) - 2); /* Followed by the path already written by tr_impl() above */ diff --git a/src/elements.c b/src/elements.c index b615f9dee..9160aa2f7 100644 --- a/src/elements.c +++ b/src/elements.c @@ -134,7 +134,7 @@ int wally_asset_final_vbf(const uint64_t *values, size_t num_values, size_t num_ if (!ctx) return WALLY_ENOMEM; - if (!values || num_values < 2u || + if (!values || num_values < 2u || num_values >= 256 || num_inputs >= num_values || !abf || abf_len != (num_values * BLINDING_FACTOR_LEN) || !vbf || vbf_len != ((num_values - 1) * BLINDING_FACTOR_LEN) || diff --git a/src/hex_.c b/src/hex_.c index bc7d59b4a..467fc9b37 100644 --- a/src/hex_.c +++ b/src/hex_.c @@ -25,8 +25,8 @@ int wally_hex_from_bytes(const unsigned char *bytes, size_t bytes_len, if (output) *output = NULL; - if (!bytes || !output) - return WALLY_EINVAL; + if (!bytes || !output || bytes_len > (SIZE_MAX - 1) / 2) + return WALLY_EINVAL; /* NULL args or hex string size would overflow */ *output = wally_malloc(hex_str_size(bytes_len)); if (!*output) diff --git a/src/hmac.c b/src/hmac.c index 4b68784d7..c57adcee2 100644 --- a/src/hmac.c +++ b/src/hmac.c @@ -3,6 +3,7 @@ #include #include #include +#include #ifdef SHA_T #undef SHA_T @@ -11,15 +12,7 @@ #define SHA_PRE(name) sha256 ## name #define HMAC_FUNCTION hmac_sha256_impl #define WALLY_HMAC_FUNCTION wally_hmac_sha256 -#ifdef CCAN_CRYPTO_SHA256_USE_MBEDTLS -#ifndef CONFIG_MBEDTLS_HARDWARE_SHA -#define SHA_CTX_BUFF c.MBEDTLS_PRIVATE(buffer) -#else -#define SHA_CTX_BUFF c.buffer -#endif -#else -#define SHA_CTX_BUFF buf.u8 -#endif +#define SHA_BLOCK_LEN 64 #include "hmac.inl" #undef SHA_T @@ -30,14 +23,6 @@ #define HMAC_FUNCTION hmac_sha512_impl #undef WALLY_HMAC_FUNCTION #define WALLY_HMAC_FUNCTION wally_hmac_sha512 -#undef SHA_CTX_BUFF -#ifdef CCAN_CRYPTO_SHA512_USE_MBEDTLS -#ifndef CONFIG_MBEDTLS_HARDWARE_SHA -#define SHA_CTX_BUFF c.MBEDTLS_PRIVATE(buffer) -#else -#define SHA_CTX_BUFF c.buffer -#endif -#else -#define SHA_CTX_BUFF buf.u8 -#endif +#undef SHA_BLOCK_LEN +#define SHA_BLOCK_LEN 128 #include "hmac.inl" diff --git a/src/hmac.inl b/src/hmac.inl index 00d4af912..68aa0a645 100644 --- a/src/hmac.inl +++ b/src/hmac.inl @@ -13,7 +13,7 @@ static void SHA_PRE(_mix)(struct SHA_T *sha, const unsigned char *pad, { struct SHA_PRE(_ctx) ctx; SHA_PRE(_init)(&ctx); - SHA_PRE(_update)(&ctx, pad, sizeof(ctx.SHA_CTX_BUFF)); + SHA_PRE(_update)(&ctx, pad, SHA_BLOCK_LEN); SHA_PRE(_update)(&ctx, data, data_len); SHA_PRE(_done)(&ctx, sha); wally_clear(&ctx, sizeof(ctx)); @@ -23,26 +23,31 @@ void HMAC_FUNCTION(struct SHA_T *sha, const unsigned char *key, size_t key_len, const unsigned char *msg, size_t msg_len) { - struct SHA_PRE(_ctx) ctx; - unsigned char ipad[sizeof(ctx.SHA_CTX_BUFF)]; - unsigned char opad[sizeof(ctx.SHA_CTX_BUFF)]; + /* The key block doubles as the buffer for the inner hash result */ + union { + unsigned char u8[SHA_BLOCK_LEN]; + struct SHA_T sha; + } key_block; + unsigned char ipad[SHA_BLOCK_LEN]; + unsigned char opad[SHA_BLOCK_LEN]; size_t i; - wally_clear(ctx.SHA_CTX_BUFF, sizeof(ctx.SHA_CTX_BUFF)); + BUILD_ASSERT(sizeof(struct SHA_T) <= SHA_BLOCK_LEN); + wally_clear(&key_block, sizeof(key_block)); - if (key_len <= sizeof(ctx.SHA_CTX_BUFF)) - memcpy(ctx.SHA_CTX_BUFF, key, key_len); + if (key_len <= SHA_BLOCK_LEN) + memcpy(key_block.u8, key, key_len); else - SHA_T((struct SHA_T *)ctx.SHA_CTX_BUFF, key, key_len); + SHA_T(&key_block.sha, key, key_len); - for (i = 0; i < sizeof(ctx.SHA_CTX_BUFF); ++i) { - opad[i] = ctx.SHA_CTX_BUFF[i] ^ 0x5c; - ipad[i] = ctx.SHA_CTX_BUFF[i] ^ 0x36; + for (i = 0; i < SHA_BLOCK_LEN; ++i) { + opad[i] = key_block.u8[i] ^ 0x5c; + ipad[i] = key_block.u8[i] ^ 0x36; } - SHA_PRE(_mix)((struct SHA_T *)ctx.SHA_CTX_BUFF, ipad, msg, msg_len); - SHA_PRE(_mix)(sha, opad, ctx.SHA_CTX_BUFF, sizeof(*sha)); - wally_clear_3(&ctx, sizeof(ctx), ipad, sizeof(ipad), opad, sizeof(opad)); + SHA_PRE(_mix)(&key_block.sha, ipad, msg, msg_len); + SHA_PRE(_mix)(sha, opad, key_block.u8, sizeof(*sha)); + wally_clear_3(&key_block, sizeof(key_block), ipad, sizeof(ipad), opad, sizeof(opad)); } int WALLY_HMAC_FUNCTION(const unsigned char *key, size_t key_len, diff --git a/src/internal.c b/src/internal.c index 4d3c58217..474ad2512 100644 --- a/src/internal.c +++ b/src/internal.c @@ -640,12 +640,19 @@ int replace_bytes(const unsigned char *bytes, size_t bytes_len, return WALLY_OK; } - +void *array_calloc(size_t n, size_t size) +{ + if (size && n > SIZE_MAX / size) + return NULL; /* Overflow */ + return wally_calloc(n * size); +} void *array_realloc(const void *src, size_t old_n, size_t new_n, size_t size) { - unsigned char *p = wally_malloc(new_n * size); - if (!p) + unsigned char *p; + if (new_n <= old_n || new_n > SIZE_MAX / size) + return NULL; /* Not growing, or overflow */ + if (!(p = wally_malloc(new_n * size))) return NULL; if (src) wally_memcpy(p, src, old_n * size); diff --git a/src/internal.h b/src/internal.h index 7a3349920..eba40ac40 100644 --- a/src/internal.h +++ b/src/internal.h @@ -122,9 +122,10 @@ WALLY_INTERNAL_API bool clone_data(void **dst, const void *src, size_t len); WALLY_INTERNAL_API bool clone_bytes(unsigned char **dst, const unsigned char *src, size_t len); WALLY_INTERNAL_API int replace_bytes(const unsigned char *bytes, size_t bytes_len, unsigned char **bytes_out, size_t *bytes_len_out); +/* Allocate a zeroed array of n items of size bytes */ +WALLY_INTERNAL_API void *array_calloc(size_t n, size_t size); WALLY_INTERNAL_API void *array_realloc(const void *src, size_t old_n, size_t new_n, size_t size); - WALLY_INTERNAL_API int array_grow(void **src, size_t num_items, size_t *allocation_len, size_t item_size); diff --git a/src/map.c b/src/map.c index 43c658e79..c162717ef 100644 --- a/src/map.c +++ b/src/map.c @@ -15,7 +15,7 @@ int wally_map_init(size_t allocation_len, wally_map_verify_fn_t verify_fn, struc wally_clear(output, sizeof(*output)); if (allocation_len) { - output->items = wally_calloc(allocation_len * sizeof(*output->items)); + output->items = array_calloc(allocation_len, sizeof(*output->items)); if (!output->items) return WALLY_ENOMEM; } diff --git a/src/psbt.c b/src/psbt.c index 4be9a324c..9fecba3c0 100644 --- a/src/psbt.c +++ b/src/psbt.c @@ -1194,12 +1194,12 @@ static int psbt_init(uint32_t version, size_t num_inputs, size_t num_outputs, if (num_inputs) { if (num_inputs > max_num_inputs) num_inputs = max_num_inputs; - psbt_out->inputs = wally_calloc(num_inputs * sizeof(struct wally_psbt_input)); + psbt_out->inputs = array_calloc(num_inputs, sizeof(struct wally_psbt_input)); } if (num_outputs) { if (num_outputs > max_num_outputs) num_outputs = max_num_outputs; - psbt_out->outputs = wally_calloc(num_outputs * sizeof(struct wally_psbt_output)); + psbt_out->outputs = array_calloc(num_outputs, sizeof(struct wally_psbt_output)); } ret = wally_map_init(num_unknowns, NULL, &psbt_out->unknowns); @@ -1604,13 +1604,13 @@ static int psbt_set_global_tx(struct wally_psbt *psbt, struct wally_tx *tx, bool } if (psbt->inputs_allocation_len < tx->num_inputs) { - new_inputs = wally_malloc(tx->num_inputs * sizeof(struct wally_psbt_input)); + new_inputs = array_calloc(tx->num_inputs, sizeof(struct wally_psbt_input)); for (i = 0; new_inputs && i < tx->num_inputs; ++i) psbt_input_init(&new_inputs[i]); } if (psbt->outputs_allocation_len < tx->num_outputs) { - new_outputs = wally_malloc(tx->num_outputs * sizeof(struct wally_psbt_output)); + new_outputs = array_calloc(tx->num_outputs, sizeof(struct wally_psbt_output)); for (i = 0; new_outputs && i < tx->num_outputs; ++i) psbt_output_init(&new_outputs[i]); } @@ -1627,11 +1627,17 @@ static int psbt_set_global_tx(struct wally_psbt *psbt, struct wally_tx *tx, bool psbt_inputs_free(psbt->inputs, psbt->num_inputs); psbt->inputs = new_inputs; psbt->inputs_allocation_len = tx->num_inputs; + } else { + for (i = 0; i < tx->num_inputs; ++i) + psbt_input_init(&psbt->inputs[i]); } if (new_outputs) { psbt_outputs_free(psbt->outputs, psbt->num_outputs); psbt->outputs = new_outputs; psbt->outputs_allocation_len = tx->num_outputs; + } else { + for (i = 0; i < tx->num_outputs; ++i) + psbt_output_init(&psbt->outputs[i]); } psbt->num_inputs = tx->num_inputs; psbt->num_outputs = tx->num_outputs; diff --git a/src/script.c b/src/script.c index c48d5395d..39bcca454 100644 --- a/src/script.c +++ b/src/script.c @@ -191,7 +191,7 @@ size_t scriptint_to_bytes(int64_t signed_v, unsigned char *bytes_out) return len; } -int64_t scriptint_from_bytes(const unsigned char *bytes, size_t len, int64_t *value_out) +int scriptint_from_bytes(const unsigned char *bytes, size_t len, int64_t *value_out) { int64_t mask = 0x80; size_t i; @@ -203,10 +203,17 @@ int64_t scriptint_from_bytes(const unsigned char *bytes, size_t len, int64_t *va * This function is intended for parsing scripts, not evaluating them * (which can use intermediate 5 byte script int stack values). */ - if (!bytes || len < 1 || len <= bytes[0] || bytes[0] > 4 || !value_out) + if (!bytes || !len || len <= bytes[0] || !bytes[0] || bytes[0] > 4 || !value_out) return WALLY_EINVAL; - for (i = 0; i < bytes[0]; ++i) { + len = bytes[0]; /* Set len to the length of the encoded number */ + if ((bytes[len] & 0x7f) == 0) { + /* MSB of the value is non-zero */ + if (len == 1 || (bytes[len - 1] & 0x80) == 0) + return WALLY_EINVAL; /* Non-minimal encoding */ + } + + for (i = 0; i < len; ++i) { *value_out |= (int64_t)(bytes[i + 1]) << (8 * i); mask <<= 8; } @@ -216,6 +223,12 @@ int64_t scriptint_from_bytes(const unsigned char *bytes, size_t len, int64_t *va *value_out ^= (mask >> 8); *value_out = -*value_out; } + /* Reject non-minimal (OP_0->OP_16) and negative numbers. + * There are no meaningful uses for negative numbers in current script + * and they are unsupported in tapscript. + */ + if (*value_out <= 16) + return WALLY_EINVAL; /* reject Non-minimal/negative (should be OP_0->OP_16) */ return WALLY_OK; } @@ -369,6 +382,23 @@ bool scriptpubkey_is_p2tr(const unsigned char *bytes, size_t bytes_len) bytes[1] == 32; /* X-ONLY-PUBKEY */ } +bool scriptpubkey_is_witness_program(const unsigned char *bytes, size_t bytes_len, + size_t *version) +{ + /* A version opcode followed by a single direct push of 2-40 bytes + * which is the remainder of the script (BIP-141). + * v0 programs must also be either 20 or 32 bytes (BIP-141/BIP-173) */ + size_t ver; + + if (!bytes || bytes_len < 4 || bytes_len > WALLY_WITNESSSCRIPT_MAX_LEN || + !script_is_op_n(bytes[0], true, &ver) || bytes[1] != bytes_len - 2 || + (!ver && bytes[1] != HASH160_LEN && bytes[1] != SHA256_LEN)) + return false; + if (version) + *version = ver; + return true; +} + static bool scriptpubkey_is_multisig(const unsigned char *bytes, size_t bytes_len) { const size_t min_1of1_len = 1 + 1 + 33 + 1 + 1; /* OP_1 [pubkey] OP_1 OP_CHECKMULTISIG */ diff --git a/src/script.h b/src/script.h index cb39e7dc2..b62c8d1cd 100644 --- a/src/script.h +++ b/src/script.h @@ -2,6 +2,8 @@ #define LIBWALLY_CORE_SCRIPT_INTERNAL_H 1 #include +#include +#include /* Get the size of a push from the script push opcode(s) */ int script_get_push_size_from_bytes( @@ -20,6 +22,11 @@ bool script_is_op_n(unsigned char op, bool allow_zero, size_t *n); bool scriptpubkey_is_p2tr(const unsigned char *bytes, size_t bytes_len); +/* Return true if bytes is exactly a witness program that can be encoded as + * a segwit address, optionally returning its witness version */ +bool scriptpubkey_is_witness_program(const unsigned char *bytes, size_t bytes_len, + size_t *version); + /* Convert 0-16 to OP_ */ size_t value_to_op_n(uint64_t v); diff --git a/src/script_int.h b/src/script_int.h index d90576c72..badb7296e 100644 --- a/src/script_int.h +++ b/src/script_int.h @@ -2,6 +2,8 @@ #define LIBWALLY_CORE_SCRIPT_INT_H 1 #include "ccan/ccan/endian/endian.h" +#include +#include "internal.h" #ifdef __cplusplus extern "C" { @@ -83,6 +85,8 @@ size_t varint_to_bytes(uint64_t v, unsigned char *bytes_out); /* Read a variant from bytes */ size_t varint_from_bytes(const unsigned char *bytes, uint64_t *v); +WALLY_INTERNAL_API int scriptint_from_bytes(const unsigned char *bytes, size_t len, int64_t *value_out); + size_t scriptint_get_length(int64_t signed_v); size_t scriptint_to_bytes(int64_t signed_v, unsigned char *bytes_out); diff --git a/src/test/test_base58.py b/src/test/test_base58.py index d1a882953..2e28dd0dc 100755 --- a/src/test/test_base58.py +++ b/src/test/test_base58.py @@ -136,6 +136,15 @@ def test_to_bytes(self): ret, _ = wally_base58_n_to_bytes(utf8('16Ho7Hs'), 0, 0, buf, buf_len) self.assertEqual(ret, WALLY_EINVAL) # Invalid length + # Lengths whose bignum size would overflow size_t are rejected + too_long = (2 ** (8 * sizeof(c_size_t)) - 1 - 7) // 6 + 1 + for s in ['2', '112']: + str_len = too_long + s.count('1') + ret, _ = wally_base58_n_get_length(utf8(s), str_len) + self.assertEqual(ret, WALLY_EINVAL) + ret, _ = wally_base58_n_to_bytes(utf8(s), str_len, 0, buf, buf_len) + self.assertEqual(ret, WALLY_EINVAL) + def test_from_bytes(self): # Leading zeros become ones @@ -154,6 +163,13 @@ def test_from_bytes(self): # O length buffer, append checksum -> NULL self.assertEqual(wally_base58_from_bytes(buf, 0, self.FLAG_CHECKSUM), FAIL_RET) + # Lengths whose bignum size would overflow size_t are rejected + too_long = (2 ** (8 * sizeof(c_size_t)) - 1) // 138 + 1 + for hex_in in ['01', '000001']: + in_buf, in_len = make_cbuffer(hex_in) + ret = wally_base58_from_bytes(in_buf, too_long + in_len - 1, 0) + self.assertEqual(ret, FAIL_RET) + # Vectors from https://github.com/bitcoinj/bitcoinj/ self.assertEqual(self.encode('00CEF022FA', 0), '16Ho7Hs') self.assertEqual(self.encode('45046252208D', self.FLAG_CHECKSUM), diff --git a/src/test/test_base64.py b/src/test/test_base64.py index 014de1ec6..eda731284 100755 --- a/src/test/test_base64.py +++ b/src/test/test_base64.py @@ -72,6 +72,12 @@ def test_base64_from_bytes(self): ret, b64_out = wally_base64_from_bytes(*args) self.assertEqual((ret, b64_out), (WALLY_EINVAL, None)) + # Lengths whose encoded length would overflow are rejected. + # The length is checked before the input is read. + too_long = (2 ** (8 * sizeof(c_size_t)) - 2) // 4 * 3 + 1 + ret, b64_out = wally_base64_from_bytes(valid_str, too_long, 0) + self.assertEqual((ret, b64_out), (WALLY_EINVAL, None)) + def test_base64_to_bytes(self): # Invalid args buf, buf_len = make_cbuffer('00' * 1024) diff --git a/src/test/test_bech32.py b/src/test/test_bech32.py index 0e51cb529..db8d556c6 100755 --- a/src/test/test_bech32.py +++ b/src/test/test_bech32.py @@ -64,6 +64,25 @@ ['bc', 'bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx'] # V > 0 must be bech32m ] +# Scripts that are not exactly a witness program and so have no segwit address +invalid_programs = [ + '5121' + '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798' + '51ae', # Bare 1-of-1 multisig + '0014' + '11' * 32, # v0: Push shorter than the remaining script + '0020' + '11' * 20, # v0: Push longer than the remaining script + '0012' + '11' * 18, # v0: Invalid program length + '0000', # v0: Empty program + '5100' + '11' * 32, # v1: Empty push followed by a 32 byte "program" + '51014e73', # v1: Push shorter than the remaining script + '51024e73ff', # v1: Trailing data after the push + '5101ff', # v1: Program too short + '5129' + '11' * 41, # v1: Program too long + '5128' + '11' * 41, # v1: Trailing data after a 40 byte push + '604c02abcd', # v16: Non-direct push + '4f024e73', # OP_1NEGATE is not a witness version + '5100', # Too short + '51', # Too short +] + class Bech32Tests(unittest.TestCase): def decode(self, addr, family): @@ -105,6 +124,13 @@ def test_segwit_address(self): self.assertEqual(ret, WALLY_EINVAL) self.assertEqual(result_ver, 0) + # Invalid witness programs + for script_hex in invalid_programs: + script_buf, script_len = make_cbuffer(script_hex) + ret, retstr = wally_addr_segwit_from_bytes(script_buf, script_len, utf8('bc'), 0) + self.assertEqual((ret, retstr), (WALLY_EINVAL, None)) + self.assertEqual(h(script_buf[:script_len]), utf8(script_hex)) # Unmodified + out, out_len = make_cbuffer('00' * (32 + 2)) bad = utf8('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg') tb = utf8('tb') diff --git a/src/test/test_descriptor.py b/src/test/test_descriptor.py index 9d69657f0..2f628a05c 100644 --- a/src/test/test_descriptor.py +++ b/src/test/test_descriptor.py @@ -350,6 +350,13 @@ def test_features_and_depth(self): # returned. (f'ct(c25deb86fa11e49d651d7eae27c220ef930fbd86ea023eebfa73e54875647963,elpkh({k1}))', 0, MS_IS_DESCRIPTOR|MS_IS_ELEMENTS|MS_IS_ELIP150, 3, 1), + # ct() descriptors are elements even without the el prefix + (f'ct({k1},pkh({k1}))', + 0, MS_IS_DESCRIPTOR|MS_IS_ELEMENTS|MS_IS_ELIP150, 3, 1), + (f'ct(0286fc9a38e765d955e9b0bcc18fa9ae81b0c893e2dd1ef5542a9c73780a086b90,wpkh({k1}))', + 0, MS_IS_DESCRIPTOR|MS_IS_ELEMENTS|MS_IS_ELIP150, 3, 1), + (f'ct(c25deb86fa11e49d651d7eae27c220ef930fbd86ea023eebfa73e54875647963,tr({k1}))', + 0, MS_IS_DESCRIPTOR|MS_IS_TAPROOT|MS_IS_ELEMENTS|MS_IS_ELIP150, 3, 1), ]) for descriptor, flags, expected_features, expected_depth, expected_keys in cases: @@ -487,6 +494,26 @@ def make_keys(xpubs): wally_map_free(keys) wally_descriptor_free(d) + # ct() policies are elements even without the el prefix, and + # generate the same confidential addresses as el-prefixed policies + keys = wally_map_from_dict({'@B': xpub1, '@0': xpub2}) + for policy in ['ct(@B,{}pkh(@0/*))', 'ct(@B,{}wpkh(@0/*))', + 'ct(@B,{}sh(wpkh(@0/*)))', 'ct(@B,{}tr(@0/*))']: + addrs = [] + for prefix in ['el', '']: + ret = wally_descriptor_parse(policy.format(prefix), keys, + NETWORK_LIQUID, P, d) + self.assertEqual(ret, WALLY_OK) + ret, features = wally_descriptor_get_features(d) + self.assertEqual(ret, WALLY_OK) + self.assertTrue(features & MS_IS_ELEMENTS) + ret, addr = wally_descriptor_to_address(d, 0, 0, 0, 0) + self.assertEqual(ret, WALLY_OK) + addrs.append(addr) + wally_descriptor_free(d) + self.assertEqual(addrs[0], addrs[1]) + wally_map_free(keys) + def test_key_iteration(self): """Test iterating descriptor keys""" origin_fp = 'd34db33f' diff --git a/src/test/test_elements.py b/src/test/test_elements.py index 3583e69f2..b2259b994 100755 --- a/src/test/test_elements.py +++ b/src/test/test_elements.py @@ -87,6 +87,35 @@ def test_asset_unblind_with_nonce(self): self.assertEqual((ret, value_out, asset_out, abf_out, vbf_out), (WALLY_OK, 80000000, UNBLINDED_ASSET, UNBLINDED_ABF, UNBLINDED_VBF)) + def test_asset_final_vbf(self): + """Test computing the final value blinding factor""" + if not wally_is_elements_build()[1]: + self.skipTest('Elements support not enabled') + values = (c_uint64 * 2)(20000, 20000) + abf, abf_len = make_cbuffer('11' * 32 + '22' * 32) + vbf, vbf_len = make_cbuffer('33' * 32) + out, out_len = make_cbuffer('00' * 32) + args = [values, 2, 1, abf, abf_len, vbf, vbf_len, out, out_len] + self.assertEqual(wally_asset_final_vbf(*args), WALLY_OK) + + too_many = 256 # Max value count is 255 + for i, arg in [ + (0, None), # NULL values + (1, 1), # Too few values + (1, 3), # Mismatched value count + (1, too_many), # Too many values + (2, 2), # Too many inputs + (3, None), # NULL abf + (4, 32), # Invalid abf length + (5, None), # NULL vbf + (6, 64), # Invalid vbf length + (7, None), # NULL output + (8, 31), # Invalid output length + ]: + invalid_args = args[:] + invalid_args[i] = arg + self.assertEqual(wally_asset_final_vbf(*invalid_args), WALLY_EINVAL) + def test_asset_generator_from_bytes(self): if not wally_is_elements_build()[1]: self.skipTest('Elements support not enabled') diff --git a/src/test/test_hex.py b/src/test/test_hex.py index 56a2e00f7..60e07c0a6 100755 --- a/src/test/test_hex.py +++ b/src/test/test_hex.py @@ -62,6 +62,12 @@ def test_hex_from_bytes(self): ret, written = wally_hex_from_bytes(None, buf_len) self.assertEqual((ret, written), (WALLY_EINVAL, None)) + # Lengths whose hex string size would overflow are rejected. + # The length is checked before the input is read. + too_long = (2 ** (8 * sizeof(c_size_t)) - 2) // 2 + 1 + ret, written = wally_hex_from_bytes(buf, too_long) + self.assertEqual((ret, written), (WALLY_EINVAL, None)) + # Empty buffer ret, written = wally_hex_from_bytes(buf, 0) self.assertEqual((ret, written), (WALLY_OK, '')) diff --git a/src/test/test_map.py b/src/test/test_map.py index b62899120..20f840ebf 100644 --- a/src/test/test_map.py +++ b/src/test/test_map.py @@ -22,6 +22,10 @@ def test_map(self): # Check invalid args self.assertEqual(wally_map_init_alloc(0, None, None), WALLY_EINVAL) + # Allocation lengths whose size would overflow fail + too_many = (2 ** (8 * sizeof(c_size_t)) - 1) // sizeof(wally_map_item) + 1 + self.assertEqual(wally_map_init_alloc(too_many, None, m), WALLY_ENOMEM) + self.assertEqual(wally_map_init(too_many, None, pointer(wally_map())), WALLY_ENOMEM) self.assertEqual(wally_map_init_alloc(0, None, m), WALLY_OK) self.assertEqual(wally_map_clear(None), WALLY_EINVAL) # Null map diff --git a/src/test/test_psbt.py b/src/test/test_psbt.py index a37e45819..92b2732f5 100644 --- a/src/test/test_psbt.py +++ b/src/test/test_psbt.py @@ -101,6 +101,32 @@ def test_valid(self): wally_psbt_free(psbt) + def test_global_tx_malloc_fail(self): + """Test allocation failures when setting a v0 PSBTs global tx""" + # Failing any allocation, including the input and output + # arrays, must fail cleanly with WALLY_ENOMEM + tx_hex = '0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300' + tx = pointer(wally_tx()) + self.assertEqual(wally_tx_from_hex(tx_hex, 1, tx), WALLY_OK) + + results = set() + max_mallocs = 8 + + @malloc_fail(range(max_mallocs)) + def check_set_global_tx(): + psbt = pointer(wally_psbt()) + ret = wally_psbt_init_alloc(0, 0, 0, 0, 0, psbt) + if ret == WALLY_OK: + ret = wally_psbt_set_global_tx(psbt, tx) + wally_psbt_free(psbt) + self.assertIn(ret, [WALLY_OK, WALLY_ENOMEM]) + results.add(ret) + + num_mallocs = check_set_global_tx() + self.assertEqual(num_mallocs, max_mallocs) + self.assertEqual(results, {WALLY_OK, WALLY_ENOMEM}) + wally_tx_free(tx) + def test_creator_role(self): """Test the PSBT creator role""" psbt = pointer(wally_psbt()) @@ -286,15 +312,17 @@ def test_v20dot1_changes(self): def test_psbt(self): """Test creating and modifying various PSBT fields""" tx = pointer(wally_tx()) - self.assertEqual(WALLY_OK, wally_tx_init_alloc(2, 0, 2, 2, tx)) - + self.assertEqual(wally_tx_init_alloc(2, 0, 2, 2, tx), WALLY_OK) + txid, txid_len = make_cbuffer('11' * 32) + self.assertEqual(wally_tx_add_raw_input(tx, txid, txid_len, 0, + 0xffffffff, None, 0, None, 0), WALLY_OK) psbt = pointer(wally_psbt()) for ver, result in [ (0, 'cHNidP8A'), (1, None), (2, 'cHNidP8BAgQCAAAAAQQBAAEFAQAB+wQCAAAAAA=='), (3, None) ]: - ret = wally_psbt_init_alloc(ver, 0, 0, 0, 0, psbt) + ret = wally_psbt_init_alloc(ver, 3, 3, 0, 0, psbt) self.assertEqual(ret, WALLY_OK if result else WALLY_EINVAL) if result: self.assertEqual(self.to_base64(psbt, MOD_NONE), result) @@ -303,6 +331,11 @@ def test_psbt(self): ret = wally_psbt_set_global_tx(psbt, tx) self.assertEqual(ret, WALLY_OK if ver == 0 else WALLY_EINVAL) + if ver == 0: + # Ensure inputs from global tx have been initialized, by + # ensuring the keypath maps validation fn is set + self.assertIsNotNone(psbt.contents.inputs[0].keypaths.verify_fn) + # Create a v2 PSBT wally_psbt_init_alloc(2, 0, 0, 0, 0, psbt) @@ -361,6 +394,9 @@ def test_invalid_args(self): ] for args in cases: self.assertEqual(WALLY_EINVAL, wally_psbt_init_alloc(*args)) + # Too many unknowns: the allocation size would overflow + too_many = (2 ** (8 * sizeof(c_size_t)) - 1) // sizeof(wally_map_item) + 1 + self.assertEqual(WALLY_ENOMEM, wally_psbt_init_alloc(0, 0, 0, too_many, 0, psbt)) # psbt_from_base64 src_base64 = JSON['valid'][0]['psbt'] diff --git a/src/test/test_transaction.py b/src/test/test_transaction.py index c343d854f..180daae02 100644 --- a/src/test/test_transaction.py +++ b/src/test/test_transaction.py @@ -90,6 +90,25 @@ def test_serialization(self): self.assertEqual(utf8(self.tx_serialize_hex(tx_copy)), utf8(self.tx_serialize_hex(tx_nf))) + def test_serialization_malloc_fail(self): + """Testing deserialization when allocations fail""" + # Failing any allocation, including the input, output and + # witness item arrays, must fail cleanly with WALLY_ENOMEM + results = set() + max_mallocs = 10 + + @malloc_fail(range(max_mallocs)) + def check_from_hex(): + tx = pointer(wally_tx()) + ret = wally_tx_from_hex(TX_WITNESS_HEX, 0, tx) + if ret == WALLY_OK: + wally_tx_free(tx) + results.add(ret) + + num_mallocs = check_from_hex() + self.assertEqual(num_mallocs, max_mallocs) + self.assertEqual(results, {WALLY_OK, WALLY_ENOMEM}) + def test_lengths(self): """Testing functions measuring different lengths for a tx""" for tx_hex, length in [ @@ -236,6 +255,16 @@ def test_witness(self): for fn in [wally_tx_witness_stack_get_num_items, wally_tx_witness_stack_get_length]: self.assertEqual((WALLY_EINVAL, 0), fn(None)) # NULL stack + + # Indices that would overflow the size of the witness array fail + w = pointer(wally_tx_witness_stack()) + self.assertEqual(WALLY_OK, wally_tx_witness_stack_init_alloc(2, w)) + size_max = 2 ** (8 * sizeof(c_size_t)) - 1 + for index in [size_max // sizeof(wally_tx_witness_item), size_max]: + ret = wally_tx_witness_stack_set(w, index, item, item_len) + self.assertEqual(ret, WALLY_ENOMEM) + self.assertEqual((WALLY_OK, 0), wally_tx_witness_stack_get_num_items(w)) + self.assertEqual(WALLY_OK, wally_tx_witness_stack_free(w)) # An empty stack has no items and is serialized as a single 0x00 byte self.assertEqual((WALLY_OK, 0), wally_tx_witness_stack_get_num_items(witness)) self.assertEqual((WALLY_OK, 1), wally_tx_witness_stack_get_length(witness)) @@ -319,6 +348,7 @@ 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 + (tx, 1, script, script_len, 1, 1, 1, out, out_len), # Invalid index (only with segwit) ]: self.assertEqual(WALLY_EINVAL, wally_tx_get_btc_signature_hash(*args)) @@ -580,6 +610,13 @@ def test_get_btc_taproot_signature_hash(self): ret = wally_tx_get_btc_taproot_signature_hash(*args) self.assertEqual(ret, WALLY_EINVAL) + # Too many values: the allocation size would overflow + args = [tx, index, scripts, values, num_values, tapleaf_script, tapleaf_script_len, + key_version, codesep_pos, annex, annex_len, sighash, flags, bytes_out, out_len] + args[4] = (2 ** (8 * sizeof(c_size_t)) - 1) // sizeof(wally_map_item) + 1 + ret = wally_tx_get_btc_taproot_signature_hash(*args) + self.assertEqual(ret, WALLY_ENOMEM) + def test_get_elements_taproot_signature_hash(self): """Tests for computing the Elements taproot signature hash""" _, is_elements_build = wally_is_elements_build() @@ -621,6 +658,15 @@ def make_map(n): tx = self.tx_deserialize_hex(keyspend_case['given']['rawUnsignedTx'], True) bytes_out, out_len = make_cbuffer('00'*32) + # Segwit v0 signature hashes require a valid input index + ret, num_inputs = wally_tx_get_num_inputs(tx) + self.assertEqual(ret, WALLY_OK) + value, value_len = make_cbuffer('010000000000001388') + ret = wally_tx_get_elements_signature_hash(tx, num_inputs, fake_script, fake_script_len, + value, value_len, 1, 1, + bytes_out, out_len) + self.assertEqual(ret, WALLY_EINVAL) + for input_index in range(len(input_spending)): sighash = input_spending[input_index]['given']['hashType'] index = input_spending[input_index]['given']['txinIndex'] @@ -651,6 +697,24 @@ def make_map(n): args[10] = fake_annex_len self.assertEqual(wally_tx_get_input_signature_hash(*args), WALLY_OK) + # Script path signing commits to the Elements tapscript leaf version + # 0xc4 (not 0xc0). Expected values are computed independently of wally + # following Elements' TaprootSignatureHash() (test_framework/script.py). + leaf_script, leaf_script_len = make_cbuffer( + '208bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac') + cache = make_map(0) + for sp_index, sp_sighash, sp_expected in [ + (0, 0x00, '1389dc115dd1623be501bd1c4dc1558f66b767b4e48b91b0185ff3d315fbff15'), + (1, 0x83, '9b9afc640a736c82867ef0143d9adb93fd738bfa81ca342c06a2ef681ac0a4e4'), + ]: + for sp_cache in [None, cache, cache]: + sp_args = [tx, sp_index, scripts, assets, values, + leaf_script, leaf_script_len, 0, 0xFFFFFFFF, + None, 0, genesis, genesis_len, + sp_sighash, SIGTYPE_SW_V1, sp_cache, bytes_out, out_len] + self.assertEqual(wally_tx_get_input_signature_hash(*sp_args), WALLY_OK) + self.assertEqual(utf8(sp_expected), h(bytes_out[:out_len])) + # Invalid args invalid_cases = [ [(0, None)], # NULL tx diff --git a/src/test/util.py b/src/test/util.py index db9873d3a..c883b18fb 100755 --- a/src/test/util.py +++ b/src/test/util.py @@ -1221,11 +1221,14 @@ def _failable_malloc(size): def malloc_fail(failures): def decorator(test_func): def wrapped(*args): + counter = 0 global _fail_malloc_at, _fail_malloc_counter for fail_at in failures: _fail_malloc_at, _fail_malloc_counter = fail_at, 0 test_func(*args) + counter = max(counter, _fail_malloc_counter) _fail_malloc_at, _fail_malloc_counter = 0, 0 + return counter return wrapped return decorator diff --git a/src/transaction.c b/src/transaction.c index 0b3f13f62..1ed2ed2df 100644 --- a/src/transaction.c +++ b/src/transaction.c @@ -162,7 +162,7 @@ int tx_witness_stack_init_alloc(size_t allocation_len, if (allocation_len) { if (allocation_len > max_allocation_len) allocation_len = max_allocation_len; - (*output)->items = wally_calloc(allocation_len * sizeof(struct wally_tx_witness_item)); + (*output)->items = array_calloc(allocation_len, sizeof(struct wally_tx_witness_item)); if (!(*output)->items) { wally_free(*output); *output = NULL; @@ -1140,12 +1140,12 @@ static int tx_init_alloc(uint32_t version, uint32_t locktime, if (inputs_allocation_len) { if (inputs_allocation_len > max_inputs_allocation_len) inputs_allocation_len = max_inputs_allocation_len; - new_inputs = wally_calloc(inputs_allocation_len * sizeof(struct wally_tx_input)); + new_inputs = array_calloc(inputs_allocation_len, sizeof(struct wally_tx_input)); } if (outputs_allocation_len) { if (outputs_allocation_len > max_outputs_allocation_len) outputs_allocation_len = max_outputs_allocation_len; - new_outputs = wally_calloc(outputs_allocation_len * sizeof(struct wally_tx_output)); + new_outputs = array_calloc(outputs_allocation_len, sizeof(struct wally_tx_output)); } if ((inputs_allocation_len && !new_inputs) || (outputs_allocation_len && !new_outputs)) { diff --git a/src/tx_io.c b/src/tx_io.c index d8ef2f6c3..1ce8752a6 100644 --- a/src/tx_io.c +++ b/src/tx_io.c @@ -759,8 +759,8 @@ static int bip143_signature_hash( 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) + if (!tx || index >= tx->num_inputs || !values || + BYTES_INVALID(scriptcode, scriptcode_len) || sighash & 0xffffff00) return WALLY_EINVAL; { @@ -1015,7 +1015,7 @@ static int bip341_signature_hash( /* Tapscript Extensions */ if (tapleaf_script) { if (!sh_anyprevout_anyscript) - txio_hash_tapleaf_hash(&io, WALLY_LEAF_VERSION_TAPSCRIPT, + txio_hash_tapleaf_hash(&io, TAPSCRIPT_LEAF_VERSION(is_elements), tapleaf_script, tapleaf_script_len, is_elements); hash_u8(&io.ctx, key_version & 0xff); hash_le32(&io.ctx, codesep_position); diff --git a/src/tx_io.h b/src/tx_io.h index af600bcc7..5708fbddf 100644 --- a/src/tx_io.h +++ b/src/tx_io.h @@ -2,6 +2,7 @@ #define LIBWALLY_CORE_TX_IO_H 1 #include +#include #include "ccan/ccan/crypto/sha256/sha256.h" /* Suggested initial size of a signing cache to avoid re-allocations */ @@ -27,6 +28,10 @@ int bip341_tapbranch_hash(const unsigned char *lhs, size_t lhs_len, const unsigned char *rhs, size_t rhs_len, bool is_elements, unsigned char *bytes_out, size_t len); +/* The tapscript leaf version: 0xc0 (BIP-342) for BTC, 0xc4 for Elements */ +#define TAPSCRIPT_LEAF_VERSION(is_elements) ((is_elements) ? \ + WALLY_LEAF_VERSION_TAPSCRIPT_ELEMENTS : WALLY_LEAF_VERSION_TAPSCRIPT) + int bip341_tapleaf_hash(unsigned char leaf_version, const unsigned char *script, size_t script_len, bool is_elements, unsigned char *bytes_out, size_t len); diff --git a/src/wasm_package/src/const.js b/src/wasm_package/src/const.js index b12fec2c8..20953d9ce 100755 --- a/src/wasm_package/src/const.js +++ b/src/wasm_package/src/const.js @@ -120,6 +120,7 @@ export const WALLY_ENOMEM = -3; /** malloc() failed */ export const WALLY_ERROR = -1; /** General error */ export const WALLY_HOST_COMMITMENT_LEN = 32; export const WALLY_LEAF_VERSION_TAPSCRIPT = 0xc0; /** BIP-342 tapscript leaf version */ +export const WALLY_LEAF_VERSION_TAPSCRIPT_ELEMENTS = 0xc4; /** Elements tapscript leaf version */ export const WALLY_MAJOR_VER = 1; export const WALLY_MAX_OP_RETURN_LEN = 80; /* Maximum length of OP_RETURN data push */ export const WALLY_MINISCRIPT_AS_ELEMENTS = 0x20; /** Treat non-elements expressions as elements, e.g. tr() as eltr() */