Skip to content

Commit d6dfc45

Browse files
apoelstrajonasnick
authored andcommitted
Add schnorrsig module which implements BIP-schnorr [0] compatible signing, verification and batch verification.
[0] https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki
1 parent 7af328e commit d6dfc45

14 files changed

+1408
-37
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
bench_inv
22
bench_ecdh
33
bench_ecmult
4+
bench_schnorrsig
45
bench_sign
56
bench_verify
6-
bench_schnorr_verify
77
bench_recover
88
bench_internal
99
tests

.travis.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,20 @@ cache:
1111
- src/java/guava/
1212
env:
1313
global:
14-
- FIELD=auto BIGNUM=auto SCALAR=auto ENDOMORPHISM=no STATICPRECOMPUTATION=yes ASM=no BUILD=check EXTRAFLAGS= HOST= ECDH=no RECOVERY=no EXPERIMENTAL=no JNI=no
14+
- FIELD=auto BIGNUM=auto SCALAR=auto ENDOMORPHISM=no STATICPRECOMPUTATION=yes ASM=no BUILD=check EXTRAFLAGS= HOST= ECDH=no RECOVERY=no EXPERIMENTAL=no JNI=no SCHNORRSIG=no
1515
- GUAVA_URL=https://search.maven.org/remotecontent?filepath=com/google/guava/guava/18.0/guava-18.0.jar GUAVA_JAR=src/java/guava/guava-18.0.jar
1616
matrix:
1717
- SCALAR=32bit RECOVERY=yes
18-
- SCALAR=32bit FIELD=32bit ECDH=yes EXPERIMENTAL=yes
18+
- SCALAR=32bit FIELD=32bit ECDH=yes EXPERIMENTAL=yes SCHNORRSIG=yes
1919
- SCALAR=64bit
20-
- FIELD=64bit RECOVERY=yes
20+
- FIELD=64bit RECOVERY=yes EXPERIMENTAL=yes SCHNORRSIG=yes
2121
- FIELD=64bit ENDOMORPHISM=yes
22-
- FIELD=64bit ENDOMORPHISM=yes ECDH=yes EXPERIMENTAL=yes
22+
- FIELD=64bit ENDOMORPHISM=yes ECDH=yes EXPERIMENTAL=yes SCHNORRSIG=yes
2323
- FIELD=64bit ASM=x86_64
2424
- FIELD=64bit ENDOMORPHISM=yes ASM=x86_64
2525
- FIELD=32bit ENDOMORPHISM=yes
2626
- BIGNUM=no
27-
- BIGNUM=no ENDOMORPHISM=yes RECOVERY=yes EXPERIMENTAL=yes
27+
- BIGNUM=no ENDOMORPHISM=yes RECOVERY=yes EXPERIMENTAL=yes SCHNORRSIG=yes
2828
- BIGNUM=no STATICPRECOMPUTATION=no
2929
- BUILD=distcheck
3030
- EXTRAFLAGS=CPPFLAGS=-DDETERMINISTIC
@@ -65,4 +65,4 @@ before_script: ./autogen.sh
6565
script:
6666
- if [ -n "$HOST" ]; then export USE_HOST="--host=$HOST"; fi
6767
- if [ "x$HOST" = "xi686-linux-gnu" ]; then export CC="$CC -m32"; fi
68-
- ./configure --enable-experimental=$EXPERIMENTAL --enable-endomorphism=$ENDOMORPHISM --with-field=$FIELD --with-bignum=$BIGNUM --with-scalar=$SCALAR --enable-ecmult-static-precomputation=$STATICPRECOMPUTATION --enable-module-ecdh=$ECDH --enable-module-recovery=$RECOVERY --enable-jni=$JNI $EXTRAFLAGS $USE_HOST && make -j2 $BUILD
68+
- ./configure --enable-experimental=$EXPERIMENTAL --enable-endomorphism=$ENDOMORPHISM --with-field=$FIELD --with-bignum=$BIGNUM --with-scalar=$SCALAR --enable-ecmult-static-precomputation=$STATICPRECOMPUTATION --enable-module-ecdh=$ECDH --enable-module-recovery=$RECOVERY --enable-module-schnorrsig=$SCHNORRSIG --enable-jni=$JNI $EXTRAFLAGS $USE_HOST && make -j2 $BUILD

Makefile.am

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ if ENABLE_MODULE_ECDH
178178
include src/modules/ecdh/Makefile.am.include
179179
endif
180180

181+
if ENABLE_MODULE_SCHNORRSIG
182+
include src/modules/schnorrsig/Makefile.am.include
183+
endif
184+
181185
if ENABLE_MODULE_RECOVERY
182186
include src/modules/recovery/Makefile.am.include
183187
endif

configure.ac

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ AC_ARG_ENABLE(module_ecdh,
129129
[enable_module_ecdh=$enableval],
130130
[enable_module_ecdh=no])
131131

132+
AC_ARG_ENABLE(module_schnorrsig,
133+
AS_HELP_STRING([--enable-module-schnorrsig],[enable schnorrsig module (experimental)]),
134+
[enable_module_schnorrsig=$enableval],
135+
[enable_module_schnorrsig=no])
136+
132137
AC_ARG_ENABLE(module_recovery,
133138
AS_HELP_STRING([--enable-module-recovery],[enable ECDSA pubkey recovery module [default=no]]),
134139
[enable_module_recovery=$enableval],
@@ -488,6 +493,10 @@ if test x"$enable_module_ecdh" = x"yes"; then
488493
AC_DEFINE(ENABLE_MODULE_ECDH, 1, [Define this symbol to enable the ECDH module])
489494
fi
490495

496+
if test x"$enable_module_schnorrsig" = x"yes"; then
497+
AC_DEFINE(ENABLE_MODULE_SCHNORRSIG, 1, [Define this symbol to enable the schnorrsig module])
498+
fi
499+
491500
if test x"$enable_module_recovery" = x"yes"; then
492501
AC_DEFINE(ENABLE_MODULE_RECOVERY, 1, [Define this symbol to enable the ECDSA pubkey recovery module])
493502
fi
@@ -507,11 +516,15 @@ if test x"$enable_experimental" = x"yes"; then
507516
AC_MSG_NOTICE([WARNING: experimental build])
508517
AC_MSG_NOTICE([Experimental features do not have stable APIs or properties, and may not be safe for production use.])
509518
AC_MSG_NOTICE([Building ECDH module: $enable_module_ecdh])
519+
AC_MSG_NOTICE([Building schnorrsig module: $enable_module_schnorrsig])
510520
AC_MSG_NOTICE([******])
511521
else
512522
if test x"$enable_module_ecdh" = x"yes"; then
513523
AC_MSG_ERROR([ECDH module is experimental. Use --enable-experimental to allow.])
514524
fi
525+
if test x"$enable_module_schnorrsig" = x"yes"; then
526+
AC_MSG_ERROR([schnorrsig module is experimental. Use --enable-experimental to allow.])
527+
fi
515528
if test x"$set_asm" = x"arm"; then
516529
AC_MSG_ERROR([ARM assembly optimization is experimental. Use --enable-experimental to allow.])
517530
fi
@@ -530,6 +543,7 @@ AM_CONDITIONAL([USE_EXHAUSTIVE_TESTS], [test x"$use_exhaustive_tests" != x"no"])
530543
AM_CONDITIONAL([USE_BENCHMARK], [test x"$use_benchmark" = x"yes"])
531544
AM_CONDITIONAL([USE_ECMULT_STATIC_PRECOMPUTATION], [test x"$set_precomp" = x"yes"])
532545
AM_CONDITIONAL([ENABLE_MODULE_ECDH], [test x"$enable_module_ecdh" = x"yes"])
546+
AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG], [test x"$enable_module_schnorrsig" = x"yes"])
533547
AM_CONDITIONAL([ENABLE_MODULE_RECOVERY], [test x"$enable_module_recovery" = x"yes"])
534548
AM_CONDITIONAL([USE_JNI], [test x"$use_jni" = x"yes"])
535549
AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$use_external_asm" = x"yes"])
@@ -552,6 +566,7 @@ echo " with benchmarks = $use_benchmark"
552566
echo " with coverage = $enable_coverage"
553567
echo " module ecdh = $enable_module_ecdh"
554568
echo " module recovery = $enable_module_recovery"
569+
echo " module schnorrsig = $enable_module_schnorrsig"
555570
echo
556571
echo " asm = $set_asm"
557572
echo " bignum = $set_bignum"

include/secp256k1.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,12 @@ SECP256K1_API int secp256k1_ecdsa_signature_normalize(
523523
*/
524524
SECP256K1_API extern const secp256k1_nonce_function secp256k1_nonce_function_rfc6979;
525525

526+
/** An implementation of the nonce generation function as defined in BIP-schnorr.
527+
* If a data pointer is passed, it is assumed to be a pointer to 32 bytes of
528+
* extra entropy.
529+
*/
530+
SECP256K1_API extern const secp256k1_nonce_function secp256k1_nonce_function_bipschnorr;
531+
526532
/** A default safe nonce generation function (currently equal to secp256k1_nonce_function_rfc6979). */
527533
SECP256K1_API extern const secp256k1_nonce_function secp256k1_nonce_function_default;
528534

include/secp256k1_schnorrsig.h

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#ifndef SECP256K1_SCHNORRSIG_H
2+
#define SECP256K1_SCHNORRSIG_H
3+
4+
#include "secp256k1.h"
5+
6+
#ifdef __cplusplus
7+
extern "C" {
8+
#endif
9+
10+
/** This module implements a variant of Schnorr signatures compliant with
11+
* BIP-schnorr
12+
* (https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki).
13+
*/
14+
15+
/** Opaque data structure that holds a parsed Schnorr signature.
16+
*
17+
* The exact representation of data inside is implementation defined and not
18+
* guaranteed to be portable between different platforms or versions. It is
19+
* however guaranteed to be 64 bytes in size, and can be safely copied/moved.
20+
* If you need to convert to a format suitable for storage, transmission, or
21+
* comparison, use the `secp256k1_schnorrsig_serialize` and
22+
* `secp256k1_schnorrsig_parse` functions.
23+
*/
24+
typedef struct {
25+
unsigned char data[64];
26+
} secp256k1_schnorrsig;
27+
28+
/** Serialize a Schnorr signature.
29+
*
30+
* Returns: 1
31+
* Args: ctx: a secp256k1 context object
32+
* Out: out64: pointer to a 64-byte array to store the serialized signature
33+
* In: sig: pointer to the signature
34+
*
35+
* See secp256k1_schnorrsig_parse for details about the encoding.
36+
*/
37+
SECP256K1_API int secp256k1_schnorrsig_serialize(
38+
const secp256k1_context* ctx,
39+
unsigned char *out64,
40+
const secp256k1_schnorrsig* sig
41+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
42+
43+
/** Parse a Schnorr signature.
44+
*
45+
* Returns: 1 when the signature could be parsed, 0 otherwise.
46+
* Args: ctx: a secp256k1 context object
47+
* Out: sig: pointer to a signature object
48+
* In: in64: pointer to the 64-byte signature to be parsed
49+
*
50+
* The signature is serialized in the form R||s, where R is a 32-byte public
51+
* key (x-coordinate only; the y-coordinate is considered to be the unique
52+
* y-coordinate satisfying the curve equation that is a quadratic residue)
53+
* and s is a 32-byte big-endian scalar.
54+
*
55+
* After the call, sig will always be initialized. If parsing failed or the
56+
* encoded numbers are out of range, signature validation with it is
57+
* guaranteed to fail for every message and public key.
58+
*/
59+
SECP256K1_API int secp256k1_schnorrsig_parse(
60+
const secp256k1_context* ctx,
61+
secp256k1_schnorrsig* sig,
62+
const unsigned char *in64
63+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
64+
65+
/** Create a Schnorr signature.
66+
*
67+
* Returns 1 on success, 0 on failure.
68+
* Args: ctx: pointer to a context object, initialized for signing (cannot be NULL)
69+
* Out: sig: pointer to the returned signature (cannot be NULL)
70+
* In: msg32: the 32-byte message being signed (cannot be NULL)
71+
* seckey: pointer to a 32-byte secret key (cannot be NULL)
72+
* noncefp: pointer to a nonce generation function. If NULL, secp256k1_nonce_function_bipschnorr is used
73+
* ndata: pointer to arbitrary data used by the nonce generation function (can be NULL)
74+
*/
75+
SECP256K1_API int secp256k1_schnorrsig_sign(
76+
const secp256k1_context* ctx,
77+
secp256k1_schnorrsig *sig,
78+
const unsigned char *msg32,
79+
const unsigned char *seckey,
80+
secp256k1_nonce_function noncefp,
81+
void *ndata
82+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
83+
84+
/** Verify a Schnorr signature.
85+
*
86+
* Returns: 1: correct signature
87+
* 0: incorrect or unparseable signature
88+
* Args: ctx: a secp256k1 context object, initialized for verification.
89+
* In: sig: the signature being verified (cannot be NULL)
90+
* msg32: the 32-byte message being verified (cannot be NULL)
91+
* pubkey: pointer to a public key to verify with (cannot be NULL)
92+
*/
93+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify(
94+
const secp256k1_context* ctx,
95+
const secp256k1_schnorrsig *sig,
96+
const unsigned char *msg32,
97+
const secp256k1_pubkey *pubkey
98+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
99+
100+
/** Verifies a set of Schnorr signatures.
101+
*
102+
* Returns 1 if all succeeded, 0 otherwise. In particular, returns 1 if n_sigs is 0.
103+
*
104+
* Args: ctx: a secp256k1 context object, initialized for verification.
105+
* scratch: scratch space used for the multiexponentiation
106+
* In: sig: array of signatures, or NULL if there are no signatures
107+
* msg32: array of messages, or NULL if there are no signatures
108+
* pk: array of public keys, or NULL if there are no signatures
109+
* n_sigs: number of signatures in above arrays. Must be smaller than
110+
* 2^31 and smaller than half the maximum size_t value. Must be 0
111+
* if above arrays are NULL.
112+
*/
113+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify_batch(
114+
const secp256k1_context* ctx,
115+
secp256k1_scratch_space *scratch,
116+
const secp256k1_schnorrsig *const *sig,
117+
const unsigned char *const *msg32,
118+
const secp256k1_pubkey *const *pk,
119+
size_t n_sigs
120+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
121+
122+
#ifdef __cplusplus
123+
}
124+
#endif
125+
126+
#endif /* SECP256K1_SCHNORRSIG_H */

src/bench_schnorrsig.c

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/**********************************************************************
2+
* Copyright (c) 2018 Andrew Poelstra *
3+
* Distributed under the MIT software license, see the accompanying *
4+
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
5+
**********************************************************************/
6+
7+
#include <string.h>
8+
#include <stdlib.h>
9+
10+
#include "include/secp256k1.h"
11+
#include "include/secp256k1_schnorrsig.h"
12+
#include "util.h"
13+
#include "bench.h"
14+
15+
#define MAX_SIGS (32768)
16+
17+
typedef struct {
18+
secp256k1_context *ctx;
19+
secp256k1_scratch_space *scratch;
20+
size_t n;
21+
const unsigned char **pk;
22+
const secp256k1_schnorrsig **sigs;
23+
const unsigned char **msgs;
24+
} bench_schnorrsig_data;
25+
26+
void bench_schnorrsig_sign(void* arg) {
27+
bench_schnorrsig_data *data = (bench_schnorrsig_data *)arg;
28+
size_t i;
29+
unsigned char sk[32] = "benchmarkexample secrettemplate";
30+
unsigned char msg[32] = "benchmarkexamplemessagetemplate";
31+
secp256k1_schnorrsig sig;
32+
33+
for (i = 0; i < 1000; i++) {
34+
msg[0] = i;
35+
msg[1] = i >> 8;
36+
sk[0] = i;
37+
sk[1] = i >> 8;
38+
CHECK(secp256k1_schnorrsig_sign(data->ctx, &sig, msg, sk, NULL, NULL));
39+
}
40+
}
41+
42+
void bench_schnorrsig_verify(void* arg) {
43+
bench_schnorrsig_data *data = (bench_schnorrsig_data *)arg;
44+
size_t i;
45+
46+
for (i = 0; i < 1000; i++) {
47+
secp256k1_pubkey pk;
48+
CHECK(secp256k1_ec_pubkey_parse(data->ctx, &pk, data->pk[i], 33) == 1);
49+
CHECK(secp256k1_schnorrsig_verify(data->ctx, data->sigs[i], data->msgs[i], &pk));
50+
}
51+
}
52+
53+
void bench_schnorrsig_verify_n(void* arg) {
54+
bench_schnorrsig_data *data = (bench_schnorrsig_data *)arg;
55+
size_t i, j;
56+
const secp256k1_pubkey **pk = (const secp256k1_pubkey **)malloc(data->n * sizeof(*pk));
57+
58+
CHECK(pk != NULL);
59+
for (j = 0; j < MAX_SIGS/data->n; j++) {
60+
for (i = 0; i < data->n; i++) {
61+
secp256k1_pubkey *pk_nonconst = (secp256k1_pubkey *)malloc(sizeof(*pk_nonconst));
62+
CHECK(secp256k1_ec_pubkey_parse(data->ctx, pk_nonconst, data->pk[i], 33) == 1);
63+
pk[i] = pk_nonconst;
64+
}
65+
CHECK(secp256k1_schnorrsig_verify_batch(data->ctx, data->scratch, data->sigs, data->msgs, pk, data->n));
66+
for (i = 0; i < data->n; i++) {
67+
free((void *)pk[i]);
68+
}
69+
}
70+
free(pk);
71+
}
72+
73+
int main(void) {
74+
size_t i;
75+
bench_schnorrsig_data data;
76+
77+
data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN);
78+
data.scratch = secp256k1_scratch_space_create(data.ctx, 1024 * 1024 * 1024);
79+
data.pk = (const unsigned char **)malloc(MAX_SIGS * sizeof(unsigned char *));
80+
data.msgs = (const unsigned char **)malloc(MAX_SIGS * sizeof(unsigned char *));
81+
data.sigs = (const secp256k1_schnorrsig **)malloc(MAX_SIGS * sizeof(secp256k1_schnorrsig *));
82+
83+
for (i = 0; i < MAX_SIGS; i++) {
84+
unsigned char sk[32];
85+
unsigned char *msg = (unsigned char *)malloc(32);
86+
secp256k1_schnorrsig *sig = (secp256k1_schnorrsig *)malloc(sizeof(*sig));
87+
unsigned char *pk_char = (unsigned char *)malloc(33);
88+
secp256k1_pubkey pk;
89+
size_t pk_len = 33;
90+
msg[0] = sk[0] = i;
91+
msg[1] = sk[1] = i >> 8;
92+
msg[2] = sk[2] = i >> 16;
93+
msg[3] = sk[3] = i >> 24;
94+
memset(&msg[4], 'm', 28);
95+
memset(&sk[4], 's', 28);
96+
97+
data.pk[i] = pk_char;
98+
data.msgs[i] = msg;
99+
data.sigs[i] = sig;
100+
101+
CHECK(secp256k1_ec_pubkey_create(data.ctx, &pk, sk));
102+
CHECK(secp256k1_ec_pubkey_serialize(data.ctx, pk_char, &pk_len, &pk, SECP256K1_EC_COMPRESSED) == 1);
103+
CHECK(secp256k1_schnorrsig_sign(data.ctx, sig, msg, sk, NULL, NULL));
104+
}
105+
106+
run_benchmark("schnorrsig_sign", bench_schnorrsig_sign, NULL, NULL, (void *) &data, 10, 1000);
107+
run_benchmark("schnorrsig_verify", bench_schnorrsig_verify, NULL, NULL, (void *) &data, 10, 1000);
108+
for (i = 1; i <= MAX_SIGS; i *= 2) {
109+
char name[64];
110+
sprintf(name, "schnorrsig_batch_verify_%d", (int) i);
111+
112+
data.n = i;
113+
run_benchmark(name, bench_schnorrsig_verify_n, NULL, NULL, (void *) &data, 3, MAX_SIGS);
114+
}
115+
116+
for (i = 0; i < MAX_SIGS; i++) {
117+
free((void *)data.pk[i]);
118+
free((void *)data.msgs[i]);
119+
free((void *)data.sigs[i]);
120+
}
121+
free(data.pk);
122+
free(data.msgs);
123+
free(data.sigs);
124+
125+
secp256k1_scratch_space_destroy(data.ctx, data.scratch);
126+
secp256k1_context_destroy(data.ctx);
127+
return 0;
128+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
include_HEADERS += include/secp256k1_schnorrsig.h
2+
noinst_HEADERS += src/modules/schnorrsig/main_impl.h
3+
noinst_HEADERS += src/modules/schnorrsig/tests_impl.h
4+
if USE_BENCHMARK
5+
noinst_PROGRAMS += bench_schnorrsig
6+
bench_schnorrsig_SOURCES = src/bench_schnorrsig.c
7+
bench_schnorrsig_LDADD = libsecp256k1.la $(SECP_LIBS) $(COMMON_LIB)
8+
endif

0 commit comments

Comments
 (0)