From 11ba1d2fa8cf4fb6c109e9c951679a50432a0683 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sun, 2 Feb 2025 15:01:08 +1300 Subject: [PATCH 01/22] psbt: check psbt validity before dereferencing it --- src/psbt.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/psbt.c b/src/psbt.c index ccc26441b..3106d6e94 100644 --- a/src/psbt.c +++ b/src/psbt.c @@ -4945,6 +4945,8 @@ int wally_psbt_finalize(struct wally_psbt *psbt, uint32_t flags) size_t i; int ret = WALLY_OK; + if (!psbt_is_valid(psbt) || (flags & ~WALLY_PSBT_FINALIZE_NO_CLEAR)) + return WALLY_EINVAL; for (i = 0; ret == WALLY_OK && i < psbt->num_inputs; ++i) ret = wally_psbt_finalize_input(psbt, i, flags); return ret; From f61b35fa51745c5be6f6133dc7ae6b8873be1c49 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sun, 2 Feb 2025 15:01:28 +1300 Subject: [PATCH 02/22] psbt: allow finalizer tests to work when elements is disabled --- src/data/psbt.json | 3 +++ src/test/test_psbt.py | 19 ++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/data/psbt.json b/src/data/psbt.json index 45e588dce..b148c986a 100644 --- a/src/data/psbt.json +++ b/src/data/psbt.json @@ -1267,18 +1267,21 @@ { "comment": "PSETv2, single 2of2 p2sh-p2wsh un-optimized csv input w/signatures, redundant finalization", "psbt": "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", + "is_pset": true, "flags": 1, "result": "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" }, { "comment": "PSETv2, single 2of2 p2sh-p2wsh multisig input w/signatures, redundant finalization", "psbt": "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", + "is_pset": true, "flags": 1, "result": "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" }, { "comment": "PSETv2, single 2of2 p2sh-p2wsh un-optimized csv input (expired) w/signature, redundant finalization", "psbt": "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", + "is_pset": true, "flags": 1, "result": "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" }, diff --git a/src/test/test_psbt.py b/src/test/test_psbt.py index a114adb1c..74a2eacc3 100644 --- a/src/test/test_psbt.py +++ b/src/test/test_psbt.py @@ -197,16 +197,21 @@ def test_signer_role(self): def test_finalizer_role(self): """Test the PSBT finalizer role""" + _, is_elements_build = wally_is_elements_build() SERIALIZE_FLAG_REDUNDANT = 0x1 for case in JSON['finalizer']: - psbt = self.parse_base64(case['psbt']) + is_pset = case.get('is_pset', False) + expected_ret = WALLY_EINVAL if is_pset and not is_elements_build else WALLY_OK + psbt = self.parse_base64(case['psbt'], expected_ret) flags = case['flags'] - extract_flags = SERIALIZE_FLAG_REDUNDANT if flags == 1 else 0 - self.assertEqual(WALLY_OK, wally_psbt_finalize(psbt, flags)) - ret, is_finalized = wally_psbt_is_finalized(psbt) - self.assertEqual((ret, is_finalized), (WALLY_OK, 1)) - self.assertEqual(self.to_base64(psbt, flags=extract_flags), case['result']) - wally_psbt_free(psbt) + ret = wally_psbt_finalize(psbt, flags) + self.assertEqual(ret, expected_ret); + if expected_ret == WALLY_OK: + ret, is_finalized = wally_psbt_is_finalized(psbt) + self.assertEqual((ret, is_finalized), (WALLY_OK, 1)) + extract_flags = SERIALIZE_FLAG_REDUNDANT if flags == 1 else 0 + self.assertEqual(self.to_base64(psbt, flags=extract_flags), case['result']) + wally_psbt_free(psbt) def test_extractor_role(self): """Test the PSBT extractor role""" From a136f449c8855094a7308653cb5bde4775bf89de Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sun, 2 Feb 2025 15:03:54 +1300 Subject: [PATCH 03/22] ccan: merge upstream base64 warning fixes --- src/ccan/ccan/base64/base64.c | 4 ++-- src/ccan/ccan/base64/base64.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ccan/ccan/base64/base64.c b/src/ccan/ccan/base64/base64.c index af35b561c..439655d5d 100644 --- a/src/ccan/ccan/base64/base64.c +++ b/src/ccan/ccan/base64/base64.c @@ -34,7 +34,7 @@ static int8_t sixbit_from_b64(const base64_maps_t *maps, int8_t ret; ret = maps->decode_map[(unsigned char)b64letter]; - if (ret == '\xff') { + if (ret == (int8_t)'\xff') { errno = EDOM; return -1; } @@ -44,7 +44,7 @@ static int8_t sixbit_from_b64(const base64_maps_t *maps, bool base64_char_in_alphabet(const base64_maps_t *maps, const char b64char) { - return (maps->decode_map[(const unsigned char)b64char] != '\xff'); + return (maps->decode_map[(const unsigned char)b64char] != (signed char)'\xff'); } void base64_init_maps(base64_maps_t *dest, const char src[64]) diff --git a/src/ccan/ccan/base64/base64.h b/src/ccan/ccan/base64/base64.h index 5dc9140d1..74b881fbe 100644 --- a/src/ccan/ccan/base64/base64.h +++ b/src/ccan/ccan/base64/base64.h @@ -118,7 +118,7 @@ ssize_t base64_decode_quartet_using_maps(const base64_maps_t *maps, * @note sets errno = EDOM if src contains invalid characters * @note sets errno = EINVAL if src is an invalid base64 tail */ -ssize_t base64_decode_tail_using_maps(const base64_maps_t *maps, char *dest, +ssize_t base64_decode_tail_using_maps(const base64_maps_t *maps, char dest[3], const char *src, size_t srclen); @@ -214,7 +214,7 @@ ssize_t base64_decode(char *dest, size_t destlen, * @note sets errno = EDOM if src contains invalid characters */ static inline -int base64_decode_quartet(char dest[3], const char src[4]) +ssize_t base64_decode_quartet(char dest[3], const char src[4]) { return base64_decode_quartet_using_maps(&base64_maps_rfc4648, dest, src); From 6959a8c985e502adbd6f4e7fbfe98aba680abdd7 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sun, 2 Feb 2025 15:04:13 +1300 Subject: [PATCH 04/22] swig: ignore -Wpedantic errors in swig generated code --- configure.ac | 1 + 1 file changed, 1 insertion(+) diff --git a/configure.ac b/configure.ac index 55211451b..0c6d5f96e 100644 --- a/configure.ac +++ b/configure.ac @@ -195,6 +195,7 @@ AC_SUBST([NOBUILTIN_CFLAGS]) SWIG_WARN_CFLAGS="-fno-strict-aliasing" AX_CHECK_COMPILE_FLAG([-Wno-unused-parameter], [SWIG_WARN_CFLAGS="$SWIG_WARN_CFLAGS -Wno-unused-parameter"]) AX_CHECK_COMPILE_FLAG([-Wno-shadow], [SWIG_WARN_CFLAGS="$SWIG_WARN_CFLAGS -Wno-shadow"]) +AX_CHECK_COMPILE_FLAG([-Wno-pedantic], [SWIG_WARN_CFLAGS="$SWIG_WARN_CFLAGS -Wno-pedantic"]) AX_CHECK_COMPILE_FLAG([-Wno-missing-field-initializers], [SWIG_WARN_CFLAGS="$SWIG_WARN_CFLAGS -Wno-missing-field-initializers"]) if echo | "$CC" -dM -E - | grep __clang__ >/dev/null; then AX_CHECK_COMPILE_FLAG([-Wno-self-assign], [SWIG_WARN_CFLAGS="$SWIG_WARN_CFLAGS -Wno-self-assign"]) From 780b845bc6f7881a7dc704828ea233d994598964 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sun, 2 Feb 2025 15:04:24 +1300 Subject: [PATCH 05/22] ci: various ci improvements - Run CI tests with/without Elements, also test minimal build as used by Jade - Enable -Werror for the main test run - Fail if scan-build errors are detected - Expose scan-build artifacts for investigating reported errors - Do not build release files if any tests fail --- .gitlab-ci.yml | 129 ++++++++++++++++++++++++++++++------------------- 1 file changed, 78 insertions(+), 51 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fa324dd11..514974634 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,51 +1,16 @@ variables: GIT_SUBMODULE_STRATEGY: recursive -build_wally_release_files: - image: greenaddress/wallycore@sha256:956b107d688f549c6e3884424991b7d3d34d84173990d43046fd760d7918db7c - artifacts: - expire_in: 7 days - name: wallycore-bindings - when: on_success - paths: - - dist/* - tags: - - ga - script: - - python3 -m build - - virtualenv -p python3 .smoketest - - source .smoketest/bin/activate - - pip install --find-links=./dist wallycore - - python -c "import wallycore as w; assert w.hex_from_bytes(w.hex_to_bytes('ff')) == 'ff'" - - deactivate - - rm -rf .smoketest dist/*.whl - - mv dist wally_dist - - ./tools/build_android_libraries.sh - - mv release wallycore-android-jni - - tar czf wally_dist/wallycore-android-jni.tar.gz --remove-files wallycore-android-jni - - source /opt/emsdk/emsdk_env.sh - - tools/build_wasm.sh - - cd dist - - tar czf wallycore-wasm.tar.gz --remove-files wallycore.html wallycore.js wallycore.wasm - - cd .. - - sphinx-build -b html -a -c docs/source docs/source docs/build/html - - cd docs/build - - tar czf ../../wally_dist/apidocs.tar.gz html/ - - cd ../.. - - mv wally_dist/* dist/ - - rmdir wally_dist +stages: + - test + - release -build_mingw_static: - image: greenaddress/wallycore@sha256:956b107d688f549c6e3884424991b7d3d34d84173990d43046fd760d7918db7c - tags: - - ga - script: - - ./tools/cleanup.sh && ./tools/autogen.sh - - CC=x86_64-w64-mingw32-gcc ./configure --host=x86_64-w64-mingw32 --disable-swig-python --disable-swig-java --disable-shared --enable-static - - make -j $(($(grep ^processor /proc/cpuinfo | wc -l) / 2)) - -run_tests: +test_with_valgrind: + stage: test image: greenaddress/wallycore@sha256:956b107d688f549c6e3884424991b7d3d34d84173990d43046fd760d7918db7c + parallel: + matrix: + - CONFIGURE_ARGS: [--enable-elements=yes,--enable-elements=no,--enable-minimal=yes] tags: - ga artifacts: @@ -53,35 +18,50 @@ run_tests: codequality: valgrind.json script: - ./tools/cleanup.sh && ./tools/autogen.sh - - ./configure --enable-export-all --enable-swig-python --enable-swig-java --enable-shared --disable-static + - CFLAGS='-Werror' ./configure --enable-export-all --enable-swig-python --enable-swig-java $CONFIGURE_ARGS --enable-shared --disable-static - make -j $(($(grep ^processor /proc/cpuinfo | wc -l) / 2)) - 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 -run_asan_ubsan_gcc: +test_asan_ubsan_gcc: + stage: test image: greenaddress/wallycore@sha256:956b107d688f549c6e3884424991b7d3d34d84173990d43046fd760d7918db7c + parallel: + matrix: + - CONFIGURE_ARGS: [--enable-elements=yes, --enable-elements=no,--enable-minimal=yes] tags: - ga script: - ./tools/cleanup.sh && ./tools/autogen.sh - - CC=gcc CFLAGS="-O1 -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 --enable-shared --disable-static --disable-clear-tests --disable-asm + - 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 - 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)) - ASAN_OPTIONS=abort_on_error=1:fast_unwind_on_malloc=0:detect_leaks=0 UBSAN_OPTIONS=print_stacktrace=1 make check V=1 -run_scan_build_clang: +test_scan_build_clang: + stage: test image: greenaddress/wallycore@sha256:956b107d688f549c6e3884424991b7d3d34d84173990d43046fd760d7918db7c + parallel: + matrix: + - CONFIGURE_ARGS: [--enable-elements=yes, --enable-elements=no,--enable-minimal=yes] tags: - ga script: - ./tools/cleanup.sh && ./tools/autogen.sh - - CC=clang CFLAGS="-O0" scan-build-11 ./configure --enable-export-all --enable-swig-python --enable-swig-java --disable-clear-tests --disable-asm - - scan-build-11 --keep-cc --exclude src/secp256k1/ make -j $(($(grep ^processor /proc/cpuinfo | wc -l) / 2)) + - CC=clang scan-build-11 ./configure --enable-export-all --enable-swig-python --enable-swig-java --disable-clear-tests --disable-asm + - scan-build-11 --keep-cc --exclude src/secp256k1/ --status-bugs --keep-empty -o scan-build$CONFIGURE_ARGS make -j $(($(grep ^processor /proc/cpuinfo | wc -l) / 2)) + artifacts: + expire_in: 3 days + name: scan-build$CONFIGURE_ARGS + when: on_success + paths: + - scan-build$CONFIGURE_ARGS/* -cmake-test: +test_cmake: + stage: test image: greenaddress/wallycore@sha256:956b107d688f549c6e3884424991b7d3d34d84173990d43046fd760d7918db7c tags: - ga @@ -110,7 +90,8 @@ cmake-test: coverage_format: cobertura path: coverage.xml -amalgamation-test: +test_amalgamation: + stage: test image: greenaddress/wallycore@sha256:956b107d688f549c6e3884424991b7d3d34d84173990d43046fd760d7918db7c tags: - ga @@ -120,3 +101,49 @@ amalgamation-test: - gcc -DBUILD_ELEMENTS -Wall -W -Wextra -Werror -I. -I./src -I./src/ccan -I./src/secp256k1/include src/ctest/amalgamation_compile_test.c - clang -Wall -W -Wextra -Werror -I. -I./src -I./src/ccan -I./src/secp256k1/include src/ctest/amalgamation_compile_test.c - clang -DBUILD_ELEMENTS -Wall -W -Wextra -Werror -I. -I./src -I./src/ccan -I./src/secp256k1/include src/ctest/amalgamation_compile_test.c + +test_mingw_static_build: + stage: test + image: greenaddress/wallycore@sha256:956b107d688f549c6e3884424991b7d3d34d84173990d43046fd760d7918db7c + tags: + - ga + script: + - ./tools/cleanup.sh && ./tools/autogen.sh + - CC=x86_64-w64-mingw32-gcc ./configure --host=x86_64-w64-mingw32 --disable-swig-python --disable-swig-java --disable-shared --enable-static + - make -j $(($(grep ^processor /proc/cpuinfo | wc -l) / 2)) + +build_wally_release_files: + stage: release + needs: [test_mingw_static_build,test_with_valgrind,test_asan_ubsan_gcc,test_scan_build_clang,test_cmake,test_amalgamation] + image: greenaddress/wallycore@sha256:956b107d688f549c6e3884424991b7d3d34d84173990d43046fd760d7918db7c + artifacts: + expire_in: 7 days + name: wallycore-bindings + when: on_success + paths: + - dist/* + tags: + - ga + script: + - python3 -m build + - virtualenv -p python3 .smoketest + - source .smoketest/bin/activate + - pip install --find-links=./dist wallycore + - python -c "import wallycore as w; assert w.hex_from_bytes(w.hex_to_bytes('ff')) == 'ff'" + - deactivate + - rm -rf .smoketest dist/*.whl + - mv dist wally_dist + - ./tools/build_android_libraries.sh + - mv release wallycore-android-jni + - tar czf wally_dist/wallycore-android-jni.tar.gz --remove-files wallycore-android-jni + - source /opt/emsdk/emsdk_env.sh + - tools/build_wasm.sh + - cd dist + - tar czf wallycore-wasm.tar.gz --remove-files wallycore.html wallycore.js wallycore.wasm + - cd .. + - sphinx-build -b html -a -c docs/source docs/source docs/build/html + - cd docs/build + - tar czf ../../wally_dist/apidocs.tar.gz html/ + - cd ../.. + - mv wally_dist/* dist/ + - rmdir wally_dist From 75564dbc93da370cde3761168d01588fa7c851ea Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sun, 2 Feb 2025 23:26:52 +1300 Subject: [PATCH 06/22] tx: use SHA256_LEN for nonce and entropy lengths This is the same size as WALLY_TX_ASSET_TAG_LEN but doesn't give the incorrect impression that these fields hold asset tags. --- include/wally_transaction.h | 16 ++++++------ src/ctest/test_elements_tx.c | 12 ++++----- src/transaction.c | 48 ++++++++++++++++++------------------ 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/include/wally_transaction.h b/include/wally_transaction.h index 0e5b54af7..42abc7d9f 100644 --- a/include/wally_transaction.h +++ b/include/wally_transaction.h @@ -892,9 +892,9 @@ WALLY_CORE_API int wally_tx_get_elements_weight_discount( * * :param input: The input to add to. * :param nonce: Asset issuance or revelation blinding factor. - * :param nonce_len: Size of ``nonce`` in bytes. Must be `WALLY_TX_ASSET_TAG_LEN`. + * :param nonce_len: Size of ``nonce`` in bytes. Must be `SHA256_LEN`. * :param entropy: Entropy for the asset tag calculation. - * :param entropy_len: Size of ``entropy`` in bytes. Must be `WALLY_TX_ASSET_TAG_LEN`. + * :param entropy_len: Size of ``entropy`` in bytes. Must be `SHA256_LEN`. * :param issuance_amount: The (blinded) issuance amount. * :param issuance_amount_len: Size of ``issuance_amount`` in bytes. * :param inflation_keys: The (blinded) token reissuance amount. @@ -939,9 +939,9 @@ WALLY_CORE_API int wally_tx_elements_input_issuance_free( * :param script_len: Size of ``script`` in bytes. * :param witness: The witness stack for the input, or NULL if no witness is present. * :param nonce: Asset issuance or revelation blinding factor. - * :param nonce_len: Size of ``nonce`` in bytes. Must be `WALLY_TX_ASSET_TAG_LEN`. + * :param nonce_len: Size of ``nonce`` in bytes. Must be `SHA256_LEN`. * :param entropy: Entropy for the asset tag calculation. - * :param entropy_len: Size of ``entropy`` in bytes. Must be `WALLY_TX_ASSET_TAG_LEN`. + * :param entropy_len: Size of ``entropy`` in bytes. Must be `SHA256_LEN`. * :param issuance_amount: The (blinded) issuance amount. * :param issuance_amount_len: Size of ``issuance_amount`` in bytes. * :param inflation_keys: The (blinded) token reissuance amount. @@ -1101,9 +1101,9 @@ WALLY_CORE_API int wally_tx_elements_output_init_alloc( * :param script_len: Size of ``script`` in bytes. * :param witness: The witness stack for the input, or NULL if no witness is present. * :param nonce: Asset issuance or revelation blinding factor. - * :param nonce_len: Size of ``nonce`` in bytes. Must be `WALLY_TX_ASSET_TAG_LEN`. + * :param nonce_len: Size of ``nonce`` in bytes. Must be `SHA256_LEN`. * :param entropy: Entropy for the asset tag calculation. - * :param entropy_len: Size of ``entropy`` in bytes. Must be `WALLY_TX_ASSET_TAG_LEN`. + * :param entropy_len: Size of ``entropy`` in bytes. Must be `SHA256_LEN`. * :param issuance_amount: The (blinded) issuance amount. * :param issuance_amount_len: Size of ``issuance_amount`` in bytes. * :param inflation_keys: The (blinded) token reissuance amount. @@ -1153,9 +1153,9 @@ WALLY_CORE_API int wally_tx_add_elements_raw_input( * :param script_len: Size of ``script`` in bytes. * :param witness: The witness stack for the input, or NULL if no witness is present. * :param nonce: Asset issuance or revelation blinding factor. - * :param nonce_len: Size of ``nonce`` in bytes. Must be `WALLY_TX_ASSET_TAG_LEN`. + * :param nonce_len: Size of ``nonce`` in bytes. Must be `SHA256_LEN`. * :param entropy: Entropy for the asset tag calculation. - * :param entropy_len: Size of ``entropy`` in bytes. Must be `WALLY_TX_ASSET_TAG_LEN`. + * :param entropy_len: Size of ``entropy`` in bytes. Must be `SHA256_LEN`. * :param issuance_amount: The (blinded) issuance amount. * :param issuance_amount_len: Size of ``issuance_amount`` in bytes. * :param inflation_keys: The (blinded) token reissuance amount. diff --git a/src/ctest/test_elements_tx.c b/src/ctest/test_elements_tx.c index 122d0c9ce..be34aab64 100644 --- a/src/ctest/test_elements_tx.c +++ b/src/ctest/test_elements_tx.c @@ -80,8 +80,8 @@ static bool tx_roundtrip(const char *tx_hex, const char *sighash_hex) ret = wally_tx_elements_input_init_alloc(in->txhash, sizeof(in->txhash), in->index, in->sequence, in->script, in->script_len, in->witness, - in->blinding_nonce, WALLY_TX_ASSET_TAG_LEN, - in->entropy, WALLY_TX_ASSET_TAG_LEN, + in->blinding_nonce, SHA256_LEN, + in->entropy, SHA256_LEN, in->issuance_amount, in->issuance_amount_len, in->inflation_keys, in->inflation_keys_len, in->issuance_amount_rangeproof, @@ -101,8 +101,8 @@ static bool tx_roundtrip(const char *tx_hex, const char *sighash_hex) new_in->index, new_in->sequence, new_in->script, new_in->script_len, new_in->witness, - new_in->blinding_nonce, WALLY_TX_ASSET_TAG_LEN, - new_in->entropy, WALLY_TX_ASSET_TAG_LEN, + new_in->blinding_nonce, SHA256_LEN, + new_in->entropy, SHA256_LEN, new_in->issuance_amount, new_in->issuance_amount_len, new_in->inflation_keys, new_in->inflation_keys_len, new_in->issuance_amount_rangeproof, @@ -262,8 +262,8 @@ static bool tx_pegin(const char *tx_hex, const char **tx_pegin_wit_hex, size_t n in->index | WALLY_TX_PEGIN_FLAG, in->sequence, in->script, in->script_len, in->witness, - in->blinding_nonce, WALLY_TX_ASSET_TAG_LEN, - in->entropy, WALLY_TX_ASSET_TAG_LEN, + in->blinding_nonce, SHA256_LEN, + in->entropy, SHA256_LEN, in->issuance_amount, in->issuance_amount_len, in->inflation_keys, in->inflation_keys_len, in->issuance_amount_rangeproof, diff --git a/src/transaction.c b/src/transaction.c index aa84279cb..d319e19f6 100644 --- a/src/transaction.c +++ b/src/transaction.c @@ -482,8 +482,8 @@ static int tx_elements_input_issuance_init( #endif if (!input || - BYTES_INVALID_N(nonce, nonce_len, WALLY_TX_ASSET_TAG_LEN) || - BYTES_INVALID_N(entropy, entropy_len, WALLY_TX_ASSET_TAG_LEN) || + BYTES_INVALID_N(nonce, nonce_len, SHA256_LEN) || + BYTES_INVALID_N(entropy, entropy_len, SHA256_LEN) || BYTES_INVALID(issuance_amount, issuance_amount_len) || BYTES_INVALID(inflation_keys, inflation_keys_len) || BYTES_INVALID(issuance_amount_rangeproof, issuance_amount_rangeproof_len) || @@ -1311,8 +1311,8 @@ static int tx_add_elements_raw_input_at( return WALLY_EINVAL; /* TODO: Allow creation of p2pkh/p2sh using flags */ if (!txhash || txhash_len != WALLY_TXHASH_LEN || - BYTES_INVALID_N(nonce, nonce_len, WALLY_TX_ASSET_TAG_LEN) || - BYTES_INVALID_N(entropy, entropy_len, WALLY_TX_ASSET_TAG_LEN) || + BYTES_INVALID_N(nonce, nonce_len, SHA256_LEN) || + BYTES_INVALID_N(entropy, entropy_len, SHA256_LEN) || BYTES_INVALID(issuance_amount, issuance_amount_len) || BYTES_INVALID(inflation_keys, inflation_keys_len) || BYTES_INVALID(issuance_amount_rangeproof, issuance_amount_rangeproof_len) || @@ -1336,9 +1336,9 @@ static int tx_add_elements_raw_input_at( memcpy(input.txhash, txhash, WALLY_TXHASH_LEN); #ifdef BUILD_ELEMENTS if (nonce) - memcpy(input.blinding_nonce, nonce, WALLY_TX_ASSET_TAG_LEN); + memcpy(input.blinding_nonce, nonce, SHA256_LEN); if (entropy) - memcpy(input.entropy, entropy, WALLY_TX_ASSET_TAG_LEN); + memcpy(input.entropy, entropy, SHA256_LEN); #endif /* BUILD_ELEMENTS */ ret = wally_tx_add_input_at(tx, index, &input); wally_clear(&input, sizeof(input)); @@ -2214,10 +2214,10 @@ static inline int tx_to_bip143_bytes(const struct wally_tx *tx, 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, WALLY_TX_ASSET_TAG_LEN); - tmp_p += WALLY_TX_ASSET_TAG_LEN; - memcpy(tmp_p, tx->inputs[i].entropy, WALLY_TX_ASSET_TAG_LEN); - tmp_p += WALLY_TX_ASSET_TAG_LEN; + 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, @@ -2249,10 +2249,10 @@ static inline int tx_to_bip143_bytes(const struct wally_tx *tx, #ifdef BUILD_ELEMENTS if (is_elements && (tx->inputs[opts->index].features & WALLY_TX_IS_ISSUANCE)) { - memcpy(p, tx->inputs[opts->index].blinding_nonce, WALLY_TX_ASSET_TAG_LEN); - p += WALLY_TX_ASSET_TAG_LEN; - memcpy(p, tx->inputs[opts->index].entropy, WALLY_TX_ASSET_TAG_LEN); - p += WALLY_TX_ASSET_TAG_LEN; + 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, @@ -2660,10 +2660,10 @@ static int tx_to_bytes(const struct wally_tx *tx, if (!is_elements) return WALLY_EINVAL; #ifdef BUILD_ELEMENTS - memcpy(p, input->blinding_nonce, WALLY_TX_ASSET_TAG_LEN); - p += WALLY_TX_ASSET_TAG_LEN; - memcpy(p, input->entropy, WALLY_TX_ASSET_TAG_LEN); - p += WALLY_TX_ASSET_TAG_LEN; + memcpy(p, input->blinding_nonce, SHA256_LEN); + p += SHA256_LEN; + memcpy(p, input->entropy, SHA256_LEN); + p += SHA256_LEN; p += confidential_value_to_bytes(input->issuance_amount, input->issuance_amount_len, p); p += confidential_value_to_bytes(input->inflation_keys, input->inflation_keys_len, p); #endif @@ -2926,8 +2926,8 @@ static int analyze_tx(const unsigned char *bytes, size_t bytes_len, ensure_n(sizeof(uint32_t)); p += sizeof(uint32_t); if (expect_issuance) { - ensure_n(2 * WALLY_TX_ASSET_TAG_LEN); - p += 2 * WALLY_TX_ASSET_TAG_LEN; + ensure_n(2 * SHA256_LEN); + p += 2 * SHA256_LEN; ensure_committed_value(p); /* issuance amount */ ensure_committed_value(p); /* inflation keys */ } @@ -3069,9 +3069,9 @@ static int tx_from_bytes(const unsigned char *bytes, size_t bytes_len, p += uint32_from_le_bytes(p, &sequence); if (is_elements && !!(index & WALLY_TX_ISSUANCE_FLAG) && !is_coinbase_bytes(txhash, WALLY_TXHASH_LEN, index)) { nonce = p; - p += WALLY_TX_ASSET_TAG_LEN; + p += SHA256_LEN; entropy = p; - p += WALLY_TX_ASSET_TAG_LEN; + p += SHA256_LEN; issuance_amount = p; p += confidential_value_varint_from_bytes(p, &issuance_amount_len); inflation_keys = p; @@ -3079,8 +3079,8 @@ static int tx_from_bytes(const unsigned char *bytes, size_t bytes_len, } ret = tx_elements_input_init(txhash, WALLY_TXHASH_LEN, index, sequence, script_len ? script : NULL, script_len, NULL, - nonce, nonce ? WALLY_TX_ASSET_TAG_LEN : 0, - entropy, entropy ? WALLY_TX_ASSET_TAG_LEN : 0, + nonce, nonce ? SHA256_LEN : 0, + entropy, entropy ? SHA256_LEN : 0, issuance_amount_len ? issuance_amount : NULL, issuance_amount_len, inflation_keys_len ? inflation_keys : NULL, inflation_keys_len, NULL, 0, NULL, 0, NULL, &(*output)->inputs[i], is_elements); From cc197dbf3aa750326a0948083bffdd315baa43f9 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sat, 8 Feb 2025 18:30:51 +1300 Subject: [PATCH 07/22] psbt: add missing psbt_add_input_keypath call --- include/wally.hpp | 6 ++++++ include/wally_psbt.h | 22 ++++++++++++++++++++++ src/psbt.c | 16 ++++++++++++++++ src/swig_java/swig.i | 1 + src/test/util.py | 1 + src/wasm_package/src/functions.js | 1 + src/wasm_package/src/index.d.ts | 1 + tools/wasm_exports.sh | 1 + 8 files changed, 49 insertions(+) diff --git a/include/wally.hpp b/include/wally.hpp index 5c49cef21..f704203d2 100644 --- a/include/wally.hpp +++ b/include/wally.hpp @@ -1144,6 +1144,12 @@ inline int pbkdf2_hmac_sha512(const PASS& pass, const SALT& salt, uint32_t flags return detail::check_ret(__FUNCTION__, ret); } +template +inline int psbt_add_input_keypath(const PSBT& psbt, uint32_t index, const PUB_KEY& pub_key, const FINGERPRINT& fingerprint, const CHILD_PATH& child_path) { + int ret = ::wally_psbt_add_input_keypath(detail::get_p(psbt), index, pub_key.data(), pub_key.size(), fingerprint.data(), fingerprint.size(), child_path.data(), child_path.size()); + return detail::check_ret(__FUNCTION__, ret); +} + template inline int psbt_add_input_taproot_keypath(const PSBT& psbt, uint32_t index, uint32_t flags, const PUB_KEY& pub_key, const TAPLEAF_HASHES& tapleaf_hashes, const FINGERPRINT& fingerprint, const CHILD_PATH& child_path) { int ret = ::wally_psbt_add_input_taproot_keypath(detail::get_p(psbt), index, flags, pub_key.data(), pub_key.size(), tapleaf_hashes.data(), tapleaf_hashes.size(), fingerprint.data(), fingerprint.size(), child_path.data(), child_path.size()); diff --git a/include/wally_psbt.h b/include/wally_psbt.h index c2d7aace1..703d8dce4 100644 --- a/include/wally_psbt.h +++ b/include/wally_psbt.h @@ -2135,6 +2135,28 @@ WALLY_CORE_API int wally_psbt_find_input_spending_utxo( uint32_t utxo_index, size_t *written); +/** + * Add a keypath to a given PSBT input. + * + * :param psbt: The PSBT to add the keypath to. + * :param index: The zero-based index of the input to add to. + * :param pub_key: The pubkey to add. + * :param pub_key_len: Length of ``pub_key`` in bytes. Must be `EC_PUBLIC_KEY_UNCOMPRESSED_LEN` or `EC_PUBLIC_KEY_LEN`. + * :param fingerprint: The master key fingerprint for the pubkey. + * :param fingerprint_len: Length of ``fingerprint`` in bytes. Must be `BIP32_KEY_FINGERPRINT_LEN`. + * :param child_path: The BIP32 derivation path for the pubkey. + * :param child_path_len: The number of items in ``child_path``. + */ +WALLY_CORE_API int wally_psbt_add_input_keypath( + struct wally_psbt *psbt, + uint32_t index, + const unsigned char *pub_key, + size_t pub_key_len, + const unsigned char *fingerprint, + size_t fingerprint_len, + const uint32_t *child_path, + size_t child_path_len); + /** * Add a taproot keypath to a given PSBT input. * diff --git a/src/psbt.c b/src/psbt.c index 3106d6e94..1490fa9e9 100644 --- a/src/psbt.c +++ b/src/psbt.c @@ -1690,6 +1690,22 @@ static int psbt_input_from_tx_input(struct wally_psbt *psbt, return ret; } +int wally_psbt_add_input_keypath( + struct wally_psbt *psbt, uint32_t index, + const unsigned char *pub_key, size_t pub_key_len, + const unsigned char *fingerprint, size_t fingerprint_len, + const uint32_t *child_path, size_t child_path_len) +{ + struct wally_psbt_input *inp = psbt_get_input(psbt, index); + if (!inp || !psbt_is_valid(psbt) || + !psbt_can_modify(psbt, WALLY_PSBT_TXMOD_INPUTS)) + return WALLY_EINVAL; + + return wally_psbt_input_keypath_add(inp, pub_key, pub_key_len, + fingerprint, fingerprint_len, + child_path, child_path_len); +} + int wally_psbt_add_input_taproot_keypath( struct wally_psbt *psbt, uint32_t index, uint32_t flags, diff --git a/src/swig_java/swig.i b/src/swig_java/swig.i index f9b45595a..70d16af1d 100644 --- a/src/swig_java/swig.i +++ b/src/swig_java/swig.i @@ -677,6 +677,7 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %returns_array_(wally_pbkdf2_hmac_sha256, 7, 8, PBKDF2_HMAC_SHA256_LEN); %returns_array_(wally_pbkdf2_hmac_sha512, 7, 8, PBKDF2_HMAC_SHA512_LEN); %returns_void__(wally_psbt_add_tx_input_at); +%returns_void__(wally_psbt_add_input_keypath); %returns_void__(wally_psbt_add_input_signature); %returns_void__(wally_psbt_add_input_taproot_keypath); %returns_void__(wally_psbt_add_output_taproot_keypath); diff --git a/src/test/util.py b/src/test/util.py index 19c7c1de6..0a1ace41f 100755 --- a/src/test/util.py +++ b/src/test/util.py @@ -431,6 +431,7 @@ class wally_psbt(Structure): ('wally_pbkdf2_hmac_sha256', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_uint32, c_void_p, c_size_t]), ('wally_pbkdf2_hmac_sha512', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_uint32, c_void_p, c_size_t]), ('wally_psbt_add_global_scalar', c_int, [POINTER(wally_psbt), c_void_p, c_size_t]), + ('wally_psbt_add_input_keypath', c_int, [POINTER(wally_psbt), c_uint32, c_void_p, c_size_t, c_void_p, c_size_t, POINTER(c_uint32), c_size_t]), ('wally_psbt_add_input_taproot_keypath', c_int, [POINTER(wally_psbt), c_uint32, c_uint32, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, POINTER(c_uint32), c_size_t]), ('wally_psbt_add_output_taproot_keypath', c_int, [POINTER(wally_psbt), c_uint32, c_uint32, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, POINTER(c_uint32), c_size_t]), ('wally_psbt_add_tx_input_at', c_int, [POINTER(wally_psbt), c_uint32, c_uint32, POINTER(wally_tx_input)]), diff --git a/src/wasm_package/src/functions.js b/src/wasm_package/src/functions.js index ba4b54a09..a8208757c 100644 --- a/src/wasm_package/src/functions.js +++ b/src/wasm_package/src/functions.js @@ -266,6 +266,7 @@ export const merkle_path_xonly_public_key_verify = wrap('wally_merkle_path_xonly export const pbkdf2_hmac_sha256 = wrap('wally_pbkdf2_hmac_sha256', [T.Bytes, T.Bytes, T.Int32, T.Int32, T.DestPtrSized(T.Bytes, C.PBKDF2_HMAC_SHA256_LEN)]); export const pbkdf2_hmac_sha512 = wrap('wally_pbkdf2_hmac_sha512', [T.Bytes, T.Bytes, T.Int32, T.Int32, T.DestPtrSized(T.Bytes, C.PBKDF2_HMAC_SHA512_LEN)]); export const psbt_add_global_scalar = wrap('wally_psbt_add_global_scalar', [T.OpaqueRef, T.Bytes]); +export const psbt_add_input_keypath = wrap('wally_psbt_add_input_keypath', [T.OpaqueRef, T.Int32, T.Bytes, T.Bytes, T.Uint32Array]); export const psbt_add_input_signature = wrap('wally_psbt_add_input_signature', [T.OpaqueRef, T.Int32, T.Bytes, T.Bytes]); export const psbt_add_input_taproot_keypath = wrap('wally_psbt_add_input_taproot_keypath', [T.OpaqueRef, T.Int32, T.Int32, T.Bytes, T.Bytes, T.Bytes, T.Uint32Array]); export const psbt_add_output_taproot_keypath = wrap('wally_psbt_add_output_taproot_keypath', [T.OpaqueRef, T.Int32, T.Int32, T.Bytes, T.Bytes, T.Bytes, T.Uint32Array]); diff --git a/src/wasm_package/src/index.d.ts b/src/wasm_package/src/index.d.ts index 44e54addc..7fb73a962 100644 --- a/src/wasm_package/src/index.d.ts +++ b/src/wasm_package/src/index.d.ts @@ -226,6 +226,7 @@ export function merkle_path_xonly_public_key_verify(key: Buffer|Uint8Array, val: export function pbkdf2_hmac_sha256(pass: Buffer|Uint8Array, salt: Buffer|Uint8Array, flags: number, cost: number): Buffer; export function pbkdf2_hmac_sha512(pass: Buffer|Uint8Array, salt: Buffer|Uint8Array, flags: number, cost: number): Buffer; export function psbt_add_global_scalar(psbt: Ref_wally_psbt, scalar: Buffer|Uint8Array): void; +export function psbt_add_input_keypath(psbt: Ref_wally_psbt, index: number, pub_key: Buffer|Uint8Array, fingerprint: Buffer|Uint8Array, child_path: Uint32Array|number[]): void; export function psbt_add_input_signature(psbt: Ref_wally_psbt, index: number, pub_key: Buffer|Uint8Array, sig: Buffer|Uint8Array): void; export function psbt_add_input_taproot_keypath(psbt: Ref_wally_psbt, index: number, flags: number, pub_key: Buffer|Uint8Array, tapleaf_hashes: Buffer|Uint8Array, fingerprint: Buffer|Uint8Array, child_path: Uint32Array|number[]): void; export function psbt_add_output_taproot_keypath(psbt: Ref_wally_psbt, index: number, flags: number, pub_key: Buffer|Uint8Array, tapleaf_hashes: Buffer|Uint8Array, fingerprint: Buffer|Uint8Array, child_path: Uint32Array|number[]): void; diff --git a/tools/wasm_exports.sh b/tools/wasm_exports.sh index afcab56d9..1ba54e63f 100644 --- a/tools/wasm_exports.sh +++ b/tools/wasm_exports.sh @@ -188,6 +188,7 @@ EXPORTED_FUNCTIONS="['_malloc','_free','_bip32_key_free' \ ,'_wally_merkle_path_xonly_public_key_verify' \ ,'_wally_pbkdf2_hmac_sha256' \ ,'_wally_pbkdf2_hmac_sha512' \ +,'_wally_psbt_add_input_keypath' \ ,'_wally_psbt_add_input_signature' \ ,'_wally_psbt_add_input_taproot_keypath' \ ,'_wally_psbt_add_output_taproot_keypath' \ From f4524065d14590a440563064f88321c393107457 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sun, 9 Feb 2025 15:34:45 +1300 Subject: [PATCH 08/22] psbt: add missing psbt_add_output_keypath call --- include/wally.hpp | 6 ++++++ include/wally_psbt.h | 22 ++++++++++++++++++++++ src/psbt.c | 16 ++++++++++++++++ src/swig_java/swig.i | 1 + src/test/util.py | 1 + src/wasm_package/src/functions.js | 1 + src/wasm_package/src/index.d.ts | 1 + tools/wasm_exports.sh | 1 + 8 files changed, 49 insertions(+) diff --git a/include/wally.hpp b/include/wally.hpp index f704203d2..c4ee08fc6 100644 --- a/include/wally.hpp +++ b/include/wally.hpp @@ -1156,6 +1156,12 @@ inline int psbt_add_input_taproot_keypath(const PSBT& psbt, uint32_t index, uint return detail::check_ret(__FUNCTION__, ret); } +template +inline int psbt_add_output_keypath(const PSBT& psbt, uint32_t index, const PUB_KEY& pub_key, const FINGERPRINT& fingerprint, const CHILD_PATH& child_path) { + int ret = ::wally_psbt_add_output_keypath(detail::get_p(psbt), index, pub_key.data(), pub_key.size(), fingerprint.data(), fingerprint.size(), child_path.data(), child_path.size()); + return detail::check_ret(__FUNCTION__, ret); +} + template inline int psbt_add_output_taproot_keypath(const PSBT& psbt, uint32_t index, uint32_t flags, const PUB_KEY& pub_key, const TAPLEAF_HASHES& tapleaf_hashes, const FINGERPRINT& fingerprint, const CHILD_PATH& child_path) { int ret = ::wally_psbt_add_output_taproot_keypath(detail::get_p(psbt), index, flags, pub_key.data(), pub_key.size(), tapleaf_hashes.data(), tapleaf_hashes.size(), fingerprint.data(), fingerprint.size(), child_path.data(), child_path.size()); diff --git a/include/wally_psbt.h b/include/wally_psbt.h index 703d8dce4..7808ad513 100644 --- a/include/wally_psbt.h +++ b/include/wally_psbt.h @@ -2315,6 +2315,28 @@ WALLY_CORE_API int wally_psbt_get_input_signature_hash( unsigned char *bytes_out, size_t len); +/** + * Add a keypath to a given PSBT output. + * + * :param psbt: The PSBT to add the keypath to. + * :param index: The zero-based index of the output to add to. + * :param pub_key: The pubkey to add. + * :param pub_key_len: Length of ``pub_key`` in bytes. Must be `EC_PUBLIC_KEY_UNCOMPRESSED_LEN` or `EC_PUBLIC_KEY_LEN`. + * :param fingerprint: The master key fingerprint for the pubkey. + * :param fingerprint_len: Length of ``fingerprint`` in bytes. Must be `BIP32_KEY_FINGERPRINT_LEN`. + * :param child_path: The BIP32 derivation path for the pubkey. + * :param child_path_len: The number of items in ``child_path``. + */ +WALLY_CORE_API int wally_psbt_add_output_keypath( + struct wally_psbt *psbt, + uint32_t index, + const unsigned char *pub_key, + size_t pub_key_len, + const unsigned char *fingerprint, + size_t fingerprint_len, + const uint32_t *child_path, + size_t child_path_len); + /** * Add a taproot keypath to a given PSBT output. * diff --git a/src/psbt.c b/src/psbt.c index 1490fa9e9..dc1d7e7d7 100644 --- a/src/psbt.c +++ b/src/psbt.c @@ -1856,6 +1856,22 @@ static int psbt_output_from_tx_output(struct wally_psbt *psbt, return ret; } +int wally_psbt_add_output_keypath( + struct wally_psbt *psbt, uint32_t index, + const unsigned char *pub_key, size_t pub_key_len, + const unsigned char *fingerprint, size_t fingerprint_len, + const uint32_t *child_path, size_t child_path_len) +{ + struct wally_psbt_output *p = psbt_get_output(psbt, index); + if (!p || !psbt_is_valid(psbt) || + !psbt_can_modify(psbt, WALLY_PSBT_TXMOD_OUTPUTS)) + return WALLY_EINVAL; + + return wally_psbt_output_keypath_add(p, pub_key, pub_key_len, + fingerprint, fingerprint_len, + child_path, child_path_len); +} + int wally_psbt_add_output_taproot_keypath( struct wally_psbt *psbt, uint32_t index, uint32_t flags, diff --git a/src/swig_java/swig.i b/src/swig_java/swig.i index 70d16af1d..dab59d43d 100644 --- a/src/swig_java/swig.i +++ b/src/swig_java/swig.i @@ -680,6 +680,7 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %returns_void__(wally_psbt_add_input_keypath); %returns_void__(wally_psbt_add_input_signature); %returns_void__(wally_psbt_add_input_taproot_keypath); +%returns_void__(wally_psbt_add_output_keypath); %returns_void__(wally_psbt_add_output_taproot_keypath); %returns_void__(wally_psbt_add_tx_output_at); %returns_void__(wally_psbt_add_global_scalar); diff --git a/src/test/util.py b/src/test/util.py index 0a1ace41f..8d76b1ebe 100755 --- a/src/test/util.py +++ b/src/test/util.py @@ -433,6 +433,7 @@ class wally_psbt(Structure): ('wally_psbt_add_global_scalar', c_int, [POINTER(wally_psbt), c_void_p, c_size_t]), ('wally_psbt_add_input_keypath', c_int, [POINTER(wally_psbt), c_uint32, c_void_p, c_size_t, c_void_p, c_size_t, POINTER(c_uint32), c_size_t]), ('wally_psbt_add_input_taproot_keypath', c_int, [POINTER(wally_psbt), c_uint32, c_uint32, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, POINTER(c_uint32), c_size_t]), + ('wally_psbt_add_output_keypath', c_int, [POINTER(wally_psbt), c_uint32, c_void_p, c_size_t, c_void_p, c_size_t, POINTER(c_uint32), c_size_t]), ('wally_psbt_add_output_taproot_keypath', c_int, [POINTER(wally_psbt), c_uint32, c_uint32, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, POINTER(c_uint32), c_size_t]), ('wally_psbt_add_tx_input_at', c_int, [POINTER(wally_psbt), c_uint32, c_uint32, POINTER(wally_tx_input)]), ('wally_psbt_add_tx_output_at', c_int, [POINTER(wally_psbt), c_uint32, c_uint32, POINTER(wally_tx_output)]), diff --git a/src/wasm_package/src/functions.js b/src/wasm_package/src/functions.js index a8208757c..ff70e4b0a 100644 --- a/src/wasm_package/src/functions.js +++ b/src/wasm_package/src/functions.js @@ -269,6 +269,7 @@ export const psbt_add_global_scalar = wrap('wally_psbt_add_global_scalar', [T.Op export const psbt_add_input_keypath = wrap('wally_psbt_add_input_keypath', [T.OpaqueRef, T.Int32, T.Bytes, T.Bytes, T.Uint32Array]); export const psbt_add_input_signature = wrap('wally_psbt_add_input_signature', [T.OpaqueRef, T.Int32, T.Bytes, T.Bytes]); export const psbt_add_input_taproot_keypath = wrap('wally_psbt_add_input_taproot_keypath', [T.OpaqueRef, T.Int32, T.Int32, T.Bytes, T.Bytes, T.Bytes, T.Uint32Array]); +export const psbt_add_output_keypath = wrap('wally_psbt_add_output_keypath', [T.OpaqueRef, T.Int32, T.Bytes, T.Bytes, T.Uint32Array]); export const psbt_add_output_taproot_keypath = wrap('wally_psbt_add_output_taproot_keypath', [T.OpaqueRef, T.Int32, T.Int32, T.Bytes, T.Bytes, T.Bytes, T.Uint32Array]); export const psbt_add_tx_input_at = wrap('wally_psbt_add_tx_input_at', [T.OpaqueRef, T.Int32, T.Int32, T.OpaqueRef]); export const psbt_add_tx_output_at = wrap('wally_psbt_add_tx_output_at', [T.OpaqueRef, T.Int32, T.Int32, T.OpaqueRef]); diff --git a/src/wasm_package/src/index.d.ts b/src/wasm_package/src/index.d.ts index 7fb73a962..207ae16f7 100644 --- a/src/wasm_package/src/index.d.ts +++ b/src/wasm_package/src/index.d.ts @@ -229,6 +229,7 @@ export function psbt_add_global_scalar(psbt: Ref_wally_psbt, scalar: Buffer|Uint export function psbt_add_input_keypath(psbt: Ref_wally_psbt, index: number, pub_key: Buffer|Uint8Array, fingerprint: Buffer|Uint8Array, child_path: Uint32Array|number[]): void; export function psbt_add_input_signature(psbt: Ref_wally_psbt, index: number, pub_key: Buffer|Uint8Array, sig: Buffer|Uint8Array): void; export function psbt_add_input_taproot_keypath(psbt: Ref_wally_psbt, index: number, flags: number, pub_key: Buffer|Uint8Array, tapleaf_hashes: Buffer|Uint8Array, fingerprint: Buffer|Uint8Array, child_path: Uint32Array|number[]): void; +export function psbt_add_output_keypath(psbt: Ref_wally_psbt, index: number, pub_key: Buffer|Uint8Array, fingerprint: Buffer|Uint8Array, child_path: Uint32Array|number[]): void; export function psbt_add_output_taproot_keypath(psbt: Ref_wally_psbt, index: number, flags: number, pub_key: Buffer|Uint8Array, tapleaf_hashes: Buffer|Uint8Array, fingerprint: Buffer|Uint8Array, child_path: Uint32Array|number[]): void; export function psbt_add_tx_input_at(psbt: Ref_wally_psbt, index: number, flags: number, input: Ref_wally_tx_input): void; export function psbt_add_tx_output_at(psbt: Ref_wally_psbt, index: number, flags: number, output: Ref_wally_tx_output): void; diff --git a/tools/wasm_exports.sh b/tools/wasm_exports.sh index 1ba54e63f..de52923dd 100644 --- a/tools/wasm_exports.sh +++ b/tools/wasm_exports.sh @@ -191,6 +191,7 @@ EXPORTED_FUNCTIONS="['_malloc','_free','_bip32_key_free' \ ,'_wally_psbt_add_input_keypath' \ ,'_wally_psbt_add_input_signature' \ ,'_wally_psbt_add_input_taproot_keypath' \ +,'_wally_psbt_add_output_keypath' \ ,'_wally_psbt_add_output_taproot_keypath' \ ,'_wally_psbt_add_tx_input_at' \ ,'_wally_psbt_add_tx_output_at' \ From b27dc4029491a7b20b87512c910bc423dc9d7c35 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sat, 8 Feb 2025 22:19:06 +1300 Subject: [PATCH 09/22] tx: update the descriptions of the signature hash generation calls --- include/wally_transaction.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/wally_transaction.h b/include/wally_transaction.h index 42abc7d9f..4b45b77dd 100644 --- a/include/wally_transaction.h +++ b/include/wally_transaction.h @@ -764,7 +764,7 @@ WALLY_CORE_API int wally_tx_get_total_output_satoshi( uint64_t *value_out); /** - * Create a BTC transaction for signing and return its hash. + * Get the hash of the preimage for signing a BTC transaction input. * * :param tx: The transaction to generate the signature hash from. * :param index: The input index of the input being signed for. @@ -790,7 +790,7 @@ WALLY_CORE_API int wally_tx_get_btc_signature_hash( size_t len); /** - * Create a BTC transaction for taproot signing and return its hash. + * Get the hash of the preimage for signing a BTC taproot transaction input. * * :param tx: The transaction to generate the signature hash from. * :param index: The input index of the input being signed for. @@ -826,7 +826,7 @@ WALLY_CORE_API int wally_tx_get_btc_taproot_signature_hash( size_t len); /** - * Create a transaction for signing and return its hash. + * Get the hash of the preimage for signing a BTC transaction input. * * :param tx: The transaction to generate the signature hash from. * :param index: The input index of the input being signed for. @@ -1297,7 +1297,7 @@ WALLY_CORE_API int wally_tx_confidential_value_to_satoshi( uint64_t *value_out); /** - * Create an Elements transaction for signing and return its hash. + * Get the hash of the preimage for signing an Elements transaction input. * * :param tx: The transaction to generate the signature hash from. * :param index: The input index of the input being signed for. From 9b582ef4b77fff4b1f879f3f0e138054bf3cba7d Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sat, 8 Feb 2025 23:46:35 +1300 Subject: [PATCH 10/22] taproot: support generating elements p2tr scripts Elements uses a different tagged hash for tweaking the internal key. --- include/wally_script.h | 2 +- src/script.c | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/include/wally_script.h b/include/wally_script.h index e8805fcb5..d032ef36a 100644 --- a/include/wally_script.h +++ b/include/wally_script.h @@ -301,7 +301,7 @@ WALLY_CORE_API int wally_witness_p2wpkh_from_der( * :param bytes: Compressed or x-only public key to create a scriptPubkey for. * :param bytes_len: The length of ``bytes`` in bytes. Must be ``EC_PUBLIC_KEY_LEN`` *| or ``EC_XONLY_PUBLIC_KEY_LEN``. - * :param flags: Must be 0. + * :param flags: Must be 0 or EC_FLAG_ELEMENTS to create an elements p2tr sctipt. * :param bytes_out: Destination for the resulting scriptPubkey. * MAX_SIZED_OUTPUT(len, bytes_out, WALLY_SCRIPTPUBKEY_P2TR_LEN) * :param written: Destination for the number of bytes written to ``bytes_out``. diff --git a/src/script.c b/src/script.c index 308580f83..7ad6c7b0a 100644 --- a/src/script.c +++ b/src/script.c @@ -1305,8 +1305,13 @@ int wally_scriptpubkey_p2tr_from_bytes(const unsigned char *bytes, size_t bytes_ if (written) *written = 0; - /* FIXME: Support EC_FLAG_ELEMENTS for Elements P2TR */ - if (!bytes || flags || !bytes_out || !written) + if (!bytes || !bytes_out || !written) + return WALLY_EINVAL; +#ifdef BUILD_ELEMENTS + if (flags & ~EC_FLAG_ELEMENTS) +#else + if (flags) +#endif return WALLY_EINVAL; if (len < WALLY_SCRIPTPUBKEY_P2TR_LEN) { @@ -1318,7 +1323,7 @@ int wally_scriptpubkey_p2tr_from_bytes(const unsigned char *bytes, size_t bytes_ if (bytes_len == EC_PUBLIC_KEY_LEN) { /* An untweaked public key, tweak it */ int ret = wally_ec_public_key_bip341_tweak(bytes, bytes_len, NULL, 0, - 0, tweaked, sizeof(tweaked)); + flags, tweaked, sizeof(tweaked)); if (ret != WALLY_OK) return ret; bytes = tweaked + 1; /* Convert to x-only */ From 7864f4beb8c046d8dbc693decdfb25af982f45a4 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sat, 8 Feb 2025 23:54:38 +1300 Subject: [PATCH 11/22] descriptor: support parsing descriptors as elements descriptors Elements core supports standard descriptors but generates (at least for taproot) different scriptpubkeys/addresses since it uses a different hash to tweak its internal keys. Allow specifying that a descriptor is an Elements descriptor when parsing, and use this to perform the correct tweak. Its unclear whether other descriptor expressions are affected since there is no documentation of this behaviour. Elements (only via rust-elements) also supports a renamed set of descriptors and extensions which we do not attempt to support in this change. --- include/wally_descriptor.h | 18 ++++++++++-------- src/ctest/test_descriptor.c | 16 ++++++++++++++-- src/descriptor.c | 34 +++++++++++++++++++++++++++++----- src/test/test_descriptor.py | 16 ++++++++++++++-- src/wasm_package/src/const.js | 18 ++++++++++-------- 5 files changed, 77 insertions(+), 25 deletions(-) diff --git a/include/wally_descriptor.h b/include/wally_descriptor.h index c395ad247..c2d0deab2 100644 --- a/include/wally_descriptor.h +++ b/include/wally_descriptor.h @@ -17,18 +17,20 @@ struct wally_descriptor; #define WALLY_MINISCRIPT_REQUIRE_CHECKSUM 0x04 /** Require a checksum to be present */ #define WALLY_MINISCRIPT_POLICY_TEMPLATE 0x08 /** Only allow policy templates with @n BIP32 keys */ #define WALLY_MINISCRIPT_UNIQUE_KEYPATHS 0x10 /** For policy templates, ensure BIP32 derivation paths differ for identical keys */ +#define WALLY_MINISCRIPT_AS_ELEMENTS 0x20 /** Treat non-elements expressions as elements, e.g. tr() as eltr() */ #define WALLY_MINISCRIPT_DEPTH_MASK 0xffff0000 /** Mask for limiting maximum depth */ #define WALLY_MINISCRIPT_DEPTH_SHIFT 16 /** Shift to convert maximum depth to flags */ /*** miniscript-features Miniscript/Descriptor feature flags */ -#define WALLY_MS_IS_RANGED 0x01 /** Allows key ranges via ``*`` */ -#define WALLY_MS_IS_MULTIPATH 0x02 /** Allows multiple paths via ```` */ -#define WALLY_MS_IS_PRIVATE 0x04 /** Contains at least one private key */ -#define WALLY_MS_IS_UNCOMPRESSED 0x08 /** Contains at least one uncompressed key */ -#define WALLY_MS_IS_RAW 0x10 /** Contains at least one raw key */ -#define WALLY_MS_IS_DESCRIPTOR 0x20 /** Contains only descriptor expressions (no miniscript) */ -#define WALLY_MS_IS_X_ONLY 0x40 /** Contains at least one x-only key */ -#define WALLY_MS_IS_PARENTED 0x80 /** Contains at least one key key with a parent key origin */ +#define WALLY_MS_IS_RANGED 0x001 /** Allows key ranges via ``*`` */ +#define WALLY_MS_IS_MULTIPATH 0x002 /** Allows multiple paths via ```` */ +#define WALLY_MS_IS_PRIVATE 0x004 /** Contains at least one private key */ +#define WALLY_MS_IS_UNCOMPRESSED 0x008 /** Contains at least one uncompressed key */ +#define WALLY_MS_IS_RAW 0x010 /** Contains at least one raw key */ +#define WALLY_MS_IS_DESCRIPTOR 0x020 /** Contains only descriptor expressions (no miniscript) */ +#define WALLY_MS_IS_X_ONLY 0x040 /** Contains at least one x-only key */ +#define WALLY_MS_IS_PARENTED 0x080 /** Contains at least one key key with a parent key origin */ +#define WALLY_MS_IS_ELEMENTS 0x100 /** Contains Elements expressions or was parsed as Elements */ /*** ms-canonicalization-flags Miniscript/Descriptor canonicalization flags */ #define WALLY_MS_CANONICAL_NO_CHECKSUM 0x01 /** Do not include a checksum */ diff --git a/src/ctest/test_descriptor.c b/src/ctest/test_descriptor.c index efffe8c74..ccc966b03 100644 --- a/src/ctest/test_descriptor.c +++ b/src/ctest/test_descriptor.c @@ -43,7 +43,9 @@ static struct wally_map_item g_key_map_items[] = { { B("mainnet_xpriv"), B("xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU") }, { B("uncompressed"), B("0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf") }, { B("x_only"), B("b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e") }, - { B("non_x_only"), B("03b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e") } + { B("non_x_only"), B("03b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e") }, + /* The taproot singlesig xpriv corresponding to Jades test_jade.py test script */ + { B("jade_ss_tr_xpriv"), B("tprv8gTfWnFCND72oJZfZTokBBXcS1FzQhrtd5wNFu3FgBE76yErH49cev2Zn3Wws3o6ZwKZVZaQP1UWKVNotpPg8U6tCgGrjMfaRQJvV1Vdbi7") } }; static const struct wally_map g_key_map = { @@ -395,7 +397,17 @@ static const struct descriptor_test { WALLY_NETWORK_BITCOIN_REGTEST, 0, 0, 0, NULL, 0, "51205fb8e39dbbdc7c831af59e44a9b2997f9daaf72c3e965b30982f3c731539e1db", "tp2ky708" - },{ + }, +#ifdef BUILD_ELEMENTS + { + "descriptor - Elements tr", + "tr([59d1f3b0/86h/1h/0h]jade_ss_tr_xpriv/0/*)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_AS_ELEMENTS, + "5120900d1d75269396d4220c4529527dbcb746a6093c7209cea2d76a87c8ab9447fc", + "3d4maj53" + }, +#endif + { "descriptor - A single key", "wsh(c:pk_k(key_1))", WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, diff --git a/src/descriptor.c b/src/descriptor.c index d4a36242e..1756653b5 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -18,7 +18,8 @@ WALLY_MINISCRIPT_ONLY | \ WALLY_MINISCRIPT_REQUIRE_CHECKSUM | \ WALLY_MINISCRIPT_POLICY_TEMPLATE | \ - WALLY_MINISCRIPT_UNIQUE_KEYPATHS) + WALLY_MINISCRIPT_UNIQUE_KEYPATHS | \ + WALLY_MINISCRIPT_AS_ELEMENTS) #define MS_FLAGS_CANONICALIZE (WALLY_MINISCRIPT_REQUIRE_CHECKSUM | \ WALLY_MINISCRIPT_POLICY_TEMPLATE) @@ -177,8 +178,8 @@ typedef struct ms_node_t { uint32_t data_len; uint32_t child_path_len; char wrapper_str[12]; + unsigned short flags; /* WALLY_MS_IS_ flags */ unsigned char builtin; - unsigned char flags; /* WALLY_MS_IS_ flags */ } ms_node; typedef struct wally_descriptor { @@ -1293,7 +1294,7 @@ static int generate_sh_wpkh(ms_ctx *ctx, ms_node *node, ms_node sh_node = { NULL, node, NULL, KIND_DESCRIPTOR_SH, TYPE_NONE, 0, NULL, NULL, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - builtin_sh_index, 0 }; + 0, builtin_sh_index }; if (ctx->variant != 3) return WALLY_ERROR; /* Should only be called to generate sh-wpkh */ @@ -1423,6 +1424,7 @@ static int generate_tr(ms_ctx *ctx, ms_node *node, unsigned char tweaked[EC_PUBLIC_KEY_LEN]; unsigned char pubkey[EC_PUBLIC_KEY_UNCOMPRESSED_LEN + 1]; size_t pubkey_len = 0; + uint32_t tweak_flags = 0; int ret; /* Generate a push of the x-only public key of our child */ @@ -1432,9 +1434,13 @@ static int generate_tr(ms_ctx *ctx, ms_node *node, return WALLY_EINVAL; /* Should be PUSH_32 [x-only pubkey] */ /* Tweak it into a compressed pubkey */ +#ifdef BUILD_ELEMENTS + if (node->flags & WALLY_MS_IS_ELEMENTS) + tweak_flags = EC_FLAG_ELEMENTS; +#endif ret = wally_ec_public_key_bip341_tweak(pubkey + 1, pubkey_len - 1, - NULL, 0, 0, /* FIXME: Support script path */ - tweaked, sizeof(tweaked)); + NULL, 0, /* FIXME: Support script path */ + tweak_flags, tweaked, sizeof(tweaked)); if (ret == WALLY_OK && script_len >= WALLY_SCRIPTPUBKEY_P2TR_LEN) { /* Generate the script using the x-only part of the tweaked key */ @@ -2378,6 +2384,11 @@ static int analyze_miniscript(ms_ctx *ctx, const char *str, size_t str_len, return WALLY_ENOMEM; node->parent = parent; +#ifdef BUILD_ELEMENTS + if (ctx->features & WALLY_MS_IS_ELEMENTS) { + node->flags |= WALLY_MS_IS_ELEMENTS; /* Treat this node as an elements node */ + } +#endif for (i = 0; i < str_len; ++i) { if (!node->builtin && str[i] == ':') { @@ -2745,6 +2756,11 @@ int wally_descriptor_parse(const char *miniscript, (network != WALLY_NETWORK_NONE && !addr_ver)) return WALLY_EINVAL; +#ifndef BUILD_ELEMENTS + if (flags & WALLY_MINISCRIPT_AS_ELEMENTS) { + return WALLY_EINVAL; + } +#endif /* Allocate a context to hold the canonicalized/parsed expression */ if (!(*output = wally_calloc(sizeof(ms_ctx)))) return WALLY_ENOMEM; @@ -2759,6 +2775,9 @@ int wally_descriptor_parse(const char *miniscript, if (ret == WALLY_OK) { ctx->src_len = strlen(ctx->src); ctx->features = WALLY_MS_IS_DESCRIPTOR; /* Un-set if miniscript found */ + if (flags & WALLY_MINISCRIPT_AS_ELEMENTS) { + ctx->features |= WALLY_MS_IS_ELEMENTS; /* Treat as an elements descriptor */ + } if (max_depth && get_max_depth(ctx->src, ctx->src_len) > max_depth) ret = WALLY_EINVAL; @@ -2830,6 +2849,11 @@ int wally_descriptor_to_script_get_maximum_length( *written = 0; if (!descriptor || (flags & ~MS_FLAGS_ALL) || !written) return WALLY_EINVAL; +#ifndef BUILD_ELEMENTS + if (flags & WALLY_MINISCRIPT_AS_ELEMENTS) { + return WALLY_EINVAL; + } +#endif *written = descriptor->script_len; return WALLY_OK; } diff --git a/src/test/test_descriptor.py b/src/test/test_descriptor.py index b49e87f3f..e9f5b0bdb 100644 --- a/src/test/test_descriptor.py +++ b/src/test/test_descriptor.py @@ -15,6 +15,7 @@ REQUIRE_CHECKSUM = 0x4 # WALLY_MINISCRIPT_REQUIRE_CHECKSUM POLICY = 0x08 # WALLY_MINISCRIPT_POLICY_TEMPLATE UNIQUE_KEYPATHS = 0x10 # WALLY_MINISCRIPT_UNIQUE_KEYPATHS +AS_ELEMENTS = 0x20 # WALLY_MINISCRIPT_AS_ELEMENTS MS_IS_RANGED = 0x1 MS_IS_MULTIPATH = 0x2 @@ -24,6 +25,7 @@ MS_IS_DESCRIPTOR = 0x20 MS_IS_X_ONLY = 0x40 MS_IS_PARENTED = 0x80 +MS_IS_ELEMENTS = 0x100 NO_CHECKSUM = 0x1 # WALLY_MS_CANONICAL_NO_CHECKSUM @@ -228,10 +230,12 @@ def test_canonicalize_checksum_bad_args(self): def test_features_and_depth(self): """Test descriptor feature detection and depth""" + _, is_elements_build = wally_is_elements_build() + k1 = 'xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB' k2 = 'xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU' # Valid args - for descriptor, flags, expected, expected_depth in [ + cases = [ # Bip32 xpub (f'pkh({k1})', 0, MS_IS_DESCRIPTOR, 2), @@ -258,7 +262,15 @@ def test_features_and_depth(self): 0, MS_IS_PRIVATE, 5), (f'or_d(thresh(1,pk({k1})),and_v(v:thresh(1,pk({k2}/)),older(30)))', MS_ONLY, MS_IS_PRIVATE, 5), - ]: + ] + if is_elements_build: + cases.extend([ + # Parsing a descriptor as elements returns elements in its features + (f'tr({k1})', + AS_ELEMENTS, MS_IS_DESCRIPTOR|MS_IS_ELEMENTS, 2), + ]) + + for descriptor, flags, expected, expected_depth in cases: d = c_void_p() ret = wally_descriptor_parse(descriptor, None, NETWORK_NONE, flags, d) ret, features = wally_descriptor_get_features(d) diff --git a/src/wasm_package/src/const.js b/src/wasm_package/src/const.js index 086de3cc6..d2b9d8c07 100755 --- a/src/wasm_package/src/const.js +++ b/src/wasm_package/src/const.js @@ -120,6 +120,7 @@ export const WALLY_ERROR = -1; /** General error */ export const WALLY_HOST_COMMITMENT_LEN = 32; 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() */ export const WALLY_MINISCRIPT_DEPTH_MASK = 0xffff0000; /** Mask for limiting maximum depth */ export const WALLY_MINISCRIPT_DEPTH_SHIFT = 16; /** Shift to convert maximum depth to flags */ export const WALLY_MINISCRIPT_ONLY = 0x02; /** Only allow miniscript (not descriptor) expressions */ @@ -129,14 +130,15 @@ export const WALLY_MINISCRIPT_TAPSCRIPT = 0x01; /** Tapscript, use x-only pubkey export const WALLY_MINISCRIPT_UNIQUE_KEYPATHS = 0x10; /** For policy templates, ensure BIP32 derivation paths differ for identical keys */ export const WALLY_MINOR_VER = 3; export const WALLY_MS_CANONICAL_NO_CHECKSUM = 0x01; /** Do not include a checksum */ -export const WALLY_MS_IS_DESCRIPTOR = 0x20; /** Contains only descriptor expressions (no miniscript) */ -export const WALLY_MS_IS_MULTIPATH = 0x02; /** Allows multiple paths via ```` */ -export const WALLY_MS_IS_PARENTED = 0x80; /** Contains at least one key key with a parent key origin */ -export const WALLY_MS_IS_PRIVATE = 0x04; /** Contains at least one private key */ -export const WALLY_MS_IS_RANGED = 0x01; /** Allows key ranges via ``*`` */ -export const WALLY_MS_IS_RAW = 0x10; /** Contains at least one raw key */ -export const WALLY_MS_IS_UNCOMPRESSED = 0x08; /** Contains at least one uncompressed key */ -export const WALLY_MS_IS_X_ONLY = 0x40; /** Contains at least one x-only key */ +export const WALLY_MS_IS_DESCRIPTOR = 0x020; /** Contains only descriptor expressions (no miniscript) */ +export const WALLY_MS_IS_ELEMENTS = 0x100; /** Contains Elements expressions or was parsed as Elements */ +export const WALLY_MS_IS_MULTIPATH = 0x002; /** Allows multiple paths via ```` */ +export const WALLY_MS_IS_PARENTED = 0x080; /** Contains at least one key key with a parent key origin */ +export const WALLY_MS_IS_PRIVATE = 0x004; /** Contains at least one private key */ +export const WALLY_MS_IS_RANGED = 0x001; /** Allows key ranges via ``*`` */ +export const WALLY_MS_IS_RAW = 0x010; /** Contains at least one raw key */ +export const WALLY_MS_IS_UNCOMPRESSED = 0x008; /** Contains at least one uncompressed key */ +export const WALLY_MS_IS_X_ONLY = 0x040; /** Contains at least one x-only key */ export const WALLY_NETWORK_BITCOIN_MAINNET = 0x01; /** Bitcoin mainnet */ export const WALLY_NETWORK_BITCOIN_REGTEST = 0xff ; /** Bitcoin regtest: Behaves as testnet except for segwit */ export const WALLY_NETWORK_BITCOIN_TESTNET = 0x02; /** Bitcoin testnet */ From add3fdd6a16aa141964d7866dd110e195e74f396 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sun, 9 Feb 2025 01:03:04 +1300 Subject: [PATCH 12/22] bip32: add calls to compute the length of a path string --- include/wally.hpp | 16 +++++++++++++-- include/wally_bip32.h | 33 ++++++++++++++++++++++++++++-- src/bip32.c | 24 +++++++++++++++++++--- src/swig_java/swig.i | 2 ++ src/swig_python/python_extra.py_in | 2 ++ src/test/util.py | 6 ++++-- src/wasm_package/src/functions.js | 6 ++++-- src/wasm_package/src/index.d.ts | 6 ++++-- tools/wasm_exports.sh | 2 ++ 9 files changed, 84 insertions(+), 13 deletions(-) diff --git a/include/wally.hpp b/include/wally.hpp index c4ee08fc6..2b8e8cd97 100644 --- a/include/wally.hpp +++ b/include/wally.hpp @@ -202,17 +202,29 @@ inline int bip32_key_unserialize_alloc(const BYTES& bytes, struct ext_key** outp } template -inline int bip32_path_from_str(const PATH_STR& path_str, uint32_t child_num, uint32_t multi_index, uint32_t flags, uint32_t* child_path_out, uint32_t child_path_out_len, size_t* written) { +inline int bip32_path_from_str(const PATH_STR& path_str, uint32_t child_num, uint32_t multi_index, uint32_t flags, uint32_t* child_path_out, size_t child_path_out_len, size_t* written) { int ret = ::bip32_path_from_str(detail::get_p(path_str), child_num, multi_index, flags, child_path_out, child_path_out_len, written); return detail::check_ret(__FUNCTION__, ret); } template -inline int bip32_path_from_str_n(const PATH_STR& path_str, size_t path_str_len, uint32_t child_num, uint32_t multi_index, uint32_t flags, uint32_t* child_path_out, uint32_t child_path_out_len, size_t* written) { +inline int bip32_path_from_str_len(const PATH_STR& path_str, uint32_t child_num, uint32_t multi_index, uint32_t flags, size_t* written) { + int ret = ::bip32_path_from_str_len(detail::get_p(path_str), child_num, multi_index, flags, written); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int bip32_path_from_str_n(const PATH_STR& path_str, size_t path_str_len, uint32_t child_num, uint32_t multi_index, uint32_t flags, uint32_t* child_path_out, size_t child_path_out_len, size_t* written) { int ret = ::bip32_path_from_str_n(detail::get_p(path_str), path_str_len, child_num, multi_index, flags, child_path_out, child_path_out_len, written); return detail::check_ret(__FUNCTION__, ret); } +template +inline int bip32_path_from_str_n_len(const PATH_STR& path_str, size_t path_str_len, uint32_t child_num, uint32_t multi_index, uint32_t flags, size_t* written) { + int ret = ::bip32_path_from_str_n_len(detail::get_p(path_str), path_str_len, child_num, multi_index, flags, written); + return detail::check_ret(__FUNCTION__, ret); +} + template inline int bip32_path_str_get_features(const PATH_STR& path_str, uint32_t* value_out) { int ret = ::bip32_path_str_get_features(detail::get_p(path_str), value_out); diff --git a/include/wally_bip32.h b/include/wally_bip32.h index 703b64350..7800b2151 100644 --- a/include/wally_bip32.h +++ b/include/wally_bip32.h @@ -465,6 +465,22 @@ WALLY_CORE_API int bip32_key_get_fingerprint( unsigned char *bytes_out, size_t len); +/** + * Get the number of child path elements in a BIP32 path string. + * + * :param path_str: The BIP32 path string of child numbers to convert from. + * :param child_num: The child number to use if ``path_str`` contains a ``*`` wildcard. + * :param multi_index: The multi-path item to use if ``path_str`` contains a ``<>`` multi-path. + * :param flags: :ref:`bip32-flags` controlling path parsing behaviour. + * :param written: Destination for the number of path elements in the path string. + */ +WALLY_CORE_API int bip32_path_from_str_len( + const char *path_str, + uint32_t child_num, + uint32_t multi_index, + uint32_t flags, + size_t *written); + /** * Convert a BIP32 path string to a path. * @@ -482,7 +498,20 @@ WALLY_CORE_API int bip32_path_from_str( uint32_t multi_index, uint32_t flags, uint32_t *child_path_out, - uint32_t child_path_out_len, + size_t child_path_out_len, + size_t *written); + +/** + * Get the number of child path elements in a known-length BIP32 path string. + * + * See `bip32_path_from_str_len`. + */ +WALLY_CORE_API int bip32_path_from_str_n_len( + const char *path_str, + size_t path_str_len, + uint32_t child_num, + uint32_t multi_index, + uint32_t flags, size_t *written); /** @@ -497,7 +526,7 @@ WALLY_CORE_API int bip32_path_from_str_n( uint32_t multi_index, uint32_t flags, uint32_t *child_path_out, - uint32_t child_path_out_len, + size_t child_path_out_len, size_t *written); /** diff --git a/src/bip32.c b/src/bip32.c index 635c282f1..dfc907ecb 100644 --- a/src/bip32.c +++ b/src/bip32.c @@ -89,7 +89,7 @@ static bool is_hardened_indicator(char c, bool allow_upper, uint32_t *features) static int path_from_str_n(const char *str, size_t str_len, uint32_t child_num, uint32_t multi_index, uint32_t *features, uint32_t flags, - uint32_t *child_path, uint32_t child_path_len, + uint32_t *child_path, size_t child_path_len, size_t *written) { const bool allow_upper = flags & BIP32_FLAG_ALLOW_UPPER; @@ -242,7 +242,7 @@ static int path_from_str_n(const char *str, size_t str_len, int bip32_path_from_str_n(const char *str, size_t str_len, uint32_t child_num, uint32_t multi_index, uint32_t flags, - uint32_t *child_path, uint32_t child_path_len, + uint32_t *child_path, size_t child_path_len, size_t *written) { uint32_t features; @@ -250,9 +250,19 @@ int bip32_path_from_str_n(const char *str, size_t str_len, flags, child_path, child_path_len, written); } +int bip32_path_from_str_n_len(const char *str, size_t str_len, + uint32_t child_num, uint32_t multi_index, + uint32_t flags, + size_t *written) +{ + uint32_t child_path; + return bip32_path_from_str_n(str, str_len, child_num, multi_index, + flags, &child_path, 1, written); +} + int bip32_path_from_str(const char *str, uint32_t child_num, uint32_t multi_index, uint32_t flags, - uint32_t *child_path, uint32_t child_path_len, + uint32_t *child_path, size_t child_path_len, size_t *written) { uint32_t features; @@ -261,6 +271,14 @@ int bip32_path_from_str(const char *str, uint32_t child_num, child_path, child_path_len, written); } +int bip32_path_from_str_len(const char *str, uint32_t child_num, + uint32_t multi_index, uint32_t flags, + size_t *written) +{ + return bip32_path_from_str_n_len(str, str ? strlen(str) : 0, child_num, + multi_index, flags, written); +} + int bip32_path_str_n_get_features(const char *str, size_t str_len, uint32_t *value_out) { diff --git a/src/swig_java/swig.i b/src/swig_java/swig.i index dab59d43d..f3c3976ab 100644 --- a/src/swig_java/swig.i +++ b/src/swig_java/swig.i @@ -499,6 +499,8 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %rename("bip32_key_unserialize") bip32_key_unserialize_alloc; %returns_struct(bip32_key_with_tweak_from_parent_path_alloc, ext_key); %rename("bip32_key_with_tweak_from_parent_path") bip32_key_with_tweak_from_parent_path_alloc; +%returns_size_t(bip32_path_from_str_len); +%returns_size_t(bip32_path_from_str_n_len); %returns_array_(bip38_raw_from_private_key, 6, 7, BIP38_SERIALIZED_LEN); %returns_string(bip38_from_private_key); %returns_array_(bip38_raw_to_private_key, 6, 7, 32); diff --git a/src/swig_python/python_extra.py_in b/src/swig_python/python_extra.py_in index a8cf554b7..8fbd92eb7 100644 --- a/src/swig_python/python_extra.py_in +++ b/src/swig_python/python_extra.py_in @@ -138,6 +138,8 @@ bip32_key_get_pub_key = _wrap_bin(bip32_key_get_pub_key, EC_PUBLIC_KEY_LEN) bip32_key_init = bip32_key_init_alloc bip32_key_serialize = _wrap_bin(bip32_key_serialize, BIP32_SERIALIZED_LEN) bip32_key_unserialize = bip32_key_unserialize_alloc +bip32_path_from_str = _wrap_int_array(bip32_path_from_str, bip32_path_from_str_len) +bip32_path_from_str_n = _wrap_int_array(bip32_path_from_str_n, bip32_path_from_str_n_len) bip340_tagged_hash = _wrap_bin(bip340_tagged_hash, SHA256_LEN) bip38_raw_from_private_key = _wrap_bin(bip38_raw_from_private_key, BIP38_SERIALIZED_LEN) bip38_raw_to_private_key = _wrap_bin(bip38_raw_to_private_key, EC_PRIVATE_KEY_LEN) diff --git a/src/test/util.py b/src/test/util.py index 8d76b1ebe..a741b9668 100755 --- a/src/test/util.py +++ b/src/test/util.py @@ -235,8 +235,10 @@ class wally_psbt(Structure): ('bip32_key_unserialize_alloc', c_int, [c_void_p, c_size_t, POINTER(POINTER(ext_key))]), ('bip32_key_with_tweak_from_parent_path', c_int, [POINTER(ext_key), POINTER(c_uint32), c_size_t, c_uint32, POINTER(ext_key)]), ('bip32_key_with_tweak_from_parent_path_alloc', c_int, [POINTER(ext_key), POINTER(c_uint32), c_size_t, c_uint32, POINTER(POINTER(ext_key))]), - ('bip32_path_from_str', c_int, [c_char_p, c_uint32, c_uint32, c_uint32, POINTER(c_uint32), c_uint32, c_size_t_p]), - ('bip32_path_from_str_n', c_int, [c_char_p, c_size_t, c_uint32, c_uint32, c_uint32, POINTER(c_uint32), c_uint32, c_size_t_p]), + ('bip32_path_from_str', c_int, [c_char_p, c_uint32, c_uint32, c_uint32, POINTER(c_uint32), c_size_t, c_size_t_p]), + ('bip32_path_from_str_len', c_int, [c_char_p, c_uint32, c_uint32, c_uint32, c_size_t_p]), + ('bip32_path_from_str_n', c_int, [c_char_p, c_size_t, c_uint32, c_uint32, c_uint32, POINTER(c_uint32), c_size_t, c_size_t_p]), + ('bip32_path_from_str_n_len', c_int, [c_char_p, c_size_t, c_uint32, c_uint32, c_uint32, c_size_t_p]), ('bip32_path_str_get_features', c_int, [c_char_p, c_uint32_p]), ('bip32_path_str_n_get_features', c_int, [c_char_p, c_size_t, c_uint32_p]), ('bip38_from_private_key', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_char_p_p]), diff --git a/src/wasm_package/src/functions.js b/src/wasm_package/src/functions.js index ff70e4b0a..73bcc1c96 100644 --- a/src/wasm_package/src/functions.js +++ b/src/wasm_package/src/functions.js @@ -135,8 +135,8 @@ export const bip32_key_unserialize = wrap('bip32_key_unserialize_alloc', [T.Byte export const bip32_key_unserialize_noalloc = wrap('bip32_key_unserialize', [T.Bytes, T.OpaqueRef]); export const bip32_key_with_tweak_from_parent_path = wrap('bip32_key_with_tweak_from_parent_path_alloc', [T.OpaqueRef, T.Uint32Array, T.Int32, T.DestPtrPtr(T.OpaqueRef)]); export const bip32_key_with_tweak_from_parent_path_noalloc = wrap('bip32_key_with_tweak_from_parent_path', [T.OpaqueRef, T.Uint32Array, T.Int32, T.OpaqueRef]); -export const bip32_path_from_str = wrap('bip32_path_from_str', [T.String, T.Int32, T.Int32, T.Int32, T.DestPtr(T.Int32), T.Int32, T.DestPtr(T.Int32)]); -export const bip32_path_from_str_n = wrap('bip32_path_from_str_n', [T.String, T.Int32, T.Int32, T.Int32, T.Int32, T.DestPtr(T.Int32), T.Int32, T.DestPtr(T.Int32)]); +export const bip32_path_from_str_len = wrap('bip32_path_from_str_len', [T.String, T.Int32, T.Int32, T.Int32, T.DestPtr(T.Int32)]); +export const bip32_path_from_str_n_len = wrap('bip32_path_from_str_n_len', [T.String, T.Int32, T.Int32, T.Int32, T.Int32, T.DestPtr(T.Int32)]); export const bip32_path_str_get_features = wrap('bip32_path_str_get_features', [T.String, T.DestPtr(T.Int32)]); export const bip32_path_str_n_get_features = wrap('bip32_path_str_n_get_features', [T.String, T.Int32, T.DestPtr(T.Int32)]); export const bip340_tagged_hash = wrap('wally_bip340_tagged_hash', [T.Bytes, T.String, T.DestPtrSized(T.Bytes, C.SHA256_LEN)]); @@ -793,6 +793,8 @@ export const base58_n_to_bytes = wrap('wally_base58_n_to_bytes', [T.String, T.In export const base58_to_bytes = wrap('wally_base58_to_bytes', [T.String, T.Int32, T.DestPtrVarLen(T.Bytes, base58_to_bytes_len, true)]); export const base64_n_to_bytes = wrap('wally_base64_n_to_bytes', [T.String, T.Int32, T.Int32, T.DestPtrVarLen(T.Bytes, base64_n_get_maximum_length, true)]); export const base64_to_bytes = wrap('wally_base64_to_bytes', [T.String, T.Int32, T.DestPtrVarLen(T.Bytes, base64_get_maximum_length, true)]); +export const bip32_path_from_str = wrap('bip32_path_from_str', [T.String, T.Int32, T.Int32, T.Int32, T.DestPtrVarLen(T.Uint32Array, bip32_path_from_str_len, false)]); +export const bip32_path_from_str_n = wrap('bip32_path_from_str_n', [T.String, T.Int32, T.Int32, T.Int32, T.Int32, T.DestPtrVarLen(T.Uint32Array, bip32_path_from_str_n_len, false)]); export const descriptor_get_key_child_path_str = wrap('wally_descriptor_get_key_child_path_str', [T.OpaqueRef, T.Int32, T.DestPtrPtr(T.String)]); export const descriptor_get_key_origin_path_str = wrap('wally_descriptor_get_key_origin_path_str', [T.OpaqueRef, T.Int32, T.DestPtrPtr(T.String)]); export const descriptor_to_script = wrap('wally_descriptor_to_script', [T.OpaqueRef, T.Int32, T.Int32, T.Int32, T.Int32, T.Int32, T.Int32, T.DestPtrVarLen(T.Bytes, descriptor_to_script_get_maximum_length, true)]); diff --git a/src/wasm_package/src/index.d.ts b/src/wasm_package/src/index.d.ts index 207ae16f7..65645b67c 100644 --- a/src/wasm_package/src/index.d.ts +++ b/src/wasm_package/src/index.d.ts @@ -95,8 +95,8 @@ export function bip32_key_unserialize(bytes: Buffer|Uint8Array): Ref_ext_key; export function bip32_key_unserialize_noalloc(bytes: Buffer|Uint8Array, output: Ref_ext_key): void; export function bip32_key_with_tweak_from_parent_path(hdkey: Ref_ext_key, child_path: Uint32Array|number[], flags: number): Ref_ext_key; export function bip32_key_with_tweak_from_parent_path_noalloc(hdkey: Ref_ext_key, child_path: Uint32Array|number[], flags: number, output: Ref_ext_key): void; -export function bip32_path_from_str(path_str: string, child_num: number, multi_index: number, flags: number, child_path_out_len: number): [child_path_out: number, written: number]; -export function bip32_path_from_str_n(path_str: string, path_str_len: number, child_num: number, multi_index: number, flags: number, child_path_out_len: number): [child_path_out: number, written: number]; +export function bip32_path_from_str_len(path_str: string, child_num: number, multi_index: number, flags: number): number; +export function bip32_path_from_str_n_len(path_str: string, path_str_len: number, child_num: number, multi_index: number, flags: number): number; export function bip32_path_str_get_features(path_str: string): number; export function bip32_path_str_n_get_features(path_str: string, path_str_len: number): number; export function bip340_tagged_hash(bytes: Buffer|Uint8Array, tag: string): Buffer; @@ -753,6 +753,8 @@ export function base58_n_to_bytes(str_in: string, str_len: number, flags: number export function base58_to_bytes(str_in: string, flags: number): Buffer; export function base64_n_to_bytes(str_in: string, str_len: number, flags: number): Buffer; export function base64_to_bytes(str_in: string, flags: number): Buffer; +export function bip32_path_from_str(path_str: string, child_num: number, multi_index: number, flags: number): Uint32Array; +export function bip32_path_from_str_n(path_str: string, path_str_len: number, child_num: number, multi_index: number, flags: number): Uint32Array; export function descriptor_get_key_child_path_str(descriptor: Ref_wally_descriptor, index: number): string; export function descriptor_get_key_origin_path_str(descriptor: Ref_wally_descriptor, index: number): string; export function descriptor_to_script(descriptor: Ref_wally_descriptor, depth: number, index: number, variant: number, multi_index: number, child_num: number, flags: number): Buffer; diff --git a/tools/wasm_exports.sh b/tools/wasm_exports.sh index de52923dd..c38a7055f 100644 --- a/tools/wasm_exports.sh +++ b/tools/wasm_exports.sh @@ -37,7 +37,9 @@ EXPORTED_FUNCTIONS="['_malloc','_free','_bip32_key_free' \ ,'_bip32_key_unserialize' \ ,'_bip32_key_unserialize_alloc' \ ,'_bip32_path_from_str' \ +,'_bip32_path_from_str_len' \ ,'_bip32_path_from_str_n' \ +,'_bip32_path_from_str_n_len' \ ,'_bip32_path_str_get_features' \ ,'_bip32_path_str_n_get_features' \ ,'_bip38_get_flags' \ From d2ee9747fe22be41e076982cbbc5ec0504cbc4dd Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Mon, 10 Feb 2025 11:52:14 +1300 Subject: [PATCH 13/22] docs: fix some incorrect psbt parameter documentation --- include/wally_psbt.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/include/wally_psbt.h b/include/wally_psbt.h index 7808ad513..4440b777c 100644 --- a/include/wally_psbt.h +++ b/include/wally_psbt.h @@ -753,8 +753,7 @@ WALLY_CORE_API int wally_psbt_input_get_pegin_genesis_blockhash_len( * * :param input: The input to update. * :param genesis_blockhash: The peg-in genesis blockhash. - * :param genesis_blockhash_len: Size of ``genesis_blockhash`` in bytes. Must - *| be `WALLY_TXHASH_LEN`. + * :param genesis_blockhash_len: Size of ``genesis_blockhash`` in bytes. Must be `SHA256_LEN`. */ WALLY_CORE_API int wally_psbt_input_set_pegin_genesis_blockhash( struct wally_psbt_input *input, @@ -2247,7 +2246,7 @@ WALLY_CORE_API int wally_psbt_get_input_signing_script_len( * :param psbt: The PSBT containing the input to get from. * :param index: The zero-based index of the input to get the script from. * :param bytes_out: Destination for the scriptPubKey or redeem script. - * :param len: Length of ``bytes`` in bytes. + * :param len: Length of ``bytes_out`` in bytes. * :param written: Destination for the number of bytes written to bytes_out. */ WALLY_CORE_API int wally_psbt_get_input_signing_script( @@ -2281,7 +2280,7 @@ WALLY_CORE_API int wally_psbt_get_input_scriptcode_len( * :param script: scriptPubKey/redeem script from `wally_psbt_get_input_signing_script`. * :param script_len: Length of ``script`` in bytes. * :param bytes_out: Destination for the scriptCode. - * :param len: Length of ``bytes`` in bytes. + * :param len: Length of ``bytes_out`` in bytes. * :param written: Destination for the number of bytes written to bytes_out. */ WALLY_CORE_API int wally_psbt_get_input_scriptcode( @@ -2421,7 +2420,7 @@ WALLY_CORE_API int wally_psbt_get_length( * :param psbt: the PSBT to serialize. * :param flags: Flags controlling serialization. Must be 0. * :param bytes_out: Destination for the serialized PSBT. - * :param len: Length of ``bytes`` in bytes (use `wally_psbt_get_length`). + * :param len: Length of ``bytes_out`` in bytes (use `wally_psbt_get_length`). * :param written: number of bytes written to bytes_out. */ WALLY_CORE_API int wally_psbt_to_bytes( From 7d2b2cdc2132015b37aadc41b140c5537aceeb6a Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Mon, 10 Feb 2025 14:22:36 +1300 Subject: [PATCH 14/22] docs: set language to english Avoids a warning when building. --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 3e92aedbf..c086bbeeb 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -177,7 +177,7 @@ def extract_docs(infile, outfile): # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. From 107ffae0f2f581de5b0f701f4a7e8a117fcbecf6 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Mon, 10 Feb 2025 14:23:04 +1300 Subject: [PATCH 15/22] pset: add support for elip-101 global genesis blockhash --- include/wally.hpp | 18 +++++++++++ include/wally_psbt.h | 40 +++++++++++++++++++++++ src/psbt.c | 52 ++++++++++++++++++++++++++++++ src/psbt_io.h | 6 ++-- src/swig_java/swig.i | 3 ++ src/swig_python/python_extra.py_in | 1 + src/test/util.py | 3 ++ src/wasm_package/src/functions.js | 3 ++ src/wasm_package/src/index.d.ts | 3 ++ tools/wasm_exports.sh | 3 ++ 10 files changed, 130 insertions(+), 2 deletions(-) diff --git a/include/wally.hpp b/include/wally.hpp index 2b8e8cd97..0e845b137 100644 --- a/include/wally.hpp +++ b/include/wally.hpp @@ -2439,6 +2439,18 @@ inline int psbt_find_global_scalar(const PSBT& psbt, const SCALAR& scalar, size_ return detail::check_ret(__FUNCTION__, ret); } +template +inline int psbt_get_global_genesis_blockhash(const PSBT& psbt, BYTES_OUT& bytes_out, size_t* written) { + int ret = ::wally_psbt_get_global_genesis_blockhash(detail::get_p(psbt), bytes_out.data(), bytes_out.size(), written); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int psbt_has_global_genesis_blockhash(const PSBT& psbt, size_t* written) { + int ret = ::wally_psbt_has_global_genesis_blockhash(detail::get_p(psbt), written); + return detail::check_ret(__FUNCTION__, ret); +} + inline int psbt_input_clear_amount_rangeproof(struct wally_psbt_input* input) { int ret = ::wally_psbt_input_clear_amount_rangeproof(input); return detail::check_ret(__FUNCTION__, ret); @@ -3050,6 +3062,12 @@ inline int psbt_output_set_value_rangeproof(const OUTPUT& output, const RANGEPRO return detail::check_ret(__FUNCTION__, ret); } +template +inline int psbt_set_global_genesis_blockhash(const PSBT& psbt, const GENESIS_BLOCKHASH& genesis_blockhash) { + int ret = ::wally_psbt_set_global_genesis_blockhash(detail::get_p(psbt), genesis_blockhash.data(), genesis_blockhash.size()); + return detail::check_ret(__FUNCTION__, ret); +} + template inline int psbt_set_global_scalars(const PSBT& psbt, const struct wally_map* map_in) { int ret = ::wally_psbt_set_global_scalars(detail::get_p(psbt), map_in); diff --git a/include/wally_psbt.h b/include/wally_psbt.h index 4440b777c..02ed4fd00 100644 --- a/include/wally_psbt.h +++ b/include/wally_psbt.h @@ -139,6 +139,7 @@ struct wally_psbt { #ifndef WALLY_ABI_NO_ELEMENTS struct wally_map global_scalars; uint32_t pset_modifiable_flags; + unsigned char genesis_blockhash[SHA256_LEN]; /* All zeros if not present */ #endif /* WALLY_ABI_NO_ELEMENTS */ }; #endif /* SWIG */ @@ -2114,6 +2115,45 @@ WALLY_CORE_API int wally_psbt_find_global_scalar( WALLY_CORE_API int wally_psbt_set_pset_modifiable_flags( struct wally_psbt *psbt, uint32_t flags); + +/** + * Set the global genesis blockhash in a PSBT. + * + * :param psbt: The psbt to update. Must be a PSET. + * :param genesis_blockhash: The genesis blockhash. + * :param genesis_blockhash_len: Size of ``genesis_blockhash`` in bytes. Must be `SHA256_LEN`. + */ +WALLY_CORE_API int wally_psbt_set_global_genesis_blockhash( + struct wally_psbt *psbt, + const unsigned char *genesis_blockhash, + size_t genesis_blockhash_len); + +/** + * Determine if a PSBT contains a global genesis blockhash. + * + * :param psbt: The psbt to check. Must be a PSET. + * :param written: On success, set to zero if no genesis blockhash is present, + *| otherwise set to one. + */ +WALLY_CORE_API int wally_psbt_has_global_genesis_blockhash( + struct wally_psbt *psbt, + size_t *written); + +/** + * Get the global genesis blockhash from a PSBT. + * + * :param psbt: The psbt to get the genesis blockhash from. + * :param bytes_out: Destination for the genesis blockhash. + * MAX_SIZED_OUTPUT(len, bytes_out, SHA256_LEN) + * :param written: Destination for the number of bytes written to ``bytes_out``. + *| Will be zero if the value is not present. + */ +WALLY_CORE_API int wally_psbt_get_global_genesis_blockhash( + struct wally_psbt *psbt, + unsigned char *bytes_out, + size_t len, + size_t *written); + #endif /* WALLY_ABI_NO_ELEMENTS */ /** diff --git a/src/psbt.c b/src/psbt.c index dc1d7e7d7..da6d23698 100644 --- a/src/psbt.c +++ b/src/psbt.c @@ -1348,6 +1348,47 @@ PSBT_GET(num_outputs, PSBT_0) PSBT_GET(fallback_locktime, PSBT_2) PSBT_GET(tx_version, PSBT_2) PSBT_GET(tx_modifiable_flags, PSBT_2) +#ifndef WALLY_ABI_NO_ELEMENTS +int wally_psbt_set_global_genesis_blockhash( + struct wally_psbt *psbt, + const unsigned char* genesis_blockhash, size_t genesis_blockhash_len) +{ + size_t is_pset; + if ((wally_psbt_is_elements(psbt, &is_pset)) != WALLY_OK || !is_pset || + !genesis_blockhash || genesis_blockhash_len != SHA256_LEN) + return WALLY_EINVAL; + memcpy(psbt->genesis_blockhash, genesis_blockhash, genesis_blockhash_len); + return WALLY_OK; +} + +int wally_psbt_has_global_genesis_blockhash(struct wally_psbt *psbt, size_t *written) +{ + size_t is_pset; + if (written) + *written = 0; + if ((wally_psbt_is_elements(psbt, &is_pset)) != WALLY_OK || !is_pset || !written) + return WALLY_EINVAL; + *written = !mem_is_zero(psbt->genesis_blockhash, sizeof(psbt->genesis_blockhash)); + return WALLY_OK; +} + +int wally_psbt_get_global_genesis_blockhash(struct wally_psbt *psbt, + unsigned char* bytes_out, size_t len, + size_t *written) +{ + size_t has_blockhash; + if (written) + *written = 0; + if ((wally_psbt_has_global_genesis_blockhash(psbt, &has_blockhash)) != WALLY_OK || + !bytes_out || len < SHA256_LEN || !written) + return WALLY_EINVAL; + if (has_blockhash) { + memcpy(bytes_out, psbt->genesis_blockhash, sizeof(psbt->genesis_blockhash)); + *written = sizeof(psbt->genesis_blockhash); + } + return WALLY_OK; +} +#endif /* WALLY_ABI_NO_ELEMENTS */ int wally_psbt_has_fallback_locktime(const struct wally_psbt *psbt, size_t *written) { @@ -2717,6 +2758,13 @@ int wally_psbt_from_bytes(const unsigned char *bytes, size_t len, if ((*output)->pset_modifiable_flags & ~PSET_TXMOD_ALL_FLAGS) ret = WALLY_EINVAL; /* Invalid flags */ break; + case PSET_FT(PSET_GLOBAL_GENESIS_HASH): { + size_t val_len; + const unsigned char *val_p; + pull_varlength_buff(cursor, max, &val_p, &val_len); + ret = wally_psbt_set_global_genesis_blockhash(*output, val_p, val_len); + break; + } #endif /* BUILD_ELEMENTS */ default: goto unknown; @@ -3357,6 +3405,10 @@ int wally_psbt_to_bytes(const struct wally_psbt *psbt, uint32_t flags, push_varint(&cursor, &max, sizeof(uint8_t)); push_u8(&cursor, &max, psbt->pset_modifiable_flags); } + if (!mem_is_zero(psbt->genesis_blockhash, sizeof(psbt->genesis_blockhash))) { + push_key(&cursor, &max, PSET_GLOBAL_GENESIS_HASH, true, NULL, 0); + push_varbuff(&cursor, &max, psbt->genesis_blockhash, sizeof(psbt->genesis_blockhash)); + } #endif /* BUILD_ELEMENTS */ } diff --git a/src/psbt_io.h b/src/psbt_io.h index 06b27cb19..61c8be7eb 100644 --- a/src/psbt_io.h +++ b/src/psbt_io.h @@ -51,7 +51,8 @@ /* Globals: PSET */ #define PSET_GLOBAL_SCALAR 0x00 #define PSET_GLOBAL_TX_MODIFIABLE 0x01 -#define PSET_GLOBAL_MAX PSET_GLOBAL_TX_MODIFIABLE +#define PSET_GLOBAL_GENESIS_HASH 0x02 +#define PSET_GLOBAL_MAX PSET_GLOBAL_GENESIS_HASH /* Global PSBT/PSET fields that can be repeated */ #define PSBT_GLOBAL_REPEATABLE (PSBT_FT(PSBT_GLOBAL_XPUB) | \ @@ -76,7 +77,8 @@ PSBT_FT(PSBT_GLOBAL_OUTPUT_COUNT) | \ PSBT_FT(PSBT_GLOBAL_TX_MODIFIABLE) | \ PSET_FT(PSET_GLOBAL_SCALAR) | \ - PSET_FT(PSET_GLOBAL_TX_MODIFIABLE)) + PSET_FT(PSET_GLOBAL_TX_MODIFIABLE) | \ + PSET_FT(PSET_GLOBAL_GENESIS_HASH)) /* Global PSBT/PSET fields that must *not* be present in v2 */ #define PSBT_GLOBAL_DISALLOWED_V2 PSBT_FT(PSBT_GLOBAL_UNSIGNED_TX) diff --git a/src/swig_java/swig.i b/src/swig_java/swig.i index f3c3976ab..ac1e77e74 100644 --- a/src/swig_java/swig.i +++ b/src/swig_java/swig.i @@ -740,6 +740,7 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %returns_struct(wally_psbt_from_tx, wally_psbt); %returns_void__(wally_psbt_generate_input_explicit_proofs); %returns_size_t(wally_psbt_get_pset_modifiable_flags); +%returns_size_t(wally_psbt_get_global_genesis_blockhash); %returns_struct(wally_psbt_get_global_tx_alloc, wally_tx); %rename("psbt_get_global_tx") wally_psbt_get_global_tx_alloc; %returns_array_(wally_psbt_get_global_scalar, 3, 4, WALLY_SCALAR_OFFSET_LEN); @@ -866,6 +867,7 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %returns_size_t(wally_psbt_get_tx_version); %returns_size_t(wally_psbt_get_version); %returns_size_t(wally_psbt_has_fallback_locktime); +%returns_size_t(wally_psbt_has_global_genesis_blockhash); %returns_size_t(wally_psbt_has_input_required_lockheight); %returns_size_t(wally_psbt_has_input_required_locktime); %returns_size_t(wally_psbt_has_input_value); @@ -889,6 +891,7 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %returns_void__(wally_psbt_remove_output); %returns_void__(wally_psbt_set_pset_modifiable_flags); %returns_void__(wally_psbt_set_fallback_locktime); +%returns_void__(wally_psbt_set_global_genesis_blockhash); %returns_void__(wally_psbt_set_global_tx); %returns_void__(wally_psbt_set_global_scalars); %returns_void__(wally_psbt_set_input_amount); diff --git a/src/swig_python/python_extra.py_in b/src/swig_python/python_extra.py_in index 8fbd92eb7..08db803cb 100644 --- a/src/swig_python/python_extra.py_in +++ b/src/swig_python/python_extra.py_in @@ -285,6 +285,7 @@ if is_elements_build(): explicit_rangeproof = _wrap_bin(explicit_rangeproof, ASSET_EXPLICIT_RANGEPROOF_MAX_LEN, resize=True) explicit_surjectionproof = _wrap_bin(explicit_surjectionproof, ASSET_EXPLICIT_SURJECTIONPROOF_LEN) psbt_blind = psbt_blind_alloc + psbt_get_global_genesis_blockhash = _wrap_bin(psbt_get_global_genesis_blockhash, SHA256_LEN, resize=True) psbt_get_global_scalar = _wrap_bin(psbt_get_global_scalar, WALLY_SCALAR_OFFSET_LEN) psbt_get_input_amount_rangeproof = _wrap_bin(psbt_get_input_amount_rangeproof, psbt_get_input_amount_rangeproof_len) psbt_get_input_asset = _wrap_bin(psbt_get_input_asset, psbt_get_input_asset_len) diff --git a/src/test/util.py b/src/test/util.py index a741b9668..6cdbadaf3 100755 --- a/src/test/util.py +++ b/src/test/util.py @@ -454,6 +454,7 @@ class wally_psbt(Structure): ('wally_psbt_from_base64_n', c_int, [c_char_p, c_size_t, c_uint32, POINTER(POINTER(wally_psbt))]), ('wally_psbt_from_bytes', c_int, [c_void_p, c_size_t, c_uint32, POINTER(POINTER(wally_psbt))]), ('wally_psbt_from_tx', c_int, [POINTER(wally_tx), c_uint32, c_uint32, POINTER(POINTER(wally_psbt))]), + ('wally_psbt_get_global_genesis_blockhash', c_int, [POINTER(wally_psbt), c_void_p, c_size_t, c_size_t_p]), ('wally_psbt_get_id', c_int, [POINTER(wally_psbt), c_uint32, c_void_p, c_size_t]), ('wally_psbt_get_input_bip32_key_from_alloc', c_int, [POINTER(wally_psbt), c_size_t, c_size_t, c_uint32, POINTER(ext_key), POINTER(POINTER(ext_key))]), ('wally_psbt_get_input_scriptcode', c_int, [POINTER(wally_psbt), c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_size_t_p]), @@ -464,6 +465,7 @@ class wally_psbt(Structure): ('wally_psbt_get_length', c_int, [POINTER(wally_psbt), c_uint32, c_size_t_p]), ('wally_psbt_get_locktime', c_int, [POINTER(wally_psbt), c_size_t_p]), ('wally_psbt_get_tx_version', c_int, [POINTER(wally_psbt), c_size_t_p]), + ('wally_psbt_has_global_genesis_blockhash', c_int, [POINTER(wally_psbt), c_size_t_p]), ('wally_psbt_init_alloc', c_int, [c_uint32, c_size_t, c_size_t, c_size_t, c_uint32, POINTER(POINTER(wally_psbt))]), ('wally_psbt_input_add_signature', c_int, [POINTER(wally_psbt_input), c_void_p, c_size_t, c_void_p, c_size_t]), ('wally_psbt_input_clear_amount_rangeproof', c_int, [POINTER(wally_psbt_input)]), @@ -617,6 +619,7 @@ class wally_psbt(Structure): ('wally_psbt_remove_input', c_int, [POINTER(wally_psbt), c_uint32]), ('wally_psbt_remove_output', c_int, [POINTER(wally_psbt), c_uint32]), ('wally_psbt_set_fallback_locktime', c_int, [POINTER(wally_psbt), c_uint32]), + ('wally_psbt_set_global_genesis_blockhash', c_int, [POINTER(wally_psbt), c_void_p, c_size_t]), ('wally_psbt_set_global_scalars', c_int, [POINTER(wally_psbt), POINTER(wally_map)]), ('wally_psbt_set_global_tx', c_int, [POINTER(wally_psbt), POINTER(wally_tx)]), ('wally_psbt_set_pset_modifiable_flags', c_int, [POINTER(wally_psbt), c_uint32]), diff --git a/src/wasm_package/src/functions.js b/src/wasm_package/src/functions.js index 73bcc1c96..fef08da9a 100644 --- a/src/wasm_package/src/functions.js +++ b/src/wasm_package/src/functions.js @@ -325,6 +325,7 @@ export const psbt_from_bytes = wrap('wally_psbt_from_bytes', [T.Bytes, T.Int32, export const psbt_from_tx = wrap('wally_psbt_from_tx', [T.OpaqueRef, T.Int32, T.Int32, T.DestPtrPtr(T.OpaqueRef)]); export const psbt_generate_input_explicit_proofs = wrap('wally_psbt_generate_input_explicit_proofs', [T.OpaqueRef, T.Int32, T.Int64, T.Bytes, T.Bytes, T.Bytes, T.Bytes]); export const psbt_get_fallback_locktime = wrap('wally_psbt_get_fallback_locktime', [T.OpaqueRef, T.DestPtr(T.Int32)]); +export const psbt_get_global_genesis_blockhash = wrap('wally_psbt_get_global_genesis_blockhash', [T.OpaqueRef, T.DestPtrVarLen(T.Bytes, C.SHA256_LEN, true)]); export const psbt_get_global_scalar = wrap('wally_psbt_get_global_scalar', [T.OpaqueRef, T.Int32, T.DestPtrSized(T.Bytes, C.WALLY_SCALAR_OFFSET_LEN)]); export const psbt_get_global_scalars_size = wrap('wally_psbt_get_global_scalars_size', [T.OpaqueRef, T.DestPtr(T.Int32)]); export const psbt_get_global_tx = wrap('wally_psbt_get_global_tx_alloc', [T.OpaqueRef, T.DestPtrPtr(T.OpaqueRef)]); @@ -403,6 +404,7 @@ export const psbt_get_tx_modifiable_flags = wrap('wally_psbt_get_tx_modifiable_f export const psbt_get_tx_version = wrap('wally_psbt_get_tx_version', [T.OpaqueRef, T.DestPtr(T.Int32)]); export const psbt_get_version = wrap('wally_psbt_get_version', [T.OpaqueRef, T.DestPtr(T.Int32)]); export const psbt_has_fallback_locktime = wrap('wally_psbt_has_fallback_locktime', [T.OpaqueRef, T.DestPtr(T.Int32)]); +export const psbt_has_global_genesis_blockhash = wrap('wally_psbt_has_global_genesis_blockhash', [T.OpaqueRef, T.DestPtr(T.Int32)]); export const psbt_has_input_required_lockheight = wrap('wally_psbt_has_input_required_lockheight', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]); export const psbt_has_input_required_locktime = wrap('wally_psbt_has_input_required_locktime', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]); export const psbt_has_output_amount = wrap('wally_psbt_has_output_amount', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]); @@ -536,6 +538,7 @@ export const psbt_output_taproot_keypath_add = wrap('wally_psbt_output_taproot_k export const psbt_remove_input = wrap('wally_psbt_remove_input', [T.OpaqueRef, T.Int32]); export const psbt_remove_output = wrap('wally_psbt_remove_output', [T.OpaqueRef, T.Int32]); export const psbt_set_fallback_locktime = wrap('wally_psbt_set_fallback_locktime', [T.OpaqueRef, T.Int32]); +export const psbt_set_global_genesis_blockhash = wrap('wally_psbt_set_global_genesis_blockhash', [T.OpaqueRef, T.Bytes]); export const psbt_set_global_scalars = wrap('wally_psbt_set_global_scalars', [T.OpaqueRef, T.OpaqueRef]); export const psbt_set_global_tx = wrap('wally_psbt_set_global_tx', [T.OpaqueRef, T.OpaqueRef]); export const psbt_set_input_amount = wrap('wally_psbt_set_input_amount', [T.OpaqueRef, T.Int32, T.Int64]); diff --git a/src/wasm_package/src/index.d.ts b/src/wasm_package/src/index.d.ts index 65645b67c..39e378f5e 100644 --- a/src/wasm_package/src/index.d.ts +++ b/src/wasm_package/src/index.d.ts @@ -285,6 +285,7 @@ export function psbt_from_bytes(bytes: Buffer|Uint8Array, flags: number): Ref_wa export function psbt_from_tx(tx: Ref_wally_tx, version: number, flags: number): Ref_wally_psbt; export function psbt_generate_input_explicit_proofs(psbt: Ref_wally_psbt, index: number, satoshi: bigint, asset: Buffer|Uint8Array, abf: Buffer|Uint8Array, vbf: Buffer|Uint8Array, entropy: Buffer|Uint8Array): void; export function psbt_get_fallback_locktime(psbt: Ref_wally_psbt): number; +export function psbt_get_global_genesis_blockhash(psbt: Ref_wally_psbt): Buffer; export function psbt_get_global_scalar(psbt: Ref_wally_psbt, index: number): Buffer; export function psbt_get_global_scalars_size(psbt: Ref_wally_psbt): number; export function psbt_get_global_tx(psbt: Ref_wally_psbt): Ref_wally_tx; @@ -363,6 +364,7 @@ export function psbt_get_tx_modifiable_flags(psbt: Ref_wally_psbt): number; export function psbt_get_tx_version(psbt: Ref_wally_psbt): number; export function psbt_get_version(psbt: Ref_wally_psbt): number; export function psbt_has_fallback_locktime(psbt: Ref_wally_psbt): number; +export function psbt_has_global_genesis_blockhash(psbt: Ref_wally_psbt): number; export function psbt_has_input_required_lockheight(psbt: Ref_wally_psbt, index: number): number; export function psbt_has_input_required_locktime(psbt: Ref_wally_psbt, index: number): number; export function psbt_has_output_amount(psbt: Ref_wally_psbt, index: number): number; @@ -496,6 +498,7 @@ export function psbt_output_taproot_keypath_add(output: Ref_wally_psbt_output, p export function psbt_remove_input(psbt: Ref_wally_psbt, index: number): void; export function psbt_remove_output(psbt: Ref_wally_psbt, index: number): void; export function psbt_set_fallback_locktime(psbt: Ref_wally_psbt, locktime: number): void; +export function psbt_set_global_genesis_blockhash(psbt: Ref_wally_psbt, genesis_blockhash: Buffer|Uint8Array): void; export function psbt_set_global_scalars(psbt: Ref_wally_psbt, map_in: Ref_wally_map): void; export function psbt_set_global_tx(psbt: Ref_wally_psbt, tx: Ref_wally_tx): void; export function psbt_set_input_amount(psbt: Ref_wally_psbt, index: number, amount: bigint): void; diff --git a/tools/wasm_exports.sh b/tools/wasm_exports.sh index c38a7055f..f4f9b295d 100644 --- a/tools/wasm_exports.sh +++ b/tools/wasm_exports.sh @@ -565,6 +565,7 @@ if [ -z "$DISABLE_ELEMENTS" ]; then ,'_wally_psbt_clear_output_value_rangeproof' \ ,'_wally_psbt_find_global_scalar' \ ,'_wally_psbt_generate_input_explicit_proofs' \ +,'_wally_psbt_get_global_genesis_blockhash' \ ,'_wally_psbt_get_global_scalar' \ ,'_wally_psbt_get_global_scalars_size' \ ,'_wally_psbt_get_input_amount' \ @@ -622,6 +623,7 @@ if [ -z "$DISABLE_ELEMENTS" ]; then ,'_wally_psbt_get_output_value_rangeproof' \ ,'_wally_psbt_get_output_value_rangeproof_len' \ ,'_wally_psbt_get_pset_modifiable_flags' \ +,'_wally_psbt_has_global_genesis_blockhash' \ ,'_wally_psbt_has_output_blinder_index' \ ,'_wally_psbt_input_clear_amount_rangeproof' \ ,'_wally_psbt_input_clear_asset' \ @@ -729,6 +731,7 @@ if [ -z "$DISABLE_ELEMENTS" ]; then ,'_wally_psbt_output_set_value_blinding_rangeproof' \ ,'_wally_psbt_output_set_value_commitment' \ ,'_wally_psbt_output_set_value_rangeproof' \ +,'_wally_psbt_set_global_genesis_blockhash' \ ,'_wally_psbt_set_global_scalars' \ ,'_wally_psbt_set_input_amount' \ ,'_wally_psbt_set_input_amount_rangeproof' \ From 45cc984956f07af328a0e166c6e86fc8957f5aab Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Wed, 12 Feb 2025 00:58:36 +1300 Subject: [PATCH 16/22] crypto: add elements ec flag to EC_FLAGS_ALL --- include/wally_crypto.h | 2 +- src/wasm_package/src/const.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/wally_crypto.h b/include/wally_crypto.h index 93f83a9ec..066603ddc 100644 --- a/include/wally_crypto.h +++ b/include/wally_crypto.h @@ -379,7 +379,7 @@ WALLY_CORE_API int wally_pbkdf2_hmac_sha512( #define EC_FLAG_ELEMENTS 0x10 /* All defined flags */ -#define EC_FLAGS_ALL (0x1 | 0x2 | 0x4 | 0x8) +#define EC_FLAGS_ALL (0x1 | 0x2 | 0x4 | 0x8 | 0x10) /** * Verify that a private key is valid. diff --git a/src/wasm_package/src/const.js b/src/wasm_package/src/const.js index d2b9d8c07..9c48139a3 100755 --- a/src/wasm_package/src/const.js +++ b/src/wasm_package/src/const.js @@ -65,7 +65,7 @@ export const BIP39_WORDLIST_LEN = 2048; export const BITCOIN_MESSAGE_FLAG_HASH = 1; export const BITCOIN_MESSAGE_MAX_LEN = (64 * 1024 - 64); export const BLINDING_FACTOR_LEN = 32; /** Length of a Blinding Factor (or blinder) */ -export const EC_FLAGS_ALL = (0x1 | 0x2 | 0x4 | 0x8); +export const EC_FLAGS_ALL = (0x1 | 0x2 | 0x4 | 0x8 | 0x10); export const EC_FLAG_ECDSA = 0x1; export const EC_FLAG_ELEMENTS = 0x10; export const EC_FLAG_GRIND_R = 0x4; From 211e719b785a8e034adb5ba65a658fe775e176af Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Tue, 28 Jan 2025 21:42:42 +1300 Subject: [PATCH 17/22] taproot: simplify the taptweak selection/flag checking code for tagged hashes --- src/sign.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/sign.c b/src/sign.c index 383e1831f..7b8a103bb 100644 --- a/src/sign.c +++ b/src/sign.c @@ -12,9 +12,9 @@ static const char MSG_PREFIX[] = "\x18" "Bitcoin Signed Message:\n"; static const char TAPTWEAK_BTC[] = "TapTweak"; #ifdef BUILD_ELEMENTS static const char TAPTWEAK_ELEMENTS[] = "TapTweak/elements"; -#define GET_TAPTWEAK(flags) ((flags & EC_FLAG_ELEMENTS)? TAPTWEAK_ELEMENTS : TAPTWEAK_BTC) +#define TAPTWEAK(is_elements) (is_elements) ? TAPTWEAK_ELEMENTS : TAPTWEAK_BTC #else -#define GET_TAPTWEAK(flags) TAPTWEAK_BTC +#define TAPTWEAK(is_elements) TAPTWEAK_BTC #endif @@ -139,13 +139,20 @@ static int get_bip341_tweak(const unsigned char *pub_key, size_t pub_key_len, unsigned char preimage[EC_XONLY_PUBLIC_KEY_LEN + SHA256_LEN]; const size_t offset = pub_key_len == EC_PUBLIC_KEY_LEN ? 1 : 0; const size_t preimage_len = merkle_root ? sizeof(preimage) : EC_XONLY_PUBLIC_KEY_LEN; - (void)flags; + +#ifdef BUILD_ELEMENTS + if (flags & ~EC_FLAG_ELEMENTS) +#else + if (flags) +#endif + return WALLY_EINVAL; memcpy(preimage, pub_key + offset, EC_XONLY_PUBLIC_KEY_LEN); if (merkle_root) memcpy(preimage + EC_XONLY_PUBLIC_KEY_LEN, merkle_root, SHA256_LEN); return wally_bip340_tagged_hash(preimage, preimage_len, - GET_TAPTWEAK(flags), tweak, tweak_len); + TAPTWEAK(flags & EC_FLAG_ELEMENTS), + tweak, tweak_len); } int wally_ec_public_key_bip341_tweak( @@ -157,11 +164,6 @@ int wally_ec_public_key_bip341_tweak( int ret; if (!pub_key || BYTES_INVALID_N(merkle_root, merkle_root_len, SHA256_LEN) || -#ifdef BUILD_ELEMENTS - (flags & ~EC_FLAG_ELEMENTS) || -#else - flags || -#endif !bytes_out || len != EC_PUBLIC_KEY_LEN) return WALLY_EINVAL; @@ -194,11 +196,6 @@ int wally_ec_private_key_bip341_tweak( if (!priv_key || priv_key_len != EC_PRIVATE_KEY_LEN || BYTES_INVALID_N(merkle_root, merkle_root_len, SHA256_LEN) || -#ifdef BUILD_ELEMENTS - (flags & ~EC_FLAG_ELEMENTS) || -#else - flags || -#endif !bytes_out || len != EC_PRIVATE_KEY_LEN) return WALLY_EINVAL; From d3bc78cd7b408a1cbf8deb548a698def6eb3bcc9 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Tue, 28 Jan 2025 21:47:30 +1300 Subject: [PATCH 18/22] taproot: support elements tx TapLeaf/TapSighash hashing --- src/transaction.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/transaction.c b/src/transaction.c index d319e19f6..6e4116826 100644 --- a/src/transaction.c +++ b/src/transaction.c @@ -35,6 +35,15 @@ static const unsigned char DUMMY_SIG[EC_SIGNATURE_DER_MAX_LEN + 1]; /* +1 for si #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 { @@ -2531,7 +2540,8 @@ static inline int tx_to_bip341_bytes(const struct wally_tx *tx, 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", p, SHA256_LEN); + 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; @@ -3398,7 +3408,7 @@ int wally_tx_get_btc_taproot_signature_hash( 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", bytes_out, len); + ret = wally_bip340_tagged_hash(buff, n, TAPSIGHASH(false), bytes_out, len); } wally_clear(buff, n); return ret; From 683dd5a69be1a3ef899a780dfdb383c32ebe55f6 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sat, 1 Feb 2025 17:46:41 +1300 Subject: [PATCH 19/22] script: make scriptpubkey_is_p2tr available internally For use by a later commit. --- src/script.c | 5 +++-- src/script.h | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/script.c b/src/script.c index 7ad6c7b0a..c48d5395d 100644 --- a/src/script.c +++ b/src/script.c @@ -361,9 +361,10 @@ static bool scriptpubkey_is_p2wsh(const unsigned char *bytes, size_t bytes_len) bytes[1] == 32; /* SHA256 */ } -static bool scriptpubkey_is_p2tr(const unsigned char *bytes, size_t bytes_len) +bool scriptpubkey_is_p2tr(const unsigned char *bytes, size_t bytes_len) { - return bytes_len == WALLY_SCRIPTPUBKEY_P2TR_LEN && + /* Note this is called from elsewhere hence we check 'bytes' for NULL */ + return bytes && bytes_len == WALLY_SCRIPTPUBKEY_P2TR_LEN && bytes[0] == OP_1 && /* Segwit v1 */ bytes[1] == 32; /* X-ONLY-PUBKEY */ } diff --git a/src/script.h b/src/script.h index 05df1d6d8..cb39e7dc2 100644 --- a/src/script.h +++ b/src/script.h @@ -18,6 +18,8 @@ int script_get_push_opcode_size_from_bytes( /* Get OP_N */ 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); + /* Convert 0-16 to OP_ */ size_t value_to_op_n(uint64_t v); From 1372cf44129d2205536edd97440a800d84cb5197 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Wed, 29 Jan 2025 08:10:05 +1300 Subject: [PATCH 20/22] tx: make the existing bip341 size calculation explicitly btc The Elements case is more complicated and will be computed in its own function. --- src/transaction.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/transaction.c b/src/transaction.c index 6e4116826..62982da68 100644 --- a/src/transaction.c +++ b/src/transaction.c @@ -1707,13 +1707,15 @@ static int get_txin_issuance_size(const struct wally_tx_input *input, return WALLY_OK; } -static size_t get_bip341_size(uint32_t sighash, bool have_annex, unsigned char ext_flag) +/* 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 = sighash & WALLY_SIGHASH_ANYONECANPAY; - const bool sh_none = (sighash & WALLY_SIGHASH_MASK) == WALLY_SIGHASH_NONE; - /* See BIP341/342/118. Note the leading 1 for the sighash epoc byte */ - return 1 + 174 - sh_anyonecanpay * 49 - sh_none * 32 + - have_annex * 32 + (ext_flag == EXT_FLAG_BIP342 ? 37 : 0); + 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 @@ -1743,8 +1745,7 @@ static int tx_get_lengths(const struct wally_tx *tx, return WALLY_ERROR; /* Segwit tx hashing uses bip143 opts member */ if (opts->bip341) { - *base_size = get_bip341_size(opts->tx_sighash, opts->annex_len != 0, - opts->ext_flag); + *base_size = get_btc_bip341_size(opts); *witness_size = 0; return WALLY_OK; } From ef4ba153bde5078f7db388bacec33880823344ee Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Fri, 31 Jan 2025 00:37:43 +1300 Subject: [PATCH 21/22] tx: also compute rangeproof size for issuances if requested We don't use this yet, but taproot requires it. --- src/transaction.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/transaction.c b/src/transaction.c index 62982da68..066569df2 100644 --- a/src/transaction.c +++ b/src/transaction.c @@ -1688,18 +1688,24 @@ static int get_txout_commitments_size(const struct wally_tx_output *output, } static int get_txin_issuance_size(const struct wally_tx_input *input, - size_t *written) + size_t *issuance_size, size_t *issuance_rp_size) { - *written = 0; + *issuance_size = 0; + if (issuance_rp_size) + *issuance_rp_size = 0; #ifdef BUILD_ELEMENTS if (input->features & WALLY_TX_IS_ISSUANCE) { size_t c_n; - if (!(*written = confidential_value_length_from_bytes(input->issuance_amount))) + if (!(*issuance_size = confidential_value_length_from_bytes(input->issuance_amount))) return WALLY_EINVAL; if (!(c_n = confidential_value_length_from_bytes(input->inflation_keys))) return WALLY_EINVAL; - *written = *written + c_n + sizeof(input->blinding_nonce) + sizeof(input->entropy); + *issuance_size += c_n + sizeof(input->blinding_nonce) + sizeof(input->entropy); + if (issuance_rp_size) { + *issuance_rp_size = input->issuance_amount_rangeproof_len + + input->inflation_keys_rangeproof_len; + } } #else (void)input; @@ -1772,7 +1778,8 @@ static int tx_get_lengths(const struct wally_tx *tx, } *base_size += amount_size; - if (get_txin_issuance_size(tx->inputs + opts->index, &issuance_size) != WALLY_OK) + if (get_txin_issuance_size(tx->inputs + opts->index, + &issuance_size, NULL) != WALLY_OK) return WALLY_EINVAL; *base_size += issuance_size; *witness_size = 0; @@ -1807,7 +1814,7 @@ static int tx_get_lengths(const struct wally_tx *tx, sizeof(input->index) + sizeof(input->sequence); - if (get_txin_issuance_size(input, &issuance_size) != WALLY_OK) + if (get_txin_issuance_size(input, &issuance_size, NULL) != WALLY_OK) return WALLY_EINVAL; n += issuance_size; @@ -2168,7 +2175,8 @@ static inline int tx_to_bip143_bytes(const struct wally_tx *tx, 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) != WALLY_OK) + if (get_txin_issuance_size(tx->inputs + i, + &issuance_size, NULL) != WALLY_OK) return WALLY_EINVAL; issuances_size += issuance_size; } else From 6998c96fbf12ac065a16b06ed5d0bfaf3584a008 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Tue, 18 Feb 2025 12:53:26 +1300 Subject: [PATCH 22/22] pullpush: add missing stdbool header --- src/pullpush.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pullpush.h b/src/pullpush.h index 539b9b1b0..7125d1b91 100644 --- a/src/pullpush.h +++ b/src/pullpush.h @@ -1,6 +1,8 @@ #ifndef LIBWALLY_CORE_PULLPUSH_H #define LIBWALLY_CORE_PULLPUSH_H 1 +#include + struct wally_tx_witness_stack; /**