Move FROST to HashMaps

Honestly, the borrowed keys are frustrating, and this probably reduces 
performance while no longer offering an order when iterating. That said, 
they enable full u16 indexing and should mildly improve the API.

Cleans the Proof of Knowledge handling present in key gen.
This commit is contained in:
Luke Parker
2022-05-24 21:41:14 -04:00
parent 5ff65bd268
commit d10c6e16dc
12 changed files with 585 additions and 747 deletions

View File

@@ -28,7 +28,7 @@ pub trait Algorithm<C: Curve>: Clone {
fn process_addendum(
&mut self,
params: &MultisigView<C>,
l: usize,
l: u16,
commitments: &[C::G; 2],
serialized: &[u8],
) -> Result<(), FrostError>;
@@ -131,7 +131,7 @@ impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
fn process_addendum(
&mut self,
_: &MultisigView<C>,
_: usize,
_: u16,
_: &[C::G; 2],
_: &[u8],
) -> Result<(), FrostError> {

View File

@@ -1,16 +1,17 @@
use core::{convert::TryFrom, cmp::min, fmt};
use core::{convert::TryFrom, fmt};
use std::collections::HashMap;
use rand_core::{RngCore, CryptoRng};
use ff::{Field, PrimeField};
use group::Group;
use crate::{Curve, MultisigParams, MultisigKeys, FrostError};
use crate::{Curve, MultisigParams, MultisigKeys, FrostError, validate_map};
#[allow(non_snake_case)]
fn challenge<C: Curve>(l: usize, context: &str, R: &[u8], Am: &[u8]) -> C::F {
fn challenge<C: Curve>(l: u16, context: &str, R: &[u8], Am: &[u8]) -> C::F {
let mut c = Vec::with_capacity(2 + context.len() + R.len() + Am.len());
c.extend(&u16::try_from(l).unwrap().to_be_bytes());
c.extend(l.to_be_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
@@ -21,48 +22,41 @@ fn challenge<C: Curve>(l: usize, context: &str, R: &[u8], Am: &[u8]) -> C::F {
// 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 {
) -> (Vec<C::F>, Vec<u8>) {
let t = usize::from(params.t);
let mut coefficients = Vec::with_capacity(t);
let mut commitments = Vec::with_capacity(t);
let mut serialized = Vec::with_capacity((C::G_len() * t) + C::G_len() + C::F_len());
for i in 0 .. 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]);
commitments.push(C::generator_table() * coefficients[i]);
// Serialize them for publication
serialized.extend(&C::G_to_bytes(&commitments[j]));
serialized.extend(&C::G_to_bytes(&commitments[i]));
}
// 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);
let r = C::F::random(rng);
#[allow(non_snake_case)]
let R = C::generator_table() * k;
let c = challenge::<C>(params.i, context, &C::G_to_bytes(&R), &serialized);
let s = k + (coefficients[0] * c);
let R = C::generator_table() * r;
let s = r + (
coefficients[0] * challenge::<C>(params.i(), context, &C::G_to_bytes(&R), &serialized)
);
serialized.extend(&C::G_to_bytes(&R));
serialized.extend(&C::F_to_bytes(&s));
// Step 4: Broadcast
(coefficients, commitments, serialized)
(coefficients, serialized)
}
// Verify the received data from the first round of key generation
@@ -70,69 +64,48 @@ 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()))
)?;
}
our_commitments: Vec<u8>,
mut serialized: HashMap<u16, Vec<u8>>,
) -> Result<HashMap<u16, Vec<C::G>>, FrostError> {
validate_map(
&mut serialized,
&(1 ..= params.n()).into_iter().collect::<Vec<_>>(),
(params.i(), our_commitments)
)?;
// 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 = usize::from(params.t()) * C::G_len();
let commitments_len = params.t * C::G_len();
let mut commitments = Vec::with_capacity(params.n + 1);
commitments.push(vec![]);
let mut commitments = HashMap::new();
#[allow(non_snake_case)]
let R_bytes = |l| &serialized[&l][commitments_len .. commitments_len + C::G_len()];
#[allow(non_snake_case)]
let R = |l| C::G_from_slice(R_bytes(l)).map_err(|_| FrostError::InvalidProofOfKnowledge(l));
#[allow(non_snake_case)]
let Am = |l| &serialized[&l][0 .. commitments_len];
let s = |l| C::F_from_slice(
&serialized[&l][commitments_len + C::G_len() ..]
).map_err(|_| FrostError::InvalidProofOfKnowledge(l));
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(
let mut scalars = Vec::with_capacity((usize::from(params.n()) - 1) * 3);
let mut points = Vec::with_capacity((usize::from(params.n()) - 1) * 3);
for l in 1 ..= params.n() {
let mut these_commitments = vec![];
for c in 0 .. usize::from(params.t()) {
these_commitments.push(
C::G_from_slice(
&serialized[l][(o * C::G_len()) .. ((o + 1) * C::G_len())]
).map_err(|_| FrostError::InvalidCommitment(l))?
&serialized[&l][(c * C::G_len()) .. ((c + 1) * C::G_len())]
).map_err(|_| FrostError::InvalidCommitment(l.try_into().unwrap()))?
);
}
commitments.insert(l, these_commitments);
// Don't bother validating our own proof of knowledge
if l == params.i() {
continue;
}
// Step 5: Validate each proof of knowledge (prep)
let mut u = C::F::one();
@@ -140,62 +113,35 @@ fn verify_r1<R: RngCore + CryptoRng, C: Curve>(
u = C::F::random(&mut *rng);
}
// uR
scalars.push(u);
points.push(
C::G_from_slice(
&serialized[l][commitments_len .. commitments_len + C::G_len()]
).map_err(|_| FrostError::InvalidProofOfKnowledge(l))?
);
points.push(R(l)?);
scalars.push(
-C::F_from_slice(
&serialized[l][commitments_len + C::G_len() .. serialized[l].len()]
).map_err(|_| FrostError::InvalidProofOfKnowledge(l))? * u
);
// -usG
scalars.push(-s(l)? * u);
points.push(C::generator());
let c = challenge::<C>(
l,
context,
&serialized[l][commitments_len .. commitments_len + C::G_len()],
&serialized[l][0 .. commitments_len]
);
if first {
scalars.push(c);
first = false;
} else {
scalars.push(c * u);
}
points.push(commitments[l][0]);
// ucA
let c = challenge::<C>(l, context, R_bytes(l), Am(l));
scalars.push(if first { first = false; c } else { 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
// s = r + ca
// sG == R + cA
// R + cA - sG == 0
if C::multiexp_vartime(&scalars, &points) != C::G::identity() {
for l in 1 ..= params.n {
if l == params.i {
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_slice(
&serialized[l][commitments_len + C::G_len() .. serialized[l].len()]
).map_err(|_| FrostError::InvalidProofOfKnowledge(l))?;
let c = challenge::<C>(
l,
context,
&serialized[l][commitments_len .. commitments_len + C::G_len()],
&serialized[l][0 .. commitments_len]
);
if R != ((C::generator_table() * s) + (commitments[l][0] * (C::F::zero() - &c))) {
if (C::generator_table() * s(l)?) != (
R(l)? + (commitments[&l][0] * challenge::<C>(l, context, R_bytes(l), Am(l)))
) {
Err(FrostError::InvalidProofOfKnowledge(l))?;
}
}
@@ -203,22 +149,19 @@ fn verify_r1<R: RngCore + CryptoRng, C: Curve>(
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
l: u16
) -> F {
let i = F::from(u64::try_from(i).unwrap());
let l = F::from(u64::from(l));
let mut share = F::zero();
for (idx, coefficient) in coefficients.iter().rev().enumerate() {
share += coefficient;
if idx != (coefficients.len() - 1) {
share *= i;
share *= l;
}
}
share
@@ -232,27 +175,25 @@ fn generate_key_r2<R: RngCore + CryptoRng, C: Curve>(
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> {
our_commitments: Vec<u8>,
commitments: HashMap<u16, Vec<u8>>,
) -> Result<(C::F, HashMap<u16, Vec<C::G>>, HashMap<u16, 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
let mut res = HashMap::new();
for l in 1 ..= params.n() {
// Don't insert our own shares 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
if l == params.i() {
continue;
}
res.push(C::F_to_bytes(&polynomial(&coefficients, i)));
res.insert(l, C::F_to_bytes(&polynomial(&coefficients, l)));
}
// Calculate our own share
let share = polynomial(&coefficients, params.i);
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
@@ -273,87 +214,67 @@ fn generate_key_r2<R: RngCore + CryptoRng, C: Curve>(
fn complete_r2<C: Curve>(
params: MultisigParams,
share: C::F,
commitments: &[Vec<C::G>],
commitments: HashMap<u16, Vec<C::G>>,
// Vec to preserve ownership
serialized: Vec<Vec<u8>>,
mut serialized: HashMap<u16, Vec<u8>>,
) -> Result<MultisigKeys<C>, FrostError> {
validate_map(
&mut serialized,
&(1 ..= params.n()).into_iter().collect::<Vec<_>>(),
(params.i(), C::F_to_bytes(&share))
)?;
// Step 2. Verify each share
if serialized.len() != (params.n + 1) {
Err(
FrostError::InvalidParticipantQuantity(params.n, serialized.len() - min(1, serialized.len()))
)?;
let mut shares = HashMap::new();
for (l, share) in serialized {
shares.insert(l, C::F_from_slice(&share).map_err(|_| FrostError::InvalidShare(params.i()))?);
}
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_slice(&serialized[i]).map_err(|_| FrostError::InvalidShare(i))?);
}
for l in 1 ..= params.n {
if l == params.i {
for (l, share) in &shares {
if *l == params.i() {
continue;
}
let i_scalar = C::F::from(u64::try_from(params.i).unwrap());
let i_scalar = C::F::from(params.i.into());
let mut exp = C::F::one();
let mut exps = Vec::with_capacity(params.t);
for _ in 0 .. params.t {
let mut exps = Vec::with_capacity(usize::from(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))?;
if C::multiexp_vartime(&exps, &commitments[&l]) != (C::generator_table() * *share) {
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 secret_share = C::F::zero();
for (_, share) in shares {
secret_share += share;
}
let mut verification_shares = vec![C::G::identity()];
for i in 1 ..= params.n {
let mut verification_shares = HashMap::new();
for l in 1 ..= params.n() {
let mut exps = vec![];
let mut cs = vec![];
for j in 1 ..= params.n {
for k in 0 .. params.t {
for i in 1 ..= params.n() {
for j in 0 .. params.t() {
let mut exp = C::F::one();
for _ in 0 .. k {
exp *= C::F::from(u64::try_from(i).unwrap());
for _ in 0 .. j {
exp *= C::F::from(u64::try_from(l).unwrap());
}
exps.push(exp);
cs.push(commitments[j][k]);
cs.push(commitments[&i][usize::from(j)]);
}
}
verification_shares.push(C::multiexp_vartime(&exps, &cs));
verification_shares.insert(l, C::multiexp_vartime(&exps, &cs));
}
debug_assert_eq!(C::generator_table() * secret_share, verification_shares[&params.i()]);
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];
}
let group_key = commitments.iter().map(|(_, commitments)| commitments[0]).sum();
// TODO: Clear serialized and shares
@@ -382,9 +303,9 @@ pub struct StateMachine<C: Curve> {
context: String,
state: State,
coefficients: Option<Vec<C::F>>,
our_commitments: Option<Vec<C::G>>,
our_commitments: Option<Vec<u8>>,
secret: Option<C::F>,
commitments: Option<Vec<Vec<C::G>>>
commitments: Option<HashMap<u16, Vec<C::G>>>
}
impl<C: Curve> StateMachine<C> {
@@ -413,14 +334,14 @@ impl<C: Curve> StateMachine<C> {
Err(FrostError::InvalidKeyGenTransition(State::Fresh, self.state))?;
}
let (coefficients, commitments, serialized) = generate_key_r1::<R, C>(
let (coefficients, serialized) = generate_key_r1::<R, C>(
rng,
&self.params,
&self.context,
);
self.coefficients = Some(coefficients);
self.our_commitments = Some(commitments);
self.our_commitments = Some(serialized.clone());
self.state = State::GeneratedCoefficients;
Ok(serialized)
}
@@ -433,8 +354,8 @@ impl<C: Curve> StateMachine<C> {
pub fn generate_secret_shares<R: RngCore + CryptoRng>(
&mut self,
rng: &mut R,
commitments: Vec<Vec<u8>>,
) -> Result<Vec<Vec<u8>>, FrostError> {
commitments: HashMap<u16, Vec<u8>>,
) -> Result<HashMap<u16, Vec<u8>>, FrostError> {
if self.state != State::GeneratedCoefficients {
Err(FrostError::InvalidKeyGenTransition(State::GeneratedCoefficients, self.state))?;
}
@@ -445,7 +366,7 @@ impl<C: Curve> StateMachine<C> {
&self.context,
self.coefficients.take().unwrap(),
self.our_commitments.take().unwrap(),
&commitments,
commitments,
)?;
self.secret = Some(secret);
@@ -462,8 +383,8 @@ impl<C: Curve> StateMachine<C> {
/// wait for all participants to report as such
pub fn complete(
&mut self,
shares: Vec<Vec<u8>>,
) -> Result<MultisigKeys<C>, FrostError> {
shares: HashMap<u16, Vec<u8>>,
) -> Result<MultisigKeys<C>, FrostError> {
if self.state != State::GeneratedSecretShares {
Err(FrostError::InvalidKeyGenTransition(State::GeneratedSecretShares, self.state))?;
}
@@ -471,7 +392,7 @@ impl<C: Curve> StateMachine<C> {
let keys = complete_r2(
self.params,
self.secret.take().unwrap(),
&self.commitments.take().unwrap(),
self.commitments.take().unwrap(),
shares,
)?;

View File

@@ -1,4 +1,5 @@
use core::{ops::Mul, fmt::Debug};
use std::collections::HashMap;
use thiserror::Error;
@@ -10,7 +11,6 @@ pub use multiexp::multiexp_vartime;
pub mod key_gen;
pub mod algorithm;
pub mod sign;
use sign::lagrange;
/// Set of errors for curve-related operations, namely encoding and decoding
#[derive(Error, Debug)]
@@ -118,27 +118,23 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug {
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct MultisigParams {
/// Participants needed to sign on behalf of the group
t: usize,
t: u16,
/// Amount of participants
n: usize,
n: u16,
/// Index of the participant being acted for
i: usize,
i: u16,
}
impl MultisigParams {
pub fn new(
t: usize,
n: usize,
i: usize
t: u16,
n: u16,
i: u16
) -> Result<MultisigParams, FrostError> {
if (t == 0) || (n == 0) {
Err(FrostError::ZeroParameter(t, n))?;
}
if u16::try_from(n).is_err() {
Err(FrostError::TooManyParticipants(n, u16::MAX))?;
}
// When t == n, this shouldn't be used (MuSig2 and other variants of MuSig exist for a reason),
// but it's not invalid to do so
if t > n {
@@ -151,21 +147,21 @@ impl MultisigParams {
Ok(MultisigParams{ t, n, i })
}
pub fn t(&self) -> usize { self.t }
pub fn n(&self) -> usize { self.n }
pub fn i(&self) -> usize { self.i }
pub fn t(&self) -> u16 { self.t }
pub fn n(&self) -> u16 { self.n }
pub fn i(&self) -> u16 { self.i }
}
#[derive(Error, Debug)]
pub enum FrostError {
#[error("a parameter was 0 (required {0}, participants {1})")]
ZeroParameter(usize, usize),
ZeroParameter(u16, u16),
#[error("too many participants (max {1}, got {0})")]
TooManyParticipants(usize, u16),
#[error("invalid amount of required participants (max {1}, got {0})")]
InvalidRequiredQuantity(usize, usize),
InvalidRequiredQuantity(u16, u16),
#[error("invalid participant index (0 < index <= {0}, yet index is {1})")]
InvalidParticipantIndex(usize, usize),
InvalidParticipantIndex(u16, u16),
#[error("invalid signing set ({0})")]
InvalidSigningSet(String),
@@ -173,16 +169,14 @@ pub enum FrostError {
InvalidParticipantQuantity(usize, usize),
#[error("duplicated participant index ({0})")]
DuplicatedIndex(usize),
#[error("participant 0 provided data despite not existing")]
NonEmptyParticipantZero,
#[error("invalid commitment quantity (participant {0}, expected {1}, got {2})")]
InvalidCommitmentQuantity(usize, usize, usize),
#[error("missing participant {0}")]
MissingParticipant(u16),
#[error("invalid commitment (participant {0})")]
InvalidCommitment(usize),
InvalidCommitment(u16),
#[error("invalid proof of knowledge (participant {0})")]
InvalidProofOfKnowledge(usize),
InvalidProofOfKnowledge(u16),
#[error("invalid share (participant {0})")]
InvalidShare(usize),
InvalidShare(u16),
#[error("invalid key generation state machine transition (expected {0}, was {1})")]
InvalidKeyGenTransition(key_gen::State, key_gen::State),
@@ -197,9 +191,9 @@ pub enum FrostError {
#[derive(Clone)]
pub struct MultisigView<C: Curve> {
group_key: C::G,
included: Vec<usize>,
included: Vec<u16>,
secret_share: C::F,
verification_shares: Vec<C::G>,
verification_shares: HashMap<u16, C::G>,
}
impl<C: Curve> MultisigView<C> {
@@ -207,7 +201,7 @@ impl<C: Curve> MultisigView<C> {
self.group_key
}
pub fn included(&self) -> Vec<usize> {
pub fn included(&self) -> Vec<u16> {
self.included.clone()
}
@@ -215,11 +209,33 @@ impl<C: Curve> MultisigView<C> {
self.secret_share
}
pub fn verification_share(&self, l: usize) -> C::G {
self.verification_shares[l]
pub fn verification_share(&self, l: u16) -> C::G {
self.verification_shares[&l]
}
}
/// Calculate the lagrange coefficient for a signing set
pub fn lagrange<F: PrimeField>(
i: u16,
included: &[u16],
) -> F {
let mut num = F::one();
let mut denom = F::one();
for l in included {
if i == *l {
continue;
}
let share = F::from(u64::try_from(*l).unwrap());
num *= share;
denom *= share - F::from(u64::try_from(i).unwrap());
}
// Safe as this will only be 0 if we're part of the above loop
// (which we have an if case to avoid)
num * denom.invert().unwrap()
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct MultisigKeys<C: Curve> {
/// Multisig Parameters
@@ -230,7 +246,7 @@ pub struct MultisigKeys<C: Curve> {
/// Group key
group_key: C::G,
/// Verification shares
verification_shares: Vec<C::G>,
verification_shares: HashMap<u16, C::G>,
/// Offset applied to these keys
offset: Option<C::F>,
@@ -258,12 +274,12 @@ impl<C: Curve> MultisigKeys<C> {
self.group_key
}
pub fn verification_shares(&self) -> Vec<C::G> {
pub fn verification_shares(&self) -> HashMap<u16, C::G> {
self.verification_shares.clone()
}
pub fn view(&self, included: &[usize]) -> Result<MultisigView<C>, FrostError> {
if (included.len() < self.params.t) || (self.params.n < included.len()) {
pub fn view(&self, included: &[u16]) -> Result<MultisigView<C>, FrostError> {
if (included.len() < self.params.t.into()) || (usize::from(self.params.n) < included.len()) {
Err(FrostError::InvalidSigningSet("invalid amount of participants included".to_string()))?;
}
@@ -274,16 +290,18 @@ impl<C: Curve> MultisigKeys<C> {
Ok(MultisigView {
group_key: self.group_key + (C::generator_table() * offset),
secret_share: secret_share + offset_share,
verification_shares: self.verification_shares.clone().iter().enumerate().map(
|(l, share)| (*share * lagrange::<C::F>(l, &included)) +
(C::generator_table() * offset_share)
verification_shares: self.verification_shares.iter().map(
|(l, share)| (
*l,
(*share * lagrange::<C::F>(*l, &included)) + (C::generator_table() * offset_share)
)
).collect(),
included: included.to_vec(),
})
}
pub fn serialized_len(n: usize) -> usize {
1 + usize::from(C::id_len()) + (3 * 8) + C::F_len() + C::G_len() + (n * C::G_len())
pub fn serialized_len(n: u16) -> usize {
1 + usize::from(C::id_len()) + (3 * 2) + C::F_len() + C::G_len() + (usize::from(n) * C::G_len())
}
pub fn serialize(&self) -> Vec<u8> {
@@ -292,13 +310,13 @@ impl<C: Curve> MultisigKeys<C> {
);
serialized.push(C::id_len());
serialized.extend(C::id().as_bytes());
serialized.extend(&(self.params.n as u64).to_le_bytes());
serialized.extend(&(self.params.t as u64).to_le_bytes());
serialized.extend(&(self.params.i as u64).to_le_bytes());
serialized.extend(&self.params.n.to_le_bytes());
serialized.extend(&self.params.t.to_le_bytes());
serialized.extend(&self.params.i.to_le_bytes());
serialized.extend(&C::F_to_bytes(&self.secret_share));
serialized.extend(&C::G_to_bytes(&self.group_key));
for i in 1 ..= self.params.n {
serialized.extend(&C::G_to_bytes(&self.verification_shares[i]));
for l in 1 ..= self.params.n.into() {
serialized.extend(&C::G_to_bytes(&self.verification_shares[&l]));
}
serialized
@@ -330,19 +348,16 @@ impl<C: Curve> MultisigKeys<C> {
Err(FrostError::InternalError("participant quantity wasn't included".to_string()))?;
}
let n = u64::from_le_bytes(serialized[cursor .. (cursor + 8)].try_into().unwrap()).try_into()
.map_err(|_| FrostError::InternalError("parameter doesn't fit into usize".to_string()))?;
cursor += 8;
let n = u16::from_le_bytes(serialized[cursor .. (cursor + 2)].try_into().unwrap());
cursor += 2;
if serialized.len() != MultisigKeys::<C>::serialized_len(n) {
Err(FrostError::InternalError("incorrect serialization length".to_string()))?;
}
let t = u64::from_le_bytes(serialized[cursor .. (cursor + 8)].try_into().unwrap()).try_into()
.map_err(|_| FrostError::InternalError("parameter doesn't fit into usize".to_string()))?;
cursor += 8;
let i = u64::from_le_bytes(serialized[cursor .. (cursor + 8)].try_into().unwrap()).try_into()
.map_err(|_| FrostError::InternalError("parameter doesn't fit into usize".to_string()))?;
cursor += 8;
let t = u16::from_le_bytes(serialized[cursor .. (cursor + 2)].try_into().unwrap());
cursor += 2;
let i = u16::from_le_bytes(serialized[cursor .. (cursor + 2)].try_into().unwrap());
cursor += 2;
let secret_share = C::F_from_slice(&serialized[cursor .. (cursor + C::F_len())])
.map_err(|_| FrostError::InternalError("invalid secret share".to_string()))?;
@@ -351,10 +366,10 @@ impl<C: Curve> MultisigKeys<C> {
.map_err(|_| FrostError::InternalError("invalid group key".to_string()))?;
cursor += C::G_len();
let mut verification_shares = vec![C::G::identity()];
verification_shares.reserve_exact(n + 1);
for _ in 0 .. n {
verification_shares.push(
let mut verification_shares = HashMap::new();
for l in 1 ..= n {
verification_shares.insert(
l,
C::G_from_slice(&serialized[cursor .. (cursor + C::G_len())])
.map_err(|_| FrostError::InternalError("invalid verification share".to_string()))?
);
@@ -373,3 +388,24 @@ impl<C: Curve> MultisigKeys<C> {
)
}
}
// Validate a map of serialized values to have the expected included participants
pub(crate) fn validate_map<T>(
map: &mut HashMap<u16, T>,
included: &[u16],
ours: (u16, T)
) -> Result<(), FrostError> {
map.insert(ours.0, ours.1);
if map.len() != included.len() {
Err(FrostError::InvalidParticipantQuantity(included.len(), map.len()))?;
}
for included in included {
if !map.contains_key(included) {
Err(FrostError::MissingParticipant(*included))?;
}
}
Ok(())
}

View File

@@ -1,36 +1,20 @@
use core::{convert::TryFrom, cmp::min, fmt};
use std::rc::Rc;
use core::fmt;
use std::{rc::Rc, collections::HashMap};
use rand_core::{RngCore, CryptoRng};
use ff::{Field, PrimeField};
use ff::Field;
use group::Group;
use transcript::Transcript;
use crate::{Curve, FrostError, MultisigParams, MultisigKeys, MultisigView, algorithm::Algorithm};
/// Calculate the lagrange coefficient
pub fn lagrange<F: PrimeField>(
i: usize,
included: &[usize],
) -> F {
let mut num = F::one();
let mut denom = F::one();
for l in included {
if i == *l {
continue;
}
let share = F::from(u64::try_from(*l).unwrap());
num *= share;
denom *= share - F::from(u64::try_from(i).unwrap());
}
// Safe as this will only be 0 if we're part of the above loop
// (which we have an if case to avoid)
num * denom.invert().unwrap()
}
use crate::{
Curve,
FrostError,
MultisigParams, MultisigKeys, MultisigView,
algorithm::Algorithm,
validate_map
};
/// Pairing of an Algorithm with a MultisigKeys instance and this specific signing set
#[derive(Clone)]
@@ -45,13 +29,13 @@ impl<C: Curve, A: Algorithm<C>> Params<C, A> {
pub fn new(
algorithm: A,
keys: Rc<MultisigKeys<C>>,
included: &[usize],
included: &[u16],
) -> Result<Params<C, A>, FrostError> {
let mut included = included.to_vec();
(&mut included).sort_unstable();
// Included < threshold
if included.len() < keys.params.t {
if included.len() < usize::from(keys.params.t) {
Err(FrostError::InvalidSigningSet("not enough signers".to_string()))?;
}
// Invalid index
@@ -65,7 +49,7 @@ impl<C: Curve, A: Algorithm<C>> Params<C, A> {
// Same signer included multiple times
for i in 0 .. included.len() - 1 {
if included[i] == included[i + 1] {
Err(FrostError::DuplicatedIndex(included[i]))?;
Err(FrostError::DuplicatedIndex(included[i].into()))?;
}
}
// Not included
@@ -88,7 +72,6 @@ impl<C: Curve, A: Algorithm<C>> Params<C, A> {
struct PreprocessPackage<C: Curve> {
nonces: [C::F; 2],
commitments: [C::G; 2],
serialized: Vec<u8>,
}
@@ -111,14 +94,14 @@ fn preprocess<R: RngCore + CryptoRng, C: Curve, A: Algorithm<C>>(
)
);
PreprocessPackage { nonces, commitments, serialized }
PreprocessPackage { nonces, serialized }
}
#[allow(non_snake_case)]
struct Package<C: Curve> {
Ris: Vec<C::G>,
Ris: HashMap<u16, C::G>,
R: C::G,
share: C::F
share: Vec<u8>
}
// Has every signer perform the role of the signature aggregator
@@ -127,22 +110,15 @@ struct Package<C: Curve> {
fn sign_with_share<C: Curve, A: Algorithm<C>>(
params: &mut Params<C, A>,
our_preprocess: PreprocessPackage<C>,
commitments: &[Option<Vec<u8>>],
mut commitments: HashMap<u16, Vec<u8>>,
msg: &[u8],
) -> Result<(Package<C>, Vec<u8>), FrostError> {
let multisig_params = params.multisig_params();
if commitments.len() != (multisig_params.n + 1) {
Err(
FrostError::InvalidParticipantQuantity(
multisig_params.n,
commitments.len() - min(1, commitments.len())
)
)?;
}
if commitments[0].is_some() {
Err(FrostError::NonEmptyParticipantZero)?;
}
validate_map(
&mut commitments,
&params.view.included,
(multisig_params.i, our_preprocess.serialized)
)?;
{
let transcript = params.algorithm.transcript();
@@ -155,108 +131,66 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
}
#[allow(non_snake_case)]
let mut B = Vec::with_capacity(multisig_params.n + 1);
B.push(None);
let mut B = HashMap::<u16, _>::with_capacity(params.view.included.len());
// Commitments + a presumed 32-byte hash of the message
let commitments_len = 2 * C::G_len();
// Parse the commitments and prepare the binding factor
for l in 1 ..= multisig_params.n {
if l == multisig_params.i {
if commitments[l].is_some() {
Err(FrostError::DuplicatedIndex(l))?;
}
B.push(Some(our_preprocess.commitments));
{
let transcript = params.algorithm.transcript();
transcript.append_message(b"participant", &u16::try_from(l).unwrap().to_be_bytes());
transcript.append_message(
b"commitments",
&our_preprocess.serialized[0 .. (C::G_len() * 2)]
);
}
continue;
}
let included = params.view.included.contains(&l);
if commitments[l].is_some() && (!included) {
Err(FrostError::InvalidCommitmentQuantity(l, 0, commitments.len() / C::G_len()))?;
}
if commitments[l].is_none() {
if included {
Err(FrostError::InvalidCommitmentQuantity(l, 2, 0))?;
}
B.push(None);
continue;
}
let commitments = commitments[l].as_ref().unwrap();
if commitments.len() < commitments_len {
Err(FrostError::InvalidCommitmentQuantity(l, 2, commitments.len() / C::G_len()))?;
}
#[allow(non_snake_case)]
let D = C::G_from_slice(&commitments[0 .. C::G_len()])
.map_err(|_| FrostError::InvalidCommitment(l))?;
#[allow(non_snake_case)]
let E = C::G_from_slice(&commitments[C::G_len() .. commitments_len])
.map_err(|_| FrostError::InvalidCommitment(l))?;
B.push(Some([D, E]));
{
let transcript = params.algorithm.transcript();
transcript.append_message(b"participant", &u16::try_from(l).unwrap().to_be_bytes());
transcript.append_message(b"commitments", &commitments[0 .. commitments_len]);
}
}
// Add the message to the binding factor
// Get the binding factor
let mut addendums = HashMap::new();
let binding = {
let transcript = params.algorithm.transcript();
// Parse the commitments
for l in &params.view.included {
transcript.append_message(b"participant", &l.to_be_bytes());
let commitments = commitments.remove(l).unwrap();
let mut read_commitment = |c, label| {
let commitment = &commitments[c .. c + C::G_len()];
transcript.append_message(label, commitment);
C::G_from_slice(commitment).map_err(|_| FrostError::InvalidCommitment(*l))
};
#[allow(non_snake_case)]
let mut read_D_E = || Ok(
[read_commitment(0, b"commitment_D")?, read_commitment(C::G_len(), b"commitment_E")?]
);
B.insert(*l, read_D_E()?);
addendums.insert(*l, commitments[(C::G_len() * 2) ..].to_vec());
}
// Append the message to the transcript
transcript.append_message(b"message", &C::hash_msg(&msg));
// Calculate the binding factor
C::hash_to_F(&transcript.challenge(b"binding"))
};
// Process the commitments and addendums
let view = &params.view;
// Process the addendums
for l in &params.view.included {
params.algorithm.process_addendum(
view,
*l,
B[*l].as_ref().unwrap(),
if *l == multisig_params.i {
&our_preprocess.serialized[commitments_len .. our_preprocess.serialized.len()]
} else {
&commitments[*l].as_ref().unwrap()[
commitments_len .. commitments[*l].as_ref().unwrap().len()
]
}
)?;
params.algorithm.process_addendum(&params.view, *l, &B[l], &addendums[l])?;
}
#[allow(non_snake_case)]
let mut Ris = vec![];
let mut Ris = HashMap::with_capacity(params.view.included.len());
#[allow(non_snake_case)]
let mut R = C::G::identity();
for i in 0 .. params.view.included.len() {
let commitments = B[params.view.included[i]].unwrap();
for l in &params.view.included {
#[allow(non_snake_case)]
let this_R = commitments[0] + (commitments[1] * binding);
Ris.push(this_R);
let this_R = B[l][0] + (B[l][1] * binding);
Ris.insert(*l, this_R);
R += this_R;
}
let view = &params.view;
let share = params.algorithm.sign_share(
view,
R,
binding,
our_preprocess.nonces[0] + (our_preprocess.nonces[1] * binding),
msg
let share = C::F_to_bytes(
&params.algorithm.sign_share(
&params.view,
R,
binding,
our_preprocess.nonces[0] + (our_preprocess.nonces[1] * binding),
msg
)
);
Ok((Package { Ris, R, share }, C::F_to_bytes(&share)))
Ok((Package { Ris, R, share: share.clone() }, share))
}
// This doesn't check the signing set is as expected and unexpected changes can cause false blames
@@ -265,37 +199,17 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
fn complete<C: Curve, A: Algorithm<C>>(
sign_params: &Params<C, A>,
sign: Package<C>,
serialized: &[Option<Vec<u8>>],
mut shares: HashMap<u16, Vec<u8>>,
) -> Result<A::Signature, FrostError> {
let params = sign_params.multisig_params();
if serialized.len() != (params.n + 1) {
Err(
FrostError::InvalidParticipantQuantity(params.n, serialized.len() - min(1, serialized.len()))
)?;
}
validate_map(&mut shares, &sign_params.view.included, (params.i(), sign.share))?;
if serialized[0].is_some() {
Err(FrostError::NonEmptyParticipantZero)?;
}
let mut responses = Vec::with_capacity(params.t);
let mut sum = sign.share;
for i in 0 .. sign_params.view.included.len() {
let l = sign_params.view.included[i];
if l == params.i {
responses.push(None);
continue;
}
// Make sure they actually provided a share
if serialized[l].is_none() {
Err(FrostError::InvalidShare(l))?;
}
let part = C::F_from_slice(serialized[l].as_ref().unwrap())
.map_err(|_| FrostError::InvalidShare(l))?;
let mut responses = HashMap::new();
let mut sum = C::F::zero();
for l in &sign_params.view.included {
let part = C::F_from_slice(&shares[l]).map_err(|_| FrostError::InvalidShare(*l))?;
sum += part;
responses.push(Some(part));
responses.insert(*l, part);
}
// Perform signature validation instead of individual share validation
@@ -307,21 +221,13 @@ fn complete<C: Curve, A: Algorithm<C>>(
}
// Find out who misbehaved
for i in 0 .. sign_params.view.included.len() {
match responses[i] {
Some(part) => {
let l = sign_params.view.included[i];
if !sign_params.algorithm.verify_share(
sign_params.view.verification_share(l),
sign.Ris[i],
part
) {
Err(FrostError::InvalidShare(l))?;
}
},
// Happens when l == i
None => {}
for l in &sign_params.view.included {
if !sign_params.algorithm.verify_share(
sign_params.view.verification_share(*l),
sign.Ris[l],
responses[l]
) {
Err(FrostError::InvalidShare(*l))?;
}
}
@@ -366,7 +272,7 @@ pub trait StateMachine {
/// for every other participant to receive, over an authenticated channel
fn sign(
&mut self,
commitments: &[Option<Vec<u8>>],
commitments: HashMap<u16, Vec<u8>>,
msg: &[u8],
) -> Result<Vec<u8>, FrostError>;
@@ -374,7 +280,7 @@ pub trait StateMachine {
/// Takes in everyone elses' shares submitted to us as a Vec, expecting participant index =
/// Vec index with None at index 0 and index i. Returns a byte vector representing the serialized
/// signature
fn complete(&mut self, shares: &[Option<Vec<u8>>]) -> Result<Self::Signature, FrostError>;
fn complete(&mut self, shares: HashMap<u16, Vec<u8>>) -> Result<Self::Signature, FrostError>;
fn multisig_params(&self) -> MultisigParams;
@@ -395,7 +301,7 @@ impl<C: Curve, A: Algorithm<C>> AlgorithmMachine<C, A> {
pub fn new(
algorithm: A,
keys: Rc<MultisigKeys<C>>,
included: &[usize],
included: &[u16],
) -> Result<AlgorithmMachine<C, A>, FrostError> {
Ok(
AlgorithmMachine {
@@ -427,7 +333,7 @@ impl<C: Curve, A: Algorithm<C>> StateMachine for AlgorithmMachine<C, A> {
fn sign(
&mut self,
commitments: &[Option<Vec<u8>>],
commitments: HashMap<u16, Vec<u8>>,
msg: &[u8],
) -> Result<Vec<u8>, FrostError> {
if self.state != State::Preprocessed {
@@ -446,7 +352,7 @@ impl<C: Curve, A: Algorithm<C>> StateMachine for AlgorithmMachine<C, A> {
Ok(serialized)
}
fn complete(&mut self, shares: &[Option<Vec<u8>>]) -> Result<A::Signature, FrostError> {
fn complete(&mut self, shares: HashMap<u16, Vec<u8>>) -> Result<A::Signature, FrostError> {
if self.state != State::Signed {
Err(FrostError::InvalidSignTransition(State::Signed, self.state))?;
}