frost trusted dealer: add example file
This commit adds an example file to demonstrate how to use the module.
This commit is contained in:
		
							parent
							
								
									123ae6a62d
								
							
						
					
					
						commit
						97c472da92
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -66,6 +66,7 @@ libsecp256k1.pc
 | 
			
		||||
contrib/gh-pr-create.sh
 | 
			
		||||
 | 
			
		||||
musig_example
 | 
			
		||||
frost_example
 | 
			
		||||
 | 
			
		||||
### CMake
 | 
			
		||||
/CMakeUserPresets.json
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										11
									
								
								Makefile.am
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								Makefile.am
									
									
									
									
									
								
							@ -195,6 +195,17 @@ musig_example_LDFLAGS += -lbcrypt
 | 
			
		||||
endif
 | 
			
		||||
TESTS += musig_example
 | 
			
		||||
endif
 | 
			
		||||
if ENABLE_MODULE_FROST
 | 
			
		||||
noinst_PROGRAMS += frost_example
 | 
			
		||||
frost_example_SOURCES = examples/frost.c
 | 
			
		||||
frost_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC
 | 
			
		||||
frost_example_LDADD = libsecp256k1.la
 | 
			
		||||
frost_example_LDFLAGS = -static
 | 
			
		||||
if BUILD_WINDOWS
 | 
			
		||||
frost_example_LDFLAGS += -lbcrypt
 | 
			
		||||
endif
 | 
			
		||||
TESTS += frost_example
 | 
			
		||||
endif
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
### Precomputed tables
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										294
									
								
								examples/frost.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										294
									
								
								examples/frost.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,294 @@
 | 
			
		||||
/***********************************************************************
 | 
			
		||||
 * Copyright (c) 2021-2023  Jesse Posner                               *
 | 
			
		||||
 * Distributed under the MIT software license, see the accompanying    *
 | 
			
		||||
 * file COPYING or https://www.opensource.org/licenses/mit-license.php.*
 | 
			
		||||
 ***********************************************************************/
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This file demonstrates how to use the FROST module to create a threshold
 | 
			
		||||
 * signature. Additionally, see the documentation in include/secp256k1_frost.h.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include <secp256k1.h>
 | 
			
		||||
#include <secp256k1_schnorrsig.h>
 | 
			
		||||
#include <secp256k1_frost.h>
 | 
			
		||||
 | 
			
		||||
#include "examples_util.h"
 | 
			
		||||
/* Number of public keys involved in creating the aggregate signature */
 | 
			
		||||
#define N_SIGNERS 5
 | 
			
		||||
 | 
			
		||||
/* Threshold required in creating the aggregate signature */
 | 
			
		||||
#define THRESHOLD 3
 | 
			
		||||
 | 
			
		||||
struct signer_secrets {
 | 
			
		||||
    secp256k1_keypair keypair;
 | 
			
		||||
    secp256k1_frost_share agg_share;
 | 
			
		||||
    secp256k1_frost_secnonce secnonce;
 | 
			
		||||
    unsigned char seed[32];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct signer {
 | 
			
		||||
    secp256k1_pubkey pubshare;
 | 
			
		||||
    secp256k1_frost_pubnonce pubnonce;
 | 
			
		||||
    secp256k1_frost_session session;
 | 
			
		||||
    secp256k1_frost_partial_sig partial_sig;
 | 
			
		||||
    secp256k1_pubkey vss_commitment[THRESHOLD];
 | 
			
		||||
    unsigned char vss_hash[32];
 | 
			
		||||
    unsigned char pok[64];
 | 
			
		||||
    unsigned char id[33];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Create a key pair and store it in seckey and pubkey */
 | 
			
		||||
int create_keypair_and_seed(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer) {
 | 
			
		||||
    unsigned char seckey[32];
 | 
			
		||||
    secp256k1_pubkey pubkey_tmp;
 | 
			
		||||
    size_t size = 33;
 | 
			
		||||
 | 
			
		||||
    while (1) {
 | 
			
		||||
        if (!fill_random(seckey, sizeof(seckey))) {
 | 
			
		||||
            printf("Failed to generate randomness\n");
 | 
			
		||||
            return 1;
 | 
			
		||||
        }
 | 
			
		||||
        if (secp256k1_keypair_create(ctx, &signer_secrets->keypair, seckey)) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (!secp256k1_keypair_pub(ctx, &pubkey_tmp, &signer_secrets->keypair)) {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    if (!secp256k1_ec_pubkey_serialize(ctx, signer->id, &size, &pubkey_tmp, SECP256K1_EC_COMPRESSED)) {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    if (!fill_random(signer_secrets->seed, sizeof(signer_secrets->seed))) {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Create shares and coefficient commitments */
 | 
			
		||||
int create_shares(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer, secp256k1_xonly_pubkey *agg_pk) {
 | 
			
		||||
    int i, j;
 | 
			
		||||
    secp256k1_frost_share shares[N_SIGNERS][N_SIGNERS];
 | 
			
		||||
    const secp256k1_pubkey *vss_commitments[N_SIGNERS];
 | 
			
		||||
    const unsigned char *ids[N_SIGNERS];
 | 
			
		||||
 | 
			
		||||
    for (i = 0; i < N_SIGNERS; i++) {
 | 
			
		||||
        vss_commitments[i] = signer[i].vss_commitment;
 | 
			
		||||
        ids[i] = signer[i].id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (i = 0; i < N_SIGNERS; i++) {
 | 
			
		||||
        /* Generate a polynomial share for the participants */
 | 
			
		||||
        if (!secp256k1_frost_shares_gen(ctx, shares[i], signer[i].vss_commitment, signer[i].pok, signer_secrets[i].seed, THRESHOLD, N_SIGNERS, ids)) {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* KeyGen communication round 1: exchange shares and coefficient
 | 
			
		||||
     * commitments */
 | 
			
		||||
    for (i = 0; i < N_SIGNERS; i++) {
 | 
			
		||||
        const secp256k1_frost_share *assigned_shares[N_SIGNERS];
 | 
			
		||||
 | 
			
		||||
        /* Each participant receives a share from each participant (including
 | 
			
		||||
         * themselves) corresponding to their index. */
 | 
			
		||||
        for (j = 0; j < N_SIGNERS; j++) {
 | 
			
		||||
            assigned_shares[j] = &shares[j][i];
 | 
			
		||||
        }
 | 
			
		||||
        /* Each participant aggregates the shares they received. */
 | 
			
		||||
        if (!secp256k1_frost_share_agg(ctx, &signer_secrets[i].agg_share, agg_pk, assigned_shares, vss_commitments, N_SIGNERS, THRESHOLD, signer[i].id)) {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
        for (j = 0; j < N_SIGNERS; j++) {
 | 
			
		||||
            /* Each participant verifies their shares. share_agg calls this
 | 
			
		||||
             * internally, so it is only neccessary to call this function if
 | 
			
		||||
             * share_agg returns an error, to determine which participant(s)
 | 
			
		||||
             * submitted faulty data. */
 | 
			
		||||
            if (!secp256k1_frost_share_verify(ctx, THRESHOLD, signer[i].id, assigned_shares[j], &vss_commitments[j])) {
 | 
			
		||||
                return 0;
 | 
			
		||||
            }
 | 
			
		||||
            /* Each participant generates public verification shares that are
 | 
			
		||||
             * used for verifying partial signatures. */
 | 
			
		||||
            if (!secp256k1_frost_compute_pubshare(ctx, &signer[j].pubshare, THRESHOLD, signer[j].id, vss_commitments, N_SIGNERS)) {
 | 
			
		||||
                return 0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Tweak the pubkey corresponding to the provided tweak cache, update the cache
 | 
			
		||||
 * and return the tweaked aggregate pk. */
 | 
			
		||||
int tweak(const secp256k1_context* ctx, secp256k1_xonly_pubkey *agg_pk, secp256k1_frost_tweak_cache *cache) {
 | 
			
		||||
    secp256k1_pubkey output_pk;
 | 
			
		||||
    unsigned char ordinary_tweak[32] = "this could be a BIP32 tweak....";
 | 
			
		||||
    unsigned char xonly_tweak[32] = "this could be a taproot tweak..";
 | 
			
		||||
 | 
			
		||||
    if (!secp256k1_frost_pubkey_tweak(ctx, cache, agg_pk)) {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Ordinary tweaking which, for example, allows deriving multiple child
 | 
			
		||||
     * public keys from a single aggregate key using BIP32 */
 | 
			
		||||
    if (!secp256k1_frost_pubkey_ec_tweak_add(ctx, NULL, cache, ordinary_tweak)) {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    /* If one is not interested in signing, the same output_pk can be obtained
 | 
			
		||||
     * by calling `secp256k1_frost_pubkey_get` right after key aggregation to
 | 
			
		||||
     * get the full pubkey and then call `secp256k1_ec_pubkey_tweak_add`. */
 | 
			
		||||
 | 
			
		||||
    /* Xonly tweaking which, for example, allows creating taproot commitments */
 | 
			
		||||
    if (!secp256k1_frost_pubkey_xonly_tweak_add(ctx, &output_pk, cache, xonly_tweak)) {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    /* Note that if we wouldn't care about signing, we can arrive at the same
 | 
			
		||||
     * output_pk by providing the untweaked public key to
 | 
			
		||||
     * `secp256k1_xonly_pubkey_tweak_add` (after converting it to an xonly pubkey
 | 
			
		||||
     * if necessary with `secp256k1_xonly_pubkey_from_pubkey`). */
 | 
			
		||||
 | 
			
		||||
    /* Now we convert the output_pk to an xonly pubkey to allow to later verify
 | 
			
		||||
     * the Schnorr signature against it. For this purpose we can ignore the
 | 
			
		||||
     * `pk_parity` output argument; we would need it if we would have to open
 | 
			
		||||
     * the taproot commitment. */
 | 
			
		||||
    if (!secp256k1_xonly_pubkey_from_pubkey(ctx, agg_pk, NULL, &output_pk)) {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Sign a message hash with the given threshold and aggregate shares and store
 | 
			
		||||
 * the result in sig */
 | 
			
		||||
int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer, const unsigned char* msg32, secp256k1_xonly_pubkey *agg_pk, unsigned char *sig64, const secp256k1_frost_tweak_cache *cache) {
 | 
			
		||||
    int i;
 | 
			
		||||
    int signer_id = 0;
 | 
			
		||||
    int signers[THRESHOLD];
 | 
			
		||||
    int is_signer[N_SIGNERS];
 | 
			
		||||
    const secp256k1_frost_pubnonce *pubnonces[THRESHOLD];
 | 
			
		||||
    const unsigned char *ids[THRESHOLD];
 | 
			
		||||
    const secp256k1_frost_partial_sig *partial_sigs[THRESHOLD];
 | 
			
		||||
 | 
			
		||||
    for (i = 0; i < N_SIGNERS; i++) {
 | 
			
		||||
        unsigned char session_id[32];
 | 
			
		||||
        /* Create random session ID. It is absolutely necessary that the session ID
 | 
			
		||||
         * is unique for every call of secp256k1_frost_nonce_gen. Otherwise
 | 
			
		||||
         * it's trivial for an attacker to extract the secret key! */
 | 
			
		||||
        if (!fill_random(session_id, sizeof(session_id))) {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
        /* Initialize session and create secret nonce for signing and public
 | 
			
		||||
         * nonce to send to the other signers. */
 | 
			
		||||
        if (!secp256k1_frost_nonce_gen(ctx, &signer_secrets[i].secnonce, &signer[i].pubnonce, session_id, &signer_secrets[i].agg_share, msg32, agg_pk, NULL)) {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
        is_signer[i] = 0; /* Initialize is_signer */
 | 
			
		||||
    }
 | 
			
		||||
    /* Select a random subset of signers */
 | 
			
		||||
    for (i = 0; i < THRESHOLD; i++) {
 | 
			
		||||
        unsigned int subset_seed;
 | 
			
		||||
 | 
			
		||||
        while (1) {
 | 
			
		||||
            if (!fill_random((unsigned char*)&subset_seed, sizeof(subset_seed))) {
 | 
			
		||||
                return 0;
 | 
			
		||||
            }
 | 
			
		||||
            signer_id = subset_seed % N_SIGNERS;
 | 
			
		||||
            /* Check if signer has already been assigned */
 | 
			
		||||
            if (!is_signer[signer_id]) {
 | 
			
		||||
                is_signer[signer_id] = 1;
 | 
			
		||||
                signers[i] = signer_id;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        /* Mark signer as assigned */
 | 
			
		||||
        pubnonces[i] = &signer[signer_id].pubnonce;
 | 
			
		||||
        /* pubkeys[i] = &signer[signer_id].pubkey; */
 | 
			
		||||
        ids[i] = signer[signer_id].id;
 | 
			
		||||
    }
 | 
			
		||||
    /* Signing communication round 1: Exchange nonces */
 | 
			
		||||
    for (i = 0; i < THRESHOLD; i++) {
 | 
			
		||||
        signer_id = signers[i];
 | 
			
		||||
        if (!secp256k1_frost_nonce_process(ctx, &signer[signer_id].session, pubnonces, THRESHOLD, msg32, agg_pk, signer[signer_id].id, ids, cache, NULL)) {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
        /* partial_sign will clear the secnonce by setting it to 0. That's because
 | 
			
		||||
         * you must _never_ reuse the secnonce (or use the same session_id to
 | 
			
		||||
         * create a secnonce). If you do, you effectively reuse the nonce and
 | 
			
		||||
         * leak the secret key. */
 | 
			
		||||
        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)) {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
        partial_sigs[i] = &signer[signer_id].partial_sig;
 | 
			
		||||
    }
 | 
			
		||||
    /* Communication round 2: A production system would exchange
 | 
			
		||||
     * partial signatures here before moving on. */
 | 
			
		||||
    for (i = 0; i < THRESHOLD; i++) {
 | 
			
		||||
        signer_id = signers[i];
 | 
			
		||||
        /* To check whether signing was successful, it suffices to either verify
 | 
			
		||||
         * the aggregate signature with the aggregate public key using
 | 
			
		||||
         * secp256k1_schnorrsig_verify, or verify all partial signatures of all
 | 
			
		||||
         * signers individually. Verifying the aggregate signature is cheaper but
 | 
			
		||||
         * verifying the individual partial signatures has the advantage that it
 | 
			
		||||
         * can be used to determine which of the partial signatures are invalid
 | 
			
		||||
         * (if any), i.e., which of the partial signatures cause the aggregate
 | 
			
		||||
         * signature to be invalid and thus the protocol run to fail. It's also
 | 
			
		||||
         * fine to first verify the aggregate sig, and only verify the individual
 | 
			
		||||
         * sigs if it does not work.
 | 
			
		||||
         */
 | 
			
		||||
        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)) {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return secp256k1_frost_partial_sig_agg(ctx, sig64, &signer[signer_id].session, partial_sigs, THRESHOLD);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main(void) {
 | 
			
		||||
    secp256k1_context* ctx;
 | 
			
		||||
    int i;
 | 
			
		||||
    struct signer_secrets signer_secrets[N_SIGNERS];
 | 
			
		||||
    struct signer signers[N_SIGNERS];
 | 
			
		||||
    secp256k1_xonly_pubkey agg_pk;
 | 
			
		||||
    secp256k1_frost_tweak_cache cache;
 | 
			
		||||
    unsigned char msg[32] = "this_could_be_the_hash_of_a_msg!";
 | 
			
		||||
    unsigned char sig[64];
 | 
			
		||||
 | 
			
		||||
    /* Create a context for signing and verification */
 | 
			
		||||
    ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
 | 
			
		||||
    printf("Creating key pairs......");
 | 
			
		||||
    for (i = 0; i < N_SIGNERS; i++) {
 | 
			
		||||
        if (!create_keypair_and_seed(ctx, &signer_secrets[i], &signers[i])) {
 | 
			
		||||
            printf("FAILED\n");
 | 
			
		||||
            return 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    printf("ok\n");
 | 
			
		||||
    printf("Creating shares.........");
 | 
			
		||||
    if (!create_shares(ctx, signer_secrets, signers, &agg_pk)) {
 | 
			
		||||
        printf("FAILED\n");
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
    printf("ok\n");
 | 
			
		||||
    printf("Tweaking................");
 | 
			
		||||
    /* Optionally tweak the aggregate key */
 | 
			
		||||
    if (!tweak(ctx, &agg_pk, &cache)) {
 | 
			
		||||
        printf("FAILED\n");
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
    printf("ok\n");
 | 
			
		||||
    printf("Signing message.........");
 | 
			
		||||
    if (!sign(ctx, signer_secrets, signers, msg, &agg_pk, sig, &cache)) {
 | 
			
		||||
        printf("FAILED\n");
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
    printf("ok\n");
 | 
			
		||||
    printf("Verifying signature.....");
 | 
			
		||||
    if (!secp256k1_schnorrsig_verify(ctx, sig, msg, 32, &agg_pk)) {
 | 
			
		||||
        printf("FAILED\n");
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
    printf("ok\n");
 | 
			
		||||
    secp256k1_context_destroy(ctx);
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user