From e7816e44b077866c5a499f2bb5a3b6d3c8c0db64 Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:04:34 -0500 Subject: [PATCH 01/23] nit: mark bitcoind dependent sign tests --- testing/test_sign.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/testing/test_sign.py b/testing/test_sign.py index 9f1dd4455..13e2e1062 100644 --- a/testing/test_sign.py +++ b/testing/test_sign.py @@ -765,6 +765,7 @@ def test_change_p2sh_p2wpkh(start_sign, end_sign, check_against_bitcoind, use_re end_sign(True) +@pytest.mark.bitcoind def test_wrong_p2sh_p2wpkh(bitcoind, start_sign, end_sign, bitcoind_d_sim_watch, cap_story): sim = bitcoind_d_sim_watch sim_addr = sim.getnewaddress("", "bech32") @@ -1335,6 +1336,7 @@ def value_tweak(spendables): @pytest.mark.parametrize('addr_fmt', ADDR_STYLES_SINGLE) @pytest.mark.parametrize('num_ins', [1, 17]) @pytest.mark.parametrize('num_outs', [1, 17]) +@pytest.mark.bitcoind def test_txid_calc(num_ins, fake_txn, try_sign, dev, decode_with_bitcoind, cap_story, txid_from_export_prompt, press_cancel, num_outs, addr_fmt): # verify correct txid for transactions is being calculated @@ -2927,6 +2929,7 @@ def random_nLockTime_test_cases(num=10): (1748671747, "2025-05-31 06:09:07"), *random_nLockTime_test_cases() ]) +@pytest.mark.bitcoind def test_timelocks_visualize(start_sign, end_sign, dev, bitcoind, use_regtest, bitcoind_d_sim_watch, nLockTime, sim_root_dir): # - works on simulator and connected USB real-device @@ -2983,6 +2986,7 @@ def test_timelocks_visualize(start_sign, end_sign, dev, bitcoind, use_regtest, @pytest.mark.parametrize('in_out', [(4,1),(2,2),(2,1)]) @pytest.mark.parametrize('partial', [False, True]) +@pytest.mark.bitcoind def test_base64_psbt_qr(in_out, partial, scan_a_qr, readback_bbqr, goto_home, use_regtest, cap_story, fake_txn, dev, decode_psbt_with_bitcoind, decode_with_bitcoind, From 853bb5ca9a8c55711a611b3425ca5260e0de6065 Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Wed, 29 Jul 2026 14:34:58 -0400 Subject: [PATCH 02/23] bump submodule version external/ckcc-protocol - Add bip375 and bip376 constants external/libngu - Expose ec_pubkey_combine - Expose ec_pubkey_tweak_mul - Add bip352_encode update libngu --- external/ckcc-protocol | 2 +- external/libngu | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/external/ckcc-protocol b/external/ckcc-protocol index 3d1dfa858..da36586bd 160000 --- a/external/ckcc-protocol +++ b/external/ckcc-protocol @@ -1 +1 @@ -Subproject commit 3d1dfa858beb58b8dac37d8c66d7aed2909812f2 +Subproject commit da36586bdb55ae7c98d7b98bc0f8b50ce996259c diff --git a/external/libngu b/external/libngu index b0ce9acff..5dd94747c 160000 --- a/external/libngu +++ b/external/libngu @@ -1 +1 @@ -Subproject commit b0ce9acffa455d9630c64d3614d0fb9b913c919e +Subproject commit 5dd94747c205af424d37c38599eba615cfc25091 From 2b3f17e8243a273d23ded88abde9d9d40c88c422 Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Tue, 25 Nov 2025 17:38:21 -0500 Subject: [PATCH 03/23] BIP-352: implement Silent Payments cryptographic primitives Add bip352 crypto primitives / helpers Add bip352 tagged hashes --- shared/precomp_tag_hash.py | 10 ++- shared/silentpayments.py | 142 +++++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 shared/silentpayments.py diff --git a/shared/precomp_tag_hash.py b/shared/precomp_tag_hash.py index 7ba0a7c95..a5f9f990f 100644 --- a/shared/precomp_tag_hash.py +++ b/shared/precomp_tag_hash.py @@ -9,4 +9,12 @@ # SHA256(TapTweak) TAP_TWEAK_H = b'\xe8\x0f\xe1c\x9c\x9c\xa0P\xe3\xaf\x1b9\xc1C\xc6>B\x9c\xbc\xeb\x15\xd9@\xfb\xb5\xc5\xa1\xf4\xafW\xc5\xe9' # SHA256(TapSighash) -TAP_SIGHASH_H = b'\xf4\nH\xdfK*p\xc8\xb4\x92K\xf2eFa\xed=\x95\xfdf\xa3\x13\xeb\x87#u\x97\xc6(\xe4\xa01' \ No newline at end of file +TAP_SIGHASH_H = b'\xf4\nH\xdfK*p\xc8\xb4\x92K\xf2eFa\xed=\x95\xfdf\xa3\x13\xeb\x87#u\x97\xc6(\xe4\xa01' + +# BIP-352 tag hashes +# SHA256(BIP0352/SharedSecret) +BIP352_SHARED_SECRET_TAG_H = b'\x9fm\x80\x11X\x1e\xb6-r\xe6\x13`L3\r\xca*\x0b\xd3I\xe2JF\xd9\xa2\xef$\xb9\xa9\x8fA\xbd' +# SHA256(BIP0352/Inputs) +BIP352_INPUTS_TAG_H = b'\x1e{\x96\xeb\x16\nh\x81\x9f\x97vKC\xd5\xd7~fY\xd7Xw\x9dC\xa8\xa7u_[\xe4Z~3' +# SHA256(BIP0352/Label) +BIP352_LABEL_TAG_H = b'\x03I\x19F5\xc2\xd0>b\xd4\x13\xba\x8c\xcdQ\x98\x91\x90\x17\xa1\xe9\x9c\xbei\x1fZ4\xa9\x93w\xe0\x95' diff --git a/shared/silentpayments.py b/shared/silentpayments.py new file mode 100644 index 000000000..f917ad657 --- /dev/null +++ b/shared/silentpayments.py @@ -0,0 +1,142 @@ +# (c) Copyright 2024 by Coinkite Inc. This file is covered by license found in COPYING-CC. +# +# silentpayments.py - BIP-352/BIP-375 +# +# Consolidates cryptographic primitives and PSBT handling logic for Silent Payments +# +import ngu +from precomp_tag_hash import BIP352_SHARED_SECRET_TAG_H, BIP352_INPUTS_TAG_H + +G = ngu.secp256k1.generator() +SECP256K1_ORDER = ngu.secp256k1.curve_order_int() + +# ----------------------------------------------------------------------------- +# Silent Payments Cryptographic Primitives +# ----------------------------------------------------------------------------- + + +def _combine_pubkeys(pubkeys): + """ + Combine a list of public keys into a single public key + + Args: + pubkeys: List of public keys to combine (33-byte compressed) + + Returns: + bytes: Combined public key (33-byte compressed) + """ + if len(pubkeys) == 1: + return pubkeys[0] + return ngu.secp256k1.ec_pubkey_combine(pubkeys) + + +def _compute_ecdh_share(a_sum_bytes, B_scan_bytes): + """ + Compute ECDH share (partial shared secret) + + Formula: ecdh_share = a_sum * B_scan + + Args: + a_sum_bytes: Combined private key (32-byte scalar) + B_scan_bytes: Scan public key (33-byte compressed) + + Returns: + bytes: ECDH share point (33-byte compressed) + """ + return ngu.secp256k1.ec_pubkey_tweak_mul(B_scan_bytes, a_sum_bytes) + + +def _compute_input_hash(outpoints, A_sum_bytes): + """ + Compute BIP-352 input hash + + Formula: input_hash = hash_BIP0352/Inputs(smallest_outpoint || A_sum) + + Args: + outpoints: List of (txid, vout) tuples, where txid is 32-byte and vout is 4-byte little-endian + A_sum_bytes: Sum of all eligible input public keys (33-byte compressed) + + Returns: + bytes: Input hash (32-byte scalar) + """ + smallest = min(outpoints, key=lambda x: x[0] + x[1]) + msg = smallest[0] + smallest[1] + A_sum_bytes + input_hash_bytes = ngu.hash.sha256t(BIP352_INPUTS_TAG_H, msg, True) + input_hash_int = int.from_bytes(input_hash_bytes, "big") + if not (0 < input_hash_int < SECP256K1_ORDER): + raise ValueError("Invalid input hash: not in valid scalar range") + return input_hash_int.to_bytes(32, "big") + + +def _compute_shared_secret_tweak(shared_secret_bytes, k): + """ + Compute BIP-352 shared secret tweak + + Formula: t_k = hash_BIP0352/SharedSecret(serP(shared_secret) || ser_32(k)) + + Args: + shared_secret_bytes: Shared secret point (33-byte compressed) + k: Output index per scan key group (int) + + Returns: + bytes: Shared secret tweak (32-byte scalar) + """ + msg = shared_secret_bytes + k.to_bytes(4, "big") + tweak_bytes = ngu.hash.sha256t(BIP352_SHARED_SECRET_TAG_H, msg, True) + tweak_int = int.from_bytes(tweak_bytes, "big") + if not (0 < tweak_int < SECP256K1_ORDER): + raise ValueError("Invalid shared secret tweak: not in valid scalar range") + return tweak_int.to_bytes(32, "big") + + +def _compute_silent_payment_output_script(outpoints, A_sum_bytes, ecdh_share_bytes, B_spend_bytes, k): + """ + Compute the P2TR scriptPubKey for silent payment + + Formula: P_k = B_spend + t_k * G + + Args: + outpoints: List of (txid, vout) tuples from eligible inputs + A_sum_bytes: Sum of eligible input public keys (33-byte compressed) + ecdh_share_bytes: ECDH share point (33-byte compressed) + B_spend_bytes: Recipient spend public key (33-byte compressed) + k: Output index per scan key group (int) + + Returns: + bytes: P2TR scriptPubKey (OP_1 <32-byte x-only pubkey>) + """ + input_hash_bytes = _compute_input_hash(outpoints, A_sum_bytes) + shared_secret_bytes = ngu.secp256k1.ec_pubkey_tweak_mul(ecdh_share_bytes, input_hash_bytes) + tweak_bytes = _compute_shared_secret_tweak(shared_secret_bytes, k) + # Derive output pubkey: P_k = B_spend + t_k * G + tweak_point = ngu.secp256k1.ec_pubkey_tweak_mul(G, tweak_bytes) + output_pubkey = ngu.secp256k1.ec_pubkey_combine([B_spend_bytes, tweak_point]) + x_only = output_pubkey[1:] + return b"\x51\x20" + x_only + + +# ----------------------------------------------------------------------------- +# Input Eligibility +# ----------------------------------------------------------------------------- + + +def _is_p2pkh(spk): + # OP_DUP OP_HASH160 OP_PUSHBYTES_20 <20 bytes> OP_EQUALVERIFY OP_CHECKSIG + return ( + len(spk) == 25 and spk[0] == 0x76 and spk[1] == 0xA9 and spk[2] == 0x14 and spk[-2] == 0x88 and spk[-1] == 0xAC + ) + + +def _is_p2wpkh(spk): + # OP_0 OP_PUSHBYTES_20 <20 bytes> + return len(spk) == 22 and spk[0] == 0x00 and spk[1] == 0x14 + + +def _is_p2tr(spk): + # OP_1 OP_PUSHBYTES_32 <32 bytes> + return len(spk) == 34 and spk[0] == 0x51 and spk[1] == 0x20 + + +def _is_p2sh(spk): + # OP_HASH160 OP_PUSHBYTES_20 <20 bytes> OP_EQUAL + return len(spk) == 23 and spk[0] == 0xA9 and spk[1] == 0x14 and spk[-1] == 0x87 From 8bd6b81102b038e2d57f88ffab16b50932190daa Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Tue, 25 Nov 2025 17:38:42 -0500 Subject: [PATCH 04/23] BIP-374: implement DLEQ proof generation and verification Add bip374 tagged hashes Add dleq bip374 reference generate_dleq_proof, verify_dleq_proof functions --- shared/dleq.py | 157 +++++++++++++++++++++++++++++++++++++ shared/precomp_tag_hash.py | 8 ++ 2 files changed, 165 insertions(+) create mode 100644 shared/dleq.py diff --git a/shared/dleq.py b/shared/dleq.py new file mode 100644 index 000000000..b3f8c6f8c --- /dev/null +++ b/shared/dleq.py @@ -0,0 +1,157 @@ +# (c) Copyright 2024 by Coinkite Inc. This file is covered by license found in COPYING-CC. +# +# dleq.py - DLEQ (Discrete Logarithm Equality) proofs for BIP-374 +# +# Implements non-interactive zero-knowledge proofs that prove: +# log_G(A) = log_B(C) +# where: +# - G is the secp256k1 generator +# - A = a*G (sender's public key) +# - B = (recipient's scan public key) +# - C = a*B (ECDH share point) +# +# This proves the ECDH computation is correct without revealing the private key +# +import ngu +from precomp_tag_hash import DLEQ_TAG_AUX_H, DLEQ_TAG_NONCE_H, DLEQ_TAG_CHALLENGE_H + +G = ngu.secp256k1.generator() +SECP256K1_ORDER = ngu.secp256k1.curve_order_int() + + +def xor_bytes(a, b): + """XOR two byte strings of equal length""" + return bytes(x ^ y for x, y in zip(a, b)) + + +def dleq_challenge(A, B, C, R1, R2, m=None, _G=None): + """Compute DLEQ challenge using BIP-374 tagged hash""" + + # BIP-374: e = TaggedHash("BIP0374/challenge", cbytes(A) || cbytes(B) || cbytes(C) || cbytes(G) || cbytes(R1) || cbytes(R2) || m) + challenge_input = A + B + C + _G + R1 + R2 + if m is not None: + challenge_input += m + + challenge_hash = ngu.hash.sha256t(DLEQ_TAG_CHALLENGE_H, challenge_input, True) + return int.from_bytes(challenge_hash, "big") + + +def generate_dleq_proof(a_sum, B_scan, aux_rand=None, m=None): + """ + Generate DLEQ proof (BIP-374) + + Args: + a_sum: Input private key a (32-byte scalar) + B_scan: Scan public key B (33-byte compressed) + aux_rand: Auxiliary randomness r (32-byte or None) + m: Optional message (32-byte or None) + + Returns: + bytes: DLEQ proof (64-byte: e || s) + + Raises: + ValueError: If inputs are invalid + """ + a_sum_int = int.from_bytes(a_sum, "big") + if not (0 < a_sum_int < SECP256K1_ORDER): + raise ValueError("Invalid input private key: not in valid scalar range") + + # Compute public key A = a*G + A_bytes = ngu.secp256k1.ec_pubkey_tweak_mul(G, a_sum) + + # Compute ECDH share C = a*B + C_bytes = ngu.secp256k1.ec_pubkey_tweak_mul(B_scan, a_sum) + + if aux_rand is None: + aux_rand = ngu.random.bytes(32) + else: + if len(aux_rand) != 32: + raise ValueError("aux_rand must be 32 bytes") + + # t = a XOR TaggedHash("BIP0374/aux", r) + aux_hash = ngu.hash.sha256t(DLEQ_TAG_AUX_H, aux_rand, True) + del aux_rand + t = xor_bytes(a_sum, aux_hash) + + # rand = TaggedHash("BIP0374/nonce", t || A || C || m) + nonce_input = t + A_bytes + C_bytes + if m is not None: + nonce_input += m + rand = ngu.hash.sha256t(DLEQ_TAG_NONCE_H, nonce_input, True) + + # k = int(rand) mod n + k = int.from_bytes(rand, "big") % SECP256K1_ORDER + if k == 0: + raise ValueError("Generated nonce k is zero") + + # R1 = k*G, R2 = k*B + k_bytes = k.to_bytes(32, "big") + R1_bytes = ngu.secp256k1.ec_pubkey_tweak_mul(G, k_bytes) + R2_bytes = ngu.secp256k1.ec_pubkey_tweak_mul(B_scan, k_bytes) + + # e = TaggedHash("BIP0374/challenge", A || B || C || G || R1 || R2 || m) + e = dleq_challenge(A_bytes, B_scan, C_bytes, R1_bytes, R2_bytes, m, _G=G) + + # s = (k + e*a) mod n + s = (k + e * a_sum_int) % SECP256K1_ORDER + + # proof = e || s + proof = e.to_bytes(32, "big") + s.to_bytes(32, "big") + + # Verify the proof before returning (sanity check) + if not verify_dleq_proof(A_bytes, B_scan, C_bytes, proof, m): + raise ValueError("Generated proof failed verification (internal error)") + return proof + + +def verify_dleq_proof(A_sum_bytes, B_scan_bytes, ecdh_share_bytes, proof, m=None): + """ + Verify DLEQ proof (BIP-374) + + Args: + A_sum_bytes: Input public key A (33-byte compressed) + B_scan_bytes: Scan public key B (33-byte compressed) + ecdh_share_bytes: ECDH share point C (33-byte compressed) + proof: DLEQ proof (64-byte: e || s) + m: Optional message (32-byte or None) + + Returns: + bool: True if proof is valid, False otherwise + """ + if len(proof) != 64: + return False + if m is not None and len(m) != 32: + return False + + e_bytes = proof[:32] + s_bytes = proof[32:] + + s = int.from_bytes(s_bytes, "big") + if not (0 < s < SECP256K1_ORDER): + return False + + # Reconstruct R1 = s*G - e*A + # We compute this as s*G + (-e)*A using point negation + sG = ngu.secp256k1.ec_pubkey_tweak_mul(G, s_bytes) + eA = ngu.secp256k1.ec_pubkey_tweak_mul(A_sum_bytes, e_bytes) + + # Negate eA by flipping the y-coordinate (change 02<->03 prefix) + eA_neg = bytearray(eA) + eA_neg[0] = 0x03 if eA[0] == 0x02 else 0x02 + R1_bytes = ngu.secp256k1.ec_pubkey_combine([sG, bytes(eA_neg)]) + + # Reconstruct R2 = s*B - e*C + sB = ngu.secp256k1.ec_pubkey_tweak_mul(B_scan_bytes, s_bytes) + eC = ngu.secp256k1.ec_pubkey_tweak_mul(ecdh_share_bytes, e_bytes) + + # Negate eC + eC_neg = bytearray(eC) + eC_neg[0] = 0x03 if eC[0] == 0x02 else 0x02 + R2_bytes = ngu.secp256k1.ec_pubkey_combine([sB, bytes(eC_neg)]) + + # Recompute challenge e' + e_check = dleq_challenge(A_sum_bytes, B_scan_bytes, ecdh_share_bytes, R1_bytes, R2_bytes, m, _G=G) + + # Verify e == e' + e = int.from_bytes(e_bytes, "big") + return e == e_check diff --git a/shared/precomp_tag_hash.py b/shared/precomp_tag_hash.py index a5f9f990f..3d328dcc2 100644 --- a/shared/precomp_tag_hash.py +++ b/shared/precomp_tag_hash.py @@ -18,3 +18,11 @@ BIP352_INPUTS_TAG_H = b'\x1e{\x96\xeb\x16\nh\x81\x9f\x97vKC\xd5\xd7~fY\xd7Xw\x9dC\xa8\xa7u_[\xe4Z~3' # SHA256(BIP0352/Label) BIP352_LABEL_TAG_H = b'\x03I\x19F5\xc2\xd0>b\xd4\x13\xba\x8c\xcdQ\x98\x91\x90\x17\xa1\xe9\x9c\xbei\x1fZ4\xa9\x93w\xe0\x95' + +# BIP-374 DLEQ proof tag hashes +# SHA256(BIP0374/aux) +DLEQ_TAG_AUX_H = b'\xfc\xe2u Date: Thu, 19 Mar 2026 14:08:45 -0400 Subject: [PATCH 05/23] SP: add SilentPaymentsMixin Extend psbt handling with silent payment specific functions Incorporate validation functions from bip375 test vectors - psbt_structure, input_eligibility, ecdh_coverage Add private key derivation helper functions --- shared/silentpayments.py | 514 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 514 insertions(+) diff --git a/shared/silentpayments.py b/shared/silentpayments.py index f917ad657..0983da833 100644 --- a/shared/silentpayments.py +++ b/shared/silentpayments.py @@ -5,9 +5,16 @@ # Consolidates cryptographic primitives and PSBT handling logic for Silent Payments # import ngu +from dleq import generate_dleq_proof, verify_dleq_proof +from exceptions import FatalPSBTIssue from precomp_tag_hash import BIP352_SHARED_SECRET_TAG_H, BIP352_INPUTS_TAG_H +from serializations import SIGHASH_ALL, SIGHASH_DEFAULT +from ubinascii import unhexlify as a2b_hex +from utils import keypath_to_str G = ngu.secp256k1.generator() +# BIP-341 NUMS point (Nothing Up My Sleeve) - x-only (32-byte) +NUMS_H = a2b_hex("50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0") SECP256K1_ORDER = ngu.secp256k1.curve_order_int() # ----------------------------------------------------------------------------- @@ -140,3 +147,510 @@ def _is_p2tr(spk): def _is_p2sh(spk): # OP_HASH160 OP_PUSHBYTES_20 <20 bytes> OP_EQUAL return len(spk) == 23 and spk[0] == 0xA9 and spk[1] == 0x14 and spk[-1] == 0x87 + + +# ----------------------------------------------------------------------------- +# PSBT Mixin +# ----------------------------------------------------------------------------- + + +class SilentPaymentsMixin: + """ + Mixin class for psbtObject to handle Silent Payments logic + + This class assumes it is mixed into psbtObject and has access to psbt as self + """ + + def _process_silent_payments(self, sv): + """ + Core SP workflow: validate, compute shares, compute scripts + + Returns: + bool: True if output scripts were computed + """ + if not self.has_silent_payment_outputs(): + return + self._validate_psbt_structure() + self._validate_input_eligibility() + self._validate_ecdh_coverage() + + if not self._compute_and_store_ecdh_shares(sv): + return False + + if self._is_ecdh_coverage_complete(): + self._compute_silent_payment_output_scripts() + return True + return False + + # ----------------------------------------------------------------------------- + # Input Helper Functions + # ----------------------------------------------------------------------------- + + def _get_ecdh_and_pubkey(self, scan_key): + """ + Get combined ECDH share and summed pubkey for a given scan key + + Returns: + tuple: (ecdh_share_bytes, summed_pubkey_bytes) or (None, None) + """ + # Global share: return ECDH share directly, sum all eligible input pubkeys + if self.sp_global_ecdh_shares and scan_key in self.sp_global_ecdh_shares: + ecdh_share = self.sp_global_ecdh_shares[scan_key] + pubkeys = [] + for inp in self.inputs: + if self._is_input_eligible(inp): + pk = self._pubkey_from_input(inp) + if pk: + pubkeys.append(pk) + if pubkeys: + return ecdh_share, _combine_pubkeys(pubkeys) + + # Per-input shares: combine EDCH shares and sum all eligible input pubkeys + combined_ecdh = None + pubkeys = [] + for inp in self.inputs: + if inp.sp_ecdh_shares and scan_key in inp.sp_ecdh_shares: + share = inp.sp_ecdh_shares[scan_key] + if combined_ecdh is None: + combined_ecdh = share + else: + combined_ecdh = ngu.secp256k1.ec_pubkey_combine([combined_ecdh, share]) + pk = self._pubkey_from_input(inp) + if pk: + pubkeys.append(pk) + + if combined_ecdh and pubkeys: + return combined_ecdh, _combine_pubkeys(pubkeys) + return None, None + + def _is_input_eligible(self, input): + """Check if input is eligible for silent payments per BIP-352""" + spk = input.utxo_spk + if not spk: + return False + + if not (_is_p2pkh(spk) or _is_p2wpkh(spk) or _is_p2tr(spk) or _is_p2sh(spk)): + return False + + if _is_p2tr(spk): + if input.taproot_internal_key: + tap_ik = self.get(input.taproot_internal_key) + if tap_ik == NUMS_H: + return False + + if _is_p2sh(spk): + if input.redeem_script: + rs = self.get(input.redeem_script) + if not _is_p2wpkh(rs): + return False + else: + return False + return True + + def _pubkey_from_input(self, input): + """ + Extract the contributing public key from an input + + Note: + P2TR: use PSBT_IN_WITNESS_UTXO to fetch x-only compressed pubkey + non-taproot: use BIP32 derivation pubkey from subpaths + + Returns: + bytes: Input public key (33-byte compressed) or None if not found + """ + spk = input.utxo_spk + if spk and _is_p2tr(spk): + return b"\x02" + spk[2:34] + if input.subpaths: + for pk_coords, _ in input.subpaths: + pk = self.get(pk_coords) + if len(pk) == 33: + return pk + return None + + # ----------------------------------------------------------------------------- + # Validation Functions + # ----------------------------------------------------------------------------- + + def _is_ecdh_coverage_complete(self): + """ + Check if all eligible inputs have ECDH shares for all scan keys + + Returns: + bool: True if coverage is complete + False if any eligible input is missing a share for any scan key + """ + scan_keys = self._get_silent_payment_scan_keys() + if not scan_keys: + return True + + for scan_key in scan_keys: + if self.sp_global_ecdh_shares and scan_key in self.sp_global_ecdh_shares: + continue + # Check per-input: every eligible input must have a share + for inp in self.inputs: + if self._is_input_eligible(inp): + if not inp.sp_ecdh_shares or scan_key not in inp.sp_ecdh_shares: + return False + return True + + def _validate_psbt_structure(self): + """ + Validate PSBT structure requirements for silent payments + + Raises FatalPSBTIssue if any structural requirements are violated + """ + for i, outp in enumerate(self.outputs): + has_sp_info = bool(outp.sp_v0_info) + has_sp_label = bool(outp.sp_v0_label) + has_script = bool(outp.script and self.get(outp.script)) + + # Output must have script or SP info + if not has_script and not has_sp_info: + raise FatalPSBTIssue("Output #%d must have either PSBT_OUT_SCRIPT or PSBT_OUT_SP_V0_INFO" % i) + + if has_sp_label and not has_sp_info: + raise FatalPSBTIssue("Output #%d has SP label but missing SP_V0_INFO" % i) + + if has_sp_info: + if len(outp.sp_v0_info) != 66: + raise FatalPSBTIssue( + "Output #%d SP_V0_INFO wrong size (%d bytes, expected 66)" % (i, len(outp.sp_v0_info)) + ) + + # Validate ECDH share sizes (33 bytes) and DLEQ proof sizes (64 bytes) + if self.sp_global_ecdh_shares: + for _, share in self.sp_global_ecdh_shares.items(): + if len(share) != 33: + raise FatalPSBTIssue("Global ECDH share wrong size (%d bytes, expected 33)" % len(share)) + + if self.sp_global_dleq_proofs: + for _, proof in self.sp_global_dleq_proofs.items(): + if len(proof) != 64: + raise FatalPSBTIssue("Global DLEQ proof wrong size (%d bytes, expected 64)" % len(proof)) + + for i, inp in enumerate(self.inputs): + if inp.sp_ecdh_shares: + for _, share in inp.sp_ecdh_shares.items(): + if len(share) != 33: + raise FatalPSBTIssue( + "Input #%d ECDH share wrong size (%d bytes, expected 33)" % (i, len(share)) + ) + if inp.sp_dleq_proofs: + for _, proof in inp.sp_dleq_proofs.items(): + if len(proof) != 64: + raise FatalPSBTIssue( + "Input #%d DLEQ proof wrong size (%d bytes, expected 64)" % (i, len(proof)) + ) + + # TX_MODIFIABLE must be cleared when output scripts are finalized + for outp in self.outputs: + if outp.sp_v0_info and outp.script: + if self.txn_modifiable is not None and self.txn_modifiable != 0: + raise FatalPSBTIssue("TX_MODIFIABLE not cleared but SP output script is set") + + def _validate_input_eligibility(self): + """ + Validate input constraints for silent payments (BIP-375) + + Raises FatalPSBTIssue if any input constraints are violated + """ + for i, inp in enumerate(self.inputs): + if not inp.utxo_spk: + continue + + spk = inp.utxo_spk + # No segwit v>1 inputs when SP outputs present + if len(spk) >= 2 and 0x52 <= spk[0] <= 0x60: + witness_version = spk[0] - 0x50 + raise FatalPSBTIssue( + "BIP-375 violation: Input #%d spends Segwit v%d output. " + "Silent payment outputs cannot be mixed with Segwit v>1 inputs." % (i, witness_version) + ) + + # SIGHASH_ALL required when SP outputs present + if inp.sighash is not None and inp.sighash not in ( + SIGHASH_ALL, + SIGHASH_DEFAULT, + ): + raise FatalPSBTIssue( + "BIP-375 violation: Input #%d uses sighash 0x%x. " + "Silent payments require SIGHASH_ALL." % (i, inp.sighash) + ) + + def _validate_ecdh_coverage(self): + """ + Validate ECDH share coverage and DLEQ proof correctness (BIP-375) + + Raises FatalPSBTIssue if any ECDH share / DLEQ requirements are violated + """ + scan_keys = self._get_silent_payment_scan_keys() + if not scan_keys: + return + + for scan_key in scan_keys: + has_global = self.sp_global_ecdh_shares and scan_key in self.sp_global_ecdh_shares + has_input = any(inp.sp_ecdh_shares and scan_key in inp.sp_ecdh_shares for inp in self.inputs) + + # Check if any output with this scan pk has a computed script + scan_key_has_script = any( + outp.sp_v0_info and outp.sp_v0_info[:33] == scan_key and outp.script for outp in self.outputs + ) + + if scan_key_has_script and not has_global and not has_input: + raise FatalPSBTIssue("SP output script set but no ECDH share for scan key") + + # Verify global DLEQ proof + if has_global: + if not self.sp_global_dleq_proofs or scan_key not in self.sp_global_dleq_proofs: + raise FatalPSBTIssue("Global ECDH share missing DLEQ proof") + + ecdh_share = self.sp_global_ecdh_shares[scan_key] + proof = self.sp_global_dleq_proofs[scan_key] + + # Sum all eligible input pubkeys + pubkeys = [] + for inp in self.inputs: + if not self._is_input_eligible(inp): + continue + pk = self._pubkey_from_input(inp) + if pk: + pubkeys.append(pk) + if not pubkeys: + raise FatalPSBTIssue("No public keys found for DLEQ verification") + combined_pk = _combine_pubkeys(pubkeys) + + if not verify_dleq_proof(combined_pk, scan_key, ecdh_share, proof): + raise FatalPSBTIssue("Global DLEQ proof verification failed") + + # Verify per-input coverage and DLEQ proofs + if scan_key_has_script and not has_global: + for i, inp in enumerate(self.inputs): + eligible = self._is_input_eligible(inp) + has_share = inp.sp_ecdh_shares and scan_key in inp.sp_ecdh_shares + + if not eligible and has_share: + raise FatalPSBTIssue("Input #%d has ECDH share but is ineligible" % i) + if eligible and not has_share: + raise FatalPSBTIssue("Eligible input #%d missing ECDH share" % i) + + if has_share: + if not inp.sp_dleq_proofs or scan_key not in inp.sp_dleq_proofs: + raise FatalPSBTIssue("Input #%d ECDH share missing DLEQ proof" % i) + + pk = self._pubkey_from_input(inp) + if not pk: + raise FatalPSBTIssue("Input #%d missing public key for DLEQ verification" % i) + + ecdh_share = inp.sp_ecdh_shares[scan_key] + proof = inp.sp_dleq_proofs[scan_key] + if not verify_dleq_proof(pk, scan_key, ecdh_share, proof): + raise FatalPSBTIssue("Input #%d DLEQ proof verification failed" % i) + + # ----------------------------------------------------------------------------- + # Modify PSBT Field Functions + # ----------------------------------------------------------------------------- + + def _compute_and_store_ecdh_shares(self, sv): + """ + Compute ECDH shares and DLEQ proofs for our inputs, store in PSBT fields + + Notes: + Sets self.sp_all_inputs_ours for callers + + Returns: + bool: True if shares were computed + False if we have no signable inputs + """ + # Collect per-input private keys for eligible inputs we own + # Track foreign ownership: if _derive_input_privkey returns None for an input + # that has derivation paths, that input belongs to another signer + has_foreign = False + input_material = [] # list of (inp, privkey_int) + for inp in self.inputs: + if not inp.sp_idxs or not self._is_input_eligible(inp): + continue + + input_sk = self._derive_input_privkey(inp, sv) + if input_sk: + input_material.append((inp, input_sk)) + elif inp.taproot_subpaths or inp.subpaths: + has_foreign = True + + # Detect foreign eligible inputs (different XFP, no sp_idxs) + if not has_foreign: + for inp in self.inputs: + if inp.sp_idxs: + continue + if self._is_input_eligible(inp) and self._pubkey_from_input(inp): + has_foreign = True + break + + if not input_material: + return False + + self.sp_all_inputs_ours = not has_foreign + all_inputs_ours = self.sp_all_inputs_ours + + scan_keys = self._get_silent_payment_scan_keys() + if not scan_keys: + return False + + for scan_key in scan_keys: + if all_inputs_ours: + # Single-signer: combine all input private keys, one global ECDH share and DLEQ proofs + combined_sk = 0 + for _, input_sk in input_material: + input_sk_int = int.from_bytes(input_sk, "big") + combined_sk = (combined_sk + input_sk_int) % SECP256K1_ORDER + + ecdh_share = _compute_ecdh_share(combined_sk.to_bytes(32, "big"), scan_key) + dleq_proof = generate_dleq_proof(combined_sk.to_bytes(32, "big"), scan_key) + + self.sp_global_ecdh_shares = self.sp_global_ecdh_shares or {} + self.sp_global_dleq_proofs = self.sp_global_dleq_proofs or {} + self.sp_global_ecdh_shares[scan_key] = ecdh_share + self.sp_global_dleq_proofs[scan_key] = dleq_proof + else: + # Multi-signer: per-input ECDH shares and DLEQ proofs for owned inputs + for inp, input_sk in input_material: + ecdh_share = _compute_ecdh_share(input_sk, scan_key) + dleq_proof = generate_dleq_proof(input_sk, scan_key) + + inp.sp_ecdh_shares = inp.sp_ecdh_shares or {} + inp.sp_dleq_proofs = inp.sp_dleq_proofs or {} + inp.sp_ecdh_shares[scan_key] = ecdh_share + inp.sp_dleq_proofs[scan_key] = dleq_proof + return True + + def _compute_silent_payment_output_scripts(self): + """ + Compute and set the scriptPubKey for each silent payment output + + Note: All validations "must" be done before calling this function + + No return value; modifies self.outputs in-place + """ + outpoints = self._get_outpoints() + + # Track k per scan key + scan_key_k = {} + + for out_idx, outp in enumerate(self.outputs): + if not outp.sp_v0_info: + continue + + scan_key = outp.sp_v0_info[:33] + B_spend = outp.sp_v0_info[33:66] + k = scan_key_k.get(scan_key, 0) + + ecdh_share, summed_pubkey = self._get_ecdh_and_pubkey(scan_key) + if not ecdh_share or not summed_pubkey: + raise FatalPSBTIssue("Missing ECDH share for output #%d" % out_idx) + + outp.script = _compute_silent_payment_output_script(outpoints, summed_pubkey, ecdh_share, B_spend, k) + scan_key_k[scan_key] = k + 1 + + # ----------------------------------------------------------------------------- + # Utility Functions + # ----------------------------------------------------------------------------- + + def has_silent_payment_outputs(self): + """ + Check if PSBT contains any silent payment outputs + + Returns: + bool: True if any output has PSBT_OUT_SP_V0_INFO field + """ + for outp in self.outputs: + if outp.sp_v0_info: + return True + return False + + def _get_outpoints(self): + """ + Get a list of outpoints (txid, vout) for all inputs + + Returns: + list: Outpoints as (txid, vout) tuples + """ + outpoints = [] + for inp in self.inputs: + if inp.previous_txid and inp.prevout_idx is not None: + outpoints.append((self.get(inp.previous_txid), self.get(inp.prevout_idx))) + else: + raise FatalPSBTIssue("Missing outpoint for silent payment input") + if not outpoints: + raise FatalPSBTIssue("Did not find any outpoints") + return outpoints + + def _get_silent_payment_scan_keys(self): + """ + Extract unique scan keys from silent payment outputs + + Returns: + list: Unique scan public keys (33-byte compressed) + """ + scan_keys = set() + for outp in self.outputs: + if outp.sp_v0_info: + scan_key = outp.sp_v0_info[:33] + scan_keys.add(scan_key) + return list(scan_keys) + + # ----------------------------------------------------------------------------- + # Key Derivation Functions + # ----------------------------------------------------------------------------- + + def _derive_input_privkey(self, input, sv): + """ + Derive the contributing private key for an eligible input + + Note: + For taproot inputs, uses the internal key's derivation path (ik_idx), XFP-checked + For non-taproot inputs, uses the first matching BIP32 derivation path + + Returns: + bytes: Derived private key as (32-byte scalar) or None if not eligible + """ + # TODO: BIP-376 sp_spend_derivation should go here + spk = input.utxo_spk + if spk and _is_p2tr(spk): + if input.ik_idx is not None and input.taproot_subpaths: + _, path_coords = input.taproot_subpaths[input.ik_idx] + xfp_path = self.parse_xfp_path(path_coords[2]) + if xfp_path[0] == self.my_xfp: + return self._path_to_privkey(xfp_path, sv) + else: + for xfp_path in self._iter_input_xfp_paths(input): + if xfp_path[0] == self.my_xfp: + return self._path_to_privkey(xfp_path, sv) + return None + + def _iter_input_xfp_paths(self, input): + """ + Iterate over all BIP32 derivation paths for an input, yielding parsed xfp_path tuples + + Note: + For taproot inputs, yields paths from taproot_subpaths (path_coords[2]) + For non-taproot inputs, yields paths from subpaths (path_coords) + + Returns: + generator: Parsed xfp_path tuples for all derivation paths in the input + """ + if input.taproot_subpaths: + for _, path_coords in input.taproot_subpaths: + yield self.parse_xfp_path(path_coords[2]) + elif input.subpaths: + for _, path_coords in input.subpaths: + yield self.parse_xfp_path(path_coords) + + def _path_to_privkey(self, xfp_path, sv): + """ + Derive private key from a parsed xfp_path tuple + + Returns: + bytes: Derived private key (32-byte scalar) + """ + node = sv.derive_path(keypath_to_str(xfp_path, skip=1), register=False) + return node.privkey() From 3608ca4713061c9316ee50966d399b71753d65f4 Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Sun, 8 Mar 2026 12:34:33 -0400 Subject: [PATCH 06/23] fix: (psbt v2) structural changes Only store key_data for short_values - remove key_type Only serialize PSBT_OUT_SCRIPT if self.script has a value Adjust v2 script assert to not required if sp_v0_info is present --- shared/psbt.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/shared/psbt.py b/shared/psbt.py index 97c5b46fd..1273a8c83 100644 --- a/shared/psbt.py +++ b/shared/psbt.py @@ -248,7 +248,8 @@ def parse(self, fd): # storing offset and length only! Mostly. if kt in self.short_values: actual = fd.read(vs) - self.store(kt, bytes(key), actual) + # only store key data for short_values + self.store(kt, bytes(key[1:]), actual) else: # skip actual data for now # TODO: could this be stored more compactly? @@ -506,7 +507,8 @@ def serialize(self, out_fd, is_v2): wr(PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS, v, k) if is_v2: - wr(PSBT_OUT_SCRIPT, self.script) + if self.script is not None: + wr(PSBT_OUT_SCRIPT, self.script) wr(PSBT_OUT_AMOUNT, self.amount) if self.proprietary: @@ -1835,7 +1837,9 @@ async def validate(self): if self.is_v2: # v2 requires inclusion assert o.amount - assert o.script + # Silent Payments: if not spending to silent payment then script must be provided + if not o.sp_v0_info: + assert o.script, "v2 script required when not silent payment output" if o.amount == 0 and o.script == b'\x6a': null_data_op_return = True else: From 3c9d37841a52fad7ea47d0bb2d2fd3d60aee0194 Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Fri, 20 Mar 2026 14:29:11 -0400 Subject: [PATCH 07/23] fix: (usb_test) Unify local and globals dict to single namespace Provides silent payments testing framework with access to MockPSBT --- shared/usb_test_commands.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shared/usb_test_commands.py b/shared/usb_test_commands.py index 4747bf8e8..b76d6a96c 100644 --- a/shared/usb_test_commands.py +++ b/shared/usb_test_commands.py @@ -33,7 +33,9 @@ def do_usb_command(cmd, args): if cmd == 'EXEC': RV = uio.BytesIO() - exec(str(args, 'utf8'), None, dict(RV=RV)) + ns = globals().copy() + ns['RV'] = RV + exec(str(args, 'utf8'), ns) return b'biny' + RV.getvalue() except BaseException as exc: From ab83284030cac0b71a206a8d50b571745a5813f9 Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Tue, 25 Nov 2025 17:49:15 -0500 Subject: [PATCH 08/23] BIP-375: add test vectors Use devtest/verify_sp_outputs.py for simulator testing Add testing/test_bip375_vectors.py and testing/bip375_test_vectors.json - Correct input eligibility ecdh share assumptions Add devtest/unit_silentpayments.py to perform unit testing via simulator Mirror psbt fields in testing/psbt.py --- shared/silentpayments.py | 118 +- testing/bip375_test_vectors.json | 1759 ++++++++++++++++++++++++ testing/devtest/unit_silentpayments.py | 608 ++++++++ testing/devtest/verify_sp_outputs.py | 208 +++ testing/psbt.py | 50 + testing/sp_helpers.py | 289 ++++ testing/test_bip375_vectors.py | 236 ++++ 7 files changed, 3250 insertions(+), 18 deletions(-) create mode 100644 testing/bip375_test_vectors.json create mode 100644 testing/devtest/unit_silentpayments.py create mode 100644 testing/devtest/verify_sp_outputs.py create mode 100644 testing/sp_helpers.py create mode 100644 testing/test_bip375_vectors.py diff --git a/shared/silentpayments.py b/shared/silentpayments.py index 0983da833..1b3ece63c 100644 --- a/shared/silentpayments.py +++ b/shared/silentpayments.py @@ -4,6 +4,7 @@ # # Consolidates cryptographic primitives and PSBT handling logic for Silent Payments # +import chains import ngu from dleq import generate_dleq_proof, verify_dleq_proof from exceptions import FatalPSBTIssue @@ -17,6 +18,7 @@ NUMS_H = a2b_hex("50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0") SECP256K1_ORDER = ngu.secp256k1.curve_order_int() + # ----------------------------------------------------------------------------- # Silent Payments Cryptographic Primitives # ----------------------------------------------------------------------------- @@ -115,13 +117,86 @@ def _compute_silent_payment_output_script(outpoints, A_sum_bytes, ecdh_share_byt input_hash_bytes = _compute_input_hash(outpoints, A_sum_bytes) shared_secret_bytes = ngu.secp256k1.ec_pubkey_tweak_mul(ecdh_share_bytes, input_hash_bytes) tweak_bytes = _compute_shared_secret_tweak(shared_secret_bytes, k) - # Derive output pubkey: P_k = B_spend + t_k * G - tweak_point = ngu.secp256k1.ec_pubkey_tweak_mul(G, tweak_bytes) - output_pubkey = ngu.secp256k1.ec_pubkey_combine([B_spend_bytes, tweak_point]) - x_only = output_pubkey[1:] + x_only = _compute_silent_payment_spending_xonly(B_spend_bytes, tweak_bytes) return b"\x51\x20" + x_only +def _compute_silent_payment_spending_privkey(b_spend_bytes, sp_tweak_bytes): + """ + Compute private key for spending a silent payment output + + Formula: d_k = (b_spend + sp_tweak) mod n + + Note: sp_tweak = combined tweak (shared secret + label if applicable) + + Args: + b_spend_bytes: Base spend private key (32-byte scalar) + sp_tweak_bytes: Tweak from PSBT_IN_SP_TWEAK (32-byte scalar) + + Returns: + bytes: Tweaked spending private key normalized to even Y (32-byte scalar) + + Raises: + ValueError: If sp_tweak or spending_privkey is invalid + """ + sp_tweak_int = int.from_bytes(sp_tweak_bytes, "big") + b_spend_int = int.from_bytes(b_spend_bytes, "big") + if not (0 < b_spend_int < SECP256K1_ORDER): + raise ValueError("Invalid spend private key: not in valid scalar range") + if not (0 < sp_tweak_int < SECP256K1_ORDER): + raise ValueError("Invalid tweak: not in valid scalar range") + spending_sk = (b_spend_int + sp_tweak_int) % SECP256K1_ORDER + if spending_sk == 0: + raise ValueError("Invalid computed spend key: result is zero") + return _negate_if_odd_y(spending_sk.to_bytes(32, "big")) + + +def _compute_silent_payment_spending_xonly(B_spend_bytes, sp_tweak_bytes): + """ + Compute x-only pubkey for spending a silent payment output + + Formula: P_k = B_spend + sp_tweak * G + + Args: + B_spend_bytes: Recipient spend public key (33-byte compressed) + sp_tweak_bytes: SP tweak (32-byte scalar) + + Returns: + bytes: x-only public key (32-byte) + """ + tweak_point = ngu.secp256k1.ec_pubkey_tweak_mul(G, sp_tweak_bytes) + output_pubkey = ngu.secp256k1.ec_pubkey_combine([B_spend_bytes, tweak_point]) + return output_pubkey[1:] + + +def _negate_if_odd_y(privkey): + """ + Normalize a private key so its corresponding public key has even Y (0x02 prefix) + + Returns: + bytes: normalized private key (32-byte scalar) + """ + pubkey = ngu.secp256k1.ec_pubkey_tweak_mul(G, privkey) + if pubkey[0] == 0x03: + privkey = (SECP256K1_ORDER - int.from_bytes(privkey, "big")).to_bytes(32, "big") + return privkey + + +def _sum_privkeys(privkeys): + """ + Sum list of private key (32-byte scalars) + + Returns: + bytes: summed private key (32-byte scalar) + """ + total = 0 + for sk in privkeys: + total = (total + int.from_bytes(sk, "big")) % SECP256K1_ORDER + if total == 0: + raise ValueError("Invalid private key sum: result is zero") + return total.to_bytes(32, "big") + + # ----------------------------------------------------------------------------- # Input Eligibility # ----------------------------------------------------------------------------- @@ -204,8 +279,9 @@ def _get_ecdh_and_pubkey(self, scan_key): pubkeys.append(pk) if pubkeys: return ecdh_share, _combine_pubkeys(pubkeys) + return None, None - # Per-input shares: combine EDCH shares and sum all eligible input pubkeys + # Per-input shares: combine shares and pubkeys from eligible inputs combined_ecdh = None pubkeys = [] for inp in self.inputs: @@ -215,9 +291,10 @@ def _get_ecdh_and_pubkey(self, scan_key): combined_ecdh = share else: combined_ecdh = ngu.secp256k1.ec_pubkey_combine([combined_ecdh, share]) - pk = self._pubkey_from_input(inp) - if pk: - pubkeys.append(pk) + if self._is_input_eligible(inp): + pk = self._pubkey_from_input(inp) + if pk: + pubkeys.append(pk) if combined_ecdh and pubkeys: return combined_ecdh, _combine_pubkeys(pubkeys) @@ -258,6 +335,9 @@ def _pubkey_from_input(self, input): Returns: bytes: Input public key (33-byte compressed) or None if not found """ + if not self._is_input_eligible(input): + return None + spk = input.utxo_spk if spk and _is_p2tr(spk): return b"\x02" + spk[2:34] @@ -411,8 +491,6 @@ def _validate_ecdh_coverage(self): # Sum all eligible input pubkeys pubkeys = [] for inp in self.inputs: - if not self._is_input_eligible(inp): - continue pk = self._pubkey_from_input(inp) if pk: pubkeys.append(pk) @@ -477,6 +555,7 @@ def _compute_and_store_ecdh_shares(self, sv): elif inp.taproot_subpaths or inp.subpaths: has_foreign = True + # FIXME: is this the right approach? cosign_xfp? # Detect foreign eligible inputs (different XFP, no sp_idxs) if not has_foreign: for inp in self.inputs: @@ -499,13 +578,9 @@ def _compute_and_store_ecdh_shares(self, sv): for scan_key in scan_keys: if all_inputs_ours: # Single-signer: combine all input private keys, one global ECDH share and DLEQ proofs - combined_sk = 0 - for _, input_sk in input_material: - input_sk_int = int.from_bytes(input_sk, "big") - combined_sk = (combined_sk + input_sk_int) % SECP256K1_ORDER - - ecdh_share = _compute_ecdh_share(combined_sk.to_bytes(32, "big"), scan_key) - dleq_proof = generate_dleq_proof(combined_sk.to_bytes(32, "big"), scan_key) + combined_sk = _sum_privkeys(sk for _, sk in input_material) + ecdh_share = _compute_ecdh_share(combined_sk, scan_key) + dleq_proof = generate_dleq_proof(combined_sk, scan_key) self.sp_global_ecdh_shares = self.sp_global_ecdh_shares or {} self.sp_global_dleq_proofs = self.sp_global_dleq_proofs or {} @@ -548,7 +623,14 @@ def _compute_silent_payment_output_scripts(self): if not ecdh_share or not summed_pubkey: raise FatalPSBTIssue("Missing ECDH share for output #%d" % out_idx) - outp.script = _compute_silent_payment_output_script(outpoints, summed_pubkey, ecdh_share, B_spend, k) + computed = _compute_silent_payment_output_script(outpoints, summed_pubkey, ecdh_share, B_spend, k) + + if outp.script: + existing = self.get(outp.script) if isinstance(outp.script, tuple) else outp.script + if existing != computed: + raise FatalPSBTIssue("SP output #%d: output script mismatch" % out_idx) + + outp.script = computed scan_key_k[scan_key] = k + 1 # ----------------------------------------------------------------------------- diff --git a/testing/bip375_test_vectors.json b/testing/bip375_test_vectors.json new file mode 100644 index 000000000..3f16f51b2 --- /dev/null +++ b/testing/bip375_test_vectors.json @@ -0,0 +1,1759 @@ +{ + "description": "BIP-375 Test Vectors", + "version": "1.1", + "notes": [ + "Generated by https://github.com/macgyver13/bip375-test-generator/", + "Each vector includes: base64-encoded psbt and description", + "Supplementary material (inputs, outputs, sp_proofs) is included for diagnostics and should not be used for validation", + "'checks' overrides the validation sequence, e.g. skip structure checks to test input eligibility" + ], + "invalid": [ + { + "description": "psbt structure: missing PSBT_OUT_SP_V0_INFO field when PSBT_OUT_SP_V0_LABEL set", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwgYcwEAAAAAAAEEIlEgImjPUplZ8wrI96VHHKTGdTalHw5bForwPu1HFe3McuABCgQBAAAAAA==", + "supplementary": {} + }, + { + "description": "psbt structure: incorrect byte length for PSBT_OUT_SP_V0_INFO field", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwgYcwEAAAAAAAEEIlEgMm31D+Cge3rLcgcL6ztjLrmtFbppXMHlqmqgB7YUb9gBCUECekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84AA=", + "supplementary": {} + }, + { + "description": "psbt structure: incorrect byte length for PSBT_IN_SP_ECDH_SHARE field", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GggA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QoiHgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaECKE7OYVUX3K9bolxSuuQmz41SoQqm7i1bNDt7SHfihmVmLMSKKSeC9fpXOEFP3xbKKy1Q6aHB2AOPOiYIu4yAhAAEDCBhzAQAAAAAAAQQiUSAybfUP4KB7estyBwvrO2Muua0VumlcweWqaqAHthRv2AEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", + "supplementary": {} + }, + { + "description": "psbt structure: incorrect byte length for PSBT_IN_SP_DLEQ field", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4Gg/ihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgAAEDCBhzAQAAAAAAAQQiUSAybfUP4KB7estyBwvrO2Muua0VumlcweWqaqAHthRv2AEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", + "supplementary": {} + }, + { + "description": "psbt structure: PSBT_GLOBAL_TX_MODIFIABLE field is non-zero when PSBT_OUT_SCRIPT set for sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAQABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwgYcwEAAAAAAAEEIlEgMm31D+Cge3rLcgcL6ztjLrmtFbppXMHlqmqgB7YUb9gBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": {} + }, + { + "description": "psbt structure: missing PSBT_OUT_SCRIPT field when sending to non-sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwgYcwEAAAAAAAA=", + "supplementary": {} + }, + { + "description": "ecdh coverage: only one ineligible P2SH multisig input when PSBT_OUT_SCRIPT set for sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiDNOBpFUI/j2cq2O34PcEoPVp2/1VqYEC2eozruYht19AEPBAAAAAABAFMCAAAAAXyoMfPUiQn+aMsOqqoR+Hc97n6McwhTN1kOJLsGOQ20AAAAAAD/////AaCGAQAAAAAAF6kU2OZohxTu7Qqi7dSJqhyjwXFwHk2HAAAAAAEER1IhAr5Ar99ismPLfe4rmF0cOP5BsZlNKwUHU0PRcsIY6rHEIQJ6R5JT0AogWhCpQ483IBm582TgqDaAC/g529KOfOa5VVKuARAE/v///wABAwiQXwEAAAAAAAEEIlEgzeOdiwW0lvjxjyCyfQr5oyMzCeBHIhnggT/ztdN4fikBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "", + "public_key": "", + "prevout_txid": "cd381a45508fe3d9cab63b7e0f704a0f569dbfd55a98102d9ea33aee621b75f4", + "prevout_index": 0, + "prevout_scriptpubkey": "a914d8e6688714eeed0aa2edd489aa1ca3c171701e4d87", + "amount": 100000, + "non_witness_utxo": "02000000017ca831f3d48909fe68cb0eaaaa11f8773dee7e8c73085337590e24bb06390db40000000000ffffffff01a08601000000000017a914d8e6688714eeed0aa2edd489aa1ca3c171701e4d8700000000", + "sequence": 4294967294, + "signed": false + } + ] + } + }, + { + "description": "ecdh coverage: missing PSBT_IN_SP_ECDH_SHARE field for input 0 when PSBT_OUT_SCRIPT set for sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////AAEOIL6dZcg5FfsJJIq738qMo+7mzjUMVU9l6NUm5d9SerUiAQ8EAAAAAAEBHxAnAAAAAAAAFgAURjPVnK0TQ0eZcuDJlpIdCl2ttl0BAwQBAAAAIgYCQ6DUDp3giCeUFhdSuuQrqQlSeEIJ8GNDDwryVjVKhbsIAAAAgAEAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghAuV6etywGNj7MVToDBS5elCZS0I1aEHqn8ExcGFEAOY3Ih4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhA9/o4HeILO0WcbkJsYhPUF1MI8kgqROMyhCMmJNTa/Vcymb868WqzAsViY2OjfUSHB/fnoevqBke8zUhiA7srpgABAwgYcwEAAAAAAAEEIlEgzeOdiwW0lvjxjyCyfQr5oyMzCeBHIhnggT/ztdN4fikBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": false + }, + { + "input_index": 1, + "private_key": "586b0810bbc3c7266971aeb7f8778b41707e092a1aeaf0d953b211f956e35541", + "public_key": "0243a0d40e9de0882794161752bae42ba90952784209f063430f0af256354a85bb", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "00144633d59cad1343479972e0c996921d0a5dadb65d", + "amount": 10000, + "witness_utxo": "10270000000000001600144633d59cad1343479972e0c996921d0a5dadb65d", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "02e57a7adcb018d8fb3154e80c14b97a50994b42356841ea9fc13170614400e637", + "dleq_proof": "f7fa381de20b3b459c6e426c6213d4175308f2482a44e33284232624d4dafd573299bf3af16ab302c5626363a37d448707f7e7a1ebea0647bccd486203bb2ba6", + "input_index": 1 + } + ] + } + }, + { + "description": "ecdh coverage: missing PSBT_IN_SP_DLEQ field for input when PSBT_IN_SP_ECDH_SHARE set", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbAAEDCBhzAQAAAAAAAQQiUSAybfUP4KB7estyBwvrO2Muua0VumlcweWqaqAHthRv2AEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": null, + "input_index": 0 + } + ] + } + }, + { + "description": "ecdh coverage: missing PSBT_GLOBAL_SP_DLEQ field when PSBT_GLOBAL_SP_ECDH_SHARE set", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBACIHAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWwABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEiBgLIF7t1Ia/DXqlvO/snDm61Dd/6VWBie5Yf7ADymWUIvwgAAACAAAAAAAEQBP7///8AAQMIGHMBAAAAAAABBCJRIDJt9Q/goHt6y3IHC+s7Yy65rRW6aVzB5apqoAe2FG/YAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA2HhseneXkLLIAf3ylS54NV+0Tk4+tVtPxnldROo/OA5AA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b" + } + ] + } + }, + { + "description": "ecdh coverage: invalid proof in PSBT_IN_SP_DLEQ field", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAbvHreDwFl9ZoVl4787j95b7SgVKV9D4XAVEk1EXcy9rdKkne51lhePEhbl1NuRdP/dpjNON8kDEQjAjOE3TeDwABAwgYcwEAAAAAAAEEIlEgMm31D+Cge3rLcgcL6ztjLrmtFbppXMHlqmqgB7YUb9gBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "6ef1eb783c0597d668565e3bf3b8fde5bed2815295f43e17015124d445dccbdadd2a49dee7596178f1216e5d4db9174ffdda6334e37c9031108c08ce1374de0f", + "input_index": 0 + } + ] + } + }, + { + "description": "ecdh coverage: invalid proof in PSBT_GLOBAL_SP_DLEQ field", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBACIHAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIIAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQOfI/UQrlNiKKmoskrQ0ZI+gB8n+CARyozeA0LUjKETQm9iLP+6G0S1DhsRlf7t4Xi0vFL/PkYV0P+EqyCUPldkAAQ4gGKcXZjsLqxSxKhp3EyP/HkB53VMuXdE+KOoQgccAmEoBDwQAAAAAAQEfoIYBAAAAAAAWABQimnLTSmRb00lru/ULu4HJBj9PlCICAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/RzBEAiAJB3pqpkrBSuehqk/mjAKs4gbJ6scjSQYa52A5ALkibgIgbxQypXj44zCvC5ACVnRryfTlKqLozHkCncjnajwpSwYBIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////AAEDCBhzAQAAAAAAAQQiUSAybfUP4KB7estyBwvrO2Muua0VumlcweWqaqAHthRv2AEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "2430479d2da1310a024d2aab8e4c7b49d76cd51f45ee8127c159b0393a675b6f75c581ebe3d0817ddaf3bb598cfd7a022f1a87ed479664f6b947a47db5941cb2" + } + ] + } + }, + { + "description": "ecdh coverage: missing PSBT_IN_BIP32_DERIVATION field for input when PSBT_IN_SP_DLEQ set", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIAkHemqmSsFK56GqT+aMAqziBsnqxyNJBhrnYDkAuSJuAiBvFDKlePjjMK8LkAJWdGvJ9OUqoujMeQKdyOdqPClLBgEBAwQBAAAAARAE/v///yIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQMIGHMBAAAAAAABBCJRIDJt9Q/goHt6y3IHC+s7Yy65rRW6aVzB5apqoAe2FG/YAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA2HhseneXkLLIAf3ylS54NV+0Tk4+tVtPxnldROo/OA5AA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ] + } + }, + { + "description": "ecdh coverage: output 1 missing ECDH share for scan key with one input / three sp outputs (different scan keys)", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEDAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR9ADQMAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdArlFYVfpRu8H90tU+gm+Sn6Tjcr7+ye0s3LFc8BZ8SqwIQLChkR0sISqyB9uMKxe30ZnOTzmBNgI0/SZoJhOi1JJdCIdA2UdLAc/ywKk2C3aU/HVAdd6A1BmZ5hUZHOyEmts0afeIQN2iSMnxOeAru7UzhvJK7ASK3t5ENlvCtUUi0lvnD6S+yIeArlFYVfpRu8H90tU+gm+Sn6Tjcr7+ye0s3LFc8BZ8SqwQPCxHVHD/ROzjgpsjvwu4Mpxdzr2dLMVHmiSFJJcS1fIFojJ4Hi3UIhabKsoa1d+k6JJfrSWNrGvG2vILnyRElEiHgNlHSwHP8sCpNgt2lPx1QHXegNQZmeYVGRzshJrbNGn3kCY8UrEWvZFmrDITUfaU2TPsUT/kL2scy+cq8bbAA3PGpXjsx97cKBCmKUbb9RRbIHXFsmVT4rxDD1ZgJruK704AAEDCJBfAQAAAAAAAQQiUSDTlxRqgoZbVOsSuepx1oJP6rD5KkgTf3p5OmhZazNnpAEJQgNlHSwHP8sCpNgt2lPx1QHXegNQZmeYVGRzshJrbNGn3gPycOClYyGDtqsBB06HfmgA2wD1x8XS+vu2j5wo1IVFJAABAwgQJwAAAAAAAAEEIlEgzeOdiwW0lvjxjyCyfQr5oyMzCeBHIhnggT/ztdN4fikBCUIDUteMQTkAMq2RgWppf8dA2OuQnPBNcIhSZLBR8jheJewC4MTBh7chX5l8hIWnW0WRtsKEd7laESO5lpItMVSGC/gAAQMIIE4AAAAAAAABBCJRINLSQezkGFFKoY45kuyVPE1K1exwSmk+IQa6IsOwWnqDAQlCArlFYVfpRu8H90tU+gm+Sn6Tjcr7+ye0s3LFc8BZ8SqwAhJoHj8J6uOKjy8/7NfdVvxYbPoCjoqzP3KpD4QKbSJcAA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 200000, + "witness_utxo": "400d030000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "03651d2c073fcb02a4d82dda53f1d501d77a0350666798546473b2126b6cd1a7de", + "ecdh_share": "0376892327c4e780aeeed4ce1bc92bb0122b7b7910d96f0ad5148b496f9c3e92fb", + "dleq_proof": "98f14ac45af6459ab0c84d47da5364cfb144ff90bdac732f9cabc6db000dcf1a95e3b31f7b70a04298a51b6fd4516c81d716c9954f8af10c3d59809aee2bbd38", + "input_index": 0 + }, + { + "scan_key": "02b9456157e946ef07f74b54fa09be4a7e938dcafbfb27b4b372c573c059f12ab0", + "ecdh_share": "02c2864474b084aac81f6e30ac5edf4667393ce604d808d3f499a0984e8b524974", + "dleq_proof": "f0b11d51c3fd13b38e0a6c8efc2ee0ca71773af674b3151e689214925c4b57c81688c9e078b750885a6cab286b577e93a2497eb49636b1af1b6bc82e7c911251", + "input_index": 0 + } + ] + } + }, + { + "description": "ecdh coverage: input 1 missing ECDH share for output 1 with two inputs / two sp outputs (different scan keys)", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQECAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdA1LXjEE5ADKtkYFqaX/HQNjrkJzwTXCIUmSwUfI4XiXsIQMJH9SAORd/RNe3npYWiEnGLrP4eADEZ2fFCo84+Y79FSIdA2UdLAc/ywKk2C3aU/HVAdd6A1BmZ5hUZHOyEmts0afeIQN2iSMnxOeAru7UzhvJK7ASK3t5ENlvCtUUi0lvnD6S+yIeA1LXjEE5ADKtkYFqaX/HQNjrkJzwTXCIUmSwUfI4XiXsQL4acjrvVeiYG5lZmpvBKzj4froBWMxwZokPImnK3kY7dzQal9KScZZNAcAkHMMi/VOQwpuZFg+CbzKTZYmMvwIiHgNlHSwHP8sCpNgt2lPx1QHXegNQZmeYVGRzshJrbNGn3kCY8UrEWvZFmrDITUfaU2TPsUT/kL2scy+cq8bbAA3PGpXjsx97cKBCmKUbb9RRbIHXFsmVT4rxDD1ZgJruK704AAEOIL6dZcg5FfsJJIq738qMo+7mzjUMVU9l6NUm5d9SerUiAQ8EAAAAAAEBH6CGAQAAAAAAFgAURjPVnK0TQ0eZcuDJlpIdCl2ttl0BAwQBAAAAIgYCQ6DUDp3giCeUFhdSuuQrqQlSeEIJ8GNDDwryVjVKhbsIAAAAgAEAAAABEAT+////Ih0DZR0sBz/LAqTYLdpT8dUB13oDUGZnmFRkc7ISa2zRp94hAnUEQAf+LQMNV3wymRzyo+deU1BCRbPL5bMFOufF8iBaIh4DZR0sBz/LAqTYLdpT8dUB13oDUGZnmFRkc7ISa2zRp95AOLnjgGtJvr7vxs7gZcKwZv2wCuzs8ei1sqr0fPBBeyma4KJW7Xy2yCcC63E31zv1eSccQoTQBuO1nXCpuWfYiQABAwiQXwEAAAAAAAEEIlEg9Jwu8ZtnBhG4bd7+iQzvlCaKgq4zHkb+GVHWxuNyPLEBCUIDZR0sBz/LAqTYLdpT8dUB13oDUGZnmFRkc7ISa2zRp94D8nDgpWMhg7arAQdOh35oANsA9cfF0vr7to+cKNSFRSQAAQMIkF8BAAAAAAABBCJRID2h4XbGT7RZU+xG4FA2NLx+jjQ6do2+77sKPojMSkyaAQlCA1LXjEE5ADKtkYFqaX/HQNjrkJzwTXCIUmSwUfI4XiXsAuDEwYe3IV+ZfISFp1tFkbbChHe5WhEjuZaSLTFUhgv4AA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": false + }, + { + "input_index": 1, + "private_key": "586b0810bbc3c7266971aeb7f8778b41707e092a1aeaf0d953b211f956e35541", + "public_key": "0243a0d40e9de0882794161752bae42ba90952784209f063430f0af256354a85bb", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "00144633d59cad1343479972e0c996921d0a5dadb65d", + "amount": 100000, + "witness_utxo": "a0860100000000001600144633d59cad1343479972e0c996921d0a5dadb65d", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "03651d2c073fcb02a4d82dda53f1d501d77a0350666798546473b2126b6cd1a7de", + "ecdh_share": "0376892327c4e780aeeed4ce1bc92bb0122b7b7910d96f0ad5148b496f9c3e92fb", + "dleq_proof": "98f14ac45af6459ab0c84d47da5364cfb144ff90bdac732f9cabc6db000dcf1a95e3b31f7b70a04298a51b6fd4516c81d716c9954f8af10c3d59809aee2bbd38", + "input_index": 0 + }, + { + "scan_key": "0352d78c41390032ad91816a697fc740d8eb909cf04d70885264b051f2385e25ec", + "ecdh_share": "03091fd48039177f44d7b79e96168849c62eb3f87800c46767c50a8f38f98efd15", + "dleq_proof": "be1a723aef55e8981b99599a9bc12b38f87eba0158cc7066890f2269cade463b77341a97d29271964d01c0241cc322fd5390c29b99160f826f329365898cbf02", + "input_index": 0 + }, + { + "scan_key": "03651d2c073fcb02a4d82dda53f1d501d77a0350666798546473b2126b6cd1a7de", + "ecdh_share": "0275044007fe2d030d577c32991cf2a3e75e53504245b3cbe5b3053ae7c5f2205a", + "dleq_proof": "38b9e3806b49bebeefc6cee065c2b066fdb00aececf1e8b5b2aaf47cf0417b299ae0a256ed7cb6c82702eb7137d73bf579271c4284d006e3b59d70a9b967d889", + "input_index": 1 + } + ] + } + }, + { + "description": "ecdh coverage: input 1 missing ECDH share for scan key with two inputs / one sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR9QwwAAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQ4gvp1lyDkV+wkkirvfyoyj7ubONQxVT2Xo1Sbl31J6tSIBDwQAAAAAAQEfUMMAAAAAAAAWABRGM9WcrRNDR5ly4MmWkh0KXa22XSIGAkOg1A6d4IgnlBYXUrrkK6kJUnhCCfBjQw8K8lY1SoW7CAAAAIABAAAAARAE/v///wABAwgYcwEAAAAAAAEEIlEgzeOdiwW0lvjxjyCyfQr5oyMzCeBHIhnggT/ztdN4fikBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 50000, + "witness_utxo": "50c3000000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": false + }, + { + "input_index": 1, + "private_key": "586b0810bbc3c7266971aeb7f8778b41707e092a1aeaf0d953b211f956e35541", + "public_key": "0243a0d40e9de0882794161752bae42ba90952784209f063430f0af256354a85bb", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "00144633d59cad1343479972e0c996921d0a5dadb65d", + "amount": 50000, + "witness_utxo": "50c30000000000001600144633d59cad1343479972e0c996921d0a5dadb65d", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ] + } + }, + { + "description": "input eligibility: segwit version greater than 1 in transaction inputs with sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABZSFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIHLzeIUvuENyYHLyUHbw53Vg7UwuBHFm7mpibHW/2znWAiAhKq5fCVdONB9YhvX/8y9XFuq5AwpKU3nmAWtqTULcJAEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwgYcwEAAAAAAAEEIlEgMm31D+Cge3rLcgcL6ztjLrmtFbppXMHlqmqgB7YUb9gBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "5214229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000165214229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ] + }, + "checks": [ + "input_eligibility" + ] + }, + { + "description": "input eligibility: non-SIGHASH_ALL signature on input with sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIHLzeIUvuENyYHLyUHbw53Vg7UwuBHFm7mpibHW/2znWAiAhKq5fCVdONB9YhvX/8y9XFuq5AwpKU3nmAWtqTULcJAEBAwQCAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwgYcwEAAAAAAAEEIlEgMm31D+Cge3rLcgcL6ztjLrmtFbppXMHlqmqgB7YUb9gBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ] + } + }, + { + "description": "output scripts: P2TR input with NUMS internal key cannot derive sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAT8Qa2S1e1sTdvn2xHGQ9AzAazvty4qrqXhQqz1/Z9AgEPBAAAAAABASsQJwAAAAAAACJRIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrAAQMEAQAAAAEQBP7///8BFyBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wCIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQMIHCUAAAAAAAABBCJRICdoyIruoiMiW6OthOeGNS+YKdLT6TeKOToL/1mvh83QAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA2HhseneXkLLIAf3ylS54NV+0Tk4+tVtPxnldROo/OA5AA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "13f106b64b57b5b1376f9f6c47190f40cc06b3bedcb8aaba97850ab3d7f67d02", + "prevout_index": 0, + "prevout_scriptpubkey": "512050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0", + "amount": 10000, + "witness_utxo": "102700000000000022512050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 9500, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "51202768c88aeea223225ba3ad84e786352f9829d2d3e9378a393a0bff59af87cdd0" + } + ] + } + }, + { + "description": "output scripts: PSBT_OUT_SCRIPT does not match derived sp output", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQMIGHMBAAAAAAABBCJRIM3jnYsFtJb48Y8gsn0K+aMjMwngRyIZ4IE/87XTeH4pAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA2HhseneXkLLIAf3ylS54NV+0Tk4+tVtPxnldROo/OA5AA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 95000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120cde39d8b05b496f8f18f20b27d0af9a3233309e0472219e0813ff3b5d3787e29" + } + ] + } + }, + { + "description": "output scripts: two sp outputs (same scan / different spend keys) not sorted lexicographically by spend key", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQECAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCID9iBcRocGpGJF8XE6u4uLmjp7/YOsdF98ByDUQxtM38AiBxWKv7gg8r9a1nRfVvwHCcmVzMrP4XCY2KobYfjJ/y/wEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwiQXwEAAAAAAAEEIlEgdUe4Fj1bvFSYQL5MwUaq0JUgc2e565RwbeZwjUCPk/kBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgCi4D5lDNIleEbx2x1q254/iwluzMvIXshHEAXvx9nfYYAAQMIECcAAAAAAAABBCJRIGziw1OV8XDyS20tgNCkDEb0a6S0hD9qsxGpNLMcXYTcAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA6r4Yq7amM+SU5r33DN10dpfsGsSHvCh8DGp9zgv1T27AA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 90000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068028b80f994334895e11bc76c75ab6e78fe2c25bb332f217b211c4017bf1f677d86", + "script": "51207547b8163d5bbc549840be4cc146aad095207367b9eb94706de6708d408f93f9" + }, + { + "output_index": 1, + "amount": 10000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe06803aaf862aeda98cf92539af7dc3375d1da5fb06b121ef0a1f031a9f7382fd53dbb", + "script": "51206ce2c35395f170f24b6d2d80d0a40c46f46ba4b4843f6ab311a934b31c5d84dc" + } + ] + } + }, + { + "description": "output scripts: k values assigned to wrong output indices with three sp outputs (same scan / spend keys)", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEDAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIFg36hg2U7tRrGNQq+bDhHvokGogZdwwSNbSCOp8DkyCAiAQ+0/6sv7Vqmy2KAqTxHnPsE5Gyp7fF6owXNFnSiQsSQEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwiQXwEAAAAAAAEEIlEgMm31D+Cge3rLcgcL6ztjLrmtFbppXMHlqmqgB7YUb9gBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkAAQMIECcAAAAAAAABBCJRIJcUQsifBHhuCCcorhFu6rgC8/qelf2w03U6aEAgIAbeAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA2HhseneXkLLIAf3ylS54NV+0Tk4+tVtPxnldROo/OA5AAEDCBAnAAAAAAAAAQQiUSA/y66kkIf44D2d79Ory9bejAMrWD+Fpl0FeIHExEPNwAEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 90000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120326df50fe0a07b7acb72070beb3b632eb9ad15ba695cc1e5aa6aa007b6146fd8" + }, + { + "output_index": 1, + "amount": 10000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120971442c89f04786e082728ae116eeab802f3fa9e95fdb0d3753a6840202006de" + }, + { + "output_index": 2, + "amount": 10000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "51203fcbaea49087f8e03d9defd3abcbd6de8c032b583f85a65d057881c4c443cdc0" + } + ] + } + } + ], + "valid": [ + { + "description": "can finalize: one P2PKH input single-signer", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiBSJ0jrF3ZNKMpJSBXsjUnn0w1SvHNCLHyG63TjlwVylAEPBAAAAAABAFUCAAAAAfTCEtWu0ef2/2M/LOCcZHxXvt2TAxTZjed1A9WOlAszAAAAAAD/////AaCGAQAAAAAAGXapFB4q14ctMpQTpW3wlovjOCIngxY7iKwAAAAAIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIDnBDcvHz0XG2UNW/1DBK42GqVUM8DcXPZzr94cU5nx1AiBxlVpC7SBTJDIHI8TwFCXc6J9CX4NwKEy0J2z9tt6jrAEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwgYcwEAAAAAAAEEIlEg4UDSh7RbRs1OqvpDdwYVcLq+g9G4vJUKdKn+oJIz16YBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "522748eb17764d28ca494815ec8d49e7d30d52bc73422c7c86eb74e397057294", + "prevout_index": 0, + "prevout_scriptpubkey": "76a9141e2ad7872d329413a56df0968be338222783163b88ac", + "amount": 100000, + "non_witness_utxo": "0200000001f4c212d5aed1e7f6ff633f2ce09c647c57bedd930314d98de77503d58e940b330000000000ffffffff01a0860100000000001976a9141e2ad7872d329413a56df0968be338222783163b88ac00000000", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 95000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120e140d287b45b46cd4eaafa4377061570babe83d1b8bc950a74a9fea09233d7a6" + } + ] + } + }, + { + "description": "can finalize: two inputs single-signer using global ECDH share", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBACIHAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQLTJicjNSYH6E/K92UfjS1jfgIK6obfrYDTBs1PSM/MYiIIAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQMZTtx+UaBipW9w58JGERbyQOgh2tzgGDp5dsdzsMKvfVOC18XhXP1EZ3nGqY60ZtEnjvLZUY5Nsi67qTZFzkrkAAQ4gGKcXZjsLqxSxKhp3EyP/HkB53VMuXdE+KOoQgccAmEoBDwQAAAAAAQEfoIYBAAAAAAAWABQimnLTSmRb00lru/ULu4HJBj9PlCICAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/RzBEAiAXBfzaBiZu2zKxaYZ3s39nu25F1JLr/OZWqIDufxOOtgIgPc3jI53ZBjavZPskEspB45fJWKAwd3z/nIlAucd310wBIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////AAEOIL6dZcg5FfsJJIq738qMo+7mzjUMVU9l6NUm5d9SerUiAQ8EAAAAAAEBH6CGAQAAAAAAFgAUE5NQEXxXeUGphYArue3vwROTK8ciAgL1tZ+l5JIiHr9VunitRCYFvq6VFmuh66MlDQu6rH4u3EcwRAIgA4mx4K7v1/q2Ce+rAYv7AWrP8hu4EPPEjDi6ckQYDpUCIBUP7GDiVvQMR1dN/RKaCOr3rB2Xq+4x9AaZ+Hk1RhnzASIGAvW1n6XkkiIev1W6eK1EJgW+rpUWa6HroyUNC7qsfi7cCAAAAIABAAAAARAE/v///wABAwgYcwEAAAAAAAEEIlEgEz1qT1CQlbV5T3SR+8PxLDxFvC2bYK8luLunBMgHKq8BCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 1, + "private_key": "295c2eedddd8331d20b5d4cf9e69bb523ed85cb0bf35ab12e04fea66fe6d4a4a", + "public_key": "02f5b59fa5e492221ebf55ba78ad442605beae95166ba1eba3250d0bbaac7e2edc", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "0014139350117c577941a985802bb9edefc113932bc7", + "amount": 100000, + "witness_utxo": "a086010000000000160014139350117c577941a985802bb9edefc113932bc7", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "02d3262723352607e84fcaf7651f8d2d637e020aea86dfad80d306cd4f48cfcc62", + "dleq_proof": "c653b71f946818a95bdc39f0918445bc903a0876b738060e9e5db1dcec30abdf54e0b5f178573f5119de71aa63ad19b449e3bcb65463936c8baeea4d917392b9" + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 95000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120133d6a4f509095b5794f7491fbc3f12c3c45bc2d9b60af25b8bba704c8072aaf" + } + ] + } + }, + { + "description": "can finalize: two inputs single-signer using per-input ECDH shares", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIBcF/NoGJm7bMrFphnezf2e7bkXUkuv85laogO5/E462AiA9zeMjndkGNq9k+yQSykHjl8lYoDB3fP+ciUC5x3fXTAEiBgLIF7t1Ia/DXqlvO/snDm61Dd/6VWBie5Yf7ADymWUIvwgAAACAAAAAAAEQBP7///8iHQJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaCED7KT/Ebco4uD2DOYiKUOm/1W52V9ie/mpnQhLyHLVClsiHgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaECKE7OYVUX3K9bolxSuuQmz41SoQqm7i1bNDt7SHfihmVmLMSKKSeC9fpXOEFP3xbKKy1Q6aHB2AOPOiYIu4yAhAAEOIL6dZcg5FfsJJIq738qMo+7mzjUMVU9l6NUm5d9SerUiAQ8EAAAAAAEBH6CGAQAAAAAAFgAUE5NQEXxXeUGphYArue3vwROTK8ciAgL1tZ+l5JIiHr9VunitRCYFvq6VFmuh66MlDQu6rH4u3EcwRAIgA4mx4K7v1/q2Ce+rAYv7AWrP8hu4EPPEjDi6ckQYDpUCIBUP7GDiVvQMR1dN/RKaCOr3rB2Xq+4x9AaZ+Hk1RhnzAQEDBAEAAAAiBgL1tZ+l5JIiHr9VunitRCYFvq6VFmuh66MlDQu6rH4u3AgAAACAAQAAAAEQBP7///8iHQJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaCEDNNLO907e1EZCJ2B/xZrB3oDBKiQzpWJxUo7cz1D6bCsiHgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaEA3wAwLQqBz9rZfDEtuVuBndLOAwd894otkCxIe4LO0z3hQ30VuC12OGXXd/yUmLIOB4g+FLNhwveP2qDKmMlvMAAEDCBhzAQAAAAAAAQQiUSATPWpPUJCVtXlPdJH7w/EsPEW8LZtgryW4u6cEyAcqrwEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 1, + "private_key": "295c2eedddd8331d20b5d4cf9e69bb523ed85cb0bf35ab12e04fea66fe6d4a4a", + "public_key": "02f5b59fa5e492221ebf55ba78ad442605beae95166ba1eba3250d0bbaac7e2edc", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "0014139350117c577941a985802bb9edefc113932bc7", + "amount": 100000, + "witness_utxo": "a086010000000000160014139350117c577941a985802bb9edefc113932bc7", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + }, + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "0334d2cef74eded4464227607fc59ac1de80c12a2433a56271528edccf50fa6c2b", + "dleq_proof": "37c00c0b42a073f6b65f0c4b6e56e06774b380c1df3de28b640b121ee0b3b4cf7850df456e0b5d8e1975ddff25262c8381e20f852cd870bde3f6a832a6325bcc", + "input_index": 1 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 95000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120133d6a4f509095b5794f7491fbc3f12c3c45bc2d9b60af25b8bba704c8072aaf" + } + ] + } + }, + { + "description": "can finalize: two inputs / two sp outputs with mixed global and per-input ECDH shares", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQECAQYBACIHAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oIQNmz8f9zgbgjtSbGzlSuNFRqPfgdf5fbdWN1o+dre7KlCIIAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oQF6riwhtSFD+nRyflhqR+Ze2HKjQhQr8BqeL3zx0nvVAUOaStT+1sIbl54eT8B5203R/ieMXen/v9vjBYX8bBuMAAQ4gGKcXZjsLqxSxKhp3EyP/HkB53VMuXdE+KOoQgccAmEoBDwQAAAAAAQEfQA0DAAAAAAAWABQimnLTSmRb00lru/ULu4HJBj9PlCICAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/SDBFAiEA1k6e8CREAEbpyjkwLrh7LfYN/FHJwKkX1oLzz4EFkIwCIB6+EqXx/SGW8u+11Tr+V850W5Af6ImXitWa7+FKT1A3AQEDBAEAAAAiBgLIF7t1Ia/DXqlvO/snDm61Dd/6VWBie5Yf7ADymWUIvwgAAACAAAAAAAEQBP7///8iHQKp8frCzEV2hPM96QBz/iDSLJR8khY7hisU0Kuh30cVECED+9Fz2cSNWMUCJCbQUni8oXuajiKCZmkBobALngS3mUkiHgKp8frCzEV2hPM96QBz/iDSLJR8khY7hisU0Kuh30cVEEDAXaQQSHz67DeP6+avUAXjrmyGI2gYsOVbIUAX61Bz7e9AF5V1tdzaqQY0kayLg/0GJNQXek0t1JlMRkDWuMzeAAEOIL6dZcg5FfsJJIq738qMo+7mzjUMVU9l6NUm5d9SerUiAQ8EAAAAAAEBH0ANAwAAAAAAFgAUE5NQEXxXeUGphYArue3vwROTK8ciAgL1tZ+l5JIiHr9VunitRCYFvq6VFmuh66MlDQu6rH4u3EcwRAIgWoJ5pshwRPECyFkwyJ7C/wGUKGJBraOMeFTy6DZR1fICICvMZkqflwTVcxLbwryDaGcIDyLEdYuR27ktF6HkEeqpAQEDBAEAAAAiBgL1tZ+l5JIiHr9VunitRCYFvq6VFmuh66MlDQu6rH4u3AgAAACAAQAAAAEQBP7///8iHQKp8frCzEV2hPM96QBz/iDSLJR8khY7hisU0Kuh30cVECEDpqibG5hqmVUz4XvtZFALVBuw1zkHHv4q4rfvsKDKa88iHgKp8frCzEV2hPM96QBz/iDSLJR8khY7hisU0Kuh30cVEEDDSmopQLvppzVsHMb/41jdREHbWIl8upnCEut1KF59oeyhHHksl6OASTNFqyJN6dlMKzbuhSauI1KBcaMjHeczAAEDCJBfAQAAAAAAAQQiUSCA65gxi89t1hGsF6lksJFzbATK6t8ubodOXO+Gx8YFFwEJQgMzYdP/cvNOa056e++W5bgVHmapea3OdTw5xYPL5XZtKAMSZlgePoQcu5TeYupzVYOiMsTIiPpu3vLsL4cHifwd1AABAwiQXwEAAAAAAAEEIlEgELYvxakKvKAWeWHb/0zq6aoyTvssfyRmJ+i1oz3jfIQBCUICqfH6wsxFdoTzPekAc/4g0iyUfJIWO4YrFNCrod9HFRACSkjXbiDUC89fLhpcP/6z3CqLO4jXmkhtVMnZ6qj6GI4A", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 200000, + "witness_utxo": "400d030000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 1, + "private_key": "295c2eedddd8331d20b5d4cf9e69bb523ed85cb0bf35ab12e04fea66fe6d4a4a", + "public_key": "02f5b59fa5e492221ebf55ba78ad442605beae95166ba1eba3250d0bbaac7e2edc", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "0014139350117c577941a985802bb9edefc113932bc7", + "amount": 200000, + "witness_utxo": "400d030000000000160014139350117c577941a985802bb9edefc113932bc7", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "0366cfc7fdce06e08ed49b1b3952b8d151a8f7e075fe5f6dd58dd68f9dadeeca94", + "dleq_proof": "5eab8b086d4850fe9d1c9f961a91f997b61ca8d0850afc06a78bdf3c749ef54050e692b53fb5b086e5e78793f01e76d3747f89e3177a7feff6f8c1617f1b06e3" + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "03fbd173d9c48d58c5022426d05278bca17b9a8e2282666901a1b00b9e04b79949", + "dleq_proof": "c05da410487cfaec378febe6af5005e3ae6c86236818b0e55b214017eb5073edef40179575b5dcdaa9063491ac8b83fd0624d4177a4d2dd4994c4640d6b8ccde", + "input_index": 0 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "03a6a89b1b986a995533e17bed64500b541bb0d739071efe2ae2b7efb0a0ca6bcf", + "dleq_proof": "c34a6a2940bbe9a7356c1cc6ffe358dd4441db58897cba99c212eb75285e7da1eca11c792c97a380493345ab224de9d94c2b36ee8526ae23528171a3231de733", + "input_index": 1 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 90000, + "sp_v0_info": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28031266581e3e841cbb94de62ea735583a232c4c888fa6edef2ec2f870789fc1dd4", + "script": "512080eb98318bcf6dd611ac17a964b091736c04caeadf2e6e874e5cef86c7c60517" + }, + { + "output_index": 1, + "amount": 90000, + "sp_v0_info": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510024a48d76e20d40bcf5f2e1a5c3ffeb3dc2a8b3b88d79a486d54c9d9eaa8fa188e", + "script": "512010b62fc5a90abca0167961dbff4ceae9aa324efb2c7f246627e8b5a33de37c84" + } + ] + } + }, + { + "description": "can finalize: one input / one sp output with both global and per-input ECDH shares", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBACIHAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oIQMKGLm7QZm4kzDrF/juWseG4IU9zS88zfF0S5fOTt4FPiIIAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oQCf7kMXWHHjjEVIA/9GD0o0KtIOAYslUGTTGQW1CZ84xvUKH6TWUUsCBevDvTVDP9XhDaw0o+e2PIbeaRHZAgI4AAQ4gGKcXZjsLqxSxKhp3EyP/HkB53VMuXdE+KOoQgccAmEoBDwQAAAAAAQEfQA0DAAAAAAAWABQimnLTSmRb00lru/ULu4HJBj9PlCICAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/RzBEAiA3eo+fWjPY+iVcM7OSJtO88CKd+tlgRw74AWpxQvIzWAIgCBvWlIh63us2Ia9aLeTw05yxNpLQQb99vM00oysFQekBAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oIQMKGLm7QZm4kzDrF/juWseG4IU9zS88zfF0S5fOTt4FPiIeAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oQAATg9RmG+hyXvKQSPSMkR+J9fNDF9fAwKzGDvZfG1K//3qW2DObYl34Orq1eqJz7c2aNceU2suxyGEXG7JnB18AAQMIkF8BAAAAAAABBCJRINxNRNnjYeXnQ2V5x44lklVB4in9wVOtWYmtiuawYkx4AQlCAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oAxJmWB4+hBy7lN5i6nNVg6IyxMiI+m7e8uwvhweJ/B3UAA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 200000, + "witness_utxo": "400d030000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "030a18b9bb4199b89330eb17f8ee5ac786e0853dcd2f3ccdf1744b97ce4ede053e", + "dleq_proof": "27fb90c5d61c78e3115200ffd183d28d0ab4838062c9541934c6416d4267ce31bd4287e9359452c0817af0ef4d50cff578436b0d28f9ed8f21b79a447640808e" + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 90000, + "sp_v0_info": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28031266581e3e841cbb94de62ea735583a232c4c888fa6edef2ec2f870789fc1dd4", + "script": "5120dc4d44d9e361e5e7436579c78e25925541e229fdc153ad5989ad8ae6b0624c78" + } + ] + } + }, + { + "description": "can finalize: three sp outputs (different scan keys) with multiple global ECDH shares", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEDAQYBACIHAqnx+sLMRXaE8z3pAHP+INIslHySFjuGKxTQq6HfRxUQIQLC2d9g7mmlqGYE3ZpigsIu+OLJJ22m5ZWlRj83Gq3uJCIHAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oIQLcfS7KxWiwEVF1ujzVSW/0xLbZCUItvMrrjyUvpU+L0CIHA47TIg5zNE+R4tMzkePhaR2zna8G5CN+GX7Q0Ae/JyhsIQJwvvXAapqFX8Y7/yvQvi1wAzyCC2FTQTyNbtpR8lobKSIIAqnx+sLMRXaE8z3pAHP+INIslHySFjuGKxTQq6HfRxUQQOyCQr97WIHO4GBWbH8TzEfkcQyJomRYwgMBswMeSBc7wL6gFoDTN1616AYRqw1Qix5Jq7VxsYeAMqrknwsC99IiCAMzYdP/cvNOa056e++W5bgVHmapea3OdTw5xYPL5XZtKEDqQ0cOXhvjKRtIoLy1P1WVLGSGDnY177pQA8yt7XfdEo4hZpew9SO+IX7Gh1B3l8zOvFzZQ2KvG0bYA2Qr1flAIggDjtMiDnM0T5Hi0zOR4+FpHbOdrwbkI34ZftDQB78nKGxAG9/lTtSioG//mwAJrk49gPxHYWR1r98hJEz+PG0QTfJHQah1UU14ZU+5yhEY5tPlUvOPjKujVS6DM8UBvwy9PwABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR8goQcAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9IMEUCIQCLg3ekbsHGTj/QlvLhIwW7hrrfzKDP2b9X3JYim55whAIgfn3H3eyoVVwXfr7/yUnqTDNSD+W+7Hun4nJ+NPvUjf0BIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////AAEOIL6dZcg5FfsJJIq738qMo+7mzjUMVU9l6NUm5d9SerUiAQ8EAAAAAAEBH6CGAQAAAAAAFgAURjPVnK0TQ0eZcuDJlpIdCl2ttl0iAgJDoNQOneCIJ5QWF1K65CupCVJ4QgnwY0MPCvJWNUqFu0gwRQIhAPmSLe+tUsYSCQS6pacAUymDbsHD2uFsoGjAFrJrBNLYAiBw+V8k+cklbkGWlGnDUqz6yOiUMjUC6s0WPJSi42nuHwEiBgJDoNQOneCIJ5QWF1K65CupCVJ4QgnwY0MPCvJWNUqFuwgAAACAAQAAAAEQBP7///8AAQMIkF8BAAAAAAABBCJRIKFPr9x+36QEQnF0sSasoOmZMTP+F+JCCurtIO9U/DjcAQlCAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oAxJmWB4+hBy7lN5i6nNVg6IyxMiI+m7e8uwvhweJ/B3UAAEDCJBfAQAAAAAAAQQiUSB99fJJWUQ0FLvv6J0tqJSGSlYotoAzuSPoaGvcw96dlAEJQgKp8frCzEV2hPM96QBz/iDSLJR8khY7hisU0Kuh30cVEAJKSNduINQLz18uGlw//rPcKos7iNeaSG1UydnqqPoYjgABAwiQXwEAAAAAAAEEIlEgsvEqf1cMxQV4QPpjk/cfBpABQgiBgYEMNLuIdh2SlQ0BCUIDjtMiDnM0T5Hi0zOR4+FpHbOdrwbkI34ZftDQB78nKGwD+BRzhEyddMkh/xelr0ELYzc0jXr7/3OJG8XLDLJMsMAA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 500000, + "witness_utxo": "20a1070000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 1, + "private_key": "586b0810bbc3c7266971aeb7f8778b41707e092a1aeaf0d953b211f956e35541", + "public_key": "0243a0d40e9de0882794161752bae42ba90952784209f063430f0af256354a85bb", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "00144633d59cad1343479972e0c996921d0a5dadb65d", + "amount": 100000, + "witness_utxo": "a0860100000000001600144633d59cad1343479972e0c996921d0a5dadb65d", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "02dc7d2ecac568b0115175ba3cd5496ff4c4b6d909422dbccaeb8f252fa54f8bd0", + "dleq_proof": "ea43470e5e1be3291b48a0bcb53f55952c64860e7635efba5003ccaded77dd128e216697b0f523be217ec687507797cccebc5cd94362af1b46d803642bd5f940" + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "02c2d9df60ee69a5a86604dd9a6282c22ef8e2c9276da6e595a5463f371aadee24", + "dleq_proof": "ec8242bf7b5881cee060566c7f13cc47e4710c89a26458c20301b3031e48173bc0bea01680d3375eb5e80611ab0d508b1e49abb571b1878032aae49f0b02f7d2" + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "0270bef5c06a9a855fc63bff2bd0be2d70033c820b6153413c8d6eda51f25a1b29", + "dleq_proof": "1bdfe54ed4a2a06fff9b0009ae4e3d80fc47616475afdf21244cfe3c6d104df24741a875514d78654fb9ca1118e6d3e552f38f8caba3552e8333c501bf0cbd3f" + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 90000, + "sp_v0_info": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28031266581e3e841cbb94de62ea735583a232c4c888fa6edef2ec2f870789fc1dd4", + "script": "5120a14fafdc7edfa404427174b126aca0e9993133fe17e2420aeaed20ef54fc38dc" + }, + { + "output_index": 1, + "amount": 90000, + "sp_v0_info": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510024a48d76e20d40bcf5f2e1a5c3ffeb3dc2a8b3b88d79a486d54c9d9eaa8fa188e", + "script": "51207df5f24959443414bbefe89d2da894864a5628b68033b923e8686bdcc3de9d94" + }, + { + "output_index": 2, + "amount": 90000, + "sp_v0_info": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c03f81473844c9d74c921ff17a5af410b6337348d7afbff73891bc5cb0cb24cb0c0", + "script": "5120b2f12a7f570cc5057840fa6393f71f06900142088181810c34bb88761d92950d" + } + ] + } + }, + { + "description": "can finalize: one P2WPKH input / two mixed outputs - labeled sp output and BIP 32 change", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQECAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR8goQcAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9IMEUCIQCspcmETsmSLIHmcIF+My2i3RXSxdOpRiq21l5TtoKJXgIgTfMs6jgucsQ8BYQE/a5wWVHmuj3jPYM8g6lbsBg4CscBAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQMIoIYBAAAAAAABBCJRICJoz1KZWfMKyPelRxykxnU2pR8OWxaK8D7tRxXtzHLgAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA+qPIovitxbaze/LKBAHKHTwCkdvXAe9BRkXEmzGSz3vAQoEAQAAAAABAwhw8wUAAAAAAAEEFgAUOMGPNd2AY+OApw/+tPmP9O80gXciAgLIF7t1Ia/DXqlvO/snDm61Dd/6VWBie5Yf7ADymWUIvwwAAAAAAAAAAAAAAAEA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 500000, + "witness_utxo": "20a1070000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 100000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "sp_v0_label": 1, + "script": "51202268cf529959f30ac8f7a5471ca4c67536a51f0e5b168af03eed4715edcc72e0" + }, + { + "output_index": 1, + "amount": 390000, + "script": "001438c18f35dd8063e380a70ffeb4f98ff4ef348177" + } + ] + } + }, + { + "description": "can finalize: one input / two sp outputs - output 0 has no label / output 1 uses label=0 convention for sp change", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQECAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR/gkwQAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCICJa45kPVF365uoUKpN0arSqgSfjyBNY63wha08anrtiAiBl42BSGRArJQdeOVXz80zjvPKjXR4QFd1dw7/tPs81qQEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0CXw7Bs42ZhKfrYvFb5AMCBBDxQu301p42P1YVr9Kmts0hA84eNnXZDZkmSfqd2xH5AlSbs7AvVvbonMCgTIuMN92UIh0Ci3kOCYf96fDRI5jra+MMc3Z5PGR4ZaKgLy78lg2WZWghAlWBv6pzMVb2uY5xom9w/XazZ4ZJ9isfxed7jpq45hBQIh4CXw7Bs42ZhKfrYvFb5AMCBBDxQu301p42P1YVr9Kmts1ADolhPR9rJTbbj6kkIA2zpvjvyhciVWTVaxEuUIZbTTAuBY+m9hwp4jZqSnJp1FVaXGHWVVnb+/rJ4urf5TY31SIeAot5DgmH/enw0SOY62vjDHN2eTxkeGWioC8u/JYNlmVoQHlAgZ626fJYwVg4fApkzBb1F8fvRAkSLM6qlPTi/1N2xCR7/BcNmyY7Dj0ga2aMh+lLNN7q+gCheSsEeteIdbsAAQMIoIYBAAAAAAABBCJRIKBIDZZnnBk25au9SLqw4B/6yx7n0cnhaC8VQBaZZYXZAQlCAot5DgmH/enw0SOY62vjDHN2eTxkeGWioC8u/JYNlmVoA7RDRzNcY/UF5V4mirKA8xi0pqoUSS/zecNmih9U74ybAAEDCDDmAgAAAAAAAQQiUSB2Hb00kpiEpmcp69zbrPFnSC32LBTwmOTvGNcP4Grv0AEJQgJfDsGzjZmEp+ti8VvkAwIEEPFC7fTWnjY/VhWv0qa2zQK+EFEqlVNkHkB62OUJo0GEUqkC6FVqDboumD9eAPe8UQEKBAAAAAAA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 300000, + "witness_utxo": "e093040000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "025f0ec1b38d9984a7eb62f15be403020410f142edf4d69e363f5615afd2a6b6cd", + "ecdh_share": "03ce1e3675d90d992649fa9ddb11f902549bb3b02f56f6e89cc0a04c8b8c37dd94", + "dleq_proof": "0e89613d1f6b2536db8fa924200db3a6f8efca17225564d56b112e50865b4d302e058fa6f61c29e2366a4a7269d4555a5c61d65559dbfbfac9e2eadfe53637d5", + "input_index": 0 + }, + { + "scan_key": "028b790e0987fde9f0d12398eb6be30c7376793c647865a2a02f2efc960d966568", + "ecdh_share": "025581bfaa733156f6b98e71a26f70fd76b3678649f62b1fc5e77b8e9ab8e61050", + "dleq_proof": "7940819eb6e9f258c158387c0a64cc16f517c7ef4409122cceaa94f4e2ff5376c4247bfc170d9b263b0e3d206b668c87e94b34deeafa00a1792b047ad78875bb", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 100000, + "sp_v0_info": "028b790e0987fde9f0d12398eb6be30c7376793c647865a2a02f2efc960d96656803b44347335c63f505e55e268ab280f318b4a6aa14492ff379c3668a1f54ef8c9b", + "script": "5120a0480d96679c1936e5abbd48bab0e01ffacb1ee7d1c9e1682f154016996585d9" + }, + { + "output_index": 1, + "amount": 190000, + "sp_v0_info": "025f0ec1b38d9984a7eb62f15be403020410f142edf4d69e363f5615afd2a6b6cd037cd462358c69c40525247a8793944d9e67484b248fd84a1e7ee4f920b4da41b5", + "sp_v0_label": 0, + "script": "5120761dbd34929884a66729ebdcdbacf167482df62c14f098e4ef18d70fe06aefd0" + } + ] + } + }, + { + "description": "can finalize: two sp outputs - output 0 uses label=3 / output 1 uses label=1", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQECAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR/gkwQAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCICJa45kPVF365uoUKpN0arSqgSfjyBNY63wha08anrtiAiBl42BSGRArJQdeOVXz80zjvPKjXR4QFd1dw7/tPs81qQEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Ci3kOCYf96fDRI5jra+MMc3Z5PGR4ZaKgLy78lg2WZWghAlWBv6pzMVb2uY5xom9w/XazZ4ZJ9isfxed7jpq45hBQIh4Ci3kOCYf96fDRI5jra+MMc3Z5PGR4ZaKgLy78lg2WZWhAeUCBnrbp8ljBWDh8CmTMFvUXx+9ECRIszqqU9OL/U3bEJHv8Fw2bJjsOPSBrZoyH6Us03ur6AKF5KwR614h1uwABAwighgEAAAAAAAEEIlEgyvcy9B51UZCclKM10VeOp7dmAnuIbN0cFwHZv8a3hIgBCUICi3kOCYf96fDRI5jra+MMc3Z5PGR4ZaKgLy78lg2WZWgDliRhtuNd91TQDUhtw53swgJxhFB2YU5cDtLJDyPT6b4BCgQDAAAAAAEDCDDmAgAAAAAAAQQiUSBNB6DTsPYafF7+NsLDxYfItphBhUYAjPgZu9pM4xgSIQEJQgKLeQ4Jh/3p8NEjmOtr4wxzdnk8ZHhloqAvLvyWDZZlaANaVzfUxngmF8xrDElrsrxiVVoyslQkT7ycnANd9GnlqwEKBAEAAAAA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 300000, + "witness_utxo": "e093040000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "028b790e0987fde9f0d12398eb6be30c7376793c647865a2a02f2efc960d966568", + "ecdh_share": "025581bfaa733156f6b98e71a26f70fd76b3678649f62b1fc5e77b8e9ab8e61050", + "dleq_proof": "7940819eb6e9f258c158387c0a64cc16f517c7ef4409122cceaa94f4e2ff5376c4247bfc170d9b263b0e3d206b668c87e94b34deeafa00a1792b047ad78875bb", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 100000, + "sp_v0_info": "028b790e0987fde9f0d12398eb6be30c7376793c647865a2a02f2efc960d96656803b44347335c63f505e55e268ab280f318b4a6aa14492ff379c3668a1f54ef8c9b", + "sp_v0_label": 3, + "script": "5120caf732f41e7551909c94a335d1578ea7b766027b886cdd1c1701d9bfc6b78488" + }, + { + "output_index": 1, + "amount": 190000, + "sp_v0_info": "028b790e0987fde9f0d12398eb6be30c7376793c647865a2a02f2efc960d96656803b44347335c63f505e55e268ab280f318b4a6aa14492ff379c3668a1f54ef8c9b", + "sp_v0_label": 1, + "script": "51204d07a0d3b0f61a7c5efe36c2c3c587c8b698418546008cf819bbda4ce3181221" + } + ] + } + }, + { + "description": "can finalize: two inputs using per-input ECDH shares - only eligible inputs contribute shares (P2SH excluded)", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9IMEUCIQCLTLcGPIp7P5Ia4ABZbNd4jlRA4dY+8e4bzsjCrJQMogIgNw2OmoWgzI3SWwuwgfMaotzFugoiSOqGCoFlHKNKDGgBAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQ4g0iImLyaHKbt8hMVYEppdaYYYXO9TXsFJwN9g3UqSwgQBDwQAAAAAAQBTAgAAAAFA5vqmEmjxehAQHWJVNYKIKuSFi+4TNj24D98oH4Jf/AAAAAAA/////wHwSQIAAAAAABepFPRfjMomjsJuS76JkXDxOCUNfPVehwAAAAABBEdSIQKHfKAvFEBZvYLQDhs5muN094pSzvehyjfyjXiLNA0veSEDTIekSHL14fAG002IoAlZKCvUU150d8PWjDFlmR6hs5ZSrgEQBP7///8iHQJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaCECYototkRuaocCWXPZOvsZTkIKEPOhzLOK3YLUZKiUpCgiHgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEDCJBfAQAAAAAAAQQiUSAybfUP4KB7estyBwvrO2Muua0VumlcweWqaqAHthRv2AEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 1, + "private_key": "", + "public_key": "", + "prevout_txid": "d222262f268729bb7c84c558129a5d6986185cef535ec149c0df60dd4a92c204", + "prevout_index": 0, + "prevout_scriptpubkey": "a914f45f8cca268ec26e4bbe899170f138250d7cf55e87", + "amount": 150000, + "non_witness_utxo": "020000000140e6faa61268f17a10101d62553582882ae4858bee13363db80fdf281f825ffc0000000000ffffffff01f04902000000000017a914f45f8cca268ec26e4bbe899170f138250d7cf55e8700000000", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 90000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120326df50fe0a07b7acb72070beb3b632eb9ad15ba695cc1e5aa6aa007b6146fd8" + } + ] + } + }, + { + "description": "can finalize: two inputs using global ECDH share - only eligible inputs contribute shares (P2SH excluded)", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBACIHAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIIAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQCQwR50toTEKAk0qq45Me0nXbNUfRe6BJ8FZsDk6Z1tvdcWB6+PQgX3a87tZjP16Ai8ah+1HlmT2uUekfbWUHLIAAQ4gGKcXZjsLqxSxKhp3EyP/HkB53VMuXdE+KOoQgccAmEoBDwQAAAAAAQEfoIYBAAAAAAAWABQimnLTSmRb00lru/ULu4HJBj9PlCICAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/SDBFAiEAi0y3BjyKez+SGuAAWWzXeI5UQOHWPvHuG87IwqyUDKICIDcNjpqFoMyN0lsLsIHzGqLcxboKIkjqhgqBZRyjSgxoASIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///wABDiDSIiYvJocpu3yExVgSml1phhhc71NewUnA32DdSpLCBAEPBAAAAAABAFMCAAAAAUDm+qYSaPF6EBAdYlU1gogq5IWL7hM2PbgP3ygfgl/8AAAAAAD/////AfBJAgAAAAAAF6kU9F+MyiaOwm5LvomRcPE4JQ189V6HAAAAAAEER1IhAod8oC8UQFm9gtAOGzma43T3ilLO96HKN/KNeIs0DS95IQNMh6RIcvXh8AbTTYigCVkoK9RTXnR3w9aMMWWZHqGzllKuARAE/v///wABAwiQXwEAAAAAAAEEIlEgMm31D+Cge3rLcgcL6ztjLrmtFbppXMHlqmqgB7YUb9gBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 1, + "private_key": "", + "public_key": "", + "prevout_txid": "d222262f268729bb7c84c558129a5d6986185cef535ec149c0df60dd4a92c204", + "prevout_index": 0, + "prevout_scriptpubkey": "a914f45f8cca268ec26e4bbe899170f138250d7cf55e87", + "amount": 150000, + "non_witness_utxo": "020000000140e6faa61268f17a10101d62553582882ae4858bee13363db80fdf281f825ffc0000000000ffffffff01f04902000000000017a914f45f8cca268ec26e4bbe899170f138250d7cf55e8700000000", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "2430479d2da1310a024d2aab8e4c7b49d76cd51f45ee8127c159b0393a675b6f75c581ebe3d0817ddaf3bb598cfd7a022f1a87ed479664f6b947a47db5941cb2" + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 90000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120326df50fe0a07b7acb72070beb3b632eb9ad15ba695cc1e5aa6aa007b6146fd8" + } + ] + } + }, + { + "description": "can finalize: two mixed input types - only eligible inputs contribute ECDH shares (NUMS internal key excluded)", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBAAABDiAT8Qa2S1e1sTdvn2xHGQ9AzAazvty4qrqXhQqz1/Z9AgEPBAAAAAABASsQJwAAAAAAACJRIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrAARAE/v///wEXIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrAAAEOIL6dZcg5FfsJJIq738qMo+7mzjUMVU9l6NUm5d9SerUiAQ8EAAAAAAEBH6CGAQAAAAAAFgAURjPVnK0TQ0eZcuDJlpIdCl2ttl0iAgJDoNQOneCIJ5QWF1K65CupCVJ4QgnwY0MPCvJWNUqFu0gwRQIhAPRhsN/2Q/wKVsFSIUU2OBYiEkh+UGpZ4GnXXtHwq4T2AiBSxx6U6TEuqKkAH80mNyvnCZfdwqpinFqBFtxR9bfpogEBAwQBAAAAIgYCQ6DUDp3giCeUFhdSuuQrqQlSeEIJ8GNDDwryVjVKhbsIAAAAgAEAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghAuV6etywGNj7MVToDBS5elCZS0I1aEHqn8ExcGFEAOY3Ih4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhA9/o4HeILO0WcbkJsYhPUF1MI8kgqROMyhCMmJNTa/Vcymb868WqzAsViY2OjfUSHB/fnoevqBke8zUhiA7srpgABAwgcJQAAAAAAAAEEIlEgBXZyJSSDmoybydLZc46Yu0DdLsUCO9x9p/cGI818hfsBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "13f106b64b57b5b1376f9f6c47190f40cc06b3bedcb8aaba97850ab3d7f67d02", + "prevout_index": 0, + "prevout_scriptpubkey": "512050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0", + "amount": 10000, + "witness_utxo": "102700000000000022512050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0", + "sequence": 4294967294, + "signed": false + }, + { + "input_index": 1, + "private_key": "586b0810bbc3c7266971aeb7f8778b41707e092a1aeaf0d953b211f956e35541", + "public_key": "0243a0d40e9de0882794161752bae42ba90952784209f063430f0af256354a85bb", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "00144633d59cad1343479972e0c996921d0a5dadb65d", + "amount": 100000, + "witness_utxo": "a0860100000000001600144633d59cad1343479972e0c996921d0a5dadb65d", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "02e57a7adcb018d8fb3154e80c14b97a50994b42356841ea9fc13170614400e637", + "dleq_proof": "f7fa381de20b3b459c6e426c6213d4175308f2482a44e33284232624d4dafd573299bf3af16ab302c5626363a37d448707f7e7a1ebea0647bccd486203bb2ba6", + "input_index": 1 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 9500, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "51200576722524839a8c9bc9d2d9738e98bb40dd2ec5023bdc7da7f70623cd7c85fb" + } + ] + } + }, + { + "description": "can finalize: three sp outputs (same scan key) - each output has distinct k value", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEEAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+AGgYAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIA3+9XB4r8pV2yK0ze42yxF/h2HkeAgptCHRBkisr62PAiBk5vWJD0/QW3emSnGDHJiGTZyYTJ7x/r6afFC08VkHIQEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GghA+yk/xG3KOLg9gzmIilDpv9VudlfYnv5qZ0IS8hy1QpbIh4Cekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GhAihOzmFVF9yvW6JcUrrkJs+NUqEKpu4tWzQ7e0h34oZlZizEiikngvX6VzhBT98WyistUOmhwdgDjzomCLuMgIQABAwighgEAAAAAAAEEIlEgMm31D+Cge3rLcgcL6ztjLrmtFbppXMHlqmqgB7YUb9gBCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkAAQMIoIYBAAAAAAABBCJRID/LrqSQh/jgPZ3v06vL1t6MAytYP4WmXQV4gcTEQ83AAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA2HhseneXkLLIAf3ylS54NV+0Tk4+tVtPxnldROo/OA5AAEDCKCGAQAAAAAAAQQiUSCXFELInwR4bggnKK4Rbuq4AvP6npX9sNN1OmhAICAG3gEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQABAwiQXwEAAAAAAAEEIlEgee9WiKP6f+/DkidFWx8cLwjHAIdCTWoA+EwxQhrKjaQA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 400000, + "witness_utxo": "801a060000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 100000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120326df50fe0a07b7acb72070beb3b632eb9ad15ba695cc1e5aa6aa007b6146fd8" + }, + { + "output_index": 1, + "amount": 100000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "51203fcbaea49087f8e03d9defd3abcbd6de8c032b583f85a65d057881c4c443cdc0" + }, + { + "output_index": 2, + "amount": 100000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120971442c89f04786e082728ae116eeab802f3fa9e95fdb0d3753a6840202006de" + }, + { + "output_index": 3, + "amount": 90000, + "script": "512079ef5688a3fa7fefc39227455b1f1c2f08c70087424d6a00f84c31421aca8da4" + } + ] + } + }, + { + "description": "can finalize: three sp outputs (same scan key) / two regular outputs - k values assigned independently of output index", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEGAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR8goQcAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9IMEUCIQD333KAYwDd2+SyTNcz9TsypkayNsKUYFfeR65QOyqxaAIgTGlAW3e+2vOyB+3ll0m7o9VMRhWoi7025Ugkr3fJ5zEBAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQMIoIYBAAAAAAABBCJRIDJt9Q/goHt6y3IHC+s7Yy65rRW6aVzB5apqoAe2FG/YAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA2HhseneXkLLIAf3ylS54NV+0Tk4+tVtPxnldROo/OA5AAEDCFDDAAAAAAAAAQQiUSCcaj0jgY6CDtHQz965Y2V8jsMne32bhlx411V/Xe3WogABAwighgEAAAAAAAEEIlEgP8uupJCH+OA9ne/Tq8vW3owDK1g/haZdBXiBxMRDzcABCUICekh/wZ+3aYd7h0LW6hgRjzxOcrHqjG3mAqetSkHb4GgDYeGx6d5eQssgB/fKVLng1X7ROTj61W0/GeV1E6j84DkAAQMIUMMAAAAAAAABBCJRIHnvVoij+n/vw5InRVsfHC8IxwCHQk1qAPhMMUIayo2kAAEDCKCGAQAAAAAAAQQiUSCXFELInwR4bggnKK4Rbuq4AvP6npX9sNN1OmhAICAG3gEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQABAwjILAEAAAAAAAEEIlEgb8UG+AUqn1qKqbmQ1emU9JXd7W1HusRMqs2t2NiZHNAA", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 500000, + "witness_utxo": "20a1070000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 100000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120326df50fe0a07b7acb72070beb3b632eb9ad15ba695cc1e5aa6aa007b6146fd8" + }, + { + "output_index": 1, + "amount": 50000, + "script": "51209c6a3d23818e820ed1d0cfdeb963657c8ec3277b7d9b865c78d7557f5dedd6a2" + }, + { + "output_index": 2, + "amount": 100000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "51203fcbaea49087f8e03d9defd3abcbd6de8c032b583f85a65d057881c4c443cdc0" + }, + { + "output_index": 3, + "amount": 50000, + "script": "512079ef5688a3fa7fefc39227455b1f1c2f08c70087424d6a00f84c31421aca8da4" + }, + { + "output_index": 4, + "amount": 100000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120971442c89f04786e082728ae116eeab802f3fa9e95fdb0d3753a6840202006de" + }, + { + "output_index": 5, + "amount": 77000, + "script": "51206fc506f8052a9f5a8aa9b990d5e994f495dded6d47bac44caacdadd8d8991cd0" + } + ] + } + }, + { + "description": "in progress: two P2TR inputs, neither is signed", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBAAABDiAT8Qa2S1e1sTdvn2xHGQ9AzAazvty4qrqXhQqz1/Z9AgEPBAAAAAABASughgEAAAAAACJRIMgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/AQMEAQAAAAEQBP7///8BFyDIF7t1Ia/DXqlvO/snDm61Dd/6VWBie5Yf7ADymWUIvyIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQ4gK5A3/0HdF05VIvD0BFw/QENiMq8mANTaXJ6hPMPKtLUBDwQAAAAAAQErECcAAAAAAAAiUSBDoNQOneCIJ5QWF1K65CupCVJ4QgnwY0MPCvJWNUqFuwEDBAEAAAABEAT+////ARcgQ6DUDp3giCeUFhdSuuQrqQlSeEIJ8GNDDwryVjVKhbsiHQJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaCEC5Xp63LAY2PsxVOgMFLl6UJlLQjVoQeqfwTFwYUQA5jciHgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaED3+jgd4gs7RZxuQmxiE9QXUwjySCpE4zKEIyYk1Nr9VzKZvzrxarMCxWJjY6N9RIcH9+eh6+oGR7zNSGIDuyumAAEDCLj5AgAAAAAAAQQiUSDLH766UNBuv/tpC9qdX/Oc4vrbT87clmMu/xukHMDAewEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "13f106b64b57b5b1376f9f6c47190f40cc06b3bedcb8aaba97850ab3d7f67d02", + "prevout_index": 0, + "prevout_scriptpubkey": "5120c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "amount": 100000, + "witness_utxo": "a086010000000000225120c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "sequence": 4294967294, + "signed": false + }, + { + "input_index": 1, + "private_key": "586b0810bbc3c7266971aeb7f8778b41707e092a1aeaf0d953b211f956e35541", + "public_key": "0243a0d40e9de0882794161752bae42ba90952784209f063430f0af256354a85bb", + "prevout_txid": "2b9037ff41dd174e5522f0f4045c3f40436232af2600d4da5c9ea13cc3cab4b5", + "prevout_index": 0, + "prevout_scriptpubkey": "512043a0d40e9de0882794161752bae42ba90952784209f063430f0af256354a85bb", + "amount": 10000, + "witness_utxo": "102700000000000022512043a0d40e9de0882794161752bae42ba90952784209f063430f0af256354a85bb", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + }, + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "02e57a7adcb018d8fb3154e80c14b97a50994b42356841ea9fc13170614400e637", + "dleq_proof": "f7fa381de20b3b459c6e426c6213d4175308f2482a44e33284232624d4dafd573299bf3af16ab302c5626363a37d448707f7e7a1ebea0647bccd486203bb2ba6", + "input_index": 1 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 195000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "script": "5120cb1fbeba50d06ebffb690bda9d5ff39ce2fadb4fcedc96632eff1ba41cc0c07b" + } + ] + } + }, + { + "description": "in progress: one P2TR input / one sp output with no ECDH shares when PSBT_OUT_SCRIPT field is not set", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEBAQYBAAABDiAT8Qa2S1e1sTdvn2xHGQ9AzAazvty4qrqXhQqz1/Z9AgEPBAAAAAABASughgEAAAAAACJRIMgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/ARAE/v///wEXIMgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/AAEDCBhzAQAAAAAAAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA2HhseneXkLLIAf3ylS54NV+0Tk4+tVtPxnldROo/OA5AA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "13f106b64b57b5b1376f9f6c47190f40cc06b3bedcb8aaba97850ab3d7f67d02", + "prevout_index": 0, + "prevout_scriptpubkey": "5120c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "amount": 100000, + "witness_utxo": "a086010000000000225120c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [], + "outputs": [ + { + "output_index": 0, + "amount": 95000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039" + } + ] + } + }, + { + "description": "in progress: two inputs / one sp output, input 1 missing ECDH share when PSBT_OUT_SCRIPT field is not set", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQ4gvp1lyDkV+wkkirvfyoyj7ubONQxVT2Xo1Sbl31J6tSIBDwQAAAAAAQEf8EkCAAAAAAAWABRGM9WcrRNDR5ly4MmWkh0KXa22XSIGAkOg1A6d4IgnlBYXUrrkK6kJUnhCCfBjQw8K8lY1SoW7CAAAAIABAAAAARAE/v///wABAwhADQMAAAAAAAEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": false + }, + { + "input_index": 1, + "private_key": "586b0810bbc3c7266971aeb7f8778b41707e092a1aeaf0d953b211f956e35541", + "public_key": "0243a0d40e9de0882794161752bae42ba90952784209f063430f0af256354a85bb", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "00144633d59cad1343479972e0c996921d0a5dadb65d", + "amount": 150000, + "witness_utxo": "f0490200000000001600144633d59cad1343479972e0c996921d0a5dadb65d", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe068", + "ecdh_share": "03eca4ff11b728e2e0f60ce6222943a6ff55b9d95f627bf9a99d084bc872d50a5b", + "dleq_proof": "8a13b3985545f72bd6e89714aeb909b3e354a842a9bb8b56cd0eded21df8a199598b31228a49e0bd7e95ce1053f7c5b28acb543a68707600e3ce89822ee32021", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 200000, + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039" + } + ] + } + }, + { + "description": "in progress: one input / two sp outputs, input 0 missing ECDH share for output 0 when PSBT_OUT_SCRIPT field is not set", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQECAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR9ADQMAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdA1LXjEE5ADKtkYFqaX/HQNjrkJzwTXCIUmSwUfI4XiXsIQMJH9SAORd/RNe3npYWiEnGLrP4eADEZ2fFCo84+Y79FSIeA1LXjEE5ADKtkYFqaX/HQNjrkJzwTXCIUmSwUfI4XiXsQL4acjrvVeiYG5lZmpvBKzj4froBWMxwZokPImnK3kY7dzQal9KScZZNAcAkHMMi/VOQwpuZFg+CbzKTZYmMvwIAAQMIkF8BAAAAAAABCUIDZR0sBz/LAqTYLdpT8dUB13oDUGZnmFRkc7ISa2zRp94D8nDgpWMhg7arAQdOh35oANsA9cfF0vr7to+cKNSFRSQAAQMIkF8BAAAAAAABBCJRIENYHfbIefmqrcU2YN6xVrnxIr0a7KczIDvOh/zx84buAQlCA1LXjEE5ADKtkYFqaX/HQNjrkJzwTXCIUmSwUfI4XiXsAuDEwYe3IV+ZfISFp1tFkbbChHe5WhEjuZaSLTFUhgv4AA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 200000, + "witness_utxo": "400d030000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": false + } + ], + "sp_proofs": [ + { + "scan_key": "0352d78c41390032ad91816a697fc740d8eb909cf04d70885264b051f2385e25ec", + "ecdh_share": "03091fd48039177f44d7b79e96168849c62eb3f87800c46767c50a8f38f98efd15", + "dleq_proof": "be1a723aef55e8981b99599a9bc12b38f87eba0158cc7066890f2269cade463b77341a97d29271964d01c0241cc322fd5390c29b99160f826f329365898cbf02", + "input_index": 0 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 90000, + "sp_v0_info": "03651d2c073fcb02a4d82dda53f1d501d77a0350666798546473b2126b6cd1a7de03f270e0a5632183b6ab01074e877e6800db00f5c7c5d2fafbb68f9c28d4854524" + }, + { + "output_index": 1, + "amount": 90000, + "sp_v0_info": "0352d78c41390032ad91816a697fc740d8eb909cf04d70885264b051f2385e25ec02e0c4c187b7215f997c8485a75b4591b6c28477b95a1123b996922d3154860bf8", + "script": "512043581df6c879f9aaadc53660deb156b9f122bd1aeca733203bce87fcf1f386ee" + } + ] + } + }, + { + "description": "in progress: large PSBT with nine mixed inputs / six outputs - some inputs signed", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQkBBQEGAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9IMEUCIQCLNThMVlDzsjgXC0oPt/Sorr8CiDHHC50NMY3FUceJxgIgMZmwKWN19OdCfS+4KMjLfkHblItuYaaIKCeY8x+RIogBAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdAqnx+sLMRXaE8z3pAHP+INIslHySFjuGKxTQq6HfRxUQIQP70XPZxI1YxQIkJtBSeLyhe5qOIoJmaQGhsAueBLeZSSIdAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oIQMKGLm7QZm4kzDrF/juWseG4IU9zS88zfF0S5fOTt4FPiIdA47TIg5zNE+R4tMzkePhaR2zna8G5CN+GX7Q0Ae/JyhsIQNzctF59PYo2oLMvU5CF72wgJ8UF8mr6qNfqAXUitZVTCIeAqnx+sLMRXaE8z3pAHP+INIslHySFjuGKxTQq6HfRxUQQMBdpBBIfPrsN4/r5q9QBeOubIYjaBiw5VshQBfrUHPt70AXlXW13NqpBjSRrIuD/QYk1Bd6TS3UmUxGQNa4zN4iHgMzYdP/cvNOa056e++W5bgVHmapea3OdTw5xYPL5XZtKEAAE4PUZhvocl7ykEj0jJEfifXzQxfXwMCsxg72XxtSv/96ltgzm2Jd+Dq6tXqic+3NmjXHlNrLschhFxuyZwdfIh4DjtMiDnM0T5Hi0zOR4+FpHbOdrwbkI34ZftDQB78nKGxApoctTrEZ/ir3CVP3UqIiNrVKO1f3DlEfEL2LQv9Wtaz2NlCTxIDqnxN3Z4SUW1kxisKSfZFariRM+2g/QYijpwABDiC+nWXIORX7CSSKu9/KjKPu5s41DFVPZejVJuXfUnq1IgEPBAAAAAABAR+ghgEAAAAAABYAFBOTUBF8V3lBqYWAK7nt78ETkyvHIgIC9bWfpeSSIh6/Vbp4rUQmBb6ulRZroeujJQ0Luqx+LtxHMEQCIGBe6cE9y5EoaPHeTsHHUaxLRis49eXxBj+EHthVABbpAiBHqaafS5jOmNbyz3jQjoWlVWhRC3tc+65oAn9FjH5+BQEBAwQBAAAAIgYC9bWfpeSSIh6/Vbp4rUQmBb6ulRZroeujJQ0Luqx+LtwIAAAAgAEAAAABEAT+////Ih0CqfH6wsxFdoTzPekAc/4g0iyUfJIWO4YrFNCrod9HFRAhA6aomxuYaplVM+F77WRQC1QbsNc5Bx7+KuK377CgymvPIh0DM2HT/3LzTmtOenvvluW4FR5mqXmtznU8OcWDy+V2bSghAvaue4qsmg+cnwTqtIQGIXLfLjjZ8vbsMbQEu1ooaqvwIh0DjtMiDnM0T5Hi0zOR4+FpHbOdrwbkI34ZftDQB78nKGwhArHT7S+kvDFqCOsUhfp17X3VTKeprhjQxKF9qT8ww4XVIh4CqfH6wsxFdoTzPekAc/4g0iyUfJIWO4YrFNCrod9HFRBAw0pqKUC76ac1bBzG/+NY3URB21iJfLqZwhLrdShefaHsoRx5LJejgEkzRasiTenZTCs27oUmriNSgXGjIx3nMyIeAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oQDNKztgQbrqkKH2spWzKJnlfLSpzl4TPa3NNozcHV/SZ4SDA5AvkBDbNdMTvJpBXBvUvp/pujnZFHoWDeDBRqEMiHgOO0yIOczRPkeLTM5Hj4Wkds52vBuQjfhl+0NAHvycobEDYQaNzrSiWuXK4z+oWNP8Uow+A2Ez9bvGIkzl7jzRsgFGcmxYaciRNDptk5UNsmM6d1OF6Byj8mXtce885zstoAAEOINBBx0LKNkRy0aGMeo6uB+bpBKnqG4XxmFJfuD7yduNDAQ8EAAAAAAEBH6CGAQAAAAAAFgAUIwf8d0yhdLQq2oFLwubkRP89gwwiAgJlXvXa8F0o0cpzuo4hEwDbK3BBZzcYPg+ow1XuTBy6xkcwRAIgKTOk1x6k+7U8mzjYjLK+bqIQKQBrzEx5rJgFGKrIYxkCICj9GcotjGhqD9CjsK6gmpqVgMw4oisFIgS+RXXwNa0/AQEDBAEAAAAiBgJlXvXa8F0o0cpzuo4hEwDbK3BBZzcYPg+ow1XuTBy6xggAAACAAgAAAAEQBP7///8iHQKp8frCzEV2hPM96QBz/iDSLJR8khY7hisU0Kuh30cVECED33Bv8m1nL+UMb1aiyh5xdOdQVckgu9M34afWvygm9twiHQMzYdP/cvNOa056e++W5bgVHmapea3OdTw5xYPL5XZtKCEDv7hiU765b1i4xGnkQs/Pn3ilQTqVQjvo5oZ2Ty+nvT8iHQOO0yIOczRPkeLTM5Hj4Wkds52vBuQjfhl+0NAHvycobCEDhgSqjP8wnd30mx82b4nCaWFlh/xB22CFQPlDFTb2ZG8iHgKp8frCzEV2hPM96QBz/iDSLJR8khY7hisU0Kuh30cVEED9LowiubZy07ueJtS22i3D8SVFWpaf2OKywbp5EHatcbOMAfkhjD1ndv2JXS8wZ0hJltheJoOQwrQz5sSyGJHxIh4DM2HT/3LzTmtOenvvluW4FR5mqXmtznU8OcWDy+V2bShAUt/78AlfKbHeJMnWjVSdHFS85pAEVP43/RD1yQPf7oX3utinKtqOUs2QkvUdnpC1p+TzbKBN+Y53+60TRzBbxyIeA47TIg5zNE+R4tMzkePhaR2zna8G5CN+GX7Q0Ae/JyhsQJPh5dSIi8HP4C7s+p3qFE6thPI6EPwaMBEDweVjk1J7Y0F8bEpa9FNZ/4tuF5r3OXjk1i6GLu3Soob1J45Ms0AAAQ4gUiYs1dQCvzJhbQf8Ayhqj3/nivesQR3/9BZV3cEBHm8BDwQAAAAAAQEfoIYBAAAAAAAWABRwGLzpZSiNi/DM8+DsSLbx27/lSCICAwOg9FKNJk6OlSVmNLlEFOJ0j3KWJwT+V+iwosxE0jg9SDBFAiEA2yW73RvPhQMJZj64j7uw62ETCVeF1/bVM42du8cFAycCIHmV0Cb1bqmeBz39CnlsbnT8Ye0I7NJGcM7zQNrVCueaAQEDBAEAAAAiBgMDoPRSjSZOjpUlZjS5RBTidI9ylicE/lfosKLMRNI4PQgAAACAAwAAAAEQBP7///8iHQKp8frCzEV2hPM96QBz/iDSLJR8khY7hisU0Kuh30cVECEDgCmZjHPCsTnbdVV+wwEexl7NISRHljAGVxhSo+kiicwiHQMzYdP/cvNOa056e++W5bgVHmapea3OdTw5xYPL5XZtKCECmAuscmThS/ggzENbaaeN9ybBd+sCbxuBtS3oEScvWOsiHQOO0yIOczRPkeLTM5Hj4Wkds52vBuQjfhl+0NAHvycobCEDon2fH6U+Q/cINaaZCVS3ijX3RwZve4K0qeqC6LteBqYiHgKp8frCzEV2hPM96QBz/iDSLJR8khY7hisU0Kuh30cVEEA4HOPBrJ/H9qUVNkViUphHaOj70BQHAjn0yxOxZ5PHTMXtZ3yKfqzBc+mI01Oj8onH4hQdt6S0FFxUmeyDfzauIh4DM2HT/3LzTmtOenvvluW4FR5mqXmtznU8OcWDy+V2bShAOOA0Y7ZFDb0FEGWoEqbyYxCH95UeRqyE5m2oEXVXF8CTGA5NSYJ7jAl8pwk/6S05P5Wl162EmkGALcTkVYq96SIeA47TIg5zNE+R4tMzkePhaR2zna8G5CN+GX7Q0Ae/JyhsQHMqoJV6nbLHWeOayl0BZ+8T0qNKQ8gS6cydcS1Fj3sihdp1EesW9h+WANIL5OlN/dK8OuN/2UP8NJR9/D/cZ5cAAQ4g4pjq8k/hymi0Hktrg8w9kc7ZnG541bmSz7FXcbVFcGQBDwQAAAAAAQEfoIYBAAAAAAAWABSTMrsCDLA9SetNBHEsHMCOKwbY2CICA6iRG+taSP4RnLAVCIzww6EYzwJnckSJctG3DHZKxZQSRzBEAiBr1hL2RzkhB3F8V6+V7oRzl08sMjurcHnoGRbsuQIcjgIgWlToLfcW0eQKehOGkN9w6tSjzILOEImugKMj+1/ui1UBAQMEAQAAACIGA6iRG+taSP4RnLAVCIzww6EYzwJnckSJctG3DHZKxZQSCAAAAIAEAAAAARAE/v///yIdAqnx+sLMRXaE8z3pAHP+INIslHySFjuGKxTQq6HfRxUQIQK+9HOMbzJ/FySxQHmIlZzfK9GSEIlL/QQ1NbYROCcIGCIdAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oIQJ+t8HEr81+Yqtq09K6JzOjdFIgZB+RkD6/57zF29xrXSIdA47TIg5zNE+R4tMzkePhaR2zna8G5CN+GX7Q0Ae/JyhsIQPKiLzbbR9SkekMddPaYBqL0pmR6rv3+UsvOUg+OFjJoSIeAqnx+sLMRXaE8z3pAHP+INIslHySFjuGKxTQq6HfRxUQQApYSScQKCC3tsrWvPsDGiOjcxKth9I/HLWoNZvi1f85tqKi/jfNDVPuNH8T5rNSV2my54nwaZy5hViOrC2vEN0iHgMzYdP/cvNOa056e++W5bgVHmapea3OdTw5xYPL5XZtKEB3sVcjNtm9Ti9zQR649qliNaEw9ETL6wekifRHkh43SJ1x/T2yigu6o1uIw3t1eezb9s4WOHANuB38hr95zRvnIh4DjtMiDnM0T5Hi0zOR4+FpHbOdrwbkI34ZftDQB78nKGxAXQ3oon8NUogCzzNUoa9Qe88P7UGdpkHJwIB811Tgevh5C1EccF1fv2MwWrIq/gSX+s8HC1r+r87jppAFR3Z3VgABDiAXMIM+y1vgE2LXFJUiYESEIW4cexjPMT0c8VQ++F3ztgEPBAAAAAABASughgEAAAAAACJRINFVcTxSDOLYJ2FkIV1lmrYjxfcwb4ZSwXVuJoM+iG4BAQMEAQAAAAEQBP7///8BFyDRVXE8Ugzi2CdhZCFdZZq2I8X3MG+GUsF1biaDPohuASIdAqnx+sLMRXaE8z3pAHP+INIslHySFjuGKxTQq6HfRxUQIQJPKhWksVprePCmMvx/3xEpKfZL1N0U9npjz0+48yWiBCIdAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oIQOAK2mzYNpFhVErHPlnsYQBAibXXOW3Yud6ASi8sGjMuyIdA47TIg5zNE+R4tMzkePhaR2zna8G5CN+GX7Q0Ae/JyhsIQJAdhVQXsveqIExJQTu9vRoGiYxbvc9M1N6f9eT9GWOqSIeAqnx+sLMRXaE8z3pAHP+INIslHySFjuGKxTQq6HfRxUQQI8iPeJ5ht2HkqA9nVtmIyPoaXgw66u2c2OIH+cshkgq5hoJJo0IO4NUz8K601wUjuspGvYi3UYGNGBjMq8bgpciHgMzYdP/cvNOa056e++W5bgVHmapea3OdTw5xYPL5XZtKEAQOuBcEOQymDdsP+4dxfDQQ4n08gr0k0bUjq0yTWFdbyj8oKMYRFEw6laoYgGbLeDCY32zxBjAsHS1hUiuiyDJIh4DjtMiDnM0T5Hi0zOR4+FpHbOdrwbkI34ZftDQB78nKGxA8FWZBzhUTQmbFZKQ5/VELLkSJiYBdKqdjByDELfLAc4rlMxdK4/fgWZ+Avt5bbDlwnh33g1BoqDnCbAURbrylgABDiDpNTLg7aOcTTnaLT+R0UUpvENfwu+5GW4l+6rEtOibgQEPBAAAAAABASughgEAAAAAACJRINAOFftian7+6FxbzmdtY2tgSJFOmjSs4LTLxEvJSAd6AQMEAQAAAAEQBP7///8BFyDQDhX7Ymp+/uhcW85nbWNrYEiRTpo0rOC0y8RLyUgHeiIdAqnx+sLMRXaE8z3pAHP+INIslHySFjuGKxTQq6HfRxUQIQIVP83+lq5wNj/RVCZG0wnCEyuDJVQOPdf4jjf5eWB5piIdAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oIQIPqjoMMa52ledpVG4wqngqvTH7aBuXChOqNcwh39E4ACIdA47TIg5zNE+R4tMzkePhaR2zna8G5CN+GX7Q0Ae/JyhsIQPzE/j0desB487LwOqsL3JtKQMsb/fDrWzDv4uxAYZSfCIeAqnx+sLMRXaE8z3pAHP+INIslHySFjuGKxTQq6HfRxUQQEMg6fYP08Km9zeqzlRsI7DH126EFZwK0onB30wPilAOUSCfKW+dfSI6HSkr1B0mb6rLG+hDvs9LcG6xXbpwKAYiHgMzYdP/cvNOa056e++W5bgVHmapea3OdTw5xYPL5XZtKEDkB0puyJPquAbV35p07SV+eF/zeGQ9uqeTPHgTp7X2yQyoK2JTnzmzjCxy/lz4HCKl5kH2c2RPbEhrPbV3RkRjIh4DjtMiDnM0T5Hi0zOR4+FpHbOdrwbkI34ZftDQB78nKGxAOynhLPaT8nITn3RFLyvMwrzQdDLc6PsQc1TBRYoSpHyFRNLWyh4zvOBXi2UIz4pZNGc/n8Q1pfhKIqVLWty8/AABDiC2Nou8Mwzd/p4lUO2338Fa50ysm8x9ln/oHm8n+Fl7HQEPBAAAAAABAFMCAAAAAf6hTz0WmysEANsUmWIhsxGCu56GyhaekbnHvZP5xz6IAAAAAAD/////AaCGAQAAAAAAF6kUDfEBNX9z5mWFAM9W86Ey17uK+92HAAAAAAEBIKCGAQAAAAAAF6kUDfEBNX9z5mWFAM9W86Ey17uK+92HIgICBQDxZ89mnn2sOeauzaHyjGa3jscJfu/vJdVPe+eF5/ZHMEQCIA134+usWENCsMr5fopp9YcHxKsAzK2+hsRKoYe1Zfv/AiBubgVutYtmJeL++r0axbRapqEto5ypCKEaIcY396JcMAEBAwQBAAAAAQQWABTGvIEF7G22DE2lOsNc2iBAaekOiiIGAgUA8WfPZp59rDnmrs2h8oxmt47HCX7v7yXVT3vnhef2CAAAAIAHAAAAARAE/v///yIdAqnx+sLMRXaE8z3pAHP+INIslHySFjuGKxTQq6HfRxUQIQL+eEHN37Rv2yypveVslVjC0bNhIWZWWQgCJOJAMUOECCIdAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oIQJvAnsjWnWTyolDxte3432S6rM5YX9/DDRBBsWN0MvGqiIdA47TIg5zNE+R4tMzkePhaR2zna8G5CN+GX7Q0Ae/JyhsIQIGEUUMbBlE7N7LePYYFsdn+6Q2jyH36bFpd5x28jz0zCIeAqnx+sLMRXaE8z3pAHP+INIslHySFjuGKxTQq6HfRxUQQIgHJVFmLD7r7lRfGIuNr8aClzovIY58oH/I5C/1ehE0YwRJ/XAxd9ehLQyc7QJIRNwbng7K1nWH3VxHlDGzahoiHgMzYdP/cvNOa056e++W5bgVHmapea3OdTw5xYPL5XZtKEAWtYFI97r/eD5DEPGFUD/pJBfbpDuWbIOiGEMiyx8gQky50ECuahrqTnREvceGJYqXVkTOGgGVVkwrn8PGBqKSIh4DjtMiDnM0T5Hi0zOR4+FpHbOdrwbkI34ZftDQB78nKGxASrNr3WnrHNDUsK6uQD3OTbUijFujopHccl3iM4LRc20mXXvDP4NpClylYJcZ8Z6BlS1MjV4oj9DLjGQREEuAowABDiADvOulN51z2ur+Z3lysD3PfvMLfxDqqDrQS8C/ANiy9wEPBAAAAAABAFUCAAAAASo0rBsNt3qBmMHikALMR7v81u2DCWAwTSyaOrVISGtdAAAAAAD/////AaCGAQAAAAAAGXapFFab8xHmijg3Z7Q/gwQt+r5gCT7eiKwAAAAAIgIDGZ0vFV8y67FnrrDMym5a+3NAiY3xt6uZNlAPbUORkCpHMEQCIGIn/SRuyIcF32NUW0i+PRLD1KVITUtOxDRAmsuEETxaAiA8y8m0eI77f7hg2oVYqUx+TEJa11XoaOLY8Fz7RhJflwEBAwQBAAAAIgYDGZ0vFV8y67FnrrDMym5a+3NAiY3xt6uZNlAPbUORkCoIAAAAgAgAAAABEAT+////Ih0CqfH6wsxFdoTzPekAc/4g0iyUfJIWO4YrFNCrod9HFRAhAvPBdbCLIHY3YWqnTme6rBoYB8lLznCAD3b1sXtSwAgtIh0DM2HT/3LzTmtOenvvluW4FR5mqXmtznU8OcWDy+V2bSghAvoc7ZMlmKJ/ooQQuVXqZ0ebWkAztp8smeTiZ2e7oKVWIh0DjtMiDnM0T5Hi0zOR4+FpHbOdrwbkI34ZftDQB78nKGwhAwDg/RcQ3FatfBc6qRQdcxV3AJvD3/y6hXakjnerNNlwIh4CqfH6wsxFdoTzPekAc/4g0iyUfJIWO4YrFNCrod9HFRBAmSJWWP7eeowkN+s4Ib3J03AKvIlui23O+E4pNHt4vCsD5tozg25PQHZzkMNmRM4Bazax1UaWNUD6UdCwQH221yIeAzNh0/9y805rTnp775bluBUeZql5rc51PDnFg8vldm0oQKCUjFF9cJdsDjnKZ6vxnnIXBA2+Fyu5ZTQTPm/6OUHBZZQW9UBmK3Vh77cAZLTXGpsXtjvFY/Yw20Qi8nPg3xIiHgOO0yIOczRPkeLTM5Hj4Wkds52vBuQjfhl+0NAHvycobEDuCqplynPMxUezHCt+0q4xpPTM2upz8q3SRjDWCO3prqXYCGiBhuvKQrh376sT815g8hDU9iBgL4UPwHMcIB6RAAEDCDB1AAAAAAAAAQQiUSAa+l2IGIzvm5Gz8QArEeD56UrTmBl4xNEqBgVWTAgSMwEJQgMzYdP/cvNOa056e++W5bgVHmapea3OdTw5xYPL5XZtKAMSZlgePoQcu5TeYupzVYOiMsTIiPpu3vLsL4cHifwd1AABAwgwdQAAAAAAAAEEIlEgyr9+Sg3kJOybut1/ckLM52c+FwH0nWoZqXBV2jMrEyQBCUIDM2HT/3LzTmtOenvvluW4FR5mqXmtznU8OcWDy+V2bSgDEmZYHj6EHLuU3mLqc1WDojLEyIj6bt7y7C+HB4n8HdQAAQMIIE4AAAAAAAABBCJRIEqtyeClkNIeDBnLICTycUD5sZwqEoLVDqfF9F6wP9SiAQlCAqnx+sLMRXaE8z3pAHP+INIslHySFjuGKxTQq6HfRxUQAkpI124g1AvPXy4aXD/+s9wqizuI15pIbVTJ2eqo+hiOAAEDCCBOAAAAAAAAAQQiUSAvpDcA6h0o/XrU3LMys+XloNjLVCWPZmHE3gN31ygE5QEJQgKp8frCzEV2hPM96QBz/iDSLJR8khY7hisU0Kuh30cVEAJKSNduINQLz18uGlw//rPcKos7iNeaSG1UydnqqPoYjgABAwgQJwAAAAAAAAEEIlEgUZhvruMZQ2tRs4QJwbMA9DluKzLjzJoSripL4TXab24BCUIDjtMiDnM0T5Hi0zOR4+FpHbOdrwbkI34ZftDQB78nKGwD+BRzhEyddMkh/xelr0ELYzc0jXr7/3OJG8XLDLJMsMAAAQMIUMMAAAAAAAABBCJRIG/FBvgFKp9aiqm5kNXplPSV3e1tR7rETKrNrdjYmRzQAA==", + "supplementary": { + "inputs": [ + { + "input_index": 0, + "private_key": "7e31eeeb1aa2597b6d63b357541461d75ddae76b7603d24619f5ebed9e88ec31", + "public_key": "02c817bb7521afc35ea96f3bfb270e6eb50ddffa5560627b961fec00f2996508bf", + "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", + "prevout_index": 0, + "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 100000, + "witness_utxo": "a086010000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 1, + "private_key": "295c2eedddd8331d20b5d4cf9e69bb523ed85cb0bf35ab12e04fea66fe6d4a4a", + "public_key": "02f5b59fa5e492221ebf55ba78ad442605beae95166ba1eba3250d0bbaac7e2edc", + "prevout_txid": "be9d65c83915fb09248abbdfca8ca3eee6ce350c554f65e8d526e5df527ab522", + "prevout_index": 0, + "prevout_scriptpubkey": "0014139350117c577941a985802bb9edefc113932bc7", + "amount": 100000, + "witness_utxo": "a086010000000000160014139350117c577941a985802bb9edefc113932bc7", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 2, + "private_key": "e6db4a04e2fc29745ea1a9dcf1fed7449c8228ffe0503d8171e64126d5399800", + "public_key": "02655ef5daf05d28d1ca73ba8e211300db2b70416737183e0fa8c355ee4c1cbac6", + "prevout_txid": "d041c742ca364472d1a18c7a8eae07e6e904a9ea1b85f198525fb83ef276e343", + "prevout_index": 0, + "prevout_scriptpubkey": "00142307fc774ca174b42ada814bc2e6e444ff3d830c", + "amount": 100000, + "witness_utxo": "a0860100000000001600142307fc774ca174b42ada814bc2e6e444ff3d830c", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 3, + "private_key": "f5208ed67b0019ba4e306aff029d247931e0dfacd1fe6e69091bc6bc70a42619", + "public_key": "0303a0f4528d264e8e95256634b94414e2748f72962704fe57e8b0a2cc44d2383d", + "prevout_txid": "52262cd5d402bf32616d07fc03286a8f7fe78af7ac411dfff41655ddc1011e6f", + "prevout_index": 0, + "prevout_scriptpubkey": "00147018bce965288d8bf0ccf3e0ec48b6f1dbbfe548", + "amount": 100000, + "witness_utxo": "a0860100000000001600147018bce965288d8bf0ccf3e0ec48b6f1dbbfe548", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 4, + "private_key": "0c19c34dc1005feb5ca60104d20472f7f2f97bd4d521264618196bf71155b20a", + "public_key": "03a8911beb5a48fe119cb015088cf0c3a118cf026772448972d1b70c764ac59412", + "prevout_txid": "e298eaf24fe1ca68b41e4b6b83cc3d91ced99c6e78d5b992cfb15771b5457064", + "prevout_index": 0, + "prevout_scriptpubkey": "00149332bb020cb03d49eb4d04712c1cc08e2b06d8d8", + "amount": 100000, + "witness_utxo": "a0860100000000001600149332bb020cb03d49eb4d04712c1cc08e2b06d8d8", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 5, + "private_key": "5aaa151cbdeb566911784d166b01ce57f35993b98f96846efec1cfefcc0d8f56", + "public_key": "02d155713c520ce2d8276164215d659ab623c5f7306f8652c1756e26833e886e01", + "prevout_txid": "1730833ecb5be01362d7149522604484216e1c7b18cf313d1cf1543ef85df3b6", + "prevout_index": 0, + "prevout_scriptpubkey": "5120d155713c520ce2d8276164215d659ab623c5f7306f8652c1756e26833e886e01", + "amount": 100000, + "witness_utxo": "a086010000000000225120d155713c520ce2d8276164215d659ab623c5f7306f8652c1756e26833e886e01", + "sequence": 4294967294, + "signed": false + }, + { + "input_index": 6, + "private_key": "d92c1506167b3a00a21f4d90d7b5ccb4713c00194aa66f9c7deb162c467f9643", + "public_key": "02d00e15fb626a7efee85c5bce676d636b6048914e9a34ace0b4cbc44bc948077a", + "prevout_txid": "e93532e0eda39c4d39da2d3f91d14529bc435fc2efb9196e25fbaac4b4e89b81", + "prevout_index": 0, + "prevout_scriptpubkey": "5120d00e15fb626a7efee85c5bce676d636b6048914e9a34ace0b4cbc44bc948077a", + "amount": 100000, + "witness_utxo": "a086010000000000225120d00e15fb626a7efee85c5bce676d636b6048914e9a34ace0b4cbc44bc948077a", + "sequence": 4294967294, + "signed": false + }, + { + "input_index": 7, + "private_key": "d8bc8d0e1233a97133f3573d6dceb6736e8b6d2e4839925145022ea3efdad8f8", + "public_key": "020500f167cf669e7dac39e6aecda1f28c66b78ec7097eefef25d54f7be785e7f6", + "prevout_txid": "b6368bbc330cddfe9e2550edb7dfc15ae74cac9bcc7d967fe81e6f27f8597b1d", + "prevout_index": 0, + "prevout_scriptpubkey": "a9140df101357f73e6658500cf56f3a132d7bb8afbdd87", + "amount": 100000, + "witness_utxo": "a08601000000000017a9140df101357f73e6658500cf56f3a132d7bb8afbdd87", + "sequence": 4294967294, + "signed": true + }, + { + "input_index": 8, + "private_key": "81e30f5b5d049bdf5c5bea9c3329bf57cc8b473240182a9f2a6eee07435bd54a", + "public_key": "03199d2f155f32ebb167aeb0ccca6e5afb7340898df1b7ab9936500f6d4391902a", + "prevout_txid": "03bceba5379d73daeafe677972b03dcf7ef30b7f10eaa83ad04bc0bf00d8b2f7", + "prevout_index": 0, + "prevout_scriptpubkey": "76a914569bf311e68a383767b43f83042dfabe60093ede88ac", + "amount": 100000, + "non_witness_utxo": "02000000012a34ac1b0db77a8198c1e29002cc47bbfcd6ed830960304d2c9a3ab548486b5d0000000000ffffffff01a0860100000000001976a914569bf311e68a383767b43f83042dfabe60093ede88ac00000000", + "sequence": 4294967294, + "signed": true + } + ], + "sp_proofs": [ + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "030a18b9bb4199b89330eb17f8ee5ac786e0853dcd2f3ccdf1744b97ce4ede053e", + "dleq_proof": "001383d4661be8725ef29048f48c911f89f5f34317d7c0c0acc60ef65f1b52bfff7a96d8339b625df83abab57aa273edcd9a35c794dacbb1c861171bb267075f", + "input_index": 0 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "03fbd173d9c48d58c5022426d05278bca17b9a8e2282666901a1b00b9e04b79949", + "dleq_proof": "c05da410487cfaec378febe6af5005e3ae6c86236818b0e55b214017eb5073edef40179575b5dcdaa9063491ac8b83fd0624d4177a4d2dd4994c4640d6b8ccde", + "input_index": 0 + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "037372d179f4f628da82ccbd4e4217bdb0809f1417c9abeaa35fa805d48ad6554c", + "dleq_proof": "a6872d4eb119fe2af70953f752a22236b54a3b57f70e511f10bd8b42ff56b5acf6365093c480ea9f13776784945b59318ac2927d915aae244cfb683f4188a3a7", + "input_index": 0 + }, + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "02f6ae7b8aac9a0f9c9f04eab484062172df2e38d9f2f6ec31b404bb5a286aabf0", + "dleq_proof": "334aced8106ebaa4287daca56cca26795f2d2a739784cf6b734da3370757f499e120c0e40be40436cd74c4ef26905706f52fa7fa6e8e76451e8583783051a843", + "input_index": 1 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "03a6a89b1b986a995533e17bed64500b541bb0d739071efe2ae2b7efb0a0ca6bcf", + "dleq_proof": "c34a6a2940bbe9a7356c1cc6ffe358dd4441db58897cba99c212eb75285e7da1eca11c792c97a380493345ab224de9d94c2b36ee8526ae23528171a3231de733", + "input_index": 1 + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "02b1d3ed2fa4bc316a08eb1485fa75ed7dd54ca7a9ae18d0c4a17da93f30c385d5", + "dleq_proof": "d841a373ad2896b972b8cfea1634ff14a30f80d84cfd6ef18893397b8f346c80519c9b161a72244d0e9b64e5436c98ce9dd4e17a0728fc997b5c7bcf39cecb68", + "input_index": 1 + }, + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "03bfb86253beb96f58b8c469e442cfcf9f78a5413a95423be8e686764f2fa7bd3f", + "dleq_proof": "52dffbf0095f29b1de24c9d68d549d1c54bce6900454fe37fd10f5c903dfee85f7bad8a72ada8e52cd9092f51d9e90b5a7e4f36ca04df98e77fbad1347305bc7", + "input_index": 2 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "03df706ff26d672fe50c6f56a2ca1e7174e75055c920bbd337e1a7d6bf2826f6dc", + "dleq_proof": "fd2e8c22b9b672d3bb9e26d4b6da2dc3f125455a969fd8e2b2c1ba791076ad71b38c01f9218c3d6776fd895d2f3067484996d85e268390c2b433e6c4b21891f1", + "input_index": 2 + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "038604aa8cff309dddf49b1f366f89c269616587fc41db608540f9431536f6646f", + "dleq_proof": "93e1e5d4888bc1cfe02eecfa9dea144ead84f23a10fc1a301103c1e56393527b63417c6c4a5af45359ff8b6e179af73978e4d62e862eedd2a286f5278e4cb340", + "input_index": 2 + }, + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "02980bac7264e14bf820cc435b69a78df726c177eb026f1b81b52de811272f58eb", + "dleq_proof": "38e03463b6450dbd051065a812a6f2631087f7951e46ac84e66da811755717c093180e4d49827b8c097ca7093fe92d393f95a5d7ad849a41802dc4e4558abde9", + "input_index": 3 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "038029998c73c2b139db75557ec3011ec65ecd212447963006571852a3e92289cc", + "dleq_proof": "381ce3c1ac9fc7f6a51536456252984768e8fbd014070239f4cb13b16793c74cc5ed677c8a7eacc173e988d353a3f289c7e2141db7a4b4145c5499ec837f36ae", + "input_index": 3 + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "03a27d9f1fa53e43f70835a6990954b78a35f747066f7b82b4a9ea82e8bb5e06a6", + "dleq_proof": "732aa0957a9db2c759e39aca5d0167ef13d2a34a43c812e9cc9d712d458f7b2285da7511eb16f61f9600d20be4e94dfdd2bc3ae37fd943fc34947dfc3fdc6797", + "input_index": 3 + }, + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "027eb7c1c4afcd7e62ab6ad3d2ba2733a3745220641f91903ebfe7bcc5dbdc6b5d", + "dleq_proof": "77b1572336d9bd4e2f73411eb8f6a96235a130f444cbeb07a489f447921e37489d71fd3db28a0bbaa35b88c37b7579ecdbf6ce1638700db81dfc86bf79cd1be7", + "input_index": 4 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "02bef4738c6f327f1724b1407988959cdf2bd19210894bfd043535b61138270818", + "dleq_proof": "0a584927102820b7b6cad6bcfb031a23a37312ad87d23f1cb5a8359be2d5ff39b6a2a2fe37cd0d53ee347f13e6b3525769b2e789f0699cb985588eac2daf10dd", + "input_index": 4 + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "03ca88bcdb6d1f5291e90c75d3da601a8bd29991eabbf7f94b2f39483e3858c9a1", + "dleq_proof": "5d0de8a27f0d528802cf3354a1af507bcf0fed419da641c9c0807cd754e07af8790b511c705d5fbf63305ab22afe0497facf070b5afeafcee3a6900547767756", + "input_index": 4 + }, + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "03802b69b360da4585512b1cf967b184010226d75ce5b762e77a0128bcb068ccbb", + "dleq_proof": "103ae05c10e43298376c3fee1dc5f0d04389f4f20af49346d48ead324d615d6f28fca0a318445130ea56a862019b2de0c2637db3c418c0b074b58548ae8b20c9", + "input_index": 5 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "024f2a15a4b15a6b78f0a632fc7fdf112929f64bd4dd14f67a63cf4fb8f325a204", + "dleq_proof": "8f223de27986dd8792a03d9d5b662323e8697830ebabb67363881fe72c86482ae61a09268d083b8354cfc2bad35c148eeb291af622dd460634606332af1b8297", + "input_index": 5 + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "02407615505ecbdea881312504eef6f4681a26316ef73d33537a7fd793f4658ea9", + "dleq_proof": "f055990738544d099b159290e7f5442cb91226260174aa9d8c1c8310b7cb01ce2b94cc5d2b8fdf81667e02fb796db0e5c27877de0d41a2a0e709b01445baf296", + "input_index": 5 + }, + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "020faa3a0c31ae7695e769546e30aa782abd31fb681b970a13aa35cc21dfd13800", + "dleq_proof": "e4074a6ec893eab806d5df9a74ed257e785ff378643dbaa7933c7813a7b5f6c90ca82b62539f39b38c2c72fe5cf81c22a5e641f673644f6c486b3db577464463", + "input_index": 6 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "02153fcdfe96ae70363fd1542646d309c2132b8325540e3dd7f88e37f9796079a6", + "dleq_proof": "4320e9f60fd3c2a6f737aace546c23b0c7d76e84159c0ad289c1df4c0f8a500e51209f296f9d7d223a1d292bd41d266faacb1be843becf4b706eb15dba702806", + "input_index": 6 + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "03f313f8f475eb01e3cecbc0eaac2f726d29032c6ff7c3ad6cc3bf8bb10186527c", + "dleq_proof": "3b29e12cf693f272139f74452f2bccc2bcd07432dce8fb107354c1458a12a47c8544d2d6ca1e33bce0578b6508cf8a5934673f9fc435a5f84a22a54b5adcbcfc", + "input_index": 6 + }, + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "026f027b235a7593ca8943c6d7b7e37d92eab339617f7f0c344106c58dd0cbc6aa", + "dleq_proof": "16b58148f7baff783e4310f185503fe92417dba43b966c83a2184322cb1f20424cb9d040ae6a1aea4e7444bdc786258a975644ce1a0195564c2b9fc3c606a292", + "input_index": 7 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "02fe7841cddfb46fdb2ca9bde56c9558c2d1b36121665659080224e24031438408", + "dleq_proof": "88072551662c3eebee545f188b8dafc682973a2f218e7ca07fc8e42ff57a1134630449fd703177d7a12d0c9ced024844dc1b9e0ecad67587dd5c479431b36a1a", + "input_index": 7 + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "020611450c6c1944ecdecb78f61816c767fba4368f21f7e9b169779c76f23cf4cc", + "dleq_proof": "4ab36bdd69eb1cd0d4b0aeae403dce4db5228c5ba3a291dc725de23382d1736d265d7bc33f83690a5ca5609719f19e81952d4c8d5e288fd0cb8c6411104b80a3", + "input_index": 7 + }, + { + "scan_key": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28", + "ecdh_share": "02fa1ced932598a27fa28410b955ea67479b5a4033b69f2c99e4e26767bba0a556", + "dleq_proof": "a0948c517d70976c0e39ca67abf19e7217040dbe172bb96534133e6ffa3941c1659416f540662b7561efb70064b4d71a9b17b63bc563f630db4422f273e0df12", + "input_index": 8 + }, + { + "scan_key": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510", + "ecdh_share": "02f3c175b08b207637616aa74e67baac1a1807c94bce70800f76f5b17b52c0082d", + "dleq_proof": "99225658fede7a8c2437eb3821bdc9d3700abc896e8b6dcef84e29347b78bc2b03e6da33836e4f40767390c36644ce016b36b1d546963540fa51d0b0407db6d7", + "input_index": 8 + }, + { + "scan_key": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c", + "ecdh_share": "0300e0fd1710dc56ad7c173aa9141d731577009bc3dffcba8576a48e77ab34d970", + "dleq_proof": "ee0aaa65ca73ccc547b31c2b7ed2ae31a4f4ccdaea73f2add24630d608ede9aea5d808688186ebca42b877efab13f35e60f210d4f620602f850fc0731c201e91", + "input_index": 8 + } + ], + "outputs": [ + { + "output_index": 0, + "amount": 30000, + "sp_v0_info": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28031266581e3e841cbb94de62ea735583a232c4c888fa6edef2ec2f870789fc1dd4", + "script": "51201afa5d88188cef9b91b3f1002b11e0f9e94ad3981978c4d12a0605564c081233" + }, + { + "output_index": 1, + "amount": 30000, + "sp_v0_info": "033361d3ff72f34e6b4e7a7bef96e5b8151e66a979adce753c39c583cbe5766d28031266581e3e841cbb94de62ea735583a232c4c888fa6edef2ec2f870789fc1dd4", + "script": "5120cabf7e4a0de424ec9bbadd7f7242cce7673e1701f49d6a19a97055da332b1324" + }, + { + "output_index": 2, + "amount": 20000, + "sp_v0_info": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510024a48d76e20d40bcf5f2e1a5c3ffeb3dc2a8b3b88d79a486d54c9d9eaa8fa188e", + "script": "51204aadc9e0a590d21e0c19cb2024f27140f9b19c2a1282d50ea7c5f45eb03fd4a2" + }, + { + "output_index": 3, + "amount": 20000, + "sp_v0_info": "02a9f1fac2cc457684f33de90073fe20d22c947c92163b862b14d0aba1df471510024a48d76e20d40bcf5f2e1a5c3ffeb3dc2a8b3b88d79a486d54c9d9eaa8fa188e", + "script": "51202fa43700ea1d28fd7ad4dcb332b3e5e5a0d8cb54258f6661c4de0377d72804e5" + }, + { + "output_index": 4, + "amount": 10000, + "sp_v0_info": "038ed3220e73344f91e2d33391e3e1691db39daf06e4237e197ed0d007bf27286c03f81473844c9d74c921ff17a5af410b6337348d7afbff73891bc5cb0cb24cb0c0", + "script": "512051986faee319436b51b38409c1b300f4396e2b32e3cc9a12ae2a4be135da6f6e" + }, + { + "output_index": 5, + "amount": 50000, + "script": "51206fc506f8052a9f5a8aa9b990d5e994f495dded6d47bac44caacdadd8d8991cd0" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/testing/devtest/unit_silentpayments.py b/testing/devtest/unit_silentpayments.py new file mode 100644 index 000000000..71a1c8f7b --- /dev/null +++ b/testing/devtest/unit_silentpayments.py @@ -0,0 +1,608 @@ +# (c) Copyright 2024 by Coinkite Inc. This file is covered by license found in COPYING-CC. +# +# Unit tests for BIP-352/BIP-375 Silent Payments implementation +# Runs inside the simulator via sim_execfile('devtest/unit_silentpayments.py') +# Success = no output; failure = assertion traceback +# +import ngu +from ubinascii import unhexlify as a2b_hex +from uhashlib import sha256 + +from dleq import generate_dleq_proof, verify_dleq_proof +from exceptions import FatalPSBTIssue +from silentpayments import ( + _compute_ecdh_share, + _compute_input_hash, + _combine_pubkeys, + _compute_shared_secret_tweak, + _compute_silent_payment_output_script, + _is_p2pkh, + _is_p2wpkh, + _is_p2tr, + _is_p2sh, + NUMS_H, + SilentPaymentsMixin, +) +from precomp_tag_hash import ( + BIP352_SHARED_SECRET_TAG_H, + BIP352_INPUTS_TAG_H, + BIP352_LABEL_TAG_H, + DLEQ_TAG_AUX_H, + DLEQ_TAG_NONCE_H, + DLEQ_TAG_CHALLENGE_H, +) +from public_constants import ( + PSBT_GLOBAL_SP_ECDH_SHARE, + PSBT_GLOBAL_SP_DLEQ, + PSBT_IN_SP_ECDH_SHARE, + PSBT_IN_SP_DLEQ, + PSBT_OUT_SP_V0_INFO, + PSBT_OUT_SP_V0_LABEL, +) + +# --------------------------------------------------------------------------- +# Constants +# --------------------------------------------------------------------------- + +TEST_PRIVKEY = a2b_hex("a5377d45114b0206f6773e231861ece8c04e13840ab007df6722a3508211c073") +TEST_PRIVKEY2 = a2b_hex("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef") +TEST_SCAN_KEY = a2b_hex("03af606abaa5e29a89b93bf971c21e46dd2797aee31273c47f979a102eb51c3629") +TEST_SPEND_KEY = a2b_hex("02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5") +MY_XFP = 0x12345678 +TEST_DERIV_OURS = (MY_XFP, 44, 0, 0, 0) +FOREIGN_XFP = 0xDEADBEEF +G = ngu.secp256k1.generator() +P2WPKH_SPK = b"\x00\x14" + b"\xab" * 20 + +# --------------------------------------------------------------------------- +# BIP-352 Crypto Primitives +# --------------------------------------------------------------------------- + +# ECDH share computation +ecdh_share = _compute_ecdh_share(TEST_PRIVKEY, TEST_SCAN_KEY) +assert len(ecdh_share) == 33 +assert ecdh_share[0] in (0x02, 0x03) + +# Input hash computation (order-independent) +outpoints = [ + ( + a2b_hex("0000000000000000000000000000000000000000000000000000000000000001"), + b"\x00\x00\x00\x00", + ), + ( + a2b_hex("0000000000000000000000000000000000000000000000000000000000000002"), + b"\x00\x00\x00\x01", + ), +] +spk = a2b_hex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798") +ih = _compute_input_hash(outpoints, spk) +assert isinstance(ih, bytes) and len(ih) == 32 +assert int.from_bytes(ih, "big") > 0 +assert _compute_input_hash(outpoints[::-1], spk) == ih + +# Shared secret tweak (different k -> different tweak) +es = a2b_hex("03ccffacf309a1570d01449966bbc0f876d23ee929e88a68968e0a606e31efcc35") +t0 = _compute_shared_secret_tweak(es, 0) +t1 = _compute_shared_secret_tweak(es, 1) +t2 = _compute_shared_secret_tweak(es, 2) +assert t0 != t1 and t1 != t2 and t0 != t2 +assert isinstance(t0, bytes) + +# --------------------------------------------------------------------------- +# Tagged Hash Constants +# --------------------------------------------------------------------------- + +assert BIP352_SHARED_SECRET_TAG_H == sha256(b"BIP0352/SharedSecret").digest() +assert BIP352_INPUTS_TAG_H == sha256(b"BIP0352/Inputs").digest() +assert BIP352_LABEL_TAG_H == sha256(b"BIP0352/Label").digest() +assert DLEQ_TAG_AUX_H == sha256(b"BIP0374/aux").digest() +assert DLEQ_TAG_NONCE_H == sha256(b"BIP0374/nonce").digest() +assert DLEQ_TAG_CHALLENGE_H == sha256(b"BIP0374/challenge").digest() + +# --------------------------------------------------------------------------- +# PSBT Field Constants (BIP-375) +# --------------------------------------------------------------------------- + +assert PSBT_GLOBAL_SP_ECDH_SHARE == 0x07 +assert PSBT_GLOBAL_SP_DLEQ == 0x08 +assert PSBT_IN_SP_ECDH_SHARE == 0x1D +assert PSBT_IN_SP_DLEQ == 0x1E +assert PSBT_OUT_SP_V0_INFO == 0x09 +assert PSBT_OUT_SP_V0_LABEL == 0x0A + +# --------------------------------------------------------------------------- +# DLEQ Proofs +# --------------------------------------------------------------------------- + +pubkey = ngu.secp256k1.ec_pubkey_tweak_mul(G, TEST_PRIVKEY) + +proof = generate_dleq_proof(TEST_PRIVKEY, TEST_SCAN_KEY) +assert len(proof) == 64 +assert verify_dleq_proof(pubkey, TEST_SCAN_KEY, ecdh_share, proof) + +# Tampered proof rejected +tampered = bytearray(proof) +tampered[0] ^= 0xFF +assert not verify_dleq_proof(pubkey, TEST_SCAN_KEY, ecdh_share, bytes(tampered)) + +# Wrong ECDH share rejected +wrong_ecdh = a2b_hex("02ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") +try: + assert not verify_dleq_proof(pubkey, TEST_SCAN_KEY, wrong_ecdh, proof) +except Exception as e: + assert isinstance(e, ValueError) + +# Deterministic with same aux_rand +p1 = generate_dleq_proof(TEST_PRIVKEY, TEST_SCAN_KEY, b"\x00" * 32) +p2 = generate_dleq_proof(TEST_PRIVKEY, TEST_SCAN_KEY, b"\x00" * 32) +assert p1 == p2 + +# --------------------------------------------------------------------------- +# Address Encoding +# --------------------------------------------------------------------------- + +sk = a2b_hex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798") +spk2 = a2b_hex("02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5") + +addr = ngu.codecs.bip352_encode("sp", sk, spk2) +assert isinstance(addr, str) and addr.startswith("sp1q") and len(addr) == 116 + +addr_tn = ngu.codecs.bip352_encode("tsp", sk, spk2) +assert addr_tn.startswith("tsp1q") and len(addr_tn) == 117 + +assert ngu.codecs.bip352_encode("sp", sk, spk2, 0) == addr + +for v in [0, 1, 15, 30, 31]: + a = ngu.codecs.bip352_encode("sp", sk, spk2, v) + assert isinstance(a, str) and a.startswith("sp1") + +for v in [32, 33, 100, -1, -10]: + try: + ngu.codecs.bip352_encode("sp", sk, spk2, v) + assert False, "Should raise for version %d" % v + except ValueError as e: + assert "version must be 0-31" in str(e) + +# Different keys -> different addresses +sk2 = a2b_hex("02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5") +assert ngu.codecs.bip352_encode("sp", sk2, spk2) != addr + +spk3 = a2b_hex("021b8c93100d35bd448f4646cc4678f278351b439b52b303ea31ec97b6eda4116f") +assert ngu.codecs.bip352_encode("sp", sk, spk3) != addr + +# Invalid key sizes +for bad_hex in [ + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ff", + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16", + "", +]: + try: + bad_key = a2b_hex(bad_hex) if bad_hex else b"" + ngu.codecs.bip352_encode("sp", sk, bad_key) + assert False, "Should raise for bad key" + except ValueError as e: + assert "33 bytes" in str(e) + +# Uncompressed key rejected +uncompressed = a2b_hex( + "04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + "1ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a" +) +try: + ngu.codecs.bip352_encode("sp", sk, uncompressed) + assert False, "Should raise for uncompressed key" +except ValueError as e: + assert "33 bytes" in str(e) + + +# --------------------------------------------------------------------------- +# Mock Infrastructure +# --------------------------------------------------------------------------- + + +class MockInput: + def __init__(self): + self.sp_idxs = None + self.sp_ecdh_shares = None + self.sp_dleq_proofs = None + self.subpaths = None + self.taproot_subpaths = None + self.previous_txid = None + self.prevout_idx = None + self.witness_utxo = None + self.taproot_internal_key = None + self.utxo_spk = None + self.ik_idx = None + self.sighash = None + self.redeem_script = None + + @property + def is_sp_spend(self): + return bool(self.sp_tweak and self.sp_spend_bip32_derivation) + + +class MockOutput: + def __init__(self): + self.sp_v0_info = None + self.sp_v0_label = None + self.script = None + + +class MockPSBT(SilentPaymentsMixin): + def __init__(self): + self.inputs = [] + self.outputs = [] + self.my_xfp = MY_XFP + self.sp_global_ecdh_shares = {} + self.sp_global_dleq_proofs = {} + + def get(self, x): + return x + + def parse_xfp_path(self, coords): + return coords + + def handle_zero_xfp(self, xfp_path, my_xfp, _): + return xfp_path + + def _path_to_privkey(self, xfp_path, sv): + return sv[xfp_path] + + +def _make_eligible_input(pk, deriv, txid, vout_bytes): + inp = MockInput() + inp.utxo_spk = P2WPKH_SPK + inp.subpaths = [(pk, deriv)] + inp.previous_txid = txid + inp.prevout_idx = vout_bytes + inp.sp_idxs = [0] + return inp + + +def _make_test_keypair(): + pk = ngu.secp256k1.ec_pubkey_tweak_mul(G, TEST_PRIVKEY) + es = _compute_ecdh_share(TEST_PRIVKEY, TEST_SCAN_KEY) + dp = generate_dleq_proof(TEST_PRIVKEY, TEST_SCAN_KEY, b"\x00" * 32) + return pk, TEST_SCAN_KEY, es, dp + + +def _make_mock_psbt_with_global_proofs(): + pk, scan_key, es, dp = _make_test_keypair() + psbt = MockPSBT() + inp = _make_eligible_input(pk, (MY_XFP, 44, 0, 0, 0), b"\x01" * 32, b"\x00\x00\x00\x00") + psbt.inputs = [inp] + outp = MockOutput() + outp.sp_v0_info = scan_key + TEST_SPEND_KEY + psbt.outputs = [outp] + psbt.sp_global_ecdh_shares = {scan_key: es} + psbt.sp_global_dleq_proofs = {scan_key: dp} + return psbt, scan_key, es, dp, pk, TEST_SPEND_KEY + + +def _make_sp_output(scan_key): + outp = MockOutput() + outp.sp_v0_info = scan_key + TEST_SPEND_KEY + return outp + + +# --------------------------------------------------------------------------- +# Mixin: ECDH Coverage +# --------------------------------------------------------------------------- + +# Global coverage complete +psbt, _, _, _, _, _ = _make_mock_psbt_with_global_proofs() +assert psbt._is_ecdh_coverage_complete() is True + +# Global missing +psbt.sp_global_ecdh_shares = {} +assert psbt._is_ecdh_coverage_complete() is False + +# Per-input coverage +pk, scan_key, es, _ = _make_test_keypair() +psbt = MockPSBT() +psbt.sp_global_ecdh_shares = {} +inp = _make_eligible_input(pk, TEST_DERIV_OURS, b"\x01" * 32, b"\x00" * 4) +inp.sp_ecdh_shares = {scan_key: es} +psbt.inputs = [inp] +psbt.outputs = [_make_sp_output(scan_key)] +assert psbt._is_ecdh_coverage_complete() is True + +# Per-input missing +inp.sp_ecdh_shares = {} +assert psbt._is_ecdh_coverage_complete() is False + +# No SP outputs: vacuously true +psbt = MockPSBT() +psbt.sp_global_ecdh_shares = {} +psbt.inputs = [MockInput()] +assert psbt._is_ecdh_coverage_complete() is True + +# --------------------------------------------------------------------------- +# Mixin: DLEQ Validation +# --------------------------------------------------------------------------- + +# Global valid proof +psbt, *_ = _make_mock_psbt_with_global_proofs() +psbt._validate_ecdh_coverage() + +# Global tampered proof +psbt, scan_key, _, dp, _, _ = _make_mock_psbt_with_global_proofs() +tampered = bytearray(dp) +tampered[0] ^= 0xFF +psbt.sp_global_dleq_proofs = {scan_key: bytes(tampered)} +try: + psbt._validate_ecdh_coverage() + assert False, "Should raise FatalPSBTIssue" +except FatalPSBTIssue: + pass + +# Global missing proof +psbt, _, _, _, _, _ = _make_mock_psbt_with_global_proofs() +psbt.sp_global_dleq_proofs = {} +try: + psbt._validate_ecdh_coverage() + assert False, "Should raise FatalPSBTIssue" +except FatalPSBTIssue: + pass + +# Per-input valid proof +pk, scan_key, es, dp = _make_test_keypair() +psbt = MockPSBT() +psbt.sp_global_ecdh_shares = {} +inp = _make_eligible_input(pk, TEST_DERIV_OURS, b"\x01" * 32, b"\x00" * 4) +inp.sp_ecdh_shares = {scan_key: es} +inp.sp_dleq_proofs = {scan_key: dp} +psbt.inputs = [inp] +outp = MockOutput() +outp.sp_v0_info = scan_key + TEST_SPEND_KEY +outp.script = b"\x51\x20" + b"\xcc" * 32 +psbt.outputs = [outp] +psbt._validate_ecdh_coverage() + +# Per-input tampered proof +pk, scan_key, es, dp = _make_test_keypair() +psbt = MockPSBT() +psbt.sp_global_ecdh_shares = {} +tampered = bytearray(dp) +tampered[0] ^= 0xFF +inp = _make_eligible_input(pk, TEST_DERIV_OURS, b"\x01" * 32, b"\x00" * 4) +inp.sp_ecdh_shares = {scan_key: es} +inp.sp_dleq_proofs = {scan_key: bytes(tampered)} +psbt.inputs = [inp] +outp = MockOutput() +outp.sp_v0_info = scan_key + TEST_SPEND_KEY +outp.script = b"\x51\x20" + b"\xcc" * 32 +psbt.outputs = [outp] +try: + psbt._validate_ecdh_coverage() + assert False, "Should raise FatalPSBTIssue" +except FatalPSBTIssue: + pass + +# --------------------------------------------------------------------------- +# Mixin: Compute and Store ECDH +# --------------------------------------------------------------------------- + +pk_ours = ngu.secp256k1.ec_pubkey_tweak_mul(G, TEST_PRIVKEY) + +# Single signer -> global +psbt = MockPSBT() +psbt.sp_global_ecdh_shares = None +psbt.sp_global_dleq_proofs = None +inp = _make_eligible_input(pk_ours, TEST_DERIV_OURS, b"\x01" * 32, b"\x00" * 4) +psbt.inputs = [inp] +outp = MockOutput() +outp.sp_v0_info = TEST_SCAN_KEY + TEST_SPEND_KEY +psbt.outputs = [outp] +sv = {TEST_DERIV_OURS: TEST_PRIVKEY} +assert psbt._compute_and_store_ecdh_shares(sv) is True +assert len(psbt.sp_global_ecdh_shares) == 1 +assert TEST_SCAN_KEY in psbt.sp_global_ecdh_shares +assert len(psbt.sp_global_dleq_proofs) == 1 + +# Idempotent +psbt._compute_and_store_ecdh_shares(sv) +assert len(psbt.sp_global_ecdh_shares) == 1 + +# Multi-signer -> per-input +psbt = MockPSBT() +inp_ours = _make_eligible_input(pk_ours, TEST_DERIV_OURS, b"\x01" * 32, b"\x00" * 4) +foreign_deriv = (FOREIGN_XFP, 44, 0, 0, 0) +inp_foreign = _make_eligible_input(b"\x02" + b"\xbb" * 32, foreign_deriv, b"\x02" * 32, b"\x01" * 4) +psbt.inputs = [inp_ours, inp_foreign] +outp = MockOutput() +outp.sp_v0_info = TEST_SCAN_KEY + TEST_SPEND_KEY +psbt.outputs = [outp] +assert psbt._compute_and_store_ecdh_shares(sv) is True +assert not psbt.sp_global_ecdh_shares +assert len(inp_ours.sp_ecdh_shares) == 1 +assert TEST_SCAN_KEY in inp_ours.sp_ecdh_shares + +# --------------------------------------------------------------------------- +# Mixin: Ownership +# --------------------------------------------------------------------------- + +# All owned +psbt = MockPSBT() +psbt.sp_global_ecdh_shares = None +psbt.sp_global_dleq_proofs = None +inp = _make_eligible_input(pk_ours, TEST_DERIV_OURS, b"\x01" * 32, b"\x00" * 4) +pub2 = ngu.secp256k1.ec_pubkey_tweak_mul(G, TEST_PRIVKEY2) +inp2 = _make_eligible_input(pub2, (MY_XFP, 44, 0, 0, 1), b"\x02" * 32, b"\x01\x00\x00\x00") +psbt.inputs = [inp, inp2] +outp = MockOutput() +outp.sp_v0_info = TEST_SCAN_KEY + TEST_SPEND_KEY +psbt.outputs = [outp] +psbt._compute_and_store_ecdh_shares( + { + TEST_DERIV_OURS: TEST_PRIVKEY, + (MY_XFP, 44, 0, 0, 1): TEST_PRIVKEY2, + } +) +assert psbt.sp_all_inputs_ours is True + +# Foreign input +psbt = MockPSBT() +inp_ours = _make_eligible_input(pk_ours, TEST_DERIV_OURS, b"\x01" * 32, b"\x00" * 4) +inp_foreign = _make_eligible_input(b"\x02" + b"\xbb" * 32, (FOREIGN_XFP, 44, 0, 0, 0), b"\x02" * 32, b"\x01" * 4) +psbt.inputs = [inp_ours, inp_foreign] +outp = MockOutput() +outp.sp_v0_info = TEST_SCAN_KEY + TEST_SPEND_KEY +psbt.outputs = [outp] +psbt._compute_and_store_ecdh_shares({TEST_DERIV_OURS: TEST_PRIVKEY}) +assert psbt.sp_all_inputs_ours is False + +# No signable inputs +psbt = MockPSBT() +inp = _make_eligible_input(b"\x02" + b"\xbb" * 32, (FOREIGN_XFP, 44, 0, 0, 0), b"\x01" * 32, b"\x00" * 4) +psbt.inputs = [inp] +outp = MockOutput() +outp.sp_v0_info = TEST_SCAN_KEY + TEST_SPEND_KEY +psbt.outputs = [outp] +assert psbt._compute_and_store_ecdh_shares({}) is False + +# --------------------------------------------------------------------------- +# Mixin: Compute Output Scripts +# --------------------------------------------------------------------------- + +# Single input, single output +psbt, scan_key, es, _, pk, spend_key = _make_mock_psbt_with_global_proofs() +psbt._compute_silent_payment_output_scripts() +outp = psbt.outputs[0] +assert outp.script is not None and len(outp.script) == 34 and outp.script[0] == 0x51 +expected = _compute_silent_payment_output_script([(b"\x01" * 32, b"\x00\x00\x00\x00")], pk, es, spend_key, 0) +assert outp.script == expected + +# Missing ECDH share +psbt, _, _, _, _, _ = _make_mock_psbt_with_global_proofs() +other_scan = a2b_hex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798") +psbt.outputs[0].sp_v0_info = other_scan + TEST_SPEND_KEY +try: + psbt._compute_silent_payment_output_scripts() + assert False, "Should raise" +except Exception as e: + assert "Missing ECDH share" in str(e) + +# Per-input share combining +pub1 = ngu.secp256k1.ec_pubkey_tweak_mul(G, TEST_PRIVKEY) +pub2 = ngu.secp256k1.ec_pubkey_tweak_mul(G, TEST_PRIVKEY2) +share1 = _compute_ecdh_share(TEST_PRIVKEY, TEST_SCAN_KEY) +share2 = _compute_ecdh_share(TEST_PRIVKEY2, TEST_SCAN_KEY) + +psbt = MockPSBT() +psbt.sp_global_ecdh_shares = {} +inp1 = _make_eligible_input(pub1, (MY_XFP, 44, 0, 0, 0), b"\x01" * 32, b"\x00\x00\x00\x00") +inp1.sp_ecdh_shares = {TEST_SCAN_KEY: share1} +inp2 = _make_eligible_input(pub2, (MY_XFP, 44, 0, 0, 1), b"\x02" * 32, b"\x01\x00\x00\x00") +inp2.sp_ecdh_shares = {TEST_SCAN_KEY: share2} +psbt.inputs = [inp1, inp2] +outp = MockOutput() +outp.sp_v0_info = TEST_SCAN_KEY + TEST_SPEND_KEY +psbt.outputs = [outp] +psbt._compute_silent_payment_output_scripts() + +combined_share = ngu.secp256k1.ec_pubkey_combine([share1, share2]) +summed_pk = _combine_pubkeys([pub1, pub2]) +outpoints = [(b"\x01" * 32, b"\x00\x00\x00\x00"), (b"\x02" * 32, b"\x01\x00\x00\x00")] +expected = _compute_silent_payment_output_script(outpoints, summed_pk, combined_share, TEST_SPEND_KEY, 0) +assert outp.script == expected + +# k counter per scan key (skips non-SP outputs) +psbt, scan_key, es, _, pk, _ = _make_mock_psbt_with_global_proofs() +spend_key_a = a2b_hex("022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4") +spend_key_b = a2b_hex("02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5") +spend_key_c = a2b_hex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798") + +sp0 = MockOutput() +sp0.sp_v0_info = scan_key + spend_key_a +reg1 = MockOutput() +sp2 = MockOutput() +sp2.sp_v0_info = scan_key + spend_key_b +reg3 = MockOutput() +sp4 = MockOutput() +sp4.sp_v0_info = scan_key + spend_key_c +psbt.outputs = [sp0, reg1, sp2, reg3, sp4] +psbt._compute_silent_payment_output_scripts() + +ops = [(b"\x01" * 32, b"\x00\x00\x00\x00")] +spk_sum = _combine_pubkeys([pk]) +assert sp0.script == _compute_silent_payment_output_script(ops, spk_sum, es, spend_key_a, k=0) +assert sp2.script == _compute_silent_payment_output_script(ops, spk_sum, es, spend_key_b, k=1) +assert sp4.script == _compute_silent_payment_output_script(ops, spk_sum, es, spend_key_c, k=2) +assert reg1.script is None +assert reg3.script is None + +# --------------------------------------------------------------------------- +# Mixin: Simple Methods +# --------------------------------------------------------------------------- + +psbt = MockPSBT() +outp = MockOutput() +outp.sp_v0_info = b"\x02" + b"\xaa" * 32 + b"\x02" + b"\xbb" * 32 +psbt.outputs = [outp] +assert psbt.has_silent_payment_outputs() is True + +psbt = MockPSBT() +psbt.outputs = [MockOutput()] +assert psbt.has_silent_payment_outputs() is False + +# _get_silent_payment_scan_keys +psbt = MockPSBT() +scan1 = b"\x02" + b"\xaa" * 32 +scan2 = b"\x03" + b"\xbb" * 32 +o1 = MockOutput() +o1.sp_v0_info = scan1 + b"\x02" + b"\xcc" * 32 +o2 = MockOutput() +o2.sp_v0_info = scan2 + b"\x02" + b"\xdd" * 32 +o3 = MockOutput() +psbt.outputs = [o1, o2, o3] +keys = psbt._get_silent_payment_scan_keys() +assert len(keys) == 2 and set(keys) == {scan1, scan2} + +# Dedup +psbt = MockPSBT() +o1 = MockOutput() +o1.sp_v0_info = scan1 + b"\x02" + b"\xbb" * 32 +o2 = MockOutput() +o2.sp_v0_info = scan1 + b"\x02" + b"\xcc" * 32 +psbt.outputs = [o1, o2] +assert len(psbt._get_silent_payment_scan_keys()) == 1 + +# --------------------------------------------------------------------------- +# Script type predicates +# --------------------------------------------------------------------------- + +assert _is_p2wpkh(b"\x00\x14" + b"\xab" * 20) is True +assert _is_p2wpkh(b"\x00\x14" + b"\xab" * 19) is False # wrong length +assert _is_p2tr(b"\x51\x20" + b"\xcd" * 32) is True +assert _is_p2tr(b"\x51\x20" + b"\xcd" * 31) is False # wrong length +assert _is_p2pkh(b"\x76\xa9\x14" + b"\xab" * 20 + b"\x88\xac") is True +assert _is_p2pkh(b"\x76\xa9\x14" + b"\xab" * 20 + b"\x88\xad") is False # wrong suffix +assert _is_p2sh(b"\xa9\x14" + b"\xab" * 20 + b"\x87") is True +assert _is_p2sh(b"\xa9\x14" + b"\xab" * 19 + b"\x87") is False # wrong length + +# P2TR with NUMS output key is still valid P2TR shape (ineligibility is checked separately) +assert _is_p2tr(b"\x51\x20" + NUMS_H) is True + +# --------------------------------------------------------------------------- +# _combine_pubkeys edge cases +# --------------------------------------------------------------------------- + +try: + _combine_pubkeys([]) + assert False, "Should raise ValueError for empty pubkey list" +except ValueError: + pass + +# Intermediate sums should not fail if final sum is non-zero. +A = ngu.secp256k1.ec_pubkey_tweak_mul(G, (1).to_bytes(32, "big")) +neg_A = bytes([0x03 if A[0] == 0x02 else 0x02]) + A[1:] +assert _combine_pubkeys([A, neg_A, A]) == A + +# Final point-at-infinity should still fail in phase 1. +try: + _combine_pubkeys([A, neg_A]) + assert False, "Should raise ValueError for point-at-infinity" +except ValueError: + pass diff --git a/testing/devtest/verify_sp_outputs.py b/testing/devtest/verify_sp_outputs.py new file mode 100644 index 000000000..35e09397d --- /dev/null +++ b/testing/devtest/verify_sp_outputs.py @@ -0,0 +1,208 @@ +# (c) Copyright 2024 by Coinkite Inc. This file is covered by license found in COPYING-CC. +# ruff: noqa: F821 +# +# Crypto verification for silent payment integration tests +# Runs inside the simulator via sim_execfile('devtest/verify_sp_outputs.py') +# +# Interface: main.SP_VERIFY dict with hex-encoded parameters +# Success = no output; failure = assertion traceback +# RV (uio.BytesIO) is injected into globals by the simulator's exec namespace. +# +import main +import ngu +import struct +import ujson +from ubinascii import hexlify as b2a_hex +from ubinascii import unhexlify as a2b_hex +from dleq import generate_dleq_proof, verify_dleq_proof +from exceptions import FatalPSBTIssue +from silentpayments import ( + _combine_pubkeys, + _compute_ecdh_share, + _compute_silent_payment_output_script, + SilentPaymentsMixin, +) + +if isinstance(main.SP_VERIFY, str): + with open(main.SP_VERIFY) as f: + params = ujson.load(f) +else: + params = main.SP_VERIFY + +mode = params.get("mode") + + +# --------------------------------------------------------------------------- +# MockPSBT infrastructure for firmware method testing +# --------------------------------------------------------------------------- + + +class MockInput: + def __init__(self): + self.utxo_spk = None + self.taproot_internal_key = None + self.redeem_script = None + self.sighash = None + self.previous_txid = None + self.prevout_idx = None + self.sp_ecdh_shares = {} + self.sp_dleq_proofs = {} + self.subpaths = [] + self.taproot_subpaths = [] + self.sp_idxs = None + self.ik_idx = None + + +class MockOutput: + def __init__(self): + self.sp_v0_info = None + self.sp_v0_label = None + self.script = None + + +class MockPSBT(SilentPaymentsMixin): + def __init__(self): + self.inputs = [] + self.outputs = [] + self.sp_global_ecdh_shares = {} + self.sp_global_dleq_proofs = {} + self.txn_modifiable = None + self.my_xfp = 0 + + def get(self, x): + return x + + def parse_xfp_path(self, coords): + return coords + + +def _build_mock_psbt(params): + psbt = MockPSBT() + psbt.txn_modifiable = params.get("txn_modifiable") + + global_ecdh = params.get("global_ecdh") or {} + global_dleq = params.get("global_dleq") or {} + psbt.sp_global_ecdh_shares = {a2b_hex(k): a2b_hex(v) for k, v in global_ecdh.items()} + psbt.sp_global_dleq_proofs = {a2b_hex(k): a2b_hex(v) for k, v in global_dleq.items()} + + for d in params.get("inputs", []): + inp = MockInput() + if d.get("utxo_spk"): + inp.utxo_spk = a2b_hex(d["utxo_spk"]) + elif d.get("witness_utxo"): + wu = a2b_hex(d["witness_utxo"]) + # Extract scriptPubKey: 8 bytes value + varint length + script + script_len = wu[8] + inp.utxo_spk = wu[9 : 9 + script_len] + if d.get("taproot_internal_key"): + inp.taproot_internal_key = a2b_hex(d["taproot_internal_key"]) + if d.get("redeem_script"): + inp.redeem_script = a2b_hex(d["redeem_script"]) + inp.sighash = d.get("sighash") + if d.get("previous_txid"): + inp.previous_txid = a2b_hex(d["previous_txid"]) + if d.get("prevout_idx") is not None: + inp.prevout_idx = struct.pack(" 1 else pubkeys[0] + RV.write(b2a_hex(result).decode()) + +elif mode == "validate_sp": + # Run firmware validation methods; write error string to RV on failure + psbt = _build_mock_psbt(params) + try: + psbt._validate_psbt_structure() + psbt._validate_input_eligibility() + psbt._validate_ecdh_coverage() + psbt._compute_silent_payment_output_scripts() + except FatalPSBTIssue as e: + RV.write(str(e)) + +elif mode == "get_ecdh_and_pubkey": + # Call _get_ecdh_and_pubkey and return "ecdh_hex,pubkey_hex" + psbt = _build_mock_psbt(params) + scan_key = a2b_hex(params["scan_key"]) + ecdh_share, summed_pubkey = psbt._get_ecdh_and_pubkey(scan_key) + ecdh_hex = b2a_hex(ecdh_share).decode() if ecdh_share else "" + pubkey_hex = b2a_hex(summed_pubkey).decode() if summed_pubkey else "" + RV.write(ecdh_hex + "," + pubkey_hex) + +elif mode == "get_outpoints": + # Call _get_outpoints and return "txid:vout,txid:vout,..." + psbt = _build_mock_psbt(params) + outpoints = psbt._get_outpoints() + parts = [] + for txid, vout in outpoints: + parts.append(b2a_hex(txid).decode() + ":" + b2a_hex(vout).decode()) + RV.write(",".join(parts)) + +elif mode == "pubkey_from_input": + # Call _pubkey_from_input for a specific input; return pubkey hex or "" + psbt = _build_mock_psbt(params) + i = params["input_index"] + pk = psbt._pubkey_from_input(psbt.inputs[i]) + RV.write(b2a_hex(pk).decode() if pk else "") diff --git a/testing/psbt.py b/testing/psbt.py index b553d15fa..26e85ebe4 100644 --- a/testing/psbt.py +++ b/testing/psbt.py @@ -25,6 +25,9 @@ PSBT_GLOBAL_INPUT_COUNT = 0x04 PSBT_GLOBAL_OUTPUT_COUNT = 0x05 PSBT_GLOBAL_TX_MODIFIABLE = 0x06 +# BIP-375 Silent Payments +PSBT_GLOBAL_SP_ECDH_SHARE = 0x07 +PSBT_GLOBAL_SP_DLEQ = 0x08 # INPUTS === PSBT_IN_NON_WITNESS_UTXO = 0x00 @@ -58,6 +61,9 @@ PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS = 0x1a PSBT_IN_MUSIG2_PUB_NONCE = 0x1b PSBT_IN_MUSIG2_PARTIAL_SIG = 0x1c +# BIP-375 Silent Payments +PSBT_IN_SP_ECDH_SHARE = 0x1d +PSBT_IN_SP_DLEQ = 0x1e # OUTPUTS === PSBT_OUT_REDEEM_SCRIPT = 0x00 @@ -71,6 +77,9 @@ PSBT_OUT_TAP_TREE = 0x06 PSBT_OUT_TAP_BIP32_DERIVATION = 0x07 PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08 +# BIP-375 Silent Payments +PSBT_OUT_SP_V0_INFO = 0x09 +PSBT_OUT_SP_V0_LABEL = 0x0a PSBT_PROP_CK_ID = b"COINKITE" @@ -141,6 +150,8 @@ def defaults(self): self.musig_pubkeys = {} self.musig_pubnonces = {} self.musig_part_sigs = {} + self.sp_ecdh_shares = {} + self.sp_dleq_proofs = {} self.others = {} self.unknown = {} @@ -171,6 +182,8 @@ def __eq__(a, b): a.musig_pubkeys == b.musig_pubkeys and \ a.musig_pubnonces == b.musig_pubnonces and \ a.musig_part_sigs == b.musig_part_sigs and \ + a.sp_ecdh_shares == b.sp_ecdh_shares and \ + a.sp_dleq_proofs == b.sp_dleq_proofs and \ a.unknown == b.unknown if rv: # NOTE: equality test on signatures requires parsing DER stupidness @@ -260,6 +273,10 @@ def parse_kv(self, kt, key, val): aggregate_key = key[33:66] tapleaf_h = key[66:] self.musig_part_sigs[(participant_key, aggregate_key, tapleaf_h)] = val + elif kt == PSBT_IN_SP_ECDH_SHARE: + self.sp_ecdh_shares[key] = val + elif kt == PSBT_IN_SP_DLEQ: + self.sp_dleq_proofs[key] = val else: self.unknown[bytes([kt]) + key] = val @@ -318,6 +335,12 @@ def serialize_kvs(self, wr, v2): if self.musig_pubkeys: for agg_k, pk_lst in self.musig_pubkeys.items(): wr(PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS, b"".join(pk_lst), agg_k) + if self.sp_ecdh_shares: + for k, v in self.sp_ecdh_shares.items(): + wr(PSBT_IN_SP_ECDH_SHARE, v, k) + if self.sp_dleq_proofs: + for k, v in self.sp_dleq_proofs.items(): + wr(PSBT_IN_SP_DLEQ, v, k) if self.musig_pubnonces: for (pk, ak, lh), pubnonce in self.musig_pubnonces.items(): @@ -349,6 +372,8 @@ def defaults(self): self.taproot_tree = None self.script = None # v2 self.amount = None # v2 + self.sp_v0_info = None + self.sp_v0_label = None self.proprietary = {} self.musig_pubkeys = {} self.unknown = {} @@ -365,6 +390,8 @@ def __eq__(a, b): a.proprietary == b.proprietary and \ a.taproot_tree == b.taproot_tree and \ a.musig_pubkeys == b.musig_pubkeys and \ + a.sp_v0_info == b.sp_v0_info and \ + a.sp_v0_label == b.sp_v0_label and \ a.unknown == b.unknown def parse_kv(self, kt, key, val): @@ -404,6 +431,10 @@ def parse_kv(self, kt, key, val): for i in range(0, len(val), 33): pk_list.append(val[i:i + 33]) self.musig_pubkeys[key] = pk_list + elif kt == PSBT_OUT_SP_V0_INFO: + self.sp_v0_info = val + elif kt == PSBT_OUT_SP_V0_LABEL: + self.sp_v0_label = val elif kt == PSBT_GLOBAL_PROPRIETARY: self.proprietary[key] = val else: @@ -435,6 +466,10 @@ def serialize_kvs(self, wr, v2): if self.musig_pubkeys: for agg_k, pk_lst in self.musig_pubkeys.items(): wr(PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS, b"".join(pk_lst), agg_k) + if self.sp_v0_info is not None: + wr(PSBT_OUT_SP_V0_INFO, self.sp_v0_info) + if self.sp_v0_label is not None: + wr(PSBT_OUT_SP_V0_LABEL, self.sp_v0_label) for k in self.proprietary: wr(PSBT_GLOBAL_PROPRIETARY, self.proprietary[k], k) @@ -463,6 +498,8 @@ def __init__(self): self.outputs = [] self.txn_modifiable = None self.fallback_locktime = None + self.sp_global_ecdh_shares = {} + self.sp_global_dleq_proofs = {} self.unknown = {} self.parsed_txn = None @@ -478,6 +515,8 @@ def __eq__(a, b): all(a.inputs[i] == b.inputs[i] for i in range(len(a.inputs))) and \ all(a.outputs[i] == b.outputs[i] for i in range(len(a.outputs))) and \ sorted(a.xpubs) == sorted(b.xpubs) and \ + a.sp_global_ecdh_shares == b.sp_global_ecdh_shares and \ + a.sp_global_dleq_proofs == b.sp_global_dleq_proofs and \ a.unknown == b.unknown def is_v2(self): @@ -530,6 +569,10 @@ def parse(self, raw): num_outs = self.output_count elif kt == PSBT_GLOBAL_TX_MODIFIABLE: self.txn_modifiable = val[0] + elif kt == PSBT_GLOBAL_SP_ECDH_SHARE: + self.sp_global_ecdh_shares[key[1:]] = val + elif kt == PSBT_GLOBAL_SP_DLEQ: + self.sp_global_dleq_proofs[key[1:]] = val else: self.unknown[key] = val @@ -588,6 +631,13 @@ def wr(ktype, val, key=b''): if self.txn_modifiable is not None: wr(PSBT_GLOBAL_TX_MODIFIABLE, bytes([self.txn_modifiable])) + if self.sp_global_ecdh_shares: + for k, v in self.sp_global_ecdh_shares.items(): + wr(PSBT_GLOBAL_SP_ECDH_SHARE, v, k) + if self.sp_global_dleq_proofs: + for k, v in self.sp_global_dleq_proofs.items(): + wr(PSBT_GLOBAL_SP_DLEQ, v, k) + if self.version is not None: wr(PSBT_GLOBAL_VERSION, struct.pack(' Date: Thu, 9 Apr 2026 21:23:34 -0400 Subject: [PATCH 09/23] BIP-352: add test vectors Add testing/test_bip352_vectors.py and testing/bip352_test_vectors.json - Verify SP sending and receiving test vectors --- testing/bip352_test_vectors.json | 5729 ++++++++++++++++++++++++++ testing/devtest/verify_sp_outputs.py | 40 + testing/test_bip352_vectors.py | 349 ++ 3 files changed, 6118 insertions(+) create mode 100644 testing/bip352_test_vectors.json create mode 100644 testing/test_bip352_vectors.py diff --git a/testing/bip352_test_vectors.json b/testing/bip352_test_vectors.json new file mode 100644 index 000000000..3a189757d --- /dev/null +++ b/testing/bip352_test_vectors.json @@ -0,0 +1,5729 @@ +[ + { + "comment": "Simple send: two inputs", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + }, + "private_key": "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1" + ] + ], + "shared_secrets": [ + "028158aff7d61ea66b2fa7f555bc3c5937d1debbde16423d630f9aa7943e14d80d" + ], + "input_private_key_sum": "7ed265a6dac7aba8508a32d6d6b84c7f1dbd0a0941dd01088d69e8d556345f86", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + } + } + ], + "outputs": [ + "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "f438b40179a3c4262de12986c0e6cce0634007cdc79c1dcd3e20b9ebc2e7eef6", + "pub_key": "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1", + "signature": "74f85b856337fbe837643b86f462118159f93ac4acc2671522f27e8f67b079959195ccc7a5dbee396d2909f5d680d6e30cda7359aa2755822509b70d6b0687a1" + } + ], + "tweak": "024ac253c216532e961988e2a8ce266a447c894c781e52ef6cee902361db960004", + "shared_secret": "028158aff7d61ea66b2fa7f555bc3c5937d1debbde16423d630f9aa7943e14d80d", + "input_pub_key_sum": "032562c1ab2d6bd45d7ca4d78f569999e5333dffd3ac5263924fd00d00dedc4bee" + } + } + ] + }, + { + "comment": "Simple send: two inputs, order reversed", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + }, + "private_key": "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1" + ] + ], + "shared_secrets": [ + "028158aff7d61ea66b2fa7f555bc3c5937d1debbde16423d630f9aa7943e14d80d" + ], + "input_private_key_sum": "7ed265a6dac7aba8508a32d6d6b84c7f1dbd0a0941dd01088d69e8d556345f86", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + } + } + ], + "outputs": [ + "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "f438b40179a3c4262de12986c0e6cce0634007cdc79c1dcd3e20b9ebc2e7eef6", + "pub_key": "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1", + "signature": "74f85b856337fbe837643b86f462118159f93ac4acc2671522f27e8f67b079959195ccc7a5dbee396d2909f5d680d6e30cda7359aa2755822509b70d6b0687a1" + } + ], + "tweak": "024ac253c216532e961988e2a8ce266a447c894c781e52ef6cee902361db960004", + "shared_secret": "028158aff7d61ea66b2fa7f555bc3c5937d1debbde16423d630f9aa7943e14d80d", + "input_pub_key_sum": "032562c1ab2d6bd45d7ca4d78f569999e5333dffd3ac5263924fd00d00dedc4bee" + } + } + ] + }, + { + "comment": "Simple send: two inputs from the same transaction", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 3, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 7, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + }, + "private_key": "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "79e71baa2ba3fc66396de3a04f168c7bf24d6870ec88ca877754790c1db357b6" + ] + ], + "shared_secrets": [ + "03aa707f7b5e94b448abd28aa217e3d7a7cc6bb07f1a8d07be4de91bf7b1417469" + ], + "input_private_key_sum": "7ed265a6dac7aba8508a32d6d6b84c7f1dbd0a0941dd01088d69e8d556345f86", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 3, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 7, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + } + } + ], + "outputs": [ + "79e71baa2ba3fc66396de3a04f168c7bf24d6870ec88ca877754790c1db357b6" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "4851455bfbe1ab4f80156570aa45063201aa5c9e1b1dcd29f0f8c33d10bf77ae", + "pub_key": "79e71baa2ba3fc66396de3a04f168c7bf24d6870ec88ca877754790c1db357b6", + "signature": "10332eea808b6a13f70059a8a73195808db782012907f5ba32b6eae66a2f66b4f65147e2b968a1678c5f73d57d5d195dbaf667b606ff80c8490eac1f3b710657" + } + ], + "tweak": "03aeea547819c08413974e2ab2b12212e007166bb2058f88b009e082b9b4914a58", + "shared_secret": "03aa707f7b5e94b448abd28aa217e3d7a7cc6bb07f1a8d07be4de91bf7b1417469", + "input_pub_key_sum": "032562c1ab2d6bd45d7ca4d78f569999e5333dffd3ac5263924fd00d00dedc4bee" + } + } + ] + }, + { + "comment": "Simple send: two inputs from the same transaction, order reversed", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 7, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 3, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + }, + "private_key": "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "f4c2da807f89cb1501f1a77322a895acfb93c28e08ed2724d2beb8e44539ba38" + ] + ], + "shared_secrets": [ + "03054f5c84b07182ba2a2e10a35e088778f95c04f059f4574b024c372eb8ce5468" + ], + "input_private_key_sum": "7ed265a6dac7aba8508a32d6d6b84c7f1dbd0a0941dd01088d69e8d556345f86", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 7, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 3, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + } + } + ], + "outputs": [ + "f4c2da807f89cb1501f1a77322a895acfb93c28e08ed2724d2beb8e44539ba38" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "ab0c9b87181bf527879f48db9f14a02233619b986f8e8f2d5d408ce68a709f51", + "pub_key": "f4c2da807f89cb1501f1a77322a895acfb93c28e08ed2724d2beb8e44539ba38", + "signature": "398a9790865791a9db41a8015afad3a47d60fec5086c50557806a49a1bc038808632b8fe679a7bb65fc6b455be994502eed849f1da3729cd948fc7be73d67295" + } + ], + "tweak": "024cad5180a093d3af0f49f586bdf37f890920178e68e80561ed53351d0fa499ad", + "shared_secret": "03054f5c84b07182ba2a2e10a35e088778f95c04f059f4574b024c372eb8ce5468", + "input_pub_key_sum": "032562c1ab2d6bd45d7ca4d78f569999e5333dffd3ac5263924fd00d00dedc4bee" + } + } + ] + }, + { + "comment": "Outpoint ordering byte-lexicographically vs. vout-integer", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 1, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 256, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + }, + "private_key": "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "a85ef8701394b517a4b35217c4bd37ac01ebeed4b008f8d0879f9e09ba95319c" + ] + ], + "shared_secrets": [ + "02cb25a6e7c9b7c6d550e0413da63834678465b5e80853a51d0335d318296ac182" + ], + "input_private_key_sum": "7ed265a6dac7aba8508a32d6d6b84c7f1dbd0a0941dd01088d69e8d556345f86", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 1, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 256, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + } + } + ], + "outputs": [ + "a85ef8701394b517a4b35217c4bd37ac01ebeed4b008f8d0879f9e09ba95319c" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "c8ac0292997b5bca98b3ebd99a57e253071137550f270452cd3df8a3e2266d36", + "pub_key": "a85ef8701394b517a4b35217c4bd37ac01ebeed4b008f8d0879f9e09ba95319c", + "signature": "c036ee38bfe46aba03234339ae7219b31b824b52ef9d5ce05810a0d6f62330dedc2b55652578aa5bdabf930fae941acd839d5a66f8fce7caa9710ccb446bddd1" + } + ], + "tweak": "031f9a80d0938cf980b51f7cc4fad713d49037f430646dff129c0570d75a40d8f0", + "shared_secret": "02cb25a6e7c9b7c6d550e0413da63834678465b5e80853a51d0335d318296ac182", + "input_pub_key_sum": "032562c1ab2d6bd45d7ca4d78f569999e5333dffd3ac5263924fd00d00dedc4bee" + } + } + ] + }, + { + "comment": "Single recipient: multiple UTXOs from the same public key", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "548ae55c8eec1e736e8d3e520f011f1f42a56d166116ad210b3937599f87f566" + ] + ], + "shared_secrets": [ + "02f6b40ff17f4010fe732ac4b0f2f211281aa09c9a5fb41f1c151ec2606fee9ec2" + ], + "input_private_key_sum": "d5b8f02cbfe3f1d5295af9fb8a9320e859e9cb07115856486ab1a4e4fb89a621", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + } + ], + "outputs": [ + "548ae55c8eec1e736e8d3e520f011f1f42a56d166116ad210b3937599f87f566" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "f032695e2636619efa523fffaa9ef93c8802299181fd0461913c1b8daf9784cd", + "pub_key": "548ae55c8eec1e736e8d3e520f011f1f42a56d166116ad210b3937599f87f566", + "signature": "f238386c5d5e5444f8d2c75aabbcb28c346f208c76f60823f5de3b67b79e0ec72ea5de2d7caec314e0971d3454f122dda342b3eede01b3857e83654e36b25f76" + } + ], + "tweak": "0319949463fc6a2368d999a2a6a2bcb2dbf64a2ac6e00b3ba5659780c860a6d9e0", + "shared_secret": "02f6b40ff17f4010fe732ac4b0f2f211281aa09c9a5fb41f1c151ec2606fee9ec2", + "input_pub_key_sum": "03e40664e222ba71e29b80efc907fa22a3c6c64f45e89dbb8511dc7a3712b0a186" + } + } + ] + }, + { + "comment": "Single recipient: taproot only inputs with even y-values", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140bd1e708f92dbeaf24a6b8dd22e59c6274355424d62baea976b449e220fd75b13578e262ab11b7aa58e037f0c6b0519b66803b7d9decaa1906dedebfb531c56c1", + "prevout": { + "scriptPubKey": { + "hex": "5120782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + } + }, + "private_key": "fc8716a97a48ba9a05a98ae47b5cd201a25a7fd5d8b73c203c5f7b6b6b3b6ad7" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "de88bea8e7ffc9ce1af30d1132f910323c505185aec8eae361670421e749a1fb" + ] + ], + "shared_secrets": [ + "02de9719785c6d09f71571dadf44bca59edba2af3e689c65cbc3bb5a4a387732ef" + ], + "input_private_key_sum": "e7638ebfda3ab3849a5707e240a6627671f7f6e609bf172691cf1e9780e51d47", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "02782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140bd1e708f92dbeaf24a6b8dd22e59c6274355424d62baea976b449e220fd75b13578e262ab11b7aa58e037f0c6b0519b66803b7d9decaa1906dedebfb531c56c1", + "prevout": { + "scriptPubKey": { + "hex": "5120782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + } + } + } + ], + "outputs": [ + "de88bea8e7ffc9ce1af30d1132f910323c505185aec8eae361670421e749a1fb" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "3fb9ce5ce1746ced103c8ed254e81f6690764637ddbc876ec1f9b3ddab776b03", + "pub_key": "de88bea8e7ffc9ce1af30d1132f910323c505185aec8eae361670421e749a1fb", + "signature": "c5acd25a8f021a4192f93bc34403fd8b76484613466336fb259c72d04c169824f2690ca34e96cee86b69f376c8377003268fda56feeb1b873e5783d7e19bcca5" + } + ], + "tweak": "02dc59cc8e8873b65c1dd5c416d4fbeb647372c329bd84a70c05b310e222e2c183", + "shared_secret": "02de9719785c6d09f71571dadf44bca59edba2af3e689c65cbc3bb5a4a387732ef", + "input_pub_key_sum": "038180a2125f9d6dd116e1a6139be4d72fd5057dab6aaabaa5654817c11baeb3ba" + } + } + ] + }, + { + "comment": "Single recipient: taproot only with mixed even/odd y-values", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "", + "txinwitness": "01400a4d0dca6293f40499394d7eefe14a1de11e0e3454f51de2e802592abf5ee549042a1b1a8fb2e149ee9dd3f086c1b69b2f182565ab6ecf599b1ec9ebadfda6c5", + "prevout": { + "scriptPubKey": { + "hex": "51208c8d23d4764feffcd5e72e380802540fa0f88e3d62ad5e0b47955f74d7b283c4" + } + }, + "private_key": "1d37787c2b7116ee983e9f9c13269df29091b391c04db94239e0d2bc2182c3bf" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "77cab7dd12b10259ee82c6ea4b509774e33e7078e7138f568092241bf26b99f1" + ] + ], + "shared_secrets": [ + "030e7f5ca4bf109fc35c8c2d878f756c891ac04c456cc5f0b05fcec4d3b2b1beb2" + ], + "input_private_key_sum": "cda4ff9a3480e1fbfc6edd61b222f280f9baa0652002c1ffdb612efcc45d2ff2", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "028c8d23d4764feffcd5e72e380802540fa0f88e3d62ad5e0b47955f74d7b283c4" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "", + "txinwitness": "01400a4d0dca6293f40499394d7eefe14a1de11e0e3454f51de2e802592abf5ee549042a1b1a8fb2e149ee9dd3f086c1b69b2f182565ab6ecf599b1ec9ebadfda6c5", + "prevout": { + "scriptPubKey": { + "hex": "51208c8d23d4764feffcd5e72e380802540fa0f88e3d62ad5e0b47955f74d7b283c4" + } + } + } + ], + "outputs": [ + "77cab7dd12b10259ee82c6ea4b509774e33e7078e7138f568092241bf26b99f1" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "f5382508609771068ed079b24e1f72e4a17ee6d1c979066bf1d4e2a5676f09d4", + "pub_key": "77cab7dd12b10259ee82c6ea4b509774e33e7078e7138f568092241bf26b99f1", + "signature": "ff65833b8fd1ed3ef9d0443b4f702b45a3f2dd457ba247687e8207745c3be9d2bdad0ab3f07118f8b2efc6a04b95f7b3e218daf8a64137ec91bd2fc67fc137a5" + } + ], + "tweak": "03b990f5b1d90ea8fd4bdd5c856a9dfe17035d196958062e2c6cb4c99e413f3548", + "shared_secret": "030e7f5ca4bf109fc35c8c2d878f756c891ac04c456cc5f0b05fcec4d3b2b1beb2", + "input_pub_key_sum": "020f0ab50f420ab1249bc2a21659c607f2873400853035aad0ca6d0ded04d62623" + } + } + ] + }, + { + "comment": "Single recipient: taproot input with even y-value and non-taproot input", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "463044021f24e010c6e475814740ba24c8cf9362c4db1276b7f46a7b1e63473159a80ec30221008198e8ece7b7f88e6c6cc6bb8c86f9f00b7458222a8c91addf6e1577bcf7697e2103e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9148cbc7dfe44f1579bff3340bbef1eddeaeb1fc97788ac" + } + }, + "private_key": "8d4751f6e8a3586880fb66c19ae277969bd5aa06f61c4ee2f1e2486efdf666d3" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "30523cca96b2a9ae3c98beb5e60f7d190ec5bc79b2d11a0b2d4d09a608c448f0" + ] + ], + "shared_secrets": [ + "021cd92ff153e638d0a97bcd11fafc81c321b111f5ba1efff593371b7b688efdd3" + ], + "input_private_key_sum": "7823ca0d4895515315a8e3bf602c080b6b732117272429e94751eb9b13a01943", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "463044021f24e010c6e475814740ba24c8cf9362c4db1276b7f46a7b1e63473159a80ec30221008198e8ece7b7f88e6c6cc6bb8c86f9f00b7458222a8c91addf6e1577bcf7697e2103e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9148cbc7dfe44f1579bff3340bbef1eddeaeb1fc97788ac" + } + } + } + ], + "outputs": [ + "30523cca96b2a9ae3c98beb5e60f7d190ec5bc79b2d11a0b2d4d09a608c448f0" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "b40017865c79b1fcbed68896791be93186d08f47e416b289b8c063777e14e8df", + "pub_key": "30523cca96b2a9ae3c98beb5e60f7d190ec5bc79b2d11a0b2d4d09a608c448f0", + "signature": "d1edeea28cf1033bcb3d89376cabaaaa2886cbd8fda112b5c61cc90a4e7f1878bdd62180b07d1dfc8ffee1863c525a0c7b5bcd413183282cfda756cb65787266" + } + ], + "tweak": "0233c2a447b8b244e4ffcfb59fe365eaa3bb22288b31e2113b9998861f40d4d6da", + "shared_secret": "021cd92ff153e638d0a97bcd11fafc81c321b111f5ba1efff593371b7b688efdd3", + "input_pub_key_sum": "031ecda9c64faaa6cd57c9f3d7c62bcfc0763c2627ed8dc0e2c3018e9ff37a0bf0" + } + } + ] + }, + { + "comment": "Single recipient: taproot input with odd y-value and non-taproot input", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "01400a4d0dca6293f40499394d7eefe14a1de11e0e3454f51de2e802592abf5ee549042a1b1a8fb2e149ee9dd3f086c1b69b2f182565ab6ecf599b1ec9ebadfda6c5", + "prevout": { + "scriptPubKey": { + "hex": "51208c8d23d4764feffcd5e72e380802540fa0f88e3d62ad5e0b47955f74d7b283c4" + } + }, + "private_key": "1d37787c2b7116ee983e9f9c13269df29091b391c04db94239e0d2bc2182c3bf" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "463044021f24e010c6e475814740ba24c8cf9362c4db1276b7f46a7b1e63473159a80ec30221008198e8ece7b7f88e6c6cc6bb8c86f9f00b7458222a8c91addf6e1577bcf7697e2103e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9148cbc7dfe44f1579bff3340bbef1eddeaeb1fc97788ac" + } + }, + "private_key": "8d4751f6e8a3586880fb66c19ae277969bd5aa06f61c4ee2f1e2486efdf666d3" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "359358f59ee9e9eec3f00bdf4882570fd5c182e451aa2650b788544aff012a3a" + ] + ], + "shared_secrets": [ + "03d9437eb3676cf5cc00feebe68bc44c4567332e4b89788dec9eceb3779054442b" + ], + "input_private_key_sum": "700fd97abd324179e8bcc72587bbd9a40b43f67535ce95a0b80175b2dc73a314", + "input_pub_keys": [ + "028c8d23d4764feffcd5e72e380802540fa0f88e3d62ad5e0b47955f74d7b283c4", + "03e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "01400a4d0dca6293f40499394d7eefe14a1de11e0e3454f51de2e802592abf5ee549042a1b1a8fb2e149ee9dd3f086c1b69b2f182565ab6ecf599b1ec9ebadfda6c5", + "prevout": { + "scriptPubKey": { + "hex": "51208c8d23d4764feffcd5e72e380802540fa0f88e3d62ad5e0b47955f74d7b283c4" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "463044021f24e010c6e475814740ba24c8cf9362c4db1276b7f46a7b1e63473159a80ec30221008198e8ece7b7f88e6c6cc6bb8c86f9f00b7458222a8c91addf6e1577bcf7697e2103e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9148cbc7dfe44f1579bff3340bbef1eddeaeb1fc97788ac" + } + } + } + ], + "outputs": [ + "359358f59ee9e9eec3f00bdf4882570fd5c182e451aa2650b788544aff012a3a" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "a2f9dd05d1d398347c885d9c61a64d18a264de6d49cea4326bafc2791d627fa7", + "pub_key": "359358f59ee9e9eec3f00bdf4882570fd5c182e451aa2650b788544aff012a3a", + "signature": "96038ad233d8befe342573a6e54828d863471fb2afbad575cc65271a2a649480ea14912b6abbd3fbf92efc1928c036f6e3eef927105af4ec1dd57cb909f360b8" + } + ], + "tweak": "02d4e4f2c4cdb71c9c39a700a9ee1a0fc05b98362a441183f5770af7d6e2b3038c", + "shared_secret": "03d9437eb3676cf5cc00feebe68bc44c4567332e4b89788dec9eceb3779054442b", + "input_pub_key_sum": "03bc118b1c8178915b716d6137633722c71adfe721551ec7b3938054691de6a2b9" + } + } + ] + }, + { + "comment": "Multiple outputs: multiple outputs, same recipient", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + }, + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ] + ], + "shared_secrets": [ + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "d97e442d110c0bdd31161a7bb6e7862e038d02a09b1484dfbb463f2e0f7c9230", + "pub_key": "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "signature": "29bd25d0f808d7fcd2aa6d5ed206053899198397506c301b218a9e47a3d7070af03e903ff718978d50d1b6b9af8cc0e313d84eda5d5b1e8e85e5516d630bbeb9" + }, + { + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "Multiple outputs: multiple outputs, multiple recipients", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + }, + { + "address": "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn", + "scan_pub_key": "02062d49ffc02787d586c608dfbec184aa91a6597d97b463ea5c6babd9d17a95a3", + "spend_pub_key": "0381eb9a9a9ec739d527c1631b31b421566f5c2a47b4ab5b1f6a686dfb68eab716" + }, + { + "address": "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn", + "scan_pub_key": "02062d49ffc02787d586c608dfbec184aa91a6597d97b463ea5c6babd9d17a95a3", + "spend_pub_key": "0381eb9a9a9ec739d527c1631b31b421566f5c2a47b4ab5b1f6a686dfb68eab716" + } + ] + }, + "expected": { + "outputs": [ + [ + "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a", + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ] + ], + "shared_secrets": [ + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "03dd5fd04d3c8863be750a1bd7474df06161461d38d3ce1397a5c78cee112cdcd2", + "03dd5fd04d3c8863be750a1bd7474df06161461d38d3ce1397a5c78cee112cdcd2" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a", + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + "key_material": { + "spend_priv_key": "9902c3c56e84002a7cd410113a9ab21d142be7f53cf5200720bb01314c5eb920", + "scan_priv_key": "060b751d7892149006ed7b98606955a29fe284a1e900070c0971f5fb93dbf422" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn" + ], + "outputs": [ + { + "priv_key_tweak": "72cd082cccb633bf85240a83494b32dc943a4d05647a6686d23ad4ca59c0ebe4", + "pub_key": "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a", + "signature": "38745f3d9f5eef0b1cfb17ca314efa8c521efab28a23aa20ec5e3abb561d42804d539906dce60c4ee7977966184e6f2cab1faa0e5377ceb7148ec5218b4e7878" + }, + { + "priv_key_tweak": "2f17ea873a0047fc01ba8010fef0969e76d0e4283f600d48f735098b1fee6eb9", + "pub_key": "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "signature": "c26f4e3cf371b90b840f48ea0e761b5ec31883ed55719f9ef06a90e282d85f565790ab780a3f491bc2668cc64e944dca849d1022a878cdadb8d168b8da4a6da3" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "03dd5fd04d3c8863be750a1bd7474df06161461d38d3ce1397a5c78cee112cdcd2", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "Receiving with labels: label with even parity", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjex54dmqmmv6rw353tsuqhs99ydvadxzrsy9nuvk74epvee55drs734pqq", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "0259352add837b6686e8d22b87017814a46b3ad308702167c65bd5c8599cd28d1c" + } + ] + }, + "expected": { + "outputs": [ + [ + "d014d4860f67d607d60b1af70e0ee236b99658b61bb769832acbbe87c374439a" + ] + ], + "shared_secrets": [ + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "d014d4860f67d607d60b1af70e0ee236b99658b61bb769832acbbe87c374439a" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 2, + 3, + 1001337 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjex54dmqmmv6rw353tsuqhs99ydvadxzrsy9nuvk74epvee55drs734pqq", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqsg59z2rppn4qlkx0yz9sdltmjv3j8zgcqadjn4ug98m3t6plujsq9qvu5n", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq7c2zfthc6x3a5yecwc52nxa0kfd20xuz08zyrjpfw4l2j257yq6qgnkdh5" + ], + "outputs": [ + { + "priv_key_tweak": "51d4e9d0d482b5700109b4b2e16ff508269b03d800192a043d61dca4a0a72a52", + "pub_key": "d014d4860f67d607d60b1af70e0ee236b99658b61bb769832acbbe87c374439a", + "signature": "c30fa63bad6f0a317f39a773a5cbf0b0f8193c71dfebba05ee6ae4ed28e3775e6e04c3ea70a83703bb888122855dc894cab61692e7fd10c9b3494d479a60785e" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "Receiving with labels: label with odd parity", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqsg59z2rppn4qlkx0yz9sdltmjv3j8zgcqadjn4ug98m3t6plujsq9qvu5n", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "0208a144a18433a83f633c822c1bf5ee4c8c8e24601d6ca75e20a7dc57a0ff9280" + } + ] + }, + "expected": { + "outputs": [ + [ + "67626aebb3c4307cf0f6c39ca23247598fabf675ab783292eb2f81ae75ad1f8c" + ] + ], + "shared_secrets": [ + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "67626aebb3c4307cf0f6c39ca23247598fabf675ab783292eb2f81ae75ad1f8c" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 2, + 3, + 1001337 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjex54dmqmmv6rw353tsuqhs99ydvadxzrsy9nuvk74epvee55drs734pqq", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqsg59z2rppn4qlkx0yz9sdltmjv3j8zgcqadjn4ug98m3t6plujsq9qvu5n", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq7c2zfthc6x3a5yecwc52nxa0kfd20xuz08zyrjpfw4l2j257yq6qgnkdh5" + ], + "outputs": [ + { + "priv_key_tweak": "6024ae214876356b8d917716e7707d267ae16a0fdb07de2a786b74a7bbcddead", + "pub_key": "67626aebb3c4307cf0f6c39ca23247598fabf675ab783292eb2f81ae75ad1f8c", + "signature": "a86d554d0d6b7aa0907155f7e0b47f0182752472fffaeddd68da90e99b9402f166fd9b33039c302c7115098d971c1399e67c19e9e4de180b10ea0b9d6f0db832" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "Receiving with labels: large label integer", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq7c2zfthc6x3a5yecwc52nxa0kfd20xuz08zyrjpfw4l2j257yq6qgnkdh5", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "03d85092bbe3468f684ce1d8a2a66ebec96a9e6e09e7110720a5d5faa4aa7880d0" + } + ] + }, + "expected": { + "outputs": [ + [ + "7efa60ce78ac343df8a013a2027c6c5ef29f9502edcbd769d2c21717fecc5951" + ] + ], + "shared_secrets": [ + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "7efa60ce78ac343df8a013a2027c6c5ef29f9502edcbd769d2c21717fecc5951" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 2, + 3, + 1001337 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjex54dmqmmv6rw353tsuqhs99ydvadxzrsy9nuvk74epvee55drs734pqq", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqsg59z2rppn4qlkx0yz9sdltmjv3j8zgcqadjn4ug98m3t6plujsq9qvu5n", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq7c2zfthc6x3a5yecwc52nxa0kfd20xuz08zyrjpfw4l2j257yq6qgnkdh5" + ], + "outputs": [ + { + "priv_key_tweak": "e336b92330c33030285ce42e4115ad92d5197913c88e06b9072b4a9b47c664a2", + "pub_key": "7efa60ce78ac343df8a013a2027c6c5ef29f9502edcbd769d2c21717fecc5951", + "signature": "c9e80dd3bdd25ca2d352ce77510f1aed37ba3509dc8cc0677f2d7c2dd04090707950ce9dd6c83d2a428063063aff5c04f1744e334f661f2fc01b4ef80b50f739" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "Multiple outputs with labels: un-labeled and labeled address; same recipient", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "03a6739499dc667d308baefea4de0c4a85cc72aece181bc05712d3919662610ff1" + }, + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + [ + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca" + ] + ], + "shared_secrets": [ + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 1 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj" + ], + "outputs": [ + { + "priv_key_tweak": "43100f89f1a6bf10081c92b473ffc57ceac7dbed600b6aba9bb3976f17dbb914", + "pub_key": "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "signature": "15c92509b67a6c211ebb4a51b7528d0666e6720de2343b2e92cfb97942ca14693c1f1fdc8451acfdb2644039f8f5c76114807fdc3d3a002d8a46afab6756bd75" + }, + { + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "Multiple outputs with labels: multiple outputs for labeled address; same recipient", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "03a6739499dc667d308baefea4de0c4a85cc72aece181bc05712d3919662610ff1" + }, + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "03a6739499dc667d308baefea4de0c4a85cc72aece181bc05712d3919662610ff1" + } + ] + }, + "expected": { + "outputs": [ + [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c" + ] + ], + "shared_secrets": [ + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 1 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj" + ], + "outputs": [ + { + "priv_key_tweak": "43100f89f1a6bf10081c92b473ffc57ceac7dbed600b6aba9bb3976f17dbb914", + "pub_key": "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "signature": "15c92509b67a6c211ebb4a51b7528d0666e6720de2343b2e92cfb97942ca14693c1f1fdc8451acfdb2644039f8f5c76114807fdc3d3a002d8a46afab6756bd75" + }, + { + "priv_key_tweak": "9d5fd3b91cac9ddfea6fc2e6f9386f680e6cee623cda02f53706306c081de87f", + "pub_key": "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "signature": "db0dfacc98b6a6fcc67cc4631f080b1ca38c60d8c397f2f19843f8f95ec91594b24e47c5bd39480a861c1209f7e3145c440371f9191fb96e324690101eac8e8e" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "Multiple outputs with labels: un-labeled, labeled, and multiple outputs for labeled address; same recipients", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + }, + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "03a6739499dc667d308baefea4de0c4a85cc72aece181bc05712d3919662610ff1" + }, + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjyh2ju7hd5gj57jg5r9lev3pckk4n2shtzaq34467erzzdfajfggty6aa5", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "0244baa5cf5db444a9e922832ff2c88716b566a85d62e8235aebd91884d4f64942" + }, + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjyh2ju7hd5gj57jg5r9lev3pckk4n2shtzaq34467erzzdfajfggty6aa5", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "0244baa5cf5db444a9e922832ff2c88716b566a85d62e8235aebd91884d4f64942" + } + ] + }, + "expected": { + "outputs": [ + [ + "006a02c308ccdbf3ac49f0638f6de128f875db5a213095cf112b3b77722472ae", + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa" + ], + [ + "006a02c308ccdbf3ac49f0638f6de128f875db5a213095cf112b3b77722472ae", + "3edf1ff6657c6e69568811bd726a7a7f480493aa42161acfe8dd4f44521f99ed", + "7ee1543ed5d123ffa66fbebc128c020173eb490d5fa2ba306e0c9573a77db8f3", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa" + ], + [ + "006a02c308ccdbf3ac49f0638f6de128f875db5a213095cf112b3b77722472ae", + "7ee1543ed5d123ffa66fbebc128c020173eb490d5fa2ba306e0c9573a77db8f3", + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701" + ], + [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "3c54444944d176437644378c23efb999ab6ab1cacdfe1dc1537b607e3df330e2", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ], + [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ], + [ + "3c54444944d176437644378c23efb999ab6ab1cacdfe1dc1537b607e3df330e2", + "602e10e6944107c9b48bd885b493676578c935723287e0ab2f8b7f136862568e", + "7ee1543ed5d123ffa66fbebc128c020173eb490d5fa2ba306e0c9573a77db8f3", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa" + ], + [ + "3c54444944d176437644378c23efb999ab6ab1cacdfe1dc1537b607e3df330e2", + "7ee1543ed5d123ffa66fbebc128c020173eb490d5fa2ba306e0c9573a77db8f3", + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ], + [ + "3edf1ff6657c6e69568811bd726a7a7f480493aa42161acfe8dd4f44521f99ed", + "7ee1543ed5d123ffa66fbebc128c020173eb490d5fa2ba306e0c9573a77db8f3", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ], + [ + "3edf1ff6657c6e69568811bd726a7a7f480493aa42161acfe8dd4f44521f99ed", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa", + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ], + [ + "602e10e6944107c9b48bd885b493676578c935723287e0ab2f8b7f136862568e", + "7ee1543ed5d123ffa66fbebc128c020173eb490d5fa2ba306e0c9573a77db8f3", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + [ + "602e10e6944107c9b48bd885b493676578c935723287e0ab2f8b7f136862568e", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa", + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca" + ], + [ + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ] + ], + "shared_secrets": [ + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "006a02c308ccdbf3ac49f0638f6de128f875db5a213095cf112b3b77722472ae", + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 1, + 1337 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjyh2ju7hd5gj57jg5r9lev3pckk4n2shtzaq34467erzzdfajfggty6aa5" + ], + "outputs": [ + { + "priv_key_tweak": "4e3352fbe0505c25e718d96007c259ef08db34f8c844e4ff742d9855ff03805a", + "pub_key": "006a02c308ccdbf3ac49f0638f6de128f875db5a213095cf112b3b77722472ae", + "signature": "6eeae1ea9eb826e3d0e812f65937100e0836ea188c04f36fabc4981eda29de8d3d3529390a0a8b3d830f7bca4f5eae5994b9788ddaf05ad259ffe26d86144b4b" + }, + { + "priv_key_tweak": "43100f89f1a6bf10081c92b473ffc57ceac7dbed600b6aba9bb3976f17dbb914", + "pub_key": "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "signature": "15c92509b67a6c211ebb4a51b7528d0666e6720de2343b2e92cfb97942ca14693c1f1fdc8451acfdb2644039f8f5c76114807fdc3d3a002d8a46afab6756bd75" + }, + { + "priv_key_tweak": "bf709f98d4418f8a67e738154ae48818dad44689cd37fbc070891a396dd1c633", + "pub_key": "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "signature": "42a19fd8a63dde1824966a95d65a28203e631e49bf96ca5dae1b390e7a0ace2cc8709c9b0c5715047032f57f536a3c80273cbecf4c05be0b5456c183fa122c06" + }, + { + "priv_key_tweak": "736f05e4e3072c3b8656bedef2e9bf54cbcaa2b6fe5320d3e86f5b96874dda71", + "pub_key": "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa", + "signature": "2e61bb3d79418ecf55f68847cf121bfc12d397b39d1da8643246b2f0a9b96c3daa4bfe9651beb5c9ce20e1f29282c4566400a4b45ee6657ec3b18fdc554da0b4" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "Single recipient: use silent payments for sender change", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + }, + { + "address": "sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqlv6saelkk5snl4wfutyxrchpzzwm8rjp3z6q7apna59z9huq4x754e5atr", + "scan_pub_key": "03b4cc0b090b6f49a684558852db60ee5eb1c5f74352839c3d18a8fc04ef7354e0", + "spend_pub_key": "03ecd43b9fdad484ff57278b21878b844276ce390622d03dd0cfb4288b7e02a6f5" + } + ] + }, + "expected": { + "outputs": [ + [ + "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ] + ], + "shared_secrets": [ + "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "037d12c02c3aed482658a28b8d1be030dac1daf995551491d74c00543af98572fb" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + "key_material": { + "spend_priv_key": "b8f87388cbb41934c50daca018901b00070a5ff6cc25a7e9e716a9d5b9e4d664", + "scan_priv_key": "11b7a82e06ca2648d5fded2366478078ec4fc9dc1d8ff487518226f229d768fd" + }, + "labels": [ + 0 + ] + }, + "expected": { + "addresses": [ + "sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqauj52ymtc4xdkmx3tgyhrsemg2g3303xk2gtzfy8h8ejet8fz8jcw23zua", + "sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqlv6saelkk5snl4wfutyxrchpzzwm8rjp3z6q7apna59z9huq4x754e5atr" + ], + "outputs": [ + { + "priv_key_tweak": "80cd767ed20bd0bb7d8ea5e803f8c381293a62e8a073cf46fb0081da46e64e1f", + "pub_key": "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff", + "signature": "7fbd5074cf1377273155eefafc7c330cb61b31da252f22206ac27530d2b2567040d9af7808342ed4a09598c26d8307446e4ed77079e6a2e61fea736e44da5f5a" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "037d12c02c3aed482658a28b8d1be030dac1daf995551491d74c00543af98572fb", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + }, + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + } + ], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "Single recipient: taproot input with NUMS point", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0440c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b22205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5ac21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac00150", + "prevout": { + "scriptPubKey": { + "hex": "5120da6f0595ecb302bbe73e2f221f05ab10f336b06817d36fd28fc6691725ddaa85" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140bd1e708f92dbeaf24a6b8dd22e59c6274355424d62baea976b449e220fd75b13578e262ab11b7aa58e037f0c6b0519b66803b7d9decaa1906dedebfb531c56c1", + "prevout": { + "scriptPubKey": { + "hex": "5120782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + } + }, + "private_key": "fc8716a97a48ba9a05a98ae47b5cd201a25a7fd5d8b73c203c5f7b6b6b3b6ad7" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 1, + "scriptSig": "", + "txinwitness": "0340268d31a9276f6380107d5321cafa6d9e8e5ea39204318fdc8206b31507c891c3bbcea3c99e2208d73bd127a8e8c5f1e45a54f1bd217205414ddb566ab7eda0092220e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85dac21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0", + "prevout": { + "scriptPubKey": { + "hex": "51200a3c9365ceb131f89b0a4feb6896ebd67bb15a98c31eaa3da143bb955a0f3fcb" + } + }, + "private_key": "8d4751f6e8a3586880fb66c19ae277969bd5aa06f61c4ee2f1e2486efdf666d3" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "79e79897c52935bfd97fc6e076a6431a0c7543ca8c31e0fc3cf719bb572c842d" + ] + ], + "shared_secrets": [ + "036f040608cd1e5ee79c54e78bea85904c895591f547beae080d0c5f6946c2730d" + ], + "input_private_key_sum": "fc8716a97a48ba9a05a98ae47b5cd201a25a7fd5d8b73c203c5f7b6b6b3b6ad7", + "input_pub_keys": [ + "02782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0440c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b22205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5ac21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac00150", + "prevout": { + "scriptPubKey": { + "hex": "5120da6f0595ecb302bbe73e2f221f05ab10f336b06817d36fd28fc6691725ddaa85" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140bd1e708f92dbeaf24a6b8dd22e59c6274355424d62baea976b449e220fd75b13578e262ab11b7aa58e037f0c6b0519b66803b7d9decaa1906dedebfb531c56c1", + "prevout": { + "scriptPubKey": { + "hex": "5120782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 1, + "scriptSig": "", + "txinwitness": "0340268d31a9276f6380107d5321cafa6d9e8e5ea39204318fdc8206b31507c891c3bbcea3c99e2208d73bd127a8e8c5f1e45a54f1bd217205414ddb566ab7eda0092220e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85dac21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0", + "prevout": { + "scriptPubKey": { + "hex": "51200a3c9365ceb131f89b0a4feb6896ebd67bb15a98c31eaa3da143bb955a0f3fcb" + } + } + } + ], + "outputs": [ + "79e79897c52935bfd97fc6e076a6431a0c7543ca8c31e0fc3cf719bb572c842d" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "3ddec3232609d348d6b8b53123b4f40f6d4f5398ca586f087b0416ec3b851496", + "pub_key": "79e79897c52935bfd97fc6e076a6431a0c7543ca8c31e0fc3cf719bb572c842d", + "signature": "d7d06e3afb68363031e4eb18035c46ceae41bdbebe7888a4754bc9848c596436869aeaecff0527649a1f458b71c9ceecec10b535c09d01d720229aa228547706" + } + ], + "tweak": "02213b872c9a6ee28a0d861384a1b3e3ec7257f4855ed09b4323e3899f3b028989", + "shared_secret": "036f040608cd1e5ee79c54e78bea85904c895591f547beae080d0c5f6946c2730d", + "input_pub_key_sum": "02782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + } + } + ] + }, + { + "comment": "Pubkey extraction from malleated p2pkh", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 1, + "scriptSig": "0075473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 2, + "scriptSig": "5163473045022100e7d26e77290b37128f5215ade25b9b908ce87cc9a4d498908b5bb8fd6daa1b8d022002568c3a8226f4f0436510283052bfb780b76f3fe4aa60c4c5eb118e43b187372102e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d67483046022100c0d3c851d3bd562ae93d56bcefd735ea57c027af46145a4d5e9cac113bfeb0c2022100ee5b2239af199fa9b7aa1d98da83a29d0a2cf1e4f29e2f37134ce386d51c544c2102ad0f26ddc7b3fcc340155963b3051b85289c1869612ecb290184ac952e2864ec68", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914c82c5ec473cbc6c86e5ef410e36f9495adcf979988ac" + } + }, + "private_key": "72b8ae09175ca7977f04993e651d88681ed932dfb92c5158cdf0161dd23fda6e" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "4612cdbf845c66c7511d70aab4d9aed11e49e48cdb8d799d787101cdd0d53e4f" + ] + ], + "shared_secrets": [ + "034773b97ccad9791cb4213964ff9896ccd6581ee69345de5d114786d9d86b03a2" + ], + "input_private_key_sum": "610e0f75fd05e5e80e088b57af0a46da06cb0700c0c5907aa6d29c6b4ce46348", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "02e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 1, + "scriptSig": "0075473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 2, + "scriptSig": "5163473045022100e7d26e77290b37128f5215ade25b9b908ce87cc9a4d498908b5bb8fd6daa1b8d022002568c3a8226f4f0436510283052bfb780b76f3fe4aa60c4c5eb118e43b187372102e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d67483046022100c0d3c851d3bd562ae93d56bcefd735ea57c027af46145a4d5e9cac113bfeb0c2022100ee5b2239af199fa9b7aa1d98da83a29d0a2cf1e4f29e2f37134ce386d51c544c2102ad0f26ddc7b3fcc340155963b3051b85289c1869612ecb290184ac952e2864ec68", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914c82c5ec473cbc6c86e5ef410e36f9495adcf979988ac" + } + } + } + ], + "outputs": [ + "4612cdbf845c66c7511d70aab4d9aed11e49e48cdb8d799d787101cdd0d53e4f" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "10bde9781def20d7701e7603ef1b1e5e71c67bae7154818814e3c81ef5b1a3d3", + "pub_key": "4612cdbf845c66c7511d70aab4d9aed11e49e48cdb8d799d787101cdd0d53e4f", + "signature": "6137969f810e9e8ef6c9755010e808f5dd1aed705882e44d7f0ae64eb0c509ec8b62a0671bee0d5914ac27d2c463443e28e999d82dc3d3a4919f093872d947bb" + } + ], + "tweak": "028d6617f9bfe08604beb2188f4eebec923f5f8cc436fa6d14e4256e49bc32e7c8", + "shared_secret": "034773b97ccad9791cb4213964ff9896ccd6581ee69345de5d114786d9d86b03a2", + "input_pub_key_sum": "038b0d201fe111bdc0e6953772bd02a41959d25d5b2f66bcbe348af27bbdd42735" + } + } + ] + }, + { + "comment": "P2PKH and P2WPKH Uncompressed Keys are skipped", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b974104782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233387c5343bf58e23269e903335b958a12182f9849297321e8d710e49a8727129cab", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9144b92ac4ac6fe6212393894addda332f2e47a315688ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 1, + "scriptSig": "", + "txinwitness": "02473045022100e7d26e77290b37128f5215ade25b9b908ce87cc9a4d498908b5bb8fd6daa1b8d022002568c3a8226f4f0436510283052bfb780b76f3fe4aa60c4c5eb118e43b187374104e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d6fe8190e189be57d0d5bcd17dbcbcd04c9b4a1c5f605b10d5c90abfcc0d12884", + "prevout": { + "scriptPubKey": { + "hex": "00140423f731a07491364e8dce98b7c00bda63336950" + } + }, + "private_key": "72b8ae09175ca7977f04993e651d88681ed932dfb92c5158cdf0161dd23fda6e" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6" + ] + ], + "shared_secrets": [ + "0295a54c359da5b2640601ddbedb26e040cb97b6a3432e60b76d1258e85f72fa64" + ], + "input_private_key_sum": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b974104782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233387c5343bf58e23269e903335b958a12182f9849297321e8d710e49a8727129cab", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9144b92ac4ac6fe6212393894addda332f2e47a315688ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 1, + "scriptSig": "", + "txinwitness": "02473045022100e7d26e77290b37128f5215ade25b9b908ce87cc9a4d498908b5bb8fd6daa1b8d022002568c3a8226f4f0436510283052bfb780b76f3fe4aa60c4c5eb118e43b187374104e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d6fe8190e189be57d0d5bcd17dbcbcd04c9b4a1c5f605b10d5c90abfcc0d12884", + "prevout": { + "scriptPubKey": { + "hex": "00140423f731a07491364e8dce98b7c00bda63336950" + } + } + } + ], + "outputs": [ + "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "688fa3aeb97d2a46ae87b03591921c2eaf4b505eb0ddca2733c94701e01060cf", + "pub_key": "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6", + "signature": "72e7ad573ac23255d4651d5b0326a200496588acb7a4894b22092236d5eda6a0a9a4d8429b022c2219081fefce5b33795cae488d10f5ea9438849ed8353624f2" + } + ], + "tweak": "02b04034f00da0678507d1345b7d56fecef825a1151f9dc7d8ca6946452a9e1f43", + "shared_secret": "0295a54c359da5b2640601ddbedb26e040cb97b6a3432e60b76d1258e85f72fa64", + "input_pub_key_sum": "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + } + ] + }, + { + "comment": "Skip invalid P2SH inputs", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "16001419c2f3ae0ca3b642bd3e49598b8da89f50c14161", + "txinwitness": "02483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "prevout": { + "scriptPubKey": { + "hex": "a9148629db5007d5fcfbdbb466637af09daf9125969387" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 1, + "scriptSig": "1600144b92ac4ac6fe6212393894addda332f2e47a3156", + "txinwitness": "02473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b974104782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233387c5343bf58e23269e903335b958a12182f9849297321e8d710e49a8727129cab", + "prevout": { + "scriptPubKey": { + "hex": "a9146c9bf136fbb7305fd99d771a95127fcf87dedd0d87" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 2, + "scriptSig": "00493046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d601483045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b97014c695221025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be52103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233382102e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d53ae", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "a9141044ddc6cea09e4ac40fbec2ba34ad62de6db25b87" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6" + ] + ], + "shared_secrets": [ + "0295a54c359da5b2640601ddbedb26e040cb97b6a3432e60b76d1258e85f72fa64" + ], + "input_private_key_sum": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "16001419c2f3ae0ca3b642bd3e49598b8da89f50c14161", + "txinwitness": "02483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "prevout": { + "scriptPubKey": { + "hex": "a9148629db5007d5fcfbdbb466637af09daf9125969387" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 1, + "scriptSig": "1600144b92ac4ac6fe6212393894addda332f2e47a3156", + "txinwitness": "02473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b974104782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233387c5343bf58e23269e903335b958a12182f9849297321e8d710e49a8727129cab", + "prevout": { + "scriptPubKey": { + "hex": "a9146c9bf136fbb7305fd99d771a95127fcf87dedd0d87" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 2, + "scriptSig": "00493046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d601483045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b97014c695221025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be52103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233382102e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d53ae", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "a9141044ddc6cea09e4ac40fbec2ba34ad62de6db25b87" + } + } + } + ], + "outputs": [ + "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "688fa3aeb97d2a46ae87b03591921c2eaf4b505eb0ddca2733c94701e01060cf", + "pub_key": "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6", + "signature": "72e7ad573ac23255d4651d5b0326a200496588acb7a4894b22092236d5eda6a0a9a4d8429b022c2219081fefce5b33795cae488d10f5ea9438849ed8353624f2" + } + ], + "tweak": "02b04034f00da0678507d1345b7d56fecef825a1151f9dc7d8ca6946452a9e1f43", + "shared_secret": "0295a54c359da5b2640601ddbedb26e040cb97b6a3432e60b76d1258e85f72fa64", + "input_pub_key_sum": "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + } + ] + }, + { + "comment": "Recipient ignores unrelated outputs", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn", + "scan_pub_key": "02062d49ffc02787d586c608dfbec184aa91a6597d97b463ea5c6babd9d17a95a3", + "spend_pub_key": "0381eb9a9a9ec739d527c1631b31b421566f5c2a47b4ab5b1f6a686dfb68eab716" + } + ] + }, + "expected": { + "outputs": [ + [ + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8" + ] + ], + "shared_secrets": [ + "03dd5fd04d3c8863be750a1bd7474df06161461d38d3ce1397a5c78cee112cdcd2" + ], + "input_private_key_sum": "ee55616ce5a93e508f03f21949ecbe70a2a0b107b6e1df5d98b4e4da4adaca1b", + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [], + "tweak": "0314bec14463d6c0181083d607fecfba67bb83f95915f6f247975ec566d5642ee8", + "shared_secret": "038efbcbc1b0938fba3bf59fea1219a3c54b6d6f9107560da05001407adc13f413", + "input_pub_key_sum": "03853f51bef283502181e93238c8708ae27235dc51ae45a0c4053987c52fc6428b" + } + } + ] + }, + { + "comment": "No valid inputs, sender generates no outputs", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d641045a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5c61836c9b1688ba431f7ea3039742251f62f0dca3da1bee58a47fa9b456c2d52", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914460e8b41545d2dbe7e0671f0f573e2232814260a88ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b974104782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233387c5343bf58e23269e903335b958a12182f9849297321e8d710e49a8727129cab", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9144b92ac4ac6fe6212393894addda332f2e47a315688ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [] + ], + "shared_secrets": [ + null + ], + "input_pub_keys": [] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d641045a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5c61836c9b1688ba431f7ea3039742251f62f0dca3da1bee58a47fa9b456c2d52", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914460e8b41545d2dbe7e0671f0f573e2232814260a88ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b974104782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233387c5343bf58e23269e903335b958a12182f9849297321e8d710e49a8727129cab", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9144b92ac4ac6fe6212393894addda332f2e47a315688ac" + } + } + } + ], + "outputs": [ + "782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [], + "tweak": null, + "shared_secret": null + } + } + ] + }, + { + "comment": "Input keys sum up to zero / point at infinity: sending fails, receiver skips tx", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e", + "vout": 0, + "scriptSig": "", + "txinwitness": "024730440220085003179ce1a3a88ce0069aa6ea045e140761ab88c22a26ae2a8cfe983a6e4602204a8a39940f0735c8a4424270ac8da65240c261ab3fda9272f6d6efbf9cfea366012102557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "prevout": { + "scriptPubKey": { + "hex": "00149d9e24f9fab4e35bf1a6df4b46cb533296ac0792" + } + }, + "private_key": "a6df6a0bb448992a301df4258e06a89fe7cf7146f59ac3bd5ff26083acb22ceb" + }, + { + "txid": "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e", + "vout": 1, + "scriptSig": "", + "txinwitness": "0247304402204586a68e1d97dd3c6928e3622799859f8c3b20c3c670cf654cc905c9be29fdb7022043fbcde1689f3f4045e8816caf6163624bd19e62e4565bc99f95c533e599782c012103557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "prevout": { + "scriptPubKey": { + "hex": "00149860538b5575962776ed0814ae222c7d60c72d7b" + } + }, + "private_key": "592095f44bb766d5cfe20bda71f9575ed2df6b9fb9addc7e5fdffe0923841456" + } + ], + "recipients": [ + { + "address": "sp1qqtrqglu5g8kh6mfsg4qxa9wq0nv9cauwfwxw70984wkqnw2uwz0w2qnehen8a7wuhwk9tgrzjh8gwzc8q2dlekedec5djk0js9d3d7qhnq6lqj3s", + "scan_pub_key": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", + "spend_pub_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + } + ] + }, + "expected": { + "outputs": [ + [] + ], + "shared_secrets": [ + null + ], + "input_pub_keys": [ + "02557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "03557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e", + "vout": 0, + "scriptSig": "", + "txinwitness": "024730440220085003179ce1a3a88ce0069aa6ea045e140761ab88c22a26ae2a8cfe983a6e4602204a8a39940f0735c8a4424270ac8da65240c261ab3fda9272f6d6efbf9cfea366012102557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "prevout": { + "scriptPubKey": { + "hex": "00149d9e24f9fab4e35bf1a6df4b46cb533296ac0792" + } + } + }, + { + "txid": "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e", + "vout": 1, + "scriptSig": "", + "txinwitness": "0247304402204586a68e1d97dd3c6928e3622799859f8c3b20c3c670cf654cc905c9be29fdb7022043fbcde1689f3f4045e8816caf6163624bd19e62e4565bc99f95c533e599782c012103557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "prevout": { + "scriptPubKey": { + "hex": "00149860538b5575962776ed0814ae222c7d60c72d7b" + } + } + } + ], + "outputs": [ + "0000000000000000000000000000000000000000000000000000000000000000" + ], + "key_material": { + "spend_priv_key": "0000000000000000000000000000000000000000000000000000000000000001", + "scan_priv_key": "0000000000000000000000000000000000000000000000000000000000000002" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqtrqglu5g8kh6mfsg4qxa9wq0nv9cauwfwxw70984wkqnw2uwz0w2qnehen8a7wuhwk9tgrzjh8gwzc8q2dlekedec5djk0js9d3d7qhnq6lqj3s" + ], + "outputs": [], + "tweak": null, + "shared_secret": null + } + } + ] + }, + { + "comment": "Input keys intermediate sum is zero but final sum is non-zero", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e", + "vout": 0, + "scriptSig": "", + "txinwitness": "0247304402203e5537fa8c876b3475e7efe4f1474b0f48b7a6e4169179db5de9bb5b55ad1bd10220200e06f8f4d29dbc48bbcdf90df3278e798ce6cbf3c9fbf90427599fde147867012102557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "prevout": { + "scriptPubKey": { + "hex": "00149d9e24f9fab4e35bf1a6df4b46cb533296ac0792" + } + }, + "private_key": "a6df6a0bb448992a301df4258e06a89fe7cf7146f59ac3bd5ff26083acb22ceb" + }, + { + "txid": "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e", + "vout": 1, + "scriptSig": "", + "txinwitness": "0247304402207fdad0faf46edc54f5a5c67d33b2fa8d3f1fdc869381fd96e659f9e0c470ab1e022044f0d973339618b18667cef9a6251817f7f431f7f2b252a8cb760ccb40e7d823012103557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "prevout": { + "scriptPubKey": { + "hex": "00149860538b5575962776ed0814ae222c7d60c72d7b" + } + }, + "private_key": "592095f44bb766d5cfe20bda71f9575ed2df6b9fb9addc7e5fdffe0923841456" + }, + { + "txid": "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e", + "vout": 2, + "scriptSig": "", + "txinwitness": "0247304402203e5537fa8c876b3475e7efe4f1474b0f48b7a6e4169179db5de9bb5b55ad1bd10220200e06f8f4d29dbc48bbcdf90df3278e798ce6cbf3c9fbf90427599fde147867012102557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "prevout": { + "scriptPubKey": { + "hex": "00149d9e24f9fab4e35bf1a6df4b46cb533296ac0792" + } + }, + "private_key": "a6df6a0bb448992a301df4258e06a89fe7cf7146f59ac3bd5ff26083acb22ceb" + } + ], + "recipients": [ + { + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "scan_pub_key": "0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4", + "spend_pub_key": "025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36" + } + ] + }, + "expected": { + "outputs": [ + [ + "7e88a7536c90770be4d2693a84ed03abe3fdcc5a29f96ec3433effec3b0c2194" + ] + ], + "shared_secrets": [ + "037dc4e5904ab4770dbdbb628860b54265fdbb7810b8afdf9f582fedaabfdebef0" + ], + "input_private_key_sum": "a6df6a0bb448992a301df4258e06a89fe7cf7146f59ac3bd5ff26083acb22ceb", + "input_pub_keys": [ + "02557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "03557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "02557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e", + "vout": 0, + "scriptSig": "", + "txinwitness": "0247304402203e5537fa8c876b3475e7efe4f1474b0f48b7a6e4169179db5de9bb5b55ad1bd10220200e06f8f4d29dbc48bbcdf90df3278e798ce6cbf3c9fbf90427599fde147867012102557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "prevout": { + "scriptPubKey": { + "hex": "00149d9e24f9fab4e35bf1a6df4b46cb533296ac0792" + } + } + }, + { + "txid": "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e", + "vout": 1, + "scriptSig": "", + "txinwitness": "0247304402207fdad0faf46edc54f5a5c67d33b2fa8d3f1fdc869381fd96e659f9e0c470ab1e022044f0d973339618b18667cef9a6251817f7f431f7f2b252a8cb760ccb40e7d823012103557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "prevout": { + "scriptPubKey": { + "hex": "00149860538b5575962776ed0814ae222c7d60c72d7b" + } + } + }, + { + "txid": "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e", + "vout": 2, + "scriptSig": "", + "txinwitness": "0247304402203e5537fa8c876b3475e7efe4f1474b0f48b7a6e4169179db5de9bb5b55ad1bd10220200e06f8f4d29dbc48bbcdf90df3278e798ce6cbf3c9fbf90427599fde147867012102557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "prevout": { + "scriptPubKey": { + "hex": "00149d9e24f9fab4e35bf1a6df4b46cb533296ac0792" + } + } + } + ], + "outputs": [ + "7e88a7536c90770be4d2693a84ed03abe3fdcc5a29f96ec3433effec3b0c2194" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "3d5b7a284108f93b9fe78f2c300d2ca9ef3b4e0cd0de673fc72990f2a4f417b9", + "pub_key": "7e88a7536c90770be4d2693a84ed03abe3fdcc5a29f96ec3433effec3b0c2194", + "signature": "f8d5222f1a682215c40ab677f2104606f2a0e6c5cb1a2d248d970fb61d4eadb31702353e6b41ea5a9b24817efa0eaf535552eeee8a794b662c3cf303b6c86672" + } + ], + "tweak": "039c68bacb7efbf2175d781822f460afc4839a5798fabb055b50d939231bf57bb6", + "shared_secret": "037dc4e5904ab4770dbdbb628860b54265fdbb7810b8afdf9f582fedaabfdebef0", + "input_pub_key_sum": "02557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975" + } + } + ] + }, + { + "comment": "Maximum per-group recipient limit K_max is exceeded (2324 matches): sending fails, receiver doesn't scan beyond limit", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "3cde0e7ad8adf24a4e52fd18efafffb94f73f4c7facf2627a5b588ad1c9db87f", + "vout": 0, + "scriptSig": "", + "txinwitness": "01407ce01bc5ef2e4aae353e7f291fec32623d6a491b5421be6dccc70345f32ebae5baa6654c60c074e72da76fd06b343474370e56d3ec0413156ec6261757125ad4", + "prevout": { + "scriptPubKey": { + "hex": "5120bca87f72e604e8850064552bedf380ca4584227057efe12a6cc238470658aaa3" + } + }, + "private_key": "0000000000000000000000000000000000000000000000000000000000001337" + } + ], + "recipients": [ + { + "address": "sp1qqtnvg7hxjck9ag24naytgd7pjwsmevwh95ydwht58w3uh7uw0ta7kqmk4a3pm24z0yepw6aw27pku56md0hq5ace0l3p3jstkqaajwrsjqc5dde5", + "scan_pub_key": "02e6c47ae6962c5ea1559f48b437c193a1bcb1d72d08d75d743ba3cbfb8e7afbeb", + "spend_pub_key": "0376af621daaa27932176bae57836e535b6bee0a77197fe218ca0bb03bd9387090", + "count": 2324 + } + ] + }, + "expected": { + "outputs": [ + [] + ], + "shared_secrets": [ + null + ], + "input_private_key_sum": "0000000000000000000000000000000000000000000000000000000000001337", + "input_pub_keys": [ + "02bca87f72e604e8850064552bedf380ca4584227057efe12a6cc238470658aaa3" + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "3cde0e7ad8adf24a4e52fd18efafffb94f73f4c7facf2627a5b588ad1c9db87f", + "vout": 0, + "scriptSig": "", + "txinwitness": "01407ce01bc5ef2e4aae353e7f291fec32623d6a491b5421be6dccc70345f32ebae5baa6654c60c074e72da76fd06b343474370e56d3ec0413156ec6261757125ad4", + "prevout": { + "scriptPubKey": { + "hex": "5120bca87f72e604e8850064552bedf380ca4584227057efe12a6cc238470658aaa3" + } + }, + "private_key": "0000000000000000000000000000000000000000000000000000000000001337" + } + ], + "outputs": [ + "5cfb6fd7a7e5d2af0fee276f2104ce2e5c86cde7955b98f2f1fb32811b448f2f", + "da290e0d79ead5cd2e5d150aac80dcab54dbd0e68f241a6c34dc30906eabd13d", + "d4a2b1458f0c8b20d756bde8e545560d7e7a65f6b4158f01ed34a913ba3a4603", + "24752e555b0edcca123f769113692224bf1e6db137cddeb7d69ae52e51144e9e", + "c911f722ef9a3594d6b85c585d4f3ed8dc437f850591dc627a0154723644114b", + "ef91ddaa83a0b748d638dc9be8c9771b049a04815f03967b7ee993bde6c260ab", + "ee4ed334fb38af95f52c6bbadcf2f491e646b9cc8ed31da67635f36c3cfdaba0", + "a17d61b4772e453b32782744902261e051510cbafc04f086f53480d1ceee1a9c", + "44d89f5e903739072e3aff8a3eb2f351520f77ff288ac8be277be3f28834bdd2", + "3eff02a4a04d66d63f6bc93a693f23db703f351c0a1ff1656b907ed348a9a35f", + "628c23a6e21e6a47b80274fc80e4decb98ff79b8040c7f9ea5972e4920b62b6d", + "e72190c0a5ea167c2663b18d378520b9228da61ebbc99c3fa2af39e01fb9501e", + "24183edbc5b80588e0e5b803e924e77fc1cab1afca54cfd285dad9bfc2a34f6d", + "c45bad25a223f0f617e8167f858037907e4454210a745f3b9798c8913646dfc6", + "9f9cec85011d7f718f96ee1a2940df21920a857cd1a545255579cb615f285a42", + "8c60891f9bc2f772efc51d44f7cc885161412835b6fd5aac3b0cf57c3c2f9154", + "85ac46a6ac9569ed67d1bc5342ed3b591af5e01581d1b09bcb2c71deb9b2c2c9", + "8a6bfe47e616c6854fc797a4af53ef51c942b99eae18f316594fc8eb8869ca05", + "ed5e1aa6a8b4e805273367b8ec3ade16ed6494270fc2169ba920b89770efe8eb", + "2448503c55a1a7035c069e1d5461b81774c43bf6061421fdd0157009bfe96a8a", + "d0dcb3d7b551eac8d31fceccb83df415c554860d5541dd08ff5fa0028ce5704f", + "f11a298b54b650613e0b0183552c9c9e42c3812e03cf713d351ef71106b6ee3f", + "49749206f23791f858c453cb60160d48710075bdbfd33907b0046d10f0b0295e", + "3fbfdb52cbf0ea9e6655a211748bf2ac2dc92ff3cfeeef0f0a26be9987016de0", + "d4a29144511981919cadbe8227fc68379e85d3fe399f177c2ae36c304b6ad6b0", + "c586cc196ff56f42396f426a0200afee3a6c41058d81f19caf6536ec0efd5605", + "c666994f027ceab025256af28d8ea6aec452fb8b0e1f69e7e13a4f3c5119f4ae", + "5c9216cc4529c3f9c62caf70b04f8354ce9ec2cabaef745092bea1b160328fda", + "d9aea54323503d78c026a8cd465c19a6df54b32ee866714d190e8260054361da", + "ba71f87ca1d74eee115d8e8a26293fe2c88d9d66cff185a7a079282f02f931ad", + "b8b930666b2df74c33dc8963221080c115246070e3fcd51f39bee71312db4fc4", + "b7f42d2f18a3ac61f570b64ae6864f7dbff774a370ba27358d76797e841d5304", + "d636d179055241a0f8bb7db193912da513a8999684d9829b266985a8083819a9", + "be9622aec125f9605bcee193a99ca27c16bc591122ff8b44c511a85e6f63ec9d", + "af5beb25776f84fadd6079c3ffd85775a35ad74096a58229e1d5076d836ca25d", + "75fb55ec31e304456f663c9a66321e67f9d9bd8f43749494acc5189ed57b0d4a", + "8dfebff33c514486d06eeb4ab8eb8471496556858c4382f73780620101a77f71", + "93bfd4e75706951de4a59c7f70c5d2ce4c617b4bde0b0b23ae046df096116726", + "b6eeca22f43bd62a4fcb3eee719e07d39ea703fa0707970fc197916f2efebbb5", + "0437c3971f73342e5c41178590e1cb1364cb855b2d20b7b6c1c1ed0e7da5b57b", + "d284c831cd9d52793977925ce3111b68f36d1ea456e46716158fe6947fb6d635", + "d95b8d2789fd06cc764d47cc7e0697404220ae8f938ef47dcc9a1bba299fbb9f", + "33599fb1fe313b5a2680b126bfee93cb097d938915d425a63e307e5834479b95", + "05ba86903fac2f02ef2e152072bc6067008ee8ee34ce55ad460801303373eaab", + "0b757802e56728b6d5b28e671a90de573d7251a47fc1bcd9319b617cac8e1463", + "b4a2451e725e4cbcdf47fc736139cfe29c847e2f75cbb7653d80c8522bd48a6c", + "8dd800c3a7cb1ee60f52db3bb69413c4c4247b0b134576552c675970f9903c9b", + "4de21129c6fdf5568040346bbdda272caaadd58218ec6f1f152518068dac4d4d", + "22f6927eae0c3717395df6483d756028839fbf0814f8cf0391159efa65dd2854", + "192d26fe0bc772c705c2657dd5c5033ac41ba408793e6c721f74c53747be16e8", + "9527b8e19570a16ce2d8dd3620dfd915e461bccc82340fe682f7f811b978814d", + "f84039ff95cd44f842d7cf6982a726957a89d90bb151a368e62770720d3efbee", + "b5b36dd84b352cddf715246d0463984b9d7894f50b566a20042c4d485df41946", + "592e1bd733a1095b9e5b770021769090df0c001260803fce9a9b761ba697a5e7", + "dd2fdbd6932d8b56d6ff91db3ed28f282a6ca01588ee5a552d061da215580411", + "82f95ef71d4d0b32e3f0cef89e6e90a8c271cd5536b38c507fc69b354ab51062", + "1800903b2d5f8483a12f0b98a5b35ed39d6cf8f4a52f495395f1816d6e3c139c", + "121a2c2ea70a6986af8bde71b7f75ac9f9e1189eeb9756395e7281702f01936d", + "8578df7e8ec4b068f64dca679310894afec095dbbedbefef5944c3a319809b8b", + "5cebde1dbb352ff62841d7c50c3c45cd0fd1b7b3ebe884a7cf29cef6686695ab", + "362f7a73f2620ea061586b5edbee01f9c026ae6e0d95131c570c44933fc41f10", + "3a19b5976236d44a3d1d6920b348fcef9ec830aac56aa672353c711471ebc36a", + "842c43c5293b03811165f1ff7e04c189ccd355fe08d37808a843a0995541c075", + "0ab1fdbf7117c88aed3f882d5f7dee8964c9246030086b9ee3e2ebed4eb2c731", + "7cf8ca4463d4bd500cc12623ac9d3984cc48e7fbb98bebb4a498c2fd66d54cde", + "631676f3369eb479686256cef33c4fb061c212cc215ab442d8f7837c039ddacc", + "2fff6de5386b65efe6062cc2242b692a1e811a083612ab79fa7f7517f1488767", + "3c7d8b7f2a56e22ade186a70a3039b4d4e4e3c0be6e661770b0a7b2ef18b178b", + "e030882188281a754c00706f621084f96869bf95bb5d005ccc7b2649fbfc747e", + "072cb899c3eb621240792a39bf727bf59398329748317d5ca1addfd8ee2f7e92", + "37cfa6b7214e99bbec6323802115b9615b10851ac85cdc26f213b9532fdefcb8", + "68b14b71ad6b5e2d41ef47fe1b9229a0b8297ede0812d1b9cb95aac185d1a5b8", + "652ef549580cf412bb217fb15b2a0a1db8feb3c79969b95364a80dd97d565f09", + "dd3504b7563a65115a050505bc88ee23a50e37c354773cac329355c16c43d839", + "6d7f05d6ee71459d2ea982f7a5cd4a251d7466d4892e6c8b6aaf45eec66472d7", + "14c28adbf9008d0cbcc978006b6c1ec7be51a134bd5587e94ee33844b62c1ffb", + "f9c20067f16feaaf3f8daa8456dd9334725ebdc8b49b4f74f5a02c38ef5f2804", + "e2c537948a01054b7a0d630952c2ddd395a3a265d618e22ee9d99d4597f82322", + "70e3a71945fee29350b5b10a8ee8dcbd19c2f734e22407bb6b50ec3acb1035a5", + "effbced848df72cd467b1ada7353b1ddc0b7f3f08190c0f2fc747f18303ed9f8", + "02d6eedc5abc295bcfbd4c30a8fa3406c8256d8e51f4714800b4aecb795a976b", + "92134c6158993a0390af92565e166418561c9b157989d02304b68c2ee1babe14", + "6065e3e3aff3e2b2f33bf4af9256e9368b8a303335b0587c8cc9e31cb9db42bd", + "732fd2ba9efabc6d79bd7404c1c6f1d5c1f14c9bfe02ee1320621824847935c2", + "209f9c958b5f4813b32205a44d05c83aa1009cb7b9c7343f0ca3717ae069defc", + "1d274fcb65b6d4c846e95030c37851a97d161f926ed88f6c4dec4ed7007fc23b", + "1d3787106cf47e1e9d08761a6c6f427ef6c07d4a571f23d340ae2d31d34b1d8e", + "5697a3f56a0a2a6bd6991ba05559df9b526d5c42e19604c398548a7cb0dec0b5", + "8c0f581f38238215f10f8017e3d234cbda9d94ca017bbd6b076ac8666014b3d3", + "78f061b0fc912fbbeb0d207b92528ad78ded55ec145c8f18133991847fb9cbe9", + "1e3555081a869f3aafa3d74d6b5afa942baace60869ecd4cd33635f84602b715", + "4f0e36ac0c4813db3fdf79d85ef0db89150dc1be2c879ba06299c049288ab8be", + "6b048c38924b7a1f970cdff081124b739202b703db5c17bafa4ad23a5bcb97e9", + "a5f5e9238aec20bd706262f64671ca2d05961f55ca383953d489edf857e4b2a4", + "a047b08e2af7ae5bfb9e54f98051ff7cd5e7dc88a25afacdd9a324526cd33c63", + "66ae2b9513a0e0ad125001bcd5f3a36e18b4ec009db4020ea4afc4951cb39a34", + "621d8269c8f93b6336f8b7480516d2191cfb15a1d9501463771d0fb70c1afa33", + "76b94079066bff52a077ba1d1ba8cdec4d6484d1ce2f7454b2b04aa3d9cf52ed", + "33c6a108f9d4fe054c085991d89d2a5c32b8f108c95b0e688c5763424c283e54", + "52575ed2ad84295a070a99e7785ff0e4dd32a93d3830be7f7c2811804f3df2f3", + "158dfe014c271c148b38459ef16b49cc3b5c6bdfe052795a6f6a6aacbb3e47b4", + "d471729cb231deeb2f1a67f91962ecfcb9e8c66f66b6b79fe1117d34ae86a7c0", + "e086b1a09ab5307d953e25366b1acf7c41047a926aa6a4d122c5a34e3e0d93d4", + "d541d825401a1734d9961fdb4f0e9ce7af220c6929648e0aa2e885cd28696343", + "a555af7a52ac07e681c247f1d6597f8240d28e76036bbf3b2be1f7cfab90dfd6", + "8598fe7a4a6c6fa865c0ca8e8e65c96301c80965e19e48f5d4ca29ac073551c7", + "70c9ac61ab3508a7419ead73503d8fe51b4ecba73a55d4b167cd4db6c9b0cb8a", + "e594eb83b38ad916176abff3f0ec16d7a814a461012c8f9bc8286274af3f1856", + "fa4e0df6df82f21050394fc2caaa8a8713b131d0d1f7d21eb92b56796095bf17", + "891711c535579b555105e5ec18ea0f2f610c6fe5126ff0f6a4257e99a24033c5", + "f192c9b2c49dab7515d405052f825abead30ef9a870ba3bdad98c1d6256be461", + "70c22f6ceb81744c83eb8dae69980f4583c75431457dbb14f5ecd6c9fae3e63d", + "7d563f44bdfcb2a10ee053f4c05b8d46fa90d44822775f97ee8cfbdeda7a4ca1", + "c09b3df69eaa232a9119e2fb805cdccacb658b2fb9c7f3a2106c8399aa928033", + "15b4b3d50fef72de80fef6e3351dae0f203902e4c4db8ce8d8737b67aed9104d", + "c9b7718633714b3ce1b489f13e53fc647be53cba7703f07a35a8815245b23ff3", + "2d344af9852fc2451ca5d2ed7155e074efd2c53ca9cbed7f48c74658967aeca6", + "c8b778b5c1fc378365d8b342915215117c3d365e12f61f3ccefbe4091879e373", + "1deb076e44c029027c1aa10000305c3eded5d401a036c21a4dd3d3455c0c33a1", + "d2d2dfe410cefbae2309e746690818e8d52dcd80ebd192cddaa00204b6833d05", + "d18a0a0bcc678908ff00b236296f0a955a9aa52a802ee4175849d0bcb3d6738a", + "7af51e08357e455e4dda86d60da75899b5ffce4bebc7f96037d1e404b1260ebe", + "65b930580f9738e1a7af2ac56faad5bdc6a417e75cb872b7f62513f4e8e36b33", + "71c8e88c7c478a4d919d0c80d351331102584d032d82aa25510801e18bb5d6b9", + "2166bad16a6e4a1f77a22b0fb13f7f11d618a123c3c077dbf1ed1504ed68cbb4", + "717a3a5903c2b12aea8be15599ea9864259a964c0139125e649fefbec763890e", + "0d042f5767f6cb2f94526b5b6da8ef986e4a5927eb5f734bdeb6c6ec3bb8211a", + "bb67d586774ae9f3653c609dbdd98f216315b54c0ce24ad9e359d0c2c21614a3", + "945b2962ec44c54ea25e3e35e32a2362c130fc3a9495b16871e8a2eadbe457e7", + "7d5324e5e158c82388cba0d497530c6de0c1ba97131688931c03dbfc60ca7541", + "21805a8f8d43aede3e927b80ef04924d388e96f3cc3947a0dbc1bd1b812539f0", + "27ec492fde5fc3d3784359d9a270b735c1820bc4a6f15ac87797d97de047a6b5", + "4fda2f855ab08a8d4dcba1874402e1662315c872e3ad55681fc4d6c5a89665a3", + "b0dc69204deaa17ac4e0f197f8b23211418b5d54f5bac55414445a66c57fd6f2", + "209262521ab45331639a57523f2e3bbf8250052b84fba81c50c990166a6f58b6", + "b02857949ebddecc58aaba80b9d635ae95cf71f4bfc5ea8fbe05fc55d4c73e96", + "5194133163b28182a261ce0473b1f3ab991a53a08d0e813d3bfb533cfaf1255b", + "c56fe98848fe79124ae1ee09d1b214ee87da51c1440134210c67fe15bf6d1bb0", + "851d949ac91f439ec84f07c481598728ca93acf79cb6505bd77ef776559275f1", + "eeccaf0cd29cb002e2140cffa9fe2c08c53ffd73b99167f4b5e6ab5c94481066", + "821f1058ae54732723937c5ae535cdc0a9ce673eec7fc02bd03c240c93aff24c", + "de836fd21ce91d4a6add661f5d259d0ade4e0ff927d03f67ab5d0b24bff37242", + "fdac0470c61d81d1498e4e0c6fc4ca5ccd02f727413e33c84478ebe844feba13", + "469b4f5af2b2377dc09e5824af17c57f1dd8fbe02294059e16641c1307eff125", + "b77348b40d52d531378dead61cf2eb6fafb4c89f85285a98cd4b6032c0e30a16", + "2cf9616aefee83eddb2df0dda1a3331185f0b772c3a8a62ea3ffbb325290f32b", + "2d23a9035ec627187108b9d13b24134d69ad7cb6f77d2c3ab939db17cc6f129c", + "4925a7008584e04fa769661dd9eb766fe2ea3b66622691249142ca155423f368", + "69090019b3c1635da9d9e4661f55863cb3ab561bcedf2632360745e62b06afc5", + "5cecb744c3e65889c22e14a58e42bb69d068c721f1c547c8c704fbc688c6c6e2", + "2f7371ba97f17c64c4c487742f83755ab61aeb1ee537bcd5f16beb4440d0233a", + "9d1e3dcad79b153296a67238771b91a92c430432a9cc9f7b2ad7667fd67b4de3", + "e9a34ca0067dbb6dbba48e736e1d32337cca5e93f2b76229fafef501979d84ae", + "4d5d36724a2baa0d73657a140493a9ba95e1de95cad74c9753eaad785ac18cbd", + "d135fe3d44b6b0875c1662ecfe33556be0f1b39bc8279dc8730d5e9e351f36ae", + "1fb8439b54ead68f72b7ba4fcaa5041f38d313d92826b57dbda299dab06ab716", + "b96e8d59a7c1006f15b0ac10cd35c08a8008e0c2ecba26d87db3f08c0f6fc991", + "73300dd04c53d270420023957c50410147ea4a2814085d3b98ef25d8d44afeed", + "6941323a74081c1ee28846c4ffe3b4023b29eb3a3b64d0d2dea0f47349fc2138", + "f7cebcfc608a982a3e11b3941abf482d8108a7d59c3719279563788173a2cc6c", + "b76c687ff464c50b58e4231379c5105470264de2826574e8d7fc58d6889d0753", + "f0148c800120bd34159684196bdfb34f08d1af93c74232dd9f59e2ca0a66aed4", + "51e0334c027b5e43e7cd557e301bceb0189903a189dadec2a11c1e2d675e3e9c", + "32a3f110862c9340106f2dbba85c8af5d7ed575635d55cf2af7637a59c83103d", + "b35bc38a430ebd8c7584d0c90bdaaa15aa252b3977fcac456646e839746f9c0a", + "87ea74b899c0b929e83bf31a8d146faea3d26140ea99a3d0d3e0be93a4d6eeb4", + "e222ed1dfcf6505fad77b9cf2e3e38d0ea86fe89a969b88e729ebdf2e6c63f0e", + "d5e92da54ae3432603ea96cd770f4da442f7dcbd0a4f2d82774a33675690297e", + "01e372d5dee08beefd03317d0897d5ef1c5126bbfe00c8967ec9aa05523c5e25", + "369df1d577f66ea1c93b58b770783a74606907713b14ad86b316319d94f84ac7", + "ef7e648860a8b3490440f2a8d541a268b4613f4a94c0dbf685df5973b55f0e88", + "f19f4b29d4d7b70abc4624c01a4ef7ab772bd4feae8cb33bb502f30a11dab29f", + "8ccbea64791275ea047a3648b092ed281acfa21f62a4b50ed111b954e534a697", + "d60a6b4846f218a8369db8deb45972760a1df33c7532d0ef1ae679ba0294216e", + "7aa4638be336fb77bc1ac5dfcd3f5ae002aeed8c42bb46475f718e3c278f0097", + "06a82a57d7951a5629a1153240f6b1f09c150f0b9b777d7c64a0a628e24a0bdc", + "d23d2ce73e24b434f2fe5a6eef2b88eaed4e57848cf357055da6914bb234851d", + "0dfbe791dfc86570004538c64195a7702e33ed121490fee5819537676a021932", + "8e2a510bc84d4600f25812994dfd5b4566c281a153e459cc9460c006ababd093", + "cf73a8cdabecf4323a194f3249cb5b1674037f779c747690eb0d9c9f703f7c8c", + "a8999928014db24e80a27cfb572eacf902b50cdac259b5be80229fe73260ce08", + "f2a13b90ffda09f999ca5d7f9a2cd171ac33ee2a9a889c70a2e2d454dee0ddeb", + "a5fa0377e644e743a38f0b427adefab90d218ba8e9e2894fc1f1f71cd815a92d", + "1a27dc6f452362eca3745218915d24726b72f5d6963a9c41a34b9b7a12aeedda", + "74657a8b361a7d235d94878cf2ac2cf3e18dbd76fa1a88440e3be7ac7a8dc54a", + "15059499248e579d66827fb6700d3c31ab3c1e824fd0b0de583668631f31abe1", + "21dab269f60f67bbce69447b7fec674662bbfe2bf1fb998e164d44e92086a89a", + "a266bfdd44a5969f46985337359fe5ebb6083b8a21cf310ebdf4a62aa16dcb89", + "612ebf41822f25ab638867c1232596a83de60c5bc1a62aea81464cd87c50a53e", + "7c7e8506946118e3af1f5c36f94fc5cddb945e6346a009d21934ea0a384778ac", + "82cc3dcfa10188e9cee8d84a60ab86cef7b564208734ebe8d1c6f4fd583e5466", + "7ec2dce8c901de5c5b5da218d4ca6c304c4a8691568631b1d9d625deab025e8a", + "baf8884aec756bba04cafe11f82e84d5721bc66689a59418e24dba0471e5ed9c", + "2033109b98dec88046390619d056fba7ab1e207591b049b5ca4e5ca7905a0736", + "04337d0bc9948530c0f6bd541442a14cb8da9342cbaae84ffd21f49a3a4ff9b5", + "95a390c9ff01592aefaab09d6409dd618263bd596d5134e024d2e04fc080f9d2", + "c314b11e8ab0957c8c11770b6dd3bf9ba8a281c312f0c25d0dbdb0dab2bf6176", + "9372c5731f8e38ca30845e277b499e77dd3f5fa442f5496c4bdde0512de1e922", + "84ff2e4e6c23f0953aec51c9d8642b1d71c653731d7585fdf021e045ebaa0c11", + "c4472b40a9ec2b01de30d79bafcff2cd4853edb77724a5ae02f4387597deaf53", + "de59785aaed498deb953dc239d06847baed05797fe3dd2420a5c6f35a4ddaa1b", + "9622558b5b744223868445c32fd92ff2ba0d9c56f5b34bbf9de7f3e26206a277", + "80887a104ef050b442514ff8292132f050bfda9b640a840c3258f01893e75f42", + "cbfb1b0ad1df2f84e6ddfa190362225f9306eb62a9f5541cc5deb5da781fa213", + "b01734822f1598f9edceacfe3d828203c629313c7ea0f51111f6b10474e070f9", + "a12cab7ec7e781e90a49f837e6efab253d896dab615aba3f6327b264df5b91be", + "ff54a2a3cae5ac505347c5577e010f713411b5401bff7491cebc18b34592683c", + "99529fea4b50b9b8bd91aee8f8a25cf88dd3612ede12610acb24e73a1fda35ae", + "679c55a753495d26c5ff8ee7e9dd2f6329fe76bc3bba1324f27ff5f3ecb7f8c5", + "c805e4ae5d2358af561cce2ab0ae13a83b68a80886fd11ce5e8200b0194a0085", + "5fbec47c66e62ecc3c483c440f0e39711f414dcc8c0ad72b17325bedb8bce577", + "c8e43b97b4aae188ac288e16f856f16e839e67ee149d8bc8f8bb6aa05a186704", + "ff4748fce9a654b3876905aef033f19030938880da461ae9e05bfe84698658ff", + "c6e39da9593ab92827ce0e81d29fd85003d110bb2e85d530769eaeb0b3fcb122", + "0a1cd405a53aeeb8c03e69eda64aca2dc6df3d599c41551e0e71067c2c3299d2", + "41a3b0883541b8e28e321b9cf326c99f07fe0bf8354e080539511dc27d382728", + "47f3ee76ee3a53b8b6c84de48dbfe40d4487c0f1e024be7ad735b4cb01b240e3", + "4605541e0a8b64ebfb694d092456455e366312df0fdb6edb975d5df6738d5f64", + "f140f701e73403c39925a8ec3bf6fc033df1de9c5d013a0b866b71808f77c3b2", + "0a9f71c5ea3656600032d72147b1fb795f1ec515eb0c0e9b142a51b63ccdd236", + "773a797fa4bb49ec882c2c972b71e509bcc094b89083340df1ebdd64a68f6045", + "bd004ac4dc73dd5304e33abe88de5b3889f233d1b27327b14814f2941f47131e", + "5c2b636057904e70179efc5366acf56b238780b1bd50d5733f3cfd06e4a894f8", + "30cd462a2b16211e365728808b2dea5591fdae5f8e7f816300440e255839d9aa", + "388cd1a37c37a2e7fc75da6363e894618c7e011f6db275cf64011312f3d377ed", + "f88beb132412107250f73babe32b820fe6b277b2cf21035bb4c36e7b60a9585a", + "c38eb4323358d7ae5f9aa8d459dee525427a9a490650246a2e8104d1346599dd", + "212947c08a738977407b769756f1f4d192eca956b07413bb3f702402aeff1307", + "c6bb090f1ea6e4350d6e71cba3c096adca356ce16d997f48dd081eaeeac50267", + "2da0a61f2510fe2fbafb40b27ba5aa8d0fac8254d7d69e475fdff6f3777b4d6a", + "785fc19dee1f3e84307dc836550f5ace30b672460267751e6fcd5e33ac46c296", + "370020c80d7e1f3f53566b5f51931a1297fa1a4efdc81ccf7c5ab72e2577ae40", + "259bc6129f4c136073173a83e0d5d5954203beb997ce140d8f69d7b950e0187c", + "11cb148948a9c92a2c962f6f2da0bc9a024ce63c12e62aab3f0693847c23ad0e", + "a09df031dc5d362e71b730b91a0e84677353406309aa06fce96f83eb77671b4c", + "046bd72f9bedc26c0ffa2bb7bb9679b7b1afc7c9cc72a0de2da2fd9e9f08837e", + "9ee8b7df8898711a797fd8816a32c181e641ae77f9d04f69fbccdcf5916cb668", + "98813c8870bd227cb607e97a296195e673829e66f6c18434e852f36719fe823e", + "5ff5c13d156c6c20c0018848eb761cc24a1739b1a2964e279e1841d587ce3441", + "11caa777f57c824055e6cfec2281deeccea4cc6a9a0cd971ec1b9ea7de49cbca", + "3a8e126522f84bb01239c8d1c51bada77fb0e3e76d51ca82245cbed04dcb7739", + "860a27fef1b6b537a6be688753599f84f29a2600f462bddb5180f0575cf10bf7", + "0bfb8295a95da4b08f084135ce5b221f4e5ea4cb7e63d96410e1b0161f0eb5ec", + "684b917015396fe12a0b84845c805f3e07534070e1a0410bd6319f40704fe392", + "5809b9f126cc43329050dec45c6ec7215c588ae85a0647a23816a73e2eee7e8b", + "21ce0014bae97d4599230abcf322b031d49fee0f48e578f173b317478f0098a2", + "79192c7dec85ace4a9cbb6daa82e76105d10e988ffabf1050a0b27e1eddc706b", + "9a4545f77055b0320345d7f147adae0552191e0b77ba53fcd82749b5ef45e334", + "e154afdc575fcfec7acc74e634eb767a87354b7e4c9277d6218174919f21fd7e", + "12c94ad4e5fccd6a8a24229298b1aee2efa0f231fb209aaa4581a5ae970e4636", + "cd089347c67b34ab0d80db569233b6f42792d9fbb08eddb769c5d2c06c03b78f", + "c95de3ac861cb2435096b17b6a6e96880331ccfe730b15fb14771ce6414fbf36", + "b355cc801fd8712315d0247517eb2ff1ffcf90ce66e786f66c0b70b05c3aac13", + "ead6b1e3c8ce5380567394714a8d3e71b739a128872a4310fd52ccc0cd015bca", + "b2aa10099a3d806f3b5fefc4cf5aade2c91e518a93a03eff4b4bfed37aa98bc1", + "ba727d2be80bdb3b47b860b7588a46ac93984db5de47ee999ad946aedc411a3b", + "5de5016468e7f8b981bd5f1b04b270eb7c5ba3665481b9f2215607137c5d6ecb", + "44a38331d4f01c48fd89cb306be7d40910f61e85c01efa64be56007e168eb704", + "876407f1f2289e2212c3f09fbdddd40e4d99331e618fe1fe76e54c0007146cca", + "3c12bc0d8d061735b0b03371c4869f9ebb423eb84ba6a80bdddf863601287190", + "35426258f74c650aac3cacfe5294871a989306eec1f93c85f34648d3e1e64b57", + "24b0f6118578f557278af2deaea6bcff73b7c1fc2c034f0529b804b5a3667f7c", + "a853095e6abb3da36dab40eba98107044c9bd706a59569c27df4956af6a2867a", + "7bc75c278a522c3f2fe32b83c4e5ac1f002b970424ea9534ecb70519e2ffe1f5", + "968a9fabed55c4ffe4df25e7aa61da56e67bb39f407d6c138d8949f0ebd2d654", + "c01b876bfce242254ed98ebe8999d5bce9d6aa8a0afcd643f192c9c58ba05697", + "0f25ddf33af868397a40586b9b29c759b5f48c5d5ecffd65c8025eae40cd455c", + "9b2aa176b8978b1a6ed7c1a7ea238fa59414085797fcc91a2f0bcd4ba2a56b17", + "454807437182e5bd112654fa4dd079fa0ec3d701f61075d1671c6300e77cc3a2", + "dfb43a5c2c31241a80a316403612fc16007a8bb04652eb3316bbcf1a0f8783ef", + "f0c2b72b9e3f3e0dda04e2c94ad826890cb8fa891b519b63a1646fb00690772b", + "8fc8c2fb737c13eee55751ce1dc798fa291d428858e1cd862eabd89507dd0f65", + "a84481a8966b29dcade6a3e58692ad8868a0b729eeeffa1e25cfc95f7b3912db", + "91c8e0ebbee601828719aa29d7664b5d94e3501f3778999c60a975ea74bb00f8", + "86862070933c5b4cbe7d1b29e15c728766079109dada7f51ab02b03796ffea61", + "2aca32ca5da86a14878e221dd132cf11e9f858a1694eb66c29cd245ed49a81c4", + "621a41b83f8e9299fa89f2f5245ccbe34ddf352e1270c6ef094161708f86edcf", + "23984583b0ab9321f11a5c054ca891c1b5940ca0ee0174a701ed91d8f7c52d33", + "69dc06a85efe0e51884bb0e8215d50ea2d2cd59ff9708079e90434bb2412b217", + "96bd0d2f1ff8360f4846e79d20a31b6eab3705f52fd737741bda0568e629acd0", + "7df4dd373b3fac18a0ec775c111b47f96484f3270a6d5d9b1455d4337c031b62", + "65b585500d0e4f021bf4f625c7f48bfb02d32e9e532ec9cbc0c264247f9c9446", + "28e2b11230c728131c2312f86434b934b1e34e985c1601fa17740b7820775fc1", + "f1eec6722e3a8a3626a3b9cb45ce9c80fd08bb7e71c38d7b637aa986c6206273", + "cd5c00f5aa138760efd51fabfba83c0964c47eaa2e54f95b952b437ed66a5a80", + "16557e57e3b29f120e52c5d9c08f07f93340bc79910a29953df8a4fcda6b745e", + "683fca73c707ae662b516a2c0e2793e6a764b990af2f2ce8c3151ea75e9379af", + "b77d4a94dbc5ce109210288aa0cd3edc83f671ae9a9172495e705ea902ba90e7", + "5506fe1e6126cabdbfdeb4cb59bf30bf21edc868046b263d692345649b3fd6c2", + "e94b83fb44f81d60f82a9844edb2559563ffc876bc584f4fc4e11049237ac60e", + "395cc58f6dc01018cd334bf6ac29b8f5ec3385ec325470b408bc2e8d9af11755", + "3463442e45c1c1df952e9760f91b99ea8a0fb604b203c27f03a955fea0381ceb", + "41f4c041586f3e7468fa44de1ea745e3d41b9942dc19cf35b5565978449a78dd", + "31b31a285469f516015a5045dd8434642b2f55f19373b9e0d5530e843deeccf7", + "943b282f0be57bc8c9b5ad4f47aadfc0a439af58522a4495b7cb36e0ad43a6ca", + "f1d7d6d9dc97dd43e3b76bc13c6973e31572fa1792335ce0387ca171236838b5", + "01cc0fadd0cbb9e8aaa0dd49291e39f3f325030943c8041178738bfb662a716a", + "d96ec821e29cbeeb8b9f25f7123c8d5d708159daf8acd12e7a800c72521a4efa", + "b5395de909f1cade846176880210f4a560ccd3180f8b70d2d4bf15325f196ddb", + "e447d8ecd54443d4674c84cc84763a18b9161c76399eadaa82dd74cf31535064", + "8f47c23e998213388a6abc7d3b5754336bd0eacf4eb545b0825abade1ae48feb", + "b2b553a0af737c3912207071469517222d3d3704c7c45bed01f3cae1b50c54d4", + "ebd2711b2330377b5d7fd2f39a720b896a7eef7d7f0ac2a1d0739002a1511a00", + "d62a3142e4c4a2c87e8014012b804805dfa6f7d5da1a270f7c3401479a3b2f51", + "0142c36fee839b588918bdadaf8c737f91d61b920dfc581e05335f6f2cbc3310", + "e729c42028460ee33d01a15c2e369048bb79f8ddfd70a22dccd43b1c58d41933", + "6fd227ca8a63cd5aae1cb590c6d3baccc3518306e22db68095de25f0ccb4b49f", + "80ece14b164adb418170ba9feaa4d352cfc6cb1e31e47b3239163d2cdb54eaf8", + "04205ac29beea2aad32008fe47c9acc02e3ebc9f66c16db685c8f0976f3562aa", + "071d3e6a4659c37fec52601fe99ccb4d2a2480f3a4a7bcd16f12ff21fe84a666", + "57f2fce9dc52bccdec3de4763a02e3700d13953003190d560a8ab306b81c1221", + "076c6ea3b84d7f2535d034e5371378e9345dee62947ffe4de3185d02a2152340", + "0e386ca6f2161a1c64a4072ce7a507e1131edbd90446723d84a41dcddaed4324", + "48042f945a825bc8cea4e8fef1da5c85b252054137adb5e123dbefbcc189db93", + "410d27dddbea610ff9c4e6354b187fe8a2f7c5b19c5563e2a1bca1a4b4b8c6f1", + "453ce5c10b3de7a4fd1b0bd5f96512d05fba41117299d588a4740585fc295590", + "677e34874e3dde27ba4a987778de7b1a64b6c63051f3cfc774ebd4c3fc605b7a", + "c9bfc40e1be4befc1b44e38b471167667f6b12457d5c82662ef3a0a0e9f66e2e", + "4cd169e13f4aca41b158924d9f1b972aa4d167d6d9c116e7094a7a06d96e6387", + "aad5a84ac08dcbcd06b4fd5a0c5d2c1af8eb0d6568f17b3d8cc47d064b72a3de", + "4d495f70907d93c7c1142990927f1a3d59e3218a34db881714250ea6e9aea84b", + "8c43d66f0d23b88c17d19aa01c64ab1d2af7c43704952326129b0dab4453cfcc", + "c12581dc707a3a6da47886aca1c2aabb5babcc0e0fb21808c27c6d5a939841db", + "2c63aef84a0c812b53e37656cc6d5e8f64f8e4f8552e4e502dbb1aa0319e13c1", + "86886a41f2639a791085b6a2be4f2ac4f76d951880eff5cafc722785ab4d64f7", + "73bb646c6363f96e1ecc19f5ddf7df417327b9089c1b70a867f799af03cb3d66", + "ebe518fccbdf53bfed56fd1f8f3b114b5849a8fb6e14ad7bf3f2a2baf29775c2", + "db82684a4b314c554fa6139c4c7ede0245f68b32a885c819efee12ab0c027414", + "5a985ff0be58aa5a46a0f4122cb70c7ebb937f7802a33a32d8262e63b3771796", + "938ffa6e22fccdc71d6ca1544cece54518b9fb75cac5736a0468420cb9f504b3", + "4726ef072888e9d5ad65342790b285c7ff024d60b084c32377eae6faf67db536", + "57292f406904048855148e3dc04cdb08ce05a58f174867de688b252e448096f0", + "ef8e25e005dda6d6cac7947006e1f688c743efacb7633c34ebbed7f87707a3d7", + "b90e5ada245515dc90c2fec028ce8359708a610927c3f9cb92bd93eff2fb59de", + "32ded7b174c9528307b8042692bd00480d31db9683c9476a4f50085aa6810893", + "532a40b49048d2695ab6ce15572377ea0befb57bf6311e5bc89a3b579561837f", + "587782fd3913fdd01f9f475f25e0704ab834d89fac46a0be1d88e54de6b50701", + "b91389a5cf8a1c10e890ee4c740fe9994d48e999b59a2c6320397a389ec7a316", + "6e737036b6c0c9d72a72c8545cc835d4cf7da70c5ea33f6c6003fb844565354f", + "2f239f75caba3909f2b6f059a782c2624a71b591a7e132ef72b4add3c9c2d738", + "95d4dd64bcc89d80efe84c9fb5d4a17ac7115d1b38a0ac0262e7687905eb1c4f", + "4487d612260e1a6c676d2967cedd84cc5adee2ac845ab2900aa193a1b86fade2", + "97edff35d1872b6a95f43606f084de35aef0292cd411541243bf7336a905a938", + "9001c5d627af968d68f12200662585921aab14619935c8be4775c457e2a40867", + "ba959223c1636c7b0d10b43b78c1821e7af6f6aee1f80337c555a470867c5ad8", + "9f8b4937db8172a5e01b31310cb3b74824910f9c52c356c17035d70173794089", + "1750c07b09dfc52f0cb0f09f4560095db135127cab4c511c525f560c42025abd", + "a51ee98093bf11eb0455cf106b3a4e027135e870e3e5fb212c42d62e190f153f", + "35501a82a47804cf7718d6bd2e4bb6fd67d7639984eade0c1232bc2c74588343", + "9c3192c2435a9cf815b717ecbc6c539cac03c7f582dbda889b39485895f3e174", + "ddf04cdfd5366d08b5886ef736dbf547a403f583cdd7c089f9f3c4239e554210", + "2675b30bcda3da6a2cb6837e614ada858c1bddbd42e6c3963eb220c290fbf967", + "dbd9134ad3f09406a76eba91323ab3ad2a4fc0bff4975b2cc480bd4403ac7560", + "2929520c43b14ce24a94bf56870520ddfe4d29bbcba0d0e1c4624292d8719832", + "f71513cd36ec057435881587013b2fea181cb57df1cd9798e9b4bc2b0457e778", + "b83971a9cbba7903239a1d2a5907351c61028b952d4dd207dd49f6fcc9abc13c", + "c3b7e679bd4d0dab1a2e7938b0d5cd8c28200c5da9534bda97ea0f1ea81c4aaf", + "77ccaeb09a35c5a1df5bece681062f6ce9fea08bbc2c2075ae235946e8e1d9d4", + "51001f07d1462cd9312b077be915dfd5225b85c3ba760e2a414275aba5fee655", + "ab0f6f8f5e58803093fff168c38de2710c2aff8b47661eaa3ae2a337c0e71ecf", + "96e9ce518bea74432b7aa4c857b2e2423a4b3f48275fc4b48f87caec87800b87", + "60c0b7a0ccbe7792b9332ad6072636d9790ff736e2d83a12f50e7dfec65a2680", + "38175abae6191a78d957e3c63121036688cb6b8c46d42d026551ad37a6aeccb1", + "48d82a8ce43a65f567584bfe7fb28cf8c84bc725c73413b8768d2e6688ae77f9", + "18621d94f2733bf9289e0353f066c53d95ef8dde449b2e160e28232caf17df74", + "e86c65449e6b871ac6886526107b8d7bfaca468ef270172576bd9f39fd208aad", + "c32bde208f9087259fab6b053db25b1b136b38276942e2420482855a5d7ad17a", + "dfb55a5293f934d40ff7c0a6749aad57806e571df185f592044e69e680e995a2", + "8c2a1c5eea4c8dad313bbc6beae0c4d94828fcb2a4877b19761e75c74fc10164", + "eed8a70bc0d94fdd32629373b50eb9ef830282b42dcb1d8b96fdb9b8a1797df9", + "2c4735a60cfae0273c4f309be579a51c6ebbed0200715a826048b1ee568d4934", + "f7e56418539529401d0780879ad1266aee0f0354da07486ceab82011a069deef", + "aca86fb3d7d9fb64e920ee5ddccfa78227a0a3a3e40bb3b275f114186e488c78", + "bd4b2c02f07305164010353383415b08f570fbf3fb297df74a38d6f6e3ae99ab", + "589908e264b0c91b1358f1a51f020daf473b7e4d5faf493b3625782866b476ff", + "70360b520b95938174c5b0c2400da3b047743e60d837bf567767732d748e991a", + "a54ed87456ed361b714da5c56fae7c4cfd92f8ac4a6e8e901a565241a39569dc", + "9df7afdf237605066f98cf95611f49f5c5f383a080c86dc3753390c5627efca1", + "6281424793e144590ddb68a85972c45272a5ca39227f9d24d0bfe78e2bb82580", + "13657e27b82c768942fab407deeb645ecfd958f506a6e02c95d119620cac86ec", + "cc67906e35cda000740c83f50b24b92bd04c58f2a0c45580e9a134c284580804", + "44e92cefa2c869560d185d18baafa780bf16f148d1a7ea186f05597b18783606", + "8ae261dd18679d8e203e7b4c8dd241a51634a84f83855b559f7ba55728d9c9de", + "0269bb4c3079709fb80f3aa451a895b639e3b7db83360a14f078f26816979dae", + "2faf079e75b3e9e0298fb15ef8c6d53715cba427c8c551e98d44a3c46a67414e", + "745a15e40bf06483cf58188b9c7df99e796754dc4bf3eb0547b11fd8016c5c66", + "70dbd06038f269ead55d560b7f960d24c7cb3a9c8864fe68c0daebf315cfcc9e", + "1dda0994af0142dc5f71646735ed3bbdb4f6eb40955836db16fc7524eff22ed7", + "492af127ff75fa55f94ed919386917778ed9c51e4cd193b75d82524c186a1b5d", + "5031aec77ba13f73b21e5cf92708f44c0edfd1081c89b4630a0ee3a50b91794b", + "859b7197bd1a5d6b8dbca9d6c96321e706708cba23866d44bf3f014deba6a4bf", + "3108f998d14696b3e945569ca9b47346b26f8a3700cdd444f545fd0a03968891", + "b7cde7af47f0df23419ccba57da19a5ff7645fa160e136d43c827f41b460decc", + "18cb7236904f9784d611eb67b53daa31efac1a8a9a7d2ff47dc34255ba276a57", + "1670703aaef6d8c722bdacacc31a80dcbac0326dcee64becba1339bcf630e330", + "6ae67bdc91167c7efd005be6881b58d24abde9f8c3a71e6aecdb8e6738f3d169", + "3efdadf9ad79dd00eeee1d80e40a8297254f249a9dde4b4d94b656d58338fc5f", + "d14ad9887a66067658afeb25a64866bf90debccb49cb8213184b807e9f1284aa", + "7f2bb055f5dd834bedc5ba9d63b3c83322e4325de7866b2af7ff8692bb2b52ec", + "729e0ab27d04bf1c14dc28c42ea56cd7fc1a8207ef207ac34e676577fc32006e", + "3aea585e172df036a10a0a3c26ebf4705972eb3efb5b61602231b8a2b2942b19", + "a3b01753e934a16eea44aed4a756b7582aa1b469ac462ccfe4c92268705591b4", + "d5af668caeda6f6af27a778359ce9196ba091cc04d9f9b3928f8f5203fc3fc88", + "a6319eb545257cd4c849e2cca4c6faabea9490eafa3723434800331f222b86fb", + "a4f001d05640267e07caac4cad7122771451f3f997caee435965cbd19e64a621", + "f852577eacf926a5db6921da93b4a78f00f57e06fc064ac02633976e35d9e00a", + "c48da9db46193fcda552476b8c08fe35a73ff1bc1950a41f3b1ec098d2f75a47", + "7b26f8d832edc5d6c025be9189216766b58ab27f59e7b253d0ece4d28f51e2d8", + "cc5b9121f74988f1bafbe4ebdb7296d2255a4af6f01f6967c7391568c32377ce", + "29fe88933391cedc886475d8aa7b68415a7f3e80e153d8837cacbaec3f47d8c1", + "392cf2ab942625fad754d738f33732d3172f4213a8606368330f72373ec99c64", + "b9ca5c8d1d64334e586ba1f0303d3fe802a03eca9c2aa12d2f57e79836d0b64a", + "c47d0f78a14f36ea4a9d1fdca74596a88f93dd9a0e746d97c91a79762420390e", + "74a1f274dda992663e03a2a10f29c84d41e2e202bf56e658c1a42ebed46db202", + "dfa9dcc132701d8b1f4332e2a6bc7faae3c7cf11ac0909a83c4c7e30fcbe7dcb", + "35d4395190d570f8288d255533eca2fd7bb13e720d6e8f5a7113094f97538c5f", + "70a16244e7480d092b617242786deee67056d07cb15365fe51d0e8ab1e8932a1", + "a26843f48f4f2a218b8f5b219147641ca236a1abd96905d8534350e570b164e2", + "5d9eed350175b4d1217b25e671c2f3c9868c8732b6dfa80e62e4a9bc7010dcf6", + "72db3913aa066e04662cc170f3363578fa47799b64561d3b9e84598b43ee8c26", + "e406db4deb9cc79c8eb7d658c382c2291668d7917d73c652c415f37a3f713b0f", + "a0faece933213756d22239d1a066c1730fe58b3e815df52c2de1fca102b03caf", + "9d6bc10478d9345260c53c40de93ccd7b1f344dc25e6e4cc8199e1ed9c6dc824", + "ff1a79b9d954943147a0b29d67ce6fcae1f9f8d5476c2b8eaf78ac575f95247c", + "ba31c30e4a28d5ad22a364042b7155e5f4ee008929f22eceb3fb7371d0c1c2a0", + "797bb553ef79e09eb1aa5400a889bcaa513583662c3708614e29b202cfc3b80d", + "3df07ae6890b2c332556c247ee91abf341c5dce226e38df09f7010b1c21615f6", + "30de2051725d9f0fc371cfa55aaded73b12e0668b1624775b657ae0f41ed8daf", + "035e448afdafd5069a783abcd6e268a82f43a52d9ea805c1141d329557d2029f", + "1cd59a8ac8c61f8f97cfcd1e43d633f41f0b3b3782e3deb3d0a9d80942b52077", + "b1bd938d8288167f4a985abf342f977cfd1c2f1da70c6037c9bdc4295a705726", + "418297a2f0c0e5c3d6b2c4d80824e1a909a61fce6c18bc937720eecb1ce3682f", + "06031df90b06aa84d58da9de78808f7b2cd7d563f41dbed683e74bcd7884720d", + "28961517d508b8e963f98272dcbe636af5f99e6fbf436d14b9b64273b17857a0", + "bac113e3e3572e90dad38b0c2b32deb78f9027e59b75db03d0fb86c49c2d6d9d", + "26a5e2f817b0596d95c8a82cbbfeec6971da2247ce1cfa52bc5098ebdfd3c084", + "ba7bae49a577e15e14568cec5f779c4700c8ecbd21bc8cfbd3aa56eb0948dd27", + "90bdd504836627c40d4c14d54da1b3dc5cf5b6b1cacfe2e220d1aa44850f6fba", + "02f1553ab9b4f4486fbee6f5735ffe0d9b305feae9a120df2ea311fde4cc6076", + "2a9ec46cca5184106d4df225de39422136c6cc995fb9d124bdd640ea45667a13", + "28a25bb686696fcfcdeed1a996ff4f8956dd4c41587d599eb78a83ab6b1358c8", + "422bb78c5107f8f0a5e1a253347a72894704cc4d8d1f60a49a49f8c5dbaaf298", + "74f3c43fcc95c26354129e18241a1e747c78975c4bfbb0b90a2b67c117dd85b5", + "8c0f5a2aeb81ef4e5805c1070bfb8667ae185b59705c3cab2aac50363327daed", + "87deb9c6b62b237a5f83b7333776baeebb4eaf804ef78b9ca2c62ab92388a697", + "42897cf080c2e405a77fd501360a7a86d0a1cb7d573f5fb07e094bb13a942f03", + "0d3b80df67851ee640b906d761fd95ad7e47d0186cdcddc59a10a22e011816ed", + "dd222577e63a176e688a09834ba7dfbc7d25af9cecda7b130e48a25fec558e2c", + "f6f7bd7315404bee4a9611e69fe5d3387de5e4577ac28267d04d69946f42f18c", + "03cce690c88d34689a3fa66b6f8c9da75747ed568e63fb20715def88e7228b38", + "d9a4eced4646f53c7840f817b4296b5bf16e7b3d5e906953b6302d7b8a848dd9", + "319741447aa9b6c819cab13368125a52f3c35d264249d5b4068d2f379010f4d6", + "0a0f3eab30a538a23ac52e4f251d383d418bccc96d5975446e093cb424e8c247", + "550942f02799dc998561d8695fdafc4554c5a89f12333403cfd7c4c9cb15bbec", + "1b0d7536d6774149b4ed8db577268f176e4c1f76e88c9f956d51577d7349cfa6", + "5d27bef5a988fb678c4e178b4b1063c4151c5d3780c1dff1ec2ca898f8f8cb5a", + "6bb4b71ce2a72c465e83586c39a2c1d2f77a47ecfca14b45af365a4ec29448ca", + "938e0c409a837260a9fc27ad5878f3b8936ca0e6219ecd3f9bd4bef6c402acd2", + "d8aea1404c3d5fca7289e2512982bda873437b43e7b8b2a912f383340f821c8f", + "1f4cd386950035ca3cec81cba5d6a506620863056da90c7dc9a5afe47d0d9dd1", + "dac066c881d458341c7db30fde7d9197e498ee577bb4f0e0e48fb859f3790143", + "09381cdc27896ab6c73c341673a61927099eaf2d300d3e00ea37ea6cecb262b8", + "65f65598a48e7b2e38efa51d454ef1f26be016c6a70181e26bf35921998671ea", + "98d991cab4e708f9281d1f56b1a507026a114ab0733ec2dd572bbeaa22fa4887", + "1c2a4e2bf0dc9a09e511baff42c297b321cb8da45c18ec50e4c39ca2465d7cd5", + "327c7a6d05d522536e61aa1fbe8fa19f781f915e20a66a647ea90abed8879fb4", + "cb46cc08d32b5bc7ae9480f30884c7670b8e8694d66d5438e5f8fe81785dcd1c", + "537fa4c41690903a48b7a632249144f154c5dc4686f51868df0159a0350a4ea5", + "75a5e4322582d7e240de8e8b0ab9ad73b7e283428537338b41b35107dd63ea2c", + "1f87054001c06863b66f55b3a897ac0d76f361908900b30e1d8ffa46742bfd16", + "22197f4735d51aa39ef48c88c4d60818245fca73b98df8b9669dc434c6ac8796", + "a2e14dc37362b89a5b85c57b3b48169adb2e6dd5360b8030de7a0e028a6db3b2", + "676f7706eb7a57397f6c9f69de04cb037d3a1045281b8ad8390244b9afc7a2d1", + "8defc187fb1f0d7bfea657619f9a020f303f0cede255b473d18817b500b94b75", + "be1166434669922a764541789705f9d86af0587241cf3da9e33a7983d048177b", + "c5adb9840c2cba41c6e49f9e97c3b78d8d0a1c4af88929e89af205f75f61cebe", + "a83b4a5b3282ab0a2dfa4b1620dd0e3ecefd89cd43fcee45a1a6e4760453e9a8", + "ae2117b3fadabe7e3d94153da7e68c6d7b371efc20373aa8e1d9326ed9a3b369", + "a0b5970a81570a1b2b4d058b32ba5f207273765be398986490ed5e9109d07863", + "9ea221fe02ecd3c974957ec24796afb677672fd1ca770a13ec9c3a94135ef06c", + "a8316aa74116d533973b74f528dac5bc257a3e9cfdeae39db7d8d32f38085f4e", + "efc5250bdfe42346a1d032ef4c7189cfeaf303af28a12b3be074daeea810b171", + "9aa7194fdc5f39e9ea59a13d04158913df4c87ed385b32a37f216c17abfc69c1", + "a17f4cce7a3aa86d9bd5d51f3c38f235fcc4b086a746030af939997d05e54fe5", + "98b049494b468b0c6cdb3cf382283110668d919005678e4e8a5550a892ce133b", + "1ff264905c3f040c3fcaa107b7df3c38ec0dc7d59ddf9ff8780d6aff0381da57", + "ef6077b9e143f01f8b500e2eb6b6ff02043ec00befeb78ee44db5f00d037bbf9", + "ca1af382997fd751c70fa1fea1cdee6ee1d2a22246463ed7867d56e57619aaa5", + "be8045324dcbd46d31a1b24be314d5f0ed7bd1d1ca67bf71ba5cc43043d5c9e3", + "70455e4b87d4b01585564a77a6a414a53338c05a984de6dcd14d7126306e22af", + "5bb2bfe0be18cf76e3f70ab3c6ef2816dd988074d43389a3c4479703d1a61041", + "3c95b917d6409a12360186d7c8839b33d6fb9a7f66f2e0e3a887601927d14043", + "0259cb83d1129316ee620361341d083c43690ae542e5fec01f887acaf2fb04b9", + "9aa9b67c720b3b2fe3eff8732fdc359ab15ed4fe1092e289fc27b7e55a9c5b50", + "1151155d7be1017544c0c2b7432ea1f67cf3191baee64771e9364f3bd1da3708", + "9c45b3560ce8419825ae84affed5223683fcccfd6e35247d1c03f4b988e8f48d", + "a3242aa0a01b82c6d5faf748c22a616b950ecd30ef0f72685aa5860f9320761c", + "0f07485d78018a506a69021080b41861f801ddad09666090e53fff3bff0cfa92", + "e819c861150099438a5ef2613bbc8e4c854cb8410521e9699fe90500a9127443", + "4e78cd42978462aaedb993b6a49a5ba1315519db360522a26add6a17c8adac8e", + "b21ef6dd5976ca60b6e335e78e479a655409da184e68c10e0b4010ff9d208c64", + "e8bd4c3f3e0a8345ae5b2b5748060b15ef0c7529fc3eff080bec1a9a77584592", + "3c8d2c54c7d5d826498d730a289d36f903720b7e2ce1f699ccfa152999b5a0fe", + "fb6e025eec367e2bf4b44af67ea674d767b77397d0568528e7fe3aa7574ae730", + "9236e92010421a2c1674eb0a8044ac2b215b605c0b94b903f3a2872d0fabd344", + "9ea25f2f9472292aba590e298fc5ff15c4cb22608b2f0f2ce1bf26e148920007", + "d4d18a3b2db8540a7bf72e0cb14b176919f995e70f0df607489d5adeedb0a932", + "e6f6e272b36731484782bcd4844087cf45678a05d8b84819f9d41f5376f0f8ca", + "5088a2b878336dd33c3a7c55c09aa70bfb1d34815f031d4b3e92402c7633b400", + "3d12743c540c798ec2587301a3fb2b4f0f6e1278e4bb15280c247272ffed0651", + "52c17f712792472ca80df50f7b9f96f51a6eb155c872ba9ef460aa6d4e10fe39", + "b97f17e90b9fc70044f8789379639e52f97707eac2863a35e9a19376555f52dc", + "19701f39368b26bd3c71a2f0630b5e89586f7249431843eb6c890045f7789280", + "49ea123cea990f6fb2029e8cbc9ff7ba7e049c32db1f6519306ab980d74d0d38", + "fd574068f0c0df88faf7b54341a310446c0ec94931bc459748e11c58435d6ea4", + "fdd6cc2c790b22471f03a32b906635b4028debfd691a0a57f847e15061a2904c", + "b7a173017905bf55dfe03c1640a4e808dd6a1d6bdaeb27722a7c839766b8465e", + "74b799aae53dafe2131b183068d8cf5fa8f2a3006c9bb9944aa493c7491b94cc", + "cef9c06a87ada326bb03e6a301f53fe5fe6d68df015b4d72f1965581545c7fd8", + "db390feffd9cdd7f8a34acafa77d80889a820c9275095e84b439ff5f05781ceb", + "57e17c1b71a226c7bf02687565e92689e5c966f5c864e64d9a621bb6757f4f2f", + "b8c8dc2624b8aa73874647bb89be2765fda8223fa7055796a59c7f4d1918e8ff", + "08a2553e3004a255c9a4135930bd9f8724423747135c0dfe45747285549e4ea2", + "4d4966d172bd8c367d828eb8a14f853999df1394fda96fdcdf5e41d70725ba21", + "23888052d915017c4b47b5307d25158fd13daa02bef1d4a2c11bfef79a851470", + "52a97f2b532139784f4a6a6250b2e61a3989a2c94534c2434041aefa13af2871", + "73c82b469fce6ce8b6b2828441bd31b66784b0994e57b82d331be9e03cdccca8", + "cdbb2bec43b864ec0d7caf5def1222c3cbe20c79d97c7642350dab42b5d1af3e", + "e047c075c048467400806f61489cc503dfcdffc73d4b9b6bfa470427f95127f2", + "82de0f386c63555542e8a34ece6dd42f2a4671d437e00aca7da175e7d18bbc85", + "fee469874ca3fb72f0871a24f410284c04311446f4c5f04480d7b3ceb4e78efa", + "830d8aa08ae29fa0941efc576cb59975d035139cc27213d0a04a0153e45207d4", + "f444da313f6e7bd9e27396f9a95129c83977e1462f86e7a9b5addb1a7ff30580", + "fa601478a412743bdf29bcf44ecd5fdedc09792c8e469a7399bc17c64e134585", + "cec5ef97aa6a39f3c146c5e3f955bc9028798591d4e38094f38f70e981566d58", + "6bbf46cb35bc96854a8fae0e20935f2ed32ab5a940e7891fa7f268d28ce5d549", + "955d9741503c2e5244f513e2c2430bf814201a2d7dc74e2e5c9c9aeed401ebc5", + "9f2e6590991e25b4fbcacbee5bf7381092cfcd7d4f3e258bf4b41b84102fc179", + "647c657c3c930537bc13047cf886869f5a0699fc52496ab30c997e5fea1b3dbd", + "9802afd3ddb4d5067fd0db7ead8c66ffb3ad1c29391ce56321c4b8f34174eeb5", + "60f782f178f30f3d9321017b2c6591f25a82f957ef1c704c6901fe2fc92ed76f", + "109992589b06b8eeba15fc2f6104140fec043b2031c8df3e9025fb094978ddaf", + "091adb2a123d0cbeae7dd399a39f4ca4a3b68086410e6c7aef0526bb2ad2fb82", + "64143c36d10d8d0659f9431dcf30cec804f4487452d5376639157354bb7ee637", + "9602ec038d85b2cc617dbdbbff0a3293ec62048f70b16a6b38702e26b99074a6", + "1c6b6508f0f0c772137ed1a53cb559fb1aa4b3690f6e1f6fd3ddbe5b74da7676", + "a4df5d4c393b255674bfd64aea4ae82da5175732d39591fa671a233004d2d0bf", + "928a67d956422b29189df8c6e88e345ed2cfc8ac941d06f15a7c24cf30bc5296", + "79abadbacc8e8bcf8930e8295dbf6626e3c2c71b8c4c6883318dd1c72e03ecba", + "ddfc0d445cc013734947279fdf7c6f0d474a622da65db4ab1c0c9cea7f60d0fd", + "61ce62f67c3526d4183f74ef0d457216c7ba9913c9a0243944c972cac6dbf42c", + "9cbe363f9bc69639efc73b9bc02264e29f7d0f8ea3b1f35ba16640a5767c5db2", + "d7a25bfdc1e8bfba013bbcdc8afe41d81511e15cece155e9be7bf1ce81af27c3", + "93f9632756d3b0d8bc72c4718dffaac5ef81630d6bd79364b7bbcc9ffbc302d5", + "2311829f9842404d9cc2243dd149bdb26d210031ea78dfb066e90381a392e641", + "cd08a8a153ded8be5d705390e3bf53cb56d7b8c20e0e08c39adb3689a68b9602", + "8d310a900176ade581ad42108605cc6bf867f21cc115e6abbf2a5e083c2bbae5", + "43c5cb40e94874eb80ec26b7df732cea255453f511992fd61ddbf4de739f3b35", + "a2a9abf1c835b824a9d311105a4c92b3d6ca8eb2761b45da7241f5879d05bcab", + "ec48aab645d64498938781156b6daffadc0ec9f3556b7e995c8dbd63299c3121", + "05f6c84dd8ff3468d8992d9dfcb4e31597f7a378828cc0108320c009ef16ceaf", + "d4d39f9d3724cad98bf1466577b79c4dcdca104ecb489e877fa8f44adb1b2ba2", + "b23a123b0bb6c11060658fd14786dd345ad2a2c699e4fdc7c8de79b1eba41fc1", + "f27d04707f4b1110ffd978f8610b2f094ac0f24c1dc819b59b70e1f64cd86a99", + "42895aa8dc4c0ca50b98c7157fbaafa53fc3d8a5221b660dc86837eed7e6d319", + "ea9f13a6e1cbe9c308bf85b50ddc729abe112c46d74b50ef53d0bd7ece84121d", + "e8d250858b5de70d138213342a6765948419b81b8e8ed65b3763b20ddf009fdf", + "827b1279e3ea50b1b7aeda4a7969cdf18bed45fa018e552190e8341d2b8be2a1", + "3584a32e4fa98cc72c35e0050fc00f46462b7286e9d6ad675c340a8d1e56fed2", + "196435304b5e92922c3453ad9f31a8d1b2821634ee6a29c07543b05eafd7b061", + "a86b7271d28b011a2749b302fcdbbf712f89d9f3efa24b9a169e97e67a57e594", + "d40ed1342bca6c536e736f1fa15aa5f5a01274b4208ac4690a4f8c293dd40b0c", + "513256a2ae5c50af825420724fadd88aa0a77d93f64e1425f768f33bc56bf862", + "83381a3fb9ec95f2fccc410ae48e0581ee4f492881cd0c20c90cf19823ffca7e", + "0d51b0210f527afde630887455f5d231425f8a8aab5d8567e5ccd49441f5effb", + "c584a68f8f6a5d8841d9b51fa9cff1a3aee73b1b4f8c8f378843c79ba5ce5902", + "b5396ef2974c2a1040b8e8ff023a82ed4ddbff2b69e5569249d139ec01dafaef", + "e52c0b2f5da5c4c5f68f4c1b7e3dc81e9572bcb6299d3386d54bf881c43dcec4", + "ba60878ab1ffd5e5b85d843ba049d669ec4d0094330147c3942a62f0394c32e5", + "e412b797b242a5619823b6b33008f45bb0b11bac0ff365b4f8c73c9f3fe6e590", + "50933f5388c97812aff9aa52db014d71556383c63c6b1b7ce294195c8efcb540", + "643ee4638b7d9c626522f9178bce2a53a875453252c46ee3ed4d524ad2710a4e", + "e72b8739dbff60185cfe3da8783fd9af56b7da9974610428a11adaf5c433b2c7", + "48c3cb8b5c7a90728fbc104f8b191a98119f8a2777c00f8c1000244013745527", + "34845f23b0bcf4e5e7fac5f82c059cd08d9bd7cabb11edbd62fdf1b40df95758", + "33bcac68cb4ae201b3d3fd356c62feac888ea3f7e0f59ff0dfa2fef97f1eb558", + "778d833969b28ccda7711e0f6e37345fa598ac1a9fd1c6a497dcfbb0d2a9d590", + "5fca36d19c656d347e71c9ca43df0d701852d9811b758d63b3ae6522dd4c0b68", + "74f0e0b61425240202486eb26a74e53bdf8460203352f1c1adf698be9c01dda5", + "d2b4e304a065b74641f42966a43398a96e85c14cb209ff06d18e6fdd8a30e1bd", + "df6401c07b51c72f6ea2654aa3069fb6bf0d8a3adbf00d926d534400d88733b0", + "3850acc410d21b19110d9f229c63d67e36e2484915693f21569b627b0f3395bb", + "6143ef6a8f8edfb6b40ad2314c0f9595fbed1f55fac3b8abbacd72f691da7780", + "b14ae396d6da8ee43a3eaf5e9a06333ce6515cbf1819bb5283d73a68cd39d2c2", + "2ccb1721e1ed7add382cf8838f7cd82106232c7b0bb7fbbc5f36291936fc8f52", + "cbde7da6d0dbf518da6923c7d82749def1260eb6632f03a7e3eaa62cb3a4eecf", + "9a9df0550f1629c3cc3c539ec83ca7e815ab8c9319ceb91f42b6b9dfc3384ede", + "f48737960c6bfd1b4631cd39048b55cad33bd0fa925a8ae094456466e16b7791", + "2514869fcc6ff50736da7f509dcc55cfe8372f82beb46529d757c0ad98fca10e", + "b21124fa2ab9868f3e7375cebd5cf95a48d0d62ed6fb62ef0e0f08f7337bb212", + "84835fd51e9bd4624f4b6feaf2387bf3652f0b03efd56cde086079f86bcf9cc2", + "868492cf4941bc0fd0ca8c37a30008fabf58e56bb3b41f7bd91e09b43914618d", + "00a8ba1a607be27447cfd6f60447a22ff6e603e347eaad21604bf56cfd3b1434", + "e50a1c9ab7655b55cda75ff82bc1d28ea48cc7fd292e5f7c72e0f48acee712de", + "249df64d683697b530c6013692fba966655c6d4e38ba40fd05a282f60570ee67", + "1e66d4f9bf24724b6ebeeba7f2ccce22cc039cba53b7e7fa76723f1dbb934e34", + "db6767231847109ee8ae86b459f0303e08e2fcf5b4165faf7edc33c66909735a", + "406ee377180093d25cc204105aaa3030473ab112f209ae1dd608ebe115a3f95b", + "0763bf2c13cdd4525a95f123d5a42ddcc9b3aad27c90dfac1f19fe2cc643f636", + "3cb4c58f6597a2e937c8bcc870bbef98fe4a2ef16089c94a0afcd0ecbbca140b", + "1a448036689115f5ad50e0587d114cd77901f23a07070b4d18ae816256d8811e", + "4bc718cc25242feeced0c3f07c5459d30e5ecd91011237dd869a2b1d32c390b9", + "127d4282b45be11fe780f20db6c1737192912449c0ba6b44ac499d9fcf546a48", + "2882d864f8aadec1babadf7ea312ca3774fd0202a4702779415a7296501e5828", + "2c1a8fb6d739f4ebfd01044c2ca11b484de6933d33c6d120c5bf36475ab7b457", + "d4224f53e199e315ffba0bb317dcf7743d764642d93770e8f6eff12506df888a", + "b8d8b26045f477d58aa21e5bd835883ae1a7bf47e416032080750e80ea7b008a", + "fad3f5f3c9d29a2165efdd3e33f0aae9e58365b379f0ac61f41ff8912f7e9281", + "c05aa5280e7bb5dc66265a8862932aeb268162c25ac2be8fca44949f94ec1ee7", + "af2c85d7085cbd5ff2760f744d22e1e5be0bb8db8817deaa14c06ba9e4cdf516", + "00305a938c19feb580e7945aaf55f217d5089185176561c5896d3643551c1aa9", + "8ac01d950862d0598424e3488ede8e9a285dd068c1d401759ecfa3c97f9bdc85", + "33f3b9159541a44f2198e6e7bff59160bb52f81c35c9f80433992edb5ed0a340", + "31244c0e5b1f7c934f92aefaab2e06b0c2b88c98576777d3e5c514a6b8ba97ad", + "72ed3556b3a25c229bb436b6bf809173540368bcc434c74548373c043f4f8602", + "00947670a458adcae6c83f223cc0b2b05cc1debeed514ea4011044ce375c8eab", + "123376bfe0702b06b98915e38ad3c8d3d8d270f2c81db595922c298d980a7991", + "b9ea637bee6f3f3bee40fb76dd375b6419fcce5fbab8523785d04b1c4956570f", + "601b25e3e686fcc9c3470bdbf4fd2af9d008ea1858cef028180e3f62df0fca7b", + "6d5a1265a95ee04afd7df5c9aee4341f31de35b9e87f2fc9e02d2fd7e0d5fa2d", + "77bef4f6fde9b94b5397e63d1690c6bcd553ca53b58c2bfcdf92726590246317", + "27f110882479c6cf64c0bb38d121e389d5ff17db4b216597c75151b78b24b2be", + "3d1d9cc9b494b22ab1df806d7b1e9745d132dd6d72ba4726f55af9210f3df4b7", + "84d60c1d787bc5d15a952183acad447856c0501c2bd5809145f8d8582c97decf", + "b79fa13459e7f875488cf30ccd2f0431cbe8dffc671880c10cd72d7e2be7e8cd", + "061c36fb310210ab07b22f39178b7f451d5142a395ececa1ec783af9c52b730b", + "fa16976c19becae31248c8477ff0e746c4e28ddcea5ee36408c05be86a142471", + "82235891577986898debf0cc0bab31fa765e925d01a27c27ec694103320d82a3", + "39989431d3ac81a61b0311181d499018fb4cddfb74b1afe4a9260b5841135989", + "a846acdc0cf349a8d2b352d3dfa8784893f5038c05277d327341d3ef7de41bce", + "f3cd88ff101e9dcaa9e05998db425b590dc45dadfcd8b8a6bc0ac7e932372055", + "a159443648d0796956f92ec4efc5aa44af0cc2fb7ba97de7e6a8e0dd16638530", + "9e381ca3bd9488a656e965de17a44487f8c0da8896ebf9c0af4c0146ce034951", + "4cfafb85f2faa88a88034c4e04b7a37be20eff7e02d3e21bdb79d5aa7de833df", + "eeae101eeff05cb6a0dee3bd70b7999858b898b8c0cc7fa96a5e9235d1377381", + "786ad41ea86eda551d0bfd3e702622e118b21e619c604772befe546e55d4cedc", + "2c2f151ad2b9d7bb8f52dc4b060d194bcc63848754d0d2983def1eb992f194dc", + "530b5035473e95e2bf117e3ba2d88d6de1e648f8672baa0c9dcb83e80ef47580", + "b34801289f8a80277b7d5fdb602ed3df305d9e915138761eb20cdd42b338509f", + "eff8301ccf9032fb1398c62896f8502a564889242c9cce7cfddf1b9b87dc9189", + "1babe594d5c84b3e67237b6abcf14b9a091368c587958eccedd4ab0367601a6f", + "b2e8b56a474d41acb1a53646d825bec845cb12c7b96dff588d6d1051136dae11", + "7b400ad7fa14af9182259b51e810e16029c947f2509fb129ebe111fdd977af3a", + "842d63a701a80a9799357ac704f2288ab62af3a62d476fd740c0f8f5c35a46b6", + "dd146653c3aabc3747df891c3a6685d24e9cfe4d61e52f5c7fc7df6f079d59ee", + "2564c5ffb78c4c132cf8c6dc06591661f2d7ee12004af6ec078af8aca04b42aa", + "8c01eb1dcc1e13892a782e9826288711f28ca7c716bb9756e49b41c8d9946694", + "f68743f290c5002b746d912702c1084cee82bdd3c323200dc0710381d98a41c1", + "292a6383fd16ec5afa4cffbf7a6fcc8c6553d4cbd2574886f623b00bf12b1e4b", + "4980e074baa3623551a56263d46933f5fca5465615467ea914fcf6e389e5eb6b", + "4cf461bc4b7f8e1104cb225bae1f3be5dc70ea47a3879257d6652b42ee302a6c", + "79f626c481d62c454a9a8e5d008141befb190c0f4e78c0f9796cdfd8b490c2d0", + "8b4ab8a45964bdb5ab237c0e014c840e224ab1ef50cf2afe79f8810613de8c9d", + "6daef9ecc84a1d29833c0edbfd38663b0e5b76f6ae1d5a22cb081984be8a1ba1", + "7cf7620117fda4ba2e7e1392a39c2c0467e3b90ef96ab021f90708e46df97e94", + "44f6020ce6f1831ef60cb293f456a5ed770aa92b1837fb571ecbf00963e77249", + "b53bc8f19e29660d7c2c9622fc080eb3839eee4b601df47eb3ac20aff8ef8343", + "114f8884785cb1ea54087bf3d19418129fbac44f8c496677778714743e678c72", + "640e38abc942b35d60027a7ca1671ef96da9dbda8d3aac61917189e0f1613a1f", + "23bc1dfe86d5be148155bcb7aea39b8cb68a887a07c7159a8832d7fa8af8d449", + "8ad3f2e44ac576c4a9c364fe497b840bae3a5673a29e69f0806208499332a001", + "ba11731e009801147b11770a0822e2f1e6cab3923726ed79efb3ed5675289c05", + "409e700d67c2541afa301f2c09a4a98de150ff30662f25dcce0bec82da44a1bb", + "74aebd9823146c25c615aff0cac7a2a7b43a3678fd4acdba428bc874c2de2351", + "1ab29e97945c3ead550846ae8af77e6f02d77d24fa8d6af4a866c45568055be2", + "729944aa7565a697548f162e0ec9f39a634fec17615a4b1b65c7b68d06da8ec7", + "dbff2d93d089c5fc366cb89c702f99022ccc53ceb39418e2a9921b1f9d895c95", + "170f0177e2fd6e2ad5e96bf1c7d5fe8443f824ca80c51cb19fe985020bac4dc6", + "44321c4c2e53ab3faa16443e0dd56d0bb18ad444224c09a686285528c04333fd", + "2af9827f953129f8eec520d1409be4d9747fe2213e877ccb06a82e9bfae761bb", + "65831a1f6d27ecf91d452140be9219d010695af0fcb2ef3f19764a4f6b3e07f9", + "103f4a32f26764273b7ab1abed25b768e7994a6bcfe4c029d2eb5a30c4e506ed", + "545d4fd5dcc11f7c29a6ba552562cf4d23890761c76ff86d6940dc707368a0ad", + "4ff9f091da95e9e8892bbe017a8fe29a05955c5c62992a78e23d3bfa4b3c7d15", + "8e82f5c6062204966bfff0f299b26b5921358e22e0880a5130004280c418827f", + "1a6df2e84c39918881ec151d620cc74007c0d122020f6c501dcaff8822f46891", + "01ff674c4dc5d40f0a53318e4a210f418dd2e504077f80e640360db4d2470be0", + "fb4b597769166e58a3b28a698f18ad0bd4c1e24d6c0e3cf58cb800b131f4fea5", + "dbef3658df7ce530752ca69e5248cbf2832c1c458f0b85918f2a4f3666d7b301", + "9b651ab03463dbfb3e7dff410e758430fe535d409805506018bb3f9ba98d726f", + "0795034bae75b616aa3d94df004f4b80717c593aae42e0eeea2bda508fd091c6", + "52d8998c0c3ea0a42e6271a43660f24f17e6201dcbe28a4e1374dd20758327b9", + "a04159ad9cdb943b39160af9eba203f3aeff9bbfd933843bf6cc32f40bbc1f54", + "cf250507d4d9f066221a2c5ce190da42e2c8e9afce5a539f09305af5074b759f", + "5fca2bf5cbda4284eb2a9aa26b55f54d43e28d0efb97e429482a11b66cbc7f8f", + "a740d86574c321a0d3ffec5124431c9a2e52882f85520f296bdb230778e74201", + "5543633ba06069b459f10a9174dff488575830c86bd32c1da6724847791ea055", + "8cb684f24f0a95ad5f4376aa8f0d9bdda6e413bf04a3c8821a547fdf46dd1938", + "b167590ad1040def2161bb854d08fc62b8e4ff4a2d60376c1692c87cb2008e7b", + "a00f55c70f539a98a23b51888c892f2b5a9b86328ec99cd56a11f0e5d8d4d49c", + "73dc98bc6b3862a513320e0e4cd61f5021b30170a2d054abbb62e87036f4ea08", + "ac0f7cc6fbc94a64b50565a929ed6aebd4c6c36000dd40b4f826052b60e98103", + "e24d3edf80ad414a6e5ffca904a259c6a28f1058c0488a524908d1ca8a2faa26", + "38d020a513200b0a6d7bad5fe1e87e39efc7f80b940698ba23a78bbe2cde0fda", + "ffb2bdb644990f4751b4795b94784b5e3c00a5d40afa2b110822eb7dd0cf2d63", + "40a6543d5faecc7fd797d8aaf091d5e4ddf5f075e7f4798b36b6ff2669afbfca", + "07ee87b5c221a21104881b0a6255c2354abfeca80be635803a387a3ef9032f3b", + "d3fe1eefcaa0d03434e6759f8b48fc50f4223edec547c94bf072d49427e2f418", + "0ce4d7ed2eb07f798588df1df044d9e124604f0178ca8941e189854daf64f26c", + "4e5657ffdeb3b88cee74452cd24a8d78ea33013f600d0a6e97c56eef47c36819", + "8e5a2d6f8b4d0878a438d7b33ed8bb7c32294968f71ebf45ab51ba45bc3836e3", + "0f81dcd7f649bbfdd0db6fc69ae5f5a7d89b99388e7d20312705ba07d16a4ab8", + "1cc3486005fa6151d957d0cb6e2599df2680055949b81d4dab85562622ded07d", + "78c8021936527768d5cd7e1dcc30ce55ab13ceb6a1209996a829e3cff8e7239d", + "ab1747694e6e07051b6739e3df2916e09d7cd8a135753c9136a8dbba0449e83a", + "25d6f0d435679e7d84147da65006a48a5a0f893ed2e956518bbcd6b98f40d368", + "3554c7682f565a20b336c40129b5f7182239f2b0b1169f39f8421f25b9ad52cd", + "51b263e67a4421f6b858eb3cafe303af0b3339e0b0509eb068fe3092d394f530", + "2bdcbc0df01dbdab2c91c4658bb9b160fe3efbb5dfecdd85ab525dc21033d90a", + "aa0105f9628907cdeebaa743c45a458553942c3e089d64a02bdcaef293581337", + "d4636ae50f908fb11cfa1f338314260f587f26c29c86cd682e5003fccb554efa", + "31ae9547640c8ebad28019e8eb66cf608074d5c1a95aefa065db97da4c1e1044", + "a228c94293d05874a00c3aa46ee128a41d10405c28e5bed3f7e7f548243fafd2", + "a7d5f3e4630be48f88cfe45147b94b9010718e55d6eaae6462a41aac52e866c3", + "56d4a383be9efe8694a572a143649be7bf89b1eb12842438162532827f7bf101", + "79f7177cf2655951fe0fe7c98cd1cca252e7eafc0b81066aab0841f7cd7895f5", + "abc1746768383ad59c647846b13dbdf51d50f9a5f2c50fac9f676531ed6d08a6", + "a1ad8a55e14acf37cd4eb5085405b62a53948065dcd3bbf299477d03b1d8cc6f", + "c731487e9086642d4e92ecbada3dc31b72049583d446d65bda3a09b4370cafdb", + "729c914190c753c6ab1f648ed04889baba5adc43e05102587672a7a0051b279d", + "3fcbf09cd64d4070062dd2eaf9ff595d3e1fefaea6996ab134e9f394e0814f56", + "2efc8bdc74b41138f0075e2603ec227f7265d3eb6e7ca16a7e1696aa83cc3635", + "cf05b470683e4090959a5e6222aabb1026d60907f9df98256ed0dc62103eff79", + "cae8a98b1c421ba34af46610134a71153d525636c02cccad8e0362b7109d7410", + "cbf00526ae885899747bad4c395b9f2c02e62bbf39fcee82565fecb1224bc311", + "b6ddac871381353848dbb438f0b7ef14fca4eb39fbda7b4eb1747ea454b3155a", + "375b66c16668308f1e768c09bc2dbb0def9dd04d2f90c42c49453f558220a338", + "a2387da7be7ec4a0ce7ef03cb1bd701330e5e950be120854efac13d903367248", + "21f4efbd857d838626a5b6eb935c1d8dc3a0a6c8defd23928c8018c4956663c1", + "ac96b176999b37719fe3a67231b811a7a435ee7b5e807fd1430803a29cab70ff", + "9c06f32a7f016a0e702b928f7de9a82297498400654109ec3a17a1c2531a36d0", + "fa932de504e54776e5b8fe75a2aa7067cff79865b766882542608eb31de1f3af", + "d994c1c85ab3611ad12fbbd4ee7e7afb30ff9ce3c67ca619d9786553b0383668", + "2e5789ccd625cf20f42a72de7cdc246ab92a1cd85ee619570549a8effa476ff9", + "ec4145c429fecc6b5109d5a4e1434c4418925fc65f53bda6cd06620dbd2d1b69", + "0978239a7a14bb768e1ca71e1d40e37a102091ad01a75f5f92c5da900761c79d", + "3382546aefe321d644bd3626c7b767bbf940df0cebfd3917c05f69c7973136cc", + "227e8c8045928af96e794585a87d6869c3ced7dab3689c5565cec54e168641a9", + "c0a3f1b8c27d0234fc64fd8d1ff9f6d3ca157b03033e505bd443f1b7bba62f7e", + "2e848b33465df0fa0a2b75d23c4df4f02824f431e78e80c0221ff5aa61348a91", + "14656b6da232af334beffd02ffb86992637993ce4aa530acf8bbc2081c5dd2eb", + "1697567eea595c835310638672cc7ff2067a568198731128280bf4167b2e45ee", + "749a784a544085ed3fe56e3ce9643d0e13aab8f3480f2fdfe7c16e15ef08d0b3", + "0b2b27579b615e086959fb35f7741791615e25c611f8ab7851c83bdfeda7d7c9", + "e444de3a6fc90b1d2626987fe98c956c1e0db2008cdbe6d863ad410e9fe0a8a3", + "89a757ea85e9d24b3e584538cded1e5db3a90bb01066af609d3401d1bb5b205f", + "b00b5a1ccbc1ec142c5ef063daa88aaac1d3e52af12256cd892803aa348c3f30", + "0ea22dee4f62ac35e5b45a3beeff82c8c32231193bcc33ba7575916dacbdebde", + "5c428762fa847e9a97fce0f306c7f0d8c4b4abdf9defcb0df0dafec41590ebb8", + "8eacd0d7d08645ad8366807a5cc9f91a4b22fd52ab4d8c74efaeb9fba398f24d", + "648ed141936c9497702be348e260b9a4aa9ff5dadc6b654381c9e54160a07f4b", + "682cc4ae34101c80a98fe4a674ef0b16d11ce70b56b3a7fc5b191b360deeaecd", + "2b82b8fa930e14bcc38f1b9bf6a99f3987b32c7e74363d20acf6bd05057cfb5e", + "1d2c04136aa4ce34040a9756ba9f16af6a3fad26b7a9669a62c3563d66a4d4a5", + "04ab01586e7e4615656127d95cd558e318457ed1641c2229b75ed676f21ae1d5", + "a7a5c84706284a3c98121e35e89e3361c39ab8b5ce26c3e479cdb4c2f30aaea9", + "d12b3299cd334b9899ce855a088577d8d757dc32125921f2ee561daa94814b7b", + "48714a6b92c6e98e3406ee38539721a3ba60254ace52c741925222955a89a8bb", + "27597f719f1a11522733a0db69462fd7d28f0a64d6cb508af5dbfee291b52f81", + "44c2fc25551c204419d798aafacde9e75b4aed663d47c0cca8c5c3482b7660ac", + "9cfc7a76eafe717ecd56501bc16aa644bac189cab58dbb90bbb0cf3a4e3053ef", + "029223d300c7186ab0c823474f67590a86e7d08a9d1ed39588307def876ad59b", + "533054cb85955768daaedd98ccd79495e4726b2e5e7d345a3cc70beb19538a57", + "60e7f410dd3c32e1c88638ca4e758db9a2a8f7e52e2faee358fdbf5dd3fce27e", + "3ed8f47b67c5cc36b0e6745a29251d0470795cb14f0ad9b30170c0e5c8efcbe6", + "1f889c28d68e869bf711e214dbe739acc35780e8b19ef9576dede8e2e91bdb9d", + "dbf00c8bd10f5ae12f38c625c8af8089cd28ff92f34a1d949dbdc20d7a80d797", + "960ef9daa1b246bb656aed819cbe898dc14ca8e558070c9468f840487c05db92", + "fe1b43ac2709550840c682ecc23d2699e9b3fcf55851999d9f09b02370aad502", + "9f58ad99bdc049a7cc29f7bc482629302d56b32ba3b4a703223945c8a61f4254", + "af61383d241fa4cee95f8065527537a237914d9b1494ac8fd3ed476a604882af", + "503e10b0bd880beb400e91fce69c4fc9f36e3c816ae86b7b8d9729d00d08efc7", + "beb9c475c020f4af8659aa27ef0d1dd143479029a8ef2f605adae61bd693c9ce", + "73b93de4843e323d7db0726e0bc301ff0194b9656bb40bd3baae073b9a4df96b", + "3f3a78847d9964cae0df94acbd73ba24eb042f44e5a473b1dc665a4db518dcbd", + "240d37aeb82815e4a61db5729ca10d069d9554e3f209eecd881d12719a47dab6", + "87f8197177364e5b345abc75771b0418890afaa0c109e2f6da58df071868c7de", + "9d0f1a386ab8b2192f4531281d50df5d354a96afca98b9122198982e0120d384", + "0bd411fccce089fbbd2eb04da0cecc3f030d036a44b78f37352816bd2bec9c1d", + "a5587eb1bb907b021e206cf17beaaeeff2c34c22a9857c0ead14d68f62b1818c", + "ed96c8097a4fa7debe2db8b1aff9baea5ebad839b60b7ac878fc93dd704f9633", + "c244897280ea22de3494642c6a8219500b884ccc77802b953f71cf12de445e28", + "e11b64f780d2e2314fe969a74a86129b8cdaf4d223376843b94f4e64e9be7eec", + "1a7771432e6782bf9a5ef436874bb9aa44a5f46d1a5a7aa785dd1fb2311d9d58", + "215be42fb8c3511dbcbccd1634895857d48d2f26de9f9f167ffa86f141819298", + "5635725b8e48404be37a8c9db3274195cd7e36a651c65d09ee8419c571d1d233", + "3bbe9d510574073e795b4f843f272a1900f31510f9ad36a522525845c90075ee", + "1b19a47569f1803ecc66d1d5163c4c6e7ab9f7e99abe3e8bcf55f8cdbb00ef7a", + "c155578db721fb204f9759119f8ff004d78ba068869c938d9ce0a57e650e085c", + "017d7f31830417c7a0ef240d7837ef9cf047ed0a8414eb085da37fe2cddbdf64", + "78ea7551cf28cb954259a28ec1340ee66d715f02c5eb7fec48145298581464f1", + "b89ee6474841186f5afab538f9ca17b4171c303d5dcc89247b79ce38389bc0da", + "f15ceaf2a19e8eb599bac5959aba3518688d4791321e618b5ff4af1700fc969f", + "6c0be8eac8a473b665f89bc3a310c033c093f5b062527a227921b072aa3123be", + "60950a25415add062c9bd5427cf5e0eb75d6fd7363af9d44656831d04cdcf7fe", + "c8ee6f1b5cafc14571642946e8d8e56191c3a9a020e5322ec30a698258c24a17", + "fd5a24fb185292c845fb8759d924a72f24c18758fe9ac5e0df99d1365e0657a4", + "85db138de6940bcd58f37836e431c6d0dda1722d21f7fe2e519d08ec8d990d7b", + "13a18fb4b02a808771e8a5eb2a64829bf27c7e6fb82b2adf8d44a920fa6a5900", + "cf37460c8ad19a604cd4d1475e8258ddbf72e3163d07bad676a0db39248f298d", + "16aaf7854629a4612442c7c7b67836990ce60a78b95bb0221646cb9d27454e2a", + "f3ff2a33d32daff5a2606ad41d8b9fa469fed175ab2d12cba2432d2cec320bcd", + "3f84dcab7582376089e959b42b47ed237ad52f5e633093bf7fcbff6164b74cbd", + "1776d3f2ca165cc20e0736e7ba69ce320b1e19575245e0e78bf628bf8a3b4c90", + "4b815273385785fe9e57dbe6bbab75423b6302f8daef245c12bc4b8572696893", + "f9daf148d2ece99869e00ed44ffc1a740792569a36d6dc21a94388453c31a08b", + "0a7f0cc9bc54b816a21785f84414bdb99f34a49a3e33e5fa1d105ba026540627", + "05ec349ece9f47e92ec0bada62dc00a7896abe818b213914b95d3cc301afa0cf", + "3f652f63459aba4684390a90687e117bfd79e09b42542733acb734adbb0a01cf", + "d7be18e61d11c4e5757f2ad5fb6b8eb70ab1f621f7785d048baec87e3c81ddb7", + "cb4ef5ac7ff2c34808e9398ff7e313cfb5b73e8b3cf9c0c3ab8059983159213c", + "c1f66f86c3dc26b2212cd7536bd40bde14015a19108d997e24d0f5b1532c5201", + "2e8766d4dd56646f8fca1cc12fe66785bd90f42d27074c71e3380732f5616558", + "feb95b15d26976b6b31bd488430da464d87102d5b35cec8bb578cffc91bc7933", + "bef956f6a785e9937e4e3bad0b1468c428534fc06658738d5e02c1a604c77003", + "fb471f8b762add49e6d91eb46cc018a6990ca7bf1fb15d26aec90f6ba968f6cd", + "d4f35e4a4e006546af1bf09f5e49b5dda60ae5dd526b85ba92d871f4f09df77b", + "795fda42bece1d711437fe504f78e940f309c89830bdb5e9dd46afe3fbb1b150", + "abaf0bc879b70ace0a82af4b9d4f32085ddc7c5d920a673c47e83199959b4de6", + "ae69a784b18524bf95e7b2349cd1c36709eb810b0168e7e140a7a411443a02dd", + "7858fa7acf40bbc2168ebf05e3389ae646d1fd4567b17470506596baa5e00ea9", + "d9f1d5f5ba2a228915342e41510bcdaac533e10025531eb9a1960b93ae667332", + "5a0def567978daf9e17209cf27f261256f11d0f8cc946c888ba3bd15e56c786a", + "589c40f1e0e442e1e227c1d45ddbd799c41cc38d025ae871bbbad98e0b65d2c4", + "d25e897ed507bc4dcbbf349cc2de3d2240ad6bede49c89fb07c9b5dcc138363c", + "b480a7829783c502490abec998b2d0253aeeee68e54bc165a5f1e98cb6a9f4c5", + "ae8c6d400c2d517eb69379c486e1ad35497e692292bbe495f0d01b4d4e48e265", + "27fe2f66e35f873b8094d7e2ced46e55811f5a936f9019e5e2db41b7ae5e819b", + "1d767698a7e6a1d6adfb55c1584880350f2c4e80f40e51776bd33a6a2e47ba8a", + "70114481a06394db7de45673ff702a2ca4047d2715d780884f3f841a493790b3", + "404487dd890566585291dbf1a22cbbb7fd70e4870b36f39ee7c710320aa0c270", + "b908e1a132f73bc705594db6e08b82fb277a87456142f6e6145fa4701d7de09c", + "7f0734a0c77fbb88b5a5a4d49f995bad567f0a676cc9326c3267211d06ffffe7", + "204e168e47177bf41e3066b1ffa5f0779d98fdc7a0ff8ac5399f1c3abd6998cd", + "fae75f8b1c0302a15813cdacc724fb9c9d06d6ac7c6a358b1f1c33a9df3da5c3", + "c18e90a57bd273f5d27751fe81ea9302f5604f24c2a2a9d306e2b0b26ed65cdc", + "dc084d1ec5c777f8cb78fa955903adb7bcc68b0d18cd2de66f9c32acfe9e9948", + "4fbad69a041e89833b72a21907459d2a9985bef82d05f70a8740f4d1abbac471", + "f0d752ca1604efdc9102728c70fc2198ed466b21acdafe6a234f7124cdc3c531", + "96a291d08e64c21d74c94982a2d1a146ca25ef24b4c2ffcdb3a8bcd77288072b", + "d84f7a140461dc63b72d66dc97af315127f8260a6176159f7120ad15e0ffbb42", + "e323b02b16317a133f695b892900cbed463c146310f13847cf52f5e8a1556403", + "56f9986c62059e091f741f5febd25cd26400acfd51302e09e853c49815345372", + "4a91c13e08448237509eefdc09ed41470f72309aeb0f3577bfbdaf2f45b359ca", + "3f1214f03f2bb53d779d05f2a3a647d0bc2550b5540ef159c2917cbab8abe006", + "c00d7a898094e86d4659ecd1ebead956c2d74969a9c7ea05b2ef5daf456cbfde", + "c9f7b7bd863e12133688f089abc3846578a8c50f8fe95d33ffae1472cd61d396", + "9c4cb359614595f16ba7a76a3a5f7a78205c32a23653a935400f147206eb4a2e", + "f81f1b1b6ec3af3a09c79607ff09a025162ddfc14cf85ddaf4ed678599031bcb", + "07518a7b6771f27678761c04c584da6475af520f2358b0886ac0f792e5b07e79", + "3d8edf9ff36d26b12f22af02685c72a8f9106295c7cd06cfe74fd59967501ef9", + "957afb2e2abda010d44ee4eda67a2b70b61c56594a31964f860b26a28cb8454f", + "f136ce60520308496e1802b6de253f4911afe61e910358b2ead474a9cf48ee6c", + "7c90e7fd57edfe1fd3504153c5fc31342a7b354e93038b45140b67fb6fd18e65", + "629c3a127f091ebee42b4d487d029bd4c3fa2ec72dbecc715e6c8c9d2671b736", + "41e8646e02f9bb0537b627ae5f269154b03d916fc490d38abbcfc3ebf6d9dda4", + "72d66600ed0cc1faa6d91c2a0da81bd90d4538cee893aae4bb0cc14eeb4dc280", + "81889ef1c6d2c81067e366ab7185eb2199be8789f114ddd6a3b693ef7821bbec", + "8adb60319e326ca0afa70e7f8ce7209bef89e1ec820a5d70f13d606add534ab6", + "ddcebfd51afd77e78f06d5a5dccbfb4d6dc61977c47ac9b1a2f6402940d9a156", + "b4cc18127051cfd93b1a5eb0358153c03f1d44b841a41cf3c2a7a23a14dec4f4", + "a3d44b5e13a0c0e6eb22da6e8811d4995d495d4d780e840ee85c83f2b1656082", + "eb69269adfc48cad8200b8e31807e9454ec615b8d33b67e7191cead52549007a", + "cc6bed8de65dc9b905e2aa95ad5fd57bb82ee21c49968cf30a6cb4b002be5f6d", + "0ee85d0f8f0f5bfb77f957de73fcb2da0ff2691b2971ea681432a67ae5c7c378", + "d8a2af3e7a30804574e74ed97275804fcc22c19607ea30251f7fcecfc86c8cd4", + "be4f45e912a7cc5a174d6cd1c058412042308381f9882d2a74c43cef9a93e51c", + "b2137011b2206e9737392b735787c1861d5df47690b5682cbd432fcac4c05198", + "f4fcc63e7a123783b643adbf0b97580b789def70d8ce5af5955b35b263f4a4fe", + "1cdf85f46a7dd1937cd43c941f1b555d582f36d3091097231f86602c73c84c7d", + "76cd77e06f946eab3f59cdccd3a3e20395f04d5e61ebc5ae6cfebbf72d96214a", + "f080f232fbf74a104a25ff2df6958e0008662596f81cfb84cd72c2a940c9b5fd", + "70a13b34f284353ecae70355403e6297fbab263f207707860ec22dc8a2da4c94", + "2d1bceb4417eabef500af978c975f18ab1b01b12946ff411832af267652f5d49", + "74847cb2b631547a13df246f10f21053e1d412b4bcc83d2a28285033c75a2839", + "0df6e0d42f19ca95d31d9b762fb58b9ee4ef0a2037cab32e49509c575c6ed19b", + "e9b6acc84e087163ed1ea23f7b7aa0fa4ea37f15473a47af6618d7f30e8f56a7", + "3578384eae5e7a10731d6de0972072f1f568a764ee9a2c2c6152eee141fdd3d7", + "69653aa6632c7fb7c69a8129861e9f3226b726a8804471165642481b598dd099", + "a296c05fca5e44afc5c2cd583cbde4a1791bdb7a8461c6676b97ea63c11fe393", + "077900cf366702fe8add4f0265cc52273b343a14218ea59ab26cd628a38240fa", + "a63d694c0e6728166e578d90061d24146d56ccda4ef281c41ce6076ad51727bc", + "1623e53bff3af6346b27b38cfc0d98f6f2ec80830789f88bbccfbecb4ce1162c", + "ce5c3ddd1852bb75590b6d8db16b4e6264a8993a3aad89f1731753565d5e54fb", + "3dc87d48cddba8c9aea71e8307e9d803925c9e4c4f55d7730d3e651dbfc7ba45", + "1cd04f320cca7fe33c2f43dd9e0098cda82edf20162a67320e68ebaf3fd74fe5", + "8d63274c4a44170e5008d2d1b790d5f468269abfa91a54c329152433a30665b9", + "89ff3946f2111436afd2b41932c475a0d09223e23b6b9e9a9ccf164d3cbe0b11", + "db63caf3eda1222a308deb317a0bc8f1deb95a6b3b0108f71d2d16691244927b", + "3d1ba03634ffaeb5d906032273191fe2595bd655a70dd8c22680fa4929012c6f", + "0d355563eac60a85c52b4b2a54c21ee7610819a9a3b30e69a9a37b5655b9b5db", + "373a44ab3936104a3d9d26a246d2365127c1726c34d2f1d7e87d7d7f6058f248", + "16022145fe055769433d4c3cd9639ce0f8f668e157bf9711e2c7bd7907178df4", + "6b34e7d77649a72887fa46cb34618d813a32fbb77e4ebdd6691527eacf5fd84c", + "df915fc7ca49225b34f2d419554d2594c8b2ffd834b2bb92f49397fcff381eac", + "6002c0ef73c781b20d7d4d9e01322f8efa8aa78e5e9792bce29a9169c7032f57", + "3a0debbc0ee42304a9edb10db770d0b8180c403971f42dad8832cc16e395c11c", + "6d76b23d46aae2e574ec2947c490f2ead9acf9592e417069e72050721224fba4", + "593602ee2803bcdd743fce93962f672cb877edc7d009be65c02a562b8604a74c", + "64759e44d4e426d1c147b405330e2fe1ed1afe168b8db60a87c935cf07ea10f6", + "d8f1eea3b2c56c9bdeeea5cc57d5fb38cda7c2dcef9276ed5602d27b9788c194", + "0da1328dfa5efb29034215279a87bf026e70aa7ceb2793e8c0ba9354580d552b", + "8bf631c56ecb7ab31c0d30bf31dd28a62877402cd6825d0ecc9c71e26f4be643", + "ae29941b70abb79f690de45e32c8b55f8114b30cae4376e991b963d4e03d970d", + "0b87ec546259a15a8c8752634c3ef219459c382d383f155f219c04d9d5bd7f01", + "87605721bbed02e5f279a29dbf5fb857a5e1d41ffbc50689ec4e1ad1ed3f2b76", + "64d33c169662adb9ae01cc1a902b8725ea84162226d860b05b22c1cf8b716b88", + "dd3e22b9aaf98f165f20c449618ebf776727386daf5695e68847462a1573f1fa", + "2eed687fa82a0df68fb2a1ccd9c7e0679f5ada28b831b105718dbd0580c0aeb6", + "3748437a03b2e086825a1b26b9a363495cb04ee3b5c6125f16fa7153d388b38d", + "d6f69cb9ff2388175b1e549df23171ea6cf0f181c52902e90740dec0fef941b5", + "81e173033c052c2a8b6c2ce928c7d9e7f7b956e9744156c8b40cf815f093de69", + "627027e95f078f86d70df8b97d509bd556c6397693025367ae4b43edc9b6fdf2", + "9c8bee1ae58091342dac7901e5846df4acbbc409656457b03e1702eeabbfc8f5", + "ee387d8e66f5bf75dfdcdeb97d28b4dbbcb1302bf20d6bf8465b7b41832550ad", + "681f0145ff4dd8a84e42b02ca5bdf4bbe8adfdff11e3ae57673740a4f98f0c67", + "9f7d29a27a80e64603c98125d6f77e23afac3ae85ab0fbbe91e74e498fa69c89", + "f23c72f39810dfa196ddac4a2b30fc97f3e05cfd67d91ef2e6ab063ee29483a9", + "cb02c55cbe1a20d200bd53e718c78f9f2307d0476082bb425d4476a70ef47172", + "2c56d7a46e2e989c71d754a9ed2ed22285adb9bc7ea0ce85e7ad0146798836e0", + "161f8ede4832a65d9d082699e93299189916a9feae06703aabba7aa7100c8078", + "c14f24845530dbca3372c7d7453441113682d76ca7c5ee93a86f8705dfa6754b", + "98e2302a8c27aff16705eac3c9b4861e0d670571ca493dd0077280785d1d32a3", + "1af7252bf32de08559d47dc9273c82a7e6cf89c2ad0d755084116bdc30e2dcb9", + "b662f05da0258a7218659df10926cb716fc6d092654ebdf9d8a0efcab72f311e", + "ad8807bdc9c0db6fff8ba8963af51f14ed809ff5cff5944d27b1923bc5cec68e", + "4502bbe76bba11effb1f91a822732bb4d1c6cbd20a12722cf7cf2276bcb224ef", + "bd100da2cd60b60a2923299e505c6c0fd23cfdeebefbcec8119567fee2b8d5ac", + "2725060e6d944dceab24729b82d3a64275ebd82cb42ab330af43f53cefa27893", + "9cad96ee381a6d63d841569e475eeff6dee67836e2d05113a8f18aee590673b2", + "c22c8e48d846ad43fc8319c4d3ed8c4af31ea7c3e079595cbb557b78e50f0a60", + "24a9bcf359c2345fdc35812a10f9f08229d11f17c5951120cda1264adb2e6d71", + "54658722a2c31e13616e4a38a817927c01d2df37830dab7e76ec16e7ec93e298", + "fdf9d5820ce67e076d78db09766a5827c6d14678d1336c8f936608d80ae83d72", + "f3c55939c47563aecd9a0be2fd16c8d839331c811403ea22d47a8dedb636f389", + "9a62ce2c9a3238370ab983857b27a6ebba407540c6a55b8d3e62fb012b9177d5", + "32ea29fa721d7727b0ad77f534bba0ccd4f08b16b1c809c60da2e83c37cd052f", + "0c8eab2d814623966122a83e079ae8629897d405fc471fe70eb8ecf63d53a6a4", + "b5f81d6be6c5ed881db8e195b850c3ebfa2cb455d0a1d14df79740eebe5d549e", + "ff39acb8667a42faa265d97a90fa7d5802c6a581dfa6bc868fc6e953b4fe3b10", + "cd1a9a932859370e971587fa26255782a1aee9a1efeafd17badd1aca3c822363", + "780034cf738e520971387345b73f42cd13770dc8cf68948ec9e8e1f6a6327f61", + "0d97a70b467fbf4af625a6b539035b19e86d011782bb7a9ce538bb89d81438f5", + "ca8dcf9e3454d6c2a346a01372c798cf2cf8c49c38f9581d705c42e4f1e7c4b4", + "0c21834cdce637a85f931e8d2d961573d1dba0ab7bee93189f74c6331995c914", + "c8aa251601172c9479b4ab2c1382a2d6608b547cf6b7a4a04a75a91fde4c08b3", + "14d36f3db5b2cfac7a99df066016eba59a21ef651e991d631c04acb11d1d2b51", + "89006b74bc61637b0808bf786df16039d0f5a26d1cde1348de0f33718d47e75c", + "098621c303cb1b9dda387a0ee66ecd267172a70defc1b403887ea74c53d5b911", + "f9428c1dc527138be5c9ddbfeed43077d06f2826b75edf9218e7b90df8b5ec29", + "245270fc3442e19475818ca0d5cd32a4728a25b5c4f2231d571eafa281e61e18", + "753e79f5b9edd2e49c30c17d3d5050a2c0265027abdf8e661281d22e6cacb701", + "7ac9b5239a33b2689c63739de793dfbbd0955c044fe062bab28f7b9e72af4755", + "ba6c405725d69b2c74661f3e4259242b054d13f197db5700d1d7728110e56955", + "a7ff3309d156a8d4af88c92e5959c98617e0c1b95a10caa544fd761f93715b80", + "4185a1fd84ac173ac31493ea80b4a6d38d37325e8d82d07ee31bbd7f5c661e17", + "78826fde40d9fed3355c37bcaaf0656a4236d58404a892d1da402955698e2904", + "9f591f6bb853601a315de1edebc0927bde202e6cdacacace8dd32bc824a8a094", + "3ae950afe6ce59737c943cd90dc8f100de10de1ee952b2d295514c4f710aa7d2", + "a75089fa658b1206f03622608239f2de628b4a4693548c864d6096e2cef76490", + "efa7ea258d706b04547b8d4a791b1113fc77a4e5547c8a308a89bcbbf8161e56", + "f0c8cd991b168fa30685d9adb508e9aeb4f2b63dc08a153b814b0cf1f989183b", + "6f1946a94c3138276073dcfe0d0d48d3cff0452993b34965a8fefe90161f0fcd", + "2bc40489956125fa47c9cff896091977b8dd0b7a0e0150f614592b11a2e4d1ff", + "b2f96fc311567ddbef1bf0fc487f973145f717f283883606ee03b4384da74003", + "afb571e0e140ddda917b23d993474493d61a8414b0bd191092b6780bed309756", + "687a64c739ccc3941a0eb74554bcd0135e0063922933ff892c7b90b88e21c7b8", + "5ddc34627e55a68c4fd82c789c10d7fecf3e380b458ecd18eb8c5ae041c0b288", + "ee7584bbd58e279c2667ffc07c1c4977654fc948a63e33633a563c757f0dd9fd", + "3b7fb2e668fbef730803f10eea0e6cd9a2819eee7c1c1418431d1dc683def7f2", + "b4e18431bfedbd98c924a64497c60ca9e4a82f22084fa1ad24c09f79b2ea6a94", + "a5073a937e8b5cbbabb8915cfd2f2fe7307e4e334d5f434077995815475f0df2", + "455b0de46808bf01d625c22f0cea27120780c8c69c0c11cf837e98823faf58ff", + "a2b26681847ad348da10f7b578cae28087a3f5aabc5f84de188ae7ba9dc754f4", + "f03694a80d8dcf0b98d4c4ae69a797218cff7688f80ea97e627e817ad36f3795", + "006670166e382b2bf369907cb2b4834d58ccdc55a4bbd6494e8f9c129f5140e6", + "ba02ab95f8cdf7e40f03a629e47a0f7d5b77096549f17a158c73e2b96c6cf0ed", + "e1180c4ae2e094ff0ef2dd8107e5f2b02bebfad6501b18965b75ce8689efea2a", + "a8114db88256707afdb0f589c4e375a7b165380a0f8c2795c0c0dc0d02f5684c", + "a8ff66f47c414f56d8129e33a45a0bd9b9d1a640a3a3454d3496ba3a800b2d1f", + "f36ca0f95e603f806380dce52d91036d8e3a5545e1dd7620a19196397b91359b", + "1c207b576a1e9e765209ac6756c7948fa3f252a4687696d0f8e831f14ca52b92", + "cc5eb95f7759dfb6cc55027d49a5fe9c52f3006568cd97a0c544638be7a887ee", + "4e15d5885837d4e5e79bff21930dcacba97d2bac39e506896e005fd55aad7e67", + "5e2e62808fbe25793a6917dfbfd06029ea30eee0892116fe6ea4696fcf62ef53", + "334d575dcfb7f6b606866af045d0a378f53447ef51d5b3d1afd83d78c0aee1eb", + "591c6dc98541b9fa044ab89f78856374c3f42ca5f8021bfa0e119d8a04a28646", + "c502d6853c8eb8a2657e069659c1f3b06878498d1b1a15f771fffaf886a1ae52", + "2d4b16c2b6cee606af3c02ae8d6a86560e2393306fed2ac0b72456a8e35e24b6", + "b2ee8ad11dbd4ddbca245b403ada57354b5df94a9de54bdf574bf0eb41645c89", + "a895e7d8adaec8c44b603648f36774980707d30ede4f5dbaf2bd3d2e3d4e90b4", + "1e76fe8a5aa1b516db5d04f3496e69170d3a6910aea79ad7554e2527b81b6b5a", + "1576d0f08a566bc422049efcdd942ba455e61a1bb2d6e1ad470e098899128f75", + "ffc42cbcc82b0b23c5616301e90aa6ed76f485cd63506ba52097a42b5c17420e", + "27fa1edc6938b7d3658c5c9d894136857fc0be8fa743924a86ee4baf583599bf", + "4c8a502fb162a9580502f4f2618be83041782f245c6023e1d6c3ecd7bbf2cbd2", + "5b72391f63f61ba48fbf8fd308ca6afb7d72b9545d1812538f2e4d1414c6fb1d", + "1968aab09560c49509dc7cdac8c5c919629a3da0a39d7628b172d5e7bde99c20", + "97656482e3f4c9dbd01cf1fd91f0deb0b319c15a89b5deec6a57fac647a81753", + "d1392d6f39252c12b76aca6762fd95086785433913e5960b3c14a708401c2897", + "c11244439c754d91aa30a2fcddeef5a0be7e300b94a5c98967785f112cdbcfff", + "21c529765b6a1048c8c946578be14e63750eeae28521fd845bf7a3ae669f203b", + "c2f22169832b5de185aaa5642f125738f164001083438328d2541c15a7f986af", + "293583e0b2b86f041204e4ddc9546ca74431407f88ce36665337a5602dbe3bbd", + "1c270bcff184b5efcdec7f630e707c3e7c90a519302233fdc08aa744d4cc0f9f", + "88a9d5a81d358a14cd92c14d669903032c93371915725d764fb01c7c1673d4e0", + "c733a54fa8d1ef07070c52ce77898eb93f148b13cdd1b6fa69884dae4b62144c", + "acc7546cee77ad0a641bd33253869e0459325db8e6e3a9c7e3812916939e5345", + "edb6a177be83d61f8e67db1b3837db0316d0205cc7303c242c697fb3c2a1af25", + "8e1e002d811734ae8033333794c2bdbe826f1c69f4dd6197e595e75837a3ca64", + "316f26c9d7b2dc523838fd4d8a102e7b817a44d00a4f31dcd30bd6529ca3070b", + "0ae34d23335c3138e2ad4a282b3bdb89542bdc29a4df69d202ac41f140e15aa9", + "6c6d48776be2e5a30e91af97bd6b0bc5a5eb76a5fd713adc399f8664918cee9f", + "dfd7c371eef529314fa853918ed4fac86e473d5bfcd9dbe261e57201d70c082f", + "0046ae85e6230a07ef27a9c7c75dc6f6ccb7742433cf8be17343ea8666512259", + "dd9fe83a560f0897bd19f23474fe744c8eade026cdb1f048e84f3459e0f19303", + "3e6a71ec2a652defe81127e60e22728c5c2b6b7ebd3f8946e347d662ff3a868b", + "a4229eb078f80905f012a474545e0e2786f98bf7fb2073dcead7ec73c65fbe9b", + "7dcad9638f5473ae40af1a5d9334ca763a09c66812806f67e75bb44e457f87c3", + "374ff61877eb866ee324a87077eb71d0a269e9845847e55a5e10fe5f3c77aade", + "ef6f393a0d140250e071d3b82f5086c13bb429266cf944bae8cdaa28834cef8b", + "9651ad41b55c6e8d6abb345d4ea06e4399cf2902d2c1cf13ebbfba0b7140c8bc", + "9d0540e58a6445fd868ea65c40185a7da7858524f419797084a602427ace7477", + "5d812312bfa56dced2d010ddbd98b79ec946a4c13fc545088c80d037e4df3c3b", + "c75d28a33c292a50e293d4e88f25421bc03c83fdbafaba4f526b8c6a7e5c2e2e", + "53cc98e7b41fa8cf94ae5ddb9807fcf8c73c209d910c892f266621fa05782ece", + "f6b589decfffc6c33957cd061404dea6482435abcabbad3485266cf4a8e7fc05", + "e4e03d0c467c2420923815fdef774f530d07c0c415e9975bed75db2ce4227dbf", + "7178a3cf86b9b27023e576a0f112ddbd3e240289bf8c4256ec384962207d749d", + "58c29327189acf5670bf364f2c9023439eab2dff643a4ff36f3d35b43263111e", + "d9b365bae902827f415288ab30e5e75d93038ffc1466356d6f5280863f18dad2", + "ffd371a7a95b47d197bc947e0e1c3cd50a96657ca14116fad6fb7ec96aec3bf1", + "10ab513a9aa33ce2ea4cf293dd0c435b5aad5a1adff65604dd3f07cfa1f340ee", + "40863aa43e50ba80bdafdba66ae053896370421d48f59c2aa962ec5e2914bd96", + "0363498ed651cf53bd469fd0dd8ffdcee6c6b4641a67bf52fd177ee7e9e81324", + "7804a9ccc78fcc0e6b6583e2fe28a32d3b409a08dc48061233f8faf88dbcfa03", + "68ae59987180df7089fe2566c5d2a61d554b267332a24dc44ffba97c1ab1b596", + "f2c29ca8dd0b0429df3fe25fe3e36e6fdef1170dbed8baa30cde80f13cd093f5", + "06d4400c68d16ce0b1b70f8fef726fdf633af85eac4e285694db95c0f8e503cd", + "8db9481d31cd20f8ebf15b2ac559ee46796f0da83b8a94139ab998e2be19f035", + "4c4a93fcff164e070a9eac32c8b7a582a00c6d32f8dd146cc603520d91fb11dd", + "3d70cb493fee1f8575dc7da9c7627d31ebf42c9842275f772ce6f0d42e06db0a", + "9f28537ea916ceebf76f1a42544c43e13e9075929228f32190d9ba4f5bb5c9b0", + "673af69eacb58312c01c9b4e93167bf8e5bec2ccb4450ab833baee81cdbbd95b", + "0d0b61e12e81bed69380c9b8eb23a2384a48bb13a4b7808a21ebdb00960e7e4c", + "284b4b1d0f126cc76b743eed1d255b181a4ea45839ef0c1dd2749ce5ae83270c", + "70cbb1998efc52d6b6aecca67551e42bb44eb6dd6e400241d2e2bab34ef9c1f5", + "a47582681ac364f031f9d19c42e0b68edc0749c5b3da9581009b5187b8b503f4", + "808c8d746109d74eae54b97f45ab7b9ed24f32835cf8a1ec9d7d46c3fae5178b", + "ab0e3e550b11a95ebe15edb506a534d70e4bdfbbc9172b251668cb8eaffa5c7b", + "8d24d23ec2012ff57024b1241dd401eea2ec104645e5dd4088b32546fd8ff904", + "523fc99da5bd881da326c94b303a852c0796792c1f3abfc1fdc7590c9a1ad80a", + "bef023158f2dfff9af92f2aa64e7da3087afe08b2cd5bed4d6a0933e46efd411", + "bdb056bd0ae30d8b926688b56a6e06029905ecb5b25922fe4f6f2004d4adfa11", + "94a65dc8805bbee393a49a95ce8c95574ab280d6cfcc4a1490cec8352754c6a4", + "50410fb63b9759e697bbb5c62d39820ce5d1f76ae9697e064578a15e0621b792", + "90bc57b7055d84741c13f3a7707770de07ac2118015e3f0d08ac9ca3a4a6e177", + "4b703ad6aaf1cbbd6f4fcaeb9745fa82f6fb055611d7164c572892b0fb786114", + "0ab836aa1a9ab4467708652e8dbec2885119358e37b32f51b46d45f33c88046c", + "1a118c7d922133ccd430b9f9857b62f8f2ce4df7d648a197f6edd3fefc07cecd", + "40e846cc774ddf6392d673f69ce95245690f25786770051ba5ad49461310b0df", + "5aadcafbcf0b1b4723d39c5d4048a8d9f3889a3f68aab66cac0ea4f0ef7d549d", + "acdd2f2a548cd325fc06b49c257bfbc9b09c016ab0859cb947aac64c4570f6e9", + "0b3ab537044f2e6142ff425079d25fcd87e0dd8187f03bf0dfc479142d8b9abd", + "497c6521a301865863f7dfcd03316ae0db690e98546116c14df05d98e8cdc893", + "ccdf5949457b2394df193f21d8cacd4971942ea3a1467e2ab13fc0cf38a2aca6", + "b38079f35e81d62e2ec89a5511537abd020e0d49f47cfeacb1fdf0000e3898c3", + "b5477b70b3e6f62fc281e349303e8e160a5cbf6af135bf3c991955b3cb963039", + "64e8bcf3fdb18248ead8b6c5a23b156d76cbc6ab5789815a2b45c365ab20d353", + "86c981eecb0af6e8ada8e68a9ba9a6cb93d0ed527a366121ab0dcf9f44d2a38d", + "93ff8bbd4dced9f09d343b2227099d63a28c121389dda386c8b58acac7e020aa", + "b9b7cc4a4e8b9f5a1ec1116d378e3f90ab3e8e4605d4cdc95b3e4b7df2815a1c", + "f2f37eaae0ce72d792c65a906b6c7334c2377c3c31e454063305e7e63e5329e0", + "cdfcf71c42e3d8d056cab0fbf3625c1b13704870709a4e0850006447f128efc0", + "83566f6ad1536d4aa2543258bdee98989807ffa133d36af3a26672708ead8410", + "68b89c27a8254d5fa39096943bd9bb0074e950fac7e686209c0113df088ae054", + "0e8c8d00c7e71968371f4597da934aeaedc4142586c6454790fff7a4e61af30e", + "0bc7e2ff2af89d45812349f03973406f27a4475c44e71589c4121a9cae9cf37e", + "53738bebafb5a06ce8140b83ef922a31a05521c07c42cb09fbcf6619bf959697", + "809f6e691647916082cde741c82df020254de10148b4b712744f1db345bc2694", + "82238f89893476756a48eaa42cffb4fcf07c8cf4cdbe12a1c43145d2b67aac07", + "ee995fdc7633e803741fffcad712a48aeafef16e5a91aba899e4295bcfa3b577", + "b65b0489ecfa62b9725b7a4c4150caf0f01350de9cfd613c90c60d0fa4875ff2", + "7988a77872e593e831989c6ca5e5e6861b63e1c2a42030d684b51fb88ab73004", + "914ea37de5dc3b2f9035cdb7fb80e6e692a0a5934756beb6d8e2ea8eee7a3895", + "d9c28dc8bc385283650fb2aeb1efe10caf848c39de37a78dbe6f47b8f3bd8b51", + "49a2581f6ebc753d70dc30d3b093882153fef2d0825252354e5beb1b35eb90aa", + "7791d37475e1e15bb9b817b180811d784963d2398c5e736a851af2ef8e622e77", + "3957f179aa7676eadd2c312cd6096f84b7a0424bc99f05c8997bac9fed057ae5", + "9d954ab5224ffd2e0301ff9b33ef834d019596a6c3f56fb634ccc9779b9c694f", + "e6235eda52b721bcc3b997bc8da554f1998b5c7ff43cb86c2d7f1915150c4ded", + "c0684e197ace1cb57ab553203b16fbccce5c993b876b90499e96d94ce308b956", + "70cb7be7ca2d01bb41c09c06fefc04e8d6dbfcdbd9b8df4bd38caa3c7fbb6001", + "96235ac39c397550e28b63f63a4bebf549215c5e72cc78488efeef131b0cdcf8", + "f3bc2994745fcbbc792d905ce1858978814bcec9a5a0a3eea4913bb87939378e", + "caf6345a1bc6013c428933fcf151ee366a11ee41070c3de2a3e5036a65145194", + "ce92dd8170fc721b8bb69c261cd868b82a2d1ccc556bef3999128a3c20bd7c5a", + "45e73df58de1b26bdc1b842b5a951da84381708cd15b40eab87ad44641777c6f", + "1a57c8e01d94f7455719c85c662f76580b9f4f530aabbecee9c5abe00fd7def4", + "c92b4679c9376df3e41d79131f42961fddcdf99afd2c476168daf9657f769373", + "2a2893a114df2630e4b32c47bd60612b4f3d9b04891e944af55fd2767d260314", + "98dfd86f3bb1b56d0e5b3d0a9b3227642b09ee0a2ae78f19eb504edd1bd91ba1", + "2bca879ad4553c1406e38ab072cda33740cf1681f18e7cf548ce040528fa5d05", + "5c3039135e50309be3292559fc0f0d6a5de7ab2803c83f206c8cfcc06683b54e", + "7b46b23f8ca2c45baf6124165030a1ee5fc27f1ae3d1226defb2574177cf728f", + "b37b504ccbcc5ae9f8b787fcc8da194fdda3a25b94fbf217444c13fa4a0863d5", + "36d5d669f4afaf603a54902269087a0f65f89962f87ba03be56b552bf04ce81f", + "f57f94affbe464d876235351bd20660ad5896e5f5bd38978d64346564a089754", + "4637eacec37dc577f7d0c780ce46a57de6da9b5173f4d0626963af480b95213b", + "e22295a0995837f730cf99e9465b0e5c41c1f4a99456f6b27d26d66b8ef2c4bf", + "33d01fca1bb71c53d131b757afe6d97342006694ae752c17747a461717f42f8d", + "ed239b46bcf7f8a66b92c15135488e1249f668625c6fd0a08a0f49c733806de0", + "324c9a8fbc527d9e9357595b1b3509847847f340cd557b9305e78fa2ba963be4", + "2fc5f4089b8b6375a41bb70257be3281cdd32778db05663c55f96cbeb976ef1c", + "691fc9f8e0e0fd9c0a0f42f17f33ea83c3462e35d777dd54734108bf273b5a4f", + "e21e3a565655f1f58ebdb421c94110e19a55030777dbbe455e350868020a574a", + "2c3e5c5791cb68bb2177332f5b79c8f7d1cf4a577a903acf225a0d11e45aa5c5", + "5707100bd5a978ef7fe1def6ba895ad388466a4db13679a899c93bcb802d3a7f", + "fb537a7e26384ea9867ad9ac845067553c1461f5c75a5bd50df1afe40b063b1c", + "afea4fc11996d2552a83a4b7bec586c9dbf3346cf2b3ee7a862b8f6ccf163522", + "67a83eb66b438519cd92e6962566f3d1277395d2edfcecdb06565e33b9b4200e", + "a763a731e9fd04d5ef02aef4e7cdc1726a34c7a048cd78c45ddf9f05c52773be", + "c992b26e5d9f56c3f1615fe30fd5d4a70d639e192583e5792909baad8695e90f", + "5f63a2abf7fde874a260f469f56f7392c715f43581fa0e0ad99522dde7c047da", + "2d92ebb998b264b0c7fe1d9dc94c8f3ac171f31a38867eefd8687216342ad4ac", + "36758fd2cddfa3f6c1658a649508727ede3db00c36b5a619260212b50b3e5da7", + "19237f8184a40216542e1f326527f7e512ada4ff9923380f9a7a7e73a0c4aba1", + "3f2d551638a07d4c6b03265f131adae2639d1600a9f31a337d5561bc22c41af6", + "212a3841885d5890d53c698bfc2ee6114c13ed4636483f65bf9f43e2a23c2dc7", + "30c6c4fede61dd964588586338cacee4e0ef8977b556b4fc104fcb6120d45d3e", + "413981cdcd8428b9e76a34010c95f810ed5f39ee66edfcda92ab0ad94f030871", + "c70a24bad26cd871d82aa4563199ce82ef8d00cebc48a1cd698ee1933ac8b6a1", + "d228077b8f08942f4f5c445f5431f6a388d8dec122471f8c24a56ac158c90742", + "6d25f50ee65fc7f46a86e1742f30afc9b9536b65762f75d9bfd1f9ca30df1d75", + "2a7ac6f4b98849eb4d6962c424e91537967350ccb60c5cad40400d46fb0dc3d6", + "002721d6c430ad953c9125dac7b4235a3f5edf80309b23fe9e197d0ef12ab0e2", + "5464573c76aa26e98ba45180d7783bf83a1ec7939c8fd8a210d635225ea1ef42", + "18395b65b1a46358ec085bafcc873706fb482866f54e02e042522ca09ff0452b", + "f4ff0049dc8e5a4f06c6274a1bcdbf8b5f8b841f921a264ea5bff329378fd620", + "19bdc09f26645d82ea05cd1fa0ffaa68885c66530b3f0eb05caa55b88aee4efa", + "6e749013aca97a5ef3b156da4dc621bfefa7e0186e5e4780c31332f9c62ac7f7", + "83800ecc6922e2a6ec0c34c1736a12f2dfcd94eb9f27c6948a5b28d961bc4ae9", + "86b6fe2935f0206083c952c07ea38c25388955eb8a806c10b69d3e52d6ec26e5", + "c7db870f8b09b9498d39b22d828b60a806f7a1de694c43d6bd29f3d76a11677d", + "b0a5809735c83af8e2a3eeb93da682bce95a699392c0c4abc8d39188563e381f", + "63af21f2f1f82d3120e4d8d98e7fda2d9f39d102c57d9c4111d07079a9ffcd47", + "ad654b3ed5a11b9a2a52fbb8dda47e91a72e4b7bac3a4ad75c074786f14c046f", + "6d9c4a44bf22648c07881e2e92a81ffe002c0177d9c7892416ce01ed1f693872", + "16f279dbb7b3f6c3d4f190b36fe2283efd5fe4669a8f70d209eeed96bf55907c", + "f79a83867614dc4d2e47353f53cb5a833582446329dbe4d5e8df941b0926dcb5", + "0cebcc9ea3729487c1b20adb8eafc6fb1398710ff6179a7fdafb1985e5f4a8e8", + "5a53e473fda88a9fd818defdc2c8e68bcea0c5aeded0c1910d6004b0e77ace89", + "f97965894c4a296fca42905242a27228e5a9a0150d31a55e65d637dcb9e60ac1", + "ec72e9e2fe2f4ab9db10b7075012c9e7cd2a4ec02b4c3b76df17f6eb27348ca2", + "a4e92900f9f5490b66e21fb66c0ae094d106eed5366a85418a86a8a3f8f87a28", + "225f9b50f64a97b9eadb625662b1a3e1b400eb86cee50fec8c62260027b4d390", + "7cd4971b0e5453d210c097904480403bd53c689c19d52e4c6a37925d14e4272d", + "3edaf61be8b6cfa06e1d42d7abd2df32033bf63cd14df8bb3ce337e62a822f16", + "89fd8d0e59d8d757b7b8a2c3a2b826e799c028df166b6897a42f08f2284d3bdd", + "0aa2fcdd4e83a4395cb5f514e1ef61ad4bb517d7c60f81bea030d5191c3c79b7", + "80a275ddf6caa4bef4039860d4c5b0c4bd1009953c19ec2c63294ad2c5c56d1d", + "e85d450859d702a0473363070a6e56d5021ebd1111232f2af28a60963acdac16", + "54d48012bd8af661baae940fb6f0d10348137cbb13c650626f42051ede55ec8f", + "bdb3a9bed24420afc6eb80930694b4afe0992318e49428156acdba5762c69e16", + "cd277985e4b4c30a1999e5bf7bf7d1cc903640a04678a93efaac1f9bb5482dea", + "d1319c6862feb76be11c089c753f62873519ec29a0c83b186efe57f50c4e342b", + "1662d8e6db309700071a801a3d066ce97f942abce241c8085f3c023258927eb9", + "e179e018705eb4a49ffa4e5271ddaa4fe1273029b521aea7a69f8f3b764ae017", + "0e7347cdb154f6d96fc8f9eb8991d4149fbb21aa3270c21fa5df61ca140a4fe1", + "34f182c2f44fab5ac9403543493be5006814b2379e0e6b7603d9e0fe76390367", + "5d710a970ba007bffbecdf12d1c920fbda1f57023719d506825e0895a2affc49", + "b99e0f318386f90df96c1d1cd4dc5bbe4726410f327ecc108108ad898e450eb5", + "7eb9ae1f66a6bdc4482175d031bd6af270fe4ff8ed1138200d6eb49e13c0b8e8", + "c508f259179f67bb6e05f2b9c5c5ea751f3a013eb925c6bfcc2a2624b962e059", + "f3d0dab6f1d30ce74c97b89f551ce0820bebad2bc1cbf4a449b11d5408cbc140", + "c116f6b0a0852a30c8a913f9e0f1680ff1be06165e19eac4d00c4e426ca9b6b3", + "25df122cffe53ab88ecb94abae69e0a1d4323eadb7537c5e4894c0e082d58529", + "a30d97485d4162e910b12effc549374221217c216b0a9487e5a07ff1fca835b8", + "5dffc63818983f6a7db2ab65d621819be9f9edac58df38571887cba22aaaf1cf", + "f042fb4eb01dde4b285f2df328f191d20621b66d7630d7691e7c69d9dcc04aaa", + "d4190e2bf10aa9800a1c2395c0c4a38737c21f8bc42dffb23addf762ff309aa6", + "4745eb0e771c9f2144ffd4abf39e63cf6d0ebe122df5f32ae399cc15fe3f7011", + "806c6fabb4059c51865f723ae619e0c55718f973680ce7eb84c49b935f59ff02", + "3352add492d3a7fa9ee046040a3f7795ec375af875c87edfdb0bc876ba14fb3b", + "27dacac03594cae3db57550410bd0f8d166863992895548f6ab782a8332462c6", + "1708396a0792c2a33b8174dd950c4a59ff06e969db873eaf2a7d82453e233b99", + "5680174e172ddbe6943cede98f8d854dabb66ba65e3349889cd6262cdf462a80", + "4960f0a4c46286d62e5bae021da9236ed0258f6c4c779e1797a9e66a47803f0a", + "06af18499315d7dd1c8d16533f0e13465d606fea2946115f23735f3fe2f6d882", + "73b0ee02de1c3c8a9b2bb8a9c48705c94df76c9c2d68a222c8493848c75327ad", + "42044a69f91d56cf8b993166dc1c724ec7d311eff01c8255987cf563d58fa5a2", + "187382209cf9b45c1df5655801a651463854a6495a7aa303887d65ad0e5b915f", + "3e44b609a8f97ea0b412abf88d7748edfa62b7ceaa174bae008e508b1b133e7e", + "d7b1c0b1bb02a6411eeb888ae26bef44c20364113bcfc1a4a3410396cb52b950", + "5534bafcac199f54a48fa713c6f7ce0568abb11b341e38b4a968bddbffffa507", + "84044786d9f286e241174796073fa47ce25c154616ba27bcca69fee1482942e1", + "45eb4756d3895fffeb913bfe149746f56e184f4c336dad29f6f81fc2a3c0168f", + "b583374fc47629a3f17476f4c9c751e851b7c4881eda50e4bead35afb77808b5", + "4a99c32e81aa36e824bc4be78495458020b1faa47d558e7263f2c0477a16e6d8", + "888b7d3a32f4566f21407c251b753b3890604001db91f88d45773bd8a53b2541", + "53eb65e5b425e7acd56ee6234ebf3d40d8fc45a3dab9968ef4273cee307c38a9", + "4bec2659f865dfae69862e128e710680f6114866b3478de257aa5ac596a66555", + "543cbc31d271c10cabc9417a4d4b6dce2dac73c20ff29bfffbbe55584e5329a1", + "4970e6469f1ace57971103d1cb2324b595ff12e34504ef8249971c76296bf578", + "010810a21205384741812fe98489cd7369d9f6fd9e6818b11dea07c78a9073b1", + "6114e32fefd54c1c2352ddf9327727bb6a34a9d8b3b7d01ce59c6e0d32700c7f", + "ecc7248b7093678e7e0ae58333dcef0eaa18e3af261b2d1722d29acacce2b8f5", + "0dc74b6f19b2bbf2f0e7f511440d4dbbc67952050bd1d962516b683ff6f9ec9b", + "fb8447ff1d5200c3acc768992705d2108c37d61b0451c7474304803e76cc09e2", + "f7f222b44c49d2d927fc2e345d64e2f0ed5f9a602758fc5f5ccc73d3b087f8bc", + "295fcc7e7fcb601b3e79c1fe403f920b622bde0f4975419c3641c4d432de8f50", + "e016f042fea1d93fcd4b2326b3ef6051e293b4abd066b3fa5c430ee7773e443c", + "7c8347faa1ad96169f5650a1d138cb10bfb60dcf861d5e2dfce6a986997d2dab", + "060928467611f28b99eaf284bc6c09d6760904ab5a6f9e072a44915a646e3ec8", + "ba582c64b17221c46d27aaf6ce1e2818562639b321666c9513c1e85727a623e6", + "e73793b5e2f33f60887c6ab428339d40d21fcf75530640977215a18db34fd7b4", + "0ea32380009a639b705fda94d93ff032d88c3126503c0d153bd0f104b039bf60", + "13bc02f7f971910299b8c61d1a16782726f22f63b31b2c0c34faece873f4418e", + "a99b3ff6413262f46cf9a44c3fd5d0424109a6373e2580fe97938c3dc04e60df", + "72b149e7ff672ec6b23352493cbcac166062282911e46d2b7ae5228d5814b720", + "58d67077a2b7c836dbc19457eef1b1e1f734370a800388bdfb1012840fb7a240", + "32beefd48538abe6eed6ae137199e03e05229d124628205cce7deb53f16f8f16", + "3e466fb18e2a437292f00acf9f6e662fdc6e0b7cb9c0606e613f81dea3393e39", + "9e537b504916eb6e9838f13eccf0c753869351778201c0813ee35cc19205409c", + "404c0560dbcd46d1aa9cd76246654bd01f62ecafd40ad2b364c426f849941c5e", + "89e5cbab667b81f08eeee6203be0af9cbe9dc0655af3272b0cb3fa722116c200", + "2fac933a1b980ba75339703bd02f055199d057355a8dbe94eda724b79c2ac534", + "c0200b5442f41779316e0e250b7bd011e12551b2babc96c9228f3187fbd17eb3", + "f3269d2e80bfa7e80397a8d32815f8fb8fc0f8dc01dd1c75cd57028a23e5d462", + "d6be8612904259aa6ff43ff30a2bbeba9d58e02f479f56480a8affc2cc9bee64", + "a67a26fdd90d624fff7e501a2a946681134e32e3afe3094a0c3776229e7db123", + "43234eef33f6bad45358374d23ded2731a35c12f472cce58d05e6d9ee56c9535", + "f0217ee0507b13f56f8417d0c92e3c76026da461c22dbb7fdb653442dcf65ef5", + "d9572135cdc5ba7afbf9871de3ffb68a1ca933e71b80e8766e678bde59ad2b52", + "9d32d8b859188827a48fbbc7660900fa51bdb1bbf3dca2ecac1392a1cbbf4757", + "daedaf79fde7f4e49a736f5b81f8b6f505884a073c5aad83997b93bb9685ee52", + "fe7b3ac50841778d1283fd836996b1865f45392d53b95f3bef90ff657a49c498", + "b89b62e4130fe4d40232744fb0182d477047acd40d32bcd15fb6181ddc890a6a", + "0ae8786dcd5bffb824b81fa038b10c9127b811664e0d9655d537d95377d6d93c", + "8608661859b838a7e8ec16e15db1114aa9db6b141877851e09439c591b46d830", + "f5351c6f978f600be1a71c95abb638bdda6a7f6587e3f66d53bda682c7aa631f", + "b6bcd591af74caa9970d39e0747bf20b56590878cc5b76da411525fd34b71cab", + "bff91200c32ad862bfee68ac1fac4b85a5e6dbba4fd00a7d09e217cd226ce777", + "40e5ebfabd309ba228262431317a2ffa268074cdf83556682d5f5e8c77660c5f", + "c379dedb960b327f684e794ec212b11fd1eb8f20a6847676554be85aa4527dd2", + "e405b5be582345aec0595f1a8a4aac6f938b539426fc7910d1271f3e2d1ba81d", + "e27a84e97deb5450a38bffa685d0461dd90e2fc9c42aa1261da9d3b9fc8fa0b0", + "c8b7e2506c4a067bacda36ed1bba81327b18a08df3fd373be25690754b9d8439", + "5b8d011d0493a5217b74d086f4b8db0bb38cb357c76b1d59441af36806ead1f5", + "4ad67654188a9c920bf0bfe25d22c433035d876e28f79a7c09c5649ae7b970eb", + "2a0abcd054b303b2077ba72f2681a7832712b526b145b62c601741efecb360db", + "bff88f3f33650251f8b9240b1bd94621460baf5d7b224030ff9619a6c708c56a", + "d8846d0b025e2eb7eb528eabb478902ad5990d507837cff5c90b1113addf4c75", + "e57728788dee9191e55c5588236680bec3f0d2cb8791da18064a6cb341b5b7a5", + "f291bfd6644103b66aaa5208ecfb536a28d6209462f98493847ba6454896a369", + "8b0a1b5d290d1069f771d50369e662c5f3c777c2ec3efee722d1a0ea58c6d9f8", + "cdc255d80343d5967d7418ad4edb8dc99ac7138c1621bb8beb0869dafce44cb3", + "cf363923e12340b2e95d7397df7775e3d6d98539001d8262048c67539be6a7c4", + "aa8e17f4657e2aa01025d8c99112d7002fdb1abb086cecb23d428081c87de6dc", + "59faa9c953bfd7f7d5bbfd363c25ed4089fbcce464b2958c9aedcd5729d4883b", + "fd0e1bdf4b5130bfaff244ebd4654e4e0df288abf7673df010bb909286f93ce2", + "f354946364f1449f4cb93847742ad2c2ec82163ccd7fb4e0d5687fccf1a2a018", + "8be151375abca57a5df2d52428068eff6f82dad14d5bfd08a699f8e747ddcf99", + "14a1cb09f11e6401ca5b119808a3176f893db83a0c23fdf9c393ac78139ba429", + "1043a8492b8a8b6a8b9355669a29fc3f2e1e01df59741458bfd3014420df60c8", + "338caae1b0d36e70fb8bbfb3e230a25fc9eab8a1c5b727e1313d61174c878411", + "8e1509bd3f536a9e263d5bbd5cb5d892aa0a860c104f0ac7af74493b9d164bd6", + "de5f3b69cd11a453b41b1595ee919dc2175ef867541c66571e6e71abba2661e4", + "653934f935d080105495891b1889aaf0403223077af687feab1570d03cc08d29", + "0cfc8f472eb4c7e30b17ecf91ba335ac1b479a01154e442d33fd0ef3c0e5dc4d", + "fbc10b32be63288c096a64a70c75dfc76f162707be9f3b09675eeb08d98a4f52", + "d76bf859e647ec2d77a7e5b5f726e8eeef59c6ab7fc1bb01c981b5cde26f3eb3", + "64fe376e3defceb8149411cc4cdffdf3dac15aedb2ae45263a0b94877c8b9ac4", + "3faacaf41e61750af0ac6df1b5a79927dba509d293a7fa2fa9e76bd3a574fd3c", + "a7a80a4cd46db9010cfe1fa8859d658d7b4534832e7b44fddee51c53d6be6408", + "c0268812c0e6cad694d5632f819b5623383fbc3e551122cad33b82536f733791", + "a5a2a39a433131759be0f1f6339c4730de3d401c54c71813066def40e6cfb046", + "61f4ea199859377dd681fd26610068f87e99081866cbf9aeaaaa1163e68fd950", + "65e2e5e77b37be178505f7f40de4e64fd4e86acb9c6236ffec62392e8184692f", + "bc82892fc9c6dc5d7bbf16ba4b2bea2cb9666815bf3f680f72be395384a299e7", + "db5139acccf8dc1294eeb56fae06397f17a040e093d307a9e7b0d62ad6b42ae2", + "43e63ea9672a65432e1b1c21baac7741c9a5f1d6c4e60ae0b4618587415c5a9f", + "75e8ad0712bf9f279d89b252e7fc17ee0add1587df3f355568999fda4dfb0a2a", + "83491196e6dfdec6dc2d09e12380cf55fca8acd4ed8af12012e41f3ac2270978", + "b31d6592a4bedfc5e5d93565a4824f2917326566070ea981e45bdaadaf3022ba", + "ad3b86deac6998141aaaf372f299bc1c39e8bbee4807cb2b6cab7cff32749815", + "e2ea5f557fea88d002756c080dff0350e2ae9c15dbbd423a2be45d660107813b", + "7750f2575cf347fc1b24fdd5b149eba76a438109f7ae02a515e2451179b2c98e", + "c34cafe9369b18ed88c2936cd7cfaa2dad9ff105a9c3a53e49da5e7ad4ae4b4d", + "c7542b4b36dab508ea0de2e1957d451160fb3616c4c81ab967d2aaa97cc0d8c8", + "9da5cd367d3dc450b2c76dfa1ea0ad1e52db56eb7426fa031f2d08d919eb39d6", + "340a3d854262aaf7a8a6bf005c21ae7ef62d58d052c1d108e7e09e708ac3f0d1", + "dc2baad94c21cf7e37a4208a6dddd74bcc84aec852eb308a39ce9f81f6fb883c", + "d21cb6a6f34d9a66c167c4939619a8823be213ceac2bf401721495628d2ea3ff", + "55f3144838496089eb2af67adccd4b27340155cf2b1cc885427f4c7d4e7a8666", + "86ac6e18b9931bd435b38f96d97aed092e1fa8be47089f00bc44b6f7897e64ff", + "b4044a883dfb2eb9f4baa42d259f87d5ab39821470f977548a435cc65897a162", + "d75f0fca387134162a37b5037c5006c3e25c991fd5c0d3eecaed3e75df90b30a", + "4ef8fd941ecc624d1f13ec8c0004744cf907bf91f0a25ea0986294b81d5e45ea", + "015e5322f9f2153091b70b0341912ea37a0da3ed20cfe9b8356c1ce882c00900", + "65f0ffa646fdd7a98f1dde849611f19b2f443703a11ff5a61568b0ba7f85b0ef", + "d9515617ba3fddf4595e395e0e033e9eae484810a478be85bb0eb3851000459b", + "e3858e031fe47a5edc3ec572173de5ea77e9ed292968af2c6ff2fbd98e5d7996", + "4fcaaf279411aa84d92f548b1dfc05458af2afe9ff577221e4b1d82a2e37368b", + "269b84393f62de52b612e2f9922a67d06e523e27d4d2fe6d3aa68caed7a2a190", + "4d179db7af1ec1b6094dd55ce21d26a54e395f23cc3428c0de6ef171d6b015ea", + "1a508533186b12799e418ae46978c8bb8bc88fbd8a478fbea38b86987d197ce1", + "ca4e5990f8a59ddaa61ea319c9ecba88cf321558b8735fc262aaa172185801b1", + "0d48ac9f64fd82bf2e95ffb1eb9f1f73a8dc778417672fbdff374e5093a35f23", + "c2b88c4f59cea3fd60369fc386ac89d22f6998a69b24f4c8ab7fee63247851a9", + "75fa241c04b786f0f2670e0b1e45134aa76445efb26280cf6dfd926f0e77446a", + "b6b80a7ed9aa4d50ec56812cd5a04fa290591976b0adce14e6657815764d1645", + "c29d8b54a265d59ba2e8eb1bed27450e411ee34ccfda853d3a83c1da2fb75b3c", + "180681a90fddc13a6f216b87128055a0947736248d967a5170149a50ec8020f8", + "f65b08dc2c498a0f01adf70e45c5ba21205e259186f928305961718fd086e08b", + "43e8f556219e8e6a7508a2c5ae19d6de0cda7b9f6e893bc19bb68e361c37c786", + "367709af13a857bd2b97701b27c88a7c430a0e791a76c72896b575e017d8d9d6", + "c9207c31bf4517e18513602801a0780242fdb91c1ef85924c2ad4ded7bbf1ada", + "9e48f7bd1d77dc7269d121584eb914303aacf25a336d42914300659e0f67871a", + "8a2b8b728756feef55e9c9bacc138eacf609589afb4422ba2ec2bf9aaaa6b0fb", + "5440c68bcfe6a804c5865c3bbf033033bcabe29e20dbd9f36f5968e2eb0bef02", + "432af6a45d6f2a44d5d6de8c82c4b43ebfc8b289b08866d0c4b4dbfe7337b31f", + "4fb77ac98b549ebda2ba41cf0ee99b55c23386dde4cb505ccc497c4337b243be", + "3f1af916ee3982bb79c31921da5c90a315c9719ef93d87be323bdfb13c6e2770", + "4268889b9776502d948382bc1c9e065793104a2ed0e8aa75b2153b62b4ccc097", + "e14719c99770cf507552d3d64eb822c8c7a6c4d12c769d6cecb6e6e05db85e47", + "70adecdd90fccabd0f39e9d41dc19c6ba9afd11ddb7f09c6ca9c3e568b9e7194", + "c9a94d170bbcb06d9170badf89f2f3533daf8e15b633dbe7ea1d02c62fd2d847", + "538c3cff924df23105e51cb100c844f356d0d05d06e3aacb7c30f98c91292f72", + "bee2c32fb72004ceb24d207bd890ba8fb4126848d2a7bb02ba580c2669f7d27d", + "98db2db86a3da56f16411bf6c2e27f2755afd38673bccc15af8991fca283cd8b", + "fd8f89cfc412008ec0752fabaec3ac72d6c89ad81286980e8138edf6ff4cd992", + "009704c84f0954d45036969fd15c7293048156ff97a76d9c72a08248a4dcd725", + "593b8d1e74c299da61950db52fd3b08f44d6cf972ec05a5c5914da9f230cacc5", + "e8a6d9cff900567aef290b62a57d8d5528b7468adbe2ee0bcef8ac47e2dc1d7c", + "9fbba3e66b12c7c926b88afb5b4a9eb03d1afaac94f673d45639ce9d4dd51416", + "b7f9c83320d1b04ecb9b4f714da462b7bb4e4ab7cf3625a5f0bb497f5405a038", + "4da28a828ab79336ff2e766d3f32e3a5ad11af8203c4428d97ce5c0054aaeca6", + "d1d9ee683589c16d6d043f383ec2023953c482f9c32704fe1a461ba17da05ea2", + "0706b6349613201f5bbb9c6125c48306ff53d4688f64dd994405abad1872d450", + "b22580d4aeda8ed6d73c59bf74efd26c3edbdeb0865c0c4b6ca61db39fc6fcdd", + "978e4abd23ead67962bb223e372c8873f283f2d6bc3b0b0ef6a21fdedbf48420", + "d37e209428acd60797e1e77636bdda5204c56af262d487469b6eb069dc5ba0d9", + "44622d438823091e0823695e5c08b633b3f0c46af0286989798e27530a20273a", + "8902b51c808d5143cf6e368faa11e4e86ff0d373cef041553662da373376ff59", + "8250faa8be69d0ccbc2df66b1046d1ccd6750503855943542b6610a9a6e9cfc2", + "e75d7f1982478da0379f975f964410f4e5645f954ace65f0ba0eeb1ba7d7b190", + "5836da561d17e30faac4342fc555878d144b1108f27112af7f425aaf90f42efb", + "b0d06739420bc6d96ccbae8336bb9b6a28adcac61f1f5712915ef42456222332", + "c8839deeb39bf4e9743d617cf5efc29e9a59f39d681128baa4e900203acab6bc", + "9fca90ccfc4d102cb60b2a6d992c9086a71b1f742dd805cb40b945e598bd255f", + "b08651ed30172874bdb63711d32ef549b43861bae4cee530da74b7874d0deffb", + "f59d3938a18249d47eae4be568d3098688df1c5eee393a913203af87f92ab871", + "68b30e55a656422b4f7757e887a7356781f17ec91acd25d2f062bb9012741423", + "ba79b09ddea8bbdbe7723a906c367b4a51cd8aa47fbe2836c169ec28b4e51af4", + "51b2803fc4d01eb5e9f011c74c5e6b294327f44052e9e56fd0b75475b3f2167b", + "2a40ad807c8ec22752fd4886570008b62762d208a5ea24140813808b64792c48", + "f2abe05e84099e996c1f9a2be789b77259322c5b28a1a24ec418038e77a7c252", + "e39996b22b171c30c0d9d7f3f9b89c8cdde606af69dbf6936ab50d4a8540a43c", + "6782c67e3849c9ce43967ee303d29b12545af84fea12c5cc4cf1f973214eb2d1", + "3477e62c947ab134c3242522425c234318e22998b4f6d7b08422427e36d01004", + "c2020358b71b1f043bff47e117027bc8112d4dd0d65131063e667da9e0974357", + "5e3c95247d735f46b45940288d0e8c0016ee553c4939aa4192ba7e52cbe63b63", + "f3ef5179680400a2a83fa5b01d823bc3906d68ffd29ef18923262472afb8c685", + "5a2f1f9be05ce5402fc0728614f98e6c5d8a30a7008fd7114d2e32f128b99468", + "80489be62cf13a2efe0f72ae08422eb8b9a033bb24783bb075e5a030b12f4d12", + "01aa376514be84131de256c9fd1f3cfa05edd4408a332e699c2f0512e8653203", + "6a59eb72f291d9209aa9ee2580a977707b28e3714d04859352826b7003759f1a", + "1ed1699d9c8af25571a1280b6f5251cf690d0005bbc222829ed1b5c8368fb7d0", + "2b8966e5eafc875e7df9b828674d7adfca2225a5c5458efe5d3490932c0011be", + "d0f30db6d5b92d763aa39c910bfd007d8ce08f58cabdfdb67e38b12478851a81", + "2b5370d6abbb3e0c0cf3d9187e5173d7784622b770dfba6c33b8d672e22e12c9", + "7a874cc8981afc43943b02be3428c2150ca2d94dc2735904be475872fd48bf4b", + "ab8bc740762787a90716c3f1fee072e57031a1525d52ce20214a984e793307b1", + "40983e94cc324c141ba33a7cc5682f3f187d832dd665a2503c4101ce3710b2d1", + "ea73153725b2ef43b2e10a05b7426aa8e241284b1702214e8dd9cd04b38df68d", + "4ab724b8efa9a325df30695daea6894d5da08aaef739f20e716619f4e595885e", + "cceb8bbf3b3bbef90da4b219fe547ad6a3edd9dcd2ae48066f2511a8769e286b", + "b8c442596d32a409e8fa7b4401d52cc8cd2e82ac216d374933dd9013535b443b", + "0a7d2aaff8434564372ad339e2ed316650f490f9932afbc9cfb5f9726edd227d", + "117e018ab57319440ca476a5799a3eb87eb7738a793fde1b95c34d32cbeb4522", + "7c84152e37659e4be0e0806e11fa7ab154e5dde6bba50409c8c475f480016120", + "4408ece10877a6ac3f4320941617b72c27fe122bf0a0b94932bec6f7f6c5953f", + "5b425b6d0c9bdb4fe93fdcb1314b2ecf3cafb77cadea5a3fbc1a377c04c77aa3", + "2352dff4400aeaefaaaa61cf37512d2076e3f5bea92d2ad51aa99f630ef25a67", + "08c9db753634d99a25264104c5b816ebc15b95e0d2f84855d32e52aa90aa5aa7", + "91d5571d7b2e0566426cef2498a5690d32e30268c0a3fc17d81e4d3acdaf65f4", + "71b408acc816438a0acf055260a433b5b0fca4a71e627e54ed7dc96079846c44", + "4c925a3c7a5c74d7ddc2095fd3c303fe9af708cc54ec1992f89719a45dd2e98e", + "60f56a735c8f2f31efd7580c90234e9fc622efcae366849c63a796bf80006a12", + "43961c4f8130c54f32b45f9ee39fe88faab957a734501e7b49e7d7ac8f81ba91", + "5a503cee0a815b1fcc4ee6000b8d88e7fa176f7fff7ebf9abe82379a9b87037c", + "b19e6f323d3ce96402d8ced832944a02728d9df0674a9a7bd8e18dc8896de94c", + "a743c955a7b559a34cd8907b9eb7a40d86f8f15a58e90b91c9fd87414a91b3ef", + "b40cb00eaa805c0d27e86cc289e34dd354d820453382954ed2ac24b27d38c2d4", + "e6016c78523ce714a360f77ad95b39bb8a724277430e6d2ea5a2130334011727", + "e8b448a80c197809e559b30fbff6a7a99d1bae7409ec00c766a129ab70e47f5f", + "4b42f9bb88185504e200703c9fa8dfbaf850978a60827cfe7688c434c502de0f", + "d48a1aaa1f36222f4acbaaa8218e249d5774acf8abea087c9e2ee9e21dfb2ab1", + "cd11b4f441f834863a99988fbecdf8b19be665edfd19ad4db62e9d290a98bd02", + "2d6152c003c48f8f6a1c2693dbb5cc5528e7e164a16beb798c772dc85bf2ee21", + "0f9af43eaaeadb7118498e562e45be1ee1e9b7f3787cbec3aeadf671a2c3138b", + "e87a2dab8e4945b8ad5fb7866efc8dc76a990638822542050506d1d0ce78fcb9", + "190a2cb2ea8d06d989d23ff075bcf61b236841a27975f89bdc085cff823e55b5", + "9b09d9ad2f3a069a4c9b33e77a833fdd6605a2095e8062f8b79a5fe197dda466", + "06dd4f3da3273bd8fe869a976006745235347ab53612247d3e0428fe152787a6", + "8dd9a7dcd863f7a8fe98ed2f452615a74560ab7ab964ff161834a6a6d5b21b4e", + "f68bdbccd9955c40848cd3acd5133f991becbcfbcf167e5d2018633229d1c0ee", + "d1990ee38345d528f0115a99aad45cf41d3a579ee1be601bee3820f181819f4a", + "b09339818aa6b2bd5bf955109969d4ed43830a672c479cf34bca1661e31eacf1", + "c5dc68711d6a27092ee6db8a779fb1972d769a4d571b759e6bca257493b1031e", + "6f7e4d57051949b2abee3b329b299d5a6f555d06cdd83811fdc7d47531ff7179", + "45862ace8f36c0a3a6d4326817f0f80f3041622e85e055e2eb89dbd20d0df324", + "b57841327c49f6c475534d0cd18ff578958790ec3c819fa5b97d8971252138e6", + "e83615f0779683ae32d0f019b20cbf2da935403f509bbb9d2856dc6201b613bb", + "be524d2863d41baa171561328c9435163eca92d6e085ebbc29a4e1e3cc6753ca", + "9dd3c020ae3e8be0f6fcc97e6fb0e8c76ce9694dd5ca8a2e8a122862f552a421", + "a7e5ca5d0be1d24c69f4992a3c1a8f6acee7929afb2538045cf737a5da62898b", + "0cdda68b16cf1e43b41e2d2eedd6547972eeb5491930f9057a3c07743219c1db", + "5c83bb522e9a09d6c81af19b7802ea30ad22484b2ef0c609ade92089e7cc7248", + "078a6ea5a4b2be65c7c423f7ad5e4ad909248729b08264b8f880ac5d90f009f3", + "47a73b39d4c6c631d148b9cc31dd43311d20d91592a9ed38d87579491cd9d855", + "7c625f9d16a27bd30064682fc896a15c4b504819233610fc4a88cccd271059a4", + "5f4c8d2cc5c67a0c393493474e74f770f8c09751cf4d712f0c906d9d3903fcc0", + "60a628f0347932c8679436a3990fe3dcf4d02fb9c24fe4fa9754717c176591e0", + "2fb12765c5e1eb877c55f574c7dc61a4123ebf7c7e72cec7d7e83420fa578853", + "2ef3b7fb03472c1f5a850b40e9b35d9a35d87f05292c1d0d43e863d7e7ec6b4d", + "16c54fff31b4da393130f6eb18111392a96e85421fcd1e928e17ae6359172221", + "56ba9c544d0ca5de806803a36a1a19f53ba914f286d92bce307f91eca1bf5652", + "e8df51a49ab47130db5332e1dd3f1ddafcc2ce80150ae3c37eb100f5627b6bf7", + "3df4f7f14f87f1ea6df31b9b8269cbdcac2d8590a89a2753d7bb11fb8ec576ce", + "ed2560a20088fa40572837ba321394fb3665cc60c15b19a2fd07333101636953", + "a5ecff5434cd0aaa835073066abb2b9f9e493dc4cb7b26983f93c94016c09297", + "af3ef059dd49490d483b8b9a99b245695483aaedbbab25bd1a54852a26b28802", + "39d2d8d4da53e0c373a1e39b4847821317f10d9fff8b7ac598b59b7e79e2be6f", + "b1b6815684cc275278a6a3b0a7de9d0c7d4216f8f1b8c8f1d075be64f70b79f4", + "fb2abc840ce8866537feb12531f420dc89ca9de12b4e192e1ef7cbe765cc4816", + "c7b6c8d3d4ec9457b4caf29f6a6198f893c51263d73d0b12e8bcf5630fea1e7f", + "aa5ae0d35bbba924165abccb826604c80f6ad55781c49e6016bd8207196f66bf", + "650d0eedab56b6492743e7c5692651e8aad37d1a9939963141a67482a686e9c3", + "22d0b3451a44adb8ef87a116097e3ac46b5f8345ebf0e5dcec960d2d55a72d93", + "e3110bee29e6162f431aa68c170d622f819bc4e289f6c3e1d695cc444c8dc171", + "92fc6ae91caffeefc1e16e816a0ef299e17c63a968d41bb91b7a6f587d34f8f2", + "76d986dbb1668f0069867df25251328e1aa15e6f31233eacc856e61b5d328e6c", + "560b821428968d6304aa517c6b0df4469059a097171e7180c0bdc1de4c8c2dfb", + "199db247cd1e61526d67823aa89c7d3e9a849a1c48983902f4e732d37491ac95", + "f3000de6fbdf5997bcd5067bddbebd4c1057346a69164446e4a2fd0fe575461d", + "a36d62775f7abac51834f25572e05a0238de20394f0b8a5cc1f33b47984c4a02", + "b6df530ded76cd3cac5eb5f6516b7b5aad5ee43beb0876c8f1eddc38f4e173af", + "a939753b3d570927cc8d90e512239cfb6e52d81b5d56ea80eb4d0c46572d9ffb", + "915cf3bab38606e7266a5f7c2179ad469b07b134765900ff6ca2a5ab83fbe80c", + "c084076c1d75861928f6a28f566527ad2913a2bf829489f57dfc8947c368c50c", + "b71e574bfd980be24d08395d32430a000953a021556ffc4d7693190c00e74e77", + "4cb8e212261c1ec6fbc52df8cac337d6f5d189537645f7d5a7f911db882db716", + "533abcd8780f59049a8462ab7ef505f5ccf1d342150c30dbab5949fb084cdf21", + "db533c3913044c03977baf092f0156071c2628208d14d81872162436b87123fa", + "ca8ac0dbf15bef994221f78c0248b9a6408ead1166e623c6db7652a16c016c06", + "0990b3a317a95d36b6b6ff1fc6736fea01529a39c0fe06e49b7fd564080b6df4", + "787469298f138d1c490251895a85d6080e52aa1df375643d195dbe7e87847949", + "68a371372ba0f489c8ab8672e257f265a418e50c6c969bbec4b24105850c371b", + "f8ba30581f3cf31050db47b7a43a5551d13b9ce7001c896bb79409f4f1d0a31f", + "23a5c21151808ad600e97139099b79c3e1ce23ac1252db87b2d4cb88359cde4a", + "9886c12c93ec0ea5d14478fa4a9a38c4a1025f8b9d595deb371d6aff79f93b04", + "4e4f81cf1628b6f43db1db218773db5f7a405311aa7ee5ae2072148a867a1437", + "a019556336364cf6a38501be26e917b0aa2cc2c37c137fc269659aa44a3e5ee8", + "7c6b6a1324fc40c2373c6215ad699f723b826f02397eb6f6326c1a5ed558a115", + "bd21001934e50a47ade3d713cec8dec036d54da0a976ed3b690ac080a7bc9ac4", + "4db0292758deb6ab35aac12fd6387276b2f32cdc859a15642f01931c845fb0ef", + "5431a87a0cd32b11db5ced545effb22f220ffc69a60ac552844db86bc7998e84", + "c6f343782af6f6ad784f9f85c1123fba0b82ac07119a61ac39ed2fbd92fc0a90", + "c412d2dea34ebbda964b17cc12c1da70e7dc428c92eaf494e17100e9ac65ffa4", + "e9b947aba6755de52a67725be3849e3383e187099aeb1bef5315d22609368b23", + "0b2d1949bead9e9995b717a821f6e5dc3136857b178638350a16c1a32149c6ff", + "dffb51c4bc98c9f0c07ce18a88877d48947706cf57f56cc6a29ce82cf2f74c07", + "97dbf9a48f3c92fd4674b20ed6f635bbd92af003de523feb5ecc4758169e5857", + "cf701be33d1b3abd3357a74ab2f7f03fa78f7ebe0362994256020fb1fad75251", + "fb26930bfdbbdd6f69be65c3c01536e09a30cedb747b69719035e31636e47937", + "5725b8ebf2345fe7e660579ae94b84e251259487da4428a5f1f0cfec19db0639", + "61b92f8043e5a035b2144fad7b1df3ca7130c56cabbe0dd2757dd737719719fc", + "35a2f85adbd0b6a818d84a16d8ccbc0947bda389b6cf43ce6ee9aa9b4aea19f2", + "69dbaa6d8a1d44259bf095b55311dd39ce028ab20de30ca49b1a173d6171109e", + "3b68203efda022be691480952c5132129d514b2cdceb0d3a8f137595be7f1c25", + "388d3dd82eecf369794186b42c964871575c03fa9772d57d31280d678f87a636", + "6b493f049d19b3d1e268babf0211d48abd018c912787a913ff71457d54ac3606", + "53261340605f5e63ecea85eabc5716f31be1d16d1c91c04f3a090773b288e62f", + "7bc9e1a6b36320bf97e60da91091c0ea848653a56cfda13f129e42719623c500", + "7347ad263d90e20635eca6fe1bc89df06a4ec273ad50e1ef78a27fb9fcffca13", + "fd286c0a1d9e4a0e0aaf97b7f2da4b219e385d7a9afc4982698574d72ae49daf", + "0f42f9f37bf77fc47e1c75e0206654e0059703f2b7dd261f91719e49e586e7ad", + "5c1eb8efa7c26ebb6a3e2cb19148783df0b7d5dac0e80b3c793ecb556166a88a", + "0fd0ace6df8a7ba03aa6d52f78c3e58a4720a3852ad936bba80e08bf7c61a287", + "020d473e1c9504366a2204d566b1ca6c968320e30382129f95a40c739cba7f19", + "ff7051698eaa84a3ffc25d5dbcf923933a1a59730db33e8161698160a090a45d", + "df0b1d015cf77e47512cda3d49be662782e52715fb747cd0f73934109a066ff1", + "930edae2fb341f18849c9b8aace01d5ed2cfaa013f59647cb542daf33ee60db6", + "acd5d0212d8d1bed17253050038a481d968cbb8007c5c69f35d328cc887723c1", + "17f76ba6af39c2d64b95554ba27b12818e60284d166a9da60a3110091c1dc3ec", + "e03914821b7b6185e239eae45598d504558175443c4f34b700c2b329beaf14cc", + "cbde6cf03818faf19d93a082a509c3601a6b043a66a222d389cd44d4a91d38cc", + "c42abe41dfa793e60e2ab797e626bd8a5904e03633b61c513c9f80c2be9ef0d0", + "b47f9040a5561c94bb5c11ac8042f34d80e144a3f5a8b7c0e47d7aa3c10812ec", + "07f44825dbef5d2c5fb089411e2eb2d4f7e7eeef6697852084acc7b1f48aa77e", + "ab143316522be7e11b267b89677251a5a1d48ce7c90ee27a6c66e1857d71e576", + "3e03c42ebff10fddf113e731d91c96cba59d8d29acb8f6e63e0fd171657414d2", + "12a073e1548bd7551d477f3ffaa790d42105596f7c161a623650f1a053c6f053", + "4882fb1b192fe817b836fff3baf788d89442f75d3ae7a529297febc3ea896779", + "a3b5c64eb6d70663ce13bad27c28cfe46ecbf721aa12d667f3903e155f37471f", + "19283ceafa4e91731909817f264f997836d529c523881913fef99c04c23f636a", + "2c9ebde51c783478616c534f8cf30c1d9bcba627e82873678054f8c05f72cd20", + "6addbacf1779166778a472eafa5388de1e51765c9264db7f44297808f30cc940", + "33c39d4cf80eddc5b93cbf11f5cf6de02023709b71c3388dfffd485becf93de5", + "73d7856eb00d8f0eef1cc6ebab299384aba68e5e7c90e4526c4a33a3f7b9725b", + "f45968304cd3eaaf430b7744dac3bd2c287c51ab1418e02973fb2f6bfd3d7847", + "1e18a797ff5a58a2a3251358d7ea2e84895a939dd63fb5a3c77753f324b9c86e", + "3ff531919376e2ad060436f9d27129b7ef17fb8ff29cfce1ef3726833aed96dd", + "92a1626588c06d5a3d74a59f23e506460f99323c09d35bf5d49bd73450803f50", + "be288f4cfe34bc80521860940d76e5a42c85f68ca190a4ad71dcdc18c4198103", + "9339b6786bf2c8c1dd4830c384e03dedac22e92bc1aa9cdc3449c674bef72ca6", + "1b22a0e578c641613e3602764129e027e101e2d423d9fde2a6718a7dbd726eb7", + "c6be938d80860b8ccc96ba087fdb2a00d980808a61dc79483710ba87f1529dde", + "4bbb241fdc1feccd155ac9feb2ab91cbe714030cab20dfbf34318ae72ecd4a6c", + "706c4c5215f54df3c0c256e9d6c54de897716618824d1fc630d00d3af059352c", + "93e883284fa263b442a033252e84dd637a6943f49f9db224855327139dc8417c", + "689dc122cf1c3b550ac11e0041b45844b794a45c31a2896a4238242422b23819", + "1aeed96414641bbf27a32c1a94b5bc224947f87bb915a53dd2362a5baaac9ba0", + "0c0f53761b2c3f168b01cb9cc05894cb86ee53a02ff0c5fd5f160a8d0d15ae76", + "da8b72cfb9146b496da6719d87da1c52f295086935e1f15da60a86b24bf29b97", + "2389058fa5e0174f217260522f49d07b76c7758b0d3f7296809104cf78561b40", + "0a59439df43ceb691a13038b87b7ed5ed2d05f7acddeb2cc502d6df537b71311", + "ad9989dc2e5e4d1a473e2f30f9581ad5dd161f68765adb6d2e70c8effb23f3ff", + "466309e21fe5c8555a231e5214596e196ad44683a8e8376e453daf42b8085d9c", + "a6ceea7c560556cdba4d7b433400688ffeffcbf27bc7d05ea507dd2d4ce948c2", + "b77e4ed4bee931d43571d6f22f0ae253ae2dc63871c5e4845c6676c7dd3f0eb7", + "7ebef185856fed824ebc3f38b79f82746b2983f8106c2ca6a8b64e410040de10", + "e9a0458703175d02b8fce2b8ca87d2a6062270e97499130aa81aea621b42567d", + "ae17cd0fa9460c9caf2c158e1ac809d0baa510933aca2413c316840ca42d69dd", + "0d485a3ddf329e8b8dd47b569c7ba6faf17d4dbe280521a520ece56136ecc218", + "20241e5f5bea83a9efe54520cb4e18b2bbda3eb83b0875f44c62994cc2f4a481", + "ecd706d3f483600f37f3b2b268cf38033db1290e0359a089e7dd44d35300eb39", + "326a9ece9955f2f872f0f6e401ff4281f1992d9f22f247ecaf25a44f5661c732", + "36f0d608c831ea7365b0ab0f2d78d44f9b6cf0a3edda625d02a0e8db1250c644", + "fb476cab0554ccbd17ef87ab086fb70310f1b468e61050e4c47a9c003f71a88f", + "eae0d52bc60c959f73cbf3ff7a84f1005b935e3d71b64dd076f23851b61e19df", + "c8320f5862f6c97350570d41ac318ab729aac137043d0ca73f467eca670fd2cd", + "ad83f844f80d1bdc54559399d55f09160e3041f63bd85f9482ef0e0159e576eb", + "faca90225e0b75e9ecdb380a4f45937016f843f188f48a956b24601dbe2e7eeb", + "da4e04a00f8b7a2724c60b78a8df4caf6d3f65a8f5b8a86561ceb9933eb87a72", + "c17975d1893a8b62915a085e714f9c6f85d9442f7916ddec6a822090c8dc655b", + "7b7f3cb7bf35478e8dab8f33acc8746aa6ed70189e0daa8e893783deb55fc50d", + "d0311b9d76278b80d7f55b8c4bacaa8dca293cccea20a784ea9fe254abe93b5a", + "91d99863528e63d01fd353457a910ad683d11614458708b31fae2454fa224b5b", + "b0ae1e07f65c9dd764db153aa46cf42f68d780e4b28e642a4559ec90e8e3cc39", + "43fea848ab8ea34e591ac9c9f98783b3f83a812a9bef6339e81bd380d4d0b0d9", + "80695fc224bc3d9f16e1be9078e62d91b1769c4147dc44421531a213b4a69460", + "ebb6cfe7e39f666efb4e256af94b5b21aac14902ef3c23e6788ffb32d16da24c", + "ef5b6beeb6cf57ad290d3be9fe6790221fe4685d99ced85082f66cea6b2e2d96", + "a4556671f393544c118880d5e2b6e0b6a9d571325e35972c8bfa5ae70a9aec8a", + "0df55165d1eb0fdd4b62a25222e060b21a4e5be7b8388e29380b86b2940efdad", + "19b60ec413d47f58a62173098d1591609281e701dd079656a09512e8684badaf", + "52e4284b527671369be86b4b20545217254f85faaa8abe39953b59fa1557442d", + "8eb1a4325c5783a09e0febbc365313f3c4d35a2c1618ee6532572ab2ef1124e3", + "48a251c051cc0c8b051b806600f00342ab81e3aa111e066e74adc044502379f9", + "97fefae91a85838a8b93a4a8cc0edadc2e50b43c1ac0e36a259a40fdf8a4e213", + "aa1342bfc905f3f255c44aa09df477228c41977c060fb5f936fc6b4838c5be7a", + "b9e6594d64492899962352ee5c918c3ade3efdf92e16bd4320e0da27ab3ce2ae", + "1a5db6b7d6da5386f76ef77cd6ddc99ff67de917819afa746dc3c5378a95f30c", + "bf64bc337a886d5d799d839f03eee210acab95f613a17db30165d3e249d07e87", + "37b794fdf56c4ab8d2b4d00506ae67f02060625d55adf6c208cd2f53b6787cb8", + "7df41b919abd69e7acee85a552ed81023aad7a4f1773b35f104af8d253e9020e", + "71be6114d2cd31bea00baa69981edc59bfaa7891ed5849178f3711f37e5a9223", + "7e8908c6f9e14300d44c2254d022578f689d8c0cab56ad73ab97c748a71e7371", + "06a70fc0c9e0e0a3a67dfec26eb5367836dc932f20f65770cd27511f76a4ef7f", + "5f902ed069cac194a12c903fd9231b3b9b73ae499e47206179a5db78053b7d48", + "c4587b59006d177ecb76f64a083c8bafc926b243a6e9ee6ef12bf0f3fae367ff", + "eeff57364b4cee2e43ed7811728c6e0dc155c94bd8630567981c00e7983c202f", + "d9ed39b27fc2cd3e1c3bdda317c856557795363bac22e5475f58614be9da5f0a", + "5fe8295650a3ca6dceb6adda98a9b2a52c8ff5aa2b879dbe74dc479c14df783e", + "bb74a5396ca5059aee5f6973f760285858dc7ee84f34631100b641d8f48b89b3", + "bc357b63fb90baaff808482eb645d5523323bf52d98c3fe493ac8eaaa4773677", + "2d052d15ac1ce4eae8e7988c586779ca83b7f20134899d1a72bc05bc94c0b35c", + "ec20f72f981ef2d1397d3f05c0cdb32110e4c7bb9b876a9aa8296f5daeee6cc4", + "039cb433d2315ab0011d4f1b3d040ec7e1f1006fc3278b96e9a8e0491d1a9d60", + "2dcdb4800462a1c11c0e832d04a1c49d9f62103f2da332286c48fe94b456b92e", + "4a8b6e2279a7392cefb1e7da03b42371a678f84c856de77cf13ff9c0891d9b00", + "57bcdcfc0ded16c30a50204f409dfc81df4ad4b1fe673d99920d9f79a397d8b7", + "0261b6d4b9c1c5a585ab93951761c05ae9cd62c56359dd821486d2b1fbbb3177", + "2a71c8ea5108d9a459c6ccb1a614cc2546ddbd85efa1eb39ab3ce0a504cdb916", + "251698409786d137dbce1f4490f83fecedaf384809eaea793c3e688bbf61811f", + "2ace8689de6c19ba00afddca95b6c4d32b9929390c628b9339a77ed21dd9ec97", + "10e81239fd71cd6fc6a7b858986c7c4e1d064811ffa0cedff96ec34e3fa9f454", + "1981916f528ccbc3547bd0daed092031c4045e1d1426398f88298f7d29e61f8b", + "271cf2cafb0c86c1058d1247771cf90cd398916ee1eb4988b19765673a2c03e6", + "9d086afd05abd5ed8e554891229752b2bd98bb124d83e5c517c727a6e41750fa", + "5f2c7ea24040a74f3badd941fc602bb8ba2c2f3dc8a6f12bf3565579587b5ac9", + "0cd14ff3e26eb0253503592d3c2231f067c0d34c9a08478b3ccb6f9708e63bb3", + "7d8c41eb7c106d34679fda959f40538e268441214561488988384d52bc4959bb", + "ad844d292ee314dcfe7b9673493aa46fc09437a3215984514f7d6e25516d82e1", + "1a2a86d0c46010632d2cc42225c28e81d33831a762904c07fc165696e0b21f56", + "dd5549881dea36dc21d79af4fe8fd523ea7198354b09f11d45cdbc16f2f1a593", + "f163f190fa2603da18b5c4d6a8ce38135027e1e27765786dda3d4aea2a9cb80b", + "6e09fafb8b6e87d6189f46e244bfac72ac097f881fd3e20548bc9b2b77a06494", + "050e25827579544b11a0659b33779e36091a9386e10529de15661476f621b4be", + "2e106b4ee4214443a32a625a8050bfbe23c994d5d11f7c06604d67449dc4b65c", + "f46f7dc3ed0045f94584f0638fed6b4a1428e757d0286e64a7d09b90bd379f44", + "4a2c43aa7fa22812393b64ef4735c4ce9dad96b2324d06204d17cd489d7ce412", + "98862e2f2fbafaf00755c99a60aa0476cb449041ee71556e1e7c6442e149e51b", + "4c118375e7aac9cd504042c81c94f58f123924bcc2dbdd07efdf3ac283bd06ec", + "bebddec9428e7802423acb00e58d998f5daa5a26bfdae57697deb463e9e86feb", + "53b03c24b8c7b3c56e56ee1e708df3eeb032bf383d7cb1b2d3fd0bb11cb984b2", + "784badbf46476520b5c6635401f74e99ef7c7a003a393aa1fd57867f185dd721", + "2f3caaeb46d182b613605bcb8bedd3831bfd1ea4e7a041c4f76e0d4718aa9eef", + "1b3aa9204c47e2d2ba2657e13382926d2ead35d868353551eaf9a66a70c669ec", + "c9f2a7d878c3f80d7627ae2b832007dababd27fd2fc13709901faebcb14b2932", + "5a39dc81cb0b6518f00f5c66ac659dbd72f881a78e0c0b13dc88ade430abb90f", + "2f0ee5fedfa55f94832fb2744a80af32bdbf7708ba65a3e0d08b446358da1499", + "1d07c7dda8607fe8e06cefe30123fb4a38302ca4e091f5ee9df4013778e13fb3", + "3fabeb2e7e8cb221b62d4e11ded6d5eaa1db1380b7639c943f60a1c5c30cad09", + "51a1fd08466d6b37cbe7a6bab184c21ca50c9feb5938283d58d89fdc1035fc9f", + "0708c2dc87f7ffaecd46d3c7dac910700bbdd5f9331f262819ec9fc90545d3c8", + "2d8f59f27fbf1306ba8453c032a2d8bc6a18ad2d8f3fec4ec260af16f6213dde", + "3a7455c90f216f2692b108f4c907cde0f64872ed13e6f75b44e5f857695ad824", + "5f1fe505ad82e5de86f16977be619103b511b0ca09393daed964ec61962cb942", + "3decaf9907eda934c124f603d3252929ccb9b99aeacf745018f2cd612ba3dcdb", + "4d232fa8d81cb6abd1fb6806b45c069b7fa7011248f55d1873daa335312dddab", + "55000d6b9fd4132be58fdfcc87b55b28dbb2bc259c33d50e03a9d470e503553a", + "225d9a9f991b12fab890ccf06ee654afad4878a3a8c0bb3ddfdd54af3ba74ee3", + "40df7f285dccf966031f1e8815bec7473a748f37c9405ad98b0ccd40c87f986d", + "c4156b9659801913909aa5955548725ed2a251e9d3b3a587786094dc299a8f61", + "5dc3a95d6f96b946237a9977b4c1e05b24799729f03c191b084c6118106fd712", + "118bf0b7868f02b1154198107b4a86873ef471153967ec7bb11341caf70c4355", + "1ad3a5310730b0b698512c8c7d3e7a8552f75b5c7eec4546860bfdc622f75e7d", + "fb74a1c13b1eb19423dd805a4d85ee771ecf52e3956b51b0409cc5bbeec34569", + "7405d158d51c83cfb8dabb3201f692e3bc089f020cae6b62a2af2c88dd8f03ed", + "08f20e5a8dea1f3217c990bc112fe510ca5aacdefabd5a2a6958339abb88ad7a", + "dec3f7bbee141e55a68d1d164e891d9bf832cab148061a1551475fe33c1bc868", + "0a311678a6266de62ceb20154e6ba5ba5560507d58d8c5798474967b4924651c", + "b4f48fd17a7cd9e0b26e90a8adfa2f9a150d47e9a203cb89df114585645c87b6", + "26a5c76a25d02b0feb4b22e549ac88447abb8fe7c86196a73292f13d8eea61aa", + "2f41b58911b04c3418bcf76dfcc500d03a55fbf1749ee83a4e981fce4e32eb4a", + "411a18b6785d5cabf9b666a9a70cdfc5a1e29228f92f80b9f8d613face0195f1", + "b17d0a2cb46dcc365e065267bbc2cc36e92e20bc0ce24d144089d69282e6fa19", + "30ede26b25bcc2e6cc42f248386c61499ae477d4fb392ee289238c40c88ab5ab", + "f1bbaafdc5f0e00a5dad95c9ddf659747c74d093e1cba8a0e72f1db1d5cd904e", + "09e411ef140daba69cfdc2c3c32fd84cd572a842f53388124b51aa2eb1e40fa5", + "2c408b685afb2574e9fe2a212e92c638090e851026ac80c9a639daf234fdb984", + "a342b54ed7092264595569a37a5e5140a48d5117f62833de3dc1923516d5b917", + "b90af846ef17cbdfdc06399f2d6790194fd2d781babbcb2c3ba005031a278650", + "16c65a8054f7c7c6beedb172498972f2c3dcf0e8da240cc8f120dcee7439c95f", + "8979da949593c80f9164828107f04602d70ca494947d6ca7722180ca85c8c0d2", + "eb4ef64a1eb138e6ec7414b3d841d24e61d6aca94a870d3568f89bfeec28ce5b", + "8db828055ca1154a1c1b60b992299b2a3cc9f9ff217e15a329b642c8d5515344", + "5813ff09a79cd74b6fb3307c277caf7e80f3506afb677eeec65555b7fdb8bccb", + "e15883831714acb3d5c08a3898ddc6280114f1b41c450f6e93a27bb36e7c6766", + "95ee3e0acc204a6bd5f5b484e907056695cf5ace25449fabe87f25d61b1262f1", + "8eb0d5da9c247c962eba9081b9b58413b8f7aaec03df2813c77ce44573a3ab1f", + "afb841db4f6c64fcfda654cf058f0a883166b4066fba6be6df5dad564da30515", + "68fce267894ffd76f551a169c9508e53810bb12e773015aa4c547a56a5806fee", + "6e614d1afb783e6b45301b2fa03788a1fe27f2808cd2b513eabeb1226e6241dd", + "6bb0cdb7d8686d79abb5453c3f5045c1acc2e268b3f2b32444e60796a18d4c12", + "9b99c863068f24297df3431025ec6564b66f2b9f64c1678b528a1dbc4bf71b16", + "204b6f2f3ec5bd0f1907fdb460e4ed6fb0d25de5cc12f777a1ce10aacbed40c4", + "615fba5e47ab0d959fbcef39996936da656e2615b954f03b61fbfaa8e1a4f500", + "529895a7de063cf6e2497d30d849a8df3c4f7339e22a486044cbb3e7496fec22", + "b7fd2e4c0b845850f044f32c5d5123af9d5eaaa693d4350c4ca8758bfb857b19", + "059e015cff4b33f019921f9242533cb45cd65e507ed882205367b746b27f4442", + "1b79153a3a25bc82c7f52a577c140d85854d76b3268e332b5871a4de4eaaa47e", + "b9730c36bf7402a5d06c8226dc11ebec9c0a4bd6a23762a6e9e78d1b996a4630", + "d22427bf91dccfacba55357d4a8038f797eeff2c4dab56220cb00d9ae897b783", + "da19f42f6dc60fcc3d075d6f38671949ce2dad31b37496a638d341757822e92d", + "78132589eb98ac63e53a329f7cee32584dd447bb9fbbbc01e27245ca29c23a23", + "e467ea63a727b9c36e666d435664875c822f94f047f60ac0d89f2733bd4d1f01", + "f4f89149b1a5779dfdfa3a9b7dc5b88791deb32c20db6d611fa013b9a2eade6b", + "7b40c196aef97aa032f8a1ba0b88aed12baf4331acacae6513e8f4fbb102dc98", + "dc87f4206941dfd0431dbae606358b03741e52c9c9830017847042f99caa1a89", + "293c650a8f2b442da199ba6dc31cbc7616c09bdfcb685d216fe5f6964ebdcc19", + "cf3736739c87082a71653540be254cad9dd2b80c788910ff51833053de0f6833", + "b7904a9d282ee147c41a87ab1de0c68f6fe6e8681b728a50b07dbf82ab61990f", + "ff43760bacfbd8a86b77ee50524df729bfc322d221bf39d73fbdcc5172be25cf", + "94b19511a901d6c9863b4e698bd3203bb45f459a706f17d5fd0217b7d4d2e880", + "5fb42d90123e7d17bdf84717dba17f8d6e2e4cb9856ebcb5dbf6892fd227d4f8", + "d403da452541afc98ea45671522db8605d28a1373baa96279149d70fec56e8db", + "c515c1f3132d7124546c2149d2f96bf8899954ed28db737d81149cbd9f84ba87", + "7559f506a7dfdd3ac7f13dbc616629b56b88f8e43e6039205182b17136e45e6d", + "6b3f153c83caaa5f7bff608f4fbc514feee1ad49652691393b26e60a3860ac49", + "b25dc960443d800d107491ed43a224db6df66c12b02b7dbd3415ad4cf38c1f25", + "a70e10b5d457c0c10ae765d7696e96d1721bbd0557386cd00db27f7dd61c8039", + "c12df355cfaa94c9154a29848e53dfb2d1bcc7b68cb1506d067f5feebeca2134", + "c9a697b7434305777c8068af8b5bfd654d74bc5ad82220f84fe16e1cc9cb2173", + "261dab494a2e8adcbdfa76b171a1dfeae0a70cfee528d781883537c35ad37546", + "df2aababde59eec1b7b499858984c1204557f599659f05f6671d1d66d2273d68", + "36bbadcddfe80758e7b91dc8bbd2d2cfca89aa1c50ad41a706c99bd192c1042f", + "bbbd1e0d041cf1b340f9b0873d876ead1b76c60f593e8ba82e2d700309ea5b4c", + "07708bd4113b5e2773ac0d65323a793e5dcc366b1260eb55f6ed80f6fb3eece5", + "b7fd39d21af1c3ff30225230a86cb382680bab72cd83c081495df252bbd31dca", + "a01f7348d23cfc9c72d4c6c8d41d00f25331a9f6b28f704e314f46b9a5fe8836", + "e14b6869725e9f7c8b4d0fbd88d1cbf9a969e800bddeb3b426ab13a9e1c6873f", + "ad16ff69baf5690783aba8de17d09a92af4fe62a985e33c0969ed734d09bede2", + "d3edfb7f2ee7cd654793fce245666efe24655095b9098152093b3e5a12cc41f7", + "50cdf981025f02190aef3c09b5aded26472fe5b155c689acd1600b6f163b0aef", + "d944eee476fcd32ecf4d85d5796e17ec2adca8f9f6ce1c1f66687e2d99a29f2c", + "6eae57da9514dd54a3f5920e1891529982d88c17e7ad6f6d8d24c731e1507992", + "ba0b8bdba07211878e29c71e21bb5aba5186e50fd45b4303f846897b8c409ae5", + "16828f8c425552366cedc41a7ca7f0df1c5e5a9dc543bfd9b0df65021d087a48", + "338222d8ae91833de9008c65cf519b1d021c6072d09cfc08f78d002300b6dbf3", + "c0a24805f980cbf60892cd89af249fb06c079c694e3cd84f0cba7f21f9e6fc9b", + "fbdde53f5541ec1feec1a381409d5ec9c6baffe97ca0c7e06f24e841d2b1161a", + "1752a73a37c14d5080efc1ec13bb1dccb9bcc0281a4d95cc2ef4b853ecfa6c31", + "fd2e0c2b50b472b90d7f4bd119085224219ee533a99922ebd8cb0f2cbee9cd7e", + "3a1d28fe96af222b8bcbbd2e937ad0d1f4128c7de8c7ae4ddf7015b07a1f6bc8", + "7919fe4c1265bc194272f2d3e0a0a0dd4853b9f318baf6287484549b01404b68", + "10f6bf39d6329af3916a571ea4ae1de70cc23a9818a866be45c267284d3abfd5", + "cb0c691d7266460c4af283861919c3ee427c09831e70161c0b7485b3f420c051", + "69d7916a1618b94a9794dbfffbdafe155f921b57b40c6a55e8ca17405d1183b0", + "0c59dc8c3caf16b480847e086cc53f4bcc55c0ad0b2d6954f943eedab457c86b", + "8accbc6c5c4abd38dd2574cf0a84286709bb377aeae9d50668b2145190ff94eb", + "66213a6bb4469587379b204233f5c4cb9213c9443c6ac8a8f5748f339223bcf8", + "1512294a5bacf0e03dd013bd22c0469b02c6a56ae6ab03d089d1b59f9bf47ba9", + "a5ed327b1392f514a7ca393657194e0cc1f5f454aefdba63e867a3905c9f3ba6", + "af21c0215b6b801617c9b590a5bbca4874f5a23e7fff46e2fc380b4e501b1b42", + "48d97a310cffb3c3f576b5615ca7d30491461980e9e6ddba01e9ab8b69c6fb87", + "c23b5db245f1cc4791a6a7f57dcbd3a72750431e1a67f5f783668c8a2825a0ea", + "ea2c2b9190a3a2e7bca55d33bdd1566863284587cbcfcdad74a617a571d006da", + "f00c701d6e9eb10e8ef79276cdc154e8ba8c4422f1d08ff330ef3f55d054d337", + "43a9d6c9ab48dc22ca3c9cf8250db069e7513537e53cd632424035f96371b52a", + "9668811e88849fe80a78b79fe33a05252ec3274a8c4c924609b8c85281ccd46b", + "f2f93449bf57bbd79c852a412dc55922802044e40a10d5b70b940a585ee720ee", + "d20bda52ea5a7e785ab94177f1d90f5688d21b3968326e9909c8d26cfe160b6c", + "1eee37e54171be9e11fdae188b2edfb0bc024b5f94de55dda4b175596d449bde", + "0af11c745a8c4891e888eed30309edc37748d712e154a078d4bdd5b13cf220b1", + "15982ded9a62a5482a3acc1ad4c33159fe6c42f09aa14365a292d1f8e4434861", + "429e21c28b9f0e415e2211ff953a01b408b9399dd2b67d0cbf212653aa9e3de5", + "9b21c3118ea6bd778beb58728a3e319c2bdc18fe9bb2218024e4d7d537758988", + "730d819c74a968d95a749664b659fece7f28f85ebb9e860542969b1f83f7135c", + "07734cbfaf9de00aa8bfdda5f205dd887fd7df72535ab729d873a5ea23080c8c", + "472f6c62614c64e30fa642fe8370b2108fc6c624bfc3652ddcabdc943b93d12e", + "41161aea39995afac088fa514c8df9905a21984920ab7ed9f5ef52f603932f4a", + "ff0ac19b15cf71021ba255bb88b9b48b8d813446364defcbd3648719c0ab4a4c", + "d9836a3268981bfe94ad1f77f2b3ced20b7079f09025391357de906d03c40e83", + "7e55b991f5f34ac4389f5574bb493fe43493e65668918329489fcd9e473eadad", + "d32bc70e6246bb9345bb5efb2ffb68f6173a0b675a8cd4c7ca74b354adaa2909", + "b9d8bbf671e794da22b3d2318c9ba690448e3a41f406b903f638b5025f7564d8", + "b28a5816f46942f4270bc625ec876659d37c5e40aaa913a6223e49e7576235d2", + "43781bb2014fa51a94bb3498c99623b76eb963f48238f9b1df9b6f31322c0970", + "8dc87e8f4df3ae436fae06c170909b87542352d349384b9c7027e950b402f65a", + "339cbb45be7f44b72f31f96503860c5a25c5f2abde498a93561fb59b433f293f", + "f2e6d476a7d47acc13d2eabd4d309398684ca739f8bce57eb55b1e79f3d1e519", + "56af0198847aa027beeb25867b66c211c78fddbf74d32c1d833d87c778120fac", + "9ac6f60c776459541ec6bc8801fb2fd02e36585debfcf3212fb0b1614ae2f6cd", + "e1792f3d1d3d161adf761720889612dd3cc96eedbbe2a432a03f7539b0cf405b", + "0618cceaafb6f8e91a6c592127ea2b5b0cb1194c3210c3d643cbd25e047a2c5c", + "c7deb398d09de0de32c114870e0843d7700252917eb69fb0546830437308e970", + "e0ac7eb0b844399d04bdf65c93459a19b00c9a23bfbee05b3deca40a05b9d389", + "23a16f8f604e45d067ae5a3b94755fd327505b4a0959844aab30ff5808f79b46", + "488b6589b506e1a4501b2485091e717411867900783807af78f87dff7a46cf0c", + "56c965124e66ae2c716bf67050bdb320f38385280e7a7c6cd51bd7f7ce43d32b", + "19edef6e3e69990ec222877913340628049da39409aab79c93af2b6948a93d58", + "128eab2b73e6a6e9c535e15b22559826c3e9d678859d92653a5b0611acd12d52", + "b60504a4e6fb685fefeb7053d02d89bff2c41c86795c4061a89d0c3685da8e84", + "6038bf6c7728e0927950e6865113a34a6646cf0ec0a2d15e1f1015b87ed6b091", + "1bc443a013d98a82943adba3b89cbdbbb38d8aabbc0d4df57d218717d50f50c1", + "9765ae2f7bd51d2631470a84f4acb6d9669f3e211ba7b3d1480b80a0cb53dea5", + "cdbb6c307084183baa8b44cd6a93f014687b2e8f6aaae063fec6eb2c61d3a331", + "d386012578fa5b91e761a714209d38301129c8ca4d1d393549a4801a9b1e0094", + "05387de7db27a628101147c5e01cf481a68866c5f4be570f35a50e5a96dd8976", + "ad2775261f5a12d7fcaea9666b48dac6ffec096d643df0bdb53dab400abc9fa9", + "c95adaffc299856ba5bdceb376860a43c0a55383ccda166614af9333986f18b5", + "2db34fa42ab7e9970883aa5744fd4ae926463a3acbd35ecabd5854f369bf3f5e", + "068881adabd87a5d568f27064212ab1bb362583ab9fcae18fdb03dbc0f3185aa", + "9ef7cd94ebb5f2b74db7933a06459ad972f43f21260ce84baf26904eae9090a6", + "99afd610f6087a27df4c16d1faa3e80af5dba4194e7aad1347dc282f493951d8", + "d036a9a86f34c9eda2f4d54a8f9df4c470c0f97695c4e5dc9de60e85af56a1e2", + "7f0137290a2f6b51638f1a439091c26c66ddd7afc9a8525c86d0490952957f5e", + "a755f2fa40c875c9592313dfad65fbe58d17940dd1fd5ffc38f9520f91b8fa63", + "6690697131f9aeea3f3a74f83c6e34475cf3c1d6c63e1555174ad875a908ece5", + "26f83c084041837eafe3647ce38404a2e24b2c3f5a870fbc57ee2f75fe863156", + "aa90255d432e68be319e26ab5b1423400d2013117121636cb27ec494a887ce49", + "72a7f09acd5ab6283df40bd92802a67c66c5308f661e98f3c12b2dd3c26817ce", + "de85e7574117c4c773bec442121ac01cdcdecd533946624f5956258644eed440", + "51e4500dcd4477c425cf9276a556041162c6d9978d56f394d32a10c963f3049e", + "7a37ec2c99e082dac260c89e7fb72c23a1f958c31e50624217391cb31b715ef4", + "1611fa2786e97a786d6f0a9eed8bf4b9a30a2158c6b9722eba150b4d53398e1e", + "0ce07e0ba171681afaa50f91d3ba9e1c1801f6c9d49d7d695d2bee3ca563dab2", + "4a9cfeba7938b7c64cd17a04e6191e9ee3b07eb39720f76794518b264817eaed", + "ba2e9a35c933057f7775202da766bd3686c4268f32d9287d8cd3e0a371002d76", + "41e92e37ed2717cac094610c3da3c64079d81b0f18adfbfbcdc46860082d05ba", + "1dd6a4446b8615ea23597f2f1fb4f01e8bb999c5cd098099694dd2d9fba09e71", + "a32552daccda15299cf311e0bb4c7ef3378f303997e5b2f3892cf3265c9aafd0", + "4a17194413577d14d26f1c1fce778081c7cedee904af0ed3a25abbde34774885", + "2d5b0ed95502bae6f4bd6738def6edf4e7e351f879b8c034646fa7421d0bf675", + "0c32676576aa6862d224cbc313ee6576fdcfb71c8041e83a789380c58413354e", + "f1c53b8e6989c4f0d55a5a08239393a9b8a5517244d8f04577300048f9e21e87", + "4c6e479e828fbd26116908338cf72758557d523d190b7cbe3c33cdb00f91f281", + "fcbab6fae7d8bacee71d82f2c1ab679c48a85ea750247f93478baafe4d8682f9", + "f79fced898dfdd08f18178825490803c4203a029faf29cbf08d8f1602f032430", + "d2f0731c4ff11f4cb6234e6f22717d0a2ae395409329cac53785269a7c26ec3a", + "535b3030171da4c461763c488d7da9e89c8cfd7c1c2dadbd360bb99edeb9b0aa", + "9f5e861a8376f92be1917f3157180c119f5b2bea66ef40bdfa77ec8f6136df7b", + "9a7f91c1a05612ddc81567d6637a6279b614dc9fba3cd0322777f437f6cf11ee", + "3e1474061b17403e96e750b3ad725b570d4ca96f98f583e0cd5dd26b84877cbe", + "c046e99c6ceb99a1ee02f92bf5099e2c44a5adebd5290dee742cc5ae9da7076f", + "0736d3d6b42c0b68cea5a9c774183db0d7ac138e6303036e46da245dda405f44", + "2677862a927266eedf5f926a1844495ed7f31f671dac9f58996ae17c637ca140", + "df622da8693db1d4245400e8bdf8a28190c1d29c27d30046f9fd1178a7554c67", + "b0adea932e628a977282825083c96a26a276dc13e623380a45a590fccd543be1", + "1c54ee5a29b8ed8013b00cdcbdd654272965ce4be0e38d7afb413e65cc8525e3", + "62b605565cc85abfa0861daebbdb0b13d58732e59bc1163d9ee5b411fc6f7af3", + "7b7226d49d687fd471e0eae0f54d9529f330d58b40a255aa871ccd36c8b048c4", + "9ea175109678095977395a2337781a4b7a0bad3984d030b6bb0223772487d27e", + "949862062b71287fb7eba6eb72efed9a06402d4372e8d4c32de15961298b9455", + "4200d4862ab89d7e11f465dbb15e722eea2ead1b6eadcf55eb76cdee2dcaaeb2", + "daeb4b3ae41eab84db5c30d4032541872bd9910f36c2f3708467b640f3d56217", + "8340ac58b1ea529e8f4f0bcf297dc402d89f412252deb830d9e6ea626ec2221b", + "b1a09ab896bccb3a2b2dd487d97392e5a3de1f2a8937cb30091a5be8320e43d6", + "772812821e2409ab4b668507b3cf42fe3bef220d4970081db69cfbb992c54a0b", + "af59ff44fe6084dcb0f148dd58429a66985c8f445d58bf7d734ee5d6cf80002c", + "308def2d531cbe674b75dcc421bc3a5ef9d1ab5c8df737243643b2692a7280a6", + "e207877fa88deb5ffae9b19ebc61d0ee6453d6c7bf911aa01263030cfa64f8a2", + "eea9b387577ce6c76eae8f5d2d4fac9de61dbfe4b03382024a752c7f90a8e366", + "ebd0fa6d638bbf5ef8948054b2c2424d490b16dcf19ba35801c25cb388a4004f", + "0f41ea73a5debb480585af132f3910f3c7b98a577fd595882121631431af470a", + "4dd11303be6ed6afbdc8a9017827c6fbf2fa8ea263117a9f08f09284559bd3a2", + "77dce608339c3e4933ebda49d99d52c3eba9e585f81bb6b94de1eeb6c4f19aff", + "e6bca493413498ac5b2ecb083cd17687e02f2fbfad5da93d4c31a02c9eb72c81", + "4b7eaef4ba7578f43422618c729ce5b1d362a11c437f4d9693da8eee9bbe6b01", + "0fffe2e60e034ee0092da0c22f560bcdce5553fc95eaf8478c3aed2d9713dcf1", + "d7dc0f79e6077711fbd11be2c49a57d984dfe43e2357de902f694f561cce6691", + "51cfd0c3aa1b96da6be92c65ef7dc25ac5f10236b515403cb7f8492791f3bbc3", + "309d74e89ffd04cc8f050dfc4be50085f197f6c23ac5f84c22f79a2729ca62b1", + "a139b028838d489dce5fa08da18ba5ea10969860dd813eb64c755f9f8a0c856e", + "2987cd385ac6ee4a84b0c2c472fea3828306b7addd11cdd28aa416ad9950a9f2", + "97e9199855dfed3d4fc6a8746ed9dee32586d73066e024e8a96bb124a187b3c6", + "d5aca4fca7529a7917cd26c7710c20eefad94ab118b592db8fa22a4c258e99ff", + "08a67a8a0f262f70e4039e10091a296fcc196e9520d5ac37f836ab109205181c", + "e71586f3b63c70b6d65672a7d84781aa67ce0d70f41e4240f56066ab67d1baeb", + "f2bfba19ad8573d2c7d48527ab2cc275f91fe08d82829908013f53dacb9478e1", + "8af272ab08b9367911a97394a580c59d6e6ffe763aadc619edef7c3b827bc927", + "8980fd4836c9ff27cfd5a999fa5a535ca557e32adce854752addef70c4144aa3", + "5d976c7296257e3a0b8411bfba3f372f8d35bd7e73ed92bc26af88e322177178", + "a01565d4915e7fb6689db3f1fb7fbddfc9e8647381c115e1c0a7b1490e8f755b", + "a8fcedb1c3ed358fc5305bdc8b618bc0f354781472eccbb8960c8ca3a0f3233c", + "094ac7a83a427bfab8eff10ecbce65c26670bacd70c941f623ff0298d354edc3", + "d7b2b22315b3ee15e124dcf4560d3bea585842f4163daf0b879a8b0c55390637", + "e6e8072a24ab7bffa9ce6e9a359c33b210e8b66172e1d35a5c8ed28871c40029", + "39c1bcabe1c30d0798d8bbc275d83a503299b08fa2935552892ce03bad2c0bc9", + "2ce057410a13bb152f276afa305eb94e0e31c5dd82a2eb828d0a01710a276756", + "86b8f99ed5240df548dcd85fd87fb2dbc78e21e117524eacebe7ed88d7340a8f", + "0ace53d368852d27daea0ec59bef8f1d5088ceda03719add6a02bc70e3aac785", + "48c7e89b19636c77e186a093b1f2946ddd692f964ba6f2d2ed136b8c2f490566", + "6d9e0dcbe46da467509a5a8a2aa709a31d47795f9f3bdb3a01812ced98dddd5c", + "1526564dfcf5b6ae528444ee7a260771bbf30aa846da37d07dafb1d743c9f68f", + "2987aef1d875722ea6f1684883eefb0ad566fd0017b7ef6094a1752e2ba68550", + "22bc33eea0b2fdfd447021a90c90d10d24652add142991676418a72e8151682c", + "0d72148b5f75fe9f6e36c5f042244bb5e35a27b62777db7ca1e1072eee9a8e48", + "ef71137c514cfa65fa5fda9b517e3c267a9c7a7a7fcd4b9177dad57d0fc2add5", + "11efa0f3f84cd47c1cb0c2d4634f317202ce00e52fa18468910a68cb317326fa", + "750711ec221178fb1ee7549333d5dfa060283226953f9096782c24cf78771345", + "9d621cbeb49423a46c56581dd621246766d6c1d2e885f38cd36ca18f88c6966e", + "e459b29d49a394e84000903ccad0edde2b83096f73e93d144cf8a4fd041d37bc", + "902e39fc44a6a9a712c9d42e0b98a08f793f9eb468ab13c120c87bf6309b68f6", + "5fd3dd562fb5d0c97a66fc5d6f2d047b7c12e9518b95d6d4b96d3cc649e893c4", + "ae5a35203af76d78f8d4c506b18fb4e1c67e5798706d7a1b98912e9fbf6669ed", + "8806e940bc2274296217b392b69987b4772bac5aa5a96980a848c1ec769bb8d5", + "bad155b7d45404ede0f07382e8d74d344dbbfe462c8b3658dc6ead106d418e9a", + "fcf3012d4e101fd7affd972b1768ea8200fddce6e0fb7e403e4fd9ba3f01bac0", + "8ce68b64c7b5675e0663e81e61c4b4379cc5f8845dbdf191031b591a0e10914e", + "ec2253aabaa2731500b94ca5b60c8226e405ea23831e87882fa127cacbed1e70", + "8333c77cd6b63b5e538dde44a5f1c37822c5e1259d7c6674ebc98d17431720af", + "abecc9a68c658a5c31008c6c594d581d36fee313f8ec2aaac469f2fdf4525569", + "e4b493bfaeeb85f35f83e84a318b3a1df7df1497ba1b03b498099e747956ef52", + "8dda479f521d6dfee106e861380824d6d06c770d1472a189bcf251a1bf0a0f55", + "e38a654a52e216ff4a200af142e4a8b9c38c448876b70b84dfc9de5211ca7024", + "12b0bf0288ad6eaad0392192143f5765bddc84d7c99c3ce7e33711083251e878", + "98055e28a5af0dbe2cdc674820e18c92a38ac89cd2b6d3e2f87a589e1f054eab", + "8e05d03fef5e1e2f69f9d4fac674d82e351e34106ad29073ae9490e9ffc15b25", + "bcbb675310f133824ce336a3c43f36f8f6bc1dc9be8efbfe3e7270fa0e7eb5af", + "20af3852b4aef5ea3fabd3847cd03c064cd822b30b5c1652b91b414d08704fb8", + "cc18a3362023966193c072630da0afe74cc08d9eb767866d85c557e6a6f26387", + "950e603915e7fcedf0dc158daff822db034c0a240f7cdc5063bbb428d1408414", + "12658fe8bebcd857c22f584c8e31d6172a87f089e1d883130b7dcb4c1600518b", + "661d8c02f86e16355681f3a682aa0efea86a985ad29e2b64b343f5315a9268b2", + "617a333e2cd2482f83715845421a1e25506724d4737f7bf8dbe9f3faa5a94aa9", + "1cf208a9abaff984c259300f9345c92683eadd83d11a6a4024ee47b3b4d1d045", + "db15ccc93275a34f2728c92076086f0f4c7b2068fab2b009adf7f0d48bdb888a", + "befb08a1da6b7c3136dca3d29c7c0c853c2d91644e55e4303cfc8be64f699538", + "ca3755a1d62e3b4a74fc458daa9a7faa3316fe7f6fb40a62282e9128882d4675", + "4fe123bbaa5d874195f63e85aea06ab08355e0e57f2204308439917ffb080a92", + "cae10b8938dd5806cfe5ea1944b39296a0a73b5957a82f308d9141558b21c6eb", + "124dfeb77b60b21d782aeb2e4998ef15ca8755a3809fc3420b1f4a7125a1f4f5", + "406e044140ce58e8cdf36d57a41892fe4b15a16871f8318afb4ba0ce43365b5a", + "59f62af4c197b3ab5b58998677a39f42ae62738fb962f775529889cd7db10925", + "7eaa2867fa01e79b9e64e17f41df246f099a3f1ec05f9feef690274f55b00182", + "3c0125bf287ec0efa250f7dfdc4f784a4b11475e7bdc5ecccbf7c6dc591d0284", + "06b9701560e87fcad9f9f9eb761b9b78bed1eb979bcbc252b9ebaaa465319a2b", + "64bf409c31578efb2717df97ff8125c218e5b2740cce771186288de418110e2c", + "91adc1e2ecabaa94988334b107b23a78af49d6cd6a8a7fa45dd0a3de7e7a5bb5", + "99ae7e7cbf973d9eee35901a95212ced0cacccdea07eac0d88b5cd29e980b956", + "563e70183d98c899d0f0fca96634f77ec89ad4bacb3f52ed2f736555149779e0", + "7f01f980bfd6c3c60f3628d61afd9bc5c92f50998b3ebb0813db0487d2c23f56", + "aaa5cfc4028fa4a0b2f1303372ab35e5c4e4a6d5816bd6b819926e8949a4bd73", + "52968b2efd496a5de2dcf26a6502e8c6c74dd1249a40723a17e84bd331dae9b6", + "9137b4da3041a7e4a3b88deffe35f371c0d8f746867c9959eca986d4a24034eb", + "1d81a4be4ab49d3baa09a94794061d770bd224d3701fe00b76680297ca819bee", + "628cc2eb26c7278894ed8ec317b4c60afe8ad5fda359f151300e1501d6b27091", + "cc9378c99c54db8ea7cfa4261e74349b30e53fea0232dfc65e595419b7311c78", + "3f3a959ed7bd378b9ea2822b166760ececc99ff65c340e9a99b550b4e233bc38", + "234aeab4fff850bdd013574828def84a96ebf53a59187fae764d691c1e16fc9b", + "b065e4e7d29530028fe92e1f48fd4944cfbcd4a4bb1aeedab43866b694f0fba5", + "5df39fea0e235a747476259f34f8b66d739f9d7bf47f50c19891222fb66ed6f3", + "67fef34becf3a89191220819e985761682583e9e2fac7fcce2181843e567b13c", + "587293d74305af4ad1bcc2d88f6ac16c47ba265e3ac5ffa5f0f6235ea8cac952", + "36a9a03e9fbc0237bd52ecf7fa23626d8ac83e91d54cc0471cc9277929582879", + "78a673152ae87540f2b3a38ba47ec5bd5cf047da62cd6be3682a723bc87ee1a9", + "8b48afe535024d84514d81fdea58934ca0f23e031565d7f74d05ec7a7d946801", + "d81ee7e407ad1311d68f0b53994e6fb68131c596735329f76cb4d18c4ddbfb77", + "16be4607f8bee1e191857372b0263ed401bfa173f2752736fbe50abb7e58da98", + "d05083dd5e6d5e0a2c320a1752c724d3a13428b9dbd2833fc67c86628f6eb1c3", + "0b2f6370271a17242d09d0292a1e0344d2806cc89e8e6e125c72ed47317171c9", + "aa1332229c78544ada666a1e7cc54fb5fa31c586370de5388bfadd86176b2250", + "d605e3d0ca62ce111f73d2ea48c5fbb56c5d47c2bc28eb68c398515703119072", + "9e0549f039e6c530dc1432adcaa40a3ee2d69329fe1a2245c6c0461c16931eaa", + "cb2f3789ae09d724f9eddb3f104e755ed2ddc3f5bac87aeb91f76fa53784e881", + "6e3b3835f989decf5441e8c524aa95b6b312d5732d614fe24d80f9dd39bad1c5", + "318440cfef57053d806d6f9c03c1331947018cf2f479b71d9f5ff4d1bf940ae9", + "51050b2569885e0a61fc2487c8428e6fdb8861727de7e8d97f7901e6f583acc7", + "02e37462d082a033c382c04ec6d90c629bd96cd0379eac6399a718d9988bf3b9", + "3515eaa553cf9f35d85fdeb367eb3eb930a79dcdefcdaf2e6eb00c550deea9f5", + "1321c47c060f371589bf9a1dc6557b9b49434a4ad6ad15f6749a2bf71e26ce14", + "627cf526b8c336e3a3d8c3e82e5503fea6338f14a438e8de6bb3dc5674a10da6", + "7cd3b2289dc1190b6ea21b9d011ec5574fa251d222af63a52bebefecbd89d7c1", + "9bb3ad4efd34c6839bf4b641d1f745d8ce60c829f22b773de0fedd566c3ca60d", + "679c477e999ae78bde65ce99acb6c2bacbcea8214ba4b7fd477ae91b1d21792d", + "bd3ec90f50cdbfa74372cfcee0f017fe7342935eb93d9367ca277dff7b02eaff", + "6a73b3ac5c74db3e940df13ea5e049432e2488b3997767dfd700e9aeb34f9cfa", + "a642e0ef2b4c17cbacb868ec0ca7d43ae9cefee5e0dc29e59e561b44aff05615", + "1a77398f0d8673a9fccd3342338f6e5fc1e96800beebe04f4aba48afdd41f6a3", + "d2bf9e00bf6050ad68e7a3ea1261b3c9f9b6394d53da2f9f1712199ff5fa96ac", + "d17c0276250825f6c8ab121c999c61cc892874488a4bc95a104dc0382e511aaa", + "7be2cb478a857b81957d7169a01c6692a254e7b029b42995b6c262e6d30f0038", + "2af54e9f62193195b0b927462e63a661c5abe69fff22037e047257dd41089646", + "120849c155437bbec39e71267d3320eae464e949c7ed1e812c91f3c5ead6ba1d", + "4d77309ed2522ec89b7e8e0f0189cfe9124babae465007ab5c905a9541d63a4b", + "4eebf5a0cca448832d9a5224a39250ecac4a18c650baafe2d8804241c274dfc1", + "5d29b8c14ac83e531b87534ef17531cfec84f49c764e6410c8d5682fd80204a7", + "625d9a10f136b103e85f0e7863df74db8049f77a75e52361748873fbdb2a3f73", + "eb21a9d33e58daa0ead2dad3a71d4e5eaa7b0321c6cc2d4e86464c70cf366634", + "6b9066c0d9049b0634edf92b34140d02a49e7a6d76483d14c32125f3ea41d8f4", + "b68f5ebb20554bddbcbd92371c4e536210a0674e61577d552b6b405939acddfc", + "2f8fc98373422a6dce98f183f25488520794b45b947c18ab0e1b924dee353b61", + "64643340086927f78905c52784692557869bcf723b2ad19b69fd0349601aa7e5", + "39c72e17874ba3a3850231ad77319c564e5fc1f23414c80d0531f8e618895ac6", + "9e2afd30de5bfa30e237d9f2f773766a82e20d1a7efa10d34e37048b172d2e91", + "c0888a46377fe1194a84313d3e9f64e90cb94648cbadc93ca1c077960b2946bf", + "c7a54836aa914f27df8424e40d4c42183c7dce32798f7964fade258768beaa4d", + "485865c169e3e60c2b0cd97fe7caf04bb5592a9a08c5abf53de92f5fa745845a", + "77038185b0024d9e2e0881b8d57e4dea0b59fcbd2bc2a776c8d7b29b5356a240", + "19be080d3f5d828b2826c221f35a07200540c47c5bd5c374175f8d1b55dcc8e9", + "32d5a77433130085014cfa02eb0b0ff36be96763b8f100502ee09a93fa4a62bd", + "9572c95caca053688f8cde1436a7d5652f1bb5bb4da92dd200df11d2a27da378", + "b3db618f9e7680be0c485377b1f9e874798b5b9cb2800a098053b025c47e70f6", + "67a77a0cdf101d5fca5d17bf2b49f3c00632bb4a53c4ef06617762f36c8c8e07", + "1fff3805be7779014868c91462838db4e8f88e7759179f144008534a1724fcda", + "efd453c249b898711874f643c4b66efd58e91ad5b3aee1aa6ad7a477b7248a94", + "bf94e5f1c7b5c8483285a8dea4345e7519581d486fcb27a8da43a2b48be22346", + "7ccb34279395a288bc3ce100245c6de77fd9fc35956f9ece35e6475bbd6f6ef8", + "810ce536aa750b60cfc6acff3ff65b36515f60c1abe5816aa5e38549b0cf21ad", + "be1c2775730ae524f44e05fc989ea33b24d262db5cb630cd13d56a2324f8fe37", + "7b8bf85e45285fa4b800e544da002c0c43c2b87644dfb62c42090631c5abd1d5", + "96e286ddeb65c5e54b4a8325cd695b53f4aff1fda635ce72c7b14ce1e926c7d4", + "12e0310edca52b08cb8a18dd2e382da62908b476f14b1a8e0aa993a945e84399", + "e8ceee44ab4241cb19d5da582abc8b9c7ec4d71bbfa456bbda2d3dfb120c1e82", + "e3951d2637df7a24c3fb48360da87247d998c9f092d7ebe349bf5b493cf89034", + "2657c928cb139ac30cb495a790e8a125d452a541267e71ca1635cb69d830c349", + "4d0fab3685c9e14d8382253670d0453430010fdb95da9a0cf5427760ff8f4bf5", + "85a65d69081dc88743067a1ae0f5abef0adf79ba23500954217f990bdd17246d", + "b3d6e83386b11a0d736d43f1eb3235388abdfaf2c90e94ba02d4c50a3c830a66", + "f1f207e702a02318635f35a70141ff96d50b1058f57c01f84fd5973112eec5bd", + "10e412a4f7c37f47b63d39cbde4721f65670bc2085ec8db8a6ebd2db0f19531c", + "a7679d3b0d86f7d19b865f9c25aa91c1bbcfcc89cac3415138393f68958699f2", + "9be5b69545813b368cb6fd8e8e2f0843a3299c268f3c78691d62f14cc08e1fd5", + "c7ef2594ead75dd56e8effb151e72c72efd2e3151c22f113a9952c2d2a094ef8", + "43406fa30813bf114ad791f19f91783ee623dbf5882e9361dbb9dd42dcc124a9", + "f45c2618d639e25d60a6ff4e3d3c54aca3afda8887feeda48b3931d5fb3a890d", + "021d36f9b63ae9509a139f5919a2c7c6daaa8b775b67d175d9fdc2caf1545247", + "221662e3384acfda6b419aca64194a7e2b515c470eb515641a43ad280f5d8c64", + "93a2280c52123349f7143d8f90b8862985eab8856ce01c1e65847f74e82f3585", + "97595d2f3e15492623c1180c3448d91e27e8caa91a2286ffdde6e0e01d73487c", + "554e51f4e422d00765369712a73dbe5e18c4f59aa55546b9aff3f1b94c8f75ee", + "373c9b4a6fa3fc747881123d342ad6fd4bfe280c1550517f329e7b5fd9c90824", + "5466a29fb42efa17c090eedb46bb213988ef7656634eff61d399c4540bf185d4", + "68882db32387229d3c096e4f075c9075d1128759c3fdf5e5d8bcdfe24072d559", + "f703f98c5b75380e73f861b3ceb685094aee6341309d5161b761fcca78acda0e", + "59f93b2d63f4643f566199a92d955bd78caaf7ec7e3078fd5264f5b478ac09b2", + "639731cfd7ab582be092da8ec8da53718c206b8f0fa5c75953fcff3cb116a172", + "2bfe2a097a074fdff140fef54c104953996a032dc162edf32dc357e09abb3fcc", + "466dd64bfa5468a071705d8b3f86206a481673ced10e0a4f172d7486cd29e8ce", + "a40e511b719b6275dedb1b46f2c5f8b0cd725312ab239132c5b9b29547f6f764", + "6583f450fd9001e5355551acb58a3c95ac663f8f58a82cc3f9f31135fe492442", + "2a818d069c34b05be52769437dba3051f931aae1b1453e950c591b3920c3b3ee", + "0b26d32e1e31400f4c3a5e2750fee52239b2dc3728f927ec455ba49fcc16bbbd", + "7d90705556fd11248be8e53c04bafeb395b123c3b42d3e0e07f02ae5bba74681", + "79ad2329c40c96cd2e4d694d51d2610b8a28971d6b253f5a54c22301afdb293d", + "147eed7b0ff213c3ba36f679ba864cb2b050de968fdedcdcb48e6916bc264574", + "d027dfcaaf6e04f676c619244515b823e7adff3dd01da6730342421168a61c45", + "e2199dbc29264ddf0edc446706d684cc19ab7c81fd19f2c25a6aedfaf38bff61", + "74219656debd84f572508135fb03c4ddb80a992c629a1483a4b32535156ee1b7", + "cc6b74e9616a1facb82a483e19039348ccbc6a387a3c6991f50133ca844bb499", + "3084b541105fe892ddfad7efe4d428a8cfb5890c6970d4c4d05bc1b1e7ea38ef", + "a0b5c69bf883147418969ffca6cd45e9f710223b8da9bd93d77ce8aec76d4bea", + "e08c7e844d6fa6cdcabb04975fb6c66ceda4cadef3b1e4fad7367207d674e973", + "6033e2342b470e18a442cc73eebfc5e77303cd4ecb3507930f777cd2fb740a15", + "3c20d132ea8e829fd8fb7b18cf5f9a5a71dd88bf35bc81f7cf37b4ab4cc7b961", + "0ee98db4b01bea147bbcb1bd492a66c85abe9e20985738bb5a035d587badff8d", + "bd78400ee797aba19c82c3bf060a13d720be4cab6358a39075e1ad721cfd0b99", + "91c0f7efc5e43eefa3e587d1e2685bbbf68a7b064c06da9b21f3e38be2f8915e", + "f0c2ff1abe61e4c0c3092afba482e7eef6f0744c267b5334e060485d01974b18", + "dd4d830bd248066832624a2a5ceca133b9f5b9fa66c1d3a41eb4cc29169f955d", + "04175c02b43883fa4b3c4b990334e2c3e23e85ddc36edcf04bee9f86e8a116af", + "2eafcbc1db5005dbf74c90a56554ba71201e21d21ef4a77094f4b1c6c6d10497", + "f6c826a659eef7eca7685ce1e574066a007906163041aaba2ac90a12f0315660", + "2dc4d5de794e832b499f493c99fa948e10079f74552f23fc5d3700722e8d0f8d", + "c02e4905c3876a3fa382c1a7f90cf91496d66da1b2495cb3f5a345993360280a", + "5a375be2718ac37a38ee1c84b84476695a384e2a2ff43068c8d7989b4fd3cea5", + "b70b2e6d667be606bd1c511f7e2cf631f533948cbb5af26db8650d4101253f63", + "35a49d20ab277ac8c092985c9faad58320d1af7427124c4828888f4bd7491b36", + "2a67df5cdd3e8c2a1241dfd24554af46feee6b138b4809663f86e9012ec47b27", + "b50c7e29386970d39258f395e05f3caeb8648d9a40a0c14a603ba1e729c1782e", + "0db1d79fc1686e0388b8439fe7331862314b77ced506dfdaccc2a21faf74f44f", + "6b3393eacda7dc18a2ce451f2541681f5dd4e9af21a2624956542b60268a9405", + "8e17ed5e690277e1156294c6904f6899de1497d15d8862a98cfe349c5296ec13", + "e44b9eb141e8ff07acd5bc5a2fdd6d334192e27b27b5f4c26a225ad28cd9ab9a", + "02b6fe45acb7af83932640c81519a7cf4d33af40e8c66510855286f1b869df7f", + "c8aff4d8670456d4e64051e65f018226f7abcb40dc77b7a36a934f4afeb42888", + "aa4a4dabb9038bb1bc8dafa93397b77b81e263fcfff59eb86c07920e6c8c218f", + "3b8203b9ed708044720313b67f9b98cf9e96544e95ada0200bc4ea0e54a3c633", + "524aced0165da7aa4aed8d02720276c3895a37fcfd8db9cffbc9d353ccc960c4", + "553fcf8e9e53d37da153a4f8bfd16a2c5379da0472076719b05a26d10100801b", + "fbb7b3e31f1d0d1606dc34bf1c624db7c62133e173203dad5cf4c3792351fbbb", + "e62eddd4a815b35bf6916c615a52f185395baceaa52cee8a836de3a4db0c5381", + "9ea285ecbb3cc9189511d2148e3d732bd98582324d2a597ccb942e8b0158548c", + "6c9ecf00a9f605bd50375d0793e8ebad382060daebbbad0ec1427d5501b95186", + "00a5392f9bf5114694596652d3f47debb758a1ef4a408490688a8b3b6f4d076d", + "c9d5bf16c017b12a95d62a92b270c9c100fee78fc85c91f830c56be00235ce7f", + "68ea43f029fa915aafc14bfa5eb8ab1ab1e6e20c467c0985dfeeb72af1859659", + "172c4fada590aedd1eb45a16ca2ac669cc0371f12aceecf1467e0dd6e2af387c", + "0e8c60669d5b505ff9f6fb941f3247bee6458fa1d6f1829c1444135c55b5b58a", + "83e174c96ba972dd05b14f9e7d8dc0923ad96cdf6b2926c81baece8c7c3ccbc0", + "55699232a0c96bd45cf5bbb81ae2e033d22b534b2fcec3b70ab661d22bc25b0f", + "9d4ba8d5cb7404ae253587e6a583c595214407007c049988a08b51e8c05a67ef", + "1d63978b718b86d8d84bd1f6e7cd053fec76dbc922c597abeeee64066d02b57a", + "6d03e10f0787dd16296266414cb6bacb961387fbec686348f586a4d053718f02", + "e4ae9203d1b61a37a98b21769f110e46de7ab6772d15b4af93a9f39cb2d096a8", + "a5de66a7fa9666e03e6dd9e114d3cbed11e14454758fb20f42586685509f315e", + "f0accba58611444a9eb7c3d41218740ffed85556762d5245f4d1bbd6d958d488", + "95de4a732eaee2996786c28c3ae80a7bbfd34ad276694cdd64194e7d45d6b1b8", + "59b03bd978a4a5ed212fd094e90048aca406d16c196410eb8be8122f2856d1d0", + "2b6c3018cfd7cc55746864434c9b2c610a4971e60d9128a338451d46c78bfc86", + "2428da7b24364be8aa93c6a255543d7da27f4cbe57dde5f14a8512fa5ffb981d", + "bce26d655a0efb6f287be49da07393f30feee6bb6c2306bbad88e04c8984e7d0", + "0fe2ad6dbadcd9c423b5eebde12b78365ff1c4fbc85e933206523afc2a2f8a48", + "67482decfd58bdc49de9ec5a8147945130a928b98202457647c66865ca336c71", + "7d22130b398f4085766f4909a68b4f7fa2c4b26db1e107f3a2ef29417ee5cfae", + "98778612b5124e3d7d455f725f4f2462ef780f0323e0c36cb9bc81811c00a045", + "51e7511c0f2ef6bfc7e17c7906fd604e42e862f3f4475d50d86bfc1401099d0d", + "f120fe5e0ef2b223977b4519069cac7e4e77c587c5b011663d06aa08f80fd477", + "9d00ace943765ba7f2a3a414f534d8e751af83fed0f8e872c36b311a15ae21bf", + "93cc266fd13aaa341f03d20115f066cb126497bc0d6d5de719c9f80604a2e57c", + "ddddd5d3636ab27be531f09a9a93e5d8b39fb2c03a79d4b7908f2b40b52bd1f2", + "626c9f4c4e3bad8d65523b0f4b820f4e59a720007fc35aa925bc99d9deae3e00", + "af9d9060c20e16bb1f94b3da86b8f4c074c472f2d402ebcadb879d9d5853dc78", + "309db5e72eed85d70fb56caffd8bfc1dffb79fb9b599916468a68982c975dca9", + "0738d677af3228b95cf444baba3469dd67aaeba54a93c91ae62845d2ab12bb3b", + "720666a5e21383c6546a87b8cc93be7044c87c015ee2995f87b55bb291e9924b", + "21d91ecc9946962e60ae1119f45687a670eb725a392425e3dca05d108808a314", + "253c1ba525d54f4075c8aa01ee5383d0ea6a41b62d243fb06d4f90987e557578", + "73429a7a5981f25624a1ea521c95c1aa4e744bca69c6b63f495e3c72571d403f", + "e92100e5825a11d06c55a544f83e71a1cb86516805549d0d16c085a690645682", + "3969b8d71699943cd01e410fb2b072638ffdc59d10eb3316ee12a98783c09181", + "3dd6b9d720af724e3bba14e7fc41213a0c6b7fd5c4a6d6081cdde8b05941023e", + "a8c6b92481b9b52f50fc996f3fc023f720b832f79f306aebe9e1db5d5d680f33", + "84bde27cda62db77fac212cc9a399467cf466e0d5af32984b04f389c28e6b0b6", + "d416c534ceaa07ce856d3fe9fb6d8cae21f28aa45eadd4fee27b791e69c8f045", + "0b370fe3369b09d22ddd43f870a428c4b65974430a18261c59781ae77d941480", + "906ad75351144066d6d0d914c5145664825d2004d778aeb41aec56644f81ba25", + "3c57221b786a6de68d8d8db9db8ed3928a20eff9e688ad1121d73085a0fe9d75", + "e1c262992728e3ede684e77f4a06efde3e2eebf8f12a0406dcede1aafe299648", + "c60819a050b2d4e6ac4d72ea6683fde04dbaf6b66bd74ff988fbc7d2b29b9bf2", + "e8468896b3f9734cd0f7c721ee0896ad1548902340242b7fdd16e39b361c4ac5", + "6a8f24b12c66adc9e771cc6b52392688bb8535c1ba026ca7f79d26ce2c7ed9a2", + "f5d607b7d774d7c5d81d6c8d58b4a7ed576403a4dfce8f2e63e1b7ce9c7f8a02", + "011d439d089fcb3c7d44c2d5a6420cf60cf1b8716db31286f7c9ce390a0f2fa6", + "d6fe9c609aefadabb0efff44e5375ea4eacc5237c21b58a2e199cc2899dbfbb0", + "3a0b54bd93d3305354b07b6bb7807bace50476c4e120a0b327de1cdb93617079", + "41e55404a5998f71571a200c032127a6241b2e7b232e82147fb6769d8256bd29", + "5756ac43c1df14927d3af8ef161c403ee1f92b1ed296d0adc72e2cb45d98d7de", + "f82cf2b7895ba7802d0478d2b20b069d3c8500637fe98d4194da02c884cf9d17", + "3a10b6cc7cc3bb244fbd0d49910e588abe7dbaf759d4cf44783a7139af064824", + "5a9b9b31eaedbb651f42d207a14b502b4b73885330c37f8e615ad89e3e61213b", + "e61688578b28b999d906798525736d64a5aac1b5544b7507badb20b34b556d8c", + "45b27a6ef06340c3ed81433083ac81ce99abcc951f5e334919f3ff56cd1c75d6", + "fceb7a9bd727f1e8438f617d02674039902f195dcbbfb01b43d5f72edea02831", + "d19c5e3b8aeb8f8b4c08ee8b61f34277bfe579398a76e28e58b972cd69089b61", + "a32c7ab77af334e2d5c4eb3caf95d3aaad689f28a2dc3a7557f2528be402407e", + "b6c17c21cecca5ac92a1fdc1032369e4c6d9cf2fd4f1d5b4406e1d7e7606b2ac", + "be1b81276adee2c0f4df4cdc6d1b82df87dceaf516b7e4d994e023a937479f9f", + "439251ab04c231c48b891702a2061921fa88cfef44741ee173853b6bf8490430", + "26b7068c7272d9feaab03fef3083e50d49885e375f77792d59ff0cd502c03df1", + "85dce2eba1a9ae5a41d06854f4db88dd7fe022727be35ad6b18e6087c8b91f1b", + "e4f7178d36429096577518bf47e2d296c021ae7349aca78e2c0a4c606acca5a8", + "e8d88f547187b278275c799b25a94d814f3174bac46077df9820a338b4d9c267", + "ac8f48385b06e1c43da7fb74e522d347603b7b973ac3c3d06412ad72f5762fd6", + "22f39a936e8e401a404457cbbef921c3568ce016a5c75de9256011b988c252c6", + "77fa7038e7ded01577a2ae592cdc9663311b5e1e42f5f87ad8a66ae0e9a54c28", + "87f6e5ae2adc65a736c9b1746ec1f18340bbee45481d7c1f5509ad71d5f3c9c2", + "180e9ee8a321a1e91c490cd90afd74d556ae2a9bae223ec6c094587f359870b0", + "f6c87366cdd0be058ebf9295d8b6b42729be58a0f7f057fe3243f9b2949ada26", + "d45c27211e8a4b77a9e76c12db25e6e9ba9d03647f3691d158edbd8b7c38ab69", + "8ebac15b55c4a2c5d3f4866858fe3ab9324bd785036dc0ce8ababa396419eb35", + "6a80d86e69694ebeec5f6f5a5d37d12c2f813147c1116d5d26538d25f8882729", + "2cf7e4c33c2a63ef54c444020bac3648738bee48779db4a3a0f9e72ca5b4e380", + "9d101afcd85b7331d4008230f29b761b9f798e8814d123140e8b648b230261f9", + "c6ee0ade266785f9dadcf6892bf4b98d7bbc292aaf2bfe5b7dd484785760375b", + "94d239b78781096e3e8e7e6a7d9ad70d0804adeb6a6675fc5da4f05d1152c19d", + "55ca49e0e313f14ae0f214ac0a51843158ee07aa5f381cb9d596b8612dbd3f36", + "e3e3672131fe9a71f54a41b0e8143c63bc0aeb823288527bda069a1616a41529", + "ad1b386b668b79c7cada251a6c2bd19c53e51045e4a4562b4b9212852d334556", + "bd08a3ba2d8603332b31b2394a556edf6ef949ec8d49e5d54a13318213a0da65", + "b34731e855ce7fc834aa0846c72ce7f455f40564b11181b340ec07a003787e88", + "ae292c8d03de63af642539e6559960770d80dc4e9590c46f54ab8a74b68f145c", + "10a28c04e90016bc744986e50d2a240ff553cea32c8ccbfc218445d6f9e32271", + "182df763c8b4dc2c1698567546dac90ed5de00c033322c9f731a64f08cf7ae61", + "eaad2e6f6dbc000fe3b8f60fc7ce7011c22c4d062ae2c33a3621098190027381", + "1e7d630f59f8c968c54a803f8c70e15d85a1214be4e0f8e82cab9b2056df4ef6", + "af485e0990350c9814bf8a8b3db91f44d4d459f513f9e0635ff54a444a45055e", + "c5339ed11420ef44af6d7eec085a7495ac1f59264119184d3183a937d7012295", + "ee4c98e7b5e1b5fc19c8e5e75b9be39690ebf15bc9b1e5fbb3ecb75a2ec89d3a", + "c88b24689438c8f097cec81f2178fd162d4c53ba51f1b835c9b7d1f33f3bfed4", + "b3f12ee06f773536bc4882441d7b40153fd02ff4abd97a5686bc8ec294de5303", + "74b69c91d7b24cbc734d5bbfccf3442b10aa94f835dd49e84aeb9fb93d48f984", + "7249af9eedd2ff198a93c9fd96d3847d05fd24e4a2dee3213f8b0157028a7a1e", + "4c663e23616d31135cf9b4c33a89556d9973b2df0fe8b5a851163ce3e770df33", + "4afede394dff0a2b6f0b1b3dedf82614ca5133814c9466e7856a53cf33a83359", + "b7f3d596f87080e6fe4b0c5e17364dc87ba269ca561689d20085c9890270145a", + "a91c666326b9198e9954c4461da9e173c21042edbc8e63e6527f69aa06144ad5", + "c11375b5ba44d9be64cf8ccc8eaba4689ce8d0a1d75f4b932d14b4e26b962ba8", + "c737cddd051af963d4109063a7c98bfbbe500abdf2761545fb2e3f592f1c3183", + "9b09f03742db7491a7a96f20d2735d669383b59570c919c5f8f4d804ccf552af", + "112ba278ae78125b0ee2a9ae9d4c857d7b09d3636d8afd9e175a3557917409f2", + "3ec2366bdb151bc04838ad4459790d217d7e70104dbb6a13de48c8e97873c874", + "e592d021807bc768bc2eb1b6b7832d7326565c39b82658ce9c340ee50a443626", + "446de54c9e312644e2fd20f49b8d9926edeb2c995e627414da25b664f14e4fb2", + "c0e6c970fb25f5ec11f0c76316e7eff5bb180acd21bc777616ad3212edf5cb3c", + "32c3a1e954262361dfea8dcd6d4e74c436d53e6518a2bfd080302187e454ef3d", + "af6c5f72dbd65e75a91a40ba9c6abad575f12b7fd8b66c41d88948fa8e026f3d", + "2f858194e1da082ac8ba94373768de9fa1842a1adba13b2bf6c67474d0a233c8", + "74b19f6456e34582f963dba6d7faebb6289050227f55d5bead6aed9f6394eb41", + "e249aa1cc573b722bfb4e6613dfc70e2a5d6cb7df94ba1002e7f549f57aedcef", + "0ad80c6bbea362b8bdb966cbc50135bfa321bb8cbb0179f76f445dde3d156c8b", + "898f699a5c52bdada6a62d24ac05ec8cb043f5679b554ae5a9b4d666a440fb76", + "305cf536a22fcbdee3353cdf59ddbd42e24ea1a8b91967d5038ca3f9094868c5", + "2847f3395f902b011e2eb0ce5e995cd97af861c925e04021a5bd893447025060", + "91f09953dda135db571d5e159e07a495b0ca9d2eb6ba584b6bff019ae5693624", + "648126d6266a94fd567040da2a9becb5e6be4055f9f4176d252ea8b29915b8a1", + "46451db7b8576c094fa561a81e6d1300220b42b886e3e3b5faeb95c78090e6e0", + "97817465e9219742eaf8121d0d053224793083991313a7b376093f5cdf7329b2", + "4e5c52de7f2d110910f12126b36f9ee712f4a70ec48a86ef10b21767b876396f", + "6510078a1522e2f137600f9183802c1581f882bd9fa0f8f1391484f84cf0fb43", + "2b687ea5a740865b89abc5657546a55b7d5f7fe4e4796354b4b4266f0c0ec10c", + "879d6960613b3138e2a92c821026f5858a99338db1f769923ccb7f32ec4afcd6", + "6c37cd022e745b6666c0388b81296be90dce442972de52400fa3adb8e734d2ee", + "e58f22310f59fdddb90479cc36c763c1fcf60d6e09501d51c495dcb95c033d05", + "70ac02944106daddc97b53db0a0f9bdc1cdf7e789b698b4234a88d8acb945233", + "e797d50c461ed06e255f8106694f1e3c3dff45ca342b631c9d93fd6868ee7862", + "d645d1ce9d365a5ea79ccaa9b24ec597a029e07e667bdd5461a7cec83344f83a", + "2fdc7e98eaa0910fd04914e2ac4c1216e2a4d37621e23c69daa7539b6d403ad6", + "3ea00f497980193dac3261c3e12d7593b1668465227775c53b01e0029a3dbaee", + "eff07e9c0aa7f889f74bd0daabfa20017aeac105fb09d11647059f37eb1027eb", + "69d3cce33ba2f60e895201dd8d0a92db55e4db99b04de54976d3f11ca280dfe4", + "64aef37268e77bcbbda83cb568ba365aa650c4363e22f08ebd9ea8dd4c5ec3ba", + "837f82d3bed0341335bd5ab9d633bb5b1117a0b71664315643f710321451b08d", + "52b1677f38980e1ba9eee33faf8a60f50d76a9020fc18aadad44ef12d102c630", + "c5727a2a22170ed9a40b4e003e7039bb6f51ef719faa103159869f715cd79d64", + "2bd8376008ef3339ad49383a12754f63710ab01657cf717aa945f8ebeb351d26", + "617d5e3734a176c6aaa970e78f412532263398023f922604d69d73a34d7c9600", + "b508b6f323862d1393522ce67c9f267f8f7a08c4f17ec2d7e9230df6a7dcc83d", + "003b1c8857c2366b415d5b1eb23ab8c0705065bd87fe08e7c25114098bc2d020", + "9c18e71750ab5a52520fa5c6f4b48047d34af42b9feedfcfa96a9b9cf6c2231d", + "c3d69220994caf490d6d8f4c7f7689da6e533bdbf775309af7b13b25359c15db", + "64945a074fe5d593413f740fbb0fb036efc1585073a5c769e6b7bba5083bff67", + "da64240fb5aefe994b80dcbf86c6775e1d8b9827c90ea914dc89ed96994c1b06", + "f3dcceebebff82171d500aa5bfa42d923af47fd9d935ff3b24e4246c2c240bb0", + "5e9c33d78cdd3d5f71a74981aee540c199f9928c132f047ff975f9c4528929ed", + "e83e08ca018747334d5bef54eff1527d03fc478a9a2ce3bc1d08eb74b1e05c5a", + "2b4404ceb208a4cbcca4eca597cc25e50c156d1d4320b937795c201bf62e8e27", + "6cf6e54742a3e3a2469296b51863123d6939a3e4f18973bed3350f361a0a7fa1", + "173648ce3c3faebf66b1b400e2ba1d09a6f7a0e90c4f6eb259cf141cf2702868", + "1602f84cd08034833d4d6b6f0a63017840b0517fcd716dc5bb82071f81cacf22", + "5c9a5a89db7ce103784cd0a185ef589967930fa6e92aa90fb8c02031694a9404", + "a99a83be39be7d0d247f3805d7c78f47bf16a3babf9d63cb68e28c0f630e15c2", + "9a92a01899a649676ac792b2a2be8db3ab24f1f464a1af6b3c03b7f67e572510", + "e0448af4bfdabe13a044362ab133a5560a77e8e6605df7d9b36f22a9ccdd806f", + "8f675662f98a9961e9a67114a98f442359b8797f729c94c5a041ebdb937e0b57", + "6dd8437315fb9be06fcbb22ebc82103648da5b142622857175dd75a0f78c7a8f", + "6c4a96d440dbfc0f26f1bdf1e1c0ac273b573c36f14e55342fd98435e806b676", + "700725449099dc98933ea1c8f1b9fdaf21ccb7f7439451cbdcf763e7b633e52d", + "22c986a29a24c02703a8be6fc3a17f00975fe6f323366da8284a4c1070b18160", + "037036f85de49e6136d1f54c2b1f754d61c262bb88c806b72a156e272d60c535", + "8813017f3878a97b15c272982b95b3534a4475dbc3a6f40aaee3eb9dd593eb2a", + "403a7a97d3c47c57e89982086de458cc1c62d01449945e9adeb160bdb487e7c8", + "e1ae2a3ca0a102d9d7b80462c5c75ce1fd49a341a3ae60a497a98c9e84dc4d2c", + "7d3cf5fdda0516c200a2a5d622c256d481f9764d6a72799995e01b31c97f4f26", + "a080f0e1bf33dc8190c844c3b13c56c058ea08af44e1c7363d55e8fd8a7a3afc", + "a611f43917fd4c23e5bb65ba8dcbde6db510142346cad2b9ab596fa75635d9e3", + "ba068f56418356e2c0ee98119cc3030fa7a9b8c27d5530c93f134e899afedc02", + "81a7d27fa4e8d911410a733248f102e5a9eff4d8f5617114e97c1267cbf3d348", + "ea711ec0f66cec18c17b3bfa39967b00480a0df0575412c0e8a7aa8869ab93ac", + "dc2efe412c0c7cf5d7e838641bdbe2d59877ca74357536da58aaa0fd1b711ac0", + "93e321c3371d69fade1451f2f537579c54934aba5271a018540f70d4ac91a78e", + "8f1368dfc99618f3e1b2302ebee88319371106bef78471b0eb876f2f06a3a818", + "5fffc415587f815bf9645acdd481294fc4d83ac5b9a37275e8725921eaeed7f7", + "3f29e067de0e036efd2684b9c54e0e6b2787fb9d83504c1201c4d0a017a0785f", + "07f4a000178a78a4a8faa370d5aea0aa5628ff669522fbf6ee0d016ce016f8dc", + "773ac8e936b1618243407a82d1ec9a6feaa5ebd9cbd82619d19b5bd7f0297536", + "af56203cef3a9126c3442d964fe09bfeba50d9243fd9bb87ec38a961c59b5960", + "fbbdfc94f38b514470de4290bba2ebdff7aac5523d97a0abbb20859ebc82065a", + "471ab6bea6f7cae837a9fc601f70c7c8e53e84a544e43008d945875d61df04ad", + "e20fa6fa46cd4d80d54d393c86e49b282ea1526c5f339e579bad5b39304498b8", + "cac629d37126684710d0bb74fb52d529f2d16c73734f2005de6de9a504ad84ff", + "a953032ffba659a809d5f733daa9db681082aa65dd08c0b05664dbefd87707ba", + "5bab2244233e9f9f2d7191dfbe1ac968652ea1bc0fac48a5557b01c40d260f4a", + "9d09dc8281294423fd516835ab30f15416cfd011e74dff4bf5e05f87416f0cf6", + "893776d2cccda1f86b0e405d13a75a11a96528a45e0872e4ffc0b87513594100", + "758a1e6461aeb3ffb55c231caacce8f663d83eef4d12f8cf94d69b09252b6e06", + "0dee497dd3a7bbc26c1b2b2f807e55bc62765b3557cd50960e8b913a2e3acdba", + "35a08cc32bdce97e91ba274bde6d6f19c74ae3fd3566cf1c40002a0775872505", + "689104a13715e4e881a4640fd5e4bcd46687c67f53c91db4b8cae8d21cf90722", + "e48d19e8a8a211358e7e7ffe964e981d3a1d11ae76a2b44a44faa856566abc5b", + "28d7c2498774be7540d3b85cd67e873db198267ebd59cefb1164969d56544940", + "8440cbb16acf418689ac4fe6d2b65ddd949e96458542539d30bfe2125e3a88ca", + "6f837c7e4a9f4c4c1b17fb202a5bc7479add8cc0d26564766328f77455627961", + "fb786f9dc125141f04857fd1393abe9172ad9c3aade782bd4d8dc23018c662bb", + "be101e3278740014287a572b2b27fe3e20712bf589db356e800fad1e633d34b6", + "02770ff50283c20a9a9de291de391fe7ebb4744da607a7780b10a20c49a753c2", + "84801d626b5998746e0865aec4fcd1378bb376edcc763a80ee96373b01cbf72c", + "1da1dd3aa77a4e2d8aa0857ccd857d4952e58ab49c26a1f0cb60275030f6607e", + "04fefbda5ec9f182c2a22ad6bbbc42ffa322338e3562976b8d32b3406c2e38c9", + "e8ccdcb248078a482359f4f7973d3b2069794b9777c4e2ac21558fbffee59c8c", + "51507a05fbdfcc522a1f29099cc06458591bcd5809d2b210901e275dc1daed7d", + "05e6b311d3a3241022b0152924627706edf857161004dc785983c5c8e4059b19", + "986ccd7ef43c8085b738a0e2eb84b98aa920fd19f372c34c37569a5c7354d6df", + "37c4370297131fcef6101bb43e37ada63ee4c63eadd53c683cdffda2c22b6a5f", + "c8a736669a5a1c69c6088735306b2aa6da1fa685584446b27c53612344a28d39", + "0c829d4a74a40e4338a406a268de64a9ac1b58ad2f0655cf1412cfe10a81f128", + "ea8aeeeb0fcfa66319b5d1560c1c7e805cb445b678f60735a5450233a08f5146", + "0c03e2902e4374dc2ae6e9d46b27028af86c461ec65b9708829f6e341c5dfa86", + "427623b7f21f13216b47d64b3cce69d8695a12a8b76ceda909038e8ed1ad7ad6", + "309bd0bf85e4b9bb130a17df032a6d057d2f4774abca3a8af6fdbf25de9182d9", + "7f3da6402da41b66b0e5e957a40a787c53a3a31228f06d45f6ddfc275c06e815", + "9453352b0b6541e28b5c36d1ef833b7f49dad472ac7bc01712f04a1d0bd8cb3d", + "35b6a024cb364ee9e7ac55b3ee07e137b7dc7756c6e7131d02d603613f876578", + "6ce8b20bf01933590374ff85f4a2fcab43cde2bbf9bf7b56c7e4c21acce02af1", + "f02acb1e0f786b78b042e5d5e4d25fe711ecc05b2cbdc37077a4a1b66d925808", + "b5697acfcc9d3d4b58bf01ee959bb519b165d6e2b3ff799dcd8581f2e4ecc4b4", + "e5dcd86553008584de8d4e67edb1049198922900801bf51da55efcb7de45c37b", + "e0c3e5997bd114db9c09728c21b8b35748eee68f8eb305b5cc099be3c265d327", + "6c1e0ef8496d1c39fcde030239772ddf1182807dde25d54fea68085434a08b02", + "4fbd6bcb31a607f792cca7f514d8259a05f684092be8ac91b242bc90169a7042", + "9dbd18a509716c53037d8713d0353094d55b48253cb0d38cd7bc08959d7ff014", + "a444e2b82333cbd5dfe250e393e245d5db8b78f91bbb6a572761a0a43ed56b38", + "b163cba8c2d0441ac59033f7742733cb77812bd74de1147ae8173faae4f9269b", + "0ac9c6fc35728d1317490b51b36277e825a49ba95be2a6783ef7af99e49228f7", + "abe44cdbc6aea6876b8f009d6279479dca2c4f17c650c51bd7de410bbf23cd24", + "4b9370d4ae2fe08db33f468ffceca5d9fe6b0273ef2887a4ea8522293870e0a1", + "98b3b29996faf548b808efa1fd4ca779882bb75cc2ab3304f049b429a3638a97", + "dfb57e8a34f4911773133529e0e222f6a5cf691b25a65c288f8d6baa68b5f406", + "d1e35d54b3df21193102e9dcbb194a294323522ae35c68597b4b08f53cbf36ec", + "53e8bb244f3c2c90fcc8ceb1662e03aab3e1a781fa1697f7383e0bdc59831195", + "255874e4ced532485777b878f2d779c62853bd8d4d05ddadfa6163465703dc8c", + "6b61c176da93e56226cb6fb7a4f8acf1df87d02be81f823dc851680ae64f80b6", + "f035af0a109e467fd4ecd7c45bed043dd1ba0d02164c2b2aa0ab1989d8eaeff7", + "81b239e1213841934e989034959c9ca0448ad64ac3ab919751c2289600a20723", + "251b4f7823c8449e2e154cbab4aa72fb2cc55495da5fdc2d31075aa5647ba4cd", + "0769a11a881bfd2f02a087803235d344ad2f005010e40ecd7af5db96245dd719", + "f4963f675eefacf4cdd14ff7b759d5752aecee4cd704744e58b9bbbe078e0faa", + "28916a97e55790f8e0a95c1203c1f8f1eac99b866aac5800445ef2d888b4be87", + "575a46ecc4c7757656b9371e9b8dc058f99f9d9ad20a3e6de2d4e6cbc193711c", + "935994c1df9323d2bdcbab8762904e48edf6298904f3ae7e234599366bfd86ef", + "dec5bac62e0ef64db4936f818e325e12aed29ee0933277259185da1298dd0a28", + "3ba063cbea967fc0b7bb9a25c5a12ee801eaa92f5570d1d1a977a229834fd0a0", + "3327cc41ebebc9e82f06f836d5868f9c5023b05498bd73d836f04179d24fc7b9", + "8d70f449bda2223d0d647d7543bc3a2c29fedffaef789dc5bd73d25a6f0afd38", + "9902826a10b226265f357562bb20774358dbc636e790257a57af7f791b600acf", + "c5821a347232865496580de7f3043822ca56eea4f8266c51e3be4a16016cdeed", + "2817d6dee3e6706b92989f2dcef7cd0fef5d2263ed3d1c5dab8ba99550b1e5d5", + "b918f2ce206701dfe60eee1d40fad119540e928b8da813fe29d77a2e26c2738b", + "7f836479ecd2c4aec31c96935e2ce932135249c6077184b837f724fbdd425408", + "1381c39fb5a68c134b948aacf72b5127a7b0d5a721ed2091da25a3856b9a9af8", + "80e6ab5756de5eba8551f62d8943e5f650f4ae8864ea6bdffc3257ef1072679a", + "ae7dd5fcf4688844f5ef72471cc0273e0e2f8f43ba4fc3dd41208f70b880e5d4", + "e0f1d8c1a745ede1198fefbd0555bc118f7ccbd9ee783d8ed7a148f54fb1903f", + "33c8a75fad49fd3b3bb1e031fb283858f79b05a8b461eb1f8f0e9d41c53474bc", + "3864e97cd6f47c2eaf3fdcbb105e014accb4eb902be8a43dfca77dfe0d92761e", + "e524e7c40053d3d1da35ac8a584ad7034cd30de0e9513bf7731f8e5f51f7178e", + "12e121ad8a9be231659d08746b1a8b2b9247304dddc69cec074711970986cc6d", + "149a0d197a6c2dba3044e43f5fac90f6a797e0d4c8d91e241725e963083bb472", + "6a062ba02e79d2b2c001e1689daa1b3ade7c917ef26b30987798123818b202bd", + "99e47c124d128f741f30e7c5bc7575f11a005befaa26ab60b560fe4a65717aa0", + "147961e3d28573540582cb4a6e266f3a677d9e5b24da19c29fa611d3f8fba8a9", + "59261c94c9000fe26b3b050c4cf7ab06343d4ffcbe3934da65f12d8a7b0c6862", + "7428bc174ef7fd68911d46b35da30bbf8a292bb6b9ea99489b0c28bb55876e54", + "6fbff71f7187e67e5cc11f85ecc7d063284c6829ffc2b647d9439cf6d80c71b3", + "d49db17aa1e54596333d1962db0050b54a96abc6184f28b46778ac3eb9bc2290", + "082436ca15376750587883056c2b5fceafd232f6fd91d839dcf7f138583565d0", + "2d6c7614ddf121cedd9db989f73da61c3e5d5d7a876139d2d441d8dc28bef0ab", + "10b5eb9db4b22b9047b2c9a2b40abefc9786d8f0b33514bcef81c462f241ca60" + ], + "key_material": { + "scan_priv_key": "000000000000000000000000000000000000000000000000000000000000dead", + "spend_priv_key": "000000000000000000000000000000000000000000000000000000000000beef" + }, + "labels": [ + 0 + ] + }, + "expected": { + "addresses": [ + "sp1qqtnvg7hxjck9ag24naytgd7pjwsmevwh95ydwht58w3uh7uw0ta7kq6xcdal4dazgg2txp472hdz4svms98328lqdflmpxppmc6ykamcrv9s7w9g", + "sp1qqtnvg7hxjck9ag24naytgd7pjwsmevwh95ydwht58w3uh7uw0ta7kqmk4a3pm24z0yepw6aw27pku56md0hq5ace0l3p3jstkqaajwrsjqc5dde5" + ], + "n_outputs": 2323, + "tweak": "022c333a8938ca0c7433c5e39d440b469f58374d0363b5bffc66aca1cc10d7b609", + "shared_secret": "03abe0979dbbe4e1bb2cd2e06524a411c0b829c1d2282052da817b46f1d6e598e4", + "input_pub_key_sum": "02bca87f72e604e8850064552bedf380ca4584227057efe12a6cc238470658aaa3" + } + } + ] + } +] diff --git a/testing/devtest/verify_sp_outputs.py b/testing/devtest/verify_sp_outputs.py index 35e09397d..0e9ac8bfe 100644 --- a/testing/devtest/verify_sp_outputs.py +++ b/testing/devtest/verify_sp_outputs.py @@ -19,7 +19,11 @@ from silentpayments import ( _combine_pubkeys, _compute_ecdh_share, + _compute_input_hash, _compute_silent_payment_output_script, + _compute_silent_payment_spending_privkey, + _negate_if_odd_y, + _sum_privkeys, SilentPaymentsMixin, ) @@ -200,9 +204,45 @@ def _build_mock_psbt(params): parts.append(b2a_hex(txid).decode() + ":" + b2a_hex(vout).decode()) RV.write(",".join(parts)) +elif mode == "sum_privkeys": + # Sum a list of normalized privkeys; return as 64-char hex + privkeys = [a2b_hex(h) for h in params["privkeys"]] + result = _sum_privkeys(privkeys) + RV.write(b2a_hex(result).decode()) + elif mode == "pubkey_from_input": # Call _pubkey_from_input for a specific input; return pubkey hex or "" psbt = _build_mock_psbt(params) i = params["input_index"] pk = psbt._pubkey_from_input(psbt.inputs[i]) RV.write(b2a_hex(pk).decode() if pk else "") + +elif mode == "compute_input_hash": + outpoints = [(a2b_hex(t), a2b_hex(v)) for t, v in params["outpoints"]] + A_sum = a2b_hex(params["A_sum"]) + result = _compute_input_hash(outpoints, A_sum) + RV.write(b2a_hex(result).decode()) + +elif mode == "compute_shared_secret": + ecdh_share = a2b_hex(params["ecdh_share"]) + input_hash = a2b_hex(params["input_hash"]) + result = ngu.secp256k1.ec_pubkey_tweak_mul(ecdh_share, input_hash) + RV.write(b2a_hex(result).decode()) + +elif mode == "pubkey_from_privkey": + # Compute pubkey = privkey * G; return 33-byte compressed hex + privkey = a2b_hex(params["privkey"]) + G = ngu.secp256k1.generator() + pubkey = ngu.secp256k1.ec_pubkey_tweak_mul(G, privkey) + RV.write(b2a_hex(pubkey).decode()) + +elif mode == "compute_spending_privkey": + b_spend = a2b_hex(params["b_spend"]) + tweak = a2b_hex(params["tweak"]) + result = _compute_silent_payment_spending_privkey(b_spend, tweak) + RV.write(b2a_hex(result).decode()) + +elif mode == "negate_if_odd_y": + privkey = a2b_hex(params["privkey"]) + result = _negate_if_odd_y(privkey) + RV.write(b2a_hex(result).decode()) diff --git a/testing/test_bip352_vectors.py b/testing/test_bip352_vectors.py new file mode 100644 index 000000000..ac4290674 --- /dev/null +++ b/testing/test_bip352_vectors.py @@ -0,0 +1,349 @@ +# (c) Copyright 2024 by Coinkite Inc. This file is covered by license found in COPYING-CC. +# +# test_bip352_vectors.py - BIP-352 send/receive test vector verification +# +# Verifies that crypto primitives in silentpayments.py match the BIP-352 +# reference test vectors. Pubkey extraction from raw scriptSig/witness is +# done CPython-side; all EC math is delegated to the simulator +# +import hashlib +import json +import struct +import pytest +from sp_helpers import ( + _sim_compute_ecdh_share, + _sim_compute_input_hash, + _sim_compute_shared_secret, + _sim_compute_spending_privkey, + _sim_compute_output_script, + _sim_pubkey_from_privkey, + _sim_combine_pubkeys, + _sim_negate_if_odd_y, + _sim_sum_privkeys, +) + +NUMS_H = bytes.fromhex( + "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" +) + + +def load_bip352_vectors(): + with open("bip352_test_vectors.json") as f: + return json.load(f) + + +# --------------------------------------------------------------------------- +# CPython-side helpers +# --------------------------------------------------------------------------- + + +def _hash160(data): + return hashlib.new("ripemd160", hashlib.sha256(data).digest()).digest() + + +def _parse_witness_stack(txinwitness_hex): + """Parse serialized witness into a list of stack items (bytes).""" + if not txinwitness_hex: + return [] + raw = bytes.fromhex(txinwitness_hex) + pos = 0 + n_items = raw[pos] + pos += 1 + items = [] + for _ in range(n_items): + # CompactSize length + length = raw[pos] + pos += 1 + if length == 0xFD: + length = int.from_bytes(raw[pos : pos + 2], "little") + pos += 2 + elif length == 0xFE: + length = int.from_bytes(raw[pos : pos + 4], "little") + pos += 4 + items.append(raw[pos : pos + length]) + pos += length + return items + + +def _is_p2pkh(spk): + return ( + len(spk) == 25 + and spk[0] == 0x76 + and spk[1] == 0xA9 + and spk[-2] == 0x88 + and spk[-1] == 0xAC + ) + + +def _is_p2wpkh(spk): + return len(spk) == 22 and spk[0] == 0x00 and spk[1] == 0x14 + + +def _is_p2tr(spk): + return len(spk) == 34 and spk[0] == 0x51 and spk[1] == 0x20 + + +def _is_p2sh(spk): + return len(spk) == 23 and spk[0] == 0xA9 and spk[1] == 0x14 and spk[-1] == 0x87 + + +def _extract_pubkey_from_vin(vin): + """Extract compressed pubkey from a BIP-352 test vector vin entry. + + Returns (pubkey_bytes, is_taproot) or (None, None) if ineligible. + Follows BIP-352 reference get_pubkey_from_input() logic. + """ + spk = bytes.fromhex(vin["prevout"]["scriptPubKey"]["hex"]) + script_sig = bytes.fromhex(vin.get("scriptSig", "")) + witness = _parse_witness_stack(vin.get("txinwitness", "")) + + if _is_p2pkh(spk): + spk_hash = spk[3:23] + for i in range(len(script_sig), 32, -1): + candidate = script_sig[i - 33 : i] + if candidate[0] in (0x02, 0x03) and _hash160(candidate) == spk_hash: + return candidate, False + return None, None + + if _is_p2sh(spk): + redeem_script = script_sig[1:] + if _is_p2wpkh(redeem_script) and witness: + pk = witness[-1] + if len(pk) == 33 and pk[0] in (0x02, 0x03): + return pk, False + return None, None + + if _is_p2wpkh(spk): + if witness: + pk = witness[-1] + if len(pk) == 33 and pk[0] in (0x02, 0x03): + return pk, False + return None, None + + if _is_p2tr(spk): + stack = list(witness) + if stack: + if len(stack) > 1 and stack[-1][0] == 0x50: + stack.pop() + if len(stack) > 1: + control_block = stack[-1] + internal_key = control_block[1:33] + if internal_key == NUMS_H: + return None, None + x_only = spk[2:34] + return b"\x02" + x_only, True + + return None, None + + +def _build_outpoints(vin_list): + """Build (txid_bytes, vout_bytes) list from vin entries. + + txid is reversed to internal byte order, vout is 4-byte LE. + """ + outpoints = [] + for vin in vin_list: + txid = bytes.fromhex(vin["txid"])[::-1] + vout = struct.pack(" 2323 for g in groups.values()): + for eo in expected["outputs"]: + assert eo == [] + return + + # 3. Shared secrets and 4. Output derivation + actual_outputs = set() + for scan_key_hex, group in groups.items(): + scan_pub_key = bytes.fromhex(scan_key_hex) + privkey_bytes = bytes.fromhex("%064x" % privkey_sum) + ecdh_share = _sim_compute_ecdh_share( + sim_exec, sim_execfile, privkey_bytes, scan_pub_key + ) + input_hash = _sim_compute_input_hash( + sim_exec, sim_execfile, outpoints, A_sum + ) + shared_secret = _sim_compute_shared_secret( + sim_exec, sim_execfile, ecdh_share, input_hash + ) + + # Check shared_secret against expected (find first recipient index for this scan key) + for ri, _ in group: + es = expected["shared_secrets"][ri] + if es is not None: + assert shared_secret.hex() == es, ( + "shared_secret mismatch for recipient %d" % ri + ) + break + + for k, (ri, recipient) in enumerate(group): + spend_pub_key = bytes.fromhex(recipient["spend_pub_key"]) + script = _sim_compute_output_script( + sim_exec, + sim_execfile, + outpoints, + A_sum, + ecdh_share, + spend_pub_key, + k, + ) + actual_outputs.add(script[2:].hex()) + + assert any(set(eo) == actual_outputs for eo in expected["outputs"]), ( + "output mismatch: got %s" % actual_outputs + ) + + +class TestBIP352Receiving: + @pytest.mark.parametrize("vec_idx", range(len(_VECTORS)), ids=_VEC_IDS) + def test_receiving(self, sim_exec, sim_execfile, vec_idx): + vec = _VECTORS[vec_idx] + receiving = vec["receiving"][0] + expected = receiving["expected"] + vin_list = receiving["given"]["vin"] + scan_priv_key = bytes.fromhex( + receiving["given"]["key_material"]["scan_priv_key"] + ) + spend_priv_key = bytes.fromhex( + receiving["given"]["key_material"]["spend_priv_key"] + ) + outpoints = _build_outpoints(vin_list) + + # 1. Extract key material and compute A_sum + extracted = _extract_key_material(vin_list) + + if not extracted: + assert expected.get("outputs", []) == [] + return + + expected_pk_sum = expected.get("input_pub_key_sum") + if expected_pk_sum is None: + # Point at infinity or no valid inputs + assert expected.get("outputs", []) == [] + return + + pubkeys = [pk for pk, _, _ in extracted] + A_sum = _sim_combine_pubkeys(sim_exec, sim_execfile, pubkeys) + assert A_sum.hex() == expected_pk_sum, "input_pub_key_sum mismatch" + + # 2. Shared secret + input_hash = _sim_compute_input_hash(sim_exec, sim_execfile, outpoints, A_sum) + ecdh_share = _sim_compute_ecdh_share( + sim_exec, sim_execfile, scan_priv_key, A_sum + ) + shared_secret = _sim_compute_shared_secret( + sim_exec, sim_execfile, ecdh_share, input_hash + ) + assert shared_secret.hex() == expected.get("shared_secret"), ( + "shared_secret mismatch" + ) + + # 3. Spending privkey tweaks + expected_outputs = expected.get("outputs", []) + if not expected_outputs: + return + + for expected_out in expected_outputs: + expected_tweak = bytes.fromhex(expected_out["priv_key_tweak"]) + expected_pubkey = expected_out["pub_key"] + d = _sim_compute_spending_privkey( + sim_exec, sim_execfile, spend_priv_key, expected_tweak + ) + computed_pk = _sim_pubkey_from_privkey(sim_exec, sim_execfile, d) + assert computed_pk[1:].hex() == expected_pubkey, ( + "spending privkey mismatch for output %s" % expected_pubkey + ) From 374c4237f990eccf467cfe3219c37cb7dcf9f734 Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Tue, 25 Nov 2025 17:50:51 -0500 Subject: [PATCH 10/23] BIP-375: implement Silent Payments PSBT signing workflows Integrate preview silent payment address as output in auth:interact Integrate silent payments in signing workflow Add sp_hrp property to chains.py Add silent payments fields to psbt.py --- shared/auth.py | 12 ++++++ shared/chains.py | 3 ++ shared/psbt.py | 90 ++++++++++++++++++++++++++++++++++++---- shared/silentpayments.py | 58 +++++++++++++++++++++++--- 4 files changed, 150 insertions(+), 13 deletions(-) diff --git a/shared/auth.py b/shared/auth.py index f28259bda..67c466de3 100644 --- a/shared/auth.py +++ b/shared/auth.py @@ -430,6 +430,12 @@ async def interact(self): ccc_c_xfp = CCCFeature.get_xfp() # can be None args = self.psbt.consider_inputs(cosign_xfp=ccc_c_xfp) + + # Silent Payments: Validate and pre-process Silent Payments outputs to make preview useful + if self.psbt.has_silent_payment_outputs(): + with stash.SensitiveValues() as sv: + self.psbt.process_silent_payments(sv) + self.psbt.consider_outputs(*args, cosign_xfp=ccc_c_xfp) del args # not needed anymore # we can properly assess sighash only after we know @@ -700,6 +706,8 @@ def output_summary_text(self, msg): total_change = 0 has_change = False + # TODO: Test pay to silent payment change address + for idx, tx_out in self.psbt.output_iter(): outp = self.psbt.outputs[idx] if outp.is_change: @@ -714,6 +722,8 @@ def output_summary_text(self, msg): else: if len(largest_outs) < MAX_VISIBLE_OUTPUTS: rendered, _ = self.render_output(tx_out) + if outp.sp_v0_info: + rendered += self.psbt.render_silent_payment_output_string(outp) largest_outs.append((tx_out.nValue, rendered)) if len(largest_outs) == MAX_VISIBLE_OUTPUTS: # descending sort from the biggest value to lowest (sort on out.nValue) @@ -1733,6 +1743,8 @@ def yield_item(self, offset, end, qr_items, change_idxs): outp = self.user_auth_action.psbt.outputs[idx] item = "Output %d%s:\n\n" % (idx, " (change)" if outp.is_change else "") msg, addr_or_script = self.user_auth_action.render_output(out) + if outp.sp_v0_info: + msg += self.user_auth_action.psbt.render_silent_payment_output_string(outp) item += msg qr_items.append(addr_or_script) if outp.is_change: diff --git a/shared/chains.py b/shared/chains.py index afed090b8..87dfce964 100644 --- a/shared/chains.py +++ b/shared/chains.py @@ -58,6 +58,7 @@ class ChainsBase: curve = 'secp256k1' menu_name = None # use 'name' if this isn't defined ccc_min_block = 0 + sp_hrp = 'sp' # BIP-352 Silent Payment address HRP (default mainnet) # b44_cointype comes from # @@ -341,6 +342,7 @@ class BitcoinTestnet(ChainsBase): } bech32_hrp = 'tb' + sp_hrp = 'tsp' # BIP-352 Silent Payment testnet HRP b58_addr = bytes([111]) b58_script = bytes([196]) @@ -353,6 +355,7 @@ class BitcoinRegtest(BitcoinTestnet): ctype = 'XRT' name = 'Bitcoin Regtest' bech32_hrp = 'bcrt' + sp_hrp = 'tsp' # BIP-352 Silent Payment regtest HRP def get_chain(short_name): diff --git a/shared/psbt.py b/shared/psbt.py index 1273a8c83..f5cc36541 100644 --- a/shared/psbt.py +++ b/shared/psbt.py @@ -41,9 +41,13 @@ PSBT_IN_REQUIRED_HEIGHT_LOCKTIME, MAX_SIGNERS, PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS, PSBT_IN_MUSIG2_PUB_NONCE, PSBT_IN_MUSIG2_PARTIAL_SIG, PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS, + PSBT_OUT_SP_V0_INFO, PSBT_OUT_SP_V0_LABEL, + PSBT_IN_SP_DLEQ, PSBT_IN_SP_ECDH_SHARE, + PSBT_GLOBAL_SP_DLEQ, PSBT_GLOBAL_SP_ECDH_SHARE, AF_P2WSH, AF_P2WSH_P2SH, AF_P2SH, AF_P2TR, AF_P2WPKH, AF_CLASSIC, AF_P2WPKH_P2SH, AFC_SEGWIT, AF_BARE_PK ) +from silentpayments import SilentPaymentsMixin psbt_tmp256 = bytearray(256) @@ -402,12 +406,15 @@ def parse_subpaths(self, my_xfp, parent, cosign_xfp=None): # Track details of each output of PSBT # class psbtOutputProxy(psbtProxy): - no_keys = { PSBT_OUT_REDEEM_SCRIPT, PSBT_OUT_WITNESS_SCRIPT, PSBT_OUT_TAP_INTERNAL_KEY, PSBT_OUT_TAP_TREE } + no_keys = { PSBT_OUT_REDEEM_SCRIPT, PSBT_OUT_WITNESS_SCRIPT, PSBT_OUT_TAP_INTERNAL_KEY, PSBT_OUT_TAP_TREE, + PSBT_OUT_SP_V0_INFO, PSBT_OUT_SP_V0_LABEL } + short_values = { PSBT_OUT_SP_V0_INFO, PSBT_OUT_SP_V0_LABEL } blank_flds = ('unknown', 'subpaths', 'redeem_script', 'witness_script', 'sp_idxs', 'is_change', 'amount', 'script', 'attestation', 'proprietary', 'taproot_internal_key', 'taproot_subpaths', 'taproot_tree', 'ik_idx', - 'musig_pubkeys') + 'musig_pubkeys', 'sp_v0_info', 'sp_v0_label', + ) def __init__(self, fd, idx): super().__init__() @@ -423,6 +430,8 @@ def __init__(self, fd, idx): #self.script = None #self.amount = None #self.musig_pubkeys = None + #self.sp_v0_info = None # used to identify silent payment outputs - 66-byte (scan-pub || spend-pub) + #self.sp_v0_label = None # this flag is set when we are assuming output will be change (same wallet) #self.is_change = False @@ -473,6 +482,10 @@ def store(self, kt, key, val): elif kt == PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS: self.musig_pubkeys = self.musig_pubkeys or [] self.musig_pubkeys.append((key, val)) + elif kt == PSBT_OUT_SP_V0_INFO: + self.sp_v0_info = val + elif kt == PSBT_OUT_SP_V0_LABEL: + self.sp_v0_label = val else: self.unknown = self.unknown or [] pos, length = key @@ -511,6 +524,12 @@ def serialize(self, out_fd, is_v2): wr(PSBT_OUT_SCRIPT, self.script) wr(PSBT_OUT_AMOUNT, self.amount) + if self.sp_v0_info: + wr(PSBT_OUT_SP_V0_INFO, self.sp_v0_info) + + if self.sp_v0_label: + wr(PSBT_OUT_SP_V0_LABEL, self.sp_v0_label) + if self.proprietary: for k, v in self.proprietary: wr(PSBT_PROPRIETARY, v, k) @@ -644,7 +663,9 @@ def fraud(idx, af, err=""): class psbtInputProxy(psbtProxy): # just need to store a simple number for these - short_values = { PSBT_IN_SIGHASH_TYPE } + # Silent Payments (shares/dleq): bytes (not proxied) so scan-pub can be used as dict key + # and dict can be mutated in place + short_values = { PSBT_IN_SIGHASH_TYPE, PSBT_IN_SP_ECDH_SHARE, PSBT_IN_SP_DLEQ } # only part-sigs have a key to be stored. no_keys = {PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_WITNESS_UTXO, PSBT_IN_SIGHASH_TYPE, @@ -660,7 +681,8 @@ class psbtInputProxy(psbtProxy): 'taproot_merkle_root', 'taproot_script_sigs', 'taproot_scripts', 'taproot_subpaths', 'taproot_internal_key', 'taproot_key_sig', 'tr_added_sigs', 'ik_idx', 'musig_pubkeys', 'musig_pubnonces', 'musig_part_sigs', 'musig_agg_idx', - 'musig_added_pubnonces', 'musig_added_sigs' + 'musig_added_pubnonces', 'musig_added_sigs', + 'sp_ecdh_shares', 'sp_dleq_proofs', ) def __init__(self, fd, idx): @@ -708,6 +730,10 @@ def __init__(self, fd, idx): #self.musig_pubnonces = None #self.musig_part_sigs = None + # === silent payments === + #self.sp_ecdh_shares = None # dict[scan-pub] = ecdh_share + #self.sp_dleq_proofs = None # dict[scan-pub] = dleq_proof + self.parse(fd) @property @@ -1165,6 +1191,12 @@ def store(self, kt, key, val): elif kt == PSBT_IN_MUSIG2_PARTIAL_SIG: self.musig_part_sigs = self.musig_part_sigs or [] self.musig_part_sigs.append((key, val)) + elif kt == PSBT_IN_SP_ECDH_SHARE: + self.sp_ecdh_shares = self.sp_ecdh_shares or {} + self.sp_ecdh_shares[key] = val + elif kt == PSBT_IN_SP_DLEQ: + self.sp_dleq_proofs = self.sp_dleq_proofs or {} + self.sp_dleq_proofs[key] = val else: # including: PSBT_IN_FINAL_SCRIPTSIG, PSBT_IN_FINAL_SCRIPTWITNESS self.unknown = self.unknown or [] @@ -1261,17 +1293,27 @@ def serialize(self, out_fd, is_v2): if self.req_height_locktime is not None: wr(PSBT_IN_REQUIRED_HEIGHT_LOCKTIME, pack(" Date: Tue, 23 Dec 2025 15:34:24 -0500 Subject: [PATCH 11/23] BIP-376: handle spending silent payments outputs Add sp_spend support to _derive_input_privkey Validate bip376 spend key derivation --- shared/psbt.py | 18 ++++++- shared/silentpayments.py | 68 +++++++++++++++++++++----- testing/devtest/unit_silentpayments.py | 17 +++++++ testing/devtest/verify_sp_outputs.py | 2 + testing/psbt.py | 16 ++++++ 5 files changed, 106 insertions(+), 15 deletions(-) diff --git a/shared/psbt.py b/shared/psbt.py index f5cc36541..a176533de 100644 --- a/shared/psbt.py +++ b/shared/psbt.py @@ -44,6 +44,7 @@ PSBT_OUT_SP_V0_INFO, PSBT_OUT_SP_V0_LABEL, PSBT_IN_SP_DLEQ, PSBT_IN_SP_ECDH_SHARE, PSBT_GLOBAL_SP_DLEQ, PSBT_GLOBAL_SP_ECDH_SHARE, + PSBT_IN_SP_TWEAK, PSBT_IN_SP_SPEND_BIP32_DERIVATION, AF_P2WSH, AF_P2WSH_P2SH, AF_P2SH, AF_P2TR, AF_P2WPKH, AF_CLASSIC, AF_P2WPKH_P2SH, AFC_SEGWIT, AF_BARE_PK ) @@ -670,7 +671,7 @@ class psbtInputProxy(psbtProxy): # only part-sigs have a key to be stored. no_keys = {PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_WITNESS_UTXO, PSBT_IN_SIGHASH_TYPE, PSBT_IN_REDEEM_SCRIPT, PSBT_IN_WITNESS_SCRIPT, PSBT_IN_FINAL_SCRIPTSIG, - PSBT_IN_FINAL_SCRIPTWITNESS,PSBT_IN_TAP_KEY_SIG, + PSBT_IN_FINAL_SCRIPTWITNESS, PSBT_IN_SP_TWEAK, PSBT_IN_TAP_KEY_SIG, PSBT_IN_TAP_INTERNAL_KEY, PSBT_IN_TAP_MERKLE_ROOT} blank_flds = ( @@ -682,7 +683,7 @@ class psbtInputProxy(psbtProxy): 'taproot_subpaths', 'taproot_internal_key', 'taproot_key_sig', 'tr_added_sigs', 'ik_idx', 'musig_pubkeys', 'musig_pubnonces', 'musig_part_sigs', 'musig_agg_idx', 'musig_added_pubnonces', 'musig_added_sigs', - 'sp_ecdh_shares', 'sp_dleq_proofs', + 'sp_ecdh_shares', 'sp_dleq_proofs', 'sp_tweak', 'sp_spend_bip32_derivation', ) def __init__(self, fd, idx): @@ -733,6 +734,8 @@ def __init__(self, fd, idx): # === silent payments === #self.sp_ecdh_shares = None # dict[scan-pub] = ecdh_share #self.sp_dleq_proofs = None # dict[scan-pub] = dleq_proof + #self.sp_tweak = None + #self.sp_spend_bip32_derivation = None # (spend-pub, xfp || path) self.parse(fd) @@ -1197,6 +1200,10 @@ def store(self, kt, key, val): elif kt == PSBT_IN_SP_DLEQ: self.sp_dleq_proofs = self.sp_dleq_proofs or {} self.sp_dleq_proofs[key] = val + elif kt == PSBT_IN_SP_TWEAK: + self.sp_tweak = self.get(val) + elif kt == PSBT_IN_SP_SPEND_BIP32_DERIVATION: + self.sp_spend_bip32_derivation = (key, val) else: # including: PSBT_IN_FINAL_SCRIPTSIG, PSBT_IN_FINAL_SCRIPTWITNESS self.unknown = self.unknown or [] @@ -1301,6 +1308,13 @@ def serialize(self, out_fd, is_v2): for k, v in self.sp_dleq_proofs.items(): wr(PSBT_IN_SP_DLEQ, v, k) + if self.sp_tweak: + wr(PSBT_IN_SP_TWEAK, self.sp_tweak) + + if self.sp_spend_bip32_derivation: + k, v = self.sp_spend_bip32_derivation + wr(PSBT_IN_SP_SPEND_BIP32_DERIVATION, v, k) + if self.unknown: for k, v in self.unknown: wr(None, v, k) diff --git a/shared/silentpayments.py b/shared/silentpayments.py index d0c8dfe90..3cf671fe6 100644 --- a/shared/silentpayments.py +++ b/shared/silentpayments.py @@ -1,6 +1,6 @@ # (c) Copyright 2024 by Coinkite Inc. This file is covered by license found in COPYING-CC. # -# silentpayments.py - BIP-352/BIP-375 +# silentpayments.py - BIP-352/BIP-375/BIP-376 # # Consolidates cryptographic primitives and PSBT handling logic for Silent Payments # @@ -41,6 +41,37 @@ def encode_silent_payment_address(scan_key, spend_key, version=0): return ngu.codecs.bip352_encode(hrp, scan_key, spend_key, version) +def validate_bip376_spend(input, output_xonly, my_xfp=None, parent=None): + """Validate silent payment spend using PSBT input data + + TODO: replace with test vectors once available + + Raises FatalPSBTIssue if any SP spend validation checks fail + """ + B_spend_coords, val_coords = input.sp_spend_bip32_derivation + xfp_path = input.parse_xfp_path(val_coords) + if my_xfp is not None: + xfp_path = input.handle_zero_xfp(xfp_path, my_xfp, parent) + + sp_path = xfp_path[1:] + if len(sp_path) != 5: + raise FatalPSBTIssue("SP spend path must have 5 components") + if sp_path[0] != (352 | 0x80000000): + raise FatalPSBTIssue("SP spend key purpose must use 352h path") + + expected_coin = chains.current_chain().b44_cointype | 0x80000000 + if sp_path[1] != expected_coin: + raise FatalPSBTIssue("SP spend path coin type does not match network") + if not (sp_path[2] & 0x80000000): + raise FatalPSBTIssue("SP spend path account must be hardened") + if sp_path[3] != 0x80000000: + raise FatalPSBTIssue("SP spend path key type must be 0h") + + B_spend = input.get(B_spend_coords) + if _compute_silent_payment_spending_xonly(B_spend, input.sp_tweak) != output_xonly: + raise FatalPSBTIssue("SP_TWEAK does not match UTXO output key") + + # ----------------------------------------------------------------------------- # Silent Payments Cryptographic Primitives # ----------------------------------------------------------------------------- @@ -737,25 +768,36 @@ def _derive_input_privkey(self, input, sv): Derive the contributing private key for an eligible input Note: + For silent payment inputs, derives from sp_spend_bip32_derivation and sp_tweak For taproot inputs, uses the internal key's derivation path (ik_idx), XFP-checked For non-taproot inputs, uses the first matching BIP32 derivation path Returns: bytes: Derived private key as (32-byte scalar) or None if not eligible """ - # TODO: BIP-376 sp_spend_derivation should go here - spk = input.utxo_spk - if spk and _is_p2tr(spk): - if input.ik_idx is not None and input.taproot_subpaths: - _, path_coords = input.taproot_subpaths[input.ik_idx] - xfp_path = self.parse_xfp_path(path_coords[2]) - if xfp_path[0] == self.my_xfp: - return self._path_to_privkey(xfp_path, sv) + privkey = None + + if input.sp_tweak and input.sp_spend_bip32_derivation: + _, val_coords = input.sp_spend_bip32_derivation + xfp_path = self.parse_xfp_path(val_coords) + if xfp_path[0] == self.my_xfp: + privkey = _compute_silent_payment_spending_privkey(self._path_to_privkey(xfp_path, sv), input.sp_tweak) else: - for xfp_path in self._iter_input_xfp_paths(input): - if xfp_path[0] == self.my_xfp: - return self._path_to_privkey(xfp_path, sv) - return None + spk = input.utxo_spk + if spk and _is_p2tr(spk): + if input.ik_idx is not None and input.taproot_subpaths: + _, path_coords = input.taproot_subpaths[input.ik_idx[0]] + xfp_path = self.parse_xfp_path(path_coords[2]) + if xfp_path[0] == self.my_xfp: + privkey = self._path_to_privkey(xfp_path, sv) + if input.taproot_internal_key: + privkey = self._normalize_p2tr_privkey(privkey, self.get(input.taproot_internal_key)) + else: + for xfp_path in self._iter_input_xfp_paths(input): + if xfp_path[0] == self.my_xfp: + privkey = self._path_to_privkey(xfp_path, sv) + break + return privkey def _iter_input_xfp_paths(self, input): """ diff --git a/testing/devtest/unit_silentpayments.py b/testing/devtest/unit_silentpayments.py index 71a1c8f7b..a2b61a2f4 100644 --- a/testing/devtest/unit_silentpayments.py +++ b/testing/devtest/unit_silentpayments.py @@ -16,6 +16,7 @@ _combine_pubkeys, _compute_shared_secret_tweak, _compute_silent_payment_output_script, + _compute_silent_payment_spending_privkey, _is_p2pkh, _is_p2wpkh, _is_p2tr, @@ -195,6 +196,20 @@ except ValueError as e: assert "33 bytes" in str(e) +# --------------------------------------------------------------------------- +# Spending Privkey +# --------------------------------------------------------------------------- + +result = _compute_silent_payment_spending_privkey((1).to_bytes(32, "big"), (42).to_bytes(32, "big")) +assert isinstance(result, bytes) and len(result) == 32 +assert result != (1).to_bytes(32, "big") +assert 0 < int.from_bytes(result, "big") < ngu.secp256k1.curve_order_int() + +try: + _compute_silent_payment_spending_privkey(b"\x00" * 32, b"\x00" * 32) + assert False, "Should raise" +except ValueError as e: + assert "not in valid scalar range" in str(e) # --------------------------------------------------------------------------- # Mock Infrastructure @@ -212,6 +227,8 @@ def __init__(self): self.prevout_idx = None self.witness_utxo = None self.taproot_internal_key = None + self.sp_tweak = None + self.sp_spend_bip32_derivation = None self.utxo_spk = None self.ik_idx = None self.sighash = None diff --git a/testing/devtest/verify_sp_outputs.py b/testing/devtest/verify_sp_outputs.py index 0e9ac8bfe..95d191d79 100644 --- a/testing/devtest/verify_sp_outputs.py +++ b/testing/devtest/verify_sp_outputs.py @@ -53,6 +53,8 @@ def __init__(self): self.sp_dleq_proofs = {} self.subpaths = [] self.taproot_subpaths = [] + self.sp_tweak = None + self.sp_spend_bip32_derivation = {} self.sp_idxs = None self.ik_idx = None diff --git a/testing/psbt.py b/testing/psbt.py index 26e85ebe4..709a5837d 100644 --- a/testing/psbt.py +++ b/testing/psbt.py @@ -64,6 +64,9 @@ # BIP-375 Silent Payments PSBT_IN_SP_ECDH_SHARE = 0x1d PSBT_IN_SP_DLEQ = 0x1e +# BIP-376 Silent Payments +PSBT_IN_SP_SPEND_BIP32_DERIVATION = 0x1f +PSBT_IN_SP_TWEAK = 0x20 # OUTPUTS === PSBT_OUT_REDEEM_SCRIPT = 0x00 @@ -152,6 +155,8 @@ def defaults(self): self.musig_part_sigs = {} self.sp_ecdh_shares = {} self.sp_dleq_proofs = {} + self.sp_tweak = None + self.sp_spend_bip32_derivation = {} self.others = {} self.unknown = {} @@ -184,6 +189,8 @@ def __eq__(a, b): a.musig_part_sigs == b.musig_part_sigs and \ a.sp_ecdh_shares == b.sp_ecdh_shares and \ a.sp_dleq_proofs == b.sp_dleq_proofs and \ + a.sp_tweak == b.sp_tweak and \ + a.sp_spend_bip32_derivation == b.sp_spend_bip32_derivation and \ a.unknown == b.unknown if rv: # NOTE: equality test on signatures requires parsing DER stupidness @@ -277,6 +284,10 @@ def parse_kv(self, kt, key, val): self.sp_ecdh_shares[key] = val elif kt == PSBT_IN_SP_DLEQ: self.sp_dleq_proofs[key] = val + elif kt == PSBT_IN_SP_TWEAK: + self.sp_tweak = val + elif kt == PSBT_IN_SP_SPEND_BIP32_DERIVATION: + self.sp_spend_bip32_derivation[key] = val else: self.unknown[bytes([kt]) + key] = val @@ -341,6 +352,11 @@ def serialize_kvs(self, wr, v2): if self.sp_dleq_proofs: for k, v in self.sp_dleq_proofs.items(): wr(PSBT_IN_SP_DLEQ, v, k) + if self.sp_tweak is not None: + wr(PSBT_IN_SP_TWEAK, self.sp_tweak) + if self.sp_spend_bip32_derivation: + for k, v in self.sp_spend_bip32_derivation.items(): + wr(PSBT_IN_SP_SPEND_BIP32_DERIVATION, v, k) if self.musig_pubnonces: for (pk, ak, lh), pubnonce in self.musig_pubnonces.items(): From 0fc9d8d8db82a5cd500fc83a60e872637b9668b7 Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:26:02 -0500 Subject: [PATCH 12/23] SP: add Silent Payments integration tests Create end-to-end tests for SP in testing/test_silentpayments.py - Expose foreign_mk for partial ownership coverage test Integrate SP spend into psbt functions: consider_inputs, determine_my_signing_key, sign_it - Synthesize parsed_subpaths from PSBT_IN_SPEND_BIP32_DERIVATION - Set input.sp_idxs to placeholder to synthesize input is_owned - Implement SP input signing Refactor _compute_and_store_ecdh_shares and _derive_input_privkey to leverage input.sp_idxs --- shared/psbt.py | 50 ++- shared/silentpayments.py | 128 ++++---- testing/devtest/unit_silentpayments.py | 7 +- testing/devtest/verify_sp_outputs.py | 8 +- testing/test_silentpayments.py | 407 +++++++++++++++++++++++++ testing/txn.py | 1 + 6 files changed, 525 insertions(+), 76 deletions(-) create mode 100644 testing/test_silentpayments.py diff --git a/shared/psbt.py b/shared/psbt.py index a176533de..1c5ad1d35 100644 --- a/shared/psbt.py +++ b/shared/psbt.py @@ -48,7 +48,7 @@ AF_P2WSH, AF_P2WSH_P2SH, AF_P2SH, AF_P2TR, AF_P2WPKH, AF_CLASSIC, AF_P2WPKH_P2SH, AFC_SEGWIT, AF_BARE_PK ) -from silentpayments import SilentPaymentsMixin +from silentpayments import SilentPaymentsMixin, compute_silent_payment_spending_privkey, validate_bip376_spend psbt_tmp256 = bytearray(256) @@ -747,6 +747,10 @@ def is_segwit(self): def is_musig(self): return bool(self.musig_pubkeys or self.musig_pubnonces or self.musig_part_sigs) + @property + def is_sp_spend(self): + return bool(self.sp_tweak and self.sp_spend_bip32_derivation) + def get_taproot_script_sigs(self): # returns set of (xonly, script) provided via PSBT_IN_TAP_SCRIPT_SIG # we do not parse control blocks (k) not needed @@ -1025,7 +1029,9 @@ def determine_my_signing_key(self, my_idx, addr_or_pubkey, my_xfp, psbt, parsed_ raise FatalPSBTIssue('Need witness script for input #%d' % my_idx) elif self.af == AF_P2TR: - if len(parsed_subpaths) == 1: + if self.is_sp_spend: + validate_bip376_spend(self, addr_or_pubkey, my_xfp, psbt) + elif len(parsed_subpaths) == 1: # keyspend without a script path assert self.taproot_merkle_root is None, "merkle_root should not be defined for simple keyspend" assert self.ik_idx == [0] @@ -1831,7 +1837,7 @@ async def validate(self): inp_have_subpath = False for i in self.inputs: - if i.subpaths or i.taproot_subpaths: + if i.subpaths or i.taproot_subpaths or i.is_sp_spend: inp_have_subpath = True if self.is_v2: @@ -2205,6 +2211,14 @@ def consider_inputs(self, cosign_xfp=None): smallest_nsequence = txi.nSequence parsed_subpaths = inp.parse_subpaths(self.my_xfp, self, cosign_xfp) + # Silent Payments: synthesize parsed_subpaths from PSBT_IN_SPEND_BIP32_DERIVATION + if inp.is_sp_spend and not parsed_subpaths: + k, v = inp.sp_spend_bip32_derivation + sp_path = inp.parse_xfp_path(v) + sp_path = inp.handle_zero_xfp(sp_path, self.my_xfp, self) + parsed_subpaths = {inp.get(k): sp_path} + if sp_path[0] in (self.my_xfp, cosign_xfp): + inp.sp_idxs = [0] # mark as owned so downstream checks treat it like any other signable input if not inp.has_utxo(): if inp.sp_idxs and not inp.fully_signed: @@ -2868,7 +2882,7 @@ def sign_it(self, alternate_secret=None, my_xfp=None): assert inp.sighash in [SIGHASH_ALL, SIGHASH_DEFAULT], "POR sighash not ALL/DEFAULT" # decide if it is appropriate to drop sighash from PSBT - if inp.taproot_subpaths: + if inp.taproot_subpaths or inp.is_sp_spend: drop_sighash = (inp.sighash == SIGHASH_DEFAULT) else: drop_sighash = (inp.sighash == SIGHASH_ALL) @@ -2950,7 +2964,11 @@ def sign_it(self, alternate_secret=None, my_xfp=None): assert not inp.added_sigs, "already done??" assert not inp.taproot_key_sig, "already done taproot??" - if inp.taproot_subpaths: + if inp.is_sp_spend: + # Silent Payments: Use spend-pub from sp_spend_bip32_derivation + schnorrsig = True + pubk, sp = inp.sp_spend_bip32_derivation + elif inp.taproot_subpaths: schnorrsig = True pubk = inp.taproot_subpaths[sp_idx][0] sp = inp.taproot_subpaths[sp_idx][1][2] @@ -2971,7 +2989,7 @@ def sign_it(self, alternate_secret=None, my_xfp=None): # expensive test, but works... and important pu = node.pubkey() - if schnorrsig: + if schnorrsig and not inp.is_sp_spend: pu = pu[1:] assert pu == pk, "Path (%s) led to wrong pubkey for input#%d" % (skp, in_idx) @@ -2990,7 +3008,7 @@ def sign_it(self, alternate_secret=None, my_xfp=None): digest = self.make_txn_sighash(in_idx, txi, inp.sighash) else: # Hash the inputs and such in totally new ways, based on BIP-143 - if not inp.taproot_subpaths: + if not inp.taproot_subpaths and not inp.is_sp_spend: digest = self.make_txn_segwit_sighash(in_idx, txi, inp.amount, inp.segwit_v0_scriptCode(), inp.sighash) @@ -3016,6 +3034,24 @@ def sign_it(self, alternate_secret=None, my_xfp=None): sk = node.privkey() # Do the ACTUAL signature ... finally!!! if schnorrsig: + # Silent Payments: handle signing SP inputs + if inp.is_sp_spend: + digest = self.make_txn_taproot_sighash(in_idx, hash_type=inp.sighash) + if sv.deltamode: + digest = ngu.hash.sha256d(digest) + tweaked_sk = compute_silent_payment_spending_privkey(sk, inp.sp_tweak) + sig = ngu.secp256k1.sign_schnorr(tweaked_sk, digest, ngu.random.bytes(32)) + stash.blank_object(tweaked_sk) + + if inp.sighash != SIGHASH_DEFAULT: + sig += bytes([inp.sighash]) + inp.taproot_key_sig = sig + self.sig_added = True + self.set_modifiable_flag(inp) + stash.blank_object(sk) + stash.blank_object(node) + del sk, node + continue kp = ngu.secp256k1.keypair(sk) xonly_pk = kp.xonly_pubkey().to_bytes() diff --git a/shared/silentpayments.py b/shared/silentpayments.py index 3cf671fe6..9e32e36c3 100644 --- a/shared/silentpayments.py +++ b/shared/silentpayments.py @@ -6,9 +6,14 @@ # import chains import ngu +import stash from dleq import generate_dleq_proof, verify_dleq_proof from exceptions import FatalPSBTIssue -from precomp_tag_hash import BIP352_SHARED_SECRET_TAG_H, BIP352_INPUTS_TAG_H +from precomp_tag_hash import ( + BIP352_SHARED_SECRET_TAG_H, + BIP352_INPUTS_TAG_H, + TAP_TWEAK_H, +) from serializations import SIGHASH_ALL, SIGHASH_DEFAULT from ubinascii import unhexlify as a2b_hex from utils import keypath_to_str @@ -174,7 +179,7 @@ def _compute_silent_payment_output_script(outpoints, A_sum_bytes, ecdh_share_byt return b"\x51\x20" + x_only -def _compute_silent_payment_spending_privkey(b_spend_bytes, sp_tweak_bytes): +def compute_silent_payment_spending_privkey(b_spend_bytes, sp_tweak_bytes): """ Compute private key for spending a silent payment output @@ -618,30 +623,24 @@ def _compute_and_store_ecdh_shares(self, sv): bool: True if shares were computed False if we have no signable inputs """ - # Collect per-input private keys for eligible inputs we own - # Track foreign ownership: if _derive_input_privkey returns None for an input - # that has derivation paths, that input belongs to another signer has_foreign = False - input_material = [] # list of (inp, privkey_int) + input_material = [] # list of [(inp, privkey_bytes), ...] for inp in self.inputs: - if not inp.sp_idxs or not self._is_input_eligible(inp): + if not self._is_input_eligible(inp): continue - input_sk = self._derive_input_privkey(inp, sv) - if input_sk: - input_material.append((inp, input_sk)) - elif inp.taproot_subpaths or inp.subpaths: - has_foreign = True - - # FIXME: is this the right approach? cosign_xfp? - # Detect foreign eligible inputs (different XFP, no sp_idxs) - if not has_foreign: - for inp in self.inputs: - if inp.sp_idxs: - continue - if self._is_input_eligible(inp) and self._pubkey_from_input(inp): + # Classify each eligible input: + # ours: sp_idxs set AND _derive_input_privkey yields a key + # foreign: sp_idxs set but derivation returns None (cosigner or WIF-store path) + # foreign: no sp_idxs but a contributing pubkey is identifiable (other signer's input) + if inp.sp_idxs: + input_sk = self._derive_input_privkey(inp, sv) + if input_sk is not None: + input_material.append((inp, input_sk)) + else: has_foreign = True - break + elif self._pubkey_from_input(inp): + has_foreign = True if not input_material: return False @@ -765,57 +764,43 @@ def _get_silent_payment_scan_keys(self): def _derive_input_privkey(self, input, sv): """ - Derive the contributing private key for an eligible input + Derive the contributing private key for our inputs - Note: - For silent payment inputs, derives from sp_spend_bip32_derivation and sp_tweak - For taproot inputs, uses the internal key's derivation path (ik_idx), XFP-checked - For non-taproot inputs, uses the first matching BIP32 derivation path + Derivation path source depends on input type: + - SP-spend: sp_spend_bip32_derivation + - P2TR: taproot_subpaths[ik_idx[0]] + - other: subpaths[sp_idxs[0]] Returns: - bytes: Derived private key as (32-byte scalar) or None if not eligible + bytes: Derived private key (32-byte scalar) or None """ - privkey = None + if not input.sp_idxs: + return None - if input.sp_tweak and input.sp_spend_bip32_derivation: + if input.is_sp_spend: _, val_coords = input.sp_spend_bip32_derivation xfp_path = self.parse_xfp_path(val_coords) - if xfp_path[0] == self.my_xfp: - privkey = _compute_silent_payment_spending_privkey(self._path_to_privkey(xfp_path, sv), input.sp_tweak) - else: - spk = input.utxo_spk - if spk and _is_p2tr(spk): - if input.ik_idx is not None and input.taproot_subpaths: - _, path_coords = input.taproot_subpaths[input.ik_idx[0]] - xfp_path = self.parse_xfp_path(path_coords[2]) - if xfp_path[0] == self.my_xfp: - privkey = self._path_to_privkey(xfp_path, sv) - if input.taproot_internal_key: - privkey = self._normalize_p2tr_privkey(privkey, self.get(input.taproot_internal_key)) - else: - for xfp_path in self._iter_input_xfp_paths(input): - if xfp_path[0] == self.my_xfp: - privkey = self._path_to_privkey(xfp_path, sv) - break - return privkey - - def _iter_input_xfp_paths(self, input): - """ - Iterate over all BIP32 derivation paths for an input, yielding parsed xfp_path tuples - - Note: - For taproot inputs, yields paths from taproot_subpaths (path_coords[2]) - For non-taproot inputs, yields paths from subpaths (path_coords) + if xfp_path[0] != self.my_xfp: + return None + return compute_silent_payment_spending_privkey(self._path_to_privkey(xfp_path, sv), input.sp_tweak) - Returns: - generator: Parsed xfp_path tuples for all derivation paths in the input - """ - if input.taproot_subpaths: - for _, path_coords in input.taproot_subpaths: - yield self.parse_xfp_path(path_coords[2]) - elif input.subpaths: - for _, path_coords in input.subpaths: - yield self.parse_xfp_path(path_coords) + spk = input.utxo_spk + if spk and _is_p2tr(spk): + if not input.taproot_subpaths or not input.ik_idx: + return None + _, path_coords = input.taproot_subpaths[input.ik_idx[0]] + xfp_path = self.parse_xfp_path(path_coords[2]) + if xfp_path[0] != self.my_xfp: + return None + return self._tweak_p2tr_privkey(self._path_to_privkey(xfp_path, sv), input) + + if not input.subpaths: + return None + _, path_coords = input.subpaths[input.sp_idxs[0]] + xfp_path = self.parse_xfp_path(path_coords) + if xfp_path[0] != self.my_xfp: + return None + return self._path_to_privkey(xfp_path, sv) def _path_to_privkey(self, xfp_path, sv): """ @@ -826,3 +811,18 @@ def _path_to_privkey(self, xfp_path, sv): """ node = sv.derive_path(keypath_to_str(xfp_path, skip=1), register=False) return node.privkey() + + def _tweak_p2tr_privkey(self, privkey_bytes, input): + """ + Tweak a P2TR private key per BIP-341, returning the BIP-340-normalized scalar + + Returns: + bytes: Tweaked private key (32-byte scalar) + """ + kp = ngu.secp256k1.keypair(privkey_bytes) + tweak_data = kp.xonly_pubkey().to_bytes() + if input.taproot_merkle_root: + tweak_data += self.get(input.taproot_merkle_root) + t = ngu.hash.sha256t(TAP_TWEAK_H, tweak_data, True) + kpt = kp.xonly_tweak_add(t) + return _negate_if_odd_y(kpt.privkey()) diff --git a/testing/devtest/unit_silentpayments.py b/testing/devtest/unit_silentpayments.py index a2b61a2f4..770bc6776 100644 --- a/testing/devtest/unit_silentpayments.py +++ b/testing/devtest/unit_silentpayments.py @@ -16,7 +16,7 @@ _combine_pubkeys, _compute_shared_secret_tweak, _compute_silent_payment_output_script, - _compute_silent_payment_spending_privkey, + compute_silent_payment_spending_privkey, _is_p2pkh, _is_p2wpkh, _is_p2tr, @@ -200,17 +200,18 @@ # Spending Privkey # --------------------------------------------------------------------------- -result = _compute_silent_payment_spending_privkey((1).to_bytes(32, "big"), (42).to_bytes(32, "big")) +result = compute_silent_payment_spending_privkey((1).to_bytes(32, "big"), (42).to_bytes(32, "big")) assert isinstance(result, bytes) and len(result) == 32 assert result != (1).to_bytes(32, "big") assert 0 < int.from_bytes(result, "big") < ngu.secp256k1.curve_order_int() try: - _compute_silent_payment_spending_privkey(b"\x00" * 32, b"\x00" * 32) + compute_silent_payment_spending_privkey(b"\x00" * 32, b"\x00" * 32) assert False, "Should raise" except ValueError as e: assert "not in valid scalar range" in str(e) + # --------------------------------------------------------------------------- # Mock Infrastructure # --------------------------------------------------------------------------- diff --git a/testing/devtest/verify_sp_outputs.py b/testing/devtest/verify_sp_outputs.py index 95d191d79..5fada052c 100644 --- a/testing/devtest/verify_sp_outputs.py +++ b/testing/devtest/verify_sp_outputs.py @@ -21,7 +21,7 @@ _compute_ecdh_share, _compute_input_hash, _compute_silent_payment_output_script, - _compute_silent_payment_spending_privkey, + compute_silent_payment_spending_privkey, _negate_if_odd_y, _sum_privkeys, SilentPaymentsMixin, @@ -58,6 +58,10 @@ def __init__(self): self.sp_idxs = None self.ik_idx = None + @property + def is_sp_spend(self): + return bool(self.sp_tweak and self.sp_spend_bip32_derivation) + class MockOutput: def __init__(self): @@ -241,7 +245,7 @@ def _build_mock_psbt(params): elif mode == "compute_spending_privkey": b_spend = a2b_hex(params["b_spend"]) tweak = a2b_hex(params["tweak"]) - result = _compute_silent_payment_spending_privkey(b_spend, tweak) + result = compute_silent_payment_spending_privkey(b_spend, tweak) RV.write(b2a_hex(result).decode()) elif mode == "negate_if_odd_y": diff --git a/testing/test_silentpayments.py b/testing/test_silentpayments.py new file mode 100644 index 000000000..80cdcb454 --- /dev/null +++ b/testing/test_silentpayments.py @@ -0,0 +1,407 @@ +# (c) Copyright 2024 by Coinkite Inc. This file is covered by license found in COPYING-CC. +# +# test_silentpayments.py - Simulator-based integration tests for BIP-375 Silent Payments +# +# Tests the full firmware signing path: PSBT construction -> upload to simulator -> +# firmware computes ECDH shares, DLEQ proofs, and set output scripts -> verify +# +# Requires simulator running. Run with: pytest test_silentpayments.py +# + +import pytest +import struct +import time +from binascii import unhexlify +from binascii import hexlify +from bip32 import BIP32Node +from ckcc_protocol.protocol import CCProtoError +from constants import simulator_fixed_tprv +from psbt import BasicPSBT +from pysecp256k1 import ( + ec_pubkey_parse, + ec_pubkey_serialize, + ec_pubkey_tweak_add, + tagged_sha256, +) +from pysecp256k1.extrakeys import xonly_pubkey_from_pubkey, xonly_pubkey_serialize +from sp_helpers import ( + _sim_sp, + _sim_get_ecdh_and_pubkey, + _sim_get_outpoints, + _sim_verify_dleq, + _sim_compute_output_script, +) + + +# --------------------------------------------------------------------------- +# Test SP recipient keys (any valid secp256k1 points; these are the recipient's +# keys, not the signer's). Taken from BIP-352 test vectors; sorted lexicographically +# so multi-output tests can use them in correct k-assignment order. +# --------------------------------------------------------------------------- + +SCAN_KEY = unhexlify("03af606abaa5e29a89b93bf971c21e46dd2797aee31273c47f979a102eb51c3629") +SCAN_KEY_2 = unhexlify("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798") + +# Sorted: SPEND_KEY_A < SPEND_KEY_B < SPEND_KEY_C (lexicographic) +SPEND_KEY_A = unhexlify("022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4") +SPEND_KEY_B = unhexlify("02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5") +SPEND_KEY_C = unhexlify("02fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556") + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _add_sp_outputs(psbt, sp_outputs): + """Set sp_v0_info on specified outputs and clear script for firmware to compute.""" + for idx, scan_key, spend_key in sp_outputs: + psbt.outputs[idx].sp_v0_info = scan_key + spend_key + psbt.outputs[idx].script = None + + +def _verify_sp_outputs(sim_exec, sim_execfile, tp, scan_key, spend_keys_in_order): + """Verify SP output scripts match expected derivation for a given scan key.""" + assert scan_key in tp.sp_global_ecdh_shares, "Missing global ECDH share" + assert scan_key in tp.sp_global_dleq_proofs, "Missing global DLEQ proof" + + ecdh_share = tp.sp_global_ecdh_shares[scan_key] + dleq_proof = tp.sp_global_dleq_proofs[scan_key] + + assert len(ecdh_share) == 33 and ecdh_share[0] in (0x02, 0x03) + assert len(dleq_proof) == 64 + + _, summed_pubkey = _sim_get_ecdh_and_pubkey(sim_exec, sim_execfile, tp, scan_key) + _sim_verify_dleq(sim_exec, sim_execfile, summed_pubkey, scan_key, ecdh_share, dleq_proof) + + outpoints = _sim_get_outpoints(sim_exec, sim_execfile, tp) + sp_outs = [o for o in tp.outputs if o.sp_v0_info and o.sp_v0_info[:33] == scan_key] + assert len(sp_outs) == len(spend_keys_in_order) + + for k, (outp, sk) in enumerate(zip(sp_outs, spend_keys_in_order)): + assert outp.script is not None and len(outp.script) == 34 + assert outp.script[0] == 0x51 + expected = _sim_compute_output_script(sim_exec, sim_execfile, outpoints, summed_pubkey, ecdh_share, sk, k) + assert outp.script == expected + + +def _compute_foreign_share(sim_exec, sim_execfile, privkey_int, scan_key): + """Compute ECDH share and DLEQ proof for a foreign input via simulator.""" + rv = _sim_sp( + sim_exec, + sim_execfile, + "compute_foreign_share", + { + "privkey": "%064x" % privkey_int, + "scan_key": scan_key.hex(), + }, + ) + parts = rv.split(",") + return bytes.fromhex(parts[0]), bytes.fromhex(parts[1]) + + +# --------------------------------------------------------------------------- +# Basic Tests +# --------------------------------------------------------------------------- + + +def test_sp_signing_story(dev, fake_txn, start_sign, end_sign, cap_story): + """SP output: approval story shows SP address, no unknown-script warning.""" + xp = dev.master_xpub + + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + + psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + + time.sleep(0.1) + title, story = cap_story() + assert title == "OK TO SEND?" + assert "not well understood script" not in story + assert "sp1" in story + + end_sign(accept=True, finalize=False) + + +def test_sp_p2wpkh_input(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): + """Single P2WPKH input signing to a single SP output.""" + xp = dev.master_xpub + + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + + psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + signed = end_sign(accept=True, finalize=False) + + tp = BasicPSBT().parse(signed) + _verify_sp_outputs(sim_exec, sim_execfile, tp, SCAN_KEY, [SPEND_KEY_B]) + + +def test_sp_p2tr_input(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): + """Single P2TR input signing to a single SP output.""" + xp = dev.master_xpub + + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + + psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2tr", psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + signed = end_sign(accept=True, finalize=False) + + tp = BasicPSBT().parse(signed) + _verify_sp_outputs(sim_exec, sim_execfile, tp, SCAN_KEY, [SPEND_KEY_B]) + + +def test_sp_mixed_inputs(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): + """P2WPKH + P2TR inputs combined produce a single SP output from summed privkey.""" + xp = dev.master_xpub + + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + + psbt_bytes = fake_txn([["p2wpkh"], ["p2tr"]], 1, xp, psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + signed = end_sign(accept=True, finalize=False) + + tp = BasicPSBT().parse(signed) + _verify_sp_outputs(sim_exec, sim_execfile, tp, SCAN_KEY, [SPEND_KEY_B]) + + +# --------------------------------------------------------------------------- +# Mixed output tests +# --------------------------------------------------------------------------- + + +def test_sp_three_outputs_same_scan_key(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): + """Three SP outputs with same scan key: k=0,1,2 all produce distinct scripts.""" + xp = dev.master_xpub + + def sp_hacker(psbt): + _add_sp_outputs( + psbt, + [ + (0, SCAN_KEY, SPEND_KEY_A), + (1, SCAN_KEY, SPEND_KEY_B), + (2, SCAN_KEY, SPEND_KEY_C), + ], + ) + + psbt_bytes = fake_txn(1, 3, xp, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + signed = end_sign(accept=True, finalize=False) + + tp = BasicPSBT().parse(signed) + _verify_sp_outputs(sim_exec, sim_execfile, tp, SCAN_KEY, [SPEND_KEY_A, SPEND_KEY_B, SPEND_KEY_C]) + + sp_outs = [o for o in tp.outputs if o.sp_v0_info and o.sp_v0_info[:33] == SCAN_KEY] + scripts = [o.script for o in sp_outs] + assert len(set(scripts)) == 3, "All three k-indexed scripts must be distinct" + + +def test_sp_two_outputs_different_scan_keys(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): + """Two SP outputs with different scan keys each get their own ECDH share.""" + xp = dev.master_xpub + + def sp_hacker(psbt): + _add_sp_outputs( + psbt, + [ + (0, SCAN_KEY, SPEND_KEY_B), + (1, SCAN_KEY_2, SPEND_KEY_A), + ], + ) + + psbt_bytes = fake_txn(1, 2, xp, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + signed = end_sign(accept=True, finalize=False) + + tp = BasicPSBT().parse(signed) + _verify_sp_outputs(sim_exec, sim_execfile, tp, SCAN_KEY, [SPEND_KEY_B]) + _verify_sp_outputs(sim_exec, sim_execfile, tp, SCAN_KEY_2, [SPEND_KEY_A]) + + assert tp.sp_global_ecdh_shares[SCAN_KEY] != tp.sp_global_ecdh_shares[SCAN_KEY_2], ( + "Different scan keys must produce different ECDH shares" + ) + + +def test_sp_mixed_output_types(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): + """SP output alongside a regular output: normal output script is preserved.""" + xp = dev.master_xpub + original_normal_script = None + + def sp_hacker(psbt): + nonlocal original_normal_script + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + original_normal_script = psbt.outputs[1].script + + psbt_bytes = fake_txn(1, 2, xp, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + signed = end_sign(accept=True, finalize=False) + + tp = BasicPSBT().parse(signed) + _verify_sp_outputs(sim_exec, sim_execfile, tp, SCAN_KEY, [SPEND_KEY_B]) + + assert tp.outputs[1].script == original_normal_script, "Normal output script must not be modified by SP processing" + + +# --------------------------------------------------------------------------- +# Multi-signer - complete coverage scenario tests +# --------------------------------------------------------------------------- + + +def test_sp_all_owned_multi_input(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): + """Scenario (a): all inputs owned -> global ECDH share -> compute scripts -> sign.""" + xp = dev.master_xpub + + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + + psbt_bytes = fake_txn([["p2wpkh"], ["p2wpkh"]], 1, xp, psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + signed = end_sign(accept=True, finalize=False) + + tp = BasicPSBT().parse(signed) + + assert tp.sp_global_ecdh_shares and SCAN_KEY in tp.sp_global_ecdh_shares + assert tp.sp_global_dleq_proofs and SCAN_KEY in tp.sp_global_dleq_proofs + for inp in tp.inputs: + assert not inp.sp_ecdh_shares, "Per-input shares must not exist in single-signer path" + + _verify_sp_outputs(sim_exec, sim_execfile, tp, SCAN_KEY, [SPEND_KEY_B]) + + for i, inp in enumerate(tp.inputs): + assert inp.part_sigs or inp.taproot_key_sig, f"Input {i} missing signature" + + +def test_sp_partial_owned_coverage_complete(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): + """Scenario (b): some owned + foreign with pre-existing shares -> coverage complete -> sign.""" + xp = dev.master_xpub + + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + foreign_sub = psbt._foreign_mk.subkey_for_path("0/1") + foreign_privkey_int = int.from_bytes(foreign_sub.privkey(), "big") + ecdh_share, proof = _compute_foreign_share(sim_exec, sim_execfile, foreign_privkey_int, SCAN_KEY) + psbt.inputs[1].sp_ecdh_shares = {SCAN_KEY: ecdh_share} + psbt.inputs[1].sp_dleq_proofs = {SCAN_KEY: proof} + + psbt_bytes = fake_txn( + [["p2wpkh"], ["p2wpkh", None, None, False]], + 1, + xp, + psbt_v2=True, + psbt_hacker=sp_hacker, + ) + start_sign(psbt_bytes) + signed = end_sign(accept=True, finalize=False) + + tp = BasicPSBT().parse(signed) + + assert not tp.sp_global_ecdh_shares, "Global ECDH shares must not exist in multi-signer path" + + assert tp.inputs[0].sp_ecdh_shares and SCAN_KEY in tp.inputs[0].sp_ecdh_shares + assert tp.inputs[1].sp_ecdh_shares and SCAN_KEY in tp.inputs[1].sp_ecdh_shares + + sp_outs = [o for o in tp.outputs if o.sp_v0_info] + assert len(sp_outs) == 1 + assert sp_outs[0].script is not None and sp_outs[0].script[0] == 0x51 + + # Verify output script by combining per-input ECDH shares via firmware + ecdh_share, summed_pubkey = _sim_get_ecdh_and_pubkey(sim_exec, sim_execfile, tp, SCAN_KEY) + outpoints = _sim_get_outpoints(sim_exec, sim_execfile, tp) + expected = _sim_compute_output_script(sim_exec, sim_execfile, outpoints, summed_pubkey, ecdh_share, SPEND_KEY_B, 0) + assert sp_outs[0].script == expected + + assert tp.inputs[0].part_sigs, "Owned input must have signature" + assert not tp.inputs[1].part_sigs, "Foreign input must not have signature" + + +# --------------------------------------------------------------------------- +# BIP-376 Silent Payments spend sp output test +# --------------------------------------------------------------------------- + + +def test_sp_spend_silent_payment_output(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): + """Spend from SP output with correct proof -> sign. + + Uses the canonical BIP-376 PSBT shape: the input has PSBT_IN_SP_TWEAK and + PSBT_IN_SP_SPEND_BIP32_DERIVATION (BIP-352-style path), with no + PSBT_IN_TAP_BIP32_DERIVATION or PSBT_IN_TAP_INTERNAL_KEY. + """ + xp = dev.master_xpub + + SP_TWEAK = bytes.fromhex("a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2") + # BIP-352 spend-key path: m/352h/1h/0h/0h/0 (testnet coin=1, account=0, spend=0, index=0) + SP_SPEND_PATH = "352h/1h/0h/0h/0" + SP_SPEND_PATH_INTS = [ + 352 | 0x80000000, + 1 | 0x80000000, + 0 | 0x80000000, + 0 | 0x80000000, + 0, + ] + + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + + inp = psbt.inputs[0] + + master = BIP32Node.from_wallet_key(simulator_fixed_tprv) + mfp = master.fingerprint() + B_spend = master.subkey_for_path(SP_SPEND_PATH).sec() + xfp_and_path = mfp + struct.pack("<" + "I" * len(SP_SPEND_PATH_INTS), *SP_SPEND_PATH_INTS) + + # Compute SP output key: P_k = B_spend + t_k * G + parsed = ec_pubkey_parse(B_spend) + tweaked = ec_pubkey_tweak_add(parsed, SP_TWEAK) + tweaked_xonly_obj, _ = xonly_pubkey_from_pubkey(tweaked) + output_xonly = xonly_pubkey_serialize(tweaked_xonly_obj) + + # Replace witness UTXO scriptPubKey with the SP output key + amount_bytes = inp.witness_utxo[:8] + inp.witness_utxo = amount_bytes + b"\x22\x51\x20" + output_xonly + print(f"output_xonly: {output_xonly.hex()}") + + # BIP-376 inputs do not carry standard taproot derivation + inp.taproot_bip32_paths = {} + inp.taproot_internal_key = None + + inp.sp_tweak = SP_TWEAK + # sp_spend_bip32_derivation: key=33-byte compressed B_spend, value=mfp+path + inp.sp_spend_bip32_derivation = {B_spend: xfp_and_path} + + psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2tr", psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + signed = end_sign(accept=True, finalize=False) + + tp = BasicPSBT().parse(signed) + + assert tp.sp_global_ecdh_shares and SCAN_KEY in tp.sp_global_ecdh_shares + assert tp.sp_global_dleq_proofs and SCAN_KEY in tp.sp_global_dleq_proofs + + _verify_sp_outputs(sim_exec, sim_execfile, tp, SCAN_KEY, [SPEND_KEY_B]) + + for i, inp in enumerate(tp.inputs): + assert inp.part_sigs or inp.taproot_key_sig, f"Input {i} missing signature" + + +# --------------------------------------------------------------------------- +# Handle failure test +# --------------------------------------------------------------------------- + + +def test_exit_gracefully_on_sp_validation_failure(dev, fake_txn, start_sign, end_sign): + """Invalid SP output info should cause signing to fail with a clear error message.""" + xp = dev.master_xpub + + def sp_hacker(psbt): + # Set sp_v0_info with incorrect length (should be 64 bytes) + psbt.outputs[0].sp_v0_info = b"\x00" * 10 + psbt.outputs[0].script = None + + psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + with pytest.raises(CCProtoError, match="SP_V0_INFO wrong size"): + end_sign(accept=False, finalize=False) diff --git a/testing/txn.py b/testing/txn.py index b8e510d67..b7ee3f90a 100644 --- a/testing/txn.py +++ b/testing/txn.py @@ -264,6 +264,7 @@ def doit(inputs, outputs, master_xpub=None, psbt_hacker=None, add_xpub=None, psb # last minute chance to mod PSBT object if psbt_hacker: + psbt._foreign_mk = foreign_mk psbt_hacker(psbt) rv = BytesIO() From c20e7ea58deb3dc3150d746f01f290c7dd8eeef8 Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Wed, 25 Mar 2026 16:58:22 -0400 Subject: [PATCH 13/23] SP: introduce partial share contribution workflow Add test to validate 'Contribute Shares?' UX Simplify SP preview and signing code paths --- shared/auth.py | 25 ++++++++++++++++++++++++- shared/psbt.py | 11 ++++------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/shared/auth.py b/shared/auth.py index 67c466de3..2a71064f5 100644 --- a/shared/auth.py +++ b/shared/auth.py @@ -434,7 +434,30 @@ async def interact(self): # Silent Payments: Validate and pre-process Silent Payments outputs to make preview useful if self.psbt.has_silent_payment_outputs(): with stash.SensitiveValues() as sv: - self.psbt.process_silent_payments(sv) + if not self.psbt.process_silent_payments(sv): + # Coverage incomplete: shares computed but waiting on other signers + # Skip normal approval flow — prompt user to contribute shares, then save + del args + ch = await ux_show_story( + "Silent payment ECDH shares will be added to this transaction.\n\n" + "Other signers must contribute their shares before signing can proceed.\n\n" + "Press %s to contribute shares. %s to abort." % (OK, X), + title="CONTRIBUTE SHARES?" + ) + if ch != 'y': + self.refused = True + await ux_dramatic_pause("Refused.", 1) + del self.psbt + self.done() + return + try: + await done_signing(self.psbt, self, self.input_method, + self.filename, self.output_encoder, + finalize=False) + self.done() + except BaseException as exc: + return await self.failure("PSBT output failed", exc) + return self.psbt.consider_outputs(*args, cosign_xfp=ccc_c_xfp) del args # not needed anymore diff --git a/shared/psbt.py b/shared/psbt.py index 1c5ad1d35..480f34613 100644 --- a/shared/psbt.py +++ b/shared/psbt.py @@ -2846,13 +2846,10 @@ def sign_it(self, alternate_secret=None, my_xfp=None): # Silent Payment Processing if self.has_silent_payment_outputs(): - sp_ready = self.process_silent_payments(sv) - if not sp_ready: - # Silent Payments: must not sign if output scripts not set - # TODO: is this still necessary? if so needs test - self.ux_notes.append(("Silent Payments", - "ECDH shares added. Signatures withheld until all signers contribute.")) - return + if not self.process_silent_payments(sv): + # Silent Payments: must not sign if output scripts not set for all signers + # Defensive re-check - ApproveTransaction::interact should handle this case before reaching signing + raise FatalPSBTIssue("Silent Payments: Signing cannot proceed until all signers contribute their shares") # progress dis.fullscreen('Signing...') From a0a04c0f67ed04747e51d28bc9917c3cd99a10cd Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Wed, 25 Mar 2026 17:29:21 -0400 Subject: [PATCH 14/23] SP: introduce UI change output validation Add silent payments label change tests - Detect presence or absence of 'Change back:' in UI story Add multi-signer incomplete coverage scenario tests --- shared/auth.py | 12 ++-- shared/psbt.py | 7 +- shared/silentpayments.py | 80 +++++++++++++++------- testing/test_silentpayments.py | 121 +++++++++++++++++++++++++++++++-- 4 files changed, 178 insertions(+), 42 deletions(-) diff --git a/shared/auth.py b/shared/auth.py index 2a71064f5..72388b44b 100644 --- a/shared/auth.py +++ b/shared/auth.py @@ -729,15 +729,16 @@ def output_summary_text(self, msg): total_change = 0 has_change = False - # TODO: Test pay to silent payment change address - for idx, tx_out in self.psbt.output_iter(): outp = self.psbt.outputs[idx] if outp.is_change: has_change = True total_change += tx_out.nValue if len(largest_change) < MAX_VISIBLE_CHANGE: - largest_change.append((tx_out.nValue, self.chain.render_address(tx_out.scriptPubKey))) + addr = self.chain.render_address(tx_out.scriptPubKey) + if outp.sp_v0_info: + addr += '\n' + self.psbt.render_silent_payment_output_string(outp) + largest_change.append((tx_out.nValue, addr)) if len(largest_change) == MAX_VISIBLE_CHANGE: largest_change = sorted(largest_change, key=lambda x: x[0], reverse=True) continue @@ -765,7 +766,10 @@ def output_summary_text(self, msg): largest.pop(-1) if outp.is_change: - ret = (here, self.chain.render_address(tx_out.scriptPubKey)) + addr = self.chain.render_address(tx_out.scriptPubKey) + if outp.sp_v0_info: + addr += '\n' + self.psbt.render_silent_payment_output_string(outp) + ret = (here, addr) else: rendered, _ = self.render_output(tx_out) ret = (here, rendered) diff --git a/shared/psbt.py b/shared/psbt.py index 480f34613..4a0ad7e21 100644 --- a/shared/psbt.py +++ b/shared/psbt.py @@ -1477,12 +1477,7 @@ def output_iter(self, start=0, stop=None): for idx in range(start, stop): out = self.outputs[idx] amount = unpack(" coverage incomplete -> save PSBT.""" + xp = dev.master_xpub + + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + + psbt_bytes = fake_txn( + [["p2wpkh"], ["p2wpkh", None, None, False]], + 1, + xp, + psbt_v2=True, + psbt_hacker=sp_hacker, + ) + start_sign(psbt_bytes) + + time.sleep(0.1) + title, story = cap_story() + assert title == "CONTRIBUTE SHARES?" + assert "ECDH shares" in story + assert "Other signers" in story + + signed = end_sign(accept=True, finalize=False) + + tp = BasicPSBT().parse(signed) + + assert not tp.sp_global_ecdh_shares, ( + "Global ECDH shares must not exist in multi-signer path" + ) + + assert tp.inputs[0].sp_ecdh_shares and SCAN_KEY in tp.inputs[0].sp_ecdh_shares + assert tp.inputs[0].sp_dleq_proofs and SCAN_KEY in tp.inputs[0].sp_dleq_proofs + assert not tp.inputs[1].sp_ecdh_shares + + sp_outs = [o for o in tp.outputs if o.sp_v0_info] + assert len(sp_outs) == 1 + assert not sp_outs[0].script, ( + "Output script must NOT be set when coverage is incomplete" + ) + + for i, inp in enumerate(tp.inputs): + assert not inp.part_sigs, f"Input {i} must not have signature" + assert not inp.taproot_key_sig, f"Input {i} must not have taproot signature" + + +def test_sp_partial_owned_coverage_incomplete_refused( + dev, fake_txn, start_sign, end_sign, cap_story +): + """Refuse to contribute SP shares -> CCUserRefused.""" + xp = dev.master_xpub + + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + + psbt_bytes = fake_txn( + [["p2wpkh"], ["p2wpkh", None, None, False]], + 1, + xp, + psbt_v2=True, + psbt_hacker=sp_hacker, + ) + start_sign(psbt_bytes) + + time.sleep(0.1) + title, _ = cap_story() + assert title == "CONTRIBUTE SHARES?" + + end_sign(accept=False) + + # --------------------------------------------------------------------------- # BIP-376 Silent Payments spend sp output test # --------------------------------------------------------------------------- @@ -405,3 +477,38 @@ def sp_hacker(psbt): start_sign(psbt_bytes) with pytest.raises(CCProtoError, match="SP_V0_INFO wrong size"): end_sign(accept=False, finalize=False) + + +# --------------------------------------------------------------------------- +# Silent Payments Change Detection Tests +# --------------------------------------------------------------------------- + + +def test_sp_spend_to_labeled_change_address( + dev, fake_txn, start_sign, end_sign, cap_story +): + """Two SP outputs: output 0 is a send (no label), output 1 is change (label=0 per BIP-352 convention).""" + xp = dev.master_xpub + + def sp_hacker(psbt): + _add_sp_outputs( + psbt, + [ + (0, SCAN_KEY, SPEND_KEY_B), + (1, SCAN_KEY_2, SPEND_KEY_A), + ], + ) + psbt.outputs[1].sp_v0_label = b"\x00\x00\x00\x00" + + psbt_bytes = fake_txn( + 1, 2, xp, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker + ) + start_sign(psbt_bytes) + + title, story = cap_story() + assert title == "OK TO SEND?" + assert "not well understood script" not in story + assert "sp1" in story + assert "Change back:" in story + + end_sign(accept=False, finalize=False) From 7508f9fefc252311bcf4f2aebfe9156bf84e6022 Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Sun, 26 Apr 2026 08:49:46 -0400 Subject: [PATCH 15/23] SP: add sp spend derivation path tests Verify SP spend path was derived from m/352h Add signature verification for taproot inputs and SP outputs --- testing/devtest/verify_sp_outputs.py | 45 ++++++ testing/sp_helpers.py | 51 ++++-- testing/test_silentpayments.py | 223 ++++++++++++++++++++------- 3 files changed, 250 insertions(+), 69 deletions(-) diff --git a/testing/devtest/verify_sp_outputs.py b/testing/devtest/verify_sp_outputs.py index 5fada052c..bd1effc5b 100644 --- a/testing/devtest/verify_sp_outputs.py +++ b/testing/devtest/verify_sp_outputs.py @@ -12,16 +12,20 @@ import ngu import struct import ujson +from uio import BytesIO from ubinascii import hexlify as b2a_hex from ubinascii import unhexlify as a2b_hex from dleq import generate_dleq_proof, verify_dleq_proof from exceptions import FatalPSBTIssue +from psbt import psbtObject +from serializations import SIGHASH_DEFAULT from silentpayments import ( _combine_pubkeys, _compute_ecdh_share, _compute_input_hash, _compute_silent_payment_output_script, compute_silent_payment_spending_privkey, + _compute_silent_payment_spending_xonly, _negate_if_odd_y, _sum_privkeys, SilentPaymentsMixin, @@ -248,7 +252,48 @@ def _build_mock_psbt(params): result = compute_silent_payment_spending_privkey(b_spend, tweak) RV.write(b2a_hex(result).decode()) +elif mode == "compute_spend_output_xonly": + B_spend = a2b_hex(params["B_spend"]) + sp_tweak = a2b_hex(params["sp_tweak"]) + result = _compute_silent_payment_spending_xonly(B_spend, sp_tweak) + RV.write(b2a_hex(result).decode()) + elif mode == "negate_if_odd_y": privkey = a2b_hex(params["privkey"]) result = _negate_if_odd_y(privkey) RV.write(b2a_hex(result).decode()) + +elif mode == "verify_taproot_key_spend_signature": + psbt = psbtObject.read_psbt(BytesIO(a2b_hex(params["psbt"]))) + input_index = params["input_index"] + expected = params.get("expected", True) + + if psbt.version is None: + psbt.version = 2 if psbt.txn is None else 0 + psbt.is_v2 = psbt.version >= 2 + + for idx, inp in enumerate(psbt.inputs): + utxo = inp.get_utxo(idx) + inp.amount = utxo.nValue + inp.utxo_spk = utxo.scriptPubKey + + inp = psbt.inputs[input_index] + # P2TR scriptPubKey: OP_1 (0x51) OP_PUSHBYTES_32 (0x20) <32-byte x-only> + output_xonly = inp.utxo_spk[2:34] + + sig_coords = inp.taproot_key_sig + assert sig_coords, "Missing taproot key signature" + sig = psbt.get(sig_coords) + + hash_type = SIGHASH_DEFAULT if len(sig) == 64 else sig[-1] + digest = psbt.make_txn_taproot_sighash(input_index, hash_type=hash_type) + + try: + result = ngu.secp256k1.verify_schnorr(sig[:64], digest, ngu.secp256k1.xonly_pubkey(output_xonly)) + except Exception: + result = False + + assert result == expected, "Taproot spend signature verify: expected %s got %s" % ( + expected, + result, + ) diff --git a/testing/sp_helpers.py b/testing/sp_helpers.py index 147fb0895..0d1bd0a22 100644 --- a/testing/sp_helpers.py +++ b/testing/sp_helpers.py @@ -107,12 +107,8 @@ def _serialize_psbt(psbt): return { "txn_modifiable": psbt.txn_modifiable, - "global_ecdh": { - k.hex(): v.hex() for k, v in psbt.sp_global_ecdh_shares.items() - }, - "global_dleq": { - k.hex(): v.hex() for k, v in psbt.sp_global_dleq_proofs.items() - }, + "global_ecdh": {k.hex(): v.hex() for k, v in psbt.sp_global_ecdh_shares.items()}, + "global_dleq": {k.hex(): v.hex() for k, v in psbt.sp_global_dleq_proofs.items()}, "inputs": inputs, "outputs": outputs, } @@ -154,9 +150,7 @@ def _sim_pubkey_from_input(sim_exec, sim_execfile, psbt, input_index): return bytes.fromhex(rv) if rv else None -def _sim_verify_dleq( - sim_exec, sim_execfile, pubkey, scan_key, ecdh_share, proof, expected=True -): +def _sim_verify_dleq(sim_exec, sim_execfile, pubkey, scan_key, ecdh_share, proof, expected=True): _sim_sp( sim_exec, sim_execfile, @@ -184,9 +178,20 @@ def _sim_compute_ecdh_share(sim_exec, sim_execfile, privkey, scan_key): return bytes.fromhex(rv) -def _sim_compute_output_script( - sim_exec, sim_execfile, outpoints, summed_pubkey, ecdh_share, spend_key, k -): +def _sim_compute_spend_output_xonly(sim_exec, sim_execfile, B_spend, sp_tweak): + rv = _sim_sp( + sim_exec, + sim_execfile, + "compute_spend_output_xonly", + { + "B_spend": B_spend.hex(), + "sp_tweak": sp_tweak.hex(), + }, + ) + return bytes.fromhex(rv) + + +def _sim_compute_output_script(sim_exec, sim_execfile, outpoints, summed_pubkey, ecdh_share, spend_key, k): rv = _sim_sp( sim_exec, sim_execfile, @@ -202,6 +207,28 @@ def _sim_compute_output_script( return bytes.fromhex(rv) +def _sim_verify_taproot_key_spend_signature(sim_exec, sim_execfile, signed_psbt, input_index, expected=True): + _sim_sp( + sim_exec, + sim_execfile, + "verify_taproot_key_spend_signature", + { + "psbt": signed_psbt.hex(), + "input_index": input_index, + "expected": expected, + }, + ) + + +def _verify_signatures(sim_exec, sim_execfile, tp, signed_psbt_bytes): + """Verify Schnorr signatures for taproot key-spend inputs""" + for i, inp in enumerate(tp.inputs): + if inp.witness_utxo and len(inp.witness_utxo) >= 11: + if inp.witness_utxo[8:11] == b"\x22\x51\x20": + _sim_verify_taproot_key_spend_signature(sim_exec, sim_execfile, signed_psbt_bytes, i) + # TODO: add ECDSA verification for P2WPKH inputs + + def _sim_combine_pubkeys(sim_exec, sim_execfile, pubkeys): rv = _sim_sp( sim_exec, diff --git a/testing/test_silentpayments.py b/testing/test_silentpayments.py index e232e32cc..ae361f5f1 100644 --- a/testing/test_silentpayments.py +++ b/testing/test_silentpayments.py @@ -16,14 +16,14 @@ from bip32 import BIP32Node from ckcc_protocol.protocol import CCProtoError from psbt import BasicPSBT -from pysecp256k1 import ec_pubkey_parse, ec_pubkey_tweak_add -from pysecp256k1.extrakeys import xonly_pubkey_from_pubkey, xonly_pubkey_serialize from sp_helpers import ( _sim_sp, _sim_get_ecdh_and_pubkey, _sim_get_outpoints, _sim_verify_dleq, _sim_compute_output_script, + _sim_compute_spend_output_xonly, + _verify_signatures, ) from constants import simulator_fixed_tprv @@ -42,6 +42,17 @@ SPEND_KEY_B = unhexlify("02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5") SPEND_KEY_C = unhexlify("02fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556") +# BIP-376 SP spend constants (BIP-352 path: m/352h/1h/0h/0h/0, testnet) +SP_TWEAK = bytes.fromhex("a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2") +SP_SPEND_PATH = "352h/1h/0h/0h/0" +SP_SPEND_PATH_INTS = [ + 352 | 0x80000000, + 1 | 0x80000000, + 0 | 0x80000000, + 0 | 0x80000000, + 0, +] + # --------------------------------------------------------------------------- # Helpers @@ -95,6 +106,49 @@ def _compute_foreign_share(sim_exec, sim_execfile, privkey_int, scan_key): return bytes.fromhex(parts[0]), bytes.fromhex(parts[1]) +def _setup_sp_spend_input( + sim_exec, + sim_execfile, + inp, + sp_tweak, + path, + path_ints, + *, + wrong_tweak=None, + clear_taproot=True, +): + """Configure a PSBT input for a BIP-376 SP spend. + + Rewrites inp.witness_utxo to lock to B_spend + utxo_tweak*G, sets + inp.sp_tweak (to psbt_tweak if given, else utxo_tweak), and sets + inp.sp_spend_bip32_derivation. Optionally clears taproot derivation. + """ + master = BIP32Node.from_wallet_key(simulator_fixed_tprv) + mfp = master.fingerprint() + B_spend = master.subkey_for_path(path).sec() + xfp_and_path = mfp + struct.pack("<" + "I" * len(path_ints), *path_ints) + + xonly = _sim_compute_spend_output_xonly(sim_exec, sim_execfile, B_spend, sp_tweak) + amount_bytes = inp.witness_utxo[:8] + inp.witness_utxo = amount_bytes + b"\x22\x51\x20" + xonly + + if clear_taproot: + inp.taproot_bip32_paths = {} + inp.taproot_internal_key = None + + inp.sp_tweak = wrong_tweak if wrong_tweak is not None else sp_tweak + inp.sp_spend_bip32_derivation = {B_spend: xfp_and_path} + + +def _assert_sp_spend_success(sim_exec, sim_execfile, signed): + """Assert a signed SP spend PSBT has correct global shares and output scripts.""" + tp = BasicPSBT().parse(signed) + assert tp.sp_global_ecdh_shares and SCAN_KEY in tp.sp_global_ecdh_shares + assert tp.sp_global_dleq_proofs and SCAN_KEY in tp.sp_global_dleq_proofs + _verify_sp_outputs(sim_exec, sim_execfile, tp, SCAN_KEY, [SPEND_KEY_B]) + _verify_signatures(sim_exec, sim_execfile, tp, signed) + + # --------------------------------------------------------------------------- # Basic Tests # --------------------------------------------------------------------------- @@ -147,6 +201,7 @@ def sp_hacker(psbt): tp = BasicPSBT().parse(signed) _verify_sp_outputs(sim_exec, sim_execfile, tp, SCAN_KEY, [SPEND_KEY_B]) + _verify_signatures(sim_exec, sim_execfile, tp, signed) def test_sp_mixed_inputs(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): @@ -162,6 +217,9 @@ def sp_hacker(psbt): tp = BasicPSBT().parse(signed) _verify_sp_outputs(sim_exec, sim_execfile, tp, SCAN_KEY, [SPEND_KEY_B]) + # Schnorr verification skipped: multi-input taproot sighash covers all inputs' + # sequences, and fake_txn may not write PSBT_IN_SEQUENCE — devtest would + # reconstruct a wrong hashSequence. # --------------------------------------------------------------------------- @@ -318,9 +376,7 @@ def sp_hacker(psbt): # --------------------------------------------------------------------------- -def test_sp_partial_owned_coverage_incomplete( - dev, fake_txn, start_sign, end_sign, cap_story -): +def test_sp_partial_owned_coverage_incomplete(dev, fake_txn, start_sign, end_sign, cap_story): """Scenario (c): some owned + foreign without shares -> coverage incomplete -> save PSBT.""" xp = dev.master_xpub @@ -346,9 +402,7 @@ def sp_hacker(psbt): tp = BasicPSBT().parse(signed) - assert not tp.sp_global_ecdh_shares, ( - "Global ECDH shares must not exist in multi-signer path" - ) + assert not tp.sp_global_ecdh_shares, "Global ECDH shares must not exist in multi-signer path" assert tp.inputs[0].sp_ecdh_shares and SCAN_KEY in tp.inputs[0].sp_ecdh_shares assert tp.inputs[0].sp_dleq_proofs and SCAN_KEY in tp.inputs[0].sp_dleq_proofs @@ -356,18 +410,14 @@ def sp_hacker(psbt): sp_outs = [o for o in tp.outputs if o.sp_v0_info] assert len(sp_outs) == 1 - assert not sp_outs[0].script, ( - "Output script must NOT be set when coverage is incomplete" - ) + assert not sp_outs[0].script, "Output script must NOT be set when coverage is incomplete" for i, inp in enumerate(tp.inputs): assert not inp.part_sigs, f"Input {i} must not have signature" assert not inp.taproot_key_sig, f"Input {i} must not have taproot signature" -def test_sp_partial_owned_coverage_incomplete_refused( - dev, fake_txn, start_sign, end_sign, cap_story -): +def test_sp_partial_owned_coverage_incomplete_refused(dev, fake_txn, start_sign, end_sign, cap_story): """Refuse to contribute SP shares -> CCUserRefused.""" xp = dev.master_xpub @@ -404,59 +454,122 @@ def test_sp_spend_silent_payment_output(dev, fake_txn, start_sign, end_sign, sim """ xp = dev.master_xpub - SP_TWEAK = bytes.fromhex("a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2") - # BIP-352 spend-key path: m/352h/1h/0h/0h/0 (testnet coin=1, account=0, spend=0, index=0) - SP_SPEND_PATH = "352h/1h/0h/0h/0" - SP_SPEND_PATH_INTS = [ - 352 | 0x80000000, - 1 | 0x80000000, - 0 | 0x80000000, - 0 | 0x80000000, - 0, - ] + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + _setup_sp_spend_input( + sim_exec, + sim_execfile, + psbt.inputs[0], + SP_TWEAK, + SP_SPEND_PATH, + SP_SPEND_PATH_INTS, + clear_taproot=True, + ) + + psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2tr", psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + signed = end_sign(accept=True, finalize=False) + _assert_sp_spend_success(sim_exec, sim_execfile, signed) + + +def test_sp_spend_silent_payment_output_with_taproot(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): + """Spend from SP output with correct proof -> sign (taproot derivation retained).""" + xp = dev.master_xpub def sp_hacker(psbt): _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + _setup_sp_spend_input( + sim_exec, + sim_execfile, + psbt.inputs[0], + SP_TWEAK, + SP_SPEND_PATH, + SP_SPEND_PATH_INTS, + clear_taproot=False, + ) + + psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2tr", psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + signed = end_sign(accept=True, finalize=False) + _assert_sp_spend_success(sim_exec, sim_execfile, signed) - inp = psbt.inputs[0] - master = BIP32Node.from_wallet_key(simulator_fixed_tprv) - mfp = master.fingerprint() - B_spend = master.subkey_for_path(SP_SPEND_PATH).sec() - xfp_and_path = mfp + struct.pack("<" + "I" * len(SP_SPEND_PATH_INTS), *SP_SPEND_PATH_INTS) +def test_sp_spend_wrong_tweak_rejected(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): + """SP_TWEAK that does not match the witness UTXO output key must be rejected.""" + xp = dev.master_xpub + WRONG_TWEAK = bytes.fromhex("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - # Compute SP output key: P_k = B_spend + t_k * G - parsed = ec_pubkey_parse(B_spend) - tweaked = ec_pubkey_tweak_add(parsed, SP_TWEAK) - tweaked_xonly_obj, _ = xonly_pubkey_from_pubkey(tweaked) - output_xonly = xonly_pubkey_serialize(tweaked_xonly_obj) + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + # Witness UTXO uses SP_TWEAK; PSBT carries WRONG_TWEAK — firmware must reject this + _setup_sp_spend_input( + sim_exec, + sim_execfile, + psbt.inputs[0], + SP_TWEAK, + SP_SPEND_PATH, + SP_SPEND_PATH_INTS, + wrong_tweak=WRONG_TWEAK, + ) - # Replace witness UTXO scriptPubKey with the SP output key - amount_bytes = inp.witness_utxo[:8] - inp.witness_utxo = amount_bytes + b"\x22\x51\x20" + output_xonly - print(f"output_xonly: {output_xonly.hex()}") + psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2tr", psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + with pytest.raises(CCProtoError, match="SP_TWEAK does not match UTXO output key"): + end_sign(accept=True, finalize=False) - # BIP-376 inputs do not carry standard taproot derivation - inp.taproot_bip32_paths = {} - inp.taproot_internal_key = None - inp.sp_tweak = SP_TWEAK - # sp_spend_bip32_derivation: key=33-byte compressed B_spend, value=mfp+path - inp.sp_spend_bip32_derivation = {B_spend: xfp_and_path} +def test_sp_spend_wrong_purpose_rejected(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): + """SP_SPEND_BIP32_DERIVATION with a non-352h purpose must be rejected.""" + xp = dev.master_xpub + # BIP-84 path instead of BIP-352 + WRONG_PATH = "84h/1h/0h/0/0" + WRONG_PATH_INTS = [84 | 0x80000000, 1 | 0x80000000, 0 | 0x80000000, 0, 0] + + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + _setup_sp_spend_input( + sim_exec, + sim_execfile, + psbt.inputs[0], + SP_TWEAK, + WRONG_PATH, + WRONG_PATH_INTS, + ) psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2tr", psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) - signed = end_sign(accept=True, finalize=False) + with pytest.raises(CCProtoError, match="SP spend key must use 352h path"): + end_sign(accept=True, finalize=False) - tp = BasicPSBT().parse(signed) - assert tp.sp_global_ecdh_shares and SCAN_KEY in tp.sp_global_ecdh_shares - assert tp.sp_global_dleq_proofs and SCAN_KEY in tp.sp_global_dleq_proofs +def test_sp_spend_wrong_coin_type_rejected(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): + """SP_SPEND_BIP32_DERIVATION with coin type mismatching the network must be rejected.""" + xp = dev.master_xpub + # Simulator is testnet (coin_type=1); use mainnet coin_type=0 to trigger mismatch + WRONG_COIN_PATH = "352h/0h/0h/0h/0" + WRONG_COIN_PATH_INTS = [ + 352 | 0x80000000, + 0 | 0x80000000, + 0 | 0x80000000, + 0 | 0x80000000, + 0, + ] - _verify_sp_outputs(sim_exec, sim_execfile, tp, SCAN_KEY, [SPEND_KEY_B]) + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + _setup_sp_spend_input( + sim_exec, + sim_execfile, + psbt.inputs[0], + SP_TWEAK, + WRONG_COIN_PATH, + WRONG_COIN_PATH_INTS, + ) - for i, inp in enumerate(tp.inputs): - assert inp.part_sigs or inp.taproot_key_sig, f"Input {i} missing signature" + psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2tr", psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + with pytest.raises(CCProtoError, match="SP spend path coin type does not match network"): + end_sign(accept=True, finalize=False) # --------------------------------------------------------------------------- @@ -484,9 +597,7 @@ def sp_hacker(psbt): # --------------------------------------------------------------------------- -def test_sp_spend_to_labeled_change_address( - dev, fake_txn, start_sign, end_sign, cap_story -): +def test_sp_spend_to_labeled_change_address(dev, fake_txn, start_sign, end_sign, cap_story): """Two SP outputs: output 0 is a send (no label), output 1 is change (label=0 per BIP-352 convention).""" xp = dev.master_xpub @@ -500,9 +611,7 @@ def sp_hacker(psbt): ) psbt.outputs[1].sp_v0_label = b"\x00\x00\x00\x00" - psbt_bytes = fake_txn( - 1, 2, xp, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker - ) + psbt_bytes = fake_txn(1, 2, xp, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) title, story = cap_story() From 2b632a1edcf24c6714ea551772d4a21358480c39 Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:16:31 -0400 Subject: [PATCH 16/23] SP: add docs/silentpayments.md --- docs/silentpayments.md | 82 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 docs/silentpayments.md diff --git a/docs/silentpayments.md b/docs/silentpayments.md new file mode 100644 index 000000000..6f0297156 --- /dev/null +++ b/docs/silentpayments.md @@ -0,0 +1,82 @@ +# Silent Payments + +**COLDCARD®** `EDGE` versions support [Silent Payments](https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki) from version `TBD`. + +COLDCARD implements the following BIPs: + +* Silent Payments [BIP-352](https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki) +* Sending Silent Payments with PSBTs [BIP-375](https://github.com/bitcoin/bips/blob/master/bip-0375.mediawiki) +* Spending Silent Payment outputs with PSBTs [BIP-376](https://github.com/bitcoin/bips/blob/master/bip-0376.mediawiki) + +## Why Silent Payments? + +**Automatic privacy by default** — share one static address publicly; each payment produces a unique on-chain output + +## How It Works + +### Silent Payments Address Generation + +* the sender collects the input public key(s) from the transaction being built +* a shared secret is derived via ECDH between the combined input key and the receiver's scan public key (part of the SP Address) +* the final output is a Taproot address derived from tweaking the receiver's spend public key (part of the SP Address) and shared secret +* only the holder of the scan private key can detect incoming payments; only the holder of the spend private key can sign for them + +### ECDH Shares & DLEQ Proofs + +The shared secret can be computed from either side: + +* **sender side**: `shared_secret = (a_1 + a_2 + ...) * B_scan` — sum of input private keys × receiver's scan public key +* **receiver side**: `shared_secret = (A_1 + A_2 + ...) * b_scan` — sum of input public keys x receiver's scan private key + +In single-signer flows, COLDCARD performs the full sender-side ECDH internally. + +In multi-signer flows (multiple input owners), each signer computes a *partial ECDH share*: + +* `share_i = a_i * B_scan` — the signer's input private key × the receiver's scan public key +* the coordinator sums all shares: `sum(share_i) = (a_1 + a_2 + ...) * B_scan => shared_secret` +* each share is accompanied by a **DLEQ proof** (Discrete Log Equality) so the coordinator or signers can verify the shares were computed from the correct input secret key + +### SP Output Computation Before Signing + +* before any signing round, the coordinator must derive the correct output script from the SP address and the transaction inputs +* this requires the full set of input public keys and the complete shared secret (assembled from partial ECDH shares) +* the computed output is inserted into the PSBT; signers verify that the output in the PSBT matches the expected tweak before signing +* a signer that cannot recompute the expected output MUST refuse to sign + +## Limitations + +* one of the transaction inputs must be eligible for ["Shared Secret Derivation"](https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki#user-content-Inputs_For_Shared_Secret_Derivation) +* MuSig2 and FROST are not supported yet + +## Example + +SP Address encodes two public keys: a *scan key* and a *spend key* +``` +sp1qqw5jexmu4358tr090qld3egjxkvwftgnwzg7g2v86wad3gywxkln6qcc0kmh5k03cheul53fd7r7h4lg9y3xkrmz3k00ujulyg2pfcaevu9nurf3 +``` + +### Partial Signing (Collaborative Inputs - Multiple Signers) +Round 1 — ECDH share collection +- Coordinator builds PSBT with inputs from each participant and partial outputs (output script not yet finalized) +- Each input owner contributes their partial ECDH share a_i * B_scan and DLEQ proof into the PSBT +- Last contributor verifies all DLEQ proofs, combines partial shares → computes shared secret → computes final output script, updates PSBT + +Round 2 — Sign +- Each signer verifies the output scripts in the PSBT then signs their inputs normally +- Coordinator finalizes and broadcasts + +## Development + +### start simulator +``` +cd unix +% ./simulator.py --q1 +``` + +### execute tests +``` +cd testing +pytest test_bip352_vectors.py +pytest test_bip375_vectors.py +pytest test_silentpayments.py +``` From d7e6105c51838f6db84aff562406d686c35f4ce1 Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Thu, 23 Apr 2026 08:50:42 -0400 Subject: [PATCH 17/23] SP: implement bip352 generic format export Include key-value pairs: spscan, deriv, name, xfp, key_exp in bip352 dictionary Add bip352 test block to test_export_coldcard Support 127 character limit for silent payments v0 --- shared/export.py | 18 ++++++++++++++++++ testing/bech32.py | 2 +- testing/sp_helpers.py | 32 ++++++++++++++++++++++++++++++++ testing/test_export.py | 20 ++++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/shared/export.py b/shared/export.py index 59c417dbc..966dc0cd0 100644 --- a/shared/export.py +++ b/shared/export.py @@ -13,6 +13,7 @@ from charcodes import KEY_NFC, KEY_CANCEL, KEY_QR from ownership import OWNERSHIP from exceptions import QRTooBigError +from silentpayments import encode_silent_payment_address async def export_by_qr(body, label, type_code, force_bbqr=False): # render as QR and show on-screen @@ -444,6 +445,23 @@ def generate_generic_export(account_num=0): node.derive(0, False).derive(0, False) rv[name]['first'] = chain.address(node, fmt) + # Silent Payments: scan-priv + spend-pub packed as a single bech32m "spscan" string + sp_deriv = "m/352h/%dh/%dh" % (chain.b44_cointype, account_num) + sp_node = sv.derive_path(sp_deriv) + scan_node = sv.derive_path(sp_deriv + "/1h/0") + spend_node = sv.derive_path(sp_deriv + "/0h/0") + + spscan = encode_silent_payment_address(scan_node.privkey(), spend_node.pubkey()) + rv["bip352"] = OrderedDict( + spscan=spscan, + deriv=sp_deriv, + name="p2tr", + xfp=xfp2str(swab32(sp_node.my_fp())), + key_exp="[%s/%s]%s" % (master_xfp_str.lower(), + sp_deriv.replace("m/", ""), + spscan), + ) + sig_deriv = "m/44h/{ct}h/{acc}h".format(ct=chain.b44_cointype, acc=account_num) + "/0/0" return ujson.dumps(rv), sig_deriv, AF_CLASSIC diff --git a/testing/bech32.py b/testing/bech32.py index b33db8716..77ffe95f8 100644 --- a/testing/bech32.py +++ b/testing/bech32.py @@ -79,7 +79,7 @@ def bech32_decode(bech): return (None, None, None) bech = bech.lower() pos = bech.rfind('1') - if pos < 1 or pos + 7 > len(bech) or len(bech) > 90: + if pos < 1 or pos + 7 > len(bech) or len(bech) > 127: return (None, None, None) if not all(x in CHARSET for x in bech[pos+1:]): return (None, None, None) diff --git a/testing/sp_helpers.py b/testing/sp_helpers.py index 0d1bd0a22..bf7c4f987 100644 --- a/testing/sp_helpers.py +++ b/testing/sp_helpers.py @@ -314,3 +314,35 @@ def _sim_negate_if_odd_y(sim_exec, sim_execfile, privkey): }, ) return bytes.fromhex(rv) + + +# --------------------------------------------------------------------------- +# Helpers for test_export.py bip352 assertions +# --------------------------------------------------------------------------- + + +def decode_spscan(s, expected_hrp): + """Decode an spscan/tspscan bech32m string into (scan_priv: bytes32, spend_pub: bytes33).""" + from bech32 import bech32_decode, convertbits, Encoding + + hrp, data, spec = bech32_decode(s) + assert hrp == expected_hrp, f"bad HRP: {hrp!r} != {expected_hrp!r}" + assert spec == Encoding.BECH32M, "must be bech32m" + assert data[0] == 0, "expected version 0" + payload = bytes(convertbits(data[1:], 5, 8, False)) + assert len(payload) == 65, f"expected 65 bytes, got {len(payload)}" + return payload[:32], payload[32:] + + +def derive_sp_keys_for_account(account_num, coin_type): + """Return (scan_priv: bytes32, spend_pub: bytes33) for the given account/coin_type. + + Derives from the simulator's fixed test seed, matching the firmware's SP derivation paths. + """ + from bip32 import BIP32Node + from constants import simulator_fixed_tprv + + root = BIP32Node.from_wallet_key(simulator_fixed_tprv) + scan_node = root.subkey_for_path("352h/%dh/%dh/1h/0" % (coin_type, account_num)) + spend_node = root.subkey_for_path("352h/%dh/%dh/0h/0" % (coin_type, account_num)) + return scan_node.privkey(), spend_node.sec() diff --git a/testing/test_export.py b/testing/test_export.py index 6e37b3ba4..2ad3923cb 100644 --- a/testing/test_export.py +++ b/testing/test_export.py @@ -425,6 +425,26 @@ def test_export_coldcard(way, dev, acct_num, app, pick_menu_item, goto_home, cap else: assert False + # BIP-352 Silent Payments block + from sp_helpers import decode_spscan, derive_sp_keys_for_account + bip352 = obj["bip352"] + ct = 0 if netcode == "BTC" else 1 + acct = int(acct_num or 0) + assert set(bip352.keys()) == {"spscan", "deriv", "name", "xfp", "key_exp"} + assert bip352["deriv"] == "m/352h/%dh/%dh" % (ct, acct) + assert bip352["name"] == "p2tr" + assert len(bip352["xfp"]) == 8 + + hrp_expected = "spscan" if netcode == "BTC" else "tspscan" + scan_priv_enc, spend_pub_enc = decode_spscan(bip352["spscan"], hrp_expected) + exp_scan_priv, exp_spend_pub = derive_sp_keys_for_account(acct, ct) + assert scan_priv_enc == exp_scan_priv + assert spend_pub_enc == exp_spend_pub + + expected_origin = "[%s/352h/%dh/%dh]" % (xfp2str(simulator_fixed_xfp).lower(), ct, acct) + assert bip352["key_exp"].startswith(expected_origin + hrp_expected + "1") + assert bip352["key_exp"].split("]", 1)[1] == bip352["spscan"] + @pytest.mark.parametrize('way', ["sd", "vdisk", "nfc", "qr"]) @pytest.mark.parametrize('testnet', [True, False]) @pytest.mark.parametrize('acct_num', [None, '0', '99', '123']) From 280d33615e6a5be4462bdb2849ad81b61f3c6a03 Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Wed, 29 Apr 2026 20:57:12 -0400 Subject: [PATCH 18/23] SP: add more integration tests Mixed inputs SP spend and P2TR Reject non-SIGHASH_ALL Reject psbt v0 send to SP output --- testing/test_silentpayments.py | 56 +++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/testing/test_silentpayments.py b/testing/test_silentpayments.py index ae361f5f1..142b9f970 100644 --- a/testing/test_silentpayments.py +++ b/testing/test_silentpayments.py @@ -222,6 +222,28 @@ def sp_hacker(psbt): # reconstruct a wrong hashSequence. +def test_sp_mixed_sp_spend_and_regular_input(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): + """Regular P2TR input + SP-spend input: both private keys summed for ECDH, both signed""" + xp = dev.master_xpub + + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + _setup_sp_spend_input( + sim_exec, + sim_execfile, + psbt.inputs[1], + SP_TWEAK, + SP_SPEND_PATH, + SP_SPEND_PATH_INTS, + clear_taproot=True, + ) + + psbt_bytes = fake_txn([["p2tr"], ["p2tr"]], 1, xp, psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + signed = end_sign(accept=True, finalize=False) + _assert_sp_spend_success(sim_exec, sim_execfile, signed) + + # --------------------------------------------------------------------------- # Mixed output tests # --------------------------------------------------------------------------- @@ -538,7 +560,7 @@ def sp_hacker(psbt): psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2tr", psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) - with pytest.raises(CCProtoError, match="SP spend key must use 352h path"): + with pytest.raises(CCProtoError, match="SP spend key purpose must use 352h path"): end_sign(accept=True, finalize=False) @@ -592,6 +614,38 @@ def sp_hacker(psbt): end_sign(accept=False, finalize=False) +def test_sp_non_sighash_all_rejected(dev, fake_txn, start_sign, end_sign): + """Non-SIGHASH_ALL on an input with SP outputs must be rejected (BIP-375).""" + xp = dev.master_xpub + + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + psbt.inputs[0].sighash = 3 # SIGHASH_SINGLE + + psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + with pytest.raises(CCProtoError, match="Silent payments require SIGHASH_ALL"): + end_sign(accept=True, finalize=False) + + +def test_sp_psbt_v0_rejected(dev, fake_txn, start_sign, end_sign): + """PSBTv0 with SP outputs is rejected: outpoint fields are PSBTv2-only. + + BIP-352 SP computation requires PSBT_IN_PREVIOUS_TXID and PSBT_IN_OUTPUT_INDEX + (PSBTv2 fields) to derive the input hash. PSBTv0 embeds outpoints only in the + unsigned transaction, so firmware raises when trying to collect them. + """ + xp = dev.master_xpub + + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + + psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2wpkh", psbt_v2=False, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + with pytest.raises(CCProtoError, match="Missing outpoint for silent payment input"): + end_sign(accept=True, finalize=False) + + # --------------------------------------------------------------------------- # Silent Payments Change Detection Tests # --------------------------------------------------------------------------- From c29279308e7d4fc08f409ecfa581da16813f51db Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Fri, 1 May 2026 08:31:13 -0400 Subject: [PATCH 19/23] SP (refactor): test hygiene Remove MockPSBT from verify_sp_outputs Test test_sp_spend_to_labeled_change_address should sign transaction Replace _serialize_psbt with read_psbt Replace single use compute_foreign_share with generate_dleq_proof Remove unnecessary int-byte conversions in test_bip352_vectors.py --- testing/devtest/verify_sp_outputs.py | 144 ++++++--------------------- testing/sp_helpers.py | 93 ++--------------- testing/test_bip352_vectors.py | 19 ++-- testing/test_bip375_vectors.py | 9 +- testing/test_silentpayments.py | 138 +++++++++++++------------ 5 files changed, 123 insertions(+), 280 deletions(-) diff --git a/testing/devtest/verify_sp_outputs.py b/testing/devtest/verify_sp_outputs.py index bd1effc5b..32d23d704 100644 --- a/testing/devtest/verify_sp_outputs.py +++ b/testing/devtest/verify_sp_outputs.py @@ -10,11 +10,7 @@ # import main import ngu -import struct import ujson -from uio import BytesIO -from ubinascii import hexlify as b2a_hex -from ubinascii import unhexlify as a2b_hex from dleq import generate_dleq_proof, verify_dleq_proof from exceptions import FatalPSBTIssue from psbt import psbtObject @@ -28,8 +24,10 @@ _compute_silent_payment_spending_xonly, _negate_if_odd_y, _sum_privkeys, - SilentPaymentsMixin, ) +from ubinascii import hexlify as b2a_hex +from ubinascii import unhexlify as a2b_hex +from uio import BytesIO if isinstance(main.SP_VERIFY, str): with open(main.SP_VERIFY) as f: @@ -40,99 +38,25 @@ mode = params.get("mode") -# --------------------------------------------------------------------------- -# MockPSBT infrastructure for firmware method testing -# --------------------------------------------------------------------------- - - -class MockInput: - def __init__(self): - self.utxo_spk = None - self.taproot_internal_key = None - self.redeem_script = None - self.sighash = None - self.previous_txid = None - self.prevout_idx = None - self.sp_ecdh_shares = {} - self.sp_dleq_proofs = {} - self.subpaths = [] - self.taproot_subpaths = [] - self.sp_tweak = None - self.sp_spend_bip32_derivation = {} - self.sp_idxs = None - self.ik_idx = None - - @property - def is_sp_spend(self): - return bool(self.sp_tweak and self.sp_spend_bip32_derivation) - - -class MockOutput: - def __init__(self): - self.sp_v0_info = None - self.sp_v0_label = None - self.script = None - - -class MockPSBT(SilentPaymentsMixin): - def __init__(self): - self.inputs = [] - self.outputs = [] - self.sp_global_ecdh_shares = {} - self.sp_global_dleq_proofs = {} - self.txn_modifiable = None - self.my_xfp = 0 - - def get(self, x): - return x - - def parse_xfp_path(self, coords): - return coords - - -def _build_mock_psbt(params): - psbt = MockPSBT() - psbt.txn_modifiable = params.get("txn_modifiable") - - global_ecdh = params.get("global_ecdh") or {} - global_dleq = params.get("global_dleq") or {} - psbt.sp_global_ecdh_shares = {a2b_hex(k): a2b_hex(v) for k, v in global_ecdh.items()} - psbt.sp_global_dleq_proofs = {a2b_hex(k): a2b_hex(v) for k, v in global_dleq.items()} - - for d in params.get("inputs", []): - inp = MockInput() - if d.get("utxo_spk"): - inp.utxo_spk = a2b_hex(d["utxo_spk"]) - elif d.get("witness_utxo"): - wu = a2b_hex(d["witness_utxo"]) - # Extract scriptPubKey: 8 bytes value + varint length + script - script_len = wu[8] - inp.utxo_spk = wu[9 : 9 + script_len] - if d.get("taproot_internal_key"): - inp.taproot_internal_key = a2b_hex(d["taproot_internal_key"]) - if d.get("redeem_script"): - inp.redeem_script = a2b_hex(d["redeem_script"]) - inp.sighash = d.get("sighash") - if d.get("previous_txid"): - inp.previous_txid = a2b_hex(d["previous_txid"]) - if d.get("prevout_idx") is not None: - inp.prevout_idx = struct.pack(" Date: Fri, 1 May 2026 17:47:51 -0400 Subject: [PATCH 20/23] SP: improve sp spend without sp outputs and change detection Convert validate_bip376_spend to validate_silent_payment_inputs Use accounts to validate sp spend material and mark output.is_change Introduce reusable sp_derive_path function Fix change detection issue raised by previous test hygiene commit - Signing was restored for test_sp_spend_labeled_change_detected Fix psbt exception in consider_outputs with sp change - Add apply_label_to_spend_key primitive - Match spend public key to labeled spend public key for change New tests: sp spend to non sp output sp spend rejection tests - unknown account, incorrect sp spend derivation path sp change output with foreign keys is allowed but displays warning Changed test: sp spend to label change - fix label spend key derivation, use sp input --- shared/auth.py | 9 +- shared/export.py | 4 +- shared/psbt.py | 17 +- shared/silentpayments.py | 187 ++++++++++++++++------ testing/devtest/unit_silentpayments.py | 4 - testing/devtest/verify_sp_outputs.py | 8 + testing/sp_helpers.py | 27 ++++ testing/test_silentpayments.py | 212 +++++++++++++++++++++++-- 8 files changed, 392 insertions(+), 76 deletions(-) diff --git a/shared/auth.py b/shared/auth.py index 72388b44b..cfaf64ab3 100644 --- a/shared/auth.py +++ b/shared/auth.py @@ -432,9 +432,12 @@ async def interact(self): args = self.psbt.consider_inputs(cosign_xfp=ccc_c_xfp) # Silent Payments: Validate and pre-process Silent Payments outputs to make preview useful - if self.psbt.has_silent_payment_outputs(): - with stash.SensitiveValues() as sv: - if not self.psbt.process_silent_payments(sv): + with stash.SensitiveValues() as sv: + if self.psbt.has_silent_payment_inputs(): + self.psbt.validate_silent_payment_inputs(sv) + + if self.psbt.has_silent_payment_outputs(): + if not self.psbt.process_silent_payment_outputs(sv): # Coverage incomplete: shares computed but waiting on other signers # Skip normal approval flow — prompt user to contribute shares, then save del args diff --git a/shared/export.py b/shared/export.py index 966dc0cd0..acbc92f2c 100644 --- a/shared/export.py +++ b/shared/export.py @@ -13,7 +13,7 @@ from charcodes import KEY_NFC, KEY_CANCEL, KEY_QR from ownership import OWNERSHIP from exceptions import QRTooBigError -from silentpayments import encode_silent_payment_address +from silentpayments import encode_silent_payment_address, sp_derive_path async def export_by_qr(body, label, type_code, force_bbqr=False): # render as QR and show on-screen @@ -446,7 +446,7 @@ def generate_generic_export(account_num=0): rv[name]['first'] = chain.address(node, fmt) # Silent Payments: scan-priv + spend-pub packed as a single bech32m "spscan" string - sp_deriv = "m/352h/%dh/%dh" % (chain.b44_cointype, account_num) + sp_deriv = sp_derive_path(chain.b44_cointype, account_num) sp_node = sv.derive_path(sp_deriv) scan_node = sv.derive_path(sp_deriv + "/1h/0") spend_node = sv.derive_path(sp_deriv + "/0h/0") diff --git a/shared/psbt.py b/shared/psbt.py index 4a0ad7e21..835db9596 100644 --- a/shared/psbt.py +++ b/shared/psbt.py @@ -48,7 +48,7 @@ AF_P2WSH, AF_P2WSH_P2SH, AF_P2SH, AF_P2TR, AF_P2WPKH, AF_CLASSIC, AF_P2WPKH_P2SH, AFC_SEGWIT, AF_BARE_PK ) -from silentpayments import SilentPaymentsMixin, compute_silent_payment_spending_privkey, validate_bip376_spend +from silentpayments import SilentPaymentsMixin, compute_silent_payment_spending_privkey psbt_tmp256 = bytearray(256) @@ -1030,7 +1030,7 @@ def determine_my_signing_key(self, my_idx, addr_or_pubkey, my_xfp, psbt, parsed_ elif self.af == AF_P2TR: if self.is_sp_spend: - validate_bip376_spend(self, addr_or_pubkey, my_xfp, psbt) + pass # Silent Payments inputs are handled in validate_silent_payment_inputs elif len(parsed_subpaths) == 1: # keyspend without a script path assert self.taproot_merkle_root is None, "merkle_root should not be defined for simple keyspend" @@ -2006,7 +2006,8 @@ def consider_outputs(self, len_pths, hard_p, prefix_pths, idx_max, cosign_xfp=No self.num_change_outputs += 1 total_change += txo.nValue - if validate_inp_pths: + # Silent Payments: SP change is validated by _detect_sp_change_outputs, skip additional checks here + if validate_inp_pths and not output.sp_v0_info: # Enforce some policy on change outputs: # - need to "look like" they are going to same wallet as inputs came from # - range limit last two path components (numerically) @@ -2797,7 +2798,11 @@ def sign_it(self, alternate_secret=None, my_xfp=None): # Double-check the change outputs are right. This is slow, but critical because # it detects bad actors, not bugs or mistakes. # - equivalent check already done for p2sh outputs when we re-built the redeem script - change_outs = [n for n,o in enumerate(self.outputs) if o.is_change] + + # SP change is verified at preview time by _detect_sp_change_outputs; + # the subpath/check_pubkey_at_path machinery below doesn't apply to it. + change_outs = [n for n, o in enumerate(self.outputs) + if o.is_change and not o.sp_v0_info] if change_outs: dis.fullscreen('Change Check...') @@ -2840,8 +2845,10 @@ def sign_it(self, alternate_secret=None, my_xfp=None): "BIP-32 path doesn't match actual address.") # Silent Payment Processing + if self.has_silent_payment_inputs(): + self.validate_silent_payment_inputs(sv) if self.has_silent_payment_outputs(): - if not self.process_silent_payments(sv): + if not self.process_silent_payment_outputs(sv): # Silent Payments: must not sign if output scripts not set for all signers # Defensive re-check - ApproveTransaction::interact should handle this case before reaching signing raise FatalPSBTIssue("Silent Payments: Signing cannot proceed until all signers contribute their shares") diff --git a/shared/silentpayments.py b/shared/silentpayments.py index 8edb87adc..4bfee85dd 100644 --- a/shared/silentpayments.py +++ b/shared/silentpayments.py @@ -7,9 +7,11 @@ import chains import ngu import stash +from glob import settings from dleq import generate_dleq_proof, verify_dleq_proof -from exceptions import FatalPSBTIssue +from exceptions import FatalPSBTIssue, FraudulentChangeOutput from precomp_tag_hash import ( + BIP352_LABEL_TAG_H, BIP352_SHARED_SECRET_TAG_H, BIP352_INPUTS_TAG_H, TAP_TWEAK_H, @@ -46,41 +48,34 @@ def encode_silent_payment_address(scan_key, spend_key, version=0): return ngu.codecs.bip352_encode(hrp, scan_key, spend_key, version) -def validate_bip376_spend(input, output_xonly, my_xfp=None, parent=None): - """Validate silent payment spend using PSBT input data - - TODO: replace with test vectors once available - - Raises FatalPSBTIssue if any SP spend validation checks fail - """ - B_spend_coords, val_coords = input.sp_spend_bip32_derivation - xfp_path = input.parse_xfp_path(val_coords) - if my_xfp is not None: - xfp_path = input.handle_zero_xfp(xfp_path, my_xfp, parent) - - sp_path = xfp_path[1:] - if len(sp_path) != 5: - raise FatalPSBTIssue("SP spend path must have 5 components") - if sp_path[0] != (352 | 0x80000000): - raise FatalPSBTIssue("SP spend key purpose must use 352h path") - - expected_coin = chains.current_chain().b44_cointype | 0x80000000 - if sp_path[1] != expected_coin: - raise FatalPSBTIssue("SP spend path coin type does not match network") - if not (sp_path[2] & 0x80000000): - raise FatalPSBTIssue("SP spend path account must be hardened") - if sp_path[3] != 0x80000000: - raise FatalPSBTIssue("SP spend path key type must be 0h") - - B_spend = input.get(B_spend_coords) - if _compute_silent_payment_spending_xonly(B_spend, input.sp_tweak) != output_xonly: - raise FatalPSBTIssue("SP_TWEAK does not match UTXO output key") +def sp_derive_path(coin_type, account): + """BIP-352 base derivation path for a given coin type and account number""" + return "m/352h/%dh/%dh" % (coin_type, account) # ----------------------------------------------------------------------------- # Silent Payments Cryptographic Primitives # ----------------------------------------------------------------------------- +def _apply_label_to_spend_key(B_spend_bytes, b_scan_bytes, label): + """ + Apply BIP-352 label tweak to spend key + + Formula: B_m = B_spend + hash_BIP0352/Label(b_scan || m)*G + + Args: + B_spend_bytes: Base spend public key (33-byte compressed) + b_scan_bytes: Scan private key (32-byte scalar) + label: Label integer (0 for change) + + Returns: + bytes: Labeled spend public key (33-byte compressed) + """ + msg = b_scan_bytes + label.to_bytes(4, 'big') + label_tweak_bytes = ngu.hash.sha256t(BIP352_LABEL_TAG_H, msg, True) + label_tweak_point = ngu.secp256k1.ec_pubkey_tweak_mul(G, label_tweak_bytes) + return ngu.secp256k1.ec_pubkey_combine([B_spend_bytes, label_tweak_point]) + def _combine_pubkeys(pubkeys): """ @@ -294,21 +289,19 @@ class SilentPaymentsMixin: This class assumes it is mixed into psbtObject and has access to psbt as self """ - def process_silent_payments(self, sv): + def process_silent_payment_outputs(self, sv): """ Core SP workflow: validate, compute shares, compute scripts Notes: - - This function is intended to be called during the preview phase, before signing + - This function is intended to be called during the preview phase and again during signing - Single-signer should be able to generate output and preview immediately - Multi-signer should generate shares and prompt user to collect all shares if not complete Returns: bool: True if output scripts were computed and are ready for preview/signing - False if we generated shares but are waiting on others, or if we don't have necessary info to proceed + False if we generated shares but are waiting on others """ - if not self.has_silent_payment_outputs(): - return False self._validate_psbt_structure() self._validate_input_eligibility() self._validate_ecdh_coverage() @@ -319,7 +312,7 @@ def process_silent_payments(self, sv): if self._is_ecdh_coverage_complete(): # Computes scripts, or validates existing ones against recomputed values self._compute_silent_payment_output_scripts() - self._detect_sp_change_outputs() + self._detect_sp_change_outputs(sv) return self._all_sp_outputs_have_scripts() return False @@ -341,6 +334,63 @@ def render_silent_payment_output_string(self, output): return " - silent payments address -\n%s\n" % encode_silent_payment_address(scan_key, spend_key) + def validate_silent_payment_inputs(self, sv): + """ + Validate inputs spending silent payments (BIP-376) + + Raises FatalPSBTIssue on any failure. + """ + coin_type = chains.current_chain().b44_cointype + expected_coin = coin_type | 0x80000000 + + candidate_accounts = self._get_candidate_sp_accounts() + + for i, inp in enumerate(self.inputs): + if not inp.is_sp_spend: + continue + + # Validate path shape and extract B_spend for both our and foreign inputs + B_spend_coords, val_coords = inp.sp_spend_bip32_derivation + xfp_path = inp.parse_xfp_path(val_coords) + xfp_path = inp.handle_zero_xfp(xfp_path, self.my_xfp, self) + + sp_path = xfp_path[1:] + if len(sp_path) != 5: + raise FatalPSBTIssue("Input #%d: SP spend path must have 5 components" % i) + if sp_path[0] != (352 | 0x80000000): + raise FatalPSBTIssue("Input #%d: SP spend key purpose must use 352h path" % i) + if sp_path[1] != expected_coin: + raise FatalPSBTIssue("Input #%d: SP spend path coin type does not match network" % i) + if not (sp_path[2] & 0x80000000): + raise FatalPSBTIssue("Input #%d: SP spend path account must be hardened" % i) + if sp_path[3] != 0x80000000: + raise FatalPSBTIssue("Input #%d: SP spend path key type must be 0h" % i) + if sp_path[4] != 0: + raise FatalPSBTIssue("Input #%d: SP spend path index must be 0" % i) + + B_spend = inp.get(B_spend_coords) + + # For our inputs, validate account is known and B_spend matches derivation from this device + if xfp_path[0] == self.my_xfp: + account = sp_path[2] & ~0x80000000 + if account not in candidate_accounts: + raise FatalPSBTIssue( + "Input #%d: SP spend account not recognized on this device" % i) + + spend_node = sv.derive_path( + sp_derive_path(coin_type, account) + "/0h/0", register=False) + derived = spend_node.pubkey() + del spend_node + if derived != B_spend: + raise FatalPSBTIssue("Input #%d: SP spend pubkey mismatch" % i) + + spk = inp.utxo_spk + if not spk or not _is_p2tr(spk): + raise FatalPSBTIssue("Input #%d: SP spend UTXO is not P2TR" % i) + utxo_xonly = spk[2:34] + if _compute_silent_payment_spending_xonly(B_spend, inp.sp_tweak) != utxo_xonly: + raise FatalPSBTIssue("Input #%d: SP_TWEAK does not match UTXO output key" % i) + # ----------------------------------------------------------------------------- # Input Helper Functions # ----------------------------------------------------------------------------- @@ -613,9 +663,6 @@ def _compute_and_store_ecdh_shares(self, sv): """ Compute ECDH shares and DLEQ proofs for our inputs, store in PSBT fields - Notes: - Sets self.sp_all_inputs_ours for callers - Returns: bool: True if shares were computed False if we have no signable inputs @@ -644,14 +691,12 @@ def _compute_and_store_ecdh_shares(self, sv): raise FatalPSBTIssue("No eligible inputs for ECDH computation") return False - self.sp_all_inputs_ours = not has_foreign - scan_keys = self._get_silent_payment_scan_keys() if not scan_keys: return False for scan_key in scan_keys: - if self.sp_all_inputs_ours: + if not has_foreign: # Single-signer: combine all input private keys, one global ECDH share and DLEQ proofs combined_sk = _sum_privkeys(sk for _, sk in input_material) ecdh_share = _compute_ecdh_share(combined_sk, scan_key) @@ -714,6 +759,10 @@ def _compute_silent_payment_output_scripts(self): # Utility Functions # ----------------------------------------------------------------------------- + def has_silent_payment_inputs(self): + """Check if PSBT contains any silent payment inputs""" + return any(inp.is_sp_spend for inp in self.inputs) + def has_silent_payment_outputs(self): """ Check if PSBT contains any silent payment outputs @@ -733,17 +782,59 @@ def _all_sp_outputs_have_scripts(self): return False return True - def _detect_sp_change_outputs(self): + def _detect_sp_change_outputs(self, sv): """ - Mark SP outputs as change if sp_v0_label is present and equals 0 (BIP-352 change convention) + Mark SP outputs as change if they match our accounts - No return value; modifies self.outputs 'is_change' in place + Raises FraudulentChangeOutput if all SP-eligible inputs are ours (single-signer); + multi-signer, a non-matching label=0 output is skipped (may belong to a co-signer). """ - for outp in self.outputs: + has_foreign = False + for inp in self.inputs: + if not self._is_input_eligible(inp): + continue + if inp.sp_idxs: + if self._derive_input_privkey(inp, sv) is None: + has_foreign = True + break + elif self._pubkey_from_input(inp): + has_foreign = True + break + + coin_type = chains.current_chain().b44_cointype + candidate_accounts = self._get_candidate_sp_accounts() + + for out_idx, outp in enumerate(self.outputs): if not outp.sp_v0_info or not outp.sp_v0_label: continue - if int.from_bytes(outp.sp_v0_label, "little") == 0: - outp.is_change = True + if int.from_bytes(outp.sp_v0_label, "little") != 0: + continue + + scan_pub = outp.sp_v0_info[:33] + spend_pub = outp.sp_v0_info[33:66] + + matched = False + for account in candidate_accounts: + base = sp_derive_path(coin_type, account) + scan_node = sv.derive_path(base + "/1h/0", register=False) + spend_node = sv.derive_path(base + "/0h/0", register=False) + label_spend_pub = _apply_label_to_spend_key(spend_node.pubkey(), scan_node.privkey(), 0) + matched = (scan_node.pubkey() == scan_pub) and (label_spend_pub == spend_pub) + del scan_node, spend_node + if matched: + outp.is_change = True + break + + if not matched and not has_foreign: + raise FraudulentChangeOutput(out_idx, "SP change output keys do not match this device") + + def _get_candidate_sp_accounts(self): + """Get all known account numbers from device settings, always include account 0""" + seen = {0} + for _af, acct_num in settings.get('accts', []): + if acct_num: + seen.add(acct_num) + return sorted(seen) def _get_outpoints(self): """ diff --git a/testing/devtest/unit_silentpayments.py b/testing/devtest/unit_silentpayments.py index 770bc6776..58130bdfb 100644 --- a/testing/devtest/unit_silentpayments.py +++ b/testing/devtest/unit_silentpayments.py @@ -458,8 +458,6 @@ def _make_sp_output(scan_key): (MY_XFP, 44, 0, 0, 1): TEST_PRIVKEY2, } ) -assert psbt.sp_all_inputs_ours is True - # Foreign input psbt = MockPSBT() inp_ours = _make_eligible_input(pk_ours, TEST_DERIV_OURS, b"\x01" * 32, b"\x00" * 4) @@ -469,8 +467,6 @@ def _make_sp_output(scan_key): outp.sp_v0_info = TEST_SCAN_KEY + TEST_SPEND_KEY psbt.outputs = [outp] psbt._compute_and_store_ecdh_shares({TEST_DERIV_OURS: TEST_PRIVKEY}) -assert psbt.sp_all_inputs_ours is False - # No signable inputs psbt = MockPSBT() inp = _make_eligible_input(b"\x02" + b"\xbb" * 32, (FOREIGN_XFP, 44, 0, 0, 0), b"\x01" * 32, b"\x00" * 4) diff --git a/testing/devtest/verify_sp_outputs.py b/testing/devtest/verify_sp_outputs.py index 32d23d704..2c17e325c 100644 --- a/testing/devtest/verify_sp_outputs.py +++ b/testing/devtest/verify_sp_outputs.py @@ -16,6 +16,7 @@ from psbt import psbtObject from serializations import SIGHASH_DEFAULT from silentpayments import ( + _apply_label_to_spend_key, _combine_pubkeys, _compute_ecdh_share, _compute_input_hash, @@ -180,6 +181,13 @@ def _load_psbt(params): result = _compute_silent_payment_spending_xonly(B_spend, sp_tweak) RV.write(b2a_hex(result).decode()) +elif mode == "apply_label_to_spend_key": + spend_pub = a2b_hex(params["spend_pub"]) + scan_priv = a2b_hex(params["scan_priv"]) + label = int(params["label"]) + result = _apply_label_to_spend_key(spend_pub, scan_priv, label) + RV.write(b2a_hex(result).decode()) + elif mode == "negate_if_odd_y": privkey = a2b_hex(params["privkey"]) result = _negate_if_odd_y(privkey) diff --git a/testing/sp_helpers.py b/testing/sp_helpers.py index 70c7c1aed..e0b078c9a 100644 --- a/testing/sp_helpers.py +++ b/testing/sp_helpers.py @@ -98,6 +98,19 @@ def _sim_compute_ecdh_share(sim_exec, sim_execfile, privkey, scan_key): return bytes.fromhex(rv) +def _sim_generate_dleq_proof(sim_exec, sim_execfile, privkey, scan_key): + rv = _sim_sp( + sim_exec, + sim_execfile, + "generate_dleq_proof", + { + "privkey": privkey.hex(), + "scan_key": scan_key.hex(), + }, + ) + return bytes.fromhex(rv) + + def _sim_compute_spend_output_xonly(sim_exec, sim_execfile, B_spend, sp_tweak): rv = _sim_sp( sim_exec, @@ -223,6 +236,20 @@ def _sim_sum_privkeys(sim_exec, sim_execfile, privkeys): return bytes.fromhex(rv) +def _sim_apply_label_to_spend_key(sim_exec, sim_execfile, spend_pub, scan_priv, label): + rv = _sim_sp( + sim_exec, + sim_execfile, + "apply_label_to_spend_key", + { + "spend_pub": spend_pub.hex(), + "scan_priv": scan_priv.hex(), + "label": label, + }, + ) + return bytes.fromhex(rv) + + def _sim_negate_if_odd_y(sim_exec, sim_execfile, privkey): rv = _sim_sp( sim_exec, diff --git a/testing/test_silentpayments.py b/testing/test_silentpayments.py index 4310d1d02..fc95db604 100644 --- a/testing/test_silentpayments.py +++ b/testing/test_silentpayments.py @@ -17,9 +17,11 @@ from constants import simulator_fixed_tprv from psbt import BasicPSBT from sp_helpers import ( + _sim_apply_label_to_spend_key, _sim_compute_ecdh_share, _sim_compute_output_script, _sim_compute_spend_output_xonly, + _sim_generate_dleq_proof, _sim_get_ecdh_and_pubkey, _sim_get_outpoints, _sim_verify_dleq, @@ -38,6 +40,7 @@ # BIP-376 SP spend constants (BIP-352 path: m/352h/1h/0h/0h/0, testnet) SP_TWEAK = bytes.fromhex("a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2") +SP_SCAN_PATH = "352h/1h/0h/1h/0" SP_SPEND_PATH = "352h/1h/0h/0h/0" SP_SPEND_PATH_INTS = [ 352 | 0x80000000, @@ -47,6 +50,7 @@ 0, ] +SIMULATOR_DELAY = 0 # seconds to wait for the simulator to process the PSBT and update the story # --------------------------------------------------------------------------- # Helpers @@ -141,8 +145,7 @@ def sp_hacker(psbt): psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) - - time.sleep(0.1) + time.sleep(SIMULATOR_DELAY) title, story = cap_story() assert title == "OK TO SEND?" assert "not well understood script" not in story @@ -160,6 +163,7 @@ def sp_hacker(psbt): psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) signed = end_sign(accept=True, finalize=False) tp = BasicPSBT().parse(signed) @@ -175,6 +179,7 @@ def sp_hacker(psbt): psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2tr", psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) signed = end_sign(accept=True, finalize=False) tp = BasicPSBT().parse(signed) @@ -191,6 +196,7 @@ def sp_hacker(psbt): psbt_bytes = fake_txn([["p2wpkh"], ["p2tr"]], 1, xp, psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) signed = end_sign(accept=True, finalize=False) tp = BasicPSBT().parse(signed) @@ -218,6 +224,7 @@ def sp_hacker(psbt): psbt_bytes = fake_txn([["p2tr"], ["p2tr"]], 1, xp, psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) signed = end_sign(accept=True, finalize=False) _assert_sp_spend_success(sim_exec, sim_execfile, signed) @@ -243,6 +250,7 @@ def sp_hacker(psbt): psbt_bytes = fake_txn(1, 3, xp, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) signed = end_sign(accept=True, finalize=False) tp = BasicPSBT().parse(signed) @@ -268,6 +276,7 @@ def sp_hacker(psbt): psbt_bytes = fake_txn(1, 2, xp, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) signed = end_sign(accept=True, finalize=False) tp = BasicPSBT().parse(signed) @@ -291,6 +300,7 @@ def sp_hacker(psbt): psbt_bytes = fake_txn(1, 2, xp, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) signed = end_sign(accept=True, finalize=False) tp = BasicPSBT().parse(signed) @@ -313,6 +323,7 @@ def sp_hacker(psbt): psbt_bytes = fake_txn([["p2wpkh"], ["p2wpkh"]], 1, xp, psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) signed = end_sign(accept=True, finalize=False) tp = BasicPSBT().parse(signed) @@ -351,6 +362,7 @@ def sp_hacker(psbt): psbt_hacker=sp_hacker, ) start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) signed = end_sign(accept=True, finalize=False) tp = BasicPSBT().parse(signed) @@ -394,8 +406,7 @@ def sp_hacker(psbt): psbt_hacker=sp_hacker, ) start_sign(psbt_bytes) - - time.sleep(0.1) + time.sleep(SIMULATOR_DELAY) title, story = cap_story() assert title == "CONTRIBUTE SHARES?" assert "ECDH shares" in story @@ -435,8 +446,7 @@ def sp_hacker(psbt): psbt_hacker=sp_hacker, ) start_sign(psbt_bytes) - - time.sleep(0.1) + time.sleep(SIMULATOR_DELAY) title, _ = cap_story() assert title == "CONTRIBUTE SHARES?" @@ -448,6 +458,36 @@ def sp_hacker(psbt): # --------------------------------------------------------------------------- +def test_sp_spend_input_to_non_sp_output(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): + """Spend from SP input to a non SP output. + + Verifies that SP input validation (tweak, path) passes and the tx is signed + without computing ECDH shares (which are only needed for SP outputs). + """ + xp = dev.master_xpub + + def sp_hacker(psbt): + _setup_sp_spend_input( + sim_exec, + sim_execfile, + psbt.inputs[0], + SP_TWEAK, + SP_SPEND_PATH, + SP_SPEND_PATH_INTS, + clear_taproot=True, + ) + + psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2tr", psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) + signed = end_sign(accept=True, finalize=False) + + tp = BasicPSBT().parse(signed) + assert not tp.sp_global_ecdh_shares, "Global ECDH shares must not exist when spending to non-SP outputs" + assert not tp.inputs[0].sp_ecdh_shares, "Per-input ECDH shares must not exist when spending to non-SP outputs" + _verify_signatures(sim_exec, sim_execfile, tp, signed) + + def test_sp_spend_silent_payment_output(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): """Spend from SP output with correct proof -> sign. @@ -471,6 +511,7 @@ def sp_hacker(psbt): psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2tr", psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) signed = end_sign(accept=True, finalize=False) _assert_sp_spend_success(sim_exec, sim_execfile, signed) @@ -493,6 +534,7 @@ def sp_hacker(psbt): psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2tr", psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) signed = end_sign(accept=True, finalize=False) _assert_sp_spend_success(sim_exec, sim_execfile, signed) _verify_sp_outputs(sim_exec, sim_execfile, BasicPSBT().parse(signed), SCAN_KEY, [SPEND_KEY_B]) @@ -518,6 +560,7 @@ def sp_hacker(psbt): psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2tr", psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) with pytest.raises(CCProtoError, match="SP_TWEAK does not match UTXO output key"): end_sign(accept=True, finalize=False) @@ -542,6 +585,7 @@ def sp_hacker(psbt): psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2tr", psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) with pytest.raises(CCProtoError, match="SP spend key purpose must use 352h path"): end_sign(accept=True, finalize=False) @@ -572,10 +616,71 @@ def sp_hacker(psbt): psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2tr", psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) with pytest.raises(CCProtoError, match="SP spend path coin type does not match network"): end_sign(accept=True, finalize=False) +def test_sp_spend_unknown_account_rejected(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): + """Path with an account not in settings.get('accts') (and not 0) must be rejected.""" + xp = dev.master_xpub + UNKNOWN_ACCT_PATH = "352h/1h/7h/0h/0" + UNKNOWN_ACCT_PATH_INTS = [ + 352 | 0x80000000, + 1 | 0x80000000, + 7 | 0x80000000, + 0 | 0x80000000, + 0, + ] + + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + _setup_sp_spend_input( + sim_exec, + sim_execfile, + psbt.inputs[0], + SP_TWEAK, + UNKNOWN_ACCT_PATH, + UNKNOWN_ACCT_PATH_INTS, + ) + + psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2tr", psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) + with pytest.raises(CCProtoError, match="SP spend account not recognized on this device"): + end_sign(accept=True, finalize=False) + + +def test_sp_spend_tampered_pubkey_rejected(dev, fake_txn, start_sign, end_sign, sim_exec, sim_execfile): + """PSBT-stated B_spend must match device-derived spend pubkey for the stated path.""" + xp = dev.master_xpub + FAKE_PUBKEY_PATH = "84h/1h/0h/0/0" + + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + master = BIP32Node.from_wallet_key(simulator_fixed_tprv) + mfp = master.fingerprint() + # Tampered: B_spend derived from a non-SP path; xfp+path advertises the legitimate SP path + fake_B_spend = master.subkey_for_path(FAKE_PUBKEY_PATH).sec() + xfp_and_path = mfp + struct.pack("<" + "I" * len(SP_SPEND_PATH_INTS), *SP_SPEND_PATH_INTS) + + # Witness UTXO locked to fake_B_spend + SP_TWEAK*G so tweak math itself passes + xonly = _sim_compute_spend_output_xonly(sim_exec, sim_execfile, fake_B_spend, SP_TWEAK) + inp = psbt.inputs[0] + amount_bytes = inp.witness_utxo[:8] + inp.witness_utxo = amount_bytes + b"\x22\x51\x20" + xonly + inp.taproot_bip32_paths = {} + inp.taproot_internal_key = None + inp.sp_tweak = SP_TWEAK + inp.sp_spend_bip32_derivation = {fake_B_spend: xfp_and_path} + + psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2tr", psbt_v2=True, psbt_hacker=sp_hacker) + start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) + with pytest.raises(CCProtoError, match="SP spend pubkey mismatch"): + end_sign(accept=True, finalize=False) + + # --------------------------------------------------------------------------- # Handle failure test # --------------------------------------------------------------------------- @@ -592,6 +697,7 @@ def sp_hacker(psbt): psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) with pytest.raises(CCProtoError, match="SP_V0_INFO wrong size"): end_sign(accept=True, finalize=False) @@ -606,6 +712,7 @@ def sp_hacker(psbt): psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) with pytest.raises(CCProtoError, match="Silent payments require SIGHASH_ALL"): end_sign(accept=True, finalize=False) @@ -624,6 +731,7 @@ def sp_hacker(psbt): psbt_bytes = fake_txn(1, 1, xp, addr_fmt="p2wpkh", psbt_v2=False, psbt_hacker=sp_hacker) start_sign(psbt_bytes) + time.sleep(SIMULATOR_DELAY) with pytest.raises(CCProtoError, match="Missing outpoint for silent payment input"): end_sign(accept=True, finalize=False) @@ -633,27 +741,37 @@ def sp_hacker(psbt): # --------------------------------------------------------------------------- -def test_sp_spend_to_labeled_change_address(dev, fake_txn, start_sign, end_sign, cap_story, sim_exec, sim_execfile): +def test_sp_spend_labeled_change_detected(dev, fake_txn, start_sign, end_sign, cap_story, sim_exec, sim_execfile): """Two SP outputs: output 0 is a send (no label), output 1 is change (label=0 per BIP-352 convention).""" xp = dev.master_xpub root = BIP32Node.from_wallet_key(simulator_fixed_tprv) - sim_scan_pub = root.subkey_for_path("352h/1h/0h/1h/0").sec() - sim_spend_pub = root.subkey_for_path("352h/1h/0h/0h/0").sec() + scan_node = root.subkey_for_path(SP_SCAN_PATH) + scan_pub = scan_node.sec() + spend_pub = root.subkey_for_path(SP_SPEND_PATH).sec() + labeled_spend_pub = _sim_apply_label_to_spend_key(sim_exec, sim_execfile, spend_pub, scan_node.privkey(), 0) def sp_hacker(psbt): + _setup_sp_spend_input( + sim_exec, + sim_execfile, + psbt.inputs[0], + SP_TWEAK, + SP_SPEND_PATH, + SP_SPEND_PATH_INTS, + ) _add_sp_outputs( psbt, [ (0, SCAN_KEY, SPEND_KEY_B), - (1, sim_scan_pub, sim_spend_pub), + (1, scan_pub, labeled_spend_pub), ], ) psbt.outputs[1].sp_v0_label = b"\x00\x00\x00\x00" - psbt_bytes = fake_txn(1, 2, xp, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker) + psbt_bytes = fake_txn(1, 2, xp, addr_fmt="p2tr", psbt_v2=True, psbt_hacker=sp_hacker) start_sign(psbt_bytes) - + time.sleep(SIMULATOR_DELAY) title, story = cap_story() assert title == "OK TO SEND?" assert "not well understood script" not in story @@ -663,9 +781,74 @@ def sp_hacker(psbt): signed = end_sign(accept=True, finalize=False) tp = BasicPSBT().parse(signed) assert SCAN_KEY in tp.sp_global_ecdh_shares and SCAN_KEY in tp.sp_global_dleq_proofs - assert sim_scan_pub in tp.sp_global_ecdh_shares and sim_scan_pub in tp.sp_global_dleq_proofs + assert scan_pub in tp.sp_global_ecdh_shares and scan_pub in tp.sp_global_dleq_proofs _verify_sp_outputs(sim_exec, sim_execfile, tp, SCAN_KEY, [SPEND_KEY_B]) - _verify_sp_outputs(sim_exec, sim_execfile, tp, sim_scan_pub, [sim_spend_pub]) + _verify_sp_outputs(sim_exec, sim_execfile, tp, scan_pub, [labeled_spend_pub]) + _verify_signatures(sim_exec, sim_execfile, tp, signed) + + +def test_cosigner_change_output_is_allowed_with_warning(dev, fake_txn, start_sign, end_sign, cap_story, sim_exec, sim_execfile): + """label=0 with foreign keys must NOT raise fraud in a multi-signer flow. + + This tests bypasses the normal "coverage incomplete" workflow to simulate this scenario: + When foreign inputs are present, a label=0 SP output + whose scan/spend keys don't belong to this device is treated as a cosigner's change + address and rendered as a normal SP recipient — not marked as change, displays a warning. + """ + xp = dev.master_xpub + + # Fixed deterministic foreign key so the test is reproducible + foreign_mk = BIP32Node.from_master_secret(bytes(range(32))) + foreign_key = foreign_mk.subkey_for_path("0/0") + foreign_pub = foreign_key.sec() + foreign_xfp = foreign_mk.fingerprint() + foreign_priv = foreign_key.privkey() + + # Pre-compute real ECDH shares and DLEQ proofs for the foreign input. + # These must be valid: after preview sets output scripts, _validate_ecdh_coverage + # re-runs and verifies per-input DLEQ proofs. + foreign_shares = { + scan_key: _sim_compute_ecdh_share(sim_exec, sim_execfile, foreign_priv, scan_key) + for scan_key in (SCAN_KEY, SCAN_KEY_2) + } + foreign_proofs = { + scan_key: _sim_generate_dleq_proof(sim_exec, sim_execfile, foreign_priv, scan_key) + for scan_key in (SCAN_KEY, SCAN_KEY_2) + } + + def sp_hacker(psbt): + # Output 0: normal SP send (no label, third-party recipient) + # Output 1: cosigner's SP change (label=0, keys are NOT this device's) + _add_sp_outputs(psbt, [ + (0, SCAN_KEY, SPEND_KEY_B), + (1, SCAN_KEY_2, SPEND_KEY_A), + ]) + psbt.outputs[1].sp_v0_label = b"\x00\x00\x00\x00" + + # Make input 0 foreign: replace the random bip32 entry with the fixed unknown key. + # The firmware will fail to derive a privkey for this XFP → all_sp_inputs_ours=False. + psbt.inputs[0].bip32_paths = {} + psbt.inputs[0].bip32_paths[foreign_pub] = foreign_xfp + struct.pack(' Date: Wed, 3 Jun 2026 18:53:12 -0400 Subject: [PATCH 21/23] fix (SP): apply sp output sort correctly when scan key is the same for multiple outputs Per BIP-375: sort by spend key then output index in ascending order for each scan key group to determine ordering of the k value - Fix compute_silent_payment_output_scripts to match spec - Update bip375_test_vectors.json to version 1.1.1 --- shared/silentpayments.py | 32 ++++++++------- testing/bip375_test_vectors.json | 41 +++++++++++-------- testing/devtest/unit_silentpayments.py | 19 +++------ testing/test_bip375_vectors.py | 56 ++++++++------------------ 4 files changed, 64 insertions(+), 84 deletions(-) diff --git a/shared/silentpayments.py b/shared/silentpayments.py index 4bfee85dd..e1b50960a 100644 --- a/shared/silentpayments.py +++ b/shared/silentpayments.py @@ -4,16 +4,17 @@ # # Consolidates cryptographic primitives and PSBT handling logic for Silent Payments # + import chains import ngu import stash -from glob import settings from dleq import generate_dleq_proof, verify_dleq_proof from exceptions import FatalPSBTIssue, FraudulentChangeOutput +from glob import settings from precomp_tag_hash import ( + BIP352_INPUTS_TAG_H, BIP352_LABEL_TAG_H, BIP352_SHARED_SECRET_TAG_H, - BIP352_INPUTS_TAG_H, TAP_TWEAK_H, ) from serializations import SIGHASH_ALL, SIGHASH_DEFAULT @@ -57,6 +58,7 @@ def sp_derive_path(coin_type, account): # Silent Payments Cryptographic Primitives # ----------------------------------------------------------------------------- + def _apply_label_to_spend_key(B_spend_bytes, b_scan_bytes, label): """ Apply BIP-352 label tweak to spend key @@ -71,7 +73,7 @@ def _apply_label_to_spend_key(B_spend_bytes, b_scan_bytes, label): Returns: bytes: Labeled spend public key (33-byte compressed) """ - msg = b_scan_bytes + label.to_bytes(4, 'big') + msg = b_scan_bytes + label.to_bytes(4, "big") label_tweak_bytes = ngu.hash.sha256t(BIP352_LABEL_TAG_H, msg, True) label_tweak_point = ngu.secp256k1.ec_pubkey_tweak_mul(G, label_tweak_bytes) return ngu.secp256k1.ec_pubkey_combine([B_spend_bytes, label_tweak_point]) @@ -374,11 +376,9 @@ def validate_silent_payment_inputs(self, sv): if xfp_path[0] == self.my_xfp: account = sp_path[2] & ~0x80000000 if account not in candidate_accounts: - raise FatalPSBTIssue( - "Input #%d: SP spend account not recognized on this device" % i) + raise FatalPSBTIssue("Input #%d: SP spend account not recognized on this device" % i) - spend_node = sv.derive_path( - sp_derive_path(coin_type, account) + "/0h/0", register=False) + spend_node = sv.derive_path(sp_derive_path(coin_type, account) + "/0h/0", register=False) derived = spend_node.pubkey() del spend_node if derived != B_spend: @@ -731,15 +731,17 @@ def _compute_silent_payment_output_scripts(self): """ outpoints = self._get_outpoints() + # BIP-375: sort by spend key then output index in ascending order for each scan key group to determine + # ordering of the k value + sp_outputs = [(outp.sp_v0_info, out_idx, outp) for out_idx, outp in enumerate(self.outputs) if outp.sp_v0_info] + sp_outputs.sort(key=lambda t: (t[0], t[1])) + # Track k per scan key scan_key_k = {} - for out_idx, outp in enumerate(self.outputs): - if not outp.sp_v0_info: - continue - - scan_key = outp.sp_v0_info[:33] - B_spend = outp.sp_v0_info[33:66] + for sp_info, out_idx, outp in sp_outputs: + scan_key = sp_info[:33] + B_spend = sp_info[33:66] k = scan_key_k.get(scan_key, 0) ecdh_share, summed_pubkey = self._get_ecdh_and_pubkey(scan_key) @@ -810,7 +812,7 @@ def _detect_sp_change_outputs(self, sv): if int.from_bytes(outp.sp_v0_label, "little") != 0: continue - scan_pub = outp.sp_v0_info[:33] + scan_pub = outp.sp_v0_info[:33] spend_pub = outp.sp_v0_info[33:66] matched = False @@ -831,7 +833,7 @@ def _detect_sp_change_outputs(self, sv): def _get_candidate_sp_accounts(self): """Get all known account numbers from device settings, always include account 0""" seen = {0} - for _af, acct_num in settings.get('accts', []): + for _af, acct_num in settings.get("accts", []): if acct_num: seen.add(acct_num) return sorted(seen) diff --git a/testing/bip375_test_vectors.json b/testing/bip375_test_vectors.json index 3f16f51b2..922eeebe1 100644 --- a/testing/bip375_test_vectors.json +++ b/testing/bip375_test_vectors.json @@ -1,6 +1,6 @@ { "description": "BIP-375 Test Vectors", - "version": "1.1", + "version": "1.1.1", "notes": [ "Generated by https://github.com/macgyver13/bip375-test-generator/", "Each vector includes: base64-encoded psbt and description", @@ -904,7 +904,7 @@ { "output_index": 0, "amount": 100000, - "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe0680361e1b1e9de5e42cb2007f7ca54b9e0d57ed13938fad56d3f19e57513a8fce039", + "sp_v0_info": "027a487fc19fb769877b8742d6ea18118f3c4e72b1ea8c6de602a7ad4a41dbe06803ea8f228be2b716dacdefcb2810072874f00a476f5c07bd051917126cc64b3def", "sp_v0_label": 1, "script": "51202268cf529959f30ac8f7a5471ca4c67536a51f0e5b168af03eed4715edcc72e0" }, @@ -918,7 +918,7 @@ }, { "description": "can finalize: one input / two sp outputs - output 0 has no label / output 1 uses label=0 convention for sp change", - "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQECAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR/gkwQAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCICJa45kPVF365uoUKpN0arSqgSfjyBNY63wha08anrtiAiBl42BSGRArJQdeOVXz80zjvPKjXR4QFd1dw7/tPs81qQEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0CXw7Bs42ZhKfrYvFb5AMCBBDxQu301p42P1YVr9Kmts0hA84eNnXZDZkmSfqd2xH5AlSbs7AvVvbonMCgTIuMN92UIh0Ci3kOCYf96fDRI5jra+MMc3Z5PGR4ZaKgLy78lg2WZWghAlWBv6pzMVb2uY5xom9w/XazZ4ZJ9isfxed7jpq45hBQIh4CXw7Bs42ZhKfrYvFb5AMCBBDxQu301p42P1YVr9Kmts1ADolhPR9rJTbbj6kkIA2zpvjvyhciVWTVaxEuUIZbTTAuBY+m9hwp4jZqSnJp1FVaXGHWVVnb+/rJ4urf5TY31SIeAot5DgmH/enw0SOY62vjDHN2eTxkeGWioC8u/JYNlmVoQHlAgZ626fJYwVg4fApkzBb1F8fvRAkSLM6qlPTi/1N2xCR7/BcNmyY7Dj0ga2aMh+lLNN7q+gCheSsEeteIdbsAAQMIoIYBAAAAAAABBCJRIKBIDZZnnBk25au9SLqw4B/6yx7n0cnhaC8VQBaZZYXZAQlCAot5DgmH/enw0SOY62vjDHN2eTxkeGWioC8u/JYNlmVoA7RDRzNcY/UF5V4mirKA8xi0pqoUSS/zecNmih9U74ybAAEDCDDmAgAAAAAAAQQiUSB2Hb00kpiEpmcp69zbrPFnSC32LBTwmOTvGNcP4Grv0AEJQgJfDsGzjZmEp+ti8VvkAwIEEPFC7fTWnjY/VhWv0qa2zQK+EFEqlVNkHkB62OUJo0GEUqkC6FVqDboumD9eAPe8UQEKBAAAAAAA", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQECAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR/gkwQAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCICJa45kPVF365uoUKpN0arSqgSfjyBNY63wha08anrtiAiBl42BSGRArJQdeOVXz80zjvPKjXR4QFd1dw7/tPs81qQEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0CXw7Bs42ZhKfrYvFb5AMCBBDxQu301p42P1YVr9Kmts0hA84eNnXZDZkmSfqd2xH5AlSbs7AvVvbonMCgTIuMN92UIh0Ci3kOCYf96fDRI5jra+MMc3Z5PGR4ZaKgLy78lg2WZWghAlWBv6pzMVb2uY5xom9w/XazZ4ZJ9isfxed7jpq45hBQIh4CXw7Bs42ZhKfrYvFb5AMCBBDxQu301p42P1YVr9Kmts1ADolhPR9rJTbbj6kkIA2zpvjvyhciVWTVaxEuUIZbTTAuBY+m9hwp4jZqSnJp1FVaXGHWVVnb+/rJ4urf5TY31SIeAot5DgmH/enw0SOY62vjDHN2eTxkeGWioC8u/JYNlmVoQHlAgZ626fJYwVg4fApkzBb1F8fvRAkSLM6qlPTi/1N2xCR7/BcNmyY7Dj0ga2aMh+lLNN7q+gCheSsEeteIdbsAAQMIoIYBAAAAAAABBCJRIKBIDZZnnBk25au9SLqw4B/6yx7n0cnhaC8VQBaZZYXZAQlCAot5DgmH/enw0SOY62vjDHN2eTxkeGWioC8u/JYNlmVoA7RDRzNcY/UF5V4mirKA8xi0pqoUSS/zecNmih9U74ybAAEDCDDmAgAAAAAAAQQiUSDkKhgUiNj/9ucWH3FfXvD0fcbISAhIqtaERxvz6ljzUgEJQgJfDsGzjZmEp+ti8VvkAwIEEPFC7fTWnjY/VhWv0qa2zQLOCzIZpapd+PIc45PpYs3afVFUbaTTXDjtAYilludg8wEKBAAAAAAA", "supplementary": { "inputs": [ { @@ -958,16 +958,16 @@ { "output_index": 1, "amount": 190000, - "sp_v0_info": "025f0ec1b38d9984a7eb62f15be403020410f142edf4d69e363f5615afd2a6b6cd037cd462358c69c40525247a8793944d9e67484b248fd84a1e7ee4f920b4da41b5", + "sp_v0_info": "025f0ec1b38d9984a7eb62f15be403020410f142edf4d69e363f5615afd2a6b6cd02ce0b3219a5aa5df8f21ce393e962cdda7d51546da4d35c38ed0188a596e760f3", "sp_v0_label": 0, - "script": "5120761dbd34929884a66729ebdcdbacf167482df62c14f098e4ef18d70fe06aefd0" + "script": "5120e42a181488d8fff6e7161f715f5ef0f47dc6c8480848aad684471bf3ea58f352" } ] } }, { - "description": "can finalize: two sp outputs - output 0 uses label=3 / output 1 uses label=1", - "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQECAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR/gkwQAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCICJa45kPVF365uoUKpN0arSqgSfjyBNY63wha08anrtiAiBl42BSGRArJQdeOVXz80zjvPKjXR4QFd1dw7/tPs81qQEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Ci3kOCYf96fDRI5jra+MMc3Z5PGR4ZaKgLy78lg2WZWghAlWBv6pzMVb2uY5xom9w/XazZ4ZJ9isfxed7jpq45hBQIh4Ci3kOCYf96fDRI5jra+MMc3Z5PGR4ZaKgLy78lg2WZWhAeUCBnrbp8ljBWDh8CmTMFvUXx+9ECRIszqqU9OL/U3bEJHv8Fw2bJjsOPSBrZoyH6Us03ur6AKF5KwR614h1uwABAwighgEAAAAAAAEEIlEgyvcy9B51UZCclKM10VeOp7dmAnuIbN0cFwHZv8a3hIgBCUICi3kOCYf96fDRI5jra+MMc3Z5PGR4ZaKgLy78lg2WZWgDliRhtuNd91TQDUhtw53swgJxhFB2YU5cDtLJDyPT6b4BCgQDAAAAAAEDCDDmAgAAAAAAAQQiUSBNB6DTsPYafF7+NsLDxYfItphBhUYAjPgZu9pM4xgSIQEJQgKLeQ4Jh/3p8NEjmOtr4wxzdnk8ZHhloqAvLvyWDZZlaANaVzfUxngmF8xrDElrsrxiVVoyslQkT7ycnANd9GnlqwEKBAEAAAAA", + "description": "can finalize: three sp outputs (same scan key) - output 0 uses label=1, outputs 1 and 2 uses label=2 (same spend key)", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQEBBQEDAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+AGgYAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9HMEQCIFaplAuaU3aICoTcvxrXSZnHH6i4SXpbYSuzopSyB0k4AiA98iUFQysKS0I3s2sHC3R8ZpycvK7JZiE0PcjMfiNnYgEBAwQBAAAAIgYCyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL8IAAAAgAAAAAABEAT+////Ih0Ci3kOCYf96fDRI5jra+MMc3Z5PGR4ZaKgLy78lg2WZWghAlWBv6pzMVb2uY5xom9w/XazZ4ZJ9isfxed7jpq45hBQIh4Ci3kOCYf96fDRI5jra+MMc3Z5PGR4ZaKgLy78lg2WZWhAeUCBnrbp8ljBWDh8CmTMFvUXx+9ECRIszqqU9OL/U3bEJHv8Fw2bJjsOPSBrZoyH6Us03ur6AKF5KwR614h1uwABAwihhgEAAAAAAAEEIlEgG/+hD9qiUCwvDJKF/grxmlD4UV47bfv1behQ2ZkBRAABCUICi3kOCYf96fDRI5jra+MMc3Z5PGR4ZaKgLy78lg2WZWgD1DFYje5hqzo5clspnDdC3Gaghh3s9ZG0I7DlJwcPgjQBCgQBAAAAAAEDCDLmAgAAAAAAAQQiUSCITaZwjIK5tWyYvTvm5fU4lLsb/ZC/rPiUAlVw9r1TBQEJQgKLeQ4Jh/3p8NEjmOtr4wxzdnk8ZHhloqAvLvyWDZZlaAMhk60Mh1+sUkNzgCKQCnckxhdd2smUQWWqtFeq35xPPAEKBAIAAAAAAQMIooYBAAAAAAABBCJRINKR0zkoDgyG4scIpZH46OL4ktEqfP6FELZneZtxExyGAQlCAot5DgmH/enw0SOY62vjDHN2eTxkeGWioC8u/JYNlmVoAyGTrQyHX6xSQ3OAIpAKdyTGF13ayZRBZaq0V6rfnE88AQoEAgAAAAA=", "supplementary": { "inputs": [ { @@ -977,8 +977,8 @@ "prevout_txid": "18a717663b0bab14b12a1a771323ff1e4079dd532e5dd13e28ea1081c700984a", "prevout_index": 0, "prevout_scriptpubkey": "0014229a72d34a645bd3496bbbf50bbb81c9063f4f94", - "amount": 300000, - "witness_utxo": "e093040000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", + "amount": 400000, + "witness_utxo": "801a060000000000160014229a72d34a645bd3496bbbf50bbb81c9063f4f94", "sequence": 4294967294, "signed": true } @@ -994,17 +994,24 @@ "outputs": [ { "output_index": 0, - "amount": 100000, - "sp_v0_info": "028b790e0987fde9f0d12398eb6be30c7376793c647865a2a02f2efc960d96656803b44347335c63f505e55e268ab280f318b4a6aa14492ff379c3668a1f54ef8c9b", - "sp_v0_label": 3, - "script": "5120caf732f41e7551909c94a335d1578ea7b766027b886cdd1c1701d9bfc6b78488" + "amount": 100001, + "sp_v0_info": "028b790e0987fde9f0d12398eb6be30c7376793c647865a2a02f2efc960d96656803d431588dee61ab3a39725b299c3742dc66a0861decf591b423b0e527070f8234", + "sp_v0_label": 1, + "script": "51201bffa10fdaa2502c2f0c9285fe0af19a50f8515e3b6dfbf56de850d999014400" }, { "output_index": 1, - "amount": 190000, - "sp_v0_info": "028b790e0987fde9f0d12398eb6be30c7376793c647865a2a02f2efc960d96656803b44347335c63f505e55e268ab280f318b4a6aa14492ff379c3668a1f54ef8c9b", - "sp_v0_label": 1, - "script": "51204d07a0d3b0f61a7c5efe36c2c3c587c8b698418546008cf819bbda4ce3181221" + "amount": 190002, + "sp_v0_info": "028b790e0987fde9f0d12398eb6be30c7376793c647865a2a02f2efc960d966568032193ad0c875fac5243738022900a7724c6175ddac9944165aab457aadf9c4f3c", + "sp_v0_label": 2, + "script": "5120884da6708c82b9b56c98bd3be6e5f53894bb1bfd90bfacf894025570f6bd5305" + }, + { + "output_index": 2, + "amount": 100002, + "sp_v0_info": "028b790e0987fde9f0d12398eb6be30c7376793c647865a2a02f2efc960d966568032193ad0c875fac5243738022900a7724c6175ddac9944165aab457aadf9c4f3c", + "sp_v0_label": 2, + "script": "5120d291d339280e0c86e2c708a591f8e8e2f892d12a7cfe8510b667799b71131c86" } ] } diff --git a/testing/devtest/unit_silentpayments.py b/testing/devtest/unit_silentpayments.py index 58130bdfb..1a2078c75 100644 --- a/testing/devtest/unit_silentpayments.py +++ b/testing/devtest/unit_silentpayments.py @@ -61,8 +61,6 @@ # ECDH share computation ecdh_share = _compute_ecdh_share(TEST_PRIVKEY, TEST_SCAN_KEY) -assert len(ecdh_share) == 33 -assert ecdh_share[0] in (0x02, 0x03) # Input hash computation (order-independent) outpoints = [ @@ -77,8 +75,6 @@ ] spk = a2b_hex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798") ih = _compute_input_hash(outpoints, spk) -assert isinstance(ih, bytes) and len(ih) == 32 -assert int.from_bytes(ih, "big") > 0 assert _compute_input_hash(outpoints[::-1], spk) == ih # Shared secret tweak (different k -> different tweak) @@ -118,8 +114,6 @@ pubkey = ngu.secp256k1.ec_pubkey_tweak_mul(G, TEST_PRIVKEY) proof = generate_dleq_proof(TEST_PRIVKEY, TEST_SCAN_KEY) -assert len(proof) == 64 -assert verify_dleq_proof(pubkey, TEST_SCAN_KEY, ecdh_share, proof) # Tampered proof rejected tampered = bytearray(proof) @@ -200,11 +194,8 @@ # Spending Privkey # --------------------------------------------------------------------------- -result = compute_silent_payment_spending_privkey((1).to_bytes(32, "big"), (42).to_bytes(32, "big")) -assert isinstance(result, bytes) and len(result) == 32 -assert result != (1).to_bytes(32, "big") -assert 0 < int.from_bytes(result, "big") < ngu.secp256k1.curve_order_int() - +# Valid computation is value-tested by BIP-352 receive vectors; only the +# zero-input error path is unique to this unit test. try: compute_silent_payment_spending_privkey(b"\x00" * 32, b"\x00" * 32) assert False, "Should raise" @@ -522,6 +513,7 @@ def _make_sp_output(scan_key): expected = _compute_silent_payment_output_script(outpoints, summed_pk, combined_share, TEST_SPEND_KEY, 0) assert outp.script == expected +# BIP-375: order by spend key when to determine k order when scan key is the same # k counter per scan key (skips non-SP outputs) psbt, scan_key, es, _, pk, _ = _make_mock_psbt_with_global_proofs() spend_key_a = a2b_hex("022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4") @@ -541,9 +533,10 @@ def _make_sp_output(scan_key): ops = [(b"\x01" * 32, b"\x00\x00\x00\x00")] spk_sum = _combine_pubkeys([pk]) +# spend keys sorted ascending: a (022f..) < c (0279..) < b (02c6..) assert sp0.script == _compute_silent_payment_output_script(ops, spk_sum, es, spend_key_a, k=0) -assert sp2.script == _compute_silent_payment_output_script(ops, spk_sum, es, spend_key_b, k=1) -assert sp4.script == _compute_silent_payment_output_script(ops, spk_sum, es, spend_key_c, k=2) +assert sp4.script == _compute_silent_payment_output_script(ops, spk_sum, es, spend_key_c, k=1) +assert sp2.script == _compute_silent_payment_output_script(ops, spk_sum, es, spend_key_b, k=2) assert reg1.script is None assert reg3.script is None diff --git a/testing/test_bip375_vectors.py b/testing/test_bip375_vectors.py index 0f6f9d581..b1944d978 100644 --- a/testing/test_bip375_vectors.py +++ b/testing/test_bip375_vectors.py @@ -90,12 +90,8 @@ def _get(self, substr): ("k values assigned to wrong output indices", "output script mismatch"), ] - @pytest.mark.parametrize( - "desc,expected_err", _SIM_INVALID, ids=[d for d, _ in _SIM_INVALID] - ) - def test_invalid_vectors_sim_validate_sp( - self, sim_exec, sim_execfile, desc, expected_err - ): + @pytest.mark.parametrize("desc,expected_err", _SIM_INVALID, ids=[d for d, _ in _SIM_INVALID]) + def test_invalid_vectors_sim_validate_sp(self, sim_exec, sim_execfile, desc, expected_err): p = BasicPSBT().parse(self._get(desc)["psbt"].encode()) err = _sim_validate_sp(sim_exec, sim_execfile, p) assert err, "Expected validation failure for: %s" % desc @@ -121,24 +117,15 @@ def test_valid_ecdh_computation(self, sim_exec, sim_execfile): eligible_privkeys = [ unhexlify(inp["private_key"]) for inp in vec["supplementary"]["inputs"] - if inp["private_key"] - and _sim_pubkey_from_input( - sim_exec, sim_execfile, p, inp["input_index"] - ) + if inp["private_key"] and _sim_pubkey_from_input(sim_exec, sim_execfile, p, inp["input_index"]) ] for expected in vec["supplementary"]["sp_proofs"]: scan_key = unhexlify(expected["scan_key"]) expected_ecdh = unhexlify(expected["ecdh_share"]) if "input_index" in expected: - ik = next( - k - for k in vec["supplementary"]["inputs"] - if k["input_index"] == expected["input_index"] - ) + ik = next(k for k in vec["supplementary"]["inputs"] if k["input_index"] == expected["input_index"]) privkey = unhexlify(ik["private_key"]) - actual = _sim_compute_ecdh_share( - sim_exec, sim_execfile, privkey, scan_key - ) + actual = _sim_compute_ecdh_share(sim_exec, sim_execfile, privkey, scan_key) else: shares = [ _sim_compute_ecdh_share( @@ -150,9 +137,9 @@ def test_valid_ecdh_computation(self, sim_exec, sim_execfile): for privkey in eligible_privkeys ] actual = _sim_combine_pubkeys(sim_exec, sim_execfile, shares) - assert actual == expected_ecdh, ( - "ECDH mismatch in valid[%d] for scan_key %s" - % (vi, expected["scan_key"][:16]) + assert actual == expected_ecdh, "ECDH mismatch in valid[%d] for scan_key %s" % ( + vi, + expected["scan_key"][:16], ) def test_valid_dleq_verification(self, sim_exec, sim_execfile): @@ -162,12 +149,8 @@ def test_valid_dleq_verification(self, sim_exec, sim_execfile): for scan_key, proof in p.sp_global_dleq_proofs.items(): ecdh_share = p.sp_global_ecdh_shares.get(scan_key) - assert ecdh_share is not None, ( - "valid[%d]: global ECDH share missing for scan key" % vi - ) - _, summed_pubkey = _sim_get_ecdh_and_pubkey( - sim_exec, sim_execfile, p, scan_key - ) + assert ecdh_share is not None, "valid[%d]: global ECDH share missing for scan key" % vi + _, summed_pubkey = _sim_get_ecdh_and_pubkey(sim_exec, sim_execfile, p, scan_key) _sim_verify_dleq( sim_exec, sim_execfile, @@ -185,9 +168,7 @@ def test_valid_dleq_verification(self, sim_exec, sim_execfile): continue for scan_key, proof in inp.sp_dleq_proofs.items(): ecdh_share = inp.sp_ecdh_shares.get(scan_key) - assert ecdh_share is not None, ( - "valid[%d] inp[%d]: ECDH share missing" % (vi, inp_idx) - ) + assert ecdh_share is not None, "valid[%d] inp[%d]: ECDH share missing" % (vi, inp_idx) _sim_verify_dleq( sim_exec, sim_execfile, @@ -202,18 +183,17 @@ def test_valid_output_script_derivation(self, sim_exec, sim_execfile): vec = self.VALID_VECTORS[vi] p = BasicPSBT().parse(vec["psbt"].encode()) sp_outputs = [ - (o.sp_v0_info[:33], o.sp_v0_info[33:66], o.script) - for o in p.outputs + (o.sp_v0_info[:33], o.sp_v0_info[33:66], out_index, o.script) + for out_index, o in enumerate(p.outputs) if o.sp_v0_info and len(o.sp_v0_info) == 66 ] if not sp_outputs: continue + sp_outputs.sort(key=lambda t: (t[0], t[1], t[2])) outpoints = _sim_get_outpoints(sim_exec, sim_execfile, p) scan_key_k = {} - for scan_key, spend_key, script in sp_outputs: - ecdh_share, summed_pubkey = _sim_get_ecdh_and_pubkey( - sim_exec, sim_execfile, p, scan_key - ) + for scan_key, spend_key, _oi, script in sp_outputs: + ecdh_share, summed_pubkey = _sim_get_ecdh_and_pubkey(sim_exec, sim_execfile, p, scan_key) if not ecdh_share: continue if not script: # If script is missing, skip vectors test missing script @@ -230,6 +210,4 @@ def test_valid_output_script_derivation(self, sim_exec, sim_execfile): spend_key, k, ) - assert script == expected, ( - "valid[%d]: output script mismatch at k=%d" % (vi, k) - ) + assert script == expected, "valid[%d]: output script mismatch at k=%d" % (vi, k) From 6c16649767dadaa06b6076b2df50b14f806108a8 Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Thu, 16 Jul 2026 09:58:47 -0400 Subject: [PATCH 22/23] feat (SP): hide export finalize txn option for silent payment outputs Discourage broadcasting silent payments directly from the device. The wallet coordinator should be responsible for verifying the PSBT and finalizing for broadcast. - Users can restore the default ColdCard behavior by using the Danger Zone -> SP Final Export setting. - USB API was not modified. --- shared/auth.py | 27 +++++++++-- shared/flow.py | 8 +++ shared/nvstore.py | 1 + testing/test_silentpayments.py | 89 ++++++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+), 5 deletions(-) diff --git a/shared/auth.py b/shared/auth.py index cfaf64ab3..620806529 100644 --- a/shared/auth.py +++ b/shared/auth.py @@ -30,6 +30,10 @@ TXN_INPUT_OFFSET = 0 TXN_OUTPUT_OFFSET = MAX_TXN_LEN +SP_EXPORT_NOTE = '''\ +Silent Payment output detected - finalize signed PSBT on wallet coordinator to broadcast. +Direct broadcast requires opt-in via Danger Zone > SP Final Export.''' + class UserAuthorizedAction: active_request = None @@ -855,6 +859,7 @@ async def done_signing(psbt, tx_req, input_method=None, filename=None, # User authorized PSBT for signing, and we added signatures. # - allow PushTX if enabled (first thing) # - can save final TXN out to SD card/VirtDisk, share by NFC, QR. + # - silent payments should discourage save final txn by default from glob import PSRAM, hsm_active from sffile import SFFile @@ -866,9 +871,15 @@ async def done_signing(psbt, tx_req, input_method=None, filename=None, base_title = "PSBT " + ("Signed" if psbt.sig_added else "Updated") is_complete = psbt.is_complete() + sp_export_hint = None if finalize is not None: # USB case - user can choose whether to attempt finalization is_complete = finalize + elif is_complete and psbt.has_silent_payment_outputs(): + from glob import settings + if not settings.get('spfin', False): + is_complete = False + sp_export_hint = SP_EXPORT_NOTE with SFFile(TXN_OUTPUT_OFFSET, max_size=MAX_TXN_LEN, message="Saving...") as psram: if is_complete: @@ -876,7 +887,9 @@ async def done_signing(psbt, tx_req, input_method=None, filename=None, noun = "Finalized TX ready for broadcast" else: psbt.serialize(psram) - noun = "Partly Signed PSBT" + # not finalizing: either genuinely partial (needs more sigs) or an SP tx we + # kept as PSBT on purpose - only the latter (sp_export_hint) is fully signed. + noun = "Signed PSBT" if sp_export_hint else "Partly Signed PSBT" txid = None data_len = psram.tell() @@ -991,13 +1004,14 @@ async def done_signing(psbt, tx_req, input_method=None, filename=None, # typical case: save to SD card, show filenames we used assert isinstance(ch, dict) msg = await _save_to_disk(psbt, txid, ch, is_complete, data_len, - output_encoder, filename) + output_encoder, filename, sp_export_hint) input_method = None first_time = False title = base_title -async def _save_to_disk(psbt, txid, save_options, is_complete, data_len, output_encoder, filename=None): +async def _save_to_disk(psbt, txid, save_options, is_complete, data_len, output_encoder, + filename=None, sp_note=None): # Saving a PSBT from PSRAM to something disk-like. # - handle save-to-SD/VirtDisk cases. With re-attempt when no card, etc. assert isinstance(save_options, dict) # from import_export_prompt @@ -1033,7 +1047,7 @@ def _chunk_write(file_d, ofs, chunk=4096): while 1: # try to put back into same spot, but also do top-of-card - if not is_complete: + if not is_complete and not sp_note: # keep the filename under control during multiple passes target_fname = base.replace('-part', '') + '-part.psbt' else: @@ -1113,7 +1127,7 @@ def _chunk_write(file_d, ofs, chunk=4096): # Done, show the filenames we used. if out_fn: msg = "Updated PSBT is:\n\n%s" % out_fn - if out2_fn: + if out2_fn or sp_note: msg += '\n\n' else: # del_after is probably set @@ -1121,6 +1135,9 @@ def _chunk_write(file_d, ofs, chunk=4096): if out2_fn: msg += 'Finalized transaction (ready for broadcast):\n\n%s' % out2_fn + elif sp_note: + # in place of the finalized-txn line: explain why it was not produced + msg += sp_note return msg diff --git a/shared/flow.py b/shared/flow.py index 0e50f32eb..af93b90c5 100644 --- a/shared/flow.py +++ b/shared/flow.py @@ -357,6 +357,14 @@ async def goto_home(*a): warnings, funds can be stolen by specially crafted PSBT or MitM. Keep blocked unless you intend to sign special transactions.'''), + ToggleMenuItem("SP Final Export", "spfin", ["Default: PSBT", "Allow Final"], + story='''\ +Silent Payment outputs are computed on-device. A coordinator requires a signed \ +PSBT to verify the computation. By default, Silent Payment transactions \ +export as PSBT for the coordinator to finalize. + +Enable only if you intend to broadcast Silent Payment \ +transactions directly from this device.'''), ToggleMenuItem('Testnet Mode', 'chain', ['Bitcoin', 'Testnet4', 'Regtest'], value_map=['BTC', 'XTN', 'XRT'], on_change=change_which_chain, diff --git a/shared/nvstore.py b/shared/nvstore.py index 2c543bcd0..a2dcc7fbc 100644 --- a/shared/nvstore.py +++ b/shared/nvstore.py @@ -70,6 +70,7 @@ # sssp = (complex) If present, a (single signer) spending-policy is defined (maybe disabled) # lfr = (string) If present, the reason why Spending Policy blocked last transaction # wifs = (list) List of tuples (public/private key) +# spfin = (bool) allow finalized/broadcast export of Silent Payment txns (default: PSBT only) # Stored w/ key=00 for access before login # _skip_pin = hard code a PIN value (dangerous, only for debug) diff --git a/testing/test_silentpayments.py b/testing/test_silentpayments.py index fc95db604..5d830495b 100644 --- a/testing/test_silentpayments.py +++ b/testing/test_silentpayments.py @@ -871,3 +871,92 @@ def sp_hacker(psbt): time.sleep(SIMULATOR_DELAY) with pytest.raises(CCProtoError, match="SP change output keys do not match this device"): end_sign(accept=True, finalize=False) + + +# --------------------------------------------------------------------------- +# Finalized/broadcast export policy for SP-output transactions +# --------------------------------------------------------------------------- + + +def _one_sp_output_psbt(dev, fake_txn): + def sp_hacker(psbt): + _add_sp_outputs(psbt, [(0, SCAN_KEY, SPEND_KEY_B)]) + return fake_txn(1, 1, dev.master_xpub, addr_fmt="p2wpkh", psbt_v2=True, psbt_hacker=sp_hacker) + + +def _sp_sign_microsd(open_microsd, microsd_path, goto_home, pick_menu_item, + cap_story, need_keypress, psbt_bytes): + # Sign an SP-output PSBT via the air-gapped microSD flow (finalize=None path), + # approve, and let the first-pass auto-echo save to SD. Returns the save-summary + # story and the files that ended up on the card. + import os + from glob import glob as _glob + + name = 'sptest' + for f in _glob(microsd_path(name + '*')): + os.remove(f) + with open_microsd(name + '.psbt', 'wb') as sd: + sd.write(psbt_bytes) + + goto_home() + pick_menu_item('Ready To Sign') + time.sleep(0.1) + title, story = cap_story() + if 'OK TO SEND' not in title: + pick_menu_item(name + '.psbt') + time.sleep(0.1) + title, story = cap_story() + assert title == 'OK TO SEND?' + need_keypress('y') + + time.sleep(0.1) + title, story = cap_story() + assert 'Updated PSBT' in story + + txn = _glob(microsd_path(name + '*-final.txn')) + signed = _glob(microsd_path(name + '*-signed.psbt')) + part = _glob(microsd_path(name + '*-part.psbt')) + return story, txn, (signed or part) + + +def test_sp_export_defaults_to_psbt(dev, fake_txn, settings_remove, open_microsd, + microsd_path, goto_home, pick_menu_item, cap_story, need_keypress): + # Default (spfin unset): SP-output tx exports the signed PSBT, no broadcast tx, + # and the policy note replaces the finalized-transaction line. + settings_remove('spfin') + psbt_bytes = _one_sp_output_psbt(dev, fake_txn) + + story, txn, psbt_out = _sp_sign_microsd(open_microsd, microsd_path, goto_home, + pick_menu_item, cap_story, need_keypress, psbt_bytes) + + assert not txn, "no finalized broadcast tx by default" + assert psbt_out and 'signed.psbt' in psbt_out[0] + assert 'Finalized transaction' not in story + assert 'SP Final Export' in story, "policy note should be shown" + + tp = BasicPSBT().parse(open(psbt_out[0], 'rb').read()) + assert any(o.sp_v0_info for o in tp.outputs), "PSBT retains SP metadata for verification" + + +def test_sp_export_final_allowed(dev, fake_txn, settings_set, settings_remove, open_microsd, + microsd_path, goto_home, pick_menu_item, cap_story, need_keypress): + # With the opt-in setting, the current finalized-export behavior is restored. + settings_set('spfin', 1) + try: + psbt_bytes = _one_sp_output_psbt(dev, fake_txn) + story, txn, _ = _sp_sign_microsd(open_microsd, microsd_path, goto_home, + pick_menu_item, cap_story, need_keypress, psbt_bytes) + assert txn, "finalized broadcast tx written when allowed" + assert 'Finalized transaction' in story + assert 'SP Final Export' not in story + finally: + settings_remove('spfin') + + +def test_sp_export_usb_finalize_unchanged(dev, fake_txn, start_sign, end_sign, settings_remove): + # USB STXN_FINALIZE is honored despite SP outputs and spfin unset (USB path untouched). + settings_remove('spfin') + psbt_bytes = _one_sp_output_psbt(dev, fake_txn) + start_sign(psbt_bytes, finalize=True) + time.sleep(SIMULATOR_DELAY) + end_sign(accept=True, finalize=True) # asserts a non-PSBT finalized tx is returned From 0075b19d1248defb2c2953acc8705a16e45c1b8e Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Mon, 3 Aug 2026 16:42:44 -0400 Subject: [PATCH 23/23] fix (SP): add dleq.py and silentpayments.py to manifest If shared files are not included in manifest bundling fails and will brick the device before PIN entry. --- shared/manifest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shared/manifest.py b/shared/manifest.py index 6d4b087a3..3e05f3a31 100644 --- a/shared/manifest.py +++ b/shared/manifest.py @@ -17,6 +17,7 @@ 'desc_utils.py', 'dev_helper.py', 'display.py', + 'dleq.py', 'drv_entro.py', 'exceptions.py', 'export.py', @@ -52,6 +53,7 @@ 'selftest.py', 'serializations.py', 'sffile.py', + 'silentpayments.py', 'ssd1306.py', 'stash.py', 'tapsigner.py',