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/.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/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..e1c6fba3fb 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 @@ -249,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 @@ -262,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) @@ -311,3 +327,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/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. 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" \ 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/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..752ec768c1 --- /dev/null +++ b/examples/silentpayments.c @@ -0,0 +1,698 @@ +/************************************************************************* + * 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 +#define N_RECIPIENTS 2 + +/* 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; + 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); + 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]; + } + + /* 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"); + 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"); + } + { + /* 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) *** + * + * 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"); + } + { + /* 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); + } + } + } + + /* This will clear everything from the context and free the memory */ + secp256k1_context_destroy(ctx); + return EXIT_SUCCESS; +} diff --git a/include/secp256k1_silentpayments.h b/include/secp256k1_silentpayments.h new file mode 100644 index 0000000000..618c83bef3 --- /dev/null +++ b/include/secp256k1_silentpayments.h @@ -0,0 +1,541 @@ +#ifndef SECP256K1_SILENTPAYMENTS_H +#define SECP256K1_SILENTPAYMENTS_H + +#include +#include "secp256k1.h" +#include "secp256k1_extrakeys.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. + */ + +static const unsigned char secp256k1_silentpayments_prevouts_summary_magic[4] = { 0xa7, 0x1c, 0xd3, 0x5e }; + +/** 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; + +/* 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 + * 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); + +/** 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 + * 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); + +/** 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); + +/** 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 + * + * 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 + +#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/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/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 } diff --git a/src/modules/silentpayments/Makefile.am.include b/src/modules/silentpayments/Makefile.am.include new file mode 100644 index 0000000000..08186253fc --- /dev/null +++ b/src/modules/silentpayments/Makefile.am.include @@ -0,0 +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/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 */ 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/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/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/main_impl.h b/src/modules/silentpayments/main_impl.h new file mode 100644 index 0000000000..24b3fb2792 --- /dev/null +++ b/src/modules/silentpayments/main_impl.h @@ -0,0 +1,876 @@ +/*********************************************************************** + * 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_extrakeys.h" +#include "../../../include/secp256k1_silentpayments.h" +#include "dleq_impl.h" + +#include "../../eckey.h" +#include "../../ecmult.h" +#include "../../ecmult_const.h" +#include "../../ecmult_gen.h" +#include "../../group.h" +#include "../../hash.h" +#include "../../hsort.h" + +/** 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 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; + + 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 + * 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); +} + +/** 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_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 +) { + 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; + secp256k1_ge shared_secret; + unsigned char shared_secret33[33]; + unsigned char proof64[64]; + secp256k1_pubkey current_scan_pubkey; + int ret = 0, sum_is_zero; + int j = 0; + size_t len; + + /* 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; + } + /* 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_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_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". */ +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; +} + +/** 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); +} + +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); + 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 diff --git a/src/modules/silentpayments/tests_impl.h b/src/modules/silentpayments/tests_impl.h new file mode 100644 index 0000000000..22651850d3 --- /dev/null +++ b/src/modules/silentpayments/tests_impl.h @@ -0,0 +1,1000 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_SILENTPAYMENTS_TESTS_H +#define SECP256K1_MODULE_SILENTPAYMENTS_TESTS_H + +#include "../../../include/secp256k1_silentpayments.h" +#include "../../../src/modules/silentpayments/dleq_impl.h" +#include "../../../src/modules/silentpayments/vectors.h" +#include "../../../src/modules/silentpayments/dleq_vectors.h" +#include "../../../include/secp256k1.h" +#include + +/** 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 +}; +/* 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]; + 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]; + 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; + + 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]; + if (i != 2){ + dleq_data_ptrs[i] = &dleq_data[i]; + } + } + 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); + } +} + +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; + } +} + +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)); + } +} + +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_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]; + 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++) { + 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]; + 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_with_proof(CTX, + generated_output_ptrs, dleq_data_ptrs, &n_dleq_size, + 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); + + /* 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) { + 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); + } + } +} + +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; + + 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); + } +} + +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); +} + +/* 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(); + test_label_api(); + test_recipient_api(); + run_silentpayments_test_vectors(); + silentpayments_sha256_tag_test(); + dleq_tests(); + test_dleq_bip_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/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 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(); 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) 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()