282 lines
12 KiB
C
282 lines
12 KiB
C
|
|
/*************************************************************************
|
||
|
|
* To the extent possible under law, the author(s) have dedicated all *
|
||
|
|
* copyright and related and neighboring rights to the software in this *
|
||
|
|
* file to the public domain worldwide. This software is distributed *
|
||
|
|
* without any warranty. For the CC0 Public Domain Dedication, see *
|
||
|
|
* EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
|
||
|
|
*************************************************************************/
|
||
|
|
|
||
|
|
/** This file demonstrates how to use the FROST module (BIP 445) to create a
|
||
|
|
* 2-of-3 threshold signature. Additionally, see the documentation in
|
||
|
|
* include/secp256k1_frost.h and src/modules/frost/frost.md.
|
||
|
|
*
|
||
|
|
* The example runs all roles (trusted dealer, signers, coordinator) in a
|
||
|
|
* single process. In a real deployment these roles are performed by
|
||
|
|
* different parties communicating over secure/authenticated channels.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
#include <secp256k1.h>
|
||
|
|
#include <secp256k1_extrakeys.h>
|
||
|
|
#include <secp256k1_frost.h>
|
||
|
|
#include <secp256k1_schnorrsig.h>
|
||
|
|
|
||
|
|
#include "examples_util.h"
|
||
|
|
|
||
|
|
/* Total number of participants n */
|
||
|
|
#define N_PARTICIPANTS 3
|
||
|
|
/* Threshold t: the minimum number of signers required to produce a
|
||
|
|
* signature */
|
||
|
|
#define THRESHOLD 2
|
||
|
|
/* The signers of this signing session (u = 2, participants 0 and 2) */
|
||
|
|
#define N_SIGNERS 2
|
||
|
|
static const uint32_t SIGNER_IDS[N_SIGNERS] = { 0, 2 };
|
||
|
|
|
||
|
|
struct signer_secrets {
|
||
|
|
/* The signer's secret share, received from the dealer over a secure
|
||
|
|
* channel */
|
||
|
|
unsigned char secshare[32];
|
||
|
|
/* Secret nonce. It is wiped by secp256k1_frost_sign; never reuse it. */
|
||
|
|
secp256k1_frost_secnonce secnonce;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct signer {
|
||
|
|
uint32_t id;
|
||
|
|
secp256k1_pubkey pubshare;
|
||
|
|
secp256k1_frost_pubnonce pubnonce;
|
||
|
|
secp256k1_frost_partial_sig partial_sig;
|
||
|
|
};
|
||
|
|
|
||
|
|
/* Run the trusted dealer key generation: from a random threshold secret key,
|
||
|
|
* derive the secret share of every participant, the threshold public key and
|
||
|
|
* the public shares.
|
||
|
|
*
|
||
|
|
* WARNING: The trusted dealer knows the threshold secret key and all secret
|
||
|
|
* shares, and must erase them securely after distributing the shares. A
|
||
|
|
* distributed key generation (DKG) protocol avoids a trusted dealer entirely
|
||
|
|
* but is out of scope for BIP 445 and for this example. */
|
||
|
|
static int trusted_dealer_keygen(const secp256k1_context* ctx, unsigned char *threshold_seckey, unsigned char *secshares, secp256k1_pubkey *thresh_pk, secp256k1_pubkey *pubshares) {
|
||
|
|
if (!fill_random(threshold_seckey, 32)) {
|
||
|
|
printf("Failed to generate randomness\n");
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
if (!secp256k1_frost_trusted_dealer_keygen(ctx, secshares, thresh_pk, pubshares, N_PARTICIPANTS, THRESHOLD, threshold_seckey)) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
/* Everyone can check that the public shares and the threshold public key
|
||
|
|
* are consistent. Note that this does NOT validate the security of the
|
||
|
|
* key generation that produced them. */
|
||
|
|
if (!secp256k1_frost_threshold_info_validate(ctx, thresh_pk, pubshares, N_PARTICIPANTS, THRESHOLD)) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Apply an x-only tweak to the threshold public key, e.g. a BIP 341
|
||
|
|
* ("Taproot") tweak committing to a script tree. All participants (and the
|
||
|
|
* coordinator) must apply exactly the same tweaks in the same order to their
|
||
|
|
* own tweak cache. */
|
||
|
|
static int tweak(const secp256k1_context* ctx, secp256k1_frost_tweak_cache *cache, secp256k1_xonly_pubkey *tweaked_pk) {
|
||
|
|
/* For Taproot tweaking the tweak is set to the TapTweak hash as defined
|
||
|
|
* in BIP 341. */
|
||
|
|
unsigned char xonly_tweak[32] = "this could be a Taproot tweak..";
|
||
|
|
|
||
|
|
if (!secp256k1_frost_pubkey_xonly_tweak_add(ctx, tweaked_pk, cache, xonly_tweak)) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
/* For BIP 32-style plain tweaking, use
|
||
|
|
* secp256k1_frost_pubkey_ec_tweak_add instead. */
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Sign a message with the given signers and store the resulting BIP340
|
||
|
|
* signature in sig64. */
|
||
|
|
static int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signers, const secp256k1_frost_tweak_cache *cache, const unsigned char *msg, size_t msglen, unsigned char *sig64) {
|
||
|
|
int i;
|
||
|
|
const secp256k1_frost_pubnonce *pubnonces[N_SIGNERS];
|
||
|
|
const secp256k1_frost_partial_sig *partial_sigs[N_SIGNERS];
|
||
|
|
secp256k1_pubkey signer_pubshares[N_SIGNERS];
|
||
|
|
uint32_t ids[N_SIGNERS];
|
||
|
|
/* The same for all signers and the coordinator */
|
||
|
|
secp256k1_frost_aggnonce aggnonce;
|
||
|
|
secp256k1_frost_session session;
|
||
|
|
/* The x-only encoding of the tweaked threshold public key, bound into the
|
||
|
|
* nonce derivation */
|
||
|
|
secp256k1_xonly_pubkey tweaked_pk;
|
||
|
|
unsigned char tweaked_pk32[32];
|
||
|
|
|
||
|
|
if (!secp256k1_frost_tweaked_pubkey_get(ctx, &tweaked_pk, cache)) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
if (!secp256k1_xonly_pubkey_serialize(ctx, tweaked_pk32, &tweaked_pk)) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Every signer creates a nonce pair and sends the pubnonce to the
|
||
|
|
* coordinator. */
|
||
|
|
for (i = 0; i < N_SIGNERS; i++) {
|
||
|
|
unsigned char session_secrand[32];
|
||
|
|
|
||
|
|
ids[i] = signers[i].id;
|
||
|
|
signer_pubshares[i] = signers[i].pubshare;
|
||
|
|
pubnonces[i] = &signers[i].pubnonce;
|
||
|
|
partial_sigs[i] = &signers[i].partial_sig;
|
||
|
|
|
||
|
|
/* Create random session randomness. It is absolutely necessary that
|
||
|
|
* this is unique for every call of secp256k1_frost_nonce_gen.
|
||
|
|
* Otherwise it's trivial for an attacker to extract the secret share!
|
||
|
|
* nonce_gen wipes session_secrand before returning. */
|
||
|
|
if (!fill_random(session_secrand, sizeof(session_secrand))) {
|
||
|
|
printf("Failed to generate randomness\n");
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
/* Bind the secret share (defense-in-depth against bad randomness),
|
||
|
|
* the public share, the tweaked threshold public key and the message
|
||
|
|
* into the nonce derivation. */
|
||
|
|
if (!secp256k1_frost_nonce_gen(ctx, &signer_secrets[i].secnonce, &signers[i].pubnonce, session_secrand, signer_secrets[i].secshare, &signers[i].pubshare, tweaked_pk32, msg, msglen, NULL, 0)) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
secure_erase(session_secrand, sizeof(session_secrand));
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Communication round 1: the coordinator aggregates the pubnonces and
|
||
|
|
* sends the aggregate nonce to the signers. */
|
||
|
|
if (!secp256k1_frost_nonce_agg(ctx, &aggnonce, NULL, pubnonces, N_SIGNERS)) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Every signer and the coordinator run session_init locally with
|
||
|
|
* identical arguments (aside from the session object), which yields
|
||
|
|
* identical sessions. */
|
||
|
|
if (!secp256k1_frost_session_init(ctx, &session, &aggnonce, ids, signer_pubshares, N_SIGNERS, N_PARTICIPANTS, THRESHOLD, cache, msg, msglen)) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Every signer creates a partial signature and sends it to the
|
||
|
|
* coordinator. frost_sign wipes the secnonce. That's because you must
|
||
|
|
* _never_ reuse the secnonce (or use the same session_secrand to create a
|
||
|
|
* secnonce). If you do, you effectively reuse the nonce and leak the
|
||
|
|
* secret share. */
|
||
|
|
for (i = 0; i < N_SIGNERS; i++) {
|
||
|
|
if (!secp256k1_frost_sign(ctx, &signers[i].partial_sig, &signer_secrets[i].secnonce, signer_secrets[i].secshare, &session, ids, signer_pubshares, N_SIGNERS, signers[i].id)) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Communication round 2: the coordinator verifies the partial signatures
|
||
|
|
* and aggregates them. Verifying the individual partial signatures (as
|
||
|
|
* opposed to only verifying the final signature) allows the coordinator
|
||
|
|
* to identify which signer misbehaved if the protocol run fails. */
|
||
|
|
for (i = 0; i < N_SIGNERS; i++) {
|
||
|
|
if (!secp256k1_frost_partial_sig_verify(ctx, &signers[i].partial_sig, &signers[i].pubnonce, &signers[i].pubshare, &session, ids, N_SIGNERS, i)) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return secp256k1_frost_partial_sig_agg(ctx, sig64, NULL, &session, partial_sigs, N_SIGNERS);
|
||
|
|
}
|
||
|
|
|
||
|
|
int main(void) {
|
||
|
|
secp256k1_context* ctx;
|
||
|
|
int i;
|
||
|
|
/* Secret key material known to the trusted dealer. The dealer must erase
|
||
|
|
* it after the participants received their shares (done at the end of
|
||
|
|
* this function). */
|
||
|
|
unsigned char threshold_seckey[32];
|
||
|
|
unsigned char secshares[N_PARTICIPANTS * 32];
|
||
|
|
secp256k1_pubkey thresh_pk;
|
||
|
|
secp256k1_pubkey pubshares[N_PARTICIPANTS];
|
||
|
|
secp256k1_frost_tweak_cache cache;
|
||
|
|
secp256k1_xonly_pubkey tweaked_pk;
|
||
|
|
struct signer_secrets signer_secrets[N_SIGNERS];
|
||
|
|
struct signer signers[N_SIGNERS];
|
||
|
|
unsigned char msg[32] = "this_could_be_the_hash_of_a_msg";
|
||
|
|
unsigned char sig[64];
|
||
|
|
unsigned char buf[33];
|
||
|
|
size_t outputlen;
|
||
|
|
|
||
|
|
/* Create a secp256k1 context */
|
||
|
|
ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
|
||
|
|
|
||
|
|
printf("Generating threshold key material...");
|
||
|
|
fflush(stdout);
|
||
|
|
if (!trusted_dealer_keygen(ctx, threshold_seckey, secshares, &thresh_pk, pubshares)) {
|
||
|
|
printf("FAILED\n");
|
||
|
|
return EXIT_FAILURE;
|
||
|
|
}
|
||
|
|
printf("ok\n");
|
||
|
|
|
||
|
|
outputlen = sizeof(buf);
|
||
|
|
if (!secp256k1_ec_pubkey_serialize(ctx, buf, &outputlen, &thresh_pk, SECP256K1_EC_COMPRESSED)) {
|
||
|
|
printf("FAILED\n");
|
||
|
|
return EXIT_FAILURE;
|
||
|
|
}
|
||
|
|
printf("Threshold public key: ");
|
||
|
|
print_hex(buf, outputlen);
|
||
|
|
fflush(stdout);
|
||
|
|
|
||
|
|
/* The dealer hands each participant their secret share (over a secure
|
||
|
|
* channel). This example signs with the participants 0 and 2. */
|
||
|
|
for (i = 0; i < N_SIGNERS; i++) {
|
||
|
|
signers[i].id = SIGNER_IDS[i];
|
||
|
|
signers[i].pubshare = pubshares[SIGNER_IDS[i]];
|
||
|
|
memcpy(signer_secrets[i].secshare, &secshares[32 * SIGNER_IDS[i]], 32);
|
||
|
|
}
|
||
|
|
|
||
|
|
printf("Applying x-only tweak (Taproot-style)...");
|
||
|
|
fflush(stdout);
|
||
|
|
/* Every participant and the coordinator initializes a tweak cache from
|
||
|
|
* the threshold public key and applies the same tweaks. */
|
||
|
|
if (!secp256k1_frost_tweak_cache_init(ctx, &cache, &thresh_pk)) {
|
||
|
|
printf("FAILED\n");
|
||
|
|
return EXIT_FAILURE;
|
||
|
|
}
|
||
|
|
if (!tweak(ctx, &cache, &tweaked_pk)) {
|
||
|
|
printf("FAILED\n");
|
||
|
|
return EXIT_FAILURE;
|
||
|
|
}
|
||
|
|
printf("ok\n");
|
||
|
|
if (!secp256k1_xonly_pubkey_serialize(ctx, buf, &tweaked_pk)) {
|
||
|
|
printf("FAILED\n");
|
||
|
|
return EXIT_FAILURE;
|
||
|
|
}
|
||
|
|
printf("Tweaked threshold public key: ");
|
||
|
|
print_hex(buf, 32);
|
||
|
|
fflush(stdout);
|
||
|
|
|
||
|
|
printf("Signing message with %d-of-%d signers...", N_SIGNERS, N_PARTICIPANTS);
|
||
|
|
fflush(stdout);
|
||
|
|
if (!sign(ctx, signer_secrets, signers, &cache, msg, sizeof(msg), sig)) {
|
||
|
|
printf("FAILED\n");
|
||
|
|
return EXIT_FAILURE;
|
||
|
|
}
|
||
|
|
printf("ok\n");
|
||
|
|
|
||
|
|
printf("Verifying signature.....");
|
||
|
|
fflush(stdout);
|
||
|
|
if (!secp256k1_schnorrsig_verify(ctx, sig, msg, sizeof(msg), &tweaked_pk)) {
|
||
|
|
printf("FAILED\n");
|
||
|
|
return EXIT_FAILURE;
|
||
|
|
}
|
||
|
|
printf("ok\n");
|
||
|
|
|
||
|
|
/* It's best practice to try to clear secrets from memory after using them.
|
||
|
|
* This is done because some bugs can allow an attacker to leak memory, for
|
||
|
|
* example through "out of bounds" array access (see Heartbleed), or the OS
|
||
|
|
* swapping them to disk. Hence, we overwrite secret key material with zeros.
|
||
|
|
*
|
||
|
|
* Here we are preventing these writes from being optimized out, as any good compiler
|
||
|
|
* will remove any writes that aren't used.
|
||
|
|
*
|
||
|
|
* The secnonces have already been wiped by secp256k1_frost_sign. */
|
||
|
|
secure_erase(threshold_seckey, sizeof(threshold_seckey));
|
||
|
|
secure_erase(secshares, sizeof(secshares));
|
||
|
|
for (i = 0; i < N_SIGNERS; i++) {
|
||
|
|
secure_erase(&signer_secrets[i], sizeof(signer_secrets[i]));
|
||
|
|
}
|
||
|
|
secp256k1_context_destroy(ctx);
|
||
|
|
return EXIT_SUCCESS;
|
||
|
|
}
|