diff --git a/.cirrus.yml b/.cirrus.yml index 04aa8f2409..79c611ce4e 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -22,6 +22,7 @@ env: RECOVERY: no SCHNORRSIG: no ELLSWIFT: no + SILENTPAYMENTS: no ### test options SECP256K1_TEST_ITERS: BENCH: yes @@ -68,6 +69,7 @@ task: RECOVERY: yes SCHNORRSIG: yes ELLSWIFT: yes + SILENTPAYMENTS: yes matrix: # Currently only gcc-snapshot, the other compilers are tested on GHA with QEMU - env: { CC: 'gcc-snapshot' } @@ -84,6 +86,7 @@ task: RECOVERY: yes SCHNORRSIG: yes ELLSWIFT: yes + SILENTPAYMENTS: yes WRAPPER_CMD: 'valgrind --error-exitcode=42' SECP256K1_TEST_ITERS: 2 matrix: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ad905af52..a2dce4a7b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,7 @@ env: RECOVERY: 'no' SCHNORRSIG: 'no' ELLSWIFT: 'no' + SILENTPAYMENTS: 'no' ### test options SECP256K1_TEST_ITERS: BENCH: 'yes' @@ -71,18 +72,18 @@ jobs: matrix: configuration: - env_vars: { WIDEMUL: 'int64', RECOVERY: 'yes' } - - env_vars: { WIDEMUL: 'int64', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' } + - env_vars: { WIDEMUL: 'int64', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' } - env_vars: { WIDEMUL: 'int128' } - env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' } - env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' } - - env_vars: { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes' } + - env_vars: { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', SILENTPAYMENTS: 'yes' } - env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' } - env_vars: { RECOVERY: 'yes', SCHNORRSIG: 'yes' } - - env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', CPPFLAGS: '-DVERIFY' } + - env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', SILENTPAYMENTS: 'yes', CPPFLAGS: '-DVERIFY' } - env_vars: { BUILD: 'distcheck', WITH_VALGRIND: 'no', CTIMETESTS: 'no', BENCH: 'no' } - env_vars: { CPPFLAGS: '-DDETERMINISTIC' } - env_vars: { CFLAGS: '-O0', CTIMETESTS: 'no' } - - env_vars: { CFLAGS: '-O1', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' } + - env_vars: { CFLAGS: '-O1', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' } - env_vars: { ECMULTGENPRECISION: 2, ECMULTWINDOW: 2 } - env_vars: { ECMULTGENPRECISION: 8, ECMULTWINDOW: 4 } cc: @@ -141,6 +142,7 @@ jobs: RECOVERY: 'yes' SCHNORRSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' CC: ${{ matrix.cc }} steps: @@ -185,6 +187,7 @@ jobs: RECOVERY: 'yes' SCHNORRSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' CTIMETESTS: 'no' steps: @@ -236,6 +239,7 @@ jobs: RECOVERY: 'yes' SCHNORRSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' CTIMETESTS: 'no' steps: @@ -281,6 +285,7 @@ jobs: RECOVERY: 'yes' SCHNORRSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' CTIMETESTS: 'no' strategy: @@ -336,6 +341,7 @@ jobs: RECOVERY: 'yes' SCHNORRSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' CTIMETESTS: 'no' steps: @@ -388,6 +394,7 @@ jobs: RECOVERY: 'yes' SCHNORRSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' CTIMETESTS: 'no' SECP256K1_TEST_ITERS: 2 @@ -439,6 +446,7 @@ jobs: RECOVERY: 'yes' SCHNORRSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' CTIMETESTS: 'no' CFLAGS: '-fsanitize=undefined,address -g' UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1' @@ -496,6 +504,7 @@ jobs: RECOVERY: 'yes' SCHNORRSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' CTIMETESTS: 'yes' CC: 'clang' SECP256K1_TEST_ITERS: 32 @@ -543,6 +552,7 @@ jobs: RECOVERY: 'yes' SCHNORRSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' CTIMETESTS: 'no' strategy: @@ -599,14 +609,14 @@ jobs: fail-fast: false matrix: env_vars: - - { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' } + - { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' } - { WIDEMUL: 'int128_struct', ECMULTGENPRECISION: 2, ECMULTWINDOW: 4 } - - { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' } + - { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' } - { WIDEMUL: 'int128', RECOVERY: 'yes' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes', CC: 'gcc' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' } - BUILD: 'distcheck' @@ -718,6 +728,7 @@ jobs: RECOVERY: 'yes' SCHNORRSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' steps: - name: Checkout diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ef7defe51..d824a6db3e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,9 +60,14 @@ option(SECP256K1_ENABLE_MODULE_RECOVERY "Enable ECDSA pubkey recovery module." O option(SECP256K1_ENABLE_MODULE_EXTRAKEYS "Enable extrakeys module." ON) option(SECP256K1_ENABLE_MODULE_SCHNORRSIG "Enable schnorrsig module." ON) option(SECP256K1_ENABLE_MODULE_ELLSWIFT "Enable ElligatorSwift module." ON) +option(SECP256K1_ENABLE_MODULE_SILENTPAYMENTS "Enable Silent Payments module." OFF) # Processing must be done in a topological sorting of the dependency graph # (dependent module first). +if(SECP256K1_ENABLE_MODULE_SILENTPAYMENTS) + add_compile_definitions(ENABLE_MODULE_SILENTPAYMENTS=1) +endif() + if(SECP256K1_ENABLE_MODULE_ELLSWIFT) add_compile_definitions(ENABLE_MODULE_ELLSWIFT=1) endif() @@ -292,6 +297,7 @@ message(" ECDSA pubkey recovery ............... ${SECP256K1_ENABLE_MODULE_RECOV message(" extrakeys ........................... ${SECP256K1_ENABLE_MODULE_EXTRAKEYS}") message(" schnorrsig .......................... ${SECP256K1_ENABLE_MODULE_SCHNORRSIG}") message(" ElligatorSwift ...................... ${SECP256K1_ENABLE_MODULE_ELLSWIFT}") +message(" Silent Payments ..................... ${SECP256K1_ENABLE_MODULE_SILENTPAYMENTS}") message("Parameters:") message(" ecmult window size .................. ${SECP256K1_ECMULT_WINDOW_SIZE}") message(" ecmult gen precision bits ........... ${SECP256K1_ECMULT_GEN_PREC_BITS}") diff --git a/Makefile.am b/Makefile.am index 5498617915..d9295799bc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -271,3 +271,7 @@ endif if ENABLE_MODULE_ELLSWIFT include src/modules/ellswift/Makefile.am.include endif + +if ENABLE_MODULE_SILENTPAYMENTS +include src/modules/silentpayments/Makefile.am.include +endif diff --git a/ci/ci.sh b/ci/ci.sh index 9cc715955e..6486db65c8 100755 --- a/ci/ci.sh +++ b/ci/ci.sh @@ -13,7 +13,7 @@ print_environment() { # does not rely on bash. for var in WERROR_CFLAGS MAKEFLAGS BUILD \ ECMULTWINDOW ECMULTGENPRECISION ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \ - EXPERIMENTAL ECDH RECOVERY SCHNORRSIG ELLSWIFT \ + EXPERIMENTAL ECDH RECOVERY SCHNORRSIG ELLSWIFT SILENTPAYMENTS \ SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS\ EXAMPLES \ HOST WRAPPER_CMD \ @@ -76,6 +76,7 @@ esac --with-ecmult-gen-precision="$ECMULTGENPRECISION" \ --enable-module-ecdh="$ECDH" --enable-module-recovery="$RECOVERY" \ --enable-module-ellswift="$ELLSWIFT" \ + --enable-module-silentpayments="$SILENTPAYMENTS" \ --enable-module-schnorrsig="$SCHNORRSIG" \ --enable-examples="$EXAMPLES" \ --enable-ctime-tests="$CTIMETESTS" \ diff --git a/configure.ac b/configure.ac index 158ed5d769..7113752b50 100644 --- a/configure.ac +++ b/configure.ac @@ -188,6 +188,10 @@ AC_ARG_ENABLE(module_ellswift, AS_HELP_STRING([--enable-module-ellswift],[enable ElligatorSwift module [default=yes]]), [], [SECP_SET_DEFAULT([enable_module_ellswift], [yes], [yes])]) +AC_ARG_ENABLE(module_silentpayments, + AS_HELP_STRING([--enable-module-silentpayments],[enable Silent Payments module [default=no]]), [], + [SECP_SET_DEFAULT([enable_module_silentpayments], [no], [yes])]) + AC_ARG_ENABLE(external_default_callbacks, AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [], [SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])]) @@ -389,6 +393,10 @@ SECP_CFLAGS="$SECP_CFLAGS $WERROR_CFLAGS" # Processing must be done in a reverse topological sorting of the dependency graph # (dependent module first). +if test x"$enable_module_silentpayments" = x"yes"; then + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_SILENTPAYMENTS=1" +fi + if test x"$enable_module_ellswift" = x"yes"; then SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ELLSWIFT=1" fi @@ -450,6 +458,7 @@ AM_CONDITIONAL([ENABLE_MODULE_RECOVERY], [test x"$enable_module_recovery" = x"ye AM_CONDITIONAL([ENABLE_MODULE_EXTRAKEYS], [test x"$enable_module_extrakeys" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG], [test x"$enable_module_schnorrsig" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_ELLSWIFT], [test x"$enable_module_ellswift" = x"yes"]) +AM_CONDITIONAL([ENABLE_MODULE_SILENTPAYMENTS], [test x"$enable_module_silentpayments" = x"yes"]) AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"]) AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm32"]) AM_CONDITIONAL([BUILD_WINDOWS], [test "$build_windows" = "yes"]) @@ -472,6 +481,7 @@ echo " module recovery = $enable_module_recovery" echo " module extrakeys = $enable_module_extrakeys" echo " module schnorrsig = $enable_module_schnorrsig" echo " module ellswift = $enable_module_ellswift" +echo " module silentpayments = $enable_module_silentpayments" echo echo " asm = $set_asm" echo " ecmult window size = $set_ecmult_window" diff --git a/include/secp256k1_silentpayments.h b/include/secp256k1_silentpayments.h new file mode 100644 index 0000000000..81baf8e655 --- /dev/null +++ b/include/secp256k1_silentpayments.h @@ -0,0 +1,320 @@ +#ifndef SECP256K1_SILENTPAYMENTS_H +#define SECP256K1_SILENTPAYMENTS_H + +#include "secp256k1.h" +#include "secp256k1_extrakeys.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* This module provides an implementation for the ECC related parts of + * Silent Payments, as specified in BIP352. This particularly involves + * the creation of input tweak data by summing up private or public keys + * and the derivation of a shared secret using Elliptic Curve Diffie-Hellman. + * Combined are either: + * - spender's private keys and receiver's public key (a * B, sender side) + * - spender's public keys and receiver's private key (A * b, receiver side) + * With this result, the necessary key material for ultimately creating/scanning + * or spending Silent Payment outputs can be determined. + * + * Note that this module is _not_ a full implementation of BIP352, as it + * inherently doesn't deal with higher-level concepts like addresses, output + * script types or transactions. The intent is to provide cryptographical + * helpers for low-level calculations that are most error-prone to custom + * implementations (e.g. enforcing the right y-parity for key material, ECDH + * calculation etc.). For any wallet software already using libsecp256k1, this + * API should provide all the functions needed for a Silent Payments + * implementation without the need for any further manual elliptic-curve + * operations. + */ + +/** Create Silent Payment tweak data from input private keys. + * + * Given a list of n private keys a_1...a_n (one for each silent payment + * eligible input to spend) and a serialized outpoint_smallest, compute + * the corresponding input private keys tweak data: + * + * a_sum = a_1 + a_2 + ... + a_n + * input_hash = hash(outpoint_smallest || (a_sum * G)) + * + * If necessary, the private keys are negated to enforce the right y-parity. + * For that reason, the private keys have to be passed in via two different parameter + * pairs, depending on whether they were used for creating taproot outputs or not. + * The resulting data is needed to create a shared secret for the sender side. + * + * Returns: 1 if shared secret creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: a_sum: pointer to the resulting 32-byte private key sum + * input_hash: pointer to the resulting 32-byte input hash + * In: plain_seckeys: pointer to an array of pointers to 32-byte private keys + * of non-taproot inputs (can be NULL if no private keys of + * non-taproot inputs are used) + * n_plain_seckeys: the number of sender's non-taproot input private keys + * taproot_seckeys: pointer to an array of pointers to 32-byte private keys + * of taproot inputs (can be NULL if no private keys of + * taproot inputs are used) + * n_taproot_seckeys: the number of sender's taproot input private keys + * outpoint_smallest36: serialized smallest outpoint + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_private_tweak_data( + const secp256k1_context *ctx, + unsigned char *a_sum, + unsigned char *input_hash, + const unsigned char * const *plain_seckeys, + size_t n_plain_seckeys, + const unsigned char * const *taproot_seckeys, + size_t n_taproot_seckeys, + const unsigned char *outpoint_smallest36 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(8); + +/** Create Silent Payment tweak data from input public keys. + * + * Given a list of n public keys A_1...A_n (one for each silent payment + * eligible input to spend) and a serialized outpoint_smallest, compute + * the corresponding input public keys tweak data: + * + * A_sum = A_1 + A_2 + ... + A_n + * input_hash = hash(outpoint_lowest || A_sum) + * + * The public keys have to be passed in via two different parameter pairs, + * one for regular and one for x-only public keys, in order to avoid the need + * of users converting to a common pubkey format before calling this function. + * The resulting data is needed to create a shared secret for the receiver's side. + * + * Returns: 1 if tweak data creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: A_sum: pointer to the resulting public keys sum + * input_hash: pointer to the resulting 32-byte input hash + * In: plain_pubkeys: pointer to an array of pointers to non-taproot + * public keys (can be NULL if no non-taproot inputs are used) + * n_plain_pubkeys: the number of non-taproot input public keys + * xonly_pubkeys: pointer to an array of pointers to taproot x-only + * public keys (can be NULL if no taproot inputs are used) + * n_xonly_pubkeys: the number of taproot input public keys + * outpoint_smallest36: serialized smallest outpoint + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_public_tweak_data( + const secp256k1_context *ctx, + secp256k1_pubkey *A_sum, + unsigned char *input_hash, + const secp256k1_pubkey * const *plain_pubkeys, + size_t n_plain_pubkeys, + const secp256k1_xonly_pubkey * const *xonly_pubkeys, + size_t n_xonly_pubkeys, + const unsigned char *outpoint_smallest36 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(8); + +/** Create Silent Payment tweaked public key from public tweak data. + * + * Given public tweak data (public keys sum and input hash), calculate the + * corresponding tweaked public key: + * + * A_tweaked = input_hash * A_sum + * + * The resulting data is useful for light clients and silent payment indexes. + * + * Returns: 1 if tweaked public key creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: A_tweaked: pointer to the resulting tweaked public key + * In: A_sum: pointer to the public keys sum + * input_hash: pointer to the 32-byte input hash + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_tweaked_pubkey( + const secp256k1_context *ctx, + secp256k1_pubkey *A_tweaked, + const secp256k1_pubkey *A_sum, + const unsigned char *input_hash +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Create Silent Payment shared secret. + * + * Given a public component Pub, a private component sec and an input_hash, + * calculate the corresponding shared secret using ECDH: + * + * shared_secret = (sec * input_hash) * Pub + * + * What the components should be set to depends on the role of the caller. + * For the sender side, the public component is set the recipient's scan public key + * B_scan, and the private component is set to the input's private keys sum: + * + * shared_secret = (a_sum * input_hash) * B_scan [Sender] + * + * For the receiver side, the public component is set to the input's public keys sum, + * and the private component is set to the receiver's scan private key: + * + * shared_secret = (b_scan * input_hash) * A_sum [Receiver, Full node scenario] + * + * In the "light client" scenario for receivers, the public component is already + * tweaked with the input hash: A_tweaked = input_hash * A_sum + * In this case, the input_hash parameter should be set to NULL, to signal that + * no further tweaking should be done before the ECDH: + * + * shared_secret = b_scan * A_tweaked [Receiver, Light client scenario] + * + * The resulting shared secret is needed as input for creating silent payments + * outputs belonging to the same receiver scan public key. + * + * Returns: 1 if shared secret creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: shared_secret33: pointer to the resulting 33-byte shared secret + * In: public_component: pointer to the public component + * private_component: pointer to 32-byte private component + * input_hash: pointer to 32-byte input hash (can be NULL if the + * public component is already tweaked with the input hash) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_shared_secret( + const secp256k1_context *ctx, + unsigned char *shared_secret33, + const secp256k1_pubkey *public_component, + const unsigned char *secret_component, + const unsigned char *input_hash +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Create Silent Payment label tweak and label. + * + * Given a recipient's scan private key b_scan and a label integer m, calculate + * the corresponding label tweak and label: + * + * label_tweak = hash(b_scan || m) + * label = label_tweak * G + * + * Returns: 1 if label tweak and label creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: label_tweak: pointer to the resulting label tweak + * In: receiver_scan_seckey: pointer to the receiver's scan private key + * m: label integer (0 is used for change outputs) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_label_tweak( + const secp256k1_context *ctx, + secp256k1_pubkey *label, + unsigned char *label_tweak32, + const unsigned char *receiver_scan_seckey, + unsigned int m +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Create Silent Payment labelled spend public key. + * + * Given a recipient's spend public key B_spend and a label, calculate + * the corresponding serialized labelled spend public key: + * + * B_m = B_spend + label + * + * The result is used by the receiver to create a Silent Payment address, consisting + * of the serialized and concatenated scan public key and (labelled) spend public key each. + * + * Returns: 1 if labelled spend public key creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: l_addr_spend_pubkey33: pointer to the resulting labelled spend public key + * In: receiver_spend_pubkey: pointer to the receiver's spend pubkey + * label: pointer to the the receiver's label + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_address_spend_pubkey( + const secp256k1_context *ctx, + unsigned char *l_addr_spend_pubkey33, + const secp256k1_pubkey *receiver_spend_pubkey, + const secp256k1_pubkey *label +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Create Silent Payment output public key (for sender). + * + * Given a shared_secret, a recipient's spend public key B_spend, and + * an output counter k, calculate the corresponding output public key: + * + * P_output_xonly = B_spend + hash(shared_secret || ser_32(k)) * G + * + * Returns: 1 if output creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: P_output_xonly: pointer to the resulting output x-only pubkey + * In: shared_secret33: shared secret, derived from either sender's + * or receiver's perspective with routines from above + * receiver_spend_pubkey: pointer to the receiver's spend pubkey + * k: output counter (usually set to 0, should be increased for + * every additional output to the same recipient) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_sender_create_output_pubkey( + const secp256k1_context *ctx, + secp256k1_xonly_pubkey *P_output_xonly, + const unsigned char *shared_secret33, + const secp256k1_pubkey *receiver_spend_pubkey, + unsigned int k +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +typedef struct { + secp256k1_pubkey label; + secp256k1_pubkey label_negated; +} secp256k1_silentpayments_label_data; + +/** Scan for Silent Payment transaction output (for receiver). + * + * Given a shared_secret, a recipient's spend public key B_spend, + * an output counter k, and a scanned tx's output x-only public key tx_output, + * calculate the corresponding scanning data: + * + * t_k = hash(shared_secret || ser_32(k)) + * P_output = B_spend + t_k * G [not returned] + * if P_output == tx_output + * direct_match = 1 + * else + * label1 = tx_output - P_output + * label2 = -tx_output - P_output + * direct_match = 0 + * + * The resulting data is needed for the receiver to efficiently scan for labels + * in silent payments eligible outputs. + * + * Returns: 1 if output scanning was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: direct_match: pointer to the resulting boolean indicating whether + * the calculated output pubkey matches the scanned one + * t_k: pointer to the resulting tweak t_k + * label_data: pointer to the resulting label structure, containing the + * two label candidates, only set if direct_match == 0 + * (can be NULL if the data is not needed) + * In: shared_secret33: shared secret, derived from either sender's + * or receiver's perspective with routines from above + * receiver_spend_pubkey: pointer to the receiver's spend pubkey + * k: output counter (usually set to 0, should be increased for + * every additional output to the same recipient) + * tx_output: pointer to the scanned tx's output x-only public key + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_receiver_scan_output( + const secp256k1_context *ctx, + int *direct_match, + unsigned char *t_k, + secp256k1_silentpayments_label_data *label_data, + const unsigned char *shared_secret33, + const secp256k1_pubkey *receiver_spend_pubkey, + unsigned int k, + const secp256k1_xonly_pubkey *tx_output +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(8); + +/** Create Silent Payment output private key (for spending receiver's funds). + * + * Given a recipient's spend private key b_spend, a tweak t_k and an + * optional label_tweak, calculate the corresponding output private key d: + * + * d = b_spend + t_k + label_tweak + * (if no label tweak is used, then label_tweak = 0) + * + * Returns: 1 if private key creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: output_seckey: pointer to the resulting spending private key + * In: receiver_spend_seckey: pointer to the receiver's spend private key + * t_k: pointer to the 32-byte output tweak + * label_tweak: pointer to an optional 32-byte label tweak + * (can be NULL if no label is used) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_output_seckey( + const secp256k1_context *ctx, + unsigned char *output_seckey, + const unsigned char *receiver_spend_seckey, + const unsigned char *t_k, + const unsigned char *label_tweak +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +#ifdef __cplusplus +} +#endif + +#endif /* SECP256K1_SILENTPAYMENTS_H */ diff --git a/src/modules/silentpayments/Makefile.am.include b/src/modules/silentpayments/Makefile.am.include new file mode 100644 index 0000000000..af917f8908 --- /dev/null +++ b/src/modules/silentpayments/Makefile.am.include @@ -0,0 +1,4 @@ +include_HEADERS += include/secp256k1_silentpayments.h +noinst_HEADERS += src/modules/silentpayments/main_impl.h +noinst_HEADERS += src/modules/silentpayments/tests_impl.h +noinst_HEADERS += src/modules/silentpayments/vectors.h diff --git a/src/modules/silentpayments/main_impl.h b/src/modules/silentpayments/main_impl.h new file mode 100644 index 0000000000..2b8872ae5a --- /dev/null +++ b/src/modules/silentpayments/main_impl.h @@ -0,0 +1,410 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_SILENTPAYMENTS_MAIN_H +#define SECP256K1_MODULE_SILENTPAYMENTS_MAIN_H + +#include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_ecdh.h" +#include "../../../include/secp256k1_extrakeys.h" +#include "../../../include/secp256k1_silentpayments.h" +#include "../../hash.h" + +/** Set hash state to the BIP340 tagged hash midstate for "BIP0352/Inputs". */ +static void secp256k1_silentpayments_sha256_init_inputs(secp256k1_sha256* hash) { + secp256k1_sha256_initialize(hash); + hash->s[0] = 0xd4143ffcul; + hash->s[1] = 0x012ea4b5ul; + hash->s[2] = 0x36e21c8ful; + hash->s[3] = 0xf7ec7b54ul; + hash->s[4] = 0x4dd4e2acul; + hash->s[5] = 0x9bcaa0a4ul; + hash->s[6] = 0xe244899bul; + hash->s[7] = 0xcd06903eul; + + hash->bytes = 64; +} + +static void secp256k1_silentpayments_calculate_input_hash(unsigned char *input_hash, const unsigned char *outpoint_smallest36, secp256k1_ge *pubkey_sum) { + secp256k1_sha256 hash; + unsigned char pubkey_sum_ser[33]; + size_t ser_size; + int ser_ret; + + secp256k1_silentpayments_sha256_init_inputs(&hash); + secp256k1_sha256_write(&hash, outpoint_smallest36, 36); + ser_ret = secp256k1_eckey_pubkey_serialize(pubkey_sum, pubkey_sum_ser, &ser_size, 1); + VERIFY_CHECK(ser_ret && ser_size == sizeof(pubkey_sum_ser)); + (void)ser_ret; + secp256k1_sha256_write(&hash, pubkey_sum_ser, sizeof(pubkey_sum_ser)); + secp256k1_sha256_finalize(&hash, input_hash); +} + +int secp256k1_silentpayments_create_private_tweak_data(const secp256k1_context *ctx, unsigned char *a_sum, unsigned char *input_hash, const unsigned char * const *plain_seckeys, size_t n_plain_seckeys, const unsigned char * const *taproot_seckeys, size_t n_taproot_seckeys, const unsigned char *outpoint_smallest36) { + size_t i; + secp256k1_scalar a_sum_scalar, addend; + secp256k1_ge A_sum_ge; + secp256k1_gej A_sum_gej; + + /* Sanity check inputs. */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(a_sum != NULL); + memset(a_sum, 0, 32); + ARG_CHECK(input_hash != NULL); + memset(input_hash, 0, 32); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(plain_seckeys == NULL || n_plain_seckeys >= 1); + ARG_CHECK(taproot_seckeys == NULL || n_taproot_seckeys >= 1); + ARG_CHECK((plain_seckeys != NULL) || (taproot_seckeys != NULL)); + ARG_CHECK((n_plain_seckeys + n_taproot_seckeys) >= 1); + ARG_CHECK(outpoint_smallest36 != NULL); + + /* Compute input private keys sum: a_sum = a_1 + a_2 + ... + a_n */ + a_sum_scalar = secp256k1_scalar_zero; + for (i = 0; i < n_plain_seckeys; i++) { + int ret = secp256k1_scalar_set_b32_seckey(&addend, plain_seckeys[i]); + VERIFY_CHECK(ret); + (void)ret; + + secp256k1_scalar_add(&a_sum_scalar, &a_sum_scalar, &addend); + VERIFY_CHECK(!secp256k1_scalar_is_zero(&a_sum_scalar)); + } + /* private keys used for taproot outputs have to be negated if they resulted in an odd point */ + for (i = 0; i < n_taproot_seckeys; i++) { + secp256k1_ge addend_point; + int ret = secp256k1_ec_pubkey_create_helper(&ctx->ecmult_gen_ctx, &addend, &addend_point, taproot_seckeys[i]); + VERIFY_CHECK(ret); + (void)ret; + /* declassify addend_point to allow using it as a branch point (this is fine because addend_point is not a secret) */ + secp256k1_declassify(ctx, &addend_point, sizeof(addend_point)); + secp256k1_fe_normalize_var(&addend_point.y); + if (secp256k1_fe_is_odd(&addend_point.y)) { + secp256k1_scalar_negate(&addend, &addend); + } + + secp256k1_scalar_add(&a_sum_scalar, &a_sum_scalar, &addend); + VERIFY_CHECK(!secp256k1_scalar_is_zero(&a_sum_scalar)); + } + if (secp256k1_scalar_is_zero(&a_sum_scalar)) { + /* TODO: do we need a special error return code for this case? */ + return 0; + } + secp256k1_scalar_get_b32(a_sum, &a_sum_scalar); + + /* Compute input_hash = hash(outpoint_L || (a_sum * G)) */ + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &A_sum_gej, &a_sum_scalar); + secp256k1_ge_set_gej(&A_sum_ge, &A_sum_gej); + secp256k1_silentpayments_calculate_input_hash(input_hash, outpoint_smallest36, &A_sum_ge); + + return 1; +} + +int secp256k1_silentpayments_create_public_tweak_data(const secp256k1_context *ctx, secp256k1_pubkey *A_sum, unsigned char *input_hash, const secp256k1_pubkey * const *plain_pubkeys, size_t n_plain_pubkeys, const secp256k1_xonly_pubkey * const *xonly_pubkeys, size_t n_xonly_pubkeys, const unsigned char *outpoint_smallest36) { + size_t i; + secp256k1_ge A_sum_ge, addend; + secp256k1_gej A_sum_gej; + + /* Sanity check inputs */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(A_sum != NULL); + ARG_CHECK(input_hash != NULL); + memset(input_hash, 0, 32); + ARG_CHECK(plain_pubkeys == NULL || n_plain_pubkeys >= 1); + ARG_CHECK(xonly_pubkeys == NULL || n_xonly_pubkeys >= 1); + ARG_CHECK((plain_pubkeys != NULL) || (xonly_pubkeys != NULL)); + ARG_CHECK((n_plain_pubkeys + n_xonly_pubkeys) >= 1); + ARG_CHECK(outpoint_smallest36 != NULL); + + /* Compute input public keys sum: A_sum = A_1 + A_2 + ... + A_n */ + secp256k1_gej_set_infinity(&A_sum_gej); + for (i = 0; i < n_plain_pubkeys; i++) { + secp256k1_pubkey_load(ctx, &addend, plain_pubkeys[i]); + secp256k1_gej_add_ge(&A_sum_gej, &A_sum_gej, &addend); + } + for (i = 0; i < n_xonly_pubkeys; i++) { + secp256k1_xonly_pubkey_load(ctx, &addend, xonly_pubkeys[i]); + secp256k1_gej_add_ge(&A_sum_gej, &A_sum_gej, &addend); + } + if (secp256k1_gej_is_infinity(&A_sum_gej)) { + /* TODO: do we need a special error return code for this case? */ + return 0; + } + secp256k1_ge_set_gej(&A_sum_ge, &A_sum_gej); + secp256k1_pubkey_save(A_sum, &A_sum_ge); + + /* Compute input_hash = hash(outpoint_L || A_sum) */ + secp256k1_silentpayments_calculate_input_hash(input_hash, outpoint_smallest36, &A_sum_ge); + + return 1; +} + +int secp256k1_silentpayments_create_tweaked_pubkey(const secp256k1_context *ctx, secp256k1_pubkey *A_tweaked, const secp256k1_pubkey *A_sum, const unsigned char *input_hash) { + /* Sanity check inputs */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(A_tweaked != NULL); + ARG_CHECK(A_sum != NULL); + ARG_CHECK(input_hash != NULL); + + /* Calculate A_tweaked = input_hash * A_sum */ + *A_tweaked = *A_sum; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, A_tweaked, input_hash)) { + return 0; + } + + return 1; +} + +/* secp256k1_ecdh expects a hash function to be passed in or uses its default + * hashing function. We don't want to hash the ECDH result, so we define a + * custom function which simply returns the pubkey without hashing. + */ +static int secp256k1_silentpayments_ecdh_return_pubkey(unsigned char *output, const unsigned char *x32, const unsigned char *y32, void *data) { + secp256k1_ge point; + secp256k1_fe x, y; + size_t ser_size; + int ser_ret; + + (void)data; + /* Parse point as group element */ + if (!secp256k1_fe_set_b32_limit(&x, x32) || !secp256k1_fe_set_b32_limit(&y, y32)) { + return 0; + } + secp256k1_ge_set_xy(&point, &x, &y); + + /* Serialize as compressed pubkey */ + ser_ret = secp256k1_eckey_pubkey_serialize(&point, output, &ser_size, 1); + VERIFY_CHECK(ser_ret && ser_size == 33); + (void)ser_ret; + + return 1; +} + +int secp256k1_silentpayments_create_shared_secret(const secp256k1_context *ctx, unsigned char *shared_secret33, const secp256k1_pubkey *public_component, const unsigned char *secret_component, const unsigned char *input_hash) { + unsigned char tweaked_secret_component[32]; + + /* Sanity check inputs */ + ARG_CHECK(shared_secret33 != NULL); + memset(shared_secret33, 0, 33); + ARG_CHECK(public_component != NULL); + ARG_CHECK(secret_component != NULL); + + /* Tweak secret component with input hash, if available */ + memcpy(tweaked_secret_component, secret_component, 32); + if (input_hash != NULL) { + if (!secp256k1_ec_seckey_tweak_mul(ctx, tweaked_secret_component, input_hash)) { + return 0; + } + } + + /* Compute shared_secret = tweaked_secret_component * Public_component */ + if (!secp256k1_ecdh(ctx, shared_secret33, public_component, tweaked_secret_component, secp256k1_silentpayments_ecdh_return_pubkey, NULL)) { + return 0; + } + + return 1; +} + +/** Set hash state to the BIP340 tagged hash midstate for "BIP0352/Label". */ +static void secp256k1_silentpayments_sha256_init_label(secp256k1_sha256* hash) { + secp256k1_sha256_initialize(hash); + hash->s[0] = 0x26b95d63ul; + hash->s[1] = 0x8bf1b740ul; + hash->s[2] = 0x10a5986ful; + hash->s[3] = 0x06a387a5ul; + hash->s[4] = 0x2d1c1c30ul; + hash->s[5] = 0xd035951aul; + hash->s[6] = 0x2d7f0f96ul; + hash->s[7] = 0x29e3e0dbul; + + hash->bytes = 64; +} + +int secp256k1_silentpayments_create_label_tweak(const secp256k1_context *ctx, secp256k1_pubkey *label, unsigned char *label_tweak32, const unsigned char *receiver_scan_seckey, unsigned int m) { + secp256k1_sha256 hash; + unsigned char m_serialized[4]; + + /* Sanity check inputs. */ + VERIFY_CHECK(ctx != NULL); + (void)ctx; + VERIFY_CHECK(label != NULL); + VERIFY_CHECK(label_tweak32 != NULL); + VERIFY_CHECK(receiver_scan_seckey != NULL); + + /* Compute label_tweak = hash(ser_256(b_scan) || ser_32(m)) [sha256 with tag "BIP0352/Label"] */ + secp256k1_silentpayments_sha256_init_label(&hash); + secp256k1_sha256_write(&hash, receiver_scan_seckey, 32); + secp256k1_write_be32(m_serialized, m); + secp256k1_sha256_write(&hash, m_serialized, sizeof(m_serialized)); + secp256k1_sha256_finalize(&hash, label_tweak32); + + /* Compute label = label_tweak * G */ + if (!secp256k1_ec_pubkey_create(ctx, label, label_tweak32)) { + return 0; + } + + return 1; +} + +int secp256k1_silentpayments_create_address_spend_pubkey(const secp256k1_context *ctx, unsigned char *l_addr_spend_pubkey33, const secp256k1_pubkey *receiver_spend_pubkey, const secp256k1_pubkey *label) { + secp256k1_ge B_m, label_addend; + secp256k1_gej result_gej; + secp256k1_ge result_ge; + size_t ser_size; + int ser_ret; + + /* Sanity check inputs. */ + VERIFY_CHECK(ctx != NULL); + VERIFY_CHECK(l_addr_spend_pubkey33 != NULL); + VERIFY_CHECK(receiver_spend_pubkey != NULL); + VERIFY_CHECK(label != NULL); + + /* Calculate B_m = B_spend + label */ + secp256k1_pubkey_load(ctx, &B_m, receiver_spend_pubkey); + secp256k1_pubkey_load(ctx, &label_addend, label); + secp256k1_gej_set_ge(&result_gej, &B_m); + secp256k1_gej_add_ge_var(&result_gej, &result_gej, &label_addend, NULL); + + /* Serialize B_m */ + secp256k1_ge_set_gej(&result_ge, &result_gej); + ser_ret = secp256k1_eckey_pubkey_serialize(&result_ge, l_addr_spend_pubkey33, &ser_size, 1); + VERIFY_CHECK(ser_ret && ser_size == 33); + (void)ser_ret; + + return 1; +} + +/** Set hash state to the BIP340 tagged hash midstate for "BIP0352/SharedSecret". */ +static void secp256k1_silentpayments_sha256_init_sharedsecret(secp256k1_sha256* hash) { + secp256k1_sha256_initialize(hash); + hash->s[0] = 0x88831537ul; + hash->s[1] = 0x5127079bul; + hash->s[2] = 0x69c2137bul; + hash->s[3] = 0xab0303e6ul; + hash->s[4] = 0x98fa21faul; + hash->s[5] = 0x4a888523ul; + hash->s[6] = 0xbd99daabul; + hash->s[7] = 0xf25e5e0aul; + + hash->bytes = 64; +} + +static void secp256k1_silentpayments_create_t_k(secp256k1_scalar *t_k_scalar, const unsigned char *shared_secret33, unsigned int k) { + secp256k1_sha256 hash; + unsigned char hash_ser[32]; + unsigned char k_serialized[4]; + + /* Compute t_k = hash(shared_secret || ser_32(k)) [sha256 with tag "BIP0352/SharedSecret"] */ + secp256k1_silentpayments_sha256_init_sharedsecret(&hash); + secp256k1_sha256_write(&hash, shared_secret33, 33); + secp256k1_write_be32(k_serialized, k); + secp256k1_sha256_write(&hash, k_serialized, sizeof(k_serialized)); + secp256k1_sha256_finalize(&hash, hash_ser); + secp256k1_scalar_set_b32(t_k_scalar, hash_ser, NULL); +} + +int secp256k1_silentpayments_sender_create_output_pubkey(const secp256k1_context *ctx, secp256k1_xonly_pubkey *P_output_xonly, const unsigned char *shared_secret33, const secp256k1_pubkey *receiver_spend_pubkey, unsigned int k) { + secp256k1_ge P_output_ge; + secp256k1_scalar t_k_scalar; + + /* Sanity check inputs */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(P_output_xonly != NULL); + ARG_CHECK(shared_secret33 != NULL); + ARG_CHECK(receiver_spend_pubkey != NULL); + + /* Calculate and return P_output_xonly = B_spend + t_k * G */ + secp256k1_silentpayments_create_t_k(&t_k_scalar, shared_secret33, k); + secp256k1_pubkey_load(ctx, &P_output_ge, receiver_spend_pubkey); + if (!secp256k1_eckey_pubkey_tweak_add(&P_output_ge, &t_k_scalar)) { + return 0; + } + secp256k1_xonly_pubkey_save(P_output_xonly, &P_output_ge); + + return 1; +} + +int secp256k1_silentpayments_receiver_scan_output(const secp256k1_context *ctx, int *direct_match, unsigned char *t_k, secp256k1_silentpayments_label_data *label_data, const unsigned char *shared_secret33, const secp256k1_pubkey *receiver_spend_pubkey, unsigned int k, const secp256k1_xonly_pubkey *tx_output) { + secp256k1_scalar t_k_scalar; + secp256k1_ge P_output_ge; + secp256k1_xonly_pubkey P_output_xonly; + + /* Sanity check inputs */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(direct_match != NULL); + ARG_CHECK(t_k != NULL); + ARG_CHECK(shared_secret33 != NULL); + ARG_CHECK(receiver_spend_pubkey != NULL); + ARG_CHECK(tx_output != NULL); + + /* Calculate t_k = hash(shared_secret || ser_32(k)) */ + secp256k1_silentpayments_create_t_k(&t_k_scalar, shared_secret33, k); + secp256k1_scalar_get_b32(t_k, &t_k_scalar); + + /* Calculate P_output = B_spend + t_k * G */ + secp256k1_pubkey_load(ctx, &P_output_ge, receiver_spend_pubkey); + if (!secp256k1_eckey_pubkey_tweak_add(&P_output_ge, &t_k_scalar)) { + return 0; + } + + /* If the calculated output matches the one from the tx, we have a direct match and can + * return without labels calculation (one of the two would result in point of infinity) */ + secp256k1_xonly_pubkey_save(&P_output_xonly, &P_output_ge); + if (secp256k1_xonly_pubkey_cmp(ctx, &P_output_xonly, tx_output) == 0) { + *direct_match = 1; + return 1; + } + *direct_match = 0; + + /* If desired, also calculate label candidates */ + if (label_data != NULL) { + secp256k1_ge P_output_negated_ge, tx_output_ge; + secp256k1_ge label_ge; + secp256k1_gej label_gej; + + /* Calculate negated P_output (common addend) first */ + secp256k1_ge_neg(&P_output_negated_ge, &P_output_ge); + + /* Calculate first scan label candidate: label1 = tx_output - P_output */ + secp256k1_xonly_pubkey_load(ctx, &tx_output_ge, tx_output); + secp256k1_gej_set_ge(&label_gej, &tx_output_ge); + secp256k1_gej_add_ge_var(&label_gej, &label_gej, &P_output_negated_ge, NULL); + secp256k1_ge_set_gej(&label_ge, &label_gej); + secp256k1_pubkey_save(&label_data->label, &label_ge); + + /* Calculate second scan label candidate: label2 = -tx_output - P_output */ + secp256k1_gej_set_ge(&label_gej, &tx_output_ge); + secp256k1_gej_neg(&label_gej, &label_gej); + secp256k1_gej_add_ge_var(&label_gej, &label_gej, &P_output_negated_ge, NULL); + secp256k1_ge_set_gej(&label_ge, &label_gej); + secp256k1_pubkey_save(&label_data->label_negated, &label_ge); + } + + return 1; +} + +int secp256k1_silentpayments_create_output_seckey(const secp256k1_context *ctx, unsigned char *output_seckey, const unsigned char *receiver_spend_seckey, const unsigned char *t_k, const unsigned char *label_tweak) { + /* Sanity check inputs */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(output_seckey != NULL); + memset(output_seckey, 0, 32); + ARG_CHECK(receiver_spend_seckey != NULL); + ARG_CHECK(t_k != NULL); + + /* Compute d = (b_spend + t_k) mod n */ + memcpy(output_seckey, receiver_spend_seckey, 32); + if (!secp256k1_ec_seckey_tweak_add(ctx, output_seckey, t_k)) { + return 0; + } + + if (label_tweak != NULL) { + if (!secp256k1_ec_seckey_tweak_add(ctx, output_seckey, label_tweak)) { + return 0; + } + } + + return 1; +} + +#endif diff --git a/src/modules/silentpayments/tests_impl.h b/src/modules/silentpayments/tests_impl.h new file mode 100644 index 0000000000..0fd434b75b --- /dev/null +++ b/src/modules/silentpayments/tests_impl.h @@ -0,0 +1,235 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_SILENTPAYMENTS_TESTS_H +#define SECP256K1_MODULE_SILENTPAYMENTS_TESTS_H + +#include "../../../include/secp256k1_silentpayments.h" +#include "../../../src/modules/silentpayments/vectors.h" + +void run_silentpayments_test_vector_send(const struct bip352_test_vector *test, const unsigned char *a_sum, const unsigned char *input_hash) { + secp256k1_pubkey last_scan_pubkey; /* needed for grouping outputs */ + unsigned char s_one[32] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}; + size_t i; + unsigned int k = 0; + + /* Check that sender creates expected outputs */ + CHECK(secp256k1_ec_pubkey_create(CTX, &last_scan_pubkey, s_one)); /* dirty hack: set last_scan_pubkey to generator point initially */ + for (i = 0; i < test->num_recipient_outputs; i++) { + secp256k1_pubkey receiver_scan_pubkey; + secp256k1_pubkey receiver_spend_pubkey; + unsigned char shared_secret[33]; + secp256k1_xonly_pubkey created_output_xonly; + unsigned char created_output[32]; + + CHECK(secp256k1_ec_pubkey_parse(CTX, &receiver_scan_pubkey, test->receiver_pubkeys[i].scan_pubkey, 33)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &receiver_spend_pubkey, test->receiver_pubkeys[i].spend_pubkey, 33)); + if (secp256k1_ec_pubkey_cmp(CTX, &receiver_scan_pubkey, &last_scan_pubkey) != 0) { + k = 0; + } else { + k++; + } + CHECK(secp256k1_silentpayments_create_shared_secret(CTX, shared_secret, &receiver_scan_pubkey, a_sum, input_hash)); + CHECK(secp256k1_silentpayments_sender_create_output_pubkey(CTX, &created_output_xonly, shared_secret, + &receiver_spend_pubkey, k)); + CHECK(secp256k1_xonly_pubkey_serialize(CTX, created_output, &created_output_xonly)); + CHECK(secp256k1_memcmp_var(created_output, test->recipient_outputs[i], 32) == 0); + + last_scan_pubkey = receiver_scan_pubkey; + } +} + +void run_silentpayments_test_vector_receive(const struct bip352_test_vector *test, const secp256k1_pubkey *A_sum, const unsigned char *input_hash) { + struct label_cache_entry { + secp256k1_pubkey label; + unsigned char label_tweak[32]; + }; + struct labels_cache { + size_t entries_used; + struct label_cache_entry entries[10]; + }; + + struct labels_cache labels_cache; + secp256k1_pubkey receiver_scan_pubkey; + secp256k1_pubkey receiver_spend_pubkey; + unsigned char shared_secret[33]; + size_t i; + unsigned int k = 0; + int outputs_to_check[10] = {0}; + size_t num_found_outputs = 0; + unsigned char found_output_pubkeys[10][32]; + unsigned char found_seckey_tweaks[10][32]; + unsigned char found_signatures[10][64]; + + /* scan / spend pubkeys are not in the given data of the receiver part, so let's compute them */ + CHECK(secp256k1_ec_pubkey_create(CTX, &receiver_scan_pubkey, test->scan_seckey)); + CHECK(secp256k1_ec_pubkey_create(CTX, &receiver_spend_pubkey, test->spend_seckey)); + + /* create shared secret */ + { + unsigned char shared_secret_fullnode[33]; + unsigned char shared_secret_lightclient[33]; + secp256k1_pubkey A_tweaked; + + CHECK(secp256k1_silentpayments_create_shared_secret(CTX, shared_secret_fullnode, A_sum, test->scan_seckey, input_hash)); + /* check that creating shared secret in light client / index mode (with intermediate A_tweaked) leads to the same result */ + CHECK(secp256k1_silentpayments_create_tweaked_pubkey(CTX, &A_tweaked, A_sum, input_hash)); + CHECK(secp256k1_silentpayments_create_shared_secret(CTX, shared_secret_lightclient, &A_tweaked, test->scan_seckey, NULL)); + CHECK(secp256k1_memcmp_var(shared_secret_fullnode, shared_secret_lightclient, 33) == 0); + + memcpy(shared_secret, shared_secret_fullnode, 33); + } + + /* create labels cache */ + labels_cache.entries_used = 0; + for (i = 0; i < test->num_labels; i++) { + unsigned int m = test->label_integers[i]; + struct label_cache_entry *cache_entry = &labels_cache.entries[labels_cache.entries_used]; + CHECK(secp256k1_silentpayments_create_label_tweak(CTX, &cache_entry->label, cache_entry->label_tweak, test->scan_seckey, m)); + labels_cache.entries_used++; + } + + /* scan through outputs, check that expected found outputs match */ + for (i = 0; i < test->num_to_scan_outputs; i++) { + outputs_to_check[i] = 1; + } + + while(1) { + int found_something = 0; + + for (i = 0; i < test->num_to_scan_outputs; i++) { + int direct_match, output_detected = 0; + unsigned char t_k[32]; + secp256k1_silentpayments_label_data label_candidates; + secp256k1_xonly_pubkey to_scan_output; + + if (outputs_to_check[i] == 0) + continue; + + CHECK(secp256k1_xonly_pubkey_parse(CTX, &to_scan_output, test->to_scan_outputs[i])); + CHECK(secp256k1_silentpayments_receiver_scan_output(CTX, &direct_match, t_k, &label_candidates, + shared_secret, &receiver_spend_pubkey, k, &to_scan_output)); + if (direct_match) { + output_detected = 1; + } else { + /* no direct match, check labels cache */ + secp256k1_pubkey *label_found = NULL; + unsigned char *label_tweak = NULL; + + size_t l; + for (l = 0; l < labels_cache.entries_used; l++) { + if (secp256k1_ec_pubkey_cmp(CTX, &label_candidates.label, &labels_cache.entries[l].label) == 0 || + secp256k1_ec_pubkey_cmp(CTX, &label_candidates.label_negated, &labels_cache.entries[l].label) == 0) { + label_found = &labels_cache.entries[l].label; + label_tweak = labels_cache.entries[l].label_tweak; + break; + } + } + if (label_found != NULL) { + CHECK(secp256k1_ec_seckey_tweak_add(CTX, t_k, label_tweak)); + output_detected = 1; + } + } + if (output_detected) { + unsigned char full_seckey[32]; + secp256k1_keypair keypair; + unsigned char signature[64]; + const unsigned char msg32[32] = /* sha256("message") */ + {0xab,0x53,0x0a,0x13,0xe4,0x59,0x14,0x98,0x2b,0x79,0xf9,0xb7,0xe3,0xfb,0xa9,0x94, + 0xcf,0xd1,0xf3,0xfb,0x22,0xf7,0x1c,0xea,0x1a,0xfb,0xf0,0x2b,0x46,0x0c,0x6d,0x1d}; + const unsigned char aux32[32] = /* sha256("random auxiliary data") */ + {0x0b,0x3f,0xdd,0xfd,0x67,0xbf,0x76,0xae,0x76,0x39,0xee,0x73,0x5b,0x70,0xff,0x15, + 0x83,0xfd,0x92,0x48,0xc0,0x57,0xd2,0x86,0x07,0xa2,0x15,0xf4,0x0b,0x0a,0x3e,0xcc}; + outputs_to_check[i] = 0; + memcpy(found_output_pubkeys[num_found_outputs], test->to_scan_outputs[i], 32); + memcpy(found_seckey_tweaks[num_found_outputs], t_k, 32); + CHECK(secp256k1_silentpayments_create_output_seckey(CTX, full_seckey, test->spend_seckey, t_k, NULL)); + CHECK(secp256k1_keypair_create(CTX, &keypair, full_seckey)); + CHECK(secp256k1_schnorrsig_sign32(CTX, signature, msg32, &keypair, aux32)); + memcpy(found_signatures[num_found_outputs], signature, 64); + num_found_outputs++; + k++; + found_something = 1; + } + } + if (!found_something) + break; + } + + /* compare expected and scanned outputs (including calculated seckey tweaks and signatures) */ + CHECK(num_found_outputs == test->num_found_outputs); + for (i = 0; i < num_found_outputs; i++) { + CHECK(secp256k1_memcmp_var(found_output_pubkeys[i], test->found_output_pubkeys[i], 32) == 0); + CHECK(secp256k1_memcmp_var(found_seckey_tweaks[i], test->found_seckey_tweaks[i], 32) == 0); + CHECK(secp256k1_memcmp_var(found_signatures[i], test->found_signatures[i], 64) == 0); + } +} + +void run_silentpayments_test_vectors(void) { + size_t i, j; + + for (i = 0; i < sizeof(bip352_test_vectors) / sizeof(bip352_test_vectors[0]); i++) { + const struct bip352_test_vector *test = &bip352_test_vectors[i]; + unsigned char a_sum[32]; + secp256k1_pubkey A_sum; + unsigned char input_hash_sender[32]; + unsigned char input_hash_receiver[32]; + + /* Calculate private tweak data (sender perspective) */ + { + unsigned char const *plain_seckeys[MAX_INPUTS_PER_TEST_CASE]; + unsigned char const *taproot_seckeys[MAX_INPUTS_PER_TEST_CASE]; + for (j = 0; j < test->num_plain_inputs; j++) { + plain_seckeys[j] = test->plain_seckeys[j]; + } + for (j = 0; j < test->num_taproot_inputs; j++) { + taproot_seckeys[j] = test->taproot_seckeys[j]; + } + CHECK(secp256k1_silentpayments_create_private_tweak_data(CTX, a_sum, input_hash_sender, + test->num_plain_inputs > 0 ? plain_seckeys : NULL, test->num_plain_inputs, + test->num_taproot_inputs > 0 ? taproot_seckeys : NULL, test->num_taproot_inputs, + test->outpoint_smallest)); + } + + /* Calculate public tweak data (receiver perspective) */ + { + secp256k1_pubkey plain_pubkeys_objs[MAX_INPUTS_PER_TEST_CASE]; + secp256k1_xonly_pubkey xonly_pubkeys_objs[MAX_INPUTS_PER_TEST_CASE]; + secp256k1_pubkey const *plain_pubkeys[MAX_INPUTS_PER_TEST_CASE]; + secp256k1_xonly_pubkey const *xonly_pubkeys[MAX_INPUTS_PER_TEST_CASE]; + for (j = 0; j < test->num_plain_inputs; j++) { + CHECK(secp256k1_ec_pubkey_parse(CTX, &plain_pubkeys_objs[j], test->plain_pubkeys[j], 33)); + plain_pubkeys[j] = &plain_pubkeys_objs[j]; + } + for (j = 0; j < test->num_taproot_inputs; j++) { + CHECK(secp256k1_xonly_pubkey_parse(CTX, &xonly_pubkeys_objs[j], test->xonly_pubkeys[j])); + xonly_pubkeys[j] = &xonly_pubkeys_objs[j]; + } + CHECK(secp256k1_silentpayments_create_public_tweak_data(CTX, &A_sum, input_hash_receiver, + test->num_plain_inputs > 0 ? plain_pubkeys : NULL, test->num_plain_inputs, + test->num_taproot_inputs > 0 ? xonly_pubkeys : NULL, test->num_taproot_inputs, + test->outpoint_smallest)); + } + + /* First sanity check: verify that a_sum * G == A_sum, and that input hashes match */ + { + secp256k1_pubkey A_sum_calculated; + CHECK(secp256k1_ec_pubkey_create(CTX, &A_sum_calculated, a_sum)); + CHECK(secp256k1_ec_pubkey_cmp(CTX, &A_sum, &A_sum_calculated) == 0); + CHECK(secp256k1_memcmp_var(input_hash_sender, input_hash_receiver, 32) == 0); + } + + run_silentpayments_test_vector_send(test, a_sum, input_hash_sender); + run_silentpayments_test_vector_receive(test, &A_sum, input_hash_receiver); + } +} + +void run_silentpayments_tests(void) { + run_silentpayments_test_vectors(); + + /* TODO: add a few manual tests here, that target the ECC-related parts of silent payments */ +} + +#endif diff --git a/src/modules/silentpayments/vectors.h b/src/modules/silentpayments/vectors.h new file mode 100644 index 0000000000..49ab1e6c1f --- /dev/null +++ b/src/modules/silentpayments/vectors.h @@ -0,0 +1,1979 @@ +/* Note: this file was autogenerated using tests_silentpayments_generate.py. Do not edit. */ +#define SECP256K1_SILENTPAYMENTS_NUMBER_TESTVECTORS (23) + +#define MAX_INPUTS_PER_TEST_CASE 3 +#define MAX_OUTPUTS_PER_TEST_CASE 4 + +struct bip352_receiver_addressdata { + unsigned char scan_pubkey[33]; + unsigned char spend_pubkey[33]; +}; + +struct bip352_test_vector { + /* Inputs (private keys / public keys + smallest outpoint) */ + size_t num_plain_inputs; + unsigned char plain_seckeys[MAX_INPUTS_PER_TEST_CASE][32]; + unsigned char plain_pubkeys[MAX_INPUTS_PER_TEST_CASE][33]; + + size_t num_taproot_inputs; + unsigned char taproot_seckeys[MAX_INPUTS_PER_TEST_CASE][32]; + unsigned char xonly_pubkeys[MAX_INPUTS_PER_TEST_CASE][32]; + + unsigned char outpoint_smallest[36]; + + /* Given sender data (pubkeys encoded per output address to send to) */ + size_t num_recipient_outputs; + struct bip352_receiver_addressdata receiver_pubkeys[MAX_OUTPUTS_PER_TEST_CASE]; + + /* Expected sender data */ + unsigned char recipient_outputs[MAX_OUTPUTS_PER_TEST_CASE][32]; + + /* Given receiver data */ + unsigned char scan_seckey[32]; + unsigned char spend_seckey[32]; + size_t num_to_scan_outputs; + unsigned char to_scan_outputs[MAX_OUTPUTS_PER_TEST_CASE][32]; + size_t num_labels; + unsigned int label_integers[MAX_OUTPUTS_PER_TEST_CASE]; + + /* Expected receiver data */ + size_t num_found_outputs; + unsigned char found_output_pubkeys[MAX_OUTPUTS_PER_TEST_CASE][32]; + unsigned char found_seckey_tweaks[MAX_OUTPUTS_PER_TEST_CASE][32]; + unsigned char found_signatures[MAX_OUTPUTS_PER_TEST_CASE][64]; +}; + +static const struct bip352_test_vector bip352_test_vectors[SECP256K1_SILENTPAYMENTS_NUMBER_TESTVECTORS] = { + /* ----- Simple send: two inputs (1) ----- */ + { + 2, + { /* input plain seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + {0x93,0xf5,0xed,0x90,0x7a,0xd5,0xb2,0xbd,0xbb,0xdc,0xb5,0xd9,0x11,0x6e,0xbc,0x0a,0x4e,0x1f,0x92,0xf9,0x10,0xd5,0x26,0x02,0x37,0xfa,0x45,0xa9,0x40,0x8a,0xad,0x16}, + "" + }, + { /* input plain pubkeys */ + {0x02,0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + {0x03,0xbd,0x85,0x68,0x5d,0x03,0xd1,0x11,0x69,0x9b,0x15,0xd0,0x46,0x31,0x9f,0xeb,0xe7,0x7f,0x8d,0xe5,0x28,0x6e,0x9e,0x51,0x27,0x03,0xcd,0xee,0x1b,0xf3,0xbe,0x37,0x92}, + "" + }, + 0, + { /* input taproot seckeys */ + "", + "", + "" + }, + { /* input x-only pubkeys */ + "", + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 1, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + "", + "" + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0x3e,0x9f,0xce,0x73,0xd4,0xe7,0x7a,0x48,0x09,0x90,0x8e,0x3c,0x3a,0x2e,0x54,0xee,0x14,0x7b,0x93,0x12,0xdc,0x50,0x44,0xa1,0x93,0xd1,0xfc,0x85,0xde,0x46,0xe3,0xc1}, + "", + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 1, + { /* outputs to scan */ + {0x3e,0x9f,0xce,0x73,0xd4,0xe7,0x7a,0x48,0x09,0x90,0x8e,0x3c,0x3a,0x2e,0x54,0xee,0x14,0x7b,0x93,0x12,0xdc,0x50,0x44,0xa1,0x93,0xd1,0xfc,0x85,0xde,0x46,0xe3,0xc1}, + "", + "", + "" + }, + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 1, + { + {0x3e,0x9f,0xce,0x73,0xd4,0xe7,0x7a,0x48,0x09,0x90,0x8e,0x3c,0x3a,0x2e,0x54,0xee,0x14,0x7b,0x93,0x12,0xdc,0x50,0x44,0xa1,0x93,0xd1,0xfc,0x85,0xde,0x46,0xe3,0xc1}, + "", + "", + "" + }, + { + {0xf4,0x38,0xb4,0x01,0x79,0xa3,0xc4,0x26,0x2d,0xe1,0x29,0x86,0xc0,0xe6,0xcc,0xe0,0x63,0x40,0x07,0xcd,0xc7,0x9c,0x1d,0xcd,0x3e,0x20,0xb9,0xeb,0xc2,0xe7,0xee,0xf6}, + "", + "", + "" + }, + { + {0x74,0xf8,0x5b,0x85,0x63,0x37,0xfb,0xe8,0x37,0x64,0x3b,0x86,0xf4,0x62,0x11,0x81,0x59,0xf9,0x3a,0xc4,0xac,0xc2,0x67,0x15,0x22,0xf2,0x7e,0x8f,0x67,0xb0,0x79,0x95,0x91,0x95,0xcc,0xc7,0xa5,0xdb,0xee,0x39,0x6d,0x29,0x09,0xf5,0xd6,0x80,0xd6,0xe3,0x0c,0xda,0x73,0x59,0xaa,0x27,0x55,0x82,0x25,0x09,0xb7,0x0d,0x6b,0x06,0x87,0xa1}, + "", + "", + "" + } + }, + + /* ----- Simple send: two inputs, order reversed (2) ----- */ + { + 2, + { /* input plain seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + {0x93,0xf5,0xed,0x90,0x7a,0xd5,0xb2,0xbd,0xbb,0xdc,0xb5,0xd9,0x11,0x6e,0xbc,0x0a,0x4e,0x1f,0x92,0xf9,0x10,0xd5,0x26,0x02,0x37,0xfa,0x45,0xa9,0x40,0x8a,0xad,0x16}, + "" + }, + { /* input plain pubkeys */ + {0x02,0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + {0x03,0xbd,0x85,0x68,0x5d,0x03,0xd1,0x11,0x69,0x9b,0x15,0xd0,0x46,0x31,0x9f,0xeb,0xe7,0x7f,0x8d,0xe5,0x28,0x6e,0x9e,0x51,0x27,0x03,0xcd,0xee,0x1b,0xf3,0xbe,0x37,0x92}, + "" + }, + 0, + { /* input taproot seckeys */ + "", + "", + "" + }, + { /* input x-only pubkeys */ + "", + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 1, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + "", + "" + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0x3e,0x9f,0xce,0x73,0xd4,0xe7,0x7a,0x48,0x09,0x90,0x8e,0x3c,0x3a,0x2e,0x54,0xee,0x14,0x7b,0x93,0x12,0xdc,0x50,0x44,0xa1,0x93,0xd1,0xfc,0x85,0xde,0x46,0xe3,0xc1}, + "", + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 1, + { /* outputs to scan */ + {0x3e,0x9f,0xce,0x73,0xd4,0xe7,0x7a,0x48,0x09,0x90,0x8e,0x3c,0x3a,0x2e,0x54,0xee,0x14,0x7b,0x93,0x12,0xdc,0x50,0x44,0xa1,0x93,0xd1,0xfc,0x85,0xde,0x46,0xe3,0xc1}, + "", + "", + "" + }, + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 1, + { + {0x3e,0x9f,0xce,0x73,0xd4,0xe7,0x7a,0x48,0x09,0x90,0x8e,0x3c,0x3a,0x2e,0x54,0xee,0x14,0x7b,0x93,0x12,0xdc,0x50,0x44,0xa1,0x93,0xd1,0xfc,0x85,0xde,0x46,0xe3,0xc1}, + "", + "", + "" + }, + { + {0xf4,0x38,0xb4,0x01,0x79,0xa3,0xc4,0x26,0x2d,0xe1,0x29,0x86,0xc0,0xe6,0xcc,0xe0,0x63,0x40,0x07,0xcd,0xc7,0x9c,0x1d,0xcd,0x3e,0x20,0xb9,0xeb,0xc2,0xe7,0xee,0xf6}, + "", + "", + "" + }, + { + {0x74,0xf8,0x5b,0x85,0x63,0x37,0xfb,0xe8,0x37,0x64,0x3b,0x86,0xf4,0x62,0x11,0x81,0x59,0xf9,0x3a,0xc4,0xac,0xc2,0x67,0x15,0x22,0xf2,0x7e,0x8f,0x67,0xb0,0x79,0x95,0x91,0x95,0xcc,0xc7,0xa5,0xdb,0xee,0x39,0x6d,0x29,0x09,0xf5,0xd6,0x80,0xd6,0xe3,0x0c,0xda,0x73,0x59,0xaa,0x27,0x55,0x82,0x25,0x09,0xb7,0x0d,0x6b,0x06,0x87,0xa1}, + "", + "", + "" + } + }, + + /* ----- Simple send: two inputs from the same transaction (3) ----- */ + { + 2, + { /* input plain seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + {0x93,0xf5,0xed,0x90,0x7a,0xd5,0xb2,0xbd,0xbb,0xdc,0xb5,0xd9,0x11,0x6e,0xbc,0x0a,0x4e,0x1f,0x92,0xf9,0x10,0xd5,0x26,0x02,0x37,0xfa,0x45,0xa9,0x40,0x8a,0xad,0x16}, + "" + }, + { /* input plain pubkeys */ + {0x02,0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + {0x03,0xbd,0x85,0x68,0x5d,0x03,0xd1,0x11,0x69,0x9b,0x15,0xd0,0x46,0x31,0x9f,0xeb,0xe7,0x7f,0x8d,0xe5,0x28,0x6e,0x9e,0x51,0x27,0x03,0xcd,0xee,0x1b,0xf3,0xbe,0x37,0x92}, + "" + }, + 0, + { /* input taproot seckeys */ + "", + "", + "" + }, + { /* input x-only pubkeys */ + "", + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x03,0x00,0x00,0x00}, + 1, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + "", + "" + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0x79,0xe7,0x1b,0xaa,0x2b,0xa3,0xfc,0x66,0x39,0x6d,0xe3,0xa0,0x4f,0x16,0x8c,0x7b,0xf2,0x4d,0x68,0x70,0xec,0x88,0xca,0x87,0x77,0x54,0x79,0x0c,0x1d,0xb3,0x57,0xb6}, + "", + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 1, + { /* outputs to scan */ + {0x79,0xe7,0x1b,0xaa,0x2b,0xa3,0xfc,0x66,0x39,0x6d,0xe3,0xa0,0x4f,0x16,0x8c,0x7b,0xf2,0x4d,0x68,0x70,0xec,0x88,0xca,0x87,0x77,0x54,0x79,0x0c,0x1d,0xb3,0x57,0xb6}, + "", + "", + "" + }, + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 1, + { + {0x79,0xe7,0x1b,0xaa,0x2b,0xa3,0xfc,0x66,0x39,0x6d,0xe3,0xa0,0x4f,0x16,0x8c,0x7b,0xf2,0x4d,0x68,0x70,0xec,0x88,0xca,0x87,0x77,0x54,0x79,0x0c,0x1d,0xb3,0x57,0xb6}, + "", + "", + "" + }, + { + {0x48,0x51,0x45,0x5b,0xfb,0xe1,0xab,0x4f,0x80,0x15,0x65,0x70,0xaa,0x45,0x06,0x32,0x01,0xaa,0x5c,0x9e,0x1b,0x1d,0xcd,0x29,0xf0,0xf8,0xc3,0x3d,0x10,0xbf,0x77,0xae}, + "", + "", + "" + }, + { + {0x10,0x33,0x2e,0xea,0x80,0x8b,0x6a,0x13,0xf7,0x00,0x59,0xa8,0xa7,0x31,0x95,0x80,0x8d,0xb7,0x82,0x01,0x29,0x07,0xf5,0xba,0x32,0xb6,0xea,0xe6,0x6a,0x2f,0x66,0xb4,0xf6,0x51,0x47,0xe2,0xb9,0x68,0xa1,0x67,0x8c,0x5f,0x73,0xd5,0x7d,0x5d,0x19,0x5d,0xba,0xf6,0x67,0xb6,0x06,0xff,0x80,0xc8,0x49,0x0e,0xac,0x1f,0x3b,0x71,0x06,0x57}, + "", + "", + "" + } + }, + + /* ----- Simple send: two inputs from the same transaction, order reversed (4) ----- */ + { + 2, + { /* input plain seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + {0x93,0xf5,0xed,0x90,0x7a,0xd5,0xb2,0xbd,0xbb,0xdc,0xb5,0xd9,0x11,0x6e,0xbc,0x0a,0x4e,0x1f,0x92,0xf9,0x10,0xd5,0x26,0x02,0x37,0xfa,0x45,0xa9,0x40,0x8a,0xad,0x16}, + "" + }, + { /* input plain pubkeys */ + {0x02,0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + {0x03,0xbd,0x85,0x68,0x5d,0x03,0xd1,0x11,0x69,0x9b,0x15,0xd0,0x46,0x31,0x9f,0xeb,0xe7,0x7f,0x8d,0xe5,0x28,0x6e,0x9e,0x51,0x27,0x03,0xcd,0xee,0x1b,0xf3,0xbe,0x37,0x92}, + "" + }, + 0, + { /* input taproot seckeys */ + "", + "", + "" + }, + { /* input x-only pubkeys */ + "", + "", + "" + }, + /* smallest outpoint */ + {0x8d,0xd4,0xf5,0xfb,0xd5,0xe9,0x80,0xfc,0x02,0xf3,0x5c,0x6c,0xe1,0x45,0x93,0x5b,0x11,0xe2,0x84,0x60,0x5b,0xf5,0x99,0xa1,0x3c,0x6d,0x41,0x5d,0xb5,0x5d,0x07,0xa1,0x03,0x00,0x00,0x00}, + 1, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + "", + "" + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0xf4,0xc2,0xda,0x80,0x7f,0x89,0xcb,0x15,0x01,0xf1,0xa7,0x73,0x22,0xa8,0x95,0xac,0xfb,0x93,0xc2,0x8e,0x08,0xed,0x27,0x24,0xd2,0xbe,0xb8,0xe4,0x45,0x39,0xba,0x38}, + "", + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 1, + { /* outputs to scan */ + {0xf4,0xc2,0xda,0x80,0x7f,0x89,0xcb,0x15,0x01,0xf1,0xa7,0x73,0x22,0xa8,0x95,0xac,0xfb,0x93,0xc2,0x8e,0x08,0xed,0x27,0x24,0xd2,0xbe,0xb8,0xe4,0x45,0x39,0xba,0x38}, + "", + "", + "" + }, + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 1, + { + {0xf4,0xc2,0xda,0x80,0x7f,0x89,0xcb,0x15,0x01,0xf1,0xa7,0x73,0x22,0xa8,0x95,0xac,0xfb,0x93,0xc2,0x8e,0x08,0xed,0x27,0x24,0xd2,0xbe,0xb8,0xe4,0x45,0x39,0xba,0x38}, + "", + "", + "" + }, + { + {0xab,0x0c,0x9b,0x87,0x18,0x1b,0xf5,0x27,0x87,0x9f,0x48,0xdb,0x9f,0x14,0xa0,0x22,0x33,0x61,0x9b,0x98,0x6f,0x8e,0x8f,0x2d,0x5d,0x40,0x8c,0xe6,0x8a,0x70,0x9f,0x51}, + "", + "", + "" + }, + { + {0x39,0x8a,0x97,0x90,0x86,0x57,0x91,0xa9,0xdb,0x41,0xa8,0x01,0x5a,0xfa,0xd3,0xa4,0x7d,0x60,0xfe,0xc5,0x08,0x6c,0x50,0x55,0x78,0x06,0xa4,0x9a,0x1b,0xc0,0x38,0x80,0x86,0x32,0xb8,0xfe,0x67,0x9a,0x7b,0xb6,0x5f,0xc6,0xb4,0x55,0xbe,0x99,0x45,0x02,0xee,0xd8,0x49,0xf1,0xda,0x37,0x29,0xcd,0x94,0x8f,0xc7,0xbe,0x73,0xd6,0x72,0x95}, + "", + "", + "" + } + }, + + /* ----- Single recipient: multiple UTXOs from the same public key (5) ----- */ + { + 2, + { /* input plain seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + "" + }, + { /* input plain pubkeys */ + {0x02,0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + {0x02,0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + "" + }, + 0, + { /* input taproot seckeys */ + "", + "", + "" + }, + { /* input x-only pubkeys */ + "", + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 1, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + "", + "" + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0x54,0x8a,0xe5,0x5c,0x8e,0xec,0x1e,0x73,0x6e,0x8d,0x3e,0x52,0x0f,0x01,0x1f,0x1f,0x42,0xa5,0x6d,0x16,0x61,0x16,0xad,0x21,0x0b,0x39,0x37,0x59,0x9f,0x87,0xf5,0x66}, + "", + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 1, + { /* outputs to scan */ + {0x54,0x8a,0xe5,0x5c,0x8e,0xec,0x1e,0x73,0x6e,0x8d,0x3e,0x52,0x0f,0x01,0x1f,0x1f,0x42,0xa5,0x6d,0x16,0x61,0x16,0xad,0x21,0x0b,0x39,0x37,0x59,0x9f,0x87,0xf5,0x66}, + "", + "", + "" + }, + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 1, + { + {0x54,0x8a,0xe5,0x5c,0x8e,0xec,0x1e,0x73,0x6e,0x8d,0x3e,0x52,0x0f,0x01,0x1f,0x1f,0x42,0xa5,0x6d,0x16,0x61,0x16,0xad,0x21,0x0b,0x39,0x37,0x59,0x9f,0x87,0xf5,0x66}, + "", + "", + "" + }, + { + {0xf0,0x32,0x69,0x5e,0x26,0x36,0x61,0x9e,0xfa,0x52,0x3f,0xff,0xaa,0x9e,0xf9,0x3c,0x88,0x02,0x29,0x91,0x81,0xfd,0x04,0x61,0x91,0x3c,0x1b,0x8d,0xaf,0x97,0x84,0xcd}, + "", + "", + "" + }, + { + {0xf2,0x38,0x38,0x6c,0x5d,0x5e,0x54,0x44,0xf8,0xd2,0xc7,0x5a,0xab,0xbc,0xb2,0x8c,0x34,0x6f,0x20,0x8c,0x76,0xf6,0x08,0x23,0xf5,0xde,0x3b,0x67,0xb7,0x9e,0x0e,0xc7,0x2e,0xa5,0xde,0x2d,0x7c,0xae,0xc3,0x14,0xe0,0x97,0x1d,0x34,0x54,0xf1,0x22,0xdd,0xa3,0x42,0xb3,0xee,0xde,0x01,0xb3,0x85,0x7e,0x83,0x65,0x4e,0x36,0xb2,0x5f,0x76}, + "", + "", + "" + } + }, + + /* ----- Single recipient: taproot only inputs with even y-values (6) ----- */ + { + 0, + { /* input plain seckeys */ + "", + "", + "" + }, + { /* input plain pubkeys */ + "", + "", + "" + }, + 2, + { /* input taproot seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + {0xfc,0x87,0x16,0xa9,0x7a,0x48,0xba,0x9a,0x05,0xa9,0x8a,0xe4,0x7b,0x5c,0xd2,0x01,0xa2,0x5a,0x7f,0xd5,0xd8,0xb7,0x3c,0x20,0x3c,0x5f,0x7b,0x6b,0x6b,0x3b,0x6a,0xd7}, + "" + }, + { /* input x-only pubkeys */ + {0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + {0x78,0x2e,0xeb,0x91,0x34,0x31,0xca,0x6e,0x9b,0x8c,0x2f,0xd8,0x0a,0x5f,0x72,0xed,0x20,0x24,0xef,0x72,0xa3,0xc6,0xfb,0x10,0x26,0x3c,0x37,0x99,0x37,0x32,0x33,0x38}, + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 1, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + "", + "" + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0xde,0x88,0xbe,0xa8,0xe7,0xff,0xc9,0xce,0x1a,0xf3,0x0d,0x11,0x32,0xf9,0x10,0x32,0x3c,0x50,0x51,0x85,0xae,0xc8,0xea,0xe3,0x61,0x67,0x04,0x21,0xe7,0x49,0xa1,0xfb}, + "", + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 1, + { /* outputs to scan */ + {0xde,0x88,0xbe,0xa8,0xe7,0xff,0xc9,0xce,0x1a,0xf3,0x0d,0x11,0x32,0xf9,0x10,0x32,0x3c,0x50,0x51,0x85,0xae,0xc8,0xea,0xe3,0x61,0x67,0x04,0x21,0xe7,0x49,0xa1,0xfb}, + "", + "", + "" + }, + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 1, + { + {0xde,0x88,0xbe,0xa8,0xe7,0xff,0xc9,0xce,0x1a,0xf3,0x0d,0x11,0x32,0xf9,0x10,0x32,0x3c,0x50,0x51,0x85,0xae,0xc8,0xea,0xe3,0x61,0x67,0x04,0x21,0xe7,0x49,0xa1,0xfb}, + "", + "", + "" + }, + { + {0x3f,0xb9,0xce,0x5c,0xe1,0x74,0x6c,0xed,0x10,0x3c,0x8e,0xd2,0x54,0xe8,0x1f,0x66,0x90,0x76,0x46,0x37,0xdd,0xbc,0x87,0x6e,0xc1,0xf9,0xb3,0xdd,0xab,0x77,0x6b,0x03}, + "", + "", + "" + }, + { + {0xc5,0xac,0xd2,0x5a,0x8f,0x02,0x1a,0x41,0x92,0xf9,0x3b,0xc3,0x44,0x03,0xfd,0x8b,0x76,0x48,0x46,0x13,0x46,0x63,0x36,0xfb,0x25,0x9c,0x72,0xd0,0x4c,0x16,0x98,0x24,0xf2,0x69,0x0c,0xa3,0x4e,0x96,0xce,0xe8,0x6b,0x69,0xf3,0x76,0xc8,0x37,0x70,0x03,0x26,0x8f,0xda,0x56,0xfe,0xeb,0x1b,0x87,0x3e,0x57,0x83,0xd7,0xe1,0x9b,0xcc,0xa5}, + "", + "", + "" + } + }, + + /* ----- Single recipient: taproot only with mixed even/odd y-values (7) ----- */ + { + 0, + { /* input plain seckeys */ + "", + "", + "" + }, + { /* input plain pubkeys */ + "", + "", + "" + }, + 2, + { /* input taproot seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + {0x1d,0x37,0x78,0x7c,0x2b,0x71,0x16,0xee,0x98,0x3e,0x9f,0x9c,0x13,0x26,0x9d,0xf2,0x90,0x91,0xb3,0x91,0xc0,0x4d,0xb9,0x42,0x39,0xe0,0xd2,0xbc,0x21,0x82,0xc3,0xbf}, + "" + }, + { /* input x-only pubkeys */ + {0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + {0x8c,0x8d,0x23,0xd4,0x76,0x4f,0xef,0xfc,0xd5,0xe7,0x2e,0x38,0x08,0x02,0x54,0x0f,0xa0,0xf8,0x8e,0x3d,0x62,0xad,0x5e,0x0b,0x47,0x95,0x5f,0x74,0xd7,0xb2,0x83,0xc4}, + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 1, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + "", + "" + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0x77,0xca,0xb7,0xdd,0x12,0xb1,0x02,0x59,0xee,0x82,0xc6,0xea,0x4b,0x50,0x97,0x74,0xe3,0x3e,0x70,0x78,0xe7,0x13,0x8f,0x56,0x80,0x92,0x24,0x1b,0xf2,0x6b,0x99,0xf1}, + "", + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 1, + { /* outputs to scan */ + {0x77,0xca,0xb7,0xdd,0x12,0xb1,0x02,0x59,0xee,0x82,0xc6,0xea,0x4b,0x50,0x97,0x74,0xe3,0x3e,0x70,0x78,0xe7,0x13,0x8f,0x56,0x80,0x92,0x24,0x1b,0xf2,0x6b,0x99,0xf1}, + "", + "", + "" + }, + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 1, + { + {0x77,0xca,0xb7,0xdd,0x12,0xb1,0x02,0x59,0xee,0x82,0xc6,0xea,0x4b,0x50,0x97,0x74,0xe3,0x3e,0x70,0x78,0xe7,0x13,0x8f,0x56,0x80,0x92,0x24,0x1b,0xf2,0x6b,0x99,0xf1}, + "", + "", + "" + }, + { + {0xf5,0x38,0x25,0x08,0x60,0x97,0x71,0x06,0x8e,0xd0,0x79,0xb2,0x4e,0x1f,0x72,0xe4,0xa1,0x7e,0xe6,0xd1,0xc9,0x79,0x06,0x6b,0xf1,0xd4,0xe2,0xa5,0x67,0x6f,0x09,0xd4}, + "", + "", + "" + }, + { + {0xff,0x65,0x83,0x3b,0x8f,0xd1,0xed,0x3e,0xf9,0xd0,0x44,0x3b,0x4f,0x70,0x2b,0x45,0xa3,0xf2,0xdd,0x45,0x7b,0xa2,0x47,0x68,0x7e,0x82,0x07,0x74,0x5c,0x3b,0xe9,0xd2,0xbd,0xad,0x0a,0xb3,0xf0,0x71,0x18,0xf8,0xb2,0xef,0xc6,0xa0,0x4b,0x95,0xf7,0xb3,0xe2,0x18,0xda,0xf8,0xa6,0x41,0x37,0xec,0x91,0xbd,0x2f,0xc6,0x7f,0xc1,0x37,0xa5}, + "", + "", + "" + } + }, + + /* ----- Single recipient: taproot input with even y-value and non-taproot input (8) ----- */ + { + 1, + { /* input plain seckeys */ + {0x8d,0x47,0x51,0xf6,0xe8,0xa3,0x58,0x68,0x80,0xfb,0x66,0xc1,0x9a,0xe2,0x77,0x96,0x9b,0xd5,0xaa,0x06,0xf6,0x1c,0x4e,0xe2,0xf1,0xe2,0x48,0x6e,0xfd,0xf6,0x66,0xd3}, + "", + "" + }, + { /* input plain pubkeys */ + {0x03,0xe0,0xec,0x4f,0x64,0xb3,0xfa,0x2e,0x46,0x3c,0xcf,0xcf,0x4e,0x85,0x6e,0x37,0xd5,0xe1,0xe2,0x02,0x75,0xbc,0x89,0xec,0x1d,0xef,0x9e,0xb0,0x98,0xef,0xf1,0xf8,0x5d}, + "", + "" + }, + 1, + { /* input taproot seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + "", + "" + }, + { /* input x-only pubkeys */ + {0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 1, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + "", + "" + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0x30,0x52,0x3c,0xca,0x96,0xb2,0xa9,0xae,0x3c,0x98,0xbe,0xb5,0xe6,0x0f,0x7d,0x19,0x0e,0xc5,0xbc,0x79,0xb2,0xd1,0x1a,0x0b,0x2d,0x4d,0x09,0xa6,0x08,0xc4,0x48,0xf0}, + "", + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 1, + { /* outputs to scan */ + {0x30,0x52,0x3c,0xca,0x96,0xb2,0xa9,0xae,0x3c,0x98,0xbe,0xb5,0xe6,0x0f,0x7d,0x19,0x0e,0xc5,0xbc,0x79,0xb2,0xd1,0x1a,0x0b,0x2d,0x4d,0x09,0xa6,0x08,0xc4,0x48,0xf0}, + "", + "", + "" + }, + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 1, + { + {0x30,0x52,0x3c,0xca,0x96,0xb2,0xa9,0xae,0x3c,0x98,0xbe,0xb5,0xe6,0x0f,0x7d,0x19,0x0e,0xc5,0xbc,0x79,0xb2,0xd1,0x1a,0x0b,0x2d,0x4d,0x09,0xa6,0x08,0xc4,0x48,0xf0}, + "", + "", + "" + }, + { + {0xb4,0x00,0x17,0x86,0x5c,0x79,0xb1,0xfc,0xbe,0xd6,0x88,0x96,0x79,0x1b,0xe9,0x31,0x86,0xd0,0x8f,0x47,0xe4,0x16,0xb2,0x89,0xb8,0xc0,0x63,0x77,0x7e,0x14,0xe8,0xdf}, + "", + "", + "" + }, + { + {0xd1,0xed,0xee,0xa2,0x8c,0xf1,0x03,0x3b,0xcb,0x3d,0x89,0x37,0x6c,0xab,0xaa,0xaa,0x28,0x86,0xcb,0xd8,0xfd,0xa1,0x12,0xb5,0xc6,0x1c,0xc9,0x0a,0x4e,0x7f,0x18,0x78,0xbd,0xd6,0x21,0x80,0xb0,0x7d,0x1d,0xfc,0x8f,0xfe,0xe1,0x86,0x3c,0x52,0x5a,0x0c,0x7b,0x5b,0xcd,0x41,0x31,0x83,0x28,0x2c,0xfd,0xa7,0x56,0xcb,0x65,0x78,0x72,0x66}, + "", + "", + "" + } + }, + + /* ----- Single recipient: taproot input with odd y-value and non-taproot input (9) ----- */ + { + 1, + { /* input plain seckeys */ + {0x8d,0x47,0x51,0xf6,0xe8,0xa3,0x58,0x68,0x80,0xfb,0x66,0xc1,0x9a,0xe2,0x77,0x96,0x9b,0xd5,0xaa,0x06,0xf6,0x1c,0x4e,0xe2,0xf1,0xe2,0x48,0x6e,0xfd,0xf6,0x66,0xd3}, + "", + "" + }, + { /* input plain pubkeys */ + {0x03,0xe0,0xec,0x4f,0x64,0xb3,0xfa,0x2e,0x46,0x3c,0xcf,0xcf,0x4e,0x85,0x6e,0x37,0xd5,0xe1,0xe2,0x02,0x75,0xbc,0x89,0xec,0x1d,0xef,0x9e,0xb0,0x98,0xef,0xf1,0xf8,0x5d}, + "", + "" + }, + 1, + { /* input taproot seckeys */ + {0x1d,0x37,0x78,0x7c,0x2b,0x71,0x16,0xee,0x98,0x3e,0x9f,0x9c,0x13,0x26,0x9d,0xf2,0x90,0x91,0xb3,0x91,0xc0,0x4d,0xb9,0x42,0x39,0xe0,0xd2,0xbc,0x21,0x82,0xc3,0xbf}, + "", + "" + }, + { /* input x-only pubkeys */ + {0x8c,0x8d,0x23,0xd4,0x76,0x4f,0xef,0xfc,0xd5,0xe7,0x2e,0x38,0x08,0x02,0x54,0x0f,0xa0,0xf8,0x8e,0x3d,0x62,0xad,0x5e,0x0b,0x47,0x95,0x5f,0x74,0xd7,0xb2,0x83,0xc4}, + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 1, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + "", + "" + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0x35,0x93,0x58,0xf5,0x9e,0xe9,0xe9,0xee,0xc3,0xf0,0x0b,0xdf,0x48,0x82,0x57,0x0f,0xd5,0xc1,0x82,0xe4,0x51,0xaa,0x26,0x50,0xb7,0x88,0x54,0x4a,0xff,0x01,0x2a,0x3a}, + "", + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 1, + { /* outputs to scan */ + {0x35,0x93,0x58,0xf5,0x9e,0xe9,0xe9,0xee,0xc3,0xf0,0x0b,0xdf,0x48,0x82,0x57,0x0f,0xd5,0xc1,0x82,0xe4,0x51,0xaa,0x26,0x50,0xb7,0x88,0x54,0x4a,0xff,0x01,0x2a,0x3a}, + "", + "", + "" + }, + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 1, + { + {0x35,0x93,0x58,0xf5,0x9e,0xe9,0xe9,0xee,0xc3,0xf0,0x0b,0xdf,0x48,0x82,0x57,0x0f,0xd5,0xc1,0x82,0xe4,0x51,0xaa,0x26,0x50,0xb7,0x88,0x54,0x4a,0xff,0x01,0x2a,0x3a}, + "", + "", + "" + }, + { + {0xa2,0xf9,0xdd,0x05,0xd1,0xd3,0x98,0x34,0x7c,0x88,0x5d,0x9c,0x61,0xa6,0x4d,0x18,0xa2,0x64,0xde,0x6d,0x49,0xce,0xa4,0x32,0x6b,0xaf,0xc2,0x79,0x1d,0x62,0x7f,0xa7}, + "", + "", + "" + }, + { + {0x96,0x03,0x8a,0xd2,0x33,0xd8,0xbe,0xfe,0x34,0x25,0x73,0xa6,0xe5,0x48,0x28,0xd8,0x63,0x47,0x1f,0xb2,0xaf,0xba,0xd5,0x75,0xcc,0x65,0x27,0x1a,0x2a,0x64,0x94,0x80,0xea,0x14,0x91,0x2b,0x6a,0xbb,0xd3,0xfb,0xf9,0x2e,0xfc,0x19,0x28,0xc0,0x36,0xf6,0xe3,0xee,0xf9,0x27,0x10,0x5a,0xf4,0xec,0x1d,0xd5,0x7c,0xb9,0x09,0xf3,0x60,0xb8}, + "", + "", + "" + } + }, + + /* ----- Multiple outputs: multiple outputs, same recipient (10) ----- */ + { + 2, + { /* input plain seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + {0x03,0x78,0xe9,0x56,0x85,0xb7,0x45,0x65,0xfa,0x56,0x75,0x1b,0x84,0xa3,0x2d,0xfd,0x18,0x54,0x5d,0x10,0xd6,0x91,0x64,0x1b,0x83,0x72,0xe3,0x21,0x64,0xfa,0xd6,0x6a}, + "" + }, + { /* input plain pubkeys */ + {0x02,0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + {0x03,0x78,0x2e,0xeb,0x91,0x34,0x31,0xca,0x6e,0x9b,0x8c,0x2f,0xd8,0x0a,0x5f,0x72,0xed,0x20,0x24,0xef,0x72,0xa3,0xc6,0xfb,0x10,0x26,0x3c,0x37,0x99,0x37,0x32,0x33,0x38}, + "" + }, + 0, + { /* input taproot seckeys */ + "", + "", + "" + }, + { /* input x-only pubkeys */ + "", + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 2, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0xf2,0x07,0x16,0x2b,0x1a,0x7a,0xbc,0x51,0xc4,0x20,0x17,0xbe,0xf0,0x55,0xe9,0xec,0x1e,0xfc,0x3d,0x35,0x67,0xcb,0x72,0x03,0x57,0xe2,0xb8,0x43,0x25,0xdb,0x33,0xac}, + {0xe9,0x76,0xa5,0x8f,0xbd,0x38,0xae,0xb4,0xe6,0x09,0x3d,0x4d,0xf0,0x2e,0x9c,0x1d,0xe0,0xc4,0x51,0x3a,0xe0,0xc5,0x88,0xce,0xf6,0x8c,0xda,0x5b,0x2f,0x88,0x34,0xca}, + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 4, + { /* outputs to scan */ + {0xf2,0x07,0x16,0x2b,0x1a,0x7a,0xbc,0x51,0xc4,0x20,0x17,0xbe,0xf0,0x55,0xe9,0xec,0x1e,0xfc,0x3d,0x35,0x67,0xcb,0x72,0x03,0x57,0xe2,0xb8,0x43,0x25,0xdb,0x33,0xac}, + {0xe9,0x76,0xa5,0x8f,0xbd,0x38,0xae,0xb4,0xe6,0x09,0x3d,0x4d,0xf0,0x2e,0x9c,0x1d,0xe0,0xc4,0x51,0x3a,0xe0,0xc5,0x88,0xce,0xf6,0x8c,0xda,0x5b,0x2f,0x88,0x34,0xca}, + {0x84,0x17,0x92,0xc3,0x3c,0x9d,0xc6,0x19,0x3e,0x76,0x74,0x41,0x34,0x12,0x5d,0x40,0xad,0xd8,0xf2,0xf4,0xa9,0x64,0x75,0xf2,0x8b,0xa1,0x50,0xbe,0x03,0x2d,0x64,0xe8}, + {0x2e,0x84,0x7b,0xb0,0x1d,0x1b,0x49,0x1d,0xa5,0x12,0xdd,0xd7,0x60,0xb8,0x50,0x96,0x17,0xee,0x38,0x05,0x70,0x03,0xd6,0x11,0x5d,0x00,0xba,0x56,0x24,0x51,0x32,0x3a} + }, + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 2, + { + {0xf2,0x07,0x16,0x2b,0x1a,0x7a,0xbc,0x51,0xc4,0x20,0x17,0xbe,0xf0,0x55,0xe9,0xec,0x1e,0xfc,0x3d,0x35,0x67,0xcb,0x72,0x03,0x57,0xe2,0xb8,0x43,0x25,0xdb,0x33,0xac}, + {0xe9,0x76,0xa5,0x8f,0xbd,0x38,0xae,0xb4,0xe6,0x09,0x3d,0x4d,0xf0,0x2e,0x9c,0x1d,0xe0,0xc4,0x51,0x3a,0xe0,0xc5,0x88,0xce,0xf6,0x8c,0xda,0x5b,0x2f,0x88,0x34,0xca}, + "", + "" + }, + { + {0x33,0xce,0x08,0x5c,0x3c,0x11,0xea,0xad,0x13,0x69,0x4a,0xae,0x3c,0x20,0x30,0x1a,0x6c,0x83,0x38,0x2e,0xc8,0x9a,0x7c,0xde,0x96,0xc6,0x79,0x9e,0x2f,0x88,0x80,0x5a}, + {0xd9,0x7e,0x44,0x2d,0x11,0x0c,0x0b,0xdd,0x31,0x16,0x1a,0x7b,0xb6,0xe7,0x86,0x2e,0x03,0x8d,0x02,0xa0,0x9b,0x14,0x84,0xdf,0xbb,0x46,0x3f,0x2e,0x0f,0x7c,0x92,0x30}, + "", + "" + }, + { + {0x33,0x56,0x67,0xca,0x6c,0xae,0x7a,0x26,0x43,0x8f,0x5c,0xfd,0xd7,0x3b,0x3d,0x48,0xfa,0x83,0x2f,0xa9,0x76,0x85,0x21,0xd7,0xd5,0x44,0x5f,0x22,0xc2,0x03,0xab,0x0d,0x74,0xed,0x85,0x08,0x8f,0x27,0xd2,0x99,0x59,0xba,0x62,0x7a,0x45,0x09,0x99,0x66,0x76,0xf4,0x7d,0xf8,0xff,0x28,0x4d,0x29,0x25,0x67,0xb1,0xbe,0xef,0x0e,0x39,0x12}, + {0x29,0xbd,0x25,0xd0,0xf8,0x08,0xd7,0xfc,0xd2,0xaa,0x6d,0x5e,0xd2,0x06,0x05,0x38,0x99,0x19,0x83,0x97,0x50,0x6c,0x30,0x1b,0x21,0x8a,0x9e,0x47,0xa3,0xd7,0x07,0x0a,0xf0,0x3e,0x90,0x3f,0xf7,0x18,0x97,0x8d,0x50,0xd1,0xb6,0xb9,0xaf,0x8c,0xc0,0xe3,0x13,0xd8,0x4e,0xda,0x5d,0x5b,0x1e,0x8e,0x85,0xe5,0x51,0x6d,0x63,0x0b,0xbe,0xb9}, + "", + "" + } + }, + + /* ----- Multiple outputs: multiple outputs, multiple recipients (11) ----- */ + { + 2, + { /* input plain seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + {0x03,0x78,0xe9,0x56,0x85,0xb7,0x45,0x65,0xfa,0x56,0x75,0x1b,0x84,0xa3,0x2d,0xfd,0x18,0x54,0x5d,0x10,0xd6,0x91,0x64,0x1b,0x83,0x72,0xe3,0x21,0x64,0xfa,0xd6,0x6a}, + "" + }, + { /* input plain pubkeys */ + {0x02,0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + {0x03,0x78,0x2e,0xeb,0x91,0x34,0x31,0xca,0x6e,0x9b,0x8c,0x2f,0xd8,0x0a,0x5f,0x72,0xed,0x20,0x24,0xef,0x72,0xa3,0xc6,0xfb,0x10,0x26,0x3c,0x37,0x99,0x37,0x32,0x33,0x38}, + "" + }, + 0, + { /* input taproot seckeys */ + "", + "", + "" + }, + { /* input x-only pubkeys */ + "", + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 4, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + {0x02,0x06,0x2d,0x49,0xff,0xc0,0x27,0x87,0xd5,0x86,0xc6,0x08,0xdf,0xbe,0xc1,0x84,0xaa,0x91,0xa6,0x59,0x7d,0x97,0xb4,0x63,0xea,0x5c,0x6b,0xab,0xd9,0xd1,0x7a,0x95,0xa3}, + {0x03,0x81,0xeb,0x9a,0x9a,0x9e,0xc7,0x39,0xd5,0x27,0xc1,0x63,0x1b,0x31,0xb4,0x21,0x56,0x6f,0x5c,0x2a,0x47,0xb4,0xab,0x5b,0x1f,0x6a,0x68,0x6d,0xfb,0x68,0xea,0xb7,0x16} + }, + { + {0x02,0x06,0x2d,0x49,0xff,0xc0,0x27,0x87,0xd5,0x86,0xc6,0x08,0xdf,0xbe,0xc1,0x84,0xaa,0x91,0xa6,0x59,0x7d,0x97,0xb4,0x63,0xea,0x5c,0x6b,0xab,0xd9,0xd1,0x7a,0x95,0xa3}, + {0x03,0x81,0xeb,0x9a,0x9a,0x9e,0xc7,0x39,0xd5,0x27,0xc1,0x63,0x1b,0x31,0xb4,0x21,0x56,0x6f,0x5c,0x2a,0x47,0xb4,0xab,0x5b,0x1f,0x6a,0x68,0x6d,0xfb,0x68,0xea,0xb7,0x16} + } + }, + { /* recipient outputs */ + {0xf2,0x07,0x16,0x2b,0x1a,0x7a,0xbc,0x51,0xc4,0x20,0x17,0xbe,0xf0,0x55,0xe9,0xec,0x1e,0xfc,0x3d,0x35,0x67,0xcb,0x72,0x03,0x57,0xe2,0xb8,0x43,0x25,0xdb,0x33,0xac}, + {0xe9,0x76,0xa5,0x8f,0xbd,0x38,0xae,0xb4,0xe6,0x09,0x3d,0x4d,0xf0,0x2e,0x9c,0x1d,0xe0,0xc4,0x51,0x3a,0xe0,0xc5,0x88,0xce,0xf6,0x8c,0xda,0x5b,0x2f,0x88,0x34,0xca}, + {0x84,0x17,0x92,0xc3,0x3c,0x9d,0xc6,0x19,0x3e,0x76,0x74,0x41,0x34,0x12,0x5d,0x40,0xad,0xd8,0xf2,0xf4,0xa9,0x64,0x75,0xf2,0x8b,0xa1,0x50,0xbe,0x03,0x2d,0x64,0xe8}, + {0x2e,0x84,0x7b,0xb0,0x1d,0x1b,0x49,0x1d,0xa5,0x12,0xdd,0xd7,0x60,0xb8,0x50,0x96,0x17,0xee,0x38,0x05,0x70,0x03,0xd6,0x11,0x5d,0x00,0xba,0x56,0x24,0x51,0x32,0x3a} + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 4, + { /* outputs to scan */ + {0xf2,0x07,0x16,0x2b,0x1a,0x7a,0xbc,0x51,0xc4,0x20,0x17,0xbe,0xf0,0x55,0xe9,0xec,0x1e,0xfc,0x3d,0x35,0x67,0xcb,0x72,0x03,0x57,0xe2,0xb8,0x43,0x25,0xdb,0x33,0xac}, + {0xe9,0x76,0xa5,0x8f,0xbd,0x38,0xae,0xb4,0xe6,0x09,0x3d,0x4d,0xf0,0x2e,0x9c,0x1d,0xe0,0xc4,0x51,0x3a,0xe0,0xc5,0x88,0xce,0xf6,0x8c,0xda,0x5b,0x2f,0x88,0x34,0xca}, + {0x84,0x17,0x92,0xc3,0x3c,0x9d,0xc6,0x19,0x3e,0x76,0x74,0x41,0x34,0x12,0x5d,0x40,0xad,0xd8,0xf2,0xf4,0xa9,0x64,0x75,0xf2,0x8b,0xa1,0x50,0xbe,0x03,0x2d,0x64,0xe8}, + {0x2e,0x84,0x7b,0xb0,0x1d,0x1b,0x49,0x1d,0xa5,0x12,0xdd,0xd7,0x60,0xb8,0x50,0x96,0x17,0xee,0x38,0x05,0x70,0x03,0xd6,0x11,0x5d,0x00,0xba,0x56,0x24,0x51,0x32,0x3a} + }, + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 2, + { + {0xf2,0x07,0x16,0x2b,0x1a,0x7a,0xbc,0x51,0xc4,0x20,0x17,0xbe,0xf0,0x55,0xe9,0xec,0x1e,0xfc,0x3d,0x35,0x67,0xcb,0x72,0x03,0x57,0xe2,0xb8,0x43,0x25,0xdb,0x33,0xac}, + {0xe9,0x76,0xa5,0x8f,0xbd,0x38,0xae,0xb4,0xe6,0x09,0x3d,0x4d,0xf0,0x2e,0x9c,0x1d,0xe0,0xc4,0x51,0x3a,0xe0,0xc5,0x88,0xce,0xf6,0x8c,0xda,0x5b,0x2f,0x88,0x34,0xca}, + "", + "" + }, + { + {0x33,0xce,0x08,0x5c,0x3c,0x11,0xea,0xad,0x13,0x69,0x4a,0xae,0x3c,0x20,0x30,0x1a,0x6c,0x83,0x38,0x2e,0xc8,0x9a,0x7c,0xde,0x96,0xc6,0x79,0x9e,0x2f,0x88,0x80,0x5a}, + {0xd9,0x7e,0x44,0x2d,0x11,0x0c,0x0b,0xdd,0x31,0x16,0x1a,0x7b,0xb6,0xe7,0x86,0x2e,0x03,0x8d,0x02,0xa0,0x9b,0x14,0x84,0xdf,0xbb,0x46,0x3f,0x2e,0x0f,0x7c,0x92,0x30}, + "", + "" + }, + { + {0x33,0x56,0x67,0xca,0x6c,0xae,0x7a,0x26,0x43,0x8f,0x5c,0xfd,0xd7,0x3b,0x3d,0x48,0xfa,0x83,0x2f,0xa9,0x76,0x85,0x21,0xd7,0xd5,0x44,0x5f,0x22,0xc2,0x03,0xab,0x0d,0x74,0xed,0x85,0x08,0x8f,0x27,0xd2,0x99,0x59,0xba,0x62,0x7a,0x45,0x09,0x99,0x66,0x76,0xf4,0x7d,0xf8,0xff,0x28,0x4d,0x29,0x25,0x67,0xb1,0xbe,0xef,0x0e,0x39,0x12}, + {0x29,0xbd,0x25,0xd0,0xf8,0x08,0xd7,0xfc,0xd2,0xaa,0x6d,0x5e,0xd2,0x06,0x05,0x38,0x99,0x19,0x83,0x97,0x50,0x6c,0x30,0x1b,0x21,0x8a,0x9e,0x47,0xa3,0xd7,0x07,0x0a,0xf0,0x3e,0x90,0x3f,0xf7,0x18,0x97,0x8d,0x50,0xd1,0xb6,0xb9,0xaf,0x8c,0xc0,0xe3,0x13,0xd8,0x4e,0xda,0x5d,0x5b,0x1e,0x8e,0x85,0xe5,0x51,0x6d,0x63,0x0b,0xbe,0xb9}, + "", + "" + } + }, + + /* ----- Receiving with labels: label with odd parity (12) ----- */ + { + 2, + { /* input plain seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + {0x03,0x78,0xe9,0x56,0x85,0xb7,0x45,0x65,0xfa,0x56,0x75,0x1b,0x84,0xa3,0x2d,0xfd,0x18,0x54,0x5d,0x10,0xd6,0x91,0x64,0x1b,0x83,0x72,0xe3,0x21,0x64,0xfa,0xd6,0x6a}, + "" + }, + { /* input plain pubkeys */ + {0x02,0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + {0x03,0x78,0x2e,0xeb,0x91,0x34,0x31,0xca,0x6e,0x9b,0x8c,0x2f,0xd8,0x0a,0x5f,0x72,0xed,0x20,0x24,0xef,0x72,0xa3,0xc6,0xfb,0x10,0x26,0x3c,0x37,0x99,0x37,0x32,0x33,0x38}, + "" + }, + 0, + { /* input taproot seckeys */ + "", + "", + "" + }, + { /* input x-only pubkeys */ + "", + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 1, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x59,0x35,0x2a,0xdd,0x83,0x7b,0x66,0x86,0xe8,0xd2,0x2b,0x87,0x01,0x78,0x14,0xa4,0x6b,0x3a,0xd3,0x08,0x70,0x21,0x67,0xc6,0x5b,0xd5,0xc8,0x59,0x9c,0xd2,0x8d,0x1c} + }, + { + "", + "" + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0xd0,0x14,0xd4,0x86,0x0f,0x67,0xd6,0x07,0xd6,0x0b,0x1a,0xf7,0x0e,0x0e,0xe2,0x36,0xb9,0x96,0x58,0xb6,0x1b,0xb7,0x69,0x83,0x2a,0xcb,0xbe,0x87,0xc3,0x74,0x43,0x9a}, + "", + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 1, + { /* outputs to scan */ + {0xd0,0x14,0xd4,0x86,0x0f,0x67,0xd6,0x07,0xd6,0x0b,0x1a,0xf7,0x0e,0x0e,0xe2,0x36,0xb9,0x96,0x58,0xb6,0x1b,0xb7,0x69,0x83,0x2a,0xcb,0xbe,0x87,0xc3,0x74,0x43,0x9a}, + "", + "", + "" + }, + 3, {2, 3, 1001337, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 1, + { + {0xd0,0x14,0xd4,0x86,0x0f,0x67,0xd6,0x07,0xd6,0x0b,0x1a,0xf7,0x0e,0x0e,0xe2,0x36,0xb9,0x96,0x58,0xb6,0x1b,0xb7,0x69,0x83,0x2a,0xcb,0xbe,0x87,0xc3,0x74,0x43,0x9a}, + "", + "", + "" + }, + { + {0x51,0xd4,0xe9,0xd0,0xd4,0x82,0xb5,0x70,0x01,0x09,0xb4,0xb2,0xe1,0x6f,0xf5,0x08,0x26,0x9b,0x03,0xd8,0x00,0x19,0x2a,0x04,0x3d,0x61,0xdc,0xa4,0xa0,0xa7,0x2a,0x52}, + "", + "", + "" + }, + { + {0xc3,0x0f,0xa6,0x3b,0xad,0x6f,0x0a,0x31,0x7f,0x39,0xa7,0x73,0xa5,0xcb,0xf0,0xb0,0xf8,0x19,0x3c,0x71,0xdf,0xeb,0xba,0x05,0xee,0x6a,0xe4,0xed,0x28,0xe3,0x77,0x5e,0x6e,0x04,0xc3,0xea,0x70,0xa8,0x37,0x03,0xbb,0x88,0x81,0x22,0x85,0x5d,0xc8,0x94,0xca,0xb6,0x16,0x92,0xe7,0xfd,0x10,0xc9,0xb3,0x49,0x4d,0x47,0x9a,0x60,0x78,0x5e}, + "", + "", + "" + } + }, + + /* ----- Receiving with labels: label with odd parity (13) ----- */ + { + 2, + { /* input plain seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + {0x03,0x78,0xe9,0x56,0x85,0xb7,0x45,0x65,0xfa,0x56,0x75,0x1b,0x84,0xa3,0x2d,0xfd,0x18,0x54,0x5d,0x10,0xd6,0x91,0x64,0x1b,0x83,0x72,0xe3,0x21,0x64,0xfa,0xd6,0x6a}, + "" + }, + { /* input plain pubkeys */ + {0x02,0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + {0x03,0x78,0x2e,0xeb,0x91,0x34,0x31,0xca,0x6e,0x9b,0x8c,0x2f,0xd8,0x0a,0x5f,0x72,0xed,0x20,0x24,0xef,0x72,0xa3,0xc6,0xfb,0x10,0x26,0x3c,0x37,0x99,0x37,0x32,0x33,0x38}, + "" + }, + 0, + { /* input taproot seckeys */ + "", + "", + "" + }, + { /* input x-only pubkeys */ + "", + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 1, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x08,0xa1,0x44,0xa1,0x84,0x33,0xa8,0x3f,0x63,0x3c,0x82,0x2c,0x1b,0xf5,0xee,0x4c,0x8c,0x8e,0x24,0x60,0x1d,0x6c,0xa7,0x5e,0x20,0xa7,0xdc,0x57,0xa0,0xff,0x92,0x80} + }, + { + "", + "" + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0x67,0x62,0x6a,0xeb,0xb3,0xc4,0x30,0x7c,0xf0,0xf6,0xc3,0x9c,0xa2,0x32,0x47,0x59,0x8f,0xab,0xf6,0x75,0xab,0x78,0x32,0x92,0xeb,0x2f,0x81,0xae,0x75,0xad,0x1f,0x8c}, + "", + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 1, + { /* outputs to scan */ + {0x67,0x62,0x6a,0xeb,0xb3,0xc4,0x30,0x7c,0xf0,0xf6,0xc3,0x9c,0xa2,0x32,0x47,0x59,0x8f,0xab,0xf6,0x75,0xab,0x78,0x32,0x92,0xeb,0x2f,0x81,0xae,0x75,0xad,0x1f,0x8c}, + "", + "", + "" + }, + 3, {2, 3, 1001337, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 1, + { + {0x67,0x62,0x6a,0xeb,0xb3,0xc4,0x30,0x7c,0xf0,0xf6,0xc3,0x9c,0xa2,0x32,0x47,0x59,0x8f,0xab,0xf6,0x75,0xab,0x78,0x32,0x92,0xeb,0x2f,0x81,0xae,0x75,0xad,0x1f,0x8c}, + "", + "", + "" + }, + { + {0x60,0x24,0xae,0x21,0x48,0x76,0x35,0x6b,0x8d,0x91,0x77,0x16,0xe7,0x70,0x7d,0x26,0x7a,0xe1,0x6a,0x0f,0xdb,0x07,0xde,0x2a,0x78,0x6b,0x74,0xa7,0xbb,0xcd,0xde,0xad}, + "", + "", + "" + }, + { + {0xa8,0x6d,0x55,0x4d,0x0d,0x6b,0x7a,0xa0,0x90,0x71,0x55,0xf7,0xe0,0xb4,0x7f,0x01,0x82,0x75,0x24,0x72,0xff,0xfa,0xed,0xdd,0x68,0xda,0x90,0xe9,0x9b,0x94,0x02,0xf1,0x66,0xfd,0x9b,0x33,0x03,0x9c,0x30,0x2c,0x71,0x15,0x09,0x8d,0x97,0x1c,0x13,0x99,0xe6,0x7c,0x19,0xe9,0xe4,0xde,0x18,0x0b,0x10,0xea,0x0b,0x9d,0x6f,0x0d,0xb8,0x32}, + "", + "", + "" + } + }, + + /* ----- Receiving with labels: label with odd parity (14) ----- */ + { + 2, + { /* input plain seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + {0x03,0x78,0xe9,0x56,0x85,0xb7,0x45,0x65,0xfa,0x56,0x75,0x1b,0x84,0xa3,0x2d,0xfd,0x18,0x54,0x5d,0x10,0xd6,0x91,0x64,0x1b,0x83,0x72,0xe3,0x21,0x64,0xfa,0xd6,0x6a}, + "" + }, + { /* input plain pubkeys */ + {0x02,0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + {0x03,0x78,0x2e,0xeb,0x91,0x34,0x31,0xca,0x6e,0x9b,0x8c,0x2f,0xd8,0x0a,0x5f,0x72,0xed,0x20,0x24,0xef,0x72,0xa3,0xc6,0xfb,0x10,0x26,0x3c,0x37,0x99,0x37,0x32,0x33,0x38}, + "" + }, + 0, + { /* input taproot seckeys */ + "", + "", + "" + }, + { /* input x-only pubkeys */ + "", + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 1, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x03,0xd8,0x50,0x92,0xbb,0xe3,0x46,0x8f,0x68,0x4c,0xe1,0xd8,0xa2,0xa6,0x6e,0xbe,0xc9,0x6a,0x9e,0x6e,0x09,0xe7,0x11,0x07,0x20,0xa5,0xd5,0xfa,0xa4,0xaa,0x78,0x80,0xd0} + }, + { + "", + "" + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0x7e,0xfa,0x60,0xce,0x78,0xac,0x34,0x3d,0xf8,0xa0,0x13,0xa2,0x02,0x7c,0x6c,0x5e,0xf2,0x9f,0x95,0x02,0xed,0xcb,0xd7,0x69,0xd2,0xc2,0x17,0x17,0xfe,0xcc,0x59,0x51}, + "", + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 1, + { /* outputs to scan */ + {0x7e,0xfa,0x60,0xce,0x78,0xac,0x34,0x3d,0xf8,0xa0,0x13,0xa2,0x02,0x7c,0x6c,0x5e,0xf2,0x9f,0x95,0x02,0xed,0xcb,0xd7,0x69,0xd2,0xc2,0x17,0x17,0xfe,0xcc,0x59,0x51}, + "", + "", + "" + }, + 3, {2, 3, 1001337, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 1, + { + {0x7e,0xfa,0x60,0xce,0x78,0xac,0x34,0x3d,0xf8,0xa0,0x13,0xa2,0x02,0x7c,0x6c,0x5e,0xf2,0x9f,0x95,0x02,0xed,0xcb,0xd7,0x69,0xd2,0xc2,0x17,0x17,0xfe,0xcc,0x59,0x51}, + "", + "", + "" + }, + { + {0xe3,0x36,0xb9,0x23,0x30,0xc3,0x30,0x30,0x28,0x5c,0xe4,0x2e,0x41,0x15,0xad,0x92,0xd5,0x19,0x79,0x13,0xc8,0x8e,0x06,0xb9,0x07,0x2b,0x4a,0x9b,0x47,0xc6,0x64,0xa2}, + "", + "", + "" + }, + { + {0xc9,0xe8,0x0d,0xd3,0xbd,0xd2,0x5c,0xa2,0xd3,0x52,0xce,0x77,0x51,0x0f,0x1a,0xed,0x37,0xba,0x35,0x09,0xdc,0x8c,0xc0,0x67,0x7f,0x2d,0x7c,0x2d,0xd0,0x40,0x90,0x70,0x79,0x50,0xce,0x9d,0xd6,0xc8,0x3d,0x2a,0x42,0x80,0x63,0x06,0x3a,0xff,0x5c,0x04,0xf1,0x74,0x4e,0x33,0x4f,0x66,0x1f,0x2f,0xc0,0x1b,0x4e,0xf8,0x0b,0x50,0xf7,0x39}, + "", + "", + "" + } + }, + + /* ----- Multiple outputs with labels: multiple outputs for labeled address; same recipient (15) ----- */ + { + 2, + { /* input plain seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + {0x03,0x78,0xe9,0x56,0x85,0xb7,0x45,0x65,0xfa,0x56,0x75,0x1b,0x84,0xa3,0x2d,0xfd,0x18,0x54,0x5d,0x10,0xd6,0x91,0x64,0x1b,0x83,0x72,0xe3,0x21,0x64,0xfa,0xd6,0x6a}, + "" + }, + { /* input plain pubkeys */ + {0x02,0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + {0x03,0x78,0x2e,0xeb,0x91,0x34,0x31,0xca,0x6e,0x9b,0x8c,0x2f,0xd8,0x0a,0x5f,0x72,0xed,0x20,0x24,0xef,0x72,0xa3,0xc6,0xfb,0x10,0x26,0x3c,0x37,0x99,0x37,0x32,0x33,0x38}, + "" + }, + 0, + { /* input taproot seckeys */ + "", + "", + "" + }, + { /* input x-only pubkeys */ + "", + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 2, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x03,0xa6,0x73,0x94,0x99,0xdc,0x66,0x7d,0x30,0x8b,0xae,0xfe,0xa4,0xde,0x0c,0x4a,0x85,0xcc,0x72,0xae,0xce,0x18,0x1b,0xc0,0x57,0x12,0xd3,0x91,0x96,0x62,0x61,0x0f,0xf1} + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0xf2,0x07,0x16,0x2b,0x1a,0x7a,0xbc,0x51,0xc4,0x20,0x17,0xbe,0xf0,0x55,0xe9,0xec,0x1e,0xfc,0x3d,0x35,0x67,0xcb,0x72,0x03,0x57,0xe2,0xb8,0x43,0x25,0xdb,0x33,0xac}, + {0x39,0xf4,0x26,0x24,0xd5,0xc3,0x2a,0x77,0xfd,0xa8,0x0f,0xf0,0xac,0xee,0x26,0x9a,0xfe,0xc6,0x01,0xd3,0x79,0x18,0x03,0xe8,0x02,0x52,0xae,0x04,0xe4,0xff,0xcf,0x4c}, + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 2, + { /* outputs to scan */ + {0xf2,0x07,0x16,0x2b,0x1a,0x7a,0xbc,0x51,0xc4,0x20,0x17,0xbe,0xf0,0x55,0xe9,0xec,0x1e,0xfc,0x3d,0x35,0x67,0xcb,0x72,0x03,0x57,0xe2,0xb8,0x43,0x25,0xdb,0x33,0xac}, + {0x39,0xf4,0x26,0x24,0xd5,0xc3,0x2a,0x77,0xfd,0xa8,0x0f,0xf0,0xac,0xee,0x26,0x9a,0xfe,0xc6,0x01,0xd3,0x79,0x18,0x03,0xe8,0x02,0x52,0xae,0x04,0xe4,0xff,0xcf,0x4c}, + "", + "" + }, + 1, {1, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 2, + { + {0xf2,0x07,0x16,0x2b,0x1a,0x7a,0xbc,0x51,0xc4,0x20,0x17,0xbe,0xf0,0x55,0xe9,0xec,0x1e,0xfc,0x3d,0x35,0x67,0xcb,0x72,0x03,0x57,0xe2,0xb8,0x43,0x25,0xdb,0x33,0xac}, + {0x39,0xf4,0x26,0x24,0xd5,0xc3,0x2a,0x77,0xfd,0xa8,0x0f,0xf0,0xac,0xee,0x26,0x9a,0xfe,0xc6,0x01,0xd3,0x79,0x18,0x03,0xe8,0x02,0x52,0xae,0x04,0xe4,0xff,0xcf,0x4c}, + "", + "" + }, + { + {0x33,0xce,0x08,0x5c,0x3c,0x11,0xea,0xad,0x13,0x69,0x4a,0xae,0x3c,0x20,0x30,0x1a,0x6c,0x83,0x38,0x2e,0xc8,0x9a,0x7c,0xde,0x96,0xc6,0x79,0x9e,0x2f,0x88,0x80,0x5a}, + {0x43,0x10,0x0f,0x89,0xf1,0xa6,0xbf,0x10,0x08,0x1c,0x92,0xb4,0x73,0xff,0xc5,0x7c,0xea,0xc7,0xdb,0xed,0x60,0x0b,0x6a,0xba,0x9b,0xb3,0x97,0x6f,0x17,0xdb,0xb9,0x14}, + "", + "" + }, + { + {0x33,0x56,0x67,0xca,0x6c,0xae,0x7a,0x26,0x43,0x8f,0x5c,0xfd,0xd7,0x3b,0x3d,0x48,0xfa,0x83,0x2f,0xa9,0x76,0x85,0x21,0xd7,0xd5,0x44,0x5f,0x22,0xc2,0x03,0xab,0x0d,0x74,0xed,0x85,0x08,0x8f,0x27,0xd2,0x99,0x59,0xba,0x62,0x7a,0x45,0x09,0x99,0x66,0x76,0xf4,0x7d,0xf8,0xff,0x28,0x4d,0x29,0x25,0x67,0xb1,0xbe,0xef,0x0e,0x39,0x12}, + {0x15,0xc9,0x25,0x09,0xb6,0x7a,0x6c,0x21,0x1e,0xbb,0x4a,0x51,0xb7,0x52,0x8d,0x06,0x66,0xe6,0x72,0x0d,0xe2,0x34,0x3b,0x2e,0x92,0xcf,0xb9,0x79,0x42,0xca,0x14,0x69,0x3c,0x1f,0x1f,0xdc,0x84,0x51,0xac,0xfd,0xb2,0x64,0x40,0x39,0xf8,0xf5,0xc7,0x61,0x14,0x80,0x7f,0xdc,0x3d,0x3a,0x00,0x2d,0x8a,0x46,0xaf,0xab,0x67,0x56,0xbd,0x75}, + "", + "" + } + }, + + /* ----- Multiple outputs with labels: multiple outputs for labeled address; same recipient (16) ----- */ + { + 2, + { /* input plain seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + {0x03,0x78,0xe9,0x56,0x85,0xb7,0x45,0x65,0xfa,0x56,0x75,0x1b,0x84,0xa3,0x2d,0xfd,0x18,0x54,0x5d,0x10,0xd6,0x91,0x64,0x1b,0x83,0x72,0xe3,0x21,0x64,0xfa,0xd6,0x6a}, + "" + }, + { /* input plain pubkeys */ + {0x02,0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + {0x03,0x78,0x2e,0xeb,0x91,0x34,0x31,0xca,0x6e,0x9b,0x8c,0x2f,0xd8,0x0a,0x5f,0x72,0xed,0x20,0x24,0xef,0x72,0xa3,0xc6,0xfb,0x10,0x26,0x3c,0x37,0x99,0x37,0x32,0x33,0x38}, + "" + }, + 0, + { /* input taproot seckeys */ + "", + "", + "" + }, + { /* input x-only pubkeys */ + "", + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 2, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x03,0xa6,0x73,0x94,0x99,0xdc,0x66,0x7d,0x30,0x8b,0xae,0xfe,0xa4,0xde,0x0c,0x4a,0x85,0xcc,0x72,0xae,0xce,0x18,0x1b,0xc0,0x57,0x12,0xd3,0x91,0x96,0x62,0x61,0x0f,0xf1} + }, + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x03,0xa6,0x73,0x94,0x99,0xdc,0x66,0x7d,0x30,0x8b,0xae,0xfe,0xa4,0xde,0x0c,0x4a,0x85,0xcc,0x72,0xae,0xce,0x18,0x1b,0xc0,0x57,0x12,0xd3,0x91,0x96,0x62,0x61,0x0f,0xf1} + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0x83,0xdc,0x94,0x4e,0x61,0x60,0x31,0x37,0x29,0x48,0x29,0xae,0xd5,0x6c,0x74,0xc9,0xb0,0x87,0xd8,0x0f,0x2c,0x02,0x1b,0x98,0xa7,0xfa,0xe5,0x79,0x90,0x00,0x69,0x6c}, + {0x39,0xf4,0x26,0x24,0xd5,0xc3,0x2a,0x77,0xfd,0xa8,0x0f,0xf0,0xac,0xee,0x26,0x9a,0xfe,0xc6,0x01,0xd3,0x79,0x18,0x03,0xe8,0x02,0x52,0xae,0x04,0xe4,0xff,0xcf,0x4c}, + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 2, + { /* outputs to scan */ + {0x83,0xdc,0x94,0x4e,0x61,0x60,0x31,0x37,0x29,0x48,0x29,0xae,0xd5,0x6c,0x74,0xc9,0xb0,0x87,0xd8,0x0f,0x2c,0x02,0x1b,0x98,0xa7,0xfa,0xe5,0x79,0x90,0x00,0x69,0x6c}, + {0x39,0xf4,0x26,0x24,0xd5,0xc3,0x2a,0x77,0xfd,0xa8,0x0f,0xf0,0xac,0xee,0x26,0x9a,0xfe,0xc6,0x01,0xd3,0x79,0x18,0x03,0xe8,0x02,0x52,0xae,0x04,0xe4,0xff,0xcf,0x4c}, + "", + "" + }, + 1, {1, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 2, + { + {0x83,0xdc,0x94,0x4e,0x61,0x60,0x31,0x37,0x29,0x48,0x29,0xae,0xd5,0x6c,0x74,0xc9,0xb0,0x87,0xd8,0x0f,0x2c,0x02,0x1b,0x98,0xa7,0xfa,0xe5,0x79,0x90,0x00,0x69,0x6c}, + {0x39,0xf4,0x26,0x24,0xd5,0xc3,0x2a,0x77,0xfd,0xa8,0x0f,0xf0,0xac,0xee,0x26,0x9a,0xfe,0xc6,0x01,0xd3,0x79,0x18,0x03,0xe8,0x02,0x52,0xae,0x04,0xe4,0xff,0xcf,0x4c}, + "", + "" + }, + { + {0x9d,0x5f,0xd3,0xb9,0x1c,0xac,0x9d,0xdf,0xea,0x6f,0xc2,0xe6,0xf9,0x38,0x6f,0x68,0x0e,0x6c,0xee,0x62,0x3c,0xda,0x02,0xf5,0x37,0x06,0x30,0x6c,0x08,0x1d,0xe8,0x7f}, + {0x43,0x10,0x0f,0x89,0xf1,0xa6,0xbf,0x10,0x08,0x1c,0x92,0xb4,0x73,0xff,0xc5,0x7c,0xea,0xc7,0xdb,0xed,0x60,0x0b,0x6a,0xba,0x9b,0xb3,0x97,0x6f,0x17,0xdb,0xb9,0x14}, + "", + "" + }, + { + {0xdb,0x0d,0xfa,0xcc,0x98,0xb6,0xa6,0xfc,0xc6,0x7c,0xc4,0x63,0x1f,0x08,0x0b,0x1c,0xa3,0x8c,0x60,0xd8,0xc3,0x97,0xf2,0xf1,0x98,0x43,0xf8,0xf9,0x5e,0xc9,0x15,0x94,0xb2,0x4e,0x47,0xc5,0xbd,0x39,0x48,0x0a,0x86,0x1c,0x12,0x09,0xf7,0xe3,0x14,0x5c,0x44,0x03,0x71,0xf9,0x19,0x1f,0xb9,0x6e,0x32,0x46,0x90,0x10,0x1e,0xac,0x8e,0x8e}, + {0x15,0xc9,0x25,0x09,0xb6,0x7a,0x6c,0x21,0x1e,0xbb,0x4a,0x51,0xb7,0x52,0x8d,0x06,0x66,0xe6,0x72,0x0d,0xe2,0x34,0x3b,0x2e,0x92,0xcf,0xb9,0x79,0x42,0xca,0x14,0x69,0x3c,0x1f,0x1f,0xdc,0x84,0x51,0xac,0xfd,0xb2,0x64,0x40,0x39,0xf8,0xf5,0xc7,0x61,0x14,0x80,0x7f,0xdc,0x3d,0x3a,0x00,0x2d,0x8a,0x46,0xaf,0xab,0x67,0x56,0xbd,0x75}, + "", + "" + } + }, + + /* ----- Multiple outputs with labels: multiple outputs for labeled address; same recipient (17) ----- */ + { + 2, + { /* input plain seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + {0x03,0x78,0xe9,0x56,0x85,0xb7,0x45,0x65,0xfa,0x56,0x75,0x1b,0x84,0xa3,0x2d,0xfd,0x18,0x54,0x5d,0x10,0xd6,0x91,0x64,0x1b,0x83,0x72,0xe3,0x21,0x64,0xfa,0xd6,0x6a}, + "" + }, + { /* input plain pubkeys */ + {0x02,0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + {0x03,0x78,0x2e,0xeb,0x91,0x34,0x31,0xca,0x6e,0x9b,0x8c,0x2f,0xd8,0x0a,0x5f,0x72,0xed,0x20,0x24,0xef,0x72,0xa3,0xc6,0xfb,0x10,0x26,0x3c,0x37,0x99,0x37,0x32,0x33,0x38}, + "" + }, + 0, + { /* input taproot seckeys */ + "", + "", + "" + }, + { /* input x-only pubkeys */ + "", + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 4, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x03,0xa6,0x73,0x94,0x99,0xdc,0x66,0x7d,0x30,0x8b,0xae,0xfe,0xa4,0xde,0x0c,0x4a,0x85,0xcc,0x72,0xae,0xce,0x18,0x1b,0xc0,0x57,0x12,0xd3,0x91,0x96,0x62,0x61,0x0f,0xf1} + }, + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x44,0xba,0xa5,0xcf,0x5d,0xb4,0x44,0xa9,0xe9,0x22,0x83,0x2f,0xf2,0xc8,0x87,0x16,0xb5,0x66,0xa8,0x5d,0x62,0xe8,0x23,0x5a,0xeb,0xd9,0x18,0x84,0xd4,0xf6,0x49,0x42} + }, + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x44,0xba,0xa5,0xcf,0x5d,0xb4,0x44,0xa9,0xe9,0x22,0x83,0x2f,0xf2,0xc8,0x87,0x16,0xb5,0x66,0xa8,0x5d,0x62,0xe8,0x23,0x5a,0xeb,0xd9,0x18,0x84,0xd4,0xf6,0x49,0x42} + } + }, + { /* recipient outputs */ + {0xf2,0x07,0x16,0x2b,0x1a,0x7a,0xbc,0x51,0xc4,0x20,0x17,0xbe,0xf0,0x55,0xe9,0xec,0x1e,0xfc,0x3d,0x35,0x67,0xcb,0x72,0x03,0x57,0xe2,0xb8,0x43,0x25,0xdb,0x33,0xac}, + {0x39,0xf4,0x26,0x24,0xd5,0xc3,0x2a,0x77,0xfd,0xa8,0x0f,0xf0,0xac,0xee,0x26,0x9a,0xfe,0xc6,0x01,0xd3,0x79,0x18,0x03,0xe8,0x02,0x52,0xae,0x04,0xe4,0xff,0xcf,0x4c}, + {0xae,0x1a,0x78,0x0c,0x04,0x23,0x7b,0xd5,0x77,0x28,0x3c,0x3d,0xdb,0x2e,0x49,0x97,0x67,0xc3,0x21,0x41,0x60,0xd5,0xa6,0xb0,0x76,0x7e,0x6b,0x8c,0x27,0x8b,0xd7,0x01}, + {0xf4,0x56,0x9f,0xc5,0xf6,0x9c,0x10,0xf0,0x08,0x2c,0xfb,0xb8,0xe0,0x72,0xe6,0x26,0x6e,0xc5,0x5f,0x69,0xfb,0xa8,0xcf,0xfc,0xa4,0xcb,0xb4,0xc1,0x44,0xb7,0xe5,0x9b} + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 4, + { /* outputs to scan */ + {0xf2,0x07,0x16,0x2b,0x1a,0x7a,0xbc,0x51,0xc4,0x20,0x17,0xbe,0xf0,0x55,0xe9,0xec,0x1e,0xfc,0x3d,0x35,0x67,0xcb,0x72,0x03,0x57,0xe2,0xb8,0x43,0x25,0xdb,0x33,0xac}, + {0x39,0xf4,0x26,0x24,0xd5,0xc3,0x2a,0x77,0xfd,0xa8,0x0f,0xf0,0xac,0xee,0x26,0x9a,0xfe,0xc6,0x01,0xd3,0x79,0x18,0x03,0xe8,0x02,0x52,0xae,0x04,0xe4,0xff,0xcf,0x4c}, + {0xae,0x1a,0x78,0x0c,0x04,0x23,0x7b,0xd5,0x77,0x28,0x3c,0x3d,0xdb,0x2e,0x49,0x97,0x67,0xc3,0x21,0x41,0x60,0xd5,0xa6,0xb0,0x76,0x7e,0x6b,0x8c,0x27,0x8b,0xd7,0x01}, + {0xf4,0x56,0x9f,0xc5,0xf6,0x9c,0x10,0xf0,0x08,0x2c,0xfb,0xb8,0xe0,0x72,0xe6,0x26,0x6e,0xc5,0x5f,0x69,0xfb,0xa8,0xcf,0xfc,0xa4,0xcb,0xb4,0xc1,0x44,0xb7,0xe5,0x9b} + }, + 1, {1, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 2, + { + {0xf2,0x07,0x16,0x2b,0x1a,0x7a,0xbc,0x51,0xc4,0x20,0x17,0xbe,0xf0,0x55,0xe9,0xec,0x1e,0xfc,0x3d,0x35,0x67,0xcb,0x72,0x03,0x57,0xe2,0xb8,0x43,0x25,0xdb,0x33,0xac}, + {0x39,0xf4,0x26,0x24,0xd5,0xc3,0x2a,0x77,0xfd,0xa8,0x0f,0xf0,0xac,0xee,0x26,0x9a,0xfe,0xc6,0x01,0xd3,0x79,0x18,0x03,0xe8,0x02,0x52,0xae,0x04,0xe4,0xff,0xcf,0x4c}, + "", + "" + }, + { + {0x33,0xce,0x08,0x5c,0x3c,0x11,0xea,0xad,0x13,0x69,0x4a,0xae,0x3c,0x20,0x30,0x1a,0x6c,0x83,0x38,0x2e,0xc8,0x9a,0x7c,0xde,0x96,0xc6,0x79,0x9e,0x2f,0x88,0x80,0x5a}, + {0x43,0x10,0x0f,0x89,0xf1,0xa6,0xbf,0x10,0x08,0x1c,0x92,0xb4,0x73,0xff,0xc5,0x7c,0xea,0xc7,0xdb,0xed,0x60,0x0b,0x6a,0xba,0x9b,0xb3,0x97,0x6f,0x17,0xdb,0xb9,0x14}, + "", + "" + }, + { + {0x33,0x56,0x67,0xca,0x6c,0xae,0x7a,0x26,0x43,0x8f,0x5c,0xfd,0xd7,0x3b,0x3d,0x48,0xfa,0x83,0x2f,0xa9,0x76,0x85,0x21,0xd7,0xd5,0x44,0x5f,0x22,0xc2,0x03,0xab,0x0d,0x74,0xed,0x85,0x08,0x8f,0x27,0xd2,0x99,0x59,0xba,0x62,0x7a,0x45,0x09,0x99,0x66,0x76,0xf4,0x7d,0xf8,0xff,0x28,0x4d,0x29,0x25,0x67,0xb1,0xbe,0xef,0x0e,0x39,0x12}, + {0x15,0xc9,0x25,0x09,0xb6,0x7a,0x6c,0x21,0x1e,0xbb,0x4a,0x51,0xb7,0x52,0x8d,0x06,0x66,0xe6,0x72,0x0d,0xe2,0x34,0x3b,0x2e,0x92,0xcf,0xb9,0x79,0x42,0xca,0x14,0x69,0x3c,0x1f,0x1f,0xdc,0x84,0x51,0xac,0xfd,0xb2,0x64,0x40,0x39,0xf8,0xf5,0xc7,0x61,0x14,0x80,0x7f,0xdc,0x3d,0x3a,0x00,0x2d,0x8a,0x46,0xaf,0xab,0x67,0x56,0xbd,0x75}, + "", + "" + } + }, + + /* ----- Single recipient: use silent payments for sender change (18) ----- */ + { + 2, + { /* input plain seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + {0x03,0x78,0xe9,0x56,0x85,0xb7,0x45,0x65,0xfa,0x56,0x75,0x1b,0x84,0xa3,0x2d,0xfd,0x18,0x54,0x5d,0x10,0xd6,0x91,0x64,0x1b,0x83,0x72,0xe3,0x21,0x64,0xfa,0xd6,0x6a}, + "" + }, + { /* input plain pubkeys */ + {0x02,0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + {0x03,0x78,0x2e,0xeb,0x91,0x34,0x31,0xca,0x6e,0x9b,0x8c,0x2f,0xd8,0x0a,0x5f,0x72,0xed,0x20,0x24,0xef,0x72,0xa3,0xc6,0xfb,0x10,0x26,0x3c,0x37,0x99,0x37,0x32,0x33,0x38}, + "" + }, + 0, + { /* input taproot seckeys */ + "", + "", + "" + }, + { /* input x-only pubkeys */ + "", + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 2, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + {0x03,0xb4,0xcc,0x0b,0x09,0x0b,0x6f,0x49,0xa6,0x84,0x55,0x88,0x52,0xdb,0x60,0xee,0x5e,0xb1,0xc5,0xf7,0x43,0x52,0x83,0x9c,0x3d,0x18,0xa8,0xfc,0x04,0xef,0x73,0x54,0xe0}, + {0x03,0xec,0xd4,0x3b,0x9f,0xda,0xd4,0x84,0xff,0x57,0x27,0x8b,0x21,0x87,0x8b,0x84,0x42,0x76,0xce,0x39,0x06,0x22,0xd0,0x3d,0xd0,0xcf,0xb4,0x28,0x8b,0x7e,0x02,0xa6,0xf5} + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0xf2,0x07,0x16,0x2b,0x1a,0x7a,0xbc,0x51,0xc4,0x20,0x17,0xbe,0xf0,0x55,0xe9,0xec,0x1e,0xfc,0x3d,0x35,0x67,0xcb,0x72,0x03,0x57,0xe2,0xb8,0x43,0x25,0xdb,0x33,0xac}, + {0xbe,0x36,0x8e,0x28,0x97,0x9d,0x95,0x02,0x45,0xd7,0x42,0x89,0x1a,0xe6,0x06,0x40,0x20,0xba,0x54,0x8c,0x1e,0x2e,0x65,0xa6,0x39,0xa8,0xbb,0x06,0x75,0xd9,0x5c,0xff}, + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x11,0xb7,0xa8,0x2e,0x06,0xca,0x26,0x48,0xd5,0xfd,0xed,0x23,0x66,0x47,0x80,0x78,0xec,0x4f,0xc9,0xdc,0x1d,0x8f,0xf4,0x87,0x51,0x82,0x26,0xf2,0x29,0xd7,0x68,0xfd}, + {0xb8,0xf8,0x73,0x88,0xcb,0xb4,0x19,0x34,0xc5,0x0d,0xac,0xa0,0x18,0x90,0x1b,0x00,0x07,0x0a,0x5f,0xf6,0xcc,0x25,0xa7,0xe9,0xe7,0x16,0xa9,0xd5,0xb9,0xe4,0xd6,0x64}, + 2, + { /* outputs to scan */ + {0xf2,0x07,0x16,0x2b,0x1a,0x7a,0xbc,0x51,0xc4,0x20,0x17,0xbe,0xf0,0x55,0xe9,0xec,0x1e,0xfc,0x3d,0x35,0x67,0xcb,0x72,0x03,0x57,0xe2,0xb8,0x43,0x25,0xdb,0x33,0xac}, + {0xbe,0x36,0x8e,0x28,0x97,0x9d,0x95,0x02,0x45,0xd7,0x42,0x89,0x1a,0xe6,0x06,0x40,0x20,0xba,0x54,0x8c,0x1e,0x2e,0x65,0xa6,0x39,0xa8,0xbb,0x06,0x75,0xd9,0x5c,0xff}, + "", + "" + }, + 1, {0, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 1, + { + {0xbe,0x36,0x8e,0x28,0x97,0x9d,0x95,0x02,0x45,0xd7,0x42,0x89,0x1a,0xe6,0x06,0x40,0x20,0xba,0x54,0x8c,0x1e,0x2e,0x65,0xa6,0x39,0xa8,0xbb,0x06,0x75,0xd9,0x5c,0xff}, + "", + "", + "" + }, + { + {0x80,0xcd,0x76,0x7e,0xd2,0x0b,0xd0,0xbb,0x7d,0x8e,0xa5,0xe8,0x03,0xf8,0xc3,0x81,0x29,0x3a,0x62,0xe8,0xa0,0x73,0xcf,0x46,0xfb,0x00,0x81,0xda,0x46,0xe6,0x4e,0x1f}, + "", + "", + "" + }, + { + {0x7f,0xbd,0x50,0x74,0xcf,0x13,0x77,0x27,0x31,0x55,0xee,0xfa,0xfc,0x7c,0x33,0x0c,0xb6,0x1b,0x31,0xda,0x25,0x2f,0x22,0x20,0x6a,0xc2,0x75,0x30,0xd2,0xb2,0x56,0x70,0x40,0xd9,0xaf,0x78,0x08,0x34,0x2e,0xd4,0xa0,0x95,0x98,0xc2,0x6d,0x83,0x07,0x44,0x6e,0x4e,0xd7,0x70,0x79,0xe6,0xa2,0xe6,0x1f,0xea,0x73,0x6e,0x44,0xda,0x5f,0x5a}, + "", + "", + "" + } + }, + + /* ----- Single receipient: taproot input with NUMS point (19) ----- */ + { + 0, + { /* input plain seckeys */ + "", + "", + "" + }, + { /* input plain pubkeys */ + "", + "", + "" + }, + 1, + { /* input taproot seckeys */ + {0xfc,0x87,0x16,0xa9,0x7a,0x48,0xba,0x9a,0x05,0xa9,0x8a,0xe4,0x7b,0x5c,0xd2,0x01,0xa2,0x5a,0x7f,0xd5,0xd8,0xb7,0x3c,0x20,0x3c,0x5f,0x7b,0x6b,0x6b,0x3b,0x6a,0xd7}, + "", + "" + }, + { /* input x-only pubkeys */ + {0x78,0x2e,0xeb,0x91,0x34,0x31,0xca,0x6e,0x9b,0x8c,0x2f,0xd8,0x0a,0x5f,0x72,0xed,0x20,0x24,0xef,0x72,0xa3,0xc6,0xfb,0x10,0x26,0x3c,0x37,0x99,0x37,0x32,0x33,0x38}, + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 1, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + "", + "" + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0x79,0xe7,0x98,0x97,0xc5,0x29,0x35,0xbf,0xd9,0x7f,0xc6,0xe0,0x76,0xa6,0x43,0x1a,0x0c,0x75,0x43,0xca,0x8c,0x31,0xe0,0xfc,0x3c,0xf7,0x19,0xbb,0x57,0x2c,0x84,0x2d}, + "", + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 1, + { /* outputs to scan */ + {0x79,0xe7,0x98,0x97,0xc5,0x29,0x35,0xbf,0xd9,0x7f,0xc6,0xe0,0x76,0xa6,0x43,0x1a,0x0c,0x75,0x43,0xca,0x8c,0x31,0xe0,0xfc,0x3c,0xf7,0x19,0xbb,0x57,0x2c,0x84,0x2d}, + "", + "", + "" + }, + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 1, + { + {0x79,0xe7,0x98,0x97,0xc5,0x29,0x35,0xbf,0xd9,0x7f,0xc6,0xe0,0x76,0xa6,0x43,0x1a,0x0c,0x75,0x43,0xca,0x8c,0x31,0xe0,0xfc,0x3c,0xf7,0x19,0xbb,0x57,0x2c,0x84,0x2d}, + "", + "", + "" + }, + { + {0x3d,0xde,0xc3,0x23,0x26,0x09,0xd3,0x48,0xd6,0xb8,0xb5,0x31,0x23,0xb4,0xf4,0x0f,0x6d,0x4f,0x53,0x98,0xca,0x58,0x6f,0x08,0x7b,0x04,0x16,0xec,0x3b,0x85,0x14,0x96}, + "", + "", + "" + }, + { + {0xd7,0xd0,0x6e,0x3a,0xfb,0x68,0x36,0x30,0x31,0xe4,0xeb,0x18,0x03,0x5c,0x46,0xce,0xae,0x41,0xbd,0xbe,0xbe,0x78,0x88,0xa4,0x75,0x4b,0xc9,0x84,0x8c,0x59,0x64,0x36,0x86,0x9a,0xea,0xec,0xff,0x05,0x27,0x64,0x9a,0x1f,0x45,0x8b,0x71,0xc9,0xce,0xec,0xec,0x10,0xb5,0x35,0xc0,0x9d,0x01,0xd7,0x20,0x22,0x9a,0xa2,0x28,0x54,0x77,0x06}, + "", + "", + "" + } + }, + + /* ----- Pubkey extraction from malleated p2pkh (20) ----- */ + { + 3, + { /* input plain seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + {0x03,0x78,0xe9,0x56,0x85,0xb7,0x45,0x65,0xfa,0x56,0x75,0x1b,0x84,0xa3,0x2d,0xfd,0x18,0x54,0x5d,0x10,0xd6,0x91,0x64,0x1b,0x83,0x72,0xe3,0x21,0x64,0xfa,0xd6,0x6a}, + {0x72,0xb8,0xae,0x09,0x17,0x5c,0xa7,0x97,0x7f,0x04,0x99,0x3e,0x65,0x1d,0x88,0x68,0x1e,0xd9,0x32,0xdf,0xb9,0x2c,0x51,0x58,0xcd,0xf0,0x16,0x1d,0xd2,0x3f,0xda,0x6e} + }, + { /* input plain pubkeys */ + {0x02,0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + {0x03,0x78,0x2e,0xeb,0x91,0x34,0x31,0xca,0x6e,0x9b,0x8c,0x2f,0xd8,0x0a,0x5f,0x72,0xed,0x20,0x24,0xef,0x72,0xa3,0xc6,0xfb,0x10,0x26,0x3c,0x37,0x99,0x37,0x32,0x33,0x38}, + {0x02,0xe0,0xec,0x4f,0x64,0xb3,0xfa,0x2e,0x46,0x3c,0xcf,0xcf,0x4e,0x85,0x6e,0x37,0xd5,0xe1,0xe2,0x02,0x75,0xbc,0x89,0xec,0x1d,0xef,0x9e,0xb0,0x98,0xef,0xf1,0xf8,0x5d} + }, + 0, + { /* input taproot seckeys */ + "", + "", + "" + }, + { /* input x-only pubkeys */ + "", + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 1, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + "", + "" + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0x46,0x12,0xcd,0xbf,0x84,0x5c,0x66,0xc7,0x51,0x1d,0x70,0xaa,0xb4,0xd9,0xae,0xd1,0x1e,0x49,0xe4,0x8c,0xdb,0x8d,0x79,0x9d,0x78,0x71,0x01,0xcd,0xd0,0xd5,0x3e,0x4f}, + "", + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 1, + { /* outputs to scan */ + {0x46,0x12,0xcd,0xbf,0x84,0x5c,0x66,0xc7,0x51,0x1d,0x70,0xaa,0xb4,0xd9,0xae,0xd1,0x1e,0x49,0xe4,0x8c,0xdb,0x8d,0x79,0x9d,0x78,0x71,0x01,0xcd,0xd0,0xd5,0x3e,0x4f}, + "", + "", + "" + }, + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 1, + { + {0x46,0x12,0xcd,0xbf,0x84,0x5c,0x66,0xc7,0x51,0x1d,0x70,0xaa,0xb4,0xd9,0xae,0xd1,0x1e,0x49,0xe4,0x8c,0xdb,0x8d,0x79,0x9d,0x78,0x71,0x01,0xcd,0xd0,0xd5,0x3e,0x4f}, + "", + "", + "" + }, + { + {0x10,0xbd,0xe9,0x78,0x1d,0xef,0x20,0xd7,0x70,0x1e,0x76,0x03,0xef,0x1b,0x1e,0x5e,0x71,0xc6,0x7b,0xae,0x71,0x54,0x81,0x88,0x14,0xe3,0xc8,0x1e,0xf5,0xb1,0xa3,0xd3}, + "", + "", + "" + }, + { + {0x61,0x37,0x96,0x9f,0x81,0x0e,0x9e,0x8e,0xf6,0xc9,0x75,0x50,0x10,0xe8,0x08,0xf5,0xdd,0x1a,0xed,0x70,0x58,0x82,0xe4,0x4d,0x7f,0x0a,0xe6,0x4e,0xb0,0xc5,0x09,0xec,0x8b,0x62,0xa0,0x67,0x1b,0xee,0x0d,0x59,0x14,0xac,0x27,0xd2,0xc4,0x63,0x44,0x3e,0x28,0xe9,0x99,0xd8,0x2d,0xc3,0xd3,0xa4,0x91,0x9f,0x09,0x38,0x72,0xd9,0x47,0xbb}, + "", + "", + "" + } + }, + + /* ----- P2PKH and P2WPKH Uncompressed Keys are skipped (21) ----- */ + { + 1, + { /* input plain seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + "", + "" + }, + { /* input plain pubkeys */ + {0x02,0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + "", + "" + }, + 0, + { /* input taproot seckeys */ + "", + "", + "" + }, + { /* input x-only pubkeys */ + "", + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 1, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + "", + "" + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0x67,0xfe,0xe2,0x77,0xda,0x9e,0x85,0x42,0xb5,0xd2,0xe6,0xf3,0x2d,0x66,0x0a,0x9b,0xbd,0x3f,0x0e,0x10,0x7c,0x2d,0x53,0x63,0x8a,0xb1,0xd8,0x69,0x08,0x88,0x82,0xd6}, + "", + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 1, + { /* outputs to scan */ + {0x67,0xfe,0xe2,0x77,0xda,0x9e,0x85,0x42,0xb5,0xd2,0xe6,0xf3,0x2d,0x66,0x0a,0x9b,0xbd,0x3f,0x0e,0x10,0x7c,0x2d,0x53,0x63,0x8a,0xb1,0xd8,0x69,0x08,0x88,0x82,0xd6}, + "", + "", + "" + }, + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 1, + { + {0x67,0xfe,0xe2,0x77,0xda,0x9e,0x85,0x42,0xb5,0xd2,0xe6,0xf3,0x2d,0x66,0x0a,0x9b,0xbd,0x3f,0x0e,0x10,0x7c,0x2d,0x53,0x63,0x8a,0xb1,0xd8,0x69,0x08,0x88,0x82,0xd6}, + "", + "", + "" + }, + { + {0x68,0x8f,0xa3,0xae,0xb9,0x7d,0x2a,0x46,0xae,0x87,0xb0,0x35,0x91,0x92,0x1c,0x2e,0xaf,0x4b,0x50,0x5e,0xb0,0xdd,0xca,0x27,0x33,0xc9,0x47,0x01,0xe0,0x10,0x60,0xcf}, + "", + "", + "" + }, + { + {0x72,0xe7,0xad,0x57,0x3a,0xc2,0x32,0x55,0xd4,0x65,0x1d,0x5b,0x03,0x26,0xa2,0x00,0x49,0x65,0x88,0xac,0xb7,0xa4,0x89,0x4b,0x22,0x09,0x22,0x36,0xd5,0xed,0xa6,0xa0,0xa9,0xa4,0xd8,0x42,0x9b,0x02,0x2c,0x22,0x19,0x08,0x1f,0xef,0xce,0x5b,0x33,0x79,0x5c,0xae,0x48,0x8d,0x10,0xf5,0xea,0x94,0x38,0x84,0x9e,0xd8,0x35,0x36,0x24,0xf2}, + "", + "", + "" + } + }, + + /* ----- Skip invalid P2SH inputs (22) ----- */ + { + 1, + { /* input plain seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + "", + "" + }, + { /* input plain pubkeys */ + {0x02,0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + "", + "" + }, + 0, + { /* input taproot seckeys */ + "", + "", + "" + }, + { /* input x-only pubkeys */ + "", + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 1, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + "", + "" + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0x67,0xfe,0xe2,0x77,0xda,0x9e,0x85,0x42,0xb5,0xd2,0xe6,0xf3,0x2d,0x66,0x0a,0x9b,0xbd,0x3f,0x0e,0x10,0x7c,0x2d,0x53,0x63,0x8a,0xb1,0xd8,0x69,0x08,0x88,0x82,0xd6}, + "", + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 1, + { /* outputs to scan */ + {0x67,0xfe,0xe2,0x77,0xda,0x9e,0x85,0x42,0xb5,0xd2,0xe6,0xf3,0x2d,0x66,0x0a,0x9b,0xbd,0x3f,0x0e,0x10,0x7c,0x2d,0x53,0x63,0x8a,0xb1,0xd8,0x69,0x08,0x88,0x82,0xd6}, + "", + "", + "" + }, + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 1, + { + {0x67,0xfe,0xe2,0x77,0xda,0x9e,0x85,0x42,0xb5,0xd2,0xe6,0xf3,0x2d,0x66,0x0a,0x9b,0xbd,0x3f,0x0e,0x10,0x7c,0x2d,0x53,0x63,0x8a,0xb1,0xd8,0x69,0x08,0x88,0x82,0xd6}, + "", + "", + "" + }, + { + {0x68,0x8f,0xa3,0xae,0xb9,0x7d,0x2a,0x46,0xae,0x87,0xb0,0x35,0x91,0x92,0x1c,0x2e,0xaf,0x4b,0x50,0x5e,0xb0,0xdd,0xca,0x27,0x33,0xc9,0x47,0x01,0xe0,0x10,0x60,0xcf}, + "", + "", + "" + }, + { + {0x72,0xe7,0xad,0x57,0x3a,0xc2,0x32,0x55,0xd4,0x65,0x1d,0x5b,0x03,0x26,0xa2,0x00,0x49,0x65,0x88,0xac,0xb7,0xa4,0x89,0x4b,0x22,0x09,0x22,0x36,0xd5,0xed,0xa6,0xa0,0xa9,0xa4,0xd8,0x42,0x9b,0x02,0x2c,0x22,0x19,0x08,0x1f,0xef,0xce,0x5b,0x33,0x79,0x5c,0xae,0x48,0x8d,0x10,0xf5,0xea,0x94,0x38,0x84,0x9e,0xd8,0x35,0x36,0x24,0xf2}, + "", + "", + "" + } + }, + + /* ----- Recipient ignores unrelated outputs (23) ----- */ + { + 1, + { /* input plain seckeys */ + {0x03,0x78,0xe9,0x56,0x85,0xb7,0x45,0x65,0xfa,0x56,0x75,0x1b,0x84,0xa3,0x2d,0xfd,0x18,0x54,0x5d,0x10,0xd6,0x91,0x64,0x1b,0x83,0x72,0xe3,0x21,0x64,0xfa,0xd6,0x6a}, + "", + "" + }, + { /* input plain pubkeys */ + {0x03,0x78,0x2e,0xeb,0x91,0x34,0x31,0xca,0x6e,0x9b,0x8c,0x2f,0xd8,0x0a,0x5f,0x72,0xed,0x20,0x24,0xef,0x72,0xa3,0xc6,0xfb,0x10,0x26,0x3c,0x37,0x99,0x37,0x32,0x33,0x38}, + "", + "" + }, + 1, + { /* input taproot seckeys */ + {0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1}, + "", + "" + }, + { /* input x-only pubkeys */ + {0x5a,0x1e,0x61,0xf8,0x98,0x17,0x30,0x40,0xe2,0x06,0x16,0xd4,0x3e,0x9f,0x49,0x6f,0xba,0x90,0x33,0x8a,0x39,0xfa,0xa1,0xed,0x98,0xfc,0xba,0xee,0xe4,0xdd,0x9b,0xe5}, + "", + "" + }, + /* smallest outpoint */ + {0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00}, + 1, + { /* recipient pubkeys (address data) */ + { + {0x02,0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35,0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4}, + {0x02,0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c,0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36} + }, + { + "", + "" + }, + { + "", + "" + }, + { + "", + "" + } + }, + { /* recipient outputs */ + {0xf2,0x07,0x16,0x2b,0x1a,0x7a,0xbc,0x51,0xc4,0x20,0x17,0xbe,0xf0,0x55,0xe9,0xec,0x1e,0xfc,0x3d,0x35,0x67,0xcb,0x72,0x03,0x57,0xe2,0xb8,0x43,0x25,0xdb,0x33,0xac}, + "", + "", + "" + }, + /* receiver data (scan and spend seckeys) */ + {0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33,0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c}, + {0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39,0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3}, + 2, + { /* outputs to scan */ + {0x78,0x2e,0xeb,0x91,0x34,0x31,0xca,0x6e,0x9b,0x8c,0x2f,0xd8,0x0a,0x5f,0x72,0xed,0x20,0x24,0xef,0x72,0xa3,0xc6,0xfb,0x10,0x26,0x3c,0x37,0x99,0x37,0x32,0x33,0x38}, + {0x84,0x17,0x92,0xc3,0x3c,0x9d,0xc6,0x19,0x3e,0x76,0x74,0x41,0x34,0x12,0x5d,0x40,0xad,0xd8,0xf2,0xf4,0xa9,0x64,0x75,0xf2,0x8b,0xa1,0x50,0xbe,0x03,0x2d,0x64,0xe8}, + "", + "" + }, + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, /* labels */ + /* expected output data (pubkeys and seckey tweaks) */ + 0, + { + "", + "", + "", + "" + }, + { + "", + "", + "", + "" + }, + { + "", + "", + "", + "" + } + }, + +}; diff --git a/src/secp256k1.c b/src/secp256k1.c index 15a5eede67..0ca054d4d9 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -804,3 +804,7 @@ int secp256k1_tagged_sha256(const secp256k1_context* ctx, unsigned char *hash32, #ifdef ENABLE_MODULE_ELLSWIFT # include "modules/ellswift/main_impl.h" #endif + +#ifdef ENABLE_MODULE_SILENTPAYMENTS +# include "modules/silentpayments/main_impl.h" +#endif diff --git a/src/tests.c b/src/tests.c index e653aeea58..bb74513857 100644 --- a/src/tests.c +++ b/src/tests.c @@ -7285,6 +7285,10 @@ static void run_ecdsa_wycheproof(void) { # include "modules/ellswift/tests_impl.h" #endif +#ifdef ENABLE_MODULE_SILENTPAYMENTS +# include "modules/silentpayments/tests_impl.h" +#endif + static void run_secp256k1_memczero_test(void) { unsigned char buf1[6] = {1, 2, 3, 4, 5, 6}; unsigned char buf2[sizeof(buf1)]; @@ -7633,6 +7637,10 @@ int main(int argc, char **argv) { run_ellswift_tests(); #endif +#ifdef ENABLE_MODULE_SILENTPAYMENTS + run_silentpayments_tests(); +#endif + /* util tests */ run_secp256k1_memczero_test(); run_secp256k1_byteorder_tests(); diff --git a/tools/bech32m.py b/tools/bech32m.py new file mode 100644 index 0000000000..795e153863 --- /dev/null +++ b/tools/bech32m.py @@ -0,0 +1,135 @@ +# Copyright (c) 2017, 2020 Pieter Wuille +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +"""Reference implementation for Bech32/Bech32m and segwit addresses.""" + + +from enum import Enum + +class Encoding(Enum): + """Enumeration type to list the various supported encodings.""" + BECH32 = 1 + BECH32M = 2 + +CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" +BECH32M_CONST = 0x2bc830a3 + +def bech32_polymod(values): + """Internal function that computes the Bech32 checksum.""" + generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] + chk = 1 + for value in values: + top = chk >> 25 + chk = (chk & 0x1ffffff) << 5 ^ value + for i in range(5): + chk ^= generator[i] if ((top >> i) & 1) else 0 + return chk + + +def bech32_hrp_expand(hrp): + """Expand the HRP into values for checksum computation.""" + return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp] + + +def bech32_verify_checksum(hrp, data): + """Verify a checksum given HRP and converted data characters.""" + const = bech32_polymod(bech32_hrp_expand(hrp) + data) + if const == 1: + return Encoding.BECH32 + if const == BECH32M_CONST: + return Encoding.BECH32M + return None + +def bech32_create_checksum(hrp, data, spec): + """Compute the checksum values given HRP and data.""" + values = bech32_hrp_expand(hrp) + data + const = BECH32M_CONST if spec == Encoding.BECH32M else 1 + polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const + return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] + + +def bech32_encode(hrp, data, spec): + """Compute a Bech32 string given HRP and data values.""" + combined = data + bech32_create_checksum(hrp, data, spec) + return hrp + '1' + ''.join([CHARSET[d] for d in combined]) + +def bech32_decode(bech): + """Validate a Bech32/Bech32m string, and determine HRP and data.""" + if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or + (bech.lower() != bech and bech.upper() != bech)): + return (None, None, None) + bech = bech.lower() + pos = bech.rfind('1') + + # remove the requirement that bech32m be less than 90 chars + if pos < 1 or pos + 7 > len(bech): + return (None, None, None) + if not all(x in CHARSET for x in bech[pos+1:]): + return (None, None, None) + hrp = bech[:pos] + data = [CHARSET.find(x) for x in bech[pos+1:]] + spec = bech32_verify_checksum(hrp, data) + if spec is None: + return (None, None, None) + return (hrp, data[:-6], spec) + +def convertbits(data, frombits, tobits, pad=True): + """General power-of-2 base conversion.""" + acc = 0 + bits = 0 + ret = [] + maxv = (1 << tobits) - 1 + max_acc = (1 << (frombits + tobits - 1)) - 1 + for value in data: + if value < 0 or (value >> frombits): + return None + acc = ((acc << frombits) | value) & max_acc + bits += frombits + while bits >= tobits: + bits -= tobits + ret.append((acc >> bits) & maxv) + if pad: + if bits: + ret.append((acc << (tobits - bits)) & maxv) + elif bits >= frombits or ((acc << (tobits - bits)) & maxv): + return None + return ret + + +def decode(hrp, addr): + """Decode a segwit address.""" + hrpgot, data, spec = bech32_decode(addr) + if hrpgot != hrp: + return (None, None) + decoded = convertbits(data[1:], 5, 8, False) + if decoded is None or len(decoded) < 2: + return (None, None) + if data[0] > 16: + return (None, None) + return (data[0], decoded) + + +def encode(hrp, witver, witprog): + """Encode a segwit address.""" + spec = Encoding.BECH32 if witver == 0 else Encoding.BECH32M + ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5), spec) + if decode(hrp, ret) == (None, None): + return None + return ret diff --git a/tools/ripemd160.py b/tools/ripemd160.py new file mode 100644 index 0000000000..12801364b4 --- /dev/null +++ b/tools/ripemd160.py @@ -0,0 +1,130 @@ +# Copyright (c) 2021 Pieter Wuille +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test-only pure Python RIPEMD160 implementation.""" + +import unittest + +# Message schedule indexes for the left path. +ML = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 +] + +# Message schedule indexes for the right path. +MR = [ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 +] + +# Rotation counts for the left path. +RL = [ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 +] + +# Rotation counts for the right path. +RR = [ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 +] + +# K constants for the left path. +KL = [0, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e] + +# K constants for the right path. +KR = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0] + + +def fi(x, y, z, i): + """The f1, f2, f3, f4, and f5 functions from the specification.""" + if i == 0: + return x ^ y ^ z + elif i == 1: + return (x & y) | (~x & z) + elif i == 2: + return (x | ~y) ^ z + elif i == 3: + return (x & z) | (y & ~z) + elif i == 4: + return x ^ (y | ~z) + else: + assert False + + +def rol(x, i): + """Rotate the bottom 32 bits of x left by i bits.""" + return ((x << i) | ((x & 0xffffffff) >> (32 - i))) & 0xffffffff + + +def compress(h0, h1, h2, h3, h4, block): + """Compress state (h0, h1, h2, h3, h4) with block.""" + # Left path variables. + al, bl, cl, dl, el = h0, h1, h2, h3, h4 + # Right path variables. + ar, br, cr, dr, er = h0, h1, h2, h3, h4 + # Message variables. + x = [int.from_bytes(block[4*i:4*(i+1)], 'little') for i in range(16)] + + # Iterate over the 80 rounds of the compression. + for j in range(80): + rnd = j >> 4 + # Perform left side of the transformation. + al = rol(al + fi(bl, cl, dl, rnd) + x[ML[j]] + KL[rnd], RL[j]) + el + al, bl, cl, dl, el = el, al, bl, rol(cl, 10), dl + # Perform right side of the transformation. + ar = rol(ar + fi(br, cr, dr, 4 - rnd) + x[MR[j]] + KR[rnd], RR[j]) + er + ar, br, cr, dr, er = er, ar, br, rol(cr, 10), dr + + # Compose old state, left transform, and right transform into new state. + return h1 + cl + dr, h2 + dl + er, h3 + el + ar, h4 + al + br, h0 + bl + cr + + +def ripemd160(data): + """Compute the RIPEMD-160 hash of data.""" + # Initialize state. + state = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0) + # Process full 64-byte blocks in the input. + for b in range(len(data) >> 6): + state = compress(*state, data[64*b:64*(b+1)]) + # Construct final blocks (with padding and size). + pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63) + fin = data[len(data) & ~63:] + pad + (8 * len(data)).to_bytes(8, 'little') + # Process final blocks. + for b in range(len(fin) >> 6): + state = compress(*state, fin[64*b:64*(b+1)]) + # Produce output. + return b"".join((h & 0xffffffff).to_bytes(4, 'little') for h in state) + + +class TestFrameworkKey(unittest.TestCase): + def test_ripemd160(self): + """RIPEMD-160 test vectors.""" + # See https://homes.esat.kuleuven.be/~bosselae/ripemd160.html + for msg, hexout in [ + (b"", "9c1185a5c5e9fc54612808977ee8f548b2258d31"), + (b"a", "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"), + (b"abc", "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"), + (b"message digest", "5d0689ef49d2fae572b881b123a85ffa21595f36"), + (b"abcdefghijklmnopqrstuvwxyz", + "f71c27109c692c1b56bbdceb5b9d2865b3708dbc"), + (b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "12a053384a9c0c88e405a06c27dcf49ada62eb2b"), + (b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + "b0e20b6e3116640286ed3a87a5713079b21f5189"), + (b"1234567890" * 8, "9b752e45573d4b39f4dbd3323cab82bf63326bfb"), + (b"a" * 1000000, "52783243c1697bdbe16d37f97f68f08325dc1528") + ]: + self.assertEqual(ripemd160(msg).hex(), hexout) diff --git a/tools/tests_silentpayments_generate.py b/tools/tests_silentpayments_generate.py new file mode 100755 index 0000000000..527189761a --- /dev/null +++ b/tools/tests_silentpayments_generate.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python3 +import hashlib +import json +import sys + +import bech32m +import ripemd160 + +NUMS_H = bytes.fromhex("50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0") + +def sha256(s): + return hashlib.sha256(s).digest() + +def hash160(s): + return ripemd160.ripemd160(sha256(s)) + +def smallest_outpoint(outpoints): + serialized_outpoints = [bytes.fromhex(txid)[::-1] + n.to_bytes(4, 'little') for txid, n in outpoints] + return sorted(serialized_outpoints)[0] + +def decode_silent_payments_address(address): + _, data = bech32m.decode("sp", address) + data = bytes(data) # convert from list to bytes + assert len(data) == 66 + return data[:33], data[33:] + +def is_p2tr(s): # OP_1 OP_PUSHBYTES_32 <32 bytes> + return (len(s) == 34) and (s[0] == 0x51) and (s[1] == 0x20) + +def is_p2wpkh(s): # OP_0 OP_PUSHBYTES_20 <20 bytes> + return (len(s) == 22) and (s[0] == 0x00) and (s[1] == 0x14) + +def is_p2sh(s): # OP_HASH160 OP_PUSHBYTES_20 <20 bytes> OP_EQUAL + return (len(s) == 23) and (s[0] == 0xA9) and (s[1] == 0x14) and (s[-1] == 0x87) + +def is_p2pkh(s): # OP_DUP OP_HASH160 OP_PUSHBYTES_20 <20 bytes> OP_EQUALVERIFY OP_CHECKSIG + return (len(s) == 25) and (s[0] == 0x76) and (s[1] == 0xA9) and (s[2] == 0x14) and \ + (s[-2] == 0x88) and (s[-1] == 0xAC) + +def get_pubkey_from_input(spk, script_sig, witness): + # build witness stack from raw witness data + witness_stack = [] + no_witness_items = 0 + if len(witness) > 0: + no_witness_items = witness[0] + witness = witness[1:] + for i in range(no_witness_items): + item_len = witness[0] + witness_stack.append(witness[1:item_len+1]) + witness = witness[item_len+1:] + + if is_p2pkh(spk): + spk_pkh = spk[3:3 + 20] + for i in range(len(script_sig), 0, -1): + if i - 33 >= 0: + pk = script_sig[i - 33:i] + if hash160(pk) == spk_pkh: + return pk + elif is_p2sh(spk) and is_p2wpkh(script_sig[1:]): + pubkey = witness_stack[-1] + if len(pubkey) == 33: + return pubkey + elif is_p2wpkh(spk): + # the witness must contain two items and the second item is the pubkey + pubkey = witness_stack[-1] + if len(pubkey) == 33: + return pubkey + elif is_p2tr(spk): + if len(witness_stack) > 1 and witness_stack[-1][0] == 0x50: + witness_stack.pop() + if len(witness_stack) > 1: # script-path spend? + control_block = witness_stack[-1] + internal_key = control_block[1:33] + if internal_key == NUMS_H: # skip + return b'' + return spk[2:] + + return b'' + +def to_c_array(x): + if x == "": + return "" + s = ',0x'.join(a+b for a,b in zip(x[::2], x[1::2])) + return "0x" + s + +def emit_key_material(comment, keys, include_count=False): + global out + if include_count: + out += f" {len(keys)}," + "\n" + out += f" {{ /* {comment} */" + "\n" + for i in range(3): + out += " " + if i < len(keys): + out += "{" + out += to_c_array(keys[i]) + out += "}" + else: + out += '""' + if i != 2: + out += ',' + out += "\n" + out += " },\n" + +def emit_receiver_addr_material(receiver_pubkeys): + global out + out += f" {len(receiver_pubkeys)}," + "\n" + out += " { /* recipient pubkeys (address data) */\n" + for i in range(4): + out += " {\n" + if i < len(receiver_pubkeys): + out += " {" + out += to_c_array(receiver_pubkeys[i][0]) + out += "},\n" + out += " {" + out += to_c_array(receiver_pubkeys[i][1]) + out += "}\n" + else: + out += ' "",\n' + out += ' ""\n' + out += " }" + if i != 3: + out += ',' + out += "\n" + out += " },\n" + +def emit_outputs(comment, outputs, include_count=False, last=False): + global out + if include_count: + out += f" {len(outputs)}," + "\n" + if comment: + out += f" {{ /* {comment} */" + "\n" + else: + out += " {\n" + for i in range(4): + if i < len(outputs): + out += " {" + out += to_c_array(outputs[i]) + out += "}" + else: + out += ' ""' + if i != 3: + out += ',' + out += "\n" + out += " }" + if not last: + out += "," + out += "\n" + +filename_input = sys.argv[1] +with open(filename_input, 'rb') as f: + hash_calculated = sha256(f.read()).hex() + if hash_calculated != "d69df3ff4e79afc6bbfc79ee1dc0415b7fa3455508ed39f2d1f2a6f316d4b4d8": + print("Error: input file doesn't match hash from BIP352", file=sys.stderr) + sys.exit(1) + +with open(filename_input) as f: + test_vectors = json.load(f) + +out = "" +num_vectors = 0 + +for test_nr, test_vector in enumerate(test_vectors): + # determine input private and public keys, grouped into plain and taproot/x-only + input_plain_seckeys = [] + input_taproot_seckeys = [] + input_plain_pubkeys = [] + input_xonly_pubkeys = [] + outpoints = [] + for i in test_vector['sending'][0]['given']['vin']: + pub_key = get_pubkey_from_input(bytes.fromhex(i['prevout']['scriptPubKey']['hex']), + bytes.fromhex(i['scriptSig']), bytes.fromhex(i['txinwitness'])) + if len(pub_key) == 33: # regular input + input_plain_seckeys.append(i['private_key']) + input_plain_pubkeys.append(pub_key.hex()) + elif len(pub_key) == 32: # taproot input + input_taproot_seckeys.append(i['private_key']) + input_xonly_pubkeys.append(pub_key.hex()) + outpoints.append((i['txid'], i['vout'])) + if len(input_plain_pubkeys) == 0 and len(input_xonly_pubkeys) == 0: + continue + + num_vectors += 1 + out += f" /* ----- {test_vector['comment']} ({num_vectors}) ----- */\n" + out += " {\n" + + outpoint_L = smallest_outpoint(outpoints).hex() + emit_key_material("input plain seckeys", input_plain_seckeys, include_count=True) + emit_key_material("input plain pubkeys", input_plain_pubkeys) + emit_key_material("input taproot seckeys", input_taproot_seckeys, include_count=True) + emit_key_material("input x-only pubkeys", input_xonly_pubkeys) + out += " /* smallest outpoint */\n" + out += " {" + out += to_c_array(outpoint_L) + out += "},\n" + + # emit recipient pubkeys (address data) + recipient_pubkeys = [] + for recipient_address, recipient_value in test_vector['sending'][0]['given']['recipients']: + recipient_B_scan, recipient_B_spend = decode_silent_payments_address(recipient_address) + recipient_pubkeys.append((recipient_B_scan.hex(), recipient_B_spend.hex())) + emit_receiver_addr_material(recipient_pubkeys) + + # emit recipient outputs + emit_outputs("recipient outputs", [o[0] for o in test_vector['sending'][0]['expected']['outputs']]) + + # emit receiver scan/spend seckeys + recv_test_given = test_vector['receiving'][0]['given'] + recv_test_expected = test_vector['receiving'][0]['expected'] + out += " /* receiver data (scan and spend seckeys) */\n" + out += " {" + f"{to_c_array(recv_test_given['key_material']['scan_priv_key'])}" + "},\n" + out += " {" + f"{to_c_array(recv_test_given['key_material']['spend_priv_key'])}" + "},\n" + + # emit receiver to-scan outputs, labels and expected-found outputs + emit_outputs("outputs to scan", recv_test_given['outputs'], include_count=True) + labels = recv_test_given['labels'] + out += f" {len(labels)}, " + "{" + for i in range(4): + if i < len(labels): + out += f"{labels[i]}" + else: + out += "0xffffffff" + if i != 3: + out += ", " + out += "}, /* labels */\n" + expected_pubkeys = [o['pub_key'] for o in recv_test_expected['outputs']] + expected_tweaks = [o['priv_key_tweak'] for o in recv_test_expected['outputs']] + expected_signatures = [o['signature'] for o in recv_test_expected['outputs']] + out += " /* expected output data (pubkeys and seckey tweaks) */\n" + emit_outputs("", expected_pubkeys, include_count=True) + emit_outputs("", expected_tweaks) + emit_outputs("", expected_signatures, last=True) + + out += " }" + if test_nr != len(test_vectors)-1: + out += "," + out += "\n\n" + +STRUCT_DEFINITIONS = """ +#define MAX_INPUTS_PER_TEST_CASE 3 +#define MAX_OUTPUTS_PER_TEST_CASE 4 + +struct bip352_receiver_addressdata { + unsigned char scan_pubkey[33]; + unsigned char spend_pubkey[33]; +}; + +struct bip352_test_vector { + /* Inputs (private keys / public keys + smallest outpoint) */ + size_t num_plain_inputs; + unsigned char plain_seckeys[MAX_INPUTS_PER_TEST_CASE][32]; + unsigned char plain_pubkeys[MAX_INPUTS_PER_TEST_CASE][33]; + + size_t num_taproot_inputs; + unsigned char taproot_seckeys[MAX_INPUTS_PER_TEST_CASE][32]; + unsigned char xonly_pubkeys[MAX_INPUTS_PER_TEST_CASE][32]; + + unsigned char outpoint_smallest[36]; + + /* Given sender data (pubkeys encoded per output address to send to) */ + size_t num_recipient_outputs; + struct bip352_receiver_addressdata receiver_pubkeys[MAX_OUTPUTS_PER_TEST_CASE]; + + /* Expected sender data */ + unsigned char recipient_outputs[MAX_OUTPUTS_PER_TEST_CASE][32]; + + /* Given receiver data */ + unsigned char scan_seckey[32]; + unsigned char spend_seckey[32]; + size_t num_to_scan_outputs; + unsigned char to_scan_outputs[MAX_OUTPUTS_PER_TEST_CASE][32]; + size_t num_labels; + unsigned int label_integers[MAX_OUTPUTS_PER_TEST_CASE]; + + /* Expected receiver data */ + size_t num_found_outputs; + unsigned char found_output_pubkeys[MAX_OUTPUTS_PER_TEST_CASE][32]; + unsigned char found_seckey_tweaks[MAX_OUTPUTS_PER_TEST_CASE][32]; + unsigned char found_signatures[MAX_OUTPUTS_PER_TEST_CASE][64]; +}; +""" + +print("/* Note: this file was autogenerated using tests_silentpayments_generate.py. Do not edit. */") +print(f"#define SECP256K1_SILENTPAYMENTS_NUMBER_TESTVECTORS ({num_vectors})") + +print(STRUCT_DEFINITIONS) + +print("static const struct bip352_test_vector bip352_test_vectors[SECP256K1_SILENTPAYMENTS_NUMBER_TESTVECTORS] = {") +print(out, end='') +print("};")