diff --git a/Cargo.lock b/Cargo.lock index 92ff5114..7b713f1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1669,12 +1669,10 @@ version = "0.2.0" dependencies = [ "chacha20 0.9.0", "ciphersuite", - "digest 0.10.6", "dleq", "flexible-transcript", "group", "hex", - "hkdf", "multiexp", "rand_core 0.6.4", "schnorr-signatures", diff --git a/crypto/dkg/Cargo.toml b/crypto/dkg/Cargo.toml index 0a1fcc3c..a63d5bb1 100644 --- a/crypto/dkg/Cargo.toml +++ b/crypto/dkg/Cargo.toml @@ -22,18 +22,12 @@ subtle = "2" hex = "0.4" -digest = "0.10" - -hkdf = "0.12" +transcript = { package = "flexible-transcript", path = "../transcript", version = "0.2", features = ["recommended"] } chacha20 = { version = "0.9", features = ["zeroize"] } group = "0.12" - -ciphersuite = { path = "../ciphersuite", version = "0.1", features = ["std"] } - -transcript = { package = "flexible-transcript", path = "../transcript", version = "0.2", features = ["recommended"] } - multiexp = { path = "../multiexp", version = "0.2", features = ["batch"] } +ciphersuite = { path = "../ciphersuite", version = "0.1", features = ["std"] } schnorr = { package = "schnorr-signatures", path = "../schnorr", version = "0.2" } dleq = { path = "../dleq", version = "0.2", features = ["serialize"] } diff --git a/crypto/dkg/src/encryption.rs b/crypto/dkg/src/encryption.rs new file mode 100644 index 00000000..652fbbea --- /dev/null +++ b/crypto/dkg/src/encryption.rs @@ -0,0 +1,181 @@ +use core::{hash::Hash, fmt::Debug}; +use std::{ + ops::Deref, + io::{self, Read, Write}, + collections::HashMap, +}; + +use zeroize::{Zeroize, Zeroizing}; +use rand_core::{RngCore, CryptoRng}; + +use chacha20::{ + cipher::{crypto_common::KeyIvInit, StreamCipher}, + Key as Cc20Key, Nonce as Cc20Iv, ChaCha20, +}; + +use group::GroupEncoding; + +use ciphersuite::Ciphersuite; + +use transcript::{Transcript, RecommendedTranscript}; + +use crate::ThresholdParams; + +pub trait ReadWrite: Sized { + fn read<R: Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self>; + fn write<W: Write>(&self, writer: &mut W) -> io::Result<()>; + + fn serialize(&self) -> Vec<u8> { + let mut buf = vec![]; + self.write(&mut buf).unwrap(); + buf + } +} + +pub trait Message: Clone + PartialEq + Eq + Debug + Zeroize + ReadWrite {} +impl<M: Clone + PartialEq + Eq + Debug + Zeroize + ReadWrite> Message for M {} + +#[derive(Clone, PartialEq, Eq, Debug, Zeroize)] +pub struct EncryptionKeyMessage<C: Ciphersuite, M: Message> { + msg: M, + enc_key: C::G, +} + +// Doesn't impl ReadWrite so that doesn't need to be imported +impl<C: Ciphersuite, M: Message> EncryptionKeyMessage<C, M> { + pub fn read<R: Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self> { + Ok(Self { msg: M::read(reader, params)?, enc_key: C::read_G(reader)? }) + } + + pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> { + self.msg.write(writer)?; + writer.write_all(self.enc_key.to_bytes().as_ref()) + } + + pub fn serialize(&self) -> Vec<u8> { + let mut buf = vec![]; + self.write(&mut buf).unwrap(); + buf + } +} + +pub trait Encryptable: Clone + AsMut<[u8]> + Zeroize + ReadWrite {} +impl<E: Clone + AsMut<[u8]> + Zeroize + ReadWrite> Encryptable for E {} +#[derive(Clone, Zeroize)] +pub struct EncryptedMessage<E: Encryptable>(Zeroizing<E>); + +impl<E: Encryptable> EncryptedMessage<E> { + pub fn read<R: Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self> { + Ok(Self(Zeroizing::new(E::read(reader, params)?))) + } + + pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> { + self.0.write(writer) + } + + pub fn serialize(&self) -> Vec<u8> { + let mut buf = vec![]; + self.write(&mut buf).unwrap(); + buf + } +} + +#[derive(Clone)] +pub(crate) struct Encryption<Id: Eq + Hash, C: Ciphersuite> { + dst: &'static [u8], + enc_key: Zeroizing<C::F>, + enc_pub_key: C::G, + enc_keys: HashMap<Id, C::G>, +} + +impl<Id: Eq + Hash, C: Ciphersuite> Zeroize for Encryption<Id, C> { + fn zeroize(&mut self) { + self.enc_key.zeroize(); + self.enc_pub_key.zeroize(); + for (_, value) in self.enc_keys.drain() { + value.zeroize(); + } + } +} + +impl<Id: Eq + Hash, C: Ciphersuite> Encryption<Id, C> { + pub(crate) fn new<R: RngCore + CryptoRng>(dst: &'static [u8], rng: &mut R) -> Self { + let enc_key = Zeroizing::new(C::random_nonzero_F(rng)); + Self { dst, enc_pub_key: C::generator() * enc_key.deref(), enc_key, enc_keys: HashMap::new() } + } + + pub(crate) fn registration<M: Message>(&self, msg: M) -> EncryptionKeyMessage<C, M> { + EncryptionKeyMessage { msg, enc_key: self.enc_pub_key } + } + + pub(crate) fn register<M: Message>( + &mut self, + participant: Id, + msg: EncryptionKeyMessage<C, M>, + ) -> M { + if self.enc_keys.contains_key(&participant) { + panic!("Re-registering encryption key for a participant"); + } + self.enc_keys.insert(participant, msg.enc_key); + msg.msg + } + + fn cipher(&self, participant: Id, encrypt: bool) -> ChaCha20 { + // Ideally, we'd box this transcript with ZAlloc, yet that's only possible on nightly + // TODO + let mut transcript = RecommendedTranscript::new(b"DKG Encryption v0"); + transcript.domain_separate(self.dst); + + let other = self.enc_keys[&participant]; + if encrypt { + transcript.append_message(b"sender", self.enc_pub_key.to_bytes()); + transcript.append_message(b"receiver", other.to_bytes()); + } else { + transcript.append_message(b"sender", other.to_bytes()); + transcript.append_message(b"receiver", self.enc_pub_key.to_bytes()); + } + + let mut shared = Zeroizing::new(other * self.enc_key.deref()).deref().to_bytes(); + transcript.append_message(b"shared_key", shared.as_ref()); + shared.as_mut().zeroize(); + + let zeroize = |buf: &mut [u8]| buf.zeroize(); + + let mut key = Cc20Key::default(); + let mut challenge = transcript.challenge(b"key"); + key.copy_from_slice(&challenge[.. 32]); + zeroize(challenge.as_mut()); + + // The RecommendedTranscript isn't vulnerable to length extension attacks, yet if it was, + // it'd make sense to clone it (and fork it) just to hedge against that + let mut iv = Cc20Iv::default(); + let mut challenge = transcript.challenge(b"iv"); + iv.copy_from_slice(&challenge[.. 12]); + zeroize(challenge.as_mut()); + + // Same commentary as the transcript regarding ZAlloc + // TODO + let res = ChaCha20::new(&key, &iv); + zeroize(key.as_mut()); + zeroize(iv.as_mut()); + res + } + + pub(crate) fn encrypt<E: Encryptable>( + &self, + participant: Id, + mut msg: Zeroizing<E>, + ) -> EncryptedMessage<E> { + self.cipher(participant, true).apply_keystream(msg.as_mut().as_mut()); + EncryptedMessage(msg) + } + + pub(crate) fn decrypt<E: Encryptable>( + &self, + participant: Id, + mut msg: EncryptedMessage<E>, + ) -> Zeroizing<E> { + self.cipher(participant, false).apply_keystream(msg.0.as_mut().as_mut()); + msg.0 + } +} diff --git a/crypto/dkg/src/frost.rs b/crypto/dkg/src/frost.rs index c65f69e6..9fd2dea4 100644 --- a/crypto/dkg/src/frost.rs +++ b/crypto/dkg/src/frost.rs @@ -9,49 +9,43 @@ use rand_core::{RngCore, CryptoRng}; use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; -use digest::Digest; -use hkdf::{Hkdf, hmac::SimpleHmac}; -use chacha20::{ - cipher::{crypto_common::KeyIvInit, StreamCipher}, - Key as Cc20Key, Nonce as Cc20Iv, ChaCha20, -}; +use transcript::{Transcript, RecommendedTranscript}; use group::{ ff::{Field, PrimeField}, GroupEncoding, }; - use ciphersuite::Ciphersuite; - use multiexp::{multiexp_vartime, BatchVerifier}; use schnorr::SchnorrSignature; -use crate::{DkgError, ThresholdParams, ThresholdCore, validate_map}; +use crate::{ + DkgError, ThresholdParams, ThresholdCore, validate_map, + encryption::{ReadWrite, EncryptionKeyMessage, EncryptedMessage, Encryption}, +}; #[allow(non_snake_case)] fn challenge<C: Ciphersuite>(context: &str, l: u16, R: &[u8], Am: &[u8]) -> C::F { - const DST: &[u8] = b"FROST Schnorr Proof of Knowledge"; - - // Hashes the context to get a fixed size value out of it - let mut transcript = C::H::digest(context.as_bytes()).as_ref().to_vec(); - transcript.extend(l.to_be_bytes()); - transcript.extend(R); - transcript.extend(Am); - C::hash_to_F(DST, &transcript) + let mut transcript = RecommendedTranscript::new(b"DKG FROST v0"); + 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"nonce", R); + transcript.append_message(b"commitments", Am); + C::hash_to_F(b"PoK 0", &transcript.challenge(b"challenge")) } /// Commitments message to be broadcast to all other parties. #[derive(Clone, PartialEq, Eq, Debug, Zeroize)] pub struct Commitments<C: Ciphersuite> { commitments: Vec<C::G>, - enc_key: C::G, cached_msg: Vec<u8>, sig: SchnorrSignature<C>, } -impl<C: Ciphersuite> Commitments<C> { - pub fn read<R: Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self> { +impl<C: Ciphersuite> ReadWrite for Commitments<C> { + fn read<R: Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self> { let mut commitments = Vec::with_capacity(params.t().into()); let mut cached_msg = vec![]; @@ -67,21 +61,14 @@ impl<C: Ciphersuite> Commitments<C> { for _ in 0 .. params.t() { commitments.push(read_G()?); } - let enc_key = read_G()?; - Ok(Commitments { commitments, enc_key, cached_msg, sig: SchnorrSignature::read(reader)? }) + Ok(Commitments { commitments, cached_msg, sig: SchnorrSignature::read(reader)? }) } - pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> { + fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> { writer.write_all(&self.cached_msg)?; self.sig.write(writer) } - - pub fn serialize(&self) -> Vec<u8> { - let mut buf = vec![]; - self.write(&mut buf).unwrap(); - buf - } } /// State machine to begin the key generation protocol. @@ -104,7 +91,7 @@ impl<C: Ciphersuite> KeyGenMachine<C> { pub fn generate_coefficients<R: RngCore + CryptoRng>( self, rng: &mut R, - ) -> (SecretShareMachine<C>, Commitments<C>) { + ) -> (SecretShareMachine<C>, EncryptionKeyMessage<C, Commitments<C>>) { let t = usize::from(self.params.t); let mut coefficients = Vec::with_capacity(t); let mut commitments = Vec::with_capacity(t); @@ -118,14 +105,6 @@ impl<C: Ciphersuite> KeyGenMachine<C> { cached_msg.extend(commitments[i].to_bytes().as_ref()); } - // Generate an encryption key for transmitting the secret shares - // It would probably be perfectly fine to use one of our polynomial elements, yet doing so - // puts the integrity of FROST at risk. While there's almost no way it could, as it's used in - // an ECDH with validated group elemnents, better to avoid any questions on it - let enc_key = Zeroizing::new(C::random_nonzero_F(&mut *rng)); - let pub_enc_key = C::generator() * enc_key.deref(); - cached_msg.extend(pub_enc_key.to_bytes().as_ref()); - // Step 2: Provide a proof of knowledge let r = Zeroizing::new(C::random_nonzero_F(rng)); let nonce = C::generator() * r.deref(); @@ -139,17 +118,21 @@ impl<C: Ciphersuite> KeyGenMachine<C> { challenge::<C>(&self.context, self.params.i(), nonce.to_bytes().as_ref(), &cached_msg), ); + // Additionally create an encryption mechanism to protect the secret shares + let encryption = Encryption::new(b"FROST", rng); + // Step 4: Broadcast + let msg = + encryption.registration(Commitments { commitments: commitments.clone(), cached_msg, sig }); ( SecretShareMachine { params: self.params, context: self.context, coefficients, - our_commitments: commitments.clone(), - enc_key, - pub_enc_key, + our_commitments: commitments, + encryption, }, - Commitments { commitments, enc_key: pub_enc_key, cached_msg, sig }, + msg, ) } } @@ -169,6 +152,11 @@ fn polynomial<F: PrimeField + Zeroize>(coefficients: &[Zeroizing<F>], l: u16) -> /// Secret share to be sent to the party it's intended for over an authenticated channel. #[derive(Clone, PartialEq, Eq, Debug)] pub struct SecretShare<F: PrimeField>(F::Repr); +impl<F: PrimeField> AsMut<[u8]> for SecretShare<F> { + fn as_mut(&mut self) -> &mut [u8] { + self.0.as_mut() + } +} impl<F: PrimeField> Zeroize for SecretShare<F> { fn zeroize(&mut self) { self.0.as_mut().zeroize() @@ -181,59 +169,16 @@ impl<F: PrimeField> Drop for SecretShare<F> { } impl<F: PrimeField> ZeroizeOnDrop for SecretShare<F> {} -impl<F: PrimeField> SecretShare<F> { - pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> { +impl<F: PrimeField> ReadWrite for SecretShare<F> { + fn read<R: Read>(reader: &mut R, _: ThresholdParams) -> io::Result<Self> { let mut repr = F::Repr::default(); reader.read_exact(repr.as_mut())?; Ok(SecretShare(repr)) } - pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> { + fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> { writer.write_all(self.0.as_ref()) } - - pub fn serialize(&self) -> Vec<u8> { - let mut buf = vec![]; - self.write(&mut buf).unwrap(); - buf - } -} - -fn create_ciphers<C: Ciphersuite>( - mut sender: <C::G as GroupEncoding>::Repr, - receiver: &mut <C::G as GroupEncoding>::Repr, - ecdh: &mut <C::G as GroupEncoding>::Repr, -) -> (ChaCha20, ChaCha20) { - let directional = |sender: &mut <C::G as GroupEncoding>::Repr| { - let mut key = Cc20Key::default(); - key.copy_from_slice( - &Hkdf::<C::H, SimpleHmac<C::H>>::extract( - Some(b"key"), - &[sender.as_ref(), ecdh.as_ref()].concat(), - ) - .0 - .as_ref()[.. 32], - ); - let mut iv = Cc20Iv::default(); - iv.copy_from_slice( - &Hkdf::<C::H, SimpleHmac<C::H>>::extract( - Some(b"iv"), - &[sender.as_ref(), ecdh.as_ref()].concat(), - ) - .0 - .as_ref()[.. 12], - ); - sender.as_mut().zeroize(); - - let res = ChaCha20::new(&key, &iv); - <Cc20Key as AsMut<[u8]>>::as_mut(&mut key).zeroize(); - <Cc20Iv as AsMut<[u8]>>::as_mut(&mut iv).zeroize(); - res - }; - - let res = (directional(&mut sender), directional(receiver)); - ecdh.as_mut().zeroize(); - res } /// Advancement of the key generation state machine. @@ -243,8 +188,7 @@ pub struct SecretShareMachine<C: Ciphersuite> { context: String, coefficients: Vec<Zeroizing<C::F>>, our_commitments: Vec<C::G>, - enc_key: Zeroizing<C::F>, - pub_enc_key: C::G, + encryption: Encryption<u16, C>, } impl<C: Ciphersuite> SecretShareMachine<C> { @@ -253,16 +197,15 @@ impl<C: Ciphersuite> SecretShareMachine<C> { fn verify_r1<R: RngCore + CryptoRng>( &mut self, rng: &mut R, - mut commitments: HashMap<u16, Commitments<C>>, - ) -> Result<(HashMap<u16, Vec<C::G>>, HashMap<u16, C::G>), DkgError> { + mut commitments: HashMap<u16, EncryptionKeyMessage<C, Commitments<C>>>, + ) -> Result<HashMap<u16, Vec<C::G>>, DkgError> { validate_map(&commitments, &(1 ..= self.params.n()).collect::<Vec<_>>(), self.params.i())?; - let mut enc_keys = HashMap::new(); let mut batch = BatchVerifier::<u16, C::G>::new(commitments.len()); let mut commitments = commitments .drain() - .map(|(l, mut msg)| { - enc_keys.insert(l, msg.enc_key); + .map(|(l, msg)| { + let mut msg = self.encryption.register(l, msg); // Step 5: Validate each proof of knowledge // This is solely the prep step for the latter batch verification @@ -281,7 +224,7 @@ impl<C: Ciphersuite> SecretShareMachine<C> { batch.verify_with_vartime_blame().map_err(DkgError::InvalidProofOfKnowledge)?; commitments.insert(self.params.i, self.our_commitments.drain(..).collect()); - Ok((commitments, enc_keys)) + Ok(commitments) } /// Continue generating a key. @@ -291,13 +234,11 @@ impl<C: Ciphersuite> SecretShareMachine<C> { pub fn generate_secret_shares<R: RngCore + CryptoRng>( mut self, rng: &mut R, - commitments: HashMap<u16, Commitments<C>>, - ) -> Result<(KeyMachine<C>, HashMap<u16, SecretShare<C::F>>), DkgError> { - let (commitments, mut enc_keys) = self.verify_r1(&mut *rng, commitments)?; + commitments: HashMap<u16, EncryptionKeyMessage<C, Commitments<C>>>, + ) -> Result<(KeyMachine<C>, HashMap<u16, EncryptedMessage<SecretShare<C::F>>>), DkgError> { + let commitments = self.verify_r1(&mut *rng, commitments)?; // Step 1: Generate secret shares for all other parties - let sender = self.pub_enc_key.to_bytes(); - let mut ciphers = HashMap::new(); let mut res = HashMap::new(); for l in 1 ..= self.params.n() { // Don't insert our own shares to the byte buffer which is meant to be sent around @@ -306,31 +247,20 @@ impl<C: Ciphersuite> SecretShareMachine<C> { continue; } - let (mut cipher_send, cipher_recv) = { - let receiver = enc_keys.get_mut(&l).unwrap(); - let mut ecdh = (*receiver * self.enc_key.deref()).to_bytes(); - - create_ciphers::<C>(sender, &mut receiver.to_bytes(), &mut ecdh) - }; - let mut share = polynomial(&self.coefficients, l); - let mut share_bytes = share.to_repr(); + let share_bytes = Zeroizing::new(SecretShare::<C::F>(share.to_repr())); share.zeroize(); - - cipher_send.apply_keystream(share_bytes.as_mut()); - drop(cipher_send); - - ciphers.insert(l, cipher_recv); - res.insert(l, SecretShare::<C::F>(share_bytes)); - share_bytes.as_mut().zeroize(); + res.insert(l, self.encryption.encrypt(l, share_bytes)); } - self.enc_key.zeroize(); // Calculate our own share let share = polynomial(&self.coefficients, self.params.i()); self.coefficients.zeroize(); - Ok((KeyMachine { params: self.params, secret: share, commitments, ciphers }, res)) + Ok(( + KeyMachine { params: self.params, secret: share, commitments, encryption: self.encryption }, + res, + )) } } @@ -338,24 +268,17 @@ impl<C: Ciphersuite> SecretShareMachine<C> { pub struct KeyMachine<C: Ciphersuite> { params: ThresholdParams, secret: Zeroizing<C::F>, - ciphers: HashMap<u16, ChaCha20>, commitments: HashMap<u16, Vec<C::G>>, + encryption: Encryption<u16, C>, } impl<C: Ciphersuite> Zeroize for KeyMachine<C> { fn zeroize(&mut self) { self.params.zeroize(); self.secret.zeroize(); - - // cipher implements ZeroizeOnDrop and zeroizes on drop, yet doesn't implement Zeroize - // The following is redundant, as Rust should automatically handle dropping it, yet it shows - // awareness of this quirk and at least attempts to be comprehensive - for (_, cipher) in self.ciphers.drain() { - drop(cipher); - } - for (_, commitments) in self.commitments.iter_mut() { commitments.zeroize(); } + self.encryption.zeroize(); } } impl<C: Ciphersuite> Drop for KeyMachine<C> { @@ -373,7 +296,7 @@ impl<C: Ciphersuite> KeyMachine<C> { pub fn complete<R: RngCore + CryptoRng>( mut self, rng: &mut R, - mut shares: HashMap<u16, SecretShare<C::F>>, + mut shares: HashMap<u16, EncryptedMessage<SecretShare<C::F>>>, ) -> Result<ThresholdCore<C>, DkgError> { validate_map(&shares, &(1 ..= self.params.n()).collect::<Vec<_>>(), self.params.i())?; @@ -391,11 +314,8 @@ impl<C: Ciphersuite> KeyMachine<C> { }; let mut batch = BatchVerifier::new(shares.len()); - for (l, mut share_bytes) in shares.drain() { - let mut cipher = self.ciphers.remove(&l).unwrap(); - cipher.apply_keystream(share_bytes.0.as_mut()); - drop(cipher); - + for (l, share_bytes) in shares.drain() { + let mut share_bytes = self.encryption.decrypt(l, share_bytes); let mut share = Zeroizing::new( Option::<C::F>::from(C::F::from_repr(share_bytes.0)).ok_or(DkgError::InvalidShare(l))?, ); diff --git a/crypto/dkg/src/lib.rs b/crypto/dkg/src/lib.rs index d7239a09..48fb4c56 100644 --- a/crypto/dkg/src/lib.rs +++ b/crypto/dkg/src/lib.rs @@ -20,6 +20,8 @@ use group::{ use ciphersuite::Ciphersuite; +mod encryption; + /// The distributed key generation protocol described in the /// [FROST paper](https://eprint.iacr.org/2020/852). pub mod frost; diff --git a/crypto/dkg/src/promote.rs b/crypto/dkg/src/promote.rs index 32a410c0..b0fad364 100644 --- a/crypto/dkg/src/promote.rs +++ b/crypto/dkg/src/promote.rs @@ -28,7 +28,7 @@ pub trait CiphersuitePromote<C2: Ciphersuite> { } fn transcript<G: GroupEncoding>(key: G, i: u16) -> RecommendedTranscript { - let mut transcript = RecommendedTranscript::new(b"FROST Generator Update"); + let mut transcript = RecommendedTranscript::new(b"DKG Generator Promotion v0"); transcript.append_message(b"group_key", key.to_bytes()); transcript.append_message(b"participant", i.to_be_bytes()); transcript diff --git a/crypto/dkg/src/tests/frost.rs b/crypto/dkg/src/tests/frost.rs index 3abba285..6bbbb3e2 100644 --- a/crypto/dkg/src/tests/frost.rs +++ b/crypto/dkg/src/tests/frost.rs @@ -4,7 +4,8 @@ use rand_core::{RngCore, CryptoRng}; use crate::{ Ciphersuite, ThresholdParams, ThresholdCore, - frost::{SecretShare, Commitments, KeyGenMachine}, + frost::KeyGenMachine, + encryption::{EncryptionKeyMessage, EncryptedMessage}, tests::{THRESHOLD, PARTICIPANTS, clone_without}, }; @@ -24,7 +25,7 @@ pub fn frost_gen<R: RngCore + CryptoRng, C: Ciphersuite>( commitments.insert( i, - Commitments::read::<&[u8]>( + EncryptionKeyMessage::read::<&[u8]>( &mut these_commitments.serialize().as_ref(), ThresholdParams { t: THRESHOLD, n: PARTICIPANTS, i: 1 }, ) @@ -41,7 +42,14 @@ pub fn frost_gen<R: RngCore + CryptoRng, C: Ciphersuite>( let shares = shares .drain() .map(|(l, share)| { - (l, SecretShare::<C::F>::read::<&[u8]>(&mut share.serialize().as_ref()).unwrap()) + ( + l, + EncryptedMessage::read::<&[u8]>( + &mut share.serialize().as_ref(), + ThresholdParams { t: THRESHOLD, n: PARTICIPANTS, i: 1 }, + ) + .unwrap(), + ) }) .collect::<HashMap<_, _>>(); secret_shares.insert(l, shares); diff --git a/docs/cryptography/Distributed Key Generation.md b/docs/cryptography/Distributed Key Generation.md new file mode 100644 index 00000000..6d6818a4 --- /dev/null +++ b/docs/cryptography/Distributed Key Generation.md @@ -0,0 +1,15 @@ +# Distributed Key Generation + +Serai uses a modification of Pedersen's Distributed Key Generation, which is +actually Feldman's Verifiable Secret Sharing Scheme run by every participant, as +described in the FROST paper. The modification included in FROST was to include +a Schnorr Proof of Knowledge for coefficient zero, preventing rogue key attacks. +This results in a two-round protocol. + +### Encryption + +In order to protect the secret shares during communication, the `dkg` library +additionally sends an encryption key. These encryption keys are used in an ECDH +to derive a shared key. This key is then hashed to obtain two keys and IVs, one +for sending and one for receiving, with the given counterparty. Chacha20 is used +as the stream cipher.