Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/modules/silentpayments/Makefile.am.include
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
include_HEADERS += include/secp256k1_silentpayments.h
noinst_HEADERS += src/modules/silentpayments/main_impl.h
noinst_HEADERS += src/modules/silentpayments/dleq_impl.h
noinst_HEADERS += src/modules/silentpayments/bench_impl.h
noinst_HEADERS += src/modules/silentpayments/tests_impl.h
noinst_HEADERS += src/modules/silentpayments/vectors.h
235 changes: 235 additions & 0 deletions src/modules/silentpayments/dleq_impl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
#ifndef SECP256K1_DLEQ_IMPL_H
#define SECP256K1_DLEQ_IMPL_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;
if (!secp256k1_eckey_pubkey_serialize(p, buf, &size, 1)) {
return 0;
}
Comment on lines +55 to +57
Copy link
Contributor

Choose a reason for hiding this comment

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

pubkey serialization should always succeed here (as it would only fail if p is point at inifinity), so could VERIFY_CHECK that instead, like e.g.

ret = secp256k1_eckey_pubkey_serialize(pk, buf, &buflen, 1);
#ifdef VERIFY
/* Serialization does not fail since the pk is not the point at infinity
* (according to this function's precondition). */
VERIFY_CHECK(ret && buflen == sizeof(buf));
#else
(void) ret;
#endif

Copy link
Contributor Author

@stratospher stratospher Feb 11, 2025

Choose a reason for hiding this comment

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

hmm true. but since input to this function is secp256k1_ge and not secp256k1_pubkey, unsure if it makes sense to restrict possible values of secp256k1_ge here? (in the musig example, input to the function is secp256k1_pubkey)

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) {
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 using the tagged hash as per the spec */
secp256k1_sha256_write(&sha, masked_key, 32);
secp256k1_sha256_write(&sha, msg, msglen);
secp256k1_sha256_finalize(&sha, nonce32);
Copy link
Contributor

Choose a reason for hiding this comment

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

should memclear the sha and masked_key instances here (as also done in the BIP340 nonce function)

}

/* 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) {
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);

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
*
* 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(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;
size_t pubkey_size = 33;

secp256k1_scalar_get_b32(a32, a);
if (!secp256k1_eckey_pubkey_serialize(B, B_33, &pubkey_size, 1)) {
return 0;
}
if (!secp256k1_eckey_pubkey_serialize(A, A_33, &pubkey_size, 1)) {
return 0;
}
if (!secp256k1_eckey_pubkey_serialize(C, C_33, &pubkey_size, 1)) {
return 0;
}
ret &= secp256k1_dleq_nonce(&k, a32, A_33, C_33, aux_rand32);
Copy link
Contributor

Choose a reason for hiding this comment

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

as per the latest version of the BIP (0.2.0), the nonce should also include the message, see https://github.com/bitcoin/bips/blob/3d0bab3cc211be9d40c0029f62c7e4eebd27ea21/bip-0374.mediawiki?plain=1#L77, I guess the test vectors are outdated and would fail if re-generated with the latest .csv file


/* 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);
Copy link
Contributor

Choose a reason for hiding this comment

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

should also clear out the secret key serialization a32 here

return ret;
}

/* DLEQ Proof Verification
*
* 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(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
109 changes: 109 additions & 0 deletions src/modules/silentpayments/tests_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#define SECP256K1_MODULE_SILENTPAYMENTS_TESTS_H

#include "../../../include/secp256k1_silentpayments.h"
#include "../../../src/modules/silentpayments/dleq_impl.h"
#include "../../../src/modules/silentpayments/vectors.h"

/** Constants
Expand Down Expand Up @@ -760,13 +761,121 @@ void run_silentpayments_test_vectors(void) {
}
}

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]) == 1);
testrand_flip(args[n_flip], n_bytes);
CHECK(secp256k1_dleq_nonce(&k2, args[0], args[1], args[2], args[3]) == 1);
CHECK(secp256k1_scalar_eq(&k1, &k2) == 0);
}

static void dleq_tests(void) {
secp256k1_scalar s, e, a, k;
secp256k1_ge A, B, C;
unsigned char *args[4];
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;
size_t pubkey_size = 33;
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(CTX, &s, &e, &a, &B, &A, &C, aux_rand, (i & 1) ? msg : NULL) == 1);
CHECK(secp256k1_dleq_verify(&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(&tmp, &e, &A, &B, &C, msg) == 0);
CHECK(secp256k1_dleq_verify(&s, &tmp, &A, &B, &C, msg) == 0);
}
{
secp256k1_ge p_tmp;
testutil_random_ge_test(&p_tmp);
CHECK(secp256k1_dleq_verify(&s, &e, &p_tmp, &B, &C, msg) == 0);
CHECK(secp256k1_dleq_verify(&s, &e, &A, &p_tmp, &C, msg) == 0);
CHECK(secp256k1_dleq_verify(&s, &e, &A, &B, &p_tmp, msg) == 0);
}
{
secp256k1_ge p_inf;
secp256k1_ge_set_infinity(&p_inf);
CHECK(secp256k1_dleq_prove(CTX, &s, &e, &a, &p_inf, &A, &C, aux_rand, msg) == 0);
CHECK(secp256k1_dleq_prove(CTX, &s, &e, &a, &B, &p_inf, &C, aux_rand, msg) == 0);
CHECK(secp256k1_dleq_prove(CTX, &s, &e, &a, &B, &A, &p_inf, aux_rand, msg) == 0);
}

/* Nonce tests */
secp256k1_scalar_get_b32(a32, &a);
CHECK(secp256k1_eckey_pubkey_serialize(&A, A_33, &pubkey_size, 1));
CHECK(secp256k1_eckey_pubkey_serialize(&C, C_33, &pubkey_size, 1));
CHECK(secp256k1_dleq_nonce(&k, a32, A_33, C_33, aux_rand) == 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;
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));
}

/* NULL aux_rand argument is allowed.*/
CHECK(secp256k1_dleq_nonce(&k, a32, A_33, C_33, NULL) == 1);
}

void run_silentpayments_tests(void) {
test_recipient_sort();
test_send_api();
test_label_api();
test_recipient_api();
run_silentpayments_test_vectors();
silentpayments_sha256_tag_test();
dleq_tests();
}

#endif