New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add BIP352 silentpayments
module
#1519
base: master
Are you sure you want to change the base?
Add BIP352 silentpayments
module
#1519
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept ACK
Left some initial feedback, especially around the scanning routine, will do an in-depth review round soon. Didn't look closer at the public_data
type routines and the examples yet.
3d08027
to
8b48bf1
Compare
8b48bf1
to
f5585d4
Compare
Updated 8b48bf1 -> f5585d4 (bip352-silentpayments-module-rebase -> bip352-silentpayments-module-02, compare):
For the label scanning, I looked for an example of using an invalid public key but didn't see anything except for the I also used |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Second review round through, looks good so far! Left a bunch of nits, mostly about naming and missing ARG_CHECKS etc.
&(*(const secp256k1_silentpayments_recipient **)pk1)->scan_pubkey, | ||
&(*(const secp256k1_silentpayments_recipient **)pk2)->scan_pubkey |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pk1
/pk2
are addresses for _silentpayments_recipient instances, so involving double-pointers here doesn't seem to be necessary:
&(*(const secp256k1_silentpayments_recipient **)pk1)->scan_pubkey, | |
&(*(const secp256k1_silentpayments_recipient **)pk2)->scan_pubkey | |
&((const secp256k1_silentpayments_recipient *)pk1)->scan_pubkey, | |
&((const secp256k1_silentpayments_recipient *)pk2)->scan_pubkey |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll admit, I don't fully understand why the double pointers are necessary, but they are: when I removed them, my tests would pass locally but the branch would fail the CI. I noticed the double pointers are also used for the secp256k1_pubkey_sort
function, so leaving for now.
|
||
/* Sanity check inputs. */ | ||
VERIFY_CHECK(ctx != NULL); | ||
ARG_CHECK(recipients != NULL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could also ARG_CHECK for n_recipients here:
ARG_CHECK(recipients != NULL); | |
ARG_CHECK(recipients != NULL); | |
ARG_CHECK(n_recipients >= 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added. Out of curiosity, I noticed we have VERIFY_CHECK
and ARG_CHECK
, do you know when one is preferred over the other? In most cases, I see VERIFY_CHECK
being used to check the ctx
, but there were a few places where VERIFY_CHECK
was also being used to check the arguments.
9d75190
to
1a3a00b
Compare
Thanks for the thorough review, @theStack ! I've addressed your feedback, along with some other changes. Update f5585d4 -> 1a3a00b (bip352-silentpayments-module-02 -> bip352-silentpayments-module-03, compare)
The sending tests now check that the generated outputs match exactly one of the possible expected output sets. Previously, the sending tests were checking that the generated outputs exist in the array of all possible outputs, but this wouldn't catch a bug where |
1a3a00b
to
92f5920
Compare
Rebased on #1518 1a3a00b -> 92f5920 (bip352-silentpayments-module-03 -> bip352-silentpayments-module-03-rebase, compare) |
Add function for calculating either a*B or A*b. This function is used by both the sender and the recipient (i.e. a*B == A*b). Add functions for generating the tagged input hash, as this is used when creating the shared secret. In a later commit, `create_shared_secret` will be exposed in the API for the receiver to use when scanning as a light client.
Add methods for creating an xonly output from a shared secret. This involves adding a tagged hash for creating the output. This function will be exposed in the API in a later commit for the receiver to use when scanning as a light client.
Given a set of private keys, the smallest outpoint, and list of recipients this function handles the entire sender flow: 1. Sum up the private keys 2. Calculate the input_hash 3. For each recipient: 3a. Calculate a shared secret 3b. Create the requested number of outputs This function assumes a single sender context in that it requires the sender to have access to all of the private keys. In the future, this API may be expanded to allow for a multiple senders or for a single sender who does not have access to all private keys at any given time, but for now these modes are considered out of scope / unsafe.
Add function for creating a label tweak. This requires a tagged hash function for labels. This function is used by the receiver for creating labels to be used for a) creating labelled addresses and b) to populate a labels cache when scanning. Add function for creating a labelled spend pubkey. This involves taking a label tweak, turning it into a public key and adding it to the spend public key. This function is used by the receiver to create a labelled silent payment address.
Add data type for passing around the summed input public key (A_sum) and the input hash tweak (input_hash). This data is passed to the scanner before the ECDH step as two separate elements so that the scanner can multiply b_scan * input_hash before doing ECDH. Add functions for deserializing / serializing a public_data object to and from a public key. When serializing a public_data object, the input_hash is multplied into A_sum. This is so the object can be stored as public key for wallet rescanning later, or to vend to light clients. For the light client, a `_parse` function is added which parses the compressed public key serialization into a `public_data` object.
Add routine for scanning a transaction and returning the necessary spending data for any found outputs. This function works with labels via a lookup callback and requires access to the transaction outputs. Requiring access to the transaction outputs is not suitable for light clients, but light client support is enabled in followup commits.
Expose a shared secret routine in the public API and a output_pubkey creation routine. These are simple wrappers around the same functions the module is using under the hood. They are exposed for the light client as the minimal set of operations they need to do scanning without access to the transaction outputs. This means the light client will need to manage their own scanning state, so wherever possible it is preferrable to use the `_recipient_scan_ouputs` function.
Demonstrate sending, scanning, and light client scanning.
Add a benchmark for `scan_outputs` as this is the most performance critical part of silent payments.
The vectors are generated with a Python script that converts the .json file from the BIP to C code: $ ./tools/tests_silentpayments_generate.py test_vectors.json > ./src/modules/silentpayments/vectors.h
92f5920
to
56ed901
Compare
Rebased on master (following #1518 merge) 92f5920 -> 56ed901 (bip352-silentpayments-module-03-rebase -> bip352-silentpayments-module-04-rebase, compare) |
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
taproot_seckeys
andplain_seckeys
Two lists are used since the
taproot_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_silentpayment_recipient
objectsThese 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 of
recipients
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 ordersilentpayments_sender_create_outputs
to generate the xonly public keys for the recipientsThis 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
taproot_pubkeys
andplain_pubeys
This avoids the caller needing to convert taproot public keys into compressed public keys (and vice versa)
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)
silentpayments_recipient_scan_outputs
to scan the transaction outputs and return the tweak data (and optionally label information) needed for spending laterIn 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: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
k = 0
)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 bitcoin/bitcoin#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: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 outputsinput_hash
from the caller, which makes for an overall simpler API IMOI 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