Skip to content

Commit 55ba2cf

Browse files
committed
Add a Schnorr signing and verifying example
1 parent b073361 commit 55ba2cf

File tree

1 file changed

+120
-0
lines changed

1 file changed

+120
-0
lines changed

examples/schnorr.c

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*************************************************************************
2+
* Copyright (c) 2020-2021 Elichai Turkel *
3+
* Distributed under the CC0 software license, see the accompanying file *
4+
* EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
5+
*************************************************************************/
6+
7+
#include <stdio.h>
8+
#include <assert.h>
9+
#include <string.h>
10+
11+
#include "random.h"
12+
#include "secp256k1.h"
13+
#include "secp256k1_extrakeys.h"
14+
#include "secp256k1_schnorrsig.h"
15+
16+
static void print_hex(unsigned char* data, size_t size) {
17+
size_t i;
18+
printf("0x");
19+
for (i = 0; i < size; i++) {
20+
printf("%02x", data[i]);
21+
}
22+
printf("\n");
23+
}
24+
25+
int main(void) {
26+
unsigned char msg_hash[32] = {0}; /* This should be a hash of the message. */
27+
unsigned char seckey[32];
28+
unsigned char randomize[32];
29+
unsigned char auxiliary_rand[32];
30+
unsigned char serialized_pubkey[32];
31+
unsigned char signature[64];
32+
int is_signature_valid;
33+
secp256k1_xonly_pubkey pubkey;
34+
secp256k1_keypair keypair;
35+
/* The docs in secp256k1_extrakeys.h above the `secp256k1_keypair_create` function say:
36+
* "pointer to a context object, initialized for signing"
37+
* And the docs above the `secp256k1_schnorrsig_verify` function say:
38+
* "a secp256k1 context object, initialized for verification"
39+
* Which is why we create a context for both signing and verification (SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY). */
40+
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
41+
if (!fill_random(randomize, sizeof(randomize))) {
42+
printf("Failed to generate randomness\n");
43+
return 1;
44+
}
45+
/* Randomizing the context is recommended to protect against side-channel leakage
46+
* See `secp256k1_context_randomize` in secp256k1.h for more information about it
47+
* Should never fail */
48+
assert(secp256k1_context_randomize(ctx, randomize));
49+
50+
/*** Key Generation ***/
51+
52+
/* If the secret key is zero or out of range (bigger than secp256k1's order), we try to sample a new key.
53+
* note that the probability of this happening is negligible */
54+
while (1) {
55+
if (!fill_random(seckey, sizeof(seckey))) {
56+
printf("Failed to generate randomness\n");
57+
return 1;
58+
}
59+
/* Try to create a keypair with a valid context, it should only fail if the secret key is zero or out of range. */
60+
if (secp256k1_keypair_create(ctx, &keypair, seckey)) {
61+
break;
62+
}
63+
}
64+
65+
/* Extract the X-only public key from the keypair.
66+
* We pass NULL for `pk_parity` as we don't care about the parity of the key,
67+
* only advanced users might care about the parity.
68+
* This should never fail with a valid context and public key. */
69+
assert(secp256k1_keypair_xonly_pub(ctx, &pubkey, NULL, &keypair));
70+
71+
/* Serialize the public key, should always return 1 for a valid public key. */
72+
assert(secp256k1_xonly_pubkey_serialize(ctx, serialized_pubkey, &pubkey));
73+
74+
/*** Signing ***/
75+
76+
/* Generate 32 bytes of randomness to use with BIP-340 schnorr signing. */
77+
if (!fill_random(auxiliary_rand, sizeof(auxiliary_rand))) {
78+
printf("Failed to generate randomness\n");
79+
return 1;
80+
}
81+
82+
/* Generate a Schnorr signature
83+
* `noncefp` and `ndata` allows you to pass a custom nonce function, passing `NULL` will use the BIP-340 safe default.
84+
* BIP-340 recommends passing 32 bytes of randomness to the nonce function to improve security against side-channel attacks.
85+
* Signing with a valid context, verified keypair and the default nonce function should never fail. */
86+
assert(secp256k1_schnorrsig_sign(ctx, signature, msg_hash, &keypair, auxiliary_rand));
87+
88+
/*** Verification ***/
89+
90+
/* Deserializing the public key, this will return 0 if the public key can't be parsed correctly */
91+
if (!secp256k1_xonly_pubkey_parse(ctx, &pubkey, serialized_pubkey)) {
92+
printf("Failed parsing the public key\n");
93+
return 1;
94+
}
95+
96+
/* Verifying a signature, This will return 1 if it's valid and 0 if it's not. */
97+
is_signature_valid = secp256k1_schnorrsig_verify(ctx, signature, msg_hash, 32, &pubkey);
98+
99+
100+
printf("Is the signature valid? %s\n", is_signature_valid ? "true" : "false");
101+
printf("Secret Key: ");
102+
print_hex(seckey, sizeof(seckey));
103+
printf("Public Key: ");
104+
print_hex(serialized_pubkey, sizeof(serialized_pubkey));
105+
printf("Signature: ");
106+
print_hex(signature, sizeof(signature));
107+
108+
/* This will clear everything from the context and free the memory */
109+
secp256k1_context_destroy(ctx);
110+
111+
/* It's best practice to try and zero out secrets after using them.
112+
* This is done because some bugs can allow an attacker leak memory, for example out of bounds array access(see Heartbleed for example).
113+
* We want to prevent the secrets from living in memory after they are used so they won't be leaked,
114+
* for that we zero out the secret key buffer.
115+
*
116+
* TODO: Prevent these writes from being optimized out, as any good compiler will remove any writes that aren't used. */
117+
memset(seckey, 0, sizeof(seckey));
118+
119+
return 0;
120+
}

0 commit comments

Comments
 (0)