From 91f6b9890b79e3e8b1a9909f28b07b3ba2d6ce3e Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:13:48 -0500 Subject: [PATCH 1/6] 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> --- CMakeLists.txt | 2 + Makefile.am | 4 + configure.ac | 10 ++ include/secp256k1_dleq.h | 76 ++++++++ src/CMakeLists.txt | 5 + src/modules/dleq/Makefile.am.include | 2 + src/modules/dleq/main_impl.h | 248 +++++++++++++++++++++++++++ src/secp256k1.c | 4 + 8 files changed, 351 insertions(+) create mode 100644 include/secp256k1_dleq.h create mode 100644 src/modules/dleq/Makefile.am.include create mode 100644 src/modules/dleq/main_impl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 11dc3f6e53..f7d2658562 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,7 @@ option(SECP256K1_ENABLE_MODULE_EXTRAKEYS "Enable extrakeys module." ON) option(SECP256K1_ENABLE_MODULE_SCHNORRSIG "Enable schnorrsig module." ON) option(SECP256K1_ENABLE_MODULE_MUSIG "Enable musig module." ON) option(SECP256K1_ENABLE_MODULE_ELLSWIFT "Enable ElligatorSwift module." ON) +option(SECP256K1_ENABLE_MODULE_DLEQ "Enable DLEQ module." OFF) option(SECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS "Enable external default callback functions." OFF) if(SECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS) @@ -285,6 +286,7 @@ message(" extrakeys ........................... ${SECP256K1_ENABLE_MODULE_EXTRA message(" schnorrsig .......................... ${SECP256K1_ENABLE_MODULE_SCHNORRSIG}") message(" musig ............................... ${SECP256K1_ENABLE_MODULE_MUSIG}") message(" ElligatorSwift ...................... ${SECP256K1_ENABLE_MODULE_ELLSWIFT}") +message(" DLEQ ................................ ${SECP256K1_ENABLE_MODULE_DLEQ}") message("Parameters:") message(" ecmult window size .................. ${SECP256K1_ECMULT_WINDOW_SIZE}") message(" ecmult gen table size ............... ${SECP256K1_ECMULT_GEN_KB} KiB") diff --git a/Makefile.am b/Makefile.am index dc798575e3..9bfda0e602 100644 --- a/Makefile.am +++ b/Makefile.am @@ -314,3 +314,7 @@ endif if ENABLE_MODULE_ELLSWIFT include src/modules/ellswift/Makefile.am.include endif + +if ENABLE_MODULE_DLEQ +include src/modules/dleq/Makefile.am.include +endif diff --git a/configure.ac b/configure.ac index 6028ee288d..927e84cbba 100644 --- a/configure.ac +++ b/configure.ac @@ -191,6 +191,10 @@ AC_ARG_ENABLE(module_ellswift, AS_HELP_STRING([--enable-module-ellswift],[enable ElligatorSwift module [default=yes]]), [], [SECP_SET_DEFAULT([enable_module_ellswift], [yes], [yes])]) +AC_ARG_ENABLE(module_dleq, + AS_HELP_STRING([--enable-module-dleq],[enable DLEQ module [default=no]]), [], + [SECP_SET_DEFAULT([enable_module_dleq], [no], [no])]) + AC_ARG_ENABLE(external_default_callbacks, AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [], [SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])]) @@ -397,6 +401,10 @@ SECP_CFLAGS="$SECP_CFLAGS $WERROR_CFLAGS" # Processing must be done in a reverse topological sorting of the dependency graph # (dependent module first). +if test x"$enable_module_dleq" = x"yes"; then + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_DLEQ=1" +fi + if test x"$enable_module_ellswift" = x"yes"; then SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ELLSWIFT=1" fi @@ -470,6 +478,7 @@ AM_CONDITIONAL([ENABLE_MODULE_EXTRAKEYS], [test x"$enable_module_extrakeys" = x" AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG], [test x"$enable_module_schnorrsig" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_MUSIG], [test x"$enable_module_musig" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_ELLSWIFT], [test x"$enable_module_ellswift" = x"yes"]) +AM_CONDITIONAL([ENABLE_MODULE_DLEQ], [test x"$enable_module_dleq" = x"yes"]) AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"]) AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm32"]) AM_CONDITIONAL([BUILD_WINDOWS], [test "$build_windows" = "yes"]) @@ -494,6 +503,7 @@ echo " module extrakeys = $enable_module_extrakeys" echo " module schnorrsig = $enable_module_schnorrsig" echo " module musig = $enable_module_musig" echo " module ellswift = $enable_module_ellswift" +echo " module dleq = $enable_module_dleq" echo echo " asm = $set_asm" echo " ecmult window size = $set_ecmult_window" diff --git a/include/secp256k1_dleq.h b/include/secp256k1_dleq.h new file mode 100644 index 0000000000..a43c2118ab --- /dev/null +++ b/include/secp256k1_dleq.h @@ -0,0 +1,76 @@ +#ifndef SECP256K1_DLEQ_H +#define SECP256K1_DLEQ_H + +#include "secp256k1.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** This module provides an implementation of Discrete Log Equality (DLEQ) proofs, + * as specified in BIP-374. A DLEQ proof allows proving knowledge of a discrete + * logarithm relationship between two pairs of elliptic curve points without + * revealing the secret scalar. + * + * Specifically, given points A, B, C, and the generator G, a DLEQ proof + * demonstrates that A = a*G and C = a*B for the same scalar a, without + * revealing a. + * + * The proof consists of two 32-byte scalars (e, s) totaling 64 bytes. + */ + +/** Generate a DLEQ proof. + * + * Proves knowledge of scalar a such that A = a*G and C = a*B without + * revealing a. + * + * Returns: 1 if proof generation succeeded + * 0 if nonce generation failed (negligible probability) or + * if any input is invalid + * + * Args: ctx: pointer to a context object + * Out: proof64: pointer to 64-byte proof = bytes(32, e) || bytes(32, s) + * In: seckey32: pointer to 32-byte secret key (scalar a) + * pubkey_B: pointer to public key B (base point) + * aux_rand32: pointer to 32-byte auxiliary randomness (can be NULL) + * msg: pointer to 32-byte message (can be NULL) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_dleq_prove( + const secp256k1_context *ctx, + unsigned char *proof64, + const unsigned char *seckey32, + const secp256k1_pubkey *pubkey_B, + const unsigned char *aux_rand32, + const unsigned char *msg +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) + SECP256K1_ARG_NONNULL(4); + +/** Verify a DLEQ proof. + * + * Verifies that A and C were generated from the same scalar. + * + * Returns: 1 if proof is valid + * 0 if proof is invalid or any input is invalid + * + * Args: ctx: pointer to a context object + * In: proof64: pointer to 64-byte proof = bytes(32, e) || bytes(32, s) + * pubkey_A: pointer to public key A + * pubkey_B: pointer to public key B (base point) + * pubkey_C: pointer to public key C + * msg: pointer to optional 32-byte message (can be NULL) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_dleq_verify( + const secp256k1_context *ctx, + const unsigned char *proof64, + const secp256k1_pubkey *pubkey_A, + const secp256k1_pubkey *pubkey_B, + const secp256k1_pubkey *pubkey_C, + const unsigned char *msg +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) + SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + +#ifdef __cplusplus +} +#endif + +#endif /* SECP256K1_DLEQ_H */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ecbbbbe8e9..578221aa48 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,6 +12,11 @@ if(SECP256K1_ENABLE_MODULE_ELLSWIFT) set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_ellswift.h) endif() +if(SECP256K1_ENABLE_MODULE_DLEQ) + add_compile_definitions(ENABLE_MODULE_DLEQ=1) + set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_dleq.h) +endif() + if(SECP256K1_ENABLE_MODULE_MUSIG) if(DEFINED SECP256K1_ENABLE_MODULE_SCHNORRSIG AND NOT SECP256K1_ENABLE_MODULE_SCHNORRSIG) message(FATAL_ERROR "Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the musig module.") diff --git a/src/modules/dleq/Makefile.am.include b/src/modules/dleq/Makefile.am.include new file mode 100644 index 0000000000..a54ce672ac --- /dev/null +++ b/src/modules/dleq/Makefile.am.include @@ -0,0 +1,2 @@ +include_HEADERS += include/secp256k1_dleq.h +noinst_HEADERS += src/modules/dleq/main_impl.h diff --git a/src/modules/dleq/main_impl.h b/src/modules/dleq/main_impl.h new file mode 100644 index 0000000000..dcb8431431 --- /dev/null +++ b/src/modules/dleq/main_impl.h @@ -0,0 +1,248 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_DLEQ_MAIN_H +#define SECP256K1_MODULE_DLEQ_MAIN_H + +#include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_dleq.h" + +/* Initializes SHA256 with fixed midstate. This midstate was computed by applying + * SHA256 to SHA256("BIP0374/aux")||SHA256("BIP0374/aux"). */ +static void secp256k1_nonce_function_bip374_sha256_tagged_aux(secp256k1_sha256 *sha) { + secp256k1_sha256_initialize(sha); + sha->s[0] = 0x48479343ul; + sha->s[1] = 0xa9eb648cul; + sha->s[2] = 0x58952fe4ul; + sha->s[3] = 0x4772d3b2ul; + sha->s[4] = 0x977ab0a0ul; + sha->s[5] = 0xcb8e2740ul; + sha->s[6] = 0x60bb4b81ul; + sha->s[7] = 0x68a41b66ul; + + sha->bytes = 64; +} + +/* Initializes SHA256 with fixed midstate. This midstate was computed by applying + * SHA256 to SHA256("BIP0374/nonce")||SHA256("BIP0374/nonce"). */ +static void secp256k1_nonce_function_bip374_sha256_tagged(secp256k1_sha256 *sha) { + secp256k1_sha256_initialize(sha); + sha->s[0] = 0xa810fc87ul; + sha->s[1] = 0x3b4a4d2aul; + sha->s[2] = 0xe302cfb4ul; + sha->s[3] = 0x322df1a0ul; + sha->s[4] = 0xd2e7fb82ul; + sha->s[5] = 0x7808570dul; + sha->s[6] = 0x9c33e0cdul; + sha->s[7] = 0x2dfbf7f6ul; + + sha->bytes = 64; +} + +/* Initializes SHA256 with fixed midstate. This midstate was computed by applying + * SHA256 to SHA256("BIP0374/challenge")||SHA256("BIP0374/challenge"). */ +static void secp256k1_dleq_sha256_tagged(secp256k1_sha256 *sha) { + secp256k1_sha256_initialize(sha); + sha->s[0] = 0x24f1c9c7ul; + sha->s[1] = 0xd1538c75ul; + sha->s[2] = 0xc9874ae8ul; + sha->s[3] = 0x6566de76ul; + sha->s[4] = 0x487843c9ul; + sha->s[5] = 0xc13d8026ul; + sha->s[6] = 0x39a2f3eful; + sha->s[7] = 0x2ad0fcb3ul; + + sha->bytes = 64; +} + +static int secp256k1_dleq_hash_point(secp256k1_sha256 *sha, secp256k1_ge *p) { + unsigned char buf[33]; + size_t size = 33; + /* Reject infinity point */ + if (secp256k1_ge_is_infinity(p)) { + return 0; + } + secp256k1_eckey_pubkey_serialize33(p, buf); + secp256k1_sha256_write(sha, buf, size); + return 1; +} + +static void secp256k1_nonce_function_dleq(unsigned char *nonce32, const unsigned char *msg, size_t msglen, const unsigned char *key32, const unsigned char *aux_rand32, const unsigned char *m) { + secp256k1_sha256 sha; + unsigned char masked_key[32]; + int i; + + if (aux_rand32 != NULL) { + secp256k1_nonce_function_bip374_sha256_tagged_aux(&sha); + secp256k1_sha256_write(&sha, aux_rand32, 32); + secp256k1_sha256_finalize(&sha, masked_key); + for (i = 0; i < 32; i++) { + masked_key[i] ^= key32[i]; + } + } else { + /* Precomputed TaggedHash("BIP0374/aux", 0x0000...00); */ + static const unsigned char ZERO_MASK[32] = { + 38, 255, 199, 133, 21, 94, 75, 99, + 18, 166, 0, 53, 197, 146, 253, 84, + 197, 228, 235, 145, 124, 59, 203, 21, + 66, 88, 250, 253, 207, 123, 43, 55 + }; + for (i = 0; i < 32; i++) { + masked_key[i] = key32[i] ^ ZERO_MASK[i]; + } + } + + secp256k1_nonce_function_bip374_sha256_tagged(&sha); + /* Hash masked-key||msg||m using the tagged hash as per BIP-374 v0.2.0 */ + secp256k1_sha256_write(&sha, masked_key, 32); + secp256k1_sha256_write(&sha, msg, msglen); + if (m != NULL) { + secp256k1_sha256_write(&sha, m, 32); + } + secp256k1_sha256_finalize(&sha, nonce32); + secp256k1_sha256_clear(&sha); + secp256k1_memclear_explicit(masked_key, sizeof(masked_key)); +} + +/* Generates a nonce as defined in BIP0374 v0.2.0 */ +static int secp256k1_dleq_nonce(secp256k1_scalar *k, const unsigned char *a32, const unsigned char *A_33, const unsigned char *C_33, const unsigned char *aux_rand32, const unsigned char *m) { + unsigned char buf[66]; + unsigned char nonce[32]; + + memcpy(buf, A_33, 33); + memcpy(buf + 33, C_33, 33); + secp256k1_nonce_function_dleq(nonce, buf, 66, a32, aux_rand32, m); + + secp256k1_scalar_set_b32(k, nonce, NULL); + if (secp256k1_scalar_is_zero(k)) { + return 0; + } + + return 1; +} + +/* Generates a challenge as defined in BIP0374 */ +static void secp256k1_dleq_challenge(secp256k1_scalar *e, secp256k1_ge *B, secp256k1_ge *R1, secp256k1_ge *R2, secp256k1_ge *A, secp256k1_ge *C, const unsigned char *m) { + unsigned char buf[32]; + secp256k1_sha256 sha; + secp256k1_ge generator_point = secp256k1_ge_const_g; + + secp256k1_dleq_sha256_tagged(&sha); + secp256k1_dleq_hash_point(&sha, A); + secp256k1_dleq_hash_point(&sha, B); + secp256k1_dleq_hash_point(&sha, C); + secp256k1_dleq_hash_point(&sha, &generator_point); + secp256k1_dleq_hash_point(&sha, R1); + secp256k1_dleq_hash_point(&sha, R2); + if (m) secp256k1_sha256_write(&sha, m, 32); + secp256k1_sha256_finalize(&sha, buf); + + secp256k1_scalar_set_b32(e, buf, NULL); +} + +/* Generate points from scalar a such that A = a*G and C = a*B */ +static void secp256k1_dleq_pair(const secp256k1_ecmult_gen_context *ecmult_gen_ctx, secp256k1_ge *A, secp256k1_ge *C, const secp256k1_scalar *a, const secp256k1_ge *B) { + secp256k1_gej Aj, Cj; + + secp256k1_ecmult_gen(ecmult_gen_ctx, &Aj, a); + secp256k1_ge_set_gej(A, &Aj); + secp256k1_ecmult_const(&Cj, B, a); + secp256k1_ge_set_gej(C, &Cj); +} + +/* DLEQ Proof Generation (internal) + * + * For given elliptic curve points A, B, C, and G, the prover generates a proof to prove knowledge of a scalar a such + * that A = a⋅G and C = a⋅B without revealing anything about a. + * + * Returns: 1 if proof creation was successful. 0 if an error occurred. + * Out: scalar e: part of proof = bytes(32, e) || bytes(32, s). + * scalar s: other part of proof = bytes(32, e) || bytes(32, s). + * In: a : scalar a to be proven that both A and C were generated from + * B : point on the curve + * A : point on the curve(a⋅G) generated from a + * C : point on the curve(a⋅B) generated from a + * aux_rand32 : pointer to 32-byte auxiliary randomness used to generate the nonce in secp256k1_nonce_function_dleq. + * m : an optional message + * */ +static int secp256k1_dleq_prove_internal(const secp256k1_context *ctx, secp256k1_scalar *s, secp256k1_scalar *e, const secp256k1_scalar *a, secp256k1_ge *B, secp256k1_ge *A, secp256k1_ge *C, const unsigned char *aux_rand32, const unsigned char *m) { + secp256k1_ge R1, R2; + secp256k1_scalar k = { 0 }; + unsigned char a32[32]; + unsigned char A_33[33]; + unsigned char B_33[33]; + unsigned char C_33[33]; + int ret = 1; + + secp256k1_scalar_get_b32(a32, a); + /* Reject infinity points */ + if (secp256k1_ge_is_infinity(B) || secp256k1_ge_is_infinity(A) || secp256k1_ge_is_infinity(C)) { + return 0; + } + secp256k1_eckey_pubkey_serialize33(B, B_33); + secp256k1_eckey_pubkey_serialize33(A, A_33); + secp256k1_eckey_pubkey_serialize33(C, C_33); + ret &= secp256k1_dleq_nonce(&k, a32, A_33, C_33, aux_rand32, m); + + /* R1 = k*G, R2 = k*B */ + secp256k1_dleq_pair(&ctx->ecmult_gen_ctx, &R1, &R2, &k, B); + /* We declassify the non-secret values R1 and R2 to allow using them as + * branch points. */ + secp256k1_declassify(ctx, &R1, sizeof(R1)); + secp256k1_declassify(ctx, &R2, sizeof(R2)); + + /* e = tagged hash(A, B, C, R1, R2) */ + /* s = k + e * a */ + secp256k1_dleq_challenge(e, B, &R1, &R2, A, C, m); + secp256k1_scalar_mul(s, e, a); + secp256k1_scalar_add(s, s, &k); + + secp256k1_scalar_clear(&k); + secp256k1_memclear_explicit(a32, sizeof(a32)); + return ret; +} + +/* DLEQ Proof Verification (internal) + * + * Verifies the proof. If the following algorithm succeeds, the points A and C were both generated from the same scalar. + * The former from multiplying by G, and the latter from multiplying by B. + * + * Returns: 1 if proof verification was successful. 0 if an error occurred. + * In: proof : proof bytes(32, e) || bytes(32, s) consists of scalar e and scalar s + * A : point on the curve(a⋅G) computed from a + * B : point on the curve + * C : point on the curve(a⋅B) computed from a + * m : optional message + * */ +static int secp256k1_dleq_verify_internal(secp256k1_scalar *s, secp256k1_scalar *e, secp256k1_ge *A, secp256k1_ge *B, secp256k1_ge *C, const unsigned char *m) { + secp256k1_scalar e_neg; + secp256k1_scalar e_expected; + secp256k1_gej Bj; + secp256k1_gej Aj, Cj; + secp256k1_gej R1j, R2j; + secp256k1_ge R1, R2; + secp256k1_gej tmpj; + + secp256k1_gej_set_ge(&Aj, A); + secp256k1_gej_set_ge(&Cj, C); + + secp256k1_scalar_negate(&e_neg, e); + /* R1 = s*G - e*A */ + secp256k1_ecmult(&R1j, &Aj, &e_neg, s); + /* R2 = s*B - e*C */ + secp256k1_ecmult(&tmpj, &Cj, &e_neg, &secp256k1_scalar_zero); + secp256k1_gej_set_ge(&Bj, B); + secp256k1_ecmult(&R2j, &Bj, s, &secp256k1_scalar_zero); + secp256k1_gej_add_var(&R2j, &R2j, &tmpj, NULL); + + secp256k1_ge_set_gej(&R1, &R1j); + secp256k1_ge_set_gej(&R2, &R2j); + secp256k1_dleq_challenge(&e_expected, B, &R1, &R2, A, C, m); + + secp256k1_scalar_add(&e_expected, &e_expected, &e_neg); + return secp256k1_scalar_is_zero(&e_expected); +} + +#endif /* SECP256K1_MODULE_DLEQ_MAIN_H */ diff --git a/src/secp256k1.c b/src/secp256k1.c index ddd9849546..4299063522 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -825,3 +825,7 @@ int secp256k1_tagged_sha256(const secp256k1_context* ctx, unsigned char *hash32, #ifdef ENABLE_MODULE_ELLSWIFT # include "modules/ellswift/main_impl.h" #endif + +#ifdef ENABLE_MODULE_DLEQ +# include "modules/dleq/main_impl.h" +#endif From 029a15b5568368aba088a2f7e121fd1f9512407c Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:14:02 -0500 Subject: [PATCH 2/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> --- src/modules/dleq/Makefile.am.include | 1 + src/modules/dleq/dleq_vectors.h | 105 ++++++++++++++ src/modules/dleq/tests_impl.h | 204 +++++++++++++++++++++++++++ src/tests.c | 7 + tools/test_vectors_dleq_generate.py | 146 +++++++++++++++++++ 5 files changed, 463 insertions(+) create mode 100644 src/modules/dleq/dleq_vectors.h create mode 100644 src/modules/dleq/tests_impl.h create mode 100644 tools/test_vectors_dleq_generate.py diff --git a/src/modules/dleq/Makefile.am.include b/src/modules/dleq/Makefile.am.include index a54ce672ac..2e72ad45e8 100644 --- a/src/modules/dleq/Makefile.am.include +++ b/src/modules/dleq/Makefile.am.include @@ -1,2 +1,3 @@ include_HEADERS += include/secp256k1_dleq.h +noinst_HEADERS += src/modules/dleq/dleq_vectors.h noinst_HEADERS += src/modules/dleq/main_impl.h diff --git a/src/modules/dleq/dleq_vectors.h b/src/modules/dleq/dleq_vectors.h new file mode 100644 index 0000000000..25e9b2c243 --- /dev/null +++ b/src/modules/dleq/dleq_vectors.h @@ -0,0 +1,105 @@ +/** + * Automatically generated by ./tools/test_vectors_dleq_generate.py. + * + * Test vectors according to BIP-374 ("Discrete Log Equality Proofs") are included in this file. + * Tests are included in src/modules/dleq/tests_impl.h. */ + +static const unsigned char a_bytes[6][32] = { + { 0xC0, 0x8C, 0xA8, 0xE0, 0xBB, 0x59, 0x76, 0x9F, 0xC6, 0xA4, 0xE0, 0x78, 0x45, 0x62, 0x84, 0xE0, 0x0E, 0xA3, 0x4F, 0x65, 0xAD, 0xD9, 0x88, 0xC2, 0x46, 0xE1, 0xBB, 0xA8, 0x58, 0x24, 0xCC, 0xDC }, + { 0x8E, 0x64, 0x1B, 0xA6, 0xBF, 0x7F, 0x64, 0xEE, 0xC7, 0x60, 0x05, 0xA2, 0x95, 0x85, 0xA5, 0x03, 0x53, 0x76, 0x37, 0x5F, 0x33, 0xE3, 0x31, 0x21, 0x5A, 0xED, 0xFE, 0x03, 0xB8, 0xE8, 0x0E, 0x7A }, + { 0xCF, 0xB9, 0xA7, 0xEC, 0xC4, 0x9B, 0xEA, 0x4F, 0x2E, 0x2E, 0xE3, 0x4C, 0x38, 0xA6, 0xF4, 0x8B, 0x5C, 0xD5, 0xBD, 0x06, 0xF4, 0xE4, 0xD4, 0xFF, 0xB4, 0x59, 0x05, 0xB3, 0xD2, 0x6D, 0xB8, 0x42 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x41 }, + { 0xCF, 0xB9, 0xA7, 0xEC, 0xC4, 0x9B, 0xEA, 0x4F, 0x2E, 0x2E, 0xE3, 0x4C, 0x38, 0xA6, 0xF4, 0x8B, 0x5C, 0xD5, 0xBD, 0x06, 0xF4, 0xE4, 0xD4, 0xFF, 0xB4, 0x59, 0x05, 0xB3, 0xD2, 0x6D, 0xB8, 0x42 }, +}; + +static const unsigned char A_bytes[13][33] = { + { 0x02, 0x63, 0x7B, 0x2C, 0x3E, 0xA8, 0xCA, 0x80, 0xB9, 0xCA, 0xEC, 0xC5, 0x0F, 0x41, 0x34, 0xC8, 0x6A, 0xE9, 0xCF, 0x7A, 0x26, 0x91, 0x33, 0xE7, 0xAF, 0xC7, 0x1F, 0x30, 0xE3, 0xA3, 0xCD, 0xA6, 0x0C }, + { 0x02, 0x98, 0x3A, 0x72, 0xB4, 0xCB, 0x44, 0xD4, 0x32, 0x26, 0x41, 0xA7, 0xB2, 0x00, 0x19, 0x00, 0xCD, 0x6A, 0xE0, 0x90, 0x8A, 0x61, 0x05, 0x46, 0xC7, 0x3E, 0xD1, 0x26, 0xAC, 0xCD, 0xBA, 0x05, 0x14 }, + { 0x03, 0x61, 0x14, 0x10, 0x56, 0x1C, 0x35, 0xDA, 0xE1, 0x31, 0x35, 0xE4, 0xAD, 0x80, 0x94, 0xBA, 0xAC, 0x9B, 0xBC, 0xF2, 0xF4, 0xE1, 0x84, 0x98, 0x18, 0x1A, 0x8F, 0xF8, 0xA6, 0xD4, 0x3B, 0xE9, 0xD9 }, + { 0x00 }, + { 0x00 }, + { 0x00 }, + { 0x03, 0x61, 0x14, 0x10, 0x56, 0x1C, 0x35, 0xDA, 0xE1, 0x31, 0x35, 0xE4, 0xAD, 0x80, 0x94, 0xBA, 0xAC, 0x9B, 0xBC, 0xF2, 0xF4, 0xE1, 0x84, 0x98, 0x18, 0x1A, 0x8F, 0xF8, 0xA6, 0xD4, 0x3B, 0xE9, 0xD9 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, + { 0x03, 0xD9, 0xA9, 0x86, 0x24, 0xC0, 0xC7, 0x4F, 0xC7, 0xEE, 0xBD, 0x39, 0xED, 0x84, 0x17, 0x5F, 0x80, 0xD0, 0x3C, 0x77, 0x49, 0x08, 0xE7, 0x5C, 0xA7, 0x37, 0xA0, 0x74, 0x5D, 0x1C, 0x64, 0xE2, 0x0A }, + { 0x03, 0xD9, 0xA9, 0x86, 0x24, 0xC0, 0xC7, 0x4F, 0xC7, 0xEE, 0xBD, 0x39, 0xED, 0x84, 0x17, 0x5F, 0x80, 0xD0, 0x3C, 0x77, 0x49, 0x08, 0xE7, 0x5C, 0xA7, 0x37, 0xA0, 0x74, 0x5D, 0x1C, 0x64, 0xE2, 0x0A }, + { 0x03, 0x61, 0x14, 0x10, 0x56, 0x1C, 0x35, 0xDA, 0xE1, 0x31, 0x35, 0xE4, 0xAD, 0x80, 0x94, 0xBA, 0xAC, 0x9B, 0xBC, 0xF2, 0xF4, 0xE1, 0x84, 0x98, 0x18, 0x1A, 0x8F, 0xF8, 0xA6, 0xD4, 0x3B, 0xE9, 0xD9 }, + { 0x03, 0x61, 0x14, 0x10, 0x56, 0x1C, 0x35, 0xDA, 0xE1, 0x31, 0x35, 0xE4, 0xAD, 0x80, 0x94, 0xBA, 0xAC, 0x9B, 0xBC, 0xF2, 0xF4, 0xE1, 0x84, 0x98, 0x18, 0x1A, 0x8F, 0xF8, 0xA6, 0xD4, 0x3B, 0xE9, 0xD9 }, +}; + +static const unsigned char B_bytes[13][33] = { + { 0x03, 0x4B, 0xCC, 0xB1, 0xC5, 0x70, 0xAC, 0x1F, 0x3B, 0xC4, 0x2D, 0x61, 0xFE, 0x35, 0xDE, 0x60, 0x5B, 0x99, 0x62, 0x65, 0x01, 0xCC, 0xB2, 0x02, 0x97, 0xE1, 0xAC, 0xBB, 0xF2, 0xD7, 0x15, 0x2A, 0xA1 }, + { 0x02, 0x31, 0xC6, 0x4E, 0x3E, 0xFA, 0x50, 0x6F, 0xDA, 0xD6, 0xAA, 0xD0, 0xF6, 0x08, 0x4D, 0x5F, 0x67, 0x39, 0xDE, 0x7F, 0x44, 0x8D, 0x7E, 0x66, 0xF9, 0xD2, 0x2F, 0x84, 0x26, 0x38, 0xF4, 0x1D, 0x60 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, + { 0x00 }, + { 0x03, 0xD9, 0xA9, 0x86, 0x24, 0xC0, 0xC7, 0x4F, 0xC7, 0xEE, 0xBD, 0x39, 0xED, 0x84, 0x17, 0x5F, 0x80, 0xD0, 0x3C, 0x77, 0x49, 0x08, 0xE7, 0x5C, 0xA7, 0x37, 0xA0, 0x74, 0x5D, 0x1C, 0x64, 0xE2, 0x0A }, + { 0x03, 0x61, 0x14, 0x10, 0x56, 0x1C, 0x35, 0xDA, 0xE1, 0x31, 0x35, 0xE4, 0xAD, 0x80, 0x94, 0xBA, 0xAC, 0x9B, 0xBC, 0xF2, 0xF4, 0xE1, 0x84, 0x98, 0x18, 0x1A, 0x8F, 0xF8, 0xA6, 0xD4, 0x3B, 0xE9, 0xD9 }, + { 0x03, 0xD9, 0xA9, 0x86, 0x24, 0xC0, 0xC7, 0x4F, 0xC7, 0xEE, 0xBD, 0x39, 0xED, 0x84, 0x17, 0x5F, 0x80, 0xD0, 0x3C, 0x77, 0x49, 0x08, 0xE7, 0x5C, 0xA7, 0x37, 0xA0, 0x74, 0x5D, 0x1C, 0x64, 0xE2, 0x0A }, + { 0x03, 0x61, 0x14, 0x10, 0x56, 0x1C, 0x35, 0xDA, 0xE1, 0x31, 0x35, 0xE4, 0xAD, 0x80, 0x94, 0xBA, 0xAC, 0x9B, 0xBC, 0xF2, 0xF4, 0xE1, 0x84, 0x98, 0x18, 0x1A, 0x8F, 0xF8, 0xA6, 0xD4, 0x3B, 0xE9, 0xD9 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, +}; + +static const unsigned char C_bytes[13][33] = { + { 0x02, 0x85, 0xB8, 0x26, 0xC8, 0xDD, 0x17, 0x58, 0x05, 0x90, 0x19, 0x06, 0xB6, 0xC9, 0xB4, 0x14, 0x0A, 0x30, 0xCB, 0xCC, 0x94, 0xC6, 0xE7, 0xDC, 0xF3, 0x64, 0x76, 0x03, 0x8B, 0xF9, 0x0D, 0x47, 0x18 }, + { 0x03, 0xAF, 0x1B, 0xC1, 0x4B, 0x38, 0x4E, 0xDA, 0x28, 0x39, 0x8D, 0xF6, 0xA7, 0x90, 0x0E, 0x56, 0x7C, 0x5B, 0x6F, 0x66, 0x13, 0xCA, 0xFC, 0xE5, 0x02, 0x7B, 0x98, 0xBE, 0x01, 0x52, 0x86, 0xF7, 0x1B }, + { 0x03, 0xD9, 0xA9, 0x86, 0x24, 0xC0, 0xC7, 0x4F, 0xC7, 0xEE, 0xBD, 0x39, 0xED, 0x84, 0x17, 0x5F, 0x80, 0xD0, 0x3C, 0x77, 0x49, 0x08, 0xE7, 0x5C, 0xA7, 0x37, 0xA0, 0x74, 0x5D, 0x1C, 0x64, 0xE2, 0x0A }, + { 0x00 }, + { 0x00 }, + { 0x00 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, + { 0x03, 0xD9, 0xA9, 0x86, 0x24, 0xC0, 0xC7, 0x4F, 0xC7, 0xEE, 0xBD, 0x39, 0xED, 0x84, 0x17, 0x5F, 0x80, 0xD0, 0x3C, 0x77, 0x49, 0x08, 0xE7, 0x5C, 0xA7, 0x37, 0xA0, 0x74, 0x5D, 0x1C, 0x64, 0xE2, 0x0A }, + { 0x03, 0x61, 0x14, 0x10, 0x56, 0x1C, 0x35, 0xDA, 0xE1, 0x31, 0x35, 0xE4, 0xAD, 0x80, 0x94, 0xBA, 0xAC, 0x9B, 0xBC, 0xF2, 0xF4, 0xE1, 0x84, 0x98, 0x18, 0x1A, 0x8F, 0xF8, 0xA6, 0xD4, 0x3B, 0xE9, 0xD9 }, + { 0x02, 0x1C, 0xB8, 0x11, 0x21, 0xA0, 0x0F, 0x89, 0x76, 0x99, 0x03, 0x30, 0x5A, 0x36, 0x7A, 0xD3, 0xCC, 0x02, 0xD5, 0xB4, 0x02, 0xB1, 0x2C, 0x02, 0x6E, 0x06, 0xAC, 0x94, 0xBD, 0xE2, 0x8C, 0xD6, 0x08 }, + { 0x03, 0x61, 0x14, 0x10, 0x56, 0x1C, 0x35, 0xDA, 0xE1, 0x31, 0x35, 0xE4, 0xAD, 0x80, 0x94, 0xBA, 0xAC, 0x9B, 0xBC, 0xF2, 0xF4, 0xE1, 0x84, 0x98, 0x18, 0x1A, 0x8F, 0xF8, 0xA6, 0xD4, 0x3B, 0xE9, 0xD9 }, + { 0x03, 0xD9, 0xA9, 0x86, 0x24, 0xC0, 0xC7, 0x4F, 0xC7, 0xEE, 0xBD, 0x39, 0xED, 0x84, 0x17, 0x5F, 0x80, 0xD0, 0x3C, 0x77, 0x49, 0x08, 0xE7, 0x5C, 0xA7, 0x37, 0xA0, 0x74, 0x5D, 0x1C, 0x64, 0xE2, 0x0A }, + { 0x03, 0xD9, 0xA9, 0x86, 0x24, 0xC0, 0xC7, 0x4F, 0xC7, 0xEE, 0xBD, 0x39, 0xED, 0x84, 0x17, 0x5F, 0x80, 0xD0, 0x3C, 0x77, 0x49, 0x08, 0xE7, 0x5C, 0xA7, 0x37, 0xA0, 0x74, 0x5D, 0x1C, 0x64, 0xE2, 0x0A }, +}; + +static const unsigned char auxrand_bytes[6][32] = { + { 0xC8, 0xD7, 0x05, 0x6A, 0xBD, 0x47, 0x26, 0xEB, 0x5A, 0x0F, 0x19, 0x87, 0x40, 0xAF, 0x14, 0xD6, 0xC1, 0xF0, 0xC1, 0x6E, 0x5D, 0x7A, 0x37, 0xEA, 0xEC, 0x62, 0x1B, 0x66, 0x1E, 0x66, 0x9A, 0xC4 }, + { 0x02, 0xA7, 0xB2, 0xE2, 0xF5, 0xA5, 0xE9, 0xB1, 0x07, 0x8D, 0xBB, 0x16, 0x05, 0x02, 0xA3, 0x24, 0x91, 0xFE, 0x80, 0xA0, 0x91, 0xE9, 0x1D, 0xD9, 0x2C, 0xF7, 0x7B, 0x0B, 0x7D, 0x90, 0x97, 0x0F }, + { 0xD3, 0x84, 0x66, 0xB7, 0x74, 0x84, 0x15, 0x4A, 0x3F, 0xCB, 0x31, 0x51, 0x09, 0x4C, 0x1C, 0x8A, 0x84, 0x5C, 0x73, 0xA3, 0xC0, 0x36, 0xB3, 0xA8, 0xEB, 0xFF, 0xD8, 0xEF, 0x62, 0xC9, 0x04, 0x7F }, + { 0xD3, 0x84, 0x66, 0xB7, 0x74, 0x84, 0x15, 0x4A, 0x3F, 0xCB, 0x31, 0x51, 0x09, 0x4C, 0x1C, 0x8A, 0x84, 0x5C, 0x73, 0xA3, 0xC0, 0x36, 0xB3, 0xA8, 0xEB, 0xFF, 0xD8, 0xEF, 0x62, 0xC9, 0x04, 0x7F }, + { 0xD3, 0x84, 0x66, 0xB7, 0x74, 0x84, 0x15, 0x4A, 0x3F, 0xCB, 0x31, 0x51, 0x09, 0x4C, 0x1C, 0x8A, 0x84, 0x5C, 0x73, 0xA3, 0xC0, 0x36, 0xB3, 0xA8, 0xEB, 0xFF, 0xD8, 0xEF, 0x62, 0xC9, 0x04, 0x7F }, + { 0xD3, 0x84, 0x66, 0xB7, 0x74, 0x84, 0x15, 0x4A, 0x3F, 0xCB, 0x31, 0x51, 0x09, 0x4C, 0x1C, 0x8A, 0x84, 0x5C, 0x73, 0xA3, 0xC0, 0x36, 0xB3, 0xA8, 0xEB, 0xFF, 0xD8, 0xEF, 0x62, 0xC9, 0x04, 0x7F }, +}; + +static const unsigned char msg_bytes[13][32] = { + { 0x00 }, + { 0x35, 0x84, 0x1C, 0xA5, 0x32, 0x84, 0x6E, 0x1C, 0xDD, 0x23, 0xA3, 0xD1, 0x07, 0x82, 0x43, 0x43, 0x58, 0x4F, 0x88, 0xEF, 0xF5, 0x80, 0x92, 0x94, 0x69, 0x86, 0x5E, 0xAE, 0x83, 0x55, 0xEE, 0x3C }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x2D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, + { 0x22, 0x61, 0x6B, 0xB5, 0xFB, 0x6D, 0x7C, 0x68, 0x27, 0x0F, 0x30, 0x51, 0x22, 0xF2, 0xA0, 0x9E, 0x83, 0x32, 0x39, 0xC4, 0xB1, 0xC9, 0xA0, 0x4E, 0x28, 0x51, 0x19, 0xFB, 0x60, 0x6A, 0xC7, 0x94 }, +}; + +static const unsigned char proof_bytes[13][64] = { + { 0x50, 0x35, 0x62, 0xD3, 0x69, 0x10, 0xCD, 0x2D, 0x61, 0xA4, 0xD0, 0x7C, 0x8F, 0xF6, 0x80, 0x26, 0x5C, 0x71, 0x3E, 0x63, 0xDD, 0xE0, 0xDC, 0xB8, 0x8E, 0x6E, 0xA3, 0xC5, 0x85, 0x97, 0xBD, 0xC0, 0x5B, 0x86, 0xDB, 0x9A, 0xF9, 0x5E, 0xCC, 0xC4, 0x75, 0xCE, 0x21, 0x77, 0xF9, 0x41, 0xC1, 0x18, 0xFE, 0xFE, 0xD2, 0x02, 0x27, 0xD4, 0xCE, 0x8C, 0xE9, 0x55, 0x7C, 0xB0, 0x08, 0x75, 0x8D, 0xE6 }, + { 0x5C, 0x7B, 0x27, 0xA3, 0x32, 0x10, 0x75, 0x0E, 0x9D, 0xE8, 0x67, 0x9D, 0x9F, 0x43, 0x49, 0x7C, 0xF9, 0xF1, 0x2A, 0xC6, 0x42, 0xCD, 0xE0, 0xA1, 0xFC, 0x26, 0x44, 0x3A, 0xA2, 0xFC, 0x89, 0xBF, 0x71, 0xAA, 0xBF, 0x7B, 0xAC, 0x89, 0xF5, 0xD8, 0xA9, 0x6C, 0xBE, 0x86, 0xDA, 0xBA, 0x15, 0x5F, 0xA7, 0x4D, 0x6F, 0x3E, 0x11, 0x11, 0x36, 0x17, 0x9E, 0x53, 0xB0, 0x4E, 0xB6, 0xD7, 0x80, 0x7F }, + { 0x78, 0xA5, 0x54, 0x4A, 0xFA, 0x75, 0xBF, 0x15, 0x26, 0x53, 0xFE, 0x55, 0xFB, 0x76, 0x92, 0x6F, 0x2F, 0x65, 0x13, 0x1B, 0xF0, 0x90, 0x97, 0x2A, 0x0B, 0x0B, 0x37, 0xD3, 0x10, 0xC2, 0x8A, 0x6B, 0xDE, 0x0E, 0x7B, 0xFA, 0xCC, 0x10, 0xAC, 0x12, 0xD3, 0x6F, 0x55, 0x31, 0x6B, 0xA1, 0x34, 0xB6, 0xBA, 0x0B, 0x84, 0x4A, 0x65, 0xAE, 0x05, 0xCA, 0xD5, 0x3C, 0x0B, 0x29, 0x6C, 0x66, 0x39, 0xBB }, + { 0x00 }, + { 0x00 }, + { 0x00 }, + { 0x78, 0xA5, 0x54, 0x4A, 0xFA, 0x75, 0xBF, 0x15, 0x26, 0x53, 0xFE, 0x55, 0xFB, 0x76, 0x92, 0x6F, 0x2F, 0x65, 0x13, 0x1B, 0xF0, 0x90, 0x97, 0x2A, 0x0B, 0x0B, 0x37, 0xD3, 0x10, 0xC2, 0x8A, 0x6B, 0xDE, 0x0E, 0x7B, 0xFA, 0xCC, 0x10, 0xAC, 0x12, 0xD3, 0x6F, 0x55, 0x31, 0x6B, 0xA1, 0x34, 0xB6, 0xBA, 0x0B, 0x84, 0x4A, 0x65, 0xAE, 0x05, 0xCA, 0xD5, 0x3C, 0x0B, 0x29, 0x6C, 0x66, 0x39, 0xBB }, + { 0x78, 0xA5, 0x54, 0x4A, 0xFA, 0x75, 0xBF, 0x15, 0x26, 0x53, 0xFE, 0x55, 0xFB, 0x76, 0x92, 0x6F, 0x2F, 0x65, 0x13, 0x1B, 0xF0, 0x90, 0x97, 0x2A, 0x0B, 0x0B, 0x37, 0xD3, 0x10, 0xC2, 0x8A, 0x6B, 0xDE, 0x0E, 0x7B, 0xFA, 0xCC, 0x10, 0xAC, 0x12, 0xD3, 0x6F, 0x55, 0x31, 0x6B, 0xA1, 0x34, 0xB6, 0xBA, 0x0B, 0x84, 0x4A, 0x65, 0xAE, 0x05, 0xCA, 0xD5, 0x3C, 0x0B, 0x29, 0x6C, 0x66, 0x39, 0xBB }, + { 0x78, 0xA5, 0x54, 0x4A, 0xFA, 0x75, 0xBF, 0x15, 0x26, 0x53, 0xFE, 0x55, 0xFB, 0x76, 0x92, 0x6F, 0x2F, 0x65, 0x13, 0x1B, 0xF0, 0x90, 0x97, 0x2A, 0x0B, 0x0B, 0x37, 0xD3, 0x10, 0xC2, 0x8A, 0x6B, 0xDE, 0x0E, 0x7B, 0xFA, 0xCC, 0x10, 0xAC, 0x12, 0xD3, 0x6F, 0x55, 0x31, 0x6B, 0xA1, 0x34, 0xB6, 0xBA, 0x0B, 0x84, 0x4A, 0x65, 0xAE, 0x05, 0xCA, 0xD5, 0x3C, 0x0B, 0x29, 0x6C, 0x66, 0x39, 0xBB }, + { 0x78, 0xA5, 0x54, 0x4A, 0xFA, 0x75, 0xBF, 0x15, 0x26, 0x53, 0xFE, 0x55, 0xFB, 0x76, 0x92, 0x6F, 0x2F, 0x65, 0x13, 0x1B, 0xF0, 0x90, 0x97, 0x2A, 0x0B, 0x0B, 0x37, 0xD3, 0x10, 0xC2, 0x8A, 0x6B, 0xDE, 0x0E, 0x7B, 0xFA, 0xCC, 0x10, 0xAC, 0x12, 0xD3, 0x6F, 0x55, 0x31, 0x6B, 0xA1, 0x34, 0xB6, 0xBA, 0x0B, 0x84, 0x4A, 0x65, 0xAE, 0x05, 0xCA, 0xD5, 0x3C, 0x0B, 0x29, 0x6C, 0x66, 0x39, 0xBB }, + { 0x78, 0xA5, 0x54, 0x4A, 0xFA, 0x75, 0xBF, 0x15, 0x26, 0x53, 0xFE, 0x55, 0xFB, 0x76, 0x92, 0x6F, 0x2F, 0x65, 0x13, 0x1B, 0xF0, 0x90, 0x97, 0x2A, 0x0B, 0x0B, 0x37, 0xD3, 0x10, 0xC2, 0x8A, 0x6B, 0xDE, 0x0E, 0x7B, 0xFA, 0xCC, 0x10, 0xAC, 0x12, 0xD3, 0x6F, 0x55, 0x31, 0x6B, 0xA1, 0x34, 0xB6, 0xBA, 0x0B, 0x84, 0x4A, 0x65, 0xAE, 0x05, 0xCA, 0xD5, 0x3C, 0x0B, 0x29, 0x6C, 0x66, 0x39, 0xBB }, + { 0x78, 0xA5, 0x54, 0x4A, 0xFA, 0x75, 0xBF, 0x15, 0x26, 0x53, 0xFE, 0x55, 0xFB, 0x76, 0x92, 0x6F, 0x2F, 0x65, 0x13, 0x1F, 0xF0, 0x90, 0x97, 0x2A, 0x0B, 0x0B, 0x37, 0xD3, 0x10, 0xC2, 0x8A, 0x6B, 0xDE, 0x0E, 0x7B, 0xFA, 0xCC, 0x10, 0xAC, 0x12, 0xD3, 0x6F, 0x55, 0x31, 0x6B, 0xA1, 0x34, 0xB6, 0xBA, 0x0B, 0x84, 0x4A, 0x65, 0xAE, 0x05, 0xCA, 0xD5, 0x3C, 0x0B, 0x29, 0x6C, 0x66, 0x39, 0xBB }, + { 0x78, 0xA5, 0x54, 0x4A, 0xFA, 0x75, 0xBF, 0x15, 0x26, 0x53, 0xFE, 0x55, 0xFB, 0x76, 0x92, 0x6F, 0x2F, 0x65, 0x13, 0x1B, 0xF0, 0x90, 0x97, 0x2A, 0x0B, 0x0B, 0x37, 0xD3, 0x10, 0xC2, 0x8A, 0x6B, 0xDE, 0x0E, 0x7B, 0xFA, 0xCC, 0x10, 0xAC, 0x12, 0xD3, 0x6F, 0x55, 0x31, 0x6B, 0xA1, 0x34, 0xB6, 0xBA, 0x0B, 0x84, 0x4A, 0x65, 0xAE, 0x05, 0xCA, 0xD5, 0x3C, 0x0B, 0x29, 0x6C, 0x66, 0x39, 0xBB }, +}; + +static const unsigned char success[13] = { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; diff --git a/src/modules/dleq/tests_impl.h b/src/modules/dleq/tests_impl.h new file mode 100644 index 0000000000..82f1fa004d --- /dev/null +++ b/src/modules/dleq/tests_impl.h @@ -0,0 +1,204 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_DLEQ_TESTS_H +#define SECP256K1_MODULE_DLEQ_TESTS_H + +#include "dleq_vectors.h" + +static void dleq_nonce_bitflip(unsigned char **args, size_t n_flip, size_t n_bytes) { + secp256k1_scalar k1, k2; + CHECK(secp256k1_dleq_nonce(&k1, args[0], args[1], args[2], args[3], args[4]) == 1); + testrand_flip(args[n_flip], n_bytes); + CHECK(secp256k1_dleq_nonce(&k2, args[0], args[1], args[2], args[3], args[4]) == 1); + CHECK(secp256k1_scalar_eq(&k1, &k2) == 0); +} + +static void run_test_dleq_prove_verify(void) { + secp256k1_scalar s, e, a, k; + secp256k1_ge A, B, C; + unsigned char *args[5]; + unsigned char a32[32]; + unsigned char A_33[33]; + unsigned char C_33[33]; + unsigned char aux_rand[32]; + unsigned char msg[32]; + unsigned char proof_64[64] = {0}; + int i; + int overflow; + secp256k1_sha256 sha; + secp256k1_sha256 sha_optimized; + unsigned char aux_tag[] = {'B', 'I', 'P', '0', '3', '7', '4', '/', 'a', 'u', 'x'}; + unsigned char tag[] = {'B', 'I', 'P', '0', '3', '7', '4', '/', 'n', 'o', 'n', 'c', 'e'}; + unsigned char challenge_tag[] = {'B', 'I', 'P', '0', '3', '7', '4', '/', 'c', 'h', 'a', 'l', 'l', 'e', 'n', 'g', 'e'}; + + /* Check that hash initialized by secp256k1_nonce_function_bip374_sha256_tagged_aux has the expected state. */ + secp256k1_sha256_initialize_tagged(&sha, aux_tag, sizeof(aux_tag)); + secp256k1_nonce_function_bip374_sha256_tagged_aux(&sha_optimized); + test_sha256_eq(&sha, &sha_optimized); + + /* Check that hash initialized by secp256k1_nonce_function_bip374_sha256_tagged has the expected state. */ + secp256k1_sha256_initialize_tagged(&sha, tag, sizeof(tag)); + secp256k1_nonce_function_bip374_sha256_tagged(&sha_optimized); + test_sha256_eq(&sha, &sha_optimized); + + /* Check that hash initialized by secp256k1_dleq_sha256_tagged has the expected state. */ + secp256k1_sha256_initialize_tagged(&sha, challenge_tag, sizeof(challenge_tag)); + secp256k1_dleq_sha256_tagged(&sha_optimized); + test_sha256_eq(&sha, &sha_optimized); + + for (i = 0; i < COUNT; i++) { + testutil_random_ge_test(&B); + testutil_random_scalar_order(&a); + testrand256(aux_rand); + testrand_bytes_test(msg, sizeof(msg)); + secp256k1_dleq_pair(&CTX->ecmult_gen_ctx, &A, &C, &a, &B); + CHECK(secp256k1_dleq_prove_internal(CTX, &s, &e, &a, &B, &A, &C, aux_rand, (i & 1) ? msg : NULL) == 1); + CHECK(secp256k1_dleq_verify_internal(&s, &e, &A, &B, &C, (i & 1) ? msg : NULL) == 1); + secp256k1_scalar_set_b32(&s, proof_64, &overflow); + VERIFY_CHECK(overflow == 0); + secp256k1_scalar_set_b32(&e, proof_64 + 32, &overflow); + VERIFY_CHECK(overflow == 0); + } + + { + secp256k1_scalar tmp; + secp256k1_scalar_set_int(&tmp, 1); + CHECK(secp256k1_dleq_verify_internal(&tmp, &e, &A, &B, &C, msg) == 0); + CHECK(secp256k1_dleq_verify_internal(&s, &tmp, &A, &B, &C, msg) == 0); + } + { + secp256k1_ge p_tmp; + testutil_random_ge_test(&p_tmp); + CHECK(secp256k1_dleq_verify_internal(&s, &e, &p_tmp, &B, &C, msg) == 0); + CHECK(secp256k1_dleq_verify_internal(&s, &e, &A, &p_tmp, &C, msg) == 0); + CHECK(secp256k1_dleq_verify_internal(&s, &e, &A, &B, &p_tmp, msg) == 0); + } + { + secp256k1_ge p_inf; + secp256k1_ge_set_infinity(&p_inf); + CHECK(secp256k1_dleq_prove_internal(CTX, &s, &e, &a, &p_inf, &A, &C, aux_rand, msg) == 0); + CHECK(secp256k1_dleq_prove_internal(CTX, &s, &e, &a, &B, &p_inf, &C, aux_rand, msg) == 0); + CHECK(secp256k1_dleq_prove_internal(CTX, &s, &e, &a, &B, &A, &p_inf, aux_rand, msg) == 0); + } + + /* Nonce tests */ + secp256k1_scalar_get_b32(a32, &a); + secp256k1_eckey_pubkey_serialize33(&A, A_33); + secp256k1_eckey_pubkey_serialize33(&C, C_33); + CHECK(secp256k1_dleq_nonce(&k, a32, A_33, C_33, aux_rand, msg) == 1); + + testrand_bytes_test(a32, sizeof(a32)); + testrand_bytes_test(A_33, sizeof(A_33)); + testrand_bytes_test(C_33, sizeof(C_33)); + testrand_bytes_test(aux_rand, sizeof(aux_rand)); + + /* Check that a bitflip in an argument results in different nonces. */ + args[0] = a32; + args[1] = A_33; + args[2] = C_33; + args[3] = aux_rand; + args[4] = msg; + for (i = 0; i < COUNT; i++) { + dleq_nonce_bitflip(args, 0, sizeof(a32)); + dleq_nonce_bitflip(args, 1, sizeof(A_33)); + /* Flip C */ + dleq_nonce_bitflip(args, 2, sizeof(C_33)); + /* Flip C again */ + dleq_nonce_bitflip(args, 2, sizeof(C_33)); + dleq_nonce_bitflip(args, 3, sizeof(aux_rand)); + dleq_nonce_bitflip(args, 4, sizeof(msg)); + } + + /* NULL aux_rand and msg arguments are allowed.*/ + CHECK(secp256k1_dleq_nonce(&k, a32, A_33, C_33, NULL, NULL) == 1); + CHECK(secp256k1_dleq_nonce(&k, a32, A_33, C_33, aux_rand, NULL) == 1); + CHECK(secp256k1_dleq_nonce(&k, a32, A_33, C_33, NULL, msg) == 1); +} + +/* Test BIP-374 test vectors ("Discrete Log Equality Proofs"). + * See tools/test_vectors_dleq_generate.py + * */ + +static unsigned char zero_array[32] = {0x00}; + +/* Helper function to check if given array is NOT equivalent to all zero array. + * Used to detect test vectors where zero array can represent: + * B_bytes at infinity + * Empty optional msg_bytes + * */ +static int is_not_empty(const unsigned char *arr){ + return (memcmp(arr, zero_array, 32) != 0); +} + +static void run_test_dleq_bip374_vectors(void) { + secp256k1_scalar a, s, e; + secp256k1_ge A; + secp256k1_ge B; + secp256k1_ge C; + int i; + + /* bip-0374/test_vectors_generate_proof.csv */ + for (i = 0; i < 6; ++i) { + int ret = 1; + const unsigned char *m = NULL; + secp256k1_ge_set_infinity(&B); + /* expect the last 3 generate proof vectors to fail */ + if (i > 2) ret = 0; + + secp256k1_scalar_set_b32(&a, a_bytes[i], NULL); + if (is_not_empty(B_bytes[i])) { + CHECK(secp256k1_eckey_pubkey_parse(&B, B_bytes[i], 33) == 1); + } + + secp256k1_dleq_pair(&CTX->ecmult_gen_ctx, &A, &C, &a, &B); + + if (is_not_empty(msg_bytes[i])) { + m = msg_bytes[i]; + } + CHECK(secp256k1_dleq_prove_internal(CTX, &s, &e, &a, &B, &A, &C, (unsigned char*)(auxrand_bytes[i]), m) == ret); + + if (ret) { + unsigned char proof[64]; + secp256k1_scalar_get_b32(proof, &e); + secp256k1_scalar_get_b32(proof + 32, &s); + CHECK(memcmp(proof, proof_bytes[i], 64) == 0); + CHECK(secp256k1_dleq_verify_internal(&s, &e, &A, &B, &C, m) == 1); + } + } + + /* bip-0374/test_vectors_verify_proof.csv */ + for (i = 0; i < 13; ++i) { + const unsigned char *m = NULL; + + 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. + * */ + continue; + } + + CHECK(secp256k1_eckey_pubkey_parse(&A, A_bytes[i], 33) == 1); + CHECK(secp256k1_eckey_pubkey_parse(&B, B_bytes[i], 33) == 1); + CHECK(secp256k1_eckey_pubkey_parse(&C, C_bytes[i], 33) == 1); + + secp256k1_scalar_set_b32(&e, proof_bytes[i], NULL); + secp256k1_scalar_set_b32(&s, proof_bytes[i] + 32, NULL); + + if (is_not_empty(msg_bytes[i])) { + m = msg_bytes[i]; + } + + CHECK(secp256k1_dleq_verify_internal(&s, &e, &A, &B, &C, m) == success[i]); + } +} + +static const struct tf_test_entry tests_dleq[] = { + CASE(test_dleq_prove_verify), + CASE(test_dleq_bip374_vectors), +}; + +#endif /* SECP256K1_MODULE_DLEQ_TESTS_H */ diff --git a/src/tests.c b/src/tests.c index e09f5c7d23..b42e47e3a3 100644 --- a/src/tests.c +++ b/src/tests.c @@ -7467,6 +7467,10 @@ static void run_ecdsa_wycheproof(void) { # include "modules/ellswift/tests_impl.h" #endif +#ifdef ENABLE_MODULE_DLEQ +#include "modules/dleq/tests_impl.h" +#endif + static void run_secp256k1_memczero_test(void) { unsigned char buf1[6] = {1, 2, 3, 4, 5, 6}; unsigned char buf2[sizeof(buf1)]; @@ -7800,6 +7804,9 @@ static const struct tf_test_module registry_modules[] = { #endif #ifdef ENABLE_MODULE_ELLSWIFT MAKE_TEST_MODULE(ellswift), +#endif +#ifdef ENABLE_MODULE_DLEQ + MAKE_TEST_MODULE(dleq), #endif MAKE_TEST_MODULE(utils), }; diff --git a/tools/test_vectors_dleq_generate.py b/tools/test_vectors_dleq_generate.py new file mode 100644 index 0000000000..d0c53b4814 --- /dev/null +++ b/tools/test_vectors_dleq_generate.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 + +import sys +import csv +import textwrap + +if len(sys.argv) < 2: + print( + "This script converts BIP 374 DLEQ test vectors in a given directory to a C file that can be used in the test framework." + ) + print("Usage: %s " % sys.argv[0]) + sys.exit(1) + +s = ( + """/** + * Automatically generated by %s. + * + * Test vectors according to BIP-374 ("Discrete Log Equality Proofs") are included in this file. + * Tests are included in src/modules/dleq/tests_impl.h. */ + """ % sys.argv[0] +) + +def hexstr_to_intarray(str): + try: + return ", ".join([f"0x{b:02X}" for b in bytes.fromhex(str)]) + except ValueError: + return "0x00" + +def create_init(name, rows, cols): + return """ +static const unsigned char %s[%d][%d] = { +""" % ( + name, + rows, + cols, + ) + +def init_array(key): + return textwrap.indent("{ %s };" % ", ".join(test_case[key]), "") + +def init_arrays(key): + s = textwrap.indent( + ",\n".join(["{ %s }" % hexstr_to_intarray(x) for x in test_case[key]]), 4 * " " + ) + s += textwrap.indent(",\n};\n", "") + return s + + +# Define lists to store each column from the test_vectors_(generate|verify)_proof.csv files +test_case = { + "index": [], + "point_G": [], + "scalar_a": [], + "point_A": [], + "point_B": [], + "point_C": [], + "auxrand_r": [], + "message": [], + "comment": [], + "result_proof": [], + "result_success": [], +} + + +with open(sys.argv[1] + "/test_vectors_generate_proof.csv", newline='') as csvfile: + reader = csv.DictReader(csvfile) + # Skip the first 5 rows since those test vectors don't use secp's generator point + for _ in range(5): + next(reader, None) + + for row in reader: + for key in test_case: + if key in row: + # these keys are present in test_vectors_generate_proof.csv + # if special cases are encountered, "0" is filled in place of the value (INFINITY/INVALID/"") + special_cases = { + "point_B": "INFINITY", + "result_proof": "INVALID", + "message": "" + } + test_case[key].append("0" if row[key] in special_cases.get(key, []) else row[key]) + else: + # these keys are not present in current csv file but are present in test_vectors_verify_proof.csv + if key in {"point_A", "point_C"}: + # "0" is filled as value for these missing keys + test_case[key].append("0") + elif key == "result_success": + # success/failure value is obtained from row["comment"] for the missing key "result_success" + test_case[key].append("1" if "Success" in row.get("comment", "") else "0") + else: + sys.exit("Unexpected missing_key encountered when parsing test_vectors_generate_proof.csv") + + +with open(sys.argv[1] + "/test_vectors_verify_proof.csv", newline='') as csvfile: + reader = csv.DictReader(csvfile) + 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): + # Fill point_A and point_C for the 3 success cases (array indices 0-2) from verify CSV + # These correspond to generate and verify CSV rows 5-7 + row = next(reader) + test_case["point_A"][i] = row["point_A"] + test_case["point_C"][i] = row["point_C"] + + for row in reader: + for key in test_case: + if key in row: + # these keys are present in test_vectors_verify_proof.csv + # not handling row[key] == "TRUE" since it doesn't appear in the BIP test vectors + test_case[key].append("0" if key == "result_success" and row[key] == "FALSE" else row[key]) + else: + # these keys are not present in current csv file but are present in test_vectors_generate_proof.csv + if key == "result_proof": + # interpret "result_proof" key in the test_vectors_generate_proof.csv test vectors + # same as "proof" key in the test_vectors_verify_proof.csv test vectors + test_case["result_proof"].append(row["proof"]) + elif key not in {"scalar_a", "auxrand_r"}: # skip expected missing keys + sys.exit("Unexpected missing key encountered when test_vectors_verify_proof.csv") + + +s += create_init("a_bytes", len(test_case["scalar_a"]), 32) +s += init_arrays("scalar_a") + +s += create_init("A_bytes", len(test_case["point_A"]), 33) +s += init_arrays("point_A") + +s += create_init("B_bytes", len(test_case["point_B"]), 33) +s += init_arrays("point_B") + +s += create_init("C_bytes", len(test_case["point_C"]), 33) +s += init_arrays("point_C") + +s += create_init("auxrand_bytes", len(test_case["auxrand_r"]), 32) +s += init_arrays("auxrand_r") + +s += create_init("msg_bytes", len(test_case["message"]), 32) +s += init_arrays("message") + +s += create_init("proof_bytes", len(test_case["result_proof"]), 64) +s += init_arrays("result_proof") + +s += "\nstatic const unsigned char success[%d] = " % len(test_case["result_success"]) +s += init_array("result_success") + +print(s) From 662d4ccf12cbd52a943205c8757239433a7ffc55 Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:16:36 -0500 Subject: [PATCH 3/6] 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. --- src/modules/dleq/main_impl.h | 86 ++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/modules/dleq/main_impl.h b/src/modules/dleq/main_impl.h index dcb8431431..6f9c7b094f 100644 --- a/src/modules/dleq/main_impl.h +++ b/src/modules/dleq/main_impl.h @@ -245,4 +245,90 @@ static int secp256k1_dleq_verify_internal(secp256k1_scalar *s, secp256k1_scalar return secp256k1_scalar_is_zero(&e_expected); } +int secp256k1_dleq_prove( + const secp256k1_context *ctx, + unsigned char *proof64, + const unsigned char *seckey32, + const secp256k1_pubkey *pubkey_B, + const unsigned char *aux_rand32, + const unsigned char *msg +) { + secp256k1_scalar a, s, e; + secp256k1_ge A, B, C; + int overflow; + int ret; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(proof64 != NULL); + ARG_CHECK(seckey32 != NULL); + ARG_CHECK(pubkey_B != NULL); + + secp256k1_scalar_set_b32(&a, seckey32, &overflow); + if (overflow || secp256k1_scalar_is_zero(&a)) { + return 0; + } + + if (!secp256k1_pubkey_load(ctx, &B, pubkey_B)) { + secp256k1_scalar_clear(&a); + return 0; + } + + secp256k1_dleq_pair(&ctx->ecmult_gen_ctx, &A, &C, &a, &B); + + ret = secp256k1_dleq_prove_internal(ctx, &s, &e, &a, &B, &A, &C, aux_rand32, msg); + if (!ret) { + secp256k1_scalar_clear(&a); + return 0; + } + + secp256k1_scalar_get_b32(&proof64[0], &e); + secp256k1_scalar_get_b32(&proof64[32], &s); + + secp256k1_scalar_clear(&a); + + return 1; +} + +int secp256k1_dleq_verify( + const secp256k1_context *ctx, + const unsigned char *proof64, + const secp256k1_pubkey *pubkey_A, + const secp256k1_pubkey *pubkey_B, + const secp256k1_pubkey *pubkey_C, + const unsigned char *msg +) { + secp256k1_scalar s, e; + secp256k1_ge A, B, C; + int overflow; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(proof64 != NULL); + ARG_CHECK(pubkey_A != NULL); + ARG_CHECK(pubkey_B != NULL); + ARG_CHECK(pubkey_C != NULL); + + secp256k1_scalar_set_b32(&e, &proof64[0], &overflow); + if (overflow) { + return 0; + } + + secp256k1_scalar_set_b32(&s, &proof64[32], &overflow); + if (overflow) { + return 0; + } + + if (!secp256k1_pubkey_load(ctx, &A, pubkey_A)) { + return 0; + } + if (!secp256k1_pubkey_load(ctx, &B, pubkey_B)) { + return 0; + } + if (!secp256k1_pubkey_load(ctx, &C, pubkey_C)) { + return 0; + } + + return secp256k1_dleq_verify_internal(&s, &e, &A, &B, &C, msg); +} + #endif /* SECP256K1_MODULE_DLEQ_MAIN_H */ From a014f0b32ec03cd7acf53102e15b2d00866576d4 Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:17:59 -0500 Subject: [PATCH 4/6] 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) --- src/modules/dleq/tests_impl.h | 44 +++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/modules/dleq/tests_impl.h b/src/modules/dleq/tests_impl.h index 82f1fa004d..fb1c411900 100644 --- a/src/modules/dleq/tests_impl.h +++ b/src/modules/dleq/tests_impl.h @@ -196,9 +196,53 @@ static void run_test_dleq_bip374_vectors(void) { } } +static void run_test_dleq_api(void) { + secp256k1_pubkey B, A, C; + unsigned char seckey[32]; + unsigned char proof[64]; + unsigned char aux_rand[32]; + unsigned char msg[32]; + secp256k1_scalar a; + secp256k1_ge A_ge, B_ge, C_ge; + + /* Generate prove material */ + testrand256(seckey); + testrand256(aux_rand); + testrand256(msg); + testutil_random_ge_test(&B_ge); + secp256k1_pubkey_save(&B, &B_ge); + + /* Check dleq prove input validation */ + CHECK_ILLEGAL(STATIC_CTX, secp256k1_dleq_prove(STATIC_CTX, proof, seckey, &B, aux_rand, msg)); + CHECK_ILLEGAL(CTX, secp256k1_dleq_prove(CTX, NULL, seckey, &B, aux_rand, msg)); + CHECK_ILLEGAL(CTX, secp256k1_dleq_prove(CTX, proof, NULL, &B, aux_rand, msg)); + CHECK_ILLEGAL(CTX, secp256k1_dleq_prove(CTX, proof, seckey, NULL, aux_rand, msg)); + CHECK(secp256k1_dleq_prove(CTX, proof, seckey, &B, NULL, msg) == 1); + CHECK(secp256k1_dleq_prove(CTX, proof, seckey, &B, aux_rand, NULL) == 1); + + /* Generate verify material */ + secp256k1_scalar_set_b32(&a, seckey, NULL); + secp256k1_dleq_pair(&CTX->ecmult_gen_ctx, &A_ge, &C_ge, &a, &B_ge); + secp256k1_pubkey_save(&A, &A_ge); + secp256k1_pubkey_save(&C, &C_ge); + + /* Check dleq verify input validation */ + CHECK_ILLEGAL(CTX, secp256k1_dleq_verify(CTX, NULL, &A, &B, &C, msg)); + 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)); + + /* Verify public API prove and verify functions */ + CHECK(secp256k1_dleq_prove(CTX, proof, seckey, &B, aux_rand, msg) == 1); + CHECK(secp256k1_dleq_verify(CTX, proof, &A, &B, &C, msg) == 1); + CHECK(secp256k1_dleq_prove(CTX, proof, seckey, &B, NULL, NULL) == 1); + CHECK(secp256k1_dleq_verify(CTX, proof, &A, &B, &C, NULL) == 1); +} + static const struct tf_test_entry tests_dleq[] = { CASE(test_dleq_prove_verify), CASE(test_dleq_bip374_vectors), + CASE(test_dleq_api), }; #endif /* SECP256K1_MODULE_DLEQ_TESTS_H */ From 403d24566db7f27113b5be91d350e4bba576e6b9 Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Wed, 14 Jan 2026 15:30:14 -0500 Subject: [PATCH 5/6] ci: enable dleq module --- .github/workflows/ci.yml | 47 +++++++++++++++++++++++++--------------- ci/ci.sh | 3 ++- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 59d2251497..09cb297228 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,7 @@ env: SCHNORRSIG: 'no' MUSIG: 'no' ELLSWIFT: 'no' + DLEQ: 'no' ### test options SECP256K1_TEST_ITERS: 64 BENCH: 'yes' @@ -83,18 +84,18 @@ jobs: matrix: configuration: - env_vars: { WIDEMUL: 'int64', RECOVERY: 'yes' } - - env_vars: { WIDEMUL: 'int64', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } + - env_vars: { WIDEMUL: 'int64', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', DLEQ: 'yes' } - env_vars: { WIDEMUL: 'int128' } - env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' } - - env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } - - env_vars: { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes' } + - env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', DLEQ: 'yes' } + - env_vars: { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', DLEQ: 'yes' } - env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' } - - env_vars: { RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes' } - - env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', CPPFLAGS: '-DVERIFY' } + - env_vars: { RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', DLEQ: 'yes' } + - env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', DLEQ: 'yes', CPPFLAGS: '-DVERIFY' } - env_vars: { BUILD: 'distcheck', WITH_VALGRIND: 'no', CTIMETESTS: 'no', BENCH: 'no' } - env_vars: { CPPFLAGS: '-DDETERMINISTIC' } - env_vars: { CFLAGS: '-O0', CTIMETESTS: 'no' } - - env_vars: { CFLAGS: '-O1', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } + - env_vars: { CFLAGS: '-O1', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', DLEQ: 'yes' } - env_vars: { ECMULTGENKB: 2, ECMULTWINDOW: 2 } - env_vars: { ECMULTGENKB: 86, ECMULTWINDOW: 4 } cc: @@ -146,6 +147,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + DLEQ: 'yes' CC: ${{ matrix.cc }} steps: @@ -174,6 +176,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + DLEQ: 'yes' CTIMETESTS: 'no' steps: @@ -204,6 +207,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + DLEQ: 'yes' CTIMETESTS: 'no' steps: @@ -225,6 +229,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + DLEQ: 'yes' CTIMETESTS: 'no' CC: ${{ matrix.cc }} @@ -265,6 +270,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + DLEQ: 'yes' CTIMETESTS: 'no' steps: @@ -310,6 +316,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + DLEQ: 'yes' CTIMETESTS: 'no' SECP256K1_TEST_ITERS: 2 @@ -339,6 +346,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + DLEQ: 'yes' CTIMETESTS: 'no' CFLAGS: '-fsanitize=undefined,address -g' UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1' @@ -385,6 +393,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + DLEQ: 'yes' CC: ${{ matrix.cc }} SECP256K1_TEST_ITERS: 32 ASM: 'no' @@ -410,6 +419,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + DLEQ: 'yes' CTIMETESTS: 'no' strategy: @@ -442,15 +452,15 @@ 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', DLEQ: 'yes' } - { WIDEMUL: 'int128_struct', ECMULTGENKB: 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', DLEQ: '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', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', DLEQ: 'yes' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', DLEQ: 'yes', CC: 'gcc' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', DLEQ: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', DLEQ: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', DLEQ: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' } - BUILD: 'distcheck' steps: @@ -499,13 +509,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', DLEQ: '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', DLEQ: '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', DLEQ: 'yes' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', DLEQ: 'yes', CC: 'gcc' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', DLEQ: 'yes', CPPFLAGS: '-DVERIFY' } - BUILD: 'distcheck' steps: @@ -618,6 +628,7 @@ jobs: SCHNORRSIG: 'yes' MUSIG: 'yes' ELLSWIFT: 'yes' + DLEQ: 'yes' steps: - *CHECKOUT diff --git a/ci/ci.sh b/ci/ci.sh index 515c14cd04..6d64750598 100755 --- a/ci/ci.sh +++ b/ci/ci.sh @@ -13,7 +13,7 @@ print_environment() { # does not rely on bash. for var in WERROR_CFLAGS MAKEFLAGS BUILD \ ECMULTWINDOW ECMULTGENKB ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \ - EXPERIMENTAL ECDH RECOVERY EXTRAKEYS MUSIG SCHNORRSIG ELLSWIFT \ + EXPERIMENTAL ECDH RECOVERY EXTRAKEYS MUSIG SCHNORRSIG ELLSWIFT DLEQ \ SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS SYMBOL_CHECK \ EXAMPLES \ HOST WRAPPER_CMD \ @@ -64,6 +64,7 @@ fi --enable-module-extrakeys="$EXTRAKEYS" \ --enable-module-schnorrsig="$SCHNORRSIG" \ --enable-module-musig="$MUSIG" \ + --enable-module-dleq="$DLEQ" \ --enable-examples="$EXAMPLES" \ --enable-ctime-tests="$CTIMETESTS" \ --with-valgrind="$WITH_VALGRIND" \ From 45911b2c7ba0a20ad97c32ac26f23a005e760a0b Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:28:18 -0500 Subject: [PATCH 6/6] dleq: Apply review feedback 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 --- src/modules/dleq/Makefile.am.include | 3 +- src/modules/dleq/dleq_vectors.h | 2 +- src/modules/dleq/main_impl.h | 18 ++++++------ src/modules/dleq/tests_impl.h | 10 +++++-- tools/test_vectors_dleq_generate.py | 42 ++++++++++++++++++---------- 5 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/modules/dleq/Makefile.am.include b/src/modules/dleq/Makefile.am.include index 2e72ad45e8..f4740a2a99 100644 --- a/src/modules/dleq/Makefile.am.include +++ b/src/modules/dleq/Makefile.am.include @@ -1,3 +1,4 @@ include_HEADERS += include/secp256k1_dleq.h -noinst_HEADERS += src/modules/dleq/dleq_vectors.h noinst_HEADERS += src/modules/dleq/main_impl.h +noinst_HEADERS += src/modules/dleq/tests_impl.h +noinst_HEADERS += src/modules/dleq/dleq_vectors.h diff --git a/src/modules/dleq/dleq_vectors.h b/src/modules/dleq/dleq_vectors.h index 25e9b2c243..b26c933915 100644 --- a/src/modules/dleq/dleq_vectors.h +++ b/src/modules/dleq/dleq_vectors.h @@ -3,7 +3,7 @@ * * Test vectors according to BIP-374 ("Discrete Log Equality Proofs") are included in this file. * Tests are included in src/modules/dleq/tests_impl.h. */ - + static const unsigned char a_bytes[6][32] = { { 0xC0, 0x8C, 0xA8, 0xE0, 0xBB, 0x59, 0x76, 0x9F, 0xC6, 0xA4, 0xE0, 0x78, 0x45, 0x62, 0x84, 0xE0, 0x0E, 0xA3, 0x4F, 0x65, 0xAD, 0xD9, 0x88, 0xC2, 0x46, 0xE1, 0xBB, 0xA8, 0x58, 0x24, 0xCC, 0xDC }, { 0x8E, 0x64, 0x1B, 0xA6, 0xBF, 0x7F, 0x64, 0xEE, 0xC7, 0x60, 0x05, 0xA2, 0x95, 0x85, 0xA5, 0x03, 0x53, 0x76, 0x37, 0x5F, 0x33, 0xE3, 0x31, 0x21, 0x5A, 0xED, 0xFE, 0x03, 0xB8, 0xE8, 0x0E, 0x7A }, diff --git a/src/modules/dleq/main_impl.h b/src/modules/dleq/main_impl.h index 6f9c7b094f..e2f6f51eb8 100644 --- a/src/modules/dleq/main_impl.h +++ b/src/modules/dleq/main_impl.h @@ -8,6 +8,7 @@ #include "../../../include/secp256k1.h" #include "../../../include/secp256k1_dleq.h" +#include "../../hash.h" /* Initializes SHA256 with fixed midstate. This midstate was computed by applying * SHA256 to SHA256("BIP0374/aux")||SHA256("BIP0374/aux"). */ @@ -60,10 +61,6 @@ static void secp256k1_dleq_sha256_tagged(secp256k1_sha256 *sha) { static int secp256k1_dleq_hash_point(secp256k1_sha256 *sha, secp256k1_ge *p) { unsigned char buf[33]; size_t size = 33; - /* Reject infinity point */ - if (secp256k1_ge_is_infinity(p)) { - return 0; - } secp256k1_eckey_pubkey_serialize33(p, buf); secp256k1_sha256_write(sha, buf, size); return 1; @@ -95,7 +92,8 @@ static void secp256k1_nonce_function_dleq(unsigned char *nonce32, const unsigned } secp256k1_nonce_function_bip374_sha256_tagged(&sha); - /* Hash masked-key||msg||m using the tagged hash as per BIP-374 v0.2.0 */ + /* Hash masked-key||msg||m using the tagged hash as defined in BIP0374 + * Note: msg contains the serialized points A||C (66 bytes) */ secp256k1_sha256_write(&sha, masked_key, 32); secp256k1_sha256_write(&sha, msg, msglen); if (m != NULL) { @@ -106,7 +104,7 @@ static void secp256k1_nonce_function_dleq(unsigned char *nonce32, const unsigned secp256k1_memclear_explicit(masked_key, sizeof(masked_key)); } -/* Generates a nonce as defined in BIP0374 v0.2.0 */ +/* Generates a nonce as defined in BIP0374 */ static int secp256k1_dleq_nonce(secp256k1_scalar *k, const unsigned char *a32, const unsigned char *A_33, const unsigned char *C_33, const unsigned char *aux_rand32, const unsigned char *m) { unsigned char buf[66]; unsigned char nonce[32]; @@ -237,6 +235,10 @@ static int secp256k1_dleq_verify_internal(secp256k1_scalar *s, secp256k1_scalar secp256k1_ecmult(&R2j, &Bj, s, &secp256k1_scalar_zero); secp256k1_gej_add_var(&R2j, &R2j, &tmpj, NULL); + /* Fail verification if R1j or R2j are infinity */ + if (secp256k1_gej_is_infinity(&R1j) || secp256k1_gej_is_infinity(&R2j)) { + return 0; + } secp256k1_ge_set_gej(&R1, &R1j); secp256k1_ge_set_gej(&R2, &R2j); secp256k1_dleq_challenge(&e_expected, B, &R1, &R2, A, C, m); @@ -277,16 +279,14 @@ int secp256k1_dleq_prove( secp256k1_dleq_pair(&ctx->ecmult_gen_ctx, &A, &C, &a, &B); ret = secp256k1_dleq_prove_internal(ctx, &s, &e, &a, &B, &A, &C, aux_rand32, msg); + secp256k1_scalar_clear(&a); if (!ret) { - secp256k1_scalar_clear(&a); return 0; } secp256k1_scalar_get_b32(&proof64[0], &e); secp256k1_scalar_get_b32(&proof64[32], &s); - secp256k1_scalar_clear(&a); - return 1; } diff --git a/src/modules/dleq/tests_impl.h b/src/modules/dleq/tests_impl.h index fb1c411900..ece3f5c548 100644 --- a/src/modules/dleq/tests_impl.h +++ b/src/modules/dleq/tests_impl.h @@ -7,6 +7,7 @@ #define SECP256K1_MODULE_DLEQ_TESTS_H #include "dleq_vectors.h" +#include "../../unit_test.h" static void dleq_nonce_bitflip(unsigned char **args, size_t n_flip, size_t n_bytes) { secp256k1_scalar k1, k2; @@ -176,7 +177,7 @@ static void run_test_dleq_bip374_vectors(void) { 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. + * fail to parse. Only indices 0-2 and 6-12 have valid test data. * */ continue; } @@ -191,7 +192,7 @@ static void run_test_dleq_bip374_vectors(void) { if (is_not_empty(msg_bytes[i])) { m = msg_bytes[i]; } - + CHECK(secp256k1_dleq_verify_internal(&s, &e, &A, &B, &C, m) == success[i]); } } @@ -231,7 +232,10 @@ static void run_test_dleq_api(void) { 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)); - + /* Verify rejects an invalid (all-zero) proof */ + memset(proof, 0, sizeof(proof)); + CHECK(secp256k1_dleq_verify(CTX, proof, &A, &B, &C, msg) == 0); + /* Verify public API prove and verify functions */ CHECK(secp256k1_dleq_prove(CTX, proof, seckey, &B, aux_rand, msg) == 1); CHECK(secp256k1_dleq_verify(CTX, proof, &A, &B, &C, msg) == 1); diff --git a/tools/test_vectors_dleq_generate.py b/tools/test_vectors_dleq_generate.py index d0c53b4814..f6ad8080ef 100644 --- a/tools/test_vectors_dleq_generate.py +++ b/tools/test_vectors_dleq_generate.py @@ -11,14 +11,13 @@ print("Usage: %s " % sys.argv[0]) sys.exit(1) -s = ( - """/** +s = """/** * Automatically generated by %s. * * Test vectors according to BIP-374 ("Discrete Log Equality Proofs") are included in this file. * Tests are included in src/modules/dleq/tests_impl.h. */ - """ % sys.argv[0] -) +""" % sys.argv[0] + def hexstr_to_intarray(str): try: @@ -26,6 +25,7 @@ def hexstr_to_intarray(str): except ValueError: return "0x00" + def create_init(name, rows, cols): return """ static const unsigned char %s[%d][%d] = { @@ -35,9 +35,11 @@ def create_init(name, rows, cols): cols, ) + def init_array(key): return textwrap.indent("{ %s };" % ", ".join(test_case[key]), "") + def init_arrays(key): s = textwrap.indent( ",\n".join(["{ %s }" % hexstr_to_intarray(x) for x in test_case[key]]), 4 * " " @@ -62,7 +64,7 @@ def init_arrays(key): } -with open(sys.argv[1] + "/test_vectors_generate_proof.csv", newline='') as csvfile: +with open(sys.argv[1] + "/test_vectors_generate_proof.csv", newline="") as csvfile: reader = csv.DictReader(csvfile) # Skip the first 5 rows since those test vectors don't use secp's generator point for _ in range(5): @@ -76,9 +78,11 @@ def init_arrays(key): special_cases = { "point_B": "INFINITY", "result_proof": "INVALID", - "message": "" + "message": "", } - test_case[key].append("0" if row[key] in special_cases.get(key, []) else row[key]) + test_case[key].append( + "0" if row[key] in special_cases.get(key, []) else row[key] + ) else: # these keys are not present in current csv file but are present in test_vectors_verify_proof.csv if key in {"point_A", "point_C"}: @@ -86,17 +90,23 @@ def init_arrays(key): test_case[key].append("0") elif key == "result_success": # success/failure value is obtained from row["comment"] for the missing key "result_success" - test_case[key].append("1" if "Success" in row.get("comment", "") else "0") + test_case[key].append( + "1" if "Success" in row.get("comment", "") else "0" + ) else: - sys.exit("Unexpected missing_key encountered when parsing test_vectors_generate_proof.csv") + sys.exit( + "Unexpected missing_key encountered when parsing test_vectors_generate_proof.csv" + ) -with open(sys.argv[1] + "/test_vectors_verify_proof.csv", newline='') as csvfile: +with open(sys.argv[1] + "/test_vectors_verify_proof.csv", newline="") as csvfile: reader = csv.DictReader(csvfile) - for _ in range(5): # Skip the first 5 rows since those test vectors don't use secp's generator point + 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): + for i in range(3): # Fill point_A and point_C for the 3 success cases (array indices 0-2) from verify CSV # These correspond to generate and verify CSV rows 5-7 row = next(reader) @@ -108,7 +118,9 @@ def init_arrays(key): if key in row: # these keys are present in test_vectors_verify_proof.csv # not handling row[key] == "TRUE" since it doesn't appear in the BIP test vectors - test_case[key].append("0" if key == "result_success" and row[key] == "FALSE" else row[key]) + test_case[key].append( + "0" if key == "result_success" and row[key] == "FALSE" else row[key] + ) else: # these keys are not present in current csv file but are present in test_vectors_generate_proof.csv if key == "result_proof": @@ -116,7 +128,9 @@ def init_arrays(key): # same as "proof" key in the test_vectors_verify_proof.csv test vectors test_case["result_proof"].append(row["proof"]) elif key not in {"scalar_a", "auxrand_r"}: # skip expected missing keys - sys.exit("Unexpected missing key encountered when test_vectors_verify_proof.csv") + sys.exit( + "Unexpected missing key encountered when test_vectors_verify_proof.csv" + ) s += create_init("a_bytes", len(test_case["scalar_a"]), 32)