Skip to content

Commit c2d48a7

Browse files
committed
frost: add example file
This commit adds an example file to demonstrate how to use the module.
1 parent ef15156 commit c2d48a7

File tree

4 files changed

+308
-1
lines changed

4 files changed

+308
-1
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ libsecp256k1.pc
6666
contrib/gh-pr-create.sh
6767

6868
musig_example
69+
frost_example
6970

7071
### CMake
7172
/CMakeUserPresets.json

Makefile.am

+11
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,17 @@ musig_example_LDFLAGS += -lbcrypt
195195
endif
196196
TESTS += musig_example
197197
endif
198+
if ENABLE_MODULE_FROST
199+
noinst_PROGRAMS += frost_example
200+
frost_example_SOURCES = examples/frost.c
201+
frost_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC
202+
frost_example_LDADD = libsecp256k1.la
203+
frost_example_LDFLAGS = -static
204+
if BUILD_WINDOWS
205+
frost_example_LDFLAGS += -lbcrypt
206+
endif
207+
TESTS += frost_example
208+
endif
198209
endif
199210

200211
### Precomputed tables

examples/frost.c

+294
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
/***********************************************************************
2+
* Copyright (c) 2021-2024 Jesse Posner *
3+
* Distributed under the MIT software license, see the accompanying *
4+
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
5+
***********************************************************************/
6+
7+
/**
8+
* This file demonstrates how to use the FROST module to create a threshold
9+
* signature. Additionally, see the documentation in include/secp256k1_frost.h.
10+
*/
11+
12+
#include <stdio.h>
13+
#include <assert.h>
14+
#include <string.h>
15+
16+
#include <secp256k1.h>
17+
#include <secp256k1_schnorrsig.h>
18+
#include <secp256k1_frost.h>
19+
20+
#include "examples_util.h"
21+
/* Number of public keys involved in creating the aggregate signature */
22+
#define N_SIGNERS 5
23+
24+
/* Threshold required in creating the aggregate signature */
25+
#define THRESHOLD 3
26+
27+
struct signer_secrets {
28+
secp256k1_keypair keypair;
29+
secp256k1_frost_share agg_share;
30+
secp256k1_frost_secnonce secnonce;
31+
unsigned char seed[32];
32+
};
33+
34+
struct signer {
35+
secp256k1_pubkey pubshare;
36+
secp256k1_frost_pubnonce pubnonce;
37+
secp256k1_frost_session session;
38+
secp256k1_frost_partial_sig partial_sig;
39+
secp256k1_pubkey vss_commitment[THRESHOLD];
40+
unsigned char vss_hash[32];
41+
unsigned char pok[64];
42+
unsigned char id[33];
43+
};
44+
45+
/* Create a key pair and store it in seckey and pubkey */
46+
int create_keypair_and_seed(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer) {
47+
unsigned char seckey[32];
48+
secp256k1_pubkey pubkey_tmp;
49+
size_t size = 33;
50+
51+
while (1) {
52+
if (!fill_random(seckey, sizeof(seckey))) {
53+
printf("Failed to generate randomness\n");
54+
return 1;
55+
}
56+
if (secp256k1_keypair_create(ctx, &signer_secrets->keypair, seckey)) {
57+
break;
58+
}
59+
}
60+
if (!secp256k1_keypair_pub(ctx, &pubkey_tmp, &signer_secrets->keypair)) {
61+
return 0;
62+
}
63+
if (!secp256k1_ec_pubkey_serialize(ctx, signer->id, &size, &pubkey_tmp, SECP256K1_EC_COMPRESSED)) {
64+
return 0;
65+
}
66+
if (!fill_random(signer_secrets->seed, sizeof(signer_secrets->seed))) {
67+
return 0;
68+
}
69+
return 1;
70+
}
71+
72+
/* Create shares and coefficient commitments */
73+
int create_shares(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer, secp256k1_xonly_pubkey *pk) {
74+
int i, j;
75+
secp256k1_frost_share shares[N_SIGNERS][N_SIGNERS];
76+
const secp256k1_pubkey *vss_commitments[N_SIGNERS];
77+
const unsigned char *ids[N_SIGNERS];
78+
79+
for (i = 0; i < N_SIGNERS; i++) {
80+
vss_commitments[i] = signer[i].vss_commitment;
81+
ids[i] = signer[i].id;
82+
}
83+
84+
for (i = 0; i < N_SIGNERS; i++) {
85+
/* Generate a polynomial share for the participants */
86+
if (!secp256k1_frost_shares_gen(ctx, shares[i], signer[i].vss_commitment, signer[i].pok, signer_secrets[i].seed, THRESHOLD, N_SIGNERS, ids)) {
87+
return 0;
88+
}
89+
}
90+
91+
/* KeyGen communication round 1: exchange shares and coefficient
92+
* commitments */
93+
for (i = 0; i < N_SIGNERS; i++) {
94+
const secp256k1_frost_share *assigned_shares[N_SIGNERS];
95+
96+
/* Each participant receives a share from each participant (including
97+
* themselves) corresponding to their index. */
98+
for (j = 0; j < N_SIGNERS; j++) {
99+
assigned_shares[j] = &shares[j][i];
100+
}
101+
/* Each participant aggregates the shares they received. */
102+
if (!secp256k1_frost_share_agg(ctx, &signer_secrets[i].agg_share, pk, assigned_shares, vss_commitments, N_SIGNERS, THRESHOLD, signer[i].id)) {
103+
return 0;
104+
}
105+
for (j = 0; j < N_SIGNERS; j++) {
106+
/* Each participant verifies their shares. share_agg calls this
107+
* internally, so it is only neccessary to call this function if
108+
* share_agg returns an error, to determine which participant(s)
109+
* submitted faulty data. */
110+
if (!secp256k1_frost_share_verify(ctx, THRESHOLD, signer[i].id, assigned_shares[j], &vss_commitments[j])) {
111+
return 0;
112+
}
113+
/* Each participant generates public verification shares that are
114+
* used for verifying partial signatures. */
115+
if (!secp256k1_frost_compute_pubshare(ctx, &signer[j].pubshare, THRESHOLD, signer[j].id, vss_commitments, N_SIGNERS)) {
116+
return 0;
117+
}
118+
}
119+
}
120+
121+
return 1;
122+
}
123+
124+
/* Tweak the pubkey corresponding to the provided tweak cache, update the cache
125+
* and return the tweaked aggregate pk. */
126+
int tweak(const secp256k1_context* ctx, secp256k1_xonly_pubkey *pk, secp256k1_frost_tweak_cache *cache) {
127+
secp256k1_pubkey output_pk;
128+
unsigned char ordinary_tweak[32] = "this could be a BIP32 tweak....";
129+
unsigned char xonly_tweak[32] = "this could be a taproot tweak..";
130+
131+
if (!secp256k1_frost_pubkey_tweak(ctx, cache, pk)) {
132+
return 0;
133+
}
134+
135+
/* Ordinary tweaking which, for example, allows deriving multiple child
136+
* public keys from a single aggregate key using BIP32 */
137+
if (!secp256k1_frost_pubkey_ec_tweak_add(ctx, NULL, cache, ordinary_tweak)) {
138+
return 0;
139+
}
140+
/* If one is not interested in signing, the same output_pk can be obtained
141+
* by calling `secp256k1_frost_pubkey_get` right after key aggregation to
142+
* get the full pubkey and then call `secp256k1_ec_pubkey_tweak_add`. */
143+
144+
/* Xonly tweaking which, for example, allows creating taproot commitments */
145+
if (!secp256k1_frost_pubkey_xonly_tweak_add(ctx, &output_pk, cache, xonly_tweak)) {
146+
return 0;
147+
}
148+
/* Note that if we wouldn't care about signing, we can arrive at the same
149+
* output_pk by providing the untweaked public key to
150+
* `secp256k1_xonly_pubkey_tweak_add` (after converting it to an xonly pubkey
151+
* if necessary with `secp256k1_xonly_pubkey_from_pubkey`). */
152+
153+
/* Now we convert the output_pk to an xonly pubkey to allow to later verify
154+
* the Schnorr signature against it. For this purpose we can ignore the
155+
* `pk_parity` output argument; we would need it if we would have to open
156+
* the taproot commitment. */
157+
if (!secp256k1_xonly_pubkey_from_pubkey(ctx, pk, NULL, &output_pk)) {
158+
return 0;
159+
}
160+
return 1;
161+
}
162+
163+
/* Sign a message hash with the given threshold and aggregate shares and store
164+
* the result in sig */
165+
int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer, const unsigned char* msg32, secp256k1_xonly_pubkey *pk, unsigned char *sig64, const secp256k1_frost_tweak_cache *cache) {
166+
int i;
167+
int signer_id = 0;
168+
int signers[THRESHOLD];
169+
int is_signer[N_SIGNERS];
170+
const secp256k1_frost_pubnonce *pubnonces[THRESHOLD];
171+
const unsigned char *ids[THRESHOLD];
172+
const secp256k1_frost_partial_sig *partial_sigs[THRESHOLD];
173+
174+
for (i = 0; i < N_SIGNERS; i++) {
175+
unsigned char session_id[32];
176+
/* Create random session ID. It is absolutely necessary that the session ID
177+
* is unique for every call of secp256k1_frost_nonce_gen. Otherwise
178+
* it's trivial for an attacker to extract the secret key! */
179+
if (!fill_random(session_id, sizeof(session_id))) {
180+
return 0;
181+
}
182+
/* Initialize session and create secret nonce for signing and public
183+
* nonce to send to the other signers. */
184+
if (!secp256k1_frost_nonce_gen(ctx, &signer_secrets[i].secnonce, &signer[i].pubnonce, session_id, &signer_secrets[i].agg_share, msg32, pk, NULL)) {
185+
return 0;
186+
}
187+
is_signer[i] = 0; /* Initialize is_signer */
188+
}
189+
/* Select a random subset of signers */
190+
for (i = 0; i < THRESHOLD; i++) {
191+
unsigned int subset_seed;
192+
193+
while (1) {
194+
if (!fill_random((unsigned char*)&subset_seed, sizeof(subset_seed))) {
195+
return 0;
196+
}
197+
signer_id = subset_seed % N_SIGNERS;
198+
/* Check if signer has already been assigned */
199+
if (!is_signer[signer_id]) {
200+
is_signer[signer_id] = 1;
201+
signers[i] = signer_id;
202+
break;
203+
}
204+
}
205+
/* Mark signer as assigned */
206+
pubnonces[i] = &signer[signer_id].pubnonce;
207+
/* pubkeys[i] = &signer[signer_id].pubkey; */
208+
ids[i] = signer[signer_id].id;
209+
}
210+
/* Signing communication round 1: Exchange nonces */
211+
for (i = 0; i < THRESHOLD; i++) {
212+
signer_id = signers[i];
213+
if (!secp256k1_frost_nonce_process(ctx, &signer[signer_id].session, pubnonces, THRESHOLD, msg32, pk, signer[signer_id].id, ids, cache, NULL)) {
214+
return 0;
215+
}
216+
/* partial_sign will clear the secnonce by setting it to 0. That's because
217+
* you must _never_ reuse the secnonce (or use the same session_id to
218+
* create a secnonce). If you do, you effectively reuse the nonce and
219+
* leak the secret key. */
220+
if (!secp256k1_frost_partial_sign(ctx, &signer[signer_id].partial_sig, &signer_secrets[signer_id].secnonce, &signer_secrets[signer_id].agg_share, &signer[signer_id].session, cache)) {
221+
return 0;
222+
}
223+
partial_sigs[i] = &signer[signer_id].partial_sig;
224+
}
225+
/* Communication round 2: A production system would exchange
226+
* partial signatures here before moving on. */
227+
for (i = 0; i < THRESHOLD; i++) {
228+
signer_id = signers[i];
229+
/* To check whether signing was successful, it suffices to either verify
230+
* the aggregate signature with the aggregate public key using
231+
* secp256k1_schnorrsig_verify, or verify all partial signatures of all
232+
* signers individually. Verifying the aggregate signature is cheaper but
233+
* verifying the individual partial signatures has the advantage that it
234+
* can be used to determine which of the partial signatures are invalid
235+
* (if any), i.e., which of the partial signatures cause the aggregate
236+
* signature to be invalid and thus the protocol run to fail. It's also
237+
* fine to first verify the aggregate sig, and only verify the individual
238+
* sigs if it does not work.
239+
*/
240+
if (!secp256k1_frost_partial_sig_verify(ctx, &signer[signer_id].partial_sig, &signer[signer_id].pubnonce, &signer[signer_id].pubshare, &signer[signer_id].session, cache)) {
241+
return 0;
242+
}
243+
}
244+
return secp256k1_frost_partial_sig_agg(ctx, sig64, &signer[signer_id].session, partial_sigs, THRESHOLD);
245+
}
246+
247+
int main(void) {
248+
secp256k1_context* ctx;
249+
int i;
250+
struct signer_secrets signer_secrets[N_SIGNERS];
251+
struct signer signers[N_SIGNERS];
252+
secp256k1_xonly_pubkey pk;
253+
secp256k1_frost_tweak_cache cache;
254+
unsigned char msg[32] = "this_could_be_the_hash_of_a_msg!";
255+
unsigned char sig[64];
256+
257+
/* Create a context for signing and verification */
258+
ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
259+
printf("Creating key pairs......");
260+
for (i = 0; i < N_SIGNERS; i++) {
261+
if (!create_keypair_and_seed(ctx, &signer_secrets[i], &signers[i])) {
262+
printf("FAILED\n");
263+
return 1;
264+
}
265+
}
266+
printf("ok\n");
267+
printf("Creating shares.........");
268+
if (!create_shares(ctx, signer_secrets, signers, &pk)) {
269+
printf("FAILED\n");
270+
return 1;
271+
}
272+
printf("ok\n");
273+
printf("Tweaking................");
274+
/* Optionally tweak the aggregate key */
275+
if (!tweak(ctx, &pk, &cache)) {
276+
printf("FAILED\n");
277+
return 1;
278+
}
279+
printf("ok\n");
280+
printf("Signing message.........");
281+
if (!sign(ctx, signer_secrets, signers, msg, &pk, sig, &cache)) {
282+
printf("FAILED\n");
283+
return 1;
284+
}
285+
printf("ok\n");
286+
printf("Verifying signature.....");
287+
if (!secp256k1_schnorrsig_verify(ctx, sig, msg, 32, &pk)) {
288+
printf("FAILED\n");
289+
return 1;
290+
}
291+
printf("ok\n");
292+
secp256k1_context_destroy(ctx);
293+
return 0;
294+
}

include/secp256k1_frost.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ extern "C" {
1515
* This module implements a variant of Flexible Round-Optimized Schnorr
1616
* Threshold Signatures (FROST) by Chelsea Komlo and Ian Goldberg
1717
* (https://crysp.uwaterloo.ca/software/frost/). Signatures are compatible with
18-
* BIP-340 ("Schnorr").
18+
* BIP-340 ("Schnorr"). There's an example C source file in the module's
19+
* directory (examples/frost.c) that demonstrates how it can be used.
1920
*
2021
* The module also supports BIP-341 ("Taproot") and BIP-32 ("ordinary") public
2122
* key tweaking, and adaptor signatures.

0 commit comments

Comments
 (0)