summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Falbesoner <sebastian.falbesoner@gmail.com>2024-06-19 23:56:42 +0200
committerSebastian Falbesoner <sebastian.falbesoner@gmail.com>2024-06-20 00:33:14 +0200
commitfe0f83531e35f76d3582813da4cbb518d9bb1d12 (patch)
treea7ae1729a193c645f2e9920d071263c4ccdbb624
parent85cda4e225b4d5fd7aff403f69d827f23f6afbbc (diff)
downloadbips-fe0f83531e35f76d3582813da4cbb518d9bb1d12.tar.xz
BIP-352: generate `input_hash` after summing up keys (simplification)
For both sender and receiver, generating the input hash is currently listed as the first step. This already involves summing up the public keys, even though summing up key material (private keys for sender, public keys of inputs for receiver) is then again listed explicitly in later steps. It seems to be more obvious and less redundant (and also hopefully less confusing for readers) to reorder the instructions to calculate the input_hash _after_ the key aggregation is done to reuse the result. In case of the sender, the private key sum has to be multiplicated with G in order to the get to the corresponding input pubkey sum. This also corresponds to the current BIP352 implementation in the secp256k1 library (https://github.com/bitcoin-core/secp256k1/pull/1519). The reference implementation in Python here is adapted for the sender side, the receiver side has already generated the input_hash after summing up the pubkeys.
-rw-r--r--bip-0352.mediawiki8
-rwxr-xr-xbip-0352/reference.py8
2 files changed, 7 insertions, 9 deletions
diff --git a/bip-0352.mediawiki b/bip-0352.mediawiki
index 4cbf9d7..f36f359 100644
--- a/bip-0352.mediawiki
+++ b/bip-0352.mediawiki
@@ -98,9 +98,8 @@ In our simplified example we have been referring to Alice's transactions as havi
Alice performs the tweak with the sum of her input private keys in the following manner:
-* Let ''A = A<sub>1</sub> + A<sub>2</sub> + ... + A<sub>n</sub>''
-* Let ''input_hash = hash(outpoint<sub>L</sub> || A)'', where ''outpoint<sub>L</sub>'' is the smallest outpoint lexicographically<ref name="why_smallest_outpoint">'''Why use the lexicographically smallest outpoint for the hash?''' Recall that the purpose of including the input hash is so that the sender and receiver can both come up with a deterministic nonce that ensures that a unique address is generated each time, even when reusing the same scriptPubKey as an input. Choosing the smallest outpoint lexicographically satisifes this requirement, while also ensuring that the generated output is not dependent on the final ordering of inputs in the transaction. Using a single outpoint also works well with memory constrained devices (such as hardware signing devices) as it does not require the device to have the entire transaction in memory in order to generate the silent payment output.</ref>
* Let ''a = a<sub>1</sub> + a<sub>2</sub> + ... + a<sub>n</sub>''
+* Let ''input_hash = hash(outpoint<sub>L</sub> || (a·G))'', where ''outpoint<sub>L</sub>'' is the smallest outpoint lexicographically<ref name="why_smallest_outpoint">'''Why use the lexicographically smallest outpoint for the hash?''' Recall that the purpose of including the input hash is so that the sender and receiver can both come up with a deterministic nonce that ensures that a unique address is generated each time, even when reusing the same scriptPubKey as an input. Choosing the smallest outpoint lexicographically satisifes this requirement, while also ensuring that the generated output is not dependent on the final ordering of inputs in the transaction. Using a single outpoint also works well with memory constrained devices (such as hardware signing devices) as it does not require the device to have the entire transaction in memory in order to generate the silent payment output.</ref>
* Let ''P<sub>0</sub> = B + hash(input_hash·a·B || 0)·G''
''' Spend and Scan Key '''
@@ -284,7 +283,6 @@ The receiver obtains the public key from the ''scriptSig''. The receiver MUST pa
The sender and receiver MUST calculate an input hash for the transaction in the following manner:
-* Let ''A = A<sub>1</sub> + A<sub>2</sub> + ... + A<sub>n</sub>'', where each ''A<sub>i</sub>'' is the public key of an input from the ''[[#inputs-for-shared-secret-derivation|Inputs For Shared Secret Derivation]]'' list<ref name="why_include_A"></ref>
* Let ''input_hash = hash<sub>BIP0352/Inputs</sub>(outpoint<sub>L</sub> || A)'', where ''outpoint<sub>L</sub>'' is the smallest outpoint lexicographically by txid and vout used in the transaction<ref name="why_smallest_outpoint"></ref>
=== Sender ===
@@ -301,10 +299,10 @@ The sending wallet performs coin selection as usual with the following restricti
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:
-* Generate the ''input_hash'' with the smallest outpoint lexicographically, using the method described above
* Collect the private keys for each input from the ''[[#inputs-for-shared-secret-derivation|Inputs For Shared Secret Derivation]]'' list
* For each private key ''a<sub>i</sub>'' 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 coordinate and negate the private key if not<ref name="why_negate_taproot_private_keys">'''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 coordinate when performing the ECDH step since the receiver will assume the even Y coordinate when summing the taproot X-only public keys.</ref>
* Let ''a = a<sub>1</sub> + a<sub>2</sub> + ... + a<sub>n</sub>'', where each ''a<sub>i</sub>'' has been negated if necessary
+* Generate the ''input_hash'' with the smallest outpoint lexicographically and ''A = a·G'', using the method described above
* Group receiver silent payment addresses by ''B<sub>scan</sub>'' (e.g. each group consists of one ''B<sub>scan</sub>'' and one or more ''B<sub>m</sub>'')
* For each group:
** Let ''ecdh_shared_secret = input_hash·a·B<sub>scan</sub>''
@@ -335,8 +333,8 @@ A scan and spend key pair using BIP32 derivation are defined (taking inspiration
If each of the checks in ''[[#scanning-silent-payment-eligible-transactions|Scanning silent payment eligible transactions]]'' passes, the receiving wallet must:
-* Generate the ''input_hash'' with the smallest outpoint lexicographically, using the method described above
* Let ''A = A<sub>1</sub> + A<sub>2</sub> + ... + A<sub>n</sub>'', where each ''A<sub>i</sub>'' is the public key of an input from the ''[[#inputs-for-shared-secret-derivation|Inputs For Shared Secret Derivation]]'' list
+* Generate the ''input_hash'' with the smallest outpoint lexicographically and ''A'', using the method described above
* Let ''ecdh_shared_secret = input_hash·b<sub>scan</sub>·A''
* Check for outputs:
** Let ''outputs_to_check'' be the taproot output keys from all taproot outputs in the transaction (spent and unspent).
diff --git a/bip-0352/reference.py b/bip-0352/reference.py
index 9f43695..9b35d04 100755
--- a/bip-0352/reference.py
+++ b/bip-0352/reference.py
@@ -117,7 +117,7 @@ def decode_silent_payment_address(address: str, hrp: str = "tsp") -> Tuple[ECPub
return B_scan, B_spend
-def create_outputs(input_priv_keys: List[Tuple[ECKey, bool]], input_hash: bytes, recipients: List[str], hrp="tsp") -> List[str]:
+def create_outputs(input_priv_keys: List[Tuple[ECKey, bool]], outpoints: List[COutPoint], recipients: List[str], hrp="tsp") -> List[str]:
G = ECKey().set(1).get_pubkey()
negated_keys = []
for key, is_xonly in input_priv_keys:
@@ -127,6 +127,7 @@ def create_outputs(input_priv_keys: List[Tuple[ECKey, bool]], input_hash: bytes,
negated_keys.append(k)
a_sum = sum(negated_keys)
+ input_hash = get_input_hash(outpoints, a_sum * G)
silent_payment_groups: Dict[ECPubKey, List[ECPubKey]] = {}
for recipient in recipients:
B_scan, B_m = decode_silent_payment_address(recipient, hrp=hrp)
@@ -236,9 +237,8 @@ if __name__ == "__main__":
sending_outputs = []
if (len(input_pub_keys) > 0):
- A_sum = reduce(lambda x, y: x + y, input_pub_keys)
- input_hash = get_input_hash([vin.outpoint for vin in vins], A_sum)
- sending_outputs = create_outputs(input_priv_keys, input_hash, given["recipients"], hrp="sp")
+ outpoints = [vin.outpoint for vin in vins]
+ sending_outputs = create_outputs(input_priv_keys, outpoints, given["recipients"], hrp="sp")
# Note: order doesn't matter for creating/finding the outputs. However, different orderings of the recipient addresses
# will produce different generated outputs if sending to multiple silent payment addresses belonging to the