From f6eff121a672478f1acc017bbfcc869aae1ce53f Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Wed, 27 Sep 2023 17:22:14 +0200 Subject: [PATCH 01/18] build: add skeleton for new silentpayments (BIP352) module --- CMakeLists.txt | 2 ++ Makefile.am | 4 +++ 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, 83 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..a00eba1fb8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -311,3 +311,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 bd9f310b5e6d99ebb9dd2d6506fe48718ae6412e Mon Sep 17 00:00:00 2001 From: josibake Date: Mon, 25 Mar 2024 17:23:37 +0100 Subject: [PATCH 02/18] 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 | 102 ++++++++ src/modules/silentpayments/main_impl.h | 307 +++++++++++++++++++++++- src/modules/silentpayments/tests_impl.h | 254 ++++++++++++++++++++ src/tests.c | 8 + 4 files changed, 669 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..ca13843d61 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,107 @@ 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 if the input private keys sum to zero, if the input private key + * sum is the negation of the spend secret key, if the input_hash + * or hash(shared_secret || k) are invalid scalars, + * or if the arguments are invalid. + * + * 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 number of recipients. This is equal to the + * total number of outputs to be generated as each + * recipient may passed multiple times to generate + * multiple outputs for the same recipient + * 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 number of sender's taproot input secret keys + * 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 number of sender's non-taproot input secret + * keys + */ +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..fb6f8939a1 100644 --- a/src/modules/silentpayments/main_impl.h +++ b/src/modules/silentpayments/main_impl.h @@ -7,10 +7,313 @@ #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; +} + +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); + 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_pubkey(const secp256k1_context *ctx, secp256k1_xonly_pubkey *output_xonly, const unsigned char *shared_secret33, const secp256k1_pubkey *spend_pubkey, uint32_t k) { + secp256k1_ge output_ge; + secp256k1_scalar output_tweak_scalar; + /* 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; + } + if (!secp256k1_pubkey_load(ctx, &output_ge, spend_pubkey)) { + 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(output_xonly, &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; + 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); + } + /* Private 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; + } + if (!secp256k1_silentpayments_create_output_pubkey(ctx, generated_outputs[recipients[i]->index], shared_secret, &recipients[i]->spend_pubkey, k)) { + secp256k1_scalar_clear(&seckey_sum_scalar); + secp256k1_memclear_explicit(&shared_secret, sizeof(shared_secret)); + return 0; + } + VERIFY_CHECK(k < SIZE_MAX); + k++; + 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..dafcac7b3d --- /dev/null +++ b/src/modules/silentpayments/tests_impl.h @@ -0,0 +1,254 @@ +/*********************************************************************** + * 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; + } +} + +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 7c5b1689a3adf6e8a18027d0e5d94a7e629b4852 Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Mon, 22 Jan 2024 18:56:05 +0100 Subject: [PATCH 03/18] 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 ca13843d61..243aedc8ba 100644 --- a/include/secp256k1_silentpayments.h +++ b/include/secp256k1_silentpayments.h @@ -127,6 +127,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 label_tweak32 is an invalid scalar, + * or if the arguments are invalid. + * + * 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: label integer (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 the spend pubkey + label sum to zero, + * or if the arguments are invalid. + * + * 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 fb6f8939a1..7fc9c6dff3 100644 --- a/src/modules/silentpayments/main_impl.h +++ b/src/modules/silentpayments/main_impl.h @@ -316,4 +316,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 dafcac7b3d..ff255cad10 100644 --- a/src/modules/silentpayments/tests_impl.h +++ b/src/modules/silentpayments/tests_impl.h @@ -246,9 +246,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 da3120e9b73f2b1548eae5d07caacc4cb81e19f0 Mon Sep 17 00:00:00 2001 From: josibake Date: Sat, 29 Jun 2024 13:33:30 +0200 Subject: [PATCH 04/18] 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 by exposing the `_create_shared_secret` and `_create_output_pubkey` functions in the API. This means the light client will need to manage their own scanning state, so wherever possible it is preferrable to use the `_recipient_scan_ouputs` function. 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 | 263 +++++++++++++++++ src/modules/silentpayments/main_impl.h | 375 ++++++++++++++++++++++++ src/modules/silentpayments/tests_impl.h | 188 ++++++++++++ 3 files changed, 826 insertions(+) diff --git a/include/secp256k1_silentpayments.h b/include/secp256k1_silentpayments.h index 243aedc8ba..45e51a863d 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,6 +27,7 @@ extern "C" { * any further elliptic-curve operations from the wallet. */ +static const unsigned char secp256k1_silentpayments_prevouts_summary_magic[4] = { 0xa7, 0x1c, 0xd3, 0x5e }; /** The data from a single recipient address * @@ -181,6 +183,267 @@ 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 as a + * compressed public key using + * `secp256k1_silentpayments_recipient_prevouts_summary_serialize`. The serialization is + * intended for sending the prevout summary data to light clients. Light clients + * can use this serialization 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 input public keys sum to zero, + * if the input_hash is an invalid scalar, + * or the arguments are invalid. + * + * 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 number of taproot input public keys + * plain_pubkeys: pointer to an array of pointers to non-taproot + * public keys (can be NULL if no non-taproot + * inputs are used) + * n_plain_pubkeys: the number of non-taproot input public keys + */ +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 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. + * + * Returns: 1 always. + * + * Args: ctx: pointer to a context object + * Out: output33: pointer to a 33-byte array to place the serialized + * `silentpayments_prevouts_summary` in + * In: prevouts_summary: pointer to an initialized silentpayments_prevouts_summary + * object + */ +SECP256K1_API int secp256k1_silentpayments_recipient_prevouts_summary_serialize( + const secp256k1_context *ctx, + unsigned char *output33, + const secp256k1_silentpayments_prevouts_summary *prevouts_summary +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Parse a 33-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 *input33 +) 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 hash(shared_secret || k) is an invalid scalar or the negation of + * either the spend secret key or the label tweak, 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 + * be initialized to be 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 tx's x-only public key outputs + * n_tx_outputs: the number of tx_outputs being scanned + * scan_key32: pointer to the recipient's 32 byte scan key + * 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); + +/** Create Silent Payment shared secret. + * + * Given the public input data (secp256k1_silentpayments_prevouts_summary), + * and the recipient's 32 byte scan key, calculate the shared secret. + * + * The resulting shared secret is needed as input for creating silent payments + * outputs belonging to the same recipient scan public key. This function is + * intended for light clients, i.e., scenarios where the caller does not have + * access to the full transaction. If the caller does have access to the full + * transaction, `secp256k1_silentpayments_recipient_scan_outputs` should be + * used instead. + * + * Returns: 1 if shared secret creation was successful. + * 0 if the arguments are invalid. + * Args: ctx: pointer to a context object + * Out: shared_secret33: pointer to the resulting 33-byte shared secret + * In: scan_key32: pointer to the recipient's 32 byte scan key + * prevouts_summary: pointer to the prevouts_summary object, loaded using + * `_recipient_prevouts_summary_parse` + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_create_shared_secret( + const secp256k1_context *ctx, + unsigned char *shared_secret33, + const unsigned char *scan_key32, + const secp256k1_silentpayments_prevouts_summary *prevouts_summary +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Create Silent Payment output public key. + * + * Given a shared_secret, a recipient spend public key, and an output counter k, + * create an output public key. + * + * This function is used by the recipient when scanning for outputs without + * access to the transaction outputs (e.g., using BIP158 block filters). When + * scanning with this function, it is the scanners responsibility to determine + * if the generated output exists in a block before proceeding to the next + * value of `k`. + * + * Returns: 1 if output creation was successful. + * 0 if hash(shared secret || k) results in an invalid scalar, + * or if the arguments are invalid. + * Args: ctx: pointer to a context object + * Out: output_xonly: pointer to the resulting output x-only public key + * In: shared_secret33: shared secret, derived from either sender's + * or recipient's perspective with routines from + * above + * spend_pubkey: pointer to the recipient's spend public key + * (labeled or unlabeled) + * k: output counter (initially set to 0, must be + * incremented for each additional output created + * or after each output found when scanning) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_create_output_pubkey( + const secp256k1_context *ctx, + secp256k1_xonly_pubkey *output_xonly, + const unsigned char *shared_secret33, + const secp256k1_pubkey *spend_pubkey, + const uint32_t k +) 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 7fc9c6dff3..3d560e93cc 100644 --- a/src/modules/silentpayments/main_impl.h +++ b/src/modules/silentpayments/main_impl.h @@ -386,4 +386,379 @@ 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 *output33, const secp256k1_silentpayments_prevouts_summary *prevouts_summary) { + secp256k1_ge ge; + size_t pubkeylen = 33; + int ret, combined; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(output33 != NULL); + ARG_CHECK(prevouts_summary != NULL); + ARG_CHECK(secp256k1_memcmp_var(&prevouts_summary->data[0], secp256k1_silentpayments_prevouts_summary_magic, 4) == 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, output33, &pubkeylen, 1); +#ifdef VERIFY + VERIFY_CHECK(ret && pubkeylen == 33); +#else + (void)ret; +#endif + return 1; +} + +int secp256k1_silentpayments_recipient_prevouts_summary_parse(const secp256k1_context *ctx, secp256k1_silentpayments_prevouts_summary *prevouts_summary, const unsigned char *input33) { + size_t inputlen = 33; + secp256k1_ge pk; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(prevouts_summary != NULL); + ARG_CHECK(input33 != NULL); + /* 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, input33, inputlen)) { + return 0; + } + /* A serialized prevouts_summary will always have 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, k, n_found, found_idx; + int found, combined, 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); + } + ret = secp256k1_scalar_set_b32_seckey(&scan_key_scalar, scan_key32); + secp256k1_declassify(ctx, &ret, sizeof(ret)); + if (!ret) { + /* Leaking this value would break indistinguishability of the transaction, so clear it. */ + 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); + + found_idx = 0; + n_found = 0; + k = 0; + while (1) { + 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(&scan_key_scalar); + 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(&scan_key_scalar); + 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 (i = 0; i < n_tx_outputs; i++) { + if (secp256k1_xonly_pubkey_cmp(ctx, &output_xonly, tx_outputs[i]) == 0) { + label_tweak = NULL; + found = 1; + found_idx = i; + 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[i]); + secp256k1_gej_set_ge(&tx_output_gej, &tx_output_ge); + secp256k1_ge_neg(&output_negated_ge, &output_ge); + /* Negate the generated output and calculate first scan label candidate: + * label1 = tx_output - generated_output + * + * Note: we can only hit this branch if tx_output != output_xonly. Thus, + * we can add tx_output_gej + output_negated_ge without needing to check + * whether or not the result is the point at infinity. + */ + 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. */ + VERIFY_CHECK(ret && len == 33); + label_tweak = label_lookup(label33, label_context); + if (label_tweak != NULL) { + found = 1; + found_idx = i; + 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 = i; + break; + } + } + } + if (found) { + found_outputs[n_found]->output = *tx_outputs[found_idx]; + secp256k1_scalar_get_b32(found_outputs[n_found]->tweak, &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). + */ + if (!secp256k1_ec_seckey_tweak_add(ctx, found_outputs[n_found]->tweak, label_tweak)) { + secp256k1_scalar_clear(&scan_key_scalar); + secp256k1_scalar_clear(&output_tweak_scalar); + secp256k1_memclear_explicit(&shared_secret, sizeof(shared_secret)); + return 0; + } + 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++; + k++; + } else { + break; + } + } + *n_found_outputs = n_found; + + /* Leaking these values would break indistinguishability of the transaction, so clear them. */ + secp256k1_scalar_clear(&scan_key_scalar); + secp256k1_scalar_clear(&output_tweak_scalar); + secp256k1_memclear_explicit(shared_secret, sizeof(shared_secret)); + return 1; +} + +int secp256k1_silentpayments_recipient_create_shared_secret(const secp256k1_context *ctx, unsigned char *shared_secret33, const unsigned char *scan_key32, const secp256k1_silentpayments_prevouts_summary *prevouts_summary) { + secp256k1_scalar scan_key_scalar; + secp256k1_ge input_pubkey_ge; + int ret, combined; + /* Sanity check inputs */ + ARG_CHECK(shared_secret33 != NULL); + 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); + ret = secp256k1_scalar_set_b32_seckey(&scan_key_scalar, scan_key32); + /* If there are any issues with the recipient scan key, return early. */ + secp256k1_declassify(ctx, &ret, sizeof(ret)); + if (!ret) { + 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_secret33, &input_pubkey_ge, &scan_key_scalar); + + secp256k1_scalar_clear(&scan_key_scalar); + return 1; +} + +int secp256k1_silentpayments_recipient_create_output_pubkey(const secp256k1_context *ctx, secp256k1_xonly_pubkey *output_xonly, const unsigned char *shared_secret33, const secp256k1_pubkey *spend_pubkey, const uint32_t k) +{ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(output_xonly != NULL); + ARG_CHECK(shared_secret33 != NULL); + ARG_CHECK(spend_pubkey != NULL); + return secp256k1_silentpayments_create_output_pubkey(ctx, output_xonly, shared_secret33, spend_pubkey, k); +} + + #endif diff --git a/src/modules/silentpayments/tests_impl.h b/src/modules/silentpayments/tests_impl.h index ff255cad10..15471fd6a4 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]; @@ -292,10 +317,173 @@ 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 o[33]; /* serialized prevouts_summary, serialized shared secret */ + 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, &pd)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_serialize(CTX, o, NULL)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_parse(CTX, NULL, o)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_prevouts_summary_parse(CTX, &pd, NULL)); + + /* 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, o, &md)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_shared_secret(CTX, o, ALICE_SECKEY, &md)); + + /* Check that malformed serializations are rejected */ + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_parse(CTX, &pd, malformed) == 0); + + /* This prevouts_summary object was created with combined = 0, i.e., it has both the input hash and summed public keypair. */ + CHECK(secp256k1_silentpayments_recipient_create_shared_secret(CTX, o, ALICE_SECKEY, &pd)); + + /* 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])); + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_serialize(CTX, o, &pd)); + /* Try to create a shared secret with a malformed recipient scan key (all zeros) */ + CHECK(secp256k1_silentpayments_recipient_create_shared_secret(CTX, o, MALFORMED_SECKEY, &pd) == 0); + /* Try to create a shared secret with a malformed prevouts_summary (all zeros) */ + memset(&pd.data[1], 0, sizeof(pd.data) - 1); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_shared_secret(CTX, o, ALICE_SECKEY, &pd)); + /* Reset pd to a valid prevouts_summary object */ + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_parse(CTX, &pd, BOB_ADDRESS[0])); + + 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_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_shared_secret(CTX, NULL, ALICE_SECKEY, &pd)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_shared_secret(CTX, o, NULL, &pd)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_shared_secret(CTX, o, ALICE_SECKEY, NULL)); + + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_output_pubkey(CTX, NULL, o, &p, 0)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_output_pubkey(CTX, &t, NULL, &p, 0)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_output_pubkey(CTX, &t, o, NULL, 0)); + + /* check the _recipient_create_output_pubkey 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 shared_secret[33]; + const uint32_t k = 0; + secp256k1_scalar output_tweak; + unsigned char output_tweak_ser[32]; + secp256k1_pubkey fake_spend_pubkey; + secp256k1_xonly_pubkey output_xonly; + + CHECK(secp256k1_silentpayments_recipient_create_shared_secret(CTX, shared_secret, ALICE_SECKEY, &pd)); + CHECK(secp256k1_silentpayments_create_output_tweak(&output_tweak, shared_secret, k)); + secp256k1_scalar_get_b32(output_tweak_ser, &output_tweak); + CHECK(secp256k1_ec_pubkey_create(CTX, &fake_spend_pubkey, output_tweak_ser)); + CHECK(secp256k1_ec_pubkey_negate(CTX, &fake_spend_pubkey)); + CHECK(secp256k1_silentpayments_recipient_create_output_pubkey(CTX, &output_xonly, shared_secret, &fake_spend_pubkey, k) == 0); + } + /* check the _recipient_scan_outputs cornercase where internal tweaking would fail; + this is the case if the label_tweak*G is maliciously created to be the negation of the shared secret. */ + { + unsigned char shared_secret[33]; + const uint32_t k = 0; + size_t len = 33; + secp256k1_scalar malformed_label_tweak; + unsigned char malformed_label_tweak_ser[32]; + secp256k1_pubkey malformed_label_pubkey; + secp256k1_xonly_pubkey const *tx_output_ptrs[1]; + secp256k1_xonly_pubkey tx_output; + + /* Start by creating a shared secret, turn that shared secret into an output tweak, and save the negation of the output tweak as a label tweak. */ + CHECK(secp256k1_silentpayments_recipient_create_shared_secret(CTX, shared_secret, ALICE_SECKEY, &pd)); + CHECK(secp256k1_silentpayments_create_output_tweak(&malformed_label_tweak, shared_secret, k)); + secp256k1_scalar_negate(&malformed_label_tweak, &malformed_label_tweak); + secp256k1_scalar_get_b32(malformed_label_tweak_ser, &malformed_label_tweak); + /* Add the maliciously created label to the cache. */ + memcpy(labels_cache.entries[0].label_tweak, malformed_label_tweak_ser, 32); + CHECK(secp256k1_ec_pubkey_create(CTX, &malformed_label_pubkey, malformed_label_tweak_ser)); + CHECK(secp256k1_ec_pubkey_serialize(CTX, labels_cache.entries[0].label, &len, &malformed_label_pubkey, SECP256K1_EC_COMPRESSED)); + /* Scan with the maliciously created labels cache. For the tx_output, we just use the spend public key. Recall that the generated output + * is P = spend_pubkey + label_tweak*G + output_tweak*G, but since the label tweak and output tweak cancel each other out, P = spend_pubkey. */ + labels_cache.entries_used = 1; + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &tx_output, NULL, &p)); + tx_output_ptrs[0] = &tx_output; + CHECK(secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tx_output_ptrs, 1, ALICE_SECKEY, &pd, &p, &label_lookup, &labels_cache) == 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); + } + + 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 0e94f2dd053698e0368ced649f1f06d6fb9ed216 Mon Sep 17 00:00:00 2001 From: josibake Date: Mon, 15 Apr 2024 19:36:29 +0200 Subject: [PATCH 05/18] silentpayments: add examples/silentpayments.c Demonstrate sending, scanning, and light client scanning. --- .gitignore | 1 + Makefile.am | 11 + examples/CMakeLists.txt | 4 + examples/silentpayments.c | 617 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 633 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 a00eba1fb8..983c0dd364 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..f29de0997b --- /dev/null +++ b/examples/silentpayments.c @@ -0,0 +1,617 @@ +/************************************************************************* + * 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_spend_pubkey[33] = { + 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 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 + }, + { + 0x03, 0x0b, 0x9d, 0xd4, 0x9d, 0xf2, 0xc6, 0x85, + 0x23, 0xbb, 0x0c, 0x72, 0xd4, 0xfb, 0x59, 0xb6, + 0x4c, 0xe5, 0xc9, 0xa9, 0x33, 0x6d, 0x0b, 0xef, + 0x94, 0x9e, 0xe0, 0x77, 0x5b, 0xea, 0x61, 0xef, 0x05 + } +}; +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]; + int ret; + size_t i; + + /* 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); + + /*** 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 private 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 private 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 mutltiple 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]; + } + for (i = 0; i < N_OUTPUTS; i++) { + tx_output_ptrs[i] = &tx_outputs[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, try again with different inputs.\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]; + + 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]; + } + + { + /*** 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` + * + */ + 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 spend_pubkey; + secp256k1_pubkey labeled_spend_pubkey; + secp256k1_pubkey address_labeled_spend_pubkey; + size_t n_found_outputs; + struct labels_cache labels_cache; + + for (i = 0; i < N_OUTPUTS; i++) { + found_output_ptrs[i] = &found_outputs[i]; + } + { + /** Labels setup + * + * These steps are only necessary if the recipient is using the + * optional labels feature. 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); + * + * In this example, since Bob has access to the full transaction + * outputs when scanning, it's easy for him to scan with labels, + * as demonstrated below. For efficient scanning, Bob keeps a + * cache of every label he has previously used and uses a + * callback to check if a potential label exists in his cache. + * Since the labels are created using an incremental integer + * `m`, if Bob ever forgets how many labels he has previously + * used, he can pregenerate a large number of labels, e.g., + * 0..100_000. + */ + size_t len = 33; + secp256k1_pubkey label; + unsigned int m = 1; + + /* Load Bob's spend public key */ + ret = secp256k1_ec_pubkey_parse(ctx, + &spend_pubkey, + bob_spend_pubkey, + 33 + ); + assert(ret); + + /* Add an entry to the cache. This implies Bob has previously + * called + * `secp256k1_silentpayments_recipient_create_labeled_spend_pubkey` + * and is using the resulting labeled spend pubkey to encode a + * labeled silent payments address. + */ + ret = secp256k1_silentpayments_recipient_create_label(ctx, + &label, + labels_cache.entries[0].label_tweak, + bob_scan_key, + m + ); + if (!ret) { + printf("Something went wrong, the resulting label_tweak is not a valid scalar. Try again with m++.\n"); + return EXIT_FAILURE; + } + ret = secp256k1_ec_pubkey_serialize(ctx, + labels_cache.entries[0].label, + &len, + &label, + SECP256K1_EC_COMPRESSED + ); + assert(ret); + labels_cache.entries_used = 1; + + /* Verify the label we just created and added to the cache is the + * same one used in Bob's silent payment address. + */ + ret = secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(ctx, + &labeled_spend_pubkey, + &spend_pubkey, + &label + ); + if (!ret) { + printf("Something went wrong, the choice of label and spend public key is invalid.\n"); + return EXIT_FAILURE; + } + + /* Load the labeled spend public key from Bob's address */ + ret = secp256k1_ec_pubkey_parse(ctx, + &address_labeled_spend_pubkey, + bob_address[1], + 33 + ); + assert(ret); + if (secp256k1_ec_pubkey_cmp(ctx, &labeled_spend_pubkey, &address_labeled_spend_pubkey) != 0) { + printf("Something went wrong, the labeled spend public key does not match Bob's address.\n"); + return EXIT_FAILURE; + }; + } + + /* 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 /* NULL because no eligible plain pubkey inputs were found in the tx */ + ); + if (!ret) { + /* We need to always check that the prevouts data object is valid + * before proceeding, since a malicious actor could create a transaction + * such that the input public keys sum to the point at infinity, which + * could cause our node to crash if, e.g., we assume that prevouts_summary_create + * will always succeed." + */ + 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, + &prevouts_summary + ); + assert(ret); + + /* Scan the transaction */ + 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, + &spend_pubkey, + label_lookup, &labels_cache /* NULL, NULL for no labels */ + ); + if (!ret) { + /* Since we've already validated the prevouts data, this shouldn't fail, but + * better to be careful here since we are scanning data that could have been + * maliciously created. + */ + printf("Something went wrong while scanning this transaction, 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 outputs. This means she will need to first generate + * an output, check if it exists in the UTXO set (e.g. BIP158 or + * some other means of querying) and only proceed to check the next + * output (by incrementing `k`) if the first output exists. It's + * also difficult for Carol to efficiently scan for labels without + * the transaction outputs, but Carol can still use labels as a + * light client by pregenerating all possible labels and adding them + * to the generated output (i.e., `k = 0`). Once at least one output + * is found, she can request the full block and scan the full + * transaction. This assumes Carol will only use a small number of + * of labels as a light client. + * + * Additionally, 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`. + * + * In practice, Carol wouldn't know the number of found outputs ahead of + * time but we are cheating here to keep the example simple. + * + */ + unsigned char ser_found_outputs[2][32]; + unsigned char shared_secret[33]; + secp256k1_pubkey spend_pubkey; + secp256k1_silentpayments_prevouts_summary prevouts_summary; + size_t n_found_outputs; + + /* Load Carol's spend public key */ + ret = secp256k1_ec_pubkey_parse(ctx, + &spend_pubkey, + carol_address[1], + 33 + ); + assert(ret); + + /* Scan one output at a time, using the serialized `prevouts_summary` + * created by Bob's full node + */ + ret = secp256k1_silentpayments_recipient_prevouts_summary_parse(ctx, + &prevouts_summary, + light_client_data33 + ); + if (!ret) { + printf("\n"); + printf("This transaction is not valid for silent payments, skipping."); + return EXIT_SUCCESS; + } + ret = secp256k1_silentpayments_recipient_create_shared_secret(ctx, + shared_secret, + carol_scan_key, + &prevouts_summary + ); + /* Since we've already validated the prevouts data, the only reason this could fail + * is if we input a bad scan key or bad spend public key, which should never happen + * because this is data under our control. + */ + assert(ret); + n_found_outputs = 0; + { + int found = 0; + size_t k = 0; + secp256k1_xonly_pubkey potential_output; + + while (1) { + ret = secp256k1_silentpayments_recipient_create_output_pubkey(ctx, + &potential_output, + shared_secret, + &spend_pubkey, + k + ); + /* We can only fail here if the particular set of inputs results in the + * output of a hash function resulting in an invalid scalar. This is + * statistically improbable for an honest execution, but if we were to + * get to this point that means this is not a silent payments transaction + * (or if it is, the sender was not following the protocol). + */ + 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 + */ + found = 0; + for (i = 0; i < N_OUTPUTS; i++) { + if (secp256k1_xonly_pubkey_cmp(ctx, &potential_output, &tx_outputs[i]) == 0) { + ret = secp256k1_xonly_pubkey_serialize(ctx, + ser_found_outputs[n_found_outputs], + &potential_output + ); + assert(ret); + /* If found, create a new output with k++ and check + * again */ + found = 1; + n_found_outputs++; + k++; + break; + } + } + /* If we generate an output and it does not exist in the + * UTXO set, we are done scanning this transaction */ + if (!found) { + break; + } + } + } + + 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(" "); + print_hex(ser_found_outputs[i], 32); + } + } 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 a47037242d73cea57a9cabe90615d0f475024671 Mon Sep 17 00:00:00 2001 From: josibake Date: Thu, 25 Apr 2024 19:33:35 +0200 Subject: [PATCH 06/18] 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 | 167 ++++++++++++++++++ 4 files changed, 218 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..0bfd5576b0 --- /dev/null +++ b/src/modules/silentpayments/bench_impl.h @@ -0,0 +1,167 @@ +/*********************************************************************** + * 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_pubkey; + 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_pubkey, 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, k = 0; + bench_silentpayments_data *data = (bench_silentpayments_data*)arg; + secp256k1_silentpayments_prevouts_summary prevouts_summary; + + for (i = 0; i < iters; i++) { + unsigned char shared_secret[33]; + secp256k1_xonly_pubkey xonly_output; + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_parse(data->ctx, &prevouts_summary, data->input_pubkey33)); + CHECK(secp256k1_silentpayments_recipient_create_shared_secret(data->ctx, + shared_secret, + data->scan_key, + &prevouts_summary + )); + CHECK(secp256k1_silentpayments_recipient_create_output_pubkey(data->ctx, + &xonly_output, + shared_secret, + &data->spend_pubkey, + k + )); + } +} + +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_pubkey, + 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 60f209cc238e5e445e3adb812fdecaa16b285698 Mon Sep 17 00:00:00 2001 From: josibake Date: Tue, 2 Jul 2024 19:45:43 +0200 Subject: [PATCH 07/18] 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 | 259 ++ src/modules/silentpayments/vectors.h | 1918 ++++++++++ tools/tests_silentpayments_generate.py | 279 ++ 6 files changed, 5652 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 983c0dd364..e1c6fba3fb 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_ECDH TESTVECTORS += src/wycheproof/ecdh_secp256k1_test.h @@ -273,6 +274,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 15471fd6a4..a44f9752d5 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]; @@ -479,11 +494,255 @@ static void test_recipient_api(void) { CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, ALICE_SECKEY, &pd, &p, NULL, NULL)); } +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]; + unsigned char found_outputs_light_client[MAX_OUTPUTS_PER_TEST_CASE][32]; + 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, prevouts_summary_index; + unsigned char shared_secret_lightclient[33]; + unsigned char light_client_data[33]; + + + /* 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); + /* Scan as a light client + * it is not recommended to use labels as a light client so here we are only + * running this on tests that do not involve labels. Primarily, this test is to + * ensure that _recipient_created_shared_secret and _create_shared_secret are the same + */ + if (test->num_labels == 0) { + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_serialize(CTX, light_client_data, &prevouts_summary)); + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_parse(CTX, &prevouts_summary_index, light_client_data)); + CHECK(secp256k1_silentpayments_recipient_create_shared_secret(CTX, shared_secret_lightclient, test->scan_seckey, &prevouts_summary_index)); + n_found = 0; + { + int found = 0; + size_t k = 0; + secp256k1_xonly_pubkey potential_output; + + while(1) { + + CHECK(secp256k1_silentpayments_recipient_create_output_pubkey(CTX, + &potential_output, + shared_secret_lightclient, + &recipient_spend_pubkey, + k + )); + /* 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 pubkeys */ + found = 0; + for (i = 0; i < test->num_to_scan_outputs; i++) { + if (secp256k1_xonly_pubkey_cmp(CTX, &potential_output, tx_outputs[i]) == 0) { + secp256k1_xonly_pubkey_serialize(CTX, found_outputs_light_client[n_found], &potential_output); + found = 1; + n_found++; + k++; + break; + } + } + if (!found) { + break; + } + } + } + CHECK(n_found == test->num_found_output_pubkeys); + for (i = 0; i < n_found; i++) { + match = 0; + for (j = 0; j < test->num_found_output_pubkeys; j++) { + if (secp256k1_memcmp_var(&found_outputs_light_client[i], test->found_output_pubkeys[j], 32) == 0) { + match = 1; + break; + } + } + CHECK(match); + } + } +} + +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 eba792a5be6407461d86bdca11723dd53d88d013 Mon Sep 17 00:00:00 2001 From: josibake Date: Thu, 7 Nov 2024 13:18:59 +0100 Subject: [PATCH 08/18] 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 | 78 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/ctime_tests.c b/src/ctime_tests.c index f81bdb9228..0abf950567 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,26 @@ 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]; + unsigned char shared_secret[33] = { 0 }; +#endif for (i = 0; i < 32; i++) { msg[i] = i + 1; @@ -263,5 +287,59 @@ 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)); + CHECK(secp256k1_silentpayments_recipient_create_shared_secret(ctx, shared_secret, key, &prevouts_summary)); + CHECK(secp256k1_silentpayments_recipient_create_output_pubkey(ctx, &xonly_pubkey, shared_secret, &recipient.spend_pubkey, 0)); + #endif } From f5c861610dafc1f256e928b9387c99df3f7f5562 Mon Sep 17 00:00:00 2001 From: josibake Date: Fri, 15 Aug 2025 09:46:46 +0100 Subject: [PATCH 09/18] 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 a44f9752d5..0f71f2ed6b 100644 --- a/src/modules/silentpayments/tests_impl.h +++ b/src/modules/silentpayments/tests_impl.h @@ -727,6 +727,29 @@ void run_silentpayments_test_vector_receive(const struct bip352_test_vector *tes } } +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; @@ -743,6 +766,7 @@ void run_silentpayments_tests(void) { test_label_api(); test_recipient_api(); run_silentpayments_test_vectors(); + silentpayments_sha256_tag_test(); } #endif From 4301cdcc6b5711129c1f9182f6af8045ee180a8b Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Fri, 23 Feb 2024 00:25:41 +0100 Subject: [PATCH 10/18] 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 c4942d3646a58bb9fd8a51e60defe1571f8ee595 Mon Sep 17 00:00:00 2001 From: josibake Date: Fri, 12 Jul 2024 14:57:54 +0200 Subject: [PATCH 11/18] 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. From 90630fb28447e020eee1019847034bec0119273d Mon Sep 17 00:00:00 2001 From: stratospher <44024636+stratospher@users.noreply.github.com> Date: Fri, 22 Nov 2024 13:03:52 +0530 Subject: [PATCH 12/18] add dleq implementation - modify secp256k1-zkp's dleq implementation to be consistent with BIP 374. - use BIP374 notations. - add DLEQ tests --- .../silentpayments/Makefile.am.include | 1 + src/modules/silentpayments/dleq_impl.h | 235 ++++++++++++++++++ src/modules/silentpayments/tests_impl.h | 109 ++++++++ 3 files changed, 345 insertions(+) create mode 100644 src/modules/silentpayments/dleq_impl.h diff --git a/src/modules/silentpayments/Makefile.am.include b/src/modules/silentpayments/Makefile.am.include index d0c51dc337..08186253fc 100644 --- a/src/modules/silentpayments/Makefile.am.include +++ b/src/modules/silentpayments/Makefile.am.include @@ -1,5 +1,6 @@ include_HEADERS += include/secp256k1_silentpayments.h noinst_HEADERS += src/modules/silentpayments/main_impl.h +noinst_HEADERS += src/modules/silentpayments/dleq_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/dleq_impl.h b/src/modules/silentpayments/dleq_impl.h new file mode 100644 index 0000000000..48014485a5 --- /dev/null +++ b/src/modules/silentpayments/dleq_impl.h @@ -0,0 +1,235 @@ +#ifndef SECP256K1_DLEQ_IMPL_H +#define SECP256K1_DLEQ_IMPL_H + +/* Initializes SHA256 with fixed midstate. This midstate was computed by applying + * SHA256 to SHA256("BIP0374/aux")||SHA256("BIP0374/aux"). */ +static void secp256k1_nonce_function_bip374_sha256_tagged_aux(secp256k1_sha256 *sha) { + secp256k1_sha256_initialize(sha); + sha->s[0] = 0x48479343ul; + sha->s[1] = 0xa9eb648cul; + sha->s[2] = 0x58952fe4ul; + sha->s[3] = 0x4772d3b2ul; + sha->s[4] = 0x977ab0a0ul; + sha->s[5] = 0xcb8e2740ul; + sha->s[6] = 0x60bb4b81ul; + sha->s[7] = 0x68a41b66ul; + + sha->bytes = 64; +} + +/* Initializes SHA256 with fixed midstate. This midstate was computed by applying + * SHA256 to SHA256("BIP0374/nonce")||SHA256("BIP0374/nonce"). */ +static void secp256k1_nonce_function_bip374_sha256_tagged(secp256k1_sha256 *sha) { + secp256k1_sha256_initialize(sha); + sha->s[0] = 0xa810fc87ul; + sha->s[1] = 0x3b4a4d2aul; + sha->s[2] = 0xe302cfb4ul; + sha->s[3] = 0x322df1a0ul; + sha->s[4] = 0xd2e7fb82ul; + sha->s[5] = 0x7808570dul; + sha->s[6] = 0x9c33e0cdul; + sha->s[7] = 0x2dfbf7f6ul; + + sha->bytes = 64; +} + +/* Initializes SHA256 with fixed midstate. This midstate was computed by applying + * SHA256 to SHA256("BIP0374/challenge")||SHA256("BIP0374/challenge"). */ +static void secp256k1_dleq_sha256_tagged(secp256k1_sha256 *sha) { + secp256k1_sha256_initialize(sha); + sha->s[0] = 0x24f1c9c7ul; + sha->s[1] = 0xd1538c75ul; + sha->s[2] = 0xc9874ae8ul; + sha->s[3] = 0x6566de76ul; + sha->s[4] = 0x487843c9ul; + sha->s[5] = 0xc13d8026ul; + sha->s[6] = 0x39a2f3eful; + sha->s[7] = 0x2ad0fcb3ul; + + sha->bytes = 64; +} + +static int secp256k1_dleq_hash_point(secp256k1_sha256 *sha, secp256k1_ge *p) { + unsigned char buf[33]; + size_t size = 33; + if (!secp256k1_eckey_pubkey_serialize(p, buf, &size, 1)) { + return 0; + } + secp256k1_sha256_write(sha, buf, size); + return 1; +} + +static void secp256k1_nonce_function_dleq(unsigned char *nonce32, const unsigned char *msg, size_t msglen, const unsigned char *key32, const unsigned char *aux_rand32) { + secp256k1_sha256 sha; + unsigned char masked_key[32]; + int i; + + if (aux_rand32 != NULL) { + secp256k1_nonce_function_bip374_sha256_tagged_aux(&sha); + secp256k1_sha256_write(&sha, aux_rand32, 32); + secp256k1_sha256_finalize(&sha, masked_key); + for (i = 0; i < 32; i++) { + masked_key[i] ^= key32[i]; + } + } else { + /* Precomputed TaggedHash("BIP0374/aux", 0x0000...00); */ + static const unsigned char ZERO_MASK[32] = { + 38, 255, 199, 133, 21, 94, 75, 99, + 18, 166, 0, 53, 197, 146, 253, 84, + 197, 228, 235, 145, 124, 59, 203, 21, + 66, 88, 250, 253, 207, 123, 43, 55 + }; + for (i = 0; i < 32; i++) { + masked_key[i] = key32[i] ^ ZERO_MASK[i]; + } + } + + secp256k1_nonce_function_bip374_sha256_tagged(&sha); + /* Hash masked-key||msg using the tagged hash as per the spec */ + secp256k1_sha256_write(&sha, masked_key, 32); + secp256k1_sha256_write(&sha, msg, msglen); + secp256k1_sha256_finalize(&sha, nonce32); +} + +/* Generates a nonce as defined in BIP0374 */ +static int secp256k1_dleq_nonce(secp256k1_scalar *k, const unsigned char *a32, const unsigned char *A_33, const unsigned char *C_33, const unsigned char *aux_rand32) { + unsigned char buf[66]; + unsigned char nonce[32]; + + memcpy(buf, A_33, 33); + memcpy(buf + 33, C_33, 33); + secp256k1_nonce_function_dleq(nonce, buf, 66, a32, aux_rand32); + + secp256k1_scalar_set_b32(k, nonce, NULL); + if (secp256k1_scalar_is_zero(k)) { + return 0; + } + + return 1; +} + +/* Generates a challenge as defined in BIP0374 */ +static void secp256k1_dleq_challenge(secp256k1_scalar *e, secp256k1_ge *B, secp256k1_ge *R1, secp256k1_ge *R2, secp256k1_ge *A, secp256k1_ge *C, const unsigned char *m) { + unsigned char buf[32]; + secp256k1_sha256 sha; + secp256k1_ge generator_point = secp256k1_ge_const_g; + + secp256k1_dleq_sha256_tagged(&sha); + secp256k1_dleq_hash_point(&sha, A); + secp256k1_dleq_hash_point(&sha, B); + secp256k1_dleq_hash_point(&sha, C); + secp256k1_dleq_hash_point(&sha, &generator_point); + secp256k1_dleq_hash_point(&sha, R1); + secp256k1_dleq_hash_point(&sha, R2); + if (m) secp256k1_sha256_write(&sha, m, 32); + secp256k1_sha256_finalize(&sha, buf); + + secp256k1_scalar_set_b32(e, buf, NULL); +} + +/* Generate points from scalar a such that A = a*G and C = a*B */ +static void secp256k1_dleq_pair(const secp256k1_ecmult_gen_context *ecmult_gen_ctx, secp256k1_ge *A, secp256k1_ge *C, const secp256k1_scalar *a, const secp256k1_ge *B) { + secp256k1_gej Aj, Cj; + + secp256k1_ecmult_gen(ecmult_gen_ctx, &Aj, a); + secp256k1_ge_set_gej(A, &Aj); + secp256k1_ecmult_const(&Cj, B, a); + secp256k1_ge_set_gej(C, &Cj); +} + +/* DLEQ Proof Generation + * + * For given elliptic curve points A, B, C, and G, the prover generates a proof to prove knowledge of a scalar a such + * that A = aâ‹…G and C = aâ‹…B without revealing anything about a. + * + * Returns: 1 if proof creation was successful. 0 if an error occurred. + * Out: scalar e: part of proof = bytes(32, e) || bytes(32, s). + * scalar s: other part of proof = bytes(32, e) || bytes(32, s). + * In: a : scalar a to be proven that both A and C were generated from + * B : point on the curve + * A : point on the curve(aâ‹…G) generated from a + * C : point on the curve(aâ‹…B) generated from a + * aux_rand32 : pointer to 32-byte auxiliary randomness used to generate the nonce in secp256k1_nonce_function_dleq. + * m : an optional message + * */ +static int secp256k1_dleq_prove(const secp256k1_context *ctx, secp256k1_scalar *s, secp256k1_scalar *e, const secp256k1_scalar *a, secp256k1_ge *B, secp256k1_ge *A, secp256k1_ge *C, const unsigned char *aux_rand32, const unsigned char *m) { + secp256k1_ge R1, R2; + secp256k1_scalar k = { 0 }; + unsigned char a32[32]; + unsigned char A_33[33]; + unsigned char B_33[33]; + unsigned char C_33[33]; + int ret = 1; + size_t pubkey_size = 33; + + secp256k1_scalar_get_b32(a32, a); + if (!secp256k1_eckey_pubkey_serialize(B, B_33, &pubkey_size, 1)) { + return 0; + } + if (!secp256k1_eckey_pubkey_serialize(A, A_33, &pubkey_size, 1)) { + return 0; + } + if (!secp256k1_eckey_pubkey_serialize(C, C_33, &pubkey_size, 1)) { + return 0; + } + ret &= secp256k1_dleq_nonce(&k, a32, A_33, C_33, aux_rand32); + + /* R1 = k*G, R2 = k*B */ + secp256k1_dleq_pair(&ctx->ecmult_gen_ctx, &R1, &R2, &k, B); + /* We declassify the non-secret values R1 and R2 to allow using them as + * branch points. */ + secp256k1_declassify(ctx, &R1, sizeof(R1)); + secp256k1_declassify(ctx, &R2, sizeof(R2)); + + /* e = tagged hash(A, B, C, R1, R2) */ + /* s = k + e * a */ + secp256k1_dleq_challenge(e, B, &R1, &R2, A, C, m); + secp256k1_scalar_mul(s, e, a); + secp256k1_scalar_add(s, s, &k); + + secp256k1_scalar_clear(&k); + return ret; +} + +/* DLEQ Proof Verification + * + * Verifies the proof. If the following algorithm succeeds, the points A and C were both generated from the same scalar. + * The former from multiplying by G, and the latter from multiplying by B. + * + * Returns: 1 if proof verification was successful. 0 if an error occurred. + * In: proof : proof bytes(32, e) || bytes(32, s) consists of scalar e and scalar s + * A : point on the curve(aâ‹…G) computed from a + * B : point on the curve + * C : point on the curve(aâ‹…B) computed from a + * m : optional message + * */ +static int secp256k1_dleq_verify(secp256k1_scalar *s, secp256k1_scalar *e, secp256k1_ge *A, secp256k1_ge *B, secp256k1_ge *C, const unsigned char *m) { + secp256k1_scalar e_neg; + secp256k1_scalar e_expected; + secp256k1_gej Bj; + secp256k1_gej Aj, Cj; + secp256k1_gej R1j, R2j; + secp256k1_ge R1, R2; + secp256k1_gej tmpj; + + secp256k1_gej_set_ge(&Aj, A); + secp256k1_gej_set_ge(&Cj, C); + + secp256k1_scalar_negate(&e_neg, e); + /* R1 = s*G - e*A */ + secp256k1_ecmult(&R1j, &Aj, &e_neg, s); + /* R2 = s*B - e*C */ + secp256k1_ecmult(&tmpj, &Cj, &e_neg, &secp256k1_scalar_zero); + secp256k1_gej_set_ge(&Bj, B); + secp256k1_ecmult(&R2j, &Bj, s, &secp256k1_scalar_zero); + secp256k1_gej_add_var(&R2j, &R2j, &tmpj, NULL); + + secp256k1_ge_set_gej(&R1, &R1j); + secp256k1_ge_set_gej(&R2, &R2j); + secp256k1_dleq_challenge(&e_expected, B, &R1, &R2, A, C, m); + + secp256k1_scalar_add(&e_expected, &e_expected, &e_neg); + return secp256k1_scalar_is_zero(&e_expected); +} + +#endif diff --git a/src/modules/silentpayments/tests_impl.h b/src/modules/silentpayments/tests_impl.h index 0f71f2ed6b..06133090bf 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/dleq_impl.h" #include "../../../src/modules/silentpayments/vectors.h" /** Constants @@ -760,6 +761,113 @@ void run_silentpayments_test_vectors(void) { } } +static void dleq_nonce_bitflip(unsigned char **args, size_t n_flip, size_t n_bytes) { + secp256k1_scalar k1, k2; + CHECK(secp256k1_dleq_nonce(&k1, args[0], args[1], args[2], args[3]) == 1); + testrand_flip(args[n_flip], n_bytes); + CHECK(secp256k1_dleq_nonce(&k2, args[0], args[1], args[2], args[3]) == 1); + CHECK(secp256k1_scalar_eq(&k1, &k2) == 0); +} + +static void dleq_tests(void) { + secp256k1_scalar s, e, a, k; + secp256k1_ge A, B, C; + unsigned char *args[4]; + unsigned char a32[32]; + unsigned char A_33[33]; + unsigned char C_33[33]; + unsigned char aux_rand[32]; + unsigned char msg[32]; + unsigned char proof_64[64] = {0}; + int i; + size_t pubkey_size = 33; + int overflow; + secp256k1_sha256 sha; + secp256k1_sha256 sha_optimized; + unsigned char aux_tag[] = {'B', 'I', 'P', '0', '3', '7', '4', '/', 'a', 'u', 'x'}; + unsigned char tag[] = {'B', 'I', 'P', '0', '3', '7', '4', '/', 'n', 'o', 'n', 'c', 'e'}; + unsigned char challenge_tag[] = {'B', 'I', 'P', '0', '3', '7', '4', '/', 'c', 'h', 'a', 'l', 'l', 'e', 'n', 'g', 'e'}; + + /* Check that hash initialized by secp256k1_nonce_function_bip374_sha256_tagged_aux has the expected state. */ + secp256k1_sha256_initialize_tagged(&sha, aux_tag, sizeof(aux_tag)); + secp256k1_nonce_function_bip374_sha256_tagged_aux(&sha_optimized); + test_sha256_eq(&sha, &sha_optimized); + + /* Check that hash initialized by secp256k1_nonce_function_bip374_sha256_tagged has the expected state. */ + secp256k1_sha256_initialize_tagged(&sha, tag, sizeof(tag)); + secp256k1_nonce_function_bip374_sha256_tagged(&sha_optimized); + test_sha256_eq(&sha, &sha_optimized); + + /* Check that hash initialized by secp256k1_dleq_sha256_tagged has the expected state. */ + secp256k1_sha256_initialize_tagged(&sha, challenge_tag, sizeof(challenge_tag)); + secp256k1_dleq_sha256_tagged(&sha_optimized); + test_sha256_eq(&sha, &sha_optimized); + + for (i = 0; i < COUNT; i++) { + testutil_random_ge_test(&B); + testutil_random_scalar_order(&a); + testrand256(aux_rand); + testrand_bytes_test(msg, sizeof(msg)); + secp256k1_dleq_pair(&CTX->ecmult_gen_ctx, &A, &C, &a, &B); + CHECK(secp256k1_dleq_prove(CTX, &s, &e, &a, &B, &A, &C, aux_rand, (i & 1) ? msg : NULL) == 1); + CHECK(secp256k1_dleq_verify(&s, &e, &A, &B, &C, (i & 1) ? msg : NULL) == 1); + secp256k1_scalar_set_b32(&s, proof_64, &overflow); + VERIFY_CHECK(overflow == 0); + secp256k1_scalar_set_b32(&e, proof_64 + 32, &overflow); + VERIFY_CHECK(overflow == 0); + } + + { + secp256k1_scalar tmp; + secp256k1_scalar_set_int(&tmp, 1); + CHECK(secp256k1_dleq_verify(&tmp, &e, &A, &B, &C, msg) == 0); + CHECK(secp256k1_dleq_verify(&s, &tmp, &A, &B, &C, msg) == 0); + } + { + secp256k1_ge p_tmp; + testutil_random_ge_test(&p_tmp); + CHECK(secp256k1_dleq_verify(&s, &e, &p_tmp, &B, &C, msg) == 0); + CHECK(secp256k1_dleq_verify(&s, &e, &A, &p_tmp, &C, msg) == 0); + CHECK(secp256k1_dleq_verify(&s, &e, &A, &B, &p_tmp, msg) == 0); + } + { + secp256k1_ge p_inf; + secp256k1_ge_set_infinity(&p_inf); + CHECK(secp256k1_dleq_prove(CTX, &s, &e, &a, &p_inf, &A, &C, aux_rand, msg) == 0); + CHECK(secp256k1_dleq_prove(CTX, &s, &e, &a, &B, &p_inf, &C, aux_rand, msg) == 0); + CHECK(secp256k1_dleq_prove(CTX, &s, &e, &a, &B, &A, &p_inf, aux_rand, msg) == 0); + } + + /* Nonce tests */ + secp256k1_scalar_get_b32(a32, &a); + CHECK(secp256k1_eckey_pubkey_serialize(&A, A_33, &pubkey_size, 1)); + CHECK(secp256k1_eckey_pubkey_serialize(&C, C_33, &pubkey_size, 1)); + CHECK(secp256k1_dleq_nonce(&k, a32, A_33, C_33, aux_rand) == 1); + + testrand_bytes_test(a32, sizeof(a32)); + testrand_bytes_test(A_33, sizeof(A_33)); + testrand_bytes_test(C_33, sizeof(C_33)); + testrand_bytes_test(aux_rand, sizeof(aux_rand)); + + /* Check that a bitflip in an argument results in different nonces. */ + args[0] = a32; + args[1] = A_33; + args[2] = C_33; + args[3] = aux_rand; + for (i = 0; i < COUNT; i++) { + dleq_nonce_bitflip(args, 0, sizeof(a32)); + dleq_nonce_bitflip(args, 1, sizeof(A_33)); + /* Flip C */ + dleq_nonce_bitflip(args, 2, sizeof(C_33)); + /* Flip C again */ + dleq_nonce_bitflip(args, 2, sizeof(C_33)); + dleq_nonce_bitflip(args, 3, sizeof(aux_rand)); + } + + /* NULL aux_rand argument is allowed.*/ + CHECK(secp256k1_dleq_nonce(&k, a32, A_33, C_33, NULL) == 1); +} + void run_silentpayments_tests(void) { test_recipient_sort(); test_send_api(); @@ -767,6 +875,7 @@ void run_silentpayments_tests(void) { test_recipient_api(); run_silentpayments_test_vectors(); silentpayments_sha256_tag_test(); + dleq_tests(); } #endif From 518c459a41d43d78ec9665f59b074a28084a435d Mon Sep 17 00:00:00 2001 From: stratospher <44024636+stratospher@users.noreply.github.com> Date: Tue, 28 Jan 2025 13:25:21 +0530 Subject: [PATCH 13/18] add BIP374 test vectors Add BIP374 test vectors. The vectors are generated with a Python script that converts the 2 csv files from the BIP - test_vectors_generate_proof.csv and test_vectors_verify_proof.csv to C code: $ ./tools/test_vectors_dleq_generate.py bips/bip-0374 > ./src/modules/silentpayments/dleq_vectors.h --- src/modules/silentpayments/dleq_vectors.h | 105 ++++++++++++++++ src/modules/silentpayments/tests_impl.h | 75 +++++++++++ tools/test_vectors_dleq_generate.py | 146 ++++++++++++++++++++++ 3 files changed, 326 insertions(+) create mode 100644 src/modules/silentpayments/dleq_vectors.h create mode 100644 tools/test_vectors_dleq_generate.py diff --git a/src/modules/silentpayments/dleq_vectors.h b/src/modules/silentpayments/dleq_vectors.h new file mode 100644 index 0000000000..420d2a021b --- /dev/null +++ b/src/modules/silentpayments/dleq_vectors.h @@ -0,0 +1,105 @@ +/** + * Automatically generated by tools/test_vectors_dleq_generate.py. + * + * Test vectors according to BIP-374 ("Discrete Log Equality Proofs") are included in this file. + * Tests are included in src/modules/silentpayments/tests_impl.h. */ + +static const unsigned char a_bytes[6][32] = { + { 0xC0, 0x8C, 0xA8, 0xE0, 0xBB, 0x59, 0x76, 0x9F, 0xC6, 0xA4, 0xE0, 0x78, 0x45, 0x62, 0x84, 0xE0, 0x0E, 0xA3, 0x4F, 0x65, 0xAD, 0xD9, 0x88, 0xC2, 0x46, 0xE1, 0xBB, 0xA8, 0x58, 0x24, 0xCC, 0xDC }, + { 0x8E, 0x64, 0x1B, 0xA6, 0xBF, 0x7F, 0x64, 0xEE, 0xC7, 0x60, 0x05, 0xA2, 0x95, 0x85, 0xA5, 0x03, 0x53, 0x76, 0x37, 0x5F, 0x33, 0xE3, 0x31, 0x21, 0x5A, 0xED, 0xFE, 0x03, 0xB8, 0xE8, 0x0E, 0x7A }, + { 0xCF, 0xB9, 0xA7, 0xEC, 0xC4, 0x9B, 0xEA, 0x4F, 0x2E, 0x2E, 0xE3, 0x4C, 0x38, 0xA6, 0xF4, 0x8B, 0x5C, 0xD5, 0xBD, 0x06, 0xF4, 0xE4, 0xD4, 0xFF, 0xB4, 0x59, 0x05, 0xB3, 0xD2, 0x6D, 0xB8, 0x42 }, + { 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 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x41 }, + { 0xCF, 0xB9, 0xA7, 0xEC, 0xC4, 0x9B, 0xEA, 0x4F, 0x2E, 0x2E, 0xE3, 0x4C, 0x38, 0xA6, 0xF4, 0x8B, 0x5C, 0xD5, 0xBD, 0x06, 0xF4, 0xE4, 0xD4, 0xFF, 0xB4, 0x59, 0x05, 0xB3, 0xD2, 0x6D, 0xB8, 0x42 }, +}; + +static const unsigned char A_bytes[13][33] = { + { 0x02, 0x63, 0x7B, 0x2C, 0x3E, 0xA8, 0xCA, 0x80, 0xB9, 0xCA, 0xEC, 0xC5, 0x0F, 0x41, 0x34, 0xC8, 0x6A, 0xE9, 0xCF, 0x7A, 0x26, 0x91, 0x33, 0xE7, 0xAF, 0xC7, 0x1F, 0x30, 0xE3, 0xA3, 0xCD, 0xA6, 0x0C }, + { 0x02, 0x98, 0x3A, 0x72, 0xB4, 0xCB, 0x44, 0xD4, 0x32, 0x26, 0x41, 0xA7, 0xB2, 0x00, 0x19, 0x00, 0xCD, 0x6A, 0xE0, 0x90, 0x8A, 0x61, 0x05, 0x46, 0xC7, 0x3E, 0xD1, 0x26, 0xAC, 0xCD, 0xBA, 0x05, 0x14 }, + { 0x03, 0x61, 0x14, 0x10, 0x56, 0x1C, 0x35, 0xDA, 0xE1, 0x31, 0x35, 0xE4, 0xAD, 0x80, 0x94, 0xBA, 0xAC, 0x9B, 0xBC, 0xF2, 0xF4, 0xE1, 0x84, 0x98, 0x18, 0x1A, 0x8F, 0xF8, 0xA6, 0xD4, 0x3B, 0xE9, 0xD9 }, + { 0x00 }, + { 0x00 }, + { 0x00 }, + { 0x03, 0x61, 0x14, 0x10, 0x56, 0x1C, 0x35, 0xDA, 0xE1, 0x31, 0x35, 0xE4, 0xAD, 0x80, 0x94, 0xBA, 0xAC, 0x9B, 0xBC, 0xF2, 0xF4, 0xE1, 0x84, 0x98, 0x18, 0x1A, 0x8F, 0xF8, 0xA6, 0xD4, 0x3B, 0xE9, 0xD9 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, + { 0x03, 0xD9, 0xA9, 0x86, 0x24, 0xC0, 0xC7, 0x4F, 0xC7, 0xEE, 0xBD, 0x39, 0xED, 0x84, 0x17, 0x5F, 0x80, 0xD0, 0x3C, 0x77, 0x49, 0x08, 0xE7, 0x5C, 0xA7, 0x37, 0xA0, 0x74, 0x5D, 0x1C, 0x64, 0xE2, 0x0A }, + { 0x03, 0xD9, 0xA9, 0x86, 0x24, 0xC0, 0xC7, 0x4F, 0xC7, 0xEE, 0xBD, 0x39, 0xED, 0x84, 0x17, 0x5F, 0x80, 0xD0, 0x3C, 0x77, 0x49, 0x08, 0xE7, 0x5C, 0xA7, 0x37, 0xA0, 0x74, 0x5D, 0x1C, 0x64, 0xE2, 0x0A }, + { 0x03, 0x61, 0x14, 0x10, 0x56, 0x1C, 0x35, 0xDA, 0xE1, 0x31, 0x35, 0xE4, 0xAD, 0x80, 0x94, 0xBA, 0xAC, 0x9B, 0xBC, 0xF2, 0xF4, 0xE1, 0x84, 0x98, 0x18, 0x1A, 0x8F, 0xF8, 0xA6, 0xD4, 0x3B, 0xE9, 0xD9 }, + { 0x03, 0x61, 0x14, 0x10, 0x56, 0x1C, 0x35, 0xDA, 0xE1, 0x31, 0x35, 0xE4, 0xAD, 0x80, 0x94, 0xBA, 0xAC, 0x9B, 0xBC, 0xF2, 0xF4, 0xE1, 0x84, 0x98, 0x18, 0x1A, 0x8F, 0xF8, 0xA6, 0xD4, 0x3B, 0xE9, 0xD9 }, +}; + +static const unsigned char B_bytes[13][33] = { + { 0x03, 0x4B, 0xCC, 0xB1, 0xC5, 0x70, 0xAC, 0x1F, 0x3B, 0xC4, 0x2D, 0x61, 0xFE, 0x35, 0xDE, 0x60, 0x5B, 0x99, 0x62, 0x65, 0x01, 0xCC, 0xB2, 0x02, 0x97, 0xE1, 0xAC, 0xBB, 0xF2, 0xD7, 0x15, 0x2A, 0xA1 }, + { 0x02, 0x31, 0xC6, 0x4E, 0x3E, 0xFA, 0x50, 0x6F, 0xDA, 0xD6, 0xAA, 0xD0, 0xF6, 0x08, 0x4D, 0x5F, 0x67, 0x39, 0xDE, 0x7F, 0x44, 0x8D, 0x7E, 0x66, 0xF9, 0xD2, 0x2F, 0x84, 0x26, 0x38, 0xF4, 0x1D, 0x60 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, + { 0x00 }, + { 0x03, 0xD9, 0xA9, 0x86, 0x24, 0xC0, 0xC7, 0x4F, 0xC7, 0xEE, 0xBD, 0x39, 0xED, 0x84, 0x17, 0x5F, 0x80, 0xD0, 0x3C, 0x77, 0x49, 0x08, 0xE7, 0x5C, 0xA7, 0x37, 0xA0, 0x74, 0x5D, 0x1C, 0x64, 0xE2, 0x0A }, + { 0x03, 0x61, 0x14, 0x10, 0x56, 0x1C, 0x35, 0xDA, 0xE1, 0x31, 0x35, 0xE4, 0xAD, 0x80, 0x94, 0xBA, 0xAC, 0x9B, 0xBC, 0xF2, 0xF4, 0xE1, 0x84, 0x98, 0x18, 0x1A, 0x8F, 0xF8, 0xA6, 0xD4, 0x3B, 0xE9, 0xD9 }, + { 0x03, 0xD9, 0xA9, 0x86, 0x24, 0xC0, 0xC7, 0x4F, 0xC7, 0xEE, 0xBD, 0x39, 0xED, 0x84, 0x17, 0x5F, 0x80, 0xD0, 0x3C, 0x77, 0x49, 0x08, 0xE7, 0x5C, 0xA7, 0x37, 0xA0, 0x74, 0x5D, 0x1C, 0x64, 0xE2, 0x0A }, + { 0x03, 0x61, 0x14, 0x10, 0x56, 0x1C, 0x35, 0xDA, 0xE1, 0x31, 0x35, 0xE4, 0xAD, 0x80, 0x94, 0xBA, 0xAC, 0x9B, 0xBC, 0xF2, 0xF4, 0xE1, 0x84, 0x98, 0x18, 0x1A, 0x8F, 0xF8, 0xA6, 0xD4, 0x3B, 0xE9, 0xD9 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, +}; + +static const unsigned char C_bytes[13][33] = { + { 0x02, 0x85, 0xB8, 0x26, 0xC8, 0xDD, 0x17, 0x58, 0x05, 0x90, 0x19, 0x06, 0xB6, 0xC9, 0xB4, 0x14, 0x0A, 0x30, 0xCB, 0xCC, 0x94, 0xC6, 0xE7, 0xDC, 0xF3, 0x64, 0x76, 0x03, 0x8B, 0xF9, 0x0D, 0x47, 0x18 }, + { 0x03, 0xAF, 0x1B, 0xC1, 0x4B, 0x38, 0x4E, 0xDA, 0x28, 0x39, 0x8D, 0xF6, 0xA7, 0x90, 0x0E, 0x56, 0x7C, 0x5B, 0x6F, 0x66, 0x13, 0xCA, 0xFC, 0xE5, 0x02, 0x7B, 0x98, 0xBE, 0x01, 0x52, 0x86, 0xF7, 0x1B }, + { 0x03, 0xD9, 0xA9, 0x86, 0x24, 0xC0, 0xC7, 0x4F, 0xC7, 0xEE, 0xBD, 0x39, 0xED, 0x84, 0x17, 0x5F, 0x80, 0xD0, 0x3C, 0x77, 0x49, 0x08, 0xE7, 0x5C, 0xA7, 0x37, 0xA0, 0x74, 0x5D, 0x1C, 0x64, 0xE2, 0x0A }, + { 0x00 }, + { 0x00 }, + { 0x00 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, + { 0x03, 0xD9, 0xA9, 0x86, 0x24, 0xC0, 0xC7, 0x4F, 0xC7, 0xEE, 0xBD, 0x39, 0xED, 0x84, 0x17, 0x5F, 0x80, 0xD0, 0x3C, 0x77, 0x49, 0x08, 0xE7, 0x5C, 0xA7, 0x37, 0xA0, 0x74, 0x5D, 0x1C, 0x64, 0xE2, 0x0A }, + { 0x03, 0x61, 0x14, 0x10, 0x56, 0x1C, 0x35, 0xDA, 0xE1, 0x31, 0x35, 0xE4, 0xAD, 0x80, 0x94, 0xBA, 0xAC, 0x9B, 0xBC, 0xF2, 0xF4, 0xE1, 0x84, 0x98, 0x18, 0x1A, 0x8F, 0xF8, 0xA6, 0xD4, 0x3B, 0xE9, 0xD9 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, + { 0x03, 0x61, 0x14, 0x10, 0x56, 0x1C, 0x35, 0xDA, 0xE1, 0x31, 0x35, 0xE4, 0xAD, 0x80, 0x94, 0xBA, 0xAC, 0x9B, 0xBC, 0xF2, 0xF4, 0xE1, 0x84, 0x98, 0x18, 0x1A, 0x8F, 0xF8, 0xA6, 0xD4, 0x3B, 0xE9, 0xD9 }, + { 0x03, 0xD9, 0xA9, 0x86, 0x24, 0xC0, 0xC7, 0x4F, 0xC7, 0xEE, 0xBD, 0x39, 0xED, 0x84, 0x17, 0x5F, 0x80, 0xD0, 0x3C, 0x77, 0x49, 0x08, 0xE7, 0x5C, 0xA7, 0x37, 0xA0, 0x74, 0x5D, 0x1C, 0x64, 0xE2, 0x0A }, + { 0x03, 0xD9, 0xA9, 0x86, 0x24, 0xC0, 0xC7, 0x4F, 0xC7, 0xEE, 0xBD, 0x39, 0xED, 0x84, 0x17, 0x5F, 0x80, 0xD0, 0x3C, 0x77, 0x49, 0x08, 0xE7, 0x5C, 0xA7, 0x37, 0xA0, 0x74, 0x5D, 0x1C, 0x64, 0xE2, 0x0A }, +}; + +static const unsigned char auxrand_bytes[6][32] = { + { 0xC8, 0xD7, 0x05, 0x6A, 0xBD, 0x47, 0x26, 0xEB, 0x5A, 0x0F, 0x19, 0x87, 0x40, 0xAF, 0x14, 0xD6, 0xC1, 0xF0, 0xC1, 0x6E, 0x5D, 0x7A, 0x37, 0xEA, 0xEC, 0x62, 0x1B, 0x66, 0x1E, 0x66, 0x9A, 0xC4 }, + { 0x02, 0xA7, 0xB2, 0xE2, 0xF5, 0xA5, 0xE9, 0xB1, 0x07, 0x8D, 0xBB, 0x16, 0x05, 0x02, 0xA3, 0x24, 0x91, 0xFE, 0x80, 0xA0, 0x91, 0xE9, 0x1D, 0xD9, 0x2C, 0xF7, 0x7B, 0x0B, 0x7D, 0x90, 0x97, 0x0F }, + { 0xD3, 0x84, 0x66, 0xB7, 0x74, 0x84, 0x15, 0x4A, 0x3F, 0xCB, 0x31, 0x51, 0x09, 0x4C, 0x1C, 0x8A, 0x84, 0x5C, 0x73, 0xA3, 0xC0, 0x36, 0xB3, 0xA8, 0xEB, 0xFF, 0xD8, 0xEF, 0x62, 0xC9, 0x04, 0x7F }, + { 0xD3, 0x84, 0x66, 0xB7, 0x74, 0x84, 0x15, 0x4A, 0x3F, 0xCB, 0x31, 0x51, 0x09, 0x4C, 0x1C, 0x8A, 0x84, 0x5C, 0x73, 0xA3, 0xC0, 0x36, 0xB3, 0xA8, 0xEB, 0xFF, 0xD8, 0xEF, 0x62, 0xC9, 0x04, 0x7F }, + { 0xD3, 0x84, 0x66, 0xB7, 0x74, 0x84, 0x15, 0x4A, 0x3F, 0xCB, 0x31, 0x51, 0x09, 0x4C, 0x1C, 0x8A, 0x84, 0x5C, 0x73, 0xA3, 0xC0, 0x36, 0xB3, 0xA8, 0xEB, 0xFF, 0xD8, 0xEF, 0x62, 0xC9, 0x04, 0x7F }, + { 0xD3, 0x84, 0x66, 0xB7, 0x74, 0x84, 0x15, 0x4A, 0x3F, 0xCB, 0x31, 0x51, 0x09, 0x4C, 0x1C, 0x8A, 0x84, 0x5C, 0x73, 0xA3, 0xC0, 0x36, 0xB3, 0xA8, 0xEB, 0xFF, 0xD8, 0xEF, 0x62, 0xC9, 0x04, 0x7F }, +}; + +static const unsigned char msg_bytes[13][32] = { + { 0x00 }, + { 0x35, 0x84, 0x1C, 0xA5, 0x32, 0x84, 0x6E, 0x1C, 0xDD, 0x23, 0xA3, 0xD1, 0x07, 0x82, 0x43, 0x43, 0x58, 0x4F, 0x88, 0xEF, 0xF5, 0x80, 0x92, 0x94, 0x69, 0x86, 0x5E, 0xAE, 0x83, 0x55, 0xEE, 0x3C }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0x84, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, +}; + +static const unsigned char proof_bytes[13][64] = { + { 0x50, 0x35, 0x62, 0xD3, 0x69, 0x10, 0xCD, 0x2D, 0x61, 0xA4, 0xD0, 0x7C, 0x8F, 0xF6, 0x80, 0x26, 0x5C, 0x71, 0x3E, 0x63, 0xDD, 0xE0, 0xDC, 0xB8, 0x8E, 0x6E, 0xA3, 0xC5, 0x85, 0x97, 0xBD, 0xC0, 0x5B, 0x86, 0xDB, 0x9A, 0xF9, 0x5E, 0xCC, 0xC4, 0x75, 0xCE, 0x21, 0x77, 0xF9, 0x41, 0xC1, 0x18, 0xFE, 0xFE, 0xD2, 0x02, 0x27, 0xD4, 0xCE, 0x8C, 0xE9, 0x55, 0x7C, 0xB0, 0x08, 0x75, 0x8D, 0xE6 }, + { 0x50, 0xAE, 0x4F, 0xF8, 0x0A, 0x6B, 0x33, 0x92, 0x53, 0xF6, 0x9F, 0x5E, 0xB6, 0xFD, 0x9D, 0x01, 0x3D, 0xC9, 0xA0, 0x2A, 0xEF, 0x00, 0x1C, 0xEC, 0x08, 0xC4, 0x86, 0x38, 0xC7, 0x2B, 0xEE, 0x5A, 0x13, 0x7D, 0x15, 0x0E, 0x0B, 0xB4, 0xDE, 0x81, 0xB6, 0x75, 0x82, 0xA9, 0x92, 0x91, 0x0D, 0x94, 0xDF, 0x80, 0xD2, 0xF5, 0x37, 0x49, 0x12, 0x70, 0x02, 0x6A, 0x90, 0xB5, 0x6E, 0xC2, 0xB1, 0x28 }, + { 0xBC, 0xE6, 0xDF, 0xDA, 0x12, 0xAF, 0x1B, 0x86, 0xC1, 0xCF, 0x6C, 0x1E, 0xD6, 0x27, 0x05, 0x32, 0x24, 0xB6, 0xC7, 0x81, 0x75, 0xED, 0xDA, 0xDB, 0xE0, 0x1E, 0xAD, 0x83, 0x6B, 0xB7, 0x77, 0x21, 0x01, 0xCB, 0x33, 0x0A, 0x91, 0xEF, 0x06, 0xA3, 0xF6, 0x3F, 0x82, 0x54, 0xC1, 0xAC, 0x5B, 0x76, 0xE3, 0x12, 0xD9, 0xDA, 0xDA, 0x45, 0x84, 0x1E, 0xA6, 0xA9, 0xB4, 0x43, 0x73, 0x81, 0x99, 0x39 }, + { 0x00 }, + { 0x00 }, + { 0x00 }, + { 0xBC, 0xE6, 0xDF, 0xDA, 0x12, 0xAF, 0x1B, 0x86, 0xC1, 0xCF, 0x6C, 0x1E, 0xD6, 0x27, 0x05, 0x32, 0x24, 0xB6, 0xC7, 0x81, 0x75, 0xED, 0xDA, 0xDB, 0xE0, 0x1E, 0xAD, 0x83, 0x6B, 0xB7, 0x77, 0x21, 0x01, 0xCB, 0x33, 0x0A, 0x91, 0xEF, 0x06, 0xA3, 0xF6, 0x3F, 0x82, 0x54, 0xC1, 0xAC, 0x5B, 0x76, 0xE3, 0x12, 0xD9, 0xDA, 0xDA, 0x45, 0x84, 0x1E, 0xA6, 0xA9, 0xB4, 0x43, 0x73, 0x81, 0x99, 0x39 }, + { 0xBC, 0xE6, 0xDF, 0xDA, 0x12, 0xAF, 0x1B, 0x86, 0xC1, 0xCF, 0x6C, 0x1E, 0xD6, 0x27, 0x05, 0x32, 0x24, 0xB6, 0xC7, 0x81, 0x75, 0xED, 0xDA, 0xDB, 0xE0, 0x1E, 0xAD, 0x83, 0x6B, 0xB7, 0x77, 0x21, 0x01, 0xCB, 0x33, 0x0A, 0x91, 0xEF, 0x06, 0xA3, 0xF6, 0x3F, 0x82, 0x54, 0xC1, 0xAC, 0x5B, 0x76, 0xE3, 0x12, 0xD9, 0xDA, 0xDA, 0x45, 0x84, 0x1E, 0xA6, 0xA9, 0xB4, 0x43, 0x73, 0x81, 0x99, 0x39 }, + { 0xBC, 0xE6, 0xDF, 0xDA, 0x12, 0xAF, 0x1B, 0x86, 0xC1, 0xCF, 0x6C, 0x1E, 0xD6, 0x27, 0x05, 0x32, 0x24, 0xB6, 0xC7, 0x81, 0x75, 0xED, 0xDA, 0xDB, 0xE0, 0x1E, 0xAD, 0x83, 0x6B, 0xB7, 0x77, 0x21, 0x01, 0xCB, 0x33, 0x0A, 0x91, 0xEF, 0x06, 0xA3, 0xF6, 0x3F, 0x82, 0x54, 0xC1, 0xAC, 0x5B, 0x76, 0xE3, 0x12, 0xD9, 0xDA, 0xDA, 0x45, 0x84, 0x1E, 0xA6, 0xA9, 0xB4, 0x43, 0x73, 0x81, 0x99, 0x39 }, + { 0xBC, 0xE6, 0xDF, 0xDA, 0x12, 0xAF, 0x1B, 0x86, 0xC1, 0xCF, 0x6C, 0x1E, 0xD6, 0x27, 0x05, 0x32, 0x24, 0xB6, 0xC7, 0x81, 0x75, 0xED, 0xDA, 0xDB, 0xE0, 0x1E, 0xAD, 0x83, 0x6B, 0xB7, 0x77, 0x21, 0x01, 0xCB, 0x33, 0x0A, 0x91, 0xEF, 0x06, 0xA3, 0xF6, 0x3F, 0x82, 0x54, 0xC1, 0xAC, 0x5B, 0x76, 0xE3, 0x12, 0xD9, 0xDA, 0xDA, 0x45, 0x84, 0x1E, 0xA6, 0xA9, 0xB4, 0x43, 0x73, 0x81, 0x99, 0x39 }, + { 0xBC, 0xE6, 0xDF, 0xDA, 0x12, 0xAF, 0x1B, 0x86, 0xC1, 0xCF, 0x6C, 0x1E, 0xD6, 0x27, 0x05, 0x32, 0x24, 0xB6, 0xC7, 0x81, 0x75, 0xED, 0xDA, 0xDB, 0xE0, 0x1E, 0xAD, 0x83, 0x6B, 0xB7, 0x77, 0x21, 0x01, 0xCB, 0x33, 0x0A, 0x91, 0xEF, 0x06, 0xA3, 0xF6, 0x3F, 0x82, 0x54, 0xC1, 0xAC, 0x5B, 0x76, 0xE3, 0x12, 0xD9, 0xDA, 0xDA, 0x45, 0x84, 0x1E, 0xA6, 0xA9, 0xB4, 0x43, 0x73, 0x81, 0x99, 0x39 }, + { 0xBC, 0xE6, 0xDF, 0xDA, 0x12, 0xAF, 0x1B, 0x86, 0xC1, 0xCF, 0x6C, 0x1E, 0xD6, 0x27, 0x05, 0x32, 0x24, 0xB6, 0xC7, 0x85, 0x75, 0xED, 0xDA, 0xDB, 0xE0, 0x1E, 0xAD, 0x83, 0x6B, 0xB7, 0x77, 0x21, 0x01, 0xCB, 0x33, 0x0A, 0x91, 0xEF, 0x06, 0xA3, 0xF6, 0x3F, 0x82, 0x54, 0xC1, 0xAC, 0x5B, 0x76, 0xE3, 0x12, 0xD9, 0xDA, 0xDA, 0x45, 0x84, 0x1E, 0xA6, 0xA9, 0xB4, 0x43, 0x73, 0x81, 0x99, 0x39 }, + { 0xBC, 0xE6, 0xDF, 0xDA, 0x12, 0xAF, 0x1B, 0x86, 0xC1, 0xCF, 0x6C, 0x1E, 0xD6, 0x27, 0x05, 0x32, 0x24, 0xB6, 0xC7, 0x81, 0x75, 0xED, 0xDA, 0xDB, 0xE0, 0x1E, 0xAD, 0x83, 0x6B, 0xB7, 0x77, 0x21, 0x01, 0xCB, 0x33, 0x0A, 0x91, 0xEF, 0x06, 0xA3, 0xF6, 0x3F, 0x82, 0x54, 0xC1, 0xAC, 0x5B, 0x76, 0xE3, 0x12, 0xD9, 0xDA, 0xDA, 0x45, 0x84, 0x1E, 0xA6, 0xA9, 0xB4, 0x43, 0x73, 0x81, 0x99, 0x39 }, +}; + +static const unsigned char success[13] = { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; diff --git a/src/modules/silentpayments/tests_impl.h b/src/modules/silentpayments/tests_impl.h index 06133090bf..a6aa405cfa 100644 --- a/src/modules/silentpayments/tests_impl.h +++ b/src/modules/silentpayments/tests_impl.h @@ -9,6 +9,8 @@ #include "../../../include/secp256k1_silentpayments.h" #include "../../../src/modules/silentpayments/dleq_impl.h" #include "../../../src/modules/silentpayments/vectors.h" +#include "../../../src/modules/silentpayments/dleq_vectors.h" +#include "../../../include/secp256k1.h" /** Constants * @@ -868,6 +870,78 @@ static void dleq_tests(void) { CHECK(secp256k1_dleq_nonce(&k, a32, A_33, C_33, NULL) == 1); } +/* Test BIP-374 test vectors ("Discrete Log Equality Proofs"). + * See tools/test_vectors_dleq_generate.py + * */ + +static unsigned char zero_array[32] = {0x00}; + +/* Helper function to check if given array is NOT equivalent to all zero array. + * Used to detect test vectors where zero array can represent: + * B_bytes at infinity + * Empty optional msg_bytes + * */ +int is_not_empty(const unsigned char *arr){ + return (memcmp(arr, zero_array, 32) != 0); +} + +static void test_dleq_bip_vectors(void) { + secp256k1_scalar a, s, e; + secp256k1_ge A; + secp256k1_ge B; + secp256k1_ge C; + int i; + + /* bip-0374/test_vectors_generate_proof.csv*/ + for (i = 0; i < 6; ++i) { + int ret = 1; + const unsigned char *m = NULL; + secp256k1_ge_set_infinity(&B); + if (i > 2) ret = 0; + + secp256k1_scalar_set_b32(&a, a_bytes[i], NULL); + if (is_not_empty(B_bytes[i])) { + CHECK(secp256k1_eckey_pubkey_parse(&B, B_bytes[i], 33) == 1); + } + + secp256k1_dleq_pair(&CTX->ecmult_gen_ctx, &A, &C, &a, &B); + + if (is_not_empty(msg_bytes[i])) { + m = msg_bytes[i]; + } + CHECK(secp256k1_dleq_prove(CTX, &s, &e, &a, &B, &A, &C, (unsigned char*)(auxrand_bytes[i]), m) == ret); + + if (ret) { + unsigned char proof[64]; + secp256k1_scalar_get_b32(proof, &e); + secp256k1_scalar_get_b32(proof + 32, &s); + CHECK(memcmp(proof, proof_bytes[i], 64) == 0); + CHECK(secp256k1_dleq_verify(&s, &e, &A, &B, &C, m) == 1); + } + } + + /* bip-0374/test_vectors_verify_proof.csv*/ + for (i = 0; i < 13; ++i) { + const unsigned char *m = NULL; + + if (i > 2 && i < 6) { + /* skip invalid test cases which are not present in test_vectors_verify_proof.csv */ + continue; + } + secp256k1_scalar_set_b32(&e, proof_bytes[i], NULL); + secp256k1_scalar_set_b32(&s, proof_bytes[i] + 32, NULL); + + CHECK(secp256k1_eckey_pubkey_parse(&A, A_bytes[i], 33) == 1); + CHECK(secp256k1_eckey_pubkey_parse(&B, B_bytes[i], 33) == 1); + CHECK(secp256k1_eckey_pubkey_parse(&C, C_bytes[i], 33) == 1); + + if (is_not_empty(msg_bytes[i])) { + m = msg_bytes[i]; + } + CHECK(secp256k1_dleq_verify(&s, &e, &A, &B, &C, m) == success[i]); + } +} + void run_silentpayments_tests(void) { test_recipient_sort(); test_send_api(); @@ -876,6 +950,7 @@ void run_silentpayments_tests(void) { run_silentpayments_test_vectors(); silentpayments_sha256_tag_test(); dleq_tests(); + test_dleq_bip_vectors(); } #endif diff --git a/tools/test_vectors_dleq_generate.py b/tools/test_vectors_dleq_generate.py new file mode 100644 index 0000000000..c35a7f2692 --- /dev/null +++ b/tools/test_vectors_dleq_generate.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 + +import sys +import csv +import textwrap + +if len(sys.argv) < 2: + print( + "This script converts BIP 374 DLEQ test vectors in a given directory to a C file that can be used in the test framework." + ) + print("Usage: %s " % sys.argv[0]) + sys.exit(1) + +s = ( + """/** + * Automatically generated by %s. + * + * Test vectors according to BIP-374 ("Discrete Log Equality Proofs") are included in this file. + * Tests are included in src/modules/silentpayments/tests_impl.h. */ + """ % sys.argv[0] +) + +def hexstr_to_intarray(str): + try: + return ", ".join([f"0x{b:02X}" for b in bytes.fromhex(str)]) + except ValueError: + return "0x00" + +def create_init(name, rows, cols): + return """ +static const unsigned char %s[%d][%d] = { +""" % ( + name, + rows, + cols, + ) + +def init_array(key): + return textwrap.indent("{ %s };" % ", ".join(test_case[key]), "") + +def init_arrays(key): + s = textwrap.indent( + ",\n".join(["{ %s }" % hexstr_to_intarray(x) for x in test_case[key]]), 4 * " " + ) + s += textwrap.indent(",\n};\n", "") + return s + + +# Define lists to store each column from the test_vectors_(generate|verify)_proof.csv files +test_case = { + "index": [], + "point_G": [], + "scalar_a": [], + "point_A": [], + "point_B": [], + "point_C": [], + "auxrand_r": [], + "message": [], + "comment": [], + "result_proof": [], + "result_success": [], +} + + +with open(sys.argv[1] + "/test_vectors_generate_proof.csv", newline='') as csvfile: + reader = csv.DictReader(csvfile) + # Skip the first 5 rows since those test vectors don't use secp's generator point + for _ in range(5): + next(reader, None) + + for row in reader: + for key in test_case: + if key in row: + # these keys are present in test_vectors_generate_proof.csv + # if special cases are encountered, "0" is filled in place of the value (INFINITY/INVALID/"") + special_cases = { + "point_B": "INFINITY", + "result_proof": "INVALID", + "message": "" + } + test_case[key].append("0" if row[key] in special_cases.get(key, []) else row[key]) + else: + # these keys are not present in current csv file but are present in test_vectors_verify_proof.csv + if key in {"point_A", "point_C"}: + # "0" is filled as value for these missing keys + test_case[key].append("0") + elif key == "result_success": + # success/failure value is obtained from row["comment"] for the missing key "result_success" + test_case[key].append("1" if "Success" in row.get("comment", "") else "0") + else: + sys.exit("Unexpected missing_key encountered when parsing test_vectors_generate_proof.csv") + + +with open(sys.argv[1] + "/test_vectors_verify_proof.csv", newline='') as csvfile: + reader = csv.DictReader(csvfile) + for _ in range(5): # Skip the first 5 rows since those test vectors don't use secp's generator point + next(reader, None) + + for i in range(3): # next 3 rows are the 1st 3 rows from test_vectors_generate_proof.csv + # point_A and point_C details were not present in test_vectors_generate_proof.csv + # so fill those up using data from test_vectors_verify_proof.csv + row = next(reader) + test_case["point_A"][i] = row["point_A"] + test_case["point_C"][i] = row["point_C"] + + for row in reader: + for key in test_case: + if key in row: + # these keys are present in test_vectors_verify_proof.csv + # not handling row[key] == "TRUE" since it doesn't appear in the BIP test vectors + test_case[key].append("0" if key == "result_success" and row[key] == "FALSE" else row[key]) + else: + # these keys are not present in current csv file but are present in test_vectors_generate_proof.csv + if key == "result_proof": + # interpret "result_proof" key in the test_vectors_generate_proof.csv test vectors + # same as "proof" key in the test_vectors_verify_proof.csv test vectors + test_case["result_proof"].append(row["proof"]) + elif key not in {"scalar_a", "auxrand_r"}: # skip expected missing keys + sys.exit("Unexpected missing key encountered when test_vectors_verify_proof.csv") + + +s += create_init("a_bytes", len(test_case["scalar_a"]), 32) +s += init_arrays("scalar_a") + +s += create_init("A_bytes", len(test_case["point_A"]), 33) +s += init_arrays("point_A") + +s += create_init("B_bytes", len(test_case["point_B"]), 33) +s += init_arrays("point_B") + +s += create_init("C_bytes", len(test_case["point_C"]), 33) +s += init_arrays("point_C") + +s += create_init("auxrand_bytes", len(test_case["auxrand_r"]), 32) +s += init_arrays("auxrand_r") + +s += create_init("msg_bytes", len(test_case["message"]), 32) +s += init_arrays("message") + +s += create_init("proof_bytes", len(test_case["result_proof"]), 64) +s += init_arrays("result_proof") + +s += "\nstatic const unsigned char success[%d] = " % len(test_case["result_success"]) +s += init_array("result_success") + +print(s) From 1f42784c58977f04ad53e807bf8e3864a54da67b Mon Sep 17 00:00:00 2001 From: stratospher <44024636+stratospher@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:33:52 +0530 Subject: [PATCH 14/18] add secp256k1_silentpayments_create_shared_secret_with_proof - add new internal function which returns both DLEQ proof and shared secret. - the existing secp256k1_silentpayments_create_shared_secret API is refactored to use secp256k1_silentpayments_create_shared_secret_with_proof. --- src/modules/silentpayments/main_impl.h | 37 +++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/modules/silentpayments/main_impl.h b/src/modules/silentpayments/main_impl.h index 3d560e93cc..39770c83ca 100644 --- a/src/modules/silentpayments/main_impl.h +++ b/src/modules/silentpayments/main_impl.h @@ -9,6 +9,7 @@ #include "../../../include/secp256k1.h" #include "../../../include/secp256k1_extrakeys.h" #include "../../../include/secp256k1_silentpayments.h" +#include "dleq_impl.h" #include "../../eckey.h" #include "../../ecmult.h" @@ -85,14 +86,43 @@ static int secp256k1_silentpayments_calculate_input_hash_scalar(secp256k1_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) { +static int secp256k1_silentpayments_create_shared_secret_with_proof(const secp256k1_context *ctx, unsigned char *proof64, secp256k1_ge *shared_secret, secp256k1_ge *public_component, const secp256k1_scalar *secret_component) { secp256k1_gej ss_j; + int ret = 1; + + secp256k1_ecmult_const(&ss_j, public_component, secret_component); + secp256k1_ge_set_gej(shared_secret, &ss_j); + secp256k1_declassify(ctx, shared_secret, sizeof(*shared_secret)); + + if (proof64 != NULL) { + secp256k1_scalar s; + secp256k1_scalar e; + secp256k1_ge ge_secret_component; + secp256k1_gej gej_secret_component; + + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &gej_secret_component, secret_component); + secp256k1_ge_set_gej(&ge_secret_component, &gej_secret_component); + secp256k1_declassify(ctx, &ge_secret_component, sizeof(ge_secret_component)); + + ret &= secp256k1_dleq_prove(ctx, &s, &e, secret_component, public_component, &ge_secret_component, shared_secret, NULL, NULL); /*todo: how to pass auxrand*/ + secp256k1_declassify(ctx, &s, sizeof(s)); + secp256k1_declassify(ctx, &e, sizeof(e)); + /* sanity check */ + ret &= secp256k1_dleq_verify(&s, &e, &ge_secret_component, public_component, shared_secret, NULL); + secp256k1_scalar_get_b32(proof64, &s); + secp256k1_scalar_get_b32(proof64 + 32, &e); + } + /* Leaking these values would break indistinguishability of the transaction, so clear them. */ + secp256k1_gej_clear(&ss_j); + return ret; +} +static void secp256k1_silentpayments_create_shared_secret(const secp256k1_context *ctx, unsigned char *shared_secret33, secp256k1_ge *public_component, const secp256k1_scalar *secret_component) { secp256k1_ge ss; size_t len; int ret; - secp256k1_ecmult_const(&ss_j, public_component, secret_component); - secp256k1_ge_set_gej(&ss, &ss_j); + ret = secp256k1_silentpayments_create_shared_secret_with_proof(ctx, NULL, &ss, public_component, secret_component); + VERIFY_CHECK(ret); 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 @@ -107,7 +137,6 @@ static void secp256k1_silentpayments_create_shared_secret(const secp256k1_contex /* 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". */ From fb1fd4e065a6eb0c2de9d810b441ddca232dd14a Mon Sep 17 00:00:00 2001 From: stratospher <44024636+stratospher@users.noreply.github.com> Date: Mon, 3 Feb 2025 14:11:59 +0530 Subject: [PATCH 15/18] introduce struct to hold DLEQ proof related data for silent payments - structure contains 33-byte shared secret point + 64-byte DLEQ proof + index of recipient in original unsorted array of silent payment recipients - add functions to serialise and parse the structure to/from bytes --- include/secp256k1_silentpayments.h | 37 ++++++++++++++++++++++++++ src/modules/silentpayments/main_impl.h | 12 +++++++++ 2 files changed, 49 insertions(+) diff --git a/include/secp256k1_silentpayments.h b/include/secp256k1_silentpayments.h index 45e51a863d..cafa0ab801 100644 --- a/include/secp256k1_silentpayments.h +++ b/include/secp256k1_silentpayments.h @@ -50,6 +50,19 @@ typedef struct secp256k1_silentpayments_recipient { size_t index; } secp256k1_silentpayments_recipient; +/* This struct contains details of the DLEQ proof + * + * Fields: + * - shared_secret : 33-byte shared secret point + * - proof: 64-byte serialized DLEQ proof + * - index: Indicates which recipient the proof pertains to based on the original (not sorted) ordering of the addresses + */ +typedef struct { + unsigned char shared_secret[33]; + unsigned char proof[64]; + size_t index; +} secp256k1_silentpayments_dleq_data; + /** Create Silent Payment outputs for recipient(s). * * Given a list of n secret keys a_1...a_n (one for each silent payment @@ -444,6 +457,30 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipien const uint32_t k ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); +/** Serialize a secp256k1_silentpayments_dleq_data object into a 101-byte sequence. + * 101-byte sequence = 33 bytes shared secret + 64 bytes proof + 4 bytes index + * where index is position in an array of pointers to silent payment recipients + * + * Out: output: pointer to a 101-byte array to place the serialized `secp256k1_silentpayments_dleq_data` in + * In: dleq_data: pointer to an initialized secp256k1_silentpayments_dleq_data object + */ +SECP256K1_API void secp256k1_silentpayments_dleq_data_serialize( + unsigned char *output33, + const secp256k1_silentpayments_dleq_data *dleq_data +)SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2); + +/** Parse a 101-byte sequence into a secp256k1_silentpayments_dleq_data object. + * 101-byte sequence = 33 bytes shared secret + 64 bytes proof + 4 bytes index + * where index is position in an array of pointers to silent payment recipients + * + * Out: dleq_data: pointer to a secp256k1_silentpayments_dleq_data object. + * In: input: pointer to a serialized secp256k1_silentpayments_dleq_data. + */ +SECP256K1_API void secp256k1_silentpayments_dleq_data_parse( + secp256k1_silentpayments_dleq_data *dleq_data, + const unsigned char *input +)SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2); + #ifdef __cplusplus } #endif diff --git a/src/modules/silentpayments/main_impl.h b/src/modules/silentpayments/main_impl.h index 39770c83ca..cf72e46a13 100644 --- a/src/modules/silentpayments/main_impl.h +++ b/src/modules/silentpayments/main_impl.h @@ -790,4 +790,16 @@ int secp256k1_silentpayments_recipient_create_output_pubkey(const secp256k1_cont } +void secp256k1_silentpayments_dleq_data_serialize(unsigned char *output, const secp256k1_silentpayments_dleq_data *dleq_data) { + memcpy(output, dleq_data->shared_secret, 33); + memcpy(output + 33, dleq_data->proof, 64); + secp256k1_write_be32(output + 33 + 64, dleq_data->index); +} + +void secp256k1_silentpayments_dleq_data_parse(secp256k1_silentpayments_dleq_data *dleq_data, const unsigned char *input) { + memcpy(dleq_data->shared_secret, input, 33); + memcpy(dleq_data->proof, input + 33, 64); + dleq_data->index = secp256k1_read_be32(input + 33 + 64); +} + #endif From c5db9222b38aa0ec07ea52d57e56fca57a3e0bbe Mon Sep 17 00:00:00 2001 From: stratospher <44024636+stratospher@users.noreply.github.com> Date: Fri, 22 Nov 2024 18:05:45 +0530 Subject: [PATCH 16/18] add secp256k1_silentpayments_sender_create_outputs_with_proof - add new API which returns DLEQ proofs along with outputs for recipients - the existing API secp256k1_silentpayments_sender_create_outputs simply calls secp256k1_silentpayments_sender_create_outputs_with_proof internally. --- include/secp256k1_silentpayments.h | 30 ++++++++++++++ src/modules/silentpayments/main_impl.h | 56 ++++++++++++++++++++++---- 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/include/secp256k1_silentpayments.h b/include/secp256k1_silentpayments.h index cafa0ab801..40d9c0f2e6 100644 --- a/include/secp256k1_silentpayments.h +++ b/include/secp256k1_silentpayments.h @@ -142,6 +142,36 @@ 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 outputs and associated DLEQ proof(s) for recipient(s). + * + * Similar to secp256k1_silentpayments_sender_create_outputs, + * but also returns information about associated DLEQ proof. + * + * Returns: 1 if creation of outputs was successful. 0 if an error occurred. + * Args: ctx: pointer to a context object + * Out: generated_outputs: pointer to an array of pointers to xonly pubkeys, + * one per recipient. + * The order of outputs here matches the original + * ordering of the recipients array. + * dleq_data: pointer to an array of pointers to secp256k1_silentpayments_dleq_data, + * one per unique recipient. + * n_dleq_size: number of DLEQ proofs generated (equivalent to number of unique recipients) + * In: See input parameters in secp256k1_silentpayments_sender_create_outputs + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_sender_create_outputs_with_proof( + const secp256k1_context *ctx, + secp256k1_xonly_pubkey **generated_outputs, + secp256k1_silentpayments_dleq_data **dleq_data, + size_t *n_dleq_size, + 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(5) SECP256K1_ARG_NONNULL(7); + /** Create Silent Payment label tweak and label. * * Given a recipient's 32 byte scan key and a label integer m, calculate the diff --git a/src/modules/silentpayments/main_impl.h b/src/modules/silentpayments/main_impl.h index cf72e46a13..5c1f1a341f 100644 --- a/src/modules/silentpayments/main_impl.h +++ b/src/modules/silentpayments/main_impl.h @@ -211,9 +211,11 @@ static int secp256k1_silentpayments_create_output_pubkey(const secp256k1_context return 1; } -int secp256k1_silentpayments_sender_create_outputs( +int secp256k1_silentpayments_sender_create_outputs_with_proof( const secp256k1_context *ctx, secp256k1_xonly_pubkey **generated_outputs, + secp256k1_silentpayments_dleq_data **dleq_data, + size_t *n_dleq_size, const secp256k1_silentpayments_recipient **recipients, size_t n_recipients, const unsigned char *outpoint_smallest36, @@ -226,9 +228,13 @@ int secp256k1_silentpayments_sender_create_outputs( 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_ge shared_secret; + unsigned char shared_secret33[33]; + unsigned char proof64[64]; secp256k1_pubkey current_scan_pubkey; - int ret, sum_is_zero; + int ret = 0, sum_is_zero; + int j = 0; + size_t len; /* Sanity check inputs. */ VERIFY_CHECK(ctx != NULL); @@ -328,21 +334,57 @@ int secp256k1_silentpayments_sender_create_outputs( secp256k1_memclear_explicit(&shared_secret, sizeof(shared_secret)); return 0; } - secp256k1_silentpayments_create_shared_secret(ctx, shared_secret, &pk, &seckey_sum_scalar); + /* Compute shared_secret = tweaked_secret_component * Public_component */ + secp256k1_silentpayments_create_shared_secret_with_proof(ctx, proof64, &shared_secret, &pk, &seckey_sum_scalar); + if (dleq_data != NULL) { + secp256k1_pubkey pubkey; + size_t pklen = 33; + secp256k1_pubkey_save(&pubkey, &shared_secret); + secp256k1_ec_pubkey_serialize(ctx, dleq_data[j]->shared_secret, &pklen, &pubkey, SECP256K1_EC_COMPRESSED); + memcpy(dleq_data[j]->proof, proof64, 64); + dleq_data[j]->index = recipients[i]->index; + } + /* 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 being used + */ + ret = secp256k1_eckey_pubkey_serialize(&shared_secret, shared_secret33, &len, 1); + VERIFY_CHECK(ret && len == 33); k = 0; + j++; } - if (!secp256k1_silentpayments_create_output_pubkey(ctx, generated_outputs[recipients[i]->index], shared_secret, &recipients[i]->spend_pubkey, k)) { + if (!secp256k1_silentpayments_create_output_pubkey(ctx, generated_outputs[recipients[i]->index], shared_secret33, &recipients[i]->spend_pubkey, k)) { secp256k1_scalar_clear(&seckey_sum_scalar); secp256k1_memclear_explicit(&shared_secret, sizeof(shared_secret)); return 0; } VERIFY_CHECK(k < SIZE_MAX); + ret &= secp256k1_silentpayments_create_output_pubkey(ctx, generated_outputs[recipients[i]->index], shared_secret33, &recipients[i]->spend_pubkey, k); k++; current_scan_pubkey = recipients[i]->scan_pubkey; } + *n_dleq_size = j; secp256k1_scalar_clear(&seckey_sum_scalar); - secp256k1_memclear_explicit(&shared_secret, sizeof(shared_secret)); - return 1; + secp256k1_ge_clear(&shared_secret); + secp256k1_memclear_explicit(&shared_secret33, sizeof(shared_secret33)); + return ret; +} + +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 n_dleq_size; + return secp256k1_silentpayments_sender_create_outputs_with_proof(ctx, generated_outputs, NULL, &n_dleq_size, recipients, n_recipients, outpoint_smallest36, taproot_seckeys, n_taproot_seckeys, plain_seckeys, n_plain_seckeys); } /** Set hash state to the BIP340 tagged hash midstate for "BIP0352/Label". */ From c70d0e5a384298eb9abddda6971a6f55faa760f0 Mon Sep 17 00:00:00 2001 From: stratospher <44024636+stratospher@users.noreply.github.com> Date: Fri, 22 Nov 2024 18:16:01 +0530 Subject: [PATCH 17/18] add secp256k1_silentpayments_verify_proof --- include/secp256k1_silentpayments.h | 23 ++++++++++++++++++++ src/modules/silentpayments/main_impl.h | 29 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/include/secp256k1_silentpayments.h b/include/secp256k1_silentpayments.h index 40d9c0f2e6..618c83bef3 100644 --- a/include/secp256k1_silentpayments.h +++ b/include/secp256k1_silentpayments.h @@ -487,6 +487,29 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipien const uint32_t k ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); +/** Verifies the Silent Payment proof. If the following algorithm succeeds, the points A and C were both generated from + * the same scalar. The former from multiplying by G, and the latter from multiplying by B. + * + * Here, A refers to input public key sum (present in prevouts_summary) + * B refers to recipient's scan pubkey + * C refers to shared_secret point + * + * Returns: 1 if verification of proof was successful. 0 if an error occurred. + * Args: ctx: pointer to a context object + * In: shared_secret: 33 bytes shared secret + * proof: 64 bytes DLEQ proof + * recipient_scan_pubkey: pointer to the recipient's scan pubkey + * prevouts_summary: pointer to the input public key sum (optionally, with the `input_hash` multiplied in, + * see `_recipient_prevouts_summary_create`). + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_verify_proof( + const secp256k1_context *ctx, + const unsigned char *shared_secret33, + const unsigned char *proof64, + const secp256k1_pubkey *recipient_scan_pubkey, + const secp256k1_silentpayments_prevouts_summary *prevouts_summary +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + /** Serialize a secp256k1_silentpayments_dleq_data object into a 101-byte sequence. * 101-byte sequence = 33 bytes shared secret + 64 bytes proof + 4 bytes index * where index is position in an array of pointers to silent payment recipients diff --git a/src/modules/silentpayments/main_impl.h b/src/modules/silentpayments/main_impl.h index 5c1f1a341f..24b3fb2792 100644 --- a/src/modules/silentpayments/main_impl.h +++ b/src/modules/silentpayments/main_impl.h @@ -831,6 +831,35 @@ int secp256k1_silentpayments_recipient_create_output_pubkey(const secp256k1_cont return secp256k1_silentpayments_create_output_pubkey(ctx, output_xonly, shared_secret33, spend_pubkey, k); } +int secp256k1_silentpayments_verify_proof(const secp256k1_context *ctx, const unsigned char *shared_secret33, const unsigned char *proof64, const secp256k1_pubkey *recipient_scan_pubkey, const secp256k1_silentpayments_prevouts_summary *prevouts_summary) +{ + secp256k1_scalar s; + secp256k1_scalar e; + secp256k1_pubkey pk; + secp256k1_ge pubkey_sum; + secp256k1_ge scan_pubkey; + secp256k1_ge shared_secret; + size_t pubkeylen = 33; + unsigned char pubkey33[33]; + int ret = 1; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(shared_secret33 != NULL); + ARG_CHECK(proof64 != NULL); + ARG_CHECK(recipient_scan_pubkey != NULL); + ARG_CHECK(prevouts_summary != NULL); + + ret &= secp256k1_silentpayments_recipient_prevouts_summary_serialize(ctx, pubkey33, prevouts_summary); + ret &= secp256k1_ec_pubkey_parse(ctx, &pk, pubkey33, pubkeylen); + ret &= secp256k1_pubkey_load(ctx, &pubkey_sum, &pk); + ret &= secp256k1_pubkey_load(ctx, &scan_pubkey, recipient_scan_pubkey); + ret &= secp256k1_ec_pubkey_parse(ctx, &pk, shared_secret33, pubkeylen); + ret &= secp256k1_pubkey_load(ctx, &shared_secret, &pk); + secp256k1_scalar_set_b32(&s, proof64, NULL); + secp256k1_scalar_set_b32(&e, proof64 + 32, NULL); + ret &= secp256k1_dleq_verify(&s, &e, &pubkey_sum, &scan_pubkey, &shared_secret, NULL); + return ret; +} void secp256k1_silentpayments_dleq_data_serialize(unsigned char *output, const secp256k1_silentpayments_dleq_data *dleq_data) { memcpy(output, dleq_data->shared_secret, 33); From ed1094896e359fdaa99595250cf1612e9a111c0d Mon Sep 17 00:00:00 2001 From: stratospher <44024636+stratospher@users.noreply.github.com> Date: Mon, 3 Feb 2025 14:14:06 +0530 Subject: [PATCH 18/18] add tests for creating and verifying proofs - in examples/silentpayments.c - sender now generates outputs with proof and verifies the proof - proof can be serialised to bytes and sent to recipient - bytes can be parsed back to proof by recipient as well - recipient can verify proof - in tests_recipients_helper, run_silentpayments_test_vector_send - along with output checks, generate DLEQ proofs and verify them --- examples/silentpayments.c | 103 +++++++++++++++++++++--- src/modules/silentpayments/tests_impl.h | 52 +++++++++++- 2 files changed, 140 insertions(+), 15 deletions(-) diff --git a/examples/silentpayments.c b/examples/silentpayments.c index f29de0997b..752ec768c1 100644 --- a/examples/silentpayments.c +++ b/examples/silentpayments.c @@ -18,6 +18,7 @@ #define N_INPUTS 2 #define N_OUTPUTS 3 +#define N_RECIPIENTS 2 /* Static data for Bob and Carol's silent payment addresses */ static unsigned char smallest_outpoint[36] = { @@ -127,6 +128,7 @@ int main(void) { secp256k1_xonly_pubkey *tx_output_ptrs[N_OUTPUTS]; int ret; size_t i; + unsigned char dleq_proof[N_RECIPIENTS][64]; /* Before we can call actual API functions, we need to create a "context" */ secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); @@ -229,18 +231,66 @@ int main(void) { for (i = 0; i < N_OUTPUTS; i++) { tx_output_ptrs[i] = &tx_outputs[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, try again with different inputs.\n"); - return EXIT_FAILURE; + + /* Sender can perform 1 of the following options: + * Option 1: generate outputs without DLEQ proofs + 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, try again with different inputs.\n"); + return EXIT_FAILURE; + } + */ + { + /* Option 2: generate outputs with DLEQ proofs*/ + secp256k1_silentpayments_prevouts_summary prevouts_summary; + size_t n_dleq_size; + secp256k1_silentpayments_dleq_data dleq_data[N_RECIPIENTS]; + secp256k1_silentpayments_dleq_data *dleq_data_ptrs[N_RECIPIENTS]; + for (i = 0; i < N_RECIPIENTS; i++) { + dleq_data_ptrs[i] = &dleq_data[i]; + } + for (i = 0; i < N_INPUTS; i++) { + tx_input_ptrs[i] = &tx_inputs[i]; + } + ret = secp256k1_silentpayments_recipient_prevouts_summary_create(ctx, &prevouts_summary, smallest_outpoint, + tx_input_ptrs, N_INPUTS, NULL, 0); + assert(ret); + + ret = secp256k1_silentpayments_sender_create_outputs_with_proof(ctx, + tx_output_ptrs, dleq_data_ptrs, &n_dleq_size, + recipient_ptrs, N_OUTPUTS, + smallest_outpoint, + sender_keypair_ptrs, N_INPUTS, + NULL, 0 + ); + assert(n_dleq_size == N_RECIPIENTS); + assert(ret); + /* Ensure that outputs are generated correctly at the sender side by verifying the DLEQ proof */ + for (i = 0; i < N_RECIPIENTS; i++) { + /* Serialized form of proof can be sent from 1 sender side device to another sender side device. + * ex: hardware wallet (which can do ECDH + proof calculation) to wallet application. */ + unsigned char ss_proof_index_bytes[33 + 64 + 4]; + secp256k1_silentpayments_dleq_data data; + secp256k1_silentpayments_dleq_data_serialize(ss_proof_index_bytes, &dleq_data[i]); + /* Parse the serialized proof on the second device. (ex: wallet application) */ + secp256k1_silentpayments_dleq_data_parse(&data, ss_proof_index_bytes); + /* Proof verification can be done on the second device. */ + ret = secp256k1_silentpayments_verify_proof(ctx, data.shared_secret, data.proof, + &recipients[data.index].scan_pubkey, + &prevouts_summary); + assert(ret); + /* Store proof to send to different receivers (Bob, Carol) later. */ + memcpy(dleq_proof[i], ss_proof_index_bytes + 33, 64); + } } - printf("Alice created the following outputs for Bob and Carol:\n"); + + 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, @@ -481,6 +531,25 @@ int main(void) { } else { printf("Bob did not find any outputs in this transaction.\n"); } + { + /* Optionally, Bob can use DLEQ proof to prove ownership of his address without revealing private keys + * DLEQ proof verification needs proof, input pubkey sum, Bob's scan pubkey and shared secret as inputs. */ + unsigned char shared_secret[33]; + secp256k1_pubkey scan_pubkey; + /* 1. Get Bob's scan pubkey */ + ret = secp256k1_ec_pubkey_parse(ctx, &scan_pubkey, bob_address[0], 33); + assert(ret); + /* 2. Compute input pubkey sum */ + ret = secp256k1_silentpayments_recipient_prevouts_summary_serialize(ctx, light_client_data33, &prevouts_summary); + assert(ret); + /* 3. Bob computes shared secret */ + ret = secp256k1_silentpayments_recipient_create_shared_secret(ctx, shared_secret, bob_scan_key, + &prevouts_summary); + assert(ret); + /* 4. Use proof we obtained from Alice for verification */ + ret &= secp256k1_silentpayments_verify_proof(ctx, shared_secret, dleq_proof[0], &scan_pubkey, &prevouts_summary); + assert(ret); + } } { /*** Scanning as a light client (Carol) *** @@ -608,6 +677,18 @@ int main(void) { } else { printf("Carol did not find any outputs in this transaction.\n"); } + { + /* Optionally, Carol can use DLEQ proof to prove ownership of her address without revealing private keys + * DLEQ proof verification needs proof, input pubkey sum, Carol's scan pubkey and shared secret as inputs. */ + /* 1. Get Carol's scan pubkey */ + secp256k1_pubkey scan_pubkey; + ret = secp256k1_ec_pubkey_parse(ctx, &scan_pubkey, carol_address[0], 33); + assert(ret); + /* 2. Input pubkey sum and shared secret already computed at this point, so verify_proof directly */ + /* 3. Use proof we obtained from Alice for verification */ + ret &= secp256k1_silentpayments_verify_proof(ctx, shared_secret, dleq_proof[1], &scan_pubkey, &prevouts_summary); + assert(ret); + } } } diff --git a/src/modules/silentpayments/tests_impl.h b/src/modules/silentpayments/tests_impl.h index a6aa405cfa..22651850d3 100644 --- a/src/modules/silentpayments/tests_impl.h +++ b/src/modules/silentpayments/tests_impl.h @@ -11,6 +11,7 @@ #include "../../../src/modules/silentpayments/vectors.h" #include "../../../src/modules/silentpayments/dleq_vectors.h" #include "../../../include/secp256k1.h" +#include /** Constants * @@ -132,6 +133,9 @@ static void test_recipient_sort_helper(unsigned char (*sp_addresses[3])[2][33], const secp256k1_silentpayments_recipient *recipient_ptrs[3]; secp256k1_xonly_pubkey generated_outputs[3]; secp256k1_xonly_pubkey *generated_output_ptrs[3]; + secp256k1_silentpayments_dleq_data dleq_data[2]; + secp256k1_silentpayments_dleq_data *dleq_data_ptrs[2]; + size_t n_dleq_size; unsigned char xonly_ser[32]; size_t i; int ret; @@ -143,15 +147,32 @@ static void test_recipient_sort_helper(unsigned char (*sp_addresses[3])[2][33], recipients[i].index = i; recipient_ptrs[i] = &recipients[i]; generated_output_ptrs[i] = &generated_outputs[i]; + if (i != 2){ + dleq_data_ptrs[i] = &dleq_data[i]; + } } - ret = secp256k1_silentpayments_sender_create_outputs(CTX, - generated_output_ptrs, + ret = secp256k1_silentpayments_sender_create_outputs_with_proof(CTX, + generated_output_ptrs, dleq_data_ptrs, &n_dleq_size, recipient_ptrs, 3, SMALLEST_OUTPOINT, NULL, 0, seckey_ptrs, 1 ); CHECK(ret == 1); + { + secp256k1_pubkey pk; + secp256k1_xonly_pubkey xonly_pk; + secp256k1_silentpayments_prevouts_summary prevouts_summary; + const secp256k1_xonly_pubkey *tx_input_ptr = &xonly_pk; + CHECK(secp256k1_ec_pubkey_create(CTX, &pk, seckey_ptrs[0]) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &xonly_pk, NULL, &pk) == 1); + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_create(CTX, &prevouts_summary, SMALLEST_OUTPOINT, &tx_input_ptr, 1, NULL, 0) == 1); + for (i = 0; i < 2; i++) { + CHECK(secp256k1_silentpayments_verify_proof(CTX, dleq_data_ptrs[i]->shared_secret, dleq_data_ptrs[i]->proof, + &recipients[dleq_data_ptrs[i]->index].scan_pubkey, + &prevouts_summary) == 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); @@ -505,9 +526,17 @@ void run_silentpayments_test_vector_send(const struct bip352_test_vector *test) 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]; + secp256k1_pubkey plain_pubkeys[MAX_INPUTS_PER_TEST_CASE]; + const secp256k1_pubkey *plain_pubkey_ptrs[MAX_INPUTS_PER_TEST_CASE]; + secp256k1_xonly_pubkey xonly_pubkeys[MAX_INPUTS_PER_TEST_CASE]; + const secp256k1_xonly_pubkey *xonly_pubkey_ptrs[MAX_INPUTS_PER_TEST_CASE]; + secp256k1_silentpayments_dleq_data dleq_data[MAX_OUTPUTS_PER_TEST_CASE]; + secp256k1_silentpayments_dleq_data *dleq_data_ptrs[MAX_OUTPUTS_PER_TEST_CASE]; unsigned char created_output[32]; size_t i, j, k; int match, ret; + size_t n_dleq_size; + secp256k1_silentpayments_prevouts_summary prevouts_summary; /* Check that sender creates expected outputs */ for (i = 0; i < test->num_outputs; i++) { @@ -516,19 +545,24 @@ void run_silentpayments_test_vector_send(const struct bip352_test_vector *test) recipients[i].index = i; recipient_ptrs[i] = &recipients[i]; generated_output_ptrs[i] = &generated_outputs[i]; + dleq_data_ptrs[i] = &dleq_data[i]; } for (i = 0; i < test->num_plain_inputs; i++) { plain_seckeys[i] = test->plain_seckeys[i]; + CHECK(secp256k1_ec_pubkey_create(CTX, &plain_pubkeys[i], plain_seckeys[i]) == 1); + plain_pubkey_ptrs[i] = &plain_pubkeys[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]; + CHECK(secp256k1_keypair_xonly_pub(CTX, &xonly_pubkeys[i], NULL, &taproot_keypairs[i]) == 1); + xonly_pubkey_ptrs[i] = &xonly_pubkeys[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, + ret = secp256k1_silentpayments_sender_create_outputs_with_proof(CTX, + generated_output_ptrs, dleq_data_ptrs, &n_dleq_size, recipient_ptrs, test->num_outputs, test->outpoint_smallest, @@ -566,6 +600,16 @@ void run_silentpayments_test_vector_send(const struct bip352_test_vector *test) } } CHECK(match); + + /* Verify generated DLEQ proofs*/ + CHECK(secp256k1_silentpayments_recipient_prevouts_summary_create(CTX, &prevouts_summary, test->outpoint_smallest, + test->num_taproot_inputs > 0 ? xonly_pubkey_ptrs : NULL, test->num_taproot_inputs, + test->num_plain_inputs > 0 ? plain_pubkey_ptrs : NULL, test->num_plain_inputs) == 1); + for (i = 0; i < n_dleq_size; i++) { + CHECK(secp256k1_silentpayments_verify_proof(CTX, dleq_data_ptrs[i]->shared_secret, dleq_data_ptrs[i]->proof, + &recipients[dleq_data_ptrs[i]->index].scan_pubkey, + &prevouts_summary) == 1); + } } void run_silentpayments_test_vector_receive(const struct bip352_test_vector *test) {