From 2d56d24d9c1d4637700f8d6038d5ac6a2e7329b7 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 23 Feb 2023 06:50:45 -0500 Subject: [PATCH] 3.3.3 (cont) Add a dedicated Participant type --- coins/monero/src/ringct/clsag/multisig.rs | 10 +- coins/monero/src/tests/clsag.rs | 9 +- coins/monero/src/wallet/send/multisig.rs | 12 +- coins/monero/tests/runner.rs | 5 +- crypto/dkg/src/encryption.rs | 34 +++--- crypto/dkg/src/frost.rs | 80 +++++++------ crypto/dkg/src/lib.rs | 132 +++++++++++++--------- crypto/dkg/src/promote.rs | 10 +- crypto/dkg/src/tests/frost.rs | 91 +++++++-------- crypto/dkg/src/tests/mod.rs | 8 +- crypto/frost/src/algorithm.rs | 11 +- crypto/frost/src/lib.rs | 24 ++-- crypto/frost/src/nonce.rs | 12 +- crypto/frost/src/sign.rs | 24 ++-- crypto/frost/src/tests/mod.rs | 31 ++--- crypto/frost/src/tests/vectors.rs | 29 +++-- processor/src/lib.rs | 4 +- processor/src/tests/mod.rs | 19 +++- 18 files changed, 308 insertions(+), 237 deletions(-) diff --git a/coins/monero/src/ringct/clsag/multisig.rs b/coins/monero/src/ringct/clsag/multisig.rs index cec1cbd6..d9f3b5f3 100644 --- a/coins/monero/src/ringct/clsag/multisig.rs +++ b/coins/monero/src/ringct/clsag/multisig.rs @@ -23,7 +23,7 @@ use dleq::DLEqProof; use frost::{ dkg::lagrange, curve::Ed25519, - FrostError, ThresholdKeys, ThresholdView, + Participant, FrostError, ThresholdKeys, ThresholdView, algorithm::{WriteAddendum, Algorithm}, }; @@ -146,8 +146,8 @@ pub(crate) fn add_key_image_share( image: &mut EdwardsPoint, generator: EdwardsPoint, offset: Scalar, - included: &[u16], - participant: u16, + included: &[Participant], + participant: Participant, share: EdwardsPoint, ) { if image.is_identity() { @@ -203,7 +203,7 @@ impl Algorithm for ClsagMultisig { fn process_addendum( &mut self, view: &ThresholdView, - l: u16, + l: Participant, addendum: ClsagAddendum, ) -> Result<(), FrostError> { if self.image.is_identity() { @@ -212,7 +212,7 @@ impl Algorithm for ClsagMultisig { self.transcript.append_message(b"mask", self.mask().to_bytes()); } - self.transcript.append_message(b"participant", l.to_be_bytes()); + self.transcript.append_message(b"participant", l.to_bytes()); addendum .dleq diff --git a/coins/monero/src/tests/clsag.rs b/coins/monero/src/tests/clsag.rs index 3becb414..273124e8 100644 --- a/coins/monero/src/tests/clsag.rs +++ b/coins/monero/src/tests/clsag.rs @@ -24,7 +24,10 @@ use crate::{ use crate::ringct::clsag::{ClsagDetails, ClsagMultisig}; #[cfg(feature = "multisig")] -use frost::tests::{key_gen, algorithm_machines, sign}; +use frost::{ + Participant, + tests::{key_gen, algorithm_machines, sign}, +}; const RING_LEN: u64 = 11; const AMOUNT: u64 = 1337; @@ -93,7 +96,7 @@ fn clsag_multisig() { mask = random_scalar(&mut OsRng); amount = OsRng.next_u64(); } else { - dest = keys[&1].group_key().0; + dest = keys[&Participant::new(1).unwrap()].group_key().0; mask = randomness; amount = AMOUNT; } @@ -103,7 +106,7 @@ fn clsag_multisig() { let mask_sum = random_scalar(&mut OsRng); let algorithm = ClsagMultisig::new( RecommendedTranscript::new(b"Monero Serai CLSAG Test"), - keys[&1].group_key().0, + keys[&Participant::new(1).unwrap()].group_key().0, Arc::new(RwLock::new(Some(ClsagDetails::new( ClsagInput::new( Commitment::new(randomness, AMOUNT), diff --git a/coins/monero/src/wallet/send/multisig.rs b/coins/monero/src/wallet/send/multisig.rs index 7dcb6f13..e8509029 100644 --- a/coins/monero/src/wallet/send/multisig.rs +++ b/coins/monero/src/wallet/send/multisig.rs @@ -14,7 +14,7 @@ use dalek_ff_group as dfg; use transcript::{Transcript, RecommendedTranscript}; use frost::{ curve::Ed25519, - FrostError, ThresholdKeys, + Participant, FrostError, ThresholdKeys, sign::{ Writable, Preprocess, CachedPreprocess, SignatureShare, PreprocessMachine, SignMachine, SignatureMachine, AlgorithmMachine, AlgorithmSignMachine, AlgorithmSignatureMachine, @@ -35,7 +35,7 @@ use crate::{ /// FROST signing machine to produce a signed transaction. pub struct TransactionMachine { signable: SignableTransaction, - i: u16, + i: Participant, transcript: RecommendedTranscript, decoys: Vec, @@ -48,7 +48,7 @@ pub struct TransactionMachine { pub struct TransactionSignMachine { signable: SignableTransaction, - i: u16, + i: Participant, transcript: RecommendedTranscript, decoys: Vec, @@ -236,7 +236,7 @@ impl SignMachine for TransactionSignMachine { fn sign( mut self, - mut commitments: HashMap, + mut commitments: HashMap, msg: &[u8], ) -> Result<(TransactionSignatureMachine, Self::SignatureShare), FrostError> { if !msg.is_empty() { @@ -263,7 +263,7 @@ impl SignMachine for TransactionSignMachine { // While each CLSAG will do this as they need to for security, they have their own // transcripts cloned from this TX's initial premise's transcript. For our TX // transcript to have the CLSAG data for entropy, it'll have to be added ourselves here - self.transcript.append_message(b"participant", (*l).to_be_bytes()); + self.transcript.append_message(b"participant", (*l).to_bytes()); let preprocess = if *l == self.i { self.our_preprocess[c].clone() @@ -389,7 +389,7 @@ impl SignatureMachine for TransactionSignatureMachine { fn complete( mut self, - shares: HashMap, + shares: HashMap, ) -> Result { let mut tx = self.tx; match tx.rct_signatures.prunable { diff --git a/coins/monero/tests/runner.rs b/coins/monero/tests/runner.rs index fe9239d7..149374ba 100644 --- a/coins/monero/tests/runner.rs +++ b/coins/monero/tests/runner.rs @@ -131,6 +131,7 @@ macro_rules! test { #[cfg(feature = "multisig")] use frost::{ curve::Ed25519, + Participant, tests::{THRESHOLD, key_gen}, }; @@ -165,7 +166,7 @@ macro_rules! test { #[cfg(not(feature = "multisig"))] panic!("Multisig branch called without the multisig feature"); #[cfg(feature = "multisig")] - keys[&1].group_key().0 + keys[&Participant::new(1).unwrap()].group_key().0 }; let view = ViewPair::new(spend_pub, Zeroizing::new(random_scalar(&mut OsRng))); @@ -211,7 +212,7 @@ macro_rules! test { #[cfg(feature = "multisig")] { let mut machines = HashMap::new(); - for i in 1 ..= THRESHOLD { + for i in (1 ..= THRESHOLD).map(|i| Participant::new(i).unwrap()) { machines.insert( i, tx diff --git a/crypto/dkg/src/encryption.rs b/crypto/dkg/src/encryption.rs index 6543f346..4bcb7802 100644 --- a/crypto/dkg/src/encryption.rs +++ b/crypto/dkg/src/encryption.rs @@ -26,7 +26,7 @@ use multiexp::BatchVerifier; use schnorr::SchnorrSignature; use dleq::DLEqProof; -use crate::ThresholdParams; +use crate::{Participant, ThresholdParams}; pub trait ReadWrite: Sized { fn read(reader: &mut R, params: ThresholdParams) -> io::Result; @@ -133,7 +133,7 @@ fn cipher(dst: &'static [u8], ecdh: &Zeroizing) -> ChaCha2 fn encrypt( rng: &mut R, dst: &'static [u8], - from: u16, + from: Participant, to: C::G, mut msg: Zeroizing, ) -> EncryptedMessage { @@ -192,7 +192,7 @@ impl EncryptedMessage { } #[cfg(test)] - pub(crate) fn invalidate_msg(&mut self, rng: &mut R, from: u16) { + pub(crate) fn invalidate_msg(&mut self, rng: &mut R, from: Participant) { // Invalidate the message by specifying a new key/Schnorr PoP // This will cause all initial checks to pass, yet a decrypt to gibberish let key = Zeroizing::new(C::random_nonzero_F(rng)); @@ -213,7 +213,7 @@ impl EncryptedMessage { &mut self, rng: &mut R, dst: &'static [u8], - from: u16, + from: Participant, to: C::G, ) { use group::ff::PrimeField; @@ -237,7 +237,7 @@ impl EncryptedMessage { &mut self, rng: &mut R, dst: &'static [u8], - from: u16, + from: Participant, to: C::G, ) { use group::ff::PrimeField; @@ -292,12 +292,12 @@ impl EncryptionKeyProof { // This doesn't need to take the msg. It just doesn't hurt as an extra layer. // This still doesn't mean the DKG offers an authenticated channel. The per-message keys have no // root of trust other than their existence in the assumed-to-exist external authenticated channel. -fn pop_challenge(nonce: C::G, key: C::G, sender: u16, msg: &[u8]) -> C::F { +fn pop_challenge(nonce: C::G, key: C::G, sender: Participant, msg: &[u8]) -> C::F { let mut transcript = RecommendedTranscript::new(b"DKG Encryption Key Proof of Possession v0.2"); transcript.append_message(b"nonce", nonce.to_bytes()); transcript.append_message(b"key", key.to_bytes()); // This is sufficient to prevent the attack this is meant to stop - transcript.append_message(b"sender", sender.to_le_bytes()); + transcript.append_message(b"sender", sender.to_bytes()); // This, as written above, doesn't hurt transcript.append_message(b"message", msg); // While this is a PoK and a PoP, it's called a PoP here since the important part is its owner @@ -322,10 +322,10 @@ pub(crate) enum DecryptionError { #[derive(Clone)] pub(crate) struct Encryption { dst: &'static [u8], - i: u16, + i: Participant, enc_key: Zeroizing, enc_pub_key: C::G, - enc_keys: HashMap, + enc_keys: HashMap, } impl Zeroize for Encryption { @@ -339,7 +339,11 @@ impl Zeroize for Encryption { } impl Encryption { - pub(crate) fn new(dst: &'static [u8], i: u16, rng: &mut R) -> Self { + pub(crate) fn new( + dst: &'static [u8], + i: Participant, + rng: &mut R, + ) -> Self { let enc_key = Zeroizing::new(C::random_nonzero_F(rng)); Self { dst, @@ -356,7 +360,7 @@ impl Encryption { pub(crate) fn register( &mut self, - participant: u16, + participant: Participant, msg: EncryptionKeyMessage, ) -> M { if self.enc_keys.contains_key(&participant) { @@ -369,7 +373,7 @@ impl Encryption { pub(crate) fn encrypt( &self, rng: &mut R, - participant: u16, + participant: Participant, msg: Zeroizing, ) -> EncryptedMessage { encrypt(rng, self.dst, self.i, self.enc_keys[&participant], msg) @@ -382,7 +386,7 @@ impl Encryption { // Uses a distinct batch ID so if this batch verifier is reused, we know its the PoP aspect // which failed, and therefore to use None for the blame batch_id: I, - from: u16, + from: Participant, mut msg: EncryptedMessage, ) -> (Zeroizing, EncryptionKeyProof) { msg.pop.batch_verify( @@ -413,8 +417,8 @@ impl Encryption { // Returns None if the key was wrong. pub(crate) fn decrypt_with_proof( &self, - from: u16, - decryptor: u16, + from: Participant, + decryptor: Participant, mut msg: EncryptedMessage, // There's no encryption key proof if the accusation is of an invalid signature proof: Option>, diff --git a/crypto/dkg/src/frost.rs b/crypto/dkg/src/frost.rs index 8884a9b2..b8b9b449 100644 --- a/crypto/dkg/src/frost.rs +++ b/crypto/dkg/src/frost.rs @@ -24,7 +24,7 @@ use multiexp::{multiexp_vartime, BatchVerifier}; use schnorr::SchnorrSignature; use crate::{ - DkgError, ThresholdParams, ThresholdCore, validate_map, + Participant, DkgError, ThresholdParams, ThresholdCore, validate_map, encryption::{ ReadWrite, EncryptionKeyMessage, EncryptedMessage, Encryption, EncryptionKeyProof, DecryptionError, @@ -34,11 +34,11 @@ use crate::{ type FrostError = DkgError>; #[allow(non_snake_case)] -fn challenge(context: &str, l: u16, R: &[u8], Am: &[u8]) -> C::F { +fn challenge(context: &str, l: Participant, R: &[u8], Am: &[u8]) -> C::F { let mut transcript = RecommendedTranscript::new(b"DKG FROST v0.2"); transcript.domain_separate(b"schnorr_proof_of_knowledge"); transcript.append_message(b"context", context.as_bytes()); - transcript.append_message(b"participant", l.to_le_bytes()); + transcript.append_message(b"participant", l.to_bytes()); transcript.append_message(b"nonce", R); transcript.append_message(b"commitments", Am); C::hash_to_F(b"DKG-FROST-proof_of_knowledge-0", &transcript.challenge(b"schnorr")) @@ -150,9 +150,13 @@ impl KeyGenMachine { } } -fn polynomial(coefficients: &[Zeroizing], l: u16) -> Zeroizing { - assert!(l != 0, "attempting to evaluate a polynomial with 0"); - let l = F::from(u64::from(l)); +fn polynomial( + coefficients: &[Zeroizing], + l: Participant, +) -> Zeroizing { + let l = F::from(u64::from(u16::from(l))); + // This should never be reached since Participant is explicitly non-zero + assert!(l != F::zero(), "zero participant passed to polynomial"); let mut share = Zeroizing::new(F::zero()); for (idx, coefficient) in coefficients.iter().rev().enumerate() { *share += coefficient.deref(); @@ -231,11 +235,15 @@ impl SecretShareMachine { fn verify_r1( &mut self, rng: &mut R, - mut commitments: HashMap>>, - ) -> Result>, FrostError> { - validate_map(&commitments, &(1 ..= self.params.n()).collect::>(), self.params.i())?; + mut commitments: HashMap>>, + ) -> Result>, FrostError> { + validate_map( + &commitments, + &(1 ..= self.params.n()).map(Participant).collect::>(), + self.params.i(), + )?; - let mut batch = BatchVerifier::::new(commitments.len()); + let mut batch = BatchVerifier::::new(commitments.len()); let mut commitments = commitments .drain() .map(|(l, msg)| { @@ -269,14 +277,16 @@ impl SecretShareMachine { pub fn generate_secret_shares( mut self, rng: &mut R, - commitments: HashMap>>, - ) -> Result<(KeyMachine, HashMap>>), FrostError> - { + commitments: HashMap>>, + ) -> Result< + (KeyMachine, HashMap>>), + FrostError, + > { let commitments = self.verify_r1(&mut *rng, commitments)?; // Step 1: Generate secret shares for all other parties let mut res = HashMap::new(); - for l in 1 ..= self.params.n() { + for l in (1 ..= self.params.n()).map(Participant) { // 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 l == self.params.i() { @@ -308,7 +318,7 @@ impl SecretShareMachine { pub struct KeyMachine { params: ThresholdParams, secret: Zeroizing, - commitments: HashMap>, + commitments: HashMap>, encryption: Encryption, } @@ -326,8 +336,8 @@ impl Zeroize for KeyMachine { // Calculate the exponent for a given participant and apply it to a series of commitments // Initially used with the actual commitments to verify the secret share, later used with // stripes to generate the verification shares -fn exponential(i: u16, values: &[C::G]) -> Vec<(C::F, C::G)> { - let i = C::F::from(i.into()); +fn exponential(i: Participant, values: &[C::G]) -> Vec<(C::F, C::G)> { + let i = C::F::from(u16::from(i).into()); let mut res = Vec::with_capacity(values.len()); (0 .. values.len()).fold(C::F::one(), |exp, l| { res.push((exp, values[l])); @@ -337,7 +347,7 @@ fn exponential(i: u16, values: &[C::G]) -> Vec<(C::F, C::G)> { } fn share_verification_statements( - target: u16, + target: Participant, commitments: &[C::G], mut share: Zeroizing, ) -> Vec<(C::F, C::G)> { @@ -359,8 +369,8 @@ fn share_verification_statements( #[derive(Clone, Copy, Hash, Debug, Zeroize)] enum BatchId { - Decryption(u16), - Share(u16), + Decryption(Participant), + Share(Participant), } impl KeyMachine { @@ -370,9 +380,13 @@ impl KeyMachine { pub fn calculate_share( mut self, rng: &mut R, - mut shares: HashMap>>, + mut shares: HashMap>>, ) -> Result, FrostError> { - validate_map(&shares, &(1 ..= self.params.n()).collect::>(), self.params.i())?; + validate_map( + &shares, + &(1 ..= self.params.n()).map(Participant).collect::>(), + self.params.i(), + )?; let mut batch = BatchVerifier::new(shares.len()); let mut blames = HashMap::new(); @@ -412,7 +426,7 @@ impl KeyMachine { // Calculate each user's verification share let mut verification_shares = HashMap::new(); - for i in 1 ..= self.params.n() { + for i in (1 ..= self.params.n()).map(Participant) { verification_shares.insert( i, if i == self.params.i() { @@ -438,7 +452,7 @@ impl KeyMachine { } pub struct BlameMachine { - commitments: HashMap>, + commitments: HashMap>, encryption: Encryption, result: ThresholdCore, } @@ -469,11 +483,11 @@ impl BlameMachine { fn blame_internal( &self, - sender: u16, - recipient: u16, + sender: Participant, + recipient: Participant, msg: EncryptedMessage>, proof: Option>, - ) -> u16 { + ) -> Participant { let share_bytes = match self.encryption.decrypt_with_proof(sender, recipient, msg, proof) { Ok(share_bytes) => share_bytes, // If there's an invalid signature, the sender did not send a properly formed message @@ -518,11 +532,11 @@ impl BlameMachine { /// order to prevent multiple instances of blame over a single incident. pub fn blame( self, - sender: u16, - recipient: u16, + sender: Participant, + recipient: Participant, msg: EncryptedMessage>, proof: Option>, - ) -> (AdditionalBlameMachine, u16) { + ) -> (AdditionalBlameMachine, Participant) { let faulty = self.blame_internal(sender, recipient, msg, proof); (AdditionalBlameMachine(self), faulty) } @@ -543,11 +557,11 @@ impl AdditionalBlameMachine { /// over a single incident. pub fn blame( self, - sender: u16, - recipient: u16, + sender: Participant, + recipient: Participant, msg: EncryptedMessage>, proof: Option>, - ) -> u16 { + ) -> Participant { self.0.blame_internal(sender, recipient, msg, proof) } } diff --git a/crypto/dkg/src/lib.rs b/crypto/dkg/src/lib.rs index 87c874a8..e513665d 100644 --- a/crypto/dkg/src/lib.rs +++ b/crypto/dkg/src/lib.rs @@ -7,7 +7,7 @@ //! provided. use core::{ - fmt::{Debug, Formatter}, + fmt::{self, Debug}, ops::Deref, }; use std::{io::Read, sync::Arc, collections::HashMap}; @@ -36,29 +36,59 @@ pub mod promote; #[cfg(any(test, feature = "tests"))] pub mod tests; +/// The ID of a participant, defined as a non-zero u16. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Zeroize)] +pub struct Participant(pub(crate) u16); +impl Participant { + pub fn new(i: u16) -> Option { + if i == 0 { + None + } else { + Some(Participant(i)) + } + } + + #[allow(clippy::wrong_self_convention)] + pub fn to_bytes(&self) -> [u8; 2] { + self.0.to_le_bytes() + } +} + +impl From for u16 { + fn from(participant: Participant) -> u16 { + participant.0 + } +} + +impl fmt::Display for Participant { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + /// Various errors possible during key generation/signing. #[derive(Clone, PartialEq, Eq, Debug, Error)] pub enum DkgError { - #[error("a parameter was 0 (required {0}, participants {1})")] + #[error("a parameter was 0 (threshold {0}, participants {1})")] ZeroParameter(u16, u16), #[error("invalid amount of required participants (max {1}, got {0})")] InvalidRequiredQuantity(u16, u16), - #[error("invalid participant index (0 < index <= {0}, yet index is {1})")] - InvalidParticipantIndex(u16, u16), + #[error("invalid participant (0 < participant <= {0}, yet participant is {1})")] + InvalidParticipant(u16, Participant), #[error("invalid signing set")] InvalidSigningSet, #[error("invalid participant quantity (expected {0}, got {1})")] InvalidParticipantQuantity(usize, usize), - #[error("duplicated participant index ({0})")] - DuplicatedIndex(u16), + #[error("duplicated participant ({0})")] + DuplicatedParticipant(Participant), #[error("missing participant {0}")] - MissingParticipant(u16), + MissingParticipant(Participant), #[error("invalid proof of knowledge (participant {0})")] - InvalidProofOfKnowledge(u16), + InvalidProofOfKnowledge(Participant), #[error("invalid share (participant {participant}, blame {blame})")] - InvalidShare { participant: u16, blame: Option }, + InvalidShare { participant: Participant, blame: Option }, #[error("internal error ({0})")] InternalError(&'static str), @@ -66,9 +96,9 @@ pub enum DkgError { // Validate a map of values to have the expected included participants pub(crate) fn validate_map( - map: &HashMap, - included: &[u16], - ours: u16, + map: &HashMap, + included: &[Participant], + ours: Participant, ) -> Result<(), DkgError> { if (map.len() + 1) != included.len() { Err(DkgError::InvalidParticipantQuantity(included.len(), map.len() + 1))?; @@ -77,7 +107,7 @@ pub(crate) fn validate_map( for included in included { if *included == ours { if map.contains_key(included) { - Err(DkgError::DuplicatedIndex(*included))?; + Err(DkgError::DuplicatedParticipant(*included))?; } continue; } @@ -99,11 +129,11 @@ pub struct ThresholdParams { /// Amount of participants. n: u16, /// Index of the participant being acted for. - i: u16, + i: Participant, } impl ThresholdParams { - pub fn new(t: u16, n: u16, i: u16) -> Result> { + pub fn new(t: u16, n: u16, i: Participant) -> Result> { if (t == 0) || (n == 0) { Err(DkgError::ZeroParameter(t, n))?; } @@ -113,8 +143,8 @@ impl ThresholdParams { if t > n { Err(DkgError::InvalidRequiredQuantity(t, n))?; } - if (i == 0) || (i > n) { - Err(DkgError::InvalidParticipantIndex(n, i))?; + if u16::from(i) > n { + Err(DkgError::InvalidParticipant(n, i))?; } Ok(ThresholdParams { t, n, i }) @@ -126,13 +156,15 @@ impl ThresholdParams { pub fn n(&self) -> u16 { self.n } - pub fn i(&self) -> u16 { + pub fn i(&self) -> Participant { self.i } } /// Calculate the lagrange coefficient for a signing set. -pub fn lagrange(i: u16, included: &[u16]) -> F { +pub fn lagrange(i: Participant, included: &[Participant]) -> F { + let i_f = F::from(u64::from(u16::from(i))); + let mut num = F::one(); let mut denom = F::one(); for l in included { @@ -140,9 +172,9 @@ pub fn lagrange(i: u16, included: &[u16]) -> F { continue; } - let share = F::from(u64::from(*l)); + let share = F::from(u64::from(u16::from(*l))); num *= share; - denom *= share - F::from(u64::from(i)); + denom *= share - i_f; } // Safe as this will only be 0 if we're part of the above loop @@ -162,11 +194,11 @@ pub struct ThresholdCore { /// Group key. group_key: C::G, /// Verification shares. - verification_shares: HashMap, + verification_shares: HashMap, } -impl Debug for ThresholdCore { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), core::fmt::Error> { +impl fmt::Debug for ThresholdCore { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt .debug_struct("ThresholdCore") .field("params", &self.params) @@ -191,16 +223,9 @@ impl ThresholdCore { pub(crate) fn new( params: ThresholdParams, secret_share: Zeroizing, - verification_shares: HashMap, + verification_shares: HashMap, ) -> ThresholdCore { - debug_assert!(validate_map::<_, ()>( - &verification_shares, - &(0 ..= params.n).collect::>(), - 0 - ) - .is_ok()); - - let t = (1 ..= params.t).collect::>(); + let t = (1 ..= params.t).map(Participant).collect::>(); ThresholdCore { params, secret_share, @@ -220,19 +245,19 @@ impl ThresholdCore { self.group_key } - pub(crate) fn verification_shares(&self) -> HashMap { + pub(crate) fn verification_shares(&self) -> HashMap { self.verification_shares.clone() } pub fn serialize(&self) -> Vec { let mut serialized = vec![]; - serialized.extend(u32::try_from(C::ID.len()).unwrap().to_be_bytes()); + serialized.extend(u32::try_from(C::ID.len()).unwrap().to_le_bytes()); serialized.extend(C::ID); - serialized.extend(self.params.t.to_be_bytes()); - serialized.extend(self.params.n.to_be_bytes()); - serialized.extend(self.params.i.to_be_bytes()); + serialized.extend(self.params.t.to_le_bytes()); + serialized.extend(self.params.n.to_le_bytes()); + serialized.extend(self.params.i.to_bytes()); serialized.extend(self.secret_share.to_repr().as_ref()); - for l in 1 ..= self.params.n { + for l in (1 ..= self.params.n).map(Participant) { serialized.extend(self.verification_shares[&l].to_bytes().as_ref()); } serialized @@ -245,7 +270,7 @@ impl ThresholdCore { let mut id_len = [0; 4]; reader.read_exact(&mut id_len).map_err(|_| missing.clone())?; - if u32::try_from(C::ID.len()).unwrap().to_be_bytes() != id_len { + if u32::try_from(C::ID.len()).unwrap().to_le_bytes() != id_len { Err(different.clone())?; } @@ -262,9 +287,14 @@ impl ThresholdCore { reader .read_exact(&mut value) .map_err(|_| DkgError::InternalError("missing participant quantities"))?; - Ok(u16::from_be_bytes(value)) + Ok(u16::from_le_bytes(value)) }; - (read_u16()?, read_u16()?, read_u16()?) + ( + read_u16()?, + read_u16()?, + Participant::new(read_u16()?) + .ok_or(DkgError::InternalError("invalid participant index"))?, + ) }; let secret_share = Zeroizing::new( @@ -272,7 +302,7 @@ impl ThresholdCore { ); let mut verification_shares = HashMap::new(); - for l in 1 ..= n { + for l in (1 ..= n).map(Participant) { verification_shares.insert( l, ::read_G(reader) @@ -306,10 +336,10 @@ pub struct ThresholdKeys { pub struct ThresholdView { offset: C::F, group_key: C::G, - included: Vec, + included: Vec, secret_share: Zeroizing, - original_verification_shares: HashMap, - verification_shares: HashMap, + original_verification_shares: HashMap, + verification_shares: HashMap, } impl Zeroize for ThresholdView { @@ -363,7 +393,7 @@ impl ThresholdKeys { } /// Returns all participants' verification shares without any offsetting. - pub(crate) fn verification_shares(&self) -> HashMap { + pub(crate) fn verification_shares(&self) -> HashMap { self.core.verification_shares() } @@ -371,7 +401,7 @@ impl ThresholdKeys { self.core.serialize() } - pub fn view(&self, included: &[u16]) -> Result, DkgError<()>> { + pub fn view(&self, included: &[Participant]) -> Result, DkgError<()>> { if (included.len() < self.params().t.into()) || (usize::from(self.params().n) < included.len()) { Err(DkgError::InvalidSigningSet)?; @@ -409,7 +439,7 @@ impl ThresholdView { self.group_key } - pub fn included(&self) -> &[u16] { + pub fn included(&self) -> &[Participant] { &self.included } @@ -417,11 +447,11 @@ impl ThresholdView { &self.secret_share } - pub fn original_verification_share(&self, l: u16) -> C::G { + pub fn original_verification_share(&self, l: Participant) -> C::G { self.original_verification_shares[&l] } - pub fn verification_share(&self, l: u16) -> C::G { + pub fn verification_share(&self, l: Participant) -> C::G { self.verification_shares[&l] } } diff --git a/crypto/dkg/src/promote.rs b/crypto/dkg/src/promote.rs index 6645cb04..1c4c223a 100644 --- a/crypto/dkg/src/promote.rs +++ b/crypto/dkg/src/promote.rs @@ -14,7 +14,7 @@ use ciphersuite::Ciphersuite; use transcript::{Transcript, RecommendedTranscript}; use dleq::DLEqProof; -use crate::{DkgError, ThresholdCore, ThresholdKeys, validate_map}; +use crate::{Participant, DkgError, ThresholdCore, ThresholdKeys, validate_map}; /// Promote a set of keys to another Ciphersuite definition. pub trait CiphersuitePromote { @@ -27,10 +27,10 @@ pub trait CiphersuitePromote { fn promote(self) -> ThresholdKeys; } -fn transcript(key: G, i: u16) -> RecommendedTranscript { +fn transcript(key: G, i: Participant) -> RecommendedTranscript { let mut transcript = RecommendedTranscript::new(b"DKG Generator Promotion v0.2"); transcript.append_message(b"group_key", key.to_bytes()); - transcript.append_message(b"participant", i.to_be_bytes()); + transcript.append_message(b"participant", i.to_bytes()); transcript } @@ -97,10 +97,10 @@ where /// Complete promotion by taking in the proofs from all other participants. pub fn complete( self, - proofs: &HashMap>, + proofs: &HashMap>, ) -> Result, DkgError<()>> { let params = self.base.params(); - validate_map(proofs, &(1 ..= params.n).collect::>(), params.i)?; + validate_map(proofs, &(1 ..= params.n).map(Participant).collect::>(), params.i)?; let original_shares = self.base.verification_shares(); diff --git a/crypto/dkg/src/tests/frost.rs b/crypto/dkg/src/tests/frost.rs index 34579758..ad8327c1 100644 --- a/crypto/dkg/src/tests/frost.rs +++ b/crypto/dkg/src/tests/frost.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use rand_core::{RngCore, CryptoRng}; use crate::{ - Ciphersuite, ThresholdParams, ThresholdCore, + Ciphersuite, Participant, ThresholdParams, ThresholdCore, frost::{KeyGenMachine, SecretShare, KeyMachine}, encryption::{EncryptionKeyMessage, EncryptedMessage}, tests::{THRESHOLD, PARTICIPANTS, clone_without}, @@ -11,31 +11,30 @@ use crate::{ // Needed so rustfmt doesn't fail to format on line length issues type FrostEncryptedMessage = EncryptedMessage::F>>; -type FrostSecretShares = HashMap>; +type FrostSecretShares = HashMap>; // Commit, then return enc key and shares #[allow(clippy::type_complexity)] fn commit_enc_keys_and_shares( rng: &mut R, -) -> (HashMap>, HashMap, HashMap>) { +) -> ( + HashMap>, + HashMap, + HashMap>, +) { let mut machines = HashMap::new(); let mut commitments = HashMap::new(); let mut enc_keys = HashMap::new(); - for i in 1 ..= PARTICIPANTS { - let machine = KeyGenMachine::::new( - ThresholdParams::new(THRESHOLD, PARTICIPANTS, i).unwrap(), - "DKG Test Key Generation".to_string(), - ); + for i in (1 ..= PARTICIPANTS).map(Participant) { + let params = ThresholdParams::new(THRESHOLD, PARTICIPANTS, i).unwrap(); + let machine = KeyGenMachine::::new(params, "DKG Test Key Generation".to_string()); let (machine, these_commitments) = machine.generate_coefficients(rng); machines.insert(i, machine); commitments.insert( i, - EncryptionKeyMessage::read::<&[u8]>( - &mut these_commitments.serialize().as_ref(), - ThresholdParams { t: THRESHOLD, n: PARTICIPANTS, i: 1 }, - ) - .unwrap(), + EncryptionKeyMessage::read::<&[u8]>(&mut these_commitments.serialize().as_ref(), params) + .unwrap(), ); enc_keys.insert(i, commitments[&i].enc_key()); } @@ -53,7 +52,8 @@ fn commit_enc_keys_and_shares( l, EncryptedMessage::read::<&[u8]>( &mut share.serialize().as_ref(), - ThresholdParams { t: THRESHOLD, n: PARTICIPANTS, i: 1 }, + // Only t/n actually matters, so hardcode i to 1 here + ThresholdParams { t: THRESHOLD, n: PARTICIPANTS, i: Participant(1) }, ) .unwrap(), ) @@ -68,8 +68,8 @@ fn commit_enc_keys_and_shares( } fn generate_secret_shares( - shares: &HashMap>, - recipient: u16, + shares: &HashMap>, + recipient: Participant, ) -> FrostSecretShares { let mut our_secret_shares = HashMap::new(); for (i, shares) in shares { @@ -84,7 +84,7 @@ fn generate_secret_shares( /// Fully perform the FROST key generation algorithm. pub fn frost_gen( rng: &mut R, -) -> HashMap> { +) -> HashMap> { let (mut machines, _, secret_shares) = commit_enc_keys_and_shares::<_, C>(rng); let mut verification_shares = None; @@ -122,16 +122,19 @@ mod literal { use super::*; + const ONE: Participant = Participant(1); + const TWO: Participant = Participant(2); + fn test_blame( machines: Vec>, msg: FrostEncryptedMessage, blame: Option>, ) { for machine in machines { - let (additional, blamed) = machine.blame(1, 2, msg.clone(), blame.clone()); - assert_eq!(blamed, 1); + let (additional, blamed) = machine.blame(ONE, TWO, msg.clone(), blame.clone()); + assert_eq!(blamed, ONE); // Verify additional blame also works - assert_eq!(additional.blame(1, 2, msg.clone(), blame.clone()), 1); + assert_eq!(additional.blame(ONE, TWO, msg.clone(), blame.clone()), ONE); } } @@ -142,7 +145,7 @@ mod literal { commit_enc_keys_and_shares::<_, Ristretto>(&mut OsRng); // Mutate the PoP of the encrypted message from 1 to 2 - secret_shares.get_mut(&1).unwrap().get_mut(&2).unwrap().invalidate_pop(); + secret_shares.get_mut(&ONE).unwrap().get_mut(&TWO).unwrap().invalidate_pop(); let mut blame = None; let machines = machines @@ -150,8 +153,8 @@ mod literal { .filter_map(|(i, machine)| { let our_secret_shares = generate_secret_shares(&secret_shares, i); let machine = machine.calculate_share(&mut OsRng, our_secret_shares); - if i == 2 { - assert_eq!(machine.err(), Some(DkgError::InvalidShare { participant: 1, blame: None })); + if i == TWO { + assert_eq!(machine.err(), Some(DkgError::InvalidShare { participant: ONE, blame: None })); // Explicitly declare we have a blame object, which happens to be None since invalid PoP // is self-explainable blame = Some(None); @@ -162,7 +165,7 @@ mod literal { }) .collect::>(); - test_blame(machines, secret_shares[&1][&2].clone(), blame.unwrap()); + test_blame(machines, secret_shares[&ONE][&TWO].clone(), blame.unwrap()); } #[test] @@ -176,7 +179,7 @@ mod literal { // We then malleate 1's blame proof, so 1 ends up malicious // Doesn't simply invalidate the PoP as that won't have a blame statement // By mutating the encrypted data, we do ensure a blame statement is created - secret_shares.get_mut(&2).unwrap().get_mut(&1).unwrap().invalidate_msg(&mut OsRng, 2); + secret_shares.get_mut(&TWO).unwrap().get_mut(&ONE).unwrap().invalidate_msg(&mut OsRng, TWO); let mut blame = None; let machines = machines @@ -184,9 +187,9 @@ mod literal { .filter_map(|(i, machine)| { let our_secret_shares = generate_secret_shares(&secret_shares, i); let machine = machine.calculate_share(&mut OsRng, our_secret_shares); - if i == 1 { + if i == ONE { blame = Some(match machine.err() { - Some(DkgError::InvalidShare { participant: 2, blame: Some(blame) }) => Some(blame), + Some(DkgError::InvalidShare { participant: TWO, blame: Some(blame) }) => Some(blame), _ => panic!(), }); None @@ -197,7 +200,7 @@ mod literal { .collect::>(); blame.as_mut().unwrap().as_mut().unwrap().invalidate_key(); - test_blame(machines, secret_shares[&2][&1].clone(), blame.unwrap()); + test_blame(machines, secret_shares[&TWO][&ONE].clone(), blame.unwrap()); } // This should be largely equivalent to the prior test @@ -206,7 +209,7 @@ mod literal { let (mut machines, _, mut secret_shares) = commit_enc_keys_and_shares::<_, Ristretto>(&mut OsRng); - secret_shares.get_mut(&2).unwrap().get_mut(&1).unwrap().invalidate_msg(&mut OsRng, 2); + secret_shares.get_mut(&TWO).unwrap().get_mut(&ONE).unwrap().invalidate_msg(&mut OsRng, TWO); let mut blame = None; let machines = machines @@ -214,9 +217,9 @@ mod literal { .filter_map(|(i, machine)| { let our_secret_shares = generate_secret_shares(&secret_shares, i); let machine = machine.calculate_share(&mut OsRng, our_secret_shares); - if i == 1 { + if i == ONE { blame = Some(match machine.err() { - Some(DkgError::InvalidShare { participant: 2, blame: Some(blame) }) => Some(blame), + Some(DkgError::InvalidShare { participant: TWO, blame: Some(blame) }) => Some(blame), _ => panic!(), }); None @@ -227,7 +230,7 @@ mod literal { .collect::>(); blame.as_mut().unwrap().as_mut().unwrap().invalidate_dleq(); - test_blame(machines, secret_shares[&2][&1].clone(), blame.unwrap()); + test_blame(machines, secret_shares[&TWO][&ONE].clone(), blame.unwrap()); } #[test] @@ -235,11 +238,11 @@ mod literal { let (mut machines, enc_keys, mut secret_shares) = commit_enc_keys_and_shares::<_, Ristretto>(&mut OsRng); - secret_shares.get_mut(&1).unwrap().get_mut(&2).unwrap().invalidate_share_serialization( + secret_shares.get_mut(&ONE).unwrap().get_mut(&TWO).unwrap().invalidate_share_serialization( &mut OsRng, b"FROST", - 1, - enc_keys[&2], + ONE, + enc_keys[&TWO], ); let mut blame = None; @@ -248,9 +251,9 @@ mod literal { .filter_map(|(i, machine)| { let our_secret_shares = generate_secret_shares(&secret_shares, i); let machine = machine.calculate_share(&mut OsRng, our_secret_shares); - if i == 2 { + if i == TWO { blame = Some(match machine.err() { - Some(DkgError::InvalidShare { participant: 1, blame: Some(blame) }) => Some(blame), + Some(DkgError::InvalidShare { participant: ONE, blame: Some(blame) }) => Some(blame), _ => panic!(), }); None @@ -260,7 +263,7 @@ mod literal { }) .collect::>(); - test_blame(machines, secret_shares[&1][&2].clone(), blame.unwrap()); + test_blame(machines, secret_shares[&ONE][&TWO].clone(), blame.unwrap()); } #[test] @@ -268,11 +271,11 @@ mod literal { let (mut machines, enc_keys, mut secret_shares) = commit_enc_keys_and_shares::<_, Ristretto>(&mut OsRng); - secret_shares.get_mut(&1).unwrap().get_mut(&2).unwrap().invalidate_share_value( + secret_shares.get_mut(&ONE).unwrap().get_mut(&TWO).unwrap().invalidate_share_value( &mut OsRng, b"FROST", - 1, - enc_keys[&2], + ONE, + enc_keys[&TWO], ); let mut blame = None; @@ -281,9 +284,9 @@ mod literal { .filter_map(|(i, machine)| { let our_secret_shares = generate_secret_shares(&secret_shares, i); let machine = machine.calculate_share(&mut OsRng, our_secret_shares); - if i == 2 { + if i == TWO { blame = Some(match machine.err() { - Some(DkgError::InvalidShare { participant: 1, blame: Some(blame) }) => Some(blame), + Some(DkgError::InvalidShare { participant: ONE, blame: Some(blame) }) => Some(blame), _ => panic!(), }); None @@ -293,6 +296,6 @@ mod literal { }) .collect::>(); - test_blame(machines, secret_shares[&1][&2].clone(), blame.unwrap()); + test_blame(machines, secret_shares[&ONE][&TWO].clone(), blame.unwrap()); } } diff --git a/crypto/dkg/src/tests/mod.rs b/crypto/dkg/src/tests/mod.rs index 89957315..81e8a9ab 100644 --- a/crypto/dkg/src/tests/mod.rs +++ b/crypto/dkg/src/tests/mod.rs @@ -7,7 +7,7 @@ use group::ff::Field; use ciphersuite::Ciphersuite; -use crate::{ThresholdCore, ThresholdKeys, lagrange}; +use crate::{Participant, ThresholdCore, ThresholdKeys, lagrange}; /// FROST generation test. pub mod frost; @@ -33,7 +33,7 @@ pub fn clone_without( } /// Recover the secret from a collection of keys. -pub fn recover_key(keys: &HashMap>) -> C::F { +pub fn recover_key(keys: &HashMap>) -> C::F { let first = keys.values().next().expect("no keys provided"); assert!(keys.len() >= first.params().t().into(), "not enough keys provided"); let included = keys.keys().cloned().collect::>(); @@ -48,7 +48,7 @@ pub fn recover_key(keys: &HashMap>) -> C:: /// Generate threshold keys for tests. pub fn key_gen( rng: &mut R, -) -> HashMap> { +) -> HashMap> { let res = frost_gen(rng) .drain() .map(|(i, core)| { @@ -59,7 +59,7 @@ pub fn key_gen( (i, ThresholdKeys::new(core)) }) .collect(); - assert_eq!(C::generator() * recover_key(&res), res[&1].group_key()); + assert_eq!(C::generator() * recover_key(&res), res[&Participant(1)].group_key()); res } diff --git a/crypto/frost/src/algorithm.rs b/crypto/frost/src/algorithm.rs index ad0dff5d..e0679523 100644 --- a/crypto/frost/src/algorithm.rs +++ b/crypto/frost/src/algorithm.rs @@ -6,7 +6,7 @@ use rand_core::{RngCore, CryptoRng}; use transcript::Transcript; -use crate::{Curve, FrostError, ThresholdKeys, ThresholdView}; +use crate::{Curve, Participant, FrostError, ThresholdKeys, ThresholdView}; pub use schnorr::SchnorrSignature; /// Write an addendum to a writer. @@ -55,7 +55,7 @@ pub trait Algorithm: Clone { fn process_addendum( &mut self, params: &ThresholdView, - l: u16, + l: Participant, reader: Self::Addendum, ) -> Result<(), FrostError>; @@ -161,7 +161,12 @@ impl> Algorithm for Schnorr { Ok(()) } - fn process_addendum(&mut self, _: &ThresholdView, _: u16, _: ()) -> Result<(), FrostError> { + fn process_addendum( + &mut self, + _: &ThresholdView, + _: Participant, + _: (), + ) -> Result<(), FrostError> { Ok(()) } diff --git a/crypto/frost/src/lib.rs b/crypto/frost/src/lib.rs index 9648e05d..a1d44030 100644 --- a/crypto/frost/src/lib.rs +++ b/crypto/frost/src/lib.rs @@ -19,7 +19,7 @@ use std::collections::HashMap; use thiserror::Error; /// Distributed key generation protocol. -pub use dkg::{self, ThresholdParams, ThresholdCore, ThresholdKeys, ThresholdView}; +pub use dkg::{self, Participant, ThresholdParams, ThresholdCore, ThresholdKeys, ThresholdView}; /// Curve trait and provided curves/HRAMs, forming various ciphersuites. pub mod curve; @@ -38,21 +38,21 @@ pub mod tests; /// Various errors possible during signing. #[derive(Clone, Copy, PartialEq, Eq, Debug, Error)] pub enum FrostError { - #[error("invalid participant index (0 < index <= {0}, yet index is {1})")] - InvalidParticipantIndex(u16, u16), + #[error("invalid participant (0 < participant <= {0}, yet participant is {1})")] + InvalidParticipant(u16, Participant), #[error("invalid signing set ({0})")] InvalidSigningSet(&'static str), #[error("invalid participant quantity (expected {0}, got {1})")] InvalidParticipantQuantity(usize, usize), - #[error("duplicated participant index ({0})")] - DuplicatedIndex(u16), + #[error("duplicated participant ({0})")] + DuplicatedParticipant(Participant), #[error("missing participant {0}")] - MissingParticipant(u16), + MissingParticipant(Participant), #[error("invalid preprocess (participant {0})")] - InvalidPreprocess(u16), + InvalidPreprocess(Participant), #[error("invalid share (participant {0})")] - InvalidShare(u16), + InvalidShare(Participant), #[error("internal error ({0})")] InternalError(&'static str), @@ -60,9 +60,9 @@ pub enum FrostError { // Validate a map of values to have the expected included participants pub fn validate_map( - map: &HashMap, - included: &[u16], - ours: u16, + map: &HashMap, + included: &[Participant], + ours: Participant, ) -> Result<(), FrostError> { if (map.len() + 1) != included.len() { Err(FrostError::InvalidParticipantQuantity(included.len(), map.len() + 1))?; @@ -71,7 +71,7 @@ pub fn validate_map( for included in included { if *included == ours { if map.contains_key(included) { - Err(FrostError::DuplicatedIndex(*included))?; + Err(FrostError::DuplicatedParticipant(*included))?; } continue; } diff --git a/crypto/frost/src/nonce.rs b/crypto/frost/src/nonce.rs index b937aff9..71f5fa47 100644 --- a/crypto/frost/src/nonce.rs +++ b/crypto/frost/src/nonce.rs @@ -25,7 +25,7 @@ use multiexp::multiexp_vartime; use dleq::MultiDLEqProof; -use crate::curve::Curve; +use crate::{curve::Curve, Participant}; // Transcript used to aggregate binomial nonces for usage within a single DLEq proof. fn aggregation_transcript(context: &[u8]) -> T { @@ -247,17 +247,17 @@ pub(crate) struct IndividualBinding { binding_factors: Option>, } -pub(crate) struct BindingFactor(pub(crate) HashMap>); +pub(crate) struct BindingFactor(pub(crate) HashMap>); impl BindingFactor { - pub(crate) fn insert(&mut self, i: u16, commitments: Commitments) { + pub(crate) fn insert(&mut self, i: Participant, commitments: Commitments) { self.0.insert(i, IndividualBinding { commitments, binding_factors: None }); } pub(crate) fn calculate_binding_factors(&mut self, transcript: &mut T) { for (l, binding) in self.0.iter_mut() { let mut transcript = transcript.clone(); - transcript.append_message(b"participant", C::F::from(u64::from(*l)).to_repr()); + transcript.append_message(b"participant", C::F::from(u64::from(u16::from(*l))).to_repr()); // It *should* be perfectly fine to reuse a binding factor for multiple nonces // This generates a binding factor per nonce just to ensure it never comes up as a question binding.binding_factors = Some( @@ -268,12 +268,12 @@ impl BindingFactor { } } - pub(crate) fn binding_factors(&self, i: u16) -> &[C::F] { + pub(crate) fn binding_factors(&self, i: Participant) -> &[C::F] { self.0[&i].binding_factors.as_ref().unwrap() } // Get the bound nonces for a specific party - pub(crate) fn bound(&self, l: u16) -> Vec> { + pub(crate) fn bound(&self, l: Participant) -> Vec> { let mut res = vec![]; for (i, (nonce, rho)) in self.0[&l].commitments.nonces.iter().zip(self.binding_factors(l).iter()).enumerate() diff --git a/crypto/frost/src/sign.rs b/crypto/frost/src/sign.rs index 183ddd6b..4c7a15ec 100644 --- a/crypto/frost/src/sign.rs +++ b/crypto/frost/src/sign.rs @@ -19,7 +19,7 @@ use multiexp::BatchVerifier; use crate::{ curve::Curve, - FrostError, ThresholdParams, ThresholdKeys, ThresholdView, + Participant, FrostError, ThresholdParams, ThresholdKeys, ThresholdView, algorithm::{WriteAddendum, Addendum, Algorithm}, validate_map, }; @@ -239,7 +239,7 @@ pub trait SignMachine: Sized { /// become the signing set for this session. fn sign( self, - commitments: HashMap, + commitments: HashMap, msg: &[u8], ) -> Result<(Self::SignatureMachine, Self::SignatureShare), FrostError>; } @@ -291,7 +291,7 @@ impl> SignMachine for AlgorithmSignMachi fn sign( mut self, - mut preprocesses: HashMap>, + mut preprocesses: HashMap>, msg: &[u8], ) -> Result<(Self::SignatureMachine, SignatureShare), FrostError> { let multisig_params = self.params.multisig_params(); @@ -307,18 +307,14 @@ impl> SignMachine for AlgorithmSignMachi if included.len() < usize::from(multisig_params.t()) { Err(FrostError::InvalidSigningSet("not enough signers"))?; } - // Invalid index - if included[0] == 0 { - Err(FrostError::InvalidParticipantIndex(included[0], multisig_params.n()))?; - } // OOB index - if included[included.len() - 1] > multisig_params.n() { - Err(FrostError::InvalidParticipantIndex(included[included.len() - 1], multisig_params.n()))?; + if u16::from(included[included.len() - 1]) > multisig_params.n() { + Err(FrostError::InvalidParticipant(multisig_params.n(), included[included.len() - 1]))?; } // 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::DuplicatedParticipant(included[i]))?; } } @@ -332,7 +328,7 @@ impl> SignMachine for AlgorithmSignMachi let nonces = self.params.algorithm.nonces(); #[allow(non_snake_case)] - let mut B = BindingFactor(HashMap::::with_capacity(included.len())); + let mut B = BindingFactor(HashMap::::with_capacity(included.len())); { // Parse the preprocesses for l in &included { @@ -341,7 +337,7 @@ impl> SignMachine for AlgorithmSignMachi .params .algorithm .transcript() - .append_message(b"participant", C::F::from(u64::from(*l)).to_repr()); + .append_message(b"participant", C::F::from(u64::from(u16::from(*l))).to_repr()); } if *l == self.params.keys.params().i() { @@ -449,7 +445,7 @@ pub trait SignatureMachine { /// Complete signing. /// Takes in everyone elses' shares. Returns the signature. - fn complete(self, shares: HashMap) -> Result; + fn complete(self, shares: HashMap) -> Result; } /// Final step of the state machine for the signing process. @@ -472,7 +468,7 @@ impl> SignatureMachine for AlgorithmSign fn complete( self, - mut shares: HashMap>, + mut shares: HashMap>, ) -> Result { let params = self.params.multisig_params(); validate_map(&shares, self.view.included(), params.i())?; diff --git a/crypto/frost/src/tests/mod.rs b/crypto/frost/src/tests/mod.rs index 9c6c965f..690be9d5 100644 --- a/crypto/frost/src/tests/mod.rs +++ b/crypto/frost/src/tests/mod.rs @@ -5,7 +5,7 @@ use rand_core::{RngCore, CryptoRng}; pub use dkg::tests::{key_gen, recover_key}; use crate::{ - Curve, ThresholdKeys, + Curve, Participant, ThresholdKeys, algorithm::Algorithm, sign::{Writable, PreprocessMachine, SignMachine, SignatureMachine, AlgorithmMachine}, }; @@ -36,11 +36,14 @@ pub fn clone_without( pub fn algorithm_machines>( rng: &mut R, algorithm: A, - keys: &HashMap>, -) -> HashMap> { + keys: &HashMap>, +) -> HashMap> { let mut included = vec![]; - while included.len() < usize::from(keys[&1].params().t()) { - let n = u16::try_from((rng.next_u64() % u64::try_from(keys.len()).unwrap()) + 1).unwrap(); + while included.len() < usize::from(keys[&Participant::new(1).unwrap()].params().t()) { + let n = Participant::new( + u16::try_from((rng.next_u64() % u64::try_from(keys.len()).unwrap()) + 1).unwrap(), + ) + .unwrap(); if included.contains(&n) { continue; } @@ -64,15 +67,15 @@ pub fn algorithm_machines>( pub(crate) fn commit_and_shares< R: RngCore + CryptoRng, M: PreprocessMachine, - F: FnMut(&mut R, &mut HashMap), + F: FnMut(&mut R, &mut HashMap), >( rng: &mut R, - mut machines: HashMap, + mut machines: HashMap, mut cache: F, msg: &[u8], ) -> ( - HashMap>::SignatureMachine>, - HashMap>::SignatureShare>, + HashMap>::SignatureMachine>, + HashMap>::SignatureShare>, ) { let mut commitments = HashMap::new(); let mut machines = machines @@ -110,10 +113,10 @@ pub(crate) fn commit_and_shares< fn sign_internal< R: RngCore + CryptoRng, M: PreprocessMachine, - F: FnMut(&mut R, &mut HashMap), + F: FnMut(&mut R, &mut HashMap), >( rng: &mut R, - machines: HashMap, + machines: HashMap, cache: F, msg: &[u8], ) -> M::Signature { @@ -135,7 +138,7 @@ fn sign_internal< /// caching. pub fn sign_without_caching( rng: &mut R, - machines: HashMap, + machines: HashMap, msg: &[u8], ) -> M::Signature { sign_internal(rng, machines, |_, _| {}, msg) @@ -146,8 +149,8 @@ pub fn sign_without_caching( pub fn sign( rng: &mut R, params: >::Params, - mut keys: HashMap>::Keys>, - machines: HashMap, + mut keys: HashMap>::Keys>, + machines: HashMap, msg: &[u8], ) -> M::Signature { sign_internal( diff --git a/crypto/frost/src/tests/vectors.rs b/crypto/frost/src/tests/vectors.rs index c4a26d6f..771a3199 100644 --- a/crypto/frost/src/tests/vectors.rs +++ b/crypto/frost/src/tests/vectors.rs @@ -13,7 +13,7 @@ use dkg::tests::key_gen; use crate::{ curve::Curve, - ThresholdCore, ThresholdKeys, FrostError, + Participant, ThresholdCore, ThresholdKeys, FrostError, algorithm::{Schnorr, Hram}, sign::{ Nonce, GeneratorCommitments, NonceCommitments, Commitments, Writable, Preprocess, SignMachine, @@ -30,7 +30,7 @@ pub struct Vectors { pub shares: Vec, pub msg: String, - pub included: Vec, + pub included: Vec, pub nonces: Vec<[String; 2]>, pub sig_shares: Vec, @@ -58,8 +58,11 @@ impl From for Vectors { included: to_str(&value["round_one_outputs"]["participant_list"]) .split(',') .map(u16::from_str) - .collect::>() - .unwrap(), + .collect::, _>>() + .unwrap() + .iter() + .map(|i| Participant::new(*i).unwrap()) + .collect(), nonces: value["round_one_outputs"]["participants"] .as_object() .unwrap() @@ -80,7 +83,7 @@ impl From for Vectors { } // Load these vectors into ThresholdKeys using a custom serialization it'll deserialize -fn vectors_to_multisig_keys(vectors: &Vectors) -> HashMap> { +fn vectors_to_multisig_keys(vectors: &Vectors) -> HashMap> { let shares = vectors .shares .iter() @@ -92,11 +95,11 @@ fn vectors_to_multisig_keys(vectors: &Vectors) -> HashMap(vectors: &Vectors) -> HashMap::deserialize::<&[u8]>(&mut serialized.as_ref()).unwrap(); assert_eq!(these_keys.params().t(), vectors.threshold); assert_eq!(usize::from(these_keys.params().n()), shares.len()); - assert_eq!(these_keys.params().i(), i); + let participant = Participant::new(i).unwrap(); + assert_eq!(these_keys.params().i(), participant); assert_eq!(these_keys.secret_share().deref(), &shares[usize::from(i - 1)]); assert_eq!(hex::encode(these_keys.group_key().to_bytes().as_ref()), vectors.group_key); - keys.insert(i, ThresholdKeys::new(these_keys)); + keys.insert(participant, ThresholdKeys::new(these_keys)); } keys @@ -124,7 +128,8 @@ pub fn test_with_vectors>( let machines = algorithm_machines(&mut *rng, Schnorr::::new(), &keys); const MSG: &[u8] = b"Hello, World!"; let sig = sign(&mut *rng, Schnorr::::new(), keys.clone(), machines, MSG); - assert!(sig.verify(keys[&1].group_key(), H::hram(&sig.R, &keys[&1].group_key(), MSG))); + let group_key = keys[&Participant::new(1).unwrap()].group_key(); + assert!(sig.verify(group_key, H::hram(&sig.R, &group_key, MSG))); } // Test blame on an invalid Schnorr signature share diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 24d8c0d2..462b682e 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -3,7 +3,7 @@ use std::{marker::Send, collections::HashMap}; use async_trait::async_trait; use thiserror::Error; -use frost::{curve::Ciphersuite, FrostError}; +use frost::{curve::Ciphersuite, Participant, FrostError}; mod coin; use coin::{CoinError, Coin}; @@ -18,7 +18,7 @@ pub enum NetworkError {} #[async_trait] pub trait Network: Send { - async fn round(&mut self, data: Vec) -> Result>, NetworkError>; + async fn round(&mut self, data: Vec) -> Result>, NetworkError>; } #[derive(Clone, Error, Debug)] diff --git a/processor/src/tests/mod.rs b/processor/src/tests/mod.rs index 577fb205..36e50f74 100644 --- a/processor/src/tests/mod.rs +++ b/processor/src/tests/mod.rs @@ -7,6 +7,8 @@ use async_trait::async_trait; use rand_core::OsRng; +use frost::Participant; + use crate::{ NetworkError, Network, coin::{Coin, Monero}, @@ -15,11 +17,11 @@ use crate::{ #[derive(Clone)] struct LocalNetwork { - i: u16, + i: Participant, size: u16, round: usize, #[allow(clippy::type_complexity)] - rounds: Arc>>>>, + rounds: Arc>>>>, } impl LocalNetwork { @@ -27,7 +29,12 @@ impl LocalNetwork { let rounds = Arc::new(RwLock::new(vec![])); let mut res = vec![]; for i in 1 ..= size { - res.push(LocalNetwork { i, size, round: 0, rounds: rounds.clone() }); + res.push(LocalNetwork { + i: Participant::new(i).unwrap(), + size, + round: 0, + rounds: rounds.clone(), + }); } res } @@ -35,7 +42,7 @@ impl LocalNetwork { #[async_trait] impl Network for LocalNetwork { - async fn round(&mut self, data: Vec) -> Result>, NetworkError> { + async fn round(&mut self, data: Vec) -> Result>, NetworkError> { { let mut rounds = self.rounds.write().unwrap(); if rounds.len() == self.round { @@ -64,14 +71,14 @@ async fn test_send(coin: C, fee: C::Fee) { let latest = coin.get_latest_block_number().await.unwrap(); let mut keys = frost::tests::key_gen::<_, C::Curve>(&mut OsRng); - let threshold = keys[&1].params().t(); + let threshold = keys[&Participant::new(1).unwrap()].params().t(); let mut networks = LocalNetwork::new(threshold); let mut wallets = vec![]; for i in 1 ..= threshold { let mut wallet = Wallet::new(MemCoinDb::new(), coin.clone()); wallet.acknowledge_block(0, latest); - wallet.add_keys(&WalletKeys::new(keys.remove(&i).unwrap(), 0)); + wallet.add_keys(&WalletKeys::new(keys.remove(&Participant::new(i).unwrap()).unwrap(), 0)); wallets.push(wallet); }