Add DLEQ proof implementing BIP 374 #1802

pull macgyver13 wants to merge 5 commits into bitcoin-core:master from macgyver13:dleq-module-standalone changing 14 files +975 −19
  1. macgyver13 commented at 1:39 am on January 15, 2026: none

    This PR adds a DLEQ (Discrete Logarithm Equality) proof module as specified in BIP 374.

    Based on [PR #1651](https://github.com/bitcoin-core/secp256k1/pull/1651) by @stratospher and the secp256k1-zkp implementation.

    Public API

    Exposes two functions for proof generation and verification:

    • secp256k1_dleq_prove
    • secp256k1_dleq_verify

    These are designed to support rust FFI bindings and follow the API patterns established in similar modules.

    Questions for Reviewers

    • Should this module be optional (current behavior) or enabled by default?
    • Are there additional changes needed to support existing Silent Payments PRs?
    • Feedback on API design, documentation, and test coverage?

    Notes

    Addressed outstanding comments from [PR #1651](https://github.com/bitcoin-core/secp256k1/pull/1651#discussion_r2417300085):

    • BIP-374 v0.2.0 test vectors
    • Proper memory clearing with memclear
  2. furszy commented at 3:01 am on January 15, 2026: member

    Have you talked with @stratospher before opening this PR? I don’t want to draw conclusions prematurely, but opening a parallel PR that implements the same changes without first engaging on the existing one by reviewing it, leaving a comment, or waiting for the author’s public response is not how we usually collaborate in open source. The lack of (co)authorship on the commits is also unusual.

    If you have contacted stratospher, please ignore this comment. It’s just to avoid putting the original author in an unfair or difficult position if still working on the PR.

  3. macgyver13 commented at 3:22 am on January 15, 2026: none

    Have you talked with @stratospher before opening this PR?

    Yes, stratospher and I had discussed who should submit this new PR. I drew the short straw :)

  4. macgyver13 marked this as a draft on Jan 15, 2026
  5. dleq: add module structure and internal implementation
    Implements BIP-374 Discrete Log Equality (DLEQ) proofs as a new module.
    
    - Build system integration (CMake, autotools)
    - Configure dleq module as "optional"
    - Public API header declarations
    - Internal cryptographic implementation (prove_internal, verify_internal)
    - Tagged SHA256 functions per BIP-374 specification
    - Nonce generation following BIP-374 v0.2.0
    
    Co-authored-by: stratospher <44024636+stratospher@users.noreply.github.com>
    2457f89d8a
  6. dleq: add test framework and BIP-374 test vectors
    - Adds test coverage for DLEQ internal functions
    - BIP-374 official test vectors (6 generation + 13 verification cases)
    - Includes Python script for generating test vectors matching BIP-374
    specification.
    
    Tests call *_internal functions directly. Public API tests will be added in a subsequent commit.
    
    Co-authored-by: stratospher <44024636+stratospher@users.noreply.github.com>
    2614b2a52b
  7. dleq: add public API wrappers
    Adds public API functions that wrap the internal DLEQ implementation:
    
    - secp256k1_dleq_prove(): Generate DLEQ proof from secret key and base point B.
      Computes A = a*G and C = a*B internally, then generates proof.
    
    - secp256k1_dleq_verify(): Verify DLEQ proof given A, B, C public keys.
    078d2da340
  8. dleq: add public API tests
    Adds comprehensive tests for the public DLEQ API:
    
    - secp256k1_dleq_prove(): Tests valid proof generation, NULL parameter
      detection, invalid secret key handling, context validation
    
    - secp256k1_dleq_verify(): Tests valid verification, NULL parameter
      detection for all required inputs (proof, A, B, C)
    9149b57a42
  9. ci: enable dleq module 30d6f5e883
  10. macgyver13 force-pushed on Jan 15, 2026
  11. macgyver13 marked this as ready for review on Jan 15, 2026
  12. in src/modules/dleq/main_impl.h:63 in 30d6f5e883
    58+}
    59+
    60+static int secp256k1_dleq_hash_point(secp256k1_sha256 *sha, secp256k1_ge *p) {
    61+    unsigned char buf[33];
    62+    size_t size = 33;
    63+    /* Reject infinity point */
    


    stratospher commented at 7:32 am on January 16, 2026:

    2457f89d: I guess this comment might be applicable now since the public API takes in secp256k1_pubkey and the ge we get is now from there - so unlikely for it to be infinity and VERIFY_CHECK might be better. though if we think about a function in isolation - it makes sense to reject infinity point.

    cc @theStack

  13. in src/modules/dleq/main_impl.h:98 in 30d6f5e883
    93+            masked_key[i] = key32[i] ^ ZERO_MASK[i];
    94+        }
    95+    }
    96+
    97+    secp256k1_nonce_function_bip374_sha256_tagged(&sha);
    98+    /* Hash masked-key||msg||m using the tagged hash as per BIP-374 v0.2.0 */
    


    stratospher commented at 7:36 am on January 16, 2026:
    2457f89d: you could clarify that msg contains A and C. and that’s different from m.
  14. in src/modules/dleq/main_impl.h:109 in 30d6f5e883
    104+    secp256k1_sha256_finalize(&sha, nonce32);
    105+    secp256k1_sha256_clear(&sha);
    106+    secp256k1_memclear_explicit(masked_key, sizeof(masked_key));
    107+}
    108+
    109+/* Generates a nonce as defined in BIP0374 v0.2.0 */
    


    stratospher commented at 7:40 am on January 16, 2026:
    2457f89d: not sure if we should mention the exact version - we don’t mention the version for other modules like musig for example. only difference in newer version is the randomness computation in proof generation anyways.
  15. in src/modules/dleq/main_impl.h:281 in 30d6f5e883
    276+
    277+    secp256k1_dleq_pair(&ctx->ecmult_gen_ctx, &A, &C, &a, &B);
    278+
    279+    ret = secp256k1_dleq_prove_internal(ctx, &s, &e, &a, &B, &A, &C, aux_rand32, msg);
    280+    if (!ret) {
    281+        secp256k1_scalar_clear(&a);
    


    stratospher commented at 7:49 am on January 16, 2026:
    078d2da: since we always clear a - we can move it up and out of the if condition.
  16. in src/modules/dleq/tests_impl.h:179 in 30d6f5e883
    174+        const unsigned char *m = NULL;
    175+
    176+        if (i > 2 && i < 6) {
    177+            /* Skip tests indices 3-5: proof generation failure cases (a=0, a=N, B=infinity).
    178+            * These contain placeholder data from test_vectors_generate_proof.csv that would
    179+            * fail to parse. Only indices 0-2 and 6-12 have valid test data. 
    


    stratospher commented at 8:00 am on January 16, 2026:
    nit: 2614b2a5: extra white space
  17. in src/modules/dleq/tests_impl.h:195 in 30d6f5e883
    190+
    191+        if (is_not_empty(msg_bytes[i])) {
    192+            m = msg_bytes[i];
    193+        }
    194+        
    195+        CHECK(secp256k1_dleq_verify_internal(&s, &e, &A, &B, &C, m) == success[i]);
    


    stratospher commented at 8:01 am on January 16, 2026:
    nit: 2614b2a5: extra white space in blank line above
  18. in src/modules/dleq/tests_impl.h:234 in 30d6f5e883
    229+    /* Check dleq verify input validation */
    230+    CHECK_ILLEGAL(CTX, secp256k1_dleq_verify(CTX, NULL, &A, &B, &C, msg));
    231+    CHECK_ILLEGAL(CTX, secp256k1_dleq_verify(CTX, proof, NULL, &B, &C, msg));
    232+    CHECK_ILLEGAL(CTX, secp256k1_dleq_verify(CTX, proof, &A, NULL, &C, msg));
    233+    CHECK_ILLEGAL(CTX, secp256k1_dleq_verify(CTX, proof, &A, &B, NULL, msg));
    234+    
    


    stratospher commented at 8:02 am on January 16, 2026:
    nit: 9149b57a: extra white space in blank line
  19. in src/modules/dleq/Makefile.am.include:2 in 30d6f5e883
    0@@ -0,0 +1,3 @@
    1+include_HEADERS += include/secp256k1_dleq.h
    2+noinst_HEADERS += src/modules/dleq/dleq_vectors.h
    


    stratospher commented at 8:05 am on January 16, 2026:
    2614b2a5: tests also need to be added. nit: slight ordering preference for main_impl before test vectors - you can refer some other makefile.
  20. in tools/test_vectors_dleq_generate.py:99 in 30d6f5e883
    94+with open(sys.argv[1] + "/test_vectors_verify_proof.csv", newline='') as csvfile:
    95+    reader = csv.DictReader(csvfile)
    96+    for _ in range(5):  # Skip the first 5 rows since those test vectors don't use secp's generator point
    97+        next(reader, None)
    98+
    99+    for i in range(3):  
    


    stratospher commented at 8:07 am on January 16, 2026:
    nit: 2614b2a5: extra white space
  21. in src/modules/dleq/main_impl.h:11 in 2457f89d8a outdated
     6+#ifndef SECP256K1_MODULE_DLEQ_MAIN_H
     7+#define SECP256K1_MODULE_DLEQ_MAIN_H
     8+
     9+#include "../../../include/secp256k1.h"
    10+#include "../../../include/secp256k1_dleq.h"
    11+
    


    stratospher commented at 12:27 pm on January 16, 2026:
    2457f89d: #include "../../hash.h"
  22. in src/modules/dleq/tests_impl.h:9 in 2614b2a52b outdated
    0@@ -0,0 +1,204 @@
    1+/***********************************************************************
    2+ * Distributed under the MIT software license, see the accompanying    *
    3+ * file COPYING or https://www.opensource.org/licenses/mit-license.php.*
    4+ ***********************************************************************/
    5+
    6+#ifndef SECP256K1_MODULE_DLEQ_TESTS_H
    7+#define SECP256K1_MODULE_DLEQ_TESTS_H
    8+
    9+#include "dleq_vectors.h"
    


    stratospher commented at 12:49 pm on January 16, 2026:
    2614b2a5: #include "../../unit_test.h"
  23. stratospher commented at 11:44 am on January 17, 2026: contributor

    sorry for the confusion and thank you @macgyver13 for the PR!

    did an initial pass and it looks good! mostly left style nits.

    1 API design question I had is whether it would be better for the proof generation API to also accept C as an argument - that is GenerateProof(a, B, r, G, m, C) kind of API instead of GenerateProof(a, B, r, G, m).

    When computing silent payment output points, we anyways compute shared secret/C for each output - so there’s a possibility to avoid recomputation again if we can pass C. But current approach might be safer since callers could accidentally provide incorrect C and errors could happen.

    Did you check other use cases of DLEQ for whether they might prefer passing C or computing C internally in the proof generation function?

  24. qatkk commented at 10:06 am on January 18, 2026: none

    I have a question regarding the choice of parameters passed to this API, and I may be missing some design context here — so I’d appreciate clarification. Is there a specific reason why the second generator B is provided directly as an input? In the original proposal of DLEQ proofs by Chaum and Pedersen [1], the security of the protocol relies on the prover not knowing the discrete‑log relation between the two generators. Specifically, if the prover knows a scalar b such that B=b⋅G, then the prover can fabricate valid-looking but incorrect proofs by exploiting this trapdoor relationship. In the context of silent payments (and the description provided in BIP 374) this makes sense, since B represents another user’s public key — a point for which the prover does not know the corresponding private key. However, as commented by stratospher #1651 (comment) this PR is intended to support more general DLEQ use cases, I am concerned that allowing callers to supply an arbitrary B might introduce risks in scenarios where generator independence is not guaranteed.

    Other constructions with two generators on the same curve (e.g., Pedersen commitments, bulletproofs) typically derive the second generator using a hash‑to‑curve procedure, ensuring that no participant knows the discrete‑log ratio between G and B. I was wondering whether a similar approach might help avoid potential misuse here, or whether this falls outside the intended scope of this PR. I would be very interested to hear your thoughts on this. [1] D. Chaum and T. P. Pedersen, Wallet Databases with Observers, 1992.
https://www.hsslb.ch/cryptopapers/other/Chaum_WalletDBswObservers.pdf


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-01-19 01:15 UTC

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