-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add "silentpayments" module implementing BIP352 (take 5, using "LabelSet" scanning approach) #1792
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
base: master
Are you sure you want to change the base?
Add "silentpayments" module implementing BIP352 (take 5, using "LabelSet" scanning approach) #1792
Conversation
Add a routine for the entire sending flow which takes a set of private keys,
the smallest outpoint, and list of recipients and returns a list of
x-only public keys by performing the following steps:
1. Sum up the private keys
2. Calculate the input_hash
3. For each recipient group:
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.
Internal to the library, add:
1. A function for creating shared secrets (i.e., a*B or b*A)
2. A function for generating the "SharedSecret" tagged hash
3. A function for creating a single output public key
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 labeled addresses and b) to populate a labels cache when scanning. Add function for creating a labeled 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 labeled silent payment address. Add tests for the label API.
|
Concept ACK per my rationale in the take 4 PR
I think it's good to add this documentation and warn of the performance penalties with a specific number as a reference is good but I still think we should probably add a hard limit too which can be higher. I was thinking of the order of 1000 without having run any actual benchmarks. From a high level perspective I think such a number implies that the user probably doesn't generate new labels manually and uses the static sp addresses in different places (the typical usage pattern we mostly expect I think). Instead such a user may have some kind of automation in place that churns out new sp addresses with new labels and that the number of labels may potentially grow unbounded. The goal would be that such a user runs into this limit rather than that their servers are brought to their knees unexpectedly, which should be the better outcome in my mind. But curious what others think. |
e00fa04 to
8b196ef
Compare
|
@fjahr: Sounds reasonable. I've added a hard limit on the number of labels as suggested, defined by a public API constant Some optional ideas on top that came to my mind:
[1] worst-case benchmarks on my machine: |
Add routine for scanning a transaction and returning the necessary spending data for any found outputs. This function works with labels via expliticly passing in a list of (label, label_tweak) entries and needs access to the transaction outputs. Requiring access to the transaction outputs is not suitable for light clients, but light client support is provided in a future release. Add an opaque data type for passing around the prevout public key 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 the scan_key * input_hash before doing ECDH. Finally, add test coverage for the receiving API.
Demonstrate sending and scanning on full nodes.
Add benchmarks for a full transaction scan, over a L/N matrix (L/N, L...number of labels, N...number of tx outputs) for the common case and worst-case. Only benchmarks for scanning are added as this is the most performance critical portion of the protocol. Co-authored-by: Sebastian Falbesoner <91535+thestack@users.noreply.github.com>
Add the BIP-352 test vectors. 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 Co-authored-by: Ron <4712150+macgyver13@users.noreply.github.com> Co-authored-by: Sebastian Falbesoner <91535+thestack@users.noreply.github.com> Co-authored-by: Tim Ruffing <1071625+real-or-random@users.noreply.github.com>
Co-authored-by: Jonas Nick <2582071+jonasnick@users.noreply.github.com> Co-authored-by: Sebastian Falbesoner <91535+thestack@users.noreply.github.com>
Test midstate tags used in silent payments.
8b196ef to
c2061df
Compare
w0xlt
left a comment
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.
Approach ACK. Will review in detail soon.
For the reasons outlined in the "Pros and cons" section of the PR description, particularly the simpler API, improved common-case performance for small label sets and mitigation of the worst-case scanning attack—the LabelSet approach seems like the right default choice.
Given that most users are expected to have a relatively small number of labels (likely well within the SECP256K1_SILENTPAYMENTS_MAX_LABELS bound), this should cover the typical use case nicely.
If there's sufficient demand, a hybrid approach could always be explored in a future iteration to accommodate more specialized scenarios.
|
It might be good to have a separate Github issue to discuss this. I think it's fine to initially go for the LabelSet approach, since many current implementations are mobile, which means they benefit from the safety this provides and won't run into the label limit anytime soon. To reduce review burden on this PR, we can add the hybrid approach in a followup, where we pick the BIP mechanism based on e.g. the I do think that hybrid approach is useful. Someone who goes through the trouble of automating label generation might happily add some CPU cores if lets them avoid the complexity of splitting their users across multiple watch keys. Especially if the cost of an attack far outweighs the cost to deal with it. We could document the hardware required to guarantee that a worst case block takes less than 10 minutes to process. For the hybrid approach that's just a single number IIUC. For the LabelSet approach it's a function of L. With that in place a warning should be enough IMO, for |
|
I had an offline conversation with @RubenSomsen about this. The conversations around the scanning approaches have been focused on the time it takes to complete a tx scan, but it's also important that we consider the time it takes to scan an entire Block. BIP-style scanning scales with the number of outputs, independently of the number of transactions. LabelSet scanning scales with the number of labels * the number of transactions. LabelSet will be Fast when you scan Blocks with few transactions and few labels, but it will get slower compared to BIP-style scanning as the number of transactions increases, even without an increase in the number of labels. I think we should do more Block scanning benchmarks. I am interested in doing this myself next week. I can use LabelSet scanning in bitcoin/bitcoin#32966 and benchmark Block scanning times. |
|
@theStack would it also make sense to split everything pre-label out of this PR so it can be merged earlier? Or is it too entangled? |
The two scanning approaches have very different APIs. We could in theory add a dedicated API function for no-labels scanning that is shared in both PRs (and create a separate PR that only contains that), but considering that there is a strong recommendation in the BIP to always scan at least for the change label (m=0), I don't think it would be very useful in practice. |
Summary
This PR implements BIP352 with scanning limited to full-nodes. Compared to #1765, an alternative scanning method called the "LabelSet approach" (initially proposed by @w0xlt) is used, with the primary goal of fixing the worst-case scanning attack, discovered and discussed at length in previous takes 3 and 4. In constrast to the previously used "BIP approach", the scanning cost depends primarily on the number of labels (something the user can control and the module can set a limit on), rather than the number of transaction outputs (limited only by Bitcoin's consensus rules, i.e. the block weight limit). See https://gist.github.com/theStack/25c77747838610931e8bbeb9d76faf78 for a high-level comparison between the two approaches with benchmarks, and a Python implementation based on secp256k1lab.
Pros and cons
Other than mitigating adversarial scanning scenarios, the LabelSet approach offers additional advantages:
secp256k1_xonly_pubkey_parsecalls are needed anymore (see Add "silentpayments" module implementing BIP352 (take 4, limited to full-node scanning) #1765 (comment) for more details about API and implementation between the two approaches)The main drawback of the LabelSet approach is that every additional label increases the scanning cost by at least one EC point addition. As a result, it is definitely not suitable for use cases that require a large number of labels.
Limiting the number of labels
To be on the safe side and still avoid the worst-case scanning attack, it was suggested to limit the number of supported labels in the module. Currently, this limit is not enforced in code; it only appears as a WARNING in the API header documentation, with the recommendation to not use more than 20 (that's the number where the worst-case takes about a second on my machine, see benchmarks below). It is unclear whether this is sufficient. We may want to enforce a hard limit instead, or introduce a configurable limit (for example at compile time or via an API call). This would better communicate that using values outside the recommended range is done at the user's own risk.
For a minimalist solution, one could in theory support only a single label for now. That's of course very restrictive, but would still comply with the minimum BIP requirements (see https://github.com/bitcoin/bips/blob/fc00f51c229088c447b3694cca9bf14ace0e1a96/bip-0352.mediawiki?plain=1#L131). The advantage would be that it would result in the simplest possible API and implementation, and expectations would be very clearly expressed to the user, if the parameter might be even called e.g.
change_label.Benchmarks
Benchmarks have been updated to show average scenarios over a N/L matrix (N...number of tx outputs, L... number of labels) as well as the worst case scenario:
Results on my machine
History and details about the "BIP approach"
Prior takes of the "silentpayments" module (PRs #1471, #1519, #1698, #1765) implemented scanning for labels as suggested in BIP352 (the "BIP approach"): for each taproot output, subtract the unlabeled output candidate and check if the result is in a pre-calculated label cache. This method has the useful property of being performance-independent of the number of labels to scan for, as the lookups in the label cache are constant and fast, if implemented in a proper efficient data structure like a hash map (even hundreds of thousands of labels can be checked without a noticeable loss in performance, see the showcase in https://groups.google.com/g/bitcoindev/c/bP6ktUyCOJI/m/HGCF9YxNAQAJ).
One drawback of the BIP approach is that it scales poorly for pathological transactions containing a large number of outputs, all corresponding to Silent Payments recipients sharing the same scan key. In the worst-case scenario of a single, very large transaction filling an entire block (note that such a transaction would be non-standard, but still consensus-valid), scanning could take several minutes for the targeted recipient (see #1698 (review)). Proposed mitigations in takes 3 and 4 that were based on the BIP approach turned out to be insufficient to fix the problem, and it is currently believed that this is an inherent problem that could only be solved by a protocol change (by e.g. limiting the Silent Payments eligible transactions to a smaller number of outputs).
Therefore, a different way of scanning has been proposed that works in the opposite direction: iterate over a list of explicitly passed labels, add the unlabeled output candidate and check if the result is in the list of taproot outputs (via binary search to be efficient), i.e. the "LabelSet approach".
Recommended review music
https://www.youtube.com/watch?v=Hm-q80gA7NI