[Draft/POC] Add secp256k1-based HPKE (Hybrid Public Key Encryption) For Payjoin v2 #32617

pull w0xlt wants to merge 1 commits into bitcoin:master from w0xlt:secp256k1_hpke changing 7 files +7447 −1
  1. w0xlt commented at 8:04 am on May 26, 2025: contributor

    This PR introduces an implementation of Hybrid Public Key Encryption (HPKE) using a secp256k1-based Diffie-Hellman KEM (per RFC 9180 and “secp256k1-based DHKEM for HPKE” specification). It provides the core cryptographic component needed to enable the Payjoin v2 protocol.

    This is an exploratory PR intended to kickstart discussion on adding Payjoin v2 support to Bitcoin Core – feedback and reviews are very welcome.

    Payjoin v2 makes use of a protocol called Oblivious HTTP (OHTTP) to strip client-identifying metadata from the request. This protocol use a binary-encoded HTTP message encapsulated through HPKE.

    The core implementation is contained in the new files src/dhkem_secp256k1.h and src/dhkem_secp256k1.cpp:

    • Full HPKE Modes: All four HPKE modes are supported – Base, PSK, Auth, and AuthPSK.
    • Secp256k1 DHKEM: Uses secp256k1 for Diffie-Hellman key exchange. The Encap() and Decap() functions perform the base mode KEM (ephemeral ECDH using the receiver’s public key), while AuthEncap() and AuthDecap() extend this to authenticated mode (including the sender’s static key in the shared secret derivation). Internally, the shared secret is derived as specified by HPKE using HKDF (SHA256) extraction and expansion.
    • Key Schedule & Nonce Derivation: After KEM, the implementation runs the HPKE key schedule to derive the encryption key, base nonce, and exporter secret. It follows RFC 9180 §7.1, hashing the info and psk_id contexts and deriving the AEAD key and nonce. Per-message nonces are computed by XOR-ing the base nonce with a message sequence number as specified in RFC 9180 §5.2.
    • AEAD Encryption (ChaCha20-Poly1305): The HPKE context uses ChaCha20-Poly1305 as the authenticated encryption scheme. The code provides Seal() and Open() functions to encrypt and decrypt data using the derived context key and a given nonce. It uses Bitcoin Core’s existing ChaCha20Poly1305 implementation.

    The test file validates all the functions mentioned above: Encapsulation/Decapsulation, Encryption/Decryption, Nonce and Key Schedule Logic. This uses the PDK (Payjoin Dev Kit) test vectors. The tests also show how to use the HPKE functions.

    This implementation only uses HKDF-SHA256 as Key Derivation Functions and ChaCha20-Poly1305 as AEAD, since these already exist in the Bitcoin Core codebase. It does not support other KDFs (such as HKDF-SHA384 and HKDF-SHA512) or other AEADs (such as AES-128-GCM and AES-256-GCM).

    To run the tests: build/bin/test_bitcoin --log_level=all --run_test=dhkem_secp256k1_tests

  2. w0xlt marked this as a draft on May 26, 2025
  3. DrahtBot commented at 8:04 am on May 26, 2025: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32617.

    Reviews

    See the guideline for information on the review process. A summary of reviews will appear here.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #28690 (build: Introduce internal kernel library by TheCharlatan)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    LLM Linter (✨ experimental)

    Possible typos and grammar issues:

    • draft-wahby-cfrg-hpke-kem-secp256k1-01^2 -> draft-wahby-cfrg-hpke-kem-secp256k1-01 [remove stray superscript] [the caret+number looks like a footnote marker and breaks the name]
    • (skE * pkR)^17 -> (skE * pkR) [remove stray superscript] [the caret+number after the expression is an unclear footnote marker]
    • “HKDF-Extract & Expand(dh, “shared secret”)^18.” -> “HKDF-Extract & Expand(dh, "shared secret").” [remove stray superscript]

    drahtbot_id_5_m

  4. w0xlt force-pushed on May 26, 2025
  5. w0xlt force-pushed on May 26, 2025
  6. DrahtBot added the label CI failed on May 26, 2025
  7. DrahtBot commented at 9:34 am on May 26, 2025: contributor

    🚧 At least one of the CI tasks failed. Task lint: https://github.com/bitcoin/bitcoin/runs/42885844689 LLM reason (✨ experimental): The CI failure is due to a lint check raising an error over missing include guards in the header file.

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  8. w0xlt force-pushed on May 26, 2025
  9. DrahtBot removed the label CI failed on May 26, 2025
  10. 1440000bytes commented at 11:42 am on May 26, 2025: none
    If rust-payjoin already works with bitcoin core wallet, why do we need to add anything in bitcoin core for payjoin?
  11. DanGould commented at 7:13 pm on May 26, 2025: none
    The main reason for the Bitcoin Core wallet to support payjoin directly is to reduce the number of necessary dependencies for those already using the core wallet. In particular rust-payjoin requires TLS (which is optional for the protocol) and rust implementations of cryptographic primatives that core could avoid with a bespoke implementation.
  12. theStack commented at 3:19 pm on June 3, 2025: contributor

    Fore more context to reviewers, could add a link to BIP 77 in the PR description, particularly to the cryptography part: https://github.com/bitcoin/bips/blob/master/bip-0077.md#secp256k1-hybrid-public-key-encryption

    Full HPKE Modes: All four HPKE modes are supported – Base, PSK, Auth, and AuthPSK.

    Do we need all of the four modes for Payjoin v2 support? Didn’t look in-depth yet, but at least BIP77 contains only the two modes “Base” and “Auth” explicitly (and no mentions of “PSK”): https://github.com/bitcoin/bips/blob/72af87fc72999e3f0a26a06e6e0a7f3134236337/bip-0077.md?plain=1#L286-L287 https://github.com/bitcoin/bips/blob/72af87fc72999e3f0a26a06e6e0a7f3134236337/bip-0077.md?plain=1#L376-L378

  13. w0xlt commented at 6:33 pm on June 4, 2025: contributor

    at least BIP77 contains only the two modes “Base” and “Auth” explicitly (and no mentions of “PSK”):

    Good catch. I still need to verify whether PSK is used by OHTTP in the Payjoin v2 protocol, but since the https://github.com/payjoin/bitcoin-hpke project used by PDK implements and has test vectors for PSK, I added the PSK and AuthPSK modes here as well.

  14. nothingmuch commented at 5:50 pm on June 9, 2025: contributor

    Good catch. I still need to verify whether PSK is used by OHTTP in the Payjoin v2 protocol,

    I can confirm that neither BIP 77 nor the rust-payjoin implementations depend on the PSK functionality. There are ideas for future extensions that might make use of that, but they are only ideas at this point and would not be a part of BIP 77 itself.

  15. DrahtBot added the label Needs rebase on Aug 6, 2025
  16. achow101 commented at 3:04 pm on October 22, 2025: member
    Are you still working on this?
  17. achow101 requested review from fjahr on Oct 22, 2025
  18. achow101 requested review from achow101 on Oct 22, 2025
  19. achow101 requested review from furszy on Oct 22, 2025
  20. achow101 requested review from theStack on Oct 22, 2025
  21. achow101 requested review from Eunovo on Oct 22, 2025
  22. w0xlt force-pushed on Oct 23, 2025
  23. DrahtBot removed the label Needs rebase on Oct 23, 2025
  24. crypto: Add secp256k1-based DHKEM for HPKE (Hybrid Public Key Encryption) 49358730b1
  25. w0xlt force-pushed on Oct 24, 2025
  26. w0xlt commented at 7:11 am on October 24, 2025: contributor
    PSK functionality removed. Thanks for the suggestion @theStack and @nothingmuch
  27. in src/dhkem_secp256k1.cpp:1 in 49358730b1
    0@@ -0,0 +1,524 @@
    1+// Copyright (c) 2018-present The Bitcoin Core developers
    


    fjahr commented at 10:15 pm on October 29, 2025:
    2018?
  28. in src/dhkem_secp256k1.cpp:327 in 49358730b1
    322+    std::array<uint8_t, 65> pkS_bytes;
    323+    if (!ComputePublicKey(skS.data(), pkS_bytes.data())) {
    324+        return std::nullopt; // Invalid sender private key
    325+    }
    326+
    327+    // 2.5. Verify that enc matches skE
    


    fjahr commented at 10:18 pm on October 29, 2025:
    There is a 2.5 but no 3?
  29. in src/dhkem_secp256k1.cpp:385 in 49358730b1
    380+    // 2. Verify receiver's private key (skR) is valid
    381+    if (secp256k1_ec_seckey_verify(g_secp256k1_ctx, skR.data()) != 1) {
    382+        return std::nullopt; // Invalid receiver private key
    383+    }
    384+
    385+    // 2. Perform two ECDH operations with receiver's private key (skR):
    


    fjahr commented at 10:18 pm on October 29, 2025:
    There are two 2s here
  30. fjahr commented at 11:57 pm on October 29, 2025: contributor
    Just skimmed a bit to start. Before spending significant time on reviewing this, I think it would be great if this could be cleaned up to the point that it can be taken out of draft status. Potentially also consider splitting it into multiple commits.
  31. fanquake commented at 10:03 am on October 30, 2025: member
    It seems a bit premature to be doing code review or merging anything here, until some discussion has played out in #33684 in regards to approach, and implementation details (can you link to it from the PR description). There still seem to be high-level questions that need answering/agreement on.

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2025-10-31 09:13 UTC

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