From 645c8716873baa4ee80159ee40f1b15d645e5462 Mon Sep 17 00:00:00 2001 From: josibake Date: Wed, 8 Mar 2023 18:45:58 +0100 Subject: [PATCH] BIP for Silent Payments --- bip-0352.mediawiki | 477 +++++ bip-0352/bech32m.py | 135 ++ bip-0352/reference.py | 219 +++ bip-0352/scan_data_downloader_per_month.png | Bin 0 -> 54276 bytes bip-0352/secp256k1.py | 681 ++++++++ bip-0352/send_and_receive_test_vectors.json | 1743 +++++++++++++++++++ 6 files changed, 3255 insertions(+) create mode 100644 bip-0352.mediawiki create mode 100644 bip-0352/bech32m.py create mode 100755 bip-0352/reference.py create mode 100644 bip-0352/scan_data_downloader_per_month.png create mode 100644 bip-0352/secp256k1.py create mode 100644 bip-0352/send_and_receive_test_vectors.json diff --git a/bip-0352.mediawiki b/bip-0352.mediawiki new file mode 100644 index 0000000000..24151ab975 --- /dev/null +++ b/bip-0352.mediawiki @@ -0,0 +1,477 @@ +
+  BIP: 352
+  Layer: Applications
+  Title: Silent Payments
+  Author: josibake 
+          Ruben Somsen 
+  Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0352
+  Status: Draft
+  Type: Informational
+  Created: 2023-03-09
+  License: BSD-2-Clause
+
+ +== Introduction == + +=== Abstract === + +This document specifies a protocol for static payment addresses in Bitcoin without on-chain linkability of payments or a need for on-chain notifications. + +=== Copyright === + +This BIP is licensed under the BSD 2-clause license. + +=== Motivation === + +Using a new address for each Bitcoin transaction is a crucial aspect of maintaining privacy. This often requires a secure interaction between sender and receiver so that the receiver can hand out a fresh address, a batch of fresh addresses, or a method for the sender to generate addresses on-demand, such as an xpub. + +However, interaction is often infeasible and in many cases undesirable. To solve for this, various protocols have been proposed which use a static payment address and notifications sent via the blockchain'''Why not use out-of-band notifications''' Out of band notifications (e.g. using something other than the Bitcoin blockchain) have been proposed as a way of addressing the privacy and cost concerns of using the Bitcoin blockchain as a messaging layer. This, however, simply moves the privacy and cost concerns somewhere else and increases the risk of losing money due to a notification not being reliably delivered, or even censored, and makes this notification data critical for backup to recover funds.. These protocols eliminate the need for interaction, but at the expense of increased costs for one-time payments and a noticeable footprint in the blockchain, potentially revealing metadata about the sender and receiver. Notification schemes also allow the receiver to link all payments from the same sender, compromising sender privacy. + +This proposal aims to address the limitations of these current approaches by presenting a solution that eliminates the need for interaction, eliminates the need for notifications, and protects both sender and receiver privacy. These benefits come at the cost of requiring wallets to scan the blockchain in order to detect payments. This added requirement is generally feasible for full nodes but poses a challenge for light clients. While it is possible today to implement a privacy-preserving light client at the cost of increased bandwidth, light client support is considered an area of open research (see [[#appendix-a-light-client-support|Appendix A: Light Client Support]]). + +== Goals == + +We aim to present a protocol which satisifies the following properties: + +* No increase in the size or cost of transactions +* Resulting transactions blend in with other bitcoin transactions and can't be distinguished +* Transactions can't be linked to a silent payment address by an outside observer +* No sender-receiver interaction required +* No linking of multiple payments to the same sender +* Each silent payment goes to a unique address, avoiding accidental address reuse +* Supports payment labeling +* Uses existing seed phrase or descriptor methods for backup and recovery +* Separates scanning and spending responsibilities +* Compatible with other spending protocols, such as CoinJoin +* Light client/SPV wallet support +* Protocol is upgradeable + +== Overview == + +We first present an informal overview of the protocol. In what follows, uppercase letters represent public keys, lowercase letters represent private keys, ''||'' refers to byte concatenation, and ''G'' represents the generator point for ''secp256k1''. Each section of the overview is incomplete on its own and is meant to build on the previous section in order to introduce and briefly explain each aspect of the protocol. For the full protocol specification, see [[#specification|Specification]]. + +''' Simple case ''' + +Bob publishes a public key ''B'' as a silent payment address. Alice discovers Bob's silent payment address, selects a UTXO with private key ''a'', public key ''A'' and creates a destination output ''P'' for Bob in the following manner: + +* Let ''P = B + hash(a·B)·G'' +* Encode ''P'' as a [https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki BIP341] taproot output + +Since ''a·B == b·A'' ([https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman Elliptic-curve Diffie–Hellman]), Bob scans with his private key ''b'' by collecting the input public keys for each transaction with at least one unspent taproot output and performing the ECDH calculation until ''P'' is found (i.e. calculating ''P = B + hash(b·A)·G'' and seeing that ''P'' is present in the transaction outputs). + +''' Creating more than one output ''' + +In order to allow Alice to create more than one output for Bob'''Why allow for more than one output?''' Allowing Alice to break her payment to Bob into multiple amounts opens up a number of privacy improving techniques for Alice, making the transaction look like a CoinJoin or better hiding the change amount by splitting both the payment and change outputs into multiple amounts. It also allows for Alice and Carol to both have their own unique output paying Bob in the event they are in a collaborative transaction and both paying Bob's silent payment address., we include an integer in the following manner: + +* Let ''k = 0'' +* Let ''P0 = B + hash(a·B || n)·G'' +* For additional outputs: +** Increment ''k'' by one (''k++'') +** Let ''Pi = B + hash(a·B || n)·G'' + +Bob detects this output the same as before by searching for ''P0 = B + hash(b·A || 0)·G''. Once he detects the first output, he must: + +* Check for ''P1 = B + hash(b·A || 1)·G'' +* If ''P1'' is not found, stop +* If ''P1'' is found, continue to check for ''P2'' and so on until an additional output is not found + +Since Bob will only perform these subsequent checks after a transaction with at least one output paying him is found, the increase to his overall scanning requirement is negligible. + +''' Preventing address reuse ''' + +If Alice were to use a different UTXO from the same public key ''A'' for a subsequent payment to Bob, she would end up deriving the same destination ''P''. To prevent this, Alice should include a hash of the outpoint in the following manner: + +* Let ''outpoint_hash = hash(txid || vout)'' +* Let ''P0 = B + hash(outpoint_hash·a·B || 0)·G'' + +Bob must include the same ''outpoint_hash'' when scanning. + +''' Using all inputs ''' + +In our simplified example we have been referring to Alice's transactions as having only one input ''A'', but in reality a Bitcoin transaction can have many inputs. Instead of requiring Alice to pick a particular input and requiring Bob to check each input separately, we can instead require Alice to perform the tweak with the sum of the input public keys'''What about inputs without public keys?''' Inputs without public keys can still be spent in the transaction but are simply ignored in the silent payments protocol.. This significantly reduces Bob's scanning requirement, makes light client support more feasible'''How does using all inputs help light clients?''' If Alice uses a random input for the tweak, Bob necessarily has to have access to and check all transaction inputs, which requires performing an ECC multiplication per input. If instead Alice performs the tweak with the sum of the input public keys, Bob only needs the summed 33 byte public key per transaction and only does one ECC multiplication per transaction. Bob can then use BIP158 block filters to determine if any of the outputs exist in a block and thus avoids downloading transactions which don't belong to him. It is still an open question as to how Bob can source the 32 bytes per transaction in a trustless manner, see [[#appendix-a-light-client-support|Appendix A: Light Client Support]] for more details., and protects Alice's privacy in collaborative transaction protocols such as CoinJoin'''Why does using all inputs matter for CoinJoin?''' If Alice uses a random input to create the output for Bob, this necessarily reveals to Bob which input Alice has control of. If Alice is paying Bob as part of a CoinJoin, this would reveal which input belongs to her, degrading the anonymity set of the CoinJoin and giving Bob more information about Alice. If instead all inputs are used, Bob has no way of knowing which input(s) belong to Alice. This comes at the cost of increased complexity as the CoinJoin participants now need to coordinate to create the silent payment output and would need to use [https://gist.github.com/RubenSomsen/be7a4760dd4596d06963d67baf140406 Blind Diffie–Hellman] to prevent the other participants from learning who Alice is paying.. + +Alice performs the tweak with the sum of her input private keys in the following manner: + +* Let ''outpoints_hash = hash(txid0 || vout0 || … txidn || voutn)'' +* Let ''a = a0 + a1 … + an'' +* Let ''P0 = B + hash(outpoints_hash·a·B || 0)·G'' + +''' Spend and Scan Key ''' + +Since Bob needs his private key ''b'' to check for incoming payments, this requires ''b'' to be exposed to an online device. To minimize the risks involved, Bob can instead publish an address of the form ''(Bscan, Bspend)''. This allows Bob to keep ''bspend'' in offline cold storage and perform the scanning with the public key ''Bspend'' and private key ''bscan''. Alice performs the tweak using both of Bob's public keys in the following manner: + +* Let ''P0 = Bspend + hash(outpoints_hash·a·Bscan || 0)·G'' + +Bob detects this payment by calculating ''P0 = Bspend + hash(outpoints_hash·bscan·A)·G'' with his online device and can spend from his cold storage signing device using ''(bspend + hash(outpoints_hash·bscan·A)) mod p'' as the private key. + +''' Labels ''' + +For a single silent payment address of the form ''(Bscan, Bspend)'', Bob may wish to differentiate incoming payments. Naively, Bob could publish multiple silent payment addresses, but this would require him to scan for each one, which becomes prohibitively expensive. Instead, Bob can label his spend public key ''Bspend'' with an integer ''m'' in the following way: + +* Let ''Bm = Bspend + m·G'' +* Publish ''(Bscan, B0)'', ''(Bscan, B1)'' etc. + +Alice performs the tweak as before using one of the published ''(Bscan, Bm)'' pairs. Bob detects the labeled payment in the following manner: + +* Let ''P0 = Bspend + hash(outpoints_hash·bscan·A || 0)·G'' +* Subtract ''P0'' from each of the transaction outputs and check if the remainder matches any of the labels (''1·G'', 2·G'' etc.) that the wallet has previously used + +It is important to note that an outside observer can easily deduce that each published ''(Bscan, Bm)'' pair is owned by the same entity as each published address will have ''Bscan'' in common. As such, labels are not meant as a way for Bob to manage separate identities, but rather a way for Bob to determine the source of an incoming payment. + +''' Labels for change ''' + +Bob can also use labels for managing his own change outputs. To do so, he can reserve a secret change label in the following manner: + +* Let ''Bchange = Bspend + hash(bscan)·G'' + +Now, whenever Bob is spending (to a silent payment address or otherwise), he can create a change output for himself using the silent payments protocol and his change label in the following manner: + +* Let ''a = a0 + a1 … + an'' represent the private keys of the inputs Bob is using to fund the transaction +* Let ''Pchange = Bchange + hash(outpoints_hash·a·Bscan || 0)·G'' + +This gives Bob an alternative to using BIP32 for managing change, while still allowing him to know which of his unspent outputs were change when recovering his wallet from the master key. The change label needs to remain a secret in order to ensure nobody else can label payments as change. + +== Specification == + +We use the following functions and conventions: + +* The constant ''n'' refers to the secp256k1 curve order, ''0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141''. +* ''outpoint'' (36 bytes): the COutPoint of an input (32-byte txid, least significant byte first || 4-byte vout, least significant byte first)'''Why are outpoints little-endian?''' Despite using big endian throughout the rest of the BIP, outpoints are sorted and hashed matching their transaction serialization, which is little-endian. This allows a wallet to parse a serialized transaction for use in silent payments without needing to re-order the bytes when computing the outpoint hash. Note: despite outpoints being stored and serialized as little-endian, the transaction hash (txid) is always displayed as big-endian. +* ser32(i): serializes 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. + +For everything not defined above, we use the notation from [https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#specification BIP340]. + +=== Versions === + +This document defines version 0 (''sp1q''). Version is communicated through the address in the same way as Segwit addresses. Future upgrades to silent payments will require a new version. As much as possible, future upgrades should support receiving from older wallets (e.g. a silent payments v0 wallet can send to both v0 and v1 addresses). Any changes that break compatibility with older silent payment versions should be a new BIP. + +Future silent payments versions will use the following scheme: + +{| class="wikitable" +|- +! +!0 +!1 +!2 +!3 +!4 +!5 +!6 +!7 +!Compatibility +|- +!+0 +|q||p||z||r||y||9||x||8||rowspan="4" | backwards compatible +|- +!+8 +|g||f||2||t||v||d||w||0 +|- +!+16 +|s||3||j||n||5||4||k||h +|- +!+24 +|c||e||6||m||u||a||7|| - +|} + +''v31'' (l) is reserved for a backwards incompatible change, if needed. For silent payments v0: + +* If the receiver's silent payment address version is: +** ''v0'': check that the data part is exactly 66-bytes. Otherwise, fail +** ''v1'' through ''v30'': read the first 66-bytes of the data part and discard the remaining bytes (if any) +** ''v31'': fail +* Receiver addresses are always [https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki BIP341] taproot outputs'''Why only taproot outputs?''' Providing too much optionality for the protocol makes it difficult to implement and can be at odds with the goal of providing the best privacy. Limiting to taproot outputs helps simplify the implementation significantly while also putting users in the best eventual anonymity set. +* The sender should sign with one of the sighash flags ''DEFAULT'', ''ALL'', ''SINGLE'', ''NONE'' (''ANYONECANPAY'' is unsafe). It is strongly recommended implementations use ''SIGHASH_ALL'' (''SIGHASH_DEFAULT'' for taproot inputs) when possible'''Why is it unsafe to use ''SIGHASH_ANYONECANPAY''?''' Since the output address for the receiver is derived from from the sum of the [[#inputs-for-shared-secret-derivation|Inputs For Shared Secret Derivation]] public keys, the inputs must not change once the sender has signed the transaction. If the inputs are allowed to change after the fact, the receiver will not be able to calculate the shared secret needed to find and spend the output. It is currently an open question on how a future version of silent payments could be made to work with new sighash flags such as ''SIGHASH_GROUP'' and ''SIGHASH_ANYPREVOUT''. +* Inputs used to derive the shared secret are from the ''[[#inputs-for-shared-secret-derivation|Inputs For Shared Secret Derivation]]'' list + +=== Scanning transactions === + +A v0 transaction MUST be scanned if and only if all of the following are true: + +* The transaction contains at least one BIP341 taproot output +* The transaction has at least one input from the ''[[#inputs-for-shared-secret-derivation|Inputs For Shared Secret Derivation]]'' list +* The transaction does not spend a new, undefined output type (e.g. SegWit versions > 1)'''Why skip transactions that spend unknown output scripts?''' Skipping transactions that spend unknown output scripts allows us to have a clean upgrade path for silent payments by avoiding the need to scan the same transaction multiple times with different rule sets. If a fancy new output type is added in the future and silent payments v1 is released with support, we would want to avoid having to first scan the transaction with the silent payment v0 rules and then again with the silent payment v1 rules. Note: this restriction only applies to the inputs of a transaction. + +Otherwise, skip the transaction. This is to ensure forward compatibility with future versions of silent payments without requiring future versions to scan a transaction multiple times with different rule sets. + +=== Address encoding === + +A silent payment address is constructed in the following manner: + +* Let ''Bscan, bscan = Receiver's scan public key and corresponding private key'' +* Let ''Bspend, bspend = Receiver's spend public key and corresponding private key'' +* Let ''Bm = Bspend + m·G'', where ''m'' an optional integer tweak for labeling +** In the case of ''m'' = 0, no label is applied and ''Bm = Bspend'' +* The final address is a [https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki Bech32m] encoding of: +** The human-readable part "sp" for mainnet, "tsp" for testnets (e.g. signet, testnet) +** The data-part values: +*** The character "q", to represent a silent payment address of version 0 +*** The 66 byte concatenation of the receiver's public keys, ''serP(Bscan) || serP(Bm)'' + +Note: [https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki BIP173] imposes a 90 character limit for Bech32 strings, whereas a silent payment address requires ''at least'' 117 characters ''' Why do silent payment addresses need at least 117 characters?''' A silent payment address is a bech32m encoding comprised of the following parts: + + +* HRP [2-3 characters] +* separator [1 character] +* version [1-2 characters] +* payload, 66 bytes concatenated pubkeys [ceil(66*8/5) = 106 characters] +* checksum [6 characters] + + +For a silent payments v0 address, this results in a 117 character address when using a 3 character HRP. Future versions of silent payment addresses may add to the payload, which is why a 1023 character limit is suggested.. Additionally, since higher versions may add to the data field, it is recommended implementations use a limit of 1023 characters (see [https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#checksum-design BIP173: Checksum design] for more details). + +=== Outpoints hash === + +The sender and receiver MUST calculate an outpoints hash for the transaction in the following manner: + +* Collect each ''outpoint'' used as an input to the transaction +* Let ''outpoints = outpoint0 || … || outpointn'', sorted lexicographically by txid and vout, ascending order'''Why are outpoints sorted before hashing?''' This way the silent payment output does not need to be recalculated if the wallet changes the order of inputs, e.g. at signing time or during an RBF bump. +* Let ''outpoints_hash = sha256(outpoints)'' + +=== Inputs For Shared Secret Derivation === + +While any UTXO with known output scripts can be used to fund the transaction, the sender and receiver MUST use inputs from the following list when deriving the shared secret: + +* ''P2TR'' +* ''P2WPKH'' +* ''P2SH-P2WPKH'' +* ''P2PKH'' + +Inputs with conditional branches or multiple public keys (e.g. ''CHECKMULTISIG'') are not included as this introduces malleability and would allow a sender to re-sign with a different set of public keys after the silent payment output has been derived. This is not a concern when the sender controls all of the inputs, but is an issue for CoinJoins and other collaborative protocols, where a malicious participant can participate in deriving the silent payment address with one set of keys and then re-broadcast the transaction with signatures for a different set of public keys. P2TR can have hidden conditional branches (script path), but we work around this by using only the output public key. + +''' P2TR ''' + +The sender MUST use the private key corresponding to the taproot output key (i.e. the tweaked private key for a key path spend). This can be a single private key or an aggregate key (e.g. taproot outputs using MuSig2 or FROST)'''Are key aggregation techniques like FROST and MuSig2 supported?''' Any taproot output able to do a key path spend is supported. While a full specification of how to do this securely is outside the scope of this BIP, in theory any offline key aggregation technique can be used, such as FROST or MuSig2. This would require participants to perform the ECDH step collaboratively e.g. ''ECDH = a0·Bscan + a1·Bscan + ... + at·Bscan'' and ''P = Bspend + hash(outpoints_hash·ECDH || 0)·G''. Additionally, it may be necessary for the participants to provide a DLEQ proof to ensure they are not acting maliciously.. If this key is not available, the output cannot be included as an input to the transaction. The receiver always uses the taproot output key when scanning, regardless of whether the taproot output is using a key path spend or a script path spend''' Why not skip all taproot script path spends? ''' This causes malleability issues for CoinJoins. If the silent payments protocol skipped taproot script path spends, this would allow an attacker to join a CoinJoin round, participate in deriving the silent payment address using the tweaked private key for a key path spend, and then broadcast their own version of the transaction using the script path spend. If the receiver were to only consider key path spends, they would skip the attacker's script path spend input when deriving the shared secret and not be able to find the funds. Additionally, there may be scenarios where a sender has access to the key path private key but spends the output using the script path.. + +The one exception is script path spends that use NUMS point ''H'' as their internal key (where ''H = lift_x(0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0)'' which is constructed by taking the hash of the standard uncompressed encoding of the secp256k1 base point ''G'' as X coordinate, see [https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs BIP341: Constructing and spending Taproot outputs] for more details), in which case the output will be skipped for the purposes of shared secret derivation'''Why skip outputs with H as the internal taproot key?''' If use cases get popularized where the taproot key path cannot be used, these outputs can still be included without getting in the way of making a silent payment, provided they specifically use H as their internal taproot key.. + +''' P2WPKH ''' + +The sender performs the tweak using the private key for the output and the receiver obtains the public key from the ''witness''. + +''' P2SH-P2WPKH ''' + +The sender performs the tweak using the private key for the nested ''P2WPKH'' output and the receiver obtains the public key from the ''witness''. + +''' P2PKH ''' + +The sender performs the tweak using the private key for the output and SHOULD sign using the standard script template: + + + scriptSig: + + +The receiver obtains the public key from the ''scriptSig''. The receiver MUST parse the ''scriptSig'' for the public key, even if the ''scriptSig'' is non-standard (e.g. OP_DROP ). This is to address the [https://en.bitcoin.it/wiki/Transaction_malleability third-party malleability of ''P2PKH'' ''scriptSigs'']. + +=== Sender === + +==== Selecting inputs ==== + +The sending wallet performs coin selection as usual with the following restrictions: + +* At least one input MUST be from the ''[[#inputs-for-shared-secret-derivation|Inputs For Shared Secret Derivation]]'' list +* Exclude inputs with witness version > 1 (see ''[[#scanning-transactions|Scanning transactions]]'') +* For each taproot output spent the sending wallet MUST have access to the private key corresponding to the taproot output key, unless ''H'' is used as the internal public key + +==== Creating outputs ==== + +After the inputs have been selected, the sender can create one or more outputs for one or more silent payment addresses in the following manner: + +* Collect the private keys for each input from the ''[[#inputs-for-shared-secret-derivation|Inputs For Shared Secret Derivation]]'' list +* For each private key ''ai'' corresponding to a [https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki BIP341] taproot output, check that the private key produces a point with an even y-value and negate the private key if not'''Why do taproot private keys need to be checked?''' Recall from [https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki BIP340] that each x-only public key has two corresponding private keys, ''d'' and ''n - d''. To maintain parity between sender and receiver, it is necessary to use the private key corresponding to the even y-value when performing the ECDH step since the receiver will assume the even y-value when summing the taproot x-only public keys. +* Let ''a = a0 + a1 + … an'', where each ''ai'' has been negated if necessary +* Generate the ''outpoints_hash'', using the method described above +* Group receiver silent payment addresses by ''Bscan'' (e.g. each group consists of one ''Bscan'' and one or more ''Bm'') +* For each group: +** Let ''ecdh_shared_secret = outpoints_hash·a·Bscan'' +** Let ''k = 0'' +** For each ''Bm'' in the group: +*** Let ''tk = sha256(serP(ecdh_shared_secret) || ser32(k))'' +*** Let ''Pmn = Bm + tk·G'' +*** Encode ''Pmn'' as a [https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki BIP341] taproot output +*** Optionally, repeat with k++ to create additional outputs for the current ''Bm'' +*** If no additional outputs are required, continue to the next ''Bm'' with ''k++''''' Why not re-use ''tk'' when paying different labels to the same receiver?''' If paying the same entity but to two separate labeled addresses in the same transaction without incrementing ''k'', the two outputs would be ''Bspend + tk·G + i·G'' and ''Bspend + tk·G + j·G''. The attacker could subtract the two values and observe that the distance between i and j is small. This would allow them to deduce that this transaction is a silent payment transaction and that a single entity received two outputs, but won't tell them who the entity is. +** Optionally, if the sending wallet implements receiving silent payments, it can create change outputs in the following manner: +*** Let ''Achange = Aspend + sha256(ser256(ascan))·G'' +*** Let ''change_shared_secret = outpoints_hash·a·Ascan'' +*** Let ''k = 0'' +*** For each change output desired: +**** Let ''ck = sha256(serP(change_shared_secret) || ser32(k))'' +**** Let ''Ck = Achange + ck·G'' +**** Encode ''Ck'' as a [https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki BIP341] taproot output +**** Repeat with ''k++'' for additional change outputs + +=== Receiver === + +==== Key Derivation ==== + +Two keys are needed to create a silent payments address: the spend key and the scan key. While these keys can be generated independently, wallet software SHOULD use BIP32 derivation'''Why use BIP32 hardened derivation?''' Using BIP32 derivation allows users to add silent payments to an existing master seed. It also ensures that a user's silent payment funds are recoverable in any wallet which supports BIP32 derivation. Using hardened derivation ensures that it is safe to export the scan private key without exposing the master key or spend private key. to ensure compatibility across wallets. + +A scan and spend key pair using BIP32 derivation are defined (taking inspiration from [https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki BIP44]) in the following manner: + + + Scan private key: m / purpose' / coin_type' / account' / 1' / 0 + Spend private key: m / purpose' / coin_type' / account' / 0' / 0 + +Wallet software MUST use hardened derivation to ensure the master key is not exposed in the event the scan private key is compromised. Purpose is a constant set to ''352'' following the BIP43 recommendation. Refer to [https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki BIP43] and [https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki BIP44] for more details. + +==== Scanning ==== + +If each of the checks in ''[[#scanning-transactions|Scanning transactions]]'' passes, the receiving wallet must: + +* Generate the ''outpoints_hash'', using the method described above +* Let ''A = A0 + A1 + … An'', where each ''Ai'' is the public key of an input from the ''[[#inputs-for-shared-secret-derivation|Inputs For Shared Secret Derivation]]'' list +* Let ''ecdh_shared_secret = outpoints_hash·bscan·A'' +* Check for outputs: +** Let ''outputs_to_check'' be the taproot output keys from all unspent taproot outputs in the transaction +** Starting with ''k = 0'': +*** Let ''tk = sha256(serP(ecdh_shared_secret) || ser32(k))'' +*** Compute ''Pk = Bspend + tk·G'' +*** For each ''output'' in ''outputs_to_check'': +**** If ''Pk'' equals ''output'': +***** Add ''Pk'' to the wallet +***** Remove ''output'' from ''outputs_to_check'' and rescan ''outputs_to_check'' with ''k++'' +**** Else, if the wallet has precomputed labels (including the change label, if used)''' Why precompute labels?''' Precomputing the labels is not strictly necessary: a wallet could track the max number of labels it has used (call it ''M'') and scan for labels by adding ''m·G'' to ''P0'' for each label ''m'' up to ''M'' and comparing to the transaction outputs. This is more performant than precomputing the labels and checking via subtraction in cases where the number of eligible outputs exceeds the number of labels in use. In practice this will mainly apply to users that choose never to use labels, or users that use a single label for generating silent payment change outputs. If using a large number of labels, the wallet would need to add all possible labels to each output. This ends up being ''n·M'' additions, where ''n'' is the number of outputs in the transaction and ''M'' is the number of labels in the wallet. By precomputing the labels, the wallet only needs to compute ''m·G'' once when creating the labeled address and can determine if a label was used via a lookup, rather than adding each label to each output.: +***** Compute ''m·G = output - Pk'' +***** Check if ''m·G'' exists in the list of labels used by the wallet +***** If a match is found: +****** Add the ''Pk + m·G'' to the wallet +****** Remove ''output'' from ''outputs_to_check'' and rescan ''outputs_to_check'' with ''k++'' +***** If the label is not found, negate ''output'' and check again +*** If no matches are found, stop + +==== Spending ==== + +Recall that a silent payments is of the form ''Bspend + tk·G + m·G'', where ''m·G'' is an optional label. To spend a silent payments output: + +* Let ''d = (bspend + tk + m) mod n'', where ''m'' is the optional label integer encoded as a 256-bit number +* Spend the [https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki BIP341] output with the private key ''d'' + +==== Backup and Recovery ==== + +Since each silent payment output address is derived independently, regular backups are recommended. When recovering from a backup, the wallet will need to scan since the last backup to detect new payments. + +If using a seed/seed phrase only style backup, the user can recover the wallet's unspent outputs from the UTXO set (i.e. only scanning transactions with at least one unspent taproot output) and can recover the full wallet history by scanning the blockchain starting from the wallet birthday. If a wallet uses labels or generates its change addresses using the change label, this information SHOULD be included in the backup. If the user does not know whether labels or the change label were used, it is strongly recommended they always check for the change label when recovering from backup and precompute a large number of labels (e.g. 100k labels) to use when re-scanning. This ensures that the wallet can recover all funds from only a seed/seed phrase backup. + +== Backward Compatibility == + +Silent payments introduces a new address format and protocol for sending and as such is not compatible with older wallet software or wallets which have not implemented the silent payments protocol. + +== Test Vectors == + +A [[bip-0352/send_and_receive_test_vectors.json|collection of test vectors in JSON format]] are provided, along with a [[bip-0352/reference.py|python reference implementation]]. Each test vector consists of a sending test case and corresponding receiving test case. This is to allow sending and receiving to be implemented separately. To ensure determinism while testing, sort the array of ''Bm'' by amount (see the [[bip-0352/reference.py|reference implementation]]). Test cases use the following schema: + +''' test_case ''' + + { + "comment": "Comment describing the behavior being tested", + "sending": [], + "receiving": [], + } + +''' sender ''' + + { + "given": { + "outpoints": [], + "input_priv_keys": [], + "recipients": [] + }, + "expected": { + "outputs": [ + "given": { + "outpoints": [], + "input_pub_keys": [], + "bip32_seed": "byte data for generating a bip32 master seed", + "scan_priv_key": , + "spend_priv_key": , + "labels": {}, + }, + "expected": { + "addresses": [], + "outputs": [ + { + "pub_key": , + "priv_key_teak": . + "signature": + }, + ... + ] + } + } + +Wallets should include inputs not in the ''[[#inputs-for-shared-secret-derivation|Inputs For Shared Secret Derivation]]'' list when testing to ensure that only inputs from the list are being used for shared secret derivation. Additionally, receiving wallets should include non-silent payment outputs for themselves in testing to ensure silent payments scanning does not interfere with regular outputs detection. + +=== Functional tests === + +Below is a list of functional tests which should be included in sending and receiving implementations. + +==== Sending ==== + +* Ensure taproot outputs are excluded during coin selection if the sender does not have access to the key path private key (unless using ''H'' as the taproot internal key) +* Ensure the silent payment address is re-derived if inputs are added or removed during RBF + +==== Receiving ==== + +* Ensure the public key can be extracted from non-standard ''P2PKH'' scriptSigs +* Ensure taproot script path spends are included, using the taproot output key (unless ''H'' is used as the taproot internal key) +* Ensure the scanner can extract the public key from each of the input types supported (e.g. ''P2WPKH'', ''P2SH-P2WPKH'', etc) + +== Appendix A: Light Client Support == + +This section proposes a few ideas for how light clients could be supported in ways that preserve bandwidth and privacy. While this is out of scope for the current BIP, it is included to movitate further research into this topic. In this context, a light client refers to any bitcoin wallet client which does not process blocks and does not have a direct connection to a node which does process blocks (e.g. a full node). Based on this definition, clients that directly connect to a personal electrum server or a bitcoin node are not light clients. + +This distinction makes the problem for light clients more clear: light clients need a way to source the necessary data for performing the tweaks and a way of determining if any of the generated outputs exist in a block. + +=== Tweak Data === + +Recall that a silent payment eligible transaction is any transaction with at least one input from the ''Inputs for Shared Secret Derivation'' list and at least one unspent taproot output. Full nodes (or any index server backed by a full node, such as electrum server) can build an index which collects all of the eligible public keys for a silent payments eligible transaction, sums them up, multiplies the sum by the ''outpoints_hash'', and serves them to clients. This would be 32 bytes per silent payment eligible transaction. + +For a typical bitcoin block of ~3500 txs, lets assume every transaction is a silent payments eligible transaction. This means a client would need to request ''32 bytes * 3500'' of data per block (roughly 100 kb per block). If a client were to request data for every block, this would amount to ~450MB per month, assuming 100% taproot usage and all outputs remain unspent for > 1 month. As of today, these numbers are closer to 2kb - 10kb per block (10MB - 40MB per month)''' Data for Appendix A ''' These numbers are based on data from January 2023 until June 2023 (the last 6 months of data at time time of writing). See [https://github.com/josibake/bitcoin-data-analysis/blob/main/notebooks/silent-payments-light-client-data.ipynb Silent payments light client data] for the full analysis.. + +=== Transaction cut-through === + +It is unlikely a light client would need to scan every block and as such can take advantage of transaction cut-through, depending on how often they choose to scan for new blocks. Empirically, ~75% of transactions with at least one unspent taproot output will have spent all taproot UTXOs in 326 blocks or less. This means a client which only scans once every 3 days could ''significantly'' cut down on the number of blocks and the number of transactions per block that they need to request by only asking for data on transactions that were created since their last scan and that still have at least one unspent taproot output as of the current block height. Assuming 100% taproot usage, a client that scans once a month would likely only need around 50MB worth of data. Based on current taproot adoption, a light client scanning once every 3 days would use roughly 15MB per month and a client scanning once per month would use less than 5MB per month. + +[[File:bip-0352/scan_data_downloader_per_month.png]] + +=== BIP158 === + +Once a light client has the tweak data for a block, they can determine whether or not an output to them exists in the block using BIP158 block filters. Per BIP158, they would then request the entire block and add the transaction to their wallet, though it maybe be possible to only request the prevout txids and vouts for all transactions with at least one taproot output, along with the scriptPubKeys and amounts. This would allow the client to download the necessary data for constructing a spending transaction, without downloading the entire block. How this affects the security assumptions of BIP158 is an open question. + +=== Out-of-band notifications === + +Assuming a secure messaging protocol exists, the sender can send an encrypted (using the scan public key of the silent payment address) notification to the receiver with the following information: +* The spend public key (communicates the label) +* The shared secret portion of the private key (i.e ''hash(ecdh_shared_secret || n)'') +* The outpoint and amount (so it's immediately spendable) + +It is important to note that these notifications are not required. At any point, the receiver can fall back to scanning for silent payment transactions if they don't trust the notifications they are receiving, are being spammed with fake notifications, or if they are concerned that they are not receiving notifications. + +A malicious notification could potentially cause the following issues: + +* You did not actually receive money to the stated key +** This can be probabilistically resolved by matching the key against the BIP158 block filters and assuming it's not a false positive, or fully resolved by downloading the block +* You received money but the outpoint or amount is incorrect, so attempts to spend it will fail or cause you to overpay fees +** There doesn't seem to be much motivation for malicious senders to ever do this, but light clients need to take into account that this can occur and should ideally check for it by downloading the block +* The private key is correct but it wasn't actually derived using the silent payment protocol, causing recovery from back-up to fail (unsafe - no implementation should ever allow this) +** This can be detected by downloading the tweak data of the corresponding block and should be resolved by immediately spending the output + +Wallet designers can choose which tradeoffs they find appropriate. For example, a wallet could check the block filter to at least probabilistically confirm the likely existence of the UTXO, thus efficiently cutting down on spam. The payment could then be marked as unconfirmed until a scan is performed and the existence of the UTXO in accordance to the silent payment specification is verified. + + +== Acknowledgements == + +This document is the result of many discussions and contains contributions by a number of people. The authors wish to thank all those who provided valuable feedback and reviews, including the participants of the [https://gist.github.com/RubenSomsen/21c477c90c942acf45f8e8f5c1ad4fae BIP47 Prague discussion], the [https://github.com/josibake/silent-payments-workshop Advancing Bitcoin silent payments Workshop], and [https://btctranscripts.com/bitcoin-core-dev-tech/2023-04-26-silent-payments/ coredev]. The authors would like to also thank [https://github.com/w0xlt w0xlt] for writing the initial implementation of silent payments. + +== Rationale and References == + + diff --git a/bip-0352/bech32m.py b/bip-0352/bech32m.py new file mode 100644 index 0000000000..795e153863 --- /dev/null +++ b/bip-0352/bech32m.py @@ -0,0 +1,135 @@ +# Copyright (c) 2017, 2020 Pieter Wuille +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +"""Reference implementation for Bech32/Bech32m and segwit addresses.""" + + +from enum import Enum + +class Encoding(Enum): + """Enumeration type to list the various supported encodings.""" + BECH32 = 1 + BECH32M = 2 + +CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" +BECH32M_CONST = 0x2bc830a3 + +def bech32_polymod(values): + """Internal function that computes the Bech32 checksum.""" + generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] + chk = 1 + for value in values: + top = chk >> 25 + chk = (chk & 0x1ffffff) << 5 ^ value + for i in range(5): + chk ^= generator[i] if ((top >> i) & 1) else 0 + return chk + + +def bech32_hrp_expand(hrp): + """Expand the HRP into values for checksum computation.""" + return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp] + + +def bech32_verify_checksum(hrp, data): + """Verify a checksum given HRP and converted data characters.""" + const = bech32_polymod(bech32_hrp_expand(hrp) + data) + if const == 1: + return Encoding.BECH32 + if const == BECH32M_CONST: + return Encoding.BECH32M + return None + +def bech32_create_checksum(hrp, data, spec): + """Compute the checksum values given HRP and data.""" + values = bech32_hrp_expand(hrp) + data + const = BECH32M_CONST if spec == Encoding.BECH32M else 1 + polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const + return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] + + +def bech32_encode(hrp, data, spec): + """Compute a Bech32 string given HRP and data values.""" + combined = data + bech32_create_checksum(hrp, data, spec) + return hrp + '1' + ''.join([CHARSET[d] for d in combined]) + +def bech32_decode(bech): + """Validate a Bech32/Bech32m string, and determine HRP and data.""" + if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or + (bech.lower() != bech and bech.upper() != bech)): + return (None, None, None) + bech = bech.lower() + pos = bech.rfind('1') + + # remove the requirement that bech32m be less than 90 chars + if pos < 1 or pos + 7 > len(bech): + return (None, None, None) + if not all(x in CHARSET for x in bech[pos+1:]): + return (None, None, None) + hrp = bech[:pos] + data = [CHARSET.find(x) for x in bech[pos+1:]] + spec = bech32_verify_checksum(hrp, data) + if spec is None: + return (None, None, None) + return (hrp, data[:-6], spec) + +def convertbits(data, frombits, tobits, pad=True): + """General power-of-2 base conversion.""" + acc = 0 + bits = 0 + ret = [] + maxv = (1 << tobits) - 1 + max_acc = (1 << (frombits + tobits - 1)) - 1 + for value in data: + if value < 0 or (value >> frombits): + return None + acc = ((acc << frombits) | value) & max_acc + bits += frombits + while bits >= tobits: + bits -= tobits + ret.append((acc >> bits) & maxv) + if pad: + if bits: + ret.append((acc << (tobits - bits)) & maxv) + elif bits >= frombits or ((acc << (tobits - bits)) & maxv): + return None + return ret + + +def decode(hrp, addr): + """Decode a segwit address.""" + hrpgot, data, spec = bech32_decode(addr) + if hrpgot != hrp: + return (None, None) + decoded = convertbits(data[1:], 5, 8, False) + if decoded is None or len(decoded) < 2: + return (None, None) + if data[0] > 16: + return (None, None) + return (data[0], decoded) + + +def encode(hrp, witver, witprog): + """Encode a segwit address.""" + spec = Encoding.BECH32 if witver == 0 else Encoding.BECH32M + ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5), spec) + if decode(hrp, ret) == (None, None): + return None + return ret diff --git a/bip-0352/reference.py b/bip-0352/reference.py new file mode 100755 index 0000000000..ac3098eaa5 --- /dev/null +++ b/bip-0352/reference.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 + +import hashlib +import json +import bip32 +from typing import List, Tuple, Dict, Union +from sys import argv + +# local files +from bech32m import convertbits, bech32_encode, decode, Encoding +from secp256k1 import ECKey, ECPubKey + + +def sha256(s: Union[bytes, bytearray]) -> bytes: + return hashlib.sha256(s).digest() + + +def ser_uint32(u: int) -> bytes: + return u.to_bytes(4, 'big') + + +def hash_outpoints(outpoints: List[Tuple[str, int]]) -> bytes: + + outpoints_sorted = sorted([ + bytearray.fromhex(txid)[::-1] + n.to_bytes(4, 'little') + for txid, n in outpoints + ]) + + s = hashlib.new('sha256') + for op in outpoints_sorted: + s.update(op) + + return s.digest() + + +def derive_silent_payment_key_pair(seed: bytes) -> Tuple[ECKey, ECKey, ECPubKey, ECPubKey]: + + SCAN_KEY="m/352h/0h/0h/1h/0" + SPEND_KEY="m/352h/0h/0h/0h/0" + + master = bip32.BIP32.from_seed(seed) + scan = ECKey().set(master.get_privkey_from_path(SCAN_KEY)) + spend = ECKey().set(master.get_privkey_from_path(SPEND_KEY)) + Scan = scan.get_pubkey() + Spend = spend.get_pubkey() + + return scan, spend, Scan, Spend + + +def encode_silent_payment_address(B_scan: ECPubKey, B_m: ECPubKey, hrp: str = "tsp", version: int = 0) -> str: + + data = convertbits(B_scan.get_bytes(False) + B_m.get_bytes(False), 8, 5) + return bech32_encode(hrp, [version] + data, Encoding.BECH32M) + + +def create_labeled_silent_payment_address(B_scan: ECPubKey, B_spend: ECPubKey, m: bytes, hrp: str = "tsp", version: int = 0) -> str: + + G = ECKey().set(1).get_pubkey() + B_m = B_spend + 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]: + + version, data = decode(hrp, address) + B_scan = ECPubKey().set(data[:33]) + B_spend = ECPubKey().set(data[33:]) + + return B_scan, B_spend + + +def create_outputs(input_priv_keys: List[Tuple[ECKey, bool]], outpoints_hash: bytes, recipients: List[Tuple[str, float]], hrp="tsp") -> List[Tuple[str, float]]: + + G = ECKey().set(1).get_pubkey() + negated_keys = [] + for key, is_xonly in input_priv_keys: + if is_xonly and key.get_pubkey().get_y()%2 != 0: + key.negate() + negated_keys.append(key) + + a_sum = sum(negated_keys) + silent_payment_groups: Dict[ECPubKey, List[Tuple[ECPubKey, float]]] = {} + for recipient in recipients: + addr, amount = recipient + B_scan, B_m = decode_silent_payment_address(addr, hrp=hrp) + if B_scan in silent_payment_groups: + silent_payment_groups[B_scan].append((B_m, amount)) + else: + silent_payment_groups[B_scan] = [(B_m, amount)] + + outputs = [] + for B_scan, B_m_values in silent_payment_groups.items(): + n = 0 + ecdh_shared_secret = outpoints_hash * a_sum * B_scan + + # Sort B_m_values by amount to ensure determinism in the tests + # Note: the receiver can find the outputs regardless of the ordering, this + # sorting step is only for testing + B_m_values.sort(key=lambda x: x[1]) + for B_m, amount in B_m_values: + t_n = sha256(ecdh_shared_secret.get_bytes(False) + ser_uint32(n)) + P_nm = B_m + t_n*G + outputs.append((P_nm.get_bytes().hex(), amount)) + n += 1 + return outputs + + +def scanning(b_scan: ECKey, B_spend: ECPubKey, A_sum: ECPubKey, outpoints_hash: bytes, outputs_to_check: List[ECPubKey], labels: Dict[str, str] = None) -> List[Dict[str, str]]: + + G = ECKey().set(1).get_pubkey() + ecdh_shared_secret = outpoints_hash * b_scan * A_sum + n = 0 + keep_scanning = True + wallet = [] + while True: + t_n = sha256(ecdh_shared_secret.get_bytes(False) + ser_uint32(n)) + P_n = B_spend + t_n*G + for output in outputs_to_check: + if P_n == output: + wallet.append({"pub_key": P_n.get_bytes().hex(), "priv_key_tweak": t_n.hex()}) + outputs_to_check.remove(output) + n += 1 + break + elif labels: + m_G_sub = output - P_n + found = False + if (m_G_sub.get_bytes(False).hex() in labels): + P_nm = P_n + m_G_sub + m_G = m_G_sub + found = True + else: + output.negate() + m_G_sub = output - P_n + if (m_G_sub.get_bytes(False).hex() in labels): + P_nm = P_n + m_G_sub + m_G = m_G_sub + found = True + if found: + wallet.append({ + "pub_key": P_nm.get_bytes().hex(), + "priv_key_tweak": (ECKey().set(t_n).add(bytes.fromhex(labels[m_G.get_bytes(False).hex()]))).get_bytes().hex() + }) + outputs_to_check.remove(output) + n += 1 + break + else: + break + return wallet + + +if __name__ == "__main__": + + with open(argv[1], "r") as f: + test_data = json.loads(f.read()) + + for case in test_data: + print(case["comment"]) + # Test sending + for sending_test in case["sending"]: + + given = sending_test["given"] + expected = sending_test["expected"] + input_priv_keys = [(ECKey().set(bytes.fromhex(key)), is_xonly) for key, is_xonly in given["input_priv_keys"]] + outpoints_hash = hash_outpoints(given["outpoints"]) + # Conver the tuples to lists so they can be easily compared to the json list of lists from the given test vectors + sending_outputs = [list(t) for t in create_outputs(input_priv_keys, outpoints_hash, given["recipients"], hrp="sp")] + # Check that for a given set of inputs, we were able to generate the expected outputs for the receiver + + sending_outputs.sort(key=lambda x: x[1]) + assert sending_outputs == expected["outputs"], "Sending test failed" + + # Test receiving + msg = sha256(b'message') + aux = 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"]] + + # Check that the given inputs for the receiving test match what was generated during the sending test + receiving_addresses = [] + b_scan, b_spend, B_scan, B_spend = derive_silent_payment_key_pair(bytes.fromhex(given["bip32_seed"])) + receiving_addresses.append(encode_silent_payment_address(B_scan, B_spend, hrp="sp")) + if given["labels"]: + for _, v in given["labels"].items(): + receiving_addresses.append(create_labeled_silent_payment_address(B_scan, B_spend, m=bytes.fromhex(v), hrp="sp")) + + # Check that the silent payment addresses match for the given BIP32 seed and labels dictionary + assert receiving_addresses == expected["addresses"], "Receiving addresses don't match" + + outpoints_hash = hash_outpoints(given["outpoints"]) + input_pub_keys = [ECPubKey().set(bytes.fromhex(key)) for key in given["input_pub_keys"]] + A_sum = sum(input_pub_keys) + add_to_wallet = scanning( + b_scan=b_scan, + B_spend=B_spend, + A_sum=A_sum, + outpoints_hash=outpoints_hash, + outputs_to_check=outputs_to_check, + labels=given["labels"], + ) + + # 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() + + sig = full_private_key.sign_schnorr(msg, aux) + assert pub_key.verify_schnorr(sig, msg), f"Invalid signature for {pub_key}" + output["signature"] = sig.hex() + + # Check if the found output public keys match the expected output public keys + assert add_to_wallet == expected["outputs"], "Receiving test failed" + + print("All tests passed") diff --git a/bip-0352/scan_data_downloader_per_month.png b/bip-0352/scan_data_downloader_per_month.png new file mode 100644 index 0000000000000000000000000000000000000000..ffcd0ddddb8c685b39055fd19112c81c8186109b GIT binary patch literal 54276 zcma&O1yq%5^f!nFA_@iqA|aBZlv0w4goK22cSv`K3QB{3bPCel-6bs`-5}i@I=}ti zd*?qh>-%P{aV?j@Ip?kC+51=fcuR^4-MT?|0}T!BmhfBt_h@LBCDG6>wOvPt{}Iu~ zkcK~AS_>#xOPlFg+i6Vui-3wjp%7tbCUSX-N0aWF8L z{Lc&M%`Ej9x}KKz!G~a)zg4tCL&MQTeqT!CP5pp|)^8!q|60aAW^KYwCDi7mZoB8D zXZ33x+Q&2rkLk_P3}@SA4NE${hRPW_@$MN7J3A^F>gtV5Ds9LvOgfo;e^S7vKT*pY zK*as|#*LUOYMVou@^MaT2Qz5+QzsR^IezHHZ;O`g#4{{`0ZuNfBs7} z_!TDd!#}|kl6=?UZ^aat-{BQx{B*+qzh1&mCm<;J+-60_$kRemI#rzV`SZR}O_|lB z#dzu`Puge~#YIGB;UBq$bIBVH=Yq(4$sey_mZ?*19mqiqE9v|12=0GIW_N^=_<76u6sk;l)U2Rm(6myVvfp6$s$CMWmy z^V8{x<6f8#6}Fsrlai96prU&2dTMXGF|ODnZ)Gx8fu}h|I`&!wq4c8Za=+B}3WNB@!^Y>R!XB_v#$gq;_{6br_ z#O0GyRF8$7-QvNNTWe!uqn4_;ctB|4-Nc!*?N(Cj_0juoZWpwx6}Ic`aokP{MFw{c z#;pP!9Q5?4luJ!@r|UjFQ>`c(ox>*KA|NFtMXg@Cg3joCXdd=Vk+0HredU~Q<@JXT zPw46C2eFgtZ#{VK!BBe|?EjFhFH@eu$dlagwO|5&HK)xAuE}UodN9`|W~0HZ_JZ2; zV_zoimfz{p)U&g*WvFgW#Y)?3hclo1Pwkg}c)k@Aqu}6(LXPEfY<&~mz1d=;=EU?X z4rzC9l2AE3<>f!=_o|%^^x$5x_$X%%?7$uDE!3B&uwHKPz`XaXvooteRY{33x8|4@ zo(2^a)mIX3M@EMo9baV|tA);){8o}H8f(M(3(EA-tR@))EUKM@*~;b1DbY*A`Q%s7 zZdmz+RLw~Cq50Z_(kG*T$bRX?WSDVgaU}t z_s73{`BHVX5WQf9L&W}#q2};%Qm=w;Boascxore!Qta`YGq@;Rvb8jUiCVply z8oW(Ps=n?hBqW4>i?HeQHLPc03<`N@I5;?W@7*)9CP7nIS4U$e^l#pQEfk>H_%*V& zR4|aF>+}f4;E;u?%^`K(x`c^|X}vKXiFuFeTTqac?ulxpEtBaudxg!aV5)d*8EmiN z5|ij=*3eRtKoahDX$fut6%``8af?;~?T8op*l=HrCn`dpNT>XU$EBsM?JyrI|J$$_ z7Y}b|t+3}VKE4pVezW>OEwty|J6`qBJ<+FfSsI61Gsvwn9%huwRWrGB0s9u?)y4T) z*HjPwWL)hZCRza@p}G0_<|(%em)=$U5@BTBVPRwIwFVFq@i<3#d3lMNa+-`Xr-;Xj zgwv}cg=2qze{Hnb7glSO+o#T0PJH;aQMt*ZqM|ZfWZU3O6akMjJKPf8xfXw}-QRCyOU4TI zaX4+)G=}puuU@_SEi9}>g7>UxIA2?_%$%&dySu5m+2UZr4mDvn#iZGAwV|N_?K%!l z?jDQk((fN{{2sDl!?vS(7v-UrtH1ImeSj+U;;6?hg42#3mi0o-$%gj9#zbcXlRH#E zCfF=!a7!b(omgRwFu7Him`?a8W$UdDX8RFy4p!ZKL{Hy6OY}m&n?L!z#l_iyw2De+ zxVmdwzXWfWS?!r^uaP9~7f({x2&h9$dYuHL#YU}BEJpV5JlI{1nPHVBI*i4;9($LV zj0J!H?hzYH{8T>YZ}E`Y8!6n4DwH_NmelQ(Dc)uOfy>E$GA!`5vC_bk&05k}aea%8 zf`aEr=@O6U=`tyu5Y*6k1h;0|9fxPSGx9~%ZlCA^0ja~^JMiHx$j|NV**n4+YS)HR z`zEWMdY7$>%%;v0$8M7H#K1?!Ln*#<_wMD0yJ_|A7~1-F+oVI+u3cLokbsYe9pjUf zME>UMO(rwcYHsaw-F94b9Fk7B@#$%4({O8%Cx3@rP=)mz_UO$}k!CoJx8ma1Q_iSc z2a`?=ZfAS=;^N|H#>07^Fi4%hEiD;PiN|<}Bx`lYyc~zUb$U1#*woadK!k!?k|dic z$F<#bkEP-Z3mnSX#U+yY!(FJ5vv7OsCnjQvIc;wf5d69)!BxL9kQvEhgm>r89Y3O% zeWx{lc2U>zOvYHl=#*QC?Z!W4WthT~XALbhe18gyjfQB~M~kK5xuuRfu4cExK1t0|EEMcD zEXEt7q^5ofU9CMSkSBxSCc3YUU{detWNdGUdLW?;;%EyoS z9z56a66l0y=Z_qkh{O6<)mr&tob>VDs=jNr^N}U;qNCk@2|BH4<`2ODIMQRd9Cm^? z2hI+ra(6eUrcODisfCt*|8U>={RT(T*_P*U`U>(U2dLYf9Vrs=IZ*c$Us}w1G&3PZ zCKB3|&(Cj%wL;guz14{GwDIe$N*%v} z^wWy{jS1x^w6xC|)Qb#)kjr%jjNy|QQ6q$Li}2^T+r^n|je1CM@OS8wxgz5dJV!eI zT4`1LSb;99*`>5lfdQ9PX{n{aDMN3fL7ujQq6v>WtVj`0Y~p8OvaXxXP?Q#Q)m@L0 zkt6*4`BTQbG^I6dcc+8lDH9VDiz9=_`Zs0!#7RmDiY-(RZ^-xWO|T7*;O7t6Oo?2M zck>BK)bh2i2_^*zk-Fd(kM1O;zfbSH$T>B_Pbe#k7#~-GQuXBN)49X#IlPndvNH2I zf3_%fPIh*rTfTq)p8f$ULpBqw-=q!|s~-Jcm5U=bpsI?CkB@I_YfA%)e>hi9s7n9Q zP4p{9Lpi#2A1__Kb}cg8)6-MFd|uk&9b7?t^A1#sDXr?jKpag?&4oKD>tm%!y#@qu ziX$T<6x7sLQfl*Hn;LExU+NDLz{bX27=rtb7Vg|5pR49Cn3TTb3Qzlt#z)Iyb?)>a zBQ4Df&b-2I69BBwk?klfFepspHbbg-+p-6H$^|-@6BRaqwA|rkD7WqHZP;lg#v@t* zz1`gbV`jAzRk?ymGdYzT6-zd;47|LgXc7`VzNT%u7HMG=ptui{C!HI)(MdH z=y)~v2tc6y$$BZ~ty{=0z)K<|AtBMx(<^W~*dT0n`VmAfG`p~%=jw8{H`HI{V1-&- zsQYvUF7Jg_)AgO5oz6{dKxx&Eds-b~bh4)2leS|f#Xg$QH}KSZA|eQI1(3fl=o0#W zg*xZkqYSG74bFYx&a02G^Tx-==})ZYL;`1KW?nluywugzW#OsG<0=0_O~@}Mb`uaS z0CUT&>DO{u3iWFvg$`@^t%tBGKJ=&D0$9M8Ef;NOq}mCmdAO7mh%{Gdtaz*RSwfiC zucz%`P4@SPyz#_V-68t$;X~Shp)cvjk4J-JN9`RQkxbg?2w(#shFAL7P>EDJ+K5Bl z)RcaHe!j?XAfvqka9gxnx#~((6q{K9JpJbWPw)sATuMzRM69iuq4G9$bogc~m(H%P zcEB2az~gKmii(T>b!lO=RFpgWdsx`l`1tr{1_O3GU(_k!m$4SO?<3__`l0IV0s;bM zm6aiJaoDd64APn%NlD{rF+1Brs9#zy{T9Hox3aS8Vq!L%98Ww$i%Wn^@=&r1z@69pG@tY`E)CMJkK*45R~ zc04sR;#=oYA0WmN<7G2MHL02eNF7C%jSndf;|VA5yIY!@KLQX8&zkSe8;6-M_zJL6 z?@^0q^_~NeMAmOvc{vl{W7uBiKqLUwR9&2($euS;=XsUzRBg9BT;K@oj^&)&*a#3w z&ahAwGRa*w9Qf;L4fpwQZ%AEOSXdK)hV|;;txyqxp&Zqb5|f9nuC4_BErvpZ^hDfQ zG9d@Eme6FHs}Ckc6%+`1crUmmgS~6yCuaLQ_;!MDy_Q&|oHf34}|)4b5z_D)RKyIptlno>Y~} zP}dW8cXuX2{}>LNcCA0@6=NSn!|8Q_jp0pXKc%J3@FP-vW>Fqxw6nck-`18snmR5X z!!EVX63gY#r}dqIlQSBQ`q|yRtAkdE5*BnTu(8f&l!b-GDKxFaqobMGSw5)E^bfV) zUB$pC)NDle@%5z>#H5|tY#%;9S|lw}q663!*=)c+-|rVnI|R+Xzd%8KOH?4j)M4eu4WMm7BcdVrykpx#l$T+wk}(!@q)v zPUPLYY+GyPVb=6dclS&I4iB6U$U69@?O?QiymUn}b3iPfr@H+u-~4%~+lM$eu53Bw zb?r6zBmi${vK+~7(9&c}vNssIfQ#gaU|rVN*H2iXE6*&a@)vxgrS%v{0pnE3Wp>-O z2!U0My3;>1Uxv0|6)I<8^fSD1b8~~`S3Z3EOvRrSvbSkpoV>xUp{)T84_ohma zLfhay-Ewc<87b6Hg3hEK*nV)_F!s~!blbDp6^b9MOBA33Wn<-uA+ulK$bc=n1B;ST zlvhyjQp;s)O&eN0T277cT)l;U1%U+qIZPZJgicOQ(4n11Ow4MI^Z+s{%u%1xe~*vv z2_WV)n5-&4`p^ko%-4j7hzO9rkkoft2`afo*b7Bv)m1uecL82%pN|6Dxd$sbhy^7_Q*+$ndJFp%9V-lPo!y{KGgCnkN)LhP$_)z z8K~IVBY-SlNg?zFvbF{1Y1v9_$7t~T5gd3JSP5)RuDw6f*2{g@BqSu_+|C^kjx9rb zACRwntlhTBP!4gUC(dFksoPKJdi1Ilc@}f9l^vmXaX_nLfUaNw^_|mhLkA?71!dMn zQt6~#MymxeA0Hocb92Unh>9qqD8RwM`DZ}B_zNNk+r@jc{uFf<>KsI z24xLtDs!!YsR%)ts*NAWl$Uq#a2_wS0JVfyEQ0ZqRFYutk2e_Y?d|N&hm5e7=i&2P zT3bDT{-i*}l8f^L-oCy*Y7S06cbjD(Im)%h zFv)UvuMvk=2R1J(<^EjtS|zqC6P0!hM@L71cxwlluCE4P75P)CAMX-IDH@gsMcDOd z9v@V#*RNj#V<+Z1-DqFB7Ku$nhbHwLd0dH>Y@9UkP9-YEY0?ou7p^zdMY2j*)n8iwnv;cH00XC^K zAQxFfLuldMM!D53eh*&QFDHjH65MnK*aL5z%g6!D>jO`58&(mjjXM7J?b~%fg+th3 zy@hHWA%a4p0M!@EEy@+!jj;Mc6;7%kCYhKlC*xYMjGRyNQ zK=*Ug({-?8JK?T-d3(=f=GJJz;qb;@a&rE|{?c6&$`r(fsKg6P#+f>q2 z9?U75RX2l9Aek<+;O+hNpHCDvf{>cM6hJCT~pvWKh(~tJ$E$n?EeX! zA~-nMeybiM(S68$A1C zC>z@?YtIz?O7*iya$;T{ZgtK6iz+P8v=e8J4$BgI|F~VIEZst6Z!$ySyEKUG?WF|! z0+GEF!;q7Mzok(=d7OJiFzJ$ey~aAD$E|(?=ClDqBTwWt0dFtc+{YF8k;fQbP;mFv zD6yBQ)XdtNkEf@EJD1v1c!jGlyrR8b1nP84R~MF$5cMlpCZ81=8JX|WDlvi8)#UW_ zri0jTuah;&247vd@x+JE!h%O!Jg2POL5Y({b8c;&xOdeC7k_bXZH+bwB>CQ7!gr4D zKPU7j@JEk{QOP3qF`$4y83}I@`1eJ~UMC~u6i&$b)@wjZ7~Y^EB^??c+TIfYSJonW zH!YK*REz&zo4&wxvM6lfg#Wp8&DXg@sO41B7mmA(jt2pAU-07!Y~Lc6-xZX)wbBc; z49ExrySWChgG0H!z5QLn(Ws^-9=``x(Z!9KgAHF$huf{2P^iX$1~*Lc){T-OHUdiu z8$qwZiktJ>yJ?OE`aSei5^*1E-O$Jd{eCLGP%kJDFEpDXfk&<%{Vt)zY>E%H;3^jP zj9dZ^O&ESUd&AF=<-BW!w>s!*MjX@w`Lc=%CaZ-~z~#zW9UUFT?`6W_muK|Y{6p@Q zJx5%D`pLHG|dAn4d%R|cVg-P=2VVJLLf^>mvP90noN@_E7F;8|gD z9_+fdkharB-k#?=Z|@2VZ~Hy8V}lx)VHp5T2|)N&KxYIjhBSrxZf&2hxdlZKhzbf; zI8=h#1WK`#?gf*!;MPKPuF^BWma}t#7p<_ z4$XP?6Gn^WbX^VY;!B6;k&Zg1hZ*tWz2U@1*=es%>YP+F_3n(f+m}}*|0YuD*mX@}=g^@J7 zPefzDF5-pHA&C^4n<-TMf zLFdoZYlfXoLc}thB9{PBnuX+v0LC zAzW!o?B%^IW^8O%Ve8LwQGovT-8;?wHHPK>R8r}bDqkg+4Xj+6MJgKCy@4nCi(@Yy zytKS|aki=48GG^&h$SN;XV{F4=pXjDsm&d)dR_u<=?|?n<_p$Eg~8?dwYARa`Ztlh z7ZQI~oIxHnRFi+nVZG4#!S(z|0-Af*Dv8tDozt@`5ie27RyUJ`raH+3{pJdLQldiq zV8wquJ>{x7)M8y96S~5v^UK_{)q3_xP1q*O;zJmPw_r}|!Jw3T1Lq>LXIKWsOWryzJ z;u>#ru&@AzJ>2Q&0g10CbAppE==o+|AGK<+9o3&4G>QEpDCAk%rpvJPB~7+Cg$i%6zEV=_e36~9R*9g zYOAGWri|Z;)$&xbjqu$xbjg>O|G*f%R6L*?Hi!&Jk>!02$KOzO3+0{v}FCDZ0dg%}NemAHVE zm+xvlv7KMG?MWCNT}YvCm$E(OX2;$T&Kwj~A-Ta1fQ3)?*Jr$`mGp@n)%Gi?jWIT*izMaTv^`bSjAH*djTwwEL)Fj-TpMOZd|eAt>fjj z1EOmz^Xuzx!s#atS94$L=W~q9UYl+i}(1hTg?0UG>~SBty`m88+{7&?AP5-Q+|F~R)Xhri@Uw!=~Dk9 zhf?tkfzuCDGpG^X#MP(@FT*z;YTYw{LjF)ki&wTB@ijEF+ zs{8A$)d=nrAzzc+T2}%v`K-{mw(1&-o#v2b7fOdw@V1S%Wkjm+x0<|~gi|sFJX*M9 zcJSXc=2~nzquKDQ)jR=J0jZveb81m!9(JDogb5!GmUJz!@>F&XjyoiiEvNk9sj2wj z=#llOxHNS}#?NeQ)F-@tuKG)Q^*cZCfBNdI)|ZrmZwzKhb5l2FBPOvVI} zk{MBo+S{8CkcSk`N>V8HJ|-q6f^{7Wd`ly_9F_WDN-PT-n`GUN+Z(lR&i))3>S-op*Y!FNqo8QT zlv}w8Wexz{dG)@pj&_WSVi%mWD*{Zx5yPp2i-7Jdba&Cot>mKxa#y7;m1fM ztC;9o+zStv|Ey+~RiKjE56;=^LOoC!Ib{k{)4TdIJ63V z;W{Q}HRz)9^p(}ldVtwYPbpeP!D4hy z$;;a_o+ksbn6u2^4|g| zY}9s7iOh~~F*C}1wxYW}eCkIW1aJ81&W;v0p)y9Wnmh*bdr@f14wu+PLpPLfkd%Jb z^=mRa-z>plGlQH(GMtP>GBM|uzTkDTobH%`R)xHCiq|zcRjzJ!z<5GUexm=wUuLsD z&)x_#VPerCnV`-Z{hLnqUwkLJRO3vzh=W}7HHr0yRikzD|9FlGXNY=BE$6#?HB*16 zZ-%y4BuPB_mwp+K#~X=jFw$CI-%C+V3oLHD^xs=MKwz#Am2@4(EDz#)@{4Ycl z&7wbGft#SLW2J=QsK80|;$Nnr84{3kKM!TVN*lV|#;N=m^E;#HyLU6S7cRbFR=UIf zGY537Vm)>Z>-=xYm|)0v+#c?O@$L#6BYLM(AgPSz8k-%aY!@- z8T|)|xXNlr*8z;bzBqRWs_S`aX_O@l{Lkl3`(l%B7a?D7$iuR@rz?Ydu3$s-@n=pw zGs?Qal3y0ME2DHK!-d6LQBg|Dd0b)PD|BH8o4j5oV! zIa~iM?N4>bjzu!gxFW28HH+7*dXMnSmuq-qK(uQPXQiPw34#sPoF?_g0o5hX;3$^V z`wJY^`aI3EuD02Y2~Gz*Ak3e~#chz)^GF{s24xqoHw3rm_dD7W-N6e-N-))iyT3D(CYfX3)L6K}7xnTzZ=K z!XXSU8y@%psBJ<3qZEn_=LP2M=ltaX0X>vX?SAwu>lP(tGxDgLnyeQF~Zem(Y8EyzNl-5pl_ZEHbFjJ8jE!z6~g|l+d z;jV0?7&sk@&+P2jxRo4d(tdw|9uE-@-bT-<>#q(rssW<*A|$hFe^hy31GQeNJ~P+v zcQAQ!9l*ZOU=PDb>iC#nP_5sJtu==2|6~sjc$!Cm2rRkbcd%y02r&QHsl^?}Xl5Tx83R4@$&} z?_Oo+SQa?5kr&y$221^^3?~Phl}r1TcAE)yn>7o6F#&1D!9^509f+er#N%^F$UR6b z)%-07I%vN?(kd)2u6Ml40O<3Tq$HM#sw&c}j!rh`|IW{=&fLF2=23Q6DfUzCU>ur; zeXblk=jaA|;JVeYl-F90eAUJpt_7sOr^vQQumHsYkd zFdD=ci`=<^?*1tvq8kDeh4|IgG*E3fP7AsHs}gTGa}g~Mu?&ySs6g^?4^k>KO9}b5 z1UB>(6u2Kls6)Cga-2=f%p@d#C-ZJOR>y&B;!8oxz%caqLFKo~N}=MU*X8Bq3(B4( zBym9Yl4#l~LudeqZq#0osk!V?a&cFeEyh{ktwJni!R_M2!^g)G6o>ZRWl~fFmc;gI zuAAyk1=4Y2`z*PAS>vuww|EQ=kB#haN`l*INXcGyjZXP%p5^=$UdP3`)60p9W=%6Q zw-N0uHJ6;cF%8B%$35E7(oOT;b!{z- zsgB*{7Yt6=U{TZ@Ej$Iw2w)VDMNUPr^;7{nU4WBq1&YU>xk5Ci5Sdg={dM7ah zsgh4{QfPp5UIP|{SFZj{P6kDu8LUH)>89X7W3mA+4SwGST7J0cHq}sH9~4Axrge5) z{7hjB9|4_!Ov=k&o}QoNTCWXb3JJ9;)y9K9W0;c1-%&vK1kG=S_-hKfiA~nKyEK|% z;o%(}47_IfSvz}>F$pNOd#2%7ke!$q12X(qP&nQJYn*gkbpa+#?}QB?7#{9Bh-|0^ zkn)OXXxs+xg#;3Z)k{25QtOA)?jOM3j=eZtR0IGb4EwgpJ1|@dAjW%5E7*Q~a6P5KWqFhEygtaiUbFtn+qr>fgmh@W6#Z)&OU*w?i!)AuXjE zZ=T=hxboxDhPAJ;>AJZge=_6+)*l%ws;OZ*x(sNU$BK)Ji=%f%UDGl%i{_^FFbV4M zyUiyPsw&yyt8UD1CTYJi*llf~y7%M9=+?G{KvMd}oo8dD{BjU zPV11^65eN{Dk-sLm*@XiA!Q~29yF)kztnFN>%P5bSz^gAFZkb&B1}BHzr`qeBvpT% z+5Rzzz3B5V=C(ke=}9qSWVHprh8;eX8!t zEj61G(b9T`fwPetcpG*A^X1h8z2G2epUejn~1>VsPq2Jc0)ggdxcURzya@ zd3tIbk|D0#tQo5^h71J1pkUO6(NqmLC~k8X=bIP5Iyx%*Ix17TqS?Yhvin`@b~%uH zpAn)Mc&qpfYITh`f!e>mQRLLtkb(f!bbjVUS5m%(H~B@)arps>>)|&a7sJWl=1RsDd9KTQ;O}WM5@e{Bn$Pg5xgP0KiAB7E zEVCjcdco!Y3${&y)na!zPJ4I2d`CFcWdjrhzn&w`G1zJu6N?}`Q9XNh6C$rbW{x0n zB>e83?qHUpytRwGs1)vRqijo4v^Am}1Mwf=scb>S{JHhgb>N;5!^VL4*dkp?TW9Aq zNELU8#xDb<^tmq@$-Yen;Me=xH#ruL9{Z{Mal9D!WCG;zYg#YHGD z>IRDtztmXtMzb!*X?3iyA0~7|n^~W`X@IG=!3NoZ$lbOZ^jXV^yj9O z_wV1QS4Q)Q#qSIJu%3)2kRUbrjf#_)o5EXssFr1Ocdv`B5JI;FziXwz@+y6%&s1yPW3NaPfmsunvLh=|+(;SoZnOr z3D~WdfDNnY=bu)r9xGRgi}TcDF>rNx-Ejv%!8xQl6aw+d;9$#wmc{p(12;)s$-$`P z&mFpw(whqME(B0q2ZkPd+-_ifHfkPhnf>Xu8=F?rX%?|~Z2I1H#2!M7QTPm9NQ%e~ zlJc6aY?^CY-X|dm0DwqI`31~C(KvF5oFpVXLcA~tJPT`Tl7SVh39AZ;tPN~vGZXf% z?m<8*faoP+bqhEfGjOP8{AuF-5X)c}Qbq|Abt+leaAdRa@*pU}O+`_u-mWvF7j z!SZ}04!tg-W+p8+p{Pl2fc6tDM9|>m5RIpF ztSBVj8b+_`15q-FW#GR{06ibicL=CEP>D(wj-g}&=7EdnY=jJFWMJ=UY}yYjn!Qz1 zn>1i1+}W|D3tI$#y4r4&1aJ{*qms0*#KgnF0U(xSW$iO4QOmta_*nSejT(%SlHYpr zG}%-JSeVfX{cmt`3g7*5``y&(Ufa?w!9>2Qr9XoQK898%ve|djkbpT9O^g0_Yyw(G9oY=OwEDtqg@r_i3&?)Pw` zU)^$S>FQr?Z4DojkL3ew8FyGLrzV!qJ;y*RR{CSz zvlx50mm#yE?}5?ZU%gdZ4TaDl?>rof#iJv_n7aq+@58D+AQ z`V5~kf?;;^hGVtOO4dr_aQ?yeDN17dc=bZx^@iE@>=!fw{~Jq-m%6KSoN;cUCpDi8R6B8jrUiMoXjr_3!qwsU&k4j>9IjnU z{vIlOaWxa#)_wJ!DPO5i^ml3woj8KS<@+8j?roXZEKMX;$jn`(WnsAwrSc69w~X!t zWRsDx2AGX_3b*%TI$p-kRA}z{`uc~F0$qQ1$i@;N;c7B6G6;tZ+4I(^=-WWTNNL^f z2bSARymF4pfQ5;ymX>;)>+!XkvweujCzq)1|HL4=oSPsu}_8A{X9U zpnJWC3-y@yRPro)pd-JqkeuuX+f(`Tckw)DD_bJQ^3tLeUvAIxQ9r*iV)+Q`K4XTR zLda>P{M^+H2)_bHD#h!7F_b!JmR(@l>yeznnNot={Y!o#KQ>S z3~b14L;Rq>*6pIh%o!1CuS~D6tFF4_+K>xPHvI|;EB!E(Ghujc*Aur5LZMlHjePD^ z-ysMO>5rFfFAS+xwkQ!L1@{{0bcDUTNoeho&GQgx=|eF9UY17c! zxVV;}j}@c>p@D_h`W1rlwIpVLx1+n z*wNu(8SC3S4*mA$Y6$d`(`BaZR5US6;btsJmPMqif3afD=vSA>pZej`D>|*;NXQ1U zQz!p}ahZwz509+60A=kPfAI~e4kgcU`*&V!HX~~^Qczs;Wt=HCg^iKsx;df8zfiSI zYhH;PHAH>I{0*NG(K^fEt+Y@7>O@{oOPob-2qbfyR>dq6-!p*s`2F{u#{dSxjCror z{o={aH~P4LsbqGv{Jn)DVrs;^td&(f2M^TpV8TCB`mmMsA`l>!0_RPzMf&he*o76p zd9oXhTEE4Hbf4c{@r= zNtv$S2B7sP#9>uDq5t8Bv4)TU%`Rws?wR4`Tl{kjGJNn{27yIv&tCfxNnyLjx$oPGHZYUP$kU?-3oIpar&ftYbvYFAR zO6X6ouC_)nm71?++bK1Di*f$k!yDfoKs*N77g#O+fO5W0PmA*2jgEEs3b7+PApCxn zl^ASO&dMxfI;dv+{*iob&X+GoASKwFJB0=lEU$@(#QHN}pyI{fg@vsVeRah(NNH_? zZE(r$yqo|BM*{EJp)aIUKJD%@10ymRp#9z!68}@R5?i(#0t@e?^h}=^+3KRf%`ul5 z8eY2h_Oh%QuE1-fKZ@(8*oG%cE(stYUjo5~9sFc@waClRpGzHFOii(&MDf^7Ixv`; zngZwY$5(fb`o}1(v?5J{kev7aI_J(Qib0!k>C)lh+yP=fwggni@2@9~RNAq{yRhDe zBo%DAE3d#ry+<`Jp`gGHp&?qsVjq}L@o6P-ihx5j#{898gwP6@ZT(zlyI7!xAX4!w7ia`bTDTIt+>z3$!E{t?YGvjm z)lTXlYfy9AnZktKKn=B}YY0 zqReksGe~HB8ZgFq=k4o-Gn;QOJZKM5CC#z8dmlhB1B2P~7caa5HWYMSpm#~9y1s^s zL4&Chu{f?zAP)DXOY5U-!MU#58+7Yhd?zY8AIR%EznrQ*-SSZA%5D51cR&s`=Z;i} zF%5aCA@!anh?e8<#snlKeK`NQv?M-m+2N0HY%ph4OvWL0wK!dW1_edmdKC}jRXBY< zgv4&bj2FswX2v9fecJ-H3bx+N-@l#FX6K|Z96$u;W4$?f4}5RDXA!OBfnw;>hucY@ zaK;0qy#zsfXs-4%gy@o35gv^f4m&zNM+o)J@@ zT0jIU8>4=43!LGcr&qL2GX9`7h|SR;|R^%cCrKc6N5y zM8iE8q*X>1x4_!110qpiF~{M!FQ&2F*X&2gf?hnjTPj#;YEgB%vwFHe*28u5mjc3? zWg8Xe5S@~Sr=Z3x4(06zc5J)$=C%qn9CD{pVC8a@Ab3ip%di{LtCjX!D^i(&{FNp{ z%X79Xy0)H4#YaY#-8r_m5-Pc#&4G^}?i-g4xf|HM8P9sUx{B?qLC&I}*wt+C@Lteu zYopFsTU=a}2qdXifV}})-RIBFFnv<5665gp;xvvhRDsR@-*6b12gfJDr zj;&AP)(LI@lJutd>q95?Q+-tND^PRv^!3$s`Iil4CzW7~?h#Xdk3gv}xJ};oS`Efn z`w|aNEJur1Ux7e6R${^wp9#|;*~)Cne_qMTe3uuVhPfz}btWM{f;Pm(c5rZjl`Zt@ z)t>Y{gabGqz6GUZFVh@w^a*vI=gQyR!D~JPtfD$F7XXPYLFDyAVkd@+EALV}LmxVd0m}0mRAq#Vp(7(V{ zOfZoOPJgufJmmx<_BzBZr}0!@mCjP1<&dLww~BSjzHgofQjtOx>spYr{5 zH)E`-3F2&`pgriqonLWkgTRj6?iIR7hCrzZY1-4h!?~W@cl&%0DGQd??AjV1Xk_3E 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 z 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 literal 0 HcmV?d00001 diff --git a/bip-0352/secp256k1.py b/bip-0352/secp256k1.py new file mode 100644 index 0000000000..340e75455a --- /dev/null +++ b/bip-0352/secp256k1.py @@ -0,0 +1,681 @@ +# 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 + +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 + +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)) + +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): + """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, but a simple random nonce (some tests rely on distinct transactions for the same operation) + 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/send_and_receive_test_vectors.json b/bip-0352/send_and_receive_test_vectors.json new file mode 100644 index 0000000000..7fc66bb528 --- /dev/null +++ b/bip-0352/send_and_receive_test_vectors.json @@ -0,0 +1,1743 @@ +[ + { + "comment": "Simple send: two inputs", + "sending": [ + { + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_priv_keys": [ + [ + "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + false + ], + [ + "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16", + false + ] + ], + "recipients": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "39a1e5ff6206cd316151b9b34cee4f80bb48ce61adee0a12ce7ff05ea436a1d9", + 1.0 + ] + ] + } + } + ], + "receiving": [ + { + "supports_labels": false, + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792" + ], + "bip32_seed": "f00dbabe", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c", + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "labels": {}, + "outputs": [ + "39a1e5ff6206cd316151b9b34cee4f80bb48ce61adee0a12ce7ff05ea436a1d9" + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "pub_key": "39a1e5ff6206cd316151b9b34cee4f80bb48ce61adee0a12ce7ff05ea436a1d9", + "priv_key_tweak": "8e4bbee712779f746337cadf39e8b1eab8e8869dd40f2e3a7281113e858ffc0b", + "signature": "e18fe06280456ed533808606f73e0d46dea49f90751078d127379a8e176a6e56bb1e86f4ca3522a58e760a4ea68e6f3a26b24dcbcb9c614d4d5d2bce9bf956bf" + } + ] + } + } + ] + }, + { + "comment": "Simple send: two inputs, order reversed", + "sending": [ + { + "given": { + "outpoints": [ + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ], + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ] + ], + "input_priv_keys": [ + [ + "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + false + ], + [ + "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16", + false + ] + ], + "recipients": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "39a1e5ff6206cd316151b9b34cee4f80bb48ce61adee0a12ce7ff05ea436a1d9", + 1.0 + ] + ] + } + } + ], + "receiving": [ + { + "supports_labels": false, + "given": { + "outpoints": [ + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ], + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ] + ], + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792" + ], + "bip32_seed": "f00dbabe", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c", + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "labels": {}, + "outputs": [ + "39a1e5ff6206cd316151b9b34cee4f80bb48ce61adee0a12ce7ff05ea436a1d9" + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "pub_key": "39a1e5ff6206cd316151b9b34cee4f80bb48ce61adee0a12ce7ff05ea436a1d9", + "priv_key_tweak": "8e4bbee712779f746337cadf39e8b1eab8e8869dd40f2e3a7281113e858ffc0b", + "signature": "e18fe06280456ed533808606f73e0d46dea49f90751078d127379a8e176a6e56bb1e86f4ca3522a58e760a4ea68e6f3a26b24dcbcb9c614d4d5d2bce9bf956bf" + } + ] + } + } + ] + }, + { + "comment": "Simple send: two inputs from the same transaction", + "sending": [ + { + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 3 + ], + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 7 + ] + ], + "input_priv_keys": [ + [ + "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + false + ], + [ + "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16", + false + ] + ], + "recipients": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "162f2298705b3ddca01ce1d214eedff439df3927582938d08e29e464908db00b", + 1.0 + ] + ] + } + } + ], + "receiving": [ + { + "supports_labels": false, + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 3 + ], + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 7 + ] + ], + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792" + ], + "bip32_seed": "f00dbabe", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c", + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "labels": {}, + "outputs": [ + "162f2298705b3ddca01ce1d214eedff439df3927582938d08e29e464908db00b" + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "pub_key": "162f2298705b3ddca01ce1d214eedff439df3927582938d08e29e464908db00b", + "priv_key_tweak": "f06d8d90561bdbc3e511c3bec7355ad3c858aaf38a132c772d6cd82ec04102ac", + "signature": "4c900d573964d31953acdaedbcbb7866fedbdc215417adfd4173073f86179cad5903ae64490629fae610bf879263c3b9f5c7e6ec1b32a159e2d2e60a16d36597" + } + ] + } + } + ] + }, + { + "comment": "Simple send: two inputs from the same transaction, order reversed", + "sending": [ + { + "given": { + "outpoints": [ + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 7 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 3 + ] + ], + "input_priv_keys": [ + [ + "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + false + ], + [ + "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16", + false + ] + ], + "recipients": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "d9ede52f7e1e64e36ccf895ca0250daad96b174987079c903519b17852b21a3f", + 1.0 + ] + ] + } + } + ], + "receiving": [ + { + "supports_labels": false, + "given": { + "outpoints": [ + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 7 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 3 + ] + ], + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792" + ], + "bip32_seed": "f00dbabe", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c", + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "labels": {}, + "outputs": [ + "d9ede52f7e1e64e36ccf895ca0250daad96b174987079c903519b17852b21a3f" + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "pub_key": "d9ede52f7e1e64e36ccf895ca0250daad96b174987079c903519b17852b21a3f", + "priv_key_tweak": "44b827516c2128287b1d571add7cfeb42f122e86bc40b4eb2b21ac144607fdb2", + "signature": "1bdb32461dd502ee9c19c7dff5f3801a26c2bc0ffe6f34671053ef7083ea0d5adca6036564252a76e427555deb17edd6f801d45cd7b830d7e3003eb3c8c85263" + } + ] + } + } + ] + }, + { + "comment": "Single recipient: multiple UTXOs from the same public key", + "sending": [ + { + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_priv_keys": [ + [ + "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + false + ], + [ + "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + false + ] + ], + "recipients": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "0aafdcdb5893ae813299b16eea75f34ec16653ac39171da04d7c4e6d2e09ab8e", + 1.0 + ] + ] + } + } + ], + "receiving": [ + { + "supports_labels": false, + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + ], + "bip32_seed": "f00dbabe", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c", + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "labels": {}, + "outputs": [ + "0aafdcdb5893ae813299b16eea75f34ec16653ac39171da04d7c4e6d2e09ab8e" + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "pub_key": "0aafdcdb5893ae813299b16eea75f34ec16653ac39171da04d7c4e6d2e09ab8e", + "priv_key_tweak": "bf7336bdc02f624715aab385cc62b71f6f494bf8a7dd0fd621cfd365039c39d1", + "signature": "e00ba3406cea12127896fbc198a9da889a4afcf3d66e46b3df0e7bb36de400a109442e5bbd005c3cc5ae30ae7d235ea111475ad621e1e2c27374fda906521c69" + } + ] + } + } + ] + }, + { + "comment": "Single recipient: taproot only inputs with even y-values", + "sending": [ + { + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_priv_keys": [ + [ + "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + true + ], + [ + "fc8716a97a48ba9a05a98ae47b5cd201a25a7fd5d8b73c203c5f7b6b6b3b6ad7", + true + ] + ], + "recipients": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "15d1dfe4403791509cf47f073be2eb3277decabe90da395e63b1f49a09fe965e", + 1.0 + ] + ] + } + } + ], + "receiving": [ + { + "supports_labels": false, + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_pub_keys": [ + "5a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ], + "bip32_seed": "f00dbabe", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c", + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "labels": {}, + "outputs": [ + "15d1dfe4403791509cf47f073be2eb3277decabe90da395e63b1f49a09fe965e" + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "pub_key": "15d1dfe4403791509cf47f073be2eb3277decabe90da395e63b1f49a09fe965e", + "priv_key_tweak": "0734de077e436e8f6f125e16287cb60dead8ebddc8532be3589ba27156f1add2", + "signature": "d743170ded6bc695f2997caed9886deb7ddc2e0e11d5f1493d6d7e498e8686f94c393c5d20eceb700a4c2035271196897a83fe1658414c38da07e0e4af00fd0a" + } + ] + } + } + ] + }, + { + "comment": "Single recipient: taproot only with mixed even/odd y-values", + "sending": [ + { + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_priv_keys": [ + [ + "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + true + ], + [ + "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a", + true + ] + ], + "recipients": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "15d1dfe4403791509cf47f073be2eb3277decabe90da395e63b1f49a09fe965e", + 1.0 + ] + ] + } + } + ], + "receiving": [ + { + "supports_labels": false, + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_pub_keys": [ + "5a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ], + "bip32_seed": "f00dbabe", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c", + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "labels": {}, + "outputs": [ + "15d1dfe4403791509cf47f073be2eb3277decabe90da395e63b1f49a09fe965e" + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "pub_key": "15d1dfe4403791509cf47f073be2eb3277decabe90da395e63b1f49a09fe965e", + "priv_key_tweak": "0734de077e436e8f6f125e16287cb60dead8ebddc8532be3589ba27156f1add2", + "signature": "d743170ded6bc695f2997caed9886deb7ddc2e0e11d5f1493d6d7e498e8686f94c393c5d20eceb700a4c2035271196897a83fe1658414c38da07e0e4af00fd0a" + } + ] + } + } + ] + }, + { + "comment": "Single recipient: taproot input with even y-value and non-taproot input", + "sending": [ + { + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_priv_keys": [ + [ + "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + true + ], + [ + "8d4751f6e8a3586880fb66c19ae277969bd5aa06f61c4ee2f1e2486efdf666d3", + false + ] + ], + "recipients": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "2b4ff8e5bc608cbdd12117171e7d265b6882ad597559caf67b5ecfaf15301dd0", + 1.0 + ] + ] + } + } + ], + "receiving": [ + { + "supports_labels": false, + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_pub_keys": [ + "5a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d" + ], + "bip32_seed": "f00dbabe", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c", + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "labels": {}, + "outputs": [ + "2b4ff8e5bc608cbdd12117171e7d265b6882ad597559caf67b5ecfaf15301dd0" + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "pub_key": "2b4ff8e5bc608cbdd12117171e7d265b6882ad597559caf67b5ecfaf15301dd0", + "priv_key_tweak": "17d93733d2acd8388279c24dc4413483802378c99f266f5961ac3338c5146861", + "signature": "7f8f909460c0357a2c1c784e92967e888c6b63ff799db3ce22e8acc715a42ab9177b9db2237d76db60e72bc30c827008266062506cd57f93f9b872529bd50376" + } + ] + } + } + ] + }, + { + "comment": "Single recipient: taproot input with odd y-value and non-taproot input", + "sending": [ + { + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_priv_keys": [ + [ + "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a", + true + ], + [ + "8d4751f6e8a3586880fb66c19ae277969bd5aa06f61c4ee2f1e2486efdf666d3", + false + ] + ], + "recipients": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "75f501f319db549aaa613717bd7af44da566d4d859b67fe436946564fafc47a3", + 1.0 + ] + ] + } + } + ], + "receiving": [ + { + "supports_labels": false, + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_pub_keys": [ + "782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "03e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d" + ], + "bip32_seed": "f00dbabe", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c", + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "labels": {}, + "outputs": [ + "75f501f319db549aaa613717bd7af44da566d4d859b67fe436946564fafc47a3" + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "pub_key": "75f501f319db549aaa613717bd7af44da566d4d859b67fe436946564fafc47a3", + "priv_key_tweak": "619a5a59a16d4a8e857ef48e63ef7c8195c858191d4e826205e8438ab70d059e", + "signature": "ba2e40de3b3acbc97d282f2d09b9c79936de109710e8d4139409964346f1221c3d4c823a1ee0a946f98b0ce644d136fbc5ea22cd73736fe05475174b25c01e62" + } + ] + } + } + ] + }, + { + "comment": "Multiple outputs: multiple outputs, same recipient", + "sending": [ + { + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_priv_keys": [ + [ + "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + false + ], + [ + "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a", + false + ] + ], + "recipients": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 2.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 3.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "64f1c7e8992352d18cdbca600b9e1c3a6025050d56a3e1cc833222e4f3b59e18", + 2.0 + ], + [ + "0a48c6ccc1d516e8244dc0153dc88db45f8f264357667c2057a29ca3c2445d09", + 3.0 + ] + ] + } + } + ], + "receiving": [ + { + "supports_labels": false, + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ], + "bip32_seed": "f00dbabe", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c", + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "labels": {}, + "outputs": [ + "64f1c7e8992352d18cdbca600b9e1c3a6025050d56a3e1cc833222e4f3b59e18", + "0a48c6ccc1d516e8244dc0153dc88db45f8f264357667c2057a29ca3c2445d09", + "c58e121044b23cba9b4695052229a9fd9e044b579f92864eb886ae7c99b021c9", + "4b15b75f3f184328c4a2f7c79357481ed06cf3b6f95512d5ed946fdc0b60d62b" + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "pub_key": "64f1c7e8992352d18cdbca600b9e1c3a6025050d56a3e1cc833222e4f3b59e18", + "priv_key_tweak": "96439446f13ddaab2c5bc5a59a08992fd9d33bf8563c8a1b362730f4dc022e30", + "signature": "3f6226feb9e4cafc0bdab8c9cfe085885308f3708c222bcec6cf26467685d897f51597abe39d1d279708e63513c7be23daed78607a98837060950493de188645" + }, + { + "pub_key": "0a48c6ccc1d516e8244dc0153dc88db45f8f264357667c2057a29ca3c2445d09", + "priv_key_tweak": "d39df91bd0e7825bfa1d30096febc5bf6fa7da79d7f25b7b4bea9538cc9a9f7f", + "signature": "be5f139f6eaad2d5eb75c6e307defb29925e16d55dbbc12872b0ab6aca38959c0c6a8f3f72bf82e3deb226cb539e117f9db4b04a5efb4e2eb01a86374f5baa12" + } + ] + } + } + ] + }, + { + "comment": "Multiple outputs: multiple outputs, multiple recipients", + "sending": [ + { + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_priv_keys": [ + [ + "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + false + ], + [ + "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a", + false + ] + ], + "recipients": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 2.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 3.0 + ], + [ + "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn", + 4.0 + ], + [ + "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn", + 5.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "64f1c7e8992352d18cdbca600b9e1c3a6025050d56a3e1cc833222e4f3b59e18", + 2.0 + ], + [ + "0a48c6ccc1d516e8244dc0153dc88db45f8f264357667c2057a29ca3c2445d09", + 3.0 + ], + [ + "c58e121044b23cba9b4695052229a9fd9e044b579f92864eb886ae7c99b021c9", + 4.0 + ], + [ + "4b15b75f3f184328c4a2f7c79357481ed06cf3b6f95512d5ed946fdc0b60d62b", + 5.0 + ] + ] + } + } + ], + "receiving": [ + { + "supports_labels": false, + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ], + "bip32_seed": "f00dbabe", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c", + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "labels": {}, + "outputs": [ + "64f1c7e8992352d18cdbca600b9e1c3a6025050d56a3e1cc833222e4f3b59e18", + "0a48c6ccc1d516e8244dc0153dc88db45f8f264357667c2057a29ca3c2445d09", + "c58e121044b23cba9b4695052229a9fd9e044b579f92864eb886ae7c99b021c9", + "4b15b75f3f184328c4a2f7c79357481ed06cf3b6f95512d5ed946fdc0b60d62b" + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "pub_key": "64f1c7e8992352d18cdbca600b9e1c3a6025050d56a3e1cc833222e4f3b59e18", + "priv_key_tweak": "96439446f13ddaab2c5bc5a59a08992fd9d33bf8563c8a1b362730f4dc022e30", + "signature": "3f6226feb9e4cafc0bdab8c9cfe085885308f3708c222bcec6cf26467685d897f51597abe39d1d279708e63513c7be23daed78607a98837060950493de188645" + }, + { + "pub_key": "0a48c6ccc1d516e8244dc0153dc88db45f8f264357667c2057a29ca3c2445d09", + "priv_key_tweak": "d39df91bd0e7825bfa1d30096febc5bf6fa7da79d7f25b7b4bea9538cc9a9f7f", + "signature": "be5f139f6eaad2d5eb75c6e307defb29925e16d55dbbc12872b0ab6aca38959c0c6a8f3f72bf82e3deb226cb539e117f9db4b04a5efb4e2eb01a86374f5baa12" + } + ] + } + }, + { + "supports_labels": false, + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ], + "bip32_seed": "decafbad", + "scan_priv_key": "060b751d7892149006ed7b98606955a29fe284a1e900070c0971f5fb93dbf422", + "spend_priv_key": "9902c3c56e84002a7cd410113a9ab21d142be7f53cf5200720bb01314c5eb920", + "labels": {}, + "outputs": [ + "64f1c7e8992352d18cdbca600b9e1c3a6025050d56a3e1cc833222e4f3b59e18", + "0a48c6ccc1d516e8244dc0153dc88db45f8f264357667c2057a29ca3c2445d09", + "c58e121044b23cba9b4695052229a9fd9e044b579f92864eb886ae7c99b021c9", + "4b15b75f3f184328c4a2f7c79357481ed06cf3b6f95512d5ed946fdc0b60d62b" + ] + }, + "expected": { + "addresses": [ + "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn" + ], + "outputs": [ + { + "pub_key": "c58e121044b23cba9b4695052229a9fd9e044b579f92864eb886ae7c99b021c9", + "priv_key_tweak": "567710d07bdaacc8de3f1cec467bcb162ed7daa6b901b59af257bcd7e39dffcf", + "signature": "d675fd6f55f42b61c8797c80d46048cfca5125bcef06e3a0ff555ace0e8f6d84da9b6f473b559376afd5ee11dc63c4415dc565f8272d2b673d39759f29c0d56a" + }, + { + "pub_key": "4b15b75f3f184328c4a2f7c79357481ed06cf3b6f95512d5ed946fdc0b60d62b", + "priv_key_tweak": "25dd11163a9a2853709c4c837aafb3347e2eaa875cf4c5170e2a3663879f4c58", + "signature": "ab872ee64623cf1ddb646c65159c09bc69cd64c6b60767a94934e12ec074f0fa7c9e4cc6a9bca2ec6592e4d64636a07fcfd71c622619c3bf46c5a2816aeb3456" + } + ] + } + } + ] + }, + { + "comment": "Receiving with labels: label with even parity", + "sending": [ + { + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_priv_keys": [ + [ + "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + false + ], + [ + "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a", + false + ] + ], + "recipients": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqhmem6grvs4nacsu0v5v5mjs934j7qfgkdkj8c95gyuru3tjpulvcwky2dz", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "2cbceeab2a4982841eb7dc34b8b4f19c04bf3bc083ebf984f5664366778eb50f", + 1.0 + ] + ] + } + } + ], + "receiving": [ + { + "supports_labels": true, + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ], + "bip32_seed": "f00dbabe", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c", + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "labels": { + "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5": "0000000000000000000000000000000000000000000000000000000000000002", + "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9": "0000000000000000000000000000000000000000000000000000000000000003", + "03348b4f5feb64b557dac8cfa10044bdc2094fca9147163bf514f68687e0d1dba6": "00000000000000000000000000000000000000000000000000000000000f4779" + }, + "outputs": [ + "2cbceeab2a4982841eb7dc34b8b4f19c04bf3bc083ebf984f5664366778eb50f" + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqhmem6grvs4nacsu0v5v5mjs934j7qfgkdkj8c95gyuru3tjpulvcwky2dz", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqc389f45lq7jyqt8jxq6fkskfukr2tlruf6w8cpcx2krntwe4fr9ykagp3j", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq4umqa5feskydh9xadc9jlc22c89tu0apcv72u2vkuwtsrgzf0uesq45zq9" + ], + "outputs": [ + { + "pub_key": "2cbceeab2a4982841eb7dc34b8b4f19c04bf3bc083ebf984f5664366778eb50f", + "priv_key_tweak": "96439446f13ddaab2c5bc5a59a08992fd9d33bf8563c8a1b362730f4dc022e32", + "signature": "0fa1b43afde9a03901dda91a0bd66fc82b6452c14a20718dc87dc70d4cedd9aeadf7c4c96116b8053c4aa113e26cea2fb64f8c408a8e8bc6e4fc9f6a06672b95" + } + ] + } + } + ] + }, + { + "comment": "Receiving with labels: label with odd parity", + "sending": [ + { + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_priv_keys": [ + [ + "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + false + ], + [ + "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a", + false + ] + ], + "recipients": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqc389f45lq7jyqt8jxq6fkskfukr2tlruf6w8cpcx2krntwe4fr9ykagp3j", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "6b4455de119f51bf4d4a12dea555f14a5dc2c1369af5fba4871c5367264c028d", + 1.0 + ] + ] + } + } + ], + "receiving": [ + { + "supports_labels": true, + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ], + "bip32_seed": "f00dbabe", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c", + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "labels": { + "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5": "0000000000000000000000000000000000000000000000000000000000000002", + "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9": "0000000000000000000000000000000000000000000000000000000000000003", + "03348b4f5feb64b557dac8cfa10044bdc2094fca9147163bf514f68687e0d1dba6": "00000000000000000000000000000000000000000000000000000000000f4779" + }, + "outputs": [ + "6b4455de119f51bf4d4a12dea555f14a5dc2c1369af5fba4871c5367264c028d" + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqhmem6grvs4nacsu0v5v5mjs934j7qfgkdkj8c95gyuru3tjpulvcwky2dz", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqc389f45lq7jyqt8jxq6fkskfukr2tlruf6w8cpcx2krntwe4fr9ykagp3j", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq4umqa5feskydh9xadc9jlc22c89tu0apcv72u2vkuwtsrgzf0uesq45zq9" + ], + "outputs": [ + { + "pub_key": "6b4455de119f51bf4d4a12dea555f14a5dc2c1369af5fba4871c5367264c028d", + "priv_key_tweak": "96439446f13ddaab2c5bc5a59a08992fd9d33bf8563c8a1b362730f4dc022e33", + "signature": "b4ea01f7f47bcdf131b5a3aa3a1c848faae75e661d63bfff84c230bcc96313d0b443b9b3a76718a7474d51994395739bc6041caabe98133e3697412e07e19c0a" + } + ] + } + } + ] + }, + { + "comment": "Receiving with labels: large label integer", + "sending": [ + { + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_priv_keys": [ + [ + "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + false + ], + [ + "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a", + false + ] + ], + "recipients": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq4umqa5feskydh9xadc9jlc22c89tu0apcv72u2vkuwtsrgzf0uesq45zq9", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "c3473bfcbe5e4d20d0790ae91f1b339bc15b46de64ca068d140118d0e325b849", + 1.0 + ] + ] + } + } + ], + "receiving": [ + { + "supports_labels": true, + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ], + "bip32_seed": "f00dbabe", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c", + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "labels": { + "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5": "0000000000000000000000000000000000000000000000000000000000000002", + "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9": "0000000000000000000000000000000000000000000000000000000000000003", + "03348b4f5feb64b557dac8cfa10044bdc2094fca9147163bf514f68687e0d1dba6": "00000000000000000000000000000000000000000000000000000000000f4779" + }, + "outputs": [ + "c3473bfcbe5e4d20d0790ae91f1b339bc15b46de64ca068d140118d0e325b849" + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqhmem6grvs4nacsu0v5v5mjs934j7qfgkdkj8c95gyuru3tjpulvcwky2dz", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqc389f45lq7jyqt8jxq6fkskfukr2tlruf6w8cpcx2krntwe4fr9ykagp3j", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq4umqa5feskydh9xadc9jlc22c89tu0apcv72u2vkuwtsrgzf0uesq45zq9" + ], + "outputs": [ + { + "pub_key": "c3473bfcbe5e4d20d0790ae91f1b339bc15b46de64ca068d140118d0e325b849", + "priv_key_tweak": "96439446f13ddaab2c5bc5a59a08992fd9d33bf8563c8a1b362730f4dc1175a9", + "signature": "ab9f3684cb497951fd013444d35909ed10669691d9fa3ac0be57f874a4df9f43c67647c9f17528110d2df0ce41dd3c05c04f4624629f8758fff1060049dc7d6b" + } + ] + } + } + ] + }, + { + "comment": "Multiple outputs with labels: un-labeled and labeled address; same recipient", + "sending": [ + { + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_priv_keys": [ + [ + "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + false + ], + [ + "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a", + false + ] + ], + "recipients": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqah4hxfsjdwyaeel4g8x2npkj7qlvf2692l5760z5ut0ggnlrhdzsy3cvsj", + 2.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "64f1c7e8992352d18cdbca600b9e1c3a6025050d56a3e1cc833222e4f3b59e18", + 1.0 + ], + [ + "7956317130124c32afd07b3f2432a3e92c1447cf58da95491a307ae3d564535e", + 2.0 + ] + ] + } + } + ], + "receiving": [ + { + "supports_labels": true, + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ], + "bip32_seed": "f00dbabe", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c", + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "labels": { + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798": "0000000000000000000000000000000000000000000000000000000000000001" + }, + "outputs": [ + "64f1c7e8992352d18cdbca600b9e1c3a6025050d56a3e1cc833222e4f3b59e18", + "7956317130124c32afd07b3f2432a3e92c1447cf58da95491a307ae3d564535e" + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqah4hxfsjdwyaeel4g8x2npkj7qlvf2692l5760z5ut0ggnlrhdzsy3cvsj" + ], + "outputs": [ + { + "pub_key": "64f1c7e8992352d18cdbca600b9e1c3a6025050d56a3e1cc833222e4f3b59e18", + "priv_key_tweak": "96439446f13ddaab2c5bc5a59a08992fd9d33bf8563c8a1b362730f4dc022e30", + "signature": "3f6226feb9e4cafc0bdab8c9cfe085885308f3708c222bcec6cf26467685d897f51597abe39d1d279708e63513c7be23daed78607a98837060950493de188645" + }, + { + "pub_key": "7956317130124c32afd07b3f2432a3e92c1447cf58da95491a307ae3d564535e", + "priv_key_tweak": "d39df91bd0e7825bfa1d30096febc5bf6fa7da79d7f25b7b4bea9538cc9a9f80", + "signature": "567f0d4d914456141ca83fe89e99f008c1f7ab9e9a65d4a60162840824737407acbaa61d7efa1a6af5d6439d213187e2f76696bb657dc709a0077bbf3b40e2f2" + } + ] + } + } + ] + }, + { + "comment": "Multiple outputs with labels: multiple outputs for labeled address; same recipient", + "sending": [ + { + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_priv_keys": [ + [ + "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + false + ], + [ + "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a", + false + ] + ], + "recipients": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqah4hxfsjdwyaeel4g8x2npkj7qlvf2692l5760z5ut0ggnlrhdzsy3cvsj", + 3.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqah4hxfsjdwyaeel4g8x2npkj7qlvf2692l5760z5ut0ggnlrhdzsy3cvsj", + 4.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "8890c19f005d6f6add5fef92d37ac6b161b7fdd5c1aef6eed1d32be3f216ac4c", + 3.0 + ], + [ + "7956317130124c32afd07b3f2432a3e92c1447cf58da95491a307ae3d564535e", + 4.0 + ] + ] + } + } + ], + "receiving": [ + { + "supports_labels": true, + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ], + "bip32_seed": "f00dbabe", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c", + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "labels": { + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798": "0000000000000000000000000000000000000000000000000000000000000001" + }, + "outputs": [ + "8890c19f005d6f6add5fef92d37ac6b161b7fdd5c1aef6eed1d32be3f216ac4c", + "7956317130124c32afd07b3f2432a3e92c1447cf58da95491a307ae3d564535e" + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqah4hxfsjdwyaeel4g8x2npkj7qlvf2692l5760z5ut0ggnlrhdzsy3cvsj" + ], + "outputs": [ + { + "pub_key": "8890c19f005d6f6add5fef92d37ac6b161b7fdd5c1aef6eed1d32be3f216ac4c", + "priv_key_tweak": "96439446f13ddaab2c5bc5a59a08992fd9d33bf8563c8a1b362730f4dc022e31", + "signature": "f0eb3b826553709356c351e1ced49a72900f261be18e64914c3c694af94595a4a80417ecbf5e86fde8b08e451fb42ec36b7a9d733eb42f92206f4f6c78da66bb" + }, + { + "pub_key": "7956317130124c32afd07b3f2432a3e92c1447cf58da95491a307ae3d564535e", + "priv_key_tweak": "d39df91bd0e7825bfa1d30096febc5bf6fa7da79d7f25b7b4bea9538cc9a9f80", + "signature": "567f0d4d914456141ca83fe89e99f008c1f7ab9e9a65d4a60162840824737407acbaa61d7efa1a6af5d6439d213187e2f76696bb657dc709a0077bbf3b40e2f2" + } + ] + } + } + ] + }, + { + "comment": "Multiple outputs with labels: un-labeled, labeled, and multiple outputs for labeled address; multiple recipients", + "sending": [ + { + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_priv_keys": [ + [ + "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + false + ], + [ + "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a", + false + ] + ], + "recipients": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 5.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqah4hxfsjdwyaeel4g8x2npkj7qlvf2692l5760z5ut0ggnlrhdzsy3cvsj", + 6.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq562yg7htxyg8eq60rl37uul37jy62apnf5ru62uef0eajpdfrnp5cmqndj", + 7.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq562yg7htxyg8eq60rl37uul37jy62apnf5ru62uef0eajpdfrnp5cmqndj", + 8.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "64f1c7e8992352d18cdbca600b9e1c3a6025050d56a3e1cc833222e4f3b59e18", + 5.0 + ], + [ + "7956317130124c32afd07b3f2432a3e92c1447cf58da95491a307ae3d564535e", + 6.0 + ], + [ + "1b90a42136fef9ff2ca192abffc7be4536dc83d4e61cf18ae078f7e92b297cce", + 7.0 + ], + [ + "87a82600c08a255bc97d172e10816e322967eed6a77c9f37dd926492d7fdc106", + 8.0 + ] + ] + } + } + ], + "receiving": [ + { + "supports_labels": true, + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ], + "bip32_seed": "f00dbabe", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c", + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "labels": { + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798": "0000000000000000000000000000000000000000000000000000000000000001", + "02db0c51cc634a4096374b0b895584a3ca2fb3bea4fd0ee2361f8db63a650fcee6": "0000000000000000000000000000000000000000000000000000000000000539" + }, + "outputs": [ + "64f1c7e8992352d18cdbca600b9e1c3a6025050d56a3e1cc833222e4f3b59e18", + "7956317130124c32afd07b3f2432a3e92c1447cf58da95491a307ae3d564535e", + "1b90a42136fef9ff2ca192abffc7be4536dc83d4e61cf18ae078f7e92b297cce", + "87a82600c08a255bc97d172e10816e322967eed6a77c9f37dd926492d7fdc106" + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqah4hxfsjdwyaeel4g8x2npkj7qlvf2692l5760z5ut0ggnlrhdzsy3cvsj", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq562yg7htxyg8eq60rl37uul37jy62apnf5ru62uef0eajpdfrnp5cmqndj" + ], + "outputs": [ + { + "pub_key": "64f1c7e8992352d18cdbca600b9e1c3a6025050d56a3e1cc833222e4f3b59e18", + "priv_key_tweak": "96439446f13ddaab2c5bc5a59a08992fd9d33bf8563c8a1b362730f4dc022e30", + "signature": "3f6226feb9e4cafc0bdab8c9cfe085885308f3708c222bcec6cf26467685d897f51597abe39d1d279708e63513c7be23daed78607a98837060950493de188645" + }, + { + "pub_key": "7956317130124c32afd07b3f2432a3e92c1447cf58da95491a307ae3d564535e", + "priv_key_tweak": "d39df91bd0e7825bfa1d30096febc5bf6fa7da79d7f25b7b4bea9538cc9a9f80", + "signature": "567f0d4d914456141ca83fe89e99f008c1f7ab9e9a65d4a60162840824737407acbaa61d7efa1a6af5d6439d213187e2f76696bb657dc709a0077bbf3b40e2f2" + }, + { + "pub_key": "1b90a42136fef9ff2ca192abffc7be4536dc83d4e61cf18ae078f7e92b297cce", + "priv_key_tweak": "255a912ad6cdebc0842d49fd9f7b2d81ee37d66c62839879371b699010f78ef1", + "signature": "aa4cc7be2d90f30984d93535058f4894a6e0c7698deaaef179eda55724cc214e8e6ed055d437f1bf37c8c5c5431dad5080d03200cdd861a5b5e3855515e15d61" + }, + { + "pub_key": "87a82600c08a255bc97d172e10816e322967eed6a77c9f37dd926492d7fdc106", + "priv_key_tweak": "d7535d792cb1388ab0b3bd5ff57337436d62f7719c1796beb5d80ab2fa34f307", + "signature": "d68d0005118fcaae6d970925b452d038a03fda40d50aa9d6d3b4aff8189f226c71428838eadaf55662048f549bc7b19380438f09df9344eff30b96497b6aafa3" + } + ] + } + } + ] + }, + { + "comment": "Single recipient: use silent payments for sender change", + "sending": [ + { + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_priv_keys": [ + [ + "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1", + false + ], + [ + "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a", + false + ] + ], + "recipients": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ], + [ + "sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqll5497pp2gcr4cmq0v5nv07x8u5jswmf8ap2q0kxmx8628mkqanyu63ck8", + 2.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "64f1c7e8992352d18cdbca600b9e1c3a6025050d56a3e1cc833222e4f3b59e18", + 1.0 + ], + [ + "0050c52a32566c0dfb517e473c68fedce4bd4543d219348d3bbdceeeb5755e34", + 2.0 + ] + ] + } + } + ], + "receiving": [ + { + "supports_labels": true, + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ], + "bip32_seed": "deadbeef", + "scan_priv_key": "11b7a82e06ca2648d5fded2366478078ec4fc9dc1d8ff487518226f229d768fd", + "spend_priv_key": "b8f87388cbb41934c50daca018901b00070a5ff6cc25a7e9e716a9d5b9e4d664", + "labels": { + "02295dc38e877b754c0d0ed767434f1572cf34a82ccc06ffea1d9e04f1f7878e1a": "91cb04398a508c9d995ff4a18e5eae24d5e9488309f189120a3fdbb977978c46" + }, + "outputs": [ + "64f1c7e8992352d18cdbca600b9e1c3a6025050d56a3e1cc833222e4f3b59e18", + "0050c52a32566c0dfb517e473c68fedce4bd4543d219348d3bbdceeeb5755e34" + ] + }, + "expected": { + "addresses": [ + "sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqauj52ymtc4xdkmx3tgyhrsemg2g3303xk2gtzfy8h8ejet8fz8jcw23zua", + "sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqll5497pp2gcr4cmq0v5nv07x8u5jswmf8ap2q0kxmx8628mkqanyu63ck8" + ], + "outputs": [ + { + "pub_key": "0050c52a32566c0dfb517e473c68fedce4bd4543d219348d3bbdceeeb5755e34", + "priv_key_tweak": "2e9c2a37cfa7827907d36357f0632d258dbd14b3a7854937ecf732fb6acefdc8", + "signature": "6ba068ee36454c5ff002082578e234917de9e384df739c43a8b7c4cce58724cba4479191cf972b235bc4bb6c2a8d6081650d1d5ba043b59bd51d6ac15d55b396" + } + ] + } + }, + { + "supports_labels": false, + "given": { + "outpoints": [ + [ + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + 0 + ], + [ + "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + 0 + ] + ], + "input_pub_keys": [ + "025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "03782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ], + "bip32_seed": "f00dbabe", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c", + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "labels": {}, + "outputs": [ + "64f1c7e8992352d18cdbca600b9e1c3a6025050d56a3e1cc833222e4f3b59e18", + "0050c52a32566c0dfb517e473c68fedce4bd4543d219348d3bbdceeeb5755e34" + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "pub_key": "64f1c7e8992352d18cdbca600b9e1c3a6025050d56a3e1cc833222e4f3b59e18", + "priv_key_tweak": "96439446f13ddaab2c5bc5a59a08992fd9d33bf8563c8a1b362730f4dc022e30", + "signature": "3f6226feb9e4cafc0bdab8c9cfe085885308f3708c222bcec6cf26467685d897f51597abe39d1d279708e63513c7be23daed78607a98837060950493de188645" + } + ] + } + } + ] + } +] \ No newline at end of file