diff --git a/Cargo.lock b/Cargo.lock index 15fcfb16..9c55b845 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2032,6 +2032,7 @@ dependencies = [ "rand_core 0.6.4", "schnorr-signatures", "serde", + "std-shims", "thiserror", "zeroize", ] @@ -8775,6 +8776,7 @@ version = "0.1.0" dependencies = [ "ciphersuite", "dalek-ff-group", + "dkg", "flexible-transcript", "minimal-ed448", "monero-generators", diff --git a/crypto/dkg/Cargo.toml b/crypto/dkg/Cargo.toml index a154bfd0..bbc9ee33 100644 --- a/crypto/dkg/Cargo.toml +++ b/crypto/dkg/Cargo.toml @@ -13,26 +13,30 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] -thiserror = "1" +thiserror = { version = "1", default-features = false, optional = true } -rand_core = "0.6" +rand_core = { version = "0.6", default-features = false } -zeroize = { version = "^1.5", features = ["zeroize_derive"] } +zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] } + +std-shims = { version = "0.1", path = "../../common/std-shims", default-features = false } serde = { version = "1", features = ["derive"], optional = true } -transcript = { package = "flexible-transcript", path = "../transcript", version = "0.3", features = ["recommended"] } -chacha20 = { version = "0.9", features = ["zeroize"] } +transcript = { package = "flexible-transcript", path = "../transcript", version = "0.3", default-features = false, features = ["recommended"] } +chacha20 = { version = "0.9", default-features = false, features = ["zeroize"] } -ciphersuite = { path = "../ciphersuite", version = "0.3", features = ["std"] } -multiexp = { path = "../multiexp", version = "0.3", features = ["batch"] } +ciphersuite = { path = "../ciphersuite", version = "0.3", default-features = false } +multiexp = { path = "../multiexp", version = "0.3", default-features = false, features = ["batch"] } schnorr = { package = "schnorr-signatures", path = "../schnorr", version = "0.4" } -dleq = { path = "../dleq", version = "0.3", features = ["serialize"] } +dleq = { path = "../dleq", version = "0.3", default-features = false, features = ["serialize"] } [dev-dependencies] -ciphersuite = { path = "../ciphersuite", version = "0.3", features = ["ristretto"] } +ciphersuite = { path = "../ciphersuite", version = "0.3", default-features = false, features = ["ristretto"] } [features] +std = ["thiserror", "std-shims/std", "ciphersuite/std", "multiexp/std", "schnorr/std", "dleq/std"] serde = ["dep:serde"] tests = ["rand_core/getrandom"] +default = ["std"] diff --git a/crypto/dkg/src/lib.rs b/crypto/dkg/src/lib.rs index 40e1282f..7f6a7c48 100644 --- a/crypto/dkg/src/lib.rs +++ b/crypto/dkg/src/lib.rs @@ -1,35 +1,27 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc = include_str!("../README.md")] -use core::{ - fmt::{self, Debug}, - ops::Deref, -}; -use std::{io, sync::Arc, collections::HashMap}; +use core::fmt::{self, Debug}; +#[cfg(feature = "std")] use thiserror::Error; -use zeroize::{Zeroize, Zeroizing}; - -use ciphersuite::{ - group::{ - ff::{Field, PrimeField}, - GroupEncoding, - }, - Ciphersuite, -}; - -/// Encryption types and utilities used to secure DKG messages. -pub mod encryption; +use zeroize::Zeroize; /// MuSig-style key aggregation. pub mod musig; +/// Encryption types and utilities used to secure DKG messages. +#[cfg(feature = "std")] +pub mod encryption; + /// The distributed key generation protocol described in the /// [FROST paper](https://eprint.iacr.org/2020/852). +#[cfg(feature = "std")] pub mod frost; /// Promote keys between ciphersuites. +#[cfg(feature = "std")] pub mod promote; /// Tests for application-provided curves and algorithms. @@ -70,447 +62,471 @@ impl fmt::Display for Participant { } /// Various errors possible during key generation. -#[derive(Clone, PartialEq, Eq, Debug, Error)] +#[derive(Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "std", derive(Error))] pub enum DkgError { /// A parameter was zero. - #[error("a parameter was 0 (threshold {0}, participants {1})")] + #[cfg_attr(feature = "std", error("a parameter was 0 (threshold {0}, participants {1})"))] ZeroParameter(u16, u16), /// The threshold exceeded the amount of participants. - #[error("invalid threshold (max {1}, got {0})")] + #[cfg_attr(feature = "std", error("invalid threshold (max {1}, got {0})"))] InvalidThreshold(u16, u16), /// Invalid participant identifier. - #[error("invalid participant (0 < participant <= {0}, yet participant is {1})")] + #[cfg_attr( + feature = "std", + error("invalid participant (0 < participant <= {0}, yet participant is {1})") + )] InvalidParticipant(u16, Participant), /// Invalid signing set. - #[error("invalid signing set")] + #[cfg_attr(feature = "std", error("invalid signing set"))] InvalidSigningSet, /// Invalid amount of participants. - #[error("invalid participant quantity (expected {0}, got {1})")] + #[cfg_attr(feature = "std", error("invalid participant quantity (expected {0}, got {1})"))] InvalidParticipantQuantity(usize, usize), /// A participant was duplicated. - #[error("duplicated participant ({0})")] + #[cfg_attr(feature = "std", error("duplicated participant ({0})"))] DuplicatedParticipant(Participant), /// A participant was missing. - #[error("missing participant {0}")] + #[cfg_attr(feature = "std", error("missing participant {0}"))] MissingParticipant(Participant), /// An invalid proof of knowledge was provided. - #[error("invalid proof of knowledge (participant {0})")] + #[cfg_attr(feature = "std", error("invalid proof of knowledge (participant {0})"))] InvalidProofOfKnowledge(Participant), /// An invalid DKG share was provided. - #[error("invalid share (participant {participant}, blame {blame})")] + #[cfg_attr(feature = "std", error("invalid share (participant {participant}, blame {blame})"))] InvalidShare { participant: Participant, blame: Option }, } -// Validate a map of values to have the expected included participants -pub(crate) fn validate_map( - map: &HashMap, - included: &[Participant], - ours: Participant, -) -> Result<(), DkgError> { - if (map.len() + 1) != included.len() { - Err(DkgError::InvalidParticipantQuantity(included.len(), map.len() + 1))?; - } +#[cfg(feature = "std")] +mod lib { + pub use super::*; - for included in included { - if *included == ours { - if map.contains_key(included) { - Err(DkgError::DuplicatedParticipant(*included))?; + use core::ops::Deref; + use std::{io, sync::Arc, collections::HashMap}; + + use zeroize::Zeroizing; + + use ciphersuite::{ + group::{ + ff::{Field, PrimeField}, + GroupEncoding, + }, + Ciphersuite, + }; + + // Validate a map of values to have the expected included participants + pub(crate) fn validate_map( + map: &HashMap, + included: &[Participant], + ours: Participant, + ) -> Result<(), DkgError> { + if (map.len() + 1) != included.len() { + Err(DkgError::InvalidParticipantQuantity(included.len(), map.len() + 1))?; + } + + for included in included { + if *included == ours { + if map.contains_key(included) { + Err(DkgError::DuplicatedParticipant(*included))?; + } + continue; + } + + if !map.contains_key(included) { + Err(DkgError::MissingParticipant(*included))?; } - continue; } - if !map.contains_key(included) { - Err(DkgError::MissingParticipant(*included))?; - } - } - - Ok(()) -} - -/// Parameters for a multisig. -// These fields should not be made public as they should be static -#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct ThresholdParams { - /// Participants needed to sign on behalf of the group. - t: u16, - /// Amount of participants. - n: u16, - /// Index of the participant being acted for. - i: Participant, -} - -impl ThresholdParams { - /// Create a new set of parameters. - pub fn new(t: u16, n: u16, i: Participant) -> Result> { - if (t == 0) || (n == 0) { - Err(DkgError::ZeroParameter(t, n))?; - } - - // When t == n, this shouldn't be used (MuSig2 and other variants of MuSig exist for a reason), - // but it's not invalid to do so - if t > n { - Err(DkgError::InvalidThreshold(t, n))?; - } - if u16::from(i) > n { - Err(DkgError::InvalidParticipant(n, i))?; - } - - Ok(ThresholdParams { t, n, i }) - } - - /// Return the threshold for a multisig with these parameters. - pub fn t(&self) -> u16 { - self.t - } - /// Return the amount of participants for a multisig with these parameters. - pub fn n(&self) -> u16 { - self.n - } - /// Return the participant index of the share with these parameters. - pub fn i(&self) -> Participant { - self.i - } -} - -/// Calculate the lagrange coefficient for a signing set. -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 { - if i == *l { - continue; - } - - let share = F::from(u64::from(u16::from(*l))); - num *= share; - denom *= share - i_f; - } - - // Safe as this will only be 0 if we're part of the above loop - // (which we have an if case to avoid) - num * denom.invert().unwrap() -} - -/// Keys and verification shares generated by a DKG. -/// Called core as they're expected to be wrapped into an Arc before usage in various operations. -#[derive(Clone, PartialEq, Eq)] -pub struct ThresholdCore { - /// Threshold Parameters. - params: ThresholdParams, - - /// Secret share key. - secret_share: Zeroizing, - /// Group key. - group_key: C::G, - /// Verification shares. - verification_shares: HashMap, -} - -impl fmt::Debug for ThresholdCore { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt - .debug_struct("ThresholdCore") - .field("params", &self.params) - .field("group_key", &self.group_key) - .field("verification_shares", &self.verification_shares) - .finish_non_exhaustive() - } -} - -impl Zeroize for ThresholdCore { - fn zeroize(&mut self) { - self.params.zeroize(); - self.secret_share.zeroize(); - self.group_key.zeroize(); - for (_, share) in self.verification_shares.iter_mut() { - share.zeroize(); - } - } -} - -impl ThresholdCore { - pub(crate) fn new( - params: ThresholdParams, - secret_share: Zeroizing, - verification_shares: HashMap, - ) -> ThresholdCore { - let t = (1 ..= params.t).map(Participant).collect::>(); - ThresholdCore { - params, - secret_share, - group_key: t.iter().map(|i| verification_shares[i] * lagrange::(*i, &t)).sum(), - verification_shares, - } - } - - /// Parameters for these keys. - pub fn params(&self) -> ThresholdParams { - self.params - } - - /// Secret share for these keys. - pub fn secret_share(&self) -> &Zeroizing { - &self.secret_share - } - - /// Group key for these keys. - pub fn group_key(&self) -> C::G { - self.group_key - } - - pub(crate) fn verification_shares(&self) -> HashMap { - self.verification_shares.clone() - } - - /// Write these keys to a type satisfying std::io::Write. - pub fn write(&self, writer: &mut W) -> io::Result<()> { - writer.write_all(&u32::try_from(C::ID.len()).unwrap().to_le_bytes())?; - writer.write_all(C::ID)?; - writer.write_all(&self.params.t.to_le_bytes())?; - writer.write_all(&self.params.n.to_le_bytes())?; - writer.write_all(&self.params.i.to_bytes())?; - let mut share_bytes = self.secret_share.to_repr(); - writer.write_all(share_bytes.as_ref())?; - share_bytes.as_mut().zeroize(); - for l in 1 ..= self.params.n { - writer - .write_all(self.verification_shares[&Participant::new(l).unwrap()].to_bytes().as_ref())?; - } Ok(()) } - /// Serialize these keys to a `Vec`. - pub fn serialize(&self) -> Zeroizing> { - let mut serialized = Zeroizing::new(vec![]); - self.write::>(serialized.as_mut()).unwrap(); - serialized + /// Parameters for a multisig. + // These fields should not be made public as they should be static + #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct ThresholdParams { + /// Participants needed to sign on behalf of the group. + pub(crate) t: u16, + /// Amount of participants. + pub(crate) n: u16, + /// Index of the participant being acted for. + pub(crate) i: Participant, } - /// Read keys from a type satisfying std::io::Read. - pub fn read(reader: &mut R) -> io::Result> { - { - let different = - || io::Error::new(io::ErrorKind::Other, "deserializing ThresholdCore for another curve"); - - let mut id_len = [0; 4]; - reader.read_exact(&mut id_len)?; - if u32::try_from(C::ID.len()).unwrap().to_le_bytes() != id_len { - Err(different())?; + impl ThresholdParams { + /// Create a new set of parameters. + pub fn new(t: u16, n: u16, i: Participant) -> Result> { + if (t == 0) || (n == 0) { + Err(DkgError::ZeroParameter(t, n))?; } - let mut id = vec![0; C::ID.len()]; - reader.read_exact(&mut id)?; - if id != C::ID { - Err(different())?; + if t > n { + Err(DkgError::InvalidThreshold(t, n))?; + } + if u16::from(i) > n { + Err(DkgError::InvalidParticipant(n, i))?; + } + + Ok(ThresholdParams { t, n, i }) + } + + /// Return the threshold for a multisig with these parameters. + pub fn t(&self) -> u16 { + self.t + } + /// Return the amount of participants for a multisig with these parameters. + pub fn n(&self) -> u16 { + self.n + } + /// Return the participant index of the share with these parameters. + pub fn i(&self) -> Participant { + self.i + } + } + + /// Calculate the lagrange coefficient for a signing set. + 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 { + if i == *l { + continue; + } + + let share = F::from(u64::from(u16::from(*l))); + num *= share; + denom *= share - i_f; + } + + // Safe as this will only be 0 if we're part of the above loop + // (which we have an if case to avoid) + num * denom.invert().unwrap() + } + + /// Keys and verification shares generated by a DKG. + /// Called core as they're expected to be wrapped into an Arc before usage in various operations. + #[derive(Clone, PartialEq, Eq)] + pub struct ThresholdCore { + /// Threshold Parameters. + pub(crate) params: ThresholdParams, + + /// Secret share key. + pub(crate) secret_share: Zeroizing, + /// Group key. + pub(crate) group_key: C::G, + /// Verification shares. + pub(crate) verification_shares: HashMap, + } + + impl fmt::Debug for ThresholdCore { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt + .debug_struct("ThresholdCore") + .field("params", &self.params) + .field("group_key", &self.group_key) + .field("verification_shares", &self.verification_shares) + .finish_non_exhaustive() + } + } + + impl Zeroize for ThresholdCore { + fn zeroize(&mut self) { + self.params.zeroize(); + self.secret_share.zeroize(); + self.group_key.zeroize(); + for (_, share) in self.verification_shares.iter_mut() { + share.zeroize(); + } + } + } + + impl ThresholdCore { + pub(crate) fn new( + params: ThresholdParams, + secret_share: Zeroizing, + verification_shares: HashMap, + ) -> ThresholdCore { + let t = (1 ..= params.t()).map(Participant).collect::>(); + ThresholdCore { + params, + secret_share, + group_key: t.iter().map(|i| verification_shares[i] * lagrange::(*i, &t)).sum(), + verification_shares, } } - let (t, n, i) = { - let mut read_u16 = || -> io::Result { - let mut value = [0; 2]; - reader.read_exact(&mut value)?; - Ok(u16::from_le_bytes(value)) + /// Parameters for these keys. + pub fn params(&self) -> ThresholdParams { + self.params + } + + /// Secret share for these keys. + pub fn secret_share(&self) -> &Zeroizing { + &self.secret_share + } + + /// Group key for these keys. + pub fn group_key(&self) -> C::G { + self.group_key + } + + pub(crate) fn verification_shares(&self) -> HashMap { + self.verification_shares.clone() + } + + /// Write these keys to a type satisfying std::io::Write. + pub fn write(&self, writer: &mut W) -> io::Result<()> { + writer.write_all(&u32::try_from(C::ID.len()).unwrap().to_le_bytes())?; + writer.write_all(C::ID)?; + writer.write_all(&self.params.t.to_le_bytes())?; + writer.write_all(&self.params.n.to_le_bytes())?; + writer.write_all(&self.params.i.to_bytes())?; + let mut share_bytes = self.secret_share.to_repr(); + writer.write_all(share_bytes.as_ref())?; + share_bytes.as_mut().zeroize(); + for l in 1 ..= self.params.n { + writer + .write_all(self.verification_shares[&Participant::new(l).unwrap()].to_bytes().as_ref())?; + } + Ok(()) + } + + /// Serialize these keys to a `Vec`. + pub fn serialize(&self) -> Zeroizing> { + let mut serialized = Zeroizing::new(vec![]); + self.write::>(serialized.as_mut()).unwrap(); + serialized + } + + /// Read keys from a type satisfying std::io::Read. + pub fn read(reader: &mut R) -> io::Result> { + { + let different = + || io::Error::new(io::ErrorKind::Other, "deserializing ThresholdCore for another curve"); + + let mut id_len = [0; 4]; + reader.read_exact(&mut id_len)?; + if u32::try_from(C::ID.len()).unwrap().to_le_bytes() != id_len { + Err(different())?; + } + + let mut id = vec![0; C::ID.len()]; + reader.read_exact(&mut id)?; + if id != C::ID { + Err(different())?; + } + } + + let (t, n, i) = { + let mut read_u16 = || -> io::Result { + let mut value = [0; 2]; + reader.read_exact(&mut value)?; + Ok(u16::from_le_bytes(value)) + }; + ( + read_u16()?, + read_u16()?, + Participant::new(read_u16()?) + .ok_or(io::Error::new(io::ErrorKind::Other, "invalid participant index"))?, + ) }; - ( - read_u16()?, - read_u16()?, - Participant::new(read_u16()?) - .ok_or(io::Error::new(io::ErrorKind::Other, "invalid participant index"))?, - ) - }; - let secret_share = Zeroizing::new(C::read_F(reader)?); + let secret_share = Zeroizing::new(C::read_F(reader)?); - let mut verification_shares = HashMap::new(); - for l in (1 ..= n).map(Participant) { - verification_shares.insert(l, ::read_G(reader)?); + let mut verification_shares = HashMap::new(); + for l in (1 ..= n).map(Participant) { + verification_shares.insert(l, ::read_G(reader)?); + } + + Ok(ThresholdCore::new( + ThresholdParams::new(t, n, i) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "invalid parameters"))?, + secret_share, + verification_shares, + )) + } + } + + /// Threshold keys usable for signing. + #[derive(Clone, Debug, Zeroize)] + pub struct ThresholdKeys { + // Core keys. + // If this is the last reference, the underlying keys will be dropped. When that happens, the + // private key present within it will be zeroed out (as it's within Zeroizing). + #[zeroize(skip)] + pub(crate) core: Arc>, + + // Offset applied to these keys. + pub(crate) offset: Option, + } + + /// View of keys, interpolated and offset for usage. + #[derive(Clone)] + pub struct ThresholdView { + offset: C::F, + group_key: C::G, + included: Vec, + secret_share: Zeroizing, + original_verification_shares: HashMap, + verification_shares: HashMap, + } + + impl fmt::Debug for ThresholdView { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt + .debug_struct("ThresholdView") + .field("offset", &self.offset) + .field("group_key", &self.group_key) + .field("included", &self.included) + .field("original_verification_shares", &self.original_verification_shares) + .field("verification_shares", &self.verification_shares) + .finish_non_exhaustive() + } + } + + impl Zeroize for ThresholdView { + fn zeroize(&mut self) { + self.offset.zeroize(); + self.group_key.zeroize(); + self.included.zeroize(); + self.secret_share.zeroize(); + for (_, share) in self.original_verification_shares.iter_mut() { + share.zeroize(); + } + for (_, share) in self.verification_shares.iter_mut() { + share.zeroize(); + } + } + } + + impl ThresholdKeys { + /// Create a new set of ThresholdKeys from a ThresholdCore. + pub fn new(core: ThresholdCore) -> ThresholdKeys { + ThresholdKeys { core: Arc::new(core), offset: None } } - Ok(ThresholdCore::new( - ThresholdParams::new(t, n, i) - .map_err(|_| io::Error::new(io::ErrorKind::Other, "invalid parameters"))?, - secret_share, - verification_shares, - )) - } -} - -/// Threshold keys usable for signing. -#[derive(Clone, Debug, Zeroize)] -pub struct ThresholdKeys { - // Core keys. - // If this is the last reference, the underlying keys will be dropped. When that happens, the - // private key present within it will be zeroed out (as it's within Zeroizing). - #[zeroize(skip)] - core: Arc>, - - // Offset applied to these keys. - pub(crate) offset: Option, -} - -/// View of keys, interpolated and offset for usage. -#[derive(Clone)] -pub struct ThresholdView { - offset: C::F, - group_key: C::G, - included: Vec, - secret_share: Zeroizing, - original_verification_shares: HashMap, - verification_shares: HashMap, -} - -impl fmt::Debug for ThresholdView { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt - .debug_struct("ThresholdView") - .field("offset", &self.offset) - .field("group_key", &self.group_key) - .field("included", &self.included) - .field("original_verification_shares", &self.original_verification_shares) - .field("verification_shares", &self.verification_shares) - .finish_non_exhaustive() - } -} - -impl Zeroize for ThresholdView { - fn zeroize(&mut self) { - self.offset.zeroize(); - self.group_key.zeroize(); - self.included.zeroize(); - self.secret_share.zeroize(); - for (_, share) in self.original_verification_shares.iter_mut() { - share.zeroize(); + /// Offset the keys by a given scalar to allow for various account and privacy schemes. + /// + /// This offset is ephemeral and will not be included when these keys are serialized. It also + /// accumulates, so calling offset multiple times will produce a offset of the offsets' sum. + #[must_use] + pub fn offset(&self, offset: C::F) -> ThresholdKeys { + let mut res = self.clone(); + // Carry any existing offset + // Enables schemes like Monero's subaddresses which have a per-subaddress offset and then a + // one-time-key offset + res.offset = Some(offset + res.offset.unwrap_or(C::F::ZERO)); + res } - for (_, share) in self.verification_shares.iter_mut() { - share.zeroize(); + + /// Return the current offset in-use for these keys. + pub fn current_offset(&self) -> Option { + self.offset + } + + /// Return the parameters for these keys. + pub fn params(&self) -> ThresholdParams { + self.core.params + } + + /// Return the secret share for these keys. + pub fn secret_share(&self) -> &Zeroizing { + &self.core.secret_share + } + + /// Return the group key, with any offset applied. + pub fn group_key(&self) -> C::G { + self.core.group_key + (C::generator() * self.offset.unwrap_or(C::F::ZERO)) + } + + /// Return all participants' verification shares without any offsetting. + pub(crate) fn verification_shares(&self) -> HashMap { + self.core.verification_shares() + } + + /// Serialize these keys to a `Vec`. + pub fn serialize(&self) -> Zeroizing> { + self.core.serialize() + } + + /// Obtain a view of these keys, with any offset applied, interpolated for the specified signing + /// set. + pub fn view(&self, mut included: Vec) -> Result, DkgError<()>> { + if (included.len() < self.params().t.into()) || + (usize::from(self.params().n()) < included.len()) + { + Err(DkgError::InvalidSigningSet)?; + } + included.sort(); + + let mut secret_share = Zeroizing::new( + lagrange::(self.params().i(), &included) * self.secret_share().deref(), + ); + + let mut verification_shares = self.verification_shares(); + for (i, share) in verification_shares.iter_mut() { + *share *= lagrange::(*i, &included); + } + + // The offset is included by adding it to the participant with the lowest ID + let offset = self.offset.unwrap_or(C::F::ZERO); + if included[0] == self.params().i() { + *secret_share += offset; + } + *verification_shares.get_mut(&included[0]).unwrap() += C::generator() * offset; + + Ok(ThresholdView { + offset, + group_key: self.group_key(), + secret_share, + original_verification_shares: self.verification_shares(), + verification_shares, + included, + }) + } + } + + impl From> for ThresholdKeys { + fn from(keys: ThresholdCore) -> ThresholdKeys { + ThresholdKeys::new(keys) + } + } + + impl ThresholdView { + /// Return the offset for this view. + pub fn offset(&self) -> C::F { + self.offset + } + + /// Return the group key. + pub fn group_key(&self) -> C::G { + self.group_key + } + + /// Return the included signers. + pub fn included(&self) -> &[Participant] { + &self.included + } + + /// Return the interpolated, offset secret share. + pub fn secret_share(&self) -> &Zeroizing { + &self.secret_share + } + + /// Return the original verification share for the specified participant. + pub fn original_verification_share(&self, l: Participant) -> C::G { + self.original_verification_shares[&l] + } + + /// Return the interpolated, offset verification share for the specified participant. + pub fn verification_share(&self, l: Participant) -> C::G { + self.verification_shares[&l] } } } - -impl ThresholdKeys { - /// Create a new set of ThresholdKeys from a ThresholdCore. - pub fn new(core: ThresholdCore) -> ThresholdKeys { - ThresholdKeys { core: Arc::new(core), offset: None } - } - - /// Offset the keys by a given scalar to allow for various account and privacy schemes. - /// - /// This offset is ephemeral and will not be included when these keys are serialized. It also - /// accumulates, so calling offset multiple times will produce a offset of the offsets' sum. - #[must_use] - pub fn offset(&self, offset: C::F) -> ThresholdKeys { - let mut res = self.clone(); - // Carry any existing offset - // Enables schemes like Monero's subaddresses which have a per-subaddress offset and then a - // one-time-key offset - res.offset = Some(offset + res.offset.unwrap_or(C::F::ZERO)); - res - } - - /// Return the current offset in-use for these keys. - pub fn current_offset(&self) -> Option { - self.offset - } - - /// Return the parameters for these keys. - pub fn params(&self) -> ThresholdParams { - self.core.params - } - - /// Return the secret share for these keys. - pub fn secret_share(&self) -> &Zeroizing { - &self.core.secret_share - } - - /// Return the group key, with any offset applied. - pub fn group_key(&self) -> C::G { - self.core.group_key + (C::generator() * self.offset.unwrap_or(C::F::ZERO)) - } - - /// Return all participants' verification shares without any offsetting. - pub(crate) fn verification_shares(&self) -> HashMap { - self.core.verification_shares() - } - - /// Serialize these keys to a `Vec`. - pub fn serialize(&self) -> Zeroizing> { - self.core.serialize() - } - - /// Obtain a view of these keys, with any offset applied, interpolated for the specified signing - /// set. - pub fn view(&self, mut included: Vec) -> Result, DkgError<()>> { - if (included.len() < self.params().t.into()) || (usize::from(self.params().n) < included.len()) - { - Err(DkgError::InvalidSigningSet)?; - } - included.sort(); - - let mut secret_share = - Zeroizing::new(lagrange::(self.params().i, &included) * self.secret_share().deref()); - - let mut verification_shares = self.verification_shares(); - for (i, share) in verification_shares.iter_mut() { - *share *= lagrange::(*i, &included); - } - - // The offset is included by adding it to the participant with the lowest ID - let offset = self.offset.unwrap_or(C::F::ZERO); - if included[0] == self.params().i() { - *secret_share += offset; - } - *verification_shares.get_mut(&included[0]).unwrap() += C::generator() * offset; - - Ok(ThresholdView { - offset, - group_key: self.group_key(), - secret_share, - original_verification_shares: self.verification_shares(), - verification_shares, - included, - }) - } -} - -impl From> for ThresholdKeys { - fn from(keys: ThresholdCore) -> ThresholdKeys { - ThresholdKeys::new(keys) - } -} - -impl ThresholdView { - /// Return the offset for this view. - pub fn offset(&self) -> C::F { - self.offset - } - - /// Return the group key. - pub fn group_key(&self) -> C::G { - self.group_key - } - - /// Return the included signers. - pub fn included(&self) -> &[Participant] { - &self.included - } - - /// Return the interpolated, offset secret share. - pub fn secret_share(&self) -> &Zeroizing { - &self.secret_share - } - - /// Return the original verification share for the specified participant. - pub fn original_verification_share(&self, l: Participant) -> C::G { - self.original_verification_shares[&l] - } - - /// Return the interpolated, offset verification share for the specified participant. - pub fn verification_share(&self, l: Participant) -> C::G { - self.verification_shares[&l] - } -} +#[cfg(feature = "std")] +pub use lib::*; diff --git a/crypto/dkg/src/musig.rs b/crypto/dkg/src/musig.rs index dc033cef..caeb98c3 100644 --- a/crypto/dkg/src/musig.rs +++ b/crypto/dkg/src/musig.rs @@ -1,16 +1,24 @@ +#[cfg(feature = "std")] use core::ops::Deref; -use std::collections::{HashSet, HashMap}; +use std_shims::collections::HashSet; +#[cfg(feature = "std")] +use std_shims::collections::HashMap; +#[cfg(feature = "std")] use zeroize::Zeroizing; use transcript::{Transcript, RecommendedTranscript}; +#[cfg(feature = "std")] +use ciphersuite::group::ff::Field; use ciphersuite::{ - group::{ff::Field, Group, GroupEncoding}, + group::{Group, GroupEncoding}, Ciphersuite, }; -use crate::{Participant, DkgError, ThresholdParams, ThresholdCore, lagrange}; +use crate::DkgError; +#[cfg(feature = "std")] +use crate::{Participant, ThresholdParams, ThresholdCore, lagrange}; fn check_keys(keys: &[C::G]) -> Result> { if keys.is_empty() { @@ -59,6 +67,7 @@ pub fn musig_key(keys: &[C::G]) -> Result> { /// A n-of-n non-interactive DKG which does not guarantee the usability of the resulting key. /// /// Creating an aggregate key with a list containing duplicated public keys returns an error. +#[cfg(feature = "std")] pub fn musig( private_key: &Zeroizing, keys: &[C::G], diff --git a/crypto/dkg/src/tests/frost.rs b/crypto/dkg/src/tests/frost.rs index 39b2f658..50a8e90c 100644 --- a/crypto/dkg/src/tests/frost.rs +++ b/crypto/dkg/src/tests/frost.rs @@ -2,8 +2,10 @@ use std::collections::HashMap; use rand_core::{RngCore, CryptoRng}; +use ciphersuite::Ciphersuite; + use crate::{ - Ciphersuite, Participant, ThresholdParams, ThresholdCore, + Participant, ThresholdParams, ThresholdCore, frost::{KeyGenMachine, SecretShare, KeyMachine}, encryption::{EncryptionKeyMessage, EncryptedMessage}, tests::{THRESHOLD, PARTICIPANTS, clone_without}, diff --git a/tests/no-std/Cargo.toml b/tests/no-std/Cargo.toml index b85ccf0d..3b5b7246 100644 --- a/tests/no-std/Cargo.toml +++ b/tests/no-std/Cargo.toml @@ -26,7 +26,7 @@ multiexp = { path = "../../crypto/multiexp", default-features = false, features # dleq = { path = "../../crypto/dleq" } schnorr-signatures = { path = "../../crypto/schnorr", default-features = false } -# dkg = { path = "../../crypto/dkg" } +dkg = { path = "../../crypto/dkg", default-features = false } # modular-frost = { path = "../../crypto/frost" } # frost-schnorrkel = { path = "../../crypto/schnorrkel" }