3.3.3 (cont) Add a dedicated Participant type

This commit is contained in:
Luke Parker 2023-02-23 06:50:45 -05:00
parent 87dea5e455
commit 2d56d24d9c
No known key found for this signature in database
18 changed files with 308 additions and 237 deletions

View file

@ -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<Ed25519> for ClsagMultisig {
fn process_addendum(
&mut self,
view: &ThresholdView<Ed25519>,
l: u16,
l: Participant,
addendum: ClsagAddendum,
) -> Result<(), FrostError> {
if self.image.is_identity() {
@ -212,7 +212,7 @@ impl Algorithm<Ed25519> 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

View file

@ -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),

View file

@ -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<Decoys>,
@ -48,7 +48,7 @@ pub struct TransactionMachine {
pub struct TransactionSignMachine {
signable: SignableTransaction,
i: u16,
i: Participant,
transcript: RecommendedTranscript,
decoys: Vec<Decoys>,
@ -236,7 +236,7 @@ impl SignMachine<Transaction> for TransactionSignMachine {
fn sign(
mut self,
mut commitments: HashMap<u16, Self::Preprocess>,
mut commitments: HashMap<Participant, Self::Preprocess>,
msg: &[u8],
) -> Result<(TransactionSignatureMachine, Self::SignatureShare), FrostError> {
if !msg.is_empty() {
@ -263,7 +263,7 @@ impl SignMachine<Transaction> 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<Transaction> for TransactionSignatureMachine {
fn complete(
mut self,
shares: HashMap<u16, Self::SignatureShare>,
shares: HashMap<Participant, Self::SignatureShare>,
) -> Result<Transaction, FrostError> {
let mut tx = self.tx;
match tx.rct_signatures.prunable {

View file

@ -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

View file

@ -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<R: Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self>;
@ -133,7 +133,7 @@ fn cipher<C: Ciphersuite>(dst: &'static [u8], ecdh: &Zeroizing<C::G>) -> ChaCha2
fn encrypt<R: RngCore + CryptoRng, C: Ciphersuite, E: Encryptable>(
rng: &mut R,
dst: &'static [u8],
from: u16,
from: Participant,
to: C::G,
mut msg: Zeroizing<E>,
) -> EncryptedMessage<C, E> {
@ -192,7 +192,7 @@ impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
}
#[cfg(test)]
pub(crate) fn invalidate_msg<R: RngCore + CryptoRng>(&mut self, rng: &mut R, from: u16) {
pub(crate) fn invalidate_msg<R: RngCore + CryptoRng>(&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<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
&mut self,
rng: &mut R,
dst: &'static [u8],
from: u16,
from: Participant,
to: C::G,
) {
use group::ff::PrimeField;
@ -237,7 +237,7 @@ impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
&mut self,
rng: &mut R,
dst: &'static [u8],
from: u16,
from: Participant,
to: C::G,
) {
use group::ff::PrimeField;
@ -292,12 +292,12 @@ impl<C: Ciphersuite> EncryptionKeyProof<C> {
// 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<C: Ciphersuite>(nonce: C::G, key: C::G, sender: u16, msg: &[u8]) -> C::F {
fn pop_challenge<C: Ciphersuite>(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<C: Ciphersuite> {
dst: &'static [u8],
i: u16,
i: Participant,
enc_key: Zeroizing<C::F>,
enc_pub_key: C::G,
enc_keys: HashMap<u16, C::G>,
enc_keys: HashMap<Participant, C::G>,
}
impl<C: Ciphersuite> Zeroize for Encryption<C> {
@ -339,7 +339,11 @@ impl<C: Ciphersuite> Zeroize for Encryption<C> {
}
impl<C: Ciphersuite> Encryption<C> {
pub(crate) fn new<R: RngCore + CryptoRng>(dst: &'static [u8], i: u16, rng: &mut R) -> Self {
pub(crate) fn new<R: RngCore + CryptoRng>(
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<C: Ciphersuite> Encryption<C> {
pub(crate) fn register<M: Message>(
&mut self,
participant: u16,
participant: Participant,
msg: EncryptionKeyMessage<C, M>,
) -> M {
if self.enc_keys.contains_key(&participant) {
@ -369,7 +373,7 @@ impl<C: Ciphersuite> Encryption<C> {
pub(crate) fn encrypt<R: RngCore + CryptoRng, E: Encryptable>(
&self,
rng: &mut R,
participant: u16,
participant: Participant,
msg: Zeroizing<E>,
) -> EncryptedMessage<C, E> {
encrypt(rng, self.dst, self.i, self.enc_keys[&participant], msg)
@ -382,7 +386,7 @@ impl<C: Ciphersuite> Encryption<C> {
// 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<C, E>,
) -> (Zeroizing<E>, EncryptionKeyProof<C>) {
msg.pop.batch_verify(
@ -413,8 +417,8 @@ impl<C: Ciphersuite> Encryption<C> {
// Returns None if the key was wrong.
pub(crate) fn decrypt_with_proof<E: Encryptable>(
&self,
from: u16,
decryptor: u16,
from: Participant,
decryptor: Participant,
mut msg: EncryptedMessage<C, E>,
// There's no encryption key proof if the accusation is of an invalid signature
proof: Option<EncryptionKeyProof<C>>,

View file

@ -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<C> = DkgError<EncryptionKeyProof<C>>;
#[allow(non_snake_case)]
fn challenge<C: Ciphersuite>(context: &str, l: u16, R: &[u8], Am: &[u8]) -> C::F {
fn challenge<C: Ciphersuite>(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<C: Ciphersuite> KeyGenMachine<C> {
}
}
fn polynomial<F: PrimeField + Zeroize>(coefficients: &[Zeroizing<F>], l: u16) -> Zeroizing<F> {
assert!(l != 0, "attempting to evaluate a polynomial with 0");
let l = F::from(u64::from(l));
fn polynomial<F: PrimeField + Zeroize>(
coefficients: &[Zeroizing<F>],
l: Participant,
) -> Zeroizing<F> {
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<C: Ciphersuite> SecretShareMachine<C> {
fn verify_r1<R: RngCore + CryptoRng>(
&mut self,
rng: &mut R,
mut commitments: HashMap<u16, EncryptionKeyMessage<C, Commitments<C>>>,
) -> Result<HashMap<u16, Vec<C::G>>, FrostError<C>> {
validate_map(&commitments, &(1 ..= self.params.n()).collect::<Vec<_>>(), self.params.i())?;
mut commitments: HashMap<Participant, EncryptionKeyMessage<C, Commitments<C>>>,
) -> Result<HashMap<Participant, Vec<C::G>>, FrostError<C>> {
validate_map(
&commitments,
&(1 ..= self.params.n()).map(Participant).collect::<Vec<_>>(),
self.params.i(),
)?;
let mut batch = BatchVerifier::<u16, C::G>::new(commitments.len());
let mut batch = BatchVerifier::<Participant, C::G>::new(commitments.len());
let mut commitments = commitments
.drain()
.map(|(l, msg)| {
@ -269,14 +277,16 @@ impl<C: Ciphersuite> SecretShareMachine<C> {
pub fn generate_secret_shares<R: RngCore + CryptoRng>(
mut self,
rng: &mut R,
commitments: HashMap<u16, EncryptionKeyMessage<C, Commitments<C>>>,
) -> Result<(KeyMachine<C>, HashMap<u16, EncryptedMessage<C, SecretShare<C::F>>>), FrostError<C>>
{
commitments: HashMap<Participant, EncryptionKeyMessage<C, Commitments<C>>>,
) -> Result<
(KeyMachine<C>, HashMap<Participant, EncryptedMessage<C, SecretShare<C::F>>>),
FrostError<C>,
> {
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<C: Ciphersuite> SecretShareMachine<C> {
pub struct KeyMachine<C: Ciphersuite> {
params: ThresholdParams,
secret: Zeroizing<C::F>,
commitments: HashMap<u16, Vec<C::G>>,
commitments: HashMap<Participant, Vec<C::G>>,
encryption: Encryption<C>,
}
@ -326,8 +336,8 @@ impl<C: Ciphersuite> Zeroize for KeyMachine<C> {
// 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<C: Ciphersuite>(i: u16, values: &[C::G]) -> Vec<(C::F, C::G)> {
let i = C::F::from(i.into());
fn exponential<C: Ciphersuite>(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<C: Ciphersuite>(i: u16, values: &[C::G]) -> Vec<(C::F, C::G)> {
}
fn share_verification_statements<C: Ciphersuite>(
target: u16,
target: Participant,
commitments: &[C::G],
mut share: Zeroizing<C::F>,
) -> Vec<(C::F, C::G)> {
@ -359,8 +369,8 @@ fn share_verification_statements<C: Ciphersuite>(
#[derive(Clone, Copy, Hash, Debug, Zeroize)]
enum BatchId {
Decryption(u16),
Share(u16),
Decryption(Participant),
Share(Participant),
}
impl<C: Ciphersuite> KeyMachine<C> {
@ -370,9 +380,13 @@ impl<C: Ciphersuite> KeyMachine<C> {
pub fn calculate_share<R: RngCore + CryptoRng>(
mut self,
rng: &mut R,
mut shares: HashMap<u16, EncryptedMessage<C, SecretShare<C::F>>>,
mut shares: HashMap<Participant, EncryptedMessage<C, SecretShare<C::F>>>,
) -> Result<BlameMachine<C>, FrostError<C>> {
validate_map(&shares, &(1 ..= self.params.n()).collect::<Vec<_>>(), self.params.i())?;
validate_map(
&shares,
&(1 ..= self.params.n()).map(Participant).collect::<Vec<_>>(),
self.params.i(),
)?;
let mut batch = BatchVerifier::new(shares.len());
let mut blames = HashMap::new();
@ -412,7 +426,7 @@ impl<C: Ciphersuite> KeyMachine<C> {
// 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<C: Ciphersuite> KeyMachine<C> {
}
pub struct BlameMachine<C: Ciphersuite> {
commitments: HashMap<u16, Vec<C::G>>,
commitments: HashMap<Participant, Vec<C::G>>,
encryption: Encryption<C>,
result: ThresholdCore<C>,
}
@ -469,11 +483,11 @@ impl<C: Ciphersuite> BlameMachine<C> {
fn blame_internal(
&self,
sender: u16,
recipient: u16,
sender: Participant,
recipient: Participant,
msg: EncryptedMessage<C, SecretShare<C::F>>,
proof: Option<EncryptionKeyProof<C>>,
) -> 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<C: Ciphersuite> BlameMachine<C> {
/// 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<C, SecretShare<C::F>>,
proof: Option<EncryptionKeyProof<C>>,
) -> (AdditionalBlameMachine<C>, u16) {
) -> (AdditionalBlameMachine<C>, Participant) {
let faulty = self.blame_internal(sender, recipient, msg, proof);
(AdditionalBlameMachine(self), faulty)
}
@ -543,11 +557,11 @@ impl<C: Ciphersuite> AdditionalBlameMachine<C> {
/// over a single incident.
pub fn blame(
self,
sender: u16,
recipient: u16,
sender: Participant,
recipient: Participant,
msg: EncryptedMessage<C, SecretShare<C::F>>,
proof: Option<EncryptionKeyProof<C>>,
) -> u16 {
) -> Participant {
self.0.blame_internal(sender, recipient, msg, proof)
}
}

View file

@ -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<Participant> {
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<Participant> 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<B: Clone + PartialEq + Eq + Debug> {
#[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<B> },
InvalidShare { participant: Participant, blame: Option<B> },
#[error("internal error ({0})")]
InternalError(&'static str),
@ -66,9 +96,9 @@ pub enum DkgError<B: Clone + PartialEq + Eq + Debug> {
// Validate a map of values to have the expected included participants
pub(crate) fn validate_map<T, B: Clone + PartialEq + Eq + Debug>(
map: &HashMap<u16, T>,
included: &[u16],
ours: u16,
map: &HashMap<Participant, T>,
included: &[Participant],
ours: Participant,
) -> Result<(), DkgError<B>> {
if (map.len() + 1) != included.len() {
Err(DkgError::InvalidParticipantQuantity(included.len(), map.len() + 1))?;
@ -77,7 +107,7 @@ pub(crate) fn validate_map<T, B: Clone + PartialEq + Eq + Debug>(
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<ThresholdParams, DkgError<()>> {
pub fn new(t: u16, n: u16, i: Participant) -> Result<ThresholdParams, DkgError<()>> {
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<F: PrimeField>(i: u16, included: &[u16]) -> F {
pub fn lagrange<F: PrimeField>(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<F: PrimeField>(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<C: Ciphersuite> {
/// Group key.
group_key: C::G,
/// Verification shares.
verification_shares: HashMap<u16, C::G>,
verification_shares: HashMap<Participant, C::G>,
}
impl<C: Ciphersuite> Debug for ThresholdCore<C> {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
impl<C: Ciphersuite> fmt::Debug for ThresholdCore<C> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt
.debug_struct("ThresholdCore")
.field("params", &self.params)
@ -191,16 +223,9 @@ impl<C: Ciphersuite> ThresholdCore<C> {
pub(crate) fn new(
params: ThresholdParams,
secret_share: Zeroizing<C::F>,
verification_shares: HashMap<u16, C::G>,
verification_shares: HashMap<Participant, C::G>,
) -> ThresholdCore<C> {
debug_assert!(validate_map::<_, ()>(
&verification_shares,
&(0 ..= params.n).collect::<Vec<_>>(),
0
)
.is_ok());
let t = (1 ..= params.t).collect::<Vec<_>>();
let t = (1 ..= params.t).map(Participant).collect::<Vec<_>>();
ThresholdCore {
params,
secret_share,
@ -220,19 +245,19 @@ impl<C: Ciphersuite> ThresholdCore<C> {
self.group_key
}
pub(crate) fn verification_shares(&self) -> HashMap<u16, C::G> {
pub(crate) fn verification_shares(&self) -> HashMap<Participant, C::G> {
self.verification_shares.clone()
}
pub fn serialize(&self) -> Vec<u8> {
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<C: Ciphersuite> ThresholdCore<C> {
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<C: Ciphersuite> ThresholdCore<C> {
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<C: Ciphersuite> ThresholdCore<C> {
);
let mut verification_shares = HashMap::new();
for l in 1 ..= n {
for l in (1 ..= n).map(Participant) {
verification_shares.insert(
l,
<C as Ciphersuite>::read_G(reader)
@ -306,10 +336,10 @@ pub struct ThresholdKeys<C: Ciphersuite> {
pub struct ThresholdView<C: Ciphersuite> {
offset: C::F,
group_key: C::G,
included: Vec<u16>,
included: Vec<Participant>,
secret_share: Zeroizing<C::F>,
original_verification_shares: HashMap<u16, C::G>,
verification_shares: HashMap<u16, C::G>,
original_verification_shares: HashMap<Participant, C::G>,
verification_shares: HashMap<Participant, C::G>,
}
impl<C: Ciphersuite> Zeroize for ThresholdView<C> {
@ -363,7 +393,7 @@ impl<C: Ciphersuite> ThresholdKeys<C> {
}
/// Returns all participants' verification shares without any offsetting.
pub(crate) fn verification_shares(&self) -> HashMap<u16, C::G> {
pub(crate) fn verification_shares(&self) -> HashMap<Participant, C::G> {
self.core.verification_shares()
}
@ -371,7 +401,7 @@ impl<C: Ciphersuite> ThresholdKeys<C> {
self.core.serialize()
}
pub fn view(&self, included: &[u16]) -> Result<ThresholdView<C>, DkgError<()>> {
pub fn view(&self, included: &[Participant]) -> Result<ThresholdView<C>, DkgError<()>> {
if (included.len() < self.params().t.into()) || (usize::from(self.params().n) < included.len())
{
Err(DkgError::InvalidSigningSet)?;
@ -409,7 +439,7 @@ impl<C: Ciphersuite> ThresholdView<C> {
self.group_key
}
pub fn included(&self) -> &[u16] {
pub fn included(&self) -> &[Participant] {
&self.included
}
@ -417,11 +447,11 @@ impl<C: Ciphersuite> ThresholdView<C> {
&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]
}
}

View file

@ -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<C2: Ciphersuite> {
@ -27,10 +27,10 @@ pub trait CiphersuitePromote<C2: Ciphersuite> {
fn promote(self) -> ThresholdKeys<C2>;
}
fn transcript<G: GroupEncoding>(key: G, i: u16) -> RecommendedTranscript {
fn transcript<G: GroupEncoding>(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<u16, GeneratorProof<C1>>,
proofs: &HashMap<Participant, GeneratorProof<C1>>,
) -> Result<ThresholdKeys<C2>, DkgError<()>> {
let params = self.base.params();
validate_map(proofs, &(1 ..= params.n).collect::<Vec<_>>(), params.i)?;
validate_map(proofs, &(1 ..= params.n).map(Participant).collect::<Vec<_>>(), params.i)?;
let original_shares = self.base.verification_shares();

View file

@ -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<C> = EncryptedMessage<C, SecretShare<<C as Ciphersuite>::F>>;
type FrostSecretShares<C> = HashMap<u16, FrostEncryptedMessage<C>>;
type FrostSecretShares<C> = HashMap<Participant, FrostEncryptedMessage<C>>;
// Commit, then return enc key and shares
#[allow(clippy::type_complexity)]
fn commit_enc_keys_and_shares<R: RngCore + CryptoRng, C: Ciphersuite>(
rng: &mut R,
) -> (HashMap<u16, KeyMachine<C>>, HashMap<u16, C::G>, HashMap<u16, FrostSecretShares<C>>) {
) -> (
HashMap<Participant, KeyMachine<C>>,
HashMap<Participant, C::G>,
HashMap<Participant, FrostSecretShares<C>>,
) {
let mut machines = HashMap::new();
let mut commitments = HashMap::new();
let mut enc_keys = HashMap::new();
for i in 1 ..= PARTICIPANTS {
let machine = KeyGenMachine::<C>::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::<C>::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<R: RngCore + CryptoRng, C: Ciphersuite>(
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<R: RngCore + CryptoRng, C: Ciphersuite>(
}
fn generate_secret_shares<C: Ciphersuite>(
shares: &HashMap<u16, FrostSecretShares<C>>,
recipient: u16,
shares: &HashMap<Participant, FrostSecretShares<C>>,
recipient: Participant,
) -> FrostSecretShares<C> {
let mut our_secret_shares = HashMap::new();
for (i, shares) in shares {
@ -84,7 +84,7 @@ fn generate_secret_shares<C: Ciphersuite>(
/// Fully perform the FROST key generation algorithm.
pub fn frost_gen<R: RngCore + CryptoRng, C: Ciphersuite>(
rng: &mut R,
) -> HashMap<u16, ThresholdCore<C>> {
) -> HashMap<Participant, ThresholdCore<C>> {
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<BlameMachine<Ristretto>>,
msg: FrostEncryptedMessage<Ristretto>,
blame: Option<EncryptionKeyProof<Ristretto>>,
) {
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::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>();
test_blame(machines, secret_shares[&1][&2].clone(), blame.unwrap());
test_blame(machines, secret_shares[&ONE][&TWO].clone(), blame.unwrap());
}
}

View file

@ -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<K: Clone + std::cmp::Eq + std::hash::Hash, V: Clone>(
}
/// Recover the secret from a collection of keys.
pub fn recover_key<C: Ciphersuite>(keys: &HashMap<u16, ThresholdKeys<C>>) -> C::F {
pub fn recover_key<C: Ciphersuite>(keys: &HashMap<Participant, ThresholdKeys<C>>) -> 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::<Vec<_>>();
@ -48,7 +48,7 @@ pub fn recover_key<C: Ciphersuite>(keys: &HashMap<u16, ThresholdKeys<C>>) -> C::
/// Generate threshold keys for tests.
pub fn key_gen<R: RngCore + CryptoRng, C: Ciphersuite>(
rng: &mut R,
) -> HashMap<u16, ThresholdKeys<C>> {
) -> HashMap<Participant, ThresholdKeys<C>> {
let res = frost_gen(rng)
.drain()
.map(|(i, core)| {
@ -59,7 +59,7 @@ pub fn key_gen<R: RngCore + CryptoRng, C: Ciphersuite>(
(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
}

View file

@ -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<C: Curve>: Clone {
fn process_addendum(
&mut self,
params: &ThresholdView<C>,
l: u16,
l: Participant,
reader: Self::Addendum,
) -> Result<(), FrostError>;
@ -161,7 +161,12 @@ impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
Ok(())
}
fn process_addendum(&mut self, _: &ThresholdView<C>, _: u16, _: ()) -> Result<(), FrostError> {
fn process_addendum(
&mut self,
_: &ThresholdView<C>,
_: Participant,
_: (),
) -> Result<(), FrostError> {
Ok(())
}

View file

@ -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<T>(
map: &HashMap<u16, T>,
included: &[u16],
ours: u16,
map: &HashMap<Participant, T>,
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<T>(
for included in included {
if *included == ours {
if map.contains_key(included) {
Err(FrostError::DuplicatedIndex(*included))?;
Err(FrostError::DuplicatedParticipant(*included))?;
}
continue;
}

View file

@ -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<T: Transcript>(context: &[u8]) -> T {
@ -247,17 +247,17 @@ pub(crate) struct IndividualBinding<C: Curve> {
binding_factors: Option<Vec<C::F>>,
}
pub(crate) struct BindingFactor<C: Curve>(pub(crate) HashMap<u16, IndividualBinding<C>>);
pub(crate) struct BindingFactor<C: Curve>(pub(crate) HashMap<Participant, IndividualBinding<C>>);
impl<C: Curve> BindingFactor<C> {
pub(crate) fn insert(&mut self, i: u16, commitments: Commitments<C>) {
pub(crate) fn insert(&mut self, i: Participant, commitments: Commitments<C>) {
self.0.insert(i, IndividualBinding { commitments, binding_factors: None });
}
pub(crate) fn calculate_binding_factors<T: Clone + Transcript>(&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<C: Curve> BindingFactor<C> {
}
}
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<Vec<C::G>> {
pub(crate) fn bound(&self, l: Participant) -> Vec<Vec<C::G>> {
let mut res = vec![];
for (i, (nonce, rho)) in
self.0[&l].commitments.nonces.iter().zip(self.binding_factors(l).iter()).enumerate()

View file

@ -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<S>: Sized {
/// become the signing set for this session.
fn sign(
self,
commitments: HashMap<u16, Self::Preprocess>,
commitments: HashMap<Participant, Self::Preprocess>,
msg: &[u8],
) -> Result<(Self::SignatureMachine, Self::SignatureShare), FrostError>;
}
@ -291,7 +291,7 @@ impl<C: Curve, A: Algorithm<C>> SignMachine<A::Signature> for AlgorithmSignMachi
fn sign(
mut self,
mut preprocesses: HashMap<u16, Preprocess<C, A::Addendum>>,
mut preprocesses: HashMap<Participant, Preprocess<C, A::Addendum>>,
msg: &[u8],
) -> Result<(Self::SignatureMachine, SignatureShare<C>), FrostError> {
let multisig_params = self.params.multisig_params();
@ -307,18 +307,14 @@ impl<C: Curve, A: Algorithm<C>> SignMachine<A::Signature> 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<C: Curve, A: Algorithm<C>> SignMachine<A::Signature> for AlgorithmSignMachi
let nonces = self.params.algorithm.nonces();
#[allow(non_snake_case)]
let mut B = BindingFactor(HashMap::<u16, _>::with_capacity(included.len()));
let mut B = BindingFactor(HashMap::<Participant, _>::with_capacity(included.len()));
{
// Parse the preprocesses
for l in &included {
@ -341,7 +337,7 @@ impl<C: Curve, A: Algorithm<C>> SignMachine<A::Signature> 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<S> {
/// Complete signing.
/// Takes in everyone elses' shares. Returns the signature.
fn complete(self, shares: HashMap<u16, Self::SignatureShare>) -> Result<S, FrostError>;
fn complete(self, shares: HashMap<Participant, Self::SignatureShare>) -> Result<S, FrostError>;
}
/// Final step of the state machine for the signing process.
@ -472,7 +468,7 @@ impl<C: Curve, A: Algorithm<C>> SignatureMachine<A::Signature> for AlgorithmSign
fn complete(
self,
mut shares: HashMap<u16, SignatureShare<C>>,
mut shares: HashMap<Participant, SignatureShare<C>>,
) -> Result<A::Signature, FrostError> {
let params = self.params.multisig_params();
validate_map(&shares, self.view.included(), params.i())?;

View file

@ -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<K: Clone + std::cmp::Eq + std::hash::Hash, V: Clone>(
pub fn algorithm_machines<R: RngCore, C: Curve, A: Algorithm<C>>(
rng: &mut R,
algorithm: A,
keys: &HashMap<u16, ThresholdKeys<C>>,
) -> HashMap<u16, AlgorithmMachine<C, A>> {
keys: &HashMap<Participant, ThresholdKeys<C>>,
) -> HashMap<Participant, AlgorithmMachine<C, A>> {
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<R: RngCore, C: Curve, A: Algorithm<C>>(
pub(crate) fn commit_and_shares<
R: RngCore + CryptoRng,
M: PreprocessMachine,
F: FnMut(&mut R, &mut HashMap<u16, M::SignMachine>),
F: FnMut(&mut R, &mut HashMap<Participant, M::SignMachine>),
>(
rng: &mut R,
mut machines: HashMap<u16, M>,
mut machines: HashMap<Participant, M>,
mut cache: F,
msg: &[u8],
) -> (
HashMap<u16, <M::SignMachine as SignMachine<M::Signature>>::SignatureMachine>,
HashMap<u16, <M::SignMachine as SignMachine<M::Signature>>::SignatureShare>,
HashMap<Participant, <M::SignMachine as SignMachine<M::Signature>>::SignatureMachine>,
HashMap<Participant, <M::SignMachine as SignMachine<M::Signature>>::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<u16, M::SignMachine>),
F: FnMut(&mut R, &mut HashMap<Participant, M::SignMachine>),
>(
rng: &mut R,
machines: HashMap<u16, M>,
machines: HashMap<Participant, M>,
cache: F,
msg: &[u8],
) -> M::Signature {
@ -135,7 +138,7 @@ fn sign_internal<
/// caching.
pub fn sign_without_caching<R: RngCore + CryptoRng, M: PreprocessMachine>(
rng: &mut R,
machines: HashMap<u16, M>,
machines: HashMap<Participant, M>,
msg: &[u8],
) -> M::Signature {
sign_internal(rng, machines, |_, _| {}, msg)
@ -146,8 +149,8 @@ pub fn sign_without_caching<R: RngCore + CryptoRng, M: PreprocessMachine>(
pub fn sign<R: RngCore + CryptoRng, M: PreprocessMachine>(
rng: &mut R,
params: <M::SignMachine as SignMachine<M::Signature>>::Params,
mut keys: HashMap<u16, <M::SignMachine as SignMachine<M::Signature>>::Keys>,
machines: HashMap<u16, M>,
mut keys: HashMap<Participant, <M::SignMachine as SignMachine<M::Signature>>::Keys>,
machines: HashMap<Participant, M>,
msg: &[u8],
) -> M::Signature {
sign_internal(

View file

@ -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<String>,
pub msg: String,
pub included: Vec<u16>,
pub included: Vec<Participant>,
pub nonces: Vec<[String; 2]>,
pub sig_shares: Vec<String>,
@ -58,8 +58,11 @@ impl From<serde_json::Value> for Vectors {
included: to_str(&value["round_one_outputs"]["participant_list"])
.split(',')
.map(u16::from_str)
.collect::<Result<_, _>>()
.unwrap(),
.collect::<Result<Vec<_>, _>>()
.unwrap()
.iter()
.map(|i| Participant::new(*i).unwrap())
.collect(),
nonces: value["round_one_outputs"]["participants"]
.as_object()
.unwrap()
@ -80,7 +83,7 @@ impl From<serde_json::Value> for Vectors {
}
// Load these vectors into ThresholdKeys using a custom serialization it'll deserialize
fn vectors_to_multisig_keys<C: Curve>(vectors: &Vectors) -> HashMap<u16, ThresholdKeys<C>> {
fn vectors_to_multisig_keys<C: Curve>(vectors: &Vectors) -> HashMap<Participant, ThresholdKeys<C>> {
let shares = vectors
.shares
.iter()
@ -92,11 +95,11 @@ fn vectors_to_multisig_keys<C: Curve>(vectors: &Vectors) -> HashMap<u16, Thresho
for i in 1 ..= u16::try_from(shares.len()).unwrap() {
// Manually re-implement the serialization for ThresholdCore to import this data
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(vectors.threshold.to_be_bytes());
serialized.extend(u16::try_from(shares.len()).unwrap().to_be_bytes());
serialized.extend(i.to_be_bytes());
serialized.extend(vectors.threshold.to_le_bytes());
serialized.extend(u16::try_from(shares.len()).unwrap().to_le_bytes());
serialized.extend(i.to_le_bytes());
serialized.extend(shares[usize::from(i) - 1].to_repr().as_ref());
for share in &verification_shares {
serialized.extend(share.to_bytes().as_ref());
@ -105,10 +108,11 @@ fn vectors_to_multisig_keys<C: Curve>(vectors: &Vectors) -> HashMap<u16, Thresho
let these_keys = ThresholdCore::<C>::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<R: RngCore + CryptoRng, C: Curve, H: Hram<C>>(
let machines = algorithm_machines(&mut *rng, Schnorr::<C, H>::new(), &keys);
const MSG: &[u8] = b"Hello, World!";
let sig = sign(&mut *rng, Schnorr::<C, H>::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

View file

@ -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<u8>) -> Result<HashMap<u16, Vec<u8>>, NetworkError>;
async fn round(&mut self, data: Vec<u8>) -> Result<HashMap<Participant, Vec<u8>>, NetworkError>;
}
#[derive(Clone, Error, Debug)]

View file

@ -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<RwLock<Vec<HashMap<u16, Vec<u8>>>>>,
rounds: Arc<RwLock<Vec<HashMap<Participant, Vec<u8>>>>>,
}
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<u8>) -> Result<HashMap<u16, Vec<u8>>, NetworkError> {
async fn round(&mut self, data: Vec<u8>) -> Result<HashMap<Participant, Vec<u8>>, NetworkError> {
{
let mut rounds = self.rounds.write().unwrap();
if rounds.len() == self.round {
@ -64,14 +71,14 @@ async fn test_send<C: Coin + Clone>(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);
}