This PR adds a new Silent Payments (BIP352) module to secp256k1. The following routines are provided ($a_i$ are input private keys, $A_i$ are input public keys, $b$ and $B$ denote recipient privkeys/pubkeys that would be encoded in silent payment addresses, $d$ and $P$ the keypair for the actual transaction taproot x-only output):
| Side | Function | Inputs | Outputs |
|---|---|---|---|
| Sender | _create_private_tweak_data |
$a_1...a_n$, $outpoint_L$ | $a_{sum} = (a_1 + a_2 + ... + a_n)$<br>$inputhash = hash_I(outpoint_L || (a_{sum} * G))$ |
| Receiver | _create_public_tweak_data |
$A_1...A_n$, $outpoint_L$ | $A_{sum} = (A_1 + A_2 + ... + A_n)$<br>$inputhash = hash_I(outpoint_L || A_{sum})$ |
| Receiver | _create_tweaked_pubkey | $A_{sum}, inputhash$ | $A_{tweaked} = inputhash * A_{sum}$
| Both | _create_shared_secret | $Pub$, $sec$<br>(Sender: $B_{scan}, a_{sum}$<br>Receiver: $A_{sum}, b_{scan}$<br>Lightclient: $A_{tweaked}, b_{scan}$) | $SS = (inputhash * sec) * Pub$ (ECDH) |
| Receiver | _create_label_tweak | $b_{scan}, m$ | $labeltweak = hash_L(b_{scan} || m)$<br>$label = labeltweak * G$ |
| Receiver | _create_address_spend_pubkey | $B_{spend}, label$ | $B_m = B_{spend} + label$ |
| Sender | _sender_create_output_pubkey | $SS, B_m, k$ | $P_{xonly} = B_m + hash_S(SS || k) * G$ |
| Receiver | _receiver_scan_output | $SS, B_m, k, tx_{output}$ | $t_k = hash_S(SS || k)$<br>$P_{xonly} = B_m + t_k * G$ [not returned]<br>$directmatch = P_{xonly} == tx_{output}$<br>if $directmatch == 0$:<br>$\quad label1 = tx_{output} - P$<br>$\quad label2 = -tx_{output} - P$
| Receiver | _create_output_seckey | $b_{spend}, t_k, (labeltweak)$ | $d = (b_{spend} + labeltweak) + t_k$ |
where
- $hash_I$ denotes a SHA256 tagged hash with tag "BIP0352/Inputs"
- $hash_L$ denotes a SHA256 tagged hash with tag "BIP0352/Label"
- $hash_S$ denotes a SHA256 tagged hash with tag "BIP0352/SharedSecret"
For ending up with output key material used for sending to / scanning for / spending from, both sides would follow the chain of tweak_data -> shared_secret -> output key. The public tweak data can be useful for faster scanning of output transactions by storing them in an index, see e.g. Bitcoin Core PR https://github.com/bitcoin/bitcoin/pull/28241. Private tweak data is arguably less useful, so in theory one could collapse the tweak data and shared secret creation functions into a single one, but IMHO it's nicer if the API is symmetric.
As discussed in #1427 (comment), the approach of passing in two separate key pairs for taproot and non-taproot inputs is followed here. This may seem a bit confusing at first, but has the advantage that the caller doesn't have to deal with enforcing even y-parity for key material manually (e.g. negating private keys of taproot outputs if they would end up in an odd point), which seems error-prone.
The last commit contains the BIP352 test vectors, converted to C code with a Python script. An earlier version of the tests, directly written in Python (by calling in to the secp256k1 shared library using ctypes) can still be found here: https://github.com/theStack/secp256k1/tree/silentpayments-module-demo_old5