2022-04-23 07:49:30 +00:00
|
|
|
use core::{convert::TryFrom, cmp::min, fmt};
|
2022-04-22 01:36:18 +00:00
|
|
|
|
|
|
|
use rand_core::{RngCore, CryptoRng};
|
|
|
|
|
|
|
|
use ff::{Field, PrimeField};
|
|
|
|
use group::Group;
|
|
|
|
|
|
|
|
use crate::{Curve, MultisigParams, MultisigKeys, FrostError};
|
|
|
|
|
2022-04-23 07:49:30 +00:00
|
|
|
#[allow(non_snake_case)]
|
|
|
|
fn challenge<C: Curve>(l: usize, context: &str, R: &[u8], Am: &[u8]) -> C::F {
|
|
|
|
let mut c = Vec::with_capacity(8 + context.len() + R.len() + Am.len());
|
|
|
|
c.extend(&u64::try_from(l).unwrap().to_le_bytes());
|
|
|
|
c.extend(context.as_bytes());
|
|
|
|
c.extend(R); // R
|
|
|
|
c.extend(Am); // A of the first commitment, which is what we're proving we have the private key
|
|
|
|
// for
|
|
|
|
// m of the rest of the commitments, authenticating them
|
|
|
|
C::hash_to_F(&c)
|
|
|
|
}
|
|
|
|
|
2022-04-22 01:36:18 +00:00
|
|
|
// Implements steps 1 through 3 of round 1 of FROST DKG. Returns the coefficients, commitments, and
|
|
|
|
// the serialized commitments to be broadcasted over an authenticated channel to all parties
|
|
|
|
// TODO: This potentially could return a much more robust serialized message, including a signature
|
|
|
|
// of its entirety. The issue is it can't use its own key as it has no chain of custody behind it.
|
|
|
|
// While we could ask for a key to be passed in, explicitly declaring the needed for authenticated
|
|
|
|
// communications in the API itself, systems will likely already provide a authenticated
|
|
|
|
// communication method making this redundant. It also doesn't guarantee the system which passed
|
|
|
|
// the key is correctly using it, meaning we can only minimize risk so much
|
|
|
|
// One notable improvement would be to include the index in the message. While the system must
|
|
|
|
// still track this to determine if it's ready for the next step, and to remove duplicates, it
|
|
|
|
// would ensure no counterparties presume the same index and this system didn't mislabel a
|
|
|
|
// counterparty
|
|
|
|
fn generate_key_r1<R: RngCore + CryptoRng, C: Curve>(
|
|
|
|
rng: &mut R,
|
|
|
|
params: &MultisigParams,
|
|
|
|
context: &str,
|
|
|
|
) -> (Vec<C::F>, Vec<C::G>, Vec<u8>) {
|
|
|
|
let mut coefficients = Vec::with_capacity(params.t);
|
|
|
|
let mut commitments = Vec::with_capacity(params.t);
|
|
|
|
let mut serialized = Vec::with_capacity((C::G_len() * params.t) + C::G_len() + C::F_len());
|
|
|
|
for j in 0 .. params.t {
|
|
|
|
// Step 1: Generate t random values to form a polynomial with
|
|
|
|
coefficients.push(C::F::random(&mut *rng));
|
|
|
|
// Step 3: Generate public commitments
|
|
|
|
commitments.push(C::generator_table() * coefficients[j]);
|
|
|
|
// Serialize them for publication
|
|
|
|
serialized.extend(&C::G_to_bytes(&commitments[j]));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 2: Provide a proof of knowledge
|
|
|
|
// This can be deterministic as the PoK is a singleton never opened up to cooperative discussion
|
|
|
|
// There's also no reason to spend the time and effort to make this deterministic besides a
|
|
|
|
// general obsession with canonicity and determinism
|
|
|
|
let k = C::F::random(rng);
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
let R = C::generator_table() * k;
|
2022-04-23 07:49:30 +00:00
|
|
|
let c = challenge::<C>(params.i, context, &C::G_to_bytes(&R), &serialized);
|
2022-04-22 01:36:18 +00:00
|
|
|
let s = k + (coefficients[0] * c);
|
|
|
|
|
|
|
|
serialized.extend(&C::G_to_bytes(&R));
|
|
|
|
serialized.extend(&C::F_to_le_bytes(&s));
|
|
|
|
|
|
|
|
// Step 4: Broadcast
|
|
|
|
(coefficients, commitments, serialized)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify the received data from the first round of key generation
|
|
|
|
fn verify_r1<R: RngCore + CryptoRng, C: Curve>(
|
|
|
|
rng: &mut R,
|
|
|
|
params: &MultisigParams,
|
|
|
|
context: &str,
|
|
|
|
our_commitments: Vec<C::G>,
|
|
|
|
serialized: &[Vec<u8>],
|
|
|
|
) -> Result<Vec<Vec<C::G>>, FrostError> {
|
|
|
|
// Deserialize all of the commitments, validating the input buffers as needed
|
|
|
|
if serialized.len() != (params.n + 1) {
|
|
|
|
Err(
|
|
|
|
// Prevents a panic if serialized.len() == 0
|
|
|
|
FrostError::InvalidParticipantQuantity(params.n, serialized.len() - min(1, serialized.len()))
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expect a null set of commitments for index 0 so the vector is guaranteed to line up with
|
|
|
|
// actual indexes. Even if we did the offset internally, the system would need to write the vec
|
|
|
|
// with the same offset in mind. Therefore, this trick which is probably slightly less efficient
|
|
|
|
// yet keeps everything simple is preferred
|
|
|
|
if serialized[0] != vec![] {
|
|
|
|
Err(FrostError::NonEmptyParticipantZero)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let commitments_len = params.t * C::G_len();
|
|
|
|
let mut commitments = Vec::with_capacity(params.n + 1);
|
|
|
|
commitments.push(vec![]);
|
|
|
|
|
|
|
|
let signature_len = C::G_len() + C::F_len();
|
|
|
|
let mut first = true;
|
|
|
|
let mut scalars = Vec::with_capacity((params.n - 1) * 3);
|
|
|
|
let mut points = Vec::with_capacity((params.n - 1) * 3);
|
|
|
|
for l in 1 ..= params.n {
|
|
|
|
if l == params.i {
|
|
|
|
if serialized[l].len() != 0 {
|
|
|
|
Err(FrostError::DuplicatedIndex(l))?;
|
|
|
|
}
|
|
|
|
commitments.push(vec![]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if serialized[l].len() != (commitments_len + signature_len) {
|
|
|
|
// Return an error with an approximation for how many commitments were included
|
|
|
|
// Prevents errors if not even the signature was included
|
|
|
|
if serialized[l].len() < signature_len {
|
|
|
|
Err(FrostError::InvalidCommitmentQuantity(l, params.t, 0))?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(
|
|
|
|
FrostError::InvalidCommitmentQuantity(
|
|
|
|
l,
|
|
|
|
params.t,
|
|
|
|
// Could technically be x.y despite this returning x, yet any y is negligible
|
|
|
|
// It could help with debugging to know a partial piece of data was read but this error
|
|
|
|
// alone should be enough
|
|
|
|
(serialized[l].len() - signature_len) / C::G_len()
|
|
|
|
)
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
commitments.push(Vec::with_capacity(params.t));
|
|
|
|
for o in 0 .. params.t {
|
|
|
|
commitments[l].push(
|
|
|
|
C::G_from_slice(
|
|
|
|
&serialized[l][(o * C::G_len()) .. ((o + 1) * C::G_len())]
|
|
|
|
).map_err(|_| FrostError::InvalidCommitment(l))?
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 5: Validate each proof of knowledge (prep)
|
|
|
|
let mut u = C::F::one();
|
|
|
|
if !first {
|
|
|
|
u = C::F::random(&mut *rng);
|
|
|
|
}
|
|
|
|
|
|
|
|
scalars.push(u);
|
|
|
|
points.push(
|
|
|
|
C::G_from_slice(
|
|
|
|
&serialized[l][commitments_len .. commitments_len + C::G_len()]
|
|
|
|
).map_err(|_| FrostError::InvalidProofOfKnowledge(l))?
|
|
|
|
);
|
|
|
|
|
|
|
|
scalars.push(
|
|
|
|
-C::F_from_le_slice(
|
|
|
|
&serialized[l][commitments_len + C::G_len() .. serialized[l].len()]
|
|
|
|
).map_err(|_| FrostError::InvalidProofOfKnowledge(l))? * u
|
|
|
|
);
|
|
|
|
points.push(C::generator());
|
|
|
|
|
2022-04-23 07:49:30 +00:00
|
|
|
let c = challenge::<C>(
|
|
|
|
l,
|
|
|
|
context,
|
|
|
|
&serialized[l][commitments_len .. commitments_len + C::G_len()],
|
|
|
|
&serialized[l][0 .. commitments_len]
|
2022-04-22 01:36:18 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
if first {
|
|
|
|
scalars.push(c);
|
|
|
|
first = false;
|
|
|
|
} else {
|
|
|
|
scalars.push(c * u);
|
|
|
|
}
|
|
|
|
points.push(commitments[l][0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 5: Implementation
|
|
|
|
// Uses batch verification to optimize the success case dramatically
|
|
|
|
// On failure, the cost is now this + blame, yet that should happen infrequently
|
|
|
|
if C::multiexp_vartime(&scalars, &points) != C::G::identity() {
|
|
|
|
for l in 1 ..= params.n {
|
|
|
|
if l == params.i {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
let R = C::G_from_slice(
|
|
|
|
&serialized[l][commitments_len .. commitments_len + C::G_len()]
|
|
|
|
).map_err(|_| FrostError::InvalidProofOfKnowledge(l))?;
|
|
|
|
|
|
|
|
let s = C::F_from_le_slice(
|
|
|
|
&serialized[l][commitments_len + C::G_len() .. serialized[l].len()]
|
|
|
|
).map_err(|_| FrostError::InvalidProofOfKnowledge(l))?;
|
|
|
|
|
2022-04-23 07:49:30 +00:00
|
|
|
let c = challenge::<C>(
|
|
|
|
l,
|
|
|
|
context,
|
|
|
|
&serialized[l][commitments_len .. commitments_len + C::G_len()],
|
|
|
|
&serialized[l][0 .. commitments_len]
|
2022-04-22 01:36:18 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
if R != ((C::generator_table() * s) + (commitments[l][0] * (C::F::zero() - &c))) {
|
|
|
|
Err(FrostError::InvalidProofOfKnowledge(l))?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(FrostError::InternalError("batch validation is broken".to_string()))?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write in our own commitments
|
|
|
|
commitments[params.i] = our_commitments;
|
|
|
|
|
|
|
|
Ok(commitments)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn polynomial<F: PrimeField>(
|
|
|
|
coefficients: &[F],
|
|
|
|
i: usize
|
|
|
|
) -> F {
|
|
|
|
let i = F::from(u64::try_from(i).unwrap());
|
|
|
|
let mut share = F::zero();
|
|
|
|
for (idx, coefficient) in coefficients.iter().rev().enumerate() {
|
|
|
|
share += coefficient;
|
|
|
|
if idx != (coefficients.len() - 1) {
|
|
|
|
share *= i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
share
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements round 1, step 5 and round 2, step 1 of FROST key generation
|
|
|
|
// Returns our secret share part, commitments for the next step, and a vector for each
|
|
|
|
// counterparty to receive
|
|
|
|
fn generate_key_r2<R: RngCore + CryptoRng, C: Curve>(
|
|
|
|
rng: &mut R,
|
|
|
|
params: &MultisigParams,
|
|
|
|
context: &str,
|
|
|
|
coefficients: Vec<C::F>,
|
|
|
|
our_commitments: Vec<C::G>,
|
|
|
|
commitments: &[Vec<u8>],
|
|
|
|
) -> Result<(C::F, Vec<Vec<C::G>>, Vec<Vec<u8>>), FrostError> {
|
|
|
|
let commitments = verify_r1::<R, C>(rng, params, context, our_commitments, commitments)?;
|
|
|
|
|
|
|
|
// Step 1: Generate secret shares for all other parties
|
|
|
|
let mut res = Vec::with_capacity(params.n + 1);
|
|
|
|
res.push(vec![]);
|
|
|
|
for i in 1 ..= params.n {
|
|
|
|
// Don't push our own to the byte buffer which is meant to be sent around
|
|
|
|
// An app developer could accidentally send it. Best to keep this black boxed
|
|
|
|
if i == params.i {
|
|
|
|
res.push(vec![]);
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
res.push(C::F_to_le_bytes(&polynomial(&coefficients, i)));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate our own share
|
|
|
|
let share = polynomial(&coefficients, params.i);
|
|
|
|
|
|
|
|
// The secret shares are discarded here, not cleared. While any system which leaves its memory
|
|
|
|
// accessible is likely totally lost already, making the distinction meaningless when the key gen
|
|
|
|
// system acts as the signer system and therefore actively holds the signing key anyways, it
|
|
|
|
// should be overwritten with /dev/urandom in the name of security (which still doesn't meet
|
|
|
|
// requirements for secure data deletion yet those requirements expect hardware access which is
|
|
|
|
// far past what this library can reasonably counter)
|
|
|
|
// TODO: Zero out the coefficients
|
|
|
|
|
|
|
|
Ok((share, commitments, res))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Finishes round 2 and returns both the secret share and the serialized public key.
|
|
|
|
/// This key is not usable until all parties confirm they have completed the protocol without
|
|
|
|
/// issue, yet simply confirming protocol completion without issue is enough to confirm the same
|
|
|
|
/// key was generated as long as a lack of duplicated commitments was also confirmed when they were
|
|
|
|
/// broadcasted initially
|
|
|
|
fn complete_r2<C: Curve>(
|
|
|
|
params: MultisigParams,
|
|
|
|
share: C::F,
|
|
|
|
commitments: &[Vec<C::G>],
|
|
|
|
// Vec to preserve ownership
|
|
|
|
serialized: Vec<Vec<u8>>,
|
|
|
|
) -> Result<MultisigKeys<C>, FrostError> {
|
|
|
|
// Step 2. Verify each share
|
|
|
|
if serialized.len() != (params.n + 1) {
|
|
|
|
Err(
|
|
|
|
FrostError::InvalidParticipantQuantity(params.n, serialized.len() - min(1, serialized.len()))
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (commitments[0].len() != 0) || (serialized[0].len() != 0) {
|
|
|
|
Err(FrostError::NonEmptyParticipantZero)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Deserialize them
|
|
|
|
let mut shares: Vec<C::F> = vec![C::F::zero()];
|
|
|
|
for i in 1 .. serialized.len() {
|
|
|
|
if i == params.i {
|
|
|
|
if serialized[i].len() != 0 {
|
|
|
|
Err(FrostError::DuplicatedIndex(i))?;
|
|
|
|
}
|
|
|
|
shares.push(C::F::zero());
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
shares.push(C::F_from_le_slice(&serialized[i]).map_err(|_| FrostError::InvalidShare(i))?);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for l in 1 ..= params.n {
|
|
|
|
if l == params.i {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let i_scalar = C::F::from(u64::try_from(params.i).unwrap());
|
|
|
|
let mut exp = C::F::one();
|
|
|
|
let mut exps = Vec::with_capacity(params.t);
|
|
|
|
for _ in 0 .. params.t {
|
|
|
|
exps.push(exp);
|
|
|
|
exp *= i_scalar;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Doesn't use multiexp_vartime with -shares[l] due to not being able to push to commitments
|
|
|
|
if C::multiexp_vartime(&exps, &commitments[l]) != (C::generator_table() * shares[l]) {
|
|
|
|
Err(FrostError::InvalidCommitment(l))?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Clear the original share
|
|
|
|
|
|
|
|
let mut secret_share = share;
|
|
|
|
for remote_share in shares {
|
|
|
|
secret_share += remote_share;
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut verification_shares = vec![C::G::identity()];
|
|
|
|
for i in 1 ..= params.n {
|
|
|
|
let mut exps = vec![];
|
|
|
|
let mut cs = vec![];
|
|
|
|
for j in 1 ..= params.n {
|
|
|
|
for k in 0 .. params.t {
|
|
|
|
let mut exp = C::F::one();
|
|
|
|
for _ in 0 .. k {
|
|
|
|
exp *= C::F::from(u64::try_from(i).unwrap());
|
|
|
|
}
|
|
|
|
exps.push(exp);
|
|
|
|
cs.push(commitments[j][k]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
verification_shares.push(C::multiexp_vartime(&exps, &cs));
|
|
|
|
}
|
|
|
|
|
|
|
|
debug_assert_eq!(
|
|
|
|
C::generator_table() * secret_share,
|
|
|
|
verification_shares[params.i]
|
|
|
|
);
|
|
|
|
|
|
|
|
let mut group_key = C::G::identity();
|
|
|
|
for j in 1 ..= params.n {
|
|
|
|
group_key += commitments[j][0];
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Clear serialized and shares
|
|
|
|
|
|
|
|
Ok(MultisigKeys { params, secret_share, group_key, verification_shares, offset: None } )
|
|
|
|
}
|
|
|
|
|
|
|
|
/// State of a Key Generation machine
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
|
|
|
pub enum State {
|
|
|
|
Fresh,
|
|
|
|
GeneratedCoefficients,
|
|
|
|
GeneratedSecretShares,
|
|
|
|
Complete,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for State {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
write!(f, "{:?}", self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// State machine which manages key generation
|
2022-04-23 07:49:30 +00:00
|
|
|
#[allow(non_snake_case)]
|
2022-04-22 01:36:18 +00:00
|
|
|
pub struct StateMachine<C: Curve> {
|
|
|
|
params: MultisigParams,
|
|
|
|
context: String,
|
|
|
|
state: State,
|
|
|
|
coefficients: Option<Vec<C::F>>,
|
|
|
|
our_commitments: Option<Vec<C::G>>,
|
|
|
|
secret: Option<C::F>,
|
2022-04-23 07:49:30 +00:00
|
|
|
commitments: Option<Vec<Vec<C::G>>>
|
2022-04-22 01:36:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<C: Curve> StateMachine<C> {
|
|
|
|
/// Creates a new machine to generate a key for the specified curve in the specified multisig
|
|
|
|
// The context string must be unique among multisigs
|
|
|
|
pub fn new(params: MultisigParams, context: String) -> StateMachine<C> {
|
|
|
|
StateMachine {
|
|
|
|
params,
|
|
|
|
context,
|
|
|
|
state: State::Fresh,
|
|
|
|
coefficients: None,
|
|
|
|
our_commitments: None,
|
|
|
|
secret: None,
|
2022-04-23 07:49:30 +00:00
|
|
|
commitments: None
|
2022-04-22 01:36:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Start generating a key according to the FROST DKG spec
|
|
|
|
/// Returns a serialized list of commitments to be sent to all parties over an authenticated
|
|
|
|
/// channel. If any party submits multiple sets of commitments, they MUST be treated as malicious
|
|
|
|
pub fn generate_coefficients<R: RngCore + CryptoRng>(
|
|
|
|
&mut self,
|
|
|
|
rng: &mut R
|
|
|
|
) -> Result<Vec<u8>, FrostError> {
|
|
|
|
if self.state != State::Fresh {
|
|
|
|
Err(FrostError::InvalidKeyGenTransition(State::Fresh, self.state))?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let (coefficients, commitments, serialized) = generate_key_r1::<R, C>(
|
|
|
|
rng,
|
|
|
|
&self.params,
|
|
|
|
&self.context,
|
|
|
|
);
|
|
|
|
|
|
|
|
self.coefficients = Some(coefficients);
|
|
|
|
self.our_commitments = Some(commitments);
|
|
|
|
self.state = State::GeneratedCoefficients;
|
|
|
|
Ok(serialized)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Continue generating a key
|
|
|
|
/// Takes in everyone else's commitments, which are expected to be in a Vec where participant
|
|
|
|
/// index = Vec index. An empty vector is expected at index 0 to allow for this. An empty vector
|
|
|
|
/// is also expected at index i which is locally handled. Returns a byte vector representing a
|
|
|
|
/// secret share for each other participant which should be encrypted before sending
|
|
|
|
pub fn generate_secret_shares<R: RngCore + CryptoRng>(
|
|
|
|
&mut self,
|
|
|
|
rng: &mut R,
|
|
|
|
commitments: Vec<Vec<u8>>,
|
|
|
|
) -> Result<Vec<Vec<u8>>, FrostError> {
|
|
|
|
if self.state != State::GeneratedCoefficients {
|
|
|
|
Err(FrostError::InvalidKeyGenTransition(State::GeneratedCoefficients, self.state))?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let (secret, commitments, shares) = generate_key_r2::<R, C>(
|
|
|
|
rng,
|
|
|
|
&self.params,
|
|
|
|
&self.context,
|
|
|
|
self.coefficients.take().unwrap(),
|
|
|
|
self.our_commitments.take().unwrap(),
|
|
|
|
&commitments,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
self.secret = Some(secret);
|
|
|
|
self.commitments = Some(commitments);
|
|
|
|
self.state = State::GeneratedSecretShares;
|
|
|
|
Ok(shares)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Complete key generation
|
|
|
|
/// Takes in everyone elses' shares submitted to us as a Vec, expecting participant index =
|
|
|
|
/// Vec index with an empty vector at index 0 and index i. Returns a byte vector representing the
|
|
|
|
/// group's public key, while setting a valid secret share inside the machine. > t participants
|
|
|
|
/// must report completion without issue before this key can be considered usable, yet you should
|
|
|
|
/// wait for all participants to report as such
|
|
|
|
pub fn complete(
|
|
|
|
&mut self,
|
|
|
|
shares: Vec<Vec<u8>>,
|
|
|
|
) -> Result<MultisigKeys<C>, FrostError> {
|
|
|
|
if self.state != State::GeneratedSecretShares {
|
|
|
|
Err(FrostError::InvalidKeyGenTransition(State::GeneratedSecretShares, self.state))?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let keys = complete_r2(
|
|
|
|
self.params,
|
|
|
|
self.secret.take().unwrap(),
|
|
|
|
&self.commitments.take().unwrap(),
|
|
|
|
shares,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
self.state = State::Complete;
|
|
|
|
Ok(keys)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn params(&self) -> MultisigParams {
|
|
|
|
self.params.clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn state(&self) -> State {
|
|
|
|
self.state
|
|
|
|
}
|
|
|
|
}
|