Skip to content

Commit 31d04c9

Browse files
committed
Add Schnorrsig half aggregation
1 parent f3708a1 commit 31d04c9

File tree

3 files changed

+204
-0
lines changed

3 files changed

+204
-0
lines changed

include/secp256k1_schnorrsig.h

+24
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#ifndef SECP256K1_SCHNORRSIG_H
22
#define SECP256K1_SCHNORRSIG_H
33

4+
#include <stdint.h>
45
#include "secp256k1.h"
56
#include "secp256k1_extrakeys.h"
67

@@ -104,6 +105,29 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify(
104105
const secp256k1_xonly_pubkey *pubkey
105106
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
106107

108+
109+
/**
110+
* TODO: doc, more tests, API tests, separate module? multiexp, z_1 = 1 optimization
111+
*/
112+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_halfagg(
113+
const secp256k1_context* ctx,
114+
unsigned char *aggsig,
115+
size_t *aggsig_size,
116+
unsigned char **sig64,
117+
const secp256k1_xonly_pubkey *pk,
118+
unsigned char **msg32,
119+
uint32_t n_sigs
120+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6);
121+
122+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_halfagg_verify(
123+
const secp256k1_context* ctx,
124+
unsigned char *aggsig,
125+
size_t aggsig_size,
126+
const secp256k1_xonly_pubkey *pk,
127+
unsigned char **msg32,
128+
uint32_t n_sigs
129+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
130+
107131
#ifdef __cplusplus
108132
}
109133
#endif

src/modules/schnorrsig/main_impl.h

+151
Original file line numberDiff line numberDiff line change
@@ -236,4 +236,155 @@ int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned cha
236236
secp256k1_fe_equal_var(&rx, &r.x);
237237
}
238238

239+
/* TODO: pk input should be parsed or raw bytes? */
240+
int secp256k1_schnorrsig_halfagg(const secp256k1_context* ctx, unsigned char *aggsig, size_t *aggsig_size, unsigned char **sig64, const secp256k1_xonly_pubkey *pk, unsigned char **msg32, uint32_t n_sigs) {
241+
uint32_t i;
242+
secp256k1_scalar s;
243+
secp256k1_sha256 sha;
244+
unsigned char input_hash[32];
245+
246+
VERIFY_CHECK(ctx != NULL);
247+
ARG_CHECK(aggsig != NULL);
248+
ARG_CHECK(aggsig_size != NULL);
249+
ARG_CHECK(sig64 != NULL);
250+
ARG_CHECK(pk != NULL);
251+
ARG_CHECK(msg32 != NULL);
252+
/* TODO: overflow? */
253+
ARG_CHECK(*aggsig_size >= 32*(1 + n_sigs));
254+
255+
secp256k1_scalar_clear(&s);
256+
257+
/* h = hash_{HalfAggregation}(sig_1[0:32] || pk_1 || m_1 || ... || sig_n[0:32] || pk_n || m_n) */
258+
secp256k1_sha256_initialize_tagged(&sha, (unsigned char*) "HalfAggregation", 15);
259+
for (i = 0; i < n_sigs; i++) {
260+
unsigned char pk_ser[32];
261+
secp256k1_sha256_write(&sha, sig64[i], 32);
262+
secp256k1_xonly_pubkey_serialize(ctx, pk_ser, &pk[i]);
263+
secp256k1_sha256_write(&sha, pk_ser, 32);
264+
secp256k1_sha256_write(&sha, msg32[i], 32);
265+
}
266+
secp256k1_sha256_finalize(&sha, input_hash);
267+
268+
for (i = 0; i < n_sigs; i++) {
269+
/* z_i = int(hash_{HalfAggregationIndex}(h || i)) mod n */
270+
/* s += z_i⋅int(sig[32:64]) */
271+
unsigned char z_bytes[32];
272+
secp256k1_scalar z;
273+
secp256k1_scalar s_i;
274+
275+
secp256k1_sha256_initialize_tagged(&sha, (unsigned char*) "HalfAggregationIndex", 20);
276+
secp256k1_sha256_write(&sha, input_hash, sizeof(input_hash));
277+
/* TODO: fix endianness (or replace by chacha anyway) and add static test vectors */
278+
secp256k1_sha256_write(&sha, (unsigned char *)&n_sigs, sizeof(uint32_t));
279+
secp256k1_sha256_finalize(&sha, z_bytes);
280+
secp256k1_scalar_set_b32(&z, z_bytes, NULL);
281+
secp256k1_scalar_set_b32(&s_i, &sig64[i][32], NULL);
282+
secp256k1_scalar_mul(&s_i, &s_i, &z);
283+
secp256k1_scalar_add(&s, &s, &s_i);
284+
memcpy(&aggsig[32*i], sig64[i], 32);
285+
}
286+
/* Return sig = byte(r_1) || ... || bytes(r_n) || bytes(s) */
287+
secp256k1_scalar_get_b32(&aggsig[32*n_sigs], &s);
288+
*aggsig_size = (n_sigs + 1) * 32;
289+
290+
return 1;
291+
}
292+
293+
int secp256k1_schnorrsig_halfagg_verify(const secp256k1_context* ctx, unsigned char *aggsig, size_t aggsig_size, const secp256k1_xonly_pubkey *pk, unsigned char **msg32, uint32_t n_sigs) {
294+
uint32_t i;
295+
secp256k1_sha256 sha;
296+
unsigned char input_hash[32];
297+
secp256k1_gej rhs;
298+
secp256k1_scalar s;
299+
secp256k1_scalar one;
300+
int overflow;
301+
302+
VERIFY_CHECK(ctx != NULL);
303+
ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx));
304+
ARG_CHECK(aggsig != NULL);
305+
ARG_CHECK(pk != NULL);
306+
ARG_CHECK(msg32 != NULL);
307+
308+
/* TODO: overflow? */
309+
if (aggsig_size != 32*(1+n_sigs)) {
310+
return 0;
311+
}
312+
313+
/* rhs = 0 */
314+
secp256k1_gej_set_infinity(&rhs);
315+
316+
/* h = hash_{HalfAggregation}(sig_1[0:32] || pk_1 || m_1 || ... || sig_n[0:32] || pk_n || m_n) */
317+
secp256k1_sha256_initialize_tagged(&sha, (unsigned char*) "HalfAggregation", 15);
318+
for (i = 0; i < n_sigs; i++) {
319+
unsigned char pk_ser[32];
320+
secp256k1_sha256_write(&sha, &aggsig[i*32], 32);
321+
secp256k1_xonly_pubkey_serialize(ctx, pk_ser, &pk[i]);
322+
secp256k1_sha256_write(&sha, pk_ser, 32);
323+
secp256k1_sha256_write(&sha, msg32[i], 32);
324+
}
325+
secp256k1_sha256_finalize(&sha, input_hash);
326+
327+
for (i = 0; i < n_sigs; i++) {
328+
secp256k1_ge P;
329+
secp256k1_gej Pj;
330+
secp256k1_fe rx;
331+
secp256k1_ge R;
332+
secp256k1_gej Rj;
333+
unsigned char buf[32];
334+
secp256k1_scalar e;
335+
unsigned char z_bytes[32];
336+
secp256k1_scalar z;
337+
338+
/* P_i = lift_x(int(pk)); fail if that fails */
339+
if (!secp256k1_xonly_pubkey_load(ctx, &P, &pk[i])) {
340+
return 0;
341+
}
342+
/* r_i = int(sig[i⋅32:(i+1)⋅32]); fail if r ≥ p */
343+
if (!secp256k1_fe_set_b32(&rx, &aggsig[32*i])) {
344+
return 0;
345+
}
346+
/* R_i = lift_x(r_i); fail if that fails */
347+
if (!secp256k1_ge_set_xo_var(&R, &rx, 0)) {
348+
return 0;
349+
}
350+
/* e_i = int(hash_{BIP0340/challenge}(bytes(r_i) || pk_i || m_i)) mod n */
351+
secp256k1_fe_get_b32(buf, &P.x);
352+
secp256k1_schnorrsig_challenge(&e, &aggsig[32*i], msg32[i], buf);
353+
354+
/* z_i = int(hash_{HalfAggregationIndex}(h || i)) mod n */
355+
secp256k1_sha256_initialize_tagged(&sha, (unsigned char*) "HalfAggregationIndex", 20);
356+
secp256k1_sha256_write(&sha, input_hash, sizeof(input_hash));
357+
/* TODO: fix endianness (or replace by chacha anyway) and add static test vectors */
358+
secp256k1_sha256_write(&sha, (unsigned char *)&n_sigs, sizeof(uint32_t));
359+
secp256k1_sha256_finalize(&sha, z_bytes);
360+
secp256k1_scalar_set_b32(&z, z_bytes, NULL);
361+
362+
/* rhs += z_i⋅(R_1 + e_1⋅P_1) = z_i⋅R_1 + z_i⋅e_1⋅P_1)*/
363+
/* P = e_i⋅R */
364+
secp256k1_gej_set_ge(&Pj, &P);
365+
secp256k1_ecmult(&ctx->ecmult_ctx, &Pj, &Pj, &e, NULL);
366+
/* P = P + R */
367+
secp256k1_gej_set_ge(&Rj, &R);
368+
secp256k1_gej_add_var(&Pj, &Pj, &Rj, NULL);
369+
/* P = z_i⋅P */
370+
secp256k1_ecmult(&ctx->ecmult_ctx, &Pj, &Pj, &z, NULL);
371+
/* rhs += P */
372+
secp256k1_gej_add_var(&rhs, &rhs, &Pj, NULL);
373+
}
374+
/* s = int(sig[n⋅32:(n+1)⋅32]) */
375+
secp256k1_scalar_set_b32(&s, &aggsig[32*n_sigs], &overflow);
376+
if (overflow) {
377+
return 0;
378+
}
379+
380+
secp256k1_scalar_set_int(&one, 1);
381+
/* Fail if s⋅G ≠ rhs */
382+
/* rhs = -rhs */
383+
secp256k1_gej_neg(&rhs, &rhs);
384+
/* rhs = rhs + sG */
385+
secp256k1_ecmult(&ctx->ecmult_ctx, &rhs, &rhs, &one, &s);
386+
387+
return secp256k1_gej_is_infinity(&rhs);
388+
}
389+
239390
#endif

src/modules/schnorrsig/tests_impl.h

+29
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,34 @@ void test_schnorrsig_taproot(void) {
789789
CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, output_pk_bytes, pk_parity, &internal_pk, tweak) == 1);
790790
}
791791

792+
#define N_SIGS 1
793+
void test_schnorrsig_halfagg(void) {
794+
unsigned char sk[N_SIGS][32];
795+
unsigned char msg[N_SIGS][32];
796+
unsigned char *msg_ptr[N_SIGS];
797+
secp256k1_xonly_pubkey pk[N_SIGS];
798+
unsigned char sig[N_SIGS][64];
799+
unsigned char *sig_ptr[N_SIGS];
800+
int i;
801+
802+
unsigned char aggsig[32 * (1 + N_SIGS)];
803+
size_t aggsig_size = sizeof(aggsig);
804+
805+
for (i = 0; i < N_SIGS; i++) {
806+
secp256k1_keypair keypair;
807+
secp256k1_testrand256(sk[i]);
808+
CHECK(secp256k1_keypair_create(ctx, &keypair, sk[i]));
809+
CHECK(secp256k1_keypair_xonly_pub(ctx, &pk[i], NULL, &keypair));
810+
secp256k1_testrand256(msg[i]);
811+
msg_ptr[i] = msg[i];
812+
CHECK(secp256k1_schnorrsig_sign(ctx, sig[i], msg[i], &keypair, NULL, NULL));
813+
sig_ptr[i] = sig[i];
814+
}
815+
CHECK(secp256k1_schnorrsig_halfagg(ctx, aggsig, &aggsig_size, sig_ptr, pk, msg_ptr, N_SIGS) == 1);
816+
CHECK(secp256k1_schnorrsig_halfagg_verify(ctx, aggsig, aggsig_size, pk, msg_ptr, N_SIGS) == 1);
817+
}
818+
#undef N_SIGS
819+
792820
void run_schnorrsig_tests(void) {
793821
int i;
794822
run_nonce_function_bip340_tests();
@@ -801,6 +829,7 @@ void run_schnorrsig_tests(void) {
801829
test_schnorrsig_sign_verify();
802830
}
803831
test_schnorrsig_taproot();
832+
test_schnorrsig_halfagg();
804833
}
805834

806835
#endif

0 commit comments

Comments
 (0)