This PR adds a new Silent Payments (BIP352) module to secp256k1. It is a continuation of the work started in #1471.
The module implements the full protocol, except for transaction input filtering and silent payment address encoding / decoding as those will be the responsibility of the wallet software. It is organized with functions for sending (prefixed with _sender
) and receiving (prefixed by _recipient
).
For sending
- Collect private keys into two lists:
taproot_seckeys
andplain_seckeys
Two lists are used since thetaproot_seckeys
may need negation.taproot_seckeys
are passed as keypairs to avoid the function needing to compute the public key to determine parity.plain_seckeys
are passed as just secret keys - Create the
_silentpayment_recipient
objects These structs hold the scan and spend public key and an index for remembering the original ordering. It is expected that a caller will start with a list of silent payment addresses (with the desired amounts), convert these into an array ofrecipients
and then match the generated outputs back to the original silent payment addresses. The index is used to return the generated outputs in the original order - Call
silentpayments_sender_create_outputs
to generate the xonly public keys for the recipients This function can be called with one or more recipients. The same recipient may be repeated to generate multiple outputs for the same recipient
For scanning
- Collect the public keys into two lists
taproot_pubkeys
andplain_pubeys
This avoids the caller needing to convert taproot public keys into compressed public keys (and vice versa) - Compute the input data needed, i.e. sum the public keys and compute the
input_hash
This is done as a separate step to allow the caller to reuse this output if scanning for multiple scan keys. It also allows a caller to use this function for aggregating the transaction inputs and storing them in an index to vend to light clients later (or for faster rescans when recovering a wallet) - Call
silentpayments_recipient_scan_outputs
to scan the transaction outputs and return the tweak data (and optionally label information) needed for spending later
In addition, a few utility functions for labels are provided for the recipient for creating a label tweak and tweaked spend public key for their address. Finally, two functions are exposed in the API for supporting light clients, _recipient_created_shared_secret
and _recipient_create_output_pubkey
. These functions enable incremental scanning for scenarios where the caller does not have access to the transaction outputs:
- Calculating a shared secret This is done as a separate step to allow the caller to reuse the shared secret result when creating outputs and avoid needing to do a costly ECDH every time they need to check for an additional output
- Generate an output (with
k = 0
) - Check if the output exists in the UTXO set (using their preferred light client protocol)
- If the output exists, proceed by generating a new output from the shared secret with
k++
See examples/silentpayments.c
for a demonstration of how the API is expected to be used.
Note for reviewers
My immediate goal is to get feedback on the API so that I can pull this module into https://github.com/bitcoin/bitcoin/pull/28122 (silent payments in the bitcoin core wallet). That unblocks from finishing the bitcoin core PRs while work continues on this module.
Notable differences between this PR and the previous version
See #1427 and #1471 for discussions on the API design. This iteration of the module attempts to be much more high level and incorporate the feedback from #1471. I also added a secp256k1_silentpayments_public_data
opaque data type, which contains the summed public key and the input_hash. My motivation here was:
- I caught myself mixing up the order of arguments between
A_sum
andrecipient_spend_key
, which was impossible to catch withARG_CHECKS
and would result in the scanning process finishing without errors, but not finding any outputs - Combining public key and input_hash into the same data type allows for completely hiding
input_hash
from the caller, which makes for an overall simpler API IMO
I also removed the need for the recipient to generate a shared secret before using the secp256k1_silentpayments_recipient_scan_outputs
function and instead create the shared secret inside the function.
Outstanding work
- clean up the testing code
- improve test coverage (currently only using the BIP352 test vectors)
- optimize the implementation, where possible