Skip to content

Conversation

@macgyver13
Copy link

@macgyver13 macgyver13 commented Jan 15, 2026

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

Based on PR #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:

  • BIP-374 v0.2.0 test vectors
  • Proper memory clearing with memclear

@furszy
Copy link
Member

furszy commented Jan 15, 2026

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.

@macgyver13
Copy link
Author

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 :)

@macgyver13 macgyver13 marked this pull request as draft January 15, 2026 10:55
@macgyver13 macgyver13 force-pushed the dleq-module-standalone branch from 3c538a6 to 30d6f5e Compare January 15, 2026 11:07
@macgyver13 macgyver13 marked this pull request as ready for review January 15, 2026 12:02
Copy link
Contributor

@stratospher stratospher left a comment

Choose a reason for hiding this comment

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

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?

static int secp256k1_dleq_hash_point(secp256k1_sha256 *sha, secp256k1_ge *p) {
unsigned char buf[33];
size_t size = 33;
/* Reject infinity point */
Copy link
Contributor

Choose a reason for hiding this comment

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

2457f89: 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

Copy link
Author

Choose a reason for hiding this comment

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

45911b2 attempts to handle the 'invalid proof (e.g., all zeros)' case more clearly by moving the infinity check from secp256k1_dleq_hash_point to secp256k1_dleq_verify_internal, where R1 and R2 are computed. The check now occurs before calling secp256k1_dleq_challenge.

A check is required: invalid proofs (e.g., all zeros) will generate R1 and R2 as infinity points, which would cause verification to fail at VERIFY_CHECK(!secp256k1_ge_is_infinity(elem)); during serialization.

Open to a better solution.

}

secp256k1_nonce_function_bip374_sha256_tagged(&sha);
/* Hash masked-key||msg||m using the tagged hash as per BIP-374 v0.2.0 */
Copy link
Contributor

Choose a reason for hiding this comment

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

2457f89: you could clarify that msg contains A and C. and that's different from m.

secp256k1_memclear_explicit(masked_key, sizeof(masked_key));
}

/* Generates a nonce as defined in BIP0374 v0.2.0 */
Copy link
Contributor

Choose a reason for hiding this comment

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

2457f89: 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.


ret = secp256k1_dleq_prove_internal(ctx, &s, &e, &a, &B, &A, &C, aux_rand32, msg);
if (!ret) {
secp256k1_scalar_clear(&a);
Copy link
Contributor

Choose a reason for hiding this comment

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

078d2da: since we always clear a - we can move it up and out of the if condition.

if (i > 2 && i < 6) {
/* Skip tests indices 3-5: proof generation failure cases (a=0, a=N, B=infinity).
* These contain placeholder data from test_vectors_generate_proof.csv that would
* fail to parse. Only indices 0-2 and 6-12 have valid test data.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: 2614b2a: extra white space

CHECK_ILLEGAL(CTX, secp256k1_dleq_verify(CTX, proof, NULL, &B, &C, msg));
CHECK_ILLEGAL(CTX, secp256k1_dleq_verify(CTX, proof, &A, NULL, &C, msg));
CHECK_ILLEGAL(CTX, secp256k1_dleq_verify(CTX, proof, &A, &B, NULL, msg));

Copy link
Contributor

Choose a reason for hiding this comment

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

nit: 9149b57: extra white space in blank line

@@ -0,0 +1,3 @@
include_HEADERS += include/secp256k1_dleq.h
noinst_HEADERS += src/modules/dleq/dleq_vectors.h
Copy link
Contributor

Choose a reason for hiding this comment

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

2614b2a: tests also need to be added. nit: slight ordering preference for main_impl before test vectors - you can refer some other makefile.

for _ in range(5): # Skip the first 5 rows since those test vectors don't use secp's generator point
next(reader, None)

for i in range(3):
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: 2614b2a: extra white space


#include "../../../include/secp256k1.h"
#include "../../../include/secp256k1_dleq.h"

Copy link
Contributor

Choose a reason for hiding this comment

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

2457f89: #include "../../hash.h"

#ifndef SECP256K1_MODULE_DLEQ_TESTS_H
#define SECP256K1_MODULE_DLEQ_TESTS_H

#include "dleq_vectors.h"
Copy link
Contributor

Choose a reason for hiding this comment

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

2614b2a: #include "../../unit_test.h"

@qatkk
Copy link

qatkk commented Jan 18, 2026

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? @furszy
In the original proposal of DLEQ proofs by Chaum and Pedersen [1], the correctness 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 two equations for the verification are no longer distinct and will be dependent on each other.
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

macgyver13 and others added 6 commits January 25, 2026 14:30
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 <[email protected]>
- 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 <[email protected]>
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.
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)
Reject proofs that produce infinity points during verification
Add public API test case for infinity point scenario
Add note to clarify contents of msg to reduce confusion with m
Add missing includes
Format tools/test_vectors_dleq_generate.py
@macgyver13 macgyver13 force-pushed the dleq-module-standalone branch from 30d6f5e to 45911b2 Compare January 25, 2026 20:04
@real-or-random
Copy link
Contributor

In the original proposal of DLEQ proofs by Chaum and Pedersen [1], the correctness of the protocol relies on the prover not knowing the discrete‑log relation between the two generators.

You probably mean soundness instead of correctness? If I'm not mistaken, this is simply not true. See for instance Nigel Smart's Cryptography: An Introduction (3rd Edition), Section 25.3.1, page 377, PDF page 389. This rather accessible write-up shows that the underlying Sigma protocol has correctness, 2-special soundness, and zero-knowledge, all with probability 1 and without any computational assumption. Technically, one doesn't even need the assumption that the discrete logarithm problem is hard in the group. Note in particular that the proof of special soundness extracts the witness and not the discrete logarithm between g and h (or G and B in our notation); the latter doesn't even show up in the proof.

Specifically, if the prover knows a scalar b such that B=b⋅G, then the two equations for the verification are no longer distinct and will be dependent on each other.

Can you elaborate and also explain why you believe that this requires the assumption that the prover doesn't know the discrete logarithm between G and B?

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