silentpayments: add light client API #1912

pull theStack wants to merge 8 commits into bitcoin-core:master from theStack:silentpayments_module_lightclient_api changing 7 files +667 −122
  1. theStack commented at 3:09 PM on August 12, 2026: contributor

    This PR extends the silentpayments module with API functions for prevouts_summary (de)serialization and output public key (k=0) creation, intended to be used by tweak indexers and light clients. Note that this functionality was originally introduced in the take 3 PR (#1698), but was then later intentionally removed again in take 4 (#1765, recently merged and released), in order to reduce the scope and lower the review burden.

    The following 3 API functions are added:

    • secp256k1_silentpayments_recipient_prevouts_summary_serialize: given a prevouts_summary object, create the corresponding 33-bytes or 65-bytes serialization
    • secp256k1_silentpayments_recipient_prevouts_summary_parse: given a 33-byte or 65-byte prevouts_summary representation, create the corresponding prevouts_summary object
    • secp256k1_silentpayments_recipient_create_output_pubkeys: given a prevouts_summary object, a recipients scan secret key and a list of spend public keys, create a list of corresponding x-only output public keys (one for each spend public key, each derived with k=0).

    Tests, benchmarks and the example are extended accordingly.

  2. theStack added the label feature on Aug 14, 2026
  3. theStack added the label needs-changelog on Aug 14, 2026
  4. theStack force-pushed on Aug 19, 2026
  5. theStack commented at 6:17 PM on August 19, 2026: contributor

    Rebased on master (adapting to the _eckey_pubkey_... -> _ge_... serialize/parse renames #1915) and fixed the unintentional accepting of prevouts_summary deserialization if they are in hybrid pubkey format (i.e. 65 bytes with prefix bytes 0x06 or 0x07). This will be a bit simpler and more explicit if #1918 gets in and secp256k1_ge_parse{33,65} can be used, added a TODO comment accordingly.

  6. bitsagarob commented at 12:22 PM on September 3, 2026: none

    Tested ACK 0b1cde32d56003bec3071a82b738d20314564c73

    Exercised the new light client API against real chain data rather than constructed inputs: the 33-byte tweak a BlindBit Oracle v2 serves for a real 50,000 sat silent payment in mainnet block 965085, paid to a throwaway wallet whose scan key is published.

    1. parse the 33-byte tweak the oracle serves
       ok
    2. scan that transaction's one output
       found f420241ebdee9a956f88f6ea704b1e02871a64082f61489150222c3a33ee6038
       MATCHES the real 50,000 sat payment
    3. round-trip: serialize the parsed summary back to 33 bytes
       in  023497fcdef5a9254be2e60e02a53bcede1e3d4ab67ed1928140a1d69438bb5678
       out 023497fcdef5a9254be2e60e02a53bcede1e3d4ab67ed1928140a1d69438bb5678
       identical
    4. the 65-byte form should scan identically
       same payment found via 65-byte form
    5. create_output_pubkeys, the filter-matching path, from the same tweak
       k=0 candidate f420241ebdee9a956f88f6ea704b1e02871a64082f61489150222c3a33ee6038
       equals the real output, so a filter would hit
    

    Step 3: the 33-byte serialization is byte-identical to the deployed BlindBit wire format, so an index server can hand its bytes straight to _prevouts_summary_parse.

    Tested with: cmake -B build -DSECP256K1_ENABLE_MODULE_SILENTPAYMENTS=ON -DSECP256K1_BUILD_BENCHMARK=ON cmake --build build -j8 && ctest --test-dir build — 223/223 passed x86-64 Linux, gcc 13.3.0. ctime_tests needs valgrind headers I don't have here, unrelated to this PR.

  7. real-or-random referenced this in commit 46db787112 on Sep 11, 2026
  8. theStack force-pushed on Sep 11, 2026
  9. theStack commented at 12:59 PM on September 11, 2026: contributor

    Rebased on master, in order to take use of the split-up group element parsing functions (ge_parse{33,65, introduced in #1918) in secp256k1_silentpayments_recipient_prevouts_summary_parse. Relevant part of the diff:

    @@ -499,12 +496,8 @@ int secp256k1_silentpayments_recipient_prevouts_summary_parse(const secp256k1_co
         ARG_CHECK(input != NULL);
         ARG_CHECK(inputlen == 33 || inputlen == 65);
         /* Since an attacker can send us malicious data that looks like a serialized public key but is not, fail early. */
    -    /* TODO: remove manual hybrid pubkey rejection and replace with `secp256k1_ge_parse33` and
    -     * `secp256k1_ge_parse65` calls (depending on inputlen) once PR [#1918](/bitcoin-core-secp256k1/1918/) gets merged */
    -    if (inputlen == 65 && input[0] != SECP256K1_TAG_PUBKEY_UNCOMPRESSED) { /* enforce uncompressed format (0x04 prefix) */
    -        return 0;
    -    }
    -    if (!secp256k1_ge_parse(&pk, input, inputlen)) {
    +    is_pubkey_valid = (inputlen == 33) ? secp256k1_ge_parse33(&pk, input) : secp256k1_ge_parse65(&pk, input);
    +    if (!is_pubkey_valid) {
             return 0;
         }
         /* A serialized prevouts_summary will always have the input_hash multiplied in, so we set
    
    
  10. nymius commented at 7:38 PM on September 24, 2026: contributor

    For any rust developer interested on this, I've implemented bindings for the light client API in rust-secp256k1#998, relevant changes are: https://git.rust-bitcoin.org/nymius/rust-secp256k1/compare/push-smspvupuxvto..push-xymyntzmtovu

  11. fametrano commented at 11:04 PM on October 20, 2026: contributor

    Tested f957513d652ccd7fd31922a7d0200fbf55eae1cd for behaviour, not a line-by-line code review: the light-client path (serialize, parse, k=0 candidates, then scan_outputs) finds exactly what the full scan finds, and the 33-byte serialization equals BIP352's input_hash·A. I checked this through ctypes against an independent Python BIP352 implementation, over the BIP352 vectors and 500 random transactions (P2TR/P2WPKH/P2PKH inputs, labels, k up to 4), plus 22 malformed inputs, all rejected. One doc nit: the header could say what the 33 bytes are, BIP352's "tweak data". Happy to share the script.

  12. nymius commented at 2:25 PM on November 2, 2026: contributor

    @theStack could you rebase on a3a5cd45dc619df3b3d6d5e54a6a756ad346b56d? I would like to check rust bindings against #1983 changes.

  13. silentpayments: add API functions for `_prevouts_summary` (de)serialization
    Co-authored-by: josibake <josibake@protonmail.com>
    5a2c0bc1c7
  14. silentpayments: scan test vectors with all `_prevouts_summary` variants
    Best reviewed with `--ignore-all-space` (or `-w` for short).
    59b20e605f
  15. silentpayments: support creating multiple output pubkeys
    This is preparatory for introducing the light client API function in the
    next commit. To keep the current call-site which only creates a single
    output pubkey (in `_sender_create_outputs`) as-is, a corresponding wrapper
    function calls the new function with one spend pubkey.
    No behaviour or API change in this commit yet.
    
    Best reviewed with `--ignore-all-space` (or `-w` for short).
    29e46b60c1
  16. silentpayments: add API function for k=0 output pubkey creation
    Add function for creating k=0 outputs for multiple spend public keys.
    These keys can then be checked for existance against the UTXO set/blockchain.
    
    If a match is found, the client needs to download the full transaction and
    rescan with `_scan_outputs`.
    
    Co-authored-by: josibake <josibake@protonmail.com>
    fce523276f
  17. silentpayments: test output pubkey creation function in recipient test vectors 6597bcbea6
  18. silentpayments: extend example with light client scanning scenario
    Co-authored-by: josibake <josibake@protonmail.com>
    a50df50931
  19. silentpayments: add benchmarks for k=0 output pubkey creation
    TODO: also test with 65-byte sized prevout_summary serializations
    8ee6a1aea1
  20. silentpayments: add ctime-tests for light client API f511b05632
  21. theStack force-pushed on Nov 2, 2026
  22. theStack commented at 3:01 PM on November 2, 2026: contributor

    @theStack could you rebase on a3a5cd4? I would like to check rust bindings against #1983 changes.

    Sure, rebased on master.

  23. in src/modules/silentpayments/main_impl.h:173 in f511b05632
     180 | +        /* `tweak_add` only fails if t_k_scalar * G = -spend_pubkey. Considering t_k is the output of a hash function, this
     181 | +         * will happen only with negligible probability for honestly created spend_pubkey, but we handle this error anyway
     182 | +         * to protect against this function being called with malicious inputs, i.e.,
     183 | +         *     spend_pubkey = -(_create_output_tweak(shared_secret33, k))*G
     184 | +         */
     185 | +        if (!secp256k1_eckey_pubkey_tweak_add(&output_ge, &t_k_scalar)) {
    


    Yudis-bit commented at 3:00 PM on November 3, 2026:

    if (!secp256k1_eckey_pubkey_tweak_add(&output_ge, &t_k_scalar)) {

    t_k_scalar is invariant across this loop. But secp256k1_eckey_pubkey_tweak_add calls secp256k1_ecmult(&pt, &pt, &secp256k1_scalar_one, tweak) and secp256k1_ge_set_gej on each iteration. For N spend pubkeys, that evaluates N full 2-scalar multiplications by G and N modular inversions.

    Instead, we can compute T_k = t_k * G once outside the loop using secp256k1_ecmult_gen_ge (falling back to secp256k1_ecmult if !ecmult_gen_ctx.built). Each output point can then be added via secp256k1_gej_add_ge_var and batch inverted with secp256k1_ge_set_all_gej_var using a small stack array.

    In bench silentpayments_create_pubkeys, this drops marginal cost per spend pubkey from ~40 us down to ~0.8 us:

    Benchmark                              Before       After     Speedup
    silentpayments_create_pubkeys_P=10     464 us      112 us        4.1x
    silentpayments_create_pubkeys_P=100   4291 us      191 us       22.5x
    silentpayments_create_pubkeys_P=1000 41101 us      916 us       44.9x
    

    Also, recipient_create_output_pubkeys currently loads all spend pubkeys into a temporary spend_pubkey_ge in an early loop, which this function immediately loads a second time.


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin-core/secp256k1. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-11-12 07:15 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me