diff --git a/.typos.toml b/.typos.toml index 38ee447461..e30e9e6bd4 100644 --- a/.typos.toml +++ b/.typos.toml @@ -15,6 +15,8 @@ extend-ignore-re = [ "ser.*", "prefix.*", "value: .*", + "pqNTRUsign", + "Strnad", ] [default.extend-words] diff --git a/README.mediawiki b/README.mediawiki index ed4e1dd24a..55580ee0aa 100644 --- a/README.mediawiki +++ b/README.mediawiki @@ -1,5 +1,5 @@ -People wishing to submit a BIP should first describe their idea to the [https://groups.google.com/g/bitcoindev -bitcoindev@googlegroups.com] mailing list to gather feedback on viability and community interest before working on a +People wishing to submit a BIP should first describe their idea to the [https://groups.google.com/g/bitcoindev bitcoindev@googlegroups.com] +mailing list to gather feedback on viability and community interest before working on a formal description. Please open a pull request to this repository only when substantial progress on the draft has been made, preferably when the draft is nearing completion. Authors do not assign a number to their own proposal. After a proposal meets the editorial criteria, a BIP Editor will assign a number to it and publish the proposal by @@ -512,6 +512,13 @@ users (see also: [https://en.bitcoin.it/wiki/Economic_majority economic majority | Dmitry Petukhov | Informational | Complete +|- +| [[bip-0089.mediawiki|89]] +| Applications +| Chain Code Delegation +| Jesse Posner, Jurvis Tan +| Specification +| Draft |- style="background-color: #cfffcf" | [[bip-0090.mediawiki|90]] | @@ -617,6 +624,13 @@ users (see also: [https://en.bitcoin.it/wiki/Economic_majority economic majority | Gavin Andresen | Specification | Closed +|- +| [[bip-0110.mediawiki|110]] +| Consensus (soft fork) +| Reduced Data Temporary Softfork +| Dathon Ohm +| Specification +| Draft |- style="background-color: #cfffcf" | [[bip-0111.mediawiki|111]] | Peer Services @@ -736,6 +750,13 @@ users (see also: [https://en.bitcoin.it/wiki/Economic_majority economic majority | Steven Roose | Specification | Draft +|- +| [[bip-0128.mediawiki|128]] +| Applications +| Timelock-Recovery Storage Format +| Oren Z +| Specification +| Draft |- style="background-color: #ffffcf" | [[bip-0129.mediawiki|129]] | Applications @@ -1171,12 +1192,19 @@ users (see also: [https://en.bitcoin.it/wiki/Economic_majority economic majority | Specification | Closed |- +| [[bip-0346.md|346]] +| Consensus (soft fork) +| OP_TXHASH +| Steven Roose, Brandon Black +| Specification +| Draft +|- style="background-color: #ffffcf" | [[bip-0347.mediawiki|347]] | Consensus (soft fork) | OP_CAT in Tapscript | Ethan Heilman, Armin Sabouri | Specification -| Draft +| Complete |- | [[bip-0348.md|348]] | Consensus (soft fork) @@ -1209,7 +1237,7 @@ users (see also: [https://en.bitcoin.it/wiki/Economic_majority economic majority | [[bip-0352.mediawiki|352]] | Applications | Silent Payments -| josibake, Ruben Somsen +| josibake, Ruben Somsen, Sebastian Falbesoner | Specification | Complete |- style="background-color: #ffffcf" @@ -1219,6 +1247,13 @@ users (see also: [https://en.bitcoin.it/wiki/Economic_majority economic majority | Matt Corallo, Bastien Teinturier | Specification | Complete +|- +| [[bip-0360.mediawiki|360]] +| Consensus (soft fork) +| Pay-to-Merkle-Root (P2MR) +| Hunter Beast, Ethan Heilman, Isabel Foxen Duke +| Specification +| Draft |- style="background-color: #cfffcf" | [[bip-0370.mediawiki|370]] | Applications @@ -1346,6 +1381,13 @@ users (see also: [https://en.bitcoin.it/wiki/Economic_majority economic majority | Informational | Draft |- +| [[bip-0392.mediawiki|392]] +| Applications +| Silent Payment Output Script Descriptors +| Craig Raw +| Specification +| Draft +|- | [[bip-0431.mediawiki|431]] | Applications | Topology Restrictions for Pinning @@ -1360,6 +1402,20 @@ users (see also: [https://en.bitcoin.it/wiki/Economic_majority economic majority | Informational | Draft |- +| [[bip-0434.md|434]] +| Peer Services +| Peer Feature Negotiation +| Anthony Towns +| Specification +| Draft +|- +| [[bip-0442.md|442]] +| Consensus (soft fork) +| OP_PAIRCOMMIT +| moonsettler, Brandon Black +| Specification +| Draft +|- | [[bip-0443.mediawiki|443]] | Consensus (soft fork) | OP_CHECKCONTRACTVERIFY diff --git a/bip-0020.mediawiki b/bip-0020.mediawiki index 93594d15df..a0d6e34b1e 100644 --- a/bip-0020.mediawiki +++ b/bip-0020.mediawiki @@ -7,6 +7,7 @@ Type: Specification Assigned: 2011-01-10 License: BSD-2-Clause + Proposed-Replacement: 21 BIP 0020 is based off an earlier document by Nils Schneider. '''And has been replaced by BIP 0021''' diff --git a/bip-0021.mediawiki b/bip-0021.mediawiki index 5f2756a17d..f97fd24348 100644 --- a/bip-0021.mediawiki +++ b/bip-0021.mediawiki @@ -7,6 +7,7 @@ Status: Closed Type: Specification Assigned: 2012-01-29 + Replaces: 20 Proposed-Replacement: 321 diff --git a/bip-0032.mediawiki b/bip-0032.mediawiki index dc7689d00a..558bbaab60 100644 --- a/bip-0032.mediawiki +++ b/bip-0032.mediawiki @@ -50,10 +50,10 @@ Addition (+) of two coordinate pair is defined as application of the EC group op Concatenation (||) is the operation of appending one byte sequence onto another. As standard conversion functions, we assume: -* point(p): returns the coordinate pair resulting from EC point multiplication (repeated application of the EC group operation) of the secp256k1 base point with the integer p. +* point(p): returns the coordinate pair resulting from EC point multiplication (repeated application of the EC group operation) of the secp256k1 base point with the integer p (i.e., the operation used to compute a public key from a private key). * ser32(i): serialize a 32-bit unsigned integer i as a 4-byte sequence, most significant byte first. * ser256(p): serializes the integer p as a 32-byte sequence, most significant byte first. -* serP(P): serializes the coordinate pair P = (x,y) as a byte sequence using SEC1's compressed form: (0x02 or 0x03) || ser256(x), where the header byte depends on the parity of the omitted y coordinate. +* serP(P): serializes the coordinate pair P = (x,y) (i.e., the public key) as a byte sequence using [https://www.secg.org/sec1-v2.pdf SEC1]'s compressed form: (0x02 or 0x03) || ser256(x), where the header byte depends on the parity of the omitted y coordinate. * parse256(p): interprets a 32-byte sequence as a 256-bit number, most significant byte first. diff --git a/bip-0044.mediawiki b/bip-0044.mediawiki index bbf9d1f04a..a94efba2ec 100644 --- a/bip-0044.mediawiki +++ b/bip-0044.mediawiki @@ -9,6 +9,7 @@ Status: Deployed Type: Specification Assigned: 2014-04-24 + Requires: 32, 43 ==Abstract== diff --git a/bip-0069/bip-0069_examples.py b/bip-0069/bip-0069_examples.py old mode 100644 new mode 100755 index be103a727c..d18a8f9ca9 --- a/bip-0069/bip-0069_examples.py +++ b/bip-0069/bip-0069_examples.py @@ -1,4 +1,6 @@ +#!/usr/bin/env python3 import binascii +from functools import cmp_to_key #returns -1 if barr1 is less, 1 if barr1 is greater, and 0 if equal def bytearr_cmp(barr1, barr2): @@ -32,9 +34,10 @@ def input_cmp(input_tuple1, input_tuple2): raise ValueError('Matching previous transaction hash and previous transaction output index for two distinct inputs. Invalid!') def sort_inputs(input_tuples): - return sorted(input_tuples, cmp=input_cmp) + return sorted(input_tuples, key=cmp_to_key(input_cmp)) def print_inputs(ordered_input_tuples): + print("inputs") index = 0 for prev_tx_hash_byte_arr_little_endian, prev_tx_output_index in ordered_input_tuples: prev_tx_hash_hex = binascii.hexlify(bytearray(prev_tx_hash_byte_arr_little_endian)) @@ -52,9 +55,10 @@ def output_cmp(output_tuple1, output_tuple2): return bytearr_cmp(output_tuple1[1], output_tuple2[1]) def sort_outputs(output_tuples): - return sorted(output_tuples, cmp=output_cmp) + return sorted(output_tuples, key=cmp_to_key(output_cmp)) def print_outputs(ordered_output_tuples): + print("outputs") index = 0 for amount, scriptPubKey_byte_arr in ordered_output_tuples: scriptPubKey_hex = binascii.hexlify(bytearray(scriptPubKey_byte_arr)) @@ -82,6 +86,7 @@ def main(): ([0x7d, 0x03, 0x7c, 0xeb, 0x2e, 0xe0, 0xdc, 0x03, 0xe8, 0x2f, 0x17, 0xbe, 0x79, 0x35, 0xd2, 0x38, 0xb3, 0x5d, 0x1d, 0xea, 0xbf, 0x95, 0x3a, 0x89, 0x2a, 0x45, 0x07, 0xbf, 0xbe, 0xeb, 0x3b, 0xa4], 1), ([0x6c, 0x1d, 0x56, 0xf3, 0x1b, 0x2d, 0xe4, 0xbf, 0xc6, 0xaa, 0xea, 0x28, 0x39, 0x6b, 0x33, 0x31, 0x02, 0xb1, 0xf6, 0x00, 0xda, 0x9c, 0x6d, 0x61, 0x49, 0xe9, 0x6c, 0xa4, 0x3f, 0x11, 0x02, 0xb1], 1), ([0xb4, 0x11, 0x2b, 0x8f, 0x90, 0x0a, 0x7c, 0xa0, 0xc8, 0xb0, 0xe7, 0xc4, 0xdf, 0xad, 0x35, 0xc6, 0xbe, 0x5f, 0x6b, 0xe4, 0x6b, 0x34, 0x58, 0x97, 0x49, 0x88, 0xe1, 0xcd, 0xb2, 0xfa, 0x61, 0xb8], 0)] + print("\ntx 0a6a357e2f7796444e02638749d9611c008b253fb55f5dc88b739b230ed0c4c3") tx_0a6a_sorted_input_tuples = sort_inputs(tx_0a6a_input_tuples) print_inputs(tx_0a6a_sorted_input_tuples) @@ -94,10 +99,11 @@ def main(): #reference data: https://blockchain.info/rawtx/28204cad1d7fc1d199e8ef4fa22f182de6258a3eaafe1bbe56ebdcacd3069a5f thanks @quantabytes! tx_2820_input_tuples = [ - # (prev_tx_hash, prev_tx_output_index) - ("35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055", 0), - ("35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055", 1)] #duplicate prev_tx_hash + # (prev_tx_hash_byte_arr_little_endian, prev_tx_output_index) + ([0x55, 0x60, 0x5d, 0xc6, 0x5f, 0x3c, 0xcc, 0x4d, 0xb1, 0xd8, 0x56, 0x4a, 0x28, 0x04, 0x2b, 0xdb, 0x2c, 0x2b, 0xe3, 0x85, 0xea, 0xb2, 0xeb, 0xea, 0x41, 0x19, 0xee, 0x9c, 0x26, 0x8d, 0x28, 0x35], 0), + ([0x55, 0x60, 0x5d, 0xc6, 0x5f, 0x3c, 0xcc, 0x4d, 0xb1, 0xd8, 0x56, 0x4a, 0x28, 0x04, 0x2b, 0xdb, 0x2c, 0x2b, 0xe3, 0x85, 0xea, 0xb2, 0xeb, 0xea, 0x41, 0x19, 0xee, 0x9c, 0x26, 0x8d, 0x28, 0x35], 1)] #duplicate prev_tx_hash + print("\ntx 28204cad1d7fc1d199e8ef4fa22f182de6258a3eaafe1bbe56ebdcacd3069a5f") tx_2820_sorted_input_tuples = sort_inputs(tx_2820_input_tuples) print_inputs(tx_2820_sorted_input_tuples) @@ -106,7 +112,7 @@ def main(): (100000000, [0x41, 0x04, 0x6a, 0x07, 0x65, 0xb5, 0x86, 0x56, 0x41, 0xce, 0x08, 0xdd, 0x39, 0x69, 0x0a, 0xad, 0xe2, 0x6d, 0xfb, 0xf5, 0x51, 0x14, 0x30, 0xca, 0x42, 0x8a, 0x30, 0x89, 0x26, 0x13, 0x61, 0xce, 0xf1, 0x70, 0xe3, 0x92, 0x9a, 0x68, 0xae, 0xe3, 0xd8, 0xd4, 0x84, 0x8b, 0x0c, 0x51, 0x11, 0xb0, 0xa3, 0x7b, 0x82, 0xb8, 0x6a, 0xd5, 0x59, 0xfd, 0x2a, 0x74, 0x5b, 0x44, 0xd8, 0xe8, 0xd9, 0xdf, 0xdc, 0x0c, 0xac]), (2400000000, [0x41, 0x04, 0x4a, 0x65, 0x6f, 0x06, 0x58, 0x71, 0xa3, 0x53, 0xf2, 0x16, 0xca, 0x26, 0xce, 0xf8, 0xdd, 0xe2, 0xf0, 0x3e, 0x8c, 0x16, 0x20, 0x2d, 0x2e, 0x8a, 0xd7, 0x69, 0xf0, 0x20, 0x32, 0xcb, 0x86, 0xa5, 0xeb, 0x5e, 0x56, 0x84, 0x2e, 0x92, 0xe1, 0x91, 0x41, 0xd6, 0x0a, 0x01, 0x92, 0x8f, 0x8d, 0xd2, 0xc8, 0x75, 0xa3, 0x90, 0xf6, 0x7c, 0x1f, 0x6c, 0x94, 0xcf, 0xc6, 0x17, 0xc0, 0xea, 0x45, 0xaf, 0xac])] tx_2820_sorted_output_tuples = sort_outputs(tx_2820_output_tuples) - print_outputs(tx_2820_output_tuples) + print_outputs(tx_2820_sorted_output_tuples) if __name__ == "__main__": main() diff --git a/bip-0085.mediawiki b/bip-0085.mediawiki index 73ea040ad4..b0f00abbef 100644 --- a/bip-0085.mediawiki +++ b/bip-0085.mediawiki @@ -200,7 +200,7 @@ OUTPUT: ====18 English words==== BIP39 English 18 word mnemonic seed -196 bits of entropy as input to BIP39 to derive 18 word mnemonic +192 bits of entropy as input to BIP39 to derive 18 word mnemonic INPUT: * MASTER BIP32 ROOT KEY: xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb @@ -443,7 +443,7 @@ BIP32, BIP39 ===2.0.0 (2025-09-19)=== -====Fixed==== +====Fixed==== * Fixed the human-readable datetime string for BIP85 GPG Keys that was incorrectly stated as '2009-01-03 18:05:05' rather than '2009-01-03 18:15:05'. Implementations that relied on the previously incorrect datetime string instead of UNIX Epoch timestamp 1231006505 will produce different key fingerprints. diff --git a/bip-0089.mediawiki b/bip-0089.mediawiki new file mode 100644 index 0000000000..b5ef080d21 --- /dev/null +++ b/bip-0089.mediawiki @@ -0,0 +1,405 @@ +
+ BIP: 89 + Layer: Applications + Title: Chain Code Delegation + Authors: Jesse Posner+ +== Abstract == +Chain Code Delegation (CCD) is a method for multi-signature wallets in which a privileged participant withholds BIP32 chain codes from one or more non-privileged participants, and supplies per-input scalar tweaks at signing time. This allows non-privileged participants to co-sign transactions without learning wallet-wide derivations, balances, or signing activity from other spending combinations. CCD defines the tweak exchange needed for verification and signing behavior when the signer does not possess a chain code. + +== Motivation == +In multisig deployments, sharing extended public keys (xpubs) or descriptors enables all participants to scan the chain and infer counterparties' activity. CCD limits that visibility by ensuring non-privileged participants only ever hold a non-extended keypair and only receive the minimum per-spend data needed to sign. The procedure keeps policy enforcement feasible for the non-privileged signer while preserving balance privacy, which is particularly useful in collaborative custody arrangements where the wallet owner wants balance privacy from their custodian. + +== Terminology == +In CCD, the chain code is the object of delegation—not signing authority. A participant who gives up their chain code delegates it to another. + +* A "Delegator" is a participant who delegates their chain code to another party. They hold only a non-extended keypair and receive scalar tweaks from the delegatee when asked to sign. +* A "Delegatee" is a participant who receives and retains a delegated chain code for another participant's public key, and computes derivation tweaks for that participant. +* A "Participant" is any key holder that can co-sign for UTXOs in the wallet (including delegators and delegatees). +* A "Non-hardened derivation" is a BIP32 child derivation where index < 2^31. + +== Overview == +CCD operates by having Delegatees deprive Delegators of BIP32 chain codes during setup and later conveying the aggregated scalar tweak computed as the sum of non-hardened derivation tweaks along the remaining path to the child key used by a given input or change output. A Delegator uses the tweak to compute the child keys for verification and signing without being able to derive or recognize keys for other paths. + +== Specification == + +=== Key material and setup === +* '''Delegator key:''' Each delegator generates a standard (non-extended) secp256k1 keypair and provides the public key to the counterparties. A delegator MUST NOT retain or be provided a chain code for this key. +* '''Delegated chain code:''' A designated delegatee computes and retains a BIP32 chain code bound to the delegator's public key, forming an xpub that MUST NOT be disclosed to the delegator. The delegatee MAY share this xpub with other delegatees. +* '''Other participants:''' Non-delegator participants use conventional extended keys and share the public half as appropriate for the wallet descriptor. +* '''Derivation constraints:''' The delegatee holds an extended public key for the delegator. All derivation from this extended key MUST be non-hardened, as hardened derivation requires the private key, which the delegatee does not possess. + +=== Notation === +The following conventions are used, with constants as defined for [https://www.secg.org/sec2-v2.pdf secp256k1]. We note that adapting this proposal to other elliptic curves is not straightforward and can result in an insecure scheme. +* Lowercase variables represent integers or byte arrays. +** The constant ''p'' refers to the field size, ''0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F''. +** The constant ''n'' refers to the curve order, ''0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141''. +* Uppercase variables refer to points on the curve with equation ''y2 = x3 + 7'' over the integers modulo ''p''. +** ''is_infinite(P)'' returns whether ''P'' is the point at infinity. +** ''x(P)'' and ''y(P)'' are integers in the range ''0..p-1'' and refer to the X and Y coordinates of a point ''P'' (assuming it is not infinity). +** The constant ''G'' refers to the base point, for which ''x(G) = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798'' and ''y(G) = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8''. +** Addition of points refers to the usual [https://en.wikipedia.org/wiki/Elliptic_curve#The_group_law elliptic curve group operation]. +** [https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication Multiplication (⋅) of an integer and a point] refers to the repeated application of the group operation. +* Functions and operations: +** ''||'' refers to byte array concatenation. +** The function ''x[i:j]'', where ''x'' is a byte array and ''i, j ≥ 0'', returns a ''(j - i)''-byte array with a copy of the ''i''-th byte (inclusive) to the ''j''-th byte (exclusive) of ''x''. +** The function ''bytes(n, x)'', where ''x'' is an integer, returns the n-byte encoding of ''x'', most significant byte first. +** The constant ''empty_bytestring'' refers to the empty byte array. It holds that ''len(empty_bytestring) = 0''. +** The function ''xbytes(P)'', where ''P'' is a point for which ''not is_infinite(P)'', returns ''bytes(32, x(P))''. +** The function ''len(x)'' where ''x'' is a byte array returns the length of the array. +** The function ''has_even_y(P)'', where ''P'' is a point for which ''not is_infinite(P)'', returns ''y(P) mod 2 == 0''. +** The function ''with_even_y(P)'', where ''P'' is a point, returns ''P'' if ''is_infinite(P)'' or ''has_even_y(P)''. Otherwise, ''with_even_y(P)'' returns ''-P''. +** The function ''cbytes(P)'', where ''P'' is a point for which ''not is_infinite(P)'', returns ''a || xbytes(P)'' where ''a'' is a byte that is ''2'' if ''has_even_y(P)'' and ''3'' otherwise. +** The function ''int(x)'', where ''x'' is a 32-byte array, returns the 256-bit unsigned integer whose most significant byte first encoding is ''x''. +** The function ''lift_x(x)'', where ''x'' is an integer in range ''0..2256-1'', returns the point ''P'' for which ''x(P) = x'' + Given a candidate X coordinate ''x'' in the range ''0..p-1'', there exist either exactly two or exactly zero valid Y coordinates. If no valid Y coordinate exists, then ''x'' is not a valid X coordinate either, i.e., no point ''P'' exists for which ''x(P) = x''. The valid Y coordinates for a given candidate ''x'' are the square roots of ''c = x3 + 7 mod p'' and they can be computed as ''y = ±c(p+1)/4 mod p'' (see [https://en.wikipedia.org/wiki/Quadratic_residue#Prime_or_prime_power_modulus Quadratic residue]) if they exist, which can be checked by squaring and comparing with ''c''. and ''has_even_y(P)'', or fails if ''x'' is greater than ''p-1'' or no such point exists. The function ''lift_x(x)'' is equivalent to the following pseudocode: +*** Fail if ''x > p-1''. +*** Let ''c = x3 + 7 mod p''. +*** Let ''y' = c(p+1)/4 mod p''. +*** Fail if ''c ≠ y'2 mod p''. +*** Let ''y = y' '' if ''y' mod 2 = 0'', otherwise let ''y = p - y' ''. +*** Return the unique point ''P'' such that ''x(P) = x'' and ''y(P) = y''. +** The function ''cpoint(x)'', where ''x'' is a 33-byte array (compressed serialization), sets ''P = lift_x(int(x[1:33]))'' and fails if that fails. If ''x[0] = 2'' it returns ''P'' and if ''x[0] = 3'' it returns ''-P''. Otherwise, it fails. +** The function ''hash256tag(x)'' where ''tag'' is a UTF-8 encoded tag name and ''x'' is a byte array returns the 32-byte hash ''SHA256(SHA256(tag) || SHA256(tag) || x)''. +** The function ''hash512tag(x)'' where ''tag'' is a UTF-8 encoded tag name and ''x'' is a byte array returns the 64-byte hash ''SHA512(SHA512(tag) || SHA512(tag) || x)''. +* Other: +** Tuples are written by listing the elements within parentheses and separated by commas. For example, ''(2, 3, 1)'' is a tuple. + +=== Tweak Calculation === +To produce CCD tweak data, a delegatee computes a per-participant scalar that aggregates the non-hardened derivation tweaks along the remaining path. Let the extended key retained by the delegatee be P at depth d, and let the target index vector be I = (id+1, …, in) with each ik < 231. + ++ Jurvis Tan + Comments-Summary: No comments yet. + Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0089 + Status: Draft + Type: Specification + Assigned: 2025-12-03 + License: BSD-3-Clause + Discussion: https://delvingbitcoin.org/t/chain-code-delegation-private-access-control-for-bitcoin-keys/1837 + Requires: 32, 340, 341 +
true if ''W′ = W'', otherwise false.
+true if ''W′ = W'', otherwise false.
+x and public key P uses the delegated tweak bundle to derive per-input signing keys. The delegator MAY first call ''InputVerification'' and ''ChangeOutputVerification'' on any input and change output that provides a tweak in order to confirm outflow or policy requirements before signing.
+
+t enables signing only for the specific child key(s) that scalar was derived for, and is typically short-lived if disclosed immediately before spending.
+* Delegatees MUST ensure every delegated path remains non-hardened and that ''ComputeBIP32Tweak'' yields the correct tweak t; incorrect scalars could render the delegator incapable of producing a signature.
+* Delegators MUST verify change outputs when tweak data is provided (for example via ''ChangeOutputVerification'') to avoid authorizing unexpected scripts.
+* Reusing the same k' (first 32 bytes in blindsecnonce) across two BlindSign calls allows recovery of the base secret key.
+* When using blinded signing, opening multiple sessions concurrently against the same signer can allow an attacker to learn the base secret key. If concurrency is required, use the concurrently secure variant (encryption + ZK) instead (not specified in this BIP).
+
+== Test Vectors ==
+A [[bip-0089/vectors|collection of JSON test vectors]] are provided, along with a [[bip-0089/reference.py|python reference implementation]].
+It uses a vendored copy of the [https://github.com/secp256k1lab/secp256k1lab/ secp256k1lab] library
+(commit [https://github.com/secp256k1lab/secp256k1lab/commit/a265da139aea27386085a2a8760f8698e1bda64e
+a265da139aea27386085a2a8760f8698e1bda64e]).
+
+You may also find example code of CCD in action [https://github.com/jurvis/chaincode-delegation here].
+
+== Changelog ==
+
+* '''0.1.3''' (2026-02-02): Upgrade secp256k1lab and add license file; fix type checker and linter issues; clarify Delegator/Delegatee terminology, derivation constraints, signing modes, and verification scope.
+* '''0.1.2''' (2025-12-03): Updated to reflect BIP number assignment.
+* '''0.1.1''' (2025-11-30): Fix acknowledgments spelling, BIP3 formatting, and use "Chain Code" with a space throughout.
+* '''0.1.0''' (2025-10-14): Publication of draft BIP
+
+== Acknowledgements ==
+* Arik Sosman and Wilmer Paulino for the initial discussions and validation of this idea.
+* Sanket Kajalkar, Jordan Mecom, Gregory Sanders, ZmnSCPxj, Yuval Kogman, and John Cantrell for code and design review.
+
+== Copyright ==
+This BIP is licensed under the BSD 3-Clause license.
diff --git a/bip-0089/bip32.py b/bip-0089/bip32.py
new file mode 100644
index 0000000000..fc82166c96
--- /dev/null
+++ b/bip-0089/bip32.py
@@ -0,0 +1,170 @@
+"""BIP32 helpers for the CCD reference implementation."""
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+import hmac
+from hashlib import new as hashlib_new, sha256, sha512
+from typing import List, Tuple, Mapping, Sequence
+
+from secp256k1lab.secp256k1 import G, GE, Scalar
+
+CURVE_N = Scalar.SIZE
+
+def int_to_bytes(value: int, length: int) -> bytes:
+ return value.to_bytes(length, "big")
+
+
+def bytes_to_int(data: bytes) -> int:
+ return int.from_bytes(data, "big")
+
+def compress_point(point: GE) -> bytes:
+ if point.infinity:
+ raise ValueError("Cannot compress point at infinity")
+ return point.to_bytes_compressed()
+
+
+def decompress_point(data: bytes) -> GE:
+ return GE.from_bytes_compressed(data)
+
+def apply_tweak_to_public(base_public: bytes, tweak: int) -> bytes:
+ base_point = GE.from_bytes_compressed(base_public)
+ tweaked_point = base_point + (tweak % CURVE_N) * G
+ if tweaked_point.infinity:
+ raise ValueError("Tweaked key is at infinity")
+ return tweaked_point.to_bytes_compressed()
+
+
+def apply_tweak_to_secret(base_secret: int, tweak: int) -> int:
+ if not (0 < base_secret < CURVE_N):
+ raise ValueError("Invalid base secret scalar")
+ return (base_secret + tweak) % CURVE_N
+
+def decode_path(path_elements: Sequence[object]) -> List[int]:
+ result: List[int] = []
+ for element in path_elements:
+ if isinstance(element, int):
+ index = element
+ else:
+ element_str = str(element)
+ hardened = element_str.endswith("'") or element_str.endswith("h")
+ suffix = element_str[:-1] if hardened else element_str
+ if not suffix:
+ raise AssertionError("invalid derivation index")
+ index = int(suffix)
+ if hardened:
+ index |= HARDENED_INDEX
+ result.append(index)
+ return result
+
+HARDENED_INDEX = 0x80000000
+
+
+def _hash160(data: bytes) -> bytes:
+ return hashlib_new("ripemd160", sha256(data).digest()).digest()
+
+
+@dataclass
+class ExtendedPublicKey:
+ point: GE
+ chain_code: bytes
+ depth: int = 0
+ parent_fingerprint: bytes = b"\x00\x00\x00\x00"
+ child_number: int = 0
+
+ def fingerprint(self) -> bytes:
+ return _hash160(compress_point(self.point))[:4]
+
+ def derive_child(self, index: int) -> Tuple[int, "ExtendedPublicKey"]:
+ tweak, child_point, child_chain = derive_public_child(self.point, self.chain_code, index)
+ child = ExtendedPublicKey(
+ point=child_point,
+ chain_code=child_chain,
+ depth=self.depth + 1,
+ parent_fingerprint=self.fingerprint(),
+ child_number=index,
+ )
+ return tweak, child
+
+
+def derive_public_child(parent_point: GE, chain_code: bytes, index: int) -> Tuple[int, GE, bytes]:
+ if index >= HARDENED_INDEX:
+ raise ValueError("Hardened derivations are not supported for delegates")
+
+ data = compress_point(parent_point) + int_to_bytes(index, 4)
+ il_ir = hmac.new(chain_code, data, sha512).digest()
+ il, ir = il_ir[:32], il_ir[32:]
+ tweak = bytes_to_int(il)
+ if tweak >= CURVE_N:
+ raise ValueError("Invalid tweak derived (>= curve order)")
+
+ child_point_bytes = apply_tweak_to_public(compress_point(parent_point), tweak)
+ child_point = decompress_point(child_point_bytes)
+ return tweak, child_point, ir
+
+
+def parse_path(path: str) -> List[int]:
+ if not path or path in {"m", "M"}:
+ return []
+ if path.startswith(("m/", "M/")):
+ path = path[2:]
+
+ components: List[int] = []
+ for element in path.split("/"):
+ if element.endswith("'") or element.endswith("h"):
+ raise ValueError("Hardened steps are not allowed in CCD derivations")
+ index = int(element)
+ if index < 0 or index >= HARDENED_INDEX:
+ raise ValueError("Derivation index out of range")
+ components.append(index)
+ return components
+
+def parse_extended_public_key(data: Mapping[str, object]) -> ExtendedPublicKey:
+ compressed_hex = data.get("compressed")
+ if not isinstance(compressed_hex, str):
+ raise ValueError("Compressed must be a string")
+
+ chain_code_hex = data.get("chain_code")
+ if not isinstance(chain_code_hex, str):
+ raise ValueError("Chain code must be a string")
+
+ depth = data.get("depth")
+ if not isinstance(depth, int):
+ raise ValueError("Depth must be an integer")
+
+ child_number = data.get("child_number", 0)
+ if not isinstance(child_number, int):
+ raise ValueError("Child number must be an integer")
+
+ parent_fp_hex = data.get("parent_fingerprint", "00000000")
+
+ compressed = bytes.fromhex(compressed_hex)
+ chain_code = bytes.fromhex(chain_code_hex)
+ parent_fp = bytes.fromhex(str(parent_fp_hex))
+ return build_extended_public_key(
+ compressed,
+ chain_code,
+ depth=depth,
+ parent_fingerprint=parent_fp,
+ child_number=child_number,
+ )
+
+
+def build_extended_public_key(
+ compressed: bytes,
+ chain_code: bytes,
+ *,
+ depth: int = 0,
+ parent_fingerprint: bytes = b"\x00\x00\x00\x00",
+ child_number: int = 0,
+) -> ExtendedPublicKey:
+ if len(chain_code) != 32:
+ raise ValueError("Chain code must be 32 bytes")
+ point = decompress_point(compressed)
+ return ExtendedPublicKey(
+ point=point,
+ chain_code=chain_code,
+ depth=depth,
+ parent_fingerprint=parent_fingerprint,
+ child_number=child_number,
+ )
diff --git a/bip-0089/descriptor.py b/bip-0089/descriptor.py
new file mode 100644
index 0000000000..ab20b356d4
--- /dev/null
+++ b/bip-0089/descriptor.py
@@ -0,0 +1,42 @@
+"""Helpers for working with minimal SortedMulti descriptor templates."""
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+from typing import Sequence
+
+
+@dataclass(frozen=True)
+class SortedMultiDescriptorTemplate:
+ """Minimal representation of a ``wsh(sortedmulti(m, ...))`` descriptor."""
+
+ threshold: int
+
+ def witness_script(self, tweaked_keys: Sequence[bytes]) -> bytes:
+ """Return the witness script for ``wsh(sortedmulti(threshold, tweaked_keys))``."""
+
+ if not tweaked_keys:
+ raise ValueError("sortedmulti requires at least one key")
+ if not 1 <= self.threshold <= len(tweaked_keys):
+ raise ValueError("threshold must satisfy 1 <= m <= n")
+
+ for key in tweaked_keys:
+ if len(key) != 33:
+ raise ValueError("sortedmulti keys must be 33-byte compressed pubkeys")
+
+ sorted_keys = sorted(tweaked_keys)
+ script = bytearray()
+ script.append(_op_n(self.threshold))
+ for key in sorted_keys:
+ script.append(len(key))
+ script.extend(key)
+ script.append(_op_n(len(sorted_keys)))
+ script.append(0xAE) # OP_CHECKMULTISIG
+ return bytes(script)
+
+def _op_n(value: int) -> int:
+ if not 0 <= value <= 16:
+ raise ValueError("OP_N value out of range")
+ if value == 0:
+ return 0x00
+ return 0x50 + value
diff --git a/bip-0089/reference.py b/bip-0089/reference.py
new file mode 100644
index 0000000000..f81d5359a2
--- /dev/null
+++ b/bip-0089/reference.py
@@ -0,0 +1,784 @@
+# BIPXXX reference implementation
+#
+# WARNING: This implementation is for demonstration purposes only and _not_ to
+# be used in production environments. The code is vulnerable to timing attacks,
+# for example.
+
+from typing import Dict, Mapping, Optional, Sequence, Tuple, NewType, NamedTuple, List, Callable, Any, cast
+import hashlib
+import json
+import os
+import secrets
+import sys
+
+from bip32 import (
+ CURVE_N,
+ ExtendedPublicKey,
+ apply_tweak_to_public,
+ apply_tweak_to_secret,
+ int_to_bytes,
+ parse_extended_public_key,
+ compress_point,
+ decode_path,
+)
+from descriptor import SortedMultiDescriptorTemplate
+
+from secp256k1lab.bip340 import schnorr_sign, schnorr_verify
+from secp256k1lab.keys import pubkey_gen_plain
+from secp256k1lab.secp256k1 import G, GE, Scalar
+
+HashFunc = Callable[[bytes], Any]
+
+PlainPk = NewType('PlainPk', bytes)
+XonlyPk = NewType('XonlyPk', bytes)
+
+def xbytes(P: GE) -> bytes:
+ return P.to_bytes_xonly()
+
+def cbytes(P: GE) -> bytes:
+ return P.to_bytes_compressed()
+
+def cpoint(x: bytes) -> GE:
+ return GE.from_bytes_compressed(x)
+
+TweakContext = NamedTuple('TweakContext', [('Q', GE),
+ ('gacc', Scalar),
+ ('tacc', Scalar)])
+
+def tweak_ctx_init(pk: PlainPk) -> TweakContext:
+ Q = cpoint(pk)
+ if Q.infinity:
+ raise ValueError('The public key cannot be infinity.')
+ gacc = Scalar(1)
+ tacc = Scalar(0)
+ return TweakContext(Q, gacc, tacc)
+
+def apply_tweak(tweak_ctx: TweakContext, tweak: bytes, is_xonly: bool) -> TweakContext:
+ if len(tweak) != 32:
+ raise ValueError('The tweak must be a 32-byte array.')
+ Q, gacc, tacc = tweak_ctx
+ if is_xonly and not Q.has_even_y():
+ g = Scalar(-1)
+ else:
+ g = Scalar(1)
+ try:
+ t = Scalar.from_bytes_checked(tweak)
+ except ValueError:
+ raise ValueError('The tweak must be less than n.')
+ Q_ = g * Q + t * G
+ if Q_.infinity:
+ raise ValueError('The result of tweaking cannot be infinity.')
+ gacc_ = g * gacc
+ tacc_ = t + g * tacc
+ return TweakContext(Q_, gacc_, tacc_)
+
+# Return the plain public key corresponding to a given secret key
+def individual_pk(seckey: bytes) -> PlainPk:
+ return PlainPk(pubkey_gen_plain(seckey))
+
+def bytes_xor(a: bytes, b: bytes) -> bytes:
+ return bytes(x ^ y for x, y in zip(a, b))
+
+# This implementation can be sped up by storing the midstate after hashing
+# tag_hash instead of rehashing it all the time.
+def tagged_hash(tag: str, msg: bytes, hash_func: HashFunc = hashlib.sha256) -> bytes:
+ tag_hash = hash_func(tag.encode()).digest()
+ return hash_func(tag_hash + tag_hash + msg).digest()
+
+def nonce_hash(rand: bytes, pk: PlainPk, extra_in: bytes) -> bytes:
+ buf = b''
+ buf += rand
+ buf += len(pk).to_bytes(1, 'big')
+ buf += pk
+ buf += len(extra_in).to_bytes(4, 'big')
+ buf += extra_in
+ return tagged_hash('CCD/blindnonce', buf)
+
+def blind_nonce_gen_internal(rand_: bytes, sk: Optional[bytes], pk: Optional[PlainPk], extra_in: Optional[bytes]) -> Tuple[bytearray, bytes]:
+ if sk is not None:
+ rand = bytes_xor(sk, tagged_hash('CCD/aux', rand_))
+ else:
+ rand = rand_
+ if pk is None:
+ pk = PlainPk(b'')
+ if extra_in is None:
+ extra_in = b''
+ k = Scalar.from_bytes_wrapping(nonce_hash(rand, pk, extra_in))
+ # k == 0 cannot occur except with negligible probability.
+ assert k != 0
+ R = k * G
+ assert R is not None
+ blindpubnonce = cbytes(R)
+ blindsecnonce = bytearray(k.to_bytes() + pk)
+ return blindsecnonce, blindpubnonce
+
+def blind_nonce_gen(sk: Optional[bytes], pk: Optional[PlainPk], extra_in: Optional[bytes]) -> Tuple[bytearray, bytes]:
+ if sk is not None and len(sk) != 32:
+ raise ValueError('The optional byte array sk must have length 32.')
+ rand_ = secrets.token_bytes(32)
+ return blind_nonce_gen_internal(rand_, sk, pk, extra_in)
+
+SessionContext = NamedTuple('SessionContext', [('pk', PlainPk),
+ ('blindfactor', bytes),
+ ('challenge', bytes),
+ ('pubnonce', bytes),
+ ('tweaks', List[bytes]),
+ ('is_xonly', List[bool])])
+
+def blind_factor_hash(rand: bytes, cpk: PlainPk, blindpubnonce: bytes, msg: bytes, extra_in: bytes) -> bytes:
+ buf = b''
+ buf += rand
+ buf += len(cpk).to_bytes(1, 'big')
+ buf += cpk
+ buf += len(blindpubnonce).to_bytes(1, 'big')
+ buf += blindpubnonce
+ buf += len(msg).to_bytes(8, 'big')
+ buf += msg
+ buf += len(extra_in).to_bytes(4, 'big')
+ buf += extra_in
+ return tagged_hash('CCD/blindfactor', buf, hashlib.sha512)
+
+def blind_challenge_gen_internal(rand: bytes, msg: bytes, blindpubnonce: bytes, pk: PlainPk, tweaks: List[bytes], is_xonly: List[bool], extra_in: Optional[bytes]) -> Tuple[SessionContext, bytes, bool, bool]:
+ if extra_in is None:
+ extra_in = b''
+ Q, gacc, tacc = pubkey_and_tweak(pk, tweaks, is_xonly)
+ cpk = PlainPk(cbytes(Q))
+ k = blind_factor_hash(rand, cpk, blindpubnonce, msg, extra_in)
+ a_ = Scalar.from_bytes_wrapping(k[0:32])
+ assert a_ != 0
+ b_ = Scalar.from_bytes_wrapping(k[32:64])
+ assert b_ != 0
+
+ g = Scalar(1) if Q.has_even_y() else Scalar(-1)
+ pk_parity = g * gacc == 1
+ X_ = cpoint(pk)
+ X = X_ if pk_parity else -X_
+
+ R_ = cpoint(blindpubnonce)
+ R = R_ + (a_ * G) + (b_ * X)
+ if R is None:
+ raise ValueError('The result of nonce blinding cannot be infinity.')
+ nonce_parity = R.has_even_y()
+ if not nonce_parity:
+ a = -a_
+ b = -b_
+ else:
+ a = a_
+ b = b_
+
+ e = Scalar.from_bytes_wrapping(tagged_hash("BIP0340/challenge", xbytes(R) + xbytes(Q) + msg))
+ e_ = e + b
+
+ session_ctx = SessionContext(pk, a.to_bytes(), e.to_bytes(), cbytes(R), tweaks, is_xonly)
+ return session_ctx, e_.to_bytes(), pk_parity, nonce_parity
+
+def blind_challenge_gen(msg: bytes, blindpubnonce: bytes, pk: PlainPk, tweaks: List[bytes], is_xonly: List[bool], extra_in: Optional[bytes]) -> Tuple[SessionContext, bytes, bool, bool]:
+ rand = secrets.token_bytes(32)
+ return blind_challenge_gen_internal(rand, msg, blindpubnonce, pk, tweaks, is_xonly, extra_in)
+
+def blind_sign(sk: bytes, blindchallenge: bytes, blindsecnonce: bytearray, pk_parity: bool, nonce_parity: bool) -> bytes:
+ try:
+ d_ = Scalar.from_bytes_checked(sk)
+ if d_ == 0:
+ raise ValueError('The secret key cannot be zero.')
+ except ValueError:
+ raise ValueError('The secret key is out of range.')
+ P = d_ * G
+ if P.infinity:
+ raise ValueError('The public key cannot be infinity.')
+ d = d_ if pk_parity else -d_
+ e_ = Scalar.from_bytes_checked(blindchallenge)
+ k_ = Scalar.from_bytes_checked(bytes(blindsecnonce[0:32]))
+ k = k_ if nonce_parity else -k_
+ # Overwrite the secnonce argument with zeros such that subsequent calls of
+ # sign with the same secnonce raise a ValueError.
+ blindsecnonce[:64] = bytearray(b'\x00'*64)
+ R_ = k_ * G
+ if R_.infinity:
+ raise ValueError('The blindpubnonce cannot be infinity.')
+ s_ = k + (e_ * d)
+ pk = PlainPk(cbytes(P))
+ blindsignature = s_.to_bytes()
+ assert verify_blind_signature(pk, cbytes(R_), blindchallenge, blindsignature, pk_parity, nonce_parity)
+ return blindsignature
+
+def verify_blind_signature(pk: PlainPk, blindpubnonce: bytes, blindchallenge: bytes, blindsignature: bytes, pk_parity: bool, nonce_parity: bool) -> bool:
+ P_ = cpoint(pk)
+ P = P_ if pk_parity else -P_
+ if P.infinity:
+ raise ValueError('The public key cannot be infinity.')
+ R_ = cpoint(blindpubnonce)
+ R = R_ if nonce_parity else -R_
+ e_ = Scalar.from_bytes_checked(blindchallenge)
+ s_ = Scalar.from_bytes_checked(blindsignature)
+ R_calc = (s_ * G) + (-e_ * P)
+ if R_calc.infinity:
+ return False
+ return R == R_calc
+
+def pubkey_and_tweak(pk: PlainPk, tweaks: List[bytes], is_xonly: List[bool]) -> TweakContext:
+ if len(tweaks) != len(is_xonly):
+ raise ValueError('The tweaks and is_xonly arrays must have the same length.')
+ tweak_ctx = tweak_ctx_init(pk)
+ v = len(tweaks)
+ for i in range(v):
+ tweak_ctx = apply_tweak(tweak_ctx, tweaks[i], is_xonly[i])
+ return tweak_ctx
+
+def get_session_values(session_ctx: SessionContext) -> Tuple[GE, Scalar, Scalar, GE, Scalar, Scalar]:
+ (pk, blindfactor, challenge, pubnonce, tweaks, is_xonly) = session_ctx
+ Q, gacc, tacc = pubkey_and_tweak(pk, tweaks, is_xonly)
+ a = Scalar.from_bytes_checked(blindfactor)
+ e = Scalar.from_bytes_checked(challenge)
+ R = cpoint(pubnonce)
+ return Q, a, e, R, gacc, tacc
+
+def unblind_signature(session_ctx: SessionContext, blindsignature: bytes) -> bytes:
+ Q, a, e, R, gacc, tacc = get_session_values(session_ctx)
+ s_ = Scalar.from_bytes_checked(blindsignature)
+ g = Scalar(1) if Q.has_even_y() else Scalar(-1)
+ s = s_ + a + (e * g * tacc)
+ return xbytes(R) + s.to_bytes()
+
+#
+# The following code is only used for testing.
+#
+
+def hx(s: str) -> bytes:
+ return bytes.fromhex(s)
+
+def fromhex_all(l): # noqa: E741
+ return [hx(l_i) for l_i in l]
+
+
+def get_error_details(tc):
+ et = tc["error"]["type"]
+ # Resolve to real class from name
+ exc_cls = getattr(__builtins__, et, None) or getattr(__import__("builtins"), et)
+ # Optional message predicate
+ msg = tc["error"].get("message")
+ if msg is None:
+ return exc_cls, (lambda e: True)
+ return exc_cls, (lambda e: msg in str(e))
+
+def assert_raises(exc_cls, fn, pred):
+ try:
+ fn()
+ except Exception as e:
+ assert isinstance(e, exc_cls), f"Raised {type(e).__name__}, expected {exc_cls.__name__}"
+ assert pred(e), f"Exception message predicate failed: {e}"
+ return
+ assert False, f"Expected {exc_cls.__name__} but no exception was raised"
+
+def build_session_ctx(obj):
+ pk = PlainPk(bytes.fromhex(obj["pk"]))
+ a = bytes.fromhex(obj["blindfactor"])
+ e = bytes.fromhex(obj["challenge"])
+ R = bytes.fromhex(obj["pubnonce"])
+ tweaks = fromhex_all(obj["tweaks"])
+ is_xonly = obj["is_xonly"]
+ return (pk, a, e, R, tweaks, is_xonly)
+
+def test_blind_nonce_gen_vectors():
+ with open(os.path.join(sys.path[0], 'vectors', 'blind_nonce_gen_vectors.json')) as f:
+ tv = json.load(f)
+
+ for tc in tv["test_cases"]:
+ def get_bytes(key) -> bytes:
+ return bytes.fromhex(tc[key])
+
+ def get_bytes_maybe(key) -> Optional[bytes]:
+ v = tc.get(key)
+ return None if v is None else bytes.fromhex(v)
+
+ rand_ = get_bytes("rand_")
+ sk = get_bytes_maybe("sk")
+ pk = get_bytes_maybe("pk")
+ if pk is not None:
+ pk = PlainPk(pk)
+ extra_in = get_bytes_maybe("extra_in")
+
+ expected_blindsecnonce = get_bytes("expected_blindsecnonce")
+ expected_blindpubnonce = get_bytes("expected_blindpubnonce")
+
+ blindsecnonce, blindpubnonce = blind_nonce_gen_internal(rand_, sk, pk, extra_in)
+
+ assert bytes(blindsecnonce) == expected_blindsecnonce
+ assert blindpubnonce == expected_blindpubnonce
+
+ pk_len = 0 if tc["pk"] is None else 33
+ assert len(expected_blindsecnonce) == 32 + pk_len
+ assert len(expected_blindpubnonce) == 33
+
+def test_blind_challenge_gen_vectors():
+ with open(os.path.join(sys.path[0], 'vectors', 'blind_challenge_gen_vectors.json')) as f:
+ tv = json.load(f)
+
+ # ---------- Valid cases ----------
+ for tc in tv["test_cases"]:
+ rand = bytes.fromhex(tc["rand"])
+ msg = bytes.fromhex(tc["msg"]) if tc["msg"] != "" else b""
+ blindpubnonce = bytes.fromhex(tc["blindpubnonce"])
+ pk = PlainPk(bytes.fromhex(tc["pk"]))
+ tweaks = fromhex_all(tc["tweaks"])
+ is_xonly = tc["is_xonly"]
+ extra_in = None if tc["extra_in"] is None else bytes.fromhex(tc["extra_in"])
+
+ expected_a = bytes.fromhex(tc["expected_blindfactor"])
+ expected_e = bytes.fromhex(tc["expected_challenge"])
+ expected_R = bytes.fromhex(tc["expected_pubnonce"])
+ expected_e_prime = bytes.fromhex(tc["expected_blindchallenge"])
+ expected_pk_parity = bool(tc["expected_pk_parity"])
+ expected_nonce_parity = bool(tc["expected_nonce_parity"])
+
+ session_ctx, blindchallenge, pk_parity, nonce_parity = blind_challenge_gen_internal(
+ rand, msg, blindpubnonce, pk, tweaks, is_xonly, extra_in
+ )
+
+ # Check tuple outputs
+ assert blindchallenge == expected_e_prime
+ assert pk_parity == expected_pk_parity
+ assert nonce_parity == expected_nonce_parity
+
+ # Check session_ctx fields
+ pk_sc, blindfactor_sc, challenge_sc, pubnonce_sc, tweaks_sc, is_xonly_sc = session_ctx
+ assert pk_sc == pk
+ assert blindfactor_sc == expected_a
+ assert challenge_sc == expected_e
+ assert pubnonce_sc == expected_R
+ assert tweaks_sc == tweaks
+ assert is_xonly_sc == is_xonly
+
+ # Extra sanity: recompute Q and e and compare
+ Q, gacc, tacc = pubkey_and_tweak(pk, tweaks, is_xonly)
+ R = cpoint(expected_R)
+ e_check = tagged_hash("BIP0340/challenge", xbytes(R) + xbytes(Q) + msg)
+ assert e_check == expected_e
+
+ # Length sanity
+ assert len(expected_a) == 32
+ assert len(expected_e) == 32
+ assert len(expected_R) == 33
+ assert len(expected_e_prime) == 32
+
+ # ---------- Error cases ----------
+ for tc in tv.get("error_test_cases", []):
+ rand = bytes.fromhex(tc["rand"])
+ msg = bytes.fromhex(tc["msg"]) if tc["msg"] != "" else b""
+ blindpubnonce = bytes.fromhex(tc["blindpubnonce"])
+ pk = PlainPk(bytes.fromhex(tc["pk"]))
+ tweaks = fromhex_all(tc["tweaks"])
+ is_xonly = tc["is_xonly"]
+ extra_in = None if tc["extra_in"] is None else bytes.fromhex(tc["extra_in"])
+
+ err = tc["error"]
+ err_type = err["type"]
+ err_message = err.get("message")
+
+ raised = False
+ try:
+ _ = blind_challenge_gen_internal(rand, msg, blindpubnonce, pk, tweaks, is_xonly, extra_in)
+ except Exception as e:
+ raised = True
+ # Type check
+ assert e.__class__.__name__ == err_type
+ # Optional substring match on message, if provided
+ if err_message is not None:
+ assert err_message in str(e)
+ assert raised, "Expected an exception but none was raised"
+
+def test_blind_sign_and_verify_vectors():
+ with open(os.path.join(sys.path[0], 'vectors', 'blind_sign_and_verify_vectors.json')) as f:
+ tv = json.load(f)
+
+ # ------------------ Valid ------------------
+ for test_case in tv["valid_test_cases"]:
+ sk = hx(test_case["sk"])
+ pk = PlainPk(hx(test_case["pk"]))
+ blindsecnonce_all = hx(test_case["blindsecnonce"])
+ blindpubnonce = hx(test_case["blindpubnonce"])
+ blindchallenge = hx(test_case["blindchallenge"])
+ pk_parity = bool(test_case["pk_parity"])
+ nonce_parity = bool(test_case["nonce_parity"])
+
+ # R' consistency check: cbytes(k'*G) == blindpubnonce
+ k_ = Scalar.from_bytes_checked(blindsecnonce_all[0:32])
+ R_prime = k_ * G
+ assert cbytes(R_prime) == blindpubnonce
+
+ expected_sprime = hx(test_case["expected"]["blindsignature"])
+
+ # Copy because blind_sign zeroizes the first 64 bytes of the buffer
+ secnonce_buf = bytearray(blindsecnonce_all)
+ s_prime = blind_sign(sk, blindchallenge, secnonce_buf, pk_parity, nonce_parity)
+ assert s_prime == expected_sprime
+
+ checks = test_case.get("checks", {})
+ if checks.get("secnonce_prefix_zeroed_after_sign", False):
+ assert all(b == 0 for b in secnonce_buf[:64])
+
+ if checks.get("verify_returns_true", True):
+ ok = verify_blind_signature(pk, blindpubnonce, blindchallenge, s_prime, pk_parity, nonce_parity)
+ assert ok is True
+
+ if checks.get("second_call_raises_valueerror", False):
+ # Reuse the same (now zeroized) buffer; must raise
+ def try_again():
+ blind_sign(sk, blindchallenge, secnonce_buf, pk_parity, nonce_parity)
+ raised = False
+ try:
+ try_again()
+ except ValueError:
+ raised = True
+ assert raised, "Expected ValueError on nonce reuse"
+
+ # ------------------ Sign errors (exceptions) ------------------
+ for test_case in tv.get("sign_error_test_cases", []):
+ exception, except_fn = get_error_details(test_case)
+
+ sk = hx(test_case["sk"])
+ blindsecnonce_all = hx(test_case["blindsecnonce"])
+ blindchallenge = hx(test_case["blindchallenge"])
+ pk_parity = bool(test_case["pk_parity"])
+ nonce_parity = bool(test_case["nonce_parity"])
+ repeat = int(test_case.get("repeat", 1))
+
+ if repeat == 1:
+ # Single-call error (e.g., out-of-range e')
+ assert_raises(exception, lambda: blind_sign(sk, blindchallenge, bytearray(blindsecnonce_all), pk_parity, nonce_parity), except_fn)
+ else:
+ # Two-call error (nonce reuse)
+ buf = bytearray(blindsecnonce_all)
+ # First call should succeed
+ _ = blind_sign(sk, blindchallenge, buf, pk_parity, nonce_parity)
+ # Second call must raise
+ assert_raises(exception, lambda: blind_sign(sk, blindchallenge, buf, pk_parity, nonce_parity), except_fn)
+
+ # ------------------ Verify returns False (no exception) ------------------
+ for test_case in tv.get("verify_fail_test_cases", []):
+ pk = PlainPk(hx(test_case["pk"]))
+ blindpubnonce = hx(test_case["blindpubnonce"])
+ blindchallenge = hx(test_case["blindchallenge"])
+ blindsignature = hx(test_case["blindsignature"])
+ pk_parity = bool(test_case["pk_parity"])
+ nonce_parity = bool(test_case["nonce_parity"])
+
+ assert verify_blind_signature(pk, blindpubnonce, blindchallenge, blindsignature, pk_parity, nonce_parity) is False
+
+ # ------------------ Verify errors (exceptions) ------------------
+ for test_case in tv.get("verify_error_test_cases", []):
+ exception, except_fn = get_error_details(test_case)
+
+ pk = PlainPk(hx(test_case["pk"]))
+ blindpubnonce = hx(test_case["blindpubnonce"])
+ blindchallenge = hx(test_case["blindchallenge"])
+ blindsignature = hx(test_case["blindsignature"])
+ pk_parity = bool(test_case["pk_parity"])
+ nonce_parity = bool(test_case["nonce_parity"])
+
+ assert_raises(exception, lambda: verify_blind_signature(pk, blindpubnonce, blindchallenge, blindsignature, pk_parity, nonce_parity), except_fn)
+
+def test_unblind_signature_vectors():
+ with open(os.path.join(sys.path[0], 'vectors', 'unblind_signature_vectors.json')) as f:
+ tv = json.load(f)
+
+ # ---------- Valid ----------
+ for tc in tv["valid_test_cases"]:
+ session_ctx = build_session_ctx(tc["session_ctx"])
+ msg = bytes.fromhex(tc["msg"]) if tc["msg"] != "" else b""
+ blindsignature = bytes.fromhex(tc["blindsignature"])
+ expected_sig = bytes.fromhex(tc["expected_bip340_sig"])
+
+ sig = unblind_signature(session_ctx, blindsignature)
+ assert sig == expected_sig
+
+ # Verify BIP340 with tweaked Q
+ pk, _, _, _, tweaks, is_xonly = session_ctx
+ Q, _, _ = pubkey_and_tweak(pk, tweaks, is_xonly)
+ assert schnorr_verify(msg, xbytes(Q), sig)
+
+ # ---------- Errors ----------
+ for tc in tv.get("error_test_cases", []):
+ session_ctx = build_session_ctx(tc["session_ctx"])
+ msg = bytes.fromhex(tc["msg"]) if tc["msg"] != "" else b""
+ blindsignature = bytes.fromhex(tc["blindsignature"])
+
+ err = tc["error"]
+ err_type = err["type"]
+ err_msg = err.get("message")
+
+ raised = False
+ try:
+ _ = unblind_signature(session_ctx, blindsignature)
+ except Exception as e:
+ raised = True
+ assert e.__class__.__name__ == err_type
+ if err_msg is not None:
+ assert err_msg in str(e)
+ assert raised, "Expected an exception but none was raised"
+
+def test_sign_and_verify_random(iters: int) -> None:
+ for _ in range(iters):
+ sk = Scalar.from_bytes_wrapping(secrets.token_bytes(32))
+ pk = individual_pk(sk.to_bytes())
+ msg = Scalar.from_bytes_wrapping(secrets.token_bytes(32))
+ v = secrets.randbelow(4)
+ tweaks = [secrets.token_bytes(32) for _ in range(v)]
+ tweak_modes = [secrets.choice([False, True]) for _ in range(v)]
+ Q, _, _ = pubkey_and_tweak(pk, tweaks, tweak_modes)
+ assert not Q.infinity
+
+ # Round 1
+ # Signer
+ extra_in_1 = secrets.token_bytes(32)
+ blindsecnonce, blindpubnonce = blind_nonce_gen(sk.to_bytes(), pk, extra_in_1)
+ # User
+ extra_in_2 = secrets.token_bytes(32)
+ session_ctx, blindchallenge, pk_parity, nonce_parity = blind_challenge_gen(msg.to_bytes(), blindpubnonce, pk, tweaks, tweak_modes, extra_in_2)
+
+ # Round 2
+ # Signer
+ blindsignature = blind_sign(sk.to_bytes(), blindchallenge, blindsecnonce, pk_parity, nonce_parity)
+ # User
+ sig = unblind_signature(session_ctx, blindsignature)
+ assert schnorr_verify(msg.to_bytes(), xbytes(Q), sig)
+
+def compute_bip32_tweak(xpub: ExtendedPublicKey, path: Sequence[int]) -> Tuple[int, ExtendedPublicKey]:
+ """Compute the CCD tweak scalar for a non-hardened derivation path."""
+
+ aggregate = 0
+ current = xpub
+ for index in path:
+ tweak, child = current.derive_child(index)
+ aggregate = (aggregate + tweak) % CURVE_N
+ current = child
+ return aggregate, current
+
+def input_verification(
+ descriptor_template: SortedMultiDescriptorTemplate,
+ witness_script: Optional[bytes],
+ tweaks: Mapping[bytes, int],
+) -> bool:
+ """Check that an input script matches the tweaked policy from CCD data."""
+
+ return _verify_tweaked_descriptor(
+ descriptor_template,
+ witness_script,
+ tweaks,
+ )
+
+
+def change_output_verification(
+ descriptor_template: SortedMultiDescriptorTemplate,
+ witness_script: Optional[bytes],
+ tweaks: Mapping[bytes, int],
+) -> bool:
+ """Validate a change output script using delegated CCD tweak data."""
+
+ return _verify_tweaked_descriptor(
+ descriptor_template,
+ witness_script,
+ tweaks,
+ )
+
+
+def _verify_tweaked_descriptor(
+ descriptor_template: SortedMultiDescriptorTemplate,
+ witness_script: Optional[bytes],
+ tweaks: Mapping[bytes, int],
+) -> bool:
+ if witness_script is None or not tweaks:
+ return False
+
+ if descriptor_template.threshold > len(tweaks):
+ return False
+
+ tweaked_keys: List[bytes] = []
+ for base_key, tweak in sorted(tweaks.items(), key=lambda item: item[0]):
+ if len(base_key) != 33:
+ return False
+ tweaked_key = apply_tweak_to_public(base_key, tweak % CURVE_N)
+ tweaked_keys.append(tweaked_key)
+
+ try:
+ expected_witness_script = descriptor_template.witness_script(tweaked_keys)
+ except ValueError:
+ return False
+
+ return witness_script == expected_witness_script
+
+def delegator_sign(
+ tweak: int,
+ base_secret: int,
+ message: bytes,
+) -> bytes:
+ """Derive the delegated key, sign ``message``, and return signature."""
+ child_secret = int_to_bytes(apply_tweak_to_secret(base_secret, tweak), 32)
+ message_digest = hashlib.sha256(message).digest()
+ signature = schnorr_sign(message_digest, child_secret, bytes(32))
+ return signature
+
+def test_compute_tweak_vectors() -> None:
+ with open(os.path.join(sys.path[0], 'vectors', 'compute_bip32_tweak_vectors.json')) as f:
+ data = json.load(f)
+
+ default_xpub_data = data.get("xpub")
+ if default_xpub_data is None:
+ raise AssertionError("compute_bip32_tweak_vectors.json missing top-level 'xpub'")
+
+ for case in data.get("valid_test_cases", []):
+ xpub_data = case.get("xpub", default_xpub_data)
+ xpub = parse_extended_public_key(xpub_data)
+ path = decode_path(case.get("path", []))
+ expected = case.get("expected")
+ if not isinstance(expected, Mapping):
+ raise AssertionError("valid compute_tweak case missing 'expected'")
+
+ tweak_hex = expected.get("tweak")
+ if not isinstance(tweak_hex, str):
+ raise AssertionError("expected 'tweak' must be a string")
+
+ derived = expected.get("derived_xpub", {})
+ derived_compressed = derived.get("compressed")
+ if not isinstance(derived_compressed, str):
+ raise AssertionError("expected 'derived_xpub.compressed' must be a string")
+
+ derived_chain_code = derived.get("chain_code")
+ if not isinstance(derived_chain_code, str):
+ raise AssertionError("expected 'derived_xpub.chain_code' must be a string")
+
+ tweak, child = compute_bip32_tweak(xpub, path)
+ actual_tweak_hex = f"{tweak:064x}"
+ if actual_tweak_hex != tweak_hex.lower():
+ raise AssertionError(f"tweak mismatch: expected {tweak_hex}, got {actual_tweak_hex}")
+
+ actual_compressed = compress_point(child.point).hex()
+ actual_chain_code = child.chain_code.hex()
+ if actual_compressed != derived_compressed.lower():
+ raise AssertionError("derived public key mismatch")
+ if actual_chain_code != derived_chain_code.lower():
+ raise AssertionError("derived chain code mismatch")
+
+ for case in data.get("error_test_cases", []):
+ xpub_data = case.get("xpub", default_xpub_data)
+ xpub = parse_extended_public_key(xpub_data)
+ path = decode_path(case.get("path", []))
+ error_spec = case.get("error", {})
+ exc_type, message = resolve_error_spec(error_spec)
+
+ try:
+ compute_bip32_tweak(xpub, path)
+ except exc_type as exc:
+ if message and message.lower() not in str(exc).lower():
+ raise AssertionError(f"expected error containing '{message}' but got '{exc}'")
+ else:
+ raise AssertionError("expected failure but case succeeded")
+
+def test_delegator_sign_vectors() -> None:
+ with open(os.path.join(sys.path[0], 'vectors', 'delegator_sign_vectors.json')) as f:
+ data = json.load(f)
+
+ for case in data.get("test_cases", []):
+ base_secret_hex = case.get("base_secret")
+ tweak_hex = case.get("tweak")
+ message_hex = case.get("message")
+
+ base_secret = int(base_secret_hex, 16)
+ tweak = int(tweak_hex, 16)
+ message = message_hex.encode('utf-8')
+
+ expected = case.get("expected")
+ if not isinstance(expected, Mapping):
+ raise AssertionError("delegator_sign case missing 'expected'")
+ expected_signature_hex = expected.get("signature")
+ if not isinstance(expected_signature_hex, str):
+ raise AssertionError("expected 'signature' must be a string")
+ expected_signature = bytes.fromhex(expected_signature_hex)
+
+ signature = delegator_sign(
+ tweak,
+ base_secret,
+ message,
+ )
+
+ if signature != expected_signature:
+ raise AssertionError("signature mismatch")
+
+
+def test_input_verification_vectors() -> None:
+ with open(os.path.join(sys.path[0], 'vectors', 'input_verification_vectors.json')) as f:
+ data = json.load(f)
+
+
+ for case in data.get("test_cases", []):
+ descriptor = SortedMultiDescriptorTemplate(threshold=2)
+ witness_hex = case.get("witness_script")
+ # Get the tweak map of the bare public keys to the BIP 32 tweak
+ tweaks_raw = case.get("tweak_map", {})
+ tweaks = parse_tweak_map(tweaks_raw)
+ expected_bool = bool(case.get("expected", False))
+
+ result = input_verification(
+ descriptor,
+ bytes.fromhex(witness_hex),
+ tweaks,
+ )
+ if result != expected_bool:
+ raise AssertionError(
+ f"input_verification result {result} did not match expected {expected_bool}"
+ )
+
+def test_change_output_verification_vectors() -> None:
+ with open(os.path.join(sys.path[0], 'vectors', 'change_output_verification_vectors.json')) as f:
+ data = json.load(f)
+
+ for case in data.get("test_cases", []):
+ descriptor = SortedMultiDescriptorTemplate(threshold=2)
+ witness_hex = case.get("witness_script")
+ # Get the tweak map of the bare public keys to the BIP 32 tweak
+ tweaks_raw = case.get("tweak_map", {})
+ tweaks = parse_tweak_map(tweaks_raw)
+ expected_bool = bool(case.get("expected", False))
+
+ result = change_output_verification(
+ descriptor,
+ bytes.fromhex(witness_hex),
+ tweaks,
+ )
+ if result != expected_bool:
+ raise AssertionError(
+ f"change_output_verification result {result} did not match expected {expected_bool}"
+ )
+
+def parse_tweak_map(raw: Mapping[str, object]) -> Dict[bytes, int]:
+ tweaks: Dict[bytes, int] = {}
+ for key_hex, tweak_hex in raw.items():
+ base_key = bytes.fromhex(key_hex)
+ if not isinstance(tweak_hex, str):
+ raise ValueError(f"tweak value for key {key_hex} must be a string")
+ tweak_value = int(tweak_hex, 16)
+ tweaks[base_key] = tweak_value % CURVE_N
+ return tweaks
+
+def resolve_error_spec(raw: object) -> Tuple[type[Exception], Optional[str]]:
+ mapping: Dict[str, type[Exception]] = {"value": ValueError, "assertion": AssertionError, "runtime": RuntimeError}
+ if not isinstance(raw, dict):
+ return ValueError, None
+
+ raw_dict = cast(Dict[str, Any], raw)
+ name = str(raw_dict.get("type", "value")).lower()
+ message = raw_dict.get("message")
+ exc_type = mapping.get(name, ValueError)
+ return exc_type, None if message is None else str(message)
+
+if __name__ == '__main__':
+ test_blind_nonce_gen_vectors()
+ test_blind_challenge_gen_vectors()
+ test_blind_sign_and_verify_vectors()
+ test_unblind_signature_vectors()
+ test_sign_and_verify_random(6)
+ test_compute_tweak_vectors()
+ test_delegator_sign_vectors()
+ test_input_verification_vectors()
+ test_change_output_verification_vectors()
+ print("All tests passed")
\ No newline at end of file
diff --git a/bip-0089/secp256k1lab/COPYING b/bip-0089/secp256k1lab/COPYING
new file mode 100644
index 0000000000..e8f2163641
--- /dev/null
+++ b/bip-0089/secp256k1lab/COPYING
@@ -0,0 +1,23 @@
+The MIT License (MIT)
+
+Copyright (c) 2009-2024 The Bitcoin Core developers
+Copyright (c) 2009-2024 Bitcoin Developers
+Copyright (c) 2025- The secp256k1lab Developers
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/bip-0089/secp256k1lab/__init__.py b/bip-0089/secp256k1lab/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/bip-0089/secp256k1lab/bip340.py b/bip-0089/secp256k1lab/bip340.py
new file mode 100644
index 0000000000..ba839d16e1
--- /dev/null
+++ b/bip-0089/secp256k1lab/bip340.py
@@ -0,0 +1,73 @@
+# The following functions are based on the BIP 340 reference implementation:
+# https://github.com/bitcoin/bips/blob/master/bip-0340/reference.py
+
+from .secp256k1 import FE, GE, G
+from .util import int_from_bytes, bytes_from_int, xor_bytes, tagged_hash
+
+
+def pubkey_gen(seckey: bytes) -> bytes:
+ d0 = int_from_bytes(seckey)
+ if not (1 <= d0 <= GE.ORDER - 1):
+ raise ValueError("The secret key must be an integer in the range 1..n-1.")
+ P = d0 * G
+ assert not P.infinity
+ return P.to_bytes_xonly()
+
+
+def schnorr_sign(
+ msg: bytes, seckey: bytes, aux_rand: bytes, tag_prefix: str = "BIP0340"
+) -> bytes:
+ d0 = int_from_bytes(seckey)
+ if not (1 <= d0 <= GE.ORDER - 1):
+ raise ValueError("The secret key must be an integer in the range 1..n-1.")
+ if len(aux_rand) != 32:
+ raise ValueError("aux_rand must be 32 bytes instead of %i." % len(aux_rand))
+ P = d0 * G
+ assert not P.infinity
+ d = d0 if P.has_even_y() else GE.ORDER - d0
+ t = xor_bytes(bytes_from_int(d), tagged_hash(tag_prefix + "/aux", aux_rand))
+ k0 = (
+ int_from_bytes(tagged_hash(tag_prefix + "/nonce", t + P.to_bytes_xonly() + msg))
+ % GE.ORDER
+ )
+ if k0 == 0:
+ raise RuntimeError("Failure. This happens only with negligible probability.")
+ R = k0 * G
+ assert not R.infinity
+ k = k0 if R.has_even_y() else GE.ORDER - k0
+ e = (
+ int_from_bytes(
+ tagged_hash(
+ tag_prefix + "/challenge", R.to_bytes_xonly() + P.to_bytes_xonly() + msg
+ )
+ )
+ % GE.ORDER
+ )
+ sig = R.to_bytes_xonly() + bytes_from_int((k + e * d) % GE.ORDER)
+ assert schnorr_verify(msg, P.to_bytes_xonly(), sig, tag_prefix=tag_prefix)
+ return sig
+
+
+def schnorr_verify(
+ msg: bytes, pubkey: bytes, sig: bytes, tag_prefix: str = "BIP0340"
+) -> bool:
+ if len(pubkey) != 32:
+ raise ValueError("The public key must be a 32-byte array.")
+ if len(sig) != 64:
+ raise ValueError("The signature must be a 64-byte array.")
+ try:
+ P = GE.from_bytes_xonly(pubkey)
+ except ValueError:
+ return False
+ r = int_from_bytes(sig[0:32])
+ s = int_from_bytes(sig[32:64])
+ if (r >= FE.SIZE) or (s >= GE.ORDER):
+ return False
+ e = (
+ int_from_bytes(tagged_hash(tag_prefix + "/challenge", sig[0:32] + pubkey + msg))
+ % GE.ORDER
+ )
+ R = s * G - e * P
+ if R.infinity or (not R.has_even_y()) or (R.x != r):
+ return False
+ return True
diff --git a/bip-0089/secp256k1lab/ecdh.py b/bip-0089/secp256k1lab/ecdh.py
new file mode 100644
index 0000000000..73f47fa1a7
--- /dev/null
+++ b/bip-0089/secp256k1lab/ecdh.py
@@ -0,0 +1,16 @@
+import hashlib
+
+from .secp256k1 import GE, Scalar
+
+
+def ecdh_compressed_in_raw_out(seckey: bytes, pubkey: bytes) -> GE:
+ """TODO"""
+ shared_secret = Scalar.from_bytes_checked(seckey) * GE.from_bytes_compressed(pubkey)
+ assert not shared_secret.infinity # prime-order group
+ return shared_secret
+
+
+def ecdh_libsecp256k1(seckey: bytes, pubkey: bytes) -> bytes:
+ """TODO"""
+ shared_secret = ecdh_compressed_in_raw_out(seckey, pubkey)
+ return hashlib.sha256(shared_secret.to_bytes_compressed()).digest()
diff --git a/bip-0089/secp256k1lab/keys.py b/bip-0089/secp256k1lab/keys.py
new file mode 100644
index 0000000000..3e28897e99
--- /dev/null
+++ b/bip-0089/secp256k1lab/keys.py
@@ -0,0 +1,15 @@
+from .secp256k1 import GE, G
+from .util import int_from_bytes
+
+# The following function is based on the BIP 327 reference implementation
+# https://github.com/bitcoin/bips/blob/master/bip-0327/reference.py
+
+
+# Return the plain public key corresponding to a given secret key
+def pubkey_gen_plain(seckey: bytes) -> bytes:
+ d0 = int_from_bytes(seckey)
+ if not (1 <= d0 <= GE.ORDER - 1):
+ raise ValueError("The secret key must be an integer in the range 1..n-1.")
+ P = d0 * G
+ assert not P.infinity
+ return P.to_bytes_compressed()
diff --git a/bip-0089/secp256k1lab/py.typed b/bip-0089/secp256k1lab/py.typed
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/bip-0089/secp256k1lab/secp256k1.py b/bip-0089/secp256k1lab/secp256k1.py
new file mode 100644
index 0000000000..0526878d91
--- /dev/null
+++ b/bip-0089/secp256k1lab/secp256k1.py
@@ -0,0 +1,483 @@
+# Copyright (c) 2022-2023 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+"""Test-only implementation of low-level secp256k1 field and group arithmetic
+
+It is designed for ease of understanding, not performance.
+
+WARNING: This code is slow and trivially vulnerable to side channel attacks. Do not use for
+anything but tests.
+
+Exports:
+* FE: class for secp256k1 field elements
+* GE: class for secp256k1 group elements
+* G: the secp256k1 generator point
+"""
+
+from __future__ import annotations
+from typing import Self
+
+# TODO Docstrings of methods still say "field element"
+class APrimeFE:
+ """Objects of this class represent elements of a prime field.
+
+ They are represented internally in numerator / denominator form, in order to delay inversions.
+ """
+
+ # The size of the field (also its modulus and characteristic).
+ SIZE: int
+
+ def __init__(self, a: int | Self = 0, b: int | Self = 1) -> None:
+ """Initialize a field element a/b; both a and b can be ints or field elements."""
+ if isinstance(a, type(self)):
+ num = a._num
+ den = a._den
+ else:
+ assert isinstance(a, int)
+ num = a % self.SIZE
+ den = 1
+ if isinstance(b, type(self)):
+ den = (den * b._num) % self.SIZE
+ num = (num * b._den) % self.SIZE
+ else:
+ assert isinstance(b, int)
+ den = (den * b) % self.SIZE
+ assert den != 0
+ if num == 0:
+ den = 1
+ self._num: int = num
+ self._den: int = den
+
+ def __add__(self, a: int | Self) -> Self:
+ """Compute the sum of two field elements (second may be int)."""
+ if isinstance(a, type(self)):
+ return type(self)(self._num * a._den + self._den * a._num, self._den * a._den)
+ if isinstance(a, int):
+ return type(self)(self._num + self._den * a, self._den)
+ return NotImplemented
+
+ def __radd__(self, a: int) -> Self:
+ """Compute the sum of an integer and a field element."""
+ return type(self)(a) + self
+
+ @classmethod
+ def sum(cls, *es: Self) -> Self:
+ """Compute the sum of field elements.
+
+ sum(a, b, c, ...) is identical to (0 + a + b + c + ...)."""
+ return sum(es, start=cls(0))
+
+ def __sub__(self, a: int | Self) -> Self:
+ """Compute the difference of two field elements (second may be int)."""
+ if isinstance(a, type(self)):
+ return type(self)(self._num * a._den - self._den * a._num, self._den * a._den)
+ if isinstance(a, int):
+ return type(self)(self._num - self._den * a, self._den)
+ return NotImplemented
+
+ def __rsub__(self, a: int) -> Self:
+ """Compute the difference of an integer and a field element."""
+ return type(self)(a) - self
+
+ def __mul__(self, a: int | Self) -> Self:
+ """Compute the product of two field elements (second may be int)."""
+ if isinstance(a, type(self)):
+ return type(self)(self._num * a._num, self._den * a._den)
+ if isinstance(a, int):
+ return type(self)(self._num * a, self._den)
+ return NotImplemented
+
+ def __rmul__(self, a: int) -> Self:
+ """Compute the product of an integer with a field element."""
+ return type(self)(a) * self
+
+ def __truediv__(self, a: int | Self) -> Self:
+ """Compute the ratio of two field elements (second may be int)."""
+ if isinstance(a, type(self)) or isinstance(a, int):
+ return type(self)(self, a)
+ return NotImplemented
+
+ def __pow__(self, a: int) -> Self:
+ """Raise a field element to an integer power."""
+ return type(self)(pow(self._num, a, self.SIZE), pow(self._den, a, self.SIZE))
+
+ def __neg__(self) -> Self:
+ """Negate a field element."""
+ return type(self)(-self._num, self._den)
+
+ def __int__(self) -> int:
+ """Convert a field element to an integer in range 0..SIZE-1. The result is cached."""
+ if self._den != 1:
+ self._num = (self._num * pow(self._den, -1, self.SIZE)) % self.SIZE
+ self._den = 1
+ return self._num
+
+ def sqrt(self) -> Self | None:
+ """Compute the square root of a field element if it exists (None otherwise)."""
+ raise NotImplementedError
+
+ def is_square(self) -> bool:
+ """Determine if this field element has a square root."""
+ # A more efficient algorithm is possible here (Jacobi symbol).
+ return self.sqrt() is not None
+
+ def is_even(self) -> bool:
+ """Determine whether this field element, represented as integer in 0..SIZE-1, is even."""
+ return int(self) & 1 == 0
+
+ def __eq__(self, a: object) -> bool:
+ """Check whether two field elements are equal (second may be an int)."""
+ if isinstance(a, type(self)):
+ return (self._num * a._den - self._den * a._num) % self.SIZE == 0
+ elif isinstance(a, int):
+ return (self._num - self._den * a) % self.SIZE == 0
+ return False # for other types
+
+ def to_bytes(self) -> bytes:
+ """Convert a field element to a 32-byte array (BE byte order)."""
+ return int(self).to_bytes(32, 'big')
+
+ @classmethod
+ def from_int_checked(cls, v: int) -> Self:
+ """Convert an integer to a field element (no overflow allowed)."""
+ if v >= cls.SIZE:
+ raise ValueError
+ return cls(v)
+
+ @classmethod
+ def from_int_wrapping(cls, v: int) -> Self:
+ """Convert an integer to a field element (reduced modulo SIZE)."""
+ return cls(v % cls.SIZE)
+
+ @classmethod
+ def from_bytes_checked(cls, b: bytes) -> Self:
+ """Convert a 32-byte array to a field element (BE byte order, no overflow allowed)."""
+ v = int.from_bytes(b, 'big')
+ return cls.from_int_checked(v)
+
+ @classmethod
+ def from_bytes_wrapping(cls, b: bytes) -> Self:
+ """Convert a 32-byte array to a field element (BE byte order, reduced modulo SIZE)."""
+ v = int.from_bytes(b, 'big')
+ return cls.from_int_wrapping(v)
+
+ def __str__(self) -> str:
+ """Convert this field element to a 64 character hex string."""
+ return f"{int(self):064x}"
+
+ def __repr__(self) -> str:
+ """Get a string representation of this field element."""
+ return f"{type(self).__qualname__}(0x{int(self):x})"
+
+
+class FE(APrimeFE):
+ SIZE = 2**256 - 2**32 - 977
+
+ def sqrt(self) -> Self | None:
+ # Due to the fact that our modulus p is of the form (p % 4) == 3, the Tonelli-Shanks
+ # algorithm (https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm) is simply
+ # raising the argument to the power (p + 1) / 4.
+
+ # To see why: (p-1) % 2 = 0, so 2 divides the order of the multiplicative group,
+ # and thus only half of the non-zero field elements are squares. An element a is
+ # a (nonzero) square when Euler's criterion, a^((p-1)/2) = 1 (mod p), holds. We're
+ # looking for x such that x^2 = a (mod p). Given a^((p-1)/2) = 1, that is equivalent
+ # to x^2 = a^(1 + (p-1)/2) mod p. As (1 + (p-1)/2) is even, this is equivalent to
+ # x = a^((1 + (p-1)/2)/2) mod p, or x = a^((p+1)/4) mod p.
+ v = int(self)
+ s = pow(v, (self.SIZE + 1) // 4, self.SIZE)
+ if s**2 % self.SIZE == v:
+ return type(self)(s)
+ return None
+
+
+class Scalar(APrimeFE):
+ """TODO Docstring"""
+ SIZE = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
+
+ @classmethod
+ def from_int_nonzero_checked(cls, v: int) -> Self:
+ """Convert an integer to a scalar (no zero or overflow allowed)."""
+ if not (0 < v < cls.SIZE):
+ raise ValueError
+ return cls(v)
+
+ @classmethod
+ def from_bytes_nonzero_checked(cls, b: bytes) -> Self:
+ """Convert a 32-byte array to a scalar (BE byte order, no zero or overflow allowed)."""
+ v = int.from_bytes(b, 'big')
+ return cls.from_int_nonzero_checked(v)
+
+
+class GE:
+ """Objects of this class represent secp256k1 group elements (curve points or infinity)
+
+ GE objects are immutable.
+
+ Normal points on the curve have fields:
+ * x: the x coordinate (a field element)
+ * y: the y coordinate (a field element, satisfying y^2 = x^3 + 7)
+ * infinity: False
+
+ The point at infinity has field:
+ * infinity: True
+ """
+
+ # TODO The following two class attributes should probably be just getters as
+ # classmethods to enforce immutability. Unfortunately Python makes it hard
+ # to create "classproperties". `G` could then also be just a classmethod.
+
+ # Order of the group (number of points on the curve, plus 1 for infinity)
+ ORDER = Scalar.SIZE
+
+ # Number of valid distinct x coordinates on the curve.
+ ORDER_HALF = ORDER // 2
+
+ @property
+ def infinity(self) -> bool:
+ """Whether the group element is the point at infinity."""
+ return self._infinity
+
+ @property
+ def x(self) -> FE:
+ """The x coordinate (a field element) of a non-infinite group element."""
+ assert not self.infinity
+ return self._x
+
+ @property
+ def y(self) -> FE:
+ """The y coordinate (a field element) of a non-infinite group element."""
+ assert not self.infinity
+ return self._y
+
+ def __init__(self, x: int | FE | None = None, y: int | FE | None = None) -> None:
+ """Initialize a group element with specified x and y coordinates, or infinity."""
+ if x is None:
+ # Initialize as infinity.
+ assert y is None
+ self._infinity = True
+ else:
+ # Initialize as point on the curve (and check that it is).
+ assert x is not None
+ assert y is not None
+ fx = FE(x)
+ fy = FE(y)
+ assert fy**2 == fx**3 + 7
+ self._infinity = False
+ self._x = fx
+ self._y = fy
+
+ def __add__(self, a: GE) -> GE:
+ """Add two group elements together."""
+ # Deal with infinity: a + infinity == infinity + a == a.
+ if self.infinity:
+ return a
+ if a.infinity:
+ return self
+ if self.x == a.x:
+ if self.y != a.y:
+ # A point added to its own negation is infinity.
+ assert self.y + a.y == 0
+ return GE()
+ else:
+ # For identical inputs, use the tangent (doubling formula).
+ lam = (3 * self.x**2) / (2 * self.y)
+ else:
+ # For distinct inputs, use the line through both points (adding formula).
+ lam = (self.y - a.y) / (self.x - a.x)
+ # Determine point opposite to the intersection of that line with the curve.
+ x = lam**2 - (self.x + a.x)
+ y = lam * (self.x - x) - self.y
+ return GE(x, y)
+
+ @staticmethod
+ def sum(*ps: GE) -> GE:
+ """Compute the sum of group elements.
+
+ GE.sum(a, b, c, ...) is identical to (GE() + a + b + c + ...)."""
+ return sum(ps, start=GE())
+
+ @staticmethod
+ def batch_mul(*aps: tuple[Scalar, GE]) -> GE:
+ """Compute a (batch) scalar group element multiplication.
+
+ GE.batch_mul((a1, p1), (a2, p2), (a3, p3)) is identical to a1*p1 + a2*p2 + a3*p3,
+ but more efficient."""
+ # Reduce all the scalars modulo order first (so we can deal with negatives etc).
+ naps = [(int(a), p) for a, p in aps]
+ # Start with point at infinity.
+ r = GE()
+ # Iterate over all bit positions, from high to low.
+ for i in range(255, -1, -1):
+ # Double what we have so far.
+ r = r + r
+ # Add then add the points for which the corresponding scalar bit is set.
+ for (a, p) in naps:
+ if (a >> i) & 1:
+ r += p
+ return r
+
+ def __rmul__(self, a: int | Scalar) -> GE:
+ """Multiply an integer or scalar with a group element."""
+ if self == G:
+ return FAST_G.mul(Scalar(a))
+ return GE.batch_mul((Scalar(a), self))
+
+ def __neg__(self) -> GE:
+ """Compute the negation of a group element."""
+ if self.infinity:
+ return self
+ return GE(self.x, -self.y)
+
+ def __sub__(self, a: GE) -> GE:
+ """Subtract a group element from another."""
+ return self + (-a)
+
+ def __eq__(self, a: object) -> bool:
+ """Check if two group elements are equal."""
+ if not isinstance(a, type(self)):
+ return False
+ return (self - a).infinity
+
+ def has_even_y(self) -> bool:
+ """Determine whether a non-infinity group element has an even y coordinate."""
+ assert not self.infinity
+ return self.y.is_even()
+
+ def to_bytes_compressed(self) -> bytes:
+ """Convert a non-infinite group element to 33-byte compressed encoding."""
+ assert not self.infinity
+ return bytes([3 - self.y.is_even()]) + self.x.to_bytes()
+
+ def to_bytes_compressed_with_infinity(self) -> bytes:
+ """Convert a group element to 33-byte compressed encoding, mapping infinity to zeros."""
+ if self.infinity:
+ return 33 * b"\x00"
+ return self.to_bytes_compressed()
+
+ def to_bytes_uncompressed(self) -> bytes:
+ """Convert a non-infinite group element to 65-byte uncompressed encoding."""
+ assert not self.infinity
+ return b'\x04' + self.x.to_bytes() + self.y.to_bytes()
+
+ def to_bytes_xonly(self) -> bytes:
+ """Convert (the x coordinate of) a non-infinite group element to 32-byte xonly encoding."""
+ assert not self.infinity
+ return self.x.to_bytes()
+
+ @staticmethod
+ def lift_x(x: int | FE) -> GE:
+ """Return group element with specified field element as x coordinate (and even y)."""
+ y = (FE(x)**3 + 7).sqrt()
+ if y is None:
+ raise ValueError
+ if not y.is_even():
+ y = -y
+ return GE(x, y)
+
+ @staticmethod
+ def from_bytes_compressed(b: bytes) -> GE:
+ """Convert a compressed to a group element."""
+ assert len(b) == 33
+ if b[0] != 2 and b[0] != 3:
+ raise ValueError
+ x = FE.from_bytes_checked(b[1:])
+ r = GE.lift_x(x)
+ if b[0] == 3:
+ r = -r
+ return r
+
+ @staticmethod
+ def from_bytes_compressed_with_infinity(b: bytes) -> GE:
+ """Convert a compressed to a group element, mapping zeros to infinity."""
+ if b == 33 * b"\x00":
+ return GE()
+ else:
+ return GE.from_bytes_compressed(b)
+
+ @staticmethod
+ def from_bytes_uncompressed(b: bytes) -> GE:
+ """Convert an uncompressed to a group element."""
+ assert len(b) == 65
+ if b[0] != 4:
+ raise ValueError
+ x = FE.from_bytes_checked(b[1:33])
+ y = FE.from_bytes_checked(b[33:])
+ if y**2 != x**3 + 7:
+ raise ValueError
+ return GE(x, y)
+
+ @staticmethod
+ def from_bytes(b: bytes) -> GE:
+ """Convert a compressed or uncompressed encoding to a group element."""
+ assert len(b) in (33, 65)
+ if len(b) == 33:
+ return GE.from_bytes_compressed(b)
+ else:
+ return GE.from_bytes_uncompressed(b)
+
+ @staticmethod
+ def from_bytes_xonly(b: bytes) -> GE:
+ """Convert a point given in xonly encoding to a group element."""
+ assert len(b) == 32
+ x = FE.from_bytes_checked(b)
+ r = GE.lift_x(x)
+ return r
+
+ @staticmethod
+ def is_valid_x(x: int | FE) -> bool:
+ """Determine whether the provided field element is a valid X coordinate."""
+ return (FE(x)**3 + 7).is_square()
+
+ def __str__(self) -> str:
+ """Convert this group element to a string."""
+ if self.infinity:
+ return "(inf)"
+ return f"({self.x},{self.y})"
+
+ def __repr__(self) -> str:
+ """Get a string representation for this group element."""
+ if self.infinity:
+ return "GE()"
+ return f"GE(0x{int(self.x):x},0x{int(self.y):x})"
+
+ def __hash__(self) -> int:
+ """Compute a non-cryptographic hash of the group element."""
+ if self.infinity:
+ return 0 # 0 is not a valid x coordinate
+ return int(self.x)
+
+
+# The secp256k1 generator point
+G = GE.lift_x(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798)
+
+
+class FastGEMul:
+ """Table for fast multiplication with a constant group element.
+
+ Speed up scalar multiplication with a fixed point P by using a precomputed lookup table with
+ its powers of 2:
+
+ table = [P, 2*P, 4*P, (2^3)*P, (2^4)*P, ..., (2^255)*P]
+
+ During multiplication, the points corresponding to each bit set in the scalar are added up,
+ i.e. on average ~128 point additions take place.
+ """
+
+ def __init__(self, p: GE) -> None:
+ self.table: list[GE] = [p] # table[i] = (2^i) * p
+ for _ in range(255):
+ p = p + p
+ self.table.append(p)
+
+ def mul(self, a: Scalar | int) -> GE:
+ result = GE()
+ a_ = int(a)
+ for bit in range(a_.bit_length()):
+ if a_ & (1 << bit):
+ result += self.table[bit]
+ return result
+
+# Precomputed table with multiples of G for fast multiplication
+FAST_G = FastGEMul(G)
diff --git a/bip-0089/secp256k1lab/util.py b/bip-0089/secp256k1lab/util.py
new file mode 100644
index 0000000000..d8c744b795
--- /dev/null
+++ b/bip-0089/secp256k1lab/util.py
@@ -0,0 +1,24 @@
+import hashlib
+
+
+# This implementation can be sped up by storing the midstate after hashing
+# tag_hash instead of rehashing it all the time.
+def tagged_hash(tag: str, msg: bytes) -> bytes:
+ tag_hash = hashlib.sha256(tag.encode()).digest()
+ return hashlib.sha256(tag_hash + tag_hash + msg).digest()
+
+
+def bytes_from_int(x: int) -> bytes:
+ return x.to_bytes(32, byteorder="big")
+
+
+def xor_bytes(b0: bytes, b1: bytes) -> bytes:
+ return bytes(x ^ y for (x, y) in zip(b0, b1))
+
+
+def int_from_bytes(b: bytes) -> int:
+ return int.from_bytes(b, byteorder="big")
+
+
+def hash_sha256(b: bytes) -> bytes:
+ return hashlib.sha256(b).digest()
diff --git a/bip-0089/vectors/blind_challenge_gen_vectors.json b/bip-0089/vectors/blind_challenge_gen_vectors.json
new file mode 100644
index 0000000000..e25b0281fc
--- /dev/null
+++ b/bip-0089/vectors/blind_challenge_gen_vectors.json
@@ -0,0 +1,51 @@
+{
+ "test_cases": [
+ {
+ "rand": "92950940B9C21B956D2950EA4C2CBD966D5DCF32517D2419636C3B434E7E7243",
+ "msg": "33DF4B220B36836C25198D4AFCFD25D1EE2E7B237C3021D7A0EDBA137E70958C",
+ "blindpubnonce": "02866A953BB982D4755FC9DCF0E09CC8EA56E2F75040DCAFE0C17A2A6FB5D4AC6E",
+ "pk": "0232D9E2657C0AA02A6E5AFF67175757832D1B3260A915970EA1CD95E2C9838B52",
+ "tweaks": ["7F91E8EA5D4FD39AAEB0FCDE90ABAAA8681D2610AF0FDDF132DEFBD5E1183580", "8F4ECAB71A22CDB15945BD2898DF005A8623B8DC50013F12700E678E92837406", "FD890EE6226ECA9EFB889DC1EC77B5D59FE0AF1D876C35F2CBE9F25F6B8FB760"],
+ "is_xonly": [true, true, false],
+ "extra_in": "FD8AA0C64B66C38EA627FABB0CFCCE5BB905D130470101ED88771E0A62331AC9",
+
+ "expected_blindfactor": "545AB2AAB17406BE3270D0DFB7B13568F9ED5FAD5ABC5E9ACBAFC8D17131CC37",
+ "expected_challenge": "AC03DF1F1DA05BFD6E01E11BD7B95E3A6A0752BBB0E31EA26251675CECCE3A15",
+ "expected_pubnonce": "0367E34DAB4F1377CD8F3E7C5CD3E1E4A4D3B27BEAB9C0C0DC6717C9C52275D03B",
+ "expected_blindchallenge": "B5B3A3D63771818E930E55D3F91EBF11ED16BCDB11E0F1B5DF06F636F870DFB5",
+ "expected_pk_parity": true,
+ "expected_nonce_parity": false
+ }
+ ],
+ "error_test_cases": [
+ {
+ "rand": "2B01EE16681AE0C2D8845C5F1D3F05F92453E95E7AC053DD5CABC736322B6CA3",
+ "msg": "6C22FC98FEEB69347A04BDE44B99FA50428689608E63B307D9F5904F86FE0B28",
+ "blindpubnonce": "02D9F53C5816BD205B8208A11491530CD6BD1EC35FFA31F026AD3444EFEA329440",
+ "pk": "03E9EBFEEAF165FBA6CD394EB1DBD514AE45CE8EA0AE56D54C8B5D7931D79FFBAF",
+ "tweaks": ["E3DD85653AAFDF2D94312FB8133D6B7E12DFC94B1B82A4E98D85E69D6F2F179A"],
+ "is_xonly": [true, false],
+ "extra_in": "C8BB4B046334864F71173C39BDE2A305289AA1AB5C0E0C624EC2D30A0A182310",
+
+ "error": {
+ "type": "ValueError",
+ "message": "The tweaks and is_xonly arrays must have the same length."
+ },
+ "comment": "mismatched arrays"
+ },
+ {
+ "rand": "A8F932BD0BAC6F31824002482A42493B7AA1CAC2814D80D470A716D47ADCDF86",
+ "msg": "1776037E19AA1A2BF2C9DB770CA12A5AB683E2D7B436090BAC8CE48CB22582E0",
+ "blindpubnonce": "04411898DF38979F1DA000CEFF9166EE258AB6B0F696B8537F90E551751AA3C6F2",
+ "pk": "0333438C1C269BD73BADE95C62EDA258F74B093DA359DEDBF990E923CEC95BD6A4",
+ "tweaks": [],
+ "is_xonly": [],
+ "extra_in": null,
+
+ "error": {
+ "type": "ValueError"
+ },
+ "comment": "invalid blindpubnonce encoding"
+ }
+ ]
+}
diff --git a/bip-0089/vectors/blind_nonce_gen_vectors.json b/bip-0089/vectors/blind_nonce_gen_vectors.json
new file mode 100644
index 0000000000..64810a98d9
--- /dev/null
+++ b/bip-0089/vectors/blind_nonce_gen_vectors.json
@@ -0,0 +1,22 @@
+{
+ "test_cases": [
+ {
+ "rand_": "0F6166D1645791EAD551572348A43CA9293E02CF0ED32B17EA5E1AEC6BC41931",
+ "sk": "F22F1B584D8B5CE15ED8F561DAD077B3FB743E6AABB97DBA758AFD88852DB490",
+ "pk": "0204B445C4EF4E822DA5842965BC03CBDC865EF846774FD27ACDE063F40CD7812C",
+ "extra_in": "887BEFE686260D09F471715719B7CB2D48E4116BD346319D9C002A4FC9D82857",
+ "expected_blindsecnonce": "A4B954BBCB05059CF0ACE8BC2C82BEA5ABD0D2C39B03D7A7205DB41E9BE9CA610204B445C4EF4E822DA5842965BC03CBDC865EF846774FD27ACDE063F40CD7812C",
+ "expected_blindpubnonce": "0355A32C1B472EE1874924CD9A1BF2536D6A2B214413684FBDFC5B84870EFDCEF8",
+ "comment": "All params present"
+ },
+ {
+ "rand_": "D4B20323E12CEC7E21B41A4FD2395844F93D4B3E9F3FED13CF3234C32702A242",
+ "sk": null,
+ "pk": null,
+ "extra_in": null,
+ "expected_blindsecnonce": "78ACDD864846BB5C18017A421E792CC771D63EDA6B63A6CDC3825F298CAC7788",
+ "expected_blindpubnonce": "025CA329F7676AECEAC10C29566D9C7883A661DB2574454AE491476EADEE3CD430",
+ "comment": "Every optional parameter is absent"
+ }
+ ]
+}
diff --git a/bip-0089/vectors/blind_sign_and_verify_vectors.json b/bip-0089/vectors/blind_sign_and_verify_vectors.json
new file mode 100644
index 0000000000..c1c131f657
--- /dev/null
+++ b/bip-0089/vectors/blind_sign_and_verify_vectors.json
@@ -0,0 +1,76 @@
+{
+ "valid_test_cases": [
+ {
+ "sk": "E4E64DB308215A81F1F41969624B9A6265D50F479BA6789E40190027AC6C72A8",
+ "pk": "03E812BE6ED9A2B180FA21B682D5FB35158A9542399D389B736AEDC930CAED04AA",
+ "blindsecnonce": "D05EC853CBCFC49EAEB5DF5AED030C880C1FB59414AD4ECC3D0E5C50CD7B906803E812BE6ED9A2B180FA21B682D5FB35158A9542399D389B736AEDC930CAED04AA",
+ "blindpubnonce": "03E97BD8C531CB0B40AC13857BCDCA6E9FF33889148BA5C9C02E0BE93D79560186",
+ "blindchallenge": "64FD1082FA5E7C5BF1267A5AB5BC3F4BD41167427E4D4A4166876709857E92EB",
+ "pk_parity": true,
+ "nonce_parity": false,
+
+ "expected": {
+ "blindsignature": "8632B771A6A923FF1561B3513C4841F2D88795B05D99BC581ABCA201EED86EC5"
+ },
+
+ "checks": {
+ "verify_returns_true": true,
+ "secnonce_prefix_zeroed_after_sign": true,
+ "second_call_raises_valueerror": true
+ }
+ }
+ ],
+
+ "sign_error_test_cases": [
+ {
+ "sk": "5D2E5F8FD68D31B28F14334CA3E2DF8B85C2F31DBBD5C3E583DBFF90E2024286",
+ "blindsecnonce": "EDBA15E0F013E5323F22998F324B5ABF75D8FEB5EF4FD4BBD7B706B057BF1F08036E3F9DB8CD5E6461E8C23F80F4A67F7006011A1AE3DBDD863213E73D1534D5DC",
+ "blindchallenge": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
+ "pk_parity": false,
+ "nonce_parity": true,
+
+ "error": { "type": "ValueError" },
+ "repeat": 1,
+ "comment": "e' out of range"
+ },
+ {
+ "sk": "8C3975176DD4A9A2CFDFBBF50243C29E6C889D3867BE5D3C3BEBCD00B1BC6469",
+ "blindsecnonce": "E1B7C8E2750577A638D26BCABE96F66C7AE5DCCC6BF429E167686CC1BCDC07AF037C1AAEF6EEDEA6DBB123DC76D8C4AF9210E33EB26D7BBA95123680E0632F7F65",
+ "blindchallenge": "93EF4DEE1C3EC61665D94448715FC756363FC775A10B6CBB158B089404E3CB1E",
+ "pk_parity": true,
+ "nonce_parity": false,
+
+ "error": { "type": "ValueError" },
+ "repeat": 2,
+ "comment": "nonce reuse: second call must raise"
+ }
+ ],
+
+ "verify_fail_test_cases": [
+ {
+ "pk": "03E812BE6ED9A2B180FA21B682D5FB35158A9542399D389B736AEDC930CAED04AA",
+ "blindpubnonce": "03E97BD8C531CB0B40AC13857BCDCA6E9FF33889148BA5C9C02E0BE93D79560186",
+ "blindchallenge": "64FD1082FA5E7C5BF1267A5AB5BC3F4BD41167427E4D4A4166876709857E92EB",
+ "blindsignature": "9632B771A6A923FF1561B3513C4841F2D88795B05D99BC581ABCA201EED86EC5",
+ "pk_parity": true,
+ "nonce_parity": false,
+
+ "expected_valid": false,
+ "comment": "Verify should return False (no exception)"
+ }
+ ],
+
+ "verify_error_test_cases": [
+ {
+ "pk": "03E812BE6ED9A2B180FA21B682D5FB35158A9542399D389B736AEDC930CAED04AA",
+ "blindpubnonce": "04E97BD8C531CB0B40AC13857BCDCA6E9FF33889148BA5C9C02E0BE93D79560186",
+ "blindchallenge": "64FD1082FA5E7C5BF1267A5AB5BC3F4BD41167427E4D4A4166876709857E92EB",
+ "blindsignature": "8632B771A6A923FF1561B3513C4841F2D88795B05D99BC581ABCA201EED86EC5",
+ "pk_parity": true,
+ "nonce_parity": false,
+
+ "error": { "type": "ValueError" },
+ "comment": "Bad blindpubnonce encoding"
+ }
+ ]
+}
diff --git a/bip-0089/vectors/change_output_verification_vectors.json b/bip-0089/vectors/change_output_verification_vectors.json
new file mode 100644
index 0000000000..0159af4ce5
--- /dev/null
+++ b/bip-0089/vectors/change_output_verification_vectors.json
@@ -0,0 +1,43 @@
+{
+ "test_cases": [
+ {
+ "comment": "Change output verification 2-of-3 (path [1,5])",
+ "expected": true,
+ "tweak_map": {
+ "02a047233eec59cf06b9a5ee62d9088eeb8127201423f88637443ff7ee591923c9": "ee665bd369e95c42180fc3e4a504ce4f19173deb6ee7ed1b2c05df7d37d8ed1e",
+ "0386623c88ed79ef5d9aacd24f227a0cd845f5840b861a25118c1200cccd046e0f": "2102aa7f5b2acf81e86d9fa841acdfe8e08d1faa800a318679ad7423dc615a2b",
+ "03c3c01af1d84ec032f7f8d6decd48d74cbbd62253e12691debd064e8b41cb0945": "96c3196aaa0af9b79148d58ff2f58dc7291d4007202722602ddbd29e6cd6c018"
+ },
+ "witness_script": "52210202573f6f0cd23e1d68894ddf5a50f65970833b75d7c1d5b862cbe17166d48850210206df37b85a2393162f1efd561297c37165dc7d8958ab4c5553ddf2e08108784d21037579ad42e47027db0734e66894863f31287b663695f643eb655873baf761a20453ae"
+ },
+ {
+ "comment": "Witness script mismatch",
+ "expected": false,
+ "tweaks": {
+ "02a047233eec59cf06b9a5ee62d9088eeb8127201423f88637443ff7ee591923c9": "ee665bd369e95c42180fc3e4a504ce4f19173deb6ee7ed1b2c05df7d37d8ed1e",
+ "0386623c88ed79ef5d9aacd24f227a0cd845f5840b861a25118c1200cccd046e0f": "2102aa7f5b2acf81e86d9fa841acdfe8e08d1faa800a318679ad7423dc615a2b",
+ "03c3c01af1d84ec032f7f8d6decd48d74cbbd62253e12691debd064e8b41cb0945": "96c3196aaa0af9b79148d58ff2f58dc7291d4007202722602ddbd29e6cd6c018"
+ },
+ "witness_script": "52210202573f6f0cd23e1d68894ddf5a50f65970833b75d7c1d5b862cbe17166d48850210206df37b85a2393162f1efd561297c37165dc7d8958ab4c5553ddf2e08108784d21037579ad42e47027db0734e66894863f31287b663695f643eb655873baf761a20453af"
+ },
+ {
+ "comment": "Missing participant tweak",
+ "expected": false,
+ "tweaks": {
+ "0386623c88ed79ef5d9aacd24f227a0cd845f5840b861a25118c1200cccd046e0f": "2102aa7f5b2acf81e86d9fa841acdfe8e08d1faa800a318679ad7423dc615a2b",
+ "03c3c01af1d84ec032f7f8d6decd48d74cbbd62253e12691debd064e8b41cb0945": "96c3196aaa0af9b79148d58ff2f58dc7291d4007202722602ddbd29e6cd6c018"
+ },
+ "witness_script": "52210202573f6f0cd23e1d68894ddf5a50f65970833b75d7c1d5b862cbe17166d48850210206df37b85a2393162f1efd561297c37165dc7d8958ab4c5553ddf2e08108784d21037579ad42e47027db0734e66894863f31287b663695f643eb655873baf761a20453ae"
+ },
+ {
+ "comment": "Invalid base key length in tweak map",
+ "expected": false,
+ "tweaks": {
+ "02a047233eec59cf06b9a5ee62d9088eeb8127201423f88637443ff7ee591923": "ee665bd369e95c42180fc3e4a504ce4f19173deb6ee7ed1b2c05df7d37d8ed1e",
+ "0386623c88ed79ef5d9aacd24f227a0cd845f5840b861a25118c1200cccd046e0f": "2102aa7f5b2acf81e86d9fa841acdfe8e08d1faa800a318679ad7423dc615a2b",
+ "03c3c01af1d84ec032f7f8d6decd48d74cbbd62253e12691debd064e8b41cb0945": "96c3196aaa0af9b79148d58ff2f58dc7291d4007202722602ddbd29e6cd6c018"
+ },
+ "witness_script": "52210202573f6f0cd23e1d68894ddf5a50f65970833b75d7c1d5b862cbe17166d48850210206df37b85a2393162f1efd561297c37165dc7d8958ab4c5553ddf2e08108784d21037579ad42e47027db0734e66894863f31287b663695f643eb655873baf761a20453ae"
+ }
+ ]
+}
diff --git a/bip-0089/vectors/compute_bip32_tweak_vectors.json b/bip-0089/vectors/compute_bip32_tweak_vectors.json
new file mode 100644
index 0000000000..959d1a750c
--- /dev/null
+++ b/bip-0089/vectors/compute_bip32_tweak_vectors.json
@@ -0,0 +1,32 @@
+{
+ "xpub": {
+ "compressed": "0296928602758150d2b4a8a253451b887625b94ab0a91f801f1408cb33b9cf0f83",
+ "chain_code": "433cf1154e61c4eb9793488880f8a795a3a72052ad14a7367852542425609640",
+ "depth": 0,
+ "parent_fingerprint": "71348c8a",
+ "child_number": 0
+ },
+ "valid_test_cases": [
+ {
+ "comment": "Delegatee tweak aggregation for a two-step path",
+ "path": ["0", "1"],
+ "expected": {
+ "tweak": "d81d8e239630639ac24f3976257d9e4d905272b3da3a6507841c1ec80b04b91b",
+ "derived_xpub": {
+ "compressed": "03636eb334a6ffdfc4b975a61dae12f49e7f94461690fa4688632db8eed5601b03",
+ "chain_code": "299bc0ad44ab883a5be9601918badd2720c86c48a6d8b9d17e1ae1c3b0ad975d"
+ }
+ }
+ }
+ ],
+ "error_test_cases": [
+ {
+ "comment": "Hardened path should raise an error",
+ "path": ["0", "2147483648"],
+ "error": {
+ "type": "value",
+ "message": "Hardened derivations are not supported for delegates"
+ }
+ }
+ ]
+}
diff --git a/bip-0089/vectors/delegator_sign_vectors.json b/bip-0089/vectors/delegator_sign_vectors.json
new file mode 100644
index 0000000000..f11e6b2aa4
--- /dev/null
+++ b/bip-0089/vectors/delegator_sign_vectors.json
@@ -0,0 +1,13 @@
+{
+ "test_cases": [
+ {
+ "comment": "Delegator signing with provided CCD tweak over arbitrary message",
+ "base_secret": "9303c68c414a6208dbc0329181dd640b135e669647ad7dcb2f09870c54b26ed9",
+ "tweak": "d81d8e239630639ac24f3976257d9e4d905272b3da3a6507841c1ec80b04b91b",
+ "message": "Chain Code Delegation",
+ "expected": {
+ "signature": "2f558d1519106f6cffdcfce09954c6ae328b98308718a0903e3efed103b457cd563c315fe6c6b5ffe6f71f413ce68ba22ee793238ab73fd2cef9d5881ae80017"
+ }
+ }
+ ]
+}
diff --git a/bip-0089/vectors/input_verification_vectors.json b/bip-0089/vectors/input_verification_vectors.json
new file mode 100644
index 0000000000..5275f38772
--- /dev/null
+++ b/bip-0089/vectors/input_verification_vectors.json
@@ -0,0 +1,43 @@
+{
+ "test_cases": [
+ {
+ "comment": "Input verification for wsh(sortedmulti) 2-of-3 (path [0,5])",
+ "expected": true,
+ "tweak_map": {
+ "02a047233eec59cf06b9a5ee62d9088eeb8127201423f88637443ff7ee591923c9": "6e4dd29833f7b88751dad6ea6ff536959122f2d07074006657d0e2ef26af3ef6",
+ "0386623c88ed79ef5d9aacd24f227a0cd845f5840b861a25118c1200cccd046e0f": "b30d8530e3464dc71ed6e20897ef5c3c9d1149ecc11f332336520addab1454f3",
+ "03c3c01af1d84ec032f7f8d6decd48d74cbbd62253e12691debd064e8b41cb0945": "c1efff9fb89227d09e54b403ae269f1991003e964f66f412e8302f8bb1c71644"
+ },
+ "witness_script": "5221034ebf1d6b674fbf3d7ff09e4bc44b23e17745188b4aac3e2e101bd210cd8f3ed42103a0d8aed25b77d286d7bf7a668b452f18def89f2e2285acd315fc00668fe0a70b2103bd4632ebd0de4573710722bf73b4bbb76713734c4756b830302b8492f29a6aae53ae"
+ },
+ {
+ "comment": "Witness script mismatch",
+ "expected": false,
+ "tweak_map": {
+ "02a047233eec59cf06b9a5ee62d9088eeb8127201423f88637443ff7ee591923c9": "6e4dd29833f7b88751dad6ea6ff536959122f2d07074006657d0e2ef26af3ef6",
+ "0386623c88ed79ef5d9aacd24f227a0cd845f5840b861a25118c1200cccd046e0f": "b30d8530e3464dc71ed6e20897ef5c3c9d1149ecc11f332336520addab1454f3",
+ "03c3c01af1d84ec032f7f8d6decd48d74cbbd62253e12691debd064e8b41cb0945": "c1efff9fb89227d09e54b403ae269f1991003e964f66f412e8302f8bb1c71644"
+ },
+ "witness_script": "5221034ebf1d6b674fbf3d7ff09e4bc44b23e17745188b4aac3e2e101bd210cd8f3ed42103a0d8aed25b77d286d7bf7a668b452f18def89f2e2285acd315fc00668fe0a70b2103bd4632ebd0de4573710722bf73b4bbb76713734c4756b830302b8492f29a6aae53af"
+ },
+ {
+ "comment": "Missing participant tweak",
+ "expected": false,
+ "tweak_map": {
+ "0386623c88ed79ef5d9aacd24f227a0cd845f5840b861a25118c1200cccd046e0f": "b30d8530e3464dc71ed6e20897ef5c3c9d1149ecc11f332336520addab1454f3",
+ "03c3c01af1d84ec032f7f8d6decd48d74cbbd62253e12691debd064e8b41cb0945": "c1efff9fb89227d09e54b403ae269f1991003e964f66f412e8302f8bb1c71644"
+ },
+ "witness_script": "5221034ebf1d6b674fbf3d7ff09e4bc44b23e17745188b4aac3e2e101bd210cd8f3ed42103a0d8aed25b77d286d7bf7a668b452f18def89f2e2285acd315fc00668fe0a70b2103bd4632ebd0de4573710722bf73b4bbb76713734c4756b830302b8492f29a6aae53ae"
+ },
+ {
+ "comment": "Invalid base key length in tweak map",
+ "expected": false,
+ "tweak_map": {
+ "02a047233eec59cf06b9a5ee62d9088eeb8127201423f88637443ff7ee591923": "6e4dd29833f7b88751dad6ea6ff536959122f2d07074006657d0e2ef26af3ef6",
+ "0386623c88ed79ef5d9aacd24f227a0cd845f5840b861a25118c1200cccd046e0f": "b30d8530e3464dc71ed6e20897ef5c3c9d1149ecc11f332336520addab1454f3",
+ "03c3c01af1d84ec032f7f8d6decd48d74cbbd62253e12691debd064e8b41cb0945": "c1efff9fb89227d09e54b403ae269f1991003e964f66f412e8302f8bb1c71644"
+ },
+ "witness_script": "5221034ebf1d6b674fbf3d7ff09e4bc44b23e17745188b4aac3e2e101bd210cd8f3ed42103a0d8aed25b77d286d7bf7a668b452f18def89f2e2285acd315fc00668fe0a70b2103bd4632ebd0de4573710722bf73b4bbb76713734c4756b830302b8492f29a6aae53ae"
+ }
+ ]
+}
diff --git a/bip-0089/vectors/unblind_signature_vectors.json b/bip-0089/vectors/unblind_signature_vectors.json
new file mode 100644
index 0000000000..77a52c2d4e
--- /dev/null
+++ b/bip-0089/vectors/unblind_signature_vectors.json
@@ -0,0 +1,63 @@
+{
+ "valid_test_cases": [
+ {
+ "session_ctx": {
+ "pk": "03A1B69A6C047657AA6A0DF9ED43E5B0CA75097260F065048606D0946B2B89A6AD",
+ "blindfactor": "D08134A1CA8F716EE99EE69179BD939CF2DCD29D3EB1827124BAEB1364088AA9",
+ "challenge": "0AB1D307369FB4D994A8DEDE3D503FDC7B8AF459AECE3C69B5C22F5BFA293618",
+ "pubnonce": "02ED7E7EB4E886F9A9DF4E375F5F9321DCF5AA909B85A028B7EBB14F2ED80AE3BD",
+ "tweaks": ["1956DF466B657FFA287B6BFC63219BB6BF3D5A72ECE44E43E14091CBF15100BB", "2CB93A737A3B9A86D678DD8060ECA5443978B87BA54CFC21AE1341B47C2640B9"],
+ "is_xonly": [false, true]
+ },
+ "msg": "28431125D79E16223AAF5401267447B8729324613B74A3A1DFD4EE8E277B5C40",
+ "blindsignature": "6180428458B0EDA605A2D897A45784C399D310060FD0BE701DA4AE5B2EEB7A40",
+
+ "expected_bip340_sig": "ED7E7EB4E886F9A9DF4E375F5F9321DCF5AA909B85A028B7EBB14F2ED80AE3BD1A606D2DE092BD1A05B82532BDEA7F11493D00EB1109CF1EF30A8D8E2FF2721C"
+ }
+ ],
+
+ "error_test_cases": [
+ {
+ "session_ctx": {
+ "pk": "03A1B69A6C047657AA6A0DF9ED43E5B0CA75097260F065048606D0946B2B89A6AD",
+ "blindfactor": "D08134A1CA8F716EE99EE69179BD939CF2DCD29D3EB1827124BAEB1364088AA9",
+ "challenge": "0AB1D307369FB4D994A8DEDE3D503FDC7B8AF459AECE3C69B5C22F5BFA293618",
+ "pubnonce": "04ED7E7EB4E886F9A9DF4E375F5F9321DCF5AA909B85A028B7EBB14F2ED80AE3BD",
+ "tweaks": ["1956DF466B657FFA287B6BFC63219BB6BF3D5A72ECE44E43E14091CBF15100BB", "2CB93A737A3B9A86D678DD8060ECA5443978B87BA54CFC21AE1341B47C2640B9"],
+ "is_xonly": [false, true]
+ },
+ "msg": "28431125D79E16223AAF5401267447B8729324613B74A3A1DFD4EE8E277B5C40",
+ "blindsignature": "6180428458B0EDA605A2D897A45784C399D310060FD0BE701DA4AE5B2EEB7A40",
+ "error": { "type": "ValueError" },
+ "comment": "Bad pubnonce encoding"
+ },
+ {
+ "session_ctx": {
+ "pk": "03A1B69A6C047657AA6A0DF9ED43E5B0CA75097260F065048606D0946B2B89A6AD",
+ "blindfactor": "D08134A1CA8F716EE99EE69179BD939CF2DCD29D3EB1827124BAEB1364088AA9",
+ "challenge": "0AB1D307369FB4D994A8DEDE3D503FDC7B8AF459AECE3C69B5C22F5BFA293618",
+ "pubnonce": "04ED7E7EB4E886F9A9DF4E375F5F9321DCF5AA909B85A028B7EBB14F2ED80AE3BD",
+ "tweaks": ["1956DF466B657FFA287B6BFC63219BB6BF3D5A72ECE44E43E14091CBF15100BB", "2CB93A737A3B9A86D678DD8060ECA5443978B87BA54CFC21AE1341B47C2640B9"],
+ "is_xonly": [true]
+ },
+ "msg": "28431125D79E16223AAF5401267447B8729324613B74A3A1DFD4EE8E277B5C40",
+ "blindsignature": "6180428458B0EDA605A2D897A45784C399D310060FD0BE701DA4AE5B2EEB7A40",
+ "error": { "type": "ValueError", "message": "must have the same length" },
+ "comment": "tweaks/is_xonly length mismatch"
+ },
+ {
+ "session_ctx": {
+ "pk": "03A1B69A6C047657AA6A0DF9ED43E5B0CA75097260F065048606D0946B2B89A6AD",
+ "blindfactor": "D08134A1CA8F716EE99EE69179BD939CF2DCD29D3EB1827124BAEB1364088AA9",
+ "challenge": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
+ "pubnonce": "04ED7E7EB4E886F9A9DF4E375F5F9321DCF5AA909B85A028B7EBB14F2ED80AE3BD",
+ "tweaks": ["1956DF466B657FFA287B6BFC63219BB6BF3D5A72ECE44E43E14091CBF15100BB", "2CB93A737A3B9A86D678DD8060ECA5443978B87BA54CFC21AE1341B47C2640B9"],
+ "is_xonly": [true]
+ },
+ "msg": "28431125D79E16223AAF5401267447B8729324613B74A3A1DFD4EE8E277B5C40",
+ "blindsignature": "6180428458B0EDA605A2D897A45784C399D310060FD0BE701DA4AE5B2EEB7A40",
+ "error": { "type": "ValueError" },
+ "comment": "challenge out of range"
+ }
+ ]
+}
diff --git a/bip-0110.mediawiki b/bip-0110.mediawiki
new file mode 100644
index 0000000000..5bc6dea841
--- /dev/null
+++ b/bip-0110.mediawiki
@@ -0,0 +1,353 @@
++ BIP: 110 + Layer: Consensus (soft fork) + Title: Reduced Data Temporary Softfork + Authors: Dathon Ohm+ +==Abstract== + +Temporarily limit the size of data fields at the consensus level, in order to correct distorted incentives caused by standardizing support for arbitrary data, and to refocus priorities on improving Bitcoin as money. + +==Copyright== + +This document is licensed under the 3-clause BSD license. + +==Specification== + +Blocks during a temporary, one-year deployment are checked with these additional rules: + +# New output scriptPubKeys exceeding 34 bytes are invalid, unless the first opcode is OP_RETURN, in which case up to 83 bytes are valid. +# OP_PUSHDATA* payloads and [[#script-argument-witness-items|script argument witness items]] exceeding 256 bytes are invalid, except for the redeemScript push in BIP16 scriptSigs. +# Spending undefined witness (or Tapleaf) versions (ie, not Witness v0/BIP 141, Taproot/BIP 341, or P2A) is invalid. (Creating outputs with undefined witness versions is still valid.) +# Witness stacks with a Taproot annex are invalid. +# Taproot control blocks larger than 257 bytes (a merkle tree with 128 script leaves) are invalid. +# Tapscripts including OP_SUCCESS* opcodes anywhere (even unexecuted) are invalid. +# Tapscripts executing the OP_IF or OP_NOTIF instruction (regardless of result) are invalid. + +Inputs spending UTXOs that were created before the activation height are exempt from the new rules. +Once the softfork expires, UTXOs of all heights are once again unrestricted. + +===GetBlockTemplate=== + +This deployment uses the GBT deployment name "reduced_data" as defined in [[bip-0009.mediawiki#getblocktemplate_changes|BIP 9]]. During mandatory signaling, the deployment's bit is included in "vbrequired". + +==Motivation== + +In order to protect Bitcoin's intended function as the Internet's native money, the Bitcoin community has historically treated techniques for embedding arbitrary data into Bitcoin transactions with antagonism. + +Such data embedding must be resisted at all times in order to ensure it doesn't become load-bearing and start to produce negative externalities, especially for node operators. + +Starting with the "inscription" hack first exploited in 2022, a trend has emerged around embedding arbitrary data into Bitcoin transactions, creating significant unnecessary burdens on node operators and diverting development focus and incentives away from Bitcoin's fundamental purpose of being sound, permissionless, borderless money. + +This BIP aims to set Bitcoin back on the path to becoming the world's money by rejecting the standardization of data storage as a supported use case at the consensus level. + +It achieves this by temporarily invalidating several of the most harmful methods of data abuse by consensus, while preserving all known monetary use cases. + +Specifically, this proposal invalidates all methods of embedding contiguous arbitrary data larger than 256 bytes; it also invalidates large scriptPubKey and Tapleaf formats that are abused almost exclusively for data embedding; and finally, it restores, in consensus, the long-established 83-byte policy limit on OP_RETURN outputs. + +In software development, it is a common practice to disable unsupported use cases, because not doing so quickly becomes unsustainable and causes development to grind to a standstill. + +Activation of these new rules thus sends a clear message that arbitrary data storage will continue to be actively resisted, and that such unsupported usage should not be permitted to derail network priorities. + +It also reinforces Bitcoin's core function as a censorship-resistant payment system, because data storage competes unfairly with payments, making Bitcoin payments unnecessarily costly. This encourages reliance on third-party payment processors, making Bitcoin payments easier to censor. + +Finally, it improves decentralization of the node network by re-establishing the long-held commitment towards minimizing the cost (financial or otherwise) of operating a node. + +Bitcoin should "do one thing, and do it well". By rejecting data storage, this BIP liberates Bitcoin developers from endless scope creep, enabling them to focus on what's really important: Bitcoin's success as money. + +==Rationale== + +===Specification nuance=== + +'''Why limit scriptPubKeys to 34 bytes?''' + +Unspent scriptPubKeys (OP_RETURN excepted) must be stored indefinitely in the UTXO set, which nodes must store on fast media and cannot prune. +Fast storage (usually RAM) is much more costly per byte than slower, non-volatile storage, so as the UTXO set size increases, the burden on node operators increases, harming decentralization. +They are also a direct cost to the sender rather than the receiver. +For these reasons, modern usage is all 34 bytes or smaller in practice; actual spending conditions have been moved to the witness, and the scriptPubKey simply commits to them in advance with a hash. +Furthermore, large scriptPubKeys, in addition to being a data embedding vector, can be abused to create malicious transactions and blocks ("poison blocks") that take a long time to validate. +Large scriptPubKeys, thus carrying a large abuse potential and no benefit, are invalidated by this BIP. + +'''What about OP_RETURN? Why not get rid of it entirely?''' + +OP_RETURN outputs are provably unspendable, and nodes do not need to store them in the UTXO set. +Historically, up to 83 bytes have been tolerated only to avoid unprovably unspendable spam in other output scripts, and with the possible exception of commitment schemes that use OP_RETURN in coinbase transaction outputs (notably Segwit), using OP_RETURN is not the optimal solution to any known use case. +With the advent of pay-to-contract and Taproot, it is now also possible to commit to external data in the Taptree, making even hypothetical use of OP_RETURN deprecated. +However, to avoid breaking legacy protocols that still include such outputs, this proposal allows these outputs. + +'''Why limit other data to 256/257 bytes?''' + +With modern compression, it is plausible to represent images in as few as 300-400 bytes. Images are likely the most harmful use case for data storage, as they have huge demand and supporting them can engender high fees and UTXO-set bloat, as well as content that a large majority of node operators might object to. + +256 bytes (2048 bits) is also more than sufficient for reasonably large numbers that might be potentially needed in legitimate cryptography, reinforcing Bitcoin's intended purpose as a monetary network. + +'''Won't spammers just spread their data over multiple fields?''' + +While it is impossible to fully prevent steganography, limiting data sizes ensures such abuses are non-contiguous and obfuscated within another intended meaning (script code, structure, etc). +As far as Bitcoin is concerned, the data has some meaning other than the spammers' misinterpretation, and any external code to "reassemble" the unintended data is responsible for producing it +(it is possible to write code that transforms *any* data into any other data - what matters is that Bitcoin has a well-defined meaning that is distinct from the unsupported one). +Requiring users to divide their files into chunks of at most 256 bytes, raising the cost both in fees and in effort, sends a clear message that data storage abuses in general are unwelcome rather than sanctioned or supported. + + +'''What are "script argument witness items", referred to in rule 2?''' + +Script argument witness items are the witness stack elements that are placed on the script interpreter's stack as inputs before the script is executed - the witness equivalent of data pushes in scriptSigs. + +The following witness stack elements are '''not''' script argument witness items, and are thus excluded from rule 2 (though some are limited/invalidated by other rules): + +* Some items are part of a spend that invokes the script interpreter, but are popped from the witness stack before the remaining elements are passed to it as inputs: +** Witness scripts and Tapleaf scripts (not limited) +** Control blocks (limited by rule 5) +** Annexes (invalidated by rule 4) +* Others are not part of a script-path spend at all, so the script interpreter is not invoked: +** Taproot key path signatures (limited to exactly 64 or 65 bytes by BIP 341 signature validation) +** Witness stacks in spends of undefined witness versions (invalidated by rule 3) + +All '''other''' witness stack elements are script argument witness items, and are thus limited to 256 bytes by rule 2. + +'''Why are BIP16 redeemScripts, witness scripts, and Tapleaf scripts exempt from rule 2?''' + +BIP16 redeemScripts, witness scripts, and Tapleaf scripts are scripts, not data. Their contents are subject to the same OP_PUSHDATA* restrictions during execution. +Restricting them is not only unnecessary, but would reduce the ability to make use of the intended script capabilities, and could impact legitimate real-world usage. + +'''Why make spending undefined witness/Tapleaf versions invalid?''' + +Since they are undefined, witness stacks spending these versions are completely unlimited currently to allow maximum flexibility in future upgrades. +Any future upgrade, however, would need more than a year of coordination, so this softfork will not actually restrict it, and only safeguards against abuse in the meantime. + +'''Why not make it invalid to send to undefined witness versions?''' + +This would require the senders of transactions to check the witness version prior to sending, and require additional coordination when a new witness version is intended to become used. + +'''Why not allow spending undefined witness versions with an empty witness?''' + +This has no use case, but would require nodes to track these UTXOs in case of potential spending. +By making spending invalid, it is possible for nodes to store them instead in slow memory not needed until this softfork expires. +(With proper planning, it also makes it possible for a future softfork making use of these witness versions to allow users to receive with an upgraded wallet even prior to activation of the upgrade.) + +'''Why make the Taproot annex invalid?''' + +The annex is currently undefined data with unlimited size. +It exists for future upgrades, but has no legitimate usage today. +Any future upgrade, however, would need more than a year of coordination, so this softfork will not actually restrict it, and only safeguards against abuse in the meantime. + +'''Why is the Taproot control block limited to 257 bytes instead of 256?''' + +The control block is a series of hashes proving the Tapscript is part of the Taptree, plus a single byte with the leaf version and parity bit. +See BIP 341 for details. + +'''Why make OP_SUCCESS* invalid?''' + +OP_SUCCESS* is meant for future upgrades. See above regarding undefined witness versions. + +'''Why make OP_IF/OP_NOTIF invalid?''' + +OP_IF/OP_NOTIF originated in pre-Taproot Bitcoin script language as a way to execute different subscripts based on a condition. +With Taproot, the conditions can instead be evaluated off-chain, revealing only the intended verification execution path. +Furthermore, when the conditions are met, the intent is that the keypath spend path should be used instead, avoiding publishing any scripts at all. + +OP_IF is not only redundant for Tapscript, it is also commonly abused today to inject spam that gets skipped at execution. +While it is impossible to fully prevent steganography, closing this gap eliminates one common abuse today basically for free, and sends a message that such abuses are not welcome. +There are some potential experimental use cases for OP_IF in Tapscript. See the Tradeoffs section for more details. + +'''Why is the proposal so simple?''' + +A more complicated proposal could be envisioned that better balances innovation with safety, but implementing this properly would require extensive refactoring and review, delaying deployment when the change is already urgent. +The rules proposed herein have been intentionally kept very simple to minimise review time and avoid unnecessary risks of overlooking unexpected side effects. + +'''Why is this softfork temporary?''' + +The impact of these restrictions would severely constrain future upgrades, potentially forcing them to be designed as a hardfork instead of a softfork. +Some restrictions are also not ideal, but an improved limit would be more complicated to develop and test - +by deploying these simpler restrictions now, we avoid making the perfect the enemy of the good enough, while still allowing for upgrading the limits to better variants in the future. + +Over the next year, interested developers can implement and propose a longer-term solution to address the needs of the protocol without the tradeoffs or blunt/simplified changes. + +===Tradeoffs=== + +'''Are there any tradeoffs?''' + +Yes: + +# Limiting Taproot control blocks to 257 bytes directly constrains the size of the on-chain, consensus-enforced script tree. This could complicate or possibly even impede advanced smart contracting like BitVM, which relies on a large number of executable scripts. In the worst case scenario, these use cases may just need to wait until this softfork expires. As they are still in early development, testnet and sidechains should be sufficient for the next year while a more scalable rule is implemented. +# Upgrade hooks are not available for other softforks. As softforks adding new opcodes typically need at least a year to activate, this shouldn't be a practical issue. +# Some wallet software such as Miniscript habitually creates Tapleaves containing OP_IF. To mitigate the risk of these funds being frozen for a year, this proposal exempts inputs that spend outputs that were created before activation, and provides a two-week grace period between lock-in and activation, to give users time to prepare. + +'''Isn't the limit on Taproot control blocks too restrictive?''' + +Possibly. +The previous limit allows for 340,282,366,920,938,463,463,374,607,431,768,211,456 scripts, which is obviously way more than anyone could ever need. +257 bytes allows for 128 scripts, which is sufficient for modern and complex transactions. +However, it may prove too limiting for advanced off-chain functionality such as used by BitVM. +This is an unfortunate tradeoff that (if this softfork is accepted) we have chosen to accept in the short-term for the immediate benefits of this softfork. +The intent is to relax this restriction later, when this softfork expires, with a new approach allowing larger trees, yet to be developed. + +Do note that non-script (or non-Bitcoin-L1 scripts) usage of the taptree does not have this same limitation: +just a single of the 128 leaves could very well be an extension of the merkle tree to greater depths than enforced by this softfork. + +'''Aren't Taptrees intended to be unbalanced?''' + +While it is true that optimal use of Taptrees may often be unbalanced to favour more-likely-executed scripts, this is optional, and the full capacity (in this case, 128 scripts) can still be used if needed. +Additionally, in ideal/ordinary circumstances, neither the Taptree nor a merkle branch through it is ever published: +all counterparties ought to evaluate the conditions for spending off-chain and rebroadcast the transaction using the keypath spending. +Tapscripts are designed to be used when one or more parties is unreachable or uncooperative; their existence mainly only serves to deter intentional non-cooperation by making it pointless. +An exception to this is protocols employing a NUMS point to restrict an output to only being spendable via the script path. + +'''Is there any risk of funds being frozen or lost?''' + +In theory, yes. This proposal goes to great pains to make sure it does not affect any known use cases, and it is reasonably certain that no one will be affected. However, there are a couple of experimental use cases involving pre-signed Taproot transactions that could end up being affected. + +Specifically: + +* The restriction on OP_IF/OP_NOTIF could temporarily invalidate some edge-case Tapleaves produced by the current version of the Miniscript compiler. There are no verified uses of these constructions currently in production, but there are some popular wallets that could, in theory, produce them. +* The restriction on Taptree depth will invalidate any Tapleaves deeper than 7 levels. Since Taptrees are usually designed with the more common spending conditions positioned higher in the tree, any funds encumbered by such a Taptree will almost certainly be easily spendable by Tapleaves higher up in the tree. + +In both scenarios, funds are spendable either by other Tapleaves in the tree, or by the keypath (unless the keypath is provably invalidated using a NUMS point). + +Funds in either scenario could end up being frozen or lost ''only'' if ''all'' of the following conditions are met: + +* The UTXO is pay-to-Taproot (P2TR); +* The UTXO is in a pre-signed transaction; +* The UTXO being spent ''must'' be confirmed ''and'' spent during the temporary, one-year deployment of these new rules; +* The Tapleaf the user selects to spend the UTXO contains an OP_IF/OP_NOTIF or exists at a depth greater than 7; +* The keypath is unusable to spend the funds, AND there are no other suitable Tapleaves in the tree to spend the UTXO (in which case funds are frozen), OR there are other Tapleaves that ''can'' spend the UTXO in unexpected ways (in which case funds are lost). + +In other words, funds are completely unaffected if: + +* They do not use Taproot; +* They use Taproot in standard and well-supported ways; +* UTXOs needed during the temporary deployment are confirmed before the fork activates; +* UTXOs in pre-signed transactions do not lock funds using Tapleaves that violate the new rules; +* UTXOs in pre-signed transactions that lock funds using Tapleaves that violate the new rules do not need to be confirmed ''and'' spent during the deployment; +* UTXOs in pre-signed transactions that lock funds using Tapleaves that violate the new rules and need to be confirmed ''and'' spent during the deployment can be spent either via the keypath OR by other, expected Tapleaves in the tree. + +This proposal does everything possible to try to avoid funds being frozen or lost, but ultimately it is impossible to prove that absolutely no one will be affected. +It is therefore up to the Bitcoin community to activate these new rules only if they feel that rejecting data storage is worth this tradeoff. +To prepare for activation, it is recommended that users begin migrating any affected funds now. +In the event that these new rules are activated, there will be at least a two-week period between lock-in and activation, during which all users will have the chance to migrate any remaining funds. + + +===Alternatives / Alsos=== +'''Why not let the fee market manage data storage?''' + +The fee market is designed to prioritize transactions based on economic urgency. + +However, the market for data storage on the blockchain is a completely different market from the market for payments, with completely different incentives. + +Specifically, the fee for a monetary transaction incentivises a miner to include the transaction in a block, representing a one-time transfer of monetary value, i.e., a payment. The miner thus provides the one-time service of securing a payment, for a one-time fee. + +Once the payment is secured, the payor does not receive any additional benefit from the Bitcoin network, besides the integrity of Bitcoin's transaction history (a service to which all node operators are happy to contribute, because Bitcoin would not function as money otherwise). + +Conversely, the fee for a data storage transaction still goes only to the miner who includes the data in a block, but the burden of storing the data falls on all node operators, who never received even a part of the fee, yet are forced to continue downloading, storing, and serving the data forever. + +In this case, the miner accepts a one-time fee, and in exchange, the priceless service of highly-available, uncensorable data storage is provided in perpetuity ''for free'' by node operators. + +The problem becomes even worse when the data is objectionable to node operators, as this represents an even larger, unexpected cost for them. + +'''How about OP_RETURN2/"blobspace" making the data optional for nodes?''' + +This has been attempted multiple times in the past. +There is perhaps no harm in trying yet again, and this proposal does not prevent doing so, +but ultimately these schemes depend on the cooperation of the sender, who usually wants to explicitly force the content on non-consenting node operators +(or they would be using other existing distribution methods already). +These other ideas also do not solve the problem of objectionable content. + +'''Why not ban PUSHDATA opcodes/Eliminate the witness discount/Apply a length limit to the Annex rather than eliminating it/Add limits to overall witness or transaction sizes/Make the softfork permanent instead of temporary/Remove the witness discount/Make OP_RETURN cheaper?''' + +These are all interesting ideas, but they all increase the complexity of the implementation, and this proposal was optimized to be simple and easy to review for fast deployment. If the community decides to do any of these things, this proposal encourages them to do so once it expires. + +'''Why not eliminate one or more of the restrictions?''' + +This proposal, as is, represents the strongest possible rejection of the arbitrary data storage use case, while minimizing complexity. Loosening any of the rules would make it less effective at achieving this objective, for not much benefit. + +'''Shouldn't spam be fought in policy? Does this proposal affirm that policy is ineffective?''' + +It remains true that policy is still the best place to fight spam. +However, it is also true that policy cannot guarantee 100% effectiveness, particularly against bad actors who are mining. +This softfork minimises the impact of such malicious miners, closing the worst-case risks. + +'''Does this proposal solve spam completely?''' + +No. +It is impossible to solve spam completely, and typically spam is best fought with policy/filters, not consensus. +What this softfork does is require users wanting to store large unencrypted files in the blockchain to disguise the data as financial data and/or break it up into multiple data pushes. Obviously doing so is considered an abuse of bitcoin and should be avoided, but if it does happen, this BIP strengthens the argument that data storage is not a supported use case. + +'''Why doesn't this proposal address non-Bitcoin tokens?''' + +There are a wide variety of non-Bitcoin tokens, mostly scams, that a significant portion of the community considers spam. +However, these schemes are best countered in policy rather than consensus, and besides, this proposal does not aim to eliminate spam entirely. + +'''Is this a slippery slope? If we make rules against data today, will we start banning use cases we don't like tomorrow?''' + +No. +These rules may be new at the consensus level, but they are merely enshrining long-standing principles of Bitcoin, as necessary to address a threat to the decentralization of the network and its usability for monetary purposes. + +This softfork does not attempt to impose restrictions on monetary activity or the validity of monetary transactions themselves. +By restricting the data storage use case as much as possible, this proposal reinforces Bitcoin's guarantee of sound, permissionless money for the long-term. +This clear distinction between mitigating a systemic risk from non-monetary data abuse and interfering with actual monetary use cases provides a strong barrier against future overreach. + +The explicitly temporary nature of the softfork further reinforces that this is a targeted intervention to mitigate a specific crisis, not a commitment or proposal of a new direction of development. +If no further action is taken by you, it will expire in a year. +Even if a followup softfork is proposed for that time, you retain the right to reject it. + +'''Doesn't this proposal break user space?''' +Yes, this proposal intentionally breaks user space, specifically the data storage user space. This is necessary in order to communicate that data storage is not supported. +Pains have been taken to avoid breaking monetary use cases, and it is unlikely that any such use cases have been affected, but in theory they might be. See the Tradeoffs section for more details. + +==Backwards compatibility== + +There are a couple of very unlikely scenarios in which funds could theoretically be frozen or lost. See the Tradeoffs section for more details. + +If this proposal activates, the Miniscript compiler will need to be modified not to produce scripts that violate the new rules, at least while the new rules are active. + +Users storing arbitrary data in the Bitcoin blockchain should start looking for other places to store their data, such as Nostr, IPFS, BitTorrent, cloud storage, etc, as the Bitcoin network will not support this use case going forward. + +All other known use cases are not affected. + +==Reference implementation== + +https://github.com/bitcoinknots/bitcoin/compare/29.x-knots...dathonohm:bitcoin:uasf-modified-bip9 + +==Deployment== + +This deployment uses a modified version of BIP9 with the following parameters: + +* '''name''': reduced_data +* '''bit''': 4 +* '''starttime''': 1764547200 (~December 1, 2025) +* '''timeout''': NO_TIMEOUT +* '''min_activation_height''': 0 +* '''max_activation_height''': 965664 (~September 1, 2026) +* '''active_duration''': 52416 blocks (~1 year after activation) +* '''threshold''': 1109/2016 (55%) + +===Deviations from BIP9=== + +This deployment deviates from standard BIP9 in five ways: + +'''Reduced threshold (55% instead of 95%)''': The standard BIP9 threshold of 95% is designed for permanent consensus changes where near-universal miner readiness is desirable. Since rejecting data storage is a matter of urgency, and since this softfork is temporary and expires after one year, a lower threshold is ideal. + +'''No timeout, using max_activation_height instead''': Standard BIP9 uses a time-based timeout that transitions to FAILED if the threshold is not reached. This deployment sets timeout to NO_TIMEOUT and instead uses a BIP8-like, height-based max_activation_height. The deployment transitions to LOCKED_IN at height 963648 (one retarget period before max_activation_height), then to ACTIVE at height 965664. + +'''Mandatory signaling period''': Similar to BIP8, this deployment enforces mandatory signaling during the retarget period immediately before mandatory lock-in (blocks 961632 to 963647; lock-in happens no later than block 963648). During this window, blocks that do not signal bit 4 are rejected as invalid. Mandatory signaling ends once the deployment reaches the LOCKED_IN state. + +'''Expiry via active_duration''': Standard BIP9 deployments are permanent once ACTIVE. This proposal includes an active_duration of 52416 blocks (~1 year), making the deployment temporary rather than permanent. After active_duration blocks, the state machine transitions from ACTIVE to EXPIRED, a new terminal state. Once EXPIRED, the rules cease to be enforced and UTXOs are once again unrestricted. + +'''State transitions''': The state machine follows this progression: +# DEFINED: Initial state until starttime +# STARTED: After starttime; miners may signal with bit 4 +# LOCKED_IN: Entered at the start of the first retarget period where the threshold was reached in the previous period; mandatory signaling (blocks 961632-963647) ensures this happens no later than height 963648 +# ACTIVE: Entered one retarget period after LOCKED_IN; rules enforced for active_duration blocks +# EXPIRED: Entered when block height >= activation_height + active_duration; rules no longer enforced (terminal state) + +The FAILED state is never reached because timeout is disabled. EXPIRED is the final state; there are no transitions after it. Miner signaling after expiry has no effect because the deployment is no longer in the STARTED state, and miner signaling only governs the STARTED -> LOCKED_IN transition. + +==Credits== + +Original draft and advice: Luke-Jr diff --git a/bip-0117.mediawiki b/bip-0117.mediawiki index cea3a13cd5..b301083f38 100644 --- a/bip-0117.mediawiki +++ b/bip-0117.mediawiki @@ -192,3 +192,5 @@ The v0 segwit rules prohibit leaving anything on the stack, so for v0 parameters [5] [https://github.com/bitcoin/bips/blob/master/bip-0114.mediawiki BIP114: Merkelized Abstract Syntax Tree] [6] [https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki BIP68: Relative lock-time using consensus-enforced sequence numbers] + +[9] [https://github.com/bitcoin/bips/blob/master/bip-0008.mediawiki BIP8: Version bits with lock-in by height] diff --git a/bip-0128.mediawiki b/bip-0128.mediawiki new file mode 100644 index 0000000000..c89ef32032 --- /dev/null +++ b/bip-0128.mediawiki @@ -0,0 +1,275 @@ ++ Status: Draft + Type: Specification + Assigned: 2025-12-03 + License: BSD-3-Clause + Discussion: https://groups.google.com/g/bitcoindev/c/nOZim6FbuF8 +
+ BIP: 128 + Layer: Applications + Title: Timelock-Recovery Storage Format + Authors: Oren Z+ +== Abstract == + +This document proposes a standard format for saving timelock-recovery plans, to allow different +wallets to generate them, and different services to monitor/execute them. + +== Motivation == + +Pre-signed transactions are one way to create a recovery-plan, for use in case of seed loss or +inheritance. +The most common example is a single pre-signed transaction with an+ Status: Draft + Type: Specification + Assigned: 2026-02-05 + License: BSD-2-Clause + Discussion: https://groups.google.com/g/bitcoindev/c/K1NpJp9_BYk +
nLocktime set to a
+future date, as explained in [[bip-0065.mediawiki|BIP-65]].
+One limitation of this approach is that in the happy-flow scenario, when the seed is not lost,
+and the nLocktime is about to be reached, the user must access their wallet and spend
+one of its UTXOs - in order to revoke the pre-signed transaction and prevent it from being able to
+move the funds with no cancellation period.
+This could be frustrating, for example, for users that split their seed over multiple geographic
+locations.
+
+''Timelock-Recovery plans'' are a way to pre-sign a pair of transactions that eventually move the
+funds to one or more secondary wallets - with a special nSequence relative-locktime
+in the second transaction, so that the user always has a cancellation-period.
+
+Executing and monitoring a ''Timelock-Recovery plan'' thus requires more than broadcasting and
+monitoring a single transaction. It also requires mechanisms for accelerating the first
+transaction (which does not move most funds to the secondary wallet), for checking whether
+the relative-timelock has passed, and a more nuanced handling of reorgs.
+
+This BIP proposes a standard format for exporting ''Timelock-Recovery plans'' from the wallet that
+generated them, and importing them into apps/services for monitoring/execution.
+
+=== Comparison with Script-Based Wallets ===
+
+Script-based wallets are another way to create recovery mechanisms, and can use absolute and
+relative locktimes using OP_CHECKLOCKTIMEVERIFY ([[bip-0065.mediawiki|BIP-65]]) and
+OP_CHECKSEQUENCEVERIFY ([[bip-0112.mediawiki|BIP-112]]).
+For example, we can build a script that allows one main key to spend the funds at any time,
+and a secondary key to spend the funds only in transactions with nLocktime above a certain
+date/block-height, or only in transactions with nSequence above a certain relative
+time-gap/number-of-blocks.
+This makes the secondary key useful only after an absolute date/block-height, or after
+a relative time since the funds were received (each UTXO independently).
+This approach does have some advantages over pre-signed transactions, for example the
+recovery-mechanism automatically applies to new funds received into the wallet.
+
+However, script-based wallets have some disadvantages over a sequence of
+pre-signed transactions:
+
+* Script-based wallets are harder to implement correctly by hardware wallets, and harder to backup properly (i.e. users may forget to backup wallet-descriptors even for basic multisig wallets).
+* As of the time of writing, scripts can limit when secondary-keys can be used, but not how they can be used: if the user doesn't touch the wallets' UTXOs for long-enough time, the secondary key will eventually become useable and could move the funds anywhere. This is true whether we measure the time in absolute terms (OP_CHECKLOCKTIMEVERIFY) or relative terms compared to when the wallets' UTXOs were created (OP_CHECKSEQUENCEVERIFY). This means that even in the happy-flow scenario of an untouched wallet, where no recovery is needed, the user must periodically "renew" the recovery-mechanism by spending the UTXO to a new wallet/address. This may be inconvenient in ultra-cold-storage scenarios (i.e. multisig with main keys hidden in different geographic locations). New opcode suggestions, such as OP_CHECKTEMPLATEVERIFY ([[bip-0119.mediawiki|BIP-119]]) and OP_CHECKCONTRACTVERIFY ([[bip-0443.mediawiki|BIP-443]]), discuss possible recovery-mechanisms in which in order for a secondary key to have full control over the funds, some onchain operations must be performed, with a required time-gap between them - giving the user enough time to revoke the whole process and move the funds elsewhere (assuming they still have the main key and the recovery-mechanism was triggered unintentionally). However, these suggestions are still in the discussion phase and even if ever implemented, their adoption may be slow.
+* New Bitcoiners today typically don't think of such recovery-mechanisms in advance, and start with a P2WPKH wallet. They can pre-sign transactions with this wallet, but to utilize script-based features they would need to create a new wallet and move the funds there - an operation that might seem intimidating for large amounts.
+
+== Specification ==
+
+A ''Timelock-Recovery plan'' consists of two transactions:
+
+* ''Alert Transaction'': A mostly-consolidation transaction that keeps most funds in the original wallet, except for a fee and a small fixed amount that goes to ''anchor-addresses'' - addresses which can be used to accelerate the ''Alert Transaction'' via CPFP. The majority of funds should remain on the original wallet, in a new previously-unused address which we call the ''alert-address''. We use the term ''Alert Transaction'' because monitoring the blockchain and looking for it should alert the user that the recovery-plan has been initiated (intentionally, unintentionally or maliciously).
+* ''Recovery Transaction'': The transaction that moves the funds from the alert-address UTXO from the ''Alert Transaction'' to one or more addresses of secondary wallets (each may receive a different amount). This transaction should have a special nSequence relative-locktime according to the size of cancellation-period requested by the user, following the rules of [[bip-0068.mediawiki|BIP-68]].
+
+With a reliable tool to monitor the blockchain for the ''Alert Transaction''
+or the ''Alert Address'', the user can safely store online backups of the recovery plan's
+JSON file (or, even without a tool, by checking the blockchain manually from time to time).
+If the presigned transactions leak and the ''Alert Transaction'' is broadcast
+unintentionally, the user has the cancellation period (expected to be at least a
+few days) to prevent most funds from moving by sending them to a new address, thereby
+invalidating the ''Recovery Transaction''.
+
+It is important that the ''Alert Transaction'' will be non-malleable (e.g. by using
+[[bip-0140.mediawiki|BIP-140]]).
+If a malleable ''Alert Transaction'' is used, a malicious miner could replace the
+''Alert Transaction'' with a similar transaction with a different txid,
+making the ''Recovery Transaction'' invalid (pointing to a non-existent UTXO).
+
+The nLocktime of both transactions should not be higher than the current
+block height.
+
+The ''anchor-addresses'' mentioned above, which are used for CPFP acceleration, could possibly
+be P2A addresses (described in [[bip-0433.mediawiki|BIP-433]]), or other addresses under the
+participants' control (i.e. addresses from the secondary wallets).
+As of the time of writing, P2A is not widely adopted, and less-technical users may
+struggle using them for CPFP acceleration - so we currently recommend using regular addresses.
+
+=== nSequence calculation ===
+
+Users will specify the cancellation-period in whole days between 2-388.
+
+Following [[bip-0068.mediawiki|BIP-68]], the nSequence can represent a timespan in
+units of 512 seconds, when bit (1 << 22) is set. An example calculation is provided below:
+
+nSequence field bits
+allocated for the relative-locktime, and is not supported.
+
+=== JSON format ===
+
+For simplicity, this BIP proposes that a ''Timelock-Recovery plan'' will be saved as a JSON
+object.
+
+The JSON object will have the following fields:
+
+* kind (mandatory): must be "timelock-recovery-plan".
+* id (mandatory): a non-empty string of up to 100 characters, to represent the plan uniquely (i.e. a UUID, or a server generated ID).
+* name (optional): a name for the plan, decided by the user. A string of up to 200 characters.
+* description (optional): a description for the plan, decided by the user. A string of up to 10,000 characters.
+* created_at (mandatory): an ISO 8601 timestamp of the plan creation time, including timezone offset ('Z' if the timezone is UTC).
+* plugin_version (optional): The version of the plugin that generated the plan. A string of up to 100 characters.
+* wallet_version (mandatory): The version of the wallet that generated the plan. A string of up to 100 characters.
+* wallet_name (mandatory): The human-readable name of the wallet app that generated the plan. A string of up to 100 characters.
+* wallet_kind (mandatory): The internal name of the wallet app that generated the plan. A string of up to 100 characters.
+* timelock_days (mandatory): The cancellation period in whole days. A number between 2 and 388.
+* anchor_amount_sats (mandatory): The amount in satoshis sent to each anchor address in the Alert Transaction. We recommend using 600 sats, which is above the dust limit.
+* anchor_addresses (mandatory): An array of up to 10,000 Bitcoin addresses that receive the anchor amount in the Alert Transaction. Each address is a string of up to 100 characters.
+* alert_address (mandatory): The Bitcoin address that receives the majority of funds in the Alert Transaction. A string of up to 100 characters.
+* alert_inputs (mandatory): An array of up to 2439 inputs spent by the Alert Transaction. Each input is a string in the format "txid:vout" where txid is a 64-character lowercase hexadecimal string and vout is a decimal number of up to 6 digits. The maximal length of 2439 is calculated from a standard transaction of 400,000 wu where each input contains at least 41 bytes.
+* alert_tx (mandatory): The raw Alert Transaction in uppercase hexadecimal format. A string of up to 800,000 characters.
+* alert_txid (mandatory): The transaction ID of the Alert Transaction. A 64-character lowercase hexadecimal string.
+* alert_fee (mandatory): The total fee paid by the Alert Transaction in satoshis. A non-negative integer.
+* alert_weight (mandatory): The weight of the Alert Transaction. A positive integer, not higher than 400,000.
+* recovery_tx (mandatory): The raw Recovery Transaction in uppercase hexadecimal format. A string of up to 800,000 characters.
+* recovery_txid (mandatory): The transaction ID of the Recovery Transaction. A 64-character lowercase hexadecimal string.
+* recovery_fee (mandatory): The total fee paid by the Recovery Transaction in satoshis. A non-negative integer.
+* recovery_weight (mandatory): The weight of the Recovery Transaction. A positive integer, not higher than 400,000.
+* recovery_outputs (mandatory): An array of up to 10,000 outputs from the Recovery Transaction. Each output is a tuple containing: [address, amount_sats, label?] where:
+** address is a mandatory Bitcoin address string (up to 100 characters).
+** amount_sats is a mandatory positive integer representing the amount in satoshis.
+** label is an optional string of up to 200 characters.
+* metadata (optional): A string of up to 10,000 characters for additional metadata, for example a digital-signature.
+* checksum (mandatory): A checksum for verifying the integrity of the plan. A string of 8 to 64 characters.
+
+=== Checksum Calculation ===
+Notice that besides the top-level JSON object, all the internal values are either primitive or
+arrays.
+This is intentional, so a conversion of the values to JSON strings will be deterministic.
+
+The checksum is calculated by converting the top-level JSON object to an array of
+[key, value] pairs, sorting the array, stringifying, calculating the
+SHA256 hash of the result in lowercase hexadecimal format, and taking a prefix of at least 8
+characters.
+
+"stringifying" in this context means converting the array to a JSON string following
+[https://262.ecma-international.org/16.0/index.html#sec-json.stringify ECMAScript's JSON.stringify function specification]
+(without additional replacer or space arguments).
+This specification covers special cases such as non-ascii characters and newlines.
+
+Optional fields without a value should not be included in the key-value pairs array.
+Also, there should not be a key-value pair for the "checksum" field itself.
+
+Code example:
+wallet_kind, wallet_version and
+plugin_version fields.
+
+Servers may decide to put more restrictions on JSON objects, for example to refuse
+storing very large transactions.
+
+Notice that the raw transactions (alert_tx and recovery_tx) are expected
+to be in uppercase hexadecimal format.
+This is useful for frontend UIs to display them as QR codes, which are more compact when using
+uppercase-only alphanumeric characters.
+
+=== Monitoring Timelock-Recovery Plans ===
+
+Checking whether the Alert Transaction is valid is trivial, via the
+testmempoolaccept RPC call in bitcoin core 0.17+.
+
+However, checking whether the Recovery Transaction is valid is more complex,
+since it depends on a UTXO created by the Alert Transaction.
+
+The testmempoolaccept RPC can receive a list of transactions in which the later
+transactions may depend on earlier transactions - however in our case the
+Recovery Transaction has an nSequence relative-locktime, and therefore
+calling testmempoolaccept 'alert-tx' 'recovery-tx' will fail, claiming that the
+Alert Transaction UTXO is not confirmed (and the required time window has not passed).
+
+We recommend services that want to verify the entire Timelock-Recovery plan to parse
+the Recovery Transaction and check its signatures manually, and reject complicated
+spending scripts. Discovering that the Recovery Transaction is invalid only at the
+time of execution, could lead to funds being locked forever.
+
+== Reference Implementation ==
+
+JSON files can be generated using the Timelock Recovery plugin on
+[https://electrum.org Electrum Wallet]:
+
+https://github.com/spesmilo/electrum/tree/master/electrum/plugins/timelock_recovery
+
+Demo Video: https://drive.google.com/file/d/10uXRouQbH1kz_HC14WnmRnYHa3gPZY8l/preview
+
+Example JSON file:
+
+message_type is b'\x00', the following 12 bytes are interpreted as an ASCII message type (as in the v1 P2P protocol), trailing padded with b'\x00' as necessary. If the first byte of message_type is in the range ''1..255'', it is interpreted as a message type ID. This structure results in smaller messages than the v1 protocol, as most messages sent/received will have a message type ID. We recommend reserving 1-byte type IDs for message types that are sent more than once per direction per connection.'''How do the lengths between v1 and v2 compare?''' For messages that use the 1-byte short message type ID, v2 packets use 3 bytes less per message than v1.'''Why not allow variable length long message type IDs?''' Allowing for variable length long IDs reduces the available 1-byte ID space by 12 (to encode the length itself) and incentivizes less descriptive message types. In addition, limiting message types to fixed lengths of 1 or 13 hampers traffic analysis.
-The following table lists currently defined message type IDs:
+The value of message_length is '''length''' minus the size of the message_type.
+
+The following table lists currently defined message type IDs and the 12-byte ASCII message type (trimmed of trailing padding) that they are treated as equivalent to:
{| class="wikitable"
|-
@@ -541,35 +544,36 @@ The following table lists currently defined message type IDs:
!3
|-
!+0
-|(12 bytes follow)||ADDR||BLOCK||BLOCKTXN
+|(12 bytes follow)||addr||block||blocktxn
|-
!+4
-|CMPCTBLOCK||FEEFILTER||FILTERADD||FILTERCLEAR
+|cmpctblock||feefilter||filteradd||filterclear
|-
!+8
-|FILTERLOAD||GETBLOCKS||GETBLOCKTXN||GETDATA
+|filterload||getblocks||getblocktxn||getdata
|-
!+12
-|GETHEADERS||HEADERS||INV||MEMPOOL
+|getheaders||headers||inv||mempool
|-
!+16
-|MERKLEBLOCK||NOTFOUND||PING||PONG
+|merkleblock||notfound||ping||pong
|-
!+20
-|SENDCMPCT||TX||GETCFILTERS||CFILTER
+|sendcmpct||tx||getcfilters||cfilter
|-
!+24
-|GETCFHEADERS||CFHEADERS||GETCFCHECKPT||CFCHECKPT
+|getcfheaders||cfheaders||getcfcheckpt||cfcheckpt
|-
!+28
-|ADDRV2
+|addrv2
|-
!≥29
|| colspan="4" | (undefined)
|}
+When a message type has both a 1-byte encoding and a 13-byte encoding defined, peers that support receiving that message type should accept messages using either encoding (e.g., if the "getblocktxn" message type is supported, then both the 1-byte b'\x0a' encoding and the 13-byte b'\x00getblocktxn\x00' should be supported, and behavior should not depend on which of the two encodings is received).
-Additional message types may be added separately after BIP finalization.
+Additional message type IDs may be defined by other BIPs. They should be added to the [[bip-0324/message_type_ids.md|message type IDs table]] to ease coordination.
=== Signaling specification ===
==== Signaling v2 support ====
@@ -583,6 +587,15 @@ For development and testing purposes, we provide a collection of test vectors in
* [[bip-0324/xswiftec_inv_test_vectors.csv|XSwiftECInv vectors]] provide examples of ''(u, x)'' pairs, and the various ''t'' values that ''xswiftec_inv'' maps them to.
* [[bip-0324/packet_encoding_test_vectors.csv|Packet encoding vectors]] illustrate the lifecycle of the authenticated encryption scheme proposed in this document.
+== Changelog ==
+
+ * 1.0.2 (2026-01-30)
+ * Add message type ID table in auxiliary file
+ * 1.0.1 (2026-01-16)
+ * Specify equivalence of 1-byte and 13-byte `message_type`
+ * 1.0.0 (2024-07-10)
+ * Marked as Final
+
== Rationale and References ==
+[ + [ + "78a11a1260c1101260", + "78a11a1260", + "c1101260", + "#SCRIPT# CAT EQUAL", + "#CONTROLBLOCK#", + 0.00000001 + ], + "", + "0x51 0x20 #TAPROOTOUTPUT#", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "OK", + "TAPSCRIPT CATs 78a11a1260 and c1101260 together and checks it is EQUAL to stack element 78a11a1260c1101260" +], +[ + [ + "51", + "bbbb", + "01", + "#SCRIPT# IF CAT ELSE DROP ENDIF", + "#CONTROLBLOCK#", + 0.00000001 + ], + "", + "0x51 0x20 #TAPROOTOUTPUT#", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "OK", + "TAPSCRIPT Tests CAT inside of an IF ELSE conditional (true IF)" +], +[ + [ + "", + "09ca7009ca7009ca7009ca7009ca70", + "#SCRIPT# CAT", + "#CONTROLBLOCK#", + 0.00000001 + ], + "", + "0x51 0x20 #TAPROOTOUTPUT#", + "P2SH,WITNESS,TAPROOT,OP_CAT", + "OK", + "TAPSCRIPT (['', 09ca7009ca7009ca7009ca7009ca70], CAT) Tests CAT succeeds when one of the two values to concatenate is of size zero" +], ++ +A full test suite with additional vectors can be found at the Bitcoin [https://github.com/bitcoin/bitcoin/pull/29247 OP_CAT PR]. + ==References==
MAJOR version is incremented if changes to the BIP are introduc
The MINOR version is incremented whenever the inputs or the output of an algorithm changes in a backward-compatible way or new backward-compatible functionality is added.
The PATCH version is incremented for other changes that are noteworthy (bug fixes, test vectors, important clarifications, etc.).
+* '''1.1.0''' (2026-03-02):
+** Introduce per-group recipient limit ''Kmax'' to mitigate quadratic scanning behavior for adversarial transactions.
* '''1.0.2''' (2025-07-25):
** Clarify how to handle the improbable corner case where the output of SHA256 is equal to 0 or greater than or equal to the secp256k1 curve order.
* '''1.0.1''' (2024-06-22):
diff --git a/bip-0352/bitcoin_utils.py b/bip-0352/bitcoin_utils.py
index 9e279e455b..7e9bd9b265 100644
--- a/bip-0352/bitcoin_utils.py
+++ b/bip-0352/bitcoin_utils.py
@@ -2,7 +2,7 @@
import struct
from io import BytesIO
from ripemd160 import ripemd160
-from secp256k1 import ECKey
+from secp256k1lab.secp256k1 import Scalar
from typing import Union
@@ -93,7 +93,7 @@ def __init__(self, outpoint=None, scriptSig=b"", txinwitness=None, prevout=b"",
else:
self.txinwitness = txinwitness
if private_key is None:
- self.private_key = ECKey()
+ self.private_key = Scalar()
else:
self.private_key = private_key
self.scriptSig = scriptSig
diff --git a/bip-0352/reference.py b/bip-0352/reference.py
index c236c0dbd9..714050e466 100755
--- a/bip-0352/reference.py
+++ b/bip-0352/reference.py
@@ -2,15 +2,19 @@
# For running the test vectors, run this script:
# ./reference.py send_and_receive_test_vectors.json
-import hashlib
import json
+from pathlib import Path
+import sys
from typing import List, Tuple, Dict, cast
-from sys import argv, exit
-from functools import reduce
-# local files
+# import the vendored copy of secp256k1lab
+sys.path.insert(0, str(Path(__file__).parent / "secp256k1lab/src"))
+from secp256k1lab.bip340 import schnorr_sign, schnorr_verify
+from secp256k1lab.secp256k1 import G, GE, Scalar
+from secp256k1lab.util import tagged_hash, hash_sha256
+
+
from bech32m import convertbits, bech32_encode, decode, Encoding
-from secp256k1 import ECKey, ECPubKey, TaggedHash, NUMS_H
from bitcoin_utils import (
deser_txid,
from_hex,
@@ -26,7 +30,11 @@
)
-def get_pubkey_from_input(vin: VinInfo) -> ECPubKey:
+K_max = 2323 # per-group recipient limit
+NUMS_H = 0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0
+
+
+def get_pubkey_from_input(vin: VinInfo) -> GE:
if is_p2pkh(vin.prevout):
# skip the first 3 op_codes and grab the 20 byte hash
# from the scriptPubKey
@@ -44,20 +52,22 @@ def get_pubkey_from_input(vin: VinInfo) -> ECPubKey:
pubkey_bytes = vin.scriptSig[i - 33:i]
pubkey_hash = hash160(pubkey_bytes)
if pubkey_hash == spk_hash:
- pubkey = ECPubKey().set(pubkey_bytes)
- if (pubkey.valid) & (pubkey.compressed):
- return pubkey
+ try:
+ return GE.from_bytes_compressed(pubkey_bytes)
+ except ValueError:
+ pass
if is_p2sh(vin.prevout):
redeem_script = vin.scriptSig[1:]
if is_p2wpkh(redeem_script):
- pubkey = ECPubKey().set(vin.txinwitness.scriptWitness.stack[-1])
- if (pubkey.valid) & (pubkey.compressed):
- return pubkey
+ try:
+ return GE.from_bytes_compressed(vin.txinwitness.scriptWitness.stack[-1])
+ except (ValueError, AssertionError):
+ pass
if is_p2wpkh(vin.prevout):
- txin = vin.txinwitness
- pubkey = ECPubKey().set(txin.scriptWitness.stack[-1])
- if (pubkey.valid) & (pubkey.compressed):
- return pubkey
+ try:
+ return GE.from_bytes_compressed(vin.txinwitness.scriptWitness.stack[-1])
+ except (ValueError, AssertionError):
+ pass
if is_p2tr(vin.prevout):
witnessStack = vin.txinwitness.scriptWitness.stack
if (len(witnessStack) >= 1):
@@ -72,71 +82,69 @@ def get_pubkey_from_input(vin: VinInfo) -> ECPubKey:
internal_key = control_block[1:33]
if (internal_key == NUMS_H.to_bytes(32, 'big')):
# Skip if NUMS_H
- return ECPubKey()
-
- pubkey = ECPubKey().set(vin.prevout[2:])
- if (pubkey.valid) & (pubkey.compressed):
- return pubkey
+ return GE()
+ try:
+ return GE.from_bytes_xonly(vin.prevout[2:])
+ except ValueError:
+ pass
- return ECPubKey()
+ return GE()
-def get_input_hash(outpoints: List[COutPoint], sum_input_pubkeys: ECPubKey) -> bytes:
+def get_input_hash(outpoints: List[COutPoint], sum_input_pubkeys: GE) -> bytes:
lowest_outpoint = sorted(outpoints, key=lambda outpoint: outpoint.serialize())[0]
- return TaggedHash("BIP0352/Inputs", lowest_outpoint.serialize() + cast(bytes, sum_input_pubkeys.get_bytes(False)))
+ return tagged_hash("BIP0352/Inputs", lowest_outpoint.serialize() + sum_input_pubkeys.to_bytes_compressed())
-def encode_silent_payment_address(B_scan: ECPubKey, B_m: ECPubKey, hrp: str = "tsp", version: int = 0) -> str:
- data = convertbits(cast(bytes, B_scan.get_bytes(False)) + cast(bytes, B_m.get_bytes(False)), 8, 5)
+def encode_silent_payment_address(B_scan: GE, B_m: GE, hrp: str = "tsp", version: int = 0) -> str:
+ data = convertbits(B_scan.to_bytes_compressed() + B_m.to_bytes_compressed(), 8, 5)
return bech32_encode(hrp, [version] + cast(List[int], data), Encoding.BECH32M)
-def generate_label(b_scan: ECKey, m: int) -> bytes:
- return TaggedHash("BIP0352/Label", b_scan.get_bytes() + ser_uint32(m))
+def generate_label(b_scan: Scalar, m: int) -> Scalar:
+ return Scalar.from_bytes_checked(tagged_hash("BIP0352/Label", b_scan.to_bytes() + ser_uint32(m)))
-def create_labeled_silent_payment_address(b_scan: ECKey, B_spend: ECPubKey, m: int, hrp: str = "tsp", version: int = 0) -> str:
- G = ECKey().set(1).get_pubkey()
- B_scan = b_scan.get_pubkey()
+def create_labeled_silent_payment_address(b_scan: Scalar, B_spend: GE, m: int, hrp: str = "tsp", version: int = 0) -> str:
+ B_scan = b_scan * G
B_m = B_spend + generate_label(b_scan, m) * G
labeled_address = encode_silent_payment_address(B_scan, B_m, hrp, version)
return labeled_address
-def decode_silent_payment_address(address: str, hrp: str = "tsp") -> Tuple[ECPubKey, ECPubKey]:
+def decode_silent_payment_address(address: str, hrp: str = "tsp") -> Tuple[GE, GE]:
_, data = decode(hrp, address)
if data is None:
- return ECPubKey(), ECPubKey()
- B_scan = ECPubKey().set(data[:33])
- B_spend = ECPubKey().set(data[33:])
+ return GE(), GE()
+ B_scan = GE.from_bytes_compressed(data[:33])
+ B_spend = GE.from_bytes_compressed(data[33:])
return B_scan, B_spend
-def create_outputs(input_priv_keys: List[Tuple[ECKey, bool]], outpoints: List[COutPoint], recipients: List[str], expected: Dict[str, any] = None, hrp="tsp") -> List[str]:
- G = ECKey().set(1).get_pubkey()
+def create_outputs(input_priv_keys: List[Tuple[Scalar, bool]], outpoints: List[COutPoint], recipients: List[str], expected: Dict[str, any] = None, hrp="tsp") -> List[str]:
negated_keys = []
for key, is_xonly in input_priv_keys:
- k = ECKey().set(key.get_bytes())
- if is_xonly and k.get_pubkey().get_y() % 2 != 0:
- k.negate()
+ k = Scalar.from_bytes_checked(key.to_bytes())
+ if is_xonly and not (k * G).has_even_y():
+ k = -k
negated_keys.append(k)
- a_sum = sum(negated_keys)
- if not a_sum.valid:
+ a_sum = Scalar.sum(*negated_keys)
+ if a_sum == 0:
# Input privkeys sum is zero -> fail
return []
- assert ECKey().set(bytes.fromhex(expected.get("input_private_key_sum"))) == a_sum, "a_sum did not match expected input_private_key_sum"
- input_hash = get_input_hash(outpoints, a_sum * G)
- silent_payment_groups: Dict[ECPubKey, List[ECPubKey]] = {}
+ assert Scalar.from_bytes_checked(bytes.fromhex(expected.get("input_private_key_sum"))) == a_sum, "a_sum did not match expected input_private_key_sum"
+ input_hash_scalar = Scalar.from_bytes_checked(get_input_hash(outpoints, a_sum * G))
+ silent_payment_groups: Dict[GE, List[GE]] = {}
for recipient in recipients:
B_scan, B_m = decode_silent_payment_address(recipient["address"], hrp=hrp)
# Verify decoded intermediate keys for recipient
- expected_B_scan = ECPubKey().set(bytes.fromhex(recipient["scan_pub_key"]))
- expected_B_m = ECPubKey().set(bytes.fromhex(recipient["spend_pub_key"]))
+ expected_B_scan = GE.from_bytes_compressed(bytes.fromhex(recipient["scan_pub_key"]))
+ expected_B_m = GE.from_bytes_compressed(bytes.fromhex(recipient["spend_pub_key"]))
assert expected_B_scan == B_scan, "B_scan did not match expected recipient.scan_pub_key"
assert expected_B_m == B_m, "B_m did not match expected recipient.spend_pub_key"
if B_scan in silent_payment_groups:
@@ -144,68 +152,73 @@ def create_outputs(input_priv_keys: List[Tuple[ECKey, bool]], outpoints: List[CO
else:
silent_payment_groups[B_scan] = [B_m]
+ # Fail if per-group recipient limit (K_max) is exceeded
+ if any([len(group) > K_max for group in silent_payment_groups.values()]):
+ return []
+
outputs = []
for B_scan, B_m_values in silent_payment_groups.items():
- ecdh_shared_secret = input_hash * a_sum * B_scan
+ ecdh_shared_secret = input_hash_scalar * a_sum * B_scan
expected_shared_secrets = expected.get("shared_secrets", {})
# Find the recipient address that corresponds to this B_scan and get its index
for recipient_idx, recipient in enumerate(recipients):
- recipient_B_scan = ECPubKey().set(bytes.fromhex(recipient["scan_pub_key"]))
+ recipient_B_scan = GE.from_bytes_compressed(bytes.fromhex(recipient["scan_pub_key"]))
if recipient_B_scan == B_scan:
expected_shared_secret_hex = expected_shared_secrets[recipient_idx]
- assert ecdh_shared_secret.get_bytes(False).hex() == expected_shared_secret_hex, f"ecdh_shared_secret did not match expected, recipient {recipient_idx} ({recipient['address']}): expected={expected_shared_secret_hex}"
+ assert ecdh_shared_secret.to_bytes_compressed().hex() == expected_shared_secret_hex, f"ecdh_shared_secret did not match expected, recipient {recipient_idx} ({recipient['address']}): expected={expected_shared_secret_hex}"
break
k = 0
for B_m in B_m_values:
- t_k = TaggedHash("BIP0352/SharedSecret", ecdh_shared_secret.get_bytes(False) + ser_uint32(k))
+ t_k = Scalar.from_bytes_checked(tagged_hash("BIP0352/SharedSecret", ecdh_shared_secret.to_bytes_compressed() + ser_uint32(k)))
P_km = B_m + t_k * G
- outputs.append(P_km.get_bytes().hex())
+ outputs.append(P_km.to_bytes_xonly().hex())
k += 1
return list(set(outputs))
-def scanning(b_scan: ECKey, B_spend: ECPubKey, A_sum: ECPubKey, input_hash: bytes, outputs_to_check: List[ECPubKey], labels: Dict[str, str] = None, expected: Dict[str, any] = None) -> List[Dict[str, str]]:
- G = ECKey().set(1).get_pubkey()
- input_hash_key = ECKey().set(input_hash)
- computed_tweak_point = input_hash_key * A_sum
- assert computed_tweak_point.get_bytes(False).hex() == expected.get("tweak"), "tweak did not match expected"
- ecdh_shared_secret = input_hash * b_scan * A_sum
- assert ecdh_shared_secret.get_bytes(False).hex() == expected.get("shared_secret"), "ecdh_shared_secret did not match expected shared_secret"
+def scanning(b_scan: Scalar, B_spend: GE, A_sum: GE, input_hash: bytes, outputs_to_check: List[bytes], labels: Dict[str, str] = None, expected: Dict[str, any] = None) -> List[Dict[str, str]]:
+ input_hash_scalar = Scalar.from_bytes_checked(input_hash)
+ computed_tweak_point = input_hash_scalar * A_sum
+ assert computed_tweak_point.to_bytes_compressed().hex() == expected.get("tweak"), "tweak did not match expected"
+ ecdh_shared_secret = input_hash_scalar * b_scan * A_sum
+ assert ecdh_shared_secret.to_bytes_compressed().hex() == expected.get("shared_secret"), "ecdh_shared_secret did not match expected shared_secret"
k = 0
wallet = []
while True:
- t_k = TaggedHash("BIP0352/SharedSecret", ecdh_shared_secret.get_bytes(False) + ser_uint32(k))
+ if k == K_max: # Don't look further than the per-group recipient limit (K_max)
+ break
+ t_k = Scalar.from_bytes_checked(tagged_hash("BIP0352/SharedSecret", ecdh_shared_secret.to_bytes_compressed() + ser_uint32(k)))
P_k = B_spend + t_k * G
for output in outputs_to_check:
- if P_k == output:
- wallet.append({"pub_key": P_k.get_bytes().hex(), "priv_key_tweak": t_k.hex()})
+ output_ge = GE.from_bytes_xonly(output)
+ if P_k.to_bytes_xonly() == output:
+ wallet.append({"pub_key": P_k.to_bytes_xonly().hex(), "priv_key_tweak": t_k.to_bytes().hex()})
outputs_to_check.remove(output)
k += 1
break
elif labels:
- m_G_sub = output - P_k
- if m_G_sub.get_bytes(False).hex() in labels:
+ m_G_sub = output_ge - P_k
+ if m_G_sub.to_bytes_compressed().hex() in labels:
P_km = P_k + m_G_sub
wallet.append({
- "pub_key": P_km.get_bytes().hex(),
- "priv_key_tweak": (ECKey().set(t_k).add(
- bytes.fromhex(labels[m_G_sub.get_bytes(False).hex()])
- )).get_bytes().hex(),
+ "pub_key": P_km.to_bytes_xonly().hex(),
+ "priv_key_tweak": (t_k + Scalar.from_bytes_checked(
+ bytes.fromhex(labels[m_G_sub.to_bytes_compressed().hex()])
+ )).to_bytes().hex(),
})
outputs_to_check.remove(output)
k += 1
break
else:
- output.negate()
- m_G_sub = output - P_k
- if m_G_sub.get_bytes(False).hex() in labels:
+ m_G_sub = -output_ge - P_k
+ if m_G_sub.to_bytes_compressed().hex() in labels:
P_km = P_k + m_G_sub
wallet.append({
- "pub_key": P_km.get_bytes().hex(),
- "priv_key_tweak": (ECKey().set(t_k).add(
- bytes.fromhex(labels[m_G_sub.get_bytes(False).hex()])
- )).get_bytes().hex(),
+ "pub_key": P_km.to_bytes_xonly().hex(),
+ "priv_key_tweak": (t_k + Scalar.from_bytes_checked(
+ bytes.fromhex(labels[m_G_sub.to_bytes_compressed().hex()])
+ )).to_bytes().hex(),
})
outputs_to_check.remove(output)
k += 1
@@ -216,15 +229,13 @@ def scanning(b_scan: ECKey, B_spend: ECPubKey, A_sum: ECPubKey, input_hash: byte
if __name__ == "__main__":
- if len(argv) != 2 or argv[1] in ('-h', '--help'):
+ if len(sys.argv) != 2 or sys.argv[1] in ('-h', '--help'):
print("Usage: ./reference.py send_and_receive_test_vectors.json")
- exit(0)
+ sys.exit(0)
- with open(argv[1], "r") as f:
+ with open(sys.argv[1], "r") as f:
test_data = json.loads(f.read())
- # G , needed for generating the labels "database"
- G = ECKey().set(1).get_pubkey()
for case in test_data:
print(case["comment"])
# Test sending
@@ -238,7 +249,7 @@ def scanning(b_scan: ECKey, B_spend: ECPubKey, A_sum: ECPubKey, input_hash: byte
scriptSig=bytes.fromhex(input["scriptSig"]),
txinwitness=CTxInWitness().deserialize(from_hex(input["txinwitness"])),
prevout=bytes.fromhex(input["prevout"]["scriptPubKey"]["hex"]),
- private_key=ECKey().set(bytes.fromhex(input["private_key"])),
+ private_key=Scalar.from_bytes_checked(bytes.fromhex(input["private_key"])),
)
for input in given["vin"]
]
@@ -247,19 +258,23 @@ def scanning(b_scan: ECKey, B_spend: ECPubKey, A_sum: ECPubKey, input_hash: byte
input_pub_keys = []
for vin in vins:
pubkey = get_pubkey_from_input(vin)
- if not pubkey.valid:
+ if pubkey.infinity:
continue
input_priv_keys.append((
vin.private_key,
is_p2tr(vin.prevout),
))
input_pub_keys.append(pubkey)
- assert [pk.get_bytes(False).hex() for pk in input_pub_keys] == expected.get("input_pub_keys"), "input_pub_keys did not match expected"
+ assert [pk.to_bytes_compressed().hex() for pk in input_pub_keys] == expected.get("input_pub_keys"), "input_pub_keys did not match expected"
sending_outputs = []
if (len(input_pub_keys) > 0):
outpoints = [vin.outpoint for vin in vins]
- sending_outputs = create_outputs(input_priv_keys, outpoints, given["recipients"], expected=expected, hrp="sp")
+ recipients = [] # expand given recipient entries to full list
+ for recipient_entry in given["recipients"]:
+ count = recipient_entry.get("count", 1)
+ recipients.extend([recipient_entry] * count)
+ sending_outputs = create_outputs(input_priv_keys, outpoints, recipients, expected=expected, hrp="sp")
# Note: order doesn't matter for creating/finding the outputs. However, different orderings of the recipient addresses
# will produce different generated outputs if sending to multiple silent payment addresses belonging to the
@@ -270,13 +285,13 @@ def scanning(b_scan: ECKey, B_spend: ECPubKey, A_sum: ECPubKey, input_hash: byte
assert(sending_outputs == expected["outputs"][0] == []), "Sending test failed"
# Test receiving
- msg = hashlib.sha256(b"message").digest()
- aux = hashlib.sha256(b"random auxiliary data").digest()
+ msg = hash_sha256(b"message")
+ aux = hash_sha256(b"random auxiliary data")
for receiving_test in case["receiving"]:
given = receiving_test["given"]
expected = receiving_test["expected"]
outputs_to_check = [
- ECPubKey().set(bytes.fromhex(p)) for p in given["outputs"]
+ bytes.fromhex(p) for p in given["outputs"]
]
vins = [
VinInfo(
@@ -289,12 +304,10 @@ def scanning(b_scan: ECKey, B_spend: ECPubKey, A_sum: ECPubKey, input_hash: byte
]
# Check that the given inputs for the receiving test match what was generated during the sending test
receiving_addresses = []
- b_scan = ECKey().set(bytes.fromhex(given["key_material"]["scan_priv_key"]))
- b_spend = ECKey().set(
- bytes.fromhex(given["key_material"]["spend_priv_key"])
- )
- B_scan = b_scan.get_pubkey()
- B_spend = b_spend.get_pubkey()
+ b_scan = Scalar.from_bytes_checked(bytes.fromhex(given["key_material"]["scan_priv_key"]))
+ b_spend = Scalar.from_bytes_checked(bytes.fromhex(given["key_material"]["spend_priv_key"]))
+ B_scan = b_scan * G
+ B_spend = b_spend * G
receiving_addresses.append(
encode_silent_payment_address(B_scan, B_spend, hrp="sp")
)
@@ -311,21 +324,21 @@ def scanning(b_scan: ECKey, B_spend: ECPubKey, A_sum: ECPubKey, input_hash: byte
input_pub_keys = []
for vin in vins:
pubkey = get_pubkey_from_input(vin)
- if not pubkey.valid:
+ if pubkey.infinity:
continue
input_pub_keys.append(pubkey)
add_to_wallet = []
if (len(input_pub_keys) > 0):
- A_sum = reduce(lambda x, y: x + y, input_pub_keys)
- if A_sum.get_bytes() is None:
+ A_sum = GE.sum(*input_pub_keys)
+ if A_sum.infinity:
# Input pubkeys sum is point at infinity -> skip tx
assert expected["outputs"] == []
continue
- assert A_sum.get_bytes(False).hex() == expected.get("input_pub_key_sum"), "A_sum did not match expected input_pub_key_sum"
+ assert A_sum.to_bytes_compressed().hex() == expected.get("input_pub_key_sum"), "A_sum did not match expected input_pub_key_sum"
input_hash = get_input_hash([vin.outpoint for vin in vins], A_sum)
pre_computed_labels = {
- (generate_label(b_scan, label) * G).get_bytes(False).hex(): generate_label(b_scan, label).hex()
+ (generate_label(b_scan, label) * G).to_bytes_compressed().hex(): generate_label(b_scan, label).to_bytes().hex()
for label in given["labels"]
}
add_to_wallet = scanning(
@@ -340,13 +353,13 @@ def scanning(b_scan: ECKey, B_spend: ECPubKey, A_sum: ECPubKey, input_hash: byte
# Check that the private key is correct for the found output public key
for output in add_to_wallet:
- pub_key = ECPubKey().set(bytes.fromhex(output["pub_key"]))
- full_private_key = b_spend.add(bytes.fromhex(output["priv_key_tweak"]))
- if full_private_key.get_pubkey().get_y() % 2 != 0:
- full_private_key.negate()
+ pub_key = GE.from_bytes_xonly(bytes.fromhex(output["pub_key"]))
+ full_private_key = b_spend + Scalar.from_bytes_checked(bytes.fromhex(output["priv_key_tweak"]))
+ if not (full_private_key * G).has_even_y():
+ full_private_key = -full_private_key
- sig = full_private_key.sign_schnorr(msg, aux)
- assert pub_key.verify_schnorr(sig, msg), f"Invalid signature for {pub_key}"
+ sig = schnorr_sign(msg, full_private_key.to_bytes(), aux)
+ assert schnorr_verify(msg, pub_key.to_bytes_xonly(), sig), f"Invalid signature for {pub_key}"
output["signature"] = sig.hex()
# Note: order doesn't matter for creating/finding the outputs. However, different orderings of the recipient addresses
@@ -354,9 +367,14 @@ def scanning(b_scan: ECKey, B_spend: ECPubKey, A_sum: ECPubKey, input_hash: byte
# same sender but with different labels. Because of this, expected["outputs"] contains all possible valid output sets,
# based on all possible permutations of recipient address orderings. Must match exactly one of the possible found output
# sets in expected["outputs"]
- generated_set = {frozenset(d.items()) for d in add_to_wallet}
- expected_set = {frozenset(d.items()) for d in expected["outputs"]}
- assert generated_set == expected_set, "Receive test failed"
+ if "outputs" in expected: # detailed check against expected outputs
+ generated_set = {frozenset(d.items()) for d in add_to_wallet}
+ expected_set = {frozenset(d.items()) for d in expected["outputs"]}
+ assert generated_set == expected_set, "Receive test failed"
+ elif "n_outputs" in expected: # only check the number of found outputs
+ assert len(add_to_wallet) == expected["n_outputs"], "Receive test failed"
+ else:
+ assert False, "either 'outputs' or 'n_outputs' must be specified in 'expected' field of receiving test vector"
print("All tests passed")
diff --git a/bip-0352/secp256k1.py b/bip-0352/secp256k1.py
deleted file mode 100644
index 0ccbc4e6a4..0000000000
--- a/bip-0352/secp256k1.py
+++ /dev/null
@@ -1,696 +0,0 @@
-# Copyright (c) 2019 Pieter Wuille
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-"""Test-only secp256k1 elliptic curve implementation
-
-WARNING: This code is slow, uses bad randomness, does not properly protect
-keys, and is trivially vulnerable to side channel attacks. Do not use for
-anything but tests."""
-import random
-import hashlib
-import hmac
-
-def TaggedHash(tag, data):
- ss = hashlib.sha256(tag.encode('utf-8')).digest()
- ss += ss
- ss += data
- return hashlib.sha256(ss).digest()
-
-def modinv(a, n):
- """Compute the modular inverse of a modulo n
-
- See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers.
- """
- t1, t2 = 0, 1
- r1, r2 = n, a
- while r2 != 0:
- q = r1 // r2
- t1, t2 = t2, t1 - q * t2
- r1, r2 = r2, r1 - q * r2
- if r1 > 1:
- return None
- if t1 < 0:
- t1 += n
- return t1
-
-def jacobi_symbol(n, k):
- """Compute the Jacobi symbol of n modulo k
-
- See http://en.wikipedia.org/wiki/Jacobi_symbol
-
- For our application k is always prime, so this is the same as the Legendre symbol."""
- assert k > 0 and k & 1, "jacobi symbol is only defined for positive odd k"
- n %= k
- t = 0
- while n != 0:
- while n & 1 == 0:
- n >>= 1
- r = k & 7
- t ^= (r == 3 or r == 5)
- n, k = k, n
- t ^= (n & k & 3 == 3)
- n = n % k
- if k == 1:
- return -1 if t else 1
- return 0
-
-def modsqrt(a, p):
- """Compute the square root of a modulo p when p % 4 = 3.
-
- The Tonelli-Shanks algorithm can be used. See https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm
-
- Limiting this function to only work for p % 4 = 3 means we don't need to
- iterate through the loop. The highest n such that p - 1 = 2^n Q with Q odd
- is n = 1. Therefore Q = (p-1)/2 and sqrt = a^((Q+1)/2) = a^((p+1)/4)
-
- secp256k1's is defined over field of size 2**256 - 2**32 - 977, which is 3 mod 4.
- """
- if p % 4 != 3:
- raise NotImplementedError("modsqrt only implemented for p % 4 = 3")
- sqrt = pow(a, (p + 1)//4, p)
- if pow(sqrt, 2, p) == a % p:
- return sqrt
- return None
-
-def int_or_bytes(s):
- "Convert 32-bytes to int while accepting also int and returning it as is."
- if isinstance(s, bytes):
- assert(len(s) == 32)
- s = int.from_bytes(s, 'big')
- elif not isinstance(s, int):
- raise TypeError
- return s
-
-class EllipticCurve:
- def __init__(self, p, a, b):
- """Initialize elliptic curve y^2 = x^3 + a*x + b over GF(p)."""
- self.p = p
- self.a = a % p
- self.b = b % p
-
- def affine(self, p1):
- """Convert a Jacobian point tuple p1 to affine form, or None if at infinity.
-
- An affine point is represented as the Jacobian (x, y, 1)"""
- x1, y1, z1 = p1
- if z1 == 0:
- return None
- inv = modinv(z1, self.p)
- inv_2 = (inv**2) % self.p
- inv_3 = (inv_2 * inv) % self.p
- return ((inv_2 * x1) % self.p, (inv_3 * y1) % self.p, 1)
-
- def has_even_y(self, p1):
- """Whether the point p1 has an even Y coordinate when expressed in affine coordinates."""
- return not (p1[2] == 0 or self.affine(p1)[1] & 1)
-
- def negate(self, p1):
- """Negate a Jacobian point tuple p1."""
- x1, y1, z1 = p1
- return (x1, (self.p - y1) % self.p, z1)
-
- def on_curve(self, p1):
- """Determine whether a Jacobian tuple p is on the curve (and not infinity)"""
- x1, y1, z1 = p1
- z2 = pow(z1, 2, self.p)
- z4 = pow(z2, 2, self.p)
- return z1 != 0 and (pow(x1, 3, self.p) + self.a * x1 * z4 + self.b * z2 * z4 - pow(y1, 2, self.p)) % self.p == 0
-
- def is_x_coord(self, x):
- """Test whether x is a valid X coordinate on the curve."""
- x_3 = pow(x, 3, self.p)
- return jacobi_symbol(x_3 + self.a * x + self.b, self.p) != -1
-
- def lift_x(self, x):
- """Given an X coordinate on the curve, return a corresponding affine point."""
- x_3 = pow(x, 3, self.p)
- v = x_3 + self.a * x + self.b
- y = modsqrt(v, self.p)
- if y is None:
- return None
- return (x, y, 1)
-
- def double(self, p1):
- """Double a Jacobian tuple p1
-
- See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Doubling"""
- x1, y1, z1 = p1
- if z1 == 0:
- return (0, 1, 0)
- y1_2 = (y1**2) % self.p
- y1_4 = (y1_2**2) % self.p
- x1_2 = (x1**2) % self.p
- s = (4*x1*y1_2) % self.p
- m = 3*x1_2
- if self.a:
- m += self.a * pow(z1, 4, self.p)
- m = m % self.p
- x2 = (m**2 - 2*s) % self.p
- y2 = (m*(s - x2) - 8*y1_4) % self.p
- z2 = (2*y1*z1) % self.p
- return (x2, y2, z2)
-
- def add_mixed(self, p1, p2):
- """Add a Jacobian tuple p1 and an affine tuple p2
-
- See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition (with affine point)"""
- x1, y1, z1 = p1
- x2, y2, z2 = p2
- assert(z2 == 1)
- # Adding to the point at infinity is a no-op
- if z1 == 0:
- return p2
- z1_2 = (z1**2) % self.p
- z1_3 = (z1_2 * z1) % self.p
- u2 = (x2 * z1_2) % self.p
- s2 = (y2 * z1_3) % self.p
- if x1 == u2:
- if (y1 != s2):
- # p1 and p2 are inverses. Return the point at infinity.
- return (0, 1, 0)
- # p1 == p2. The formulas below fail when the two points are equal.
- return self.double(p1)
- h = u2 - x1
- r = s2 - y1
- h_2 = (h**2) % self.p
- h_3 = (h_2 * h) % self.p
- u1_h_2 = (x1 * h_2) % self.p
- x3 = (r**2 - h_3 - 2*u1_h_2) % self.p
- y3 = (r*(u1_h_2 - x3) - y1*h_3) % self.p
- z3 = (h*z1) % self.p
- return (x3, y3, z3)
-
- def add(self, p1, p2):
- """Add two Jacobian tuples p1 and p2
-
- See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition"""
- x1, y1, z1 = p1
- x2, y2, z2 = p2
- # Adding the point at infinity is a no-op
- if z1 == 0:
- return p2
- if z2 == 0:
- return p1
- # Adding an Affine to a Jacobian is more efficient since we save field multiplications and squarings when z = 1
- if z1 == 1:
- return self.add_mixed(p2, p1)
- if z2 == 1:
- return self.add_mixed(p1, p2)
- z1_2 = (z1**2) % self.p
- z1_3 = (z1_2 * z1) % self.p
- z2_2 = (z2**2) % self.p
- z2_3 = (z2_2 * z2) % self.p
- u1 = (x1 * z2_2) % self.p
- u2 = (x2 * z1_2) % self.p
- s1 = (y1 * z2_3) % self.p
- s2 = (y2 * z1_3) % self.p
- if u1 == u2:
- if (s1 != s2):
- # p1 and p2 are inverses. Return the point at infinity.
- return (0, 1, 0)
- # p1 == p2. The formulas below fail when the two points are equal.
- return self.double(p1)
- h = u2 - u1
- r = s2 - s1
- h_2 = (h**2) % self.p
- h_3 = (h_2 * h) % self.p
- u1_h_2 = (u1 * h_2) % self.p
- x3 = (r**2 - h_3 - 2*u1_h_2) % self.p
- y3 = (r*(u1_h_2 - x3) - s1*h_3) % self.p
- z3 = (h*z1*z2) % self.p
- return (x3, y3, z3)
-
- def mul(self, ps):
- """Compute a (multi) point multiplication
-
- ps is a list of (Jacobian tuple, scalar) pairs.
- """
- r = (0, 1, 0)
- for i in range(255, -1, -1):
- r = self.double(r)
- for (p, n) in ps:
- if ((n >> i) & 1):
- r = self.add(r, p)
- return r
-
-SECP256K1_FIELD_SIZE = 2**256 - 2**32 - 977
-SECP256K1 = EllipticCurve(SECP256K1_FIELD_SIZE, 0, 7)
-SECP256K1_G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, 1)
-SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
-SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2
-NUMS_H = 0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0
-
-class ECPubKey():
- """A secp256k1 public key"""
-
- def __init__(self):
- """Construct an uninitialized public key"""
- self.valid = False
-
- def __repr__(self):
- return self.get_bytes().hex()
-
- def __eq__(self, other):
- assert isinstance(other, ECPubKey)
- return self.get_bytes() == other.get_bytes()
-
- def __hash__(self):
- return hash(self.get_bytes())
-
- def set(self, data):
- """Construct a public key from a serialization in compressed or uncompressed DER format or BIP340 format"""
- if (len(data) == 65 and data[0] == 0x04):
- p = (int.from_bytes(data[1:33], 'big'), int.from_bytes(data[33:65], 'big'), 1)
- self.valid = SECP256K1.on_curve(p)
- if self.valid:
- self.p = p
- self.compressed = False
- elif (len(data) == 33 and (data[0] == 0x02 or data[0] == 0x03)):
- x = int.from_bytes(data[1:33], 'big')
- if SECP256K1.is_x_coord(x):
- p = SECP256K1.lift_x(x)
- # if the oddness of the y co-ord isn't correct, find the other
- # valid y
- if (p[1] & 1) != (data[0] & 1):
- p = SECP256K1.negate(p)
- self.p = p
- self.valid = True
- self.compressed = True
- else:
- self.valid = False
- elif (len(data) == 32):
- x = int.from_bytes(data[0:32], 'big')
- if SECP256K1.is_x_coord(x):
- p = SECP256K1.lift_x(x)
- # if the oddness of the y co-ord isn't correct, find the other
- # valid y
- if p[1]%2 != 0:
- p = SECP256K1.negate(p)
- self.p = p
- self.valid = True
- self.compressed = True
- else:
- self.valid = False
- else:
- self.valid = False
- return self
-
- @property
- def is_compressed(self):
- return self.compressed
-
- @property
- def is_valid(self):
- return self.valid
-
- def get_y(self):
- return SECP256K1.affine(self.p)[1]
-
- def get_x(self):
- return SECP256K1.affine(self.p)[0]
-
- def get_bytes(self, bip340=True):
- assert(self.valid)
- p = SECP256K1.affine(self.p)
- if p is None:
- return None
- if bip340:
- return bytes(p[0].to_bytes(32, 'big'))
- elif self.compressed:
- return bytes([0x02 + (p[1] & 1)]) + p[0].to_bytes(32, 'big')
- else:
- return bytes([0x04]) + p[0].to_bytes(32, 'big') + p[1].to_bytes(32, 'big')
-
- def verify_ecdsa(self, sig, msg, low_s=True):
- """Verify a strictly DER-encoded ECDSA signature against this pubkey.
-
- See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the
- ECDSA verifier algorithm"""
- assert(self.valid)
-
- # Extract r and s from the DER formatted signature. Return false for
- # any DER encoding errors.
- if (sig[1] + 2 != len(sig)):
- return False
- if (len(sig) < 4):
- return False
- if (sig[0] != 0x30):
- return False
- if (sig[2] != 0x02):
- return False
- rlen = sig[3]
- if (len(sig) < 6 + rlen):
- return False
- if rlen < 1 or rlen > 33:
- return False
- if sig[4] >= 0x80:
- return False
- if (rlen > 1 and (sig[4] == 0) and not (sig[5] & 0x80)):
- return False
- r = int.from_bytes(sig[4:4+rlen], 'big')
- if (sig[4+rlen] != 0x02):
- return False
- slen = sig[5+rlen]
- if slen < 1 or slen > 33:
- return False
- if (len(sig) != 6 + rlen + slen):
- return False
- if sig[6+rlen] >= 0x80:
- return False
- if (slen > 1 and (sig[6+rlen] == 0) and not (sig[7+rlen] & 0x80)):
- return False
- s = int.from_bytes(sig[6+rlen:6+rlen+slen], 'big')
-
- # Verify that r and s are within the group order
- if r < 1 or s < 1 or r >= SECP256K1_ORDER or s >= SECP256K1_ORDER:
- return False
- if low_s and s >= SECP256K1_ORDER_HALF:
- return False
- z = int.from_bytes(msg, 'big')
-
- # Run verifier algorithm on r, s
- w = modinv(s, SECP256K1_ORDER)
- u1 = z*w % SECP256K1_ORDER
- u2 = r*w % SECP256K1_ORDER
- R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, u1), (self.p, u2)]))
- if R is None or R[0] != r:
- return False
- return True
-
- def verify_schnorr(self, sig, msg):
- assert(len(msg) == 32)
- assert(len(sig) == 64)
- assert(self.valid)
- r = int.from_bytes(sig[0:32], 'big')
- if r >= SECP256K1_FIELD_SIZE:
- return False
- s = int.from_bytes(sig[32:64], 'big')
- if s >= SECP256K1_ORDER:
- return False
- e = int.from_bytes(TaggedHash("BIP0340/challenge", sig[0:32] + self.get_bytes() + msg), 'big') % SECP256K1_ORDER
- R = SECP256K1.mul([(SECP256K1_G, s), (self.p, SECP256K1_ORDER - e)])
- if not SECP256K1.has_even_y(R):
- return False
- if ((r * R[2] * R[2]) % SECP256K1_FIELD_SIZE) != R[0]:
- return False
- return True
-
- def __add__(self, other):
- """Adds two ECPubKey points."""
- assert isinstance(other, ECPubKey)
- assert self.valid
- assert other.valid
- ret = ECPubKey()
- ret.p = SECP256K1.add(other.p, self.p)
- ret.valid = True
- ret.compressed = self.compressed
- return ret
-
- def __radd__(self, other):
- """Allows this ECPubKey to be added to 0 for sum()"""
- if other == 0:
- return self
- else:
- return self + other
-
- def __mul__(self, other):
- """Multiplies ECPubKey point with a scalar(int/32bytes/ECKey)."""
- if isinstance(other, ECKey):
- assert self.valid
- assert other.secret is not None
- multiplier = other.secret
- else:
- # int_or_bytes checks that other is `int` or `bytes`
- multiplier = int_or_bytes(other)
-
- assert multiplier < SECP256K1_ORDER
- multiplier = multiplier % SECP256K1_ORDER
- ret = ECPubKey()
- ret.p = SECP256K1.mul([(self.p, multiplier)])
- ret.valid = True
- ret.compressed = self.compressed
- return ret
-
- def __rmul__(self, other):
- """Multiplies a scalar(int/32bytes/ECKey) with an ECPubKey point"""
- return self * other
-
- def __sub__(self, other):
- """Subtract one point from another"""
- assert isinstance(other, ECPubKey)
- assert self.valid
- assert other.valid
- ret = ECPubKey()
- ret.p = SECP256K1.add(self.p, SECP256K1.negate(other.p))
- ret.valid = True
- ret.compressed = self.compressed
- return ret
-
- def tweak_add(self, tweak):
- assert(self.valid)
- t = int_or_bytes(tweak)
- if t >= SECP256K1_ORDER:
- return None
- tweaked = SECP256K1.affine(SECP256K1.mul([(self.p, 1), (SECP256K1_G, t)]))
- if tweaked is None:
- return None
- ret = ECPubKey()
- ret.p = tweaked
- ret.valid = True
- ret.compressed = self.compressed
- return ret
-
- def mul(self, data):
- """Multiplies ECPubKey point with scalar data."""
- assert self.valid
- other = ECKey()
- other.set(data, True)
- return self * other
-
- def negate(self):
- self.p = SECP256K1.affine(SECP256K1.negate(self.p))
-
-def rfc6979_nonce(key):
- """Compute signing nonce using RFC6979."""
- v = bytes([1] * 32)
- k = bytes([0] * 32)
- k = hmac.new(k, v + b"\x00" + key, 'sha256').digest()
- v = hmac.new(k, v, 'sha256').digest()
- k = hmac.new(k, v + b"\x01" + key, 'sha256').digest()
- v = hmac.new(k, v, 'sha256').digest()
- return hmac.new(k, v, 'sha256').digest()
-
-class ECKey():
- """A secp256k1 private key"""
-
- def __init__(self):
- self.valid = False
-
- def __repr__(self):
- return str(self.secret)
-
- def __eq__(self, other):
- assert isinstance(other, ECKey)
- return self.secret == other.secret
-
- def __hash__(self):
- return hash(self.secret)
-
- def set(self, secret, compressed=True):
- """Construct a private key object from either 32-bytes or an int secret and a compressed flag."""
- secret = int_or_bytes(secret)
-
- self.valid = (secret > 0 and secret < SECP256K1_ORDER)
- if self.valid:
- self.secret = secret
- self.compressed = compressed
- return self
-
- def generate(self, compressed=True):
- """Generate a random private key (compressed or uncompressed)."""
- self.set(random.randrange(1, SECP256K1_ORDER).to_bytes(32, 'big'), compressed)
- return self
-
- def get_bytes(self):
- """Retrieve the 32-byte representation of this key."""
- assert(self.valid)
- return self.secret.to_bytes(32, 'big')
-
- def as_int(self):
- return self.secret
-
- def from_int(self, secret, compressed=True):
- self.valid = (secret > 0 and secret < SECP256K1_ORDER)
- if self.valid:
- self.secret = secret
- self.compressed = compressed
-
- def __add__(self, other):
- """Add key secrets. Returns compressed key."""
- assert isinstance(other, ECKey)
- assert other.secret > 0 and other.secret < SECP256K1_ORDER
- assert self.valid is True
- ret_data = ((self.secret + other.secret) % SECP256K1_ORDER).to_bytes(32, 'big')
- ret = ECKey()
- ret.set(ret_data, True)
- return ret
-
- def __radd__(self, other):
- """Allows this ECKey to be added to 0 for sum()"""
- if other == 0:
- return self
- else:
- return self + other
-
- def __sub__(self, other):
- """Subtract key secrets. Returns compressed key."""
- assert isinstance(other, ECKey)
- assert other.secret > 0 and other.secret < SECP256K1_ORDER
- assert self.valid is True
- ret_data = ((self.secret - other.secret) % SECP256K1_ORDER).to_bytes(32, 'big')
- ret = ECKey()
- ret.set(ret_data, True)
- return ret
-
- def __mul__(self, other):
- """Multiply a private key by another private key or multiply a public key by a private key. Returns compressed key."""
- if isinstance(other, ECKey):
- assert other.secret > 0 and other.secret < SECP256K1_ORDER
- assert self.valid is True
- ret_data = ((self.secret * other.secret) % SECP256K1_ORDER).to_bytes(32, 'big')
- ret = ECKey()
- ret.set(ret_data, True)
- return ret
- elif isinstance(other, ECPubKey):
- return other * self
- else:
- # ECKey().set() checks that other is an `int` or `bytes`
- assert self.valid
- second = ECKey().set(other, self.compressed)
- return self * second
-
- def __rmul__(self, other):
- return self * other
-
- def add(self, data):
- """Add key to scalar data. Returns compressed key."""
- other = ECKey()
- other.set(data, True)
- return self + other
-
- def mul(self, data):
- """Multiply key secret with scalar data. Returns compressed key."""
- other = ECKey()
- other.set(data, True)
- return self * other
-
- def negate(self):
- """Negate a private key."""
- assert self.valid
- self.secret = SECP256K1_ORDER - self.secret
-
- @property
- def is_valid(self):
- return self.valid
-
- @property
- def is_compressed(self):
- return self.compressed
-
- def get_pubkey(self):
- """Compute an ECPubKey object for this secret key."""
- assert(self.valid)
- ret = ECPubKey()
- p = SECP256K1.mul([(SECP256K1_G, self.secret)])
- ret.p = p
- ret.valid = True
- ret.compressed = self.compressed
- return ret
-
- def sign_ecdsa(self, msg, low_s=True, rfc6979=False):
- """Construct a DER-encoded ECDSA signature with this key.
-
- See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the
- ECDSA signer algorithm."""
- assert(self.valid)
- z = int.from_bytes(msg, 'big')
- # Note: no RFC6979 by default, but a simple random nonce (some tests rely on distinct transactions for the same operation)
- if rfc6979:
- k = int.from_bytes(rfc6979_nonce(self.secret.to_bytes(32, 'big') + msg), 'big')
- else:
- k = random.randrange(1, SECP256K1_ORDER)
- R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, k)]))
- r = R[0] % SECP256K1_ORDER
- s = (modinv(k, SECP256K1_ORDER) * (z + self.secret * r)) % SECP256K1_ORDER
- if low_s and s > SECP256K1_ORDER_HALF:
- s = SECP256K1_ORDER - s
- # Represent in DER format. The byte representations of r and s have
- # length rounded up (255 bits becomes 32 bytes and 256 bits becomes 33
- # bytes).
- rb = r.to_bytes((r.bit_length() + 8) // 8, 'big')
- sb = s.to_bytes((s.bit_length() + 8) // 8, 'big')
- return b'\x30' + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + rb + bytes([2, len(sb)]) + sb
-
- def sign_schnorr(self, msg, aux=None):
- """Create a Schnorr signature (see BIP340)."""
- if aux is None:
- aux = bytes(32)
-
- assert self.valid
- assert len(msg) == 32
- assert len(aux) == 32
-
- t = (self.secret ^ int.from_bytes(TaggedHash("BIP0340/aux", aux), 'big')).to_bytes(32, 'big')
- kp = int.from_bytes(TaggedHash("BIP0340/nonce", t + self.get_pubkey().get_bytes() + msg), 'big') % SECP256K1_ORDER
- assert kp != 0
- R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, kp)]))
- k = kp if SECP256K1.has_even_y(R) else SECP256K1_ORDER - kp
- e = int.from_bytes(TaggedHash("BIP0340/challenge", R[0].to_bytes(32, 'big') + self.get_pubkey().get_bytes() + msg), 'big') % SECP256K1_ORDER
- return R[0].to_bytes(32, 'big') + ((k + e * self.secret) % SECP256K1_ORDER).to_bytes(32, 'big')
-
- def tweak_add(self, tweak):
- """Return a tweaked version of this private key."""
- assert(self.valid)
- t = int_or_bytes(tweak)
- if t >= SECP256K1_ORDER:
- return None
- tweaked = (self.secret + t) % SECP256K1_ORDER
- if tweaked == 0:
- return None
- ret = ECKey()
- ret.set(tweaked.to_bytes(32, 'big'), self.compressed)
- return ret
-
-def generate_key_pair(secret=None, compressed=True):
- """Convenience function to generate a private-public key pair."""
- d = ECKey()
- if secret:
- d.set(secret, compressed)
- else:
- d.generate(compressed)
-
- P = d.get_pubkey()
- return d, P
-
-def generate_bip340_key_pair():
- """Convenience function to generate a BIP0340 private-public key pair."""
- d = ECKey()
- d.generate()
- P = d.get_pubkey()
- if P.get_y()%2 != 0:
- d.negate()
- P.negate()
- return d, P
-
-def generate_schnorr_nonce():
- """Generate a random valid BIP340 nonce.
-
- See https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki.
- This implementation ensures the y-coordinate of the nonce point is even."""
- kp = random.randrange(1, SECP256K1_ORDER)
- assert kp != 0
- R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, kp)]))
- k = kp if R[1] % 2 == 0 else SECP256K1_ORDER - kp
- k_key = ECKey()
- k_key.set(k.to_bytes(32, 'big'), True)
- return k_key
diff --git a/bip-0352/secp256k1lab/.github/workflows/main.yml b/bip-0352/secp256k1lab/.github/workflows/main.yml
new file mode 100644
index 0000000000..4950b96550
--- /dev/null
+++ b/bip-0352/secp256k1lab/.github/workflows/main.yml
@@ -0,0 +1,17 @@
+name: Tests
+on: [push, pull_request]
+jobs:
+ ruff:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install the latest version of uv
+ uses: astral-sh/setup-uv@v5
+ - run: uvx ruff check .
+ mypy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install the latest version of uv
+ uses: astral-sh/setup-uv@v5
+ - run: uvx mypy .
diff --git a/bip-0352/secp256k1lab/.python-version b/bip-0352/secp256k1lab/.python-version
new file mode 100644
index 0000000000..bd28b9c5c2
--- /dev/null
+++ b/bip-0352/secp256k1lab/.python-version
@@ -0,0 +1 @@
+3.9
diff --git a/bip-0352/secp256k1lab/CHANGELOG.md b/bip-0352/secp256k1lab/CHANGELOG.md
new file mode 100644
index 0000000000..15779717c4
--- /dev/null
+++ b/bip-0352/secp256k1lab/CHANGELOG.md
@@ -0,0 +1,10 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [1.0.0] - 2025-03-31
+
+Initial release.
diff --git a/bip-0352/secp256k1lab/COPYING b/bip-0352/secp256k1lab/COPYING
new file mode 100644
index 0000000000..e8f2163641
--- /dev/null
+++ b/bip-0352/secp256k1lab/COPYING
@@ -0,0 +1,23 @@
+The MIT License (MIT)
+
+Copyright (c) 2009-2024 The Bitcoin Core developers
+Copyright (c) 2009-2024 Bitcoin Developers
+Copyright (c) 2025- The secp256k1lab Developers
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/bip-0352/secp256k1lab/README.md b/bip-0352/secp256k1lab/README.md
new file mode 100644
index 0000000000..dbc9dbd04c
--- /dev/null
+++ b/bip-0352/secp256k1lab/README.md
@@ -0,0 +1,13 @@
+secp256k1lab
+============
+
+
+
+An INSECURE implementation of the secp256k1 elliptic curve and related cryptographic schemes written in Python, intended for prototyping, experimentation and education.
+
+Features:
+* Low-level secp256k1 field and group arithmetic.
+* Schnorr signing/verification and key generation according to [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
+* ECDH key exchange.
+
+WARNING: The code in this library is slow and trivially vulnerable to side channel attacks.
diff --git a/bip-0352/secp256k1lab/pyproject.toml b/bip-0352/secp256k1lab/pyproject.toml
new file mode 100644
index 0000000000..a0bdd19f42
--- /dev/null
+++ b/bip-0352/secp256k1lab/pyproject.toml
@@ -0,0 +1,34 @@
+[project]
+name = "secp256k1lab"
+version = "1.0.0"
+description = "An INSECURE implementation of the secp256k1 elliptic curve and related cryptographic schemes, intended for prototyping, experimentation and education"
+readme = "README.md"
+authors = [
+ { name = "Pieter Wuille", email = "pieter@wuille.net" },
+ { name = "Tim Ruffing", email = "me@real-or-random.org" },
+ { name = "Jonas Nick", email = "jonasd.nick@gmail.com" },
+ { name = "Sebastian Falbesoner", email = "sebastian.falbesoner@gmail.com" }
+]
+maintainers = [
+ { name = "Tim Ruffing", email = "me@real-or-random.org" },
+ { name = "Jonas Nick", email = "jonasd.nick@gmail.com" },
+ { name = "Sebastian Falbesoner", email = "sebastian.falbesoner@gmail.com" }
+]
+requires-python = ">=3.9"
+license = "MIT"
+license-files = ["COPYING"]
+keywords = ["secp256k1", "elliptic curves", "cryptography", "Bitcoin"]
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Developers",
+ "Intended Audience :: Education",
+ "Intended Audience :: Science/Research",
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: Python",
+ "Topic :: Security :: Cryptography",
+]
+dependencies = []
+
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
diff --git a/bip-0352/secp256k1lab/src/secp256k1lab/__init__.py b/bip-0352/secp256k1lab/src/secp256k1lab/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/bip-0352/secp256k1lab/src/secp256k1lab/bip340.py b/bip-0352/secp256k1lab/src/secp256k1lab/bip340.py
new file mode 100644
index 0000000000..ba839d16e1
--- /dev/null
+++ b/bip-0352/secp256k1lab/src/secp256k1lab/bip340.py
@@ -0,0 +1,73 @@
+# The following functions are based on the BIP 340 reference implementation:
+# https://github.com/bitcoin/bips/blob/master/bip-0340/reference.py
+
+from .secp256k1 import FE, GE, G
+from .util import int_from_bytes, bytes_from_int, xor_bytes, tagged_hash
+
+
+def pubkey_gen(seckey: bytes) -> bytes:
+ d0 = int_from_bytes(seckey)
+ if not (1 <= d0 <= GE.ORDER - 1):
+ raise ValueError("The secret key must be an integer in the range 1..n-1.")
+ P = d0 * G
+ assert not P.infinity
+ return P.to_bytes_xonly()
+
+
+def schnorr_sign(
+ msg: bytes, seckey: bytes, aux_rand: bytes, tag_prefix: str = "BIP0340"
+) -> bytes:
+ d0 = int_from_bytes(seckey)
+ if not (1 <= d0 <= GE.ORDER - 1):
+ raise ValueError("The secret key must be an integer in the range 1..n-1.")
+ if len(aux_rand) != 32:
+ raise ValueError("aux_rand must be 32 bytes instead of %i." % len(aux_rand))
+ P = d0 * G
+ assert not P.infinity
+ d = d0 if P.has_even_y() else GE.ORDER - d0
+ t = xor_bytes(bytes_from_int(d), tagged_hash(tag_prefix + "/aux", aux_rand))
+ k0 = (
+ int_from_bytes(tagged_hash(tag_prefix + "/nonce", t + P.to_bytes_xonly() + msg))
+ % GE.ORDER
+ )
+ if k0 == 0:
+ raise RuntimeError("Failure. This happens only with negligible probability.")
+ R = k0 * G
+ assert not R.infinity
+ k = k0 if R.has_even_y() else GE.ORDER - k0
+ e = (
+ int_from_bytes(
+ tagged_hash(
+ tag_prefix + "/challenge", R.to_bytes_xonly() + P.to_bytes_xonly() + msg
+ )
+ )
+ % GE.ORDER
+ )
+ sig = R.to_bytes_xonly() + bytes_from_int((k + e * d) % GE.ORDER)
+ assert schnorr_verify(msg, P.to_bytes_xonly(), sig, tag_prefix=tag_prefix)
+ return sig
+
+
+def schnorr_verify(
+ msg: bytes, pubkey: bytes, sig: bytes, tag_prefix: str = "BIP0340"
+) -> bool:
+ if len(pubkey) != 32:
+ raise ValueError("The public key must be a 32-byte array.")
+ if len(sig) != 64:
+ raise ValueError("The signature must be a 64-byte array.")
+ try:
+ P = GE.from_bytes_xonly(pubkey)
+ except ValueError:
+ return False
+ r = int_from_bytes(sig[0:32])
+ s = int_from_bytes(sig[32:64])
+ if (r >= FE.SIZE) or (s >= GE.ORDER):
+ return False
+ e = (
+ int_from_bytes(tagged_hash(tag_prefix + "/challenge", sig[0:32] + pubkey + msg))
+ % GE.ORDER
+ )
+ R = s * G - e * P
+ if R.infinity or (not R.has_even_y()) or (R.x != r):
+ return False
+ return True
diff --git a/bip-0352/secp256k1lab/src/secp256k1lab/ecdh.py b/bip-0352/secp256k1lab/src/secp256k1lab/ecdh.py
new file mode 100644
index 0000000000..73f47fa1a7
--- /dev/null
+++ b/bip-0352/secp256k1lab/src/secp256k1lab/ecdh.py
@@ -0,0 +1,16 @@
+import hashlib
+
+from .secp256k1 import GE, Scalar
+
+
+def ecdh_compressed_in_raw_out(seckey: bytes, pubkey: bytes) -> GE:
+ """TODO"""
+ shared_secret = Scalar.from_bytes_checked(seckey) * GE.from_bytes_compressed(pubkey)
+ assert not shared_secret.infinity # prime-order group
+ return shared_secret
+
+
+def ecdh_libsecp256k1(seckey: bytes, pubkey: bytes) -> bytes:
+ """TODO"""
+ shared_secret = ecdh_compressed_in_raw_out(seckey, pubkey)
+ return hashlib.sha256(shared_secret.to_bytes_compressed()).digest()
diff --git a/bip-0352/secp256k1lab/src/secp256k1lab/keys.py b/bip-0352/secp256k1lab/src/secp256k1lab/keys.py
new file mode 100644
index 0000000000..3e28897e99
--- /dev/null
+++ b/bip-0352/secp256k1lab/src/secp256k1lab/keys.py
@@ -0,0 +1,15 @@
+from .secp256k1 import GE, G
+from .util import int_from_bytes
+
+# The following function is based on the BIP 327 reference implementation
+# https://github.com/bitcoin/bips/blob/master/bip-0327/reference.py
+
+
+# Return the plain public key corresponding to a given secret key
+def pubkey_gen_plain(seckey: bytes) -> bytes:
+ d0 = int_from_bytes(seckey)
+ if not (1 <= d0 <= GE.ORDER - 1):
+ raise ValueError("The secret key must be an integer in the range 1..n-1.")
+ P = d0 * G
+ assert not P.infinity
+ return P.to_bytes_compressed()
diff --git a/bip-0352/secp256k1lab/src/secp256k1lab/py.typed b/bip-0352/secp256k1lab/src/secp256k1lab/py.typed
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/bip-0374/secp256k1.py b/bip-0352/secp256k1lab/src/secp256k1lab/secp256k1.py
old mode 100755
new mode 100644
similarity index 55%
rename from bip-0374/secp256k1.py
rename to bip-0352/secp256k1lab/src/secp256k1lab/secp256k1.py
index b83d028f92..6e262bf51e
--- a/bip-0374/secp256k1.py
+++ b/bip-0352/secp256k1lab/src/secp256k1lab/secp256k1.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python3
-
# Copyright (c) 2022-2023 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -17,31 +15,29 @@
* G: the secp256k1 generator point
"""
-import unittest
-from hashlib import sha256
-
-class FE:
- """Objects of this class represent elements of the field GF(2**256 - 2**32 - 977).
+# TODO Docstrings of methods still say "field element"
+class APrimeFE:
+ """Objects of this class represent elements of a prime field.
They are represented internally in numerator / denominator form, in order to delay inversions.
"""
# The size of the field (also its modulus and characteristic).
- SIZE = 2**256 - 2**32 - 977
+ SIZE: int
def __init__(self, a=0, b=1):
"""Initialize a field element a/b; both a and b can be ints or field elements."""
- if isinstance(a, FE):
+ if isinstance(a, type(self)):
num = a._num
den = a._den
else:
- num = a % FE.SIZE
+ num = a % self.SIZE
den = 1
- if isinstance(b, FE):
- den = (den * b._num) % FE.SIZE
- num = (num * b._den) % FE.SIZE
+ if isinstance(b, type(self)):
+ den = (den * b._num) % self.SIZE
+ num = (num * b._den) % self.SIZE
else:
- den = (den * b) % FE.SIZE
+ den = (den * b) % self.SIZE
assert den != 0
if num == 0:
den = 1
@@ -50,71 +46,74 @@ def __init__(self, a=0, b=1):
def __add__(self, a):
"""Compute the sum of two field elements (second may be int)."""
- if isinstance(a, FE):
- return FE(self._num * a._den + self._den * a._num, self._den * a._den)
- return FE(self._num + self._den * a, self._den)
+ if isinstance(a, type(self)):
+ return type(self)(self._num * a._den + self._den * a._num, self._den * a._den)
+ if isinstance(a, int):
+ return type(self)(self._num + self._den * a, self._den)
+ return NotImplemented
def __radd__(self, a):
"""Compute the sum of an integer and a field element."""
- return FE(a) + self
+ return type(self)(a) + self
+
+ @classmethod
+ # REVIEW This should be
+ # def sum(cls, *es: Iterable[Self]) -> Self:
+ # but Self needs the typing_extension package on Python <= 3.12.
+ def sum(cls, *es):
+ """Compute the sum of field elements.
+
+ sum(a, b, c, ...) is identical to (0 + a + b + c + ...)."""
+ return sum(es, start=cls(0))
def __sub__(self, a):
"""Compute the difference of two field elements (second may be int)."""
- if isinstance(a, FE):
- return FE(self._num * a._den - self._den * a._num, self._den * a._den)
- return FE(self._num - self._den * a, self._den)
+ if isinstance(a, type(self)):
+ return type(self)(self._num * a._den - self._den * a._num, self._den * a._den)
+ if isinstance(a, int):
+ return type(self)(self._num - self._den * a, self._den)
+ return NotImplemented
def __rsub__(self, a):
"""Compute the difference of an integer and a field element."""
- return FE(a) - self
+ return type(self)(a) - self
def __mul__(self, a):
"""Compute the product of two field elements (second may be int)."""
- if isinstance(a, FE):
- return FE(self._num * a._num, self._den * a._den)
- return FE(self._num * a, self._den)
+ if isinstance(a, type(self)):
+ return type(self)(self._num * a._num, self._den * a._den)
+ if isinstance(a, int):
+ return type(self)(self._num * a, self._den)
+ return NotImplemented
def __rmul__(self, a):
"""Compute the product of an integer with a field element."""
- return FE(a) * self
+ return type(self)(a) * self
def __truediv__(self, a):
"""Compute the ratio of two field elements (second may be int)."""
- return FE(self, a)
+ if isinstance(a, type(self)) or isinstance(a, int):
+ return type(self)(self, a)
+ return NotImplemented
def __pow__(self, a):
"""Raise a field element to an integer power."""
- return FE(pow(self._num, a, FE.SIZE), pow(self._den, a, FE.SIZE))
+ return type(self)(pow(self._num, a, self.SIZE), pow(self._den, a, self.SIZE))
def __neg__(self):
"""Negate a field element."""
- return FE(-self._num, self._den)
+ return type(self)(-self._num, self._den)
def __int__(self):
- """Convert a field element to an integer in range 0..p-1. The result is cached."""
+ """Convert a field element to an integer in range 0..SIZE-1. The result is cached."""
if self._den != 1:
- self._num = (self._num * pow(self._den, -1, FE.SIZE)) % FE.SIZE
+ self._num = (self._num * pow(self._den, -1, self.SIZE)) % self.SIZE
self._den = 1
return self._num
def sqrt(self):
- """Compute the square root of a field element if it exists (None otherwise).
-
- Due to the fact that our modulus is of the form (p % 4) == 3, the Tonelli-Shanks
- algorithm (https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm) is simply
- raising the argument to the power (p + 1) / 4.
-
- To see why: (p-1) % 2 = 0, so 2 divides the order of the multiplicative group,
- and thus only half of the non-zero field elements are squares. An element a is
- a (nonzero) square when Euler's criterion, a^((p-1)/2) = 1 (mod p), holds. We're
- looking for x such that x^2 = a (mod p). Given a^((p-1)/2) = 1, that is equivalent
- to x^2 = a^(1 + (p-1)/2) mod p. As (1 + (p-1)/2) is even, this is equivalent to
- x = a^((1 + (p-1)/2)/2) mod p, or x = a^((p+1)/4) mod p."""
- v = int(self)
- s = pow(v, (FE.SIZE + 1) // 4, FE.SIZE)
- if s**2 % FE.SIZE == v:
- return FE(s)
- return None
+ """Compute the square root of a field element if it exists (None otherwise)."""
+ raise NotImplementedError
def is_square(self):
"""Determine if this field element has a square root."""
@@ -122,26 +121,42 @@ def is_square(self):
return self.sqrt() is not None
def is_even(self):
- """Determine whether this field element, represented as integer in 0..p-1, is even."""
+ """Determine whether this field element, represented as integer in 0..SIZE-1, is even."""
return int(self) & 1 == 0
def __eq__(self, a):
"""Check whether two field elements are equal (second may be an int)."""
- if isinstance(a, FE):
- return (self._num * a._den - self._den * a._num) % FE.SIZE == 0
- return (self._num - self._den * a) % FE.SIZE == 0
+ if isinstance(a, type(self)):
+ return (self._num * a._den - self._den * a._num) % self.SIZE == 0
+ return (self._num - self._den * a) % self.SIZE == 0
def to_bytes(self):
"""Convert a field element to a 32-byte array (BE byte order)."""
return int(self).to_bytes(32, 'big')
- @staticmethod
- def from_bytes(b):
+ @classmethod
+ def from_int_checked(cls, v):
+ """Convert an integer to a field element (no overflow allowed)."""
+ if v >= cls.SIZE:
+ raise ValueError
+ return cls(v)
+
+ @classmethod
+ def from_int_wrapping(cls, v):
+ """Convert an integer to a field element (reduced modulo SIZE)."""
+ return cls(v % cls.SIZE)
+
+ @classmethod
+ def from_bytes_checked(cls, b):
"""Convert a 32-byte array to a field element (BE byte order, no overflow allowed)."""
v = int.from_bytes(b, 'big')
- if v >= FE.SIZE:
- return None
- return FE(v)
+ return cls.from_int_checked(v)
+
+ @classmethod
+ def from_bytes_wrapping(cls, b):
+ """Convert a 32-byte array to a field element (BE byte order, reduced modulo SIZE)."""
+ v = int.from_bytes(b, 'big')
+ return cls.from_int_wrapping(v)
def __str__(self):
"""Convert this field element to a 64 character hex string."""
@@ -149,12 +164,40 @@ def __str__(self):
def __repr__(self):
"""Get a string representation of this field element."""
- return f"FE(0x{int(self):x})"
+ return f"{type(self).__qualname__}(0x{int(self):x})"
+
+
+class FE(APrimeFE):
+ SIZE = 2**256 - 2**32 - 977
+
+ def sqrt(self):
+ # Due to the fact that our modulus p is of the form (p % 4) == 3, the Tonelli-Shanks
+ # algorithm (https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm) is simply
+ # raising the argument to the power (p + 1) / 4.
+
+ # To see why: (p-1) % 2 = 0, so 2 divides the order of the multiplicative group,
+ # and thus only half of the non-zero field elements are squares. An element a is
+ # a (nonzero) square when Euler's criterion, a^((p-1)/2) = 1 (mod p), holds. We're
+ # looking for x such that x^2 = a (mod p). Given a^((p-1)/2) = 1, that is equivalent
+ # to x^2 = a^(1 + (p-1)/2) mod p. As (1 + (p-1)/2) is even, this is equivalent to
+ # x = a^((1 + (p-1)/2)/2) mod p, or x = a^((p+1)/4) mod p.
+ v = int(self)
+ s = pow(v, (self.SIZE + 1) // 4, self.SIZE)
+ if s**2 % self.SIZE == v:
+ return type(self)(s)
+ return None
+
+
+class Scalar(APrimeFE):
+ """TODO Docstring"""
+ SIZE = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
class GE:
"""Objects of this class represent secp256k1 group elements (curve points or infinity)
+ GE objects are immutable.
+
Normal points on the curve have fields:
* x: the x coordinate (a field element)
* y: the y coordinate (a field element, satisfying y^2 = x^3 + 7)
@@ -164,26 +207,47 @@ class GE:
* infinity: True
"""
+ # TODO The following two class attributes should probably be just getters as
+ # classmethods to enforce immutability. Unfortunately Python makes it hard
+ # to create "classproperties". `G` could then also be just a classmethod.
+
# Order of the group (number of points on the curve, plus 1 for infinity)
- ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
+ ORDER = Scalar.SIZE
# Number of valid distinct x coordinates on the curve.
ORDER_HALF = ORDER // 2
+ @property
+ def infinity(self):
+ """Whether the group element is the point at infinity."""
+ return self._infinity
+
+ @property
+ def x(self):
+ """The x coordinate (a field element) of a non-infinite group element."""
+ assert not self.infinity
+ return self._x
+
+ @property
+ def y(self):
+ """The y coordinate (a field element) of a non-infinite group element."""
+ assert not self.infinity
+ return self._y
+
def __init__(self, x=None, y=None):
"""Initialize a group element with specified x and y coordinates, or infinity."""
if x is None:
# Initialize as infinity.
assert y is None
- self.infinity = True
+ self._infinity = True
else:
# Initialize as point on the curve (and check that it is).
fx = FE(x)
fy = FE(y)
assert fy**2 == fx**3 + 7
- self.infinity = False
- self.x = fx
- self.y = fy
+ self._infinity = False
+ self._x = fx
+ self._y = fy
def __add__(self, a):
"""Add two group elements together."""
@@ -209,13 +273,20 @@ def __add__(self, a):
return GE(x, y)
@staticmethod
- def mul(*aps):
+ def sum(*ps):
+ """Compute the sum of group elements.
+
+ GE.sum(a, b, c, ...) is identical to (GE() + a + b + c + ...)."""
+ return sum(ps, start=GE())
+
+ @staticmethod
+ def batch_mul(*aps):
"""Compute a (batch) scalar group element multiplication.
- GE.mul((a1, p1), (a2, p2), (a3, p3)) is identical to a1*p1 + a2*p2 + a3*p3,
+ GE.batch_mul((a1, p1), (a2, p2), (a3, p3)) is identical to a1*p1 + a2*p2 + a3*p3,
but more efficient."""
# Reduce all the scalars modulo order first (so we can deal with negatives etc).
- naps = [(a % GE.ORDER, p) for a, p in aps]
+ naps = [(int(a), p) for a, p in aps]
# Start with point at infinity.
r = GE()
# Iterate over all bit positions, from high to low.
@@ -231,8 +302,8 @@ def mul(*aps):
def __rmul__(self, a):
"""Multiply an integer with a group element."""
if self == G:
- return FAST_G.mul(a)
- return GE.mul((a, self))
+ return FAST_G.mul(Scalar(a))
+ return GE.batch_mul((Scalar(a), self))
def __neg__(self):
"""Compute the negation of a group element."""
@@ -244,11 +315,26 @@ def __sub__(self, a):
"""Subtract a group element from another."""
return self + (-a)
+ def __eq__(self, a):
+ """Check if two group elements are equal."""
+ return (self - a).infinity
+
+ def has_even_y(self):
+ """Determine whether a non-infinity group element has an even y coordinate."""
+ assert not self.infinity
+ return self.y.is_even()
+
def to_bytes_compressed(self):
"""Convert a non-infinite group element to 33-byte compressed encoding."""
assert not self.infinity
return bytes([3 - self.y.is_even()]) + self.x.to_bytes()
+ def to_bytes_compressed_with_infinity(self):
+ """Convert a group element to 33-byte compressed encoding, mapping infinity to zeros."""
+ if self.infinity:
+ return 33 * b"\x00"
+ return self.to_bytes_compressed()
+
def to_bytes_uncompressed(self):
"""Convert a non-infinite group element to 65-byte uncompressed encoding."""
assert not self.infinity
@@ -264,44 +350,51 @@ def lift_x(x):
"""Return group element with specified field element as x coordinate (and even y)."""
y = (FE(x)**3 + 7).sqrt()
if y is None:
- return None
+ raise ValueError
if not y.is_even():
y = -y
return GE(x, y)
+ @staticmethod
+ def from_bytes_compressed(b):
+ """Convert a compressed to a group element."""
+ assert len(b) == 33
+ if b[0] != 2 and b[0] != 3:
+ raise ValueError
+ x = FE.from_bytes_checked(b[1:])
+ r = GE.lift_x(x)
+ if b[0] == 3:
+ r = -r
+ return r
+
+ @staticmethod
+ def from_bytes_uncompressed(b):
+ """Convert an uncompressed to a group element."""
+ assert len(b) == 65
+ if b[0] != 4:
+ raise ValueError
+ x = FE.from_bytes_checked(b[1:33])
+ y = FE.from_bytes_checked(b[33:])
+ if y**2 != x**3 + 7:
+ raise ValueError
+ return GE(x, y)
+
@staticmethod
def from_bytes(b):
"""Convert a compressed or uncompressed encoding to a group element."""
assert len(b) in (33, 65)
if len(b) == 33:
- if b[0] != 2 and b[0] != 3:
- return None
- x = FE.from_bytes(b[1:])
- if x is None:
- return None
- r = GE.lift_x(x)
- if r is None:
- return None
- if b[0] == 3:
- r = -r
- return r
+ return GE.from_bytes_compressed(b)
else:
- if b[0] != 4:
- return None
- x = FE.from_bytes(b[1:33])
- y = FE.from_bytes(b[33:])
- if y**2 != x**3 + 7:
- return None
- return GE(x, y)
+ return GE.from_bytes_uncompressed(b)
@staticmethod
def from_bytes_xonly(b):
"""Convert a point given in xonly encoding to a group element."""
assert len(b) == 32
- x = FE.from_bytes(b)
- if x is None:
- return None
- return GE.lift_x(x)
+ x = FE.from_bytes_checked(b)
+ r = GE.lift_x(x)
+ return r
@staticmethod
def is_valid_x(x):
@@ -320,6 +413,13 @@ def __repr__(self):
return "GE()"
return f"GE(0x{int(self.x):x},0x{int(self.y):x})"
+ def __hash__(self):
+ """Compute a non-cryptographic hash of the group element."""
+ if self.infinity:
+ return 0 # 0 is not a valid x coordinate
+ return int(self.x)
+
+
# The secp256k1 generator point
G = GE.lift_x(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798)
@@ -344,7 +444,7 @@ def __init__(self, p):
def mul(self, a):
result = GE()
- a = a % GE.ORDER
+ a = int(a)
for bit in range(a.bit_length()):
if a & (1 << bit):
result += self.table[bit]
@@ -352,9 +452,3 @@ def mul(self, a):
# Precomputed table with multiples of G for fast multiplication
FAST_G = FastGEMul(G)
-
-class TestFrameworkSecp256k1(unittest.TestCase):
- def test_H(self):
- H = sha256(G.to_bytes_uncompressed()).digest()
- assert GE.lift_x(FE.from_bytes(H)) is not None
- self.assertEqual(H.hex(), "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0")
diff --git a/bip-0352/secp256k1lab/src/secp256k1lab/util.py b/bip-0352/secp256k1lab/src/secp256k1lab/util.py
new file mode 100644
index 0000000000..d8c744b795
--- /dev/null
+++ b/bip-0352/secp256k1lab/src/secp256k1lab/util.py
@@ -0,0 +1,24 @@
+import hashlib
+
+
+# This implementation can be sped up by storing the midstate after hashing
+# tag_hash instead of rehashing it all the time.
+def tagged_hash(tag: str, msg: bytes) -> bytes:
+ tag_hash = hashlib.sha256(tag.encode()).digest()
+ return hashlib.sha256(tag_hash + tag_hash + msg).digest()
+
+
+def bytes_from_int(x: int) -> bytes:
+ return x.to_bytes(32, byteorder="big")
+
+
+def xor_bytes(b0: bytes, b1: bytes) -> bytes:
+ return bytes(x ^ y for (x, y) in zip(b0, b1))
+
+
+def int_from_bytes(b: bytes) -> int:
+ return int.from_bytes(b, byteorder="big")
+
+
+def hash_sha256(b: bytes) -> bytes:
+ return hashlib.sha256(b).digest()
diff --git a/bip-0352/send_and_receive_test_vectors.json b/bip-0352/send_and_receive_test_vectors.json
index 3a5e1d3d94..07a330afb9 100644
--- a/bip-0352/send_and_receive_test_vectors.json
+++ b/bip-0352/send_and_receive_test_vectors.json
@@ -3185,5 +3185,2411 @@
}
}
]
+ },
+ {
+ "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/bip-0360.mediawiki b/bip-0360.mediawiki
new file mode 100644
index 0000000000..7a3f00447d
--- /dev/null
+++ b/bip-0360.mediawiki
@@ -0,0 +1,430 @@
++ BIP: 360 + Layer: Consensus (soft fork) + Title: Pay-to-Merkle-Root (P2MR) + Authors: Hunter Beast+ +==Introduction== + +===Abstract=== + +This document proposes a new output type: Pay-to-Merkle-Root (P2MR), via a soft fork. P2MR outputs operate with nearly the same functionality as P2TR (Pay-to-Taproot) outputs, but with the key path spend removed. + +Through this modification, P2MR outputs allow developers to use script trees and tapscript in a manner that is: + +# resistant to long exposure attacks by Cryptographically Relevant Quantum Computers (CRQCs), and +# resistant to future cryptanalytic approaches that may compromise the elliptic curve cryptography (ECC) used by Bitcoin. + +It is worth noting that proposed P2MR outputs are only resistant to "long exposure attacks" on elliptic curve cryptography; that is, attacks on keys exposed for time periods longer than needed to confirm a spending transaction. + +Protection against more sophisticated quantum attacks, including protection against private key recovery from public keys exposed in the mempool while a transaction is waiting to be confirmed (a.k.a. "short exposure attacks"), may require the introduction of post-quantum signatures in Bitcoin. We believe it's worth considering this path in the future and intend to offer a separate proposal for this purpose upon further research. + +This document additionally defines "long exposure" and "short exposure" attacks, and other new terminology in the Glossary. + +===Copyright=== + +This document is licensed under the 3-clause BSD license. + +===Motivation=== + +The primary threat to Bitcoin from Cryptographically Relevant Quantum Computers (CRQCs) is their potential to break the key cryptographic assumption which secures the digital signatures used in Bitcoin.A Cryptographically Relevant Quantum Computer is an ''object'' which is only loosely defined by ''characteristics'' in quantum physics as of today. It could be understood in the context of this BIP and in Bitcoin that it's a ''hardware-agnostic'' computer supposed to have the architecture to keep ''coherent'' a sufficient number of logical qubits to be able to run Shor's algorithm in an efficient fashion. More specifically, [https://arxiv.org/pdf/quant-ph/0301141 Shor's algorithm] enables a CRQC to solve the Discrete Logarithm Problem (DLP) exponentially faster than classical methods.Shor's algorithm is believed to need 10^8 operations to break a 256-bit elliptic curve public key. This allows the derivation of private keys from public keys — a process referred to here as quantum key recovery.Meaning, deriving private keys from public keys via Shor's algorithm While it is unclear when or if CRQCs will become viable in the future, we propose the addition of a quantum-resistant, [[#script-tree-output-type|script tree output type]] for those interested in this level of protection. + +While some may balk at the potential threat of quantum computers to Bitcoin given their limited functionality to date, some others — including governments, corporations and some existing and potential Bitcoin users — are concerned about their potential for advancement. The Commercial National Security Algorithm Suite (CNSA) 2.0, for instance, has mandated software and networking equipment to be upgraded to post-quantum schemes by 2030, with browsers and operating systems fully upgraded by 2033. Additionally, according to NIST IR 8547, Elliptic Curve Cryptography (ECC) is planned to be disallowed within the US federal government after 2035 (with an exception made for hybrid cryptography, or the use of ECC and post-quantum algorithms together). These kinds of mandates have triggered concern by some ECC users, including some Bitcoin users who prefer to be prepared out of an abundance of caution. + +In the most optimistic case, wherein quantum computers never pose a significant risk to ECC, we understand that the possibility of quantum advancement alone may be influencing adoption and broad confidence in the Bitcoin network. In other words, we believe users' fear of quantum computers may be worth addressing regardless of CRQC viability. Given these concerns, we think it's worth considering simple low risk changes that create options for using Bitcoin in a quantum-resistant way. + +As a conservative first step in this effort, we propose Pay-to-Merkle-Root (P2MR), a script tree output that can be used in a quantum resistant manner. + +===Long Exposure vs Short Exposure Attacks=== + +For clarity, this proposal specifically mitigates the risk of long exposure attacks on outputs that support tapscript and script trees. While some other Bitcoin output types, such as P2SH, are safe against long exposure attacks, taproot is not and taproot is the only currently activated output type that supports tapscript and script trees. + +A long exposure attack is an attack performed on exposed blockchain data, such as exposed public keys, or the scripts of spent outputs. These are likely to be the earliest quantum attacks made possible on Bitcoin, because attackers will have ample time — as much time as vulnerable keys are exposed — to carry out quantum key recovery. + +Short exposure attacks, however, require faster quantum computers, because they must occur within the relatively short time that a transaction is unconfirmed in the mempool. + +Bitcoin outputs are generally vulnerable to short exposure attacks, as most Bitcoin transactions require revealing the associated public key when spending. Full protection of outputs from short exposure attacks may require the use of post-quantum signature schemes. + +Since long exposure attacks on public keys are likely to be the first quantum-enabled threat to Bitcoin, we propose a script tree output that is resistant to long exposure attacks as a first step in hardening Bitcoin against the potential threat of quantum computers. + +The following list of output types describes their long exposure attack vulnerability: + +{| class="wikitable" +|- +! Type +! Vulnerable +! Prefix +! Example +|- +| P2PK +| Yes +| Varies +| 02103203b768951584fe9af6d9d9e6ff26a5f76e453212f19ba163774182ab8057f3eac +|- +| P2PKH +| No* +| 1 +| 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa +|- +| P2MS +| Yes +| Varies +| 52410496ec45f878b62c46c4be8e336dff7cc58df9b502178cc240e... +|- +| P2SH +| No* +| 3 +| 3FkhZo7sGNue153xhgqPBcUaBsYvJW6tTx +|- +| P2WPKH +| No* +| bc1q +| bc1qsnh5ktku9ztqeqfr89yrqjd05eh58nah884mku +|- +| P2WSH +| No* +| bc1q +| bc1qvhu3557twysq2ldn6dut6rmaj3qk04p60h9l79wk4lzgy0ca8mfsnffz65 +|- +| P2TR +| Yes +| bc1p +| bc1p92aslsnseq786wxfk3ekra90ds9ku47qttupfjsqmmj4z82xdq4q3rr58u +|- +| P2MR +| No* +| bc1z +| bc1zzmv50jjgxxhww6ve4g5zpewrkjqhr06fyujpm20tuezdlxmfphcqfc80ve +|} + +The following output types are fundamentally vulnerable to long exposure attacks: + +* P2PK outputs (e.g. Satoshi's coins, CPU miners) +* Reused outputs* +* Taproot outputs (starts with bc1p) + ++ Ethan Heilman + Isabel Foxen Duke + Status: Draft + Type: Specification + Assigned: 2024-12-18 + License: BSD-3-Clause + Version: 0.11.0 + Requires: 340, 341, 342 +
OP_SUCCESSx is a mechanism to upgrade tapscript
+
+3. Facilitate gradual integration of quantum resistant features that can be carried out iteratively as quantum computers evolve. This approach encourages responsiveness to the current threat-level, while avoiding heavy-handedness in our reactions to a potential threat.
+
+We designed P2MR with an eye towards integrating post-quantum signatures in the future, without proposing more complex changes while CRQCs are still in their infancy.
+
+===P2MR Trade-Offs===
+
+While P2TR outputs (and the use of key path spend) will remain an option for folks wishing to use them, we aim to be clear about the tradeoffs of using P2MR outputs, which disable the key path spend for the benefit of quantum resistance.
+
+The witness to a P2MR spend is always larger than the witness to a P2TR key path spend. This is because a P2TR key path spend requires only a Schnorr signature in the witness. For P2MR, the witness must include the chosen leaf script, the initial stack, and a control block consisting of the control byte and Merkle path (if any).
+
+That said, the witness to a P2MR spend will always be smaller than the witness to an equivalent P2TR script path spend, because there is no longer any internal key in P2MR that must be revealed in the control block. For a more complete comparison of output type transaction sizes, the "Transaction Size and Fees" section may be reviewed later in this proposal.
+
+Additionally, there is a privacy tradeoff when comparing P2MR and P2TR, which is that users reveal they are spending to a script tree whenever they are using P2MR outputs, since P2MR outputs can only be spent via script path spend. In P2TR when you spend an output as a key path spend, you don't reveal if you have any script path spends. This trade-off only exists when comparing P2TR key path spends to P2MR script path spends; P2TR and P2MR provide the same level of privacy when both are script path spends.
+
+'''Note:''' P2MR and P2TR both provide greater script privacy than P2SH [[bip-0016.mediawiki|BIP 16]] because unused script paths are not revealed.
+
+==Specification==
+
+We define the Pay-to-Merkle-Root (P2MR) output structure as follows:
+
+A P2MR output is similar to a P2TR output (as defined in [[bip-0341.mediawiki|BIP 341]]); however, unlike P2TR outputs, we disable the key path spend for the benefit of quantum resistance by omitting the internal key and the tap tweak step.
+A P2MR output is then a SegWit version 2 byte followed by the Merkle root of the script tree as the witness program.
+
+
+===Address Format===
+
+P2MR outputs use SegWit version 2, resulting in mainnet addresses that start with bc1z, following [[bip-0173.mediawiki|BIP 173]]. Bech32m encoding maps version 2 to the prefix z.
+
+Example P2MR address:
+
+scriptPubKey for a P2MR output is:
+
+OP_2 indicates SegWit version 2.
+* is the 32-byte Merkle root of the script tree.
+
+===Script Validation===
+
+A P2MR output is a native SegWit output (see [[bip-0141.mediawiki|BIP 141]]) with version 2 and a 32-byte witness program. For the sake of comparison, we have — as much as possible — copied the language verbatim from the script validation section of [[bip-0341.mediawiki|BIP 341]].
+
+* Let ''q'' be the 32-byte array containing the witness program (the second push in the scriptPubKey) which represents the Merkle root of the script tree.
+* Fail if the witness stack does not have two or more elements.
+* Fail if the witness stack has exactly two elements and the first byte of the last element is 0x50.
+* If there are at least three witness elements, and the first byte of the last element is 0x50, this last element is called ''annex a'' and is removed from the witness stack. The annex (or the lack thereof) is always covered by the signature and contributes to transaction weight, but is otherwise ignored during P2MR validation.
+* There must be at least two witness elements left.
+** Call the second-to-last stack element ''s'', the script (as defined in [[bip-0341.mediawiki|BIP 341]]).
+** The last stack element is called the control block ''c'', and must have length ''1 + 32 * m'', for a value of ''m'' that is an integer between 0 and 128, inclusive. Fail if it does not have such a length.
+** Let ''v = c[0] & 0xfe'' be the ''leaf version'' (as defined in [[bip-0341.mediawiki|BIP 341]]). To maintain ''leaf version'' encoding compatibility the last bit of c[0] is unused and must be 1.Why set the last bit of c[0] to one? Consider a faulty implementation that deserializes the ''leaf version'' as c[0] rather than c[0] & 0xfe for both P2TR and P2MR. If they test against P2MR outputs and require that last bit is 1, this deserialization bug will cause an immediate error.
+** Let ''k0 = hashTapLeaf(v || compact_size(size of s) || s)''; also call it the ''tapleaf hash''.
+** For ''j'' in ''[0,1,...,m-1]'':
+*** Let ''ej = c[1+32j:33+32j]''.
+*** Let ''kj+1'' depend on whether ''kj < ej'' (lexicographically):
+**** If ''kj < ej'': ''kj+1 = hashTapBranch(kj || ej)''.
+**** If ''kj ≥ ej'': ''kj+1 = hashTapBranch(ej || kj)''.
+** Let ''r = km''.
+** If ''q ≠ r'', fail.
+** Execute the script, according to the script rules specified in BIP 342, using the witness stack elements excluding the script ''s'', the control block ''c'', and the annex ''a'' if present, as initial stack. This implies that for the future leaf versions (non-''0xC0'') the execution must succeed.
+
+The steps above follow the script path spend logic from [[bip-0341.mediawiki|BIP 341]] with the following changes:
+
+* The witness program is the the Merkle root of the script tree and not a tweaked public key. This means that we skip directly to the BIP 341 script path validation.
+* We compute the script tree Merkle root ''r'' and compare it directly to the witness program ''q''.
+* The control block is ''1 + 32*m'' bytes, instead of ''33 + 32*m'' bytes.
+
+===Common Signature Message Construction===
+
+The [https://learnmeabitcoin.com/technical/upgrades/taproot/#common-signature-message common signature message] construction for P2MR outputs is exactly the same procedure as defined in [[bip-0342.mediawiki#user-content-Common_Signature_Message_Extension|BIP342 Common Signature Message]].
+
+=== Compatibility with BIP 141 ===
+
+By adhering to the SegWit transaction structure and versioning, P2MR outputs are compatible with existing transaction processing rules. Nodes that do not recognize SegWit version 2 will treat these outputs as anyone-can-spend but generally will not relay or mine such transactions.
+
+===Transaction Size and Fees===
+
+All P2MR and P2TR outputs are always the same size. P2MR inputs can be slightly larger or smaller than their equivalent P2TR inputs, depending on the use of key path vs script path spend in the case of P2TR. Let's consider the cases.
+
+====Comparison with P2TR key path spend====
+
+A P2MR witness will be larger than a P2TR witness when the P2TR output is spent via the key path spend. A witness to a P2TR key path spend is simply a signature. P2MR quantum resistance comes from removing the P2TR key path spend. Every P2MR spend is a P2TR script path spend and so requires a script, its input stack and a control block. Consequently, P2MR loses this size advantage of P2TR key path spends in order to gain quantum resistance. If the script tree only has a single leaf script, no Merkle path is needed in the control block, giving us a minimal size control block of 1 byte.
+
+P2MR witness for depth-0 tree (103 bytes):
+
+./bip-0374/gen_test_vectors.py which will produce a CSV file of random test vectors for both generating and verifying proofs. These can be run against the reference implementation with ./bip-0374/run_test_vectors.py.
== Changelog ==
diff --git a/bip-0374/gen_test_vectors.py b/bip-0374/gen_test_vectors.py
index 792a59a45b..a828074e4a 100755
--- a/bip-0374/gen_test_vectors.py
+++ b/bip-0374/gen_test_vectors.py
@@ -1,30 +1,29 @@
#!/usr/bin/env python3
"""Generate the BIP-0374 test vectors."""
import csv
-import os
-import sys
+from pathlib import Path
from reference import (
- TaggedHash,
dleq_generate_proof,
dleq_verify_proof,
)
-from secp256k1 import G as GENERATOR, GE
+from secp256k1lab.secp256k1 import G as GENERATOR, GE
+from secp256k1lab.util import tagged_hash
NUM_SUCCESS_TEST_VECTORS = 8
DLEQ_TAG_TESTVECTORS_RNG = "BIP0374/testvectors_rng"
-FILENAME_GENERATE_PROOF_TEST = os.path.join(sys.path[0], 'test_vectors_generate_proof.csv')
-FILENAME_VERIFY_PROOF_TEST = os.path.join(sys.path[0], 'test_vectors_verify_proof.csv')
+FILENAME_GENERATE_PROOF_TEST = Path(__file__).parent / 'test_vectors_generate_proof.csv'
+FILENAME_VERIFY_PROOF_TEST = Path(__file__).parent / 'test_vectors_verify_proof.csv'
def random_scalar_int(vector_i, purpose):
- rng_out = TaggedHash(DLEQ_TAG_TESTVECTORS_RNG, purpose.encode() + vector_i.to_bytes(4, 'little'))
+ rng_out = tagged_hash(DLEQ_TAG_TESTVECTORS_RNG, purpose.encode() + vector_i.to_bytes(4, 'little'))
return int.from_bytes(rng_out, 'big') % GE.ORDER
def random_bytes(vector_i, purpose):
- rng_out = TaggedHash(DLEQ_TAG_TESTVECTORS_RNG, purpose.encode() + vector_i.to_bytes(4, 'little'))
+ rng_out = tagged_hash(DLEQ_TAG_TESTVECTORS_RNG, purpose.encode() + vector_i.to_bytes(4, 'little'))
return rng_out
diff --git a/bip-0374/reference.py b/bip-0374/reference.py
index e0fcbeaf97..8068b9c25c 100755
--- a/bip-0374/reference.py
+++ b/bip-0374/reference.py
@@ -2,30 +2,22 @@
"""Reference implementation of DLEQ BIP for secp256k1 with unit tests."""
-from hashlib import sha256
+from pathlib import Path
import random
-from secp256k1 import G, GE
import sys
import unittest
+# Prefer the vendored copy of secp256k1lab
+sys.path.insert(0, str(Path(__file__).parent / "secp256k1lab/src"))
+from secp256k1lab.secp256k1 import G, GE
+from secp256k1lab.util import tagged_hash, xor_bytes
+
DLEQ_TAG_AUX = "BIP0374/aux"
DLEQ_TAG_NONCE = "BIP0374/nonce"
DLEQ_TAG_CHALLENGE = "BIP0374/challenge"
-def TaggedHash(tag: str, data: bytes) -> bytes:
- ss = sha256(tag.encode()).digest()
- ss += ss
- ss += data
- return sha256(ss).digest()
-
-
-def xor_bytes(lhs: bytes, rhs: bytes) -> bytes:
- assert len(lhs) == len(rhs)
- return bytes([lhs[i] ^ rhs[i] for i in range(len(lhs))])
-
-
def dleq_challenge(
A: GE, B: GE, C: GE, R1: GE, R2: GE, m: bytes | None, G: GE,
) -> int:
@@ -33,7 +25,7 @@ def dleq_challenge(
assert len(m) == 32
m = bytes([]) if m is None else m
return int.from_bytes(
- TaggedHash(
+ tagged_hash(
DLEQ_TAG_CHALLENGE,
A.to_bytes_compressed()
+ B.to_bytes_compressed()
@@ -59,9 +51,9 @@ def dleq_generate_proof(
assert len(m) == 32
A = a * G
C = a * B
- t = xor_bytes(a.to_bytes(32, "big"), TaggedHash(DLEQ_TAG_AUX, r))
+ t = xor_bytes(a.to_bytes(32, "big"), tagged_hash(DLEQ_TAG_AUX, r))
m_prime = bytes([]) if m is None else m
- rand = TaggedHash(
+ rand = tagged_hash(
DLEQ_TAG_NONCE, t + A.to_bytes_compressed() + C.to_bytes_compressed() + m_prime
)
k = int.from_bytes(rand, "big") % GE.ORDER
diff --git a/bip-0374/run_test_vectors.py b/bip-0374/run_test_vectors.py
index 4831fbe20b..54349aed48 100755
--- a/bip-0374/run_test_vectors.py
+++ b/bip-0374/run_test_vectors.py
@@ -1,17 +1,17 @@
#!/usr/bin/env python3
"""Run the BIP-DLEQ test vectors."""
import csv
-import os
+from pathlib import Path
import sys
from reference import (
dleq_generate_proof,
dleq_verify_proof,
)
-from secp256k1 import GE
+from secp256k1lab.secp256k1 import GE
-FILENAME_GENERATE_PROOF_TEST = os.path.join(sys.path[0], 'test_vectors_generate_proof.csv')
-FILENAME_VERIFY_PROOF_TEST = os.path.join(sys.path[0], 'test_vectors_verify_proof.csv')
+FILENAME_GENERATE_PROOF_TEST = Path(__file__).parent / 'test_vectors_generate_proof.csv'
+FILENAME_VERIFY_PROOF_TEST = Path(__file__).parent / 'test_vectors_verify_proof.csv'
all_passed = True
diff --git a/bip-0374/secp256k1lab/.github/workflows/main.yml b/bip-0374/secp256k1lab/.github/workflows/main.yml
new file mode 100644
index 0000000000..4950b96550
--- /dev/null
+++ b/bip-0374/secp256k1lab/.github/workflows/main.yml
@@ -0,0 +1,17 @@
+name: Tests
+on: [push, pull_request]
+jobs:
+ ruff:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install the latest version of uv
+ uses: astral-sh/setup-uv@v5
+ - run: uvx ruff check .
+ mypy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install the latest version of uv
+ uses: astral-sh/setup-uv@v5
+ - run: uvx mypy .
diff --git a/bip-0374/secp256k1lab/.python-version b/bip-0374/secp256k1lab/.python-version
new file mode 100644
index 0000000000..bd28b9c5c2
--- /dev/null
+++ b/bip-0374/secp256k1lab/.python-version
@@ -0,0 +1 @@
+3.9
diff --git a/bip-0374/secp256k1lab/CHANGELOG.md b/bip-0374/secp256k1lab/CHANGELOG.md
new file mode 100644
index 0000000000..15779717c4
--- /dev/null
+++ b/bip-0374/secp256k1lab/CHANGELOG.md
@@ -0,0 +1,10 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [1.0.0] - 2025-03-31
+
+Initial release.
diff --git a/bip-0374/secp256k1lab/COPYING b/bip-0374/secp256k1lab/COPYING
new file mode 100644
index 0000000000..e8f2163641
--- /dev/null
+++ b/bip-0374/secp256k1lab/COPYING
@@ -0,0 +1,23 @@
+The MIT License (MIT)
+
+Copyright (c) 2009-2024 The Bitcoin Core developers
+Copyright (c) 2009-2024 Bitcoin Developers
+Copyright (c) 2025- The secp256k1lab Developers
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/bip-0374/secp256k1lab/README.md b/bip-0374/secp256k1lab/README.md
new file mode 100644
index 0000000000..dbc9dbd04c
--- /dev/null
+++ b/bip-0374/secp256k1lab/README.md
@@ -0,0 +1,13 @@
+secp256k1lab
+============
+
+
+
+An INSECURE implementation of the secp256k1 elliptic curve and related cryptographic schemes written in Python, intended for prototyping, experimentation and education.
+
+Features:
+* Low-level secp256k1 field and group arithmetic.
+* Schnorr signing/verification and key generation according to [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
+* ECDH key exchange.
+
+WARNING: The code in this library is slow and trivially vulnerable to side channel attacks.
diff --git a/bip-0374/secp256k1lab/pyproject.toml b/bip-0374/secp256k1lab/pyproject.toml
new file mode 100644
index 0000000000..a0bdd19f42
--- /dev/null
+++ b/bip-0374/secp256k1lab/pyproject.toml
@@ -0,0 +1,34 @@
+[project]
+name = "secp256k1lab"
+version = "1.0.0"
+description = "An INSECURE implementation of the secp256k1 elliptic curve and related cryptographic schemes, intended for prototyping, experimentation and education"
+readme = "README.md"
+authors = [
+ { name = "Pieter Wuille", email = "pieter@wuille.net" },
+ { name = "Tim Ruffing", email = "me@real-or-random.org" },
+ { name = "Jonas Nick", email = "jonasd.nick@gmail.com" },
+ { name = "Sebastian Falbesoner", email = "sebastian.falbesoner@gmail.com" }
+]
+maintainers = [
+ { name = "Tim Ruffing", email = "me@real-or-random.org" },
+ { name = "Jonas Nick", email = "jonasd.nick@gmail.com" },
+ { name = "Sebastian Falbesoner", email = "sebastian.falbesoner@gmail.com" }
+]
+requires-python = ">=3.9"
+license = "MIT"
+license-files = ["COPYING"]
+keywords = ["secp256k1", "elliptic curves", "cryptography", "Bitcoin"]
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Developers",
+ "Intended Audience :: Education",
+ "Intended Audience :: Science/Research",
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: Python",
+ "Topic :: Security :: Cryptography",
+]
+dependencies = []
+
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
diff --git a/bip-0374/secp256k1lab/src/secp256k1lab/__init__.py b/bip-0374/secp256k1lab/src/secp256k1lab/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/bip-0374/secp256k1lab/src/secp256k1lab/bip340.py b/bip-0374/secp256k1lab/src/secp256k1lab/bip340.py
new file mode 100644
index 0000000000..ba839d16e1
--- /dev/null
+++ b/bip-0374/secp256k1lab/src/secp256k1lab/bip340.py
@@ -0,0 +1,73 @@
+# The following functions are based on the BIP 340 reference implementation:
+# https://github.com/bitcoin/bips/blob/master/bip-0340/reference.py
+
+from .secp256k1 import FE, GE, G
+from .util import int_from_bytes, bytes_from_int, xor_bytes, tagged_hash
+
+
+def pubkey_gen(seckey: bytes) -> bytes:
+ d0 = int_from_bytes(seckey)
+ if not (1 <= d0 <= GE.ORDER - 1):
+ raise ValueError("The secret key must be an integer in the range 1..n-1.")
+ P = d0 * G
+ assert not P.infinity
+ return P.to_bytes_xonly()
+
+
+def schnorr_sign(
+ msg: bytes, seckey: bytes, aux_rand: bytes, tag_prefix: str = "BIP0340"
+) -> bytes:
+ d0 = int_from_bytes(seckey)
+ if not (1 <= d0 <= GE.ORDER - 1):
+ raise ValueError("The secret key must be an integer in the range 1..n-1.")
+ if len(aux_rand) != 32:
+ raise ValueError("aux_rand must be 32 bytes instead of %i." % len(aux_rand))
+ P = d0 * G
+ assert not P.infinity
+ d = d0 if P.has_even_y() else GE.ORDER - d0
+ t = xor_bytes(bytes_from_int(d), tagged_hash(tag_prefix + "/aux", aux_rand))
+ k0 = (
+ int_from_bytes(tagged_hash(tag_prefix + "/nonce", t + P.to_bytes_xonly() + msg))
+ % GE.ORDER
+ )
+ if k0 == 0:
+ raise RuntimeError("Failure. This happens only with negligible probability.")
+ R = k0 * G
+ assert not R.infinity
+ k = k0 if R.has_even_y() else GE.ORDER - k0
+ e = (
+ int_from_bytes(
+ tagged_hash(
+ tag_prefix + "/challenge", R.to_bytes_xonly() + P.to_bytes_xonly() + msg
+ )
+ )
+ % GE.ORDER
+ )
+ sig = R.to_bytes_xonly() + bytes_from_int((k + e * d) % GE.ORDER)
+ assert schnorr_verify(msg, P.to_bytes_xonly(), sig, tag_prefix=tag_prefix)
+ return sig
+
+
+def schnorr_verify(
+ msg: bytes, pubkey: bytes, sig: bytes, tag_prefix: str = "BIP0340"
+) -> bool:
+ if len(pubkey) != 32:
+ raise ValueError("The public key must be a 32-byte array.")
+ if len(sig) != 64:
+ raise ValueError("The signature must be a 64-byte array.")
+ try:
+ P = GE.from_bytes_xonly(pubkey)
+ except ValueError:
+ return False
+ r = int_from_bytes(sig[0:32])
+ s = int_from_bytes(sig[32:64])
+ if (r >= FE.SIZE) or (s >= GE.ORDER):
+ return False
+ e = (
+ int_from_bytes(tagged_hash(tag_prefix + "/challenge", sig[0:32] + pubkey + msg))
+ % GE.ORDER
+ )
+ R = s * G - e * P
+ if R.infinity or (not R.has_even_y()) or (R.x != r):
+ return False
+ return True
diff --git a/bip-0374/secp256k1lab/src/secp256k1lab/ecdh.py b/bip-0374/secp256k1lab/src/secp256k1lab/ecdh.py
new file mode 100644
index 0000000000..73f47fa1a7
--- /dev/null
+++ b/bip-0374/secp256k1lab/src/secp256k1lab/ecdh.py
@@ -0,0 +1,16 @@
+import hashlib
+
+from .secp256k1 import GE, Scalar
+
+
+def ecdh_compressed_in_raw_out(seckey: bytes, pubkey: bytes) -> GE:
+ """TODO"""
+ shared_secret = Scalar.from_bytes_checked(seckey) * GE.from_bytes_compressed(pubkey)
+ assert not shared_secret.infinity # prime-order group
+ return shared_secret
+
+
+def ecdh_libsecp256k1(seckey: bytes, pubkey: bytes) -> bytes:
+ """TODO"""
+ shared_secret = ecdh_compressed_in_raw_out(seckey, pubkey)
+ return hashlib.sha256(shared_secret.to_bytes_compressed()).digest()
diff --git a/bip-0374/secp256k1lab/src/secp256k1lab/keys.py b/bip-0374/secp256k1lab/src/secp256k1lab/keys.py
new file mode 100644
index 0000000000..3e28897e99
--- /dev/null
+++ b/bip-0374/secp256k1lab/src/secp256k1lab/keys.py
@@ -0,0 +1,15 @@
+from .secp256k1 import GE, G
+from .util import int_from_bytes
+
+# The following function is based on the BIP 327 reference implementation
+# https://github.com/bitcoin/bips/blob/master/bip-0327/reference.py
+
+
+# Return the plain public key corresponding to a given secret key
+def pubkey_gen_plain(seckey: bytes) -> bytes:
+ d0 = int_from_bytes(seckey)
+ if not (1 <= d0 <= GE.ORDER - 1):
+ raise ValueError("The secret key must be an integer in the range 1..n-1.")
+ P = d0 * G
+ assert not P.infinity
+ return P.to_bytes_compressed()
diff --git a/bip-0374/secp256k1lab/src/secp256k1lab/py.typed b/bip-0374/secp256k1lab/src/secp256k1lab/py.typed
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/bip-0374/secp256k1lab/src/secp256k1lab/secp256k1.py b/bip-0374/secp256k1lab/src/secp256k1lab/secp256k1.py
new file mode 100644
index 0000000000..6e262bf51e
--- /dev/null
+++ b/bip-0374/secp256k1lab/src/secp256k1lab/secp256k1.py
@@ -0,0 +1,454 @@
+# Copyright (c) 2022-2023 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+"""Test-only implementation of low-level secp256k1 field and group arithmetic
+
+It is designed for ease of understanding, not performance.
+
+WARNING: This code is slow and trivially vulnerable to side channel attacks. Do not use for
+anything but tests.
+
+Exports:
+* FE: class for secp256k1 field elements
+* GE: class for secp256k1 group elements
+* G: the secp256k1 generator point
+"""
+
+# TODO Docstrings of methods still say "field element"
+class APrimeFE:
+ """Objects of this class represent elements of a prime field.
+
+ They are represented internally in numerator / denominator form, in order to delay inversions.
+ """
+
+ # The size of the field (also its modulus and characteristic).
+ SIZE: int
+
+ def __init__(self, a=0, b=1):
+ """Initialize a field element a/b; both a and b can be ints or field elements."""
+ if isinstance(a, type(self)):
+ num = a._num
+ den = a._den
+ else:
+ num = a % self.SIZE
+ den = 1
+ if isinstance(b, type(self)):
+ den = (den * b._num) % self.SIZE
+ num = (num * b._den) % self.SIZE
+ else:
+ den = (den * b) % self.SIZE
+ assert den != 0
+ if num == 0:
+ den = 1
+ self._num = num
+ self._den = den
+
+ def __add__(self, a):
+ """Compute the sum of two field elements (second may be int)."""
+ if isinstance(a, type(self)):
+ return type(self)(self._num * a._den + self._den * a._num, self._den * a._den)
+ if isinstance(a, int):
+ return type(self)(self._num + self._den * a, self._den)
+ return NotImplemented
+
+ def __radd__(self, a):
+ """Compute the sum of an integer and a field element."""
+ return type(self)(a) + self
+
+ @classmethod
+ # REVIEW This should be
+ # def sum(cls, *es: Iterable[Self]) -> Self:
+ # but Self needs the typing_extension package on Python <= 3.12.
+ def sum(cls, *es):
+ """Compute the sum of field elements.
+
+ sum(a, b, c, ...) is identical to (0 + a + b + c + ...)."""
+ return sum(es, start=cls(0))
+
+ def __sub__(self, a):
+ """Compute the difference of two field elements (second may be int)."""
+ if isinstance(a, type(self)):
+ return type(self)(self._num * a._den - self._den * a._num, self._den * a._den)
+ if isinstance(a, int):
+ return type(self)(self._num - self._den * a, self._den)
+ return NotImplemented
+
+ def __rsub__(self, a):
+ """Compute the difference of an integer and a field element."""
+ return type(self)(a) - self
+
+ def __mul__(self, a):
+ """Compute the product of two field elements (second may be int)."""
+ if isinstance(a, type(self)):
+ return type(self)(self._num * a._num, self._den * a._den)
+ if isinstance(a, int):
+ return type(self)(self._num * a, self._den)
+ return NotImplemented
+
+ def __rmul__(self, a):
+ """Compute the product of an integer with a field element."""
+ return type(self)(a) * self
+
+ def __truediv__(self, a):
+ """Compute the ratio of two field elements (second may be int)."""
+ if isinstance(a, type(self)) or isinstance(a, int):
+ return type(self)(self, a)
+ return NotImplemented
+
+ def __pow__(self, a):
+ """Raise a field element to an integer power."""
+ return type(self)(pow(self._num, a, self.SIZE), pow(self._den, a, self.SIZE))
+
+ def __neg__(self):
+ """Negate a field element."""
+ return type(self)(-self._num, self._den)
+
+ def __int__(self):
+ """Convert a field element to an integer in range 0..SIZE-1. The result is cached."""
+ if self._den != 1:
+ self._num = (self._num * pow(self._den, -1, self.SIZE)) % self.SIZE
+ self._den = 1
+ return self._num
+
+ def sqrt(self):
+ """Compute the square root of a field element if it exists (None otherwise)."""
+ raise NotImplementedError
+
+ def is_square(self):
+ """Determine if this field element has a square root."""
+ # A more efficient algorithm is possible here (Jacobi symbol).
+ return self.sqrt() is not None
+
+ def is_even(self):
+ """Determine whether this field element, represented as integer in 0..SIZE-1, is even."""
+ return int(self) & 1 == 0
+
+ def __eq__(self, a):
+ """Check whether two field elements are equal (second may be an int)."""
+ if isinstance(a, type(self)):
+ return (self._num * a._den - self._den * a._num) % self.SIZE == 0
+ return (self._num - self._den * a) % self.SIZE == 0
+
+ def to_bytes(self):
+ """Convert a field element to a 32-byte array (BE byte order)."""
+ return int(self).to_bytes(32, 'big')
+
+ @classmethod
+ def from_int_checked(cls, v):
+ """Convert an integer to a field element (no overflow allowed)."""
+ if v >= cls.SIZE:
+ raise ValueError
+ return cls(v)
+
+ @classmethod
+ def from_int_wrapping(cls, v):
+ """Convert an integer to a field element (reduced modulo SIZE)."""
+ return cls(v % cls.SIZE)
+
+ @classmethod
+ def from_bytes_checked(cls, b):
+ """Convert a 32-byte array to a field element (BE byte order, no overflow allowed)."""
+ v = int.from_bytes(b, 'big')
+ return cls.from_int_checked(v)
+
+ @classmethod
+ def from_bytes_wrapping(cls, b):
+ """Convert a 32-byte array to a field element (BE byte order, reduced modulo SIZE)."""
+ v = int.from_bytes(b, 'big')
+ return cls.from_int_wrapping(v)
+
+ def __str__(self):
+ """Convert this field element to a 64 character hex string."""
+ return f"{int(self):064x}"
+
+ def __repr__(self):
+ """Get a string representation of this field element."""
+ return f"{type(self).__qualname__}(0x{int(self):x})"
+
+
+class FE(APrimeFE):
+ SIZE = 2**256 - 2**32 - 977
+
+ def sqrt(self):
+ # Due to the fact that our modulus p is of the form (p % 4) == 3, the Tonelli-Shanks
+ # algorithm (https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm) is simply
+ # raising the argument to the power (p + 1) / 4.
+
+ # To see why: (p-1) % 2 = 0, so 2 divides the order of the multiplicative group,
+ # and thus only half of the non-zero field elements are squares. An element a is
+ # a (nonzero) square when Euler's criterion, a^((p-1)/2) = 1 (mod p), holds. We're
+ # looking for x such that x^2 = a (mod p). Given a^((p-1)/2) = 1, that is equivalent
+ # to x^2 = a^(1 + (p-1)/2) mod p. As (1 + (p-1)/2) is even, this is equivalent to
+ # x = a^((1 + (p-1)/2)/2) mod p, or x = a^((p+1)/4) mod p.
+ v = int(self)
+ s = pow(v, (self.SIZE + 1) // 4, self.SIZE)
+ if s**2 % self.SIZE == v:
+ return type(self)(s)
+ return None
+
+
+class Scalar(APrimeFE):
+ """TODO Docstring"""
+ SIZE = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
+
+
+class GE:
+ """Objects of this class represent secp256k1 group elements (curve points or infinity)
+
+ GE objects are immutable.
+
+ Normal points on the curve have fields:
+ * x: the x coordinate (a field element)
+ * y: the y coordinate (a field element, satisfying y^2 = x^3 + 7)
+ * infinity: False
+
+ The point at infinity has field:
+ * infinity: True
+ """
+
+ # TODO The following two class attributes should probably be just getters as
+ # classmethods to enforce immutability. Unfortunately Python makes it hard
+ # to create "classproperties". `G` could then also be just a classmethod.
+
+ # Order of the group (number of points on the curve, plus 1 for infinity)
+ ORDER = Scalar.SIZE
+
+ # Number of valid distinct x coordinates on the curve.
+ ORDER_HALF = ORDER // 2
+
+ @property
+ def infinity(self):
+ """Whether the group element is the point at infinity."""
+ return self._infinity
+
+ @property
+ def x(self):
+ """The x coordinate (a field element) of a non-infinite group element."""
+ assert not self.infinity
+ return self._x
+
+ @property
+ def y(self):
+ """The y coordinate (a field element) of a non-infinite group element."""
+ assert not self.infinity
+ return self._y
+
+ def __init__(self, x=None, y=None):
+ """Initialize a group element with specified x and y coordinates, or infinity."""
+ if x is None:
+ # Initialize as infinity.
+ assert y is None
+ self._infinity = True
+ else:
+ # Initialize as point on the curve (and check that it is).
+ fx = FE(x)
+ fy = FE(y)
+ assert fy**2 == fx**3 + 7
+ self._infinity = False
+ self._x = fx
+ self._y = fy
+
+ def __add__(self, a):
+ """Add two group elements together."""
+ # Deal with infinity: a + infinity == infinity + a == a.
+ if self.infinity:
+ return a
+ if a.infinity:
+ return self
+ if self.x == a.x:
+ if self.y != a.y:
+ # A point added to its own negation is infinity.
+ assert self.y + a.y == 0
+ return GE()
+ else:
+ # For identical inputs, use the tangent (doubling formula).
+ lam = (3 * self.x**2) / (2 * self.y)
+ else:
+ # For distinct inputs, use the line through both points (adding formula).
+ lam = (self.y - a.y) / (self.x - a.x)
+ # Determine point opposite to the intersection of that line with the curve.
+ x = lam**2 - (self.x + a.x)
+ y = lam * (self.x - x) - self.y
+ return GE(x, y)
+
+ @staticmethod
+ def sum(*ps):
+ """Compute the sum of group elements.
+
+ GE.sum(a, b, c, ...) is identical to (GE() + a + b + c + ...)."""
+ return sum(ps, start=GE())
+
+ @staticmethod
+ def batch_mul(*aps):
+ """Compute a (batch) scalar group element multiplication.
+
+ GE.batch_mul((a1, p1), (a2, p2), (a3, p3)) is identical to a1*p1 + a2*p2 + a3*p3,
+ but more efficient."""
+ # Reduce all the scalars modulo order first (so we can deal with negatives etc).
+ naps = [(int(a), p) for a, p in aps]
+ # Start with point at infinity.
+ r = GE()
+ # Iterate over all bit positions, from high to low.
+ for i in range(255, -1, -1):
+ # Double what we have so far.
+ r = r + r
+ # Add then add the points for which the corresponding scalar bit is set.
+ for (a, p) in naps:
+ if (a >> i) & 1:
+ r += p
+ return r
+
+ def __rmul__(self, a):
+ """Multiply an integer with a group element."""
+ if self == G:
+ return FAST_G.mul(Scalar(a))
+ return GE.batch_mul((Scalar(a), self))
+
+ def __neg__(self):
+ """Compute the negation of a group element."""
+ if self.infinity:
+ return self
+ return GE(self.x, -self.y)
+
+ def __sub__(self, a):
+ """Subtract a group element from another."""
+ return self + (-a)
+
+ def __eq__(self, a):
+ """Check if two group elements are equal."""
+ return (self - a).infinity
+
+ def has_even_y(self):
+ """Determine whether a non-infinity group element has an even y coordinate."""
+ assert not self.infinity
+ return self.y.is_even()
+
+ def to_bytes_compressed(self):
+ """Convert a non-infinite group element to 33-byte compressed encoding."""
+ assert not self.infinity
+ return bytes([3 - self.y.is_even()]) + self.x.to_bytes()
+
+ def to_bytes_compressed_with_infinity(self):
+ """Convert a group element to 33-byte compressed encoding, mapping infinity to zeros."""
+ if self.infinity:
+ return 33 * b"\x00"
+ return self.to_bytes_compressed()
+
+ def to_bytes_uncompressed(self):
+ """Convert a non-infinite group element to 65-byte uncompressed encoding."""
+ assert not self.infinity
+ return b'\x04' + self.x.to_bytes() + self.y.to_bytes()
+
+ def to_bytes_xonly(self):
+ """Convert (the x coordinate of) a non-infinite group element to 32-byte xonly encoding."""
+ assert not self.infinity
+ return self.x.to_bytes()
+
+ @staticmethod
+ def lift_x(x):
+ """Return group element with specified field element as x coordinate (and even y)."""
+ y = (FE(x)**3 + 7).sqrt()
+ if y is None:
+ raise ValueError
+ if not y.is_even():
+ y = -y
+ return GE(x, y)
+
+ @staticmethod
+ def from_bytes_compressed(b):
+ """Convert a compressed to a group element."""
+ assert len(b) == 33
+ if b[0] != 2 and b[0] != 3:
+ raise ValueError
+ x = FE.from_bytes_checked(b[1:])
+ r = GE.lift_x(x)
+ if b[0] == 3:
+ r = -r
+ return r
+
+ @staticmethod
+ def from_bytes_uncompressed(b):
+ """Convert an uncompressed to a group element."""
+ assert len(b) == 65
+ if b[0] != 4:
+ raise ValueError
+ x = FE.from_bytes_checked(b[1:33])
+ y = FE.from_bytes_checked(b[33:])
+ if y**2 != x**3 + 7:
+ raise ValueError
+ return GE(x, y)
+
+ @staticmethod
+ def from_bytes(b):
+ """Convert a compressed or uncompressed encoding to a group element."""
+ assert len(b) in (33, 65)
+ if len(b) == 33:
+ return GE.from_bytes_compressed(b)
+ else:
+ return GE.from_bytes_uncompressed(b)
+
+ @staticmethod
+ def from_bytes_xonly(b):
+ """Convert a point given in xonly encoding to a group element."""
+ assert len(b) == 32
+ x = FE.from_bytes_checked(b)
+ r = GE.lift_x(x)
+ return r
+
+ @staticmethod
+ def is_valid_x(x):
+ """Determine whether the provided field element is a valid X coordinate."""
+ return (FE(x)**3 + 7).is_square()
+
+ def __str__(self):
+ """Convert this group element to a string."""
+ if self.infinity:
+ return "(inf)"
+ return f"({self.x},{self.y})"
+
+ def __repr__(self):
+ """Get a string representation for this group element."""
+ if self.infinity:
+ return "GE()"
+ return f"GE(0x{int(self.x):x},0x{int(self.y):x})"
+
+ def __hash__(self):
+ """Compute a non-cryptographic hash of the group element."""
+ if self.infinity:
+ return 0 # 0 is not a valid x coordinate
+ return int(self.x)
+
+
+# The secp256k1 generator point
+G = GE.lift_x(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798)
+
+
+class FastGEMul:
+ """Table for fast multiplication with a constant group element.
+
+ Speed up scalar multiplication with a fixed point P by using a precomputed lookup table with
+ its powers of 2:
+
+ table = [P, 2*P, 4*P, (2^3)*P, (2^4)*P, ..., (2^255)*P]
+
+ During multiplication, the points corresponding to each bit set in the scalar are added up,
+ i.e. on average ~128 point additions take place.
+ """
+
+ def __init__(self, p):
+ self.table = [p] # table[i] = (2^i) * p
+ for _ in range(255):
+ p = p + p
+ self.table.append(p)
+
+ def mul(self, a):
+ result = GE()
+ a = int(a)
+ for bit in range(a.bit_length()):
+ if a & (1 << bit):
+ result += self.table[bit]
+ return result
+
+# Precomputed table with multiples of G for fast multiplication
+FAST_G = FastGEMul(G)
diff --git a/bip-0374/secp256k1lab/src/secp256k1lab/util.py b/bip-0374/secp256k1lab/src/secp256k1lab/util.py
new file mode 100644
index 0000000000..d8c744b795
--- /dev/null
+++ b/bip-0374/secp256k1lab/src/secp256k1lab/util.py
@@ -0,0 +1,24 @@
+import hashlib
+
+
+# This implementation can be sped up by storing the midstate after hashing
+# tag_hash instead of rehashing it all the time.
+def tagged_hash(tag: str, msg: bytes) -> bytes:
+ tag_hash = hashlib.sha256(tag.encode()).digest()
+ return hashlib.sha256(tag_hash + tag_hash + msg).digest()
+
+
+def bytes_from_int(x: int) -> bytes:
+ return x.to_bytes(32, byteorder="big")
+
+
+def xor_bytes(b0: bytes, b1: bytes) -> bytes:
+ return bytes(x ^ y for (x, y) in zip(b0, b1))
+
+
+def int_from_bytes(b: bytes) -> int:
+ return int.from_bytes(b, byteorder="big")
+
+
+def hash_sha256(b: bytes) -> bytes:
+ return hashlib.sha256(b).digest()
diff --git a/bip-0380.mediawiki b/bip-0380.mediawiki
index 3f113f2a60..e6311224be 100644
--- a/bip-0380.mediawiki
+++ b/bip-0380.mediawiki
@@ -334,4 +334,7 @@ This Table lists all available Script expressions and the BIPs specifying them.
|-
| musig(KEY, KEY, ..., KEY)
| [[bip-0390.mediawiki|390]]
+|-
+| sp(KEY), sp(KEY, KEY)
+| [[bip-0392.mediawiki|392]]
|}
diff --git a/bip-0383.mediawiki b/bip-0383.mediawiki
index 60ef16fedc..b4008f71ab 100644
--- a/bip-0383.mediawiki
+++ b/bip-0383.mediawiki
@@ -56,7 +56,7 @@ For values greater than 16, they must be a push of the signed little endian enco
The only change for sortedmulti() is that the keys are sorted lexicographically prior to the creation of the output script.
This sorting is on the keys that are to be put into the output script, i.e. after all extended keys are derived.
-===Multiple Extended Keys===
+===Multiple Extended Keys===
When one or more the key expressions in a multi() or sortedmulti() expression are extended keys, the derived keys use the same child index.
This changes the keys in lockstep and allows for output scripts to be indexed in the same way that the derived keys are indexed.
diff --git a/bip-0390.mediawiki b/bip-0390.mediawiki
index 2daed4d2de..a6b5526a67 100644
--- a/bip-0390.mediawiki
+++ b/bip-0390.mediawiki
@@ -7,6 +7,7 @@
Type: Informational
Assigned: 2024-06-04
License: CC0-1.0
+ Version: 0.2.0
Requires: 380, 328
@@ -33,8 +34,8 @@ and [[bip-0389.mediawiki|BIP-389]].
===musig(KEY, KEY, ..., KEY)===
-The musig(KEY, KEY, ..., KEY) expression can only be used inside of a tr() or
-rawtr() expression as a key expression. It additionally cannot be nested within another musig()
+The musig(KEY, KEY, ..., KEY) expression can only be used inside of a tr(), rawtr()
+or sp() expression as a key expression. It additionally cannot be nested within another musig()
expression. Participant public keys may be repeated. The aggregate public key is produced
by using the KeyAgg algorithm on all KEYs specified in the expression after performing all
specified derivation. As with script expressions, KEY can contain child derivation specified by
@@ -118,6 +119,11 @@ are likely to be familiar with them.
The reference implementation is available in Bitcoin Core [[https://github.com/bitcoin/bitcoin/pull/31244|PR #31244]].
+==Changelog==
+
+* __0.2.0__ (2026-03-04) - Allow musig() inside sp() expressions.
+* __0.1.0__ (2025-12-08) - Allow musig() inside rawtr() expressions.
+
==Acknowledgements==
Thanks to Pieter Wuille, Andrew Poelstra, Sanket Kanjalkar, Salvatore Ingala, and all others who
diff --git a/bip-0392.mediawiki b/bip-0392.mediawiki
new file mode 100644
index 0000000000..c1424bfdd2
--- /dev/null
+++ b/bip-0392.mediawiki
@@ -0,0 +1,114 @@
++ BIP: 392 + Layer: Applications + Title: Silent Payment Output Script Descriptors + Authors: Craig Raw+ +==Abstract== + +This document specifies sp() output script descriptors for silent payments. +sp() descriptors take silent payment key material and describe P2TR outputs when combined with sender input public keys as defined in BIP352. + +==Copyright== + +This BIP is licensed under the BSD 2-clause license. + +==Motivation== + +BIP352 defines silent payments, a protocol for static payment addresses without on-chain linkability. +This descriptor provides a standardized way to represent silent payment outputs within the output descriptor framework, enabling wallet interoperability and backup/recovery using existing descriptor-based infrastructure. + +==Specification== + +A new top level script expression is defined: sp(). + +===Key Expressions=== + +Two new key expression types are defined for use with sp() descriptors: + +====spscan==== + +The spscan key expression encodes the scan private key and spend public key. +It is a [https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki Bech32m] encoding of: +* The human-readable part "spscan" for mainnet, "tspscan" for testnets +* The data-part values: +** The character "q", to represent silent payments version 0 +** The payload: ser256(bscan) || serP(Bspend) + +====spspend==== + +The spspend key expression encodes both the scan and spend private keys. +It is a [https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki Bech32m] encoding of: +* The human-readable part "spspend" for mainnet, "tspspend" for testnets +* The data-part values: +** The character "q", to represent silent payments version 0 +** The payload: ser256(bscan) || ser256(bspend) + +Note: The serialization of ser256(p) and serP(P) follows the definition in BIP352. + +===sp()=== + +The sp(KEY) or sp(KEY,KEY) expression can only be used as a top level descriptor. + +sp(KEY) takes a single key expression as an argument, which must be either an spscan or spspend encoded key, optionally with key origin information. +If included, the key origin information specifies the fingerprint and derivation path to the depth from which the scan and spend keys are derived using the child paths recommended in BIP352 (1h/0 for scan, 0h/0 for spend). + +sp(KEY,KEY) takes two key expressions. +The first key expression represents the scan key and must be a single private key (e.g. a WIF compressed private key or xprv extended private key). +The second key expression represents the spend key and may be any key expression as defined in BIP380 or other key expression BIPs (e.g. musig() as defined in BIP390) that represents a single key, public or private. +Note however that uncompressed keys are not allowed under any sp() expression, as BIP352 only permits compressed public keys. + +When combined with sender input public keys, the descriptor produces P2TR output scripts describing silent payments made to wallets represented by the key expression(s). + +The output scripts produced are BIP341 taproot outputs as specified in BIP352. + +==Examples== + +Valid descriptors: + +* sp(spscan1q...) - Using spscan encoded key (watch-only) +* sp([deadbeef/352h/0h/0h]spscan1q...) - With key origin +* sp(spspend1q...) - Using spspend encoded key (full wallet) +* sp(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600) - WIF scan key with compressed public spend key (watch-only) +* sp([deadbeef/352h/0h/0h]xprv.../0h,xpub.../0h) - Extended private scan key with extended public spend key (watch-only) +* sp([deadbeef/352h/0h/0h]xprv.../0h,xprv.../0h) - Extended private keys for both scan and spend (full wallet) +* sp(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,musig(03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659,023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66)) - WIF scan key with MuSig2 aggregate spend key (watch-only) + +Invalid descriptors: + +* sp() requires at least one key expression +* sp(xpub...) single argument form requires spscan or spspend encoded key +* sp(xpub...,xpub...) two argument form requires private scan key (e.g. WIF or xprv) +* sp(spscan1q...,spscan1q...) two argument form requires single key expressions +* sp(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600) uncompressed private key +* sh(sp(spscan1q...)) sp() is top level only +* wsh(sp(spscan1q...)) sp() is top level only + +==Usage Notes== + +For watch-only wallets, use spscan encoding or the two argument form with a public spend key. +For full wallets that can both scan and spend, use spspend encoding or the two argument form with a private spend key. + +When using the two argument form, the scan key must be private (e.g. WIF or xprv) since scanning requires the private scan key. + +==Backwards Compatibility== + +sp() descriptors use the format and general operation specified in BIP380. +As this is a wholly new descriptor, it is not compatible with any prior implementation. +The scripts produced are BIP341 taproot outputs, making them indistinguishable from other taproot outputs on-chain. + +==Reference Implementation== + +TBD + +==Test Vectors== + +TBD + diff --git a/bip-0434.md b/bip-0434.md new file mode 100644 index 0000000000..afa770e168 --- /dev/null +++ b/bip-0434.md @@ -0,0 +1,322 @@ +``` + BIP: 434 + Layer: Peer Services + Title: Peer Feature Negotiation + Authors: Anthony Towns+ Status: Draft + Type: Specification + Assigned: 2026-02-06 + License: BSD-2-Clause + Discussion: https://groups.google.com/g/bitcoindev/c/bP6ktUyCOJI + Requires: 341, 350, 352, 380 +