diff --git a/CMakeLists.txt b/CMakeLists.txt index cf0dc3ba93ff33..d824a6db3e005f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,29 +51,45 @@ endif() option(SECP256K1_INSTALL "Enable installation." ${PROJECT_IS_TOP_LEVEL}) +## Modules + +# We declare all options before processing them, to make sure we can express +# dependendencies while processing. option(SECP256K1_ENABLE_MODULE_ECDH "Enable ECDH module." ON) -if(SECP256K1_ENABLE_MODULE_ECDH) - add_compile_definitions(ENABLE_MODULE_ECDH=1) +option(SECP256K1_ENABLE_MODULE_RECOVERY "Enable ECDSA pubkey recovery module." OFF) +option(SECP256K1_ENABLE_MODULE_EXTRAKEYS "Enable extrakeys module." ON) +option(SECP256K1_ENABLE_MODULE_SCHNORRSIG "Enable schnorrsig module." ON) +option(SECP256K1_ENABLE_MODULE_ELLSWIFT "Enable ElligatorSwift module." ON) +option(SECP256K1_ENABLE_MODULE_SILENTPAYMENTS "Enable Silent Payments module." OFF) + +# Processing must be done in a topological sorting of the dependency graph +# (dependent module first). +if(SECP256K1_ENABLE_MODULE_SILENTPAYMENTS) + add_compile_definitions(ENABLE_MODULE_SILENTPAYMENTS=1) endif() -option(SECP256K1_ENABLE_MODULE_RECOVERY "Enable ECDSA pubkey recovery module." OFF) -if(SECP256K1_ENABLE_MODULE_RECOVERY) - add_compile_definitions(ENABLE_MODULE_RECOVERY=1) +if(SECP256K1_ENABLE_MODULE_ELLSWIFT) + add_compile_definitions(ENABLE_MODULE_ELLSWIFT=1) endif() -option(SECP256K1_ENABLE_MODULE_EXTRAKEYS "Enable extrakeys module." ON) -option(SECP256K1_ENABLE_MODULE_SCHNORRSIG "Enable schnorrsig module." ON) 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.") + endif() set(SECP256K1_ENABLE_MODULE_EXTRAKEYS ON) add_compile_definitions(ENABLE_MODULE_SCHNORRSIG=1) endif() + if(SECP256K1_ENABLE_MODULE_EXTRAKEYS) add_compile_definitions(ENABLE_MODULE_EXTRAKEYS=1) endif() -option(SECP256K1_ENABLE_MODULE_ELLSWIFT "Enable ElligatorSwift module." ON) -if(SECP256K1_ENABLE_MODULE_ELLSWIFT) - add_compile_definitions(ENABLE_MODULE_ELLSWIFT=1) +if(SECP256K1_ENABLE_MODULE_RECOVERY) + add_compile_definitions(ENABLE_MODULE_RECOVERY=1) +endif() + +if(SECP256K1_ENABLE_MODULE_ECDH) + add_compile_definitions(ENABLE_MODULE_ECDH=1) endif() option(SECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS "Enable external default callback functions." OFF) @@ -254,9 +270,14 @@ if(SECP256K1_BUILD_BENCHMARK OR SECP256K1_BUILD_TESTS OR SECP256K1_BUILD_EXHAUST enable_testing() endif() +set(SECP256K1_LATE_CFLAGS "" CACHE STRING "Compiler flags that are added to the command line after all other flags added by the build system.") +include(AllTargetsCompileOptions) + add_subdirectory(src) +all_targets_compile_options(src "${SECP256K1_LATE_CFLAGS}") if(SECP256K1_BUILD_EXAMPLES) add_subdirectory(examples) + all_targets_compile_options(examples "${SECP256K1_LATE_CFLAGS}") endif() message("\n") @@ -276,6 +297,7 @@ message(" ECDSA pubkey recovery ............... ${SECP256K1_ENABLE_MODULE_RECOV message(" extrakeys ........................... ${SECP256K1_ENABLE_MODULE_EXTRAKEYS}") message(" schnorrsig .......................... ${SECP256K1_ENABLE_MODULE_SCHNORRSIG}") message(" ElligatorSwift ...................... ${SECP256K1_ENABLE_MODULE_ELLSWIFT}") +message(" Silent Payments ..................... ${SECP256K1_ENABLE_MODULE_SILENTPAYMENTS}") message("Parameters:") message(" ecmult window size .................. ${SECP256K1_ECMULT_WINDOW_SIZE}") message(" ecmult gen precision bits ........... ${SECP256K1_ECMULT_GEN_PREC_BITS}") @@ -330,6 +352,9 @@ else() message(" - LDFLAGS for executables ............ ${CMAKE_EXE_LINKER_FLAGS_DEBUG}") message(" - LDFLAGS for shared libraries ....... ${CMAKE_SHARED_LINKER_FLAGS_DEBUG}") endif() +if(SECP256K1_LATE_CFLAGS) + message("SECP256K1_LATE_CFLAGS ................. ${SECP256K1_LATE_CFLAGS}") +endif() message("\n") if(SECP256K1_EXPERIMENTAL) message( diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a5e457913acf83..5fbf7332c97a31 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,7 +44,7 @@ The Contributor Workflow & Peer Review in libsecp256k1 are similar to Bitcoin Co In addition, libsecp256k1 tries to maintain the following coding conventions: -* No runtime heap allocation (e.g., no `malloc`) unless explicitly requested by the caller (via `secp256k1_context_create` or `secp256k1_scratch_space_create`, for example). Morever, it should be possible to use the library without any heap allocations. +* No runtime heap allocation (e.g., no `malloc`) unless explicitly requested by the caller (via `secp256k1_context_create` or `secp256k1_scratch_space_create`, for example). Moreover, it should be possible to use the library without any heap allocations. * The tests should cover all lines and branches of the library (see [Test coverage](#coverage)). * Operations involving secret data should be tested for being constant time with respect to the secrets (see [src/ctime_tests.c](src/ctime_tests.c)). * Local variables containing secret data should be cleared explicitly to try to delete secrets from memory. diff --git a/Makefile.am b/Makefile.am index 549861791569d2..d9295799bc9f5c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -271,3 +271,7 @@ endif if ENABLE_MODULE_ELLSWIFT include src/modules/ellswift/Makefile.am.include endif + +if ENABLE_MODULE_SILENTPAYMENTS +include src/modules/silentpayments/Makefile.am.include +endif diff --git a/README.md b/README.md index 4013e6a93b892f..6e88eb4ecbcd49 100644 --- a/README.md +++ b/README.md @@ -79,9 +79,9 @@ To maintain a pristine source tree, CMake encourages to perform an out-of-source $ mkdir build && cd build $ cmake .. - $ make - $ make check # run the test suite - $ sudo make install # optional + $ cmake --build . + $ ctest # run the test suite + $ sudo cmake --build . --target install # optional To compile optional modules (such as Schnorr signatures), you need to run `cmake` with additional flags (such as `-DSECP256K1_ENABLE_MODULE_SCHNORRSIG=ON`). Run `cmake .. -LH` to see the full list of available flags. diff --git a/bip352-testsuite/bech32m.py b/bip352-testsuite/bech32m.py new file mode 100644 index 00000000000000..795e1538639c40 --- /dev/null +++ b/bip352-testsuite/bech32m.py @@ -0,0 +1,135 @@ +# Copyright (c) 2017, 2020 Pieter Wuille +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +"""Reference implementation for Bech32/Bech32m and segwit addresses.""" + + +from enum import Enum + +class Encoding(Enum): + """Enumeration type to list the various supported encodings.""" + BECH32 = 1 + BECH32M = 2 + +CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" +BECH32M_CONST = 0x2bc830a3 + +def bech32_polymod(values): + """Internal function that computes the Bech32 checksum.""" + generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] + chk = 1 + for value in values: + top = chk >> 25 + chk = (chk & 0x1ffffff) << 5 ^ value + for i in range(5): + chk ^= generator[i] if ((top >> i) & 1) else 0 + return chk + + +def bech32_hrp_expand(hrp): + """Expand the HRP into values for checksum computation.""" + return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp] + + +def bech32_verify_checksum(hrp, data): + """Verify a checksum given HRP and converted data characters.""" + const = bech32_polymod(bech32_hrp_expand(hrp) + data) + if const == 1: + return Encoding.BECH32 + if const == BECH32M_CONST: + return Encoding.BECH32M + return None + +def bech32_create_checksum(hrp, data, spec): + """Compute the checksum values given HRP and data.""" + values = bech32_hrp_expand(hrp) + data + const = BECH32M_CONST if spec == Encoding.BECH32M else 1 + polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const + return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] + + +def bech32_encode(hrp, data, spec): + """Compute a Bech32 string given HRP and data values.""" + combined = data + bech32_create_checksum(hrp, data, spec) + return hrp + '1' + ''.join([CHARSET[d] for d in combined]) + +def bech32_decode(bech): + """Validate a Bech32/Bech32m string, and determine HRP and data.""" + if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or + (bech.lower() != bech and bech.upper() != bech)): + return (None, None, None) + bech = bech.lower() + pos = bech.rfind('1') + + # remove the requirement that bech32m be less than 90 chars + if pos < 1 or pos + 7 > len(bech): + return (None, None, None) + if not all(x in CHARSET for x in bech[pos+1:]): + return (None, None, None) + hrp = bech[:pos] + data = [CHARSET.find(x) for x in bech[pos+1:]] + spec = bech32_verify_checksum(hrp, data) + if spec is None: + return (None, None, None) + return (hrp, data[:-6], spec) + +def convertbits(data, frombits, tobits, pad=True): + """General power-of-2 base conversion.""" + acc = 0 + bits = 0 + ret = [] + maxv = (1 << tobits) - 1 + max_acc = (1 << (frombits + tobits - 1)) - 1 + for value in data: + if value < 0 or (value >> frombits): + return None + acc = ((acc << frombits) | value) & max_acc + bits += frombits + while bits >= tobits: + bits -= tobits + ret.append((acc >> bits) & maxv) + if pad: + if bits: + ret.append((acc << (tobits - bits)) & maxv) + elif bits >= frombits or ((acc << (tobits - bits)) & maxv): + return None + return ret + + +def decode(hrp, addr): + """Decode a segwit address.""" + hrpgot, data, spec = bech32_decode(addr) + if hrpgot != hrp: + return (None, None) + decoded = convertbits(data[1:], 5, 8, False) + if decoded is None or len(decoded) < 2: + return (None, None) + if data[0] > 16: + return (None, None) + return (data[0], decoded) + + +def encode(hrp, witver, witprog): + """Encode a segwit address.""" + spec = Encoding.BECH32 if witver == 0 else Encoding.BECH32M + ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5), spec) + if decode(hrp, ret) == (None, None): + return None + return ret diff --git a/bip352-testsuite/bip352-send_and_receive_test_vectors.json b/bip352-testsuite/bip352-send_and_receive_test_vectors.json new file mode 100644 index 00000000000000..6ceb55d78ac35b --- /dev/null +++ b/bip352-testsuite/bip352-send_and_receive_test_vectors.json @@ -0,0 +1,2039 @@ +[ + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1", + "priv_key_tweak": "f438b40179a3c4262de12986c0e6cce0634007cdc79c1dcd3e20b9ebc2e7eef6", + "signature": "74f85b856337fbe837643b86f462118159f93ac4acc2671522f27e8f67b079959195ccc7a5dbee396d2909f5d680d6e30cda7359aa2755822509b70d6b0687a1" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1", + "priv_key_tweak": "f438b40179a3c4262de12986c0e6cce0634007cdc79c1dcd3e20b9ebc2e7eef6", + "signature": "74f85b856337fbe837643b86f462118159f93ac4acc2671522f27e8f67b079959195ccc7a5dbee396d2909f5d680d6e30cda7359aa2755822509b70d6b0687a1" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "79e71baa2ba3fc66396de3a04f168c7bf24d6870ec88ca877754790c1db357b6", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "79e71baa2ba3fc66396de3a04f168c7bf24d6870ec88ca877754790c1db357b6", + "priv_key_tweak": "4851455bfbe1ab4f80156570aa45063201aa5c9e1b1dcd29f0f8c33d10bf77ae", + "signature": "10332eea808b6a13f70059a8a73195808db782012907f5ba32b6eae66a2f66b4f65147e2b968a1678c5f73d57d5d195dbaf667b606ff80c8490eac1f3b710657" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "f4c2da807f89cb1501f1a77322a895acfb93c28e08ed2724d2beb8e44539ba38", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "f4c2da807f89cb1501f1a77322a895acfb93c28e08ed2724d2beb8e44539ba38", + "priv_key_tweak": "ab0c9b87181bf527879f48db9f14a02233619b986f8e8f2d5d408ce68a709f51", + "signature": "398a9790865791a9db41a8015afad3a47d60fec5086c50557806a49a1bc038808632b8fe679a7bb65fc6b455be994502eed849f1da3729cd948fc7be73d67295" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "548ae55c8eec1e736e8d3e520f011f1f42a56d166116ad210b3937599f87f566", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "548ae55c8eec1e736e8d3e520f011f1f42a56d166116ad210b3937599f87f566", + "priv_key_tweak": "f032695e2636619efa523fffaa9ef93c8802299181fd0461913c1b8daf9784cd", + "signature": "f238386c5d5e5444f8d2c75aabbcb28c346f208c76f60823f5de3b67b79e0ec72ea5de2d7caec314e0971d3454f122dda342b3eede01b3857e83654e36b25f76" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "de88bea8e7ffc9ce1af30d1132f910323c505185aec8eae361670421e749a1fb", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "de88bea8e7ffc9ce1af30d1132f910323c505185aec8eae361670421e749a1fb", + "priv_key_tweak": "3fb9ce5ce1746ced103c8ed254e81f6690764637ddbc876ec1f9b3ddab776b03", + "signature": "c5acd25a8f021a4192f93bc34403fd8b76484613466336fb259c72d04c169824f2690ca34e96cee86b69f376c8377003268fda56feeb1b873e5783d7e19bcca5" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "77cab7dd12b10259ee82c6ea4b509774e33e7078e7138f568092241bf26b99f1", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "77cab7dd12b10259ee82c6ea4b509774e33e7078e7138f568092241bf26b99f1", + "priv_key_tweak": "f5382508609771068ed079b24e1f72e4a17ee6d1c979066bf1d4e2a5676f09d4", + "signature": "ff65833b8fd1ed3ef9d0443b4f702b45a3f2dd457ba247687e8207745c3be9d2bdad0ab3f07118f8b2efc6a04b95f7b3e218daf8a64137ec91bd2fc67fc137a5" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "30523cca96b2a9ae3c98beb5e60f7d190ec5bc79b2d11a0b2d4d09a608c448f0", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "30523cca96b2a9ae3c98beb5e60f7d190ec5bc79b2d11a0b2d4d09a608c448f0", + "priv_key_tweak": "b40017865c79b1fcbed68896791be93186d08f47e416b289b8c063777e14e8df", + "signature": "d1edeea28cf1033bcb3d89376cabaaaa2886cbd8fda112b5c61cc90a4e7f1878bdd62180b07d1dfc8ffee1863c525a0c7b5bcd413183282cfda756cb65787266" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "359358f59ee9e9eec3f00bdf4882570fd5c182e451aa2650b788544aff012a3a", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "359358f59ee9e9eec3f00bdf4882570fd5c182e451aa2650b788544aff012a3a", + "priv_key_tweak": "a2f9dd05d1d398347c885d9c61a64d18a264de6d49cea4326bafc2791d627fa7", + "signature": "96038ad233d8befe342573a6e54828d863471fb2afbad575cc65271a2a649480ea14912b6abbd3fbf92efc1928c036f6e3eef927105af4ec1dd57cb909f360b8" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 2.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 3.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + 2.0 + ], + [ + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + 3.0 + ] + ] + } + } + ], + "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": [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + }, + { + "pub_key": "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "priv_key_tweak": "d97e442d110c0bdd31161a7bb6e7862e038d02a09b1484dfbb463f2e0f7c9230", + "signature": "29bd25d0f808d7fcd2aa6d5ed206053899198397506c301b218a9e47a3d7070af03e903ff718978d50d1b6b9af8cc0e313d84eda5d5b1e8e85e5516d630bbeb9" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 2.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 3.0 + ], + [ + "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn", + 4.0 + ], + [ + "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn", + 5.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + 2.0 + ], + [ + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + 3.0 + ], + [ + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + 4.0 + ], + [ + "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a", + 5.0 + ] + ] + } + } + ], + "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": [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + }, + { + "pub_key": "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "priv_key_tweak": "d97e442d110c0bdd31161a7bb6e7862e038d02a09b1484dfbb463f2e0f7c9230", + "signature": "29bd25d0f808d7fcd2aa6d5ed206053899198397506c301b218a9e47a3d7070af03e903ff718978d50d1b6b9af8cc0e313d84eda5d5b1e8e85e5516d630bbeb9" + } + ] + } + }, + { + "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": [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a" + ], + "key_material": { + "spend_priv_key": "9902c3c56e84002a7cd410113a9ab21d142be7f53cf5200720bb01314c5eb920", + "scan_priv_key": "060b751d7892149006ed7b98606955a29fe284a1e900070c0971f5fb93dbf422" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn" + ], + "outputs": [ + { + "pub_key": "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "priv_key_tweak": "2f17ea873a0047fc01ba8010fef0969e76d0e4283f600d48f735098b1fee6eb9", + "signature": "c26f4e3cf371b90b840f48ea0e761b5ec31883ed55719f9ef06a90e282d85f565790ab780a3f491bc2668cc64e944dca849d1022a878cdadb8d168b8da4a6da3" + }, + { + "pub_key": "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a", + "priv_key_tweak": "72cd082cccb633bf85240a83494b32dc943a4d05647a6686d23ad4ca59c0ebe4", + "signature": "38745f3d9f5eef0b1cfb17ca314efa8c521efab28a23aa20ec5e3abb561d42804d539906dce60c4ee7977966184e6f2cab1faa0e5377ceb7148ec5218b4e7878" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjex54dmqmmv6rw353tsuqhs99ydvadxzrsy9nuvk74epvee55drs734pqq", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "d014d4860f67d607d60b1af70e0ee236b99658b61bb769832acbbe87c374439a", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "d014d4860f67d607d60b1af70e0ee236b99658b61bb769832acbbe87c374439a", + "priv_key_tweak": "51d4e9d0d482b5700109b4b2e16ff508269b03d800192a043d61dca4a0a72a52", + "signature": "c30fa63bad6f0a317f39a773a5cbf0b0f8193c71dfebba05ee6ae4ed28e3775e6e04c3ea70a83703bb888122855dc894cab61692e7fd10c9b3494d479a60785e" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqsg59z2rppn4qlkx0yz9sdltmjv3j8zgcqadjn4ug98m3t6plujsq9qvu5n", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "67626aebb3c4307cf0f6c39ca23247598fabf675ab783292eb2f81ae75ad1f8c", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "67626aebb3c4307cf0f6c39ca23247598fabf675ab783292eb2f81ae75ad1f8c", + "priv_key_tweak": "6024ae214876356b8d917716e7707d267ae16a0fdb07de2a786b74a7bbcddead", + "signature": "a86d554d0d6b7aa0907155f7e0b47f0182752472fffaeddd68da90e99b9402f166fd9b33039c302c7115098d971c1399e67c19e9e4de180b10ea0b9d6f0db832" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq7c2zfthc6x3a5yecwc52nxa0kfd20xuz08zyrjpfw4l2j257yq6qgnkdh5", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "7efa60ce78ac343df8a013a2027c6c5ef29f9502edcbd769d2c21717fecc5951", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "7efa60ce78ac343df8a013a2027c6c5ef29f9502edcbd769d2c21717fecc5951", + "priv_key_tweak": "e336b92330c33030285ce42e4115ad92d5197913c88e06b9072b4a9b47c664a2", + "signature": "c9e80dd3bdd25ca2d352ce77510f1aed37ba3509dc8cc0677f2d7c2dd04090707950ce9dd6c83d2a428063063aff5c04f1744e334f661f2fc01b4ef80b50f739" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + 2.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + 1.0 + ], + [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + 2.0 + ] + ] + } + } + ], + "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": [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 1 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj" + ], + "outputs": [ + { + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + }, + { + "pub_key": "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "priv_key_tweak": "43100f89f1a6bf10081c92b473ffc57ceac7dbed600b6aba9bb3976f17dbb914", + "signature": "15c92509b67a6c211ebb4a51b7528d0666e6720de2343b2e92cfb97942ca14693c1f1fdc8451acfdb2644039f8f5c76114807fdc3d3a002d8a46afab6756bd75" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + 3.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + 4.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + 3.0 + ], + [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + 4.0 + ] + ] + } + } + ], + "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": [ + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 1 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj" + ], + "outputs": [ + { + "pub_key": "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "priv_key_tweak": "9d5fd3b91cac9ddfea6fc2e6f9386f680e6cee623cda02f53706306c081de87f", + "signature": "db0dfacc98b6a6fcc67cc4631f080b1ca38c60d8c397f2f19843f8f95ec91594b24e47c5bd39480a861c1209f7e3145c440371f9191fb96e324690101eac8e8e" + }, + { + "pub_key": "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "priv_key_tweak": "43100f89f1a6bf10081c92b473ffc57ceac7dbed600b6aba9bb3976f17dbb914", + "signature": "15c92509b67a6c211ebb4a51b7528d0666e6720de2343b2e92cfb97942ca14693c1f1fdc8451acfdb2644039f8f5c76114807fdc3d3a002d8a46afab6756bd75" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 5.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + 6.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjyh2ju7hd5gj57jg5r9lev3pckk4n2shtzaq34467erzzdfajfggty6aa5", + 7.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjyh2ju7hd5gj57jg5r9lev3pckk4n2shtzaq34467erzzdfajfggty6aa5", + 8.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + 5.0 + ], + [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + 6.0 + ], + [ + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + 7.0 + ], + [ + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b", + 8.0 + ] + ] + } + } + ], + "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": [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 1 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj" + ], + "outputs": [ + { + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + }, + { + "pub_key": "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "priv_key_tweak": "43100f89f1a6bf10081c92b473ffc57ceac7dbed600b6aba9bb3976f17dbb914", + "signature": "15c92509b67a6c211ebb4a51b7528d0666e6720de2343b2e92cfb97942ca14693c1f1fdc8451acfdb2644039f8f5c76114807fdc3d3a002d8a46afab6756bd75" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ], + [ + "sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqlv6saelkk5snl4wfutyxrchpzzwm8rjp3z6q7apna59z9huq4x754e5atr", + 2.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + 1.0 + ], + [ + "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff", + 2.0 + ] + ] + } + } + ], + "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": [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff" + ], + "key_material": { + "spend_priv_key": "b8f87388cbb41934c50daca018901b00070a5ff6cc25a7e9e716a9d5b9e4d664", + "scan_priv_key": "11b7a82e06ca2648d5fded2366478078ec4fc9dc1d8ff487518226f229d768fd" + }, + "labels": [ + 0 + ] + }, + "expected": { + "addresses": [ + "sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqauj52ymtc4xdkmx3tgyhrsemg2g3303xk2gtzfy8h8ejet8fz8jcw23zua", + "sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqlv6saelkk5snl4wfutyxrchpzzwm8rjp3z6q7apna59z9huq4x754e5atr" + ], + "outputs": [ + { + "pub_key": "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff", + "priv_key_tweak": "80cd767ed20bd0bb7d8ea5e803f8c381293a62e8a073cf46fb0081da46e64e1f", + "signature": "7fbd5074cf1377273155eefafc7c330cb61b31da252f22206ac27530d2b2567040d9af7808342ed4a09598c26d8307446e4ed77079e6a2e61fea736e44da5f5a" + } + ] + } + }, + { + "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": [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + } + ] + } + } + ] + } +] \ No newline at end of file diff --git a/bip352-testsuite/ripemd160.py b/bip352-testsuite/ripemd160.py new file mode 100644 index 00000000000000..12801364b406a3 --- /dev/null +++ b/bip352-testsuite/ripemd160.py @@ -0,0 +1,130 @@ +# Copyright (c) 2021 Pieter Wuille +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test-only pure Python RIPEMD160 implementation.""" + +import unittest + +# Message schedule indexes for the left path. +ML = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 +] + +# Message schedule indexes for the right path. +MR = [ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 +] + +# Rotation counts for the left path. +RL = [ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 +] + +# Rotation counts for the right path. +RR = [ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 +] + +# K constants for the left path. +KL = [0, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e] + +# K constants for the right path. +KR = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0] + + +def fi(x, y, z, i): + """The f1, f2, f3, f4, and f5 functions from the specification.""" + if i == 0: + return x ^ y ^ z + elif i == 1: + return (x & y) | (~x & z) + elif i == 2: + return (x | ~y) ^ z + elif i == 3: + return (x & z) | (y & ~z) + elif i == 4: + return x ^ (y | ~z) + else: + assert False + + +def rol(x, i): + """Rotate the bottom 32 bits of x left by i bits.""" + return ((x << i) | ((x & 0xffffffff) >> (32 - i))) & 0xffffffff + + +def compress(h0, h1, h2, h3, h4, block): + """Compress state (h0, h1, h2, h3, h4) with block.""" + # Left path variables. + al, bl, cl, dl, el = h0, h1, h2, h3, h4 + # Right path variables. + ar, br, cr, dr, er = h0, h1, h2, h3, h4 + # Message variables. + x = [int.from_bytes(block[4*i:4*(i+1)], 'little') for i in range(16)] + + # Iterate over the 80 rounds of the compression. + for j in range(80): + rnd = j >> 4 + # Perform left side of the transformation. + al = rol(al + fi(bl, cl, dl, rnd) + x[ML[j]] + KL[rnd], RL[j]) + el + al, bl, cl, dl, el = el, al, bl, rol(cl, 10), dl + # Perform right side of the transformation. + ar = rol(ar + fi(br, cr, dr, 4 - rnd) + x[MR[j]] + KR[rnd], RR[j]) + er + ar, br, cr, dr, er = er, ar, br, rol(cr, 10), dr + + # Compose old state, left transform, and right transform into new state. + return h1 + cl + dr, h2 + dl + er, h3 + el + ar, h4 + al + br, h0 + bl + cr + + +def ripemd160(data): + """Compute the RIPEMD-160 hash of data.""" + # Initialize state. + state = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0) + # Process full 64-byte blocks in the input. + for b in range(len(data) >> 6): + state = compress(*state, data[64*b:64*(b+1)]) + # Construct final blocks (with padding and size). + pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63) + fin = data[len(data) & ~63:] + pad + (8 * len(data)).to_bytes(8, 'little') + # Process final blocks. + for b in range(len(fin) >> 6): + state = compress(*state, fin[64*b:64*(b+1)]) + # Produce output. + return b"".join((h & 0xffffffff).to_bytes(4, 'little') for h in state) + + +class TestFrameworkKey(unittest.TestCase): + def test_ripemd160(self): + """RIPEMD-160 test vectors.""" + # See https://homes.esat.kuleuven.be/~bosselae/ripemd160.html + for msg, hexout in [ + (b"", "9c1185a5c5e9fc54612808977ee8f548b2258d31"), + (b"a", "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"), + (b"abc", "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"), + (b"message digest", "5d0689ef49d2fae572b881b123a85ffa21595f36"), + (b"abcdefghijklmnopqrstuvwxyz", + "f71c27109c692c1b56bbdceb5b9d2865b3708dbc"), + (b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "12a053384a9c0c88e405a06c27dcf49ada62eb2b"), + (b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + "b0e20b6e3116640286ed3a87a5713079b21f5189"), + (b"1234567890" * 8, "9b752e45573d4b39f4dbd3323cab82bf63326bfb"), + (b"a" * 1000000, "52783243c1697bdbe16d37f97f68f08325dc1528") + ]: + self.assertEqual(ripemd160(msg).hex(), hexout) diff --git a/bip352-testsuite/run_bip352_tests.py b/bip352-testsuite/run_bip352_tests.py new file mode 100755 index 00000000000000..d1880487a3165e --- /dev/null +++ b/bip352-testsuite/run_bip352_tests.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python3 +import bech32m +import hashlib +import json +import ripemd160 +import secp256k1_glue + +SIGN_MSG = hashlib.sha256(b"message").digest() +AUX_MSG = hashlib.sha256(b"random auxiliary data").digest() + +def sha256(s): + return hashlib.sha256(s).digest() + +def hash160(s): + return ripemd160.ripemd160(sha256(s)) + +def TaggedHash(tag, data): + return sha256(sha256(tag) + sha256(tag) + data) + +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 encode_silent_payment_address(scan_pubkey, spend_pubkey): + assert len(scan_pubkey) == 33 + assert len(spend_pubkey) == 33 + data = bech32m.convertbits(scan_pubkey + spend_pubkey, 8, 5) + return bech32m.bech32_encode("sp", [0] + data, bech32m.Encoding.BECH32M) + +def decode_silent_payments_address(address): + version, data = bech32m.decode("sp", address) + data = bytes(data) # convert from list to bytes + assert len(data) == 66 + return data[:33], data[33:] + +def is_p2tr(s): # OP_1 OP_PUSHBYTES_32 <32 bytes> + return (len(s) == 34) and (s[0] == 0x51) and (s[1] == 0x20) + +def is_p2wpkh(s): # OP_0 OP_PUSHBYTES_20 <20 bytes> + return (len(s) == 22) and (s[0] == 0x00) and (s[1] == 0x14) + +def is_p2sh(s): # OP_HASH160 OP_PUSHBYTES_20 <20 bytes> OP_EQUAL + return (len(s) == 23) and (s[0] == 0xA9) and (s[1] == 0x14) and (s[-1] == 0x87) + +def is_p2pkh(s): # OP_DUP OP_HASH160 OP_PUSHBYTES_20 <20 bytes> OP_EQUALVERIFY OP_CHECKSIG + return (len(s) == 25) and (s[0] == 0x76) and (s[1] == 0xA9) and (s[2] == 0x14) and (s[-2] == 0x88) and (s[-1] == 0xAC) + +def get_pubkey_from_input(spk, script_sig, witness): + if is_p2pkh(spk): + spk_pkh = spk[3:3 + 20] + for i in range(len(script_sig), 0, -1): + if i - 33 >= 0: + pk = script_sig[i - 33:i] + if hash160(pk) == spk_pkh: + return pk + # should never happen, as this would be an invalid spend + assert False + if is_p2sh(spk): + redeem_script = script_sig[1:] + if is_p2wpkh(redeem_script): + return redeem_script[-33:] + if is_p2wpkh(spk): + # the witness must contain two items and the second item is the pubkey + return witness[-33:] + if is_p2tr(spk): + return spk[2:] + assert False + +with open('./bip352-send_and_receive_test_vectors.json') as f: + test_vectors = json.load(f) + +passed_send, passed_receive = 0, 0 +for test_nr, test_vector in enumerate(test_vectors): + print(f"----- Test vector: \'{test_vector['comment']}\' {test_nr+1}/{len(test_vectors)} -----") + ###################### sending ######################### + assert len(test_vector['sending']) == 1 + send_data_given = test_vector['sending'][0]['given'] + send_data_expected = test_vector['sending'][0]['expected'] + input_priv_keys = [] + outpoints = [] + for i in send_data_given['vin']: + priv_key = i['private_key'] + pub_key = get_pubkey_from_input(bytes.fromhex(i['prevout']['scriptPubKey']['hex']), + bytes.fromhex(i['scriptSig']), bytes.fromhex(i['txinwitness'])) + input_priv_keys.append((i['private_key'], len(pub_key) == 32)) + outpoints.append((i['txid'], i['vout'])) + outpoint_L = smallest_outpoint(outpoints) + outputs_calculated = [] + + groups = {} + for recipient_address, recipient_value in send_data_given['recipients']: + recipient_B_scan, recipient_B_spend = decode_silent_payments_address(recipient_address) + groups.setdefault(recipient_B_scan, []).append((recipient_B_spend, recipient_value)) + + for recipient_B_scan, recipient_spend_data in groups.items(): + plain_seckeys, xonly_seckeys = [], [] + for seckey_hex, is_taproot in input_priv_keys: + (xonly_seckeys if is_taproot else plain_seckeys).append(bytes.fromhex(seckey_hex)) + a_sum, input_hash = secp256k1_glue.silentpayments_create_private_tweak_data( + plain_seckeys, xonly_seckeys, outpoint_L) + recipient_B_scan_obj = secp256k1_glue.pubkey_parse(recipient_B_scan) + shared_secret = secp256k1_glue.silentpayments_create_shared_secret(recipient_B_scan_obj, a_sum, input_hash) + k = 0 + for recipient_B_spend in recipient_spend_data: + output_xonly = secp256k1_glue.silentpayments_sender_create_output_pubkey(shared_secret, recipient_B_spend[0], k) + outputs_calculated.append(output_xonly.hex()) + k += 1 + outputs_expected = [o[0] for o in send_data_expected['outputs']] + if outputs_calculated == outputs_expected: + print("Sending test \033[0;32mPASSED. ✓\033[0m") + passed_send += 1 + else: + print("Sending test \033[0;31mFAILED. ✖\033[0m") + print(f"Calculated outputs: {outputs_calculated}") + print(f"Expected outputs: {outputs_expected}") + + ###################### receiving ######################## + assert len(test_vector['receiving']) >= 1 + for subtest_nr, receive_data in enumerate(test_vector['receiving']): + receive_data_given = receive_data['given'] + receive_data_expected = receive_data['expected'] + + input_pub_keys = [] + outpoints = [] + for i in receive_data_given['vin']: + pub_key = get_pubkey_from_input(bytes.fromhex(i['prevout']['scriptPubKey']['hex']), + bytes.fromhex(i['scriptSig']), bytes.fromhex(i['txinwitness'])) + input_pub_keys.append(pub_key) + outpoints.append((i['txid'], i['vout'])) + receive_outpoint_L = smallest_outpoint(outpoints) + + # test data sanity check: outpoint_L of send and receive has to match + assert receive_outpoint_L == outpoint_L + # derive tweak_data and shared_secret + plain_pubkeys, xonly_pubkeys = [], [] + for pubkey in input_pub_keys: + if len(pubkey) == 32: + xonly_pubkeys.append(pubkey) + elif len(pubkey) == 33: + plain_pubkeys.append(pubkey) + else: + assert False + A_sum, input_hash2 = secp256k1_glue.silentpayments_create_tweak_data(plain_pubkeys, xonly_pubkeys, receive_outpoint_L) + scan_privkey = bytes.fromhex(receive_data_given['key_material']['scan_priv_key']) + spend_privkey = bytes.fromhex(receive_data_given['key_material']['spend_priv_key']) + # spend pubkey is not in the given data of the receiver part, so let's compute it + scan_pubkey = secp256k1_glue.pubkey_serialize((secp256k1_glue.pubkey_create(scan_privkey))) + spend_pubkey = secp256k1_glue.pubkey_serialize((secp256k1_glue.pubkey_create(spend_privkey))) + shared_secret = secp256k1_glue.silentpayments_create_shared_secret(A_sum, scan_privkey, input_hash2) + + A_tweaked = secp256k1_glue.silentpayments_create_tweaked_pubkey(A_sum, input_hash2) + shared_secret_lightclient = secp256k1_glue.silentpayments_create_shared_secret(A_tweaked, scan_privkey, None) + assert shared_secret == shared_secret_lightclient + + outputs_pubkeys_expected = [o['pub_key'] for o in receive_data_expected['outputs']] + outputs_privtweaks_expected = [o['priv_key_tweak'] for o in receive_data_expected['outputs']] + outputs_signatures_expected = [o['signature'] for o in receive_data_expected['outputs']] + outputs_scanned = [] + outputs_privtweaks = [] + outputs_signatures = [] + + # scan through outputs + k = 0 + outputs_to_check = receive_data_given['outputs'].copy() + # create labels cache + labels_cache = {} + for label_m in receive_data_given['labels']: + label, label_tweak = secp256k1_glue.silentpayments_create_label_tweak(scan_privkey, label_m) + labels_cache[label] = label_tweak + + while True: + if len(outputs_to_check) == 0: + break + for output_to_check in outputs_to_check: + found_sth = False + direct_match, t_k, label1, label2 = secp256k1_glue.silentpayments_receiver_scan_output(shared_secret, spend_pubkey, k, bytes.fromhex(output_to_check)) + if direct_match: + outputs_to_check.remove(output_to_check) + outputs_scanned.append(output_to_check) + outputs_privtweaks.append(t_k.hex()) + full_privkey = secp256k1_glue.silentpayments_create_output_seckey(spend_privkey, t_k) + outputs_signatures.append(secp256k1_glue.schnorrsig_sign32( + SIGN_MSG, full_privkey, AUX_MSG).hex()) + k += 1 + found_sth = True + break + # now, if there was no match yet, scan for labels + label_found = None + for label_candidate in [label1, label2]: + if label_candidate in labels_cache: + label_found = label_candidate + break + if label_found: + outputs_to_check.remove(output_to_check) + outputs_scanned.append(output_to_check) + privkey_tweak = secp256k1_glue.seckey_add(t_k, labels_cache[label_found]) + outputs_privtweaks.append(privkey_tweak.hex()) + full_privkey = secp256k1_glue.silentpayments_create_output_seckey( + spend_privkey, t_k, labels_cache[label_found]) + outputs_signatures.append(secp256k1_glue.schnorrsig_sign32( + SIGN_MSG, full_privkey, AUX_MSG).hex()) + k += 1 + found_sth = True + break + + if not found_sth: + break + + all_subtests_passed = True + # check if addresses match + calculated_addresses = [] + for label_m in [None] + receive_data_given['labels']: + if label_m is None: + output_spend_pubkey = spend_pubkey + else: + label, _ = secp256k1_glue.silentpayments_create_label_tweak(scan_privkey, label_m) + output_spend_pubkey = secp256k1_glue.silentpayments_create_address_spend_pubkey(spend_pubkey, label) + output_address = encode_silent_payment_address(scan_pubkey, output_spend_pubkey) + calculated_addresses.append(output_address) + + if calculated_addresses == receive_data_expected['addresses']: + print(f"Receiving sub-test [addresses] {subtest_nr+1}/{len(test_vector['receiving'])} \033[0;32mPASSED. ✓\033[0m") + else: + print(f"Receiving sub-test [addresses] {subtest_nr+1}/{len(test_vector['receiving'])} \033[0;31mFAILED. ✖\033[0m") + print(f"Calculated addresses: {calculated_addresses}") + print(f"Expected addresses: {receive_data_expected['addresses']}") + all_subtests_passed = False + + # check if output pubkey match + if outputs_scanned == outputs_pubkeys_expected: + print(f"Receiving sub-test [output pubkey] {subtest_nr+1}/{len(test_vector['receiving'])} \033[0;32mPASSED. ✓\033[0m") + else: + print(f"Receiving sub-test [output pubkey] {subtest_nr+1}/{len(test_vector['receiving'])} \033[0;31mFAILED. ✖\033[0m") + print(f"Scanned output pubkeys: {outputs_scanned}") + print(f"Expected outputs pubkeys: {outputs_expected}") + all_subtests_passed = False + + # check if output spending keys also match + if outputs_privtweaks == outputs_privtweaks_expected: + print(f"Receiving sub-test [output privtweak] {subtest_nr+1}/{len(test_vector['receiving'])} \033[0;32mPASSED. ✓\033[0m") + else: + print(f"Receiving sub-test [output privtweak] {subtest_nr+1}/{len(test_vector['receiving'])} \033[0;31mFAILED. ✖\033[0m") + print(f"Calculated output privtweaks: {outputs_privtweaks}") + print(f"Expected outputs privtweaks: {outputs_privtweaks_expected}") + all_subtests_passed = False + + # check if output signatures also match + if outputs_signatures == outputs_signatures_expected: + print(f"Receiving sub-test [output signature] {subtest_nr+1}/{len(test_vector['receiving'])} \033[0;32mPASSED. ✓\033[0m") + else: + print(f"Receiving sub-test [output signature] {subtest_nr+1}/{len(test_vector['receiving'])} \033[0;31mFAILED. ✖\033[0m") + print(f"Calculated output signatures: {outputs_signatures}") + print(f"Expected outputs signatures: {outputs_signatures_expected}") + all_subtests_passed = False + + if all_subtests_passed: + print("Receiving test \033[0;32mPASSED. ✓\033[0m") + passed_receive += 1 + else: + print("Receiving test \033[0;31mFAILED. ✖\033[0m") + +print( "+=================================+") +print( "| Summary: |") +print( "+---------------------------------+") +print(f"| {passed_send}/{len(test_vectors)} sending tests passed. |") +print(f"| {passed_receive}/{len(test_vectors)} receiving tests passed. |") +print( "+=================================+") diff --git a/bip352-testsuite/secp256k1_glue.py b/bip352-testsuite/secp256k1_glue.py new file mode 100644 index 00000000000000..19ac3ac24a1ce4 --- /dev/null +++ b/bip352-testsuite/secp256k1_glue.py @@ -0,0 +1,388 @@ +from ctypes import * +import os + +secplib = CDLL('../.libs/libsecp256k1.so') +secplib.secp256k1_context_create.restype = c_voidp +secp_context = secplib.secp256k1_context_create(1) + +################################## +### API calls from secp256k1.h ### +################################## +SECP256K1_PUBKEY_STRUCT_SIZE = 64 +""" +int secp256k1_ec_pubkey_create( + const secp256k1_context *ctx, + secp256k1_pubkey *pubkey, + const unsigned char *seckey +); +""" +secplib.secp256k1_ec_pubkey_create.argtypes = [c_voidp, c_voidp, c_voidp] +secplib.secp256k1_ec_pubkey_create.restype = c_int +def pubkey_create(privkey_bytes): + pk = create_string_buffer(SECP256K1_PUBKEY_STRUCT_SIZE) + retval = secplib.secp256k1_ec_pubkey_create(secp_context, pk, privkey_bytes) + assert retval == 1 + return pk + +""" +int secp256k1_ec_pubkey_parse( + const secp256k1_context *ctx, + secp256k1_pubkey *pubkey, + const unsigned char *input, + size_t inputlen +); +""" +secplib.secp256k1_ec_pubkey_parse.argtypes = [c_voidp, c_voidp, c_voidp, c_size_t] +secplib.secp256k1_ec_pubkey_parse.restype = c_int +def pubkey_parse(pubkey_bytes): + pk = create_string_buffer(SECP256K1_PUBKEY_STRUCT_SIZE) + retval = secplib.secp256k1_ec_pubkey_parse(secp_context, pk, pubkey_bytes, 33) + assert retval == 1 + return pk + +""" +int secp256k1_ec_pubkey_serialize( + const secp256k1_context *ctx, + unsigned char *output, + size_t *outputlen, + const secp256k1_pubkey *pubkey, + unsigned int flags +); +""" +secplib.secp256k1_ec_pubkey_serialize.argtypes = [c_voidp, c_voidp, c_voidp, c_voidp, c_uint] +secplib.secp256k1_ec_pubkey_serialize.restype = c_int +def pubkey_serialize(pubkey_obj): + ser_pubkey = create_string_buffer(33) + ser_pubkey_size = c_size_t(33) + retval = secplib.secp256k1_ec_pubkey_serialize(secp_context, + ser_pubkey, pointer(ser_pubkey_size), pubkey_obj, (1 << 1) | (1 << 8)) + assert retval == 1 + return bytes(ser_pubkey) + +""" +int secp256k1_ec_seckey_tweak_add( + const secp256k1_context *ctx, + unsigned char *seckey, + const unsigned char *tweak32 +); +""" +secplib.secp256k1_ec_seckey_tweak_add.argtypes = [c_voidp, c_voidp] +secplib.secp256k1_ec_seckey_tweak_add.restype = c_int +def seckey_add(seckey_lhs, seckey_rhs): + seckey_result = create_string_buffer(seckey_lhs, 32) + retval = secplib.secp256k1_ec_seckey_tweak_add(secp_context, seckey_result, seckey_rhs) + assert retval == 1 + return bytes(seckey_result) + +############################################ +### API calls from secp256k1_extrakeys.h ### +############################################ +SECP256K1_XONLY_PUBKEY_STRUCT_SIZE = 64 +SECP256K1_KEYPAIR_STRUCT_SIZE = 96 +""" +int secp256k1_xonly_pubkey_parse( + const secp256k1_context *ctx, + secp256k1_xonly_pubkey *pubkey, + const unsigned char *input32 +); +""" +secplib.secp256k1_xonly_pubkey_parse.argtypes = [c_voidp, c_voidp, c_voidp] +secplib.secp256k1_xonly_pubkey_parse.restype = c_int +def xonly_pubkey_parse(xonly_pubkey_bytes): + xpk = create_string_buffer(SECP256K1_XONLY_PUBKEY_STRUCT_SIZE) + retval = secplib.secp256k1_xonly_pubkey_parse(secp_context, xpk, xonly_pubkey_bytes) + assert retval == 1 + return xpk + +""" +int secp256k1_xonly_pubkey_serialize( + const secp256k1_context *ctx, + unsigned char *output32, + const secp256k1_xonly_pubkey *pubkey +); +""" +secplib.secp256k1_xonly_pubkey_serialize.argtypes = [c_voidp, c_voidp, c_voidp] +secplib.secp256k1_xonly_pubkey_serialize.restype = c_int +def xonly_pubkey_serialize(xpubkey_obj): + ser_xpubkey = create_string_buffer(32) + retval = secplib.secp256k1_xonly_pubkey_serialize(secp_context, ser_xpubkey, xpubkey_obj) + assert retval == 1 + return bytes(ser_xpubkey) + +""" +int secp256k1_keypair_create( + const secp256k1_context *ctx, + secp256k1_keypair *keypair, + const unsigned char *seckey +); +""" +secplib.secp256k1_keypair_create.argtypes = [c_voidp, c_voidp, c_voidp] +secplib.secp256k1_keypair_create.restype = c_int +def keypair_create(seckey): + result_keypair = create_string_buffer(SECP256K1_KEYPAIR_STRUCT_SIZE) + retval = secplib.secp256k1_keypair_create(secp_context, result_keypair, seckey) + assert retval == 1 + return result_keypair + +########################################## +### API calls from secp256k1_schnorr.h ### +########################################## +""" +int secp256k1_schnorrsig_sign32( + const secp256k1_context *ctx, + unsigned char *sig64, + const unsigned char *msg32, + const secp256k1_keypair *keypair, + const unsigned char *aux_rand32 +); +""" +secplib.secp256k1_schnorrsig_sign32.argtypes = [c_voidp, c_voidp, c_voidp, c_voidp, c_voidp] +secplib.secp256k1_schnorrsig_sign32.restype = c_int +def schnorrsig_sign32(msg32, privkey, aux32): + keypair_obj = keypair_create(privkey) + result_sig = create_string_buffer(64) + retval = secplib.secp256k1_schnorrsig_sign32( + secp_context, result_sig, msg32, keypair_obj, aux32) + assert retval == 1 + return bytes(result_sig) + +################################################# +### API calls from secp256k1_silentpayments.h ### +################################################# +""" +int secp256k1_silentpayments_create_private_tweak_data( + const secp256k1_context *ctx, + unsigned char *a_sum, + unsigned char *input_hash, + const unsigned char * const *plain_seckeys, + size_t n_plain_seckeys, + const unsigned char * const *taproot_seckeys, + size_t n_taproot_seckeys, + const unsigned char *outpoint_smallest36 +); +""" +secplib.secp256k1_silentpayments_create_private_tweak_data.argtypes = [ + c_voidp, c_voidp, c_voidp, c_voidp, c_size_t, c_voidp, c_size_t, c_voidp +] +secplib.secp256k1_silentpayments_create_private_tweak_data.restype = c_int +def silentpayments_create_private_tweak_data(plain_seckeys, xonly_seckeys, outpoint_lowest): + array_plain_seckeys = (c_char_p * len(plain_seckeys))() + array_xonly_seckeys = (c_char_p * len(xonly_seckeys))() + for i in range(len(plain_seckeys)): array_plain_seckeys[i] = plain_seckeys[i] + if len(array_plain_seckeys) == 0: array_plain_seckeys = None + for i in range(len(xonly_seckeys)): array_xonly_seckeys[i] = xonly_seckeys[i] + if len(array_xonly_seckeys) == 0: array_xonly_seckeys = None + + result_a_sum = create_string_buffer(32) + result_input_hash = create_string_buffer(32) + retval = secplib.secp256k1_silentpayments_create_private_tweak_data( + secp_context, result_a_sum, result_input_hash, array_plain_seckeys, len(plain_seckeys), + array_xonly_seckeys, len(xonly_seckeys), outpoint_lowest) + assert retval == 1 + + return bytes(result_a_sum), bytes(result_input_hash) + +""" +int secp256k1_silentpayments_create_public_tweak_data( + const secp256k1_context *ctx, + secp256k1_pubkey *A_sum, + unsigned char *input_hash, + const secp256k1_pubkey * const *plain_pubkeys, + size_t n_plain_pubkeys, + const secp256k1_xonly_pubkey * const *xonly_pubkeys, + size_t n_xonly_pubkeys, + const unsigned char *outpoint_smallest36 +); +""" +secplib.secp256k1_silentpayments_create_public_tweak_data.argtypes = [ + c_voidp, c_voidp, c_voidp, c_voidp, c_size_t, c_voidp, c_size_t, c_voidp +] +secplib.secp256k1_silentpayments_create_public_tweak_data.restype = c_int +def silentpayments_create_tweak_data(plain_pubkeys, xonly_pubkeys, outpoint_lowest): + # convert raw plain pubkeys to secp256k1 pubkeys + plain_pubkeys_objs = [pubkey_parse(p) for p in plain_pubkeys] + xonly_pubkeys_objs = [xonly_pubkey_parse(p) for p in xonly_pubkeys] + array_plain_pubkeys = (c_voidp * len(plain_pubkeys))() + for i in range(len(plain_pubkeys)): array_plain_pubkeys[i] = cast(plain_pubkeys_objs[i], c_voidp) + array_xonly_pubkeys = (c_voidp * len(xonly_pubkeys))() + for i in range(len(xonly_pubkeys)): array_xonly_pubkeys[i] = cast(xonly_pubkeys_objs[i], c_voidp) + if len(array_plain_pubkeys) == 0: array_plain_pubkeys = None + if len(array_xonly_pubkeys) == 0: array_xonly_pubkeys = None + + result_A_sum = create_string_buffer(SECP256K1_PUBKEY_STRUCT_SIZE) + result_input_hash = create_string_buffer(32) + retval = secplib.secp256k1_silentpayments_create_public_tweak_data( + secp_context, result_A_sum, result_input_hash, array_plain_pubkeys, len(plain_pubkeys), + array_xonly_pubkeys, len(xonly_pubkeys), outpoint_lowest) + assert retval == 1 + + return bytes(result_A_sum), bytes(result_input_hash) + +""" +int secp256k1_silentpayments_create_tweaked_pubkey( + const secp256k1_context *ctx, + secp256k1_pubkey *A_tweaked, + const secp256k1_pubkey *A_sum, + const unsigned char *input_hash +); +""" +secplib.secp256k1_silentpayments_create_tweaked_pubkey.argtypes = [ + c_voidp, c_voidp, c_voidp, c_voidp +] +secplib.secp256k1_silentpayments_create_tweaked_pubkey.restype = c_int +def silentpayments_create_tweaked_pubkey(A_sum, input_hash): + result_tweaked_pubkey = create_string_buffer(SECP256K1_PUBKEY_STRUCT_SIZE) + retval = secplib.secp256k1_silentpayments_create_tweaked_pubkey( + secp_context, result_tweaked_pubkey, A_sum, input_hash) + assert retval == 1 + return bytes(result_tweaked_pubkey) + +""" +int secp256k1_silentpayments_create_shared_secret( + const secp256k1_context *ctx, + unsigned char *shared_secret33, + const secp256k1_pubkey *public_component, + const unsigned char *secret_component, + const unsigned char *input_hash +); +""" +secplib.secp256k1_silentpayments_create_shared_secret.argtypes = [ + c_voidp, c_voidp, c_voidp, c_voidp, c_voidp +] +secplib.secp256k1_silentpayments_create_shared_secret.restype = c_int +def silentpayments_create_shared_secret(public_component, secret_component, input_hash): + result_shared_secret = create_string_buffer(33) + retval = secplib.secp256k1_silentpayments_create_shared_secret( + secp_context, result_shared_secret, public_component, secret_component, input_hash) + assert retval == 1 + return bytes(result_shared_secret) + +""" +int secp256k1_silentpayments_create_label_tweak( + const secp256k1_context *ctx, + secp256k1_pubkey *label, + unsigned char *label_tweak32, + const unsigned char *receiver_scan_seckey, + unsigned int m +); +""" +secplib.secp256k1_silentpayments_create_label_tweak.argtypes = [ + c_voidp, c_voidp, c_voidp, c_voidp, c_uint +] +secplib.secp256k1_silentpayments_create_label_tweak.restype = c_int +def silentpayments_create_label_tweak(receiver_scan_seckey, m): + result_label_tweak = create_string_buffer(32) + result_label_obj = create_string_buffer(SECP256K1_PUBKEY_STRUCT_SIZE) + retval = secplib.secp256k1_silentpayments_create_label_tweak( + secp_context, result_label_obj, result_label_tweak, receiver_scan_seckey, m) + assert retval == 1 + return pubkey_serialize(result_label_obj), bytes(result_label_tweak) + +""" +int secp256k1_silentpayments_create_address_spend_pubkey( + const secp256k1_context *ctx, + unsigned char *l_addr_spend_pubkey33, + const secp256k1_pubkey *receiver_spend_pubkey, + const secp256k1_pubkey *label +""" +secplib.secp256k1_silentpayments_create_address_spend_pubkey.argtypes = [ + c_voidp, c_voidp, c_voidp, c_voidp +] +secplib.secp256k1_silentpayments_create_address_spend_pubkey.restype = c_int +def silentpayments_create_address_spend_pubkey(receiver_spend_pubkey, label): + result_address_spend_pubkey = create_string_buffer(33) + # convert receiver pubkey to secp256k1 object + receiver_spend_pubkey_obj = pubkey_parse(receiver_spend_pubkey) + label_obj = pubkey_parse(label) + + retval = secplib.secp256k1_silentpayments_create_address_spend_pubkey( + secp_context, result_address_spend_pubkey, receiver_spend_pubkey_obj, label_obj) + assert retval == 1 + return bytes(result_address_spend_pubkey) + +""" +int secp256k1_silentpayments_sender_create_output_pubkey( + const secp256k1_context *ctx, + secp256k1_xonly_pubkey *P_output_xonly, + const unsigned char *shared_secret33, + const secp256k1_pubkey *receiver_spend_pubkey, + unsigned int k +); +""" +secplib.secp256k1_silentpayments_sender_create_output_pubkey.argtypes = [ + c_voidp, c_voidp, c_voidp, c_voidp, c_uint +] +secplib.secp256k1_silentpayments_sender_create_output_pubkey.restype = c_int +def silentpayments_sender_create_output_pubkey(shared_secret, receiver_spend_pubkey, k): + result_xonly_pubkey = create_string_buffer(SECP256K1_XONLY_PUBKEY_STRUCT_SIZE) + # convert receiver pubkey to secp256k1 object + receiver_spend_pubkey_obj = pubkey_parse(receiver_spend_pubkey) + + retval = secplib.secp256k1_silentpayments_sender_create_output_pubkey( + secp_context, result_xonly_pubkey, shared_secret, receiver_spend_pubkey_obj, k) + assert retval == 1 + + return xonly_pubkey_serialize(result_xonly_pubkey) + +""" +typedef struct { + secp256k1_pubkey label; + secp256k1_pubkey label_negated; +} secp256k1_silentpayments_label_data; + +int secp256k1_silentpayments_receiver_create_scanning_data( + const secp256k1_context *ctx, + int *direct_match, + unsigned char *t_k, + secp256k1_silentpayments_label_data *label_data, + const unsigned char *shared_secret33, + const secp256k1_pubkey *receiver_spend_pubkey, + unsigned int k, + const secp256k1_xonly_pubkey *tx_output +); +""" +secplib.secp256k1_silentpayments_receiver_scan_output.argtypes = [ + c_voidp, POINTER(c_int), c_voidp, c_voidp, c_voidp, c_voidp, c_uint, c_voidp +] +secplib.secp256k1_silentpayments_receiver_scan_output.restype = c_int +def silentpayments_receiver_scan_output(shared_secret, receiver_spend_pubkey, k, tx_output): + # TODO: we are assuming here that the structs are packed, but i guess there is no guarantee? + result_label_data = create_string_buffer(2 * SECP256K1_PUBKEY_STRUCT_SIZE) + result_t_k = create_string_buffer(32) + result_direct_match = c_int() + + receiver_spend_pubkey_obj = pubkey_parse(receiver_spend_pubkey) + tx_output_obj = xonly_pubkey_parse(tx_output) + + retval = secplib.secp256k1_silentpayments_receiver_scan_output( + secp_context, pointer(result_direct_match), result_t_k, result_label_data, + shared_secret, receiver_spend_pubkey_obj, k, tx_output_obj) + assert retval == 1 + + label1, label2 = result_label_data[:64], result_label_data[64:] + + if result_direct_match: + return True, bytes(result_t_k), None, None + else: + return False, bytes(result_t_k), pubkey_serialize(label1), pubkey_serialize(label2) + +""" +int secp256k1_silentpayments_create_output_seckey( + const secp256k1_context *ctx, + unsigned char *output_seckey, + const unsigned char *receiver_spend_seckey, + const unsigned char *t_k, + const unsigned char *label_tweak +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); +); +""" +secplib.secp256k1_silentpayments_create_output_seckey.argtypes = [ + c_voidp, c_voidp, c_voidp, c_voidp, c_voidp +] +secplib.secp256k1_silentpayments_create_output_seckey.restype = c_int +def silentpayments_create_output_seckey(receiver_spend_seckey, t_k, label_tweak=None): + result_seckey = create_string_buffer(32) + retval = secplib.secp256k1_silentpayments_create_output_seckey( + secp_context, result_seckey, receiver_spend_seckey, t_k, label_tweak) + assert retval == 1 + return bytes(result_seckey) diff --git a/cmake/AllTargetsCompileOptions.cmake b/cmake/AllTargetsCompileOptions.cmake new file mode 100644 index 00000000000000..6e420e0fdee264 --- /dev/null +++ b/cmake/AllTargetsCompileOptions.cmake @@ -0,0 +1,12 @@ +# Add compile options to all targets added in the subdirectory. +function(all_targets_compile_options dir options) + get_directory_property(targets DIRECTORY ${dir} BUILDSYSTEM_TARGETS) + separate_arguments(options) + set(compiled_target_types STATIC_LIBRARY SHARED_LIBRARY OBJECT_LIBRARY EXECUTABLE) + foreach(target ${targets}) + get_target_property(type ${target} TYPE) + if(type IN_LIST compiled_target_types) + target_compile_options(${target} PRIVATE ${options}) + endif() + endforeach() +endfunction() diff --git a/configure.ac b/configure.ac index 2c1596775ed487..7113752b5096ce 100644 --- a/configure.ac +++ b/configure.ac @@ -188,6 +188,10 @@ AC_ARG_ENABLE(module_ellswift, AS_HELP_STRING([--enable-module-ellswift],[enable ElligatorSwift module [default=yes]]), [], [SECP_SET_DEFAULT([enable_module_ellswift], [yes], [yes])]) +AC_ARG_ENABLE(module_silentpayments, + AS_HELP_STRING([--enable-module-silentpayments],[enable Silent Payments module [default=no]]), [], + [SECP_SET_DEFAULT([enable_module_silentpayments], [no], [yes])]) + AC_ARG_ENABLE(external_default_callbacks, AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [], [SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])]) @@ -387,29 +391,36 @@ SECP_CFLAGS="$SECP_CFLAGS $WERROR_CFLAGS" ### Handle module options ### -if test x"$enable_module_ecdh" = x"yes"; then - SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ECDH=1" +# Processing must be done in a reverse topological sorting of the dependency graph +# (dependent module first). +if test x"$enable_module_silentpayments" = x"yes"; then + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_SILENTPAYMENTS=1" fi -if test x"$enable_module_recovery" = x"yes"; then - SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_RECOVERY=1" +if test x"$enable_module_ellswift" = x"yes"; then + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ELLSWIFT=1" fi if test x"$enable_module_schnorrsig" = x"yes"; then - SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_SCHNORRSIG=1" + if test x"$enable_module_extrakeys" = x"no"; then + AC_MSG_ERROR([Module dependency error: You have disabled the extrakeys module explicitly, but it is required by the schnorrsig module.]) + fi enable_module_extrakeys=yes + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_SCHNORRSIG=1" fi -if test x"$enable_module_ellswift" = x"yes"; then - SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ELLSWIFT=1" -fi - -# Test if extrakeys is set after the schnorrsig module to allow the schnorrsig -# module to set enable_module_extrakeys=yes if test x"$enable_module_extrakeys" = x"yes"; then SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_EXTRAKEYS=1" fi +if test x"$enable_module_recovery" = x"yes"; then + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_RECOVERY=1" +fi + +if test x"$enable_module_ecdh" = x"yes"; then + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ECDH=1" +fi + if test x"$enable_external_default_callbacks" = x"yes"; then SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DUSE_EXTERNAL_DEFAULT_CALLBACKS=1" fi @@ -447,6 +458,7 @@ AM_CONDITIONAL([ENABLE_MODULE_RECOVERY], [test x"$enable_module_recovery" = x"ye AM_CONDITIONAL([ENABLE_MODULE_EXTRAKEYS], [test x"$enable_module_extrakeys" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG], [test x"$enable_module_schnorrsig" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_ELLSWIFT], [test x"$enable_module_ellswift" = x"yes"]) +AM_CONDITIONAL([ENABLE_MODULE_SILENTPAYMENTS], [test x"$enable_module_silentpayments" = x"yes"]) AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"]) AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm32"]) AM_CONDITIONAL([BUILD_WINDOWS], [test "$build_windows" = "yes"]) @@ -469,6 +481,7 @@ echo " module recovery = $enable_module_recovery" echo " module extrakeys = $enable_module_extrakeys" echo " module schnorrsig = $enable_module_schnorrsig" echo " module ellswift = $enable_module_ellswift" +echo " module silentpayments = $enable_module_silentpayments" echo echo " asm = $set_asm" echo " ecmult window size = $set_ecmult_window" diff --git a/contrib/lax_der_parsing.h b/contrib/lax_der_parsing.h index 034a38e6a0e1d0..37c8c691f2f043 100644 --- a/contrib/lax_der_parsing.h +++ b/contrib/lax_der_parsing.h @@ -67,8 +67,8 @@ extern "C" { * * Returns: 1 when the signature could be parsed, 0 otherwise. * Args: ctx: a secp256k1 context object - * Out: sig: a pointer to a signature object - * In: input: a pointer to the signature to be parsed + * Out: sig: pointer to a signature object + * In: input: pointer to the signature to be parsed * inputlen: the length of the array pointed to be input * * This function will accept any valid DER encoded signature, even if the diff --git a/doc/release-process.md b/doc/release-process.md index 51e337a5ab650f..cdf62430dfb7db 100644 --- a/doc/release-process.md +++ b/doc/release-process.md @@ -1,4 +1,4 @@ -# Release Process +# Release process This document outlines the process for releasing versions of the form `$MAJOR.$MINOR.$PATCH`. @@ -14,31 +14,30 @@ This process also assumes that there will be no minor releases for old major rel We aim to cut a regular release every 3-4 months, approximately twice as frequent as major Bitcoin Core releases. Every second release should be published one month before the feature freeze of the next major Bitcoin Core release, allowing sufficient time to update the library in Core. -## Sanity Checks -Perform these checks before creating a release: +## Sanity checks +Perform these checks when reviewing the release PR (see below): 1. Ensure `make distcheck` doesn't fail. -```shell -./autogen.sh && ./configure --enable-dev-mode && make distcheck -``` + ```shell + ./autogen.sh && ./configure --enable-dev-mode && make distcheck + ``` 2. Check installation with autotools: -```shell -dir=$(mktemp -d) -./autogen.sh && ./configure --prefix=$dir && make clean && make install && ls -RlAh $dir -gcc -o ecdsa examples/ecdsa.c $(PKG_CONFIG_PATH=$dir/lib/pkgconfig pkg-config --cflags --libs libsecp256k1) -Wl,-rpath,"$dir/lib" && ./ecdsa -``` + ```shell + dir=$(mktemp -d) + ./autogen.sh && ./configure --prefix=$dir && make clean && make install && ls -RlAh $dir + gcc -o ecdsa examples/ecdsa.c $(PKG_CONFIG_PATH=$dir/lib/pkgconfig pkg-config --cflags --libs libsecp256k1) -Wl,-rpath,"$dir/lib" && ./ecdsa + ``` 3. Check installation with CMake: -```shell -dir=$(mktemp -d) -build=$(mktemp -d) -cmake -B $build -DCMAKE_INSTALL_PREFIX=$dir && cmake --build $build --target install && ls -RlAh $dir -gcc -o ecdsa examples/ecdsa.c -I $dir/include -L $dir/lib*/ -l secp256k1 -Wl,-rpath,"$dir/lib",-rpath,"$dir/lib64" && ./ecdsa -``` -4. Use the [`check-abi.sh`](/tools/check-abi.sh) tool to ensure there are no unexpected ABI incompatibilities and that the version number and release notes accurately reflect all potential ABI changes. To run this tool, the `abi-dumper` and `abi-compliance-checker` packages are required. - -```shell -tools/check-abi.sh -``` + ```shell + dir=$(mktemp -d) + build=$(mktemp -d) + cmake -B $build -DCMAKE_INSTALL_PREFIX=$dir && cmake --build $build --target install && ls -RlAh $dir + gcc -o ecdsa examples/ecdsa.c -I $dir/include -L $dir/lib*/ -l secp256k1 -Wl,-rpath,"$dir/lib",-rpath,"$dir/lib64" && ./ecdsa + ``` +4. Use the [`check-abi.sh`](/tools/check-abi.sh) tool to verify that there are no unexpected ABI incompatibilities and that the version number and the release notes accurately reflect all potential ABI changes. To run this tool, the `abi-dumper` and `abi-compliance-checker` packages are required. + ```shell + tools/check-abi.sh + ``` ## Regular release @@ -47,27 +46,29 @@ tools/check-abi.sh * adding a section for the release (make sure that the version number is a link to a diff between the previous and new version), * removing the `[Unreleased]` section header, and * including an entry for `### ABI Compatibility` if it doesn't exist, - * sets `_PKG_VERSION_IS_RELEASE` to `true` in `configure.ac`, and - * if this is not a patch release - * updates `_PKG_VERSION_*` and `_LIB_VERSION_*` in `configure.ac` and + * sets `_PKG_VERSION_IS_RELEASE` to `true` in `configure.ac`, and, + * if this is not a patch release, + * updates `_PKG_VERSION_*` and `_LIB_VERSION_*` in `configure.ac`, and * updates `project(libsecp256k1 VERSION ...)` and `${PROJECT_NAME}_LIB_VERSION_*` in `CMakeLists.txt`. -2. After the PR is merged, tag the commit and push it: +2. Perform the [sanity checks](#sanity-checks) on the PR branch. +3. After the PR is merged, tag the commit, and push the tag: ``` RELEASE_COMMIT= git tag -s v$MAJOR.$MINOR.$PATCH -m "libsecp256k1 $MAJOR.$MINOR.$PATCH" $RELEASE_COMMIT git push git@github.com:bitcoin-core/secp256k1.git v$MAJOR.$MINOR.$PATCH ``` -3. Open a PR to the master branch with a commit (using message `"release cleanup: bump version after $MAJOR.$MINOR.$PATCH"`, for example) that +4. Open a PR to the master branch with a commit (using message `"release cleanup: bump version after $MAJOR.$MINOR.$PATCH"`, for example) that * sets `_PKG_VERSION_IS_RELEASE` to `false` and increments `_PKG_VERSION_PATCH` and `_LIB_VERSION_REVISION` in `configure.ac`, * increments the `$PATCH` component of `project(libsecp256k1 VERSION ...)` and `${PROJECT_NAME}_LIB_VERSION_REVISION` in `CMakeLists.txt`, and * adds an `[Unreleased]` section header to the [CHANGELOG.md](../CHANGELOG.md). If other maintainers are not present to approve the PR, it can be merged without ACKs. -4. Create a new GitHub release with a link to the corresponding entry in [CHANGELOG.md](../CHANGELOG.md). +5. Create a new GitHub release with a link to the corresponding entry in [CHANGELOG.md](../CHANGELOG.md). +6. Send an announcement email to the bitcoin-dev mailing list. ## Maintenance release -Note that bugfixes only need to be backported to releases for which no compatible release without the bug exists. +Note that bug fixes need to be backported only to releases for which no compatible release without the bug exists. 1. If there's no maintenance branch `$MAJOR.$MINOR`, create one: ``` @@ -75,19 +76,18 @@ Note that bugfixes only need to be backported to releases for which no compatibl git push git@github.com:bitcoin-core/secp256k1.git $MAJOR.$MINOR ``` 2. Open a pull request to the `$MAJOR.$MINOR` branch that - * includes the bugfixes, + * includes the bug fixes, * finalizes the release notes similar to a regular release, * increments `_PKG_VERSION_PATCH` and `_LIB_VERSION_REVISION` in `configure.ac` and the `$PATCH` component of `project(libsecp256k1 VERSION ...)` and `${PROJECT_NAME}_LIB_VERSION_REVISION` in `CMakeLists.txt` (with commit message `"release: bump versions for $MAJOR.$MINOR.$PATCH"`, for example). -3. After the PRs are merged, update the release branch and tag the commit: +3. Perform the [sanity checks](#sanity-checks) on the PR branch. +4. After the PRs are merged, update the release branch, tag the commit, and push the tag: ``` git checkout $MAJOR.$MINOR && git pull git tag -s v$MAJOR.$MINOR.$PATCH -m "libsecp256k1 $MAJOR.$MINOR.$PATCH" - ``` -4. Push tag: - ``` git push git@github.com:bitcoin-core/secp256k1.git v$MAJOR.$MINOR.$PATCH ``` -5. Create a new GitHub release with a link to the corresponding entry in [CHANGELOG.md](../CHANGELOG.md). -6. Open PR to the master branch that includes a commit (with commit message `"release notes: add $MAJOR.$MINOR.$PATCH"`, for example) that adds release notes to [CHANGELOG.md](../CHANGELOG.md). +6. Create a new GitHub release with a link to the corresponding entry in [CHANGELOG.md](../CHANGELOG.md). +7. Send an announcement email to the bitcoin-dev mailing list. +8. Open PR to the master branch that includes a commit (with commit message `"release notes: add $MAJOR.$MINOR.$PATCH"`, for example) that adds release notes to [CHANGELOG.md](../CHANGELOG.md). diff --git a/include/secp256k1.h b/include/secp256k1.h index 936f0b42b7d033..f4053f2a9311da 100644 --- a/include/secp256k1.h +++ b/include/secp256k1.h @@ -265,7 +265,7 @@ SECP256K1_API void secp256k1_selftest(void); * memory allocation entirely, see secp256k1_context_static and the functions in * secp256k1_preallocated.h. * - * Returns: a newly created context object. + * Returns: pointer to a newly created context object. * In: flags: Always set to SECP256K1_CONTEXT_NONE (see below). * * The only valid non-deprecated flag in recent library versions is @@ -296,8 +296,8 @@ SECP256K1_API secp256k1_context *secp256k1_context_create( * Cloning secp256k1_context_static is not possible, and should not be emulated by * the caller (e.g., using memcpy). Create a new context instead. * - * Returns: a newly created context object. - * Args: ctx: an existing context to copy (not secp256k1_context_static) + * Returns: pointer to a newly created context object. + * Args: ctx: pointer to a context to copy (not secp256k1_context_static). */ SECP256K1_API secp256k1_context *secp256k1_context_clone( const secp256k1_context *ctx @@ -313,7 +313,7 @@ SECP256K1_API secp256k1_context *secp256k1_context_clone( * behaviour is undefined. In that case, secp256k1_context_preallocated_destroy must * be used instead. * - * Args: ctx: an existing context to destroy, constructed using + * Args: ctx: pointer to a context to destroy, constructed using * secp256k1_context_create or secp256k1_context_clone * (i.e., not secp256k1_context_static). */ @@ -350,8 +350,8 @@ SECP256K1_API void secp256k1_context_destroy( * fails. In this case, the corresponding default handler will be called with * the data pointer argument set to NULL. * - * Args: ctx: an existing context object. - * In: fun: a pointer to a function to call when an illegal argument is + * Args: ctx: pointer to a context object. + * In: fun: pointer to a function to call when an illegal argument is * passed to the API, taking a message and an opaque pointer. * (NULL restores the default handler.) * data: the opaque pointer to pass to fun above, must be NULL for the default handler. @@ -377,8 +377,8 @@ SECP256K1_API void secp256k1_context_set_illegal_callback( * for that). After this callback returns, anything may happen, including * crashing. * - * Args: ctx: an existing context object. - * In: fun: a pointer to a function to call when an internal error occurs, + * Args: ctx: pointer to a context object. + * In: fun: pointer to a function to call when an internal error occurs, * taking a message and an opaque pointer (NULL restores the * default handler, see secp256k1_context_set_illegal_callback * for details). @@ -395,7 +395,7 @@ SECP256K1_API void secp256k1_context_set_error_callback( /** Create a secp256k1 scratch space object. * * Returns: a newly created scratch space. - * Args: ctx: an existing context object. + * Args: ctx: pointer to a context object. * In: size: amount of memory to be available as scratch space. Some extra * (<100 bytes) will be allocated for extra accounting. */ @@ -407,7 +407,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT secp256k1_scratch_space *secp256k1_sc /** Destroy a secp256k1 scratch space. * * The pointer may not be used afterwards. - * Args: ctx: a secp256k1 context object. + * Args: ctx: pointer to a context object. * scratch: space to destroy */ SECP256K1_API void secp256k1_scratch_space_destroy( @@ -419,7 +419,7 @@ SECP256K1_API void secp256k1_scratch_space_destroy( * * Returns: 1 if the public key was fully valid. * 0 if the public key could not be parsed or is invalid. - * Args: ctx: a secp256k1 context object. + * Args: ctx: pointer to a context object. * Out: pubkey: pointer to a pubkey object. If 1 is returned, it is set to a * parsed version of input. If not, its value is undefined. * In: input: pointer to a serialized public key @@ -439,14 +439,14 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_parse( /** Serialize a pubkey object into a serialized byte sequence. * * Returns: 1 always. - * Args: ctx: a secp256k1 context object. - * Out: output: a pointer to a 65-byte (if compressed==0) or 33-byte (if + * Args: ctx: pointer to a context object. + * Out: output: pointer to a 65-byte (if compressed==0) or 33-byte (if * compressed==1) byte array to place the serialized key * in. - * In/Out: outputlen: a pointer to an integer which is initially set to the + * In/Out: outputlen: pointer to an integer which is initially set to the * size of output, and is overwritten with the written * size. - * In: pubkey: a pointer to a secp256k1_pubkey containing an + * In: pubkey: pointer to a secp256k1_pubkey containing an * initialized public key. * flags: SECP256K1_EC_COMPRESSED if serialization should be in * compressed format, otherwise SECP256K1_EC_UNCOMPRESSED. @@ -464,7 +464,7 @@ SECP256K1_API int secp256k1_ec_pubkey_serialize( * Returns: <0 if the first public key is less than the second * >0 if the first public key is greater than the second * 0 if the two public keys are equal - * Args: ctx: a secp256k1 context object. + * Args: ctx: pointer to a context object * In: pubkey1: first public key to compare * pubkey2: second public key to compare */ @@ -477,9 +477,9 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_cmp( /** Parse an ECDSA signature in compact (64 bytes) format. * * Returns: 1 when the signature could be parsed, 0 otherwise. - * Args: ctx: a secp256k1 context object - * Out: sig: a pointer to a signature object - * In: input64: a pointer to the 64-byte array to parse + * Args: ctx: pointer to a context object + * Out: sig: pointer to a signature object + * In: input64: pointer to the 64-byte array to parse * * The signature must consist of a 32-byte big endian R value, followed by a * 32-byte big endian S value. If R or S fall outside of [0..order-1], the @@ -498,9 +498,9 @@ SECP256K1_API int secp256k1_ecdsa_signature_parse_compact( /** Parse a DER ECDSA signature. * * Returns: 1 when the signature could be parsed, 0 otherwise. - * Args: ctx: a secp256k1 context object - * Out: sig: a pointer to a signature object - * In: input: a pointer to the signature to be parsed + * Args: ctx: pointer to a context object + * Out: sig: pointer to a signature object + * In: input: pointer to the signature to be parsed * inputlen: the length of the array pointed to be input * * This function will accept any valid DER encoded signature, even if the @@ -520,13 +520,13 @@ SECP256K1_API int secp256k1_ecdsa_signature_parse_der( /** Serialize an ECDSA signature in DER format. * * Returns: 1 if enough space was available to serialize, 0 otherwise - * Args: ctx: a secp256k1 context object - * Out: output: a pointer to an array to store the DER serialization - * In/Out: outputlen: a pointer to a length integer. Initially, this integer + * Args: ctx: pointer to a context object + * Out: output: pointer to an array to store the DER serialization + * In/Out: outputlen: pointer to a length integer. Initially, this integer * should be set to the length of output. After the call * it will be set to the length of the serialization (even * if 0 was returned). - * In: sig: a pointer to an initialized signature object + * In: sig: pointer to an initialized signature object */ SECP256K1_API int secp256k1_ecdsa_signature_serialize_der( const secp256k1_context *ctx, @@ -538,9 +538,9 @@ SECP256K1_API int secp256k1_ecdsa_signature_serialize_der( /** Serialize an ECDSA signature in compact (64 byte) format. * * Returns: 1 - * Args: ctx: a secp256k1 context object - * Out: output64: a pointer to a 64-byte array to store the compact serialization - * In: sig: a pointer to an initialized signature object + * Args: ctx: pointer to a context object + * Out: output64: pointer to a 64-byte array to store the compact serialization + * In: sig: pointer to an initialized signature object * * See secp256k1_ecdsa_signature_parse_compact for details about the encoding. */ @@ -554,7 +554,7 @@ SECP256K1_API int secp256k1_ecdsa_signature_serialize_compact( * * Returns: 1: correct signature * 0: incorrect or unparseable signature - * Args: ctx: a secp256k1 context object. + * Args: ctx: pointer to a context object * In: sig: the signature being verified. * msghash32: the 32-byte message hash being verified. * The verifier must make sure to apply a cryptographic @@ -585,12 +585,12 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_verify( /** Convert a signature to a normalized lower-S form. * * Returns: 1 if sigin was not normalized, 0 if it already was. - * Args: ctx: a secp256k1 context object - * Out: sigout: a pointer to a signature to fill with the normalized form, + * Args: ctx: pointer to a context object + * Out: sigout: pointer to a signature to fill with the normalized form, * or copy if the input was already normalized. (can be NULL if * you're only interested in whether the input was already * normalized). - * In: sigin: a pointer to a signature to check/normalize (can be identical to sigout) + * In: sigin: pointer to a signature to check/normalize (can be identical to sigout) * * With ECDSA a third-party can forge a second distinct signature of the same * message, given a single initial signature, but without knowing the key. This diff --git a/include/secp256k1_ecdh.h b/include/secp256k1_ecdh.h index 515e17429986be..4d9da3461d224c 100644 --- a/include/secp256k1_ecdh.h +++ b/include/secp256k1_ecdh.h @@ -39,7 +39,7 @@ SECP256K1_API const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_de * 0: scalar was invalid (zero or overflow) or hashfp returned 0 * Args: ctx: pointer to a context object. * Out: output: pointer to an array to be filled by hashfp. - * In: pubkey: a pointer to a secp256k1_pubkey containing an initialized public key. + * In: pubkey: pointer to a secp256k1_pubkey containing an initialized public key. * seckey: a 32-byte scalar with which to multiply the point. * hashfp: pointer to a hash function. If NULL, * secp256k1_ecdh_hash_function_sha256 is used diff --git a/include/secp256k1_ellswift.h b/include/secp256k1_ellswift.h index f79bd883961ebf..ae37287f820efc 100644 --- a/include/secp256k1_ellswift.h +++ b/include/secp256k1_ellswift.h @@ -87,7 +87,7 @@ SECP256K1_API const secp256k1_ellswift_xdh_hash_function secp256k1_ellswift_xdh_ * Returns: 1 always. * Args: ctx: pointer to a context object * Out: ell64: pointer to a 64-byte array to be filled - * In: pubkey: a pointer to a secp256k1_pubkey containing an + * In: pubkey: pointer to a secp256k1_pubkey containing an * initialized public key * rnd32: pointer to 32 bytes of randomness * @@ -169,7 +169,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ellswift_create( * (will not be NULL) * ell_b64: pointer to the 64-byte encoded public key of party B * (will not be NULL) - * seckey32: a pointer to our 32-byte secret key + * seckey32: pointer to our 32-byte secret key * party: boolean indicating which party we are: zero if we are * party A, non-zero if we are party B. seckey32 must be * the private key corresponding to that party's ell_?64. diff --git a/include/secp256k1_extrakeys.h b/include/secp256k1_extrakeys.h index 7fcce68e68181f..ad70b92f959cd2 100644 --- a/include/secp256k1_extrakeys.h +++ b/include/secp256k1_extrakeys.h @@ -39,7 +39,7 @@ typedef struct { * Returns: 1 if the public key was fully valid. * 0 if the public key could not be parsed or is invalid. * - * Args: ctx: a secp256k1 context object. + * Args: ctx: pointer to a context object. * Out: pubkey: pointer to a pubkey object. If 1 is returned, it is set to a * parsed version of input. If not, it's set to an invalid value. * In: input32: pointer to a serialized xonly_pubkey. @@ -54,9 +54,9 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_parse( * * Returns: 1 always. * - * Args: ctx: a secp256k1 context object. - * Out: output32: a pointer to a 32-byte array to place the serialized key in. - * In: pubkey: a pointer to a secp256k1_xonly_pubkey containing an initialized public key. + * Args: ctx: pointer to a context object. + * Out: output32: pointer to a 32-byte array to place the serialized key in. + * In: pubkey: pointer to a secp256k1_xonly_pubkey containing an initialized public key. */ SECP256K1_API int secp256k1_xonly_pubkey_serialize( const secp256k1_context *ctx, @@ -69,7 +69,7 @@ SECP256K1_API int secp256k1_xonly_pubkey_serialize( * Returns: <0 if the first public key is less than the second * >0 if the first public key is greater than the second * 0 if the two public keys are equal - * Args: ctx: a secp256k1 context object. + * Args: ctx: pointer to a context object. * In: pubkey1: first public key to compare * pubkey2: second public key to compare */ diff --git a/include/secp256k1_preallocated.h b/include/secp256k1_preallocated.h index f37744777b042c..f2d95c245e2c55 100644 --- a/include/secp256k1_preallocated.h +++ b/include/secp256k1_preallocated.h @@ -52,8 +52,8 @@ SECP256K1_API size_t secp256k1_context_preallocated_size( * in the memory. In simpler words, the prealloc pointer (or any pointer derived * from it) should not be used during the lifetime of the context object. * - * Returns: a newly created context object. - * In: prealloc: a pointer to a rewritable contiguous block of memory of + * Returns: pointer to newly created context object. + * In: prealloc: pointer to a rewritable contiguous block of memory of * size at least secp256k1_context_preallocated_size(flags) * bytes, as detailed above. * flags: which parts of the context to initialize. @@ -72,7 +72,7 @@ SECP256K1_API secp256k1_context *secp256k1_context_preallocated_create( * caller-provided memory. * * Returns: the required size of the caller-provided memory block. - * In: ctx: an existing context to copy. + * In: ctx: pointer to a context to copy. */ SECP256K1_API size_t secp256k1_context_preallocated_clone_size( const secp256k1_context *ctx @@ -91,9 +91,9 @@ SECP256K1_API size_t secp256k1_context_preallocated_clone_size( * Cloning secp256k1_context_static is not possible, and should not be emulated by * the caller (e.g., using memcpy). Create a new context instead. * - * Returns: a newly created context object. - * Args: ctx: an existing context to copy (not secp256k1_context_static). - * In: prealloc: a pointer to a rewritable contiguous block of memory of + * Returns: pointer to a newly created context object. + * Args: ctx: pointer to a context to copy (not secp256k1_context_static). + * In: prealloc: pointer to a rewritable contiguous block of memory of * size at least secp256k1_context_preallocated_size(flags) * bytes, as detailed above. */ @@ -118,7 +118,7 @@ SECP256K1_API secp256k1_context *secp256k1_context_preallocated_clone( * preallocated pointer given to secp256k1_context_preallocated_create or * secp256k1_context_preallocated_clone. * - * Args: ctx: an existing context to destroy, constructed using + * Args: ctx: pointer to a context to destroy, constructed using * secp256k1_context_preallocated_create or * secp256k1_context_preallocated_clone * (i.e., not secp256k1_context_static). diff --git a/include/secp256k1_recovery.h b/include/secp256k1_recovery.h index b12ca4d9720714..341b8bac63b56d 100644 --- a/include/secp256k1_recovery.h +++ b/include/secp256k1_recovery.h @@ -28,9 +28,9 @@ typedef struct { /** Parse a compact ECDSA signature (64 bytes + recovery id). * * Returns: 1 when the signature could be parsed, 0 otherwise - * Args: ctx: a secp256k1 context object - * Out: sig: a pointer to a signature object - * In: input64: a pointer to a 64-byte compact signature + * Args: ctx: pointer to a context object + * Out: sig: pointer to a signature object + * In: input64: pointer to a 64-byte compact signature * recid: the recovery id (0, 1, 2 or 3) */ SECP256K1_API int secp256k1_ecdsa_recoverable_signature_parse_compact( @@ -43,9 +43,9 @@ SECP256K1_API int secp256k1_ecdsa_recoverable_signature_parse_compact( /** Convert a recoverable signature into a normal signature. * * Returns: 1 - * Args: ctx: a secp256k1 context object. - * Out: sig: a pointer to a normal signature. - * In: sigin: a pointer to a recoverable signature. + * Args: ctx: pointer to a context object. + * Out: sig: pointer to a normal signature. + * In: sigin: pointer to a recoverable signature. */ SECP256K1_API int secp256k1_ecdsa_recoverable_signature_convert( const secp256k1_context *ctx, @@ -56,10 +56,10 @@ SECP256K1_API int secp256k1_ecdsa_recoverable_signature_convert( /** Serialize an ECDSA signature in compact format (64 bytes + recovery id). * * Returns: 1 - * Args: ctx: a secp256k1 context object. - * Out: output64: a pointer to a 64-byte array of the compact signature. - * recid: a pointer to an integer to hold the recovery id. - * In: sig: a pointer to an initialized signature object. + * Args: ctx: pointer to a context object. + * Out: output64: pointer to a 64-byte array of the compact signature. + * recid: pointer to an integer to hold the recovery id. + * In: sig: pointer to an initialized signature object. */ SECP256K1_API int secp256k1_ecdsa_recoverable_signature_serialize_compact( const secp256k1_context *ctx, diff --git a/include/secp256k1_schnorrsig.h b/include/secp256k1_schnorrsig.h index 26358533f67fb7..23163de2fb0028 100644 --- a/include/secp256k1_schnorrsig.h +++ b/include/secp256k1_schnorrsig.h @@ -169,11 +169,11 @@ SECP256K1_API int secp256k1_schnorrsig_sign_custom( * * Returns: 1: correct signature * 0: incorrect signature - * Args: ctx: a secp256k1 context object. + * Args: ctx: pointer to a context object. * In: sig64: pointer to the 64-byte signature to verify. * msg: the message being verified. Can only be NULL if msglen is 0. * msglen: length of the message - * pubkey: pointer to an x-only public key to verify with (cannot be NULL) + * pubkey: pointer to an x-only public key to verify with */ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify( const secp256k1_context *ctx, diff --git a/include/secp256k1_silentpayments.h b/include/secp256k1_silentpayments.h new file mode 100644 index 00000000000000..81baf8e6558f1a --- /dev/null +++ b/include/secp256k1_silentpayments.h @@ -0,0 +1,320 @@ +#ifndef SECP256K1_SILENTPAYMENTS_H +#define SECP256K1_SILENTPAYMENTS_H + +#include "secp256k1.h" +#include "secp256k1_extrakeys.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* This module provides an implementation for the ECC related parts of + * Silent Payments, as specified in BIP352. This particularly involves + * the creation of input tweak data by summing up private or public keys + * and the derivation of a shared secret using Elliptic Curve Diffie-Hellman. + * Combined are either: + * - spender's private keys and receiver's public key (a * B, sender side) + * - spender's public keys and receiver's private key (A * b, receiver side) + * With this result, the necessary key material for ultimately creating/scanning + * or spending Silent Payment outputs can be determined. + * + * Note that this module is _not_ a full implementation of BIP352, as it + * inherently doesn't deal with higher-level concepts like addresses, output + * script types or transactions. The intent is to provide cryptographical + * helpers for low-level calculations that are most error-prone to custom + * implementations (e.g. enforcing the right y-parity for key material, ECDH + * calculation etc.). For any wallet software already using libsecp256k1, this + * API should provide all the functions needed for a Silent Payments + * implementation without the need for any further manual elliptic-curve + * operations. + */ + +/** Create Silent Payment tweak data from input private keys. + * + * Given a list of n private keys a_1...a_n (one for each silent payment + * eligible input to spend) and a serialized outpoint_smallest, compute + * the corresponding input private keys tweak data: + * + * a_sum = a_1 + a_2 + ... + a_n + * input_hash = hash(outpoint_smallest || (a_sum * G)) + * + * If necessary, the private keys are negated to enforce the right y-parity. + * For that reason, the private keys have to be passed in via two different parameter + * pairs, depending on whether they were used for creating taproot outputs or not. + * The resulting data is needed to create a shared secret for the sender side. + * + * Returns: 1 if shared secret creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: a_sum: pointer to the resulting 32-byte private key sum + * input_hash: pointer to the resulting 32-byte input hash + * In: plain_seckeys: pointer to an array of pointers to 32-byte private keys + * of non-taproot inputs (can be NULL if no private keys of + * non-taproot inputs are used) + * n_plain_seckeys: the number of sender's non-taproot input private keys + * taproot_seckeys: pointer to an array of pointers to 32-byte private keys + * of taproot inputs (can be NULL if no private keys of + * taproot inputs are used) + * n_taproot_seckeys: the number of sender's taproot input private keys + * outpoint_smallest36: serialized smallest outpoint + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_private_tweak_data( + const secp256k1_context *ctx, + unsigned char *a_sum, + unsigned char *input_hash, + const unsigned char * const *plain_seckeys, + size_t n_plain_seckeys, + const unsigned char * const *taproot_seckeys, + size_t n_taproot_seckeys, + const unsigned char *outpoint_smallest36 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(8); + +/** Create Silent Payment tweak data from input public keys. + * + * Given a list of n public keys A_1...A_n (one for each silent payment + * eligible input to spend) and a serialized outpoint_smallest, compute + * the corresponding input public keys tweak data: + * + * A_sum = A_1 + A_2 + ... + A_n + * input_hash = hash(outpoint_lowest || A_sum) + * + * The public keys have to be passed in via two different parameter pairs, + * one for regular and one for x-only public keys, in order to avoid the need + * of users converting to a common pubkey format before calling this function. + * The resulting data is needed to create a shared secret for the receiver's side. + * + * Returns: 1 if tweak data creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: A_sum: pointer to the resulting public keys sum + * input_hash: pointer to the resulting 32-byte input hash + * In: plain_pubkeys: pointer to an array of pointers to non-taproot + * public keys (can be NULL if no non-taproot inputs are used) + * n_plain_pubkeys: the number of non-taproot input public keys + * xonly_pubkeys: pointer to an array of pointers to taproot x-only + * public keys (can be NULL if no taproot inputs are used) + * n_xonly_pubkeys: the number of taproot input public keys + * outpoint_smallest36: serialized smallest outpoint + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_public_tweak_data( + const secp256k1_context *ctx, + secp256k1_pubkey *A_sum, + unsigned char *input_hash, + const secp256k1_pubkey * const *plain_pubkeys, + size_t n_plain_pubkeys, + const secp256k1_xonly_pubkey * const *xonly_pubkeys, + size_t n_xonly_pubkeys, + const unsigned char *outpoint_smallest36 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(8); + +/** Create Silent Payment tweaked public key from public tweak data. + * + * Given public tweak data (public keys sum and input hash), calculate the + * corresponding tweaked public key: + * + * A_tweaked = input_hash * A_sum + * + * The resulting data is useful for light clients and silent payment indexes. + * + * Returns: 1 if tweaked public key creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: A_tweaked: pointer to the resulting tweaked public key + * In: A_sum: pointer to the public keys sum + * input_hash: pointer to the 32-byte input hash + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_tweaked_pubkey( + const secp256k1_context *ctx, + secp256k1_pubkey *A_tweaked, + const secp256k1_pubkey *A_sum, + const unsigned char *input_hash +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Create Silent Payment shared secret. + * + * Given a public component Pub, a private component sec and an input_hash, + * calculate the corresponding shared secret using ECDH: + * + * shared_secret = (sec * input_hash) * Pub + * + * What the components should be set to depends on the role of the caller. + * For the sender side, the public component is set the recipient's scan public key + * B_scan, and the private component is set to the input's private keys sum: + * + * shared_secret = (a_sum * input_hash) * B_scan [Sender] + * + * For the receiver side, the public component is set to the input's public keys sum, + * and the private component is set to the receiver's scan private key: + * + * shared_secret = (b_scan * input_hash) * A_sum [Receiver, Full node scenario] + * + * In the "light client" scenario for receivers, the public component is already + * tweaked with the input hash: A_tweaked = input_hash * A_sum + * In this case, the input_hash parameter should be set to NULL, to signal that + * no further tweaking should be done before the ECDH: + * + * shared_secret = b_scan * A_tweaked [Receiver, Light client scenario] + * + * The resulting shared secret is needed as input for creating silent payments + * outputs belonging to the same receiver scan public key. + * + * Returns: 1 if shared secret creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: shared_secret33: pointer to the resulting 33-byte shared secret + * In: public_component: pointer to the public component + * private_component: pointer to 32-byte private component + * input_hash: pointer to 32-byte input hash (can be NULL if the + * public component is already tweaked with the input hash) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_shared_secret( + const secp256k1_context *ctx, + unsigned char *shared_secret33, + const secp256k1_pubkey *public_component, + const unsigned char *secret_component, + const unsigned char *input_hash +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Create Silent Payment label tweak and label. + * + * Given a recipient's scan private key b_scan and a label integer m, calculate + * the corresponding label tweak and label: + * + * label_tweak = hash(b_scan || m) + * label = label_tweak * G + * + * Returns: 1 if label tweak and label creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: label_tweak: pointer to the resulting label tweak + * In: receiver_scan_seckey: pointer to the receiver's scan private key + * m: label integer (0 is used for change outputs) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_label_tweak( + const secp256k1_context *ctx, + secp256k1_pubkey *label, + unsigned char *label_tweak32, + const unsigned char *receiver_scan_seckey, + unsigned int m +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Create Silent Payment labelled spend public key. + * + * Given a recipient's spend public key B_spend and a label, calculate + * the corresponding serialized labelled spend public key: + * + * B_m = B_spend + label + * + * The result is used by the receiver to create a Silent Payment address, consisting + * of the serialized and concatenated scan public key and (labelled) spend public key each. + * + * Returns: 1 if labelled spend public key creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: l_addr_spend_pubkey33: pointer to the resulting labelled spend public key + * In: receiver_spend_pubkey: pointer to the receiver's spend pubkey + * label: pointer to the the receiver's label + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_address_spend_pubkey( + const secp256k1_context *ctx, + unsigned char *l_addr_spend_pubkey33, + const secp256k1_pubkey *receiver_spend_pubkey, + const secp256k1_pubkey *label +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Create Silent Payment output public key (for sender). + * + * Given a shared_secret, a recipient's spend public key B_spend, and + * an output counter k, calculate the corresponding output public key: + * + * P_output_xonly = B_spend + hash(shared_secret || ser_32(k)) * G + * + * Returns: 1 if output creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: P_output_xonly: pointer to the resulting output x-only pubkey + * In: shared_secret33: shared secret, derived from either sender's + * or receiver's perspective with routines from above + * receiver_spend_pubkey: pointer to the receiver's spend pubkey + * k: output counter (usually set to 0, should be increased for + * every additional output to the same recipient) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_sender_create_output_pubkey( + const secp256k1_context *ctx, + secp256k1_xonly_pubkey *P_output_xonly, + const unsigned char *shared_secret33, + const secp256k1_pubkey *receiver_spend_pubkey, + unsigned int k +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +typedef struct { + secp256k1_pubkey label; + secp256k1_pubkey label_negated; +} secp256k1_silentpayments_label_data; + +/** Scan for Silent Payment transaction output (for receiver). + * + * Given a shared_secret, a recipient's spend public key B_spend, + * an output counter k, and a scanned tx's output x-only public key tx_output, + * calculate the corresponding scanning data: + * + * t_k = hash(shared_secret || ser_32(k)) + * P_output = B_spend + t_k * G [not returned] + * if P_output == tx_output + * direct_match = 1 + * else + * label1 = tx_output - P_output + * label2 = -tx_output - P_output + * direct_match = 0 + * + * The resulting data is needed for the receiver to efficiently scan for labels + * in silent payments eligible outputs. + * + * Returns: 1 if output scanning was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: direct_match: pointer to the resulting boolean indicating whether + * the calculated output pubkey matches the scanned one + * t_k: pointer to the resulting tweak t_k + * label_data: pointer to the resulting label structure, containing the + * two label candidates, only set if direct_match == 0 + * (can be NULL if the data is not needed) + * In: shared_secret33: shared secret, derived from either sender's + * or receiver's perspective with routines from above + * receiver_spend_pubkey: pointer to the receiver's spend pubkey + * k: output counter (usually set to 0, should be increased for + * every additional output to the same recipient) + * tx_output: pointer to the scanned tx's output x-only public key + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_receiver_scan_output( + const secp256k1_context *ctx, + int *direct_match, + unsigned char *t_k, + secp256k1_silentpayments_label_data *label_data, + const unsigned char *shared_secret33, + const secp256k1_pubkey *receiver_spend_pubkey, + unsigned int k, + const secp256k1_xonly_pubkey *tx_output +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(8); + +/** Create Silent Payment output private key (for spending receiver's funds). + * + * Given a recipient's spend private key b_spend, a tweak t_k and an + * optional label_tweak, calculate the corresponding output private key d: + * + * d = b_spend + t_k + label_tweak + * (if no label tweak is used, then label_tweak = 0) + * + * Returns: 1 if private key creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: output_seckey: pointer to the resulting spending private key + * In: receiver_spend_seckey: pointer to the receiver's spend private key + * t_k: pointer to the 32-byte output tweak + * label_tweak: pointer to an optional 32-byte label tweak + * (can be NULL if no label is used) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_output_seckey( + const secp256k1_context *ctx, + unsigned char *output_seckey, + const unsigned char *receiver_spend_seckey, + const unsigned char *t_k, + const unsigned char *label_tweak +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +#ifdef __cplusplus +} +#endif + +#endif /* SECP256K1_SILENTPAYMENTS_H */ diff --git a/src/assumptions.h b/src/assumptions.h index 8ed04209e91dc7..7961005350bfef 100644 --- a/src/assumptions.h +++ b/src/assumptions.h @@ -19,65 +19,69 @@ reduce the odds of experiencing an unwelcome surprise. */ -struct secp256k1_assumption_checker { - /* This uses a trick to implement a static assertion in C89: a type with an array of negative size is not - allowed. */ - int dummy_array[( - /* Bytes are 8 bits. */ - (CHAR_BIT == 8) && +#if defined(__has_attribute) +# if __has_attribute(__unavailable__) +__attribute__((__unavailable__("Don't call this function. It only exists because STATIC_ASSERT cannot be used outside a function."))) +# endif +#endif +static void secp256k1_assumption_checker(void) { + /* Bytes are 8 bits. */ + STATIC_ASSERT(CHAR_BIT == 8); - /* No integer promotion for uint32_t. This ensures that we can multiply uintXX_t values where XX >= 32 - without signed overflow, which would be undefined behaviour. */ - (UINT_MAX <= UINT32_MAX) && + /* No integer promotion for uint32_t. This ensures that we can multiply uintXX_t values where XX >= 32 + without signed overflow, which would be undefined behaviour. */ + STATIC_ASSERT(UINT_MAX <= UINT32_MAX); - /* Conversions from unsigned to signed outside of the bounds of the signed type are - implementation-defined. Verify that they function as reinterpreting the lower - bits of the input in two's complement notation. Do this for conversions: - - from uint(N)_t to int(N)_t with negative result - - from uint(2N)_t to int(N)_t with negative result - - from int(2N)_t to int(N)_t with negative result - - from int(2N)_t to int(N)_t with positive result */ + /* Conversions from unsigned to signed outside of the bounds of the signed type are + implementation-defined. Verify that they function as reinterpreting the lower + bits of the input in two's complement notation. Do this for conversions: + - from uint(N)_t to int(N)_t with negative result + - from uint(2N)_t to int(N)_t with negative result + - from int(2N)_t to int(N)_t with negative result + - from int(2N)_t to int(N)_t with positive result */ - /* To int8_t. */ - ((int8_t)(uint8_t)0xAB == (int8_t)-(int8_t)0x55) && - ((int8_t)(uint16_t)0xABCD == (int8_t)-(int8_t)0x33) && - ((int8_t)(int16_t)(uint16_t)0xCDEF == (int8_t)(uint8_t)0xEF) && - ((int8_t)(int16_t)(uint16_t)0x9234 == (int8_t)(uint8_t)0x34) && + /* To int8_t. */ + STATIC_ASSERT(((int8_t)(uint8_t)0xAB == (int8_t)-(int8_t)0x55)); + STATIC_ASSERT((int8_t)(uint16_t)0xABCD == (int8_t)-(int8_t)0x33); + STATIC_ASSERT((int8_t)(int16_t)(uint16_t)0xCDEF == (int8_t)(uint8_t)0xEF); + STATIC_ASSERT((int8_t)(int16_t)(uint16_t)0x9234 == (int8_t)(uint8_t)0x34); - /* To int16_t. */ - ((int16_t)(uint16_t)0xBCDE == (int16_t)-(int16_t)0x4322) && - ((int16_t)(uint32_t)0xA1B2C3D4 == (int16_t)-(int16_t)0x3C2C) && - ((int16_t)(int32_t)(uint32_t)0xC1D2E3F4 == (int16_t)(uint16_t)0xE3F4) && - ((int16_t)(int32_t)(uint32_t)0x92345678 == (int16_t)(uint16_t)0x5678) && + /* To int16_t. */ + STATIC_ASSERT((int16_t)(uint16_t)0xBCDE == (int16_t)-(int16_t)0x4322); + STATIC_ASSERT((int16_t)(uint32_t)0xA1B2C3D4 == (int16_t)-(int16_t)0x3C2C); + STATIC_ASSERT((int16_t)(int32_t)(uint32_t)0xC1D2E3F4 == (int16_t)(uint16_t)0xE3F4); + STATIC_ASSERT((int16_t)(int32_t)(uint32_t)0x92345678 == (int16_t)(uint16_t)0x5678); - /* To int32_t. */ - ((int32_t)(uint32_t)0xB2C3D4E5 == (int32_t)-(int32_t)0x4D3C2B1B) && - ((int32_t)(uint64_t)0xA123B456C789D012ULL == (int32_t)-(int32_t)0x38762FEE) && - ((int32_t)(int64_t)(uint64_t)0xC1D2E3F4A5B6C7D8ULL == (int32_t)(uint32_t)0xA5B6C7D8) && - ((int32_t)(int64_t)(uint64_t)0xABCDEF0123456789ULL == (int32_t)(uint32_t)0x23456789) && + /* To int32_t. */ + STATIC_ASSERT((int32_t)(uint32_t)0xB2C3D4E5 == (int32_t)-(int32_t)0x4D3C2B1B); + STATIC_ASSERT((int32_t)(uint64_t)0xA123B456C789D012ULL == (int32_t)-(int32_t)0x38762FEE); + STATIC_ASSERT((int32_t)(int64_t)(uint64_t)0xC1D2E3F4A5B6C7D8ULL == (int32_t)(uint32_t)0xA5B6C7D8); + STATIC_ASSERT((int32_t)(int64_t)(uint64_t)0xABCDEF0123456789ULL == (int32_t)(uint32_t)0x23456789); - /* To int64_t. */ - ((int64_t)(uint64_t)0xB123C456D789E012ULL == (int64_t)-(int64_t)0x4EDC3BA928761FEEULL) && + /* To int64_t. */ + STATIC_ASSERT((int64_t)(uint64_t)0xB123C456D789E012ULL == (int64_t)-(int64_t)0x4EDC3BA928761FEEULL); #if defined(SECP256K1_INT128_NATIVE) - ((int64_t)(((uint128_t)0xA1234567B8901234ULL << 64) + 0xC5678901D2345678ULL) == (int64_t)-(int64_t)0x3A9876FE2DCBA988ULL) && - (((int64_t)(int128_t)(((uint128_t)0xB1C2D3E4F5A6B7C8ULL << 64) + 0xD9E0F1A2B3C4D5E6ULL)) == (int64_t)(uint64_t)0xD9E0F1A2B3C4D5E6ULL) && - (((int64_t)(int128_t)(((uint128_t)0xABCDEF0123456789ULL << 64) + 0x0123456789ABCDEFULL)) == (int64_t)(uint64_t)0x0123456789ABCDEFULL) && + STATIC_ASSERT((int64_t)(((uint128_t)0xA1234567B8901234ULL << 64) + 0xC5678901D2345678ULL) == (int64_t)-(int64_t)0x3A9876FE2DCBA988ULL); + STATIC_ASSERT(((int64_t)(int128_t)(((uint128_t)0xB1C2D3E4F5A6B7C8ULL << 64) + 0xD9E0F1A2B3C4D5E6ULL)) == (int64_t)(uint64_t)0xD9E0F1A2B3C4D5E6ULL); + STATIC_ASSERT(((int64_t)(int128_t)(((uint128_t)0xABCDEF0123456789ULL << 64) + 0x0123456789ABCDEFULL)) == (int64_t)(uint64_t)0x0123456789ABCDEFULL); - /* To int128_t. */ - ((int128_t)(((uint128_t)0xB1234567C8901234ULL << 64) + 0xD5678901E2345678ULL) == (int128_t)(-(int128_t)0x8E1648B3F50E80DCULL * 0x8E1648B3F50E80DDULL + 0x5EA688D5482F9464ULL)) && + /* To int128_t. */ + STATIC_ASSERT((int128_t)(((uint128_t)0xB1234567C8901234ULL << 64) + 0xD5678901E2345678ULL) == (int128_t)(-(int128_t)0x8E1648B3F50E80DCULL * 0x8E1648B3F50E80DDULL + 0x5EA688D5482F9464ULL)); #endif - /* Right shift on negative signed values is implementation defined. Verify that it - acts as a right shift in two's complement with sign extension (i.e duplicating - the top bit into newly added bits). */ - ((((int8_t)0xE8) >> 2) == (int8_t)(uint8_t)0xFA) && - ((((int16_t)0xE9AC) >> 4) == (int16_t)(uint16_t)0xFE9A) && - ((((int32_t)0x937C918A) >> 9) == (int32_t)(uint32_t)0xFFC9BE48) && - ((((int64_t)0xA8B72231DF9CF4B9ULL) >> 19) == (int64_t)(uint64_t)0xFFFFF516E4463BF3ULL) && + /* Right shift on negative signed values is implementation defined. Verify that it + acts as a right shift in two's complement with sign extension (i.e duplicating + the top bit into newly added bits). */ + STATIC_ASSERT((((int8_t)0xE8) >> 2) == (int8_t)(uint8_t)0xFA); + STATIC_ASSERT((((int16_t)0xE9AC) >> 4) == (int16_t)(uint16_t)0xFE9A); + STATIC_ASSERT((((int32_t)0x937C918A) >> 9) == (int32_t)(uint32_t)0xFFC9BE48); + STATIC_ASSERT((((int64_t)0xA8B72231DF9CF4B9ULL) >> 19) == (int64_t)(uint64_t)0xFFFFF516E4463BF3ULL); #if defined(SECP256K1_INT128_NATIVE) - ((((int128_t)(((uint128_t)0xCD833A65684A0DBCULL << 64) + 0xB349312F71EA7637ULL)) >> 39) == (int128_t)(((uint128_t)0xFFFFFFFFFF9B0674ULL << 64) + 0xCAD0941B79669262ULL)) && + STATIC_ASSERT((((int128_t)(((uint128_t)0xCD833A65684A0DBCULL << 64) + 0xB349312F71EA7637ULL)) >> 39) == (int128_t)(((uint128_t)0xFFFFFFFFFF9B0674ULL << 64) + 0xCAD0941B79669262ULL)); #endif - 1) * 2 - 1]; -}; + + /* This function is not supposed to be called. */ + VERIFY_CHECK(0); +} #endif /* SECP256K1_ASSUMPTIONS_H */ diff --git a/src/modules/silentpayments/Makefile.am.include b/src/modules/silentpayments/Makefile.am.include new file mode 100644 index 00000000000000..842a33e2d9b410 --- /dev/null +++ b/src/modules/silentpayments/Makefile.am.include @@ -0,0 +1,2 @@ +include_HEADERS += include/secp256k1_silentpayments.h +noinst_HEADERS += src/modules/silentpayments/main_impl.h diff --git a/src/modules/silentpayments/main_impl.h b/src/modules/silentpayments/main_impl.h new file mode 100644 index 00000000000000..2b8872ae5ac721 --- /dev/null +++ b/src/modules/silentpayments/main_impl.h @@ -0,0 +1,410 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_SILENTPAYMENTS_MAIN_H +#define SECP256K1_MODULE_SILENTPAYMENTS_MAIN_H + +#include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_ecdh.h" +#include "../../../include/secp256k1_extrakeys.h" +#include "../../../include/secp256k1_silentpayments.h" +#include "../../hash.h" + +/** Set hash state to the BIP340 tagged hash midstate for "BIP0352/Inputs". */ +static void secp256k1_silentpayments_sha256_init_inputs(secp256k1_sha256* hash) { + secp256k1_sha256_initialize(hash); + hash->s[0] = 0xd4143ffcul; + hash->s[1] = 0x012ea4b5ul; + hash->s[2] = 0x36e21c8ful; + hash->s[3] = 0xf7ec7b54ul; + hash->s[4] = 0x4dd4e2acul; + hash->s[5] = 0x9bcaa0a4ul; + hash->s[6] = 0xe244899bul; + hash->s[7] = 0xcd06903eul; + + hash->bytes = 64; +} + +static void secp256k1_silentpayments_calculate_input_hash(unsigned char *input_hash, const unsigned char *outpoint_smallest36, secp256k1_ge *pubkey_sum) { + secp256k1_sha256 hash; + unsigned char pubkey_sum_ser[33]; + size_t ser_size; + int ser_ret; + + secp256k1_silentpayments_sha256_init_inputs(&hash); + secp256k1_sha256_write(&hash, outpoint_smallest36, 36); + ser_ret = secp256k1_eckey_pubkey_serialize(pubkey_sum, pubkey_sum_ser, &ser_size, 1); + VERIFY_CHECK(ser_ret && ser_size == sizeof(pubkey_sum_ser)); + (void)ser_ret; + secp256k1_sha256_write(&hash, pubkey_sum_ser, sizeof(pubkey_sum_ser)); + secp256k1_sha256_finalize(&hash, input_hash); +} + +int secp256k1_silentpayments_create_private_tweak_data(const secp256k1_context *ctx, unsigned char *a_sum, unsigned char *input_hash, const unsigned char * const *plain_seckeys, size_t n_plain_seckeys, const unsigned char * const *taproot_seckeys, size_t n_taproot_seckeys, const unsigned char *outpoint_smallest36) { + size_t i; + secp256k1_scalar a_sum_scalar, addend; + secp256k1_ge A_sum_ge; + secp256k1_gej A_sum_gej; + + /* Sanity check inputs. */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(a_sum != NULL); + memset(a_sum, 0, 32); + ARG_CHECK(input_hash != NULL); + memset(input_hash, 0, 32); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(plain_seckeys == NULL || n_plain_seckeys >= 1); + ARG_CHECK(taproot_seckeys == NULL || n_taproot_seckeys >= 1); + ARG_CHECK((plain_seckeys != NULL) || (taproot_seckeys != NULL)); + ARG_CHECK((n_plain_seckeys + n_taproot_seckeys) >= 1); + ARG_CHECK(outpoint_smallest36 != NULL); + + /* Compute input private keys sum: a_sum = a_1 + a_2 + ... + a_n */ + a_sum_scalar = secp256k1_scalar_zero; + for (i = 0; i < n_plain_seckeys; i++) { + int ret = secp256k1_scalar_set_b32_seckey(&addend, plain_seckeys[i]); + VERIFY_CHECK(ret); + (void)ret; + + secp256k1_scalar_add(&a_sum_scalar, &a_sum_scalar, &addend); + VERIFY_CHECK(!secp256k1_scalar_is_zero(&a_sum_scalar)); + } + /* private keys used for taproot outputs have to be negated if they resulted in an odd point */ + for (i = 0; i < n_taproot_seckeys; i++) { + secp256k1_ge addend_point; + int ret = secp256k1_ec_pubkey_create_helper(&ctx->ecmult_gen_ctx, &addend, &addend_point, taproot_seckeys[i]); + VERIFY_CHECK(ret); + (void)ret; + /* declassify addend_point to allow using it as a branch point (this is fine because addend_point is not a secret) */ + secp256k1_declassify(ctx, &addend_point, sizeof(addend_point)); + secp256k1_fe_normalize_var(&addend_point.y); + if (secp256k1_fe_is_odd(&addend_point.y)) { + secp256k1_scalar_negate(&addend, &addend); + } + + secp256k1_scalar_add(&a_sum_scalar, &a_sum_scalar, &addend); + VERIFY_CHECK(!secp256k1_scalar_is_zero(&a_sum_scalar)); + } + if (secp256k1_scalar_is_zero(&a_sum_scalar)) { + /* TODO: do we need a special error return code for this case? */ + return 0; + } + secp256k1_scalar_get_b32(a_sum, &a_sum_scalar); + + /* Compute input_hash = hash(outpoint_L || (a_sum * G)) */ + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &A_sum_gej, &a_sum_scalar); + secp256k1_ge_set_gej(&A_sum_ge, &A_sum_gej); + secp256k1_silentpayments_calculate_input_hash(input_hash, outpoint_smallest36, &A_sum_ge); + + return 1; +} + +int secp256k1_silentpayments_create_public_tweak_data(const secp256k1_context *ctx, secp256k1_pubkey *A_sum, unsigned char *input_hash, const secp256k1_pubkey * const *plain_pubkeys, size_t n_plain_pubkeys, const secp256k1_xonly_pubkey * const *xonly_pubkeys, size_t n_xonly_pubkeys, const unsigned char *outpoint_smallest36) { + size_t i; + secp256k1_ge A_sum_ge, addend; + secp256k1_gej A_sum_gej; + + /* Sanity check inputs */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(A_sum != NULL); + ARG_CHECK(input_hash != NULL); + memset(input_hash, 0, 32); + ARG_CHECK(plain_pubkeys == NULL || n_plain_pubkeys >= 1); + ARG_CHECK(xonly_pubkeys == NULL || n_xonly_pubkeys >= 1); + ARG_CHECK((plain_pubkeys != NULL) || (xonly_pubkeys != NULL)); + ARG_CHECK((n_plain_pubkeys + n_xonly_pubkeys) >= 1); + ARG_CHECK(outpoint_smallest36 != NULL); + + /* Compute input public keys sum: A_sum = A_1 + A_2 + ... + A_n */ + secp256k1_gej_set_infinity(&A_sum_gej); + for (i = 0; i < n_plain_pubkeys; i++) { + secp256k1_pubkey_load(ctx, &addend, plain_pubkeys[i]); + secp256k1_gej_add_ge(&A_sum_gej, &A_sum_gej, &addend); + } + for (i = 0; i < n_xonly_pubkeys; i++) { + secp256k1_xonly_pubkey_load(ctx, &addend, xonly_pubkeys[i]); + secp256k1_gej_add_ge(&A_sum_gej, &A_sum_gej, &addend); + } + if (secp256k1_gej_is_infinity(&A_sum_gej)) { + /* TODO: do we need a special error return code for this case? */ + return 0; + } + secp256k1_ge_set_gej(&A_sum_ge, &A_sum_gej); + secp256k1_pubkey_save(A_sum, &A_sum_ge); + + /* Compute input_hash = hash(outpoint_L || A_sum) */ + secp256k1_silentpayments_calculate_input_hash(input_hash, outpoint_smallest36, &A_sum_ge); + + return 1; +} + +int secp256k1_silentpayments_create_tweaked_pubkey(const secp256k1_context *ctx, secp256k1_pubkey *A_tweaked, const secp256k1_pubkey *A_sum, const unsigned char *input_hash) { + /* Sanity check inputs */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(A_tweaked != NULL); + ARG_CHECK(A_sum != NULL); + ARG_CHECK(input_hash != NULL); + + /* Calculate A_tweaked = input_hash * A_sum */ + *A_tweaked = *A_sum; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, A_tweaked, input_hash)) { + return 0; + } + + return 1; +} + +/* secp256k1_ecdh expects a hash function to be passed in or uses its default + * hashing function. We don't want to hash the ECDH result, so we define a + * custom function which simply returns the pubkey without hashing. + */ +static int secp256k1_silentpayments_ecdh_return_pubkey(unsigned char *output, const unsigned char *x32, const unsigned char *y32, void *data) { + secp256k1_ge point; + secp256k1_fe x, y; + size_t ser_size; + int ser_ret; + + (void)data; + /* Parse point as group element */ + if (!secp256k1_fe_set_b32_limit(&x, x32) || !secp256k1_fe_set_b32_limit(&y, y32)) { + return 0; + } + secp256k1_ge_set_xy(&point, &x, &y); + + /* Serialize as compressed pubkey */ + ser_ret = secp256k1_eckey_pubkey_serialize(&point, output, &ser_size, 1); + VERIFY_CHECK(ser_ret && ser_size == 33); + (void)ser_ret; + + return 1; +} + +int secp256k1_silentpayments_create_shared_secret(const secp256k1_context *ctx, unsigned char *shared_secret33, const secp256k1_pubkey *public_component, const unsigned char *secret_component, const unsigned char *input_hash) { + unsigned char tweaked_secret_component[32]; + + /* Sanity check inputs */ + ARG_CHECK(shared_secret33 != NULL); + memset(shared_secret33, 0, 33); + ARG_CHECK(public_component != NULL); + ARG_CHECK(secret_component != NULL); + + /* Tweak secret component with input hash, if available */ + memcpy(tweaked_secret_component, secret_component, 32); + if (input_hash != NULL) { + if (!secp256k1_ec_seckey_tweak_mul(ctx, tweaked_secret_component, input_hash)) { + return 0; + } + } + + /* Compute shared_secret = tweaked_secret_component * Public_component */ + if (!secp256k1_ecdh(ctx, shared_secret33, public_component, tweaked_secret_component, secp256k1_silentpayments_ecdh_return_pubkey, NULL)) { + return 0; + } + + return 1; +} + +/** Set hash state to the BIP340 tagged hash midstate for "BIP0352/Label". */ +static void secp256k1_silentpayments_sha256_init_label(secp256k1_sha256* hash) { + secp256k1_sha256_initialize(hash); + hash->s[0] = 0x26b95d63ul; + hash->s[1] = 0x8bf1b740ul; + hash->s[2] = 0x10a5986ful; + hash->s[3] = 0x06a387a5ul; + hash->s[4] = 0x2d1c1c30ul; + hash->s[5] = 0xd035951aul; + hash->s[6] = 0x2d7f0f96ul; + hash->s[7] = 0x29e3e0dbul; + + hash->bytes = 64; +} + +int secp256k1_silentpayments_create_label_tweak(const secp256k1_context *ctx, secp256k1_pubkey *label, unsigned char *label_tweak32, const unsigned char *receiver_scan_seckey, unsigned int m) { + secp256k1_sha256 hash; + unsigned char m_serialized[4]; + + /* Sanity check inputs. */ + VERIFY_CHECK(ctx != NULL); + (void)ctx; + VERIFY_CHECK(label != NULL); + VERIFY_CHECK(label_tweak32 != NULL); + VERIFY_CHECK(receiver_scan_seckey != NULL); + + /* Compute label_tweak = hash(ser_256(b_scan) || ser_32(m)) [sha256 with tag "BIP0352/Label"] */ + secp256k1_silentpayments_sha256_init_label(&hash); + secp256k1_sha256_write(&hash, receiver_scan_seckey, 32); + secp256k1_write_be32(m_serialized, m); + secp256k1_sha256_write(&hash, m_serialized, sizeof(m_serialized)); + secp256k1_sha256_finalize(&hash, label_tweak32); + + /* Compute label = label_tweak * G */ + if (!secp256k1_ec_pubkey_create(ctx, label, label_tweak32)) { + return 0; + } + + return 1; +} + +int secp256k1_silentpayments_create_address_spend_pubkey(const secp256k1_context *ctx, unsigned char *l_addr_spend_pubkey33, const secp256k1_pubkey *receiver_spend_pubkey, const secp256k1_pubkey *label) { + secp256k1_ge B_m, label_addend; + secp256k1_gej result_gej; + secp256k1_ge result_ge; + size_t ser_size; + int ser_ret; + + /* Sanity check inputs. */ + VERIFY_CHECK(ctx != NULL); + VERIFY_CHECK(l_addr_spend_pubkey33 != NULL); + VERIFY_CHECK(receiver_spend_pubkey != NULL); + VERIFY_CHECK(label != NULL); + + /* Calculate B_m = B_spend + label */ + secp256k1_pubkey_load(ctx, &B_m, receiver_spend_pubkey); + secp256k1_pubkey_load(ctx, &label_addend, label); + secp256k1_gej_set_ge(&result_gej, &B_m); + secp256k1_gej_add_ge_var(&result_gej, &result_gej, &label_addend, NULL); + + /* Serialize B_m */ + secp256k1_ge_set_gej(&result_ge, &result_gej); + ser_ret = secp256k1_eckey_pubkey_serialize(&result_ge, l_addr_spend_pubkey33, &ser_size, 1); + VERIFY_CHECK(ser_ret && ser_size == 33); + (void)ser_ret; + + return 1; +} + +/** Set hash state to the BIP340 tagged hash midstate for "BIP0352/SharedSecret". */ +static void secp256k1_silentpayments_sha256_init_sharedsecret(secp256k1_sha256* hash) { + secp256k1_sha256_initialize(hash); + hash->s[0] = 0x88831537ul; + hash->s[1] = 0x5127079bul; + hash->s[2] = 0x69c2137bul; + hash->s[3] = 0xab0303e6ul; + hash->s[4] = 0x98fa21faul; + hash->s[5] = 0x4a888523ul; + hash->s[6] = 0xbd99daabul; + hash->s[7] = 0xf25e5e0aul; + + hash->bytes = 64; +} + +static void secp256k1_silentpayments_create_t_k(secp256k1_scalar *t_k_scalar, const unsigned char *shared_secret33, unsigned int k) { + secp256k1_sha256 hash; + unsigned char hash_ser[32]; + unsigned char k_serialized[4]; + + /* Compute t_k = hash(shared_secret || ser_32(k)) [sha256 with tag "BIP0352/SharedSecret"] */ + secp256k1_silentpayments_sha256_init_sharedsecret(&hash); + secp256k1_sha256_write(&hash, shared_secret33, 33); + secp256k1_write_be32(k_serialized, k); + secp256k1_sha256_write(&hash, k_serialized, sizeof(k_serialized)); + secp256k1_sha256_finalize(&hash, hash_ser); + secp256k1_scalar_set_b32(t_k_scalar, hash_ser, NULL); +} + +int secp256k1_silentpayments_sender_create_output_pubkey(const secp256k1_context *ctx, secp256k1_xonly_pubkey *P_output_xonly, const unsigned char *shared_secret33, const secp256k1_pubkey *receiver_spend_pubkey, unsigned int k) { + secp256k1_ge P_output_ge; + secp256k1_scalar t_k_scalar; + + /* Sanity check inputs */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(P_output_xonly != NULL); + ARG_CHECK(shared_secret33 != NULL); + ARG_CHECK(receiver_spend_pubkey != NULL); + + /* Calculate and return P_output_xonly = B_spend + t_k * G */ + secp256k1_silentpayments_create_t_k(&t_k_scalar, shared_secret33, k); + secp256k1_pubkey_load(ctx, &P_output_ge, receiver_spend_pubkey); + if (!secp256k1_eckey_pubkey_tweak_add(&P_output_ge, &t_k_scalar)) { + return 0; + } + secp256k1_xonly_pubkey_save(P_output_xonly, &P_output_ge); + + return 1; +} + +int secp256k1_silentpayments_receiver_scan_output(const secp256k1_context *ctx, int *direct_match, unsigned char *t_k, secp256k1_silentpayments_label_data *label_data, const unsigned char *shared_secret33, const secp256k1_pubkey *receiver_spend_pubkey, unsigned int k, const secp256k1_xonly_pubkey *tx_output) { + secp256k1_scalar t_k_scalar; + secp256k1_ge P_output_ge; + secp256k1_xonly_pubkey P_output_xonly; + + /* Sanity check inputs */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(direct_match != NULL); + ARG_CHECK(t_k != NULL); + ARG_CHECK(shared_secret33 != NULL); + ARG_CHECK(receiver_spend_pubkey != NULL); + ARG_CHECK(tx_output != NULL); + + /* Calculate t_k = hash(shared_secret || ser_32(k)) */ + secp256k1_silentpayments_create_t_k(&t_k_scalar, shared_secret33, k); + secp256k1_scalar_get_b32(t_k, &t_k_scalar); + + /* Calculate P_output = B_spend + t_k * G */ + secp256k1_pubkey_load(ctx, &P_output_ge, receiver_spend_pubkey); + if (!secp256k1_eckey_pubkey_tweak_add(&P_output_ge, &t_k_scalar)) { + return 0; + } + + /* If the calculated output matches the one from the tx, we have a direct match and can + * return without labels calculation (one of the two would result in point of infinity) */ + secp256k1_xonly_pubkey_save(&P_output_xonly, &P_output_ge); + if (secp256k1_xonly_pubkey_cmp(ctx, &P_output_xonly, tx_output) == 0) { + *direct_match = 1; + return 1; + } + *direct_match = 0; + + /* If desired, also calculate label candidates */ + if (label_data != NULL) { + secp256k1_ge P_output_negated_ge, tx_output_ge; + secp256k1_ge label_ge; + secp256k1_gej label_gej; + + /* Calculate negated P_output (common addend) first */ + secp256k1_ge_neg(&P_output_negated_ge, &P_output_ge); + + /* Calculate first scan label candidate: label1 = tx_output - P_output */ + secp256k1_xonly_pubkey_load(ctx, &tx_output_ge, tx_output); + secp256k1_gej_set_ge(&label_gej, &tx_output_ge); + secp256k1_gej_add_ge_var(&label_gej, &label_gej, &P_output_negated_ge, NULL); + secp256k1_ge_set_gej(&label_ge, &label_gej); + secp256k1_pubkey_save(&label_data->label, &label_ge); + + /* Calculate second scan label candidate: label2 = -tx_output - P_output */ + secp256k1_gej_set_ge(&label_gej, &tx_output_ge); + secp256k1_gej_neg(&label_gej, &label_gej); + secp256k1_gej_add_ge_var(&label_gej, &label_gej, &P_output_negated_ge, NULL); + secp256k1_ge_set_gej(&label_ge, &label_gej); + secp256k1_pubkey_save(&label_data->label_negated, &label_ge); + } + + return 1; +} + +int secp256k1_silentpayments_create_output_seckey(const secp256k1_context *ctx, unsigned char *output_seckey, const unsigned char *receiver_spend_seckey, const unsigned char *t_k, const unsigned char *label_tweak) { + /* Sanity check inputs */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(output_seckey != NULL); + memset(output_seckey, 0, 32); + ARG_CHECK(receiver_spend_seckey != NULL); + ARG_CHECK(t_k != NULL); + + /* Compute d = (b_spend + t_k) mod n */ + memcpy(output_seckey, receiver_spend_seckey, 32); + if (!secp256k1_ec_seckey_tweak_add(ctx, output_seckey, t_k)) { + return 0; + } + + if (label_tweak != NULL) { + if (!secp256k1_ec_seckey_tweak_add(ctx, output_seckey, label_tweak)) { + return 0; + } + } + + return 1; +} + +#endif diff --git a/src/modules/silentpayments/tests_impl.h b/src/modules/silentpayments/tests_impl.h new file mode 100644 index 00000000000000..a61f2d460d8dc4 --- /dev/null +++ b/src/modules/silentpayments/tests_impl.h @@ -0,0 +1,131 @@ +/*********************************************************************** + * 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" + +void run_silentpayments_tests(void) { + /* BIP-352 test vector + * "Single recipient: taproot input with odd y-value and non-taproot input" */ + unsigned char outpoint_lowest[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 + }; + unsigned char input_privkey_plain0[32] = { + 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 + }; + unsigned char input_privkey_taproot0[32] = { + 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 + }; + const unsigned char* input_privkeys_plain[1]; + const unsigned char* input_privkeys_taproot[1]; + unsigned char input_pubkey_plain0[33] = {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 + }; + unsigned char input_pubkey_xonly0[32] = { + 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 + }; + const secp256k1_pubkey* input_pubkeys_plain[1]; + const secp256k1_xonly_pubkey* input_pubkeys_xonly[1]; + unsigned char receiver_scan_privkey[32] = { + 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 + }; + unsigned char receiver_scan_pubkey[33] = {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 + }; + unsigned char receiver_spend_privkey[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 + }; + unsigned char receiver_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 + }; + unsigned char output_expected[32] = { + 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 + }; + unsigned char privkey_tweak_expected[32] = { + 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 + }; + + unsigned char shared_secret_sender[33]; + unsigned char shared_secret_receiver[33]; + unsigned char shared_secret_lightclient[33]; + secp256k1_pubkey public_keys_sum; + secp256k1_pubkey tweaked_public_key; + unsigned char private_keys_sum[32]; + unsigned char input_hash1[32]; + unsigned char input_hash2[32]; + secp256k1_xonly_pubkey output_expected_xonly_obj; + secp256k1_xonly_pubkey output_calculated_xonly_obj; + unsigned char output_calculated[32]; + unsigned char privkey_calculated[32]; + unsigned char privkey_expected[32]; + int direct_match; + unsigned char t_k[32]; + + /* convert raw key material into secp256k1 objects where necessary */ + secp256k1_pubkey input_pubkey_plain_obj, receiver_scan_pubkey_obj, receiver_spend_pubkey_obj; + secp256k1_xonly_pubkey input_pubkey_xonly_obj; + CHECK(secp256k1_ec_pubkey_parse(CTX, &input_pubkey_plain_obj, input_pubkey_plain0, 33)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &receiver_scan_pubkey_obj, receiver_scan_pubkey, 33)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &receiver_spend_pubkey_obj, receiver_spend_pubkey, 33)); + CHECK(secp256k1_xonly_pubkey_parse(CTX, &input_pubkey_xonly_obj, input_pubkey_xonly0)); + CHECK(secp256k1_xonly_pubkey_parse(CTX, &output_expected_xonly_obj, output_expected)); + input_pubkeys_plain[0] = &input_pubkey_plain_obj; + input_pubkeys_xonly[0] = &input_pubkey_xonly_obj; + + /* create shared secret from sender and receiver perspective, and check that they match */ + input_privkeys_plain[0] = input_privkey_plain0; + input_privkeys_taproot[0] = input_privkey_taproot0; + CHECK(secp256k1_silentpayments_create_private_tweak_data(CTX, private_keys_sum, input_hash1, + input_privkeys_plain, 1, input_privkeys_taproot, 1, outpoint_lowest)); + CHECK(secp256k1_silentpayments_create_shared_secret(CTX, shared_secret_sender, + &receiver_scan_pubkey_obj, private_keys_sum, input_hash1)); + + CHECK(secp256k1_silentpayments_create_public_tweak_data(CTX, &public_keys_sum, input_hash2, + input_pubkeys_plain, 1, input_pubkeys_xonly, 1, outpoint_lowest)); + CHECK(secp256k1_silentpayments_create_shared_secret(CTX, shared_secret_receiver, + &public_keys_sum, receiver_scan_privkey, input_hash2)); + + /* light client case */ + CHECK(secp256k1_silentpayments_create_tweaked_pubkey(CTX, &tweaked_public_key, &public_keys_sum, input_hash2)); + CHECK(secp256k1_silentpayments_create_shared_secret(CTX, shared_secret_lightclient, + &tweaked_public_key, receiver_scan_privkey, NULL)); + CHECK(secp256k1_memcmp_var(shared_secret_receiver, shared_secret_lightclient, 33) == 0); + + CHECK(secp256k1_memcmp_var(input_hash1, input_hash2, 32) == 0); + CHECK(secp256k1_memcmp_var(shared_secret_sender, shared_secret_receiver, 33) == 0); + + /* check that calculated silent payments output matches */ + CHECK(secp256k1_silentpayments_sender_create_output_pubkey(CTX, &output_calculated_xonly_obj, + shared_secret_sender, &receiver_spend_pubkey_obj, 0)); + CHECK(secp256k1_xonly_pubkey_serialize(CTX, output_calculated, &output_calculated_xonly_obj)); + CHECK(secp256k1_memcmp_var(output_calculated, output_expected, 32) == 0); + + CHECK(secp256k1_silentpayments_receiver_scan_output(CTX, &direct_match, t_k, NULL, + shared_secret_receiver, &receiver_spend_pubkey_obj, 0, &output_expected_xonly_obj)); + CHECK(direct_match == 1); + + /* check that calculated silent payment output spending private key matches */ + memcpy(privkey_expected, receiver_spend_privkey, 32); + CHECK(secp256k1_ec_seckey_tweak_add(CTX, privkey_expected, privkey_tweak_expected)); + CHECK(secp256k1_silentpayments_create_output_seckey(CTX, privkey_calculated, + receiver_spend_privkey, t_k, NULL)); + CHECK(secp256k1_memcmp_var(privkey_calculated, privkey_expected, 32) == 0); +} + +#endif diff --git a/src/scalar_impl.h b/src/scalar_impl.h index bbba83e937e06a..972d8041b0afbe 100644 --- a/src/scalar_impl.h +++ b/src/scalar_impl.h @@ -229,7 +229,7 @@ static void secp256k1_scalar_split_lambda(secp256k1_scalar * SECP256K1_RESTRICT * <= {triangle inequality} * a1*|k*b2/n - c1| + a2*|k*(-b1)/n - c2| * < {Lemma 1 and Lemma 2} - * a1*(2^-1 + epslion1) + a2*(2^-1 + epsilon2) + * a1*(2^-1 + epsilon1) + a2*(2^-1 + epsilon2) * < {rounding up to an integer} * (a1 + a2 + 1)/2 * < {rounding up to a power of 2} @@ -247,7 +247,7 @@ static void secp256k1_scalar_split_lambda(secp256k1_scalar * SECP256K1_RESTRICT * <= {triangle inequality} * (-b1)*|k*b2/n - c1| + b2*|k*(-b1)/n - c2| * < {Lemma 1 and Lemma 2} - * (-b1)*(2^-1 + epslion1) + b2*(2^-1 + epsilon2) + * (-b1)*(2^-1 + epsilon1) + b2*(2^-1 + epsilon2) * < {rounding up to an integer} * (-b1 + b2)/2 + 1 * < {rounding up to a power of 2} diff --git a/src/secp256k1.c b/src/secp256k1.c index 4c11e7f0b8b582..0ca054d4d9c9cd 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -237,36 +237,25 @@ static SECP256K1_INLINE void secp256k1_declassify(const secp256k1_context* ctx, } static int secp256k1_pubkey_load(const secp256k1_context* ctx, secp256k1_ge* ge, const secp256k1_pubkey* pubkey) { - if (sizeof(secp256k1_ge_storage) == 64) { - /* When the secp256k1_ge_storage type is exactly 64 byte, use its - * representation inside secp256k1_pubkey, as conversion is very fast. - * Note that secp256k1_pubkey_save must use the same representation. */ - secp256k1_ge_storage s; - memcpy(&s, &pubkey->data[0], sizeof(s)); - secp256k1_ge_from_storage(ge, &s); - } else { - /* Otherwise, fall back to 32-byte big endian for X and Y. */ - secp256k1_fe x, y; - ARG_CHECK(secp256k1_fe_set_b32_limit(&x, pubkey->data)); - ARG_CHECK(secp256k1_fe_set_b32_limit(&y, pubkey->data + 32)); - secp256k1_ge_set_xy(ge, &x, &y); - } + secp256k1_ge_storage s; + + /* We require that the secp256k1_ge_storage type is exactly 64 bytes. + * This is formally not guaranteed by the C standard, but should hold on any + * sane compiler in the real world. */ + STATIC_ASSERT(sizeof(secp256k1_ge_storage) == 64); + memcpy(&s, &pubkey->data[0], 64); + secp256k1_ge_from_storage(ge, &s); ARG_CHECK(!secp256k1_fe_is_zero(&ge->x)); return 1; } static void secp256k1_pubkey_save(secp256k1_pubkey* pubkey, secp256k1_ge* ge) { - if (sizeof(secp256k1_ge_storage) == 64) { - secp256k1_ge_storage s; - secp256k1_ge_to_storage(&s, ge); - memcpy(&pubkey->data[0], &s, sizeof(s)); - } else { - VERIFY_CHECK(!secp256k1_ge_is_infinity(ge)); - secp256k1_fe_normalize_var(&ge->x); - secp256k1_fe_normalize_var(&ge->y); - secp256k1_fe_get_b32(pubkey->data, &ge->x); - secp256k1_fe_get_b32(pubkey->data + 32, &ge->y); - } + secp256k1_ge_storage s; + + STATIC_ASSERT(sizeof(secp256k1_ge_storage) == 64); + VERIFY_CHECK(!secp256k1_ge_is_infinity(ge)); + secp256k1_ge_to_storage(&s, ge); + memcpy(&pubkey->data[0], &s, 64); } int secp256k1_ec_pubkey_parse(const secp256k1_context* ctx, secp256k1_pubkey* pubkey, const unsigned char *input, size_t inputlen) { @@ -815,3 +804,7 @@ int secp256k1_tagged_sha256(const secp256k1_context* ctx, unsigned char *hash32, #ifdef ENABLE_MODULE_ELLSWIFT # include "modules/ellswift/main_impl.h" #endif + +#ifdef ENABLE_MODULE_SILENTPAYMENTS +# include "modules/silentpayments/main_impl.h" +#endif diff --git a/src/tests.c b/src/tests.c index bec1c45585b640..bb745138577149 100644 --- a/src/tests.c +++ b/src/tests.c @@ -2927,20 +2927,18 @@ static void run_scalar_tests(void) { secp256k1_scalar_set_b32(&r2, res[i][1], &overflow); CHECK(!overflow); secp256k1_scalar_mul(&z, &x, &y); - CHECK(!secp256k1_scalar_check_overflow(&z)); CHECK(secp256k1_scalar_eq(&r1, &z)); if (!secp256k1_scalar_is_zero(&y)) { secp256k1_scalar_inverse(&zz, &y); - CHECK(!secp256k1_scalar_check_overflow(&zz)); secp256k1_scalar_inverse_var(&zzv, &y); CHECK(secp256k1_scalar_eq(&zzv, &zz)); secp256k1_scalar_mul(&z, &z, &zz); - CHECK(!secp256k1_scalar_check_overflow(&z)); CHECK(secp256k1_scalar_eq(&x, &z)); secp256k1_scalar_mul(&zz, &zz, &y); - CHECK(!secp256k1_scalar_check_overflow(&zz)); CHECK(secp256k1_scalar_eq(&secp256k1_scalar_one, &zz)); } + secp256k1_scalar_mul(&z, &x, &x); + CHECK(secp256k1_scalar_eq(&r2, &z)); } } } @@ -7287,6 +7285,10 @@ static void run_ecdsa_wycheproof(void) { # include "modules/ellswift/tests_impl.h" #endif +#ifdef ENABLE_MODULE_SILENTPAYMENTS +# include "modules/silentpayments/tests_impl.h" +#endif + static void run_secp256k1_memczero_test(void) { unsigned char buf1[6] = {1, 2, 3, 4, 5, 6}; unsigned char buf2[sizeof(buf1)]; @@ -7635,6 +7637,10 @@ int main(int argc, char **argv) { run_ellswift_tests(); #endif +#ifdef ENABLE_MODULE_SILENTPAYMENTS + run_silentpayments_tests(); +#endif + /* util tests */ run_secp256k1_memczero_test(); run_secp256k1_byteorder_tests(); diff --git a/src/util.h b/src/util.h index 187bf1c5e0a6c2..154d9ebcf13a60 100644 --- a/src/util.h +++ b/src/util.h @@ -51,13 +51,27 @@ static void print_buf_plain(const unsigned char *buf, size_t len) { # define SECP256K1_INLINE inline # endif +/** Assert statically that expr is true. + * + * This is a statement-like macro and can only be used inside functions. + */ +#define STATIC_ASSERT(expr) do { \ + switch(0) { \ + case 0: \ + /* If expr evaluates to 0, we have two case labels "0", which is illegal. */ \ + case /* ERROR: static assertion failed */ (expr): \ + ; \ + } \ +} while(0) + /** Assert statically that expr is an integer constant expression, and run stmt. * * Useful for example to enforce that magnitude arguments are constant. */ #define ASSERT_INT_CONST_AND_DO(expr, stmt) do { \ switch(42) { \ - case /* ERROR: integer argument is not constant */ expr: \ + /* C allows only integer constant expressions as case labels. */ \ + case /* ERROR: integer argument is not constant */ (expr): \ break; \ default: ; \ } \ diff --git a/tools/check-abi.sh b/tools/check-abi.sh index 8f6119cd8e82ba..55c945ac16122b 100755 --- a/tools/check-abi.sh +++ b/tools/check-abi.sh @@ -3,17 +3,19 @@ set -eu default_base_version="$(git describe --match "v*.*.*" --abbrev=0)" -default_new_version="master" +default_new_version="HEAD" display_help_and_exit() { - echo "Usage: $0 " + echo "Usage: $0 [ []]" echo "" echo "Description: This script uses the ABI Compliance Checker tool to determine if the ABI" echo " of a new version of libsecp256k1 has changed in a backward-incompatible way." echo "" echo "Options:" - echo " base_ver Specify the base version (default: $default_base_version)" - echo " new_ver Specify the new version (default: $default_new_version)" + echo " base_ver Specify the base version as a git commit-ish" + echo " (default: most recent reachable tag matching \"v.*.*\", currently \"$default_base_version\")" + echo " new_ver Specify the new version as a git commit-ish" + echo " (default: $default_new_version)" echo " -h, --help Display this help message" exit 0 } @@ -23,9 +25,11 @@ if [ "$#" -eq 0 ]; then new_version="$default_new_version" elif [ "$#" -eq 1 ] && { [ "$1" = "-h" ] || [ "$1" = "--help" ]; }; then display_help_and_exit -elif [ "$#" -eq 2 ]; then +elif [ "$#" -eq 1 ] || [ "$#" -eq 2 ]; then base_version="$1" - new_version="$2" + if [ "$#" -eq 2 ]; then + new_version="$2" + fi else echo "Invalid usage. See help:" echo "" @@ -33,7 +37,8 @@ else fi checkout_and_build() { - git worktree add -d "$1" "$2" + _orig_dir="$(pwd)" + git worktree add --detach "$1" "$2" cd "$1" mkdir build && cd build cmake -S .. --preset dev-mode \ @@ -45,20 +50,18 @@ checkout_and_build() { -DSECP256K1_BUILD_EXAMPLES=OFF cmake --build . -j "$(nproc)" abi-dumper src/libsecp256k1.so -o ABI.dump -lver "$2" + cd "$_orig_dir" } echo "Comparing $base_version (base version) to $new_version (new version)" echo -original_dir="$(pwd)" - -base_source_dir=$(mktemp -d) +base_source_dir="$(mktemp -d)" checkout_and_build "$base_source_dir" "$base_version" -new_source_dir=$(mktemp -d) +new_source_dir="$(mktemp -d)" checkout_and_build "$new_source_dir" "$new_version" -cd "$original_dir" abi-compliance-checker -lib libsecp256k1 -old "${base_source_dir}/build/ABI.dump" -new "${new_source_dir}/build/ABI.dump" git worktree remove "$base_source_dir" git worktree remove "$new_source_dir"