From bb2c5bff15149acd6fd8977e52cba4788cfa7216 Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Wed, 27 Sep 2023 17:22:14 +0200 Subject: [PATCH 01/12] build: add skeleton for new silentpayments (BIP352) module --- CMakeLists.txt | 2 ++ Makefile.am | 8 +++++ configure.ac | 14 ++++++++ include/secp256k1_silentpayments.h | 32 +++++++++++++++++++ src/CMakeLists.txt | 9 ++++++ .../silentpayments/Makefile.am.include | 2 ++ src/modules/silentpayments/main_impl.h | 16 ++++++++++ src/secp256k1.c | 4 +++ 8 files changed, 87 insertions(+) create mode 100644 include/secp256k1_silentpayments.h create mode 100644 src/modules/silentpayments/Makefile.am.include create mode 100644 src/modules/silentpayments/main_impl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 11dc3f6e53..2d1c10ddd4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,7 @@ option(SECP256K1_ENABLE_MODULE_EXTRAKEYS "Enable extrakeys module." ON) option(SECP256K1_ENABLE_MODULE_SCHNORRSIG "Enable schnorrsig module." ON) option(SECP256K1_ENABLE_MODULE_MUSIG "Enable musig module." ON) option(SECP256K1_ENABLE_MODULE_ELLSWIFT "Enable ElligatorSwift module." ON) +option(SECP256K1_ENABLE_MODULE_SILENTPAYMENTS "Enable Silent Payments module." ON) option(SECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS "Enable external default callback functions." OFF) if(SECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS) @@ -285,6 +286,7 @@ message(" extrakeys ........................... ${SECP256K1_ENABLE_MODULE_EXTRA message(" schnorrsig .......................... ${SECP256K1_ENABLE_MODULE_SCHNORRSIG}") message(" musig ............................... ${SECP256K1_ENABLE_MODULE_MUSIG}") 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 table size ............... ${SECP256K1_ECMULT_GEN_KB} KiB") diff --git a/Makefile.am b/Makefile.am index d511853b05..d71c7c7d32 100644 --- a/Makefile.am +++ b/Makefile.am @@ -250,6 +250,10 @@ maintainer-clean-local: clean-precomp ### (see the comments in the previous section for detailed rationale) TESTVECTORS = src/wycheproof/ecdsa_secp256k1_sha256_bitcoin_test.h +if ENABLE_MODULE_SILENTPAYMENTS +TESTVECTORS += src/modules/silentpayments/vectors.h +endif + if ENABLE_MODULE_ECDH TESTVECTORS += src/wycheproof/ecdh_secp256k1_test.h endif @@ -311,3 +315,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/configure.ac b/configure.ac index 2f156ddc25..ee443fd514 100644 --- a/configure.ac +++ b/configure.ac @@ -191,6 +191,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=yes]]), [], + [SECP_SET_DEFAULT([enable_module_silentpayments], [yes], [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])]) @@ -397,6 +401,14 @@ 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 + if test x"$enable_module_schnorrsig" = x"no"; then + AC_MSG_ERROR([Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the silentpayments module.]) + fi + enable_module_schnorrsig=yes + 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 @@ -462,6 +474,7 @@ AM_CONDITIONAL([ENABLE_MODULE_EXTRAKEYS], [test x"$enable_module_extrakeys" = x" AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG], [test x"$enable_module_schnorrsig" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_MUSIG], [test x"$enable_module_musig" = 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"]) @@ -486,6 +499,7 @@ echo " module extrakeys = $enable_module_extrakeys" echo " module schnorrsig = $enable_module_schnorrsig" echo " module musig = $enable_module_musig" 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..2519ba9a48 --- /dev/null +++ b/include/secp256k1_silentpayments.h @@ -0,0 +1,32 @@ +#ifndef SECP256K1_SILENTPAYMENTS_H +#define SECP256K1_SILENTPAYMENTS_H + +#include "secp256k1.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** This module provides an implementation for Silent Payments, as specified in + * BIP352. This particularly involves the creation of input tweak data by + * summing up secret or public keys and the derivation of a shared secret using + * Elliptic Curve Diffie-Hellman. Combined are either: + * - spender's secret keys and recipient's public key (a * B, sender side) + * - spender's public keys and recipient's secret key (A * b, recipient 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 a module for + * abstracting away the elliptic-curve operations required for the protocol. For + * any wallet software already using libsecp256k1, this API should provide all + * the functions needed for a Silent Payments implementation without requiring + * any further elliptic-curve operations from the wallet. + */ + +#ifdef __cplusplus +} +#endif + +#endif /* SECP256K1_SILENTPAYMENTS_H */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fa3b2903eb..1740f1f507 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -21,6 +21,15 @@ if(SECP256K1_ENABLE_MODULE_MUSIG) set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_musig.h) endif() +if(SECP256K1_ENABLE_MODULE_SILENTPAYMENTS) + if(DEFINED SECP256K1_ENABLE_MODULE_SCHNORRSIG AND NOT SECP256K1_ENABLE_MODULE_SCHNORRSIG) + message(FATAL_ERROR "Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the silent payments module.") + endif() + set(SECP256K1_ENABLE_MODULE_SCHNORRSIG ON) + add_compile_definitions(ENABLE_MODULE_SILENTPAYMENTS=1) + set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_silentpayments.h) +endif() + if(SECP256K1_ENABLE_MODULE_SCHNORRSIG) if(DEFINED SECP256K1_ENABLE_MODULE_EXTRAKEYS AND NOT SECP256K1_ENABLE_MODULE_EXTRAKEYS) message(FATAL_ERROR "Module dependency error: You have disabled the extrakeys module explicitly, but it is required by the schnorrsig module.") diff --git a/src/modules/silentpayments/Makefile.am.include b/src/modules/silentpayments/Makefile.am.include new file mode 100644 index 0000000000..842a33e2d9 --- /dev/null +++ b/src/modules/silentpayments/Makefile.am.include @@ -0,0 +1,2 @@ +include_HEADERS += include/secp256k1_silentpayments.h +noinst_HEADERS += src/modules/silentpayments/main_impl.h diff --git a/src/modules/silentpayments/main_impl.h b/src/modules/silentpayments/main_impl.h new file mode 100644 index 0000000000..f8ccdd7baa --- /dev/null +++ b/src/modules/silentpayments/main_impl.h @@ -0,0 +1,16 @@ +/*********************************************************************** + * 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_silentpayments.h" + +/* TODO: implement functions for sender side. */ + +/* TODO: implement functions for receiver side. */ + +#endif diff --git a/src/secp256k1.c b/src/secp256k1.c index 26336a45cc..a0256aa176 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -817,3 +817,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 From 6fe3a7ae94866c42dd52520074759dc384d2b84d Mon Sep 17 00:00:00 2001 From: josibake Date: Mon, 25 Mar 2024 17:23:37 +0100 Subject: [PATCH 02/12] silentpayments: sending Add a routine for the entire sending flow which takes a set of private keys, the smallest outpoint, and list of recipients and returns a list of x-only public keys by performing the following steps: 1. Sum up the private keys 2. Calculate the input_hash 3. For each recipient group: 3a. Calculate a shared secret 3b. Create the requested number of outputs This function assumes a single sender context in that it requires the sender to have access to all of the private keys. In the future, this API may be expanded to allow for a multiple senders or for a single sender who does not have access to all private keys at any given time, but for now these modes are considered out of scope / unsafe. Internal to the library, add: 1. A function for creating shared secrets (i.e., a*B or b*A) 2. A function for generating the "SharedSecret" tagged hash 3. A function for creating a single output public key --- include/secp256k1_silentpayments.h | 101 ++++++++ src/modules/silentpayments/main_impl.h | 324 +++++++++++++++++++++++- src/modules/silentpayments/tests_impl.h | 271 ++++++++++++++++++++ src/tests.c | 8 + 4 files changed, 702 insertions(+), 2 deletions(-) create mode 100644 src/modules/silentpayments/tests_impl.h diff --git a/include/secp256k1_silentpayments.h b/include/secp256k1_silentpayments.h index 2519ba9a48..11167525ef 100644 --- a/include/secp256k1_silentpayments.h +++ b/include/secp256k1_silentpayments.h @@ -2,6 +2,7 @@ #define SECP256K1_SILENTPAYMENTS_H #include "secp256k1.h" +#include "secp256k1_extrakeys.h" #ifdef __cplusplus extern "C" { @@ -25,6 +26,106 @@ extern "C" { * any further elliptic-curve operations from the wallet. */ + +/** The data from a single recipient address + * + * This struct serves as an input argument to `silentpayments_sender_create_outputs`. + * + * `index` must be set to the position (starting with 0) of this recipient in the + * `recipients` array passed to `silentpayments_sender_create_outputs`. It is + * used to map the returned generated outputs back to the original recipient. + * + * Note: + * The spend public key named `spend_pubkey` may have been optionally tweaked with + * a label by the recipient. Whether `spend_pubkey` has actually been tagged with + * a label is irrelevant for the sender. As a documentation convention in this API, + * `unlabeled_spend_pubkey` is used to indicate when the unlabeled spend public must + * be used. + */ +typedef struct secp256k1_silentpayments_recipient { + secp256k1_pubkey scan_pubkey; + secp256k1_pubkey spend_pubkey; + size_t index; +} secp256k1_silentpayments_recipient; + +/** Create Silent Payment outputs for recipient(s). + * + * Given a list of n secret keys a_1...a_n (one for each silent payment + * eligible input to spend), a serialized outpoint, and a list of recipients, + * create the taproot outputs. Inputs with conditional branches or multiple + * public keys are excluded from silent payments eligible inputs; see BIP352 + * for more information. + * + * `outpoint_smallest36` refers to the smallest outpoint lexicographically + * from the transaction inputs (both silent payments eligible and non-eligible + * inputs). This value MUST be the smallest outpoint out of all of the + * transaction inputs, otherwise the recipient will be unable to find the + * payment. Determining the smallest outpoint from the list of transaction + * inputs is the responsibility of the caller. It is strongly recommended + * that implementations ensure they are doing this correctly by using the + * test vectors from BIP352. + * + * When creating more than one generated output, all of the generated outputs + * MUST be included in the final transaction. Dropping any of the generated + * outputs from the final transaction may make all or some of the outputs + * unfindable by the recipient. + * + * Returns: 1 if creation of outputs was successful. + * 0 on failure. This is expected only with an adversarially chosen + * recipient spend key. Specifically, failure occurs when: + * - Input secret keys sum to 0 or the negation of a spend key + * (negligible probability if at least one of the input secret + * keys is uniformly random and independent of all other keys) + * - A hash output is not a valid scalar (negligible probability + * per hash evaluation) + * + * Args: ctx: pointer to a context object + * (not secp256k1_context_static). + * Out: generated_outputs: pointer to an array of pointers to xonly public keys, + * one per recipient. + * The outputs are ordered to match the original + * ordering of the recipient objects, i.e., + * `generated_outputs[0]` is the generated output + * for the `_silentpayments_recipient` object with + * index = 0. + * In: recipients: pointer to an array of pointers to silent payment + * recipients, where each recipient is a scan public + * key, a spend public key, and an index indicating + * its position in the original ordering. The + * recipient array will be grouped by scan public key + * in place (as specified in BIP0352), but generated + * outputs are saved in the `generated_outputs` array + * to match the original ordering (using the index + * field). This ensures the caller is able to match + * the generated outputs to the correct silent + * payment addresses. The same recipient can be + * passed multiple times to create multiple outputs + * for the same recipient. + * n_recipients: the size of the recipients array. + * outpoint_smallest36: serialized (36-byte) smallest outpoint + * (lexicographically) from the transaction inputs + * taproot_seckeys: pointer to an array of pointers to taproot + * keypair inputs (can be NULL if no secret keys + * of taproot inputs are used) + * n_taproot_seckeys: the size of taproot_seckeys array. + * plain_seckeys: pointer to an array of pointers to 32-byte + * secret keys of non-taproot inputs (can be NULL + * if no secret keys of non-taproot inputs are + * used) + * n_plain_seckeys: the size of the plain_seckeys array. + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_sender_create_outputs( + const secp256k1_context *ctx, + secp256k1_xonly_pubkey **generated_outputs, + const secp256k1_silentpayments_recipient **recipients, + size_t n_recipients, + const unsigned char *outpoint_smallest36, + const secp256k1_keypair * const *taproot_seckeys, + size_t n_taproot_seckeys, + const unsigned char * const *plain_seckeys, + size_t n_plain_seckeys +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5); + #ifdef __cplusplus } #endif diff --git a/src/modules/silentpayments/main_impl.h b/src/modules/silentpayments/main_impl.h index f8ccdd7baa..9e2f1160cb 100644 --- a/src/modules/silentpayments/main_impl.h +++ b/src/modules/silentpayments/main_impl.h @@ -7,10 +7,330 @@ #define SECP256K1_MODULE_SILENTPAYMENTS_MAIN_H #include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_extrakeys.h" #include "../../../include/secp256k1_silentpayments.h" -/* TODO: implement functions for sender side. */ +#include "../../eckey.h" +#include "../../ecmult.h" +#include "../../ecmult_const.h" +#include "../../ecmult_gen.h" +#include "../../group.h" +#include "../../hash.h" +#include "../../hsort.h" -/* TODO: implement functions for receiver side. */ +/** Sort an array of silent payment recipients. This is used to group recipients by scan pubkey to + * ensure the correct values of k are used when creating multiple outputs for a recipient. + * + * Note: secp256k1_ec_pubkey_cmp uses heap sort, which is unstable. Developers cannot and should not + * rely on deterministic sorting of _recipient objects. + */ +static int secp256k1_silentpayments_recipient_sort_cmp(const void* pk1, const void* pk2, void *ctx) { + return secp256k1_ec_pubkey_cmp((secp256k1_context *)ctx, + &(*(const secp256k1_silentpayments_recipient **)pk1)->scan_pubkey, + &(*(const secp256k1_silentpayments_recipient **)pk2)->scan_pubkey + ); +} + +static void secp256k1_silentpayments_recipient_sort(const secp256k1_context* ctx, const secp256k1_silentpayments_recipient **recipients, size_t n_recipients) { + /* Suppress wrong warning (fixed in MSVC 19.33) */ + #if defined(_MSC_VER) && (_MSC_VER < 1933) + #pragma warning(push) + #pragma warning(disable: 4090) + #endif + + secp256k1_hsort(recipients, n_recipients, sizeof(*recipients), secp256k1_silentpayments_recipient_sort_cmp, (void *)ctx); + + #if defined(_MSC_VER) && (_MSC_VER < 1933) + #pragma warning(pop) + #endif +} + +/** 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; +} + +/** Callers must ensure that pubkey_sum is not the point at infinity before calling this function. */ +static int secp256k1_silentpayments_calculate_input_hash_scalar(secp256k1_scalar *input_hash_scalar, const unsigned char *outpoint_smallest36, secp256k1_ge *pubkey_sum) { + secp256k1_sha256 hash; + unsigned char pubkey_sum_ser[33]; + unsigned char input_hash[32]; + size_t len; + int ret, overflow; + + secp256k1_silentpayments_sha256_init_inputs(&hash); + secp256k1_sha256_write(&hash, outpoint_smallest36, 36); + ret = secp256k1_eckey_pubkey_serialize(pubkey_sum, pubkey_sum_ser, &len, 1); + VERIFY_CHECK(ret && len == sizeof(pubkey_sum_ser)); + secp256k1_sha256_write(&hash, pubkey_sum_ser, sizeof(pubkey_sum_ser)); + secp256k1_sha256_finalize(&hash, input_hash); + /* Convert input_hash to a scalar to ensure the value is less than the curve order. + * + * This can only fail if the output of the hash function is zero or greater than or equal to the curve order, which + * happens with negligible probability. Normally, we would use VERIFY_CHECK as opposed to returning an error + * since returning an error here would result in an untestable branch in the code. But in this case, we return + * an error to ensure strict compliance with BIP0352. + */ + secp256k1_scalar_set_b32(input_hash_scalar, input_hash, &overflow); + ret &= !secp256k1_scalar_is_zero(input_hash_scalar); + return ret & !overflow; +} + +static void secp256k1_silentpayments_create_shared_secret(const secp256k1_context *ctx, unsigned char *shared_secret33, const secp256k1_ge *public_component, const secp256k1_scalar *secret_component) { + secp256k1_gej ss_j; + secp256k1_ge ss; + size_t len; + int ret; + + secp256k1_ecmult_const(&ss_j, public_component, secret_component); + secp256k1_ge_set_gej(&ss, &ss_j); + /* We declassify the shared secret group elemement because serializing a group element is a non-constant time operation. */ + secp256k1_declassify(ctx, &ss, sizeof(ss)); + /* This can only fail if the shared secret is the point at infinity, which should be + * impossible at this point considering we have already validated the public key and + * the secret key. + */ + ret = secp256k1_eckey_pubkey_serialize(&ss, shared_secret33, &len, 1); +#ifdef VERIFY + VERIFY_CHECK(ret && len == 33); +#else + (void)ret; +#endif + + /* Leaking these values would break indistinguishability of the transaction, so clear them. */ + secp256k1_ge_clear(&ss); + secp256k1_gej_clear(&ss_j); +} + +/** 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 int secp256k1_silentpayments_create_output_tweak(secp256k1_scalar *output_tweak_scalar, const unsigned char *shared_secret33, uint32_t k) { + secp256k1_sha256 hash; + unsigned char hash_ser[32]; + unsigned char k_serialized[4]; + int ret, overflow; + + /* Compute 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); + /* Convert output_tweak to a scalar to ensure the value is less than the curve order. + * + * This can only fail if the output of the hash function is zero greater than or equal to the curve order, which + * happens with negligible probability. Normally, we would use VERIFY_CHECK as opposed to returning an error + * since returning an error here would result in an untestable branch in the code. But in this case, we return + * an error to ensure strict compliance with BIP0352. + */ + secp256k1_scalar_set_b32(output_tweak_scalar, hash_ser, &overflow); + ret = !secp256k1_scalar_is_zero(output_tweak_scalar); + /* Leaking this value would break indistinguishability of the transaction, so clear it. */ + secp256k1_memclear_explicit(hash_ser, sizeof(hash_ser)); + secp256k1_sha256_clear(&hash); + return ret & !overflow; +} + +static int secp256k1_silentpayments_create_output_pubkeys(const secp256k1_context *ctx, secp256k1_xonly_pubkey **outputs_xonly, const unsigned char *shared_secret33, const secp256k1_pubkey **spend_pubkeys, size_t n_spend_pubkeys, uint32_t k) { + secp256k1_ge output_ge; + secp256k1_scalar output_tweak_scalar; + size_t i; + /* Calculate the output_tweak and convert it to a scalar to ensure the value is less than the curve order. + * + * Note: _create_output_tweak can only fail if the output of the hash function is greater than or equal to the curve order, which is statistically improbable. + * Returning an error here results in an untestable branch in the code, but we do this anyways to ensure strict compliance with BIP0352. + */ + if (!secp256k1_silentpayments_create_output_tweak(&output_tweak_scalar, shared_secret33, k)) { + return 0; + } + for (i = 0; i < n_spend_pubkeys; i++) { + if (!secp256k1_pubkey_load(ctx, &output_ge, spend_pubkeys[i])) { + secp256k1_scalar_clear(&output_tweak_scalar); + return 0; + } + /* `tweak_add` only fails if output_tweak_scalar*G = -spend_pubkey. Considering output_tweak is the output of a hash function, + * this will happen only with negligible probability for honestly created spend_pubkey, but we handle this + * error anyway to protect against this function being called with a malicious inputs, i.e., spend_pubkey = -(_create_output_tweak(shared_secret33, k))*G + */ + if (!secp256k1_eckey_pubkey_tweak_add(&output_ge, &output_tweak_scalar)) { + secp256k1_scalar_clear(&output_tweak_scalar); + return 0; + }; + secp256k1_xonly_pubkey_save(outputs_xonly[i], &output_ge); + } + + /* Leaking this value would break indistinguishability of the transaction, so clear it. */ + secp256k1_scalar_clear(&output_tweak_scalar); + return 1; +} + +int secp256k1_silentpayments_sender_create_outputs( + const secp256k1_context *ctx, + secp256k1_xonly_pubkey **generated_outputs, + const secp256k1_silentpayments_recipient **recipients, + size_t n_recipients, + const unsigned char *outpoint_smallest36, + const secp256k1_keypair * const *taproot_seckeys, + size_t n_taproot_seckeys, + const unsigned char * const *plain_seckeys, + size_t n_plain_seckeys +) { + size_t i, k; + secp256k1_scalar seckey_sum_scalar, addend, input_hash_scalar; + secp256k1_ge prevouts_pubkey_sum_ge; + secp256k1_gej prevouts_pubkey_sum_gej; + unsigned char shared_secret[33]; + secp256k1_pubkey current_scan_pubkey; + const secp256k1_pubkey *spend_pubkey_ptrs[1]; + secp256k1_xonly_pubkey *generated_output_ptrs[1]; + int ret, sum_is_zero; + + /* Sanity check inputs. */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(generated_outputs != NULL); + ARG_CHECK(recipients != NULL); + ARG_CHECK(n_recipients > 0); + ARG_CHECK(outpoint_smallest36 != NULL); + ARG_CHECK((plain_seckeys != NULL) || (taproot_seckeys != NULL)); + if (taproot_seckeys != NULL) { + ARG_CHECK(n_taproot_seckeys > 0); + } else { + ARG_CHECK(n_taproot_seckeys == 0); + } + if (plain_seckeys != NULL) { + ARG_CHECK(n_plain_seckeys > 0); + } else { + ARG_CHECK(n_plain_seckeys == 0); + } + for (i = 0; i < n_recipients; i++) { + ARG_CHECK(recipients[i]->index == i); + } + + seckey_sum_scalar = secp256k1_scalar_zero; + for (i = 0; i < n_plain_seckeys; i++) { + ret = secp256k1_scalar_set_b32_seckey(&addend, plain_seckeys[i]); + secp256k1_declassify(ctx, &ret, sizeof(ret)); + if (!ret) { + secp256k1_scalar_clear(&addend); + secp256k1_scalar_clear(&seckey_sum_scalar); + return 0; + } + secp256k1_scalar_add(&seckey_sum_scalar, &seckey_sum_scalar, &addend); + } + /* Secret keys used for taproot outputs have to be negated if they result in an odd point. This is to ensure + * the sender and recipient can arrive at the same shared secret when using x-only public keys. */ + for (i = 0; i < n_taproot_seckeys; i++) { + secp256k1_ge addend_point; + ret = secp256k1_keypair_load(ctx, &addend, &addend_point, taproot_seckeys[i]); + secp256k1_declassify(ctx, &ret, sizeof(ret)); + if (!ret) { + secp256k1_scalar_clear(&addend); + secp256k1_scalar_clear(&seckey_sum_scalar); + return 0; + } + if (secp256k1_fe_is_odd(&addend_point.y)) { + secp256k1_scalar_negate(&addend, &addend); + } + secp256k1_scalar_add(&seckey_sum_scalar, &seckey_sum_scalar, &addend); + } + /* If there are any failures in loading/summing up the secret keys, fail early. */ + sum_is_zero = secp256k1_scalar_is_zero(&seckey_sum_scalar); + secp256k1_declassify(ctx, &sum_is_zero, sizeof(sum_is_zero)); + secp256k1_scalar_clear(&addend); + if (sum_is_zero) { + secp256k1_scalar_clear(&seckey_sum_scalar); + return 0; + } + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &prevouts_pubkey_sum_gej, &seckey_sum_scalar); + secp256k1_ge_set_gej(&prevouts_pubkey_sum_ge, &prevouts_pubkey_sum_gej); + /* We declassify the pubkey sum because serializing a group element (done in the + * `_calculate_input_hash_scalar` call following) is not a constant-time operation. + */ + secp256k1_declassify(ctx, &prevouts_pubkey_sum_ge, sizeof(prevouts_pubkey_sum_ge)); + + /* Calculate the input_hash and convert it to a scalar so that it can be multiplied with the summed up private keys, i.e., a_sum = a_sum * input_hash. + * By multiplying the scalars together first, we can save an elliptic curve multiplication. + * + * Note: _input_hash_scalar can only fail if the output of the hash function is greater than or equal to the curve order, which is statistically improbable. + * Returning an error here results in an untestable branch in the code, but we do this anyways to ensure strict compliance with BIP0352. + */ + if (!secp256k1_silentpayments_calculate_input_hash_scalar(&input_hash_scalar, outpoint_smallest36, &prevouts_pubkey_sum_ge)) { + secp256k1_scalar_clear(&seckey_sum_scalar); + return 0; + } + secp256k1_scalar_mul(&seckey_sum_scalar, &seckey_sum_scalar, &input_hash_scalar); + /* _recipient_sort sorts the array of recipients in place by their scan public keys (lexicographically). + * This ensures that all recipients with the same scan public key are grouped together, as specified in BIP0352. + * + * More specifically, this ensures `k` is incremented from 0 to the number of requested outputs for each recipient group, + * where a recipient group is all addresses with the same scan public key. + */ + secp256k1_silentpayments_recipient_sort(ctx, recipients, n_recipients); + current_scan_pubkey = recipients[0]->scan_pubkey; + k = 0; /* This is a dead store but clang will emit a false positive warning if we omit it. */ + for (i = 0; i < n_recipients; i++) { + if ((i == 0) || (secp256k1_ec_pubkey_cmp(ctx, ¤t_scan_pubkey, &recipients[i]->scan_pubkey) != 0)) { + /* If we are on a different scan pubkey, its time to recreate the shared secret and reset k to 0. + * It's very unlikely the scan public key is invalid by this point, since this means the caller would + * have created the _silentpayments_recipient object incorrectly, but just to be sure we still check that + * the public key is valid. + */ + secp256k1_ge pk; + if (!secp256k1_pubkey_load(ctx, &pk, &recipients[i]->scan_pubkey)) { + secp256k1_scalar_clear(&seckey_sum_scalar); + /* Leaking this value would break indistinguishability of the transaction, so clear it. */ + secp256k1_memclear_explicit(&shared_secret, sizeof(shared_secret)); + return 0; + } + secp256k1_silentpayments_create_shared_secret(ctx, shared_secret, &pk, &seckey_sum_scalar); + k = 0; + } + generated_output_ptrs[0] = generated_outputs[recipients[i]->index]; + spend_pubkey_ptrs[0] = &recipients[i]->spend_pubkey; + if (!secp256k1_silentpayments_create_output_pubkeys(ctx, generated_output_ptrs, shared_secret, spend_pubkey_ptrs, 1, k)) { + secp256k1_scalar_clear(&seckey_sum_scalar); + secp256k1_memclear_explicit(&shared_secret, sizeof(shared_secret)); + return 0; + } + /* BIP0352 specifies that k is serialized as a 4 byte (32 bit) value, so we check to make + * sure we are not exceeding the max value for a uint32 before incrementing k. + * In practice, this should never happen as it would be impossible to create a transaction + * with this many outputs. + */ + if (k < UINT32_MAX) { + k++; + } else { + return 0; + } + current_scan_pubkey = recipients[i]->scan_pubkey; + } + secp256k1_scalar_clear(&seckey_sum_scalar); + secp256k1_memclear_explicit(&shared_secret, sizeof(shared_secret)); + 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..525175f88b --- /dev/null +++ b/src/modules/silentpayments/tests_impl.h @@ -0,0 +1,271 @@ +/*********************************************************************** + * 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" + +/** Constants + * + * Malformed Seckey: a seckey that is all zeros + * Addresses: scan and spend public keys for Bob and Carol + * Outputs: generated outputs from Alice's secret key and Bob/Carol's + * scan public keys + * Smallest Outpoint: smallest outpoint lexicographically from the transaction + * Seckey: secret key for Alice + * + * The values themselves are not important. + */ +static unsigned char MALFORMED_SECKEY[32] = { 0x00 }; +static unsigned char BOB_ADDRESS[2][33] = { + { + 0x02, 0x15, 0x40, 0xae, 0xa8, 0x97, 0x54, 0x7a, + 0xd4, 0x39, 0xb4, 0xe0, 0xf6, 0x09, 0xe5, 0xf0, + 0xfa, 0x63, 0xde, 0x89, 0xab, 0x11, 0xed, 0xe3, + 0x1e, 0x8c, 0xde, 0x4b, 0xe2, 0x19, 0x42, 0x5f, + 0x23 + }, + { + 0x02, 0x3e, 0xff, 0xf8, 0x18, 0x51, 0x65, 0xea, + 0x63, 0xa9, 0x92, 0xb3, 0x9f, 0x31, 0xd8, 0xfd, + 0x8e, 0x0e, 0x64, 0xae, 0xf9, 0xd3, 0x88, 0x07, + 0x34, 0x97, 0x37, 0x14, 0xa5, 0x3d, 0x83, 0x11, + 0x8d + } +}; +static unsigned char CAROL_ADDRESS[2][33] = { + { + 0x03, 0xbb, 0xc6, 0x3f, 0x12, 0x74, 0x5d, 0x3b, + 0x9e, 0x9d, 0x24, 0xc6, 0xcd, 0x7a, 0x1e, 0xfe, + 0xba, 0xd0, 0xa7, 0xf4, 0x69, 0x23, 0x2f, 0xbe, + 0xcf, 0x31, 0xfb, 0xa7, 0xb4, 0xf7, 0xdd, 0xed, + 0xa8 + }, + { + 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 + } +}; +static unsigned char BOB_OUTPUT[32] = { + 0x46, 0x0d, 0x68, 0x08, 0x65, 0x64, 0x45, 0xee, + 0x4d, 0x4e, 0xc0, 0x8e, 0xba, 0x8a, 0x66, 0xea, + 0x66, 0x8e, 0x4e, 0x12, 0x98, 0x9a, 0x0e, 0x60, + 0x4b, 0x5c, 0x36, 0x0e, 0x43, 0xf5, 0x5a, 0xfa +}; +static unsigned char CAROL_OUTPUT_ONE[32] = { + 0xb7, 0xf3, 0xc6, 0x79, 0x30, 0x4a, 0xef, 0x8c, + 0xc0, 0xc7, 0x61, 0xf1, 0x00, 0x99, 0xdd, 0x7b, + 0x20, 0x65, 0x20, 0xd7, 0x11, 0x6f, 0xb7, 0x91, + 0xee, 0x74, 0x54, 0xa2, 0xfc, 0x22, 0x79, 0xf4 +}; +static unsigned char CAROL_OUTPUT_TWO[32] = { + 0x4b, 0x81, 0x34, 0x5d, 0x53, 0x89, 0xba, 0xa3, + 0xd8, 0x93, 0xe2, 0xfb, 0xe7, 0x08, 0xdd, 0x6d, + 0x82, 0xdc, 0xd8, 0x49, 0xab, 0x03, 0xc1, 0xdb, + 0x68, 0xbe, 0xc7, 0xe9, 0x2a, 0x45, 0xfa, 0xc5 +}; +static unsigned char SMALLEST_OUTPOINT[36] = { + 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 +}; +static unsigned char ALICE_SECKEY[32] = { + 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 +}; + +static void test_recipient_sort_helper(unsigned char (*sp_addresses[3])[2][33], unsigned char (*sp_outputs[3])[32]) { + unsigned char const *seckey_ptrs[1]; + secp256k1_silentpayments_recipient recipients[3]; + const secp256k1_silentpayments_recipient *recipient_ptrs[3]; + secp256k1_xonly_pubkey generated_outputs[3]; + secp256k1_xonly_pubkey *generated_output_ptrs[3]; + unsigned char xonly_ser[32]; + size_t i; + int ret; + + seckey_ptrs[0] = ALICE_SECKEY; + for (i = 0; i < 3; i++) { + CHECK(secp256k1_ec_pubkey_parse(CTX, &recipients[i].scan_pubkey, (*sp_addresses[i])[0], 33)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &recipients[i].spend_pubkey,(*sp_addresses[i])[1], 33)); + recipients[i].index = i; + recipient_ptrs[i] = &recipients[i]; + generated_output_ptrs[i] = &generated_outputs[i]; + } + ret = secp256k1_silentpayments_sender_create_outputs(CTX, + generated_output_ptrs, + recipient_ptrs, 3, + SMALLEST_OUTPOINT, + NULL, 0, + seckey_ptrs, 1 + ); + CHECK(ret == 1); + for (i = 0; i < 3; i++) { + secp256k1_xonly_pubkey_serialize(CTX, xonly_ser, &generated_outputs[i]); + CHECK(secp256k1_memcmp_var(xonly_ser, (*sp_outputs[i]), 32) == 0); + } +} + +static void test_recipient_sort(void) { + unsigned char (*sp_addresses[3])[2][33]; + unsigned char (*sp_outputs[3])[32]; + + /* With a fixed set of addresses and a fixed set of inputs, + * test that we always get the same outputs, regardless of the ordering + * of the recipients + */ + sp_addresses[0] = &CAROL_ADDRESS; + sp_addresses[1] = &BOB_ADDRESS; + sp_addresses[2] = &CAROL_ADDRESS; + + sp_outputs[0] = &CAROL_OUTPUT_ONE; + sp_outputs[1] = &BOB_OUTPUT; + sp_outputs[2] = &CAROL_OUTPUT_TWO; + test_recipient_sort_helper(sp_addresses, sp_outputs); + + sp_addresses[0] = &CAROL_ADDRESS; + sp_addresses[1] = &CAROL_ADDRESS; + sp_addresses[2] = &BOB_ADDRESS; + + sp_outputs[0] = &CAROL_OUTPUT_ONE; + sp_outputs[1] = &CAROL_OUTPUT_TWO; + sp_outputs[2] = &BOB_OUTPUT; + test_recipient_sort_helper(sp_addresses, sp_outputs); + + sp_addresses[0] = &BOB_ADDRESS; + sp_addresses[1] = &CAROL_ADDRESS; + sp_addresses[2] = &CAROL_ADDRESS; + + /* Note: in this case, the second output for Carol comes before the first. + * This is because heapsort is an unstable sorting algorithm, i.e., the ordering + * of identical elements is not guaranteed to be preserved + */ + sp_outputs[0] = &BOB_OUTPUT; + sp_outputs[1] = &CAROL_OUTPUT_TWO; + sp_outputs[2] = &CAROL_OUTPUT_ONE; + test_recipient_sort_helper(sp_addresses, sp_outputs); +} + +static void test_send_api(void) { + unsigned char (*sp_addresses[2])[2][33]; + unsigned char const *p[1]; + secp256k1_keypair const *t[1]; + secp256k1_silentpayments_recipient r[2]; + const secp256k1_silentpayments_recipient *rp[2]; + secp256k1_xonly_pubkey o[2]; + secp256k1_xonly_pubkey *op[2]; + secp256k1_keypair taproot; + size_t i; + + /* Set up Bob and Carol as the recipients */ + sp_addresses[0] = &BOB_ADDRESS; + sp_addresses[1] = &CAROL_ADDRESS; + for (i = 0; i < 2; i++) { + CHECK(secp256k1_ec_pubkey_parse(CTX, &r[i].scan_pubkey, (*sp_addresses[i])[0], 33)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &r[i].spend_pubkey,(*sp_addresses[i])[1], 33)); + /* Set the index value incorrectly */ + r[i].index = 0; + rp[i] = &r[i]; + op[i] = &o[i]; + } + /* Set up a taproot key and a plain key for Alice */ + CHECK(secp256k1_keypair_create(CTX, &taproot, ALICE_SECKEY)); + t[0] = &taproot; + p[0] = ALICE_SECKEY; + + /* Fails if the index is set incorrectly */ + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1)); + + /* Set the index correctly for the next tests */ + for (i = 0; i < 2; i++) { + r[i].index = i; + } + CHECK(secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1)); + + /* Check that null arguments are handled */ + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, NULL, rp, 2, SMALLEST_OUTPOINT, t, 1, p, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, NULL, 2, SMALLEST_OUTPOINT, t, 1, p, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, NULL, t, 1, p, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 1, p, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, t, 1, NULL, 1)); + + /* Check correct context is used */ + CHECK_ILLEGAL(STATIC_CTX, secp256k1_silentpayments_sender_create_outputs(STATIC_CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1)); + + /* Check that array arguments are verified */ + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, NULL, 0)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 0, SMALLEST_OUTPOINT, NULL, 0, p, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, t, 0, p, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, t, 1, p, 0)); + + /* Create malformed keys for Alice by using a key that will overflow */ + CHECK(secp256k1_ec_seckey_verify(CTX, secp256k1_group_order_bytes) == 0); + p[0] = secp256k1_group_order_bytes; + CHECK(secp256k1_keypair_create(CTX, &taproot, ALICE_SECKEY)); + /* Malleate the keypair object so that the secret key is all zeros. We need to keep + * public key as is since it is loaded first and would hit an ARG_CHECK if invalid. + */ + memset(&taproot.data[0], 0, 32); + /* Check that an invalid plain secret key is caught */ + CHECK(secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1) == 0); + /* Check that an invalid keypair is caught */ + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, t, 1, NULL, 0)); + /* Create malformed keys for Alice by using a zero'd seckey */ + p[0] = MALFORMED_SECKEY; + CHECK(secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1) == 0); + p[0] = ALICE_SECKEY; + /* Create malformed recipients by setting all of the public key bytes to zero. + * Realistically, this would never happen since a bad public key would get caught when + * trying to parse the public key with _ec_pubkey_parse + */ + { + secp256k1_pubkey tmp = r[1].spend_pubkey; + memset(&r[1].spend_pubkey, 0, sizeof(r[1].spend_pubkey)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1)); + r[1].spend_pubkey = tmp; + } + { + secp256k1_pubkey tmp = r[1].scan_pubkey; + int32_t ecount = 0; + + memset(&r[1].scan_pubkey, 0, sizeof(r[1].scan_pubkey)); + secp256k1_context_set_illegal_callback(CTX, counting_callback_fn, &ecount); + CHECK(secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1) == 0); + CHECK(ecount == 2); + secp256k1_context_set_illegal_callback(CTX, NULL, NULL); + r[1].scan_pubkey = tmp; + } + { + unsigned char malformed_spend_key[32] = { + 0x83, 0xe1, 0x79, 0xdf, 0x51, 0xbb, 0xc9, 0x6f, + 0xfb, 0x59, 0xb6, 0x2e, 0x57, 0xcf, 0x4e, 0x54, + 0x71, 0x79, 0x04, 0x9c, 0x01, 0x47, 0x00, 0xfe, + 0x52, 0xef, 0x5f, 0x53, 0x76, 0x39, 0xec, 0xe0 + }; + secp256k1_pubkey neg_spend_pubkey; + CHECK(secp256k1_ec_pubkey_create(CTX, &neg_spend_pubkey, malformed_spend_key)); + CHECK(secp256k1_ec_pubkey_negate(CTX, &neg_spend_pubkey)); + r[0].spend_pubkey = neg_spend_pubkey; + for (i = 0; i < 2; i++) { + r[i].index = i; + rp[i] = &r[i]; + } + CHECK(secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1) == 0); + } +} + +void run_silentpayments_tests(void) { + test_recipient_sort(); + test_send_api(); +} + +#endif diff --git a/src/tests.c b/src/tests.c index cb3b3c4248..9bb191fd21 100644 --- a/src/tests.c +++ b/src/tests.c @@ -7442,6 +7442,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)]; @@ -7810,6 +7814,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_is_zero_array_test(); From 28917fa1620c960bb4e142bc86dde0b11bc7ef3c Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Mon, 22 Jan 2024 18:56:05 +0100 Subject: [PATCH 03/12] silentpayments: recipient label support Add function for creating a label tweak. This requires a tagged hash function for labels. This function is used by the receiver for creating labels to be used for a) creating labeled addresses and b) to populate a labels cache when scanning. Add function for creating a labeled spend pubkey. This involves taking a label tweak, turning it into a public key and adding it to the spend public key. This function is used by the receiver to create a labeled silent payment address. Add tests for the label API. --- include/secp256k1_silentpayments.h | 54 +++++++++++++++++++ src/modules/silentpayments/main_impl.h | 70 +++++++++++++++++++++++++ src/modules/silentpayments/tests_impl.h | 47 +++++++++++++++++ 3 files changed, 171 insertions(+) diff --git a/include/secp256k1_silentpayments.h b/include/secp256k1_silentpayments.h index 11167525ef..ba63b6895d 100644 --- a/include/secp256k1_silentpayments.h +++ b/include/secp256k1_silentpayments.h @@ -126,6 +126,60 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_sender_c size_t n_plain_seckeys ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5); +/** Create Silent Payment label tweak and label. + * + * Given a recipient's 32 byte scan key and a label integer m, calculate the + * corresponding label tweak and label: + * + * label_tweak = hash(scan_key || m) + * label = label_tweak * G + * + * Returns: 1 if label tweak and label creation was successful. + * 0 if hash output label_tweak32 is not valid scalar (negligible + * probability per hash evaluation). + * + * Args: ctx: pointer to a context object + * (not secp256k1_context_static) + * Out: label: pointer to the resulting label public key + * label_tweak32: pointer to the 32 byte label tweak + * In: scan_key32: pointer to the recipient's 32 byte scan key + * m: integer for the m-th label (0 is used for change outputs) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_create_label( + const secp256k1_context *ctx, + secp256k1_pubkey *label, + unsigned char *label_tweak32, + const unsigned char *scan_key32, + const uint32_t m +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Create Silent Payment labeled spend public key. + * + * Given a recipient's spend public key and a label, calculate the + * corresponding labeled spend public key: + * + * labeled_spend_pubkey = unlabeled_spend_pubkey + label + * + * The result is used by the recipient to create a Silent Payment address, + * consisting of the serialized and concatenated scan public key and + * (labeled) spend public key. + * + * Returns: 1 if labeled spend public key creation was successful. + * 0 if spend pubkey and label sum to zero (negligible probability for + * labels created according to BIP352). + * + * Args: ctx: pointer to a context object + * Out: labeled_spend_pubkey: pointer to the resulting labeled spend public key + * In: unlabeled_spend_pubkey: pointer to the recipient's unlabeled spend public key + * label: pointer to the recipient's label public key + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_create_labeled_spend_pubkey( + const secp256k1_context *ctx, + secp256k1_pubkey *labeled_spend_pubkey, + const secp256k1_pubkey *unlabeled_spend_pubkey, + const secp256k1_pubkey *label +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + #ifdef __cplusplus } #endif diff --git a/src/modules/silentpayments/main_impl.h b/src/modules/silentpayments/main_impl.h index 9e2f1160cb..c942d9011e 100644 --- a/src/modules/silentpayments/main_impl.h +++ b/src/modules/silentpayments/main_impl.h @@ -333,4 +333,74 @@ int secp256k1_silentpayments_sender_create_outputs( 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_recipient_create_label(const secp256k1_context *ctx, secp256k1_pubkey *label, unsigned char *label_tweak32, const unsigned char *scan_key32, const uint32_t m) { + secp256k1_sha256 hash; + unsigned char m_serialized[4]; + + /* Sanity check inputs. */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(label != NULL); + ARG_CHECK(label_tweak32 != NULL); + ARG_CHECK(scan_key32 != NULL); + + /* Compute hash(ser_256(b_scan) || ser_32(m)) [sha256 with tag "BIP0352/Label"] */ + secp256k1_silentpayments_sha256_init_label(&hash); + secp256k1_sha256_write(&hash, scan_key32, 32); + secp256k1_write_be32(m_serialized, m); + secp256k1_sha256_write(&hash, m_serialized, sizeof(m_serialized)); + secp256k1_sha256_finalize(&hash, label_tweak32); + + secp256k1_memclear_explicit(m_serialized, sizeof(m_serialized)); + secp256k1_sha256_clear(&hash); + return secp256k1_ec_pubkey_create(ctx, label, label_tweak32); +} + +int secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(const secp256k1_context *ctx, secp256k1_pubkey *labeled_spend_pubkey, const secp256k1_pubkey *unlabeled_spend_pubkey, const secp256k1_pubkey *label) { + secp256k1_ge labeled_spend_pubkey_ge, label_addend; + secp256k1_gej result_gej; + secp256k1_ge result_ge; + int ret; + + /* Sanity check inputs. */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(labeled_spend_pubkey != NULL); + ARG_CHECK(unlabeled_spend_pubkey != NULL); + ARG_CHECK(label != NULL); + + /* Calculate labeled_spend_pubkey = spend_pubkey + label. + * If either the label or spend public key is an invalid public key, + * return early + */ + ret = secp256k1_pubkey_load(ctx, &labeled_spend_pubkey_ge, unlabeled_spend_pubkey); + ret &= secp256k1_pubkey_load(ctx, &label_addend, label); + if (!ret) { + return 0; + } + secp256k1_gej_set_ge(&result_gej, &labeled_spend_pubkey_ge); + secp256k1_gej_add_ge_var(&result_gej, &result_gej, &label_addend, NULL); + if (secp256k1_gej_is_infinity(&result_gej)) { + return 0; + } + + secp256k1_ge_set_gej_var(&result_ge, &result_gej); + secp256k1_pubkey_save(labeled_spend_pubkey, &result_ge); + + return 1; +} + #endif diff --git a/src/modules/silentpayments/tests_impl.h b/src/modules/silentpayments/tests_impl.h index 525175f88b..86948725d8 100644 --- a/src/modules/silentpayments/tests_impl.h +++ b/src/modules/silentpayments/tests_impl.h @@ -263,9 +263,56 @@ static void test_send_api(void) { } } +static void test_label_api(void) { + secp256k1_pubkey l, s, ls, e; /* label pk, spend pk, labeled spend pk, expected labeled spend pk */ + unsigned char lt[32]; /* label tweak */ + const unsigned char expected[33] = { + 0x03, 0xdc, 0x7f, 0x09, 0x9a, 0xbe, 0x95, 0x7a, + 0x58, 0x43, 0xd2, 0xb6, 0xbb, 0x35, 0x79, 0x61, + 0x5c, 0x60, 0x36, 0xa4, 0x9b, 0x86, 0xf4, 0xbe, + 0x46, 0x38, 0x60, 0x28, 0xa8, 0x1a, 0x77, 0xd4, + 0x91 + }; + + /* Create a label and labeled spend public key, verify we get the expected result */ + CHECK(secp256k1_ec_pubkey_parse(CTX, &s, BOB_ADDRESS[1], 33)); + CHECK(secp256k1_silentpayments_recipient_create_label(CTX, &l, lt, ALICE_SECKEY, 1)); + CHECK(secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, &s, &l)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &e, expected, 33)); + CHECK(secp256k1_ec_pubkey_cmp(CTX, &ls, &e) == 0); + + /* Check null values are handled */ + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_label(CTX, NULL, lt, ALICE_SECKEY, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_label(CTX, &l, NULL, ALICE_SECKEY, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_label(CTX, &l, lt, NULL, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, NULL, &s, &l)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, NULL, &l)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, &s, NULL)); + /* Check for malformed spend and label public keys, i.e., any single pubkey is malformed or the public + * keys are valid but sum up to zero. + */ + { + secp256k1_pubkey neg_spend_pubkey = s; + CHECK(secp256k1_ec_pubkey_negate(CTX, &neg_spend_pubkey)); + CHECK(secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, &s, &neg_spend_pubkey) == 0); + /* Also test with a malformed spend public key. */ + memset(&s, 0, sizeof(s)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, &s, &neg_spend_pubkey)); + /* Reset s back to a valid public key for the next test. */ + CHECK(secp256k1_ec_pubkey_parse(CTX, &s, BOB_ADDRESS[1], 33)); + memset(&l, 0, sizeof(l)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, &s, &l)); + /* Reset l back to a valid public key for the next test */ + CHECK(secp256k1_silentpayments_recipient_create_label(CTX, &l, lt, ALICE_SECKEY, 1)); + memset(&s, 0, sizeof(s)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, &s, &l)); + } +} + void run_silentpayments_tests(void) { test_recipient_sort(); test_send_api(); + test_label_api(); } #endif From 55dce8c6a2859b5462fe1b07549639bd00f4f8cc Mon Sep 17 00:00:00 2001 From: josibake Date: Thu, 2 Oct 2025 15:18:13 +0100 Subject: [PATCH 04/12] silentpayments: receiving Add routine for scanning a transaction and returning the necessary spending data for any found outputs. This function works with labels via a lookup callback and requires access to the transaction outputs. Requiring access to the transaction outputs is not suitable for light clients, but light client support is enabled in the next commit. Add an opaque data type for passing around the prevout public key sum and the input hash tweak (input_hash). This data is passed to the scanner before the ECDH step as two separate elements so that the scanner can multiply the scan_key * input_hash before doing ECDH. Add functions for deserializing / serializing a prevouts_summary object to and from a public key. When serializing a prevouts_summary object, the input_hash is multplied into the prevout public key sum. This is so the object can be stored as public key for wallet rescanning later, or to send to light clients. For the light client, a `_parse` function is added which parses the compressed public key serialization into a `prevouts_summary` object. Finally, add test coverage for the receiving API. --- include/secp256k1_silentpayments.h | 206 +++++++++++++- src/modules/silentpayments/main_impl.h | 358 ++++++++++++++++++++++++ src/modules/silentpayments/tests_impl.h | 164 +++++++++++ 3 files changed, 727 insertions(+), 1 deletion(-) diff --git a/include/secp256k1_silentpayments.h b/include/secp256k1_silentpayments.h index ba63b6895d..0b75a2ccc7 100644 --- a/include/secp256k1_silentpayments.h +++ b/include/secp256k1_silentpayments.h @@ -1,6 +1,7 @@ #ifndef SECP256K1_SILENTPAYMENTS_H #define SECP256K1_SILENTPAYMENTS_H +#include #include "secp256k1.h" #include "secp256k1_extrakeys.h" @@ -26,7 +27,6 @@ extern "C" { * any further elliptic-curve operations from the wallet. */ - /** The data from a single recipient address * * This struct serves as an input argument to `silentpayments_sender_create_outputs`. @@ -180,6 +180,210 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipien const secp256k1_pubkey *label ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); +/** Opaque data structure that holds silent payments prevout summary data. + * + * This structure does not contain secret data. Guaranteed to be 101 bytes in + * size. It can be safely copied/moved. Created with + * `secp256k1_silentpayments_recipient_prevouts_summary_create`. Can be serialized with + * `secp256k1_silentpayments_recipient_prevouts_summary_serialize`. The serialization is + * intended for storing the object or sending the prevout summary data to light clients. + * The serialization is is parsed with + * `secp256k1_silentpayments_recipient_prevouts_summary_parse`. + */ +typedef struct secp256k1_silentpayments_prevouts_summary { + unsigned char data[101]; +} secp256k1_silentpayments_prevouts_summary; + +/** Compute Silent Payment prevout summary from prevout public keys and transaction + * inputs. + * + * Given a list of n public keys A_1...A_n (one for each silent payment + * eligible input to spend) and a serialized outpoint_smallest36, create a + * `prevouts_summary` object. This object summarizes the prevout data from the + * transaction inputs needed for scanning. + * + * `outpoint_smallest36` refers to the smallest outpoint lexicographically + * from the transaction inputs (both silent payments eligible and non-eligible + * inputs). This value MUST be the smallest outpoint out of all of the + * transaction inputs, otherwise the recipient will be unable to find the + * payment. + * + * 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 public key format before calling this function. + * The resulting data can be used for scanning on the recipient side, or + * stored in an index for later use (e.g., wallet rescanning, sending data to + * light clients). + * + * If calling this function for simply aggregating the public transaction data + * for later use, the caller can save the result with + * `silentpayments_recipient_prevouts_summary_serialize`. + * + * Returns: 1 if prevout summary creation was successful. + * 0 if the transaction is not a silent payments transaction. + * + * Args: ctx: pointer to a context object + * Out: prevouts_summary: pointer to prevouts_summary object containing the + * summed public key and input_hash. + * In: outpoint_smallest36: serialized smallest outpoint (lexicographically) + * from the transaction inputs + * 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 size of the xonly_pubkeys array. + * 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 size of the plain_pubkeys array. + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_prevouts_summary_create( + const secp256k1_context *ctx, + secp256k1_silentpayments_prevouts_summary *prevouts_summary, + const unsigned char *outpoint_smallest36, + const secp256k1_xonly_pubkey * const *xonly_pubkeys, + size_t n_xonly_pubkeys, + const secp256k1_pubkey * const *plain_pubkeys, + size_t n_plain_pubkeys +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Serialize a silentpayments_prevouts_summary object into a 33-byte or 65-byte sequence. + * + * Serializing a prevouts_summary object created with `_recipent_prevouts_summary_create` will result in + * an EC multiplication. This allows for a more compact serialization, but also means a serialized + * prevouts_summary will not parse back to a the same prevouts_summary object (due to the EC multiplication). + * + * Returns: 1 always. + * + * Args: ctx: pointer to a context object + * Out: output: pointer to a byte array to place the serialized + * `silentpayments_prevouts_summary` in. + * size: size of the byte array. Must be either 33 or 65. + * In: prevouts_summary: pointer to an initialized silentpayments_prevouts_summary + * object + * flags: SECP256K1_EC_COMPRESSED if serialization should be in + * compressed format, otherwise SECP256K1_EC_UNCOMPRESSED. + */ +SECP256K1_API int secp256k1_silentpayments_recipient_prevouts_summary_serialize( + const secp256k1_context *ctx, + unsigned char *output, + size_t size, + const secp256k1_silentpayments_prevouts_summary *prevouts_summary, + unsigned int flags +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4); + +/** Parse a 33-byte or 65-byte sequence into a silentpayments_recipients_prevouts_summary object. + * + * Returns: 1 if the data was able to be parsed. + * 0 if the arguments are invalid. + * + * Args: ctx: pointer to a context object. + * Out: prevouts_summary: pointer to a silentpayments_prevouts_summary object. If 1 is + * returned, it is set to a parsed version of input33. + * In: input33: pointer to a serialized silentpayments_prevouts_summary. + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_prevouts_summary_parse( + const secp256k1_context *ctx, + secp256k1_silentpayments_prevouts_summary *prevouts_summary, + const unsigned char *input, + size_t inputlen +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Callback function for label lookups + * + * This function is implemented by the recipient to check if a value exists in + * the recipients label cache during scanning. + * + * For creating the labels cache data, + * `secp256k1_silentpayments_recipient_create_label` can be used. + * + * Returns: pointer to the 32-byte label tweak if there is a match. + * NULL pointer if there is no match. + * + * In: label: pointer to the label public key to check (computed during + * scanning) + * label_context: pointer to the recipients label cache. + */ +typedef const unsigned char* (*secp256k1_silentpayments_label_lookup)(const unsigned char* label33, const void* label_context); + +/** Found outputs struct + * + * Struct for holding a found output along with data needed to spend it later. + * + * output: the x-only public key for the taproot output + * tweak: the 32-byte tweak needed to spend the output + * found_with_label: boolean value to indicate if the output was sent to a + * labeled address. If true, label will be set with a valid + * public key. + * label: public key representing the label used. + * If found_with_label = false, this is set to an invalid + * public key. + */ +typedef struct secp256k1_silentpayments_found_output { + secp256k1_xonly_pubkey output; + unsigned char tweak[32]; + int found_with_label; + secp256k1_pubkey label; +} secp256k1_silentpayments_found_output; + +/** Scan for Silent Payment transaction outputs. + * + * Given a prevouts_summary object, a recipient's 32 byte scan key and spend public key, + * and the relevant transaction outputs, scan for outputs belonging to + * the recipient and return the tweak(s) needed for spending the output(s). An + * optional label_lookup callback function and label_context can be passed if + * the recipient uses labels. This allows for checking if a label exists in + * the recipients label cache and retrieving the label tweak during scanning. + * + * If used, the `label_lookup` function must return a pointer to a 32-byte label + * tweak if the label is found, or NULL otherwise. The returned pointer must remain + * valid until the next call to `label_lookup` or until the function returns, + * whichever comes first. It is not retained beyond that. + * + * For creating the labels cache, `secp256k1_silentpayments_recipient_create_label` + * can be used. + * + * Returns: 1 if output scanning was successful. + * 0 if the transaction is not a silent payments transaction, + * or if the arguments are invalid. + * + * Args: ctx: pointer to a context object + * Out: found_outputs: pointer to an array of pointers to found + * output objects. The found outputs array MUST + * have the same length as the tx_outputs array. + * n_found_outputs: pointer to an integer indicating the final + * size of the found outputs array. This number + * represents the number of outputs found while + * scanning (0 if none are found). + * In: tx_outputs: pointer to the transaction's x-only public key outputs + * n_tx_outputs: the size of the tx_outputs array. + * scan_key32: pointer to the recipient's 32 byte scan key. The scan + * key is valid if it passes secp256k1_ec_seckey_verify + * prevouts_summary: pointer to the transaction prevouts summary data + * (see `_recipient_prevouts_summary_create`). + * unlabeled_spend_pubkey: pointer to the recipient's unlabeled spend public key + * label_lookup: pointer to a callback function for looking up + * a label value. This function takes a label + * public key as an argument and returns a pointer to + * the label tweak if the label exists, otherwise + * returns a NULL pointer (NULL if labels are not + * used) + * label_context: pointer to a label context object (NULL if + * labels are not used or context is not needed) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_scan_outputs( + const secp256k1_context *ctx, + secp256k1_silentpayments_found_output **found_outputs, + size_t *n_found_outputs, + const secp256k1_xonly_pubkey * const *tx_outputs, + size_t n_tx_outputs, + const unsigned char *scan_key32, + const secp256k1_silentpayments_prevouts_summary *prevouts_summary, + const secp256k1_pubkey *unlabeled_spend_pubkey, + const secp256k1_silentpayments_label_lookup label_lookup, + const void *label_context +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) + SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8); + #ifdef __cplusplus } #endif diff --git a/src/modules/silentpayments/main_impl.h b/src/modules/silentpayments/main_impl.h index c942d9011e..3f8929af47 100644 --- a/src/modules/silentpayments/main_impl.h +++ b/src/modules/silentpayments/main_impl.h @@ -18,6 +18,9 @@ #include "../../hash.h" #include "../../hsort.h" +/** magic bytes for ensuring prevouts_summary objects were initialized correctly. */ +static const unsigned char secp256k1_silentpayments_prevouts_summary_magic[4] = { 0xa7, 0x1c, 0xd3, 0x5e }; + /** Sort an array of silent payment recipients. This is used to group recipients by scan pubkey to * ensure the correct values of k are used when creating multiple outputs for a recipient. * @@ -403,4 +406,359 @@ int secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(const secp256 return 1; } +/** An explanation of the prevouts_summary object and its usage: + * + * The prevouts_summary object contains: + * + * [magic: 4 bytes][boolean: 1 byte][prevouts_pubkey_sum: 64 bytes][input_hash: 32 bytes] + * + * The magic bytes are checked by functions using the prevouts_summary object to + * check that the prevouts_summary object was initialized correctly. + * + * The boolean (combined) indicates whether or not the summed prevout public keys and the + * input_hash scalar have already been combined or are both included. The reason + * for keeping input_hash and the summed prevout public keys separate is so that an elliptic + * curve multiplication can be avoided when creating the shared secret, i.e., + * (recipient_scan_key * input_hash) * prevouts_pubkey_sum. + * + * But when storing the prevouts_summary object, either to send to light clients or for + * wallet rescans, we can save 32-bytes by combining the input_hash and prevouts_pubkey_sum and saving + * the resulting point serialized as a compressed public key, i.e., input_hash * prevouts_pubkey_sum. + * + * For each function: + * + * - `_recipient_prevouts_summary_create` always creates a prevouts_summary object with combined = false + * - `_recipient_prevouts_summary_serialize` multiplies the input_hash into the summed public key before + * serializing the resulting point as a compressed public key, if combined = false. If combined = true, + * the point is serialized back into a compressed public key. + * - `_recipient_prevouts_summary_parse` assumes the input represents a previously serialized + * prevouts_summary object and always deserializes into a prevouts_summary object with combined = true + * (and the input_hash portion zeroed out). + */ + +int secp256k1_silentpayments_recipient_prevouts_summary_create( + const secp256k1_context *ctx, + secp256k1_silentpayments_prevouts_summary *prevouts_summary, + const unsigned char *outpoint_smallest36, + const secp256k1_xonly_pubkey * const *xonly_pubkeys, + size_t n_xonly_pubkeys, + const secp256k1_pubkey * const *plain_pubkeys, + size_t n_plain_pubkeys +) { + size_t i; + secp256k1_ge prevouts_pubkey_sum_ge, addend; + secp256k1_gej prevouts_pubkey_sum_gej; + secp256k1_scalar input_hash_scalar; + + /* Sanity check inputs */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(prevouts_summary != NULL); + ARG_CHECK(outpoint_smallest36 != NULL); + ARG_CHECK((plain_pubkeys != NULL) || (xonly_pubkeys != NULL)); + if (xonly_pubkeys != NULL) { + ARG_CHECK(n_xonly_pubkeys > 0); + } else { + ARG_CHECK(n_xonly_pubkeys == 0); + } + if (plain_pubkeys != NULL) { + ARG_CHECK(n_plain_pubkeys > 0); + } else { + ARG_CHECK(n_plain_pubkeys == 0); + } + + /* Compute prevouts_pubkey_sum = A_1 + A_2 + ... + A_n. + * + * Since an attacker can maliciously craft transactions where the public keys sum to zero, fail early here + * to avoid making the caller do extra work, e.g., when building an index or scanning a malicious transaction. + * + * This will also fail if any of the provided prevout public keys are malformed. + */ + secp256k1_gej_set_infinity(&prevouts_pubkey_sum_gej); + for (i = 0; i < n_plain_pubkeys; i++) { + if (!secp256k1_pubkey_load(ctx, &addend, plain_pubkeys[i])) { + return 0; + } + secp256k1_gej_add_ge_var(&prevouts_pubkey_sum_gej, &prevouts_pubkey_sum_gej, &addend, NULL); + } + for (i = 0; i < n_xonly_pubkeys; i++) { + if (!secp256k1_xonly_pubkey_load(ctx, &addend, xonly_pubkeys[i])) { + return 0; + } + secp256k1_gej_add_ge_var(&prevouts_pubkey_sum_gej, &prevouts_pubkey_sum_gej, &addend, NULL); + } + if (secp256k1_gej_is_infinity(&prevouts_pubkey_sum_gej)) { + return 0; + } + secp256k1_ge_set_gej_var(&prevouts_pubkey_sum_ge, &prevouts_pubkey_sum_gej); + /* Calculate the input_hash and convert it to a scalar to ensure the value is less than the curve order. + * + * Note: _input_hash_scalar can only fail if the output of the hash function is greater than or equal to the curve order, which is statistically improbable. + * Returning an error here results in an untestable branch in the code, but we do this anyways to ensure strict compliance with BIP0352. + */ + if (!secp256k1_silentpayments_calculate_input_hash_scalar(&input_hash_scalar, outpoint_smallest36, &prevouts_pubkey_sum_ge)) { + return 0; + } + memcpy(&prevouts_summary->data[0], secp256k1_silentpayments_prevouts_summary_magic, 4); + prevouts_summary->data[4] = 0; + secp256k1_ge_to_bytes(&prevouts_summary->data[5], &prevouts_pubkey_sum_ge); + secp256k1_scalar_get_b32(&prevouts_summary->data[5 + 64], &input_hash_scalar); + return 1; +} + +int secp256k1_silentpayments_recipient_prevouts_summary_serialize(const secp256k1_context *ctx, unsigned char *output, size_t size, const secp256k1_silentpayments_prevouts_summary *prevouts_summary, unsigned int flags) { + secp256k1_ge ge; + int ret, combined, compressed; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(output != NULL); + ARG_CHECK(size == 33 || size == 65); + ARG_CHECK(prevouts_summary != NULL); + ARG_CHECK(flags == SECP256K1_EC_COMPRESSED || flags == SECP256K1_EC_UNCOMPRESSED); + if (flags == SECP256K1_EC_COMPRESSED) { + ARG_CHECK(size == 33); + } + if (flags == SECP256K1_EC_UNCOMPRESSED) { + ARG_CHECK(size == 65); + } + ARG_CHECK(secp256k1_memcmp_var(&prevouts_summary->data[0], secp256k1_silentpayments_prevouts_summary_magic, 4) == 0); + if (flags == SECP256K1_EC_COMPRESSED) { + compressed = 1; + } else { + compressed = 0; + } + /* These functions should never fail at this point considering: + * - loading the pubkey and input hash can only fail if the prevouts_summary object was created incorrectly + * and we already check for this above. + * - `_tweak_mul` can only fail if input_hash_scalar is zero, but assuming the prevouts_summary object + * was created correctly, this is impossible because input_hash_scalar is the output of a hash function. + * - `_eckey_pubkey_serialize` can only fail if the point we are trying to serialize is the point at infinity. + * + * Note: we don't verify that the input hash is less than the curve order since this is verified when the + * prevouts_summary object is created. + */ + secp256k1_ge_from_bytes(&ge, &prevouts_summary->data[5]); + combined = (int)prevouts_summary->data[4]; + ret = 1; + if (!combined) { + secp256k1_scalar input_hash_scalar; + secp256k1_scalar_set_b32(&input_hash_scalar, &prevouts_summary->data[5 + 64], NULL); + ret &= secp256k1_eckey_pubkey_tweak_mul(&ge, &input_hash_scalar); + } + ret &= secp256k1_eckey_pubkey_serialize(&ge, output, &size, compressed); + (void)ret; + return 1; +} + +int secp256k1_silentpayments_recipient_prevouts_summary_parse(const secp256k1_context *ctx, secp256k1_silentpayments_prevouts_summary *prevouts_summary, const unsigned char *input, size_t inputlen) { + secp256k1_ge pk; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(prevouts_summary != NULL); + ARG_CHECK(input != NULL); + ARG_CHECK(inputlen == 33 || inputlen == 65); + /* Since an attacker can send us malicious data that looks like a serialized public key but is not, fail early. */ + if (!secp256k1_eckey_pubkey_parse(&pk, input, inputlen)) { + return 0; + } + /* A serialized prevouts_summary will always have the input_hash multiplied in, so we set combined = true. + * Additionally, we zero out the 32 bytes used to represent the input_hash. + */ + memcpy(&prevouts_summary->data[0], secp256k1_silentpayments_prevouts_summary_magic, 4); + prevouts_summary->data[4] = 1; + secp256k1_ge_to_bytes(&prevouts_summary->data[5], &pk); + memset(&prevouts_summary->data[5 + 64], 0, 32); + return 1; +} + +int secp256k1_silentpayments_recipient_scan_outputs( + const secp256k1_context *ctx, + secp256k1_silentpayments_found_output **found_outputs, size_t *n_found_outputs, + const secp256k1_xonly_pubkey * const *tx_outputs, size_t n_tx_outputs, + const unsigned char *scan_key32, + const secp256k1_silentpayments_prevouts_summary *prevouts_summary, + const secp256k1_pubkey *spend_pubkey, + const secp256k1_silentpayments_label_lookup label_lookup, + const void *label_context +) { + secp256k1_scalar output_tweak_scalar, scan_key_scalar; + secp256k1_ge label_ge, spend_pubkey_ge, prevouts_pubkey_sum_ge; + secp256k1_xonly_pubkey output_xonly; + unsigned char shared_secret[33]; + const unsigned char *label_tweak = NULL; + size_t i, j, k, n_found, found_idx; + int found, combined, valid_scan_key, ret; + + /* Sanity check inputs */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(found_outputs != NULL); + ARG_CHECK(n_found_outputs != NULL); + ARG_CHECK(tx_outputs != NULL); + ARG_CHECK(n_tx_outputs > 0); + ARG_CHECK(scan_key32 != NULL); + ARG_CHECK(prevouts_summary != NULL); + ARG_CHECK(secp256k1_memcmp_var(&prevouts_summary->data[0], secp256k1_silentpayments_prevouts_summary_magic, 4) == 0); + ARG_CHECK(spend_pubkey != NULL); + /* Passing a context without a lookup function is non-sensical */ + if (label_context != NULL) { + ARG_CHECK(label_lookup != NULL); + } + valid_scan_key = secp256k1_scalar_set_b32_seckey(&scan_key_scalar, scan_key32); + secp256k1_declassify(ctx, &valid_scan_key, sizeof(valid_scan_key)); + if (!valid_scan_key) { + secp256k1_scalar_clear(&scan_key_scalar); + return 0; + } + secp256k1_ge_from_bytes(&prevouts_pubkey_sum_ge, &prevouts_summary->data[5]); + combined = (int)prevouts_summary->data[4]; + if (!combined) { + secp256k1_scalar input_hash_scalar; + secp256k1_scalar_set_b32(&input_hash_scalar, &prevouts_summary->data[5 + 64], NULL); + secp256k1_scalar_mul(&scan_key_scalar, &scan_key_scalar, &input_hash_scalar); + } + ret = secp256k1_pubkey_load(ctx, &spend_pubkey_ge, spend_pubkey); + if (!ret) { + secp256k1_scalar_clear(&scan_key_scalar); + return 0; + } + secp256k1_silentpayments_create_shared_secret(ctx, shared_secret, &prevouts_pubkey_sum_ge, &scan_key_scalar); + /* Clear the scan_key_scalar since we no longer need it and leaking this value would break indistinguishability of the transaction. */ + secp256k1_scalar_clear(&scan_key_scalar); + + found_idx = 0; + n_found = 0; + k = 0; + for (i = 0; i <= n_tx_outputs; i++) { + secp256k1_ge output_ge = spend_pubkey_ge; + /* Calculate the output_tweak and convert it to a scalar to ensure the value is less than the curve order. + * + * Note: _create_output_tweak can only fail if the output of the hash function is greater than or equal to the curve order, which is statistically improbable. + * Returning an error here results in an untestable branch in the code, but we do this anyways to ensure strict compliance with BIP0352. + */ + if (!secp256k1_silentpayments_create_output_tweak(&output_tweak_scalar, shared_secret, k)) { + secp256k1_scalar_clear(&output_tweak_scalar); + secp256k1_memclear_explicit(&shared_secret, sizeof(shared_secret)); + return 0; + } + + /* Calculate output = spend_pubkey + output_tweak * G. + * This can fail if output_tweak * G is the negation of spend_pubkey, but this happens only + * with negligible probability for honestly created spend_pubkey as output_tweak is the output of a hash function. */ + if (!secp256k1_eckey_pubkey_tweak_add(&output_ge, &output_tweak_scalar)) { + /* Leaking these values would break indistinguishability of the transaction, so clear them. */ + secp256k1_scalar_clear(&output_tweak_scalar); + secp256k1_memclear_explicit(&shared_secret, sizeof(shared_secret)); + return 0; + } + found = 0; + secp256k1_xonly_pubkey_save(&output_xonly, &output_ge); + for (j = 0; j < n_tx_outputs; j++) { + if (secp256k1_xonly_pubkey_cmp(ctx, &output_xonly, tx_outputs[j]) == 0) { + label_tweak = NULL; + found = 1; + found_idx = j; + break; + } + + /* If not found, proceed to check for labels (if a label lookup function is provided). */ + if (label_lookup != NULL) { + secp256k1_ge output_negated_ge, tx_output_ge; + secp256k1_gej tx_output_gej, label_gej; + unsigned char label33[33]; + size_t len; + + secp256k1_xonly_pubkey_load(ctx, &tx_output_ge, tx_outputs[j]); + secp256k1_gej_set_ge(&tx_output_gej, &tx_output_ge); + secp256k1_ge_neg(&output_negated_ge, &output_ge); + secp256k1_gej_add_ge_var(&label_gej, &tx_output_gej, &output_negated_ge, NULL); + secp256k1_ge_set_gej_var(&label_ge, &label_gej); + ret = secp256k1_eckey_pubkey_serialize(&label_ge, label33, &len, 1); + /* Serialize must succeed because the point was just loaded. + * + * Note: serialize will also fail if label_ge is the point at infinity, but we know + * this cannot happen since we only hit this branch if tx_output != output_xonly. + * Thus, we know that label_ge = tx_output_gej + output_negated_ge cannot be the + * point at infinity. + */ + VERIFY_CHECK(ret && len == 33); + label_tweak = label_lookup(label33, label_context); + if (label_tweak != NULL) { + found = 1; + found_idx = j; + break; + } + + secp256k1_gej_neg(&label_gej, &tx_output_gej); + /* If not found, negate the tx_output and calculate second scan label candidate: + * label2 = -tx_output - generated_output + */ + secp256k1_gej_add_ge_var(&label_gej, &label_gej, &output_negated_ge, NULL); + secp256k1_ge_set_gej_var(&label_ge, &label_gej); + ret = secp256k1_eckey_pubkey_serialize(&label_ge, label33, &len, 1); + /* Serialize must succeed because the point was just loaded. + * + * Note: serialize will also fail if label_ge is the point at infinity, but we know + * this cannot happen since we only hit this branch if tx_output != output_xonly. + * Thus, we know that label_ge = tx_output_gej + output_negated_ge cannot be the + * point at infinity. + */ + VERIFY_CHECK(ret && len == 33); + label_tweak = label_lookup(label33, label_context); + if (label_tweak != NULL) { + found = 1; + found_idx = j; + break; + } + } + } + if (found) { + found_outputs[n_found]->output = *tx_outputs[found_idx]; + secp256k1_scalar_get_b32(found_outputs[n_found]->tweak, &output_tweak_scalar); + /* Clear the output_tweak_scalar since we no longer need it and leaking this value would + * break indistinguishability of the transaction. */ + secp256k1_scalar_clear(&output_tweak_scalar); + if (label_tweak != NULL) { + found_outputs[n_found]->found_with_label = 1; + /* This is extremely unlikely to fail in that it can only really fail if label_tweak + * is the negation of the shared secret tweak. But since both tweak and label_tweak are + * created by hashing data, practically speaking this would only happen if an attacker + * tricked us into using a particular label_tweak (deviating from the protocol). + * + * Furthermore, although technically a failure for ec_seckey_tweak_add, this is not treated + * as a failure for silent payments because the output is still spendable with just the + * spend secret key. We set `tweak = 0` for this case. + */ + if (!secp256k1_ec_seckey_tweak_add(ctx, found_outputs[n_found]->tweak, label_tweak)) { + memset(found_outputs[n_found]->tweak, 0, 32); + } + secp256k1_pubkey_save(&found_outputs[n_found]->label, &label_ge); + } else { + found_outputs[n_found]->found_with_label = 0; + /* Set the label public key with an invalid public key value. */ + memset(&found_outputs[n_found]->label, 0, sizeof(secp256k1_pubkey)); + } + /* Reset everything for the next round of scanning. */ + label_tweak = NULL; + n_found++; + /* BIP0352 specifies that k is serialized as a 4 byte (32 bit) value, so we check to make + * sure we are not exceeding the max value for a uint32 before incrementing k. + * In practice, this should never happen as it would be impossible to create a transaction + * with this many outputs. + */ + if (k < UINT32_MAX) { + k++; + } else { + return 0; + } + } else { + secp256k1_scalar_clear(&output_tweak_scalar); + break; + } + } + *n_found_outputs = n_found; + + /* Leaking the shared_secret would break indistinguishability of the transaction, so clear it. */ + secp256k1_memclear_explicit(shared_secret, sizeof(shared_secret)); + return 1; +} + #endif diff --git a/src/modules/silentpayments/tests_impl.h b/src/modules/silentpayments/tests_impl.h index 86948725d8..1eda4f904b 100644 --- a/src/modules/silentpayments/tests_impl.h +++ b/src/modules/silentpayments/tests_impl.h @@ -83,6 +83,31 @@ static unsigned char ALICE_SECKEY[32] = { 0x15, 0x42, 0x01, 0xb8, 0xe5, 0xdf, 0xf3, 0xb1 }; +struct label_cache_entry { + unsigned char label[33]; + unsigned char label_tweak[32]; +}; +struct labels_cache { + size_t entries_used; + struct label_cache_entry entries[10]; +}; +struct labels_cache labels_cache; +const unsigned char* label_lookup(const unsigned char* key, const void* cache_ptr) { + const struct labels_cache* cache; + size_t i; + + if (cache_ptr == NULL) { + return NULL; + } + cache = (const struct labels_cache*)cache_ptr; + for (i = 0; i < cache->entries_used; i++) { + if (secp256k1_memcmp_var(cache->entries[i].label, key, 33) == 0) { + return cache->entries[i].label_tweak; + } + } + return NULL; +} + static void test_recipient_sort_helper(unsigned char (*sp_addresses[3])[2][33], unsigned char (*sp_outputs[3])[32]) { unsigned char const *seckey_ptrs[1]; secp256k1_silentpayments_recipient recipients[3]; @@ -309,10 +334,149 @@ static void test_label_api(void) { } } +static void test_recipient_api(void) { + secp256k1_silentpayments_prevouts_summary pd; /* prevouts_summary */ + secp256k1_silentpayments_prevouts_summary md; /* malformed prevouts_summary */ + secp256k1_silentpayments_found_output f; /* a silent payment found output */ + secp256k1_silentpayments_found_output *fp[1]; /* array of pointers to found outputs */ + secp256k1_xonly_pubkey t; /* taproot x-only public key */ + secp256k1_xonly_pubkey malformed_t; /* malformed x-only public key */ + secp256k1_xonly_pubkey const *tp[1]; /* array of pointers to xonly pks */ + secp256k1_pubkey p; /* plain public key */ + secp256k1_pubkey malformed_p; /* malformed public key */ + secp256k1_pubkey const *pp[1]; /* array of pointers to plain pks */ + unsigned char o33[33]; /* serialized prevouts_summary, serialized shared secret */ + unsigned char o65[65]; /* serialized prevouts_summary, uncompressed */ + unsigned char malformed[33] = { 0x01 }; /* malformed public key serialization */ + size_t n_f; /* number of found outputs */ + + CHECK(secp256k1_ec_pubkey_parse(CTX, &p, BOB_ADDRESS[0], 33)); + memset(&malformed_p, 0, sizeof(malformed_p)); + memset(&malformed_t, 0, sizeof(malformed_t)); + memset(&md, 0, sizeof(md)); + md.data[4] = 1; + CHECK(secp256k1_xonly_pubkey_parse(CTX, &t, &BOB_ADDRESS[0][1])); + tp[0] = &t; + pp[0] = &p; + fp[0] = &f; + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_create(CTX, &pd, SMALLEST_OUTPOINT, tp, 1, pp, 1)); + /* Check that malformed input public keys are caught. Input public keys summing to zero is tested later, + * in the BIP0352 test vectors. + */ + pp[0] = &malformed_p; + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_create(CTX, &pd, SMALLEST_OUTPOINT, tp, 1, pp, 1)); + pp[0] = &p; + /* Check that malformed x-only input public keys are caught. */ + tp[0] = &malformed_t; + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_create(CTX, &pd, SMALLEST_OUTPOINT, tp, 1, pp, 1)); + tp[0] = &t; + + /* Check null values are handled */ + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_create(CTX, NULL, SMALLEST_OUTPOINT, tp, 1, pp, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_create(CTX, &pd, NULL, tp, 1, pp, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_create(CTX, &pd, SMALLEST_OUTPOINT, NULL, 1, pp, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_create(CTX, &pd, SMALLEST_OUTPOINT, tp, 1, NULL, 1)); + + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_serialize(CTX, NULL, 33, &pd, SECP256K1_EC_COMPRESSED)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_serialize(CTX, o33, 33, NULL, SECP256K1_EC_COMPRESSED)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_serialize(CTX, o33, 33, &pd, 101)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_serialize(CTX, o33, 65, &pd, SECP256K1_EC_COMPRESSED)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_serialize(CTX, o33, 33, &pd, SECP256K1_EC_UNCOMPRESSED)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_serialize(CTX, o33, 0, &pd, SECP256K1_EC_COMPRESSED)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_parse(CTX, NULL, o33, 33)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_parse(CTX, &pd, NULL, 33)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_parse(CTX, &pd, o33, 0)); + + /* Check that serializing or using a malformed, i.e., not created by us, prevouts_summary object fails */ + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_serialize(CTX, o33, 33, &md, SECP256K1_EC_COMPRESSED)); + + /* Check that malformed serializations are rejected */ + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_parse(CTX, &pd, malformed, 33) == 0); + + /* This prevouts_summary object was created with combined = 0, i.e., it has both the input hash and summed public keypair. */ + + /* Parse a prevouts_summary object from a 33 byte serialization and check that this round trips into a valid serialization */ + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_parse(CTX, &pd, BOB_ADDRESS[0], 33)); + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_serialize(CTX, o33, 33, &pd, SECP256K1_EC_COMPRESSED)); + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_serialize(CTX, o65, 65, &pd, SECP256K1_EC_UNCOMPRESSED)); + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_parse(CTX, &pd, o33, 33)); + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_parse(CTX, &pd, o65, 65)); + /* Reset pd to a valid prevouts_summary object */ + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_parse(CTX, &pd, BOB_ADDRESS[0], 33)); + + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_create(CTX, &pd, SMALLEST_OUTPOINT, tp, 0, pp, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_create(CTX, &pd, SMALLEST_OUTPOINT, tp, 1, pp, 0)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_create(CTX, &pd, SMALLEST_OUTPOINT, NULL, 0, pp, 0)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_create(CTX, &pd, SMALLEST_OUTPOINT, NULL, 0, NULL, 0)); + + /* check the _recipient_scan_outputs cornercase where internal tweaking would fail; + this is the case if the recipient spend public key is P = -(create_output_tweak(shared_secret, k))*G */ + { + unsigned char malformed_spend_key[32] = { + 0xed, 0x52, 0x40, 0x3f, 0x47, 0x96, 0x62, 0xb0, + 0x80, 0xaa, 0xf3, 0xee, 0xf3, 0x9a, 0xfa, 0x86, + 0xc4, 0x7a, 0xb0, 0xed, 0x5f, 0xbc, 0xa9, 0x31, + 0xf8, 0x80, 0x4a, 0x0e, 0x0a, 0xed, 0x2a, 0x84, + }; + secp256k1_pubkey neg_spend_pubkey; + CHECK(secp256k1_ec_pubkey_create(CTX, &neg_spend_pubkey, malformed_spend_key)); + CHECK(secp256k1_ec_pubkey_negate(CTX, &neg_spend_pubkey)); + CHECK(secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, ALICE_SECKEY, &pd, &neg_spend_pubkey, &label_lookup, &labels_cache) == 0); + } + /* check the _recipients_scan_outputs cornercase where the output_tweak is the negation of the label_tweak */ + { + unsigned char output_tweak[32] = { + 0xed, 0x52, 0x40, 0x3f, 0x47, 0x96, 0x62, 0xb0, + 0x80, 0xaa, 0xf3, 0xee, 0xf3, 0x9a, 0xfa, 0x86, + 0xc4, 0x7a, 0xb0, 0xed, 0x5f, 0xbc, 0xa9, 0x31, + 0xf8, 0x80, 0x4a, 0x0e, 0x0a, 0xed, 0x2a, 0x84 + }; + static const unsigned char zero[32] = {0}; + secp256k1_pubkey spk, neg_label_pubkey; + secp256k1_xonly_pubkey output_xonly; + secp256k1_xonly_pubkey const *output_xonly_ptrs[1]; + size_t len = 33; + size_t found; + CHECK(secp256k1_ec_pubkey_parse(CTX, &spk, BOB_ADDRESS[0], 33)); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &output_xonly, NULL, &spk)); + output_xonly_ptrs[0] = &output_xonly; + CHECK(secp256k1_ec_seckey_negate(CTX, output_tweak)); + CHECK(secp256k1_ec_pubkey_create(CTX, &neg_label_pubkey, output_tweak)); + CHECK(secp256k1_ec_pubkey_serialize(CTX, labels_cache.entries[0].label, &len, &neg_label_pubkey, SECP256K1_EC_COMPRESSED)); + memcpy(labels_cache.entries[0].label_tweak, output_tweak, 32); + labels_cache.entries_used = 1; + found = 0; + CHECK(secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &found, output_xonly_ptrs, 1, ALICE_SECKEY, &pd, &spk, &label_lookup, &labels_cache)); + CHECK(found == 1); + CHECK(secp256k1_memcmp_var(fp[0]->tweak, zero, 32) == 0); + } + + n_f = 0; + labels_cache.entries_used = 0; + CHECK(secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, ALICE_SECKEY, &pd, &p, &label_lookup, &labels_cache)); + CHECK(secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, ALICE_SECKEY, &pd, &p, &label_lookup, NULL)); + CHECK(secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, ALICE_SECKEY, &pd, &p, NULL, NULL)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, NULL, &n_f, tp, 1, ALICE_SECKEY, &pd, &p, &label_lookup, &labels_cache)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, NULL, tp, 1, ALICE_SECKEY, &pd, &p, &label_lookup, &labels_cache)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, NULL, 1, ALICE_SECKEY, &pd, &p, &label_lookup, &labels_cache)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, NULL, &pd, &p, &label_lookup, &labels_cache)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, ALICE_SECKEY, NULL, &p, &label_lookup, &labels_cache)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, ALICE_SECKEY, &pd, NULL, &label_lookup, &labels_cache)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 0, ALICE_SECKEY, &pd, &p, &label_lookup, &labels_cache)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, ALICE_SECKEY, &pd, &p, NULL, &labels_cache)); + + /* Check that malformed secret key, public key, and prevouts_summary arguments are handled */ + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, ALICE_SECKEY, &pd, &malformed_p, NULL, NULL)); + CHECK(secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, MALFORMED_SECKEY, &pd, &p, NULL, NULL) == 0); + memset(&pd, 0, sizeof(pd)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, ALICE_SECKEY, &pd, &p, NULL, NULL)); +} + void run_silentpayments_tests(void) { test_recipient_sort(); test_send_api(); test_label_api(); + test_recipient_api(); } #endif From 14b1bf04ff7def73b80f92a6d4c9cc307b393ada Mon Sep 17 00:00:00 2001 From: josibake Date: Thu, 2 Oct 2025 15:20:53 +0100 Subject: [PATCH 05/12] silentpayments: light client scanning Add function for creating k=0 outputs for multiple spend public keys. These keys can then be checked for existance against the UTXO set/blockchain. If a match is found, the client needs to download the full transaction and rescan with `_scan_outputs`. --- include/secp256k1_silentpayments.h | 38 +++++++++++++++++++++++++ src/modules/silentpayments/main_impl.h | 33 +++++++++++++++++++++ src/modules/silentpayments/tests_impl.h | 28 ++++++++++++++++++ 3 files changed, 99 insertions(+) diff --git a/include/secp256k1_silentpayments.h b/include/secp256k1_silentpayments.h index 0b75a2ccc7..b8357b813e 100644 --- a/include/secp256k1_silentpayments.h +++ b/include/secp256k1_silentpayments.h @@ -384,6 +384,44 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipien ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8); +/** Create Silent Payment output public keys. + * + * Given a scan key, a prevouts_summary, and array of recipient spend public keys, + * create the silent payments output public keys. + * + * This function is used by the recipient when scanning for outputs without + * access to the transaction outputs (e.g., using BIP158 block filters). It will + * create an output (the first output) for each of the spend public keys provided. + * It is the caller's responsibility to determine if the created outputs exist. + * + * If a match is found, the caller must download the full transaction and call + * `secp256k1_silentpayments_scan_outputs` to check if there are additional outputs + * for the recipient and get the full output tweak needed to spend the outputs. + * + * Returns: 1 if output creation was successful. + * 0 if the transaction is not a silent payments transaction. + * + * Args: ctx: pointer to a context object + * Out: outputs_xonly: pointer to an array of pointers to the resulting + * output x-only public keys. The outputs_xonly array + * MUST have the same size as the spend_pubkeys array. + * In: scan_key32: pointer to the recipient's 32 byte scan key. + * The scan key is valid if it passes secp256k1_ec_seckey_verify. + * prevouts_summary: pointer to the transaction prevouts summary data + * (see `_recipient_prevouts_summary_create`). + * spend_pubkeys: pointer to an array of pointers to the recipient's spend public keys + * (labeled or unlabeled). + * n_spend_pubkeys: the size of the spend_pubkeys array. + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_create_output_pubkeys( + const secp256k1_context *ctx, + secp256k1_xonly_pubkey **outputs_xonly, + const unsigned char *scan_key32, + const secp256k1_silentpayments_prevouts_summary *prevouts_summary, + const secp256k1_pubkey **spend_pubkeys, + size_t n_spend_pubkeys +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + #ifdef __cplusplus } #endif diff --git a/src/modules/silentpayments/main_impl.h b/src/modules/silentpayments/main_impl.h index 3f8929af47..c0f4822fda 100644 --- a/src/modules/silentpayments/main_impl.h +++ b/src/modules/silentpayments/main_impl.h @@ -761,4 +761,37 @@ int secp256k1_silentpayments_recipient_scan_outputs( return 1; } +int secp256k1_silentpayments_recipient_create_output_pubkeys(const secp256k1_context *ctx, secp256k1_xonly_pubkey **outputs_xonly, const unsigned char *scan_key32, const secp256k1_silentpayments_prevouts_summary *prevouts_summary, const secp256k1_pubkey **spend_pubkeys, size_t n_spend_pubkeys) +{ + secp256k1_scalar scan_key_scalar; + secp256k1_ge input_pubkey_ge; + int combined, valid_scan_key; + unsigned char shared_secret[33]; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(outputs_xonly != NULL); + ARG_CHECK(scan_key32 != NULL); + ARG_CHECK(prevouts_summary != NULL); + ARG_CHECK(spend_pubkeys != NULL); + ARG_CHECK(n_spend_pubkeys > 0); + ARG_CHECK(secp256k1_memcmp_var(&prevouts_summary->data[0], secp256k1_silentpayments_prevouts_summary_magic, 4) == 0); + /* If there are any issues with the recipient scan key, return early. */ + valid_scan_key = secp256k1_scalar_set_b32_seckey(&scan_key_scalar, scan_key32); + secp256k1_declassify(ctx, &valid_scan_key, sizeof(valid_scan_key)); + if (!valid_scan_key) { + secp256k1_scalar_clear(&scan_key_scalar); + return 0; + } + secp256k1_ge_from_bytes(&input_pubkey_ge, &prevouts_summary->data[5]); + combined = (int)prevouts_summary->data[4]; + if (!combined) { + secp256k1_scalar input_hash_scalar; + secp256k1_scalar_set_b32(&input_hash_scalar, &prevouts_summary->data[5 + 64], NULL); + secp256k1_scalar_mul(&scan_key_scalar, &scan_key_scalar, &input_hash_scalar); + } + secp256k1_silentpayments_create_shared_secret(ctx, shared_secret, &input_pubkey_ge, &scan_key_scalar); + secp256k1_scalar_clear(&scan_key_scalar); + return secp256k1_silentpayments_create_output_pubkeys(ctx, outputs_xonly, shared_secret, spend_pubkeys, n_spend_pubkeys, 0); +} + #endif diff --git a/src/modules/silentpayments/tests_impl.h b/src/modules/silentpayments/tests_impl.h index 1eda4f904b..ad66be9aea 100644 --- a/src/modules/silentpayments/tests_impl.h +++ b/src/modules/silentpayments/tests_impl.h @@ -342,6 +342,10 @@ static void test_recipient_api(void) { secp256k1_xonly_pubkey t; /* taproot x-only public key */ secp256k1_xonly_pubkey malformed_t; /* malformed x-only public key */ secp256k1_xonly_pubkey const *tp[1]; /* array of pointers to xonly pks */ + secp256k1_xonly_pubkey outputs[2]; /* array of generated xonly pks */ + secp256k1_xonly_pubkey *output_ptrs[2]; /* array of pointers to generated xonly pks */ + secp256k1_pubkey spend_pubkeys[2]; /* array of spend public keys */ + secp256k1_pubkey const *spend_pubkey_ptrs[2]; /* array of pointers to spend public keys */ secp256k1_pubkey p; /* plain public key */ secp256k1_pubkey malformed_p; /* malformed public key */ secp256k1_pubkey const *pp[1]; /* array of pointers to plain pks */ @@ -351,6 +355,12 @@ static void test_recipient_api(void) { size_t n_f; /* number of found outputs */ CHECK(secp256k1_ec_pubkey_parse(CTX, &p, BOB_ADDRESS[0], 33)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &spend_pubkeys[0], BOB_ADDRESS[0], 33)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &spend_pubkeys[1], BOB_ADDRESS[0], 33)); + spend_pubkey_ptrs[0] = &spend_pubkeys[0]; + spend_pubkey_ptrs[1] = &spend_pubkeys[1]; + output_ptrs[0] = &outputs[0]; + output_ptrs[1] = &outputs[1]; memset(&malformed_p, 0, sizeof(malformed_p)); memset(&malformed_t, 0, sizeof(malformed_t)); memset(&md, 0, sizeof(md)); @@ -470,6 +480,24 @@ static void test_recipient_api(void) { CHECK(secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, MALFORMED_SECKEY, &pd, &p, NULL, NULL) == 0); memset(&pd, 0, sizeof(pd)); CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, ALICE_SECKEY, &pd, &p, NULL, NULL)); + /* Reset pd to a valid prevouts_summary object */ + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_parse(CTX, &pd, BOB_ADDRESS[0], 33)); + + /* Test recipient light client API */ + CHECK(secp256k1_silentpayments_recipient_create_output_pubkeys(CTX, output_ptrs, ALICE_SECKEY, &pd, spend_pubkey_ptrs, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_output_pubkeys(CTX, NULL, ALICE_SECKEY, &pd, spend_pubkey_ptrs, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_output_pubkeys(CTX, output_ptrs, NULL, &pd, spend_pubkey_ptrs, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_output_pubkeys(CTX, output_ptrs, ALICE_SECKEY, NULL, spend_pubkey_ptrs, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_output_pubkeys(CTX, output_ptrs, ALICE_SECKEY, &pd, NULL, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_output_pubkeys(CTX, output_ptrs, ALICE_SECKEY, &pd, spend_pubkey_ptrs, 0)); + memset(&pd, 0, sizeof(pd)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_output_pubkeys(CTX, output_ptrs, ALICE_SECKEY, &pd, spend_pubkey_ptrs, 1)); + /* Reset pd to a valid prevouts_summary object */ + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_parse(CTX, &pd, BOB_ADDRESS[0], 33)); + CHECK(secp256k1_silentpayments_recipient_create_output_pubkeys(CTX, output_ptrs, MALFORMED_SECKEY, &pd, spend_pubkey_ptrs, 1) == 0); + /* Create uncombined prevouts_summary */ + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_create(CTX, &pd, SMALLEST_OUTPOINT, tp, 1, pp, 1)); + CHECK(secp256k1_silentpayments_recipient_create_output_pubkeys(CTX, output_ptrs, ALICE_SECKEY, &pd, spend_pubkey_ptrs, 1)); } void run_silentpayments_tests(void) { From a0b77dcf2fe0a3898d2896121f7117767976ee3d Mon Sep 17 00:00:00 2001 From: josibake Date: Mon, 15 Apr 2024 19:36:29 +0200 Subject: [PATCH 06/12] silentpayments: add examples/silentpayments.c Demonstrate sending, scanning, and light client scanning. --- .gitignore | 1 + Makefile.am | 11 + examples/CMakeLists.txt | 4 + examples/silentpayments.c | 554 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 570 insertions(+) create mode 100644 examples/silentpayments.c diff --git a/.gitignore b/.gitignore index ce33a84adf..e2d0a46b57 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ ecdsa_example schnorr_example ellswift_example musig_example +silentpayments_example *.exe *.so *.a diff --git a/Makefile.am b/Makefile.am index d71c7c7d32..fcaf081ec9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -207,6 +207,17 @@ musig_example_LDFLAGS += -lbcrypt endif TESTS += musig_example endif +if ENABLE_MODULE_SILENTPAYMENTS +noinst_PROGRAMS += silentpayments_example +silentpayments_example_SOURCES = examples/silentpayments.c +silentpayments_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC +silentpayments_example_LDADD = libsecp256k1.la +silentpayments_example_LDFLAGS = -static +if BUILD_WINDOWS +silentpayments_example_LDFLAGS += -lbcrypt +endif +TESTS += silentpayments_example +endif endif ### Precomputed tables diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c9da9de6be..a1d91f66cc 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -29,3 +29,7 @@ endif() if(SECP256K1_ENABLE_MODULE_MUSIG) add_example(musig) endif() + +if(SECP256K1_ENABLE_MODULE_SILENTPAYMENTS) + add_example(silentpayments) +endif() diff --git a/examples/silentpayments.c b/examples/silentpayments.c new file mode 100644 index 0000000000..e344bf2bb7 --- /dev/null +++ b/examples/silentpayments.c @@ -0,0 +1,554 @@ +/************************************************************************* + * To the extent possible under law, the author(s) have dedicated all * + * copyright and related and neighboring rights to the software in this * + * file to the public domain worldwide. This software is distributed * + * without any warranty. For the CC0 Public Domain Dedication, see * + * EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 * + *************************************************************************/ + +#include +#include +#include +#include + +#include +#include + +#include "examples_util.h" + +#define N_INPUTS 2 +#define N_OUTPUTS 3 + +/* Static data for Bob and Carol's silent payment addresses */ +static unsigned char smallest_outpoint[36] = { + 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 +}; +static unsigned char bob_scan_key[32] = { + 0xa8, 0x90, 0x54, 0xc9, 0x5b, 0xe3, 0xc3, 0x01, + 0x56, 0x65, 0x74, 0xf2, 0xaa, 0x93, 0xad, 0xe0, + 0x51, 0x85, 0x09, 0x03, 0xa6, 0x9c, 0xbd, 0xd1, + 0xd4, 0x7e, 0xae, 0x26, 0x3d, 0x7b, 0xc0, 0x31 +}; +static unsigned char bob_spend_key[32] = { + 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 +}; +static unsigned char bob_scan_and_spend_pubkeys[2][33] = { + { + 0x02, 0x15, 0x40, 0xae, 0xa8, 0x97, 0x54, 0x7a, + 0xd4, 0x39, 0xb4, 0xe0, 0xf6, 0x09, 0xe5, 0xf0, + 0xfa, 0x63, 0xde, 0x89, 0xab, 0x11, 0xed, 0xe3, + 0x1e, 0x8c, 0xde, 0x4b, 0xe2, 0x19, 0x42, 0x5f, 0x23 + }, + { + 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 + } +}; +static unsigned char carol_scan_key[32] = { + 0x04, 0xb2, 0xa4, 0x11, 0x63, 0x5c, 0x09, 0x77, + 0x59, 0xaa, 0xcd, 0x0f, 0x00, 0x5a, 0x4c, 0x82, + 0xc8, 0xc9, 0x28, 0x62, 0xc6, 0xfc, 0x28, 0x4b, + 0x80, 0xb8, 0xef, 0xeb, 0xc2, 0x0c, 0x3d, 0x17 +}; +static unsigned char carol_address[2][33] = { + { + 0x03, 0xbb, 0xc6, 0x3f, 0x12, 0x74, 0x5d, 0x3b, + 0x9e, 0x9d, 0x24, 0xc6, 0xcd, 0x7a, 0x1e, 0xfe, + 0xba, 0xd0, 0xa7, 0xf4, 0x69, 0x23, 0x2f, 0xbe, + 0xcf, 0x31, 0xfb, 0xa7, 0xb4, 0xf7, 0xdd, 0xed, 0xa8 + }, + { + 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 + } +}; + +/** Labels + * + * The structs and callback function are implemented here as a demonstration + * of how the label lookup callback is meant to query a label cache and return + * the label tweak when a match is found. This is for demonstration purposes + * only and not optimized. In production, it is expected that the + * caller will be using a much more performant method for storing and querying + * labels. + * + * Recipients not using labels can ignore these steps and simply pass `NULL` + * for the label_lookup and label_context arguments: + * + * secp256k1_silentpayments_recipient_scan_outputs(..., NULL, NULL); + */ + +struct label_cache_entry { + unsigned char label[33]; + unsigned char label_tweak[32]; +}; + +struct labels_cache { + size_t entries_used; + struct label_cache_entry entries[5]; +}; + +const unsigned char* label_lookup( + const unsigned char* label33, + const void* cache_ptr +) { + const struct labels_cache* cache = (const struct labels_cache*)cache_ptr; + size_t i; + for (i = 0; i < cache->entries_used; i++) { + if (memcmp(cache->entries[i].label, label33, 33) == 0) { + return cache->entries[i].label_tweak; + } + } + return NULL; +} + +int main(void) { + unsigned char randomize[32]; + unsigned char serialized_xonly[32]; + secp256k1_xonly_pubkey tx_inputs[N_INPUTS]; + const secp256k1_xonly_pubkey *tx_input_ptrs[N_INPUTS]; + secp256k1_xonly_pubkey tx_outputs[N_OUTPUTS]; + secp256k1_xonly_pubkey *tx_output_ptrs[N_OUTPUTS]; + secp256k1_silentpayments_found_output found_outputs[N_OUTPUTS]; + secp256k1_silentpayments_found_output *found_output_ptrs[N_OUTPUTS]; + secp256k1_silentpayments_prevouts_summary prevouts_summary; + secp256k1_pubkey unlabeled_spend_pubkey; + struct labels_cache bob_labels_cache; + unsigned char bob_address[2][33]; + int ret, found; + size_t i, n_found_outputs; + + /* Before we can call actual API functions, we need to create a "context" */ + secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + if (!fill_random(randomize, sizeof(randomize))) { + printf("Failed to generate randomness\n"); + return EXIT_FAILURE; + } + /* Randomizing the context is recommended to protect against side-channel + * leakage. See `secp256k1_context_randomize` in secp256k1.h for more + * information about it. This should never fail. */ + ret = secp256k1_context_randomize(ctx, randomize); + assert(ret); + + /* Set up the pointer arrays. These will be used for sending and scanning. */ + for (i = 0; i < N_INPUTS; i++) { + tx_input_ptrs[i] = &tx_inputs[i]; + } + for (i = 0; i < N_OUTPUTS; i++) { + tx_output_ptrs[i] = &tx_outputs[i]; + found_output_ptrs[i] = &found_outputs[i]; + } + + /*** Create a labeled silent payment address (Bob) ***/ + { + size_t len = 33; + secp256k1_pubkey label, labeled_spend_pubkey; + unsigned int m = 1; + + /* Load Bob's spend public key */ + ret = secp256k1_ec_pubkey_parse(ctx, + &unlabeled_spend_pubkey, + bob_scan_and_spend_pubkeys[1], + 33 + ); + assert(ret); + /* Create a (label_tweak, label) pair and add them to the labels cache. + * + * Bob MUST keep track of all of the labels he has used by adding them + * to the labels cache. Otherwise, he will not find outputs sent to his + * labeled addresses when scanning. If Bob ever loses access to his cache + * or forgets the total number of labels used he can create a large cache + * with m = 0 ... 100_000, following the recommendation from BIP0352. + */ + ret = secp256k1_silentpayments_recipient_create_label(ctx, + &label, + bob_labels_cache.entries[0].label_tweak, + bob_scan_key, + m + ); + if (!ret) { + printf("Something went wrong, event with negligible probability happened.\n"); + return EXIT_FAILURE; + } + ret = secp256k1_ec_pubkey_serialize(ctx, + bob_labels_cache.entries[0].label, + &len, + &label, + SECP256K1_EC_COMPRESSED + ); + assert(ret); + bob_labels_cache.entries_used = 1; + /* Now that the labels cache has been updated, Bob creates his labeled + * silent payments address and publishes it. + */ + ret = secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(ctx, &labeled_spend_pubkey, &unlabeled_spend_pubkey, &label); + assert(ret); + memcpy(bob_address[0], bob_scan_and_spend_pubkeys[0], 33); + ret = secp256k1_ec_pubkey_serialize(ctx, + bob_address[1], + &len, + &labeled_spend_pubkey, + SECP256K1_EC_COMPRESSED + ); + } + /*** Sending (Alice) ***/ + { + secp256k1_keypair sender_keypairs[N_INPUTS]; + const secp256k1_keypair *sender_keypair_ptrs[N_INPUTS]; + secp256k1_silentpayments_recipient recipients[N_OUTPUTS]; + const secp256k1_silentpayments_recipient *recipient_ptrs[N_OUTPUTS]; + /* 2D array for holding multiple public key pairs. The second index, i.e., [2], + * is to represent the spend and scan public keys. */ + unsigned char (*sp_addresses[N_OUTPUTS])[2][33]; + unsigned char seckey[32]; + + /*** Generate secret keys for the sender *** + * + * In this example, only taproot inputs are used but the function can be + * called with a mix of taproot seckeys and plain seckeys. Taproot + * seckeys are passed as keypairs to allow the sending function to check + * if the secret keys need to be negated without needing to do an + * expensive pubkey generation. This is not needed for plain seckeys + * since there is no need for negation. + * + * The public key from each input keypair is saved in the `tx_inputs` + * array. This array will be used later in the example to represent the + * public keys the recipient will extract from the transaction inputs. + * + * If the secret key is zero or out of range (bigger than secp256k1's + * order), fail. Note that the probability of this happening is + * negligible. */ + for (i = 0; i < N_INPUTS; i++) { + if (!fill_random(seckey, sizeof(seckey))) { + printf("Failed to generate randomness\n"); + return EXIT_FAILURE; + } + /* Try to create a keypair with a valid context, it should only fail + * if the secret key is zero or out of range. */ + if (secp256k1_keypair_create(ctx, &sender_keypairs[i], seckey)) { + sender_keypair_ptrs[i] = &sender_keypairs[i]; + ret = secp256k1_keypair_xonly_pub( + ctx, + &tx_inputs[i], + NULL, + &sender_keypairs[i] + ); + assert(ret); + } else { + printf("Failed to create keypair\n"); + return EXIT_FAILURE; + } + } + /*** Create the recipient objects ***/ + + /* Alice is sending to Bob and Carol in this transaction: + * + * 1. One output to Bob's labeled address + * 2. Two outputs for Carol + * + * To create multiple outputs for Carol, Alice simply passes Carol's + * silent payment address multiple times. + */ + sp_addresses[0] = &carol_address; + sp_addresses[1] = &bob_address; + sp_addresses[2] = &carol_address; + for (i = 0; i < N_OUTPUTS; i++) { + ret = secp256k1_ec_pubkey_parse(ctx, + &recipients[i].scan_pubkey, + (*(sp_addresses[i]))[0], + 33 + ); + ret &= secp256k1_ec_pubkey_parse(ctx, + &recipients[i].spend_pubkey, + (*(sp_addresses[i]))[1], + 33 + ); + if (!ret) { + printf("Something went wrong, this is not a valid silent payments address.\n"); + return EXIT_FAILURE; + } + + /* Alice creates the recipient objects and adds the index of the + * original ordering (the ordering of the `sp_addresses` array) to + * each object. This index is used to return the generated outputs + * in the original ordering so that Alice can match up the generated + * outputs with the correct amounts. + */ + recipients[i].index = i; + recipient_ptrs[i] = &recipients[i]; + } + ret = secp256k1_silentpayments_sender_create_outputs(ctx, + tx_output_ptrs, + recipient_ptrs, N_OUTPUTS, + smallest_outpoint, + sender_keypair_ptrs, N_INPUTS, + NULL, 0 + ); + if (!ret) { + printf("Something went wrong, a recipient provided an invalid address.\n"); + return EXIT_FAILURE; + } + printf("Alice created the following outputs for Bob and Carol:\n"); + for (i = 0; i < N_OUTPUTS; i++) { + printf(" "); + ret = secp256k1_xonly_pubkey_serialize(ctx, + serialized_xonly, + &tx_outputs[i] + ); + assert(ret); + print_hex(serialized_xonly, sizeof(serialized_xonly)); + } + /* It's best practice to try to clear secrets from memory after using + * them. This is done because some bugs can allow an attacker to leak + * memory, for example through "out of bounds" array access (see + * Heartbleed), or the OS swapping them to disk. Hence, we overwrite the + * secret key buffer with zeros. + * + * Here we are preventing these writes from being optimized out, as any + * good compiler will remove any writes that aren't used. */ + secure_erase(seckey, sizeof(seckey)); + for (i = 0; i < N_INPUTS; i++) { + secure_erase(&sender_keypairs[i], sizeof(sender_keypairs[i])); + } + } + + /*** Receiving ***/ + { + unsigned char light_client_data33[33]; + { + /*** Scanning as a full node (Bob) *** + * + * Since Bob has access to the full transaction, scanning is simple: + * + * 1. Collect the relevant prevouts from the transaction and call + * `secp256k1_silentpayments_recipient_prevouts_summary_create` + * 2. Call `secp256k1_silentpayments_recipient_scan_outputs` + * + * Bob collects the prevouts data from the transaction inputs and + * creates a `secp256k1_silentpayments_prevouts_summary` object. He uses + * this for his own scanning and also serializes the `prevouts_summary` + * object to send to light clients. We will use this later for + * Carol, who is scanning as a light client. Note, anyone can create + * and provide these `prevouts_summary` objects, i.e. you don't need to be + * a silent payments wallet, just someone interested in providing this + * data to light clients, e.g. a wallet service provider. In our + * example, Bob is scanning for himself but also sharing this data + * with light clients. + */ + ret = secp256k1_silentpayments_recipient_prevouts_summary_create(ctx, + &prevouts_summary, + smallest_outpoint, + tx_input_ptrs, N_INPUTS, + NULL, 0 + ); + if (!ret) { + /* We need to always check that the prevouts data object is valid + * before proceeding. + */ + printf("This transaction is not valid for silent payments, skipping.\n"); + return EXIT_SUCCESS; + } + /* Serialize the prevouts data object for later use. */ + ret = secp256k1_silentpayments_recipient_prevouts_summary_serialize(ctx, + light_client_data33, + 33, + &prevouts_summary, + SECP256K1_EC_COMPRESSED + ); + assert(ret); + + /* Scan the transaction */ + found = 0; + n_found_outputs = 0; + ret = secp256k1_silentpayments_recipient_scan_outputs(ctx, + found_output_ptrs, &n_found_outputs, + (const secp256k1_xonly_pubkey * const *)tx_output_ptrs, N_OUTPUTS, + bob_scan_key, + &prevouts_summary, + &unlabeled_spend_pubkey, + label_lookup, &bob_labels_cache /* NULL, NULL for no labels */ + ); + if (!ret) { + printf("This transaction is not valid for silent payments, skipping.\n"); + return EXIT_SUCCESS; + } + if (n_found_outputs > 0) { + secp256k1_keypair kp; + secp256k1_xonly_pubkey xonly_output; + unsigned char full_seckey[32]; + + printf("\n"); + printf("Bob found the following outputs: \n"); + for (i = 0; i < n_found_outputs; i++) { + printf(" "); + ret = secp256k1_xonly_pubkey_serialize(ctx, + serialized_xonly, + &found_outputs[i].output + ); + assert(ret); + print_hex(serialized_xonly, sizeof(serialized_xonly)); + + /* Verify that this output is spendable by Bob by reconstructing the full + * secret key for the xonly output. + * + * This is done by adding the tweak from the transaction to Bob's spend key. + * If the output was sent to a labeled address, the label tweak has already + * been added to the tweak in `secp256k1_silentpayments_found_output`. + * + * To verify that we are able to sign for this output, it is sufficient to + * check that the public key generated from `full_seckey` matches the output + * in the transaction. For a full example on signing for a taproot ouput, + * see `examples/schnorr.c`. + */ + memcpy(&full_seckey, &bob_spend_key, 32); + ret = secp256k1_ec_seckey_tweak_add(ctx, full_seckey, found_outputs[i].tweak); + ret &= secp256k1_keypair_create(ctx, &kp, full_seckey); + ret &= secp256k1_keypair_xonly_pub( + ctx, + &xonly_output, + NULL, + &kp + ); + /* We assert here because the only way the seckey_tweak_add operation can fail + * is if the tweak is the negation of Bob's spend key. + * + * We also assert that the generated public key matches the transaction output, + * as it should be impossible for a mismatch at this point considering the + * scanning function completed without errors and indicated found outputs. + */ + assert(ret); + assert(secp256k1_xonly_pubkey_cmp(ctx, &xonly_output, &found_outputs[i].output) == 0); + secure_erase(full_seckey, sizeof(full_seckey)); + } + } else { + printf("Bob did not find any outputs in this transaction.\n"); + } + } + { + /*** Scanning as a light client (Carol) *** + * + * Being a light client, Carol likely does not have access to the + * transaction inputs and prevout information, so she uses the + * `prevouts_summary` object created by Bob's full node earlier. This + * serialized `prevouts_summary` object contains everything she needs for + * generating the shared secret, i.e., `input_hash * prevouts_pubkey_sum`. + * + * Additionally, she likely does not have access to the transaction outputs. + * This means she will need to first generate outputs, check if they exist + * in the UTXO set (e.g. BIP158 or some other means of querying), and + * proceed to download the full transaction if there is a match. + * + * Once she has the full transaction, she scans with + * `secp256k1_silentpayments_recipient_scan_outputs` to find all of + * the outputs and extract the tweaks needed for spending later. + */ + + /* Load Carol's spend public key. */ + ret = secp256k1_ec_pubkey_parse(ctx, + &unlabeled_spend_pubkey, + carol_address[1], + 33 + ); + assert(ret); + /* Parse the serialized prevouts_summary object. */ + ret = secp256k1_silentpayments_recipient_prevouts_summary_parse(ctx, + &prevouts_summary, + light_client_data33, + 33 + ); + if (!ret) { + printf("\n"); + printf("This transaction is not valid for silent payments, skipping."); + return EXIT_SUCCESS; + } + found = 0; + n_found_outputs = 0; + { + secp256k1_xonly_pubkey *potential_output_ptrs[1]; + secp256k1_xonly_pubkey potential_outputs[1]; + const secp256k1_pubkey *spend_pubkey_ptrs[1]; + + spend_pubkey_ptrs[0] = &unlabeled_spend_pubkey; + potential_output_ptrs[0] = &potential_outputs[0]; + + ret = secp256k1_silentpayments_recipient_create_output_pubkeys(ctx, + potential_output_ptrs, + carol_scan_key, + &prevouts_summary, + spend_pubkey_ptrs, + 1 + ); + if (!ret) { + printf("This transaction is not valid for silent payments, skipping.\n"); + return EXIT_SUCCESS; + } + /* At this point, we check that the UTXO exists with a light + * client protocol. For this example, we'll just iterate + * through the list of transaction outputs. + * + * If we generate an output and it does not exist in the + * UTXO set, we are done scanning this transaction. It is + * sufficient to stop after the first match, since we will be + * doing a full scan of the transaction once we have access to + * all of the outputs. + */ + found = 0; + for (i = 0; i < N_OUTPUTS; i++) { + if (secp256k1_xonly_pubkey_cmp(ctx, &potential_outputs[0], &tx_outputs[i]) == 0) { + found = 1; + break; + } + } + } + + if (found) { + /* Carol now needs to request the full transaction to do a complete scan. */ + n_found_outputs = 0; + ret = secp256k1_silentpayments_recipient_scan_outputs(ctx, + found_output_ptrs, &n_found_outputs, + (const secp256k1_xonly_pubkey * const *)tx_output_ptrs, N_OUTPUTS, + carol_scan_key, + &prevouts_summary, + &unlabeled_spend_pubkey, + NULL, NULL /* NULL, NULL for no labels */ + ); + if (!ret) { + printf("This transaction is not valid for silent payments, skipping.\n"); + return EXIT_SUCCESS; + } + if (n_found_outputs > 0) { + /* Carol would spend these outputs the same as Bob, by tweaking her + * spend key with the tweak corresponding to the found output. See above + * for an example for Bob's outputs. */ + printf("\n"); + printf("Carol found the following outputs: \n"); + for (i = 0; i < n_found_outputs; i++) { + printf(" "); + ret = secp256k1_xonly_pubkey_serialize(ctx, + serialized_xonly, + &found_outputs[i].output + ); + assert(ret); + print_hex(serialized_xonly, sizeof(serialized_xonly)); + } + } else { + printf("Carol did not find any outputs in this transaction.\n"); + } + } + } + } + + /* This will clear everything from the context and free the memory */ + secp256k1_context_destroy(ctx); + return EXIT_SUCCESS; +} From 9c6d949438bebb49f38c958c59d56288d07ef8e5 Mon Sep 17 00:00:00 2001 From: josibake Date: Thu, 25 Apr 2024 19:33:35 +0200 Subject: [PATCH 07/12] silentpayments: add benchmarks for scanning Add a benchmark for a full transaction scan and for scanning a single output. Only benchmarks for scanning are added as this is the most performance critical portion of the protocol. Co-authored-by: Sebastian Falbesoner <91535+thestack@users.noreply.github.com> --- src/bench.c | 65 +++++-- src/bench.h | 4 +- .../silentpayments/Makefile.am.include | 1 + src/modules/silentpayments/bench_impl.h | 166 ++++++++++++++++++ 4 files changed, 217 insertions(+), 19 deletions(-) create mode 100644 src/modules/silentpayments/bench_impl.h diff --git a/src/bench.c b/src/bench.c index 8ba7623a0e..849b2295a5 100644 --- a/src/bench.c +++ b/src/bench.c @@ -32,6 +32,10 @@ static void help(int default_iters) { printf(" - ElligatorSwift (optional module)\n"); #endif +#ifdef ENABLE_MODULE_SILENTPAYMENTS + printf(" - Silent payments (optional module)\n"); +#endif + printf("\n"); printf("The default number of iterations for each benchmark is %d. This can be\n", default_iters); printf("customized using the SECP256K1_BENCH_ITERS environment variable.\n"); @@ -39,33 +43,40 @@ static void help(int default_iters) { printf("Usage: ./bench [args]\n"); printf("By default, all benchmarks will be run.\n"); printf("args:\n"); - printf(" help : display this help and exit\n"); - printf(" ecdsa : all ECDSA algorithms--sign, verify, recovery (if enabled)\n"); - printf(" ecdsa_sign : ECDSA siging algorithm\n"); - printf(" ecdsa_verify : ECDSA verification algorithm\n"); - printf(" ec : all EC public key algorithms (keygen)\n"); - printf(" ec_keygen : EC public key generation\n"); + printf(" help : display this help and exit\n"); + printf(" ecdsa : all ECDSA algorithms--sign, verify, recovery (if enabled)\n"); + printf(" ecdsa_sign : ECDSA siging algorithm\n"); + printf(" ecdsa_verify : ECDSA verification algorithm\n"); + printf(" ec : all EC public key algorithms (keygen)\n"); + printf(" ec_keygen : EC public key generation\n"); #ifdef ENABLE_MODULE_RECOVERY - printf(" ecdsa_recover : ECDSA public key recovery algorithm\n"); + printf(" ecdsa_recover : ECDSA public key recovery algorithm\n"); #endif #ifdef ENABLE_MODULE_ECDH - printf(" ecdh : ECDH key exchange algorithm\n"); + printf(" ecdh : ECDH key exchange algorithm\n"); #endif #ifdef ENABLE_MODULE_SCHNORRSIG - printf(" schnorrsig : all Schnorr signature algorithms (sign, verify)\n"); - printf(" schnorrsig_sign : Schnorr sigining algorithm\n"); - printf(" schnorrsig_verify : Schnorr verification algorithm\n"); + printf(" schnorrsig : all Schnorr signature algorithms (sign, verify)\n"); + printf(" schnorrsig_sign : Schnorr sigining algorithm\n"); + printf(" schnorrsig_verify : Schnorr verification algorithm\n"); #endif #ifdef ENABLE_MODULE_ELLSWIFT - printf(" ellswift : all ElligatorSwift benchmarks (encode, decode, keygen, ecdh)\n"); - printf(" ellswift_encode : ElligatorSwift encoding\n"); - printf(" ellswift_decode : ElligatorSwift decoding\n"); - printf(" ellswift_keygen : ElligatorSwift key generation\n"); - printf(" ellswift_ecdh : ECDH on ElligatorSwift keys\n"); + printf(" ellswift : all ElligatorSwift benchmarks (encode, decode, keygen, ecdh)\n"); + printf(" ellswift_encode : ElligatorSwift encoding\n"); + printf(" ellswift_decode : ElligatorSwift decoding\n"); + printf(" ellswift_keygen : ElligatorSwift key generation\n"); + printf(" ellswift_ecdh : ECDH on ElligatorSwift keys\n"); +#endif + +#ifdef ENABLE_MODULE_SILENTPAYMENTS + printf(" silentpayments : all Silent payments benchmarks (full_scan, full_scan_with_labels, output_scan)\n"); + printf(" silentpayments_full_scan : Silent payments full transaction scanning\n"); + printf(" silentpayments_full_scan_with_labels : Silent payments full transaction scanning with labels\n"); + printf(" silentpayments_output_scan : Silent payments scan on a single output (e.g., light client)\n"); #endif printf("\n"); @@ -170,6 +181,10 @@ static void bench_keygen_run(void *arg, int iters) { # include "modules/ellswift/bench_impl.h" #endif +#ifdef ENABLE_MODULE_SILENTPAYMENTS +# include "modules/silentpayments/bench_impl.h" +#endif + int main(int argc, char** argv) { int i; secp256k1_pubkey pubkey; @@ -184,7 +199,8 @@ int main(int argc, char** argv) { char* valid_args[] = {"ecdsa", "verify", "ecdsa_verify", "sign", "ecdsa_sign", "ecdh", "recover", "ecdsa_recover", "schnorrsig", "schnorrsig_verify", "schnorrsig_sign", "ec", "keygen", "ec_keygen", "ellswift", "encode", "ellswift_encode", "decode", - "ellswift_decode", "ellswift_keygen", "ellswift_ecdh"}; + "ellswift_decode", "ellswift_keygen", "ellswift_ecdh", "silentpayments", + "silentpayments_output_scan", "silentpayments_full_scan", "silentpayments_full_scan_with_labels"}; size_t valid_args_size = sizeof(valid_args)/sizeof(valid_args[0]); int invalid_args = have_invalid_args(argc, argv, valid_args, valid_args_size); @@ -236,6 +252,15 @@ int main(int argc, char** argv) { } #endif +#ifndef ENABLE_MODULE_SILENTPAYMENTS + if (have_flag(argc, argv, "silentpayments") || have_flag(argc, argv, "silentpayments_full_scan") || + have_flag(argc, argv, "silentpayments_full_scan_with_labels") || have_flag(argc, argv, "silentpayments_output_scan")) { + fprintf(stderr, "./bench: silentpayments module not enabled.\n"); + fprintf(stderr, "Use ./configure --enable-module-silentpayments.\n\n"); + return EXIT_FAILURE; + } +#endif + /* ECDSA benchmark */ data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); @@ -280,5 +305,11 @@ int main(int argc, char** argv) { run_ellswift_bench(iters, argc, argv); #endif +#ifdef ENABLE_MODULE_SILENTPAYMENTS + /* SilentPayments benchmarks */ + run_silentpayments_bench(iters, argc, argv); +#endif + + return EXIT_SUCCESS; } diff --git a/src/bench.h b/src/bench.h index 232fb35fc0..5aa3d665f9 100644 --- a/src/bench.h +++ b/src/bench.h @@ -120,7 +120,7 @@ static void run_benchmark(char *name, void (*benchmark)(void*, int), void (*setu sum += total; } /* ',' is used as a column delimiter */ - printf("%-30s, ", name); + printf("%-40s, ", name); print_number(min * FP_MULT / iter); printf(" , "); print_number(((sum * FP_MULT) / count) / iter); @@ -181,7 +181,7 @@ static void print_output_table_header_row(void) { char* min_str = " Min(us) "; /* center alignment */ char* avg_str = " Avg(us) "; char* max_str = " Max(us) "; - printf("%-30s,%-15s,%-15s,%-15s\n", bench_str, min_str, avg_str, max_str); + printf("%-40s,%-15s,%-15s,%-15s\n", bench_str, min_str, avg_str, max_str); printf("\n"); } diff --git a/src/modules/silentpayments/Makefile.am.include b/src/modules/silentpayments/Makefile.am.include index 842a33e2d9..d377974c69 100644 --- a/src/modules/silentpayments/Makefile.am.include +++ b/src/modules/silentpayments/Makefile.am.include @@ -1,2 +1,3 @@ include_HEADERS += include/secp256k1_silentpayments.h noinst_HEADERS += src/modules/silentpayments/main_impl.h +noinst_HEADERS += src/modules/silentpayments/bench_impl.h diff --git a/src/modules/silentpayments/bench_impl.h b/src/modules/silentpayments/bench_impl.h new file mode 100644 index 0000000000..ee0a730c91 --- /dev/null +++ b/src/modules/silentpayments/bench_impl.h @@ -0,0 +1,166 @@ +/*********************************************************************** + * Copyright (c) 2024 josibake * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_SILENTPAYMENTS_BENCH_H +#define SECP256K1_MODULE_SILENTPAYMENTS_BENCH_H + +#include "../../../include/secp256k1_silentpayments.h" + +typedef struct { + secp256k1_context *ctx; + secp256k1_pubkey spend_pubkeys[1]; + unsigned char scan_key[32]; + unsigned char input_pubkey33[33]; + secp256k1_xonly_pubkey tx_outputs[2]; + secp256k1_xonly_pubkey tx_inputs[2]; + secp256k1_silentpayments_found_output found_outputs[2]; + unsigned char scalar[32]; + unsigned char smallest_outpoint[36]; +} bench_silentpayments_data; + +/* we need a non-null pointer for the cache */ +static int noop; +void* label_cache = &noop; +const unsigned char* label_lookup(const unsigned char* key, const void* cache_ptr) { + (void)key; + (void)cache_ptr; + return NULL; +} + +static void bench_silentpayments_scan_setup(void* arg) { + int i; + bench_silentpayments_data *data = (bench_silentpayments_data*)arg; + const unsigned char tx_outputs[2][32] = { + {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}, + }; + const unsigned char static_tx_input[32] = { + 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 + }; + const unsigned char smallest_outpoint[36] = { + 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, + }; + const unsigned char spend_pubkey[33] = { + 0x02,0xee,0x97,0xdf,0x83,0xb2,0x54,0x6a, + 0xf5,0xa7,0xd0,0x62,0x15,0xd9,0x8b,0xcb, + 0x63,0x7f,0xe0,0x5d,0xd0,0xfa,0x37,0x3b, + 0xd8,0x20,0xe6,0x64,0xd3,0x72,0xde,0x9a,0x01 + }; + const unsigned char scan_key[32] = { + 0xa8,0x90,0x54,0xc9,0x5b,0xe3,0xc3,0x01, + 0x56,0x65,0x74,0xf2,0xaa,0x93,0xad,0xe0, + 0x51,0x85,0x09,0x03,0xa6,0x9c,0xbd,0xd1, + 0xd4,0x7e,0xae,0x26,0x3d,0x7b,0xc0,0x31 + }; + secp256k1_keypair input_keypair; + secp256k1_pubkey input_pubkey; + size_t pubkeylen = 33; + + for (i = 0; i < 32; i++) { + data->scalar[i] = i + 1; + } + for (i = 0; i < 2; i++) { + CHECK(secp256k1_xonly_pubkey_parse(data->ctx, &data->tx_outputs[i], tx_outputs[i])); + } + /* Create the first input public key from the scalar. + * This input is also used to create the serialized prevouts_summary object for the light client + */ + CHECK(secp256k1_keypair_create(data->ctx, &input_keypair, data->scalar)); + CHECK(secp256k1_keypair_pub(data->ctx, &input_pubkey, &input_keypair)); + CHECK(secp256k1_ec_pubkey_serialize(data->ctx, data->input_pubkey33, &pubkeylen, &input_pubkey, SECP256K1_EC_COMPRESSED)); + /* Create the input public keys for the full scan */ + CHECK(secp256k1_keypair_xonly_pub(data->ctx, &data->tx_inputs[0], NULL, &input_keypair)); + CHECK(secp256k1_xonly_pubkey_parse(data->ctx, &data->tx_inputs[1], static_tx_input)); + CHECK(secp256k1_ec_pubkey_parse(data->ctx, &data->spend_pubkeys[0], spend_pubkey, pubkeylen)); + memcpy(data->scan_key, scan_key, 32); + memcpy(data->smallest_outpoint, smallest_outpoint, 36); +} + +static void bench_silentpayments_output_scan(void* arg, int iters) { + int i = 0; + bench_silentpayments_data *data = (bench_silentpayments_data*)arg; + secp256k1_silentpayments_prevouts_summary prevouts_summary; + const secp256k1_pubkey *spend_ptrs[1]; + spend_ptrs[0] = &data->spend_pubkeys[0]; + + for (i = 0; i < iters; i++) { + secp256k1_xonly_pubkey *xonly_output_ptrs[1]; + secp256k1_xonly_pubkey xonly_outputs[1]; + xonly_output_ptrs[0] = &xonly_outputs[0]; + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_parse(data->ctx, &prevouts_summary, data->input_pubkey33, 33)); + CHECK(secp256k1_silentpayments_recipient_create_output_pubkeys(data->ctx, + xonly_output_ptrs, + data->scan_key, + &prevouts_summary, + spend_ptrs, + 1 + )); + } +} + +static void bench_silentpayments_full_tx_scan(void* arg, int iters, int use_labels) { + int i; + size_t n_found = 0; + secp256k1_silentpayments_found_output *found_output_ptrs[2]; + const secp256k1_xonly_pubkey *tx_output_ptrs[2]; + const secp256k1_xonly_pubkey *tx_input_ptrs[2]; + bench_silentpayments_data *data = (bench_silentpayments_data*)arg; + secp256k1_silentpayments_prevouts_summary prevouts_summary; + const secp256k1_silentpayments_label_lookup label_lookup_fn = use_labels ? label_lookup : NULL; + const void *label_context = use_labels ? label_cache : NULL; + + for (i = 0; i < 2; i++) { + found_output_ptrs[i] = &data->found_outputs[i]; + tx_output_ptrs[i] = &data->tx_outputs[i]; + tx_input_ptrs[i] = &data->tx_inputs[i]; + } + for (i = 0; i < iters; i++) { + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_create(data->ctx, + &prevouts_summary, + data->smallest_outpoint, + tx_input_ptrs, 2, + NULL, 0 + )); + CHECK(secp256k1_silentpayments_recipient_scan_outputs(data->ctx, + found_output_ptrs, &n_found, + tx_output_ptrs, 2, + data->scan_key, + &prevouts_summary, + &data->spend_pubkeys[0], + label_lookup_fn, label_context) + ); + CHECK(n_found == 0); + } +} + +static void bench_silentpayments_full_scan(void *arg, int iters) { + bench_silentpayments_full_tx_scan(arg, iters, 0); +} + +static void bench_silentpayments_full_scan_with_labels(void *arg, int iters) { + bench_silentpayments_full_tx_scan(arg, iters, 1); +} + +static void run_silentpayments_bench(int iters, int argc, char** argv) { + bench_silentpayments_data data; + int d = argc == 1; + + data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + + if (d || have_flag(argc, argv, "silentpayments") || have_flag(argc, argv, "silentpayments_output_scan")) run_benchmark("silentpayments_output_scan", bench_silentpayments_output_scan, bench_silentpayments_scan_setup, NULL, &data, 10, iters); + if (d || have_flag(argc, argv, "silentpayments") || have_flag(argc, argv, "silentpayments_full_scan")) run_benchmark("silentpayments_full_scan", bench_silentpayments_full_scan, bench_silentpayments_scan_setup, NULL, &data, 10, iters); + if (d || have_flag(argc, argv, "silentpayments") || have_flag(argc, argv, "silentpayments_full_scan_with_labels")) run_benchmark("silentpayments_full_scan_with_labels", bench_silentpayments_full_scan_with_labels, bench_silentpayments_scan_setup, NULL, &data, 10, iters); + + secp256k1_context_destroy(data.ctx); +} + +#endif /* SECP256K1_MODULE_SILENTPAYMENTS_BENCH_H */ From e759094a555599ffbc48f34e81572ae078a3d77b Mon Sep 17 00:00:00 2001 From: josibake Date: Tue, 2 Jul 2024 19:45:43 +0200 Subject: [PATCH 08/12] tests: add BIP-352 test vectors Add the BIP-352 test vectors. The vectors are generated with a Python script that converts the .json file from the BIP to C code: $ ./tools/tests_silentpayments_generate.py test_vectors.json > ./src/modules/silentpayments/vectors.h Co-authored-by: Ron <4712150+macgyver13@users.noreply.github.com> Co-authored-by: Sebastian Falbesoner <91535+thestack@users.noreply.github.com> Co-authored-by: Tim Ruffing <1071625+real-or-random@users.noreply.github.com> --- Makefile.am | 5 + .../silentpayments/Makefile.am.include | 2 + .../bip352_send_and_receive_test_vectors.json | 3189 +++++++++++++++++ src/modules/silentpayments/tests_impl.h | 204 ++ src/modules/silentpayments/vectors.h | 1918 ++++++++++ tools/tests_silentpayments_generate.py | 279 ++ 6 files changed, 5597 insertions(+) create mode 100644 src/modules/silentpayments/bip352_send_and_receive_test_vectors.json create mode 100644 src/modules/silentpayments/vectors.h create mode 100755 tools/tests_silentpayments_generate.py diff --git a/Makefile.am b/Makefile.am index fcaf081ec9..e9009884b8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -260,6 +260,7 @@ maintainer-clean-local: clean-precomp ### Pregenerated test vectors ### (see the comments in the previous section for detailed rationale) TESTVECTORS = src/wycheproof/ecdsa_secp256k1_sha256_bitcoin_test.h +TESTVECTORS += src/modules/silentpayments/vectors.h if ENABLE_MODULE_SILENTPAYMENTS TESTVECTORS += src/modules/silentpayments/vectors.h @@ -277,6 +278,10 @@ src/wycheproof/ecdh_secp256k1_test.h: mkdir -p $(@D) python3 $(top_srcdir)/tools/tests_wycheproof_generate_ecdh.py $(top_srcdir)/src/wycheproof/ecdh_secp256k1_test.json > $@ +src/modules/silentpayments/vectors.h: + mkdir -p $(@D) + python3 $(top_srcdir)/tools/tests_silentpayments_generate.py $(top_srcdir)/src/modules/silentpayments/bip352_send_and_receive_test_vectors.json > $@ + testvectors: $(TESTVECTORS) BUILT_SOURCES += $(TESTVECTORS) diff --git a/src/modules/silentpayments/Makefile.am.include b/src/modules/silentpayments/Makefile.am.include index d377974c69..d0c51dc337 100644 --- a/src/modules/silentpayments/Makefile.am.include +++ b/src/modules/silentpayments/Makefile.am.include @@ -1,3 +1,5 @@ include_HEADERS += include/secp256k1_silentpayments.h noinst_HEADERS += src/modules/silentpayments/main_impl.h noinst_HEADERS += src/modules/silentpayments/bench_impl.h +noinst_HEADERS += src/modules/silentpayments/tests_impl.h +noinst_HEADERS += src/modules/silentpayments/vectors.h diff --git a/src/modules/silentpayments/bip352_send_and_receive_test_vectors.json b/src/modules/silentpayments/bip352_send_and_receive_test_vectors.json new file mode 100644 index 0000000000..3a5e1d3d94 --- /dev/null +++ b/src/modules/silentpayments/bip352_send_and_receive_test_vectors.json @@ -0,0 +1,3189 @@ +[ + { + "comment": "Simple send: two inputs", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + }, + "private_key": "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1" + ] + ], + "shared_secrets": [ + "028158aff7d61ea66b2fa7f555bc3c5937d1debbde16423d630f9aa7943e14d80d" + ], + "input_private_key_sum": "7ed265a6dac7aba8508a32d6d6b84c7f1dbd0a0941dd01088d69e8d556345f86", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + } + } + ], + "outputs": [ + "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "f438b40179a3c4262de12986c0e6cce0634007cdc79c1dcd3e20b9ebc2e7eef6", + "pub_key": "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1", + "signature": "74f85b856337fbe837643b86f462118159f93ac4acc2671522f27e8f67b079959195ccc7a5dbee396d2909f5d680d6e30cda7359aa2755822509b70d6b0687a1" + } + ], + "tweak": "024ac253c216532e961988e2a8ce266a447c894c781e52ef6cee902361db960004", + "shared_secret": "028158aff7d61ea66b2fa7f555bc3c5937d1debbde16423d630f9aa7943e14d80d", + "input_pub_key_sum": "032562c1ab2d6bd45d7ca4d78f569999e5333dffd3ac5263924fd00d00dedc4bee" + } + } + ] + }, + { + "comment": "Simple send: two inputs, order reversed", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + }, + "private_key": "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1" + ] + ], + "shared_secrets": [ + "028158aff7d61ea66b2fa7f555bc3c5937d1debbde16423d630f9aa7943e14d80d" + ], + "input_private_key_sum": "7ed265a6dac7aba8508a32d6d6b84c7f1dbd0a0941dd01088d69e8d556345f86", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + } + } + ], + "outputs": [ + "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "f438b40179a3c4262de12986c0e6cce0634007cdc79c1dcd3e20b9ebc2e7eef6", + "pub_key": "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1", + "signature": "74f85b856337fbe837643b86f462118159f93ac4acc2671522f27e8f67b079959195ccc7a5dbee396d2909f5d680d6e30cda7359aa2755822509b70d6b0687a1" + } + ], + "tweak": "024ac253c216532e961988e2a8ce266a447c894c781e52ef6cee902361db960004", + "shared_secret": "028158aff7d61ea66b2fa7f555bc3c5937d1debbde16423d630f9aa7943e14d80d", + "input_pub_key_sum": "032562c1ab2d6bd45d7ca4d78f569999e5333dffd3ac5263924fd00d00dedc4bee" + } + } + ] + }, + { + "comment": "Simple send: two inputs from the same transaction", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 3, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 7, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + }, + "private_key": "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "79e71baa2ba3fc66396de3a04f168c7bf24d6870ec88ca877754790c1db357b6" + ] + ], + "shared_secrets": [ + "03aa707f7b5e94b448abd28aa217e3d7a7cc6bb07f1a8d07be4de91bf7b1417469" + ], + "input_private_key_sum": "7ed265a6dac7aba8508a32d6d6b84c7f1dbd0a0941dd01088d69e8d556345f86", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 3, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 7, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + } + } + ], + "outputs": [ + "79e71baa2ba3fc66396de3a04f168c7bf24d6870ec88ca877754790c1db357b6" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "4851455bfbe1ab4f80156570aa45063201aa5c9e1b1dcd29f0f8c33d10bf77ae", + "pub_key": "79e71baa2ba3fc66396de3a04f168c7bf24d6870ec88ca877754790c1db357b6", + "signature": "10332eea808b6a13f70059a8a73195808db782012907f5ba32b6eae66a2f66b4f65147e2b968a1678c5f73d57d5d195dbaf667b606ff80c8490eac1f3b710657" + } + ], + "tweak": "03aeea547819c08413974e2ab2b12212e007166bb2058f88b009e082b9b4914a58", + "shared_secret": "03aa707f7b5e94b448abd28aa217e3d7a7cc6bb07f1a8d07be4de91bf7b1417469", + "input_pub_key_sum": "032562c1ab2d6bd45d7ca4d78f569999e5333dffd3ac5263924fd00d00dedc4bee" + } + } + ] + }, + { + "comment": "Simple send: two inputs from the same transaction, order reversed", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 7, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 3, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + }, + "private_key": "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "f4c2da807f89cb1501f1a77322a895acfb93c28e08ed2724d2beb8e44539ba38" + ] + ], + "shared_secrets": [ + "03054f5c84b07182ba2a2e10a35e088778f95c04f059f4574b024c372eb8ce5468" + ], + "input_private_key_sum": "7ed265a6dac7aba8508a32d6d6b84c7f1dbd0a0941dd01088d69e8d556345f86", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 7, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 3, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + } + } + ], + "outputs": [ + "f4c2da807f89cb1501f1a77322a895acfb93c28e08ed2724d2beb8e44539ba38" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "ab0c9b87181bf527879f48db9f14a02233619b986f8e8f2d5d408ce68a709f51", + "pub_key": "f4c2da807f89cb1501f1a77322a895acfb93c28e08ed2724d2beb8e44539ba38", + "signature": "398a9790865791a9db41a8015afad3a47d60fec5086c50557806a49a1bc038808632b8fe679a7bb65fc6b455be994502eed849f1da3729cd948fc7be73d67295" + } + ], + "tweak": "024cad5180a093d3af0f49f586bdf37f890920178e68e80561ed53351d0fa499ad", + "shared_secret": "03054f5c84b07182ba2a2e10a35e088778f95c04f059f4574b024c372eb8ce5468", + "input_pub_key_sum": "032562c1ab2d6bd45d7ca4d78f569999e5333dffd3ac5263924fd00d00dedc4bee" + } + } + ] + }, + { + "comment": "Outpoint ordering byte-lexicographically vs. vout-integer", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 1, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 256, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + }, + "private_key": "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "a85ef8701394b517a4b35217c4bd37ac01ebeed4b008f8d0879f9e09ba95319c" + ] + ], + "shared_secrets": [ + "02cb25a6e7c9b7c6d550e0413da63834678465b5e80853a51d0335d318296ac182" + ], + "input_private_key_sum": "7ed265a6dac7aba8508a32d6d6b84c7f1dbd0a0941dd01088d69e8d556345f86", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 1, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 256, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + } + } + ], + "outputs": [ + "a85ef8701394b517a4b35217c4bd37ac01ebeed4b008f8d0879f9e09ba95319c" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "c8ac0292997b5bca98b3ebd99a57e253071137550f270452cd3df8a3e2266d36", + "pub_key": "a85ef8701394b517a4b35217c4bd37ac01ebeed4b008f8d0879f9e09ba95319c", + "signature": "c036ee38bfe46aba03234339ae7219b31b824b52ef9d5ce05810a0d6f62330dedc2b55652578aa5bdabf930fae941acd839d5a66f8fce7caa9710ccb446bddd1" + } + ], + "tweak": "031f9a80d0938cf980b51f7cc4fad713d49037f430646dff129c0570d75a40d8f0", + "shared_secret": "02cb25a6e7c9b7c6d550e0413da63834678465b5e80853a51d0335d318296ac182", + "input_pub_key_sum": "032562c1ab2d6bd45d7ca4d78f569999e5333dffd3ac5263924fd00d00dedc4bee" + } + } + ] + }, + { + "comment": "Single recipient: multiple UTXOs from the same public key", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "548ae55c8eec1e736e8d3e520f011f1f42a56d166116ad210b3937599f87f566" + ] + ], + "shared_secrets": [ + "02f6b40ff17f4010fe732ac4b0f2f211281aa09c9a5fb41f1c151ec2606fee9ec2" + ], + "input_private_key_sum": "d5b8f02cbfe3f1d5295af9fb8a9320e859e9cb07115856486ab1a4e4fb89a621", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + } + ], + "outputs": [ + "548ae55c8eec1e736e8d3e520f011f1f42a56d166116ad210b3937599f87f566" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "f032695e2636619efa523fffaa9ef93c8802299181fd0461913c1b8daf9784cd", + "pub_key": "548ae55c8eec1e736e8d3e520f011f1f42a56d166116ad210b3937599f87f566", + "signature": "f238386c5d5e5444f8d2c75aabbcb28c346f208c76f60823f5de3b67b79e0ec72ea5de2d7caec314e0971d3454f122dda342b3eede01b3857e83654e36b25f76" + } + ], + "tweak": "0319949463fc6a2368d999a2a6a2bcb2dbf64a2ac6e00b3ba5659780c860a6d9e0", + "shared_secret": "02f6b40ff17f4010fe732ac4b0f2f211281aa09c9a5fb41f1c151ec2606fee9ec2", + "input_pub_key_sum": "03e40664e222ba71e29b80efc907fa22a3c6c64f45e89dbb8511dc7a3712b0a186" + } + } + ] + }, + { + "comment": "Single recipient: taproot only inputs with even y-values", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140bd1e708f92dbeaf24a6b8dd22e59c6274355424d62baea976b449e220fd75b13578e262ab11b7aa58e037f0c6b0519b66803b7d9decaa1906dedebfb531c56c1", + "prevout": { + "scriptPubKey": { + "hex": "5120782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + } + }, + "private_key": "fc8716a97a48ba9a05a98ae47b5cd201a25a7fd5d8b73c203c5f7b6b6b3b6ad7" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "de88bea8e7ffc9ce1af30d1132f910323c505185aec8eae361670421e749a1fb" + ] + ], + "shared_secrets": [ + "02de9719785c6d09f71571dadf44bca59edba2af3e689c65cbc3bb5a4a387732ef" + ], + "input_private_key_sum": "e7638ebfda3ab3849a5707e240a6627671f7f6e609bf172691cf1e9780e51d47", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "02782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140bd1e708f92dbeaf24a6b8dd22e59c6274355424d62baea976b449e220fd75b13578e262ab11b7aa58e037f0c6b0519b66803b7d9decaa1906dedebfb531c56c1", + "prevout": { + "scriptPubKey": { + "hex": "5120782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + } + } + } + ], + "outputs": [ + "de88bea8e7ffc9ce1af30d1132f910323c505185aec8eae361670421e749a1fb" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "3fb9ce5ce1746ced103c8ed254e81f6690764637ddbc876ec1f9b3ddab776b03", + "pub_key": "de88bea8e7ffc9ce1af30d1132f910323c505185aec8eae361670421e749a1fb", + "signature": "c5acd25a8f021a4192f93bc34403fd8b76484613466336fb259c72d04c169824f2690ca34e96cee86b69f376c8377003268fda56feeb1b873e5783d7e19bcca5" + } + ], + "tweak": "02dc59cc8e8873b65c1dd5c416d4fbeb647372c329bd84a70c05b310e222e2c183", + "shared_secret": "02de9719785c6d09f71571dadf44bca59edba2af3e689c65cbc3bb5a4a387732ef", + "input_pub_key_sum": "038180a2125f9d6dd116e1a6139be4d72fd5057dab6aaabaa5654817c11baeb3ba" + } + } + ] + }, + { + "comment": "Single recipient: taproot only with mixed even/odd y-values", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "", + "txinwitness": "01400a4d0dca6293f40499394d7eefe14a1de11e0e3454f51de2e802592abf5ee549042a1b1a8fb2e149ee9dd3f086c1b69b2f182565ab6ecf599b1ec9ebadfda6c5", + "prevout": { + "scriptPubKey": { + "hex": "51208c8d23d4764feffcd5e72e380802540fa0f88e3d62ad5e0b47955f74d7b283c4" + } + }, + "private_key": "1d37787c2b7116ee983e9f9c13269df29091b391c04db94239e0d2bc2182c3bf" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "77cab7dd12b10259ee82c6ea4b509774e33e7078e7138f568092241bf26b99f1" + ] + ], + "shared_secrets": [ + "030e7f5ca4bf109fc35c8c2d878f756c891ac04c456cc5f0b05fcec4d3b2b1beb2" + ], + "input_private_key_sum": "cda4ff9a3480e1fbfc6edd61b222f280f9baa0652002c1ffdb612efcc45d2ff2", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "028c8d23d4764feffcd5e72e380802540fa0f88e3d62ad5e0b47955f74d7b283c4" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "", + "txinwitness": "01400a4d0dca6293f40499394d7eefe14a1de11e0e3454f51de2e802592abf5ee549042a1b1a8fb2e149ee9dd3f086c1b69b2f182565ab6ecf599b1ec9ebadfda6c5", + "prevout": { + "scriptPubKey": { + "hex": "51208c8d23d4764feffcd5e72e380802540fa0f88e3d62ad5e0b47955f74d7b283c4" + } + } + } + ], + "outputs": [ + "77cab7dd12b10259ee82c6ea4b509774e33e7078e7138f568092241bf26b99f1" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "f5382508609771068ed079b24e1f72e4a17ee6d1c979066bf1d4e2a5676f09d4", + "pub_key": "77cab7dd12b10259ee82c6ea4b509774e33e7078e7138f568092241bf26b99f1", + "signature": "ff65833b8fd1ed3ef9d0443b4f702b45a3f2dd457ba247687e8207745c3be9d2bdad0ab3f07118f8b2efc6a04b95f7b3e218daf8a64137ec91bd2fc67fc137a5" + } + ], + "tweak": "03b990f5b1d90ea8fd4bdd5c856a9dfe17035d196958062e2c6cb4c99e413f3548", + "shared_secret": "030e7f5ca4bf109fc35c8c2d878f756c891ac04c456cc5f0b05fcec4d3b2b1beb2", + "input_pub_key_sum": "020f0ab50f420ab1249bc2a21659c607f2873400853035aad0ca6d0ded04d62623" + } + } + ] + }, + { + "comment": "Single recipient: taproot input with even y-value and non-taproot input", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "463044021f24e010c6e475814740ba24c8cf9362c4db1276b7f46a7b1e63473159a80ec30221008198e8ece7b7f88e6c6cc6bb8c86f9f00b7458222a8c91addf6e1577bcf7697e2103e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9148cbc7dfe44f1579bff3340bbef1eddeaeb1fc97788ac" + } + }, + "private_key": "8d4751f6e8a3586880fb66c19ae277969bd5aa06f61c4ee2f1e2486efdf666d3" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "30523cca96b2a9ae3c98beb5e60f7d190ec5bc79b2d11a0b2d4d09a608c448f0" + ] + ], + "shared_secrets": [ + "021cd92ff153e638d0a97bcd11fafc81c321b111f5ba1efff593371b7b688efdd3" + ], + "input_private_key_sum": "7823ca0d4895515315a8e3bf602c080b6b732117272429e94751eb9b13a01943", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "463044021f24e010c6e475814740ba24c8cf9362c4db1276b7f46a7b1e63473159a80ec30221008198e8ece7b7f88e6c6cc6bb8c86f9f00b7458222a8c91addf6e1577bcf7697e2103e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9148cbc7dfe44f1579bff3340bbef1eddeaeb1fc97788ac" + } + } + } + ], + "outputs": [ + "30523cca96b2a9ae3c98beb5e60f7d190ec5bc79b2d11a0b2d4d09a608c448f0" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "b40017865c79b1fcbed68896791be93186d08f47e416b289b8c063777e14e8df", + "pub_key": "30523cca96b2a9ae3c98beb5e60f7d190ec5bc79b2d11a0b2d4d09a608c448f0", + "signature": "d1edeea28cf1033bcb3d89376cabaaaa2886cbd8fda112b5c61cc90a4e7f1878bdd62180b07d1dfc8ffee1863c525a0c7b5bcd413183282cfda756cb65787266" + } + ], + "tweak": "0233c2a447b8b244e4ffcfb59fe365eaa3bb22288b31e2113b9998861f40d4d6da", + "shared_secret": "021cd92ff153e638d0a97bcd11fafc81c321b111f5ba1efff593371b7b688efdd3", + "input_pub_key_sum": "031ecda9c64faaa6cd57c9f3d7c62bcfc0763c2627ed8dc0e2c3018e9ff37a0bf0" + } + } + ] + }, + { + "comment": "Single recipient: taproot input with odd y-value and non-taproot input", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "01400a4d0dca6293f40499394d7eefe14a1de11e0e3454f51de2e802592abf5ee549042a1b1a8fb2e149ee9dd3f086c1b69b2f182565ab6ecf599b1ec9ebadfda6c5", + "prevout": { + "scriptPubKey": { + "hex": "51208c8d23d4764feffcd5e72e380802540fa0f88e3d62ad5e0b47955f74d7b283c4" + } + }, + "private_key": "1d37787c2b7116ee983e9f9c13269df29091b391c04db94239e0d2bc2182c3bf" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "463044021f24e010c6e475814740ba24c8cf9362c4db1276b7f46a7b1e63473159a80ec30221008198e8ece7b7f88e6c6cc6bb8c86f9f00b7458222a8c91addf6e1577bcf7697e2103e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9148cbc7dfe44f1579bff3340bbef1eddeaeb1fc97788ac" + } + }, + "private_key": "8d4751f6e8a3586880fb66c19ae277969bd5aa06f61c4ee2f1e2486efdf666d3" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "359358f59ee9e9eec3f00bdf4882570fd5c182e451aa2650b788544aff012a3a" + ] + ], + "shared_secrets": [ + "03d9437eb3676cf5cc00feebe68bc44c4567332e4b89788dec9eceb3779054442b" + ], + "input_private_key_sum": "700fd97abd324179e8bcc72587bbd9a40b43f67535ce95a0b80175b2dc73a314", + "input_pub_keys": [ + "028c8d23d4764feffcd5e72e380802540fa0f88e3d62ad5e0b47955f74d7b283c4", + "03e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "01400a4d0dca6293f40499394d7eefe14a1de11e0e3454f51de2e802592abf5ee549042a1b1a8fb2e149ee9dd3f086c1b69b2f182565ab6ecf599b1ec9ebadfda6c5", + "prevout": { + "scriptPubKey": { + "hex": "51208c8d23d4764feffcd5e72e380802540fa0f88e3d62ad5e0b47955f74d7b283c4" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "463044021f24e010c6e475814740ba24c8cf9362c4db1276b7f46a7b1e63473159a80ec30221008198e8ece7b7f88e6c6cc6bb8c86f9f00b7458222a8c91addf6e1577bcf7697e2103e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9148cbc7dfe44f1579bff3340bbef1eddeaeb1fc97788ac" + } + } + } + ], + "outputs": [ + "359358f59ee9e9eec3f00bdf4882570fd5c182e451aa2650b788544aff012a3a" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "a2f9dd05d1d398347c885d9c61a64d18a264de6d49cea4326bafc2791d627fa7", + "pub_key": "359358f59ee9e9eec3f00bdf4882570fd5c182e451aa2650b788544aff012a3a", + "signature": "96038ad233d8befe342573a6e54828d863471fb2afbad575cc65271a2a649480ea14912b6abbd3fbf92efc1928c036f6e3eef927105af4ec1dd57cb909f360b8" + } + ], + "tweak": "02d4e4f2c4cdb71c9c39a700a9ee1a0fc05b98362a441183f5770af7d6e2b3038c", + "shared_secret": "03d9437eb3676cf5cc00feebe68bc44c4567332e4b89788dec9eceb3779054442b", + "input_pub_key_sum": "03bc118b1c8178915b716d6137633722c71adfe721551ec7b3938054691de6a2b9" + } + } + ] + }, + { + "comment": "Multiple outputs: multiple outputs, same recipient", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + }, + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ] + ], + "shared_secrets": [ + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "d97e442d110c0bdd31161a7bb6e7862e038d02a09b1484dfbb463f2e0f7c9230", + "pub_key": "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "signature": "29bd25d0f808d7fcd2aa6d5ed206053899198397506c301b218a9e47a3d7070af03e903ff718978d50d1b6b9af8cc0e313d84eda5d5b1e8e85e5516d630bbeb9" + }, + { + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "Multiple outputs: multiple outputs, multiple recipients", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + }, + { + "address": "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn", + "scan_pub_key": "02062d49ffc02787d586c608dfbec184aa91a6597d97b463ea5c6babd9d17a95a3", + "spend_pub_key": "0381eb9a9a9ec739d527c1631b31b421566f5c2a47b4ab5b1f6a686dfb68eab716" + }, + { + "address": "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn", + "scan_pub_key": "02062d49ffc02787d586c608dfbec184aa91a6597d97b463ea5c6babd9d17a95a3", + "spend_pub_key": "0381eb9a9a9ec739d527c1631b31b421566f5c2a47b4ab5b1f6a686dfb68eab716" + } + ] + }, + "expected": { + "outputs": [ + [ + "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a", + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ] + ], + "shared_secrets": [ + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "03dd5fd04d3c8863be750a1bd7474df06161461d38d3ce1397a5c78cee112cdcd2", + "03dd5fd04d3c8863be750a1bd7474df06161461d38d3ce1397a5c78cee112cdcd2" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a", + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + "key_material": { + "spend_priv_key": "9902c3c56e84002a7cd410113a9ab21d142be7f53cf5200720bb01314c5eb920", + "scan_priv_key": "060b751d7892149006ed7b98606955a29fe284a1e900070c0971f5fb93dbf422" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn" + ], + "outputs": [ + { + "priv_key_tweak": "72cd082cccb633bf85240a83494b32dc943a4d05647a6686d23ad4ca59c0ebe4", + "pub_key": "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a", + "signature": "38745f3d9f5eef0b1cfb17ca314efa8c521efab28a23aa20ec5e3abb561d42804d539906dce60c4ee7977966184e6f2cab1faa0e5377ceb7148ec5218b4e7878" + }, + { + "priv_key_tweak": "2f17ea873a0047fc01ba8010fef0969e76d0e4283f600d48f735098b1fee6eb9", + "pub_key": "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "signature": "c26f4e3cf371b90b840f48ea0e761b5ec31883ed55719f9ef06a90e282d85f565790ab780a3f491bc2668cc64e944dca849d1022a878cdadb8d168b8da4a6da3" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "03dd5fd04d3c8863be750a1bd7474df06161461d38d3ce1397a5c78cee112cdcd2", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "Receiving with labels: label with even parity", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjex54dmqmmv6rw353tsuqhs99ydvadxzrsy9nuvk74epvee55drs734pqq", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "0259352add837b6686e8d22b87017814a46b3ad308702167c65bd5c8599cd28d1c" + } + ] + }, + "expected": { + "outputs": [ + [ + "d014d4860f67d607d60b1af70e0ee236b99658b61bb769832acbbe87c374439a" + ] + ], + "shared_secrets": [ + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "d014d4860f67d607d60b1af70e0ee236b99658b61bb769832acbbe87c374439a" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 2, + 3, + 1001337 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjex54dmqmmv6rw353tsuqhs99ydvadxzrsy9nuvk74epvee55drs734pqq", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqsg59z2rppn4qlkx0yz9sdltmjv3j8zgcqadjn4ug98m3t6plujsq9qvu5n", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq7c2zfthc6x3a5yecwc52nxa0kfd20xuz08zyrjpfw4l2j257yq6qgnkdh5" + ], + "outputs": [ + { + "priv_key_tweak": "51d4e9d0d482b5700109b4b2e16ff508269b03d800192a043d61dca4a0a72a52", + "pub_key": "d014d4860f67d607d60b1af70e0ee236b99658b61bb769832acbbe87c374439a", + "signature": "c30fa63bad6f0a317f39a773a5cbf0b0f8193c71dfebba05ee6ae4ed28e3775e6e04c3ea70a83703bb888122855dc894cab61692e7fd10c9b3494d479a60785e" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "Receiving with labels: label with odd parity", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqsg59z2rppn4qlkx0yz9sdltmjv3j8zgcqadjn4ug98m3t6plujsq9qvu5n", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "0208a144a18433a83f633c822c1bf5ee4c8c8e24601d6ca75e20a7dc57a0ff9280" + } + ] + }, + "expected": { + "outputs": [ + [ + "67626aebb3c4307cf0f6c39ca23247598fabf675ab783292eb2f81ae75ad1f8c" + ] + ], + "shared_secrets": [ + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "67626aebb3c4307cf0f6c39ca23247598fabf675ab783292eb2f81ae75ad1f8c" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 2, + 3, + 1001337 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjex54dmqmmv6rw353tsuqhs99ydvadxzrsy9nuvk74epvee55drs734pqq", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqsg59z2rppn4qlkx0yz9sdltmjv3j8zgcqadjn4ug98m3t6plujsq9qvu5n", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq7c2zfthc6x3a5yecwc52nxa0kfd20xuz08zyrjpfw4l2j257yq6qgnkdh5" + ], + "outputs": [ + { + "priv_key_tweak": "6024ae214876356b8d917716e7707d267ae16a0fdb07de2a786b74a7bbcddead", + "pub_key": "67626aebb3c4307cf0f6c39ca23247598fabf675ab783292eb2f81ae75ad1f8c", + "signature": "a86d554d0d6b7aa0907155f7e0b47f0182752472fffaeddd68da90e99b9402f166fd9b33039c302c7115098d971c1399e67c19e9e4de180b10ea0b9d6f0db832" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "Receiving with labels: large label integer", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq7c2zfthc6x3a5yecwc52nxa0kfd20xuz08zyrjpfw4l2j257yq6qgnkdh5", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "03d85092bbe3468f684ce1d8a2a66ebec96a9e6e09e7110720a5d5faa4aa7880d0" + } + ] + }, + "expected": { + "outputs": [ + [ + "7efa60ce78ac343df8a013a2027c6c5ef29f9502edcbd769d2c21717fecc5951" + ] + ], + "shared_secrets": [ + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "7efa60ce78ac343df8a013a2027c6c5ef29f9502edcbd769d2c21717fecc5951" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 2, + 3, + 1001337 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjex54dmqmmv6rw353tsuqhs99ydvadxzrsy9nuvk74epvee55drs734pqq", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqsg59z2rppn4qlkx0yz9sdltmjv3j8zgcqadjn4ug98m3t6plujsq9qvu5n", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq7c2zfthc6x3a5yecwc52nxa0kfd20xuz08zyrjpfw4l2j257yq6qgnkdh5" + ], + "outputs": [ + { + "priv_key_tweak": "e336b92330c33030285ce42e4115ad92d5197913c88e06b9072b4a9b47c664a2", + "pub_key": "7efa60ce78ac343df8a013a2027c6c5ef29f9502edcbd769d2c21717fecc5951", + "signature": "c9e80dd3bdd25ca2d352ce77510f1aed37ba3509dc8cc0677f2d7c2dd04090707950ce9dd6c83d2a428063063aff5c04f1744e334f661f2fc01b4ef80b50f739" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "Multiple outputs with labels: un-labeled and labeled address; same recipient", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "03a6739499dc667d308baefea4de0c4a85cc72aece181bc05712d3919662610ff1" + }, + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + [ + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca" + ] + ], + "shared_secrets": [ + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 1 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj" + ], + "outputs": [ + { + "priv_key_tweak": "43100f89f1a6bf10081c92b473ffc57ceac7dbed600b6aba9bb3976f17dbb914", + "pub_key": "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "signature": "15c92509b67a6c211ebb4a51b7528d0666e6720de2343b2e92cfb97942ca14693c1f1fdc8451acfdb2644039f8f5c76114807fdc3d3a002d8a46afab6756bd75" + }, + { + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "Multiple outputs with labels: multiple outputs for labeled address; same recipient", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "03a6739499dc667d308baefea4de0c4a85cc72aece181bc05712d3919662610ff1" + }, + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "03a6739499dc667d308baefea4de0c4a85cc72aece181bc05712d3919662610ff1" + } + ] + }, + "expected": { + "outputs": [ + [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c" + ] + ], + "shared_secrets": [ + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 1 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj" + ], + "outputs": [ + { + "priv_key_tweak": "43100f89f1a6bf10081c92b473ffc57ceac7dbed600b6aba9bb3976f17dbb914", + "pub_key": "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "signature": "15c92509b67a6c211ebb4a51b7528d0666e6720de2343b2e92cfb97942ca14693c1f1fdc8451acfdb2644039f8f5c76114807fdc3d3a002d8a46afab6756bd75" + }, + { + "priv_key_tweak": "9d5fd3b91cac9ddfea6fc2e6f9386f680e6cee623cda02f53706306c081de87f", + "pub_key": "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "signature": "db0dfacc98b6a6fcc67cc4631f080b1ca38c60d8c397f2f19843f8f95ec91594b24e47c5bd39480a861c1209f7e3145c440371f9191fb96e324690101eac8e8e" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "Multiple outputs with labels: un-labeled, labeled, and multiple outputs for labeled address; same recipients", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + }, + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "03a6739499dc667d308baefea4de0c4a85cc72aece181bc05712d3919662610ff1" + }, + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjyh2ju7hd5gj57jg5r9lev3pckk4n2shtzaq34467erzzdfajfggty6aa5", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "0244baa5cf5db444a9e922832ff2c88716b566a85d62e8235aebd91884d4f64942" + }, + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjyh2ju7hd5gj57jg5r9lev3pckk4n2shtzaq34467erzzdfajfggty6aa5", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "0244baa5cf5db444a9e922832ff2c88716b566a85d62e8235aebd91884d4f64942" + } + ] + }, + "expected": { + "outputs": [ + [ + "006a02c308ccdbf3ac49f0638f6de128f875db5a213095cf112b3b77722472ae", + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa" + ], + [ + "006a02c308ccdbf3ac49f0638f6de128f875db5a213095cf112b3b77722472ae", + "3edf1ff6657c6e69568811bd726a7a7f480493aa42161acfe8dd4f44521f99ed", + "7ee1543ed5d123ffa66fbebc128c020173eb490d5fa2ba306e0c9573a77db8f3", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa" + ], + [ + "006a02c308ccdbf3ac49f0638f6de128f875db5a213095cf112b3b77722472ae", + "7ee1543ed5d123ffa66fbebc128c020173eb490d5fa2ba306e0c9573a77db8f3", + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701" + ], + [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "3c54444944d176437644378c23efb999ab6ab1cacdfe1dc1537b607e3df330e2", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ], + [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ], + [ + "3c54444944d176437644378c23efb999ab6ab1cacdfe1dc1537b607e3df330e2", + "602e10e6944107c9b48bd885b493676578c935723287e0ab2f8b7f136862568e", + "7ee1543ed5d123ffa66fbebc128c020173eb490d5fa2ba306e0c9573a77db8f3", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa" + ], + [ + "3c54444944d176437644378c23efb999ab6ab1cacdfe1dc1537b607e3df330e2", + "7ee1543ed5d123ffa66fbebc128c020173eb490d5fa2ba306e0c9573a77db8f3", + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ], + [ + "3edf1ff6657c6e69568811bd726a7a7f480493aa42161acfe8dd4f44521f99ed", + "7ee1543ed5d123ffa66fbebc128c020173eb490d5fa2ba306e0c9573a77db8f3", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ], + [ + "3edf1ff6657c6e69568811bd726a7a7f480493aa42161acfe8dd4f44521f99ed", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa", + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ], + [ + "602e10e6944107c9b48bd885b493676578c935723287e0ab2f8b7f136862568e", + "7ee1543ed5d123ffa66fbebc128c020173eb490d5fa2ba306e0c9573a77db8f3", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + [ + "602e10e6944107c9b48bd885b493676578c935723287e0ab2f8b7f136862568e", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa", + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca" + ], + [ + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ] + ], + "shared_secrets": [ + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "006a02c308ccdbf3ac49f0638f6de128f875db5a213095cf112b3b77722472ae", + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 1, + 1337 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjyh2ju7hd5gj57jg5r9lev3pckk4n2shtzaq34467erzzdfajfggty6aa5" + ], + "outputs": [ + { + "priv_key_tweak": "4e3352fbe0505c25e718d96007c259ef08db34f8c844e4ff742d9855ff03805a", + "pub_key": "006a02c308ccdbf3ac49f0638f6de128f875db5a213095cf112b3b77722472ae", + "signature": "6eeae1ea9eb826e3d0e812f65937100e0836ea188c04f36fabc4981eda29de8d3d3529390a0a8b3d830f7bca4f5eae5994b9788ddaf05ad259ffe26d86144b4b" + }, + { + "priv_key_tweak": "43100f89f1a6bf10081c92b473ffc57ceac7dbed600b6aba9bb3976f17dbb914", + "pub_key": "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "signature": "15c92509b67a6c211ebb4a51b7528d0666e6720de2343b2e92cfb97942ca14693c1f1fdc8451acfdb2644039f8f5c76114807fdc3d3a002d8a46afab6756bd75" + }, + { + "priv_key_tweak": "bf709f98d4418f8a67e738154ae48818dad44689cd37fbc070891a396dd1c633", + "pub_key": "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "signature": "42a19fd8a63dde1824966a95d65a28203e631e49bf96ca5dae1b390e7a0ace2cc8709c9b0c5715047032f57f536a3c80273cbecf4c05be0b5456c183fa122c06" + }, + { + "priv_key_tweak": "736f05e4e3072c3b8656bedef2e9bf54cbcaa2b6fe5320d3e86f5b96874dda71", + "pub_key": "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa", + "signature": "2e61bb3d79418ecf55f68847cf121bfc12d397b39d1da8643246b2f0a9b96c3daa4bfe9651beb5c9ce20e1f29282c4566400a4b45ee6657ec3b18fdc554da0b4" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "Single recipient: use silent payments for sender change", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + }, + { + "address": "sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqlv6saelkk5snl4wfutyxrchpzzwm8rjp3z6q7apna59z9huq4x754e5atr", + "scan_pub_key": "03b4cc0b090b6f49a684558852db60ee5eb1c5f74352839c3d18a8fc04ef7354e0", + "spend_pub_key": "03ecd43b9fdad484ff57278b21878b844276ce390622d03dd0cfb4288b7e02a6f5" + } + ] + }, + "expected": { + "outputs": [ + [ + "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ] + ], + "shared_secrets": [ + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "037d12c02c3aed482658a28b8d1be030dac1daf995551491d74c00543af98572fb" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + "key_material": { + "spend_priv_key": "b8f87388cbb41934c50daca018901b00070a5ff6cc25a7e9e716a9d5b9e4d664", + "scan_priv_key": "11b7a82e06ca2648d5fded2366478078ec4fc9dc1d8ff487518226f229d768fd" + }, + "labels": [ + 0 + ] + }, + "expected": { + "addresses": [ + "sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqauj52ymtc4xdkmx3tgyhrsemg2g3303xk2gtzfy8h8ejet8fz8jcw23zua", + "sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqlv6saelkk5snl4wfutyxrchpzzwm8rjp3z6q7apna59z9huq4x754e5atr" + ], + "outputs": [ + { + "priv_key_tweak": "80cd767ed20bd0bb7d8ea5e803f8c381293a62e8a073cf46fb0081da46e64e1f", + "pub_key": "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff", + "signature": "7fbd5074cf1377273155eefafc7c330cb61b31da252f22206ac27530d2b2567040d9af7808342ed4a09598c26d8307446e4ed77079e6a2e61fea736e44da5f5a" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "037d12c02c3aed482658a28b8d1be030dac1daf995551491d74c00543af98572fb", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + }, + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "Single recipient: taproot input with NUMS point", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0440c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b22205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5ac21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac00150", + "prevout": { + "scriptPubKey": { + "hex": "5120da6f0595ecb302bbe73e2f221f05ab10f336b06817d36fd28fc6691725ddaa85" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140bd1e708f92dbeaf24a6b8dd22e59c6274355424d62baea976b449e220fd75b13578e262ab11b7aa58e037f0c6b0519b66803b7d9decaa1906dedebfb531c56c1", + "prevout": { + "scriptPubKey": { + "hex": "5120782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + } + }, + "private_key": "fc8716a97a48ba9a05a98ae47b5cd201a25a7fd5d8b73c203c5f7b6b6b3b6ad7" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 1, + "scriptSig": "", + "txinwitness": "0340268d31a9276f6380107d5321cafa6d9e8e5ea39204318fdc8206b31507c891c3bbcea3c99e2208d73bd127a8e8c5f1e45a54f1bd217205414ddb566ab7eda0092220e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85dac21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0", + "prevout": { + "scriptPubKey": { + "hex": "51200a3c9365ceb131f89b0a4feb6896ebd67bb15a98c31eaa3da143bb955a0f3fcb" + } + }, + "private_key": "8d4751f6e8a3586880fb66c19ae277969bd5aa06f61c4ee2f1e2486efdf666d3" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "79e79897c52935bfd97fc6e076a6431a0c7543ca8c31e0fc3cf719bb572c842d" + ] + ], + "shared_secrets": [ + "036f040608cd1e5ee79c54e78bea85904c895591f547beae080d0c5f6946c2730d" + ], + "input_private_key_sum": "fc8716a97a48ba9a05a98ae47b5cd201a25a7fd5d8b73c203c5f7b6b6b3b6ad7", + "input_pub_keys": [ + "02782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0440c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b22205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5ac21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac00150", + "prevout": { + "scriptPubKey": { + "hex": "5120da6f0595ecb302bbe73e2f221f05ab10f336b06817d36fd28fc6691725ddaa85" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140bd1e708f92dbeaf24a6b8dd22e59c6274355424d62baea976b449e220fd75b13578e262ab11b7aa58e037f0c6b0519b66803b7d9decaa1906dedebfb531c56c1", + "prevout": { + "scriptPubKey": { + "hex": "5120782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 1, + "scriptSig": "", + "txinwitness": "0340268d31a9276f6380107d5321cafa6d9e8e5ea39204318fdc8206b31507c891c3bbcea3c99e2208d73bd127a8e8c5f1e45a54f1bd217205414ddb566ab7eda0092220e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85dac21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0", + "prevout": { + "scriptPubKey": { + "hex": "51200a3c9365ceb131f89b0a4feb6896ebd67bb15a98c31eaa3da143bb955a0f3fcb" + } + } + } + ], + "outputs": [ + "79e79897c52935bfd97fc6e076a6431a0c7543ca8c31e0fc3cf719bb572c842d" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "3ddec3232609d348d6b8b53123b4f40f6d4f5398ca586f087b0416ec3b851496", + "pub_key": "79e79897c52935bfd97fc6e076a6431a0c7543ca8c31e0fc3cf719bb572c842d", + "signature": "d7d06e3afb68363031e4eb18035c46ceae41bdbebe7888a4754bc9848c596436869aeaecff0527649a1f458b71c9ceecec10b535c09d01d720229aa228547706" + } + ], + "tweak": "02213b872c9a6ee28a0d861384a1b3e3ec7257f4855ed09b4323e3899f3b028989", + "shared_secret": "036f040608cd1e5ee79c54e78bea85904c895591f547beae080d0c5f6946c2730d", + "input_pub_key_sum": "02782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + } + } + ] + }, + { + "comment": "Pubkey extraction from malleated p2pkh", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 1, + "scriptSig": "0075473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 2, + "scriptSig": "5163473045022100e7d26e77290b37128f5215ade25b9b908ce87cc9a4d498908b5bb8fd6daa1b8d022002568c3a8226f4f0436510283052bfb780b76f3fe4aa60c4c5eb118e43b187372102e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d67483046022100c0d3c851d3bd562ae93d56bcefd735ea57c027af46145a4d5e9cac113bfeb0c2022100ee5b2239af199fa9b7aa1d98da83a29d0a2cf1e4f29e2f37134ce386d51c544c2102ad0f26ddc7b3fcc340155963b3051b85289c1869612ecb290184ac952e2864ec68", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914c82c5ec473cbc6c86e5ef410e36f9495adcf979988ac" + } + }, + "private_key": "72b8ae09175ca7977f04993e651d88681ed932dfb92c5158cdf0161dd23fda6e" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "4612cdbf845c66c7511d70aab4d9aed11e49e48cdb8d799d787101cdd0d53e4f" + ] + ], + "shared_secrets": [ + "034773b97ccad9791cb4213964ff9896ccd6581ee69345de5d114786d9d86b03a2" + ], + "input_private_key_sum": "610e0f75fd05e5e80e088b57af0a46da06cb0700c0c5907aa6d29c6b4ce46348", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "02e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 1, + "scriptSig": "0075473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 2, + "scriptSig": "5163473045022100e7d26e77290b37128f5215ade25b9b908ce87cc9a4d498908b5bb8fd6daa1b8d022002568c3a8226f4f0436510283052bfb780b76f3fe4aa60c4c5eb118e43b187372102e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d67483046022100c0d3c851d3bd562ae93d56bcefd735ea57c027af46145a4d5e9cac113bfeb0c2022100ee5b2239af199fa9b7aa1d98da83a29d0a2cf1e4f29e2f37134ce386d51c544c2102ad0f26ddc7b3fcc340155963b3051b85289c1869612ecb290184ac952e2864ec68", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914c82c5ec473cbc6c86e5ef410e36f9495adcf979988ac" + } + } + } + ], + "outputs": [ + "4612cdbf845c66c7511d70aab4d9aed11e49e48cdb8d799d787101cdd0d53e4f" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "10bde9781def20d7701e7603ef1b1e5e71c67bae7154818814e3c81ef5b1a3d3", + "pub_key": "4612cdbf845c66c7511d70aab4d9aed11e49e48cdb8d799d787101cdd0d53e4f", + "signature": "6137969f810e9e8ef6c9755010e808f5dd1aed705882e44d7f0ae64eb0c509ec8b62a0671bee0d5914ac27d2c463443e28e999d82dc3d3a4919f093872d947bb" + } + ], + "tweak": "028d6617f9bfe08604beb2188f4eebec923f5f8cc436fa6d14e4256e49bc32e7c8", + "shared_secret": "034773b97ccad9791cb4213964ff9896ccd6581ee69345de5d114786d9d86b03a2", + "input_pub_key_sum": "038b0d201fe111bdc0e6953772bd02a41959d25d5b2f66bcbe348af27bbdd42735" + } + } + ] + }, + { + "comment": "P2PKH and P2WPKH Uncompressed Keys are skipped", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b974104782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233387c5343bf58e23269e903335b958a12182f9849297321e8d710e49a8727129cab", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9144b92ac4ac6fe6212393894addda332f2e47a315688ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 1, + "scriptSig": "", + "txinwitness": "02473045022100e7d26e77290b37128f5215ade25b9b908ce87cc9a4d498908b5bb8fd6daa1b8d022002568c3a8226f4f0436510283052bfb780b76f3fe4aa60c4c5eb118e43b187374104e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d6fe8190e189be57d0d5bcd17dbcbcd04c9b4a1c5f605b10d5c90abfcc0d12884", + "prevout": { + "scriptPubKey": { + "hex": "00140423f731a07491364e8dce98b7c00bda63336950" + } + }, + "private_key": "72b8ae09175ca7977f04993e651d88681ed932dfb92c5158cdf0161dd23fda6e" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6" + ] + ], + "shared_secrets": [ + "0295a54c359da5b2640601ddbedb26e040cb97b6a3432e60b76d1258e85f72fa64" + ], + "input_private_key_sum": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b974104782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233387c5343bf58e23269e903335b958a12182f9849297321e8d710e49a8727129cab", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9144b92ac4ac6fe6212393894addda332f2e47a315688ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 1, + "scriptSig": "", + "txinwitness": "02473045022100e7d26e77290b37128f5215ade25b9b908ce87cc9a4d498908b5bb8fd6daa1b8d022002568c3a8226f4f0436510283052bfb780b76f3fe4aa60c4c5eb118e43b187374104e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d6fe8190e189be57d0d5bcd17dbcbcd04c9b4a1c5f605b10d5c90abfcc0d12884", + "prevout": { + "scriptPubKey": { + "hex": "00140423f731a07491364e8dce98b7c00bda63336950" + } + } + } + ], + "outputs": [ + "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "688fa3aeb97d2a46ae87b03591921c2eaf4b505eb0ddca2733c94701e01060cf", + "pub_key": "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6", + "signature": "72e7ad573ac23255d4651d5b0326a200496588acb7a4894b22092236d5eda6a0a9a4d8429b022c2219081fefce5b33795cae488d10f5ea9438849ed8353624f2" + } + ], + "tweak": "02b04034f00da0678507d1345b7d56fecef825a1151f9dc7d8ca6946452a9e1f43", + "shared_secret": "0295a54c359da5b2640601ddbedb26e040cb97b6a3432e60b76d1258e85f72fa64", + "input_pub_key_sum": "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + } + ] + }, + { + "comment": "Skip invalid P2SH inputs", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "16001419c2f3ae0ca3b642bd3e49598b8da89f50c14161", + "txinwitness": "02483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "prevout": { + "scriptPubKey": { + "hex": "a9148629db5007d5fcfbdbb466637af09daf9125969387" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 1, + "scriptSig": "1600144b92ac4ac6fe6212393894addda332f2e47a3156", + "txinwitness": "02473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b974104782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233387c5343bf58e23269e903335b958a12182f9849297321e8d710e49a8727129cab", + "prevout": { + "scriptPubKey": { + "hex": "a9146c9bf136fbb7305fd99d771a95127fcf87dedd0d87" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 2, + "scriptSig": "00493046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d601483045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b97014c695221025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be52103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233382102e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d53ae", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "a9141044ddc6cea09e4ac40fbec2ba34ad62de6db25b87" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6" + ] + ], + "shared_secrets": [ + "0295a54c359da5b2640601ddbedb26e040cb97b6a3432e60b76d1258e85f72fa64" + ], + "input_private_key_sum": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "16001419c2f3ae0ca3b642bd3e49598b8da89f50c14161", + "txinwitness": "02483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "prevout": { + "scriptPubKey": { + "hex": "a9148629db5007d5fcfbdbb466637af09daf9125969387" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 1, + "scriptSig": "1600144b92ac4ac6fe6212393894addda332f2e47a3156", + "txinwitness": "02473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b974104782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233387c5343bf58e23269e903335b958a12182f9849297321e8d710e49a8727129cab", + "prevout": { + "scriptPubKey": { + "hex": "a9146c9bf136fbb7305fd99d771a95127fcf87dedd0d87" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 2, + "scriptSig": "00493046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d601483045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b97014c695221025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be52103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233382102e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d53ae", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "a9141044ddc6cea09e4ac40fbec2ba34ad62de6db25b87" + } + } + } + ], + "outputs": [ + "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "688fa3aeb97d2a46ae87b03591921c2eaf4b505eb0ddca2733c94701e01060cf", + "pub_key": "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6", + "signature": "72e7ad573ac23255d4651d5b0326a200496588acb7a4894b22092236d5eda6a0a9a4d8429b022c2219081fefce5b33795cae488d10f5ea9438849ed8353624f2" + } + ], + "tweak": "02b04034f00da0678507d1345b7d56fecef825a1151f9dc7d8ca6946452a9e1f43", + "shared_secret": "0295a54c359da5b2640601ddbedb26e040cb97b6a3432e60b76d1258e85f72fa64", + "input_pub_key_sum": "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + } + ] + }, + { + "comment": "Recipient ignores unrelated outputs", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn", + "scan_pub_key": "02062d49ffc02787d586c608dfbec184aa91a6597d97b463ea5c6babd9d17a95a3", + "spend_pub_key": "0381eb9a9a9ec739d527c1631b31b421566f5c2a47b4ab5b1f6a686dfb68eab716" + } + ] + }, + "expected": { + "outputs": [ + [ + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8" + ] + ], + "shared_secrets": [ + "03dd5fd04d3c8863be750a1bd7474df06161461d38d3ce1397a5c78cee112cdcd2" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "No valid inputs, sender generates no outputs", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d641045a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5c61836c9b1688ba431f7ea3039742251f62f0dca3da1bee58a47fa9b456c2d52", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914460e8b41545d2dbe7e0671f0f573e2232814260a88ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b974104782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233387c5343bf58e23269e903335b958a12182f9849297321e8d710e49a8727129cab", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9144b92ac4ac6fe6212393894addda332f2e47a315688ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [] + ], + "shared_secrets": [ + null + ], + "input_pub_keys": [] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d641045a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5c61836c9b1688ba431f7ea3039742251f62f0dca3da1bee58a47fa9b456c2d52", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914460e8b41545d2dbe7e0671f0f573e2232814260a88ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b974104782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233387c5343bf58e23269e903335b958a12182f9849297321e8d710e49a8727129cab", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9144b92ac4ac6fe6212393894addda332f2e47a315688ac" + } + } + } + ], + "outputs": [ + "782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [], + "tweak": null, + "shared_secret": null + } + } + ] + }, + { + "comment": "Input keys sum up to zero / point at infinity: sending fails, receiver skips tx", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e", + "vout": 0, + "scriptSig": "", + "txinwitness": "024730440220085003179ce1a3a88ce0069aa6ea045e140761ab88c22a26ae2a8cfe983a6e4602204a8a39940f0735c8a4424270ac8da65240c261ab3fda9272f6d6efbf9cfea366012102557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "prevout": { + "scriptPubKey": { + "hex": "00149d9e24f9fab4e35bf1a6df4b46cb533296ac0792" + } + }, + "private_key": "a6df6a0bb448992a301df4258e06a89fe7cf7146f59ac3bd5ff26083acb22ceb" + }, + { + "txid": "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e", + "vout": 1, + "scriptSig": "", + "txinwitness": "0247304402204586a68e1d97dd3c6928e3622799859f8c3b20c3c670cf654cc905c9be29fdb7022043fbcde1689f3f4045e8816caf6163624bd19e62e4565bc99f95c533e599782c012103557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "prevout": { + "scriptPubKey": { + "hex": "00149860538b5575962776ed0814ae222c7d60c72d7b" + } + }, + "private_key": "592095f44bb766d5cfe20bda71f9575ed2df6b9fb9addc7e5fdffe0923841456" + } + ], + "recipients": [ + { + "address": "sp1qqtrqglu5g8kh6mfsg4qxa9wq0nv9cauwfwxw70984wkqnw2uwz0w2qnehen8a7wuhwk9tgrzjh8gwzc8q2dlekedec5djk0js9d3d7qhnq6lqj3s", + "scan_pub_key": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", + "spend_pub_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + } + ] + }, + "expected": { + "outputs": [ + [] + ], + "shared_secrets": [ + null + ], + "input_pub_keys": [ + "02557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "03557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e", + "vout": 0, + "scriptSig": "", + "txinwitness": "024730440220085003179ce1a3a88ce0069aa6ea045e140761ab88c22a26ae2a8cfe983a6e4602204a8a39940f0735c8a4424270ac8da65240c261ab3fda9272f6d6efbf9cfea366012102557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "prevout": { + "scriptPubKey": { + "hex": "00149d9e24f9fab4e35bf1a6df4b46cb533296ac0792" + } + } + }, + { + "txid": "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e", + "vout": 1, + "scriptSig": "", + "txinwitness": "0247304402204586a68e1d97dd3c6928e3622799859f8c3b20c3c670cf654cc905c9be29fdb7022043fbcde1689f3f4045e8816caf6163624bd19e62e4565bc99f95c533e599782c012103557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "prevout": { + "scriptPubKey": { + "hex": "00149860538b5575962776ed0814ae222c7d60c72d7b" + } + } + } + ], + "outputs": [ + "0000000000000000000000000000000000000000000000000000000000000000" + ], + "key_material": { + "spend_priv_key": "0000000000000000000000000000000000000000000000000000000000000001", + "scan_priv_key": "0000000000000000000000000000000000000000000000000000000000000002" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqtrqglu5g8kh6mfsg4qxa9wq0nv9cauwfwxw70984wkqnw2uwz0w2qnehen8a7wuhwk9tgrzjh8gwzc8q2dlekedec5djk0js9d3d7qhnq6lqj3s" + ], + "outputs": [], + "tweak": null, + "shared_secret": null + } + } + ] + } +] diff --git a/src/modules/silentpayments/tests_impl.h b/src/modules/silentpayments/tests_impl.h index ad66be9aea..ec538bbf50 100644 --- a/src/modules/silentpayments/tests_impl.h +++ b/src/modules/silentpayments/tests_impl.h @@ -7,6 +7,7 @@ #define SECP256K1_MODULE_SILENTPAYMENTS_TESTS_H #include "../../../include/secp256k1_silentpayments.h" +#include "../../../src/modules/silentpayments/vectors.h" /** Constants * @@ -82,6 +83,20 @@ static unsigned char ALICE_SECKEY[32] = { 0x8a, 0x4c, 0x53, 0xf6, 0xe0, 0x50, 0x7b, 0x42, 0x15, 0x42, 0x01, 0xb8, 0xe5, 0xdf, 0xf3, 0xb1 }; +/* sha256("message") */ +static unsigned char MSG32[32] = { + 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 +}; +/* sha256("random auxiliary data") */ +static unsigned char AUX32[32] = { + 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 +}; struct label_cache_entry { unsigned char label[33]; @@ -500,11 +515,200 @@ static void test_recipient_api(void) { CHECK(secp256k1_silentpayments_recipient_create_output_pubkeys(CTX, output_ptrs, ALICE_SECKEY, &pd, spend_pubkey_ptrs, 1)); } +void run_silentpayments_test_vector_send(const struct bip352_test_vector *test) { + secp256k1_silentpayments_recipient recipients[MAX_OUTPUTS_PER_TEST_CASE]; + const secp256k1_silentpayments_recipient *recipient_ptrs[MAX_OUTPUTS_PER_TEST_CASE]; + secp256k1_xonly_pubkey generated_outputs[MAX_OUTPUTS_PER_TEST_CASE]; + secp256k1_xonly_pubkey *generated_output_ptrs[MAX_OUTPUTS_PER_TEST_CASE]; + secp256k1_keypair taproot_keypairs[MAX_INPUTS_PER_TEST_CASE]; + secp256k1_keypair const *taproot_keypair_ptrs[MAX_INPUTS_PER_TEST_CASE]; + unsigned char const *plain_seckeys[MAX_INPUTS_PER_TEST_CASE]; + unsigned char created_output[32]; + size_t i, j, k; + int match, ret; + + /* Check that sender creates expected outputs */ + for (i = 0; i < test->num_outputs; i++) { + CHECK(secp256k1_ec_pubkey_parse(CTX, &recipients[i].scan_pubkey, test->recipient_pubkeys[i].scan_pubkey, 33)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &recipients[i].spend_pubkey, test->recipient_pubkeys[i].spend_pubkey, 33)); + recipients[i].index = i; + recipient_ptrs[i] = &recipients[i]; + generated_output_ptrs[i] = &generated_outputs[i]; + } + for (i = 0; i < test->num_plain_inputs; i++) { + plain_seckeys[i] = test->plain_seckeys[i]; + } + for (i = 0; i < test->num_taproot_inputs; i++) { + CHECK(secp256k1_keypair_create(CTX, &taproot_keypairs[i], test->taproot_seckeys[i])); + taproot_keypair_ptrs[i] = &taproot_keypairs[i]; + } + { + int32_t ecount = 0; + secp256k1_context_set_illegal_callback(CTX, counting_callback_fn, &ecount); + ret = secp256k1_silentpayments_sender_create_outputs(CTX, + generated_output_ptrs, + recipient_ptrs, + test->num_outputs, + test->outpoint_smallest, + test->num_taproot_inputs > 0 ? taproot_keypair_ptrs : NULL, test->num_taproot_inputs, + test->num_plain_inputs > 0 ? plain_seckeys : NULL, test->num_plain_inputs + ); + secp256k1_context_set_illegal_callback(CTX, NULL, NULL); + /* We expect exactly one ARG_CHECK if the number of input keys was 0. */ + CHECK(ecount == (test->num_taproot_inputs + test->num_plain_inputs == 0)); + } + /* If we are unable to create outputs, e.g., the input keys sum to zero, check that the + * expected number of recipient outputs for this test case is zero + */ + if (!ret) { + CHECK(test->num_recipient_outputs == 0); + return; + } + + match = 0; + for (i = 0; i < test->num_output_sets; i++) { + size_t n_matches = 0; + for (j = 0; j < test->num_outputs; j++) { + CHECK(secp256k1_xonly_pubkey_serialize(CTX, created_output, &generated_outputs[j])); + /* Loop over both lists to ensure tests don't fail due to different orderings of outputs */ + for (k = 0; k < test->num_recipient_outputs; k++) { + if (secp256k1_memcmp_var(created_output, test->recipient_outputs[i][k], 32) == 0) { + n_matches++; + break; + } + } + } + if (n_matches == test->num_recipient_outputs) { + match = 1; + break; + } + } + CHECK(match); +} + +void run_silentpayments_test_vector_receive(const struct bip352_test_vector *test) { + secp256k1_pubkey plain_pubkeys_objs[MAX_INPUTS_PER_TEST_CASE]; + secp256k1_xonly_pubkey xonly_pubkeys_objs[MAX_INPUTS_PER_TEST_CASE]; + secp256k1_xonly_pubkey tx_output_objs[MAX_OUTPUTS_PER_TEST_CASE]; + secp256k1_silentpayments_found_output found_output_objs[MAX_OUTPUTS_PER_TEST_CASE]; + secp256k1_pubkey const *plain_pubkeys[MAX_INPUTS_PER_TEST_CASE]; + secp256k1_xonly_pubkey const *xonly_pubkeys[MAX_INPUTS_PER_TEST_CASE]; + secp256k1_xonly_pubkey const *tx_outputs[MAX_OUTPUTS_PER_TEST_CASE]; + secp256k1_silentpayments_found_output *found_outputs[MAX_OUTPUTS_PER_TEST_CASE]; + secp256k1_pubkey recipient_scan_pubkey; + secp256k1_pubkey recipient_spend_pubkey; + secp256k1_pubkey label; + size_t len = 33; + size_t i,j; + int match, ret; + size_t n_found = 0; + unsigned char found_output[32]; + unsigned char found_signatures[10][64]; + secp256k1_silentpayments_prevouts_summary prevouts_summary; + + + /* prepare the inputs */ + for (i = 0; i < test->num_plain_inputs; i++) { + CHECK(secp256k1_ec_pubkey_parse(CTX, &plain_pubkeys_objs[i], test->plain_pubkeys[i], 33)); + plain_pubkeys[i] = &plain_pubkeys_objs[i]; + } + for (i = 0; i < test->num_taproot_inputs; i++) { + CHECK(secp256k1_xonly_pubkey_parse(CTX, &xonly_pubkeys_objs[i], test->xonly_pubkeys[i])); + xonly_pubkeys[i] = &xonly_pubkeys_objs[i]; + } + { + int32_t ecount = 0; + secp256k1_context_set_illegal_callback(CTX, counting_callback_fn, &ecount); + ret = secp256k1_silentpayments_recipient_prevouts_summary_create(CTX, &prevouts_summary, + test->outpoint_smallest, + test->num_taproot_inputs > 0 ? xonly_pubkeys : NULL, test->num_taproot_inputs, + test->num_plain_inputs > 0 ? plain_pubkeys : NULL, test->num_plain_inputs + ); + secp256k1_context_set_illegal_callback(CTX, NULL, NULL); + /* We expect exactly one ARG_CHECK if the number of input keys was 0. */ + CHECK(ecount == (test->num_taproot_inputs + test->num_plain_inputs == 0)); + } + /* If we are unable to create the prevouts_summary object, e.g., the input public keys sum to + * zero, check that the expected number of recipient outputs for this test case is zero + */ + if (!ret) { + CHECK(test->num_found_output_pubkeys == 0); + return; + } + /* prepare the outputs */ + for (i = 0; i < test->num_to_scan_outputs; i++) { + CHECK(secp256k1_xonly_pubkey_parse(CTX, &tx_output_objs[i], test->to_scan_outputs[i])); + tx_outputs[i] = &tx_output_objs[i]; + } + for (i = 0; i < test->num_found_output_pubkeys; i++) { + found_outputs[i] = &found_output_objs[i]; + } + + /* scan / spend pubkeys are not in the given data of the recipient part, so let's compute them */ + CHECK(secp256k1_ec_pubkey_create(CTX, &recipient_scan_pubkey, test->scan_seckey)); + CHECK(secp256k1_ec_pubkey_create(CTX, &recipient_spend_pubkey, test->spend_seckey)); + + /* 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_recipient_create_label(CTX, &label, cache_entry->label_tweak, test->scan_seckey, m)); + CHECK(secp256k1_ec_pubkey_serialize(CTX, cache_entry->label, &len, &label, SECP256K1_EC_COMPRESSED)); + labels_cache.entries_used++; + } + CHECK(secp256k1_silentpayments_recipient_scan_outputs(CTX, + found_outputs, &n_found, + tx_outputs, test->num_to_scan_outputs, + test->scan_seckey, + &prevouts_summary, + &recipient_spend_pubkey, + label_lookup, &labels_cache) + ); + for (i = 0; i < n_found; i++) { + unsigned char full_seckey[32]; + secp256k1_keypair keypair; + unsigned char signature[64]; + memcpy(&full_seckey, test->spend_seckey, 32); + CHECK(secp256k1_ec_seckey_tweak_add(CTX, full_seckey, found_outputs[i]->tweak)); + CHECK(secp256k1_keypair_create(CTX, &keypair, full_seckey)); + CHECK(secp256k1_schnorrsig_sign32(CTX, signature, MSG32, &keypair, AUX32)); + memcpy(found_signatures[i], signature, 64); + } + + /* compare expected and scanned outputs (including calculated seckey tweaks and signatures) */ + match = 0; + for (i = 0; i < n_found; i++) { + CHECK(secp256k1_xonly_pubkey_serialize(CTX, found_output, &found_outputs[i]->output)); + for (j = 0; j < test->num_found_output_pubkeys; j++) { + if (secp256k1_memcmp_var(&found_output, test->found_output_pubkeys[j], 32) == 0) { + CHECK(secp256k1_memcmp_var(found_outputs[i]->tweak, test->found_seckey_tweaks[j], 32) == 0); + CHECK(secp256k1_memcmp_var(found_signatures[i], test->found_signatures[j], 64) == 0); + match = 1; + break; + } + } + CHECK(match); + } + CHECK(n_found == test->num_found_output_pubkeys); +} + +void run_silentpayments_test_vectors(void) { + size_t i; + + for (i = 0; i < sizeof(bip352_test_vectors) / sizeof(bip352_test_vectors[0]); i++) { + const struct bip352_test_vector *test = &bip352_test_vectors[i]; + run_silentpayments_test_vector_send(test); + run_silentpayments_test_vector_receive(test); + } +} + void run_silentpayments_tests(void) { test_recipient_sort(); test_send_api(); test_label_api(); test_recipient_api(); + run_silentpayments_test_vectors(); } #endif diff --git a/src/modules/silentpayments/vectors.h b/src/modules/silentpayments/vectors.h new file mode 100644 index 0000000000..15b7047065 --- /dev/null +++ b/src/modules/silentpayments/vectors.h @@ -0,0 +1,1918 @@ +/* Note: this file was autogenerated using tests_silentpayments_generate.py. Do not edit. */ + +#include + +#define MAX_INPUTS_PER_TEST_CASE 3 +#define MAX_OUTPUTS_PER_TEST_CASE 4 +#define MAX_PERMUTATIONS_PER_SENDING_TEST_CASE 12 + +struct bip352_recipient_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_outputs; + struct bip352_recipient_addressdata recipient_pubkeys[MAX_OUTPUTS_PER_TEST_CASE]; + + /* Expected sender data */ + size_t num_output_sets; + size_t num_recipient_outputs; + unsigned char recipient_outputs[MAX_PERMUTATIONS_PER_SENDING_TEST_CASE][MAX_OUTPUTS_PER_TEST_CASE][32]; + + /* Given recipient 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 recipient data */ + size_t num_found_output_pubkeys; + 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]; +}; + +#define SECP256K1_SILENTPAYMENTS_NUMBER_TESTVECTORS 26 + +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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 1, + { /* 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}, + }, + }, + /* recipient 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}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* 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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 1, + { /* 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}, + }, + }, + /* recipient 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}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* 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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 1, + { /* 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}, + }, + }, + /* recipient 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}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* 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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 1, + { /* 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}, + }, + }, + /* recipient 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}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* 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}, + }, + }, + + /* ----- Outpoint ordering byte-lexicographically vs. vout-integer (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}, + {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,0x01,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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 1, + { /* recipient outputs */ + { + {0xa8,0x5e,0xf8,0x70,0x13,0x94,0xb5,0x17,0xa4,0xb3,0x52,0x17,0xc4,0xbd,0x37,0xac,0x01,0xeb,0xee,0xd4,0xb0,0x08,0xf8,0xd0,0x87,0x9f,0x9e,0x09,0xba,0x95,0x31,0x9c}, + }, + }, + /* recipient 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 */ + {0xa8,0x5e,0xf8,0x70,0x13,0x94,0xb5,0x17,0xa4,0xb3,0x52,0x17,0xc4,0xbd,0x37,0xac,0x01,0xeb,0xee,0xd4,0xb0,0x08,0xf8,0xd0,0x87,0x9f,0x9e,0x09,0xba,0x95,0x31,0x9c}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* expected output data (pubkeys and seckey tweaks) */ + 1, + { + {0xa8,0x5e,0xf8,0x70,0x13,0x94,0xb5,0x17,0xa4,0xb3,0x52,0x17,0xc4,0xbd,0x37,0xac,0x01,0xeb,0xee,0xd4,0xb0,0x08,0xf8,0xd0,0x87,0x9f,0x9e,0x09,0xba,0x95,0x31,0x9c}, + }, + { + {0xc8,0xac,0x02,0x92,0x99,0x7b,0x5b,0xca,0x98,0xb3,0xeb,0xd9,0x9a,0x57,0xe2,0x53,0x07,0x11,0x37,0x55,0x0f,0x27,0x04,0x52,0xcd,0x3d,0xf8,0xa3,0xe2,0x26,0x6d,0x36}, + }, + { + {0xc0,0x36,0xee,0x38,0xbf,0xe4,0x6a,0xba,0x03,0x23,0x43,0x39,0xae,0x72,0x19,0xb3,0x1b,0x82,0x4b,0x52,0xef,0x9d,0x5c,0xe0,0x58,0x10,0xa0,0xd6,0xf6,0x23,0x30,0xde,0xdc,0x2b,0x55,0x65,0x25,0x78,0xaa,0x5b,0xda,0xbf,0x93,0x0f,0xae,0x94,0x1a,0xcd,0x83,0x9d,0x5a,0x66,0xf8,0xfc,0xe7,0xca,0xa9,0x71,0x0c,0xcb,0x44,0x6b,0xdd,0xd1}, + }, + }, + + /* ----- Single recipient: multiple UTXOs from the same public key (6) ----- */ + { + 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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 1, + { /* 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}, + }, + }, + /* recipient 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}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* 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 (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}, + {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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 1, + { /* 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}, + }, + }, + /* recipient 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}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* 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 (8) ----- */ + { + 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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 1, + { /* 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}, + }, + }, + /* recipient 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}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* 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 (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 */ + {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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 1, + { /* 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}, + }, + }, + /* recipient 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}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* 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 (10) ----- */ + { + 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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 1, + { /* 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}, + }, + }, + /* recipient 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}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* 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 (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}, + 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}, + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 2, + { /* recipient outputs */ + { + {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}, + {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}, + }, + }, + /* recipient 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 */ + {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}, + {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}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* expected output data (pubkeys and seckey tweaks) */ + 2, + { + {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}, + {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}, + }, + { + {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,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}, + }, + { + {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}, + {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}, + }, + }, + + /* ----- Multiple outputs: multiple outputs, multiple recipients (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}, + 3, + { /* 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,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}, + }, + { + "", + "", + }, + }, + 1, + 3, + { /* recipient outputs */ + { + {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}, + {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}, + {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}, + }, + }, + /* recipient data (scan and spend seckeys) */ + {0x06,0x0b,0x75,0x1d,0x78,0x92,0x14,0x90,0x06,0xed,0x7b,0x98,0x60,0x69,0x55,0xa2,0x9f,0xe2,0x84,0xa1,0xe9,0x00,0x07,0x0c,0x09,0x71,0xf5,0xfb,0x93,0xdb,0xf4,0x22}, + {0x99,0x02,0xc3,0xc5,0x6e,0x84,0x00,0x2a,0x7c,0xd4,0x10,0x11,0x3a,0x9a,0xb2,0x1d,0x14,0x2b,0xe7,0xf5,0x3c,0xf5,0x20,0x07,0x20,0xbb,0x01,0x31,0x4c,0x5e,0xb9,0x20}, + 3, + { /* outputs to scan */ + {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}, + {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}, + {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}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* expected output data (pubkeys and seckey tweaks) */ + 2, + { + {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}, + {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}, + }, + { + {0x72,0xcd,0x08,0x2c,0xcc,0xb6,0x33,0xbf,0x85,0x24,0x0a,0x83,0x49,0x4b,0x32,0xdc,0x94,0x3a,0x4d,0x05,0x64,0x7a,0x66,0x86,0xd2,0x3a,0xd4,0xca,0x59,0xc0,0xeb,0xe4}, + {0x2f,0x17,0xea,0x87,0x3a,0x00,0x47,0xfc,0x01,0xba,0x80,0x10,0xfe,0xf0,0x96,0x9e,0x76,0xd0,0xe4,0x28,0x3f,0x60,0x0d,0x48,0xf7,0x35,0x09,0x8b,0x1f,0xee,0x6e,0xb9}, + }, + { + {0x38,0x74,0x5f,0x3d,0x9f,0x5e,0xef,0x0b,0x1c,0xfb,0x17,0xca,0x31,0x4e,0xfa,0x8c,0x52,0x1e,0xfa,0xb2,0x8a,0x23,0xaa,0x20,0xec,0x5e,0x3a,0xbb,0x56,0x1d,0x42,0x80,0x4d,0x53,0x99,0x06,0xdc,0xe6,0x0c,0x4e,0xe7,0x97,0x79,0x66,0x18,0x4e,0x6f,0x2c,0xab,0x1f,0xaa,0x0e,0x53,0x77,0xce,0xb7,0x14,0x8e,0xc5,0x21,0x8b,0x4e,0x78,0x78}, + {0xc2,0x6f,0x4e,0x3c,0xf3,0x71,0xb9,0x0b,0x84,0x0f,0x48,0xea,0x0e,0x76,0x1b,0x5e,0xc3,0x18,0x83,0xed,0x55,0x71,0x9f,0x9e,0xf0,0x6a,0x90,0xe2,0x82,0xd8,0x5f,0x56,0x57,0x90,0xab,0x78,0x0a,0x3f,0x49,0x1b,0xc2,0x66,0x8c,0xc6,0x4e,0x94,0x4d,0xca,0x84,0x9d,0x10,0x22,0xa8,0x78,0xcd,0xad,0xb8,0xd1,0x68,0xb8,0xda,0x4a,0x6d,0xa3}, + }, + }, + + /* ----- Receiving with labels: label with even 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,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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 1, + { /* 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}, + }, + }, + /* recipient 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}, + }, + /* labels */ + 3, {2, 3, 1001337, 0xffffffff, }, + /* 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 (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}, + {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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 1, + { /* 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}, + }, + }, + /* recipient 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}, + }, + /* labels */ + 3, {2, 3, 1001337, 0xffffffff, }, + /* 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: large label integer (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}, + 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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 1, + { /* 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}, + }, + }, + /* recipient 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}, + }, + /* labels */ + 3, {2, 3, 1001337, 0xffffffff, }, + /* 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: un-labeled and 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}, + {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}, + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 2, + 2, + { /* recipient outputs */ + { + {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}, + {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}, + }, + { + {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}, + {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}, + }, + }, + /* recipient 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 */ + {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}, + {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}, + }, + /* labels */ + 1, {1, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* expected output data (pubkeys and seckey tweaks) */ + 2, + { + {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}, + {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}, + }, + { + {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,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}, + }, + { + {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}, + {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}, + }, + }, + + /* ----- 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}, + 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}, + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 2, + { /* recipient outputs */ + { + {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}, + {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}, + }, + }, + /* recipient 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 */ + {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}, + {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}, + }, + /* labels */ + 1, {1, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* expected output data (pubkeys and seckey tweaks) */ + 2, + { + {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}, + {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}, + }, + { + {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}, + {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}, + }, + { + {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}, + {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}, + }, + }, + + /* ----- Multiple outputs with labels: un-labeled, labeled, and multiple outputs for labeled address; same recipients (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}, + 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}, + }, + }, + 12, + 4, + { /* recipient outputs */ + { + {0x00,0x6a,0x02,0xc3,0x08,0xcc,0xdb,0xf3,0xac,0x49,0xf0,0x63,0x8f,0x6d,0xe1,0x28,0xf8,0x75,0xdb,0x5a,0x21,0x30,0x95,0xcf,0x11,0x2b,0x3b,0x77,0x72,0x24,0x72,0xae}, + {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}, + {0xca,0x64,0xab,0xe1,0xe0,0xf7,0x37,0x82,0x3f,0xb9,0xa9,0x4f,0x59,0x7e,0xed,0x41,0x8f,0xb2,0xdf,0x77,0xb1,0x31,0x7e,0x26,0xb8,0x81,0xa1,0x4b,0xb5,0x94,0xfa,0xaa}, + }, + { + {0x00,0x6a,0x02,0xc3,0x08,0xcc,0xdb,0xf3,0xac,0x49,0xf0,0x63,0x8f,0x6d,0xe1,0x28,0xf8,0x75,0xdb,0x5a,0x21,0x30,0x95,0xcf,0x11,0x2b,0x3b,0x77,0x72,0x24,0x72,0xae}, + {0x3e,0xdf,0x1f,0xf6,0x65,0x7c,0x6e,0x69,0x56,0x88,0x11,0xbd,0x72,0x6a,0x7a,0x7f,0x48,0x04,0x93,0xaa,0x42,0x16,0x1a,0xcf,0xe8,0xdd,0x4f,0x44,0x52,0x1f,0x99,0xed}, + {0x7e,0xe1,0x54,0x3e,0xd5,0xd1,0x23,0xff,0xa6,0x6f,0xbe,0xbc,0x12,0x8c,0x02,0x01,0x73,0xeb,0x49,0x0d,0x5f,0xa2,0xba,0x30,0x6e,0x0c,0x95,0x73,0xa7,0x7d,0xb8,0xf3}, + {0xca,0x64,0xab,0xe1,0xe0,0xf7,0x37,0x82,0x3f,0xb9,0xa9,0x4f,0x59,0x7e,0xed,0x41,0x8f,0xb2,0xdf,0x77,0xb1,0x31,0x7e,0x26,0xb8,0x81,0xa1,0x4b,0xb5,0x94,0xfa,0xaa}, + }, + { + {0x00,0x6a,0x02,0xc3,0x08,0xcc,0xdb,0xf3,0xac,0x49,0xf0,0x63,0x8f,0x6d,0xe1,0x28,0xf8,0x75,0xdb,0x5a,0x21,0x30,0x95,0xcf,0x11,0x2b,0x3b,0x77,0x72,0x24,0x72,0xae}, + {0x7e,0xe1,0x54,0x3e,0xd5,0xd1,0x23,0xff,0xa6,0x6f,0xbe,0xbc,0x12,0x8c,0x02,0x01,0x73,0xeb,0x49,0x0d,0x5f,0xa2,0xba,0x30,0x6e,0x0c,0x95,0x73,0xa7,0x7d,0xb8,0xf3}, + {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}, + {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}, + }, + { + {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}, + {0x3c,0x54,0x44,0x49,0x44,0xd1,0x76,0x43,0x76,0x44,0x37,0x8c,0x23,0xef,0xb9,0x99,0xab,0x6a,0xb1,0xca,0xcd,0xfe,0x1d,0xc1,0x53,0x7b,0x60,0x7e,0x3d,0xf3,0x30,0xe2}, + {0xca,0x64,0xab,0xe1,0xe0,0xf7,0x37,0x82,0x3f,0xb9,0xa9,0x4f,0x59,0x7e,0xed,0x41,0x8f,0xb2,0xdf,0x77,0xb1,0x31,0x7e,0x26,0xb8,0x81,0xa1,0x4b,0xb5,0x94,0xfa,0xaa}, + {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}, + }, + { + {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}, + {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}, + {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}, + }, + { + {0x3c,0x54,0x44,0x49,0x44,0xd1,0x76,0x43,0x76,0x44,0x37,0x8c,0x23,0xef,0xb9,0x99,0xab,0x6a,0xb1,0xca,0xcd,0xfe,0x1d,0xc1,0x53,0x7b,0x60,0x7e,0x3d,0xf3,0x30,0xe2}, + {0x60,0x2e,0x10,0xe6,0x94,0x41,0x07,0xc9,0xb4,0x8b,0xd8,0x85,0xb4,0x93,0x67,0x65,0x78,0xc9,0x35,0x72,0x32,0x87,0xe0,0xab,0x2f,0x8b,0x7f,0x13,0x68,0x62,0x56,0x8e}, + {0x7e,0xe1,0x54,0x3e,0xd5,0xd1,0x23,0xff,0xa6,0x6f,0xbe,0xbc,0x12,0x8c,0x02,0x01,0x73,0xeb,0x49,0x0d,0x5f,0xa2,0xba,0x30,0x6e,0x0c,0x95,0x73,0xa7,0x7d,0xb8,0xf3}, + {0xca,0x64,0xab,0xe1,0xe0,0xf7,0x37,0x82,0x3f,0xb9,0xa9,0x4f,0x59,0x7e,0xed,0x41,0x8f,0xb2,0xdf,0x77,0xb1,0x31,0x7e,0x26,0xb8,0x81,0xa1,0x4b,0xb5,0x94,0xfa,0xaa}, + }, + { + {0x3c,0x54,0x44,0x49,0x44,0xd1,0x76,0x43,0x76,0x44,0x37,0x8c,0x23,0xef,0xb9,0x99,0xab,0x6a,0xb1,0xca,0xcd,0xfe,0x1d,0xc1,0x53,0x7b,0x60,0x7e,0x3d,0xf3,0x30,0xe2}, + {0x7e,0xe1,0x54,0x3e,0xd5,0xd1,0x23,0xff,0xa6,0x6f,0xbe,0xbc,0x12,0x8c,0x02,0x01,0x73,0xeb,0x49,0x0d,0x5f,0xa2,0xba,0x30,0x6e,0x0c,0x95,0x73,0xa7,0x7d,0xb8,0xf3}, + {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}, + {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}, + }, + { + {0x3e,0xdf,0x1f,0xf6,0x65,0x7c,0x6e,0x69,0x56,0x88,0x11,0xbd,0x72,0x6a,0x7a,0x7f,0x48,0x04,0x93,0xaa,0x42,0x16,0x1a,0xcf,0xe8,0xdd,0x4f,0x44,0x52,0x1f,0x99,0xed}, + {0x7e,0xe1,0x54,0x3e,0xd5,0xd1,0x23,0xff,0xa6,0x6f,0xbe,0xbc,0x12,0x8c,0x02,0x01,0x73,0xeb,0x49,0x0d,0x5f,0xa2,0xba,0x30,0x6e,0x0c,0x95,0x73,0xa7,0x7d,0xb8,0xf3}, + {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}, + {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}, + }, + { + {0x3e,0xdf,0x1f,0xf6,0x65,0x7c,0x6e,0x69,0x56,0x88,0x11,0xbd,0x72,0x6a,0x7a,0x7f,0x48,0x04,0x93,0xaa,0x42,0x16,0x1a,0xcf,0xe8,0xdd,0x4f,0x44,0x52,0x1f,0x99,0xed}, + {0xca,0x64,0xab,0xe1,0xe0,0xf7,0x37,0x82,0x3f,0xb9,0xa9,0x4f,0x59,0x7e,0xed,0x41,0x8f,0xb2,0xdf,0x77,0xb1,0x31,0x7e,0x26,0xb8,0x81,0xa1,0x4b,0xb5,0x94,0xfa,0xaa}, + {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}, + {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}, + }, + { + {0x60,0x2e,0x10,0xe6,0x94,0x41,0x07,0xc9,0xb4,0x8b,0xd8,0x85,0xb4,0x93,0x67,0x65,0x78,0xc9,0x35,0x72,0x32,0x87,0xe0,0xab,0x2f,0x8b,0x7f,0x13,0x68,0x62,0x56,0x8e}, + {0x7e,0xe1,0x54,0x3e,0xd5,0xd1,0x23,0xff,0xa6,0x6f,0xbe,0xbc,0x12,0x8c,0x02,0x01,0x73,0xeb,0x49,0x0d,0x5f,0xa2,0xba,0x30,0x6e,0x0c,0x95,0x73,0xa7,0x7d,0xb8,0xf3}, + {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}, + {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}, + }, + { + {0x60,0x2e,0x10,0xe6,0x94,0x41,0x07,0xc9,0xb4,0x8b,0xd8,0x85,0xb4,0x93,0x67,0x65,0x78,0xc9,0x35,0x72,0x32,0x87,0xe0,0xab,0x2f,0x8b,0x7f,0x13,0x68,0x62,0x56,0x8e}, + {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}, + {0xca,0x64,0xab,0xe1,0xe0,0xf7,0x37,0x82,0x3f,0xb9,0xa9,0x4f,0x59,0x7e,0xed,0x41,0x8f,0xb2,0xdf,0x77,0xb1,0x31,0x7e,0x26,0xb8,0x81,0xa1,0x4b,0xb5,0x94,0xfa,0xaa}, + {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}, + }, + { + {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}, + {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}, + {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}, + {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}, + }, + }, + /* recipient 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 */ + {0x00,0x6a,0x02,0xc3,0x08,0xcc,0xdb,0xf3,0xac,0x49,0xf0,0x63,0x8f,0x6d,0xe1,0x28,0xf8,0x75,0xdb,0x5a,0x21,0x30,0x95,0xcf,0x11,0x2b,0x3b,0x77,0x72,0x24,0x72,0xae}, + {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}, + {0xca,0x64,0xab,0xe1,0xe0,0xf7,0x37,0x82,0x3f,0xb9,0xa9,0x4f,0x59,0x7e,0xed,0x41,0x8f,0xb2,0xdf,0x77,0xb1,0x31,0x7e,0x26,0xb8,0x81,0xa1,0x4b,0xb5,0x94,0xfa,0xaa}, + }, + /* labels */ + 2, {1, 1337, 0xffffffff, 0xffffffff, }, + /* expected output data (pubkeys and seckey tweaks) */ + 4, + { + {0x00,0x6a,0x02,0xc3,0x08,0xcc,0xdb,0xf3,0xac,0x49,0xf0,0x63,0x8f,0x6d,0xe1,0x28,0xf8,0x75,0xdb,0x5a,0x21,0x30,0x95,0xcf,0x11,0x2b,0x3b,0x77,0x72,0x24,0x72,0xae}, + {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}, + {0xca,0x64,0xab,0xe1,0xe0,0xf7,0x37,0x82,0x3f,0xb9,0xa9,0x4f,0x59,0x7e,0xed,0x41,0x8f,0xb2,0xdf,0x77,0xb1,0x31,0x7e,0x26,0xb8,0x81,0xa1,0x4b,0xb5,0x94,0xfa,0xaa}, + }, + { + {0x4e,0x33,0x52,0xfb,0xe0,0x50,0x5c,0x25,0xe7,0x18,0xd9,0x60,0x07,0xc2,0x59,0xef,0x08,0xdb,0x34,0xf8,0xc8,0x44,0xe4,0xff,0x74,0x2d,0x98,0x55,0xff,0x03,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}, + {0xbf,0x70,0x9f,0x98,0xd4,0x41,0x8f,0x8a,0x67,0xe7,0x38,0x15,0x4a,0xe4,0x88,0x18,0xda,0xd4,0x46,0x89,0xcd,0x37,0xfb,0xc0,0x70,0x89,0x1a,0x39,0x6d,0xd1,0xc6,0x33}, + {0x73,0x6f,0x05,0xe4,0xe3,0x07,0x2c,0x3b,0x86,0x56,0xbe,0xde,0xf2,0xe9,0xbf,0x54,0xcb,0xca,0xa2,0xb6,0xfe,0x53,0x20,0xd3,0xe8,0x6f,0x5b,0x96,0x87,0x4d,0xda,0x71}, + }, + { + {0x6e,0xea,0xe1,0xea,0x9e,0xb8,0x26,0xe3,0xd0,0xe8,0x12,0xf6,0x59,0x37,0x10,0x0e,0x08,0x36,0xea,0x18,0x8c,0x04,0xf3,0x6f,0xab,0xc4,0x98,0x1e,0xda,0x29,0xde,0x8d,0x3d,0x35,0x29,0x39,0x0a,0x0a,0x8b,0x3d,0x83,0x0f,0x7b,0xca,0x4f,0x5e,0xae,0x59,0x94,0xb9,0x78,0x8d,0xda,0xf0,0x5a,0xd2,0x59,0xff,0xe2,0x6d,0x86,0x14,0x4b,0x4b}, + {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}, + {0x42,0xa1,0x9f,0xd8,0xa6,0x3d,0xde,0x18,0x24,0x96,0x6a,0x95,0xd6,0x5a,0x28,0x20,0x3e,0x63,0x1e,0x49,0xbf,0x96,0xca,0x5d,0xae,0x1b,0x39,0x0e,0x7a,0x0a,0xce,0x2c,0xc8,0x70,0x9c,0x9b,0x0c,0x57,0x15,0x04,0x70,0x32,0xf5,0x7f,0x53,0x6a,0x3c,0x80,0x27,0x3c,0xbe,0xcf,0x4c,0x05,0xbe,0x0b,0x54,0x56,0xc1,0x83,0xfa,0x12,0x2c,0x06}, + {0x2e,0x61,0xbb,0x3d,0x79,0x41,0x8e,0xcf,0x55,0xf6,0x88,0x47,0xcf,0x12,0x1b,0xfc,0x12,0xd3,0x97,0xb3,0x9d,0x1d,0xa8,0x64,0x32,0x46,0xb2,0xf0,0xa9,0xb9,0x6c,0x3d,0xaa,0x4b,0xfe,0x96,0x51,0xbe,0xb5,0xc9,0xce,0x20,0xe1,0xf2,0x92,0x82,0xc4,0x56,0x64,0x00,0xa4,0xb4,0x5e,0xe6,0x65,0x7e,0xc3,0xb1,0x8f,0xdc,0x55,0x4d,0xa0,0xb4}, + }, + }, + + /* ----- Single recipient: use silent payments for sender change (19) ----- */ + { + 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}, + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 2, + { /* recipient outputs */ + { + {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}, + {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}, + }, + }, + /* recipient 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 */ + {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}, + {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}, + }, + /* labels */ + 1, {0, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* 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 recipient: taproot input with NUMS point (20) ----- */ + { + 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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 1, + { /* 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}, + }, + }, + /* recipient 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}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* 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 (21) ----- */ + { + 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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 1, + { /* 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}, + }, + }, + /* recipient 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}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* 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 (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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 1, + { /* 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}, + }, + }, + /* recipient 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}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* 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 (23) ----- */ + { + 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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 1, + { /* 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}, + }, + }, + /* recipient 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}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* 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 (24) ----- */ + { + 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,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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 1, + { /* recipient outputs */ + { + {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}, + }, + }, + /* recipient 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 */ + {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}, + {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}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* expected output data (pubkeys and seckey tweaks) */ + 0, + { + "", + }, + { + "", + }, + { + "", + }, + }, + + /* ----- No valid inputs, sender generates no outputs (25) ----- */ + { + 0, + { /* input plain seckeys */ + "", + }, + { /* input plain pubkeys */ + "", + }, + 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}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 0, + { /* recipient outputs */ + { + "", + }, + }, + /* recipient 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}, + {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}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* expected output data (pubkeys and seckey tweaks) */ + 0, + { + "", + }, + { + "", + }, + { + "", + }, + }, + + /* ----- Input keys sum up to zero / point at infinity: sending fails, receiver skips tx (26) ----- */ + { + 2, + { /* input plain seckeys */ + {0xa6,0xdf,0x6a,0x0b,0xb4,0x48,0x99,0x2a,0x30,0x1d,0xf4,0x25,0x8e,0x06,0xa8,0x9f,0xe7,0xcf,0x71,0x46,0xf5,0x9a,0xc3,0xbd,0x5f,0xf2,0x60,0x83,0xac,0xb2,0x2c,0xeb}, + {0x59,0x20,0x95,0xf4,0x4b,0xb7,0x66,0xd5,0xcf,0xe2,0x0b,0xda,0x71,0xf9,0x57,0x5e,0xd2,0xdf,0x6b,0x9f,0xb9,0xad,0xdc,0x7e,0x5f,0xdf,0xfe,0x09,0x23,0x84,0x14,0x56}, + }, + { /* input plain pubkeys */ + {0x02,0x55,0x7e,0xf3,0xe5,0x5b,0x0a,0x52,0x48,0x9b,0x44,0x54,0xc1,0x16,0x9e,0x06,0xbd,0xea,0x43,0x68,0x7a,0x69,0xc1,0xf1,0x90,0xeb,0x50,0x78,0x16,0x44,0xab,0x69,0x75}, + {0x03,0x55,0x7e,0xf3,0xe5,0x5b,0x0a,0x52,0x48,0x9b,0x44,0x54,0xc1,0x16,0x9e,0x06,0xbd,0xea,0x43,0x68,0x7a,0x69,0xc1,0xf1,0x90,0xeb,0x50,0x78,0x16,0x44,0xab,0x69,0x75}, + }, + 0, + { /* input taproot seckeys */ + "", + }, + { /* input x-only pubkeys */ + "", + }, + /* smallest outpoint */ + {0x4e,0x17,0x94,0x8a,0xbc,0xde,0x5c,0x24,0x71,0xc0,0x45,0x0f,0xc4,0x18,0x54,0x56,0x6e,0x3c,0x67,0xf2,0x06,0xf4,0xaf,0x80,0xae,0x16,0x5e,0xb2,0x47,0x61,0x28,0x3a,0x00,0x00,0x00,0x00}, + 1, + { /* recipient pubkeys (address data) */ + { + {0x02,0xc6,0x04,0x7f,0x94,0x41,0xed,0x7d,0x6d,0x30,0x45,0x40,0x6e,0x95,0xc0,0x7c,0xd8,0x5c,0x77,0x8e,0x4b,0x8c,0xef,0x3c,0xa7,0xab,0xac,0x09,0xb9,0x5c,0x70,0x9e,0xe5}, + {0x02,0x79,0xbe,0x66,0x7e,0xf9,0xdc,0xbb,0xac,0x55,0xa0,0x62,0x95,0xce,0x87,0x0b,0x07,0x02,0x9b,0xfc,0xdb,0x2d,0xce,0x28,0xd9,0x59,0xf2,0x81,0x5b,0x16,0xf8,0x17,0x98}, + }, + { + "", + "", + }, + { + "", + "", + }, + { + "", + "", + }, + }, + 1, + 0, + { /* recipient outputs */ + { + "", + }, + }, + /* recipient data (scan and spend seckeys) */ + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01}, + 1, + { /* outputs to scan */ + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + }, + /* labels */ + 0, {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }, + /* expected output data (pubkeys and seckey tweaks) */ + 0, + { + "", + }, + { + "", + }, + { + "", + }, + }, + +}; diff --git a/tools/tests_silentpayments_generate.py b/tools/tests_silentpayments_generate.py new file mode 100755 index 0000000000..becf87edec --- /dev/null +++ b/tools/tests_silentpayments_generate.py @@ -0,0 +1,279 @@ +#!/usr/bin/env python3 + +""" +A script to convert BIP352 test vectors from JSON to a C header. + +Usage: + + ./tools/tests_silentpayments_generate.py src/modules/silentpayments/bip352_send_and_receive_test_vectors.json > ./src/modules/silentpayments/vectors.h +""" + +import hashlib +import json +import sys + +NUMS_H = bytes.fromhex("50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0") +MAX_INPUTS_PER_TEST_CASE = 3 +MAX_OUTPUTS_PER_TEST_CASE = 4 +MAX_PERMUTATIONS_PER_SENDING_TEST_CASE = 12 + +def sha256(s): + return hashlib.sha256(s).digest() + +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 is_p2tr(s): # OP_1 OP_PUSHBYTES_32 <32 bytes> + return (len(s) == 34) and (s[0] == 0x51) and (s[1] == 0x20) + +def get_pubkey_from_input(input_data, pubkey_hex): + """Extract the correct pubkey for an input, handling NUMS_H filtering and format conversion""" + spk = bytes.fromhex(input_data['prevout']['scriptPubKey']['hex']) + pubkey = bytes.fromhex(pubkey_hex) + + if is_p2tr(spk): # taproot input + # Check for NUMS_H in witness (should be skipped) + witness = bytes.fromhex(input_data.get('txinwitness', '')) + # Parse witness stack + witness_stack = [] + num_witness_items = 0 + if len(witness) > 0: + num_witness_items = witness[0] + witness = witness[1:] + for i in range(num_witness_items): + item_len = witness[0] + witness_stack.append(witness[1:item_len+1]) + witness = witness[item_len+1:] + + # Check for script-path spend with NUMS_H + 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'' + + # Convert to x-only (32 bytes) for taproot + if len(pubkey) == 33: + pubkey = pubkey[1:] # Remove prefix byte + return pubkey + else: # regular input - use full compressed pubkey (33 bytes) + return pubkey + +def gen_byte_array(hex): + assert len(hex) % 2 == 0 + if hex == "": + return "{0x00}" + s = ',0x'.join(a + b for a, b in zip(hex[::2], hex[1::2])) + return "{0x" + s + "}" + +def maybe_gen_comment(comment): + if comment: + return f" /* {comment} */" + else: + return "" + +def gen_key_material(keys, comment=None, prepend_count=False): + assert len(keys) <= MAX_INPUTS_PER_TEST_CASE + out = "" + if prepend_count: + out += f" {len(keys)},\n" + out += f" {{{maybe_gen_comment(comment)}\n" + for k in keys: + out += f" {gen_byte_array(k)},\n" + if not keys: + out += ' "",\n' + out += " },\n" + return out + +def gen_recipient_addr_material(recipients): + assert len(recipients) <= MAX_OUTPUTS_PER_TEST_CASE + out = f" {len(recipients)},\n" + out += " { /* recipient pubkeys (address data) */\n" + for i in range(MAX_OUTPUTS_PER_TEST_CASE): + out += " {\n" + if i < len(recipients): + # Use the scan_pubkey and spend_pubkey directly from the recipient + scan_pubkey = bytes.fromhex(recipients[i]['scan_pub_key']) + spend_pubkey = bytes.fromhex(recipients[i]['spend_pub_key']) + + out += f" {gen_byte_array(scan_pubkey.hex())},\n" + out += f" {gen_byte_array(spend_pubkey.hex())},\n" + else: + out += ' "",\n' + out += ' "",\n' + out += " },\n" + out += " },\n" + return out + +def gen_sending_outputs(output_sets, comment=None, prepend_count=False): + assert len(output_sets) <= MAX_PERMUTATIONS_PER_SENDING_TEST_CASE + out = "" + if prepend_count: + out += f" {len(output_sets)},\n" + out += f" {len(output_sets[0])},\n" + out += f" {{{maybe_gen_comment(comment)}\n" + for o in output_sets: + out += gen_outputs(outputs=o, prepend_count=False, indent=12) + if not output_sets: + out += gen_outputs(comment=None, outputs=[], prepend_count=False, indent=12) + out += " },\n" + return out + +def gen_outputs(outputs, comment=None, prepend_count=False, indent=8): + out = "" + spaces = indent * " " + if prepend_count: + out += spaces + f"{len(outputs)},\n" + out += f" {{{maybe_gen_comment(comment)}\n" + for o in outputs: + out += spaces + f" {gen_byte_array(o)},\n" + if not outputs: + out += spaces + ' "",\n' + out += spaces + "},\n" + return out + +def gen_labels(labels): + assert len(labels) <= MAX_OUTPUTS_PER_TEST_CASE + out = " /* labels */\n" + out += f" {len(labels)}, {{" + for i in range(MAX_OUTPUTS_PER_TEST_CASE): + if i < len(labels): + out += f"{labels[i]}, " + else: + out += "0xffffffff, " + out += "},\n" + return out + +def gen_preamble(test_vectors): + out = "/* Note: this file was autogenerated using tests_silentpayments_generate.py. Do not edit. */\n" + out += f""" +#include + +#define MAX_INPUTS_PER_TEST_CASE {MAX_INPUTS_PER_TEST_CASE} +#define MAX_OUTPUTS_PER_TEST_CASE {MAX_OUTPUTS_PER_TEST_CASE} +#define MAX_PERMUTATIONS_PER_SENDING_TEST_CASE {MAX_PERMUTATIONS_PER_SENDING_TEST_CASE} + +struct bip352_recipient_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_outputs; + struct bip352_recipient_addressdata recipient_pubkeys[MAX_OUTPUTS_PER_TEST_CASE]; + + /* Expected sender data */ + size_t num_output_sets; + size_t num_recipient_outputs; + unsigned char recipient_outputs[MAX_PERMUTATIONS_PER_SENDING_TEST_CASE][MAX_OUTPUTS_PER_TEST_CASE][32]; + + /* Given recipient 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 recipient data */ + size_t num_found_output_pubkeys; + 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]; +}}; +""" + return out + +def gen_test_vectors(test_vectors): + out = f"#define SECP256K1_SILENTPAYMENTS_NUMBER_TESTVECTORS {len(test_vectors)}\n\n" + out += "static const struct bip352_test_vector bip352_test_vectors[SECP256K1_SILENTPAYMENTS_NUMBER_TESTVECTORS] = {\n" + for test_i, 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 = [] + + pubkey_index = 0 + input_pubkeys_hex = test_vector['sending'][0]['expected']['input_pub_keys'] + + for vec in test_vector['sending'][0]['given']['vin']: + outpoints.append((vec['txid'], vec['vout'])) + + if pubkey_index < len(input_pubkeys_hex): + pubkey = get_pubkey_from_input(vec, input_pubkeys_hex[pubkey_index]) + if len(pubkey) == 33: # regular input + input_plain_seckeys.append(vec['private_key']) + input_plain_pubkeys.append(pubkey.hex()) + pubkey_index += 1 + elif len(pubkey) == 32: # taproot input + input_taproot_seckeys.append(vec['private_key']) + input_xonly_pubkeys.append(pubkey.hex()) + pubkey_index += 1 + # len(pubkey) == 0, it's a NUMS_H input - skip without incrementing + + out += f" /* ----- {test_vector['comment']} ({test_i + 1}) ----- */\n" + out += " {\n" + + outpoint_L = smallest_outpoint(outpoints).hex() + out += gen_key_material(input_plain_seckeys, "input plain seckeys", prepend_count=True) + out += gen_key_material(input_plain_pubkeys, "input plain pubkeys") + out += gen_key_material(input_taproot_seckeys, "input taproot seckeys", prepend_count=True) + out += gen_key_material(input_xonly_pubkeys, "input x-only pubkeys") + out += " /* smallest outpoint */\n" + out += f" {gen_byte_array(outpoint_L)},\n" + + # emit recipient pubkeys (address data) + out += gen_recipient_addr_material(test_vector['sending'][0]['given']['recipients']) + # emit recipient outputs + out += gen_sending_outputs(test_vector['sending'][0]['expected']['outputs'], "recipient outputs", prepend_count=True) + + # emit recipient scan/spend seckeys + recv_test_given = test_vector['receiving'][0]['given'] + recv_test_expected = test_vector['receiving'][0]['expected'] + out += " /* recipient data (scan and spend seckeys) */\n" + out += f" {gen_byte_array(recv_test_given['key_material']['scan_priv_key'])},\n" + out += f" {gen_byte_array(recv_test_given['key_material']['spend_priv_key'])},\n" + + # emit recipient to-scan outputs, labels and expected-found outputs + out += gen_outputs(recv_test_given['outputs'], "outputs to scan", prepend_count=True) + out += gen_labels(recv_test_given['labels']) + 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" + out += gen_outputs(expected_pubkeys, prepend_count=True) + out += gen_outputs(expected_tweaks) + out += gen_outputs(expected_signatures) + out += " },\n\n" + out += "};\n" + return out + +def main(): + if len(sys.argv) != 2: + print(__doc__) + sys.exit(1) + + filename_input = sys.argv[1] + with open(filename_input) as f: + test_vectors = json.load(f) + + print(gen_preamble(test_vectors)) + print(gen_test_vectors(test_vectors), end="") + +if __name__ == "__main__": + main() From 568e3aeb82aca217bab999f94c43f639a2a03589 Mon Sep 17 00:00:00 2001 From: josibake Date: Thu, 7 Nov 2024 13:18:59 +0100 Subject: [PATCH 09/12] tests: add constant time tests Co-authored-by: Jonas Nick <2582071+jonasnick@users.noreply.github.com> Co-authored-by: Sebastian Falbesoner <91535+thestack@users.noreply.github.com> --- src/ctime_tests.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/ctime_tests.c b/src/ctime_tests.c index f81bdb9228..73eb43b9e2 100644 --- a/src/ctime_tests.c +++ b/src/ctime_tests.c @@ -40,6 +40,10 @@ #include "../include/secp256k1_ellswift.h" #endif +#ifdef ENABLE_MODULE_SILENTPAYMENTS +#include "../include/secp256k1_silentpayments.h" +#endif + static void run_tests(secp256k1_context *ctx, unsigned char *key); int main(void) { @@ -94,6 +98,25 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { unsigned char ellswift[64]; static const unsigned char prefix[64] = {'t', 'e', 's', 't'}; #endif +#ifdef ENABLE_MODULE_SILENTPAYMENTS + secp256k1_xonly_pubkey generated_output; + secp256k1_xonly_pubkey *generated_outputs[1]; + secp256k1_silentpayments_recipient recipient; + const secp256k1_silentpayments_recipient *recipients[1]; + unsigned char outpoint_smallest[36] = { 0 }; + secp256k1_keypair taproot_seckey; + const secp256k1_keypair *taproot_seckeys[1]; + const unsigned char *plain_seckeys[1]; + secp256k1_silentpayments_found_output *found_outputs[1]; + size_t n_found_outputs; + const secp256k1_xonly_pubkey *tx_outputs[1]; + secp256k1_silentpayments_prevouts_summary prevouts_summary; + unsigned char label_tweak[32] = { 0 }; + secp256k1_xonly_pubkey xonly_pubkey; + const secp256k1_xonly_pubkey *xonly_pubkeys[1]; + secp256k1_pubkey plain_pubkey; + const secp256k1_pubkey *plain_pubkeys[1]; +#endif for (i = 0; i < 32; i++) { msg[i] = i + 1; @@ -263,5 +286,57 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { CHECK(ret == 1); } +#endif + +#ifdef ENABLE_MODULE_SILENTPAYMENTS + SECP256K1_CHECKMEM_DEFINE(key, 32); + + generated_outputs[0] = &generated_output; + + /* Initialize recipient */ + CHECK(secp256k1_ec_pubkey_create(ctx, &recipient.scan_pubkey, key)); + key[31] ^= 1; + CHECK(secp256k1_ec_pubkey_create(ctx, &recipient.spend_pubkey, key)); + key[31] ^= (1 << 1); + recipient.index = 0; + recipients[0] = &recipient; + + /* Set up secret keys */ + SECP256K1_CHECKMEM_UNDEFINE(key, 32); + ret = secp256k1_keypair_create(ctx, &taproot_seckey, key); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret); + key[31] ^= (1 << 2); + taproot_seckeys[0] = &taproot_seckey; + plain_seckeys[0] = key; + + ret = secp256k1_silentpayments_sender_create_outputs(ctx, generated_outputs, recipients, 1, outpoint_smallest, taproot_seckeys, 1, plain_seckeys, 1); + CHECK(ret == 1); + + ret = secp256k1_silentpayments_recipient_create_label(ctx, &recipient.spend_pubkey, label_tweak, key, 0); + key[31] ^= (1 << 3); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + + CHECK(secp256k1_keypair_xonly_pub(ctx, &xonly_pubkey, NULL, &taproot_seckey)); + SECP256K1_CHECKMEM_DEFINE(&xonly_pubkey, sizeof(xonly_pubkey)); + xonly_pubkeys[0] = &xonly_pubkey; + ret = secp256k1_ec_pubkey_create(ctx, &plain_pubkey, plain_seckeys[0]); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + SECP256K1_CHECKMEM_DEFINE(&plain_pubkey, sizeof(plain_pubkey)); + plain_pubkeys[0] = &plain_pubkey; + + ret = secp256k1_silentpayments_recipient_prevouts_summary_create(ctx, &prevouts_summary, outpoint_smallest, xonly_pubkeys, 1, plain_pubkeys, 1); + CHECK(ret == 1); + + tx_outputs[0] = generated_outputs[0]; + n_found_outputs = 1; + SECP256K1_CHECKMEM_DEFINE(&recipient.spend_pubkey, sizeof(recipient.spend_pubkey)); + /* It is sufficient to check _recipient_scan_outputs without a label lookup function, since the shared secret is created once (which is where the constant timeness matters) + * and then reused for the rest of the scanning logic. + */ + CHECK(secp256k1_silentpayments_recipient_scan_outputs(ctx, found_outputs, &n_found_outputs, tx_outputs, 1, key, &prevouts_summary, &recipient.spend_pubkey, NULL, NULL)); + #endif } From c7d3827e4fc11782e52333a104cb11c2d88c27f5 Mon Sep 17 00:00:00 2001 From: josibake Date: Fri, 15 Aug 2025 09:46:46 +0100 Subject: [PATCH 10/12] tests: add sha256 tag test Test midstate tags used in silent payments. --- src/modules/silentpayments/tests_impl.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/modules/silentpayments/tests_impl.h b/src/modules/silentpayments/tests_impl.h index ec538bbf50..96a5668d14 100644 --- a/src/modules/silentpayments/tests_impl.h +++ b/src/modules/silentpayments/tests_impl.h @@ -693,6 +693,29 @@ void run_silentpayments_test_vector_receive(const struct bip352_test_vector *tes CHECK(n_found == test->num_found_output_pubkeys); } +static void silentpayments_sha256_tag_test(void) { + secp256k1_sha256 sha; + { + /* "BIP0352/Inputs" */ + static const unsigned char tag[] = {'B','I','P','0','3','5','2','/','I','n','p','u','t','s'}; + secp256k1_silentpayments_sha256_init_inputs(&sha); + test_sha256_tag_midstate(&sha, tag, sizeof(tag)); + } + { + /* "BIP0352/SharedSecret" */ + static const unsigned char tag[] = {'B','I','P','0','3','5','2','/','S','h','a','r','e','d', 'S','e','c','r','e','t'}; + secp256k1_silentpayments_sha256_init_sharedsecret(&sha); + test_sha256_tag_midstate(&sha, tag, sizeof(tag)); + } + { + /* "BIP0352/Label" */ + static const unsigned char tag[] = {'B','I','P','0','3','5','2','/','L','a','b','e','l'}; + secp256k1_silentpayments_sha256_init_label(&sha); + test_sha256_tag_midstate(&sha, tag, sizeof(tag)); + } +} + + void run_silentpayments_test_vectors(void) { size_t i; @@ -709,6 +732,7 @@ void run_silentpayments_tests(void) { test_label_api(); test_recipient_api(); run_silentpayments_test_vectors(); + silentpayments_sha256_tag_test(); } #endif From 29bf59344156dabf6136d0331525e42edd0f2911 Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Fri, 23 Feb 2024 00:25:41 +0100 Subject: [PATCH 11/12] ci: enable silentpayments module --- .github/workflows/ci.yml | 41 +++++++++++++++++++++++++--------------- ci/ci.sh | 3 ++- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9783251d65..34efeebcc8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,7 @@ env: SCHNORRSIG: 'no' MUSIG: 'no' ELLSWIFT: 'no' + SILENTPAYMENTS: 'no' ### test options SECP256K1_TEST_ITERS: 64 BENCH: 'yes' @@ -84,18 +85,18 @@ jobs: matrix: configuration: - env_vars: { WIDEMUL: 'int64', RECOVERY: 'yes' } - - env_vars: { WIDEMUL: 'int64', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } + - env_vars: { WIDEMUL: 'int64', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' } - env_vars: { WIDEMUL: 'int128' } - env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' } - env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } - - env_vars: { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes' } + - env_vars: { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', SILENTPAYMENTS: 'yes' } - env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' } - env_vars: { RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes' } - - env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', CPPFLAGS: '-DVERIFY' } + - env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: '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', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } + - env_vars: { CFLAGS: '-O1', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' } - env_vars: { ECMULTGENKB: 2, ECMULTWINDOW: 2 } - env_vars: { ECMULTGENKB: 86, ECMULTWINDOW: 4 } cc: @@ -142,6 +143,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' CC: ${{ matrix.cc }} steps: @@ -174,6 +176,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' CTIMETESTS: 'no' steps: @@ -214,6 +217,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' CTIMETESTS: 'no' steps: @@ -245,6 +249,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' CTIMETESTS: 'no' CC: ${{ matrix.cc }} @@ -287,6 +292,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' CTIMETESTS: 'no' steps: @@ -348,6 +354,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' CTIMETESTS: 'no' SECP256K1_TEST_ITERS: 2 @@ -387,6 +394,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' CTIMETESTS: 'no' CFLAGS: '-fsanitize=undefined,address -g' UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1' @@ -440,6 +448,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' CC: 'clang' SECP256K1_TEST_ITERS: 32 ASM: 'no' @@ -476,6 +485,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' CTIMETESTS: 'no' strategy: @@ -519,14 +529,14 @@ jobs: fail-fast: false matrix: env_vars: - - { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } + - { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' } - { WIDEMUL: 'int128_struct', ECMULTGENKB: 2, ECMULTWINDOW: 4 } - - { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } + - { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' } - { WIDEMUL: 'int128', RECOVERY: 'yes' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes', CC: 'gcc' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' } - BUILD: 'distcheck' @@ -573,13 +583,13 @@ jobs: fail-fast: false matrix: env_vars: - - { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } + - { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' } - { WIDEMUL: 'int128_struct', ECMULTGENPRECISION: 2, ECMULTWINDOW: 4 } - - { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } + - { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' } - { WIDEMUL: 'int128', RECOVERY: 'yes' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CPPFLAGS: '-DVERIFY' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes', CC: 'gcc' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes', CPPFLAGS: '-DVERIFY' } - BUILD: 'distcheck' steps: @@ -705,6 +715,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + SILENTPAYMENTS: 'yes' steps: - name: Checkout diff --git a/ci/ci.sh b/ci/ci.sh index 08e84efd4f..f8a091fb53 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 ECMULTGENKB ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \ - EXPERIMENTAL ECDH RECOVERY EXTRAKEYS MUSIG SCHNORRSIG ELLSWIFT \ + EXPERIMENTAL ECDH RECOVERY EXTRAKEYS MUSIG SCHNORRSIG ELLSWIFT SILENTPAYMENTS \ SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS SYMBOL_CHECK \ EXAMPLES \ HOST WRAPPER_CMD \ @@ -80,6 +80,7 @@ esac --enable-module-extrakeys="$EXTRAKEYS" \ --enable-module-schnorrsig="$SCHNORRSIG" \ --enable-module-musig="$MUSIG" \ + --enable-module-silentpayments="$SILENTPAYMENTS" \ --enable-examples="$EXAMPLES" \ --enable-ctime-tests="$CTIMETESTS" \ --with-valgrind="$WITH_VALGRIND" \ From a0d2a33690e51b79aee037a50ce78e438849b823 Mon Sep 17 00:00:00 2001 From: josibake Date: Fri, 12 Jul 2024 14:57:54 +0200 Subject: [PATCH 12/12] docs: update README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 90edae1a2c..a9e4645057 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Features: * Optional module for Schnorr signatures according to [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki). * Optional module for ElligatorSwift key exchange according to [BIP-324](https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki). * Optional module for MuSig2 Schnorr multi-signatures according to [BIP-327](https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki). +* Optional module for Silent Payments sending and receiving according to [BIP-352](https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki). Implementation details ---------------------- @@ -150,6 +151,7 @@ Usage examples can be found in the [examples](examples) directory. To compile th * [Deriving a shared secret (ECDH) example](examples/ecdh.c) * [ElligatorSwift key exchange example](examples/ellswift.c) * [MuSig2 Schnorr multi-signatures example](examples/musig.c) + * [Silent Payments send and receive example](examples/silentpayments.c) To compile the examples, make sure the corresponding modules are enabled.