diff --git a/Makefile.am b/Makefile.am index 2388a19469..689499e172 100644 --- a/Makefile.am +++ b/Makefile.am @@ -288,3 +288,10 @@ if ENABLE_MODULE_FROST include src/modules/frost/Makefile.am.include endif # FROST_SPECIFIC - END + +# FROST_SPECIFIC - START +# TODO: use the same approach used for wycheproof +TESTVECTORS = src/modules/frost/frost_ietf_test_vectors.h +src/modules/frost/frost_ietf_test_vectors.h: + python3 tools/tests_frost_ietf_generate.py src/modules/frost/frost_ietf_test_vectors.json > $@ +# FROST_SPECIFIC - END diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index c50166fbd2..f0a5aef06f 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -179,29 +179,29 @@ SECP256K1_API void secp256k1_frost_keypair_destroy(secp256k1_frost_keypair *keyp * secp256k1_frost_keygen_dkg_begin() is performed by each participant to initialize a Pedersen * * This function assumes there is an additional layer which performs the - * distribution of shares to their intended participants. + * distribution of secret_key_shares to their intended participants. * - * Note that while secp256k1_frost_keygen_dkg_begin() returns Shares, these shares + * Note that while secp256k1_frost_keygen_dkg_begin() returns Shares, these secret_key_shares * should be sent *after* participants have exchanged commitments via * secp256k1_frost_keygen_dkg_commitment_validate(). So, the caller of - * secp256k1_frost_keygen_dkg_begin() should store shares until after + * secp256k1_frost_keygen_dkg_begin() should store secret_key_shares until after * secp256k1_frost_keygen_dkg_commitment_validate() is complete, and then - * exchange shares via secp256k1_frost_keygen_dkg_finalize(). + * exchange secret_key_shares via secp256k1_frost_keygen_dkg_finalize(). * * Returns 1 on success, 0 on failure. * Args: ctx: pointer to a context object, initialized for signing. - * Out: dkg_commitment: pointer to a secp256k1_frost_vss_commitments to store the DKG first phase result. - * shares: pointer to an array of num_shares shares - * In: num_participants: number of participants and shares that will be produced. - * threshold: validity threshold for signatures. - * generator_index: index of the participant running the DKG. - * context: pointer to a char array containing DKG context tag. - * context_length: length of the char array with the DKG context. + * Out: vss_commitments: pointer to a secp256k1_frost_vss_commitments to store the DKG first phase result. + * secret_key_shares: pointer to an array of num_shares secret_key_shares + * In: num_participants: number of participants and secret_key_shares that will be produced. + * threshold: validity threshold for signatures. + * generator_index: index of the participant running the DKG. + * context: pointer to a char array containing DKG context tag. + * context_length: length of the char array with the DKG context. */ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_keygen_dkg_begin( const secp256k1_context *ctx, - secp256k1_frost_vss_commitments *dkg_commitment, - secp256k1_frost_keygen_secret_share *shares, + secp256k1_frost_vss_commitments *vss_commitments, + secp256k1_frost_keygen_secret_share *secret_key_shares, uint32_t num_participants, uint32_t threshold, uint32_t generator_index, @@ -257,20 +257,20 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_keygen_dkg_finali /* * secp256k1_frost_keygen_with_dealer() allows to create keygen for each participant. * This function is intended to be executed by a trusted dealer that generates and - * distributes the secret shares. + * distributes the secret secret_key_shares. * * Returns 1 on success, 0 on failure. * Args: ctx: pointer to a context object, initialized for signing. - * Out: share_commitment: pointer to a secp256k1_frost_vss_commitments to store the dealer commitments. - * shares: pointer to an array of num_shares shares - * keypair: pointer to a frost_keypair to store the generated keypairs. - * In: num_participants: number of participants and shares that will be produced. - * threshold: validity threshold for signatures. + * Out: vss_commitments: pointer to a secp256k1_frost_vss_commitments to store the dealer commitments. + * secret_key_shares: pointer to an array of num_shares secret_key_shares + * keypair: pointer to a frost_keypair to store the generated keypairs. + * In: num_participants: number of participants and secret_key_shares that will be produced. + * threshold: validity threshold for signatures. */ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_keygen_with_dealer( const secp256k1_context *ctx, - secp256k1_frost_vss_commitments *share_commitment, - secp256k1_frost_keygen_secret_share *shares, + secp256k1_frost_vss_commitments *vss_commitments, + secp256k1_frost_keygen_secret_share *secret_key_shares, secp256k1_frost_keypair *keypairs, uint32_t num_participants, uint32_t threshold diff --git a/src/modules/frost/frost_ietf_test_vectors.h b/src/modules/frost/frost_ietf_test_vectors.h new file mode 100644 index 0000000000..05186d7cbf --- /dev/null +++ b/src/modules/frost/frost_ietf_test_vectors.h @@ -0,0 +1,72 @@ +/* Note: this file was autogenerated using tests_frost_ietf_generate.py. Do not edit. */ + +#define IETF_FROST_MAX_PARTICIPANTS 3 +#define IETF_FROST_MIN_PARTICIPANTS 2 +#define IETF_FROST_NUM_PARTICIPANTS 2 + +/* Section: group_input_parameters */ +static const uint32_t ietf_frost_participants[] = {1, 3}; +static const unsigned char ietf_frost_group_secret_key[] = { 0x0d,0x00,0x41,0x50,0xd2,0x7c,0x3b,0xf2,0xa4,0x2f,0x31,0x26,0x83,0xd3,0x5f,0xac,0x73,0x94,0xb1,0xe9,0xe3,0x18,0x24,0x9c,0x1b,0xfe,0x7f,0x07,0x95,0xa8,0x31,0x14}; +static const unsigned char ietf_frost_group_public_key[] = { 0x02,0xf3,0x7c,0x34,0xb6,0x6c,0xed,0x1f,0xb5,0x1c,0x34,0xa9,0x0b,0xda,0xe0,0x06,0x90,0x1f,0x10,0x62,0x5c,0xc0,0x6c,0x4f,0x64,0x66,0x3b,0x0e,0xae,0x87,0xd8,0x7b,0x4f}; +static const unsigned char ietf_frost_message[] = { 0x74,0x65,0x73,0x74}; +static const size_t ietf_frost_message_length = 8; +static const unsigned char ietf_frost_share_polynomial_coefficients_0[] = { 0xfb,0xf8,0x5e,0xad,0xae,0x30,0x58,0xea,0x14,0xf1,0x91,0x48,0xbb,0x72,0xb4,0x5e,0x43,0x99,0xc0,0xb1,0x60,0x28,0xac,0xaf,0x03,0x95,0xc9,0xb0,0x3c,0x82,0x35,0x79}; + +/* Section: signer_input_parameters */ +#define IETF_FROST_PARTICIPANT_SHARE_SIZE 32 +static const unsigned char ietf_frost_participant_shares[] = { +0x08,0xf8,0x9f,0xfe,0x80,0xac,0x94,0xdc,0xb9,0x20,0xc2,0x6f,0x3f,0x46,0x14,0x0b,0xfc,0x7f,0x95,0xb4,0x93,0xf8,0x31,0x0f,0x5f,0xc1,0xea,0x2b,0x01,0xf4,0x25,0x4c, +0x04,0xf0,0xfe,0xac,0x2e,0xdc,0xed,0xc6,0xce,0x12,0x53,0xb7,0xfa,0xb8,0xc8,0x6b,0x85,0x6a,0x79,0x7f,0x44,0xd8,0x3d,0x82,0xa3,0x85,0x55,0x4e,0x6e,0x40,0x19,0x84, +0x00,0xe9,0x5d,0x59,0xdd,0x0d,0x46,0xb0,0xe3,0x03,0xe5,0x00,0xb6,0x2b,0x7c,0xcb,0x0e,0x55,0x5d,0x49,0xf5,0xb8,0x49,0xf5,0xe7,0x48,0xc0,0x71,0xda,0x8c,0x0d,0xbc, +}; + +/* Section: round_one.signer_outputs */ +#define IETF_FROST_HIDING_NONCE_RANDOMNESS_SIZE 32 +#define IETF_FROST_HIDING_NONCE_SIZE 32 +#define IETF_FROST_BINDING_NONCE_RANDOMNESS_SIZE 32 +#define IETF_FROST_BINDING_NONCE_SIZE 32 +#define IETF_FROST_HIDING_NONCE_COMMITMENT_SIZE 33 +#define IETF_FROST_BINDING_NONCE_COMMITMENT_SIZE 33 +#define IETF_FROST_BINDING_FACTOR_INPUT_SIZE 129 +#define IETF_FROST_BINDING_FACTOR_SIZE 32 +static const unsigned char ietf_frost_hiding_nonce_randomnesses[] = {0x7e,0xa5,0xed,0x09,0xaf,0x19,0xf6,0xff,0x21,0x04,0x0c,0x07,0xec,0x2d,0x2a,0xdb,0xd3,0x5b,0x75,0x9d,0xa5,0xa4,0x01,0xd4,0xc9,0x9d,0xd2,0x6b,0x82,0x39,0x1c,0xb2, +0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0xe6,0xcc,0x56,0xcc,0xbd,0x05,0x02,0xb3,0xf6,0xf8,0x31,0xd9,0x1e,0x2e,0xbd,0x01,0xc4,0xde,0x04,0x79,0xe0,0x19,0x1b,0x66,0x89,0x5a,0x4f,0xfd,0x9b,0x68,0xd5,0x44, +}; +static const unsigned char ietf_frost_binding_nonce_randomnesses[] = {0x47,0xac,0xab,0x01,0x8f,0x11,0x60,0x20,0xc1,0x0c,0xb9,0xb9,0xab,0xdc,0x7a,0xc1,0x0a,0xae,0x1b,0x48,0xca,0x6e,0x36,0xdc,0x15,0xac,0xb6,0xec,0x9b,0xe5,0xcd,0xc5, +0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x72,0x03,0xd5,0x5e,0xb8,0x2a,0x5c,0xa0,0xd7,0xd8,0x36,0x74,0x54,0x1a,0xb5,0x5f,0x6e,0x76,0xf1,0xb8,0x53,0x91,0xd2,0xc1,0x37,0x06,0xa8,0x9a,0x06,0x4f,0xd5,0xb9, +}; +static const unsigned char ietf_frost_hiding_nonces[] = {0x84,0x1d,0x3a,0x64,0x50,0xd7,0x58,0x0b,0x4d,0xa8,0x3c,0x8e,0x61,0x84,0x14,0xd0,0xf0,0x24,0x39,0x1f,0x2a,0xeb,0x51,0x1d,0x75,0x79,0x22,0x44,0x20,0xaa,0x81,0xf0, +0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x2b,0x19,0xb1,0x3f,0x19,0x3f,0x4c,0xe8,0x3a,0x39,0x93,0x62,0xa9,0x0c,0xdc,0x1e,0x0d,0xdc,0xd8,0x3e,0x57,0x08,0x9a,0x7a,0xf0,0xbd,0xca,0x71,0xd4,0x78,0x69,0xb2, +}; +static const unsigned char ietf_frost_binding_nonces[] = {0x8d,0x26,0x24,0xf5,0x32,0xaf,0x63,0x13,0x77,0xf3,0x3c,0xf4,0x4b,0x5a,0xc5,0xf8,0x49,0x06,0x7c,0xae,0x2e,0xac,0xb8,0x86,0x80,0xa3,0x1e,0x77,0xc7,0x9b,0x5a,0x80, +0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x7a,0x44,0x3b,0xde,0x83,0xdc,0x63,0xef,0x52,0xdd,0xa3,0x54,0x00,0x52,0x25,0xba,0x0e,0x55,0x32,0x43,0x40,0x2a,0x47,0x05,0xce,0x28,0xff,0xaa,0xfe,0x0f,0x5b,0x98, +}; +static const unsigned char ietf_frost_hiding_nonce_commitments[] = {0x03,0xc6,0x99,0xaf,0x97,0xd2,0x6b,0xb4,0xd3,0xf0,0x52,0x32,0xec,0x5e,0x19,0x38,0xc1,0x2f,0x1e,0x6a,0xe9,0x76,0x43,0xc8,0xf8,0xf1,0x1c,0x98,0x20,0x30,0x3f,0x19,0x04, +0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x03,0x07,0x75,0x07,0xba,0x32,0x7f,0xc0,0x74,0xd2,0x79,0x39,0x55,0xef,0x34,0x10,0xee,0x3f,0x03,0xb8,0x2b,0x4c,0xdc,0x23,0x70,0xf7,0x1d,0x86,0x5b,0xeb,0x92,0x6e,0xf6, +}; +static const unsigned char ietf_frost_binding_nonce_commitments[] = {0x02,0xfa,0x2a,0xac,0xcd,0x51,0xb9,0x48,0xc9,0xdc,0x1a,0x32,0x5d,0x77,0x22,0x6e,0x98,0xa5,0xa3,0xfe,0x65,0xfe,0x9b,0xa2,0x13,0x76,0x1a,0x60,0x12,0x30,0x40,0xa4,0x5e, +0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x02,0xad,0x53,0x03,0x1d,0xdf,0xbb,0xac,0xfc,0x5f,0xbd,0xa3,0xd3,0xb0,0xc2,0x44,0x5c,0x8e,0x3e,0x99,0xcb,0xc4,0xca,0x2d,0xb2,0xaa,0x28,0x3f,0xa6,0x85,0x25,0xb1,0x35, +}; +static const unsigned char ietf_frost_binding_factor_inputs[] = {0x02,0xf3,0x7c,0x34,0xb6,0x6c,0xed,0x1f,0xb5,0x1c,0x34,0xa9,0x0b,0xda,0xe0,0x06,0x90,0x1f,0x10,0x62,0x5c,0xc0,0x6c,0x4f,0x64,0x66,0x3b,0x0e,0xae,0x87,0xd8,0x7b,0x4f,0xff,0x9b,0x52,0x10,0xff,0xbb,0x3c,0x07,0xa7,0x3a,0x7c,0x89,0x35,0xbe,0x4a,0x8c,0x62,0xcf,0x01,0x5f,0x6c,0xf7,0xad,0xe6,0xef,0xac,0x09,0xa6,0x51,0x35,0x40,0xfc,0x3f,0x5a,0x81,0x6a,0xae,0xbc,0x21,0x14,0xa8,0x11,0xa4,0x15,0xd7,0xa5,0x5d,0xb7,0xc5,0xcb,0xc1,0xcf,0x27,0x18,0x3e,0x79,0xdd,0x9d,0xef,0x94,0x1b,0x5d,0x48,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01, +0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x02,0xf3,0x7c,0x34,0xb6,0x6c,0xed,0x1f,0xb5,0x1c,0x34,0xa9,0x0b,0xda,0xe0,0x06,0x90,0x1f,0x10,0x62,0x5c,0xc0,0x6c,0x4f,0x64,0x66,0x3b,0x0e,0xae,0x87,0xd8,0x7b,0x4f,0xff,0x9b,0x52,0x10,0xff,0xbb,0x3c,0x07,0xa7,0x3a,0x7c,0x89,0x35,0xbe,0x4a,0x8c,0x62,0xcf,0x01,0x5f,0x6c,0xf7,0xad,0xe6,0xef,0xac,0x09,0xa6,0x51,0x35,0x40,0xfc,0x3f,0x5a,0x81,0x6a,0xae,0xbc,0x21,0x14,0xa8,0x11,0xa4,0x15,0xd7,0xa5,0x5d,0xb7,0xc5,0xcb,0xc1,0xcf,0x27,0x18,0x3e,0x79,0xdd,0x9d,0xef,0x94,0x1b,0x5d,0x48,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03, +}; +static const unsigned char ietf_frost_binding_factors[] = {0x3e,0x08,0xfe,0x56,0x1e,0x07,0x5c,0x65,0x3c,0xbf,0xd4,0x69,0x08,0xa1,0x0e,0x76,0x37,0xc7,0x0c,0x74,0xf0,0xa7,0x7d,0x5f,0xd4,0x5d,0x1a,0x75,0x0c,0x73,0x9e,0xc6, +0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x93,0xf7,0x90,0x41,0xbb,0x3f,0xd2,0x66,0x10,0x5b,0xe2,0x51,0xad,0xae,0xb5,0xfd,0x7f,0x8b,0x10,0x4f,0xb5,0x54,0xa4,0xba,0x9a,0x0b,0xec,0xea,0x48,0xdd,0xbf,0xd7, +}; + +/* Section: round_two.signer_outputs */ +#define IETF_FROST_SIG_SHARE_SIZE 32 +#define IETF_FROST_SIG_SIZE 65 +static const unsigned char ietf_frost_sig_shares[] = {0xc4,0xfc,0xe1,0x77,0x5a,0x1e,0x14,0x1f,0xb5,0x79,0x94,0x41,0x66,0xea,0xb0,0xd6,0x5e,0xef,0xe7,0xb9,0x8d,0x48,0x0a,0x56,0x9b,0xbb,0xfc,0xb1,0x4f,0x91,0xc1,0x97, +0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x01,0x60,0xfd,0x0d,0x38,0x89,0x32,0xf4,0x82,0x6d,0x2e,0xbc,0xd6,0xb9,0xea,0xba,0x73,0x4f,0x7c,0x71,0xcf,0x25,0xb4,0x27,0x9a,0x4c,0xa2,0x58,0x1e,0x47,0xb1,0x8d, +}; +static const unsigned char ietf_frost_sig[] = {0x02,0x05,0xb6,0xd0,0x4d,0x37,0x74,0xc8,0x92,0x94,0x13,0xe3,0xc7,0x60,0x24,0xd5,0x41,0x49,0xc3,0x72,0xd5,0x7a,0xae,0x62,0x57,0x4e,0xd7,0x43,0x19,0xb5,0xea,0x14,0xd0,0xc6,0x5d,0xde,0x84,0x92,0xa7,0x47,0x14,0x37,0xe6,0xc2,0xfe,0x3d,0xa4,0x9b,0x90,0xd2,0x3f,0x64,0x2b,0x5c,0x6d,0xbe,0x7e,0x36,0x08,0x9f,0x09,0x6d,0xd9,0x73,0x24}; diff --git a/src/modules/frost/frost_ietf_test_vectors.json b/src/modules/frost/frost_ietf_test_vectors.json new file mode 100644 index 0000000000..e7e23c853f --- /dev/null +++ b/src/modules/frost/frost_ietf_test_vectors.json @@ -0,0 +1,61 @@ +{ + "info" : "FROST(secp256k1, SHA-256)", + "version" : "draft-irtf-cfrg-frost-15", + "url": "https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-frost-15#name-frostsecp256k1-sha-256-2", + "configuration_information": { + "MAX_PARTICIPANTS": 3, + "MIN_PARTICIPANTS": 2, + "NUM_PARTICIPANTS": 2 + }, + "group_input_parameters": { + "participant_list": [1, 3], + "group_secret_key": "0d004150d27c3bf2a42f312683d35fac7394b1e9e318249c1bfe7f0795a83114", + "group_public_key": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4f", + "message": "74657374", + "share_polynomial_coefficients": [ + "fbf85eadae3058ea14f19148bb72b45e4399c0b16028acaf0395c9b03c823579" + ] + }, + "signer_input_parameters": { + "participant_share": [ + "08f89ffe80ac94dcb920c26f3f46140bfc7f95b493f8310f5fc1ea2b01f4254c", + "04f0feac2edcedc6ce1253b7fab8c86b856a797f44d83d82a385554e6e401984", + "00e95d59dd0d46b0e303e500b62b7ccb0e555d49f5b849f5e748c071da8c0dbc" + ] + }, + "round_one": { + "signer_outputs": { + "participant_1": { + "hiding_nonce_randomness": "7ea5ed09af19f6ff21040c07ec2d2adbd35b759da5a401d4c99dd26b82391cb2", + "binding_nonce_randomness": "47acab018f116020c10cb9b9abdc7ac10aae1b48ca6e36dc15acb6ec9be5cdc5", + "hiding_nonce": "841d3a6450d7580b4da83c8e618414d0f024391f2aeb511d7579224420aa81f0", + "binding_nonce": "8d2624f532af631377f33cf44b5ac5f849067cae2eacb88680a31e77c79b5a80", + "hiding_nonce_commitment": "03c699af97d26bb4d3f05232ec5e1938c12f1e6ae97643c8f8f11c9820303f1904", + "binding_nonce_commitment": "02fa2aaccd51b948c9dc1a325d77226e98a5a3fe65fe9ba213761a60123040a45e", + "binding_factor_input": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4fff9b5210ffbb3c07a73a7c8935be4a8c62cf015f6cf7ade6efac09a6513540fc3f5a816aaebc2114a811a415d7a55db7c5cbc1cf27183e79dd9def941b5d48010000000000000000000000000000000000000000000000000000000000000001", + "binding_factor": "3e08fe561e075c653cbfd46908a10e7637c70c74f0a77d5fd45d1a750c739ec6" + }, + "participant_3": { + "hiding_nonce_randomness": "e6cc56ccbd0502b3f6f831d91e2ebd01c4de0479e0191b66895a4ffd9b68d544", + "binding_nonce_randomness": "7203d55eb82a5ca0d7d83674541ab55f6e76f1b85391d2c13706a89a064fd5b9", + "hiding_nonce": "2b19b13f193f4ce83a399362a90cdc1e0ddcd83e57089a7af0bdca71d47869b2", + "binding_nonce": "7a443bde83dc63ef52dda354005225ba0e553243402a4705ce28ffaafe0f5b98", + "hiding_nonce_commitment": "03077507ba327fc074d2793955ef3410ee3f03b82b4cdc2370f71d865beb926ef6", + "binding_nonce_commitment": "02ad53031ddfbbacfc5fbda3d3b0c2445c8e3e99cbc4ca2db2aa283fa68525b135", + "binding_factor_input": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4fff9b5210ffbb3c07a73a7c8935be4a8c62cf015f6cf7ade6efac09a6513540fc3f5a816aaebc2114a811a415d7a55db7c5cbc1cf27183e79dd9def941b5d48010000000000000000000000000000000000000000000000000000000000000003", + "binding_factor": "93f79041bb3fd266105be251adaeb5fd7f8b104fb554a4ba9a0becea48ddbfd7" + } + } + }, + "round_two": { + "signer_outputs": { + "participant_1" : { + "sig_share": "c4fce1775a1e141fb579944166eab0d65eefe7b98d480a569bbbfcb14f91c197" + }, + "participant_3": { + "sig_share": "0160fd0d388932f4826d2ebcd6b9eaba734f7c71cf25b4279a4ca2581e47b18d" + } + }, + "sig": "0205b6d04d3774c8929413e3c76024d54149c372d57aae62574ed74319b5ea14d0c65dde8492a7471437e6c2fe3da49b90d23f642b5c6dbe7e36089f096dd97324" + } +} diff --git a/src/modules/frost/main_impl.h b/src/modules/frost/main_impl.h index c45d1a94e4..375483ac81 100644 --- a/src/modules/frost/main_impl.h +++ b/src/modules/frost/main_impl.h @@ -11,10 +11,10 @@ #include "../../../include/secp256k1.h" #include "../../../include/secp256k1_frost.h" -static const unsigned char hash_context_prefix_h1[29] = "FROST-secp256k1-SHA256-v11rho"; -static const unsigned char hash_context_prefix_h3[31] = "FROST-secp256k1-SHA256-v11nonce"; -static const unsigned char hash_context_prefix_h4[29] = "FROST-secp256k1-SHA256-v11msg"; -static const unsigned char hash_context_prefix_h5[29] = "FROST-secp256k1-SHA256-v11com"; +static const unsigned char hash_context_prefix_h1[28] = "FROST-secp256k1-SHA256-v1rho"; +static const unsigned char hash_context_prefix_h3[30] = "FROST-secp256k1-SHA256-v1nonce"; +static const unsigned char hash_context_prefix_h4[28] = "FROST-secp256k1-SHA256-v1msg"; +static const unsigned char hash_context_prefix_h5[28] = "FROST-secp256k1-SHA256-v1com"; #define SCALAR_SIZE (32U) #define SHA256_SIZE (32U) @@ -184,11 +184,16 @@ static void compute_hash_h3(const unsigned char *msg, uint32_t msg_len, unsigned /* TODO: replace with hash-to-curve * H3(m): Implemented using hash_to_field from [HASH-TO-CURVE], Section 5.2 using L = 48, * expand_message_xmd with SHA-256, DST = "FROST-secp256k1-SHA256-v11" || "nonce", and prime modulus equal to Order(). */ + secp256k1_scalar scalar; secp256k1_sha256 sha; secp256k1_sha256_initialize(&sha); secp256k1_sha256_write(&sha, hash_context_prefix_h3, sizeof(hash_context_prefix_h3)); secp256k1_sha256_write(&sha, msg, msg_len); secp256k1_sha256_finalize(&sha, hash_value); + + /* Reduce hash to a scalar, and get back its binary representation */ + secp256k1_scalar_set_b32(&scalar, hash_value, NULL); + secp256k1_scalar_get_b32(hash_value, &scalar); } static void compute_hash_h4(const unsigned char *msg, uint32_t msg_len, unsigned char *hash_value) { @@ -263,32 +268,24 @@ SECP256K1_API int secp256k1_frost_pubkey_load(secp256k1_frost_pubkey *pubkey, return 1; } -SECP256K1_API int secp256k1_frost_pubkey_save(unsigned char *pubkey33, - unsigned char *group_pubkey33, - const secp256k1_frost_pubkey *pubkey) { +static int secp256k1_frost_pubkey_serialize(unsigned char *output33, const unsigned char *pubkey64) { + secp256k1_ge pk; size_t size; - int compressed; - secp256k1_ge pk, gpk; - - if (pubkey == NULL || pubkey33 == NULL || group_pubkey33 == NULL) { - return 0; - } - compressed = 1; - if (secp256k1_fe_set_b32_limit(&pk.x, pubkey->public_key) == 0) { + if (secp256k1_fe_set_b32_limit(&pk.x, pubkey64) == 0) { return 0; } - if (secp256k1_fe_set_b32_limit(&pk.y, pubkey->public_key + SERIALIZED_PUBKEY_X_ONLY_SIZE) == 0) { + if (secp256k1_fe_set_b32_limit(&pk.y, pubkey64 + SERIALIZED_PUBKEY_X_ONLY_SIZE) == 0) { return 0; } - pk.infinity = 0; + /* * 0 is a purposely illegal value. We will verify that * secp256k1_eckey_pubkey_serialize() sets it to 33 */ size = 0; - if (secp256k1_eckey_pubkey_serialize(&pk, pubkey33, &size, compressed) == 0) { + if (secp256k1_eckey_pubkey_serialize(&pk, output33, &size, 1) == 0) { return 0; } if (size != 33) { @@ -296,27 +293,21 @@ SECP256K1_API int secp256k1_frost_pubkey_save(unsigned char *pubkey33, } secp256k1_ge_clear(&pk); - if (secp256k1_fe_set_b32_limit(&gpk.x, pubkey->group_public_key) == 0) { - return 0; - } - if (secp256k1_fe_set_b32_limit(&gpk.y, pubkey->group_public_key + SERIALIZED_PUBKEY_X_ONLY_SIZE) == 0) { + return 1; +} + +SECP256K1_API int secp256k1_frost_pubkey_save(unsigned char *pubkey33, + unsigned char *group_pubkey33, + const secp256k1_frost_pubkey *pubkey) { + if (pubkey == NULL || pubkey33 == NULL || group_pubkey33 == NULL) { return 0; } - - gpk.infinity = 0; - /* - * 0 is a purposely illegal value. We will verify that - * secp256k1_eckey_pubkey_serialize() sets it to 33 - */ - size = 0; - if (secp256k1_eckey_pubkey_serialize(&gpk, group_pubkey33, &size, compressed) == 0) { + if (secp256k1_frost_pubkey_serialize(pubkey33, pubkey->public_key) == 0){ return 0; } - if (size != 33) { + if (secp256k1_frost_pubkey_serialize(group_pubkey33, pubkey->group_public_key) == 0){ return 0; } - secp256k1_ge_clear(&gpk); - return 1; } @@ -447,87 +438,114 @@ SECP256K1_API void secp256k1_frost_keypair_destroy(secp256k1_frost_keypair *keyp } /* - * Generate coefficients for Shamir Secret Sharing. + * Generate random coefficients for Shamir Secret Sharing. * * Returns: 1: on success; 0: on failure - * Args: ctx: a secp256k1 context object, initialized for verification. - * Out: dkg_commitments: pointer to shamir_coefficients where coefficients will be stored. - * coefficients: pointer to shamir_coefficients where coefficients will be stored. + * Out: coefficients: pointer to shamir_coefficients where coefficients will be stored. * In: generator_index: index of participant generating coefficients. - * secret: secret to be used as known term of the Shamir polynomial - * num_participants: number of participants to the secret sharing * threshold: min number of participants needed to reconstruct the secret. */ -static SECP256K1_WARN_UNUSED_RESULT int generate_coefficients(const secp256k1_context *ctx, - secp256k1_frost_vss_commitments *dkg_commitments, - shamir_coefficients *coefficients, - uint32_t generator_index, const secp256k1_scalar *secret, - uint32_t threshold) { +static SECP256K1_WARN_UNUSED_RESULT int generate_random_coefficients(shamir_coefficients *coefficients, + uint32_t generator_index, + uint32_t threshold) { uint32_t c_idx; - secp256k1_gej coefficient_cmt; const uint32_t num_coefficients = threshold - 1; coefficients->index = generator_index; - dkg_commitments->index = generator_index; - - /* Compute the commitment of the secret term (saved as commitment[0]) */ - secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &coefficient_cmt, secret); - serialize_point(&coefficient_cmt, dkg_commitments->coefficient_commitments[0].data); - for (c_idx = 0; c_idx < num_coefficients; c_idx++) { /* Generate random coefficients */ if (initialize_random_scalar(&(coefficients->coefficients[c_idx])) == 0) { return 0; } + } + return 1; +} + +/* + * Commit to Verifiable Secret Shares + * + * Args: ctx: a secp256k1 context object, initialized for verification. + * Out: vss_commitments: pointer to shamir_coefficients where coefficients will be stored. + * In: coefficients: pointer to shamir_coefficients where coefficients will be stored. + * secret: secret to be used as known term of the Shamir polynomial + * threshold: min number of participants needed to reconstruct the secret. + */ +static void vss_commit(const secp256k1_context *ctx, + secp256k1_frost_vss_commitments *vss_commitments, + const shamir_coefficients *coefficients, + const secp256k1_scalar *secret, + uint32_t threshold) { + uint32_t c_idx; + secp256k1_gej coefficient_cmt; + const uint32_t num_coefficients = threshold - 1; + vss_commitments->index = coefficients->index; + + /* Compute the commitment of the secret term (saved as commitment[0]) */ + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &coefficient_cmt, secret); + serialize_point(&coefficient_cmt, vss_commitments->coefficient_commitments[0].data); + + for (c_idx = 0; c_idx < num_coefficients; c_idx++) { /* Compute the commitment of each random coefficient (saved as commitment[1...]) */ secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &coefficient_cmt, &(coefficients->coefficients[c_idx])); - serialize_point(&coefficient_cmt, dkg_commitments->coefficient_commitments[c_idx + 1].data); + serialize_point(&coefficient_cmt, vss_commitments->coefficient_commitments[c_idx + 1].data); } - return 1; +} + + +/* + * Evaluate the Shamir polynomial f(x) at a particular point x using Horner's method. + * + * Out: value: Scalar result of the polynomial evaluated at input x. + * In: coefficients: pointer to shamir_coefficients + * secret: secret to be used as known term of the Shamir polynomial + * x: input at which to evaluate the polynomial (treated as a Scalar) + */ +static void polynomial_evaluate(secp256k1_scalar *value, + const shamir_coefficients *coefficients, const secp256k1_scalar *secret, + uint32_t x) { + secp256k1_scalar scalar_x; + uint32_t c_idx; + + secp256k1_scalar_set_int(&scalar_x, x); + secp256k1_scalar_set_int(value, 0); + for (c_idx = coefficients->num_coefficients; c_idx > 0; c_idx--) { + secp256k1_scalar_add(value, value, &(coefficients->coefficients[c_idx - 1])); + secp256k1_scalar_mul(value, value, &scalar_x); + } + + /* The secret is the *constant* term in the polynomial used for secret sharing, + * this is typical in schemes that build upon Shamir Secret Sharing. */ + secp256k1_scalar_add(value, value, secret); } /* - * Evaluate Shamir polynomial for each participant. + * Shard secret for each participant by evaluating the Shamir polynomial. * * Returns: 1: on success; 0: on failure - * Out: shares: pointer to shamir_coefficients where coefficients will be stored (expected to be already allocated). - * In: coefficients: pointer to shamir_coefficients where coefficients will be stored. - * generator_index: index of participant generating coefficients. - * num_participants: number of participants to the secret sharing - * coefficients: pointer to shamir_coefficients. - * secret: secret to be used as known term of the Shamir polynomial. + * Out: secret_key_shares: pointer to shamir_coefficients where coefficients will be stored (expected to be already allocated). + * In: generator_index: index of participant generating coefficients. + * num_participants: number of participants to the secret sharing + * coefficients: pointer to shamir_coefficients. + * secret: secret to be used as known term of the Shamir polynomial. */ -static void evaluate_shamir_polynomial(secp256k1_frost_keygen_secret_share *shares, - uint32_t generator_index, uint32_t num_participants, - const shamir_coefficients *coefficients, const secp256k1_scalar *secret) { - /* For each participant, evaluate the polynomial and save in shares: +static void secret_share_shard(secp256k1_frost_keygen_secret_share *secret_key_shares, + uint32_t generator_index, uint32_t num_participants, + const shamir_coefficients *coefficients, const secp256k1_scalar *secret) { + /* For each participant, evaluate the polynomial and save in secret_key_shares: * {generator_index, participant_index, f(participant_index)} */ uint32_t index; for (index = 1; index < num_participants + 1; index++) { - /* Evaluate the polynomial with `secret` as the constant term - * and `coefficients` as the other coefficients at the point x=share_index - * using Horner's method */ - secp256k1_scalar scalar_index; secp256k1_scalar value; - uint32_t c_idx; - secp256k1_scalar_set_int(&scalar_index, index); - secp256k1_scalar_set_int(&value, 0); - for (c_idx = coefficients->num_coefficients; c_idx > 0; c_idx--) { - secp256k1_scalar_add(&value, &value, &(coefficients->coefficients[c_idx - 1])); - secp256k1_scalar_mul(&value, &value, &scalar_index); - } + polynomial_evaluate(&value, coefficients, secret, index); - /* The secret is the *constant* term in the polynomial used for secret sharing, - * this is typical in schemes that build upon Shamir Secret Sharing. */ - secp256k1_scalar_add(&value, &value, secret); - secp256k1_scalar_get_b32(shares[index - 1].value, &value); - - shares[index - 1].generator_index = generator_index; - shares[index - 1].receiver_index = index; + /* Save share in secret_key_shares */ + secp256k1_scalar_get_b32(secret_key_shares[index - 1].value, &value); + secret_key_shares[index - 1].generator_index = generator_index; + secret_key_shares[index - 1].receiver_index = index; } } @@ -537,27 +555,27 @@ static void evaluate_shamir_polynomial(secp256k1_frost_keygen_secret_share *shar * * Returns 1 on success, 0 on failure. * Args: ctx: pointer to a context object, initialized for signing. - * Out: coefficients: commitments to the Shamir polynomial coefficients. - * shares: array containing the polynomial computed for each participant (expected to be allocated) - * In: num_participants: number of shares and commitments. - * threshold: Signature threshold - * generator_index: participant index. - * secret: Secret value to use as constant term of the polynomial + * Out: vss_coefficients: commitments to the Shamir polynomial coefficients. + * secret_key_shares: array containing the polynomial computed for each participant (expected to be allocated) + * In: num_participants: number of secret_key_shares and commitments. + * threshold: Signature threshold + * generator_index: participant index. + * secret: Secret value to use as constant term of the polynomial */ -static SECP256K1_WARN_UNUSED_RESULT int generate_shares(const secp256k1_context *ctx, - secp256k1_frost_vss_commitments *dkg_commitments, - secp256k1_frost_keygen_secret_share *shares, - uint32_t num_participants, uint32_t threshold, - uint32_t generator_index, - const secp256k1_scalar *secret) { +static SECP256K1_WARN_UNUSED_RESULT int generate_shares_with_random_polynomial(const secp256k1_context *ctx, + secp256k1_frost_vss_commitments *vss_commitments, + secp256k1_frost_keygen_secret_share *secret_key_shares, + uint32_t num_participants, uint32_t threshold, + uint32_t generator_index, + const secp256k1_scalar *secret) { int ret_coefficients; shamir_coefficients *coefficients; coefficients = shamir_coefficients_create(threshold); - ret_coefficients = generate_coefficients(ctx, dkg_commitments, coefficients, generator_index, secret, threshold); + ret_coefficients = generate_random_coefficients(coefficients, generator_index, threshold); if (ret_coefficients == 1) { - evaluate_shamir_polynomial(shares, generator_index, - num_participants, coefficients, secret); + secret_share_shard(secret_key_shares, generator_index, num_participants, coefficients, secret); + vss_commit(ctx, vss_commitments, coefficients, secret, threshold); } shamir_coefficients_destroy(coefficients); @@ -567,7 +585,6 @@ static SECP256K1_WARN_UNUSED_RESULT int generate_shares(const secp256k1_context /* * Generate a challenge for DKG. * - * Returns 1 on success, 0 on failure. * Out: challenge: pointer to scalar where the challenge will be stored. * In: index: participant identifier. * context_nonce: tag to use during DKG @@ -575,11 +592,11 @@ static SECP256K1_WARN_UNUSED_RESULT int generate_shares(const secp256k1_context * public_key: participant public key used for computing the challenge. * commitment: commitment to a random value. */ -static SECP256K1_WARN_UNUSED_RESULT int generate_dkg_challenge(secp256k1_scalar *challenge, - const uint32_t index, const unsigned char *context_nonce, - const uint32_t nonce_length, - const secp256k1_gej *public_key, - const secp256k1_gej *commitment) { +static void generate_dkg_challenge(secp256k1_scalar *challenge, + const uint32_t index, const unsigned char *context_nonce, + const uint32_t nonce_length, + const secp256k1_gej *public_key, + const secp256k1_gej *commitment) { uint32_t challenge_input_length; unsigned char *challenge_input; unsigned char hash_value[SHA256_SIZE]; @@ -607,7 +624,6 @@ static SECP256K1_WARN_UNUSED_RESULT int generate_dkg_challenge(secp256k1_scalar if (challenge_input != NULL) { free(challenge_input); } - return 1; } static SECP256K1_WARN_UNUSED_RESULT int is_valid_zkp(const secp256k1_context *ctx, const secp256k1_scalar *challenge, @@ -628,8 +644,8 @@ static SECP256K1_WARN_UNUSED_RESULT int is_valid_zkp(const secp256k1_context *ct /* TODO: to improve testability of this function, it should be deterministic. */ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_keygen_dkg_begin(const secp256k1_context *ctx, - secp256k1_frost_vss_commitments *dkg_commitment, - secp256k1_frost_keygen_secret_share *shares, + secp256k1_frost_vss_commitments *vss_commitments, + secp256k1_frost_keygen_secret_share *secret_key_shares, uint32_t num_participants, uint32_t threshold, uint32_t generator_index, @@ -638,19 +654,19 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_keygen_dkg_begin( secp256k1_scalar secret, r, z, challenge; secp256k1_gej s_pub, zkp_r; - if (ctx == NULL || dkg_commitment == NULL || shares == NULL || context == NULL) { + if (ctx == NULL || vss_commitments == NULL || secret_key_shares == NULL || context == NULL) { return 0; } if (threshold < 1 || num_participants < 1 || threshold > num_participants) { return 0; } - dkg_commitment->index = generator_index; + vss_commitments->index = generator_index; if (initialize_random_scalar(&secret) == 0) { return 0; } - if (generate_shares(ctx, dkg_commitment, shares, num_participants, - threshold, generator_index, &secret) == 0) { + if (generate_shares_with_random_polynomial(ctx, vss_commitments, secret_key_shares, num_participants, + threshold, generator_index, &secret) == 0) { return 0; } @@ -659,15 +675,13 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_keygen_dkg_begin( } secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &s_pub, &secret); secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &zkp_r, &r); - serialize_point(&zkp_r, dkg_commitment->zkp_r); - if (generate_dkg_challenge(&challenge, generator_index, context, context_length, &s_pub, &zkp_r) == 0) { - return 0; - } + serialize_point(&zkp_r, vss_commitments->zkp_r); + generate_dkg_challenge(&challenge, generator_index, context, context_length, &s_pub, &zkp_r); /* z = r + secret * H(context, G^secret, G^r) */ secp256k1_scalar_mul(&z, &secret, &challenge); secp256k1_scalar_add(&z, &r, &z); - secp256k1_scalar_get_b32(dkg_commitment->zkp_z, &z); + secp256k1_scalar_get_b32(vss_commitments->zkp_z, &z); /* Cleaning context */ secp256k1_scalar_set_int(&secret, 0); @@ -688,12 +702,10 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_keygen_dkg_commit deserialize_point(&peer_zkp_r, peer_commitment->zkp_r); deserialize_point(&secret_commitment, peer_commitment->coefficient_commitments[0].data); - if (generate_dkg_challenge(&challenge, peer_commitment->index, + generate_dkg_challenge(&challenge, peer_commitment->index, context, context_length, &secret_commitment, - &peer_zkp_r) == 0) { - return 0; - } + &peer_zkp_r); return is_valid_zkp(ctx, &challenge, peer_commitment); } @@ -773,60 +785,86 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_keygen_dkg_finali return 1; } -SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_keygen_with_dealer( +static SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_keygen_with_dealer_core( const secp256k1_context *ctx, - secp256k1_frost_vss_commitments *share_commitment, - secp256k1_frost_keygen_secret_share *shares, + secp256k1_frost_vss_commitments *vss_commitments, + secp256k1_frost_keygen_secret_share *secret_key_shares, secp256k1_frost_keypair *keypairs, uint32_t num_participants, - uint32_t threshold) { - - secp256k1_scalar secret; + uint32_t threshold, + const secp256k1_scalar *secret, + const shamir_coefficients *coefficients, + uint32_t generator_index) { secp256k1_gej group_public_key; - uint32_t generator_index, index; + uint32_t index; - if (ctx == NULL || share_commitment == NULL || shares == NULL || keypairs == NULL) { + /* Parameter checking */ + if (ctx == NULL || vss_commitments == NULL || secret_key_shares == NULL || keypairs == NULL) { return 0; } - - /* We use generator_index=0 as we are generating shares with a dealer */ - generator_index = 0; - - /* Parameter checking */ if (threshold < 1 || num_participants < 1 || threshold > num_participants) { return 0; } - /* Initialization */ - share_commitment->index = generator_index; - if (initialize_random_scalar(&secret) == 0) { - return 0; - } - secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &group_public_key, &secret); + /* Compute group public key */ + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &group_public_key, secret); - /* Generate shares */ - if (generate_shares(ctx, share_commitment, shares, num_participants, - threshold, generator_index, &secret) == 0) { - return 0; - } + /* Compute secret shares and commit to polynomial coefficients */ + secret_share_shard(secret_key_shares, generator_index, num_participants, coefficients, secret); + vss_commitments->index = generator_index; + vss_commit(ctx, vss_commitments, coefficients, secret, threshold); - /* Preparing output */ + /* Preparing output key-pairs */ for (index = 0; index < num_participants; index++) { secp256k1_scalar share_value; secp256k1_gej pubkey; - secp256k1_scalar_set_b32(&share_value, shares[index].value, NULL); + secp256k1_scalar_set_b32(&share_value, secret_key_shares[index].value, NULL); secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pubkey, &share_value); serialize_point(&pubkey, keypairs[index].public_keys.public_key); - memcpy(&keypairs[index].secret, &shares[index].value, SCALAR_SIZE); + memcpy(&keypairs[index].secret, &secret_key_shares[index].value, SCALAR_SIZE); serialize_point(&group_public_key, keypairs[index].public_keys.group_public_key); - keypairs[index].public_keys.index = shares[index].receiver_index; + keypairs[index].public_keys.index = secret_key_shares[index].receiver_index; keypairs[index].public_keys.max_participants = num_participants; } return 1; } +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_keygen_with_dealer( + const secp256k1_context *ctx, + secp256k1_frost_vss_commitments *vss_commitments, + secp256k1_frost_keygen_secret_share *secret_key_shares, + secp256k1_frost_keypair *keypairs, + uint32_t num_participants, + uint32_t threshold) { + + secp256k1_scalar secret; + uint32_t generator_index; + shamir_coefficients *coefficients; + int result; + + /* We use generator_index=0 as we are generating secret_key_shares with a dealer */ + generator_index = 0; + + /* Generate random as secret */ + if (initialize_random_scalar(&secret) == 0) { + return 0; + } + /* Generate secret_key_shares */ + coefficients = shamir_coefficients_create(threshold); + result = 0; + if (generate_random_coefficients(coefficients, generator_index, threshold) == 1) { + result = secp256k1_frost_keygen_with_dealer_core( + ctx, vss_commitments, secret_key_shares, keypairs, + num_participants, threshold, &secret, coefficients, generator_index); + } + + /* Free allocated memory */ + shamir_coefficients_destroy(coefficients); + return result; +} + static SECP256K1_WARN_UNUSED_RESULT int signing_commitment_compare(secp256k1_frost_nonce_commitment *s1, secp256k1_frost_nonce_commitment *s2) { if (s1->index > s2->index) { diff --git a/src/modules/frost/tests_impl.h b/src/modules/frost/tests_impl.h index 3518e1fed4..52cfff8f65 100644 --- a/src/modules/frost/tests_impl.h +++ b/src/modules/frost/tests_impl.h @@ -8,7 +8,7 @@ #define SECP256K1_MODULE_FROST_TESTS_H #include "../../../include/secp256k1_frost.h" - +#include "frost_ietf_test_vectors.h" void test_secp256k1_gej_eq_case_1(void) { secp256k1_gej a, b; @@ -3075,8 +3075,281 @@ void test_secp256k1_frost_verify_to_be_invalid(void) { secp256k1_context_destroy(test_ctx); } +/* + * Check FROST against IETF test vector for FROST(secp256k1, SHA-256) + * See Appendix of: + * https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost/ + */ +#define SCALAR_SIZE 32 +#include +void test_secp256k1_frost_ietf_test_vector(void) { + unsigned char group_secret_key[32]; + unsigned char group_public_key[33]; + unsigned char participant_public_key[33]; + unsigned char share_polynomial_coefficients[SCALAR_SIZE]; + unsigned char expanded_pub_key[64]; + + secp256k1_context *sign_ctx; + secp256k1_frost_vss_commitments *vss_commitments; + secp256k1_frost_keygen_secret_share secret_key_shares[3]; + secp256k1_frost_keypair keypairs[3]; + int result, i, j; + secp256k1_scalar secret; + shamir_coefficients *coefficients; + unsigned char binding_seed[32] = {0}; + unsigned char hiding_seed[32] = {0}; + secp256k1_frost_signature_share signature_share[3]; + secp256k1_frost_nonce *nonces[3]; + secp256k1_frost_nonce_commitment signing_commitments[3]; + + /* Step 1. initialization */ + sign_ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + vss_commitments = secp256k1_frost_vss_commitments_create(IETF_FROST_MIN_PARTICIPANTS); + coefficients = shamir_coefficients_create(IETF_FROST_NUM_PARTICIPANTS); + + /* Read group secret key from IETF test vector */ + secp256k1_scalar_set_b32(&secret, ietf_frost_group_secret_key, NULL); + /* Read shamir coefficient from IETF test vector */ + secp256k1_scalar_set_b32(&coefficients->coefficients[0], ietf_frost_share_polynomial_coefficients_0, NULL); + + result = secp256k1_frost_keygen_with_dealer_core(sign_ctx, + vss_commitments, + secret_key_shares, + keypairs, + IETF_FROST_MAX_PARTICIPANTS, + IETF_FROST_NUM_PARTICIPANTS, + &secret, + coefficients, 0); + CHECK(result == 1); + + /* Check: Verify that the computed group public key matches the one in the test vector */ + result = secp256k1_frost_pubkey_serialize(group_public_key, keypairs[0].public_keys.group_public_key); + CHECK(result == 1); + result = memcmp(ietf_frost_group_public_key, group_public_key, 33); + CHECK(result == 0); + + /* Check: Verify participant shares */ + for(i = 0; i < IETF_FROST_MAX_PARTICIPANTS; i++) { + result = memcmp(&ietf_frost_participant_shares[i * IETF_FROST_PARTICIPANT_SHARE_SIZE], + secret_key_shares[i].value, + IETF_FROST_PARTICIPANT_SHARE_SIZE); + CHECK(result == 0); + } + + /* TODO: implement vss_verify */ + + /* Round one: commitment; participants: (1, 3) */ + + /* Step 2: prepare signature commitments */ + for (i = 0; i < IETF_FROST_MAX_PARTICIPANTS; i++) { + nonces[i] = secp256k1_frost_nonce_create(sign_ctx, &keypairs[i], + &ietf_frost_binding_nonce_randomnesses[i * IETF_FROST_BINDING_NONCE_RANDOMNESS_SIZE], + &ietf_frost_hiding_nonce_randomnesses[i * IETF_FROST_HIDING_NONCE_RANDOMNESS_SIZE]); + + memcpy(&signing_commitments[i], &(nonces[i]->commitments), sizeof(secp256k1_frost_nonce_commitment)); + } + for (j = 0; j < IETF_FROST_NUM_PARTICIPANTS; j++) { + i = (int) ietf_frost_participants[j] - 1; + result = memcmp(&ietf_frost_hiding_nonces[i * IETF_FROST_HIDING_NONCE_SIZE], + nonces[i]->hiding, + IETF_FROST_HIDING_NONCE_SIZE); + CHECK(result == 0); + result = memcmp(&ietf_frost_binding_nonces[i * IETF_FROST_BINDING_NONCE_SIZE], + nonces[i]->binding, + IETF_FROST_BINDING_NONCE_SIZE); + CHECK(result == 0); + + result = memcmp(&ietf_frost_hiding_nonce_commitments[i * IETF_FROST_HIDING_NONCE_COMMITMENT_SIZE], + nonces[i]->commitments.hiding, + IETF_FROST_HIDING_NONCE_COMMITMENT_SIZE); + CHECK(result == 0); + result = memcmp(&ietf_frost_binding_nonce_commitments[i * IETF_FROST_BINDING_NONCE_COMMITMENT_SIZE], + nonces[i]->commitments.binding, + IETF_FROST_BINDING_NONCE_COMMITMENT_SIZE); + CHECK(result == 0); + + } + + + /* Cleanup */ + secp256k1_frost_vss_commitments_destroy(vss_commitments); + secp256k1_context_destroy(sign_ctx); + + +/* + byte_t hiding_nonce_randomness_1[ecc_frost_ristretto255_sha512_SCALARSIZE]; + byte_t binding_nonce_randomness_1[ecc_frost_ristretto255_sha512_SCALARSIZE]; + ecc_hex2bin(hiding_nonce_randomness_1, ecc_json_string(json, "round_one_outputs.participants.1.hiding_nonce_randomness"), 64); + ecc_hex2bin(binding_nonce_randomness_1, ecc_json_string(json, "round_one_outputs.participants.1.binding_nonce_randomness"), 64); + + byte_t hiding_nonce_randomness_3[ecc_frost_ristretto255_sha512_SCALARSIZE]; + byte_t binding_nonce_randomness_3[ecc_frost_ristretto255_sha512_SCALARSIZE]; + ecc_hex2bin(hiding_nonce_randomness_3, ecc_json_string(json, "round_one_outputs.participants.3.hiding_nonce_randomness"), 64); + ecc_hex2bin(binding_nonce_randomness_3, ecc_json_string(json, "round_one_outputs.participants.3.binding_nonce_randomness"), 64); + + byte_t nonce_1[ecc_frost_ristretto255_sha512_NONCEPAIRSIZE]; + byte_t comm_1[ecc_frost_ristretto255_sha512_NONCECOMMITMENTPAIRSIZE]; + ecc_frost_ristretto255_sha512_commit_with_randomness( + nonce_1, + comm_1, + &participant_private_keys[0 * ecc_frost_ristretto255_sha512_POINTSIZE + ecc_frost_ristretto255_sha512_SCALARSIZE], + hiding_nonce_randomness_1, + binding_nonce_randomness_1 + ); + ecc_bin2hex(value_hex, &nonce_1[0], 32); + assert_string_equal(value_hex, ecc_json_string(json, "round_one_outputs.participants.1.hiding_nonce")); + ecc_bin2hex(value_hex, &nonce_1[32], 32); + assert_string_equal(value_hex, ecc_json_string(json, "round_one_outputs.participants.1.binding_nonce")); + ecc_bin2hex(value_hex, &comm_1[0], 32); + assert_string_equal(value_hex, ecc_json_string(json, "round_one_outputs.participants.1.hiding_nonce_commitment")); + ecc_bin2hex(value_hex, &comm_1[32], 32); + assert_string_equal(value_hex, ecc_json_string(json, "round_one_outputs.participants.1.binding_nonce_commitment")); + + byte_t nonce_3[ecc_frost_ristretto255_sha512_NONCEPAIRSIZE]; + byte_t comm_3[ecc_frost_ristretto255_sha512_NONCECOMMITMENTPAIRSIZE]; + ecc_frost_ristretto255_sha512_commit_with_randomness( + nonce_3, + comm_3, + &participant_private_keys[2 * ecc_frost_ristretto255_sha512_POINTSIZE + ecc_frost_ristretto255_sha512_SCALARSIZE], + hiding_nonce_randomness_3, + binding_nonce_randomness_3 + ); + ecc_bin2hex(value_hex, &nonce_3[0], 32); + assert_string_equal(value_hex, ecc_json_string(json, "round_one_outputs.participants.3.hiding_nonce")); + ecc_bin2hex(value_hex, &nonce_3[32], 32); + assert_string_equal(value_hex, ecc_json_string(json, "round_one_outputs.participants.3.binding_nonce")); + ecc_bin2hex(value_hex, &comm_3[0], 32); + assert_string_equal(value_hex, ecc_json_string(json, "round_one_outputs.participants.3.hiding_nonce_commitment")); + ecc_bin2hex(value_hex, &comm_3[32], 32); + assert_string_equal(value_hex, ecc_json_string(json, "round_one_outputs.participants.3.binding_nonce_commitment")); + + byte_t commitment_list[2 * ecc_frost_ristretto255_sha512_COMMITMENTSIZE] = {0}; + commitment_list[0] = 1; + memcpy(&commitment_list[32], comm_1, ecc_frost_ristretto255_sha512_NONCECOMMITMENTPAIRSIZE); + commitment_list[ecc_frost_ristretto255_sha512_COMMITMENTSIZE] = 3; + memcpy(&commitment_list[ecc_frost_ristretto255_sha512_COMMITMENTSIZE + 32], comm_3, ecc_frost_ristretto255_sha512_NONCECOMMITMENTPAIRSIZE); + + byte_t binding_factor_list[2 * ecc_frost_ristretto255_sha512_BINDINGFACTORSIZE]; + ecc_frost_ristretto255_sha512_compute_binding_factors( + binding_factor_list, + commitment_list, 2, + message, message_len + ); + + ecc_bin2hex(value_hex, &binding_factor_list[32], 32); + assert_string_equal(value_hex, ecc_json_string(json, "round_one_outputs.participants.1.binding_factor")); + ecc_bin2hex(value_hex, &binding_factor_list[96], 32); + assert_string_equal(value_hex, ecc_json_string(json, "round_one_outputs.participants.3.binding_factor")); + + // Round two: sign + + byte_t sig_shares[2 * ecc_frost_ristretto255_sha512_SCALARSIZE]; + + byte_t identifier_1[ecc_frost_ristretto255_sha512_SCALARSIZE] = {1, 0}; + ecc_frost_ristretto255_sha512_sign( + &sig_shares[0], + identifier_1, + &participant_private_keys[0 * ecc_frost_ristretto255_sha512_POINTSIZE + + ecc_frost_ristretto255_sha512_SCALARSIZE], + group_public_key, + nonce_1, + message, message_len, + commitment_list, 2 + ); + ecc_bin2hex(value_hex, &sig_shares[0], 32); + assert_string_equal(value_hex, ecc_json_string(json, "round_two_outputs.participants.1.sig_share")); + + byte_t identifier_3[ecc_frost_ristretto255_sha512_SCALARSIZE] = {3, 0}; + ecc_frost_ristretto255_sha512_sign( + &sig_shares[32], + identifier_3, + &participant_private_keys[2 * ecc_frost_ristretto255_sha512_POINTSIZE + + ecc_frost_ristretto255_sha512_SCALARSIZE], + group_public_key, + nonce_3, + message, message_len, + commitment_list, 2 + ); + ecc_bin2hex(value_hex, &sig_shares[32], 32); + assert_string_equal(value_hex, ecc_json_string(json, "round_two_outputs.participants.3.sig_share")); + + // Final step: aggregate + + // validation + + assert_int_equal( + ecc_frost_ristretto255_sha512_verify_signature_share( + identifier_1, + &participant_public_keys[0 * 32], + comm_1, + &sig_shares[0 * 32], + commitment_list, 2, + group_public_key, + message, message_len + ), + 1 + ); + assert_int_equal( + ecc_frost_ristretto255_sha512_verify_signature_share( + identifier_3, + &participant_public_keys[2 * 32], + comm_3, + &sig_shares[1 * 32], + commitment_list, 2, + group_public_key, + message, message_len + ), + 1 + ); + + byte_t signature[ecc_frost_ristretto255_sha512_SIGNATURESIZE]; + ecc_frost_ristretto255_sha512_aggregate( + signature, + commitment_list, 2, + message, sizeof message, + sig_shares, 2 + ); + + char signature_hex[2 * ecc_frost_ristretto255_sha512_SIGNATURESIZE + 1]; + ecc_bin2hex(signature_hex, signature, sizeof signature); + assert_string_equal(signature_hex, ecc_json_string(json, "final_output.sig")); + + // Sanity check verification logic (from the draft appendix) + // single_sig = prime_order_sign(G, H, group_secret_key, message) + // assert(prime_order_verify(G, H, group_public_key, message, single_sig)) + byte_t single_sig[ecc_frost_ristretto255_sha512_SIGNATURESIZE]; + ecc_frost_ristretto255_sha512_prime_order_sign( + single_sig, + message, message_len, + group_secret_key + ); + assert_int_equal( + ecc_frost_ristretto255_sha512_prime_order_verify( + message, message_len, + single_sig, + group_public_key + ), + 1 + ); + + // Verify the group signature just the same + // assert(prime_order_verify(G, H, group_public_key, message, sig)) + assert_int_equal( + ecc_frost_ristretto255_sha512_prime_order_verify( + message, message_len, + signature, + group_public_key + ), + 1 + ); + + ecc_json_destroy(json);*/ +} + void run_frost_tests(void) { + test_secp256k1_frost_ietf_test_vector(); + /* Test auxiliary internal functions */ test_secp256k1_gej_eq_case_1(); test_secp256k1_gej_eq_case_2(); diff --git a/tools/tests_frost_ietf_generate.py b/tools/tests_frost_ietf_generate.py new file mode 100755 index 0000000000..64a260348b --- /dev/null +++ b/tools/tests_frost_ietf_generate.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +# Copyright (c) 2023 Random "Randy" Lattice and Sean Andersen +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://www.opensource.org/licenses/mit-license.php. +''' +Generate a C file with FROST IETF test vectors. +''' + +import json +import hashlib +import urllib.request +import sys + +filename_input = sys.argv[1] + +with open(filename_input) as f: + doc = json.load(f) + +def to_c_array(x): + if x == "": return "" + s = ',0x'.join(a+b for a,b in zip(x[::2], x[1::2])) + return "0x" + s + + +num_vectors = 0 +offset_msg_running, offset_pk_running, offset_sig = 0, 0, 0 +out = "" +messages = "" +signatures = "" +public_keys = "" +cache_msgs = {} +cache_public_keys = {} + +print("/* Note: this file was autogenerated using tests_frost_ietf_generate.py. Do not edit. */") +print("") + +max_participants = int(doc['configuration_information']['MAX_PARTICIPANTS']) +for i in doc['configuration_information']: + print(f"#define IETF_FROST_{i} {doc['configuration_information'][i]}") +print("") + + +print("/* Section: group_input_parameters */") + +participant_list = ", ".join([str(x) for x in doc['group_input_parameters']['participant_list']]) +print(f"static const uint32_t ietf_frost_participants[] = {{{participant_list}}};") +print("static const unsigned char ietf_frost_group_secret_key[] = { " + + to_c_array(doc['group_input_parameters']['group_secret_key']) + "};") +print("static const unsigned char ietf_frost_group_public_key[] = { " + + to_c_array(doc['group_input_parameters']['group_public_key']) + "};") +print("static const unsigned char ietf_frost_message[] = { " + + to_c_array(doc['group_input_parameters']['message']) + "};") +print(f"static const size_t ietf_frost_message_length = {str(len(doc['group_input_parameters']['message']))};") +# TODO: improve representation of coefficients (threshold - 1) +print("static const unsigned char ietf_frost_share_polynomial_coefficients_0[] = { " + + to_c_array(doc['group_input_parameters']['share_polynomial_coefficients'][0]) + "};") +print("") + + +print("/* Section: signer_input_parameters */") + +print(f"#define IETF_FROST_PARTICIPANT_SHARE_SIZE {str(int(len(doc['signer_input_parameters']['participant_share'][0])/2))}") +print("static const unsigned char ietf_frost_participant_shares[] = { ") +for i in doc['signer_input_parameters']['participant_share']: + print(f"{to_c_array(i)},") +print("};") +print("") + + +print("/* Section: round_one.signer_outputs */") + +signer_outputs = doc['round_one']['signer_outputs'] +len_hnr = int(len(signer_outputs['participant_1']['hiding_nonce_randomness'])/2) +len_hn = int(len(signer_outputs['participant_1']['hiding_nonce'])/2) +len_bnr = int(len(signer_outputs['participant_1']['binding_nonce_randomness'])/2) +len_bn = int(len(signer_outputs['participant_1']['binding_nonce'])/2) +len_hnc = int(len(signer_outputs['participant_1']['hiding_nonce_commitment'])/2) +len_bnc = int(len(signer_outputs['participant_1']['binding_nonce_commitment'])/2) +len_bfi = int(len(signer_outputs['participant_1']['binding_factor_input'])/2) +len_bf = int(len(signer_outputs['participant_1']['binding_factor'])/2) + +print(f"#define IETF_FROST_HIDING_NONCE_RANDOMNESS_SIZE {str(len_hnr)}") +print(f"#define IETF_FROST_HIDING_NONCE_SIZE {str(len_hn)}") +print(f"#define IETF_FROST_BINDING_NONCE_RANDOMNESS_SIZE {str(len_bnr)}") +print(f"#define IETF_FROST_BINDING_NONCE_SIZE {str(len_bn)}") +print(f"#define IETF_FROST_HIDING_NONCE_COMMITMENT_SIZE {str(len_hnc)}") +print(f"#define IETF_FROST_BINDING_NONCE_COMMITMENT_SIZE {str(len_bnc)}") +print(f"#define IETF_FROST_BINDING_FACTOR_INPUT_SIZE {str(len_bfi)}") +print(f"#define IETF_FROST_BINDING_FACTOR_SIZE {str(len_bf)}") + +hiding_nonce_randomnesses = "" +binding_nonce_randomnesses = "" +hiding_nonces = "" +binding_nonces = "" +hiding_nonce_commitments = "" +binding_nonce_commitments = "" +binding_factor_inputs = "" +binding_factors = "" + +for i in range(1, max_participants + 1): + signer = signer_outputs.get(f"participant_{i}") + if signer is None: + hiding_nonce_randomnesses += f"{ '0x0,' * len_hnr} \n" + binding_nonce_randomnesses += f"{ '0x0,' * len_bnr} \n" + hiding_nonces += f"{ '0x0,' * len_hn} \n" + binding_nonces += f"{ '0x0,' * len_bn} \n" + hiding_nonce_commitments += f"{ '0x0,' * len_hnc} \n" + binding_nonce_commitments += f"{ '0x0,' * len_bnc} \n" + binding_factor_inputs += f"{ '0x0,' * len_bfi} \n" + binding_factors += f"{ '0x0,' * len_bf} \n" + else: + hiding_nonce_randomnesses += f"{ to_c_array(signer['hiding_nonce_randomness'])}, \n" + binding_nonce_randomnesses += f"{ to_c_array(signer['binding_nonce_randomness'])}, \n" + hiding_nonces += f"{ to_c_array(signer['hiding_nonce'])}, \n" + binding_nonces += f"{ to_c_array(signer['binding_nonce'])}, \n" + hiding_nonce_commitments += f"{ to_c_array(signer['hiding_nonce_commitment'])}, \n" + binding_nonce_commitments += f"{ to_c_array(signer['binding_nonce_commitment'])}, \n" + binding_factor_inputs += f"{ to_c_array(signer['binding_factor_input'])}, \n" + binding_factors += f"{ to_c_array(signer['binding_factor'])}, \n" + +print(f"static const unsigned char ietf_frost_hiding_nonce_randomnesses[] = {{{hiding_nonce_randomnesses}}};") +print(f"static const unsigned char ietf_frost_binding_nonce_randomnesses[] = {{{binding_nonce_randomnesses}}};") +print(f"static const unsigned char ietf_frost_hiding_nonces[] = {{{hiding_nonces}}};") +print(f"static const unsigned char ietf_frost_binding_nonces[] = {{{binding_nonces}}};") +print(f"static const unsigned char ietf_frost_hiding_nonce_commitments[] = {{{hiding_nonce_commitments}}};") +print(f"static const unsigned char ietf_frost_binding_nonce_commitments[] = {{{binding_nonce_commitments}}};") +print(f"static const unsigned char ietf_frost_binding_factor_inputs[] = {{{binding_factor_inputs}}};") +print(f"static const unsigned char ietf_frost_binding_factors[] = {{{binding_factors}}};") +print("") + + +print("/* Section: round_two.signer_outputs */") + +signer_outputs = doc['round_two']['signer_outputs'] +sig = doc['round_two']['sig'] + +len_sig_share = int(len(signer_outputs['participant_1']['sig_share'])/2) +print(f"#define IETF_FROST_SIG_SHARE_SIZE {str(len_sig_share)}") + +len_sig = int(len(sig)/2) +print(f"#define IETF_FROST_SIG_SIZE {str(len_sig)}") + +sig_shares = "" +for i in range(1, max_participants + 1): + signer = signer_outputs.get(f"participant_{i}") + if signer is None: + sig_shares += f"{ '0x0,' * len_sig_share} \n" + else: + sig_shares += f"{ to_c_array(signer['sig_share'])}, \n" + +print(f"static const unsigned char ietf_frost_sig_shares[] = {{{sig_shares}}};") +print(f"static const unsigned char ietf_frost_sig[] = {{{to_c_array(sig)}}};")