Skip to content

Add BIP352 module (take 3) #1698

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

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

josibake
Copy link
Member

This PR implements BIP352 - Silent payments. It is recommended to read through the BIP before reviewing this PR.

This is a continuation of the work in #1519 and only opened as a new PR due to the comment history on #1519 becoming quite long and difficult to sift through. It is recommended reviewers go through #1519 for background context, if interested.

@josibake josibake force-pushed the bip352-silentpayments-module-2025 branch from 6264c3d to 9e85256 Compare July 14, 2025 14:54
@josibake
Copy link
Member Author

Updated 6264c3d -> 9e85256 (2025_00 -> 2025_01, compare)

  • Added documentation for expectations around label_lookup pointer lifetimes (h/t @antonilol)
  • Update docs to accurately reflect that label_context is optional (h/t @antonilol)
  • Added a test case for passing a lookup callback with a null context (which required some small updates to the test label lookup function)

@josibake josibake force-pushed the bip352-silentpayments-module-2025 branch from 9e85256 to a4db279 Compare July 21, 2025 14:09
@real-or-random
Copy link
Contributor

Sorry, stopping CI here. We're about to make a release and need to the CI. :)

We'll restart the jobs here afterwards.

@josibake
Copy link
Member Author

Update 9e85256 -> a4db279 (2025_01 -> 2025_02, compare)

  • Update the constant time tests to cover the _recipient_created_shared_secret and _recipient_created_output_pubkey functions (h/t @theStack )
  • Remove no longer needed TODO comments and clarify why a constant time test without a label lookup function is sufficient for _recipient_scan_outputs

@josibake josibake force-pushed the bip352-silentpayments-module-2025 branch from a4db279 to e35bede Compare July 22, 2025 10:27
@josibake
Copy link
Member Author

Rebased on top of 0.7.0 release 🎉 a4db279 -> e35bede (2025_02 -> 2025_02_rebase, compare)

@josibake
Copy link
Member Author

I did a deep dive on using (*arg)[size] in this PR and opened #1710 for discussion, since this is a broader topic than just this PR. The relevant changes for here and the downstream Bitcoin Core PRs are josibake@5a10880 and josibake/bitcoin@5835d98

@josibake josibake force-pushed the bip352-silentpayments-module-2025 branch from e35bede to 1a84908 Compare July 24, 2025 12:18
@josibake
Copy link
Member Author

Updated e35bede -> 1a84908 (2025_02_rebase -> 2025_03, compare)

  • Added a test case for the _recipient_create_output_pubkey corner case (h/t @theStack)
  • Removed the VERIFY_CHECK in favour of returning an error

@josibake josibake force-pushed the bip352-silentpayments-module-2025 branch from 1a84908 to 2948a9b Compare July 25, 2025 09:16
@josibake
Copy link
Member Author

Update 1a84908 -> 2948a9b (2025_03 -> 2025_04, compare)

  • Fixed valgrind error in test
  • Update the example to use EXIT_SUCCESS/EXIT_FAILURE (h/t @theStack)
  • Clear shared secret variable consistently (and update comment) (h/t @theStack)
  • Add comment explaining why we declassify the pubkey sum (h/t @theStack)

Thanks for the thorough review, @theStack !

@josibake josibake force-pushed the bip352-silentpayments-module-2025 branch from 2948a9b to 64ecd6c Compare July 25, 2025 14:25
@josibake
Copy link
Member Author

Update 2948a9b -> 64ecd6c (2025_04 -> 2025_05, compare)

  • Remove no longer needed TODO comment regarding _cmov
  • Remove todo comment regarding input_hash, now that this is properly specified in the BIP

cc @jonasnick and @real-or-random regarding the use of a VERIFY_CHECK in favour of returning an error, when returning an error results in an untestable branch. I'm happy with the approach here were we use a VERIFY_CHECK for input_hash and t_k to check for an overflow of the curve order. However, given this is something we've discussed a few times in the post, would be great to hear your thoughts on this and I'm happy to defer to whatever you both think is best.

This should address all of the outstanding TODOs (at least the ones we left comments for 😅 )

@josibake josibake force-pushed the bip352-silentpayments-module-2025 branch from 64ecd6c to 3c4af8f Compare July 28, 2025 18:01
@josibake
Copy link
Member Author

Updated 64ecd6c -> 3c4af8f (2025_05 -> 2025_06, compare)

  • Updates the benchmarks per @theStack 's suggestion to have separate benchmarks for _full_scan and full_scan_with_labels
  • Leave a TODO comment for a follow-up to make the labels benchmark more representative of real world usage
  • Cleans up the benchmark arguments and formatting

Copy link
Contributor

@theStack theStack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the CI commit, could add the silent payments module also to the native macOS arm64 job (as done for musig recently in #1699), e.g.

diff
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8ee13ce..f612a84 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -583,13 +583,13 @@ jobs:
       fail-fast: false
       matrix:
         env_vars:
-          - { WIDEMUL: 'int64',  RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
+          - { WIDEMUL: 'int64',  RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' }
           - { WIDEMUL: 'int128_struct', ECMULTGENPRECISION: 2, ECMULTWINDOW: 4 }
-          - { WIDEMUL: 'int128',                  ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
+          - { WIDEMUL: 'int128',                  ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' }
           - { WIDEMUL: 'int128', RECOVERY: 'yes' }
-          - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
-          - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc' }
-          - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CPPFLAGS: '-DVERIFY' }
+          - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' }
+          - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes', CC: 'gcc' }
+          - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes', CPPFLAGS: '-DVERIFY' }
           - BUILD: 'distcheck'
 
     steps:
(untested)

@josibake josibake force-pushed the bip352-silentpayments-module-2025 branch from 142f07b to 2b57d2a Compare August 12, 2025 09:36
@josibake
Copy link
Member Author

Updated 142f07b -> 2b57d2a (2025_11 -> 2025_12, compare)

  • Documentation fixes (h/t @theStack)
  • Added VERIFY_CHECK and more consistent ret (h/t @theStack)
  • Removed dead code from tests and improved legibility (h/t @theStack)

@josibake josibake force-pushed the bip352-silentpayments-module-2025 branch from 2b57d2a to a428424 Compare August 14, 2025 12:45
@josibake
Copy link
Member Author

Updated 2b57d2a -> a428424 (2025_12 -> 2025_13, compare)

  • Remove extra check to make the autotools build system consistent with other modules (h/t @real-or-random )
  • Various wording fix-ups (h/t @real-or-random)
  • Use spend_pubkey to indicate a spend public key that might also have a tweak and unlabeled_spend_pubkey we mean to refer the unlabeled spend public key (vs labeled_spend_pubkey and spend_pubkey) (h/t @real-or-random )
  • Reword the sending API docs to avoid mentioning "sorting"

Thanks for the review @real-or-random , much prefer your wording suggestions. I pushed a version of the docs that I think helps minimise confusion about sorting, but curious to hear your thoughts as I imagine these docs will still need a bit of refining.

@josibake josibake force-pushed the bip352-silentpayments-module-2025 branch from a428424 to 99aef92 Compare August 15, 2025 10:21
@josibake
Copy link
Member Author

Updated a428424 -> 99aef92 (2025_13 -> 2025_14, compare)

  • Add tests for the tagged hash midstates used in silent payments (h/t @real-or-random )

The first commit moves the convenience function for verifying tagged hashes out of the musig module and is pulled in from #1725.

@real-or-random
Copy link
Contributor

real-or-random commented Aug 19, 2025

I've made my way through sending (ignoring the public header for now) and I pushed some suggestions for fixups here: master...real-or-random:secp256k1:bip352-silentpayments-module-2025-sending-fixups (and some fixups also to receiving, I hope I always picked the right commit to fixup). See commit messages for rationale. Some of these may be opinionated, so feel free to reject, disagree, or adjust.

Comment on lines +281 to +331
/* Check for malformed spend and label public keys, i.e., any single pubkey is malformed or the public
* keys are valid but sum up to zero.
*/
{
secp256k1_pubkey neg_spend_pubkey = s;
CHECK(secp256k1_ec_pubkey_negate(CTX, &neg_spend_pubkey));
CHECK(secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, &s, &neg_spend_pubkey) == 0);
memset(&l, 0, sizeof(l));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, &s, &l));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could also test with an illegal s, I think.

josibake and others added 12 commits August 20, 2025 10:51
Move the sha256_tag_test_internal function out of the musig module
into tests.c. This makes it available to other modules wishing to verify tagged
hashes without needing to duplicate the function.

Change the function signature to expect a const unsigned char and update
the tagged hash tests to use static const unsigned char character
arrays (where necessary).

Add a comment for each tag. This is done as a convenience for checking
the strings against the protocol specifications, where the tags are
normally specified as strings.

Update tests in the ellswift and schnorrsig modules to use the
sha256_tag_test_internal helper function.
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.
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 by exposing the
`_create_shared_secret` and `_create_output_pubkey` functions in the
API. 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.

Add an opaque 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.

Finally, add test coverage for the receiving API.
Demonstrate sending, scanning, and light client scanning.
Add a benchmark for a full transaction scan and for scanning a single
output. Only benchmarks for scanning are added as this is the most
performance critical portion of the protocol.

Co-authored-by: Sebastian Falbesoner <[email protected]>
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: Jonas Nick <[email protected]>
Co-authored-by: Sebastian Falbesoner <[email protected]>
Test midstate tags used in silent payments.
@josibake josibake force-pushed the bip352-silentpayments-module-2025 branch from 99aef92 to 2164d5c Compare August 20, 2025 09:59
@josibake
Copy link
Member Author

Updated 99aef92 -> 2164d5c (2025_14 -> 2025_15, compare)

Primarily updates the sending commit, with a few changes to the receiving commit:

  • Rebased on tests: refactor tagged hash verification #1725
  • IWYU (h/t @real-or-random )
  • Improve comments regarding the scan key, i.e., stop referring to it as "secret-but-not-really" and explicitly mention what happens if the scan key is leaked (h/t @real-or-random )
  • Use the more precise terminology "negligible probability" (h/t @real-or-random and crypto camp, where this has been explained! 😄 )
  • General wording, clarity improvements for the code/comments

Thanks for the thorough review, @real-or-random ! I reviewed each fixup commit and ended up taking all of them, as I agree with the rational and much prefer the wording(s) you suggested. Also, thanks for the fixup! branch; the branch made it super simple to integrate your changes.

real-or-random added a commit that referenced this pull request Aug 21, 2025
5153cf1 tests: refactor tagged hash tests (josibake)

Pull request description:

  Opened in response to #1698 (comment)

  ---

  We use tagged hashes in `modules/musig`, `modules/schnorrsig`, `modules/ellswift`, and the proposed `modules/silentpayments`. In looking for inspiration on how to add tagged hash midstate verification for #1698, it seemed like a good opportunity to DRY up the code across all of the modules.

  I chose the convention used in the ellswift module as this seems the most idiomatic C. Since the tags are normally specified as strings in the BIPs, I also added a comment above each char array for convenience.

  If its deemed too invasive to refactor the existing modules in this PR, I'm happy to drop the refactor commits for the ellswift and schnorrsig modules. All I need for #1698 is the first commit which moves the utility function out of the musig module to make it available to use in the silent payments module.

ACKs for top commit:
  real-or-random:
    utACK 5153cf1 assuming CI passes
  theStack:
    Code-review ACK 5153cf1

Tree-SHA512: 335ec3ee6a265e13cc379968f8fa1624534bef2389e4e21b85e6a9572ce1bd9dee4eabd2cb6d187ac974db3ab8246c2626d309ccfbee5744c30cf7560d1e261c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants