From 8d4d630e0fba34521d0a7db08ae32803a6c7cb4d Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 20 Mar 2023 20:10:00 -0400 Subject: [PATCH] Fully document crypto/ --- coins/monero/generators/src/hash_to_point.rs | 2 +- crypto/ciphersuite/README.md | 7 +- crypto/ciphersuite/src/dalek.rs | 2 +- crypto/ciphersuite/src/ed448.rs | 4 +- crypto/ciphersuite/src/lib.md | 9 ++ crypto/ciphersuite/src/lib.rs | 2 +- crypto/dalek-ff-group/README.md | 7 +- crypto/dalek-ff-group/src/field.rs | 3 + crypto/dalek-ff-group/src/lib.rs | 31 ++---- crypto/dkg/README.md | 15 ++- crypto/dkg/src/encryption.rs | 50 ++++----- crypto/dkg/src/frost.rs | 33 +++--- crypto/dkg/src/lib.rs | 108 ++++++++++++------- crypto/dkg/src/promote.rs | 3 +- crypto/dkg/src/tests/mod.rs | 4 +- crypto/dleq/README.md | 11 +- crypto/dleq/src/cross_group/mod.rs | 83 +++++++++++--- crypto/dleq/src/lib.rs | 5 + crypto/dleq/src/tests/cross_group/mod.rs | 6 +- crypto/ed448/src/backend.rs | 1 + crypto/ed448/src/field.rs | 3 +- crypto/ed448/src/lib.rs | 7 +- crypto/ed448/src/point.rs | 3 +- crypto/ed448/src/scalar.rs | 6 +- crypto/ff-group-tests/README.md | 14 +-- crypto/ff-group-tests/src/lib.rs | 1 + crypto/ff-group-tests/src/prime_field.rs | 4 +- crypto/frost/README.md | 9 +- crypto/frost/src/curve/dalek.rs | 1 + crypto/frost/src/curve/ed448.rs | 6 +- crypto/frost/src/curve/kp256.rs | 1 + crypto/frost/src/curve/mod.rs | 11 +- crypto/frost/src/lib.rs | 19 +--- crypto/frost/src/sign.rs | 19 ++-- crypto/frost/src/tests/mod.rs | 6 +- crypto/frost/src/tests/vectors.rs | 2 + crypto/multiexp/README.md | 7 +- crypto/multiexp/src/batch.rs | 5 +- crypto/multiexp/src/lib.rs | 1 + crypto/schnorr/README.md | 9 +- crypto/schnorr/src/aggregate.rs | 5 +- crypto/schnorr/src/lib.rs | 4 + crypto/transcript/README.md | 7 +- crypto/transcript/src/lib.rs | 6 +- crypto/transcript/src/tests.rs | 1 + 45 files changed, 335 insertions(+), 208 deletions(-) create mode 100644 crypto/ciphersuite/src/lib.md diff --git a/coins/monero/generators/src/hash_to_point.rs b/coins/monero/generators/src/hash_to_point.rs index bf25f9ea..7f2116ba 100644 --- a/coins/monero/generators/src/hash_to_point.rs +++ b/coins/monero/generators/src/hash_to_point.rs @@ -3,7 +3,7 @@ use subtle::ConditionallySelectable; use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY}; use group::ff::{Field, PrimeField}; -use dalek_ff_group::field::FieldElement; +use dalek_ff_group::FieldElement; use crate::hash; diff --git a/crypto/ciphersuite/README.md b/crypto/ciphersuite/README.md index 8f11b567..6af02ff4 100644 --- a/crypto/ciphersuite/README.md +++ b/crypto/ciphersuite/README.md @@ -3,9 +3,10 @@ Ciphersuites for elliptic curves premised on ff/group. This library, except for the not recommended Ed448 ciphersuite, was -[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/74924095e1a0f266b58181b539d9e74fa35dc37a/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), -culminating in commit 669d2dbffc1dafb82a09d9419ea182667115df06. Any subsequent -changes have not undergone auditing. +[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/e1bb2c191b7123fd260d008e31656d090d559d21/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), +culminating in commit +[669d2dbffc1dafb82a09d9419ea182667115df06](https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06). +Any subsequent changes have not undergone auditing. ### Secp256k1/P-256 diff --git a/crypto/ciphersuite/src/dalek.rs b/crypto/ciphersuite/src/dalek.rs index 2a6fe708..bd9c70c1 100644 --- a/crypto/ciphersuite/src/dalek.rs +++ b/crypto/ciphersuite/src/dalek.rs @@ -66,7 +66,7 @@ fn test_ristretto() { ); } -/// Ciphersuite for Ed25519. +/// Ciphersuite for Ed25519, inspired by RFC-8032. /// /// hash_to_F is implemented with a naive concatenation of the dst and data, allowing transposition /// between the two. This means `dst: b"abc", data: b"def"`, will produce the same scalar as diff --git a/crypto/ciphersuite/src/ed448.rs b/crypto/ciphersuite/src/ed448.rs index e4f1d0a9..6b36fb11 100644 --- a/crypto/ciphersuite/src/ed448.rs +++ b/crypto/ciphersuite/src/ed448.rs @@ -7,7 +7,7 @@ use digest::{ use sha3::Shake256; use group::Group; -use minimal_ed448::{scalar::Scalar, point::Point}; +use minimal_ed448::{Scalar, Point}; use crate::Ciphersuite; @@ -48,7 +48,7 @@ impl FixedOutput for Shake256_114 { } impl HashMarker for Shake256_114 {} -/// Ciphersuite for Ed448. +/// Ciphersuite for Ed448, inspired by RFC-8032. This is not recommended for usage. /// /// hash_to_F is implemented with a naive concatenation of the dst and data, allowing transposition /// between the two. This means `dst: b"abc", data: b"def"`, will produce the same scalar as diff --git a/crypto/ciphersuite/src/lib.md b/crypto/ciphersuite/src/lib.md new file mode 100644 index 00000000..75eb2be9 --- /dev/null +++ b/crypto/ciphersuite/src/lib.md @@ -0,0 +1,9 @@ +# Ciphersuite + +Ciphersuites for elliptic curves premised on ff/group. + +This library, except for the not recommended Ed448 ciphersuite, was +[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/e1bb2c191b7123fd260d008e31656d090d559d21/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), +culminating in commit +[669d2dbffc1dafb82a09d9419ea182667115df06](https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06). +Any subsequent changes have not undergone auditing. diff --git a/crypto/ciphersuite/src/lib.rs b/crypto/ciphersuite/src/lib.rs index c4bbcf55..7b504b27 100644 --- a/crypto/ciphersuite/src/lib.rs +++ b/crypto/ciphersuite/src/lib.rs @@ -1,6 +1,6 @@ -#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(not(feature = "std"), no_std)] +#![doc = include_str!("lib.md")] use core::fmt::Debug; #[cfg(feature = "std")] diff --git a/crypto/dalek-ff-group/README.md b/crypto/dalek-ff-group/README.md index 5894ce31..631ad617 100644 --- a/crypto/dalek-ff-group/README.md +++ b/crypto/dalek-ff-group/README.md @@ -4,6 +4,7 @@ ff/group bindings around curve25519-dalek with a from_hash/random function based around modern dependencies. This library was -[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/74924095e1a0f266b58181b539d9e74fa35dc37a/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), -culminating in commit 669d2dbffc1dafb82a09d9419ea182667115df06. Any subsequent -changes have not undergone auditing. +[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/e1bb2c191b7123fd260d008e31656d090d559d21/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), +culminating in commit +[669d2dbffc1dafb82a09d9419ea182667115df06](https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06). +Any subsequent changes have not undergone auditing. diff --git a/crypto/dalek-ff-group/src/field.rs b/crypto/dalek-ff-group/src/field.rs index f70fa40e..9b78b895 100644 --- a/crypto/dalek-ff-group/src/field.rs +++ b/crypto/dalek-ff-group/src/field.rs @@ -19,6 +19,7 @@ use crate::{u8_from_bool, constant_time, math, from_uint}; const MODULUS: U256 = U256::from_u8(1).shl_vartime(255).saturating_sub(&U256::from_u8(19)); const WIDE_MODULUS: U512 = U256::ZERO.concat(&MODULUS); +/// A constant-time implementation of the Ed25519 field. #[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] pub struct FieldElement(U256); @@ -184,11 +185,13 @@ impl PrimeFieldBits for FieldElement { } impl FieldElement { + /// Interpret the value as a little-endian integer, square it, and reduce it into a FieldElement. pub fn from_square(value: [u8; 32]) -> FieldElement { let value = U256::from_le_bytes(value); FieldElement(value) * FieldElement(value) } + /// Perform an exponentation. pub fn pow(&self, other: FieldElement) -> FieldElement { let mut table = [FieldElement::one(); 16]; table[1] = *self; diff --git a/crypto/dalek-ff-group/src/lib.rs b/crypto/dalek-ff-group/src/lib.rs index 5a63375e..8bc71213 100644 --- a/crypto/dalek-ff-group/src/lib.rs +++ b/crypto/dalek-ff-group/src/lib.rs @@ -1,5 +1,6 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![no_std] +#![doc = include_str!("../README.md")] use core::{ borrow::Borrow, @@ -23,15 +24,10 @@ use dalek::{ constants, traits::Identity, scalar::Scalar as DScalar, - edwards::{ - EdwardsPoint as DEdwardsPoint, EdwardsBasepointTable as DEdwardsBasepointTable, - CompressedEdwardsY as DCompressedEdwards, - }, - ristretto::{ - RistrettoPoint as DRistrettoPoint, RistrettoBasepointTable as DRistrettoBasepointTable, - CompressedRistretto as DCompressedRistretto, - }, + edwards::{EdwardsPoint as DEdwardsPoint, EdwardsBasepointTable, CompressedEdwardsY}, + ristretto::{RistrettoPoint as DRistrettoPoint, RistrettoBasepointTable, CompressedRistretto}, }; +pub use constants::{ED25519_BASEPOINT_TABLE, RISTRETTO_BASEPOINT_TABLE}; use group::{ ff::{Field, PrimeField, FieldBits, PrimeFieldBits}, @@ -39,7 +35,8 @@ use group::{ prime::PrimeGroup, }; -pub mod field; +mod field; +pub use field::FieldElement; // Feature gated due to MSRV requirements #[cfg(feature = "black_box")] @@ -362,7 +359,6 @@ macro_rules! dalek_group { $torsion_free: expr, $Table: ident, - $DTable: ident, $DCompressed: ident, @@ -376,6 +372,7 @@ macro_rules! dalek_group { constant_time!($Point, $DPoint); math_neg!($Point, Scalar, $DPoint::add, $DPoint::sub, $DPoint::mul); + /// The basepoint for this curve. pub const $BASEPOINT_POINT: $Point = $Point(constants::$BASEPOINT_POINT); impl Sum<$Point> for $Point { @@ -437,16 +434,10 @@ macro_rules! dalek_group { impl PrimeGroup for $Point {} - /// Wrapper around the dalek Table type, offering efficient multiplication against the - /// basepoint. - pub struct $Table(pub $DTable); - deref_borrow!($Table, $DTable); - pub const $BASEPOINT_TABLE: $Table = $Table(constants::$BASEPOINT_TABLE); - impl Mul for &$Table { type Output = $Point; fn mul(self, b: Scalar) -> $Point { - $Point(&b.0 * &self.0) + $Point(&b.0 * self) } } @@ -468,8 +459,7 @@ dalek_group!( DEdwardsPoint, |point: DEdwardsPoint| point.is_torsion_free(), EdwardsBasepointTable, - DEdwardsBasepointTable, - DCompressedEdwards, + CompressedEdwardsY, ED25519_BASEPOINT_POINT, ED25519_BASEPOINT_TABLE ); @@ -485,8 +475,7 @@ dalek_group!( DRistrettoPoint, |_| true, RistrettoBasepointTable, - DRistrettoBasepointTable, - DCompressedRistretto, + CompressedRistretto, RISTRETTO_BASEPOINT_POINT, RISTRETTO_BASEPOINT_TABLE ); diff --git a/crypto/dkg/README.md b/crypto/dkg/README.md index c6a35967..27e3412a 100644 --- a/crypto/dkg/README.md +++ b/crypto/dkg/README.md @@ -3,15 +3,14 @@ A collection of implementations of various distributed key generation protocols. All included protocols resolve into the provided `Threshold` types, intended to -enable their modularity. +enable their modularity. Additional utilities around these types, such as +promotion from one generator to another, are also provided. -Additional utilities around them, such as promotion from one generator to -another, are also provided. - -Currently included is the two-round protocol from the +Currently, the only included protocol is the two-round protocol from the [FROST paper](https://eprint.iacr.org/2020/852). This library was -[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/74924095e1a0f266b58181b539d9e74fa35dc37a/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), -culminating in commit 669d2dbffc1dafb82a09d9419ea182667115df06. Any subsequent -changes have not undergone auditing. +[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/e1bb2c191b7123fd260d008e31656d090d559d21/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), +culminating in commit +[669d2dbffc1dafb82a09d9419ea182667115df06](https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06). +Any subsequent changes have not undergone auditing. diff --git a/crypto/dkg/src/encryption.rs b/crypto/dkg/src/encryption.rs index c37221ec..55fd041d 100644 --- a/crypto/dkg/src/encryption.rs +++ b/crypto/dkg/src/encryption.rs @@ -1,8 +1,5 @@ use core::{ops::Deref, fmt}; -use std::{ - io::{self, Read, Write}, - collections::HashMap, -}; +use std::{io, collections::HashMap}; use thiserror::Error; @@ -26,19 +23,27 @@ use dleq::DLEqProof; use crate::{Participant, ThresholdParams}; -pub trait ReadWrite: Sized { - fn read(reader: &mut R, params: ThresholdParams) -> io::Result; - fn write(&self, writer: &mut W) -> io::Result<()>; +mod sealed { + use super::*; - fn serialize(&self) -> Vec { - let mut buf = vec![]; - self.write(&mut buf).unwrap(); - buf + pub trait ReadWrite: Sized { + fn read(reader: &mut R, params: ThresholdParams) -> io::Result; + fn write(&self, writer: &mut W) -> io::Result<()>; + + fn serialize(&self) -> Vec { + let mut buf = vec![]; + self.write(&mut buf).unwrap(); + buf + } } -} -pub trait Message: Clone + PartialEq + Eq + fmt::Debug + Zeroize + ReadWrite {} -impl Message for M {} + pub trait Message: Clone + PartialEq + Eq + fmt::Debug + Zeroize + ReadWrite {} + impl Message for M {} + + pub trait Encryptable: Clone + AsRef<[u8]> + AsMut<[u8]> + Zeroize + ReadWrite {} + impl + AsMut<[u8]> + Zeroize + ReadWrite> Encryptable for E {} +} +pub(crate) use sealed::*; /// Wraps a message with a key to use for encryption in the future. #[derive(Clone, PartialEq, Eq, Debug, Zeroize)] @@ -49,11 +54,11 @@ pub struct EncryptionKeyMessage { // Doesn't impl ReadWrite so that doesn't need to be imported impl EncryptionKeyMessage { - pub fn read(reader: &mut R, params: ThresholdParams) -> io::Result { + pub fn read(reader: &mut R, params: ThresholdParams) -> io::Result { Ok(Self { msg: M::read(reader, params)?, enc_key: C::read_G(reader)? }) } - pub fn write(&self, writer: &mut W) -> io::Result<()> { + pub fn write(&self, writer: &mut W) -> io::Result<()> { self.msg.write(writer)?; writer.write_all(self.enc_key.to_bytes().as_ref()) } @@ -70,9 +75,6 @@ impl EncryptionKeyMessage { } } -pub trait Encryptable: Clone + AsRef<[u8]> + AsMut<[u8]> + Zeroize + ReadWrite {} -impl + AsMut<[u8]> + Zeroize + ReadWrite> Encryptable for E {} - /// An encrypted message, with a per-message encryption key enabling revealing specific messages /// without side effects. #[derive(Clone, Zeroize)] @@ -166,7 +168,7 @@ fn encrypt( } impl EncryptedMessage { - pub fn read(reader: &mut R, params: ThresholdParams) -> io::Result { + pub fn read(reader: &mut R, params: ThresholdParams) -> io::Result { Ok(Self { key: C::read_G(reader)?, pop: SchnorrSignature::::read(reader)?, @@ -174,7 +176,7 @@ impl EncryptedMessage { }) } - pub fn write(&self, writer: &mut W) -> io::Result<()> { + pub fn write(&self, writer: &mut W) -> io::Result<()> { writer.write_all(self.key.to_bytes().as_ref())?; self.pop.write(writer)?; self.msg.write(writer) @@ -254,7 +256,7 @@ impl EncryptedMessage { } } -/// A proof that the provided point is the legitimately derived shared key for some message. +/// A proof that the provided encryption key is a legitimately derived shared key for some message. #[derive(Clone, PartialEq, Eq, Debug, Zeroize)] pub struct EncryptionKeyProof { key: Zeroizing, @@ -262,11 +264,11 @@ pub struct EncryptionKeyProof { } impl EncryptionKeyProof { - pub fn read(reader: &mut R) -> io::Result { + pub fn read(reader: &mut R) -> io::Result { Ok(Self { key: Zeroizing::new(C::read_G(reader)?), dleq: DLEqProof::read(reader)? }) } - pub fn write(&self, writer: &mut W) -> io::Result<()> { + pub fn write(&self, writer: &mut W) -> io::Result<()> { writer.write_all(self.key.to_bytes().as_ref())?; self.dleq.write(writer) } diff --git a/crypto/dkg/src/frost.rs b/crypto/dkg/src/frost.rs index 2c4d74d4..95c2f718 100644 --- a/crypto/dkg/src/frost.rs +++ b/crypto/dkg/src/frost.rs @@ -43,11 +43,11 @@ fn challenge(context: &str, l: Participant, R: &[u8], Am: &[u8]) } /// The commitments message, intended to be broadcast to all other parties. -/// Every participant should only provide one set of commitments to all parties. -/// If any participant sends multiple sets of commitments, they are faulty and should be presumed -/// malicious. -/// As this library does not handle networking, it is also unable to detect if any participant is -/// so faulty. That responsibility lies with the caller. +/// +/// Every participant should only provide one set of commitments to all parties. If any +/// participant sends multiple sets of commitments, they are faulty and should be presumed +/// malicious. As this library does not handle networking, it is unable to detect if any +/// participant is so faulty. That responsibility lies with the caller. #[derive(Clone, PartialEq, Eq, Debug, Zeroize)] pub struct Commitments { commitments: Vec, @@ -91,13 +91,14 @@ pub struct KeyGenMachine { } impl KeyGenMachine { - /// Creates a new machine to generate a key for the specified curve in the specified multisig. + /// Create a new machine to generate a key. // The context string should be unique among multisigs. pub fn new(params: ThresholdParams, context: String) -> KeyGenMachine { KeyGenMachine { params, context, _curve: PhantomData } } /// Start generating a key according to the FROST DKG spec. + /// /// Returns a commitments message to be sent to all parties over an authenticated channel. If any /// party submits multiple sets of commitments, they MUST be treated as malicious. pub fn generate_coefficients( @@ -168,7 +169,9 @@ fn polynomial( /// The secret share message, to be sent to the party it's intended for over an authenticated /// channel. +/// /// If any participant sends multiple secret shares to another participant, they are faulty. + // This should presumably be written as SecretShare(Zeroizing). // It's unfortunately not possible as F::Repr doesn't have Zeroize as a bound. // The encryption system also explicitly uses Zeroizing so it can ensure anything being @@ -281,8 +284,10 @@ impl SecretShareMachine { } /// Continue generating a key. + /// /// Takes in everyone else's commitments. Returns a HashMap of encrypted secret shares to be sent /// over authenticated channels to their relevant counterparties. + /// /// If any participant sends multiple secret shares to another participant, they are faulty. #[allow(clippy::type_complexity)] pub fn generate_secret_shares( @@ -321,11 +326,11 @@ impl SecretShareMachine { } } -/// Advancement of the the secret share state machine protocol. -/// This machine will 'complete' the protocol, by a local perspective, and can be the last -/// interactive component. In order to be secure, the parties must confirm having successfully -/// completed the protocol (an effort out of scope to this library), yet this is modelled by one -/// more state transition. +/// Advancement of the the secret share state machine. +/// +/// This machine will 'complete' the protocol, by a local perspective. In order to be secure, +/// the parties must confirm having successfully completed the protocol (an effort out of scope to +/// this library), yet this is modeled by one more state transition (BlameMachine). pub struct KeyMachine { params: ThresholdParams, secret: Zeroizing, @@ -397,8 +402,10 @@ enum BatchId { impl KeyMachine { /// Calculate our share given the shares sent to us. + /// /// Returns a BlameMachine usable to determine if faults in the protocol occurred. - /// Will error on, and return a blame proof for, the first-observed case of faulty behavior. + /// + /// This will error on, and return a blame proof for, the first-observed case of faulty behavior. pub fn calculate_share( mut self, rng: &mut R, @@ -473,6 +480,7 @@ impl KeyMachine { } } +/// A machine capable of handling blame proofs. pub struct BlameMachine { commitments: HashMap>, encryption: Encryption, @@ -574,6 +582,7 @@ impl BlameMachine { } } +/// A machine capable of handling an arbitrary amount of additional blame proofs. #[derive(Debug, Zeroize)] pub struct AdditionalBlameMachine(BlameMachine); impl AdditionalBlameMachine { diff --git a/crypto/dkg/src/lib.rs b/crypto/dkg/src/lib.rs index 81e58b1b..27fb5670 100644 --- a/crypto/dkg/src/lib.rs +++ b/crypto/dkg/src/lib.rs @@ -1,10 +1,5 @@ -#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))] - -//! A collection of implementations of various distributed key generation protocols. -//! They all resolve into the provided Threshold types intended to enable their modularity. -//! Additional utilities around them, such as promotion from one generator to another, are also -//! provided. +#![doc = include_str!("../README.md")] use core::{ fmt::{self, Debug}, @@ -43,6 +38,7 @@ pub mod tests; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Participant(pub(crate) u16); impl Participant { + /// Create a new Participant identifier from a u16. pub fn new(i: u16) -> Option { if i == 0 { None @@ -51,6 +47,7 @@ impl Participant { } } + /// Convert a Participant identifier to bytes. #[allow(clippy::wrong_self_convention)] pub fn to_bytes(&self) -> [u8; 2] { self.0.to_le_bytes() @@ -69,32 +66,38 @@ impl fmt::Display for Participant { } } -/// Various errors possible during key generation/signing. +/// Various errors possible during key generation. #[derive(Clone, PartialEq, Eq, Debug, Error)] pub enum DkgError { + /// A parameter was zero. #[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), + /// The threshold exceeded the amount of participants. + #[error("invalid threshold (max {1}, got {0})")] + InvalidThreshold(u16, u16), + /// Invalid participant identifier. #[error("invalid participant (0 < participant <= {0}, yet participant is {1})")] InvalidParticipant(u16, Participant), + /// Invalid signing set. #[error("invalid signing set")] InvalidSigningSet, + /// Invalid amount of participants. #[error("invalid participant quantity (expected {0}, got {1})")] InvalidParticipantQuantity(usize, usize), + /// A participant was duplicated. #[error("duplicated participant ({0})")] DuplicatedParticipant(Participant), + /// A participant was missing. #[error("missing participant {0}")] MissingParticipant(Participant), + /// An invalid proof of knowledge was provided. #[error("invalid proof of knowledge (participant {0})")] InvalidProofOfKnowledge(Participant), + /// An invalid DKG share was provided. #[error("invalid share (participant {participant}, blame {blame})")] InvalidShare { participant: Participant, blame: Option }, - - #[error("internal error ({0})")] - InternalError(&'static str), } // Validate a map of values to have the expected included participants @@ -137,6 +140,7 @@ pub struct ThresholdParams { } 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))?; @@ -145,7 +149,7 @@ impl ThresholdParams { // 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::InvalidRequiredQuantity(t, n))?; + Err(DkgError::InvalidThreshold(t, n))?; } if u16::from(i) > n { Err(DkgError::InvalidParticipant(n, i))?; @@ -154,12 +158,15 @@ impl ThresholdParams { 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 } @@ -237,14 +244,18 @@ impl ThresholdCore { 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 } @@ -253,6 +264,7 @@ impl ThresholdCore { 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)?; @@ -269,61 +281,56 @@ impl ThresholdCore { 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 } - pub fn read(reader: &mut R) -> Result, DkgError<()>> { + /// Read keys from a type satisfying std::io::Read. + pub fn read(reader: &mut R) -> io::Result> { { - let missing = DkgError::InternalError("ThresholdCore serialization is missing its curve"); - let different = DkgError::InternalError("deserializing ThresholdCore for another curve"); + 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).map_err(|_| missing.clone())?; + reader.read_exact(&mut id_len)?; if u32::try_from(C::ID.len()).unwrap().to_le_bytes() != id_len { - Err(different.clone())?; + Err(different())?; } let mut id = vec![0; C::ID.len()]; - reader.read_exact(&mut id).map_err(|_| missing)?; + reader.read_exact(&mut id)?; if id != C::ID { - Err(different)?; + Err(different())?; } } let (t, n, i) = { - let mut read_u16 = || { + let mut read_u16 = || -> io::Result { let mut value = [0; 2]; - reader - .read_exact(&mut value) - .map_err(|_| DkgError::InternalError("missing participant quantities"))?; + reader.read_exact(&mut value)?; Ok(u16::from_le_bytes(value)) }; ( read_u16()?, read_u16()?, Participant::new(read_u16()?) - .ok_or(DkgError::InternalError("invalid participant index"))?, + .ok_or(io::Error::new(io::ErrorKind::Other, "invalid participant index"))?, ) }; - let secret_share = Zeroizing::new( - C::read_F(reader).map_err(|_| DkgError::InternalError("invalid secret share"))?, - ); + 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) - .map_err(|_| DkgError::InternalError("invalid verification share"))?, - ); + verification_shares.insert(l, ::read_G(reader)?); } Ok(ThresholdCore::new( - ThresholdParams::new(t, n, i).map_err(|_| DkgError::InternalError("invalid parameters"))?, + ThresholdParams::new(t, n, i) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "invalid parameters"))?, secret_share, verification_shares, )) @@ -343,7 +350,7 @@ pub struct ThresholdKeys { pub(crate) offset: Option, } -/// View of keys passed to algorithm implementations. +/// View of keys, interpolated and offset for usage. #[derive(Clone)] pub struct ThresholdView { offset: C::F, @@ -383,13 +390,15 @@ impl Zeroize for ThresholdView { } 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 account and privacy schemes. - /// This offset is ephemeral and will not be included when these keys are serialized. - /// Keys offset multiple times will form a new offset of their sum. + /// 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(); @@ -400,33 +409,38 @@ impl ThresholdKeys { res } - /// Returns the current offset in-use for these keys. + /// 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 } - /// Returns the group key with any offset applied. + /// 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_else(C::F::zero)) } - /// Returns all participants' verification shares without any offsetting. + /// 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()) { @@ -460,27 +474,39 @@ impl ThresholdKeys { } } +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] } diff --git a/crypto/dkg/src/promote.rs b/crypto/dkg/src/promote.rs index 7edb85d5..8f42c19d 100644 --- a/crypto/dkg/src/promote.rs +++ b/crypto/dkg/src/promote.rs @@ -60,9 +60,10 @@ impl GeneratorProof { } /// Promote a set of keys from one generator to another, where the elliptic curve is the same. +/// /// Since the Ciphersuite trait additionally specifies a generator, this provides an O(n) way to /// update the generator used with keys. This outperforms the key generation protocol which is -// exponential. +/// exponential. pub struct GeneratorPromotion { base: ThresholdKeys, proof: GeneratorProof, diff --git a/crypto/dkg/src/tests/mod.rs b/crypto/dkg/src/tests/mod.rs index 5617121f..250d7a59 100644 --- a/crypto/dkg/src/tests/mod.rs +++ b/crypto/dkg/src/tests/mod.rs @@ -7,7 +7,7 @@ use ciphersuite::{group::ff::Field, Ciphersuite}; use crate::{Participant, ThresholdCore, ThresholdKeys, lagrange}; -/// FROST generation test. +/// FROST key generation testing utility. pub mod frost; use frost::frost_gen; @@ -17,7 +17,7 @@ use promote::test_generator_promotion; /// Constant amount of participants to use when testing. pub const PARTICIPANTS: u16 = 5; -/// Constant threshold of participants to use when signing. +/// Constant threshold of participants to use when testing. pub const THRESHOLD: u16 = ((PARTICIPANTS / 3) * 2) + 1; /// Clone a map without a specific value. diff --git a/crypto/dleq/README.md b/crypto/dleq/README.md index 76c33d6a..58505711 100644 --- a/crypto/dleq/README.md +++ b/crypto/dleq/README.md @@ -1,14 +1,17 @@ # Discrete Log Equality Implementation of discrete log equality proofs for curves implementing -`ff`/`group`. There is also a highly experimental cross-group DLEq proof, under +`ff`/`group`. + +There is also a highly experimental cross-group DLEq proof, under the `experimental` feature, which has no formal proofs available yet is available here regardless. This library, except for the `experimental` feature, was -[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/74924095e1a0f266b58181b539d9e74fa35dc37a/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), -culminating in commit 669d2dbffc1dafb82a09d9419ea182667115df06. Any subsequent -changes have not undergone auditing. +[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/e1bb2c191b7123fd260d008e31656d090d559d21/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), +culminating in commit +[669d2dbffc1dafb82a09d9419ea182667115df06](https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06). +Any subsequent changes have not undergone auditing. ### Cross-Group DLEq diff --git a/crypto/dleq/src/cross_group/mod.rs b/crypto/dleq/src/cross_group/mod.rs index b59a84fc..c9bd113a 100644 --- a/crypto/dleq/src/cross_group/mod.rs +++ b/crypto/dleq/src/cross_group/mod.rs @@ -18,6 +18,7 @@ use group::{ }; use multiexp::BatchVerifier; +/// Scalar utilities. pub mod scalar; use scalar::{scalar_convert, mutual_scalar_from_bytes}; @@ -63,15 +64,25 @@ pub(crate) fn read_point(r: &mut R) -> std::io::Result { + /// The generator used to commit to values. + /// + /// This should likely be the curve's traditional 'basepoint'. pub primary: G, + /// The generator used to blind values. This must be distinct from the primary generator. pub alt: G, } impl Generators { - pub fn new(primary: G, alt: G) -> Generators { - Generators { primary, alt } + /// Create a new set of generators. + pub fn new(primary: G, alt: G) -> Option> { + if primary == alt { + None?; + } + Some(Generators { primary, alt }) } fn transcript(&self, transcript: &mut T) { @@ -81,14 +92,19 @@ impl Generators { } } +/// Error for cross-group DLEq proofs. #[derive(Error, PartialEq, Eq, Debug)] pub enum DLEqError { + /// Invalid proof of knowledge. #[error("invalid proof of knowledge")] InvalidProofOfKnowledge, + /// Invalid proof length. #[error("invalid proof length")] InvalidProofLength, + /// Invalid challenge. #[error("invalid challenge")] InvalidChallenge, + /// Invalid proof. #[error("invalid proof")] InvalidProof, } @@ -115,7 +131,8 @@ pub struct __DLEqProof< } macro_rules! dleq { - ($name: ident, $signature: expr, $remainder: literal) => { + ($doc_str: expr, $name: ident, $signature: expr, $remainder: literal,) => { + #[doc = $doc_str] pub type $name = __DLEqProof< G0, G1, @@ -141,21 +158,50 @@ macro_rules! dleq { // over both scalar fields, hence its application here as well. This is mainly here as a point of // reference for the following DLEq proofs, all which use merged challenges, and isn't performant // in comparison to the others -dleq!(ClassicLinearDLEq, BitSignature::ClassicLinear, false); +dleq!( + "The DLEq proof described in MRL-0010.", + ClassicLinearDLEq, + BitSignature::ClassicLinear, + false, +); // Proves for 2-bits at a time to save 3/7 elements of every other bit // <9% smaller than CompromiseLinear, yet ~12% slower -dleq!(ConciseLinearDLEq, BitSignature::ConciseLinear, true); +dleq!( + "A DLEq proof modified from MRL-0010, proving for two bits at a time to save on space.", + ConciseLinearDLEq, + BitSignature::ConciseLinear, + true, +); // Uses AOS signatures of the form R, s, to enable the final step of the ring signature to be // batch verified, at the cost of adding an additional element per bit -dleq!(EfficientLinearDLEq, BitSignature::EfficientLinear, false); +dleq!( + " + A DLEq proof modified from MRL-0010, using R, s forms instead of c, s forms to enable batch + verification at the cost of space usage. + ", + EfficientLinearDLEq, + BitSignature::EfficientLinear, + false, +); // Proves for 2-bits at a time while using the R, s form. This saves 3/7 elements of every other // bit, while adding 1 element to every bit, and is more efficient than ConciseLinear yet less // efficient than EfficientLinear due to having more ring signature steps which aren't batched // >25% smaller than EfficientLinear and just 11% slower, making it the recommended option -dleq!(CompromiseLinearDLEq, BitSignature::CompromiseLinear, true); +dleq!( + " + A DLEq proof modified from MRL-0010, using R, s forms instead of c, s forms, while proving for + two bits at a time, to enable batch verification and take advantage of space savings. + + This isn't quite as efficient as EfficientLinearDLEq, and isn't as compact as + ConciseLinearDLEq, yet strikes a strong balance of performance and conciseness. + ", + CompromiseLinearDLEq, + BitSignature::CompromiseLinear, + true, +); impl< G0: PrimeGroup + Zeroize, @@ -297,10 +343,13 @@ where (proof, f) } - /// Prove the cross-Group Discrete Log Equality for the points derived from the scalar created as - /// the output of the passed in Digest. Given the non-standard requirements to achieve - /// uniformity, needing to be < 2^x instead of less than a prime moduli, this is the simplest way - /// to safely and securely generate a Scalar, without risk of failure, nor bias. + /// Prove the Cross-Group Discrete Log Equality for the points derived from the scalar created as + /// the output of the passed in Digest. + /// + /// Given the non-standard requirements to achieve uniformity, needing to be < 2^x instead of + /// less than a prime moduli, this is the simplest way to safely and securely generate a Scalar, + /// without risk of failure nor bias. + /// /// It also ensures a lack of determinable relation between keys, guaranteeing security in the /// currently expected use case for this, atomic swaps, where each swap leaks the key. Knowing /// the relationship between keys would allow breaking all swaps after just one. @@ -323,9 +372,11 @@ where Self::prove_internal(rng, transcript, generators, f) } - /// Prove the cross-Group Discrete Log Equality for the points derived from the scalar passed in, - /// failing if it's not mutually valid. This allows for rejection sampling externally derived - /// scalars until they're safely usable, as needed. + /// Prove the Cross-Group Discrete Log Equality for the points derived from the scalar passed in, + /// failing if it's not mutually valid. + /// + /// This allows for rejection sampling externally derived scalars until they're safely usable, + /// as needed. #[allow(clippy::type_complexity)] pub fn prove_without_bias( rng: &mut R, @@ -337,7 +388,7 @@ where .map(|f1| Self::prove_internal(rng, transcript, generators, (f0, Zeroizing::new(f1)))) } - /// Verify a cross-Group Discrete Log Equality statement, returning the points proven for. + /// Verify a Cross-Group Discrete Log Equality proof, returning the points proven for. pub fn verify( &self, rng: &mut R, @@ -386,6 +437,7 @@ where Ok(keys) } + /// Write a Cross-Group Discrete Log Equality proof to a type satisfying std::io::Write. #[cfg(feature = "serialize")] pub fn write(&self, w: &mut W) -> std::io::Result<()> { for bit in &self.bits { @@ -398,6 +450,7 @@ where self.poks.1.write(w) } + /// Read a Cross-Group Discrete Log Equality proof from a type satisfying std::io::Read. #[cfg(feature = "serialize")] pub fn read(r: &mut R) -> std::io::Result { let capacity = usize::try_from(G0::Scalar::CAPACITY.min(G1::Scalar::CAPACITY)).unwrap(); diff --git a/crypto/dleq/src/lib.rs b/crypto/dleq/src/lib.rs index 25062feb..b99a1de7 100644 --- a/crypto/dleq/src/lib.rs +++ b/crypto/dleq/src/lib.rs @@ -1,5 +1,6 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(not(feature = "std"), no_std)] +#![doc = include_str!("../README.md")] use core::ops::Deref; @@ -15,6 +16,8 @@ use group::prime::PrimeGroup; #[cfg(feature = "serialize")] use std::io::{self, ErrorKind, Error, Read, Write}; +/// A cross-group DLEq proof capable of proving that two public keys, across two different curves, +/// share a private key. #[cfg(feature = "experimental")] pub mod cross_group; @@ -96,6 +99,7 @@ fn read_scalar(r: &mut R) -> io::Result { /// Error for DLEq proofs. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum DLEqError { + /// The proof was invalid. InvalidProof, } @@ -201,6 +205,7 @@ impl DLEqProof { } /// A proof that multiple series of points each have a single discrete logarithm across generators. +/// /// This is effectively n distinct DLEq proofs, one for each discrete logarithm and its points /// across some generators, yet with a smaller overall proof size. #[cfg(feature = "std")] diff --git a/crypto/dleq/src/tests/cross_group/mod.rs b/crypto/dleq/src/tests/cross_group/mod.rs index b05f31eb..373cf1bf 100644 --- a/crypto/dleq/src/tests/cross_group/mod.rs +++ b/crypto/dleq/src/tests/cross_group/mod.rs @@ -40,14 +40,16 @@ pub(crate) fn generators() -> (Generators, Generators) { &(hex!("0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0").into()), ) .unwrap(), - ), + ) + .unwrap(), Generators::new( EdwardsPoint::generator(), EdwardsPoint::from_bytes(&hex!( "8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94" )) .unwrap(), - ), + ) + .unwrap(), ) } diff --git a/crypto/ed448/src/backend.rs b/crypto/ed448/src/backend.rs index 5148cebf..03e4993e 100644 --- a/crypto/ed448/src/backend.rs +++ b/crypto/ed448/src/backend.rs @@ -73,6 +73,7 @@ macro_rules! field { } impl $FieldName { + /// Perform an exponentation. pub fn pow(&self, other: $FieldName) -> $FieldName { let mut table = [Self(U512::ONE); 16]; table[1] = *self; diff --git a/crypto/ed448/src/field.rs b/crypto/ed448/src/field.rs index cbabb62f..4ec62f75 100644 --- a/crypto/ed448/src/field.rs +++ b/crypto/ed448/src/field.rs @@ -4,11 +4,12 @@ use crypto_bigint::{U512, U1024}; use crate::field; +/// Ed448 field element. #[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Zeroize)] pub struct FieldElement(pub(crate) U512); // 2**448 - 2**224 - 1 -pub const MODULUS: FieldElement = FieldElement(U512::from_be_hex(concat!( +pub(crate) const MODULUS: FieldElement = FieldElement(U512::from_be_hex(concat!( "00000000000000", "00", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffe", diff --git a/crypto/ed448/src/lib.rs b/crypto/ed448/src/lib.rs index 81823aac..558533fe 100644 --- a/crypto/ed448/src/lib.rs +++ b/crypto/ed448/src/lib.rs @@ -1,13 +1,14 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![no_std] +#![doc = include_str!("../README.md")] mod backend; -pub mod scalar; +mod scalar; pub use scalar::Scalar; -pub mod field; +mod field; pub use field::FieldElement; -pub mod point; +mod point; pub use point::Point; diff --git a/crypto/ed448/src/point.rs b/crypto/ed448/src/point.rs index 54e8caac..0267187e 100644 --- a/crypto/ed448/src/point.rs +++ b/crypto/ed448/src/point.rs @@ -47,6 +47,7 @@ fn recover_x(y: FieldElement) -> CtOption { }) } +/// Ed448 point. #[derive(Clone, Copy, Debug, Zeroize)] pub struct Point { x: FieldElement, @@ -270,7 +271,7 @@ impl MulAssign<&Scalar> for Point { } impl Point { - pub fn is_torsion_free(&self) -> Choice { + fn is_torsion_free(&self) -> Choice { (*self * SCALAR_MODULUS).is_identity() } } diff --git a/crypto/ed448/src/scalar.rs b/crypto/ed448/src/scalar.rs index f2ce816d..3821385c 100644 --- a/crypto/ed448/src/scalar.rs +++ b/crypto/ed448/src/scalar.rs @@ -2,13 +2,14 @@ use zeroize::Zeroize; use crypto_bigint::{U512, U1024}; -pub use crate::field; +use crate::field; +/// Ed448 Scalar field element. #[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Zeroize)] pub struct Scalar(pub(crate) U512); // 2**446 - 13818066809895115352007386748515426880336692474882178609894547503885 -pub const MODULUS: Scalar = Scalar(U512::from_be_hex(concat!( +pub(crate) const MODULUS: Scalar = Scalar(U512::from_be_hex(concat!( "00000000000000", "00", "3fffffffffffffffffffffffffffffffffffffffffffffffffffffff", @@ -27,6 +28,7 @@ const WIDE_MODULUS: U1024 = U1024::from_be_hex(concat!( field!(Scalar, MODULUS, WIDE_MODULUS, 446); impl Scalar { + /// Perform a wide reduction to obtain a non-biased Scalar. pub fn wide_reduce(bytes: [u8; 114]) -> Scalar { Scalar(reduce(U1024::from_le_slice(&[bytes.as_ref(), &[0; 14]].concat()))) } diff --git a/crypto/ff-group-tests/README.md b/crypto/ff-group-tests/README.md index cb7a161d..6783dca4 100644 --- a/crypto/ff-group-tests/README.md +++ b/crypto/ff-group-tests/README.md @@ -1,10 +1,12 @@ # FF/Group Tests -A series of sanity checks for implementors of the ff/group APIs. Implementors -are assumed to be of a non-trivial size. These tests do not attempt to check if -constant time implementations are used. +A series of sanity checks for implementors of the ff/group APIs. + +Implementors are assumed to be of a non-trivial size. These tests do not attempt +to check if constant time implementations are used. This library was -[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/74924095e1a0f266b58181b539d9e74fa35dc37a/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), -culminating in commit 669d2dbffc1dafb82a09d9419ea182667115df06. Any subsequent -changes have not undergone auditing. +[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/e1bb2c191b7123fd260d008e31656d090d559d21/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), +culminating in commit +[669d2dbffc1dafb82a09d9419ea182667115df06](https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06). +Any subsequent changes have not undergone auditing. diff --git a/crypto/ff-group-tests/src/lib.rs b/crypto/ff-group-tests/src/lib.rs index 1b05b98a..0f14eb69 100644 --- a/crypto/ff-group-tests/src/lib.rs +++ b/crypto/ff-group-tests/src/lib.rs @@ -1,4 +1,5 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] /// Tests for the Field trait. pub mod field; diff --git a/crypto/ff-group-tests/src/prime_field.rs b/crypto/ff-group-tests/src/prime_field.rs index 0f639372..e62259fe 100644 --- a/crypto/ff-group-tests/src/prime_field.rs +++ b/crypto/ff-group-tests/src/prime_field.rs @@ -14,7 +14,7 @@ pub fn test_one() { assert_eq!(F::one(), F::from(1u64), "1 != 1"); } -/// Test From for F works. +/// Test `From` for F works. pub fn test_from_u64() { assert_eq!(F::one().double(), F::from(2u64), "2 != 2"); } @@ -279,7 +279,7 @@ pub fn test_s() { assert_eq!(s, F::S, "incorrect S"); } -// Test the root of unity is correct for the given multiplicative generator. +/// Test the root of unity is correct for the provided multiplicative generator. pub fn test_root_of_unity() { // "It can be calculated by exponentiating `Self::multiplicative_generator` by `t`, where // `t = (modulus - 1) >> Self::S`." diff --git a/crypto/frost/README.md b/crypto/frost/README.md index 957c24fa..27845844 100644 --- a/crypto/frost/README.md +++ b/crypto/frost/README.md @@ -13,9 +13,10 @@ This library offers ciphersuites compatible with the 11 is supported. This library was -[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/74924095e1a0f266b58181b539d9e74fa35dc37a/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), -culminating in commit 669d2dbffc1dafb82a09d9419ea182667115df06. Any subsequent -changes have not undergone auditing. While this audit included FROST's -definition of Ed448, the underlying Ed448 ciphersuite (offered by the +[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/e1bb2c191b7123fd260d008e31656d090d559d21/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), +culminating in commit +[669d2dbffc1dafb82a09d9419ea182667115df06](https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06). +Any subsequent changes have not undergone auditing. While this audit included +FROST's definition of Ed448, the underlying Ed448 ciphersuite (offered by the ciphersuite crate) was not audited, nor was the minimal-ed448 crate implementing the curve itself. diff --git a/crypto/frost/src/curve/dalek.rs b/crypto/frost/src/curve/dalek.rs index 25e97c60..f4821aed 100644 --- a/crypto/frost/src/curve/dalek.rs +++ b/crypto/frost/src/curve/dalek.rs @@ -22,6 +22,7 @@ macro_rules! dalek_curve { const CONTEXT: &'static [u8] = $CONTEXT; } + /// The challenge function for this ciphersuite. #[derive(Copy, Clone)] pub struct $Hram; impl Hram<$Curve> for $Hram { diff --git a/crypto/frost/src/curve/ed448.rs b/crypto/frost/src/curve/ed448.rs index 5b24dad0..5a29598a 100644 --- a/crypto/frost/src/curve/ed448.rs +++ b/crypto/frost/src/curve/ed448.rs @@ -11,11 +11,12 @@ impl Curve for Ed448 { const CONTEXT: &'static [u8] = CONTEXT; } +// The RFC-8032 Ed448 challenge function. #[derive(Copy, Clone)] -pub struct Ietf8032Ed448Hram; +pub(crate) struct Ietf8032Ed448Hram; impl Ietf8032Ed448Hram { #[allow(non_snake_case)] - pub fn hram(context: &[u8], R: &Point, A: &Point, m: &[u8]) -> Scalar { + pub(crate) fn hram(context: &[u8], R: &Point, A: &Point, m: &[u8]) -> Scalar { Scalar::wide_reduce( Shake256_114::digest( [ @@ -32,6 +33,7 @@ impl Ietf8032Ed448Hram { } } +/// The challenge function for FROST's Ed448 ciphersuite. #[derive(Copy, Clone)] pub struct IetfEd448Hram; impl Hram for IetfEd448Hram { diff --git a/crypto/frost/src/curve/kp256.rs b/crypto/frost/src/curve/kp256.rs index 582d9d1d..81c0499c 100644 --- a/crypto/frost/src/curve/kp256.rs +++ b/crypto/frost/src/curve/kp256.rs @@ -17,6 +17,7 @@ macro_rules! kp_curve { const CONTEXT: &'static [u8] = $CONTEXT; } + /// The challenge function for this ciphersuite. #[derive(Clone)] pub struct $Hram; impl Hram<$Curve> for $Hram { diff --git a/crypto/frost/src/curve/mod.rs b/crypto/frost/src/curve/mod.rs index a941bbe7..ec91a99f 100644 --- a/crypto/frost/src/curve/mod.rs +++ b/crypto/frost/src/curve/mod.rs @@ -33,10 +33,14 @@ pub use kp256::{P256, IetfP256Hram}; #[cfg(feature = "ed448")] mod ed448; #[cfg(feature = "ed448")] -pub use ed448::{Ed448, Ietf8032Ed448Hram, IetfEd448Hram}; +pub use ed448::{Ed448, IetfEd448Hram}; +#[cfg(all(test, feature = "ed448"))] +pub(crate) use ed448::Ietf8032Ed448Hram; -/// FROST Ciphersuite, except for the signing algorithm specific H2, making this solely the curve, -/// its associated hash function, and the functions derived from it. +/// FROST Ciphersuite. +/// +/// This exclude the signing algorithm specific H2, making this solely the curve, its associated +/// hash function, and the functions derived from it. pub trait Curve: Ciphersuite { /// Context string for this curve. const CONTEXT: &'static [u8]; @@ -98,6 +102,7 @@ pub trait Curve: Ciphersuite { res } + /// Read a point from a reader, rejecting identity. #[allow(non_snake_case)] fn read_G(reader: &mut R) -> io::Result { let res = ::read_G(reader)?; diff --git a/crypto/frost/src/lib.rs b/crypto/frost/src/lib.rs index a1d44030..c6f6916f 100644 --- a/crypto/frost/src/lib.rs +++ b/crypto/frost/src/lib.rs @@ -1,17 +1,5 @@ -#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))] - -//! A modular implementation of FROST for any curve with a ff/group API. -//! Additionally, custom algorithms may be specified so any signature reducible to -//! Schnorr-like may be used with FROST. -//! -//! A Schnorr algorithm is provided, of the form (R, s) where `s = r + cx`, which -//! allows specifying the challenge format. This is intended to easily allow -//! integrating with existing systems. -//! -//! This library offers ciphersuites compatible with the -//! [IETF draft](https://github.com/cfrg/draft-irtf-cfrg-frost). Currently, version -//! 11 is supported. +#![doc = include_str!("../README.md")] use core::fmt::Debug; use std::collections::HashMap; @@ -53,12 +41,9 @@ pub enum FrostError { InvalidPreprocess(Participant), #[error("invalid share (participant {0})")] InvalidShare(Participant), - - #[error("internal error ({0})")] - InternalError(&'static str), } -// Validate a map of values to have the expected included participants +/// Validate a map of values to have the expected participants. pub fn validate_map( map: &HashMap, included: &[Participant], diff --git a/crypto/frost/src/sign.rs b/crypto/frost/src/sign.rs index 27257433..66eea880 100644 --- a/crypto/frost/src/sign.rs +++ b/crypto/frost/src/sign.rs @@ -43,9 +43,9 @@ impl Writable for Vec { } } -/// Pairing of an Algorithm with a ThresholdKeys instance and this specific signing set. +// Pairing of an Algorithm with a ThresholdKeys instance. #[derive(Clone, Zeroize)] -pub struct Params> { +struct Params> { // Skips the algorithm due to being too large a bound to feasibly enforce on users #[zeroize(skip)] algorithm: A, @@ -53,11 +53,11 @@ pub struct Params> { } impl> Params { - pub fn new(algorithm: A, keys: ThresholdKeys) -> Params { + fn new(algorithm: A, keys: ThresholdKeys) -> Params { Params { algorithm, keys } } - pub fn multisig_params(&self) -> ThresholdParams { + fn multisig_params(&self) -> ThresholdParams { self.keys.params() } } @@ -66,6 +66,7 @@ impl> Params { #[derive(Clone, PartialEq, Eq)] pub struct Preprocess { pub(crate) commitments: Commitments, + /// The addendum used by the algorithm. pub addendum: A, } @@ -76,9 +77,11 @@ impl Writable for Preprocess { } } -/// A cached preprocess. A preprocess MUST only be used once. Reuse will enable third-party -/// recovery of your private key share. Additionally, this MUST be handled with the same security -/// as your private key share, as knowledge of it also enables recovery. +/// A cached preprocess. +/// +/// A preprocess MUST only be used once. Reuse will enable third-party recovery of your private +/// key share. Additionally, this MUST be handled with the same security as your private key share, +/// as knowledge of it also enables recovery. // Directly exposes the [u8; 32] member to void needing to route through std::io interfaces. // Still uses Zeroizing internally so when users grab it, they have a higher likelihood of // appreciating how to handle it and don't immediately start copying it just by grabbing it. @@ -510,6 +513,6 @@ impl> SignatureMachine for AlgorithmSign } // If everyone has a valid share, and there were enough participants, this should've worked - Err(FrostError::InternalError("everyone had a valid share yet the signature was still invalid")) + panic!("everyone had a valid share yet the signature was still invalid"); } } diff --git a/crypto/frost/src/tests/mod.rs b/crypto/frost/src/tests/mod.rs index 7930d5f3..fd271409 100644 --- a/crypto/frost/src/tests/mod.rs +++ b/crypto/frost/src/tests/mod.rs @@ -203,7 +203,7 @@ pub fn test_schnorr>(rng: &mut R) { assert!(sig.verify(group_key, H::hram(&sig.R, &group_key, MSG))); } -// Test an offset Schnorr signature. +/// Test an offset Schnorr signature. pub fn test_offset_schnorr>(rng: &mut R) { const MSG: &[u8] = b"Hello, World!"; @@ -223,7 +223,7 @@ pub fn test_offset_schnorr>(rng: &m assert!(sig.verify(offset_key, H::hram(&sig.R, &group_key, MSG))); } -// Test blame for an invalid Schnorr signature share. +/// Test blame for an invalid Schnorr signature share. pub fn test_schnorr_blame>(rng: &mut R) { const MSG: &[u8] = b"Hello, World!"; @@ -245,7 +245,7 @@ pub fn test_schnorr_blame>(rng: &mu } } -// Run a variety of tests against a ciphersuite. +/// Run a variety of tests against a ciphersuite. pub fn test_ciphersuite>(rng: &mut R) { test_schnorr::(rng); test_offset_schnorr::(rng); diff --git a/crypto/frost/src/tests/vectors.rs b/crypto/frost/src/tests/vectors.rs index 1068b707..74ab8069 100644 --- a/crypto/frost/src/tests/vectors.rs +++ b/crypto/frost/src/tests/vectors.rs @@ -22,6 +22,7 @@ use crate::{ tests::{clone_without, recover_key, test_ciphersuite}, }; +/// Vectors for a ciphersuite. pub struct Vectors { pub threshold: u16, @@ -141,6 +142,7 @@ fn vectors_to_multisig_keys(vectors: &Vectors) -> HashMap>( rng: &mut R, vectors: Vectors, diff --git a/crypto/multiexp/README.md b/crypto/multiexp/README.md index 81638ab7..b830c3fb 100644 --- a/crypto/multiexp/README.md +++ b/crypto/multiexp/README.md @@ -6,6 +6,7 @@ secure multiexponentation batch verification given a series of values which should sum to 0, identifying which doesn't via binary search if they don't. This library was -[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/74924095e1a0f266b58181b539d9e74fa35dc37a/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), -culminating in commit 669d2dbffc1dafb82a09d9419ea182667115df06. Any subsequent -changes have not undergone auditing. +[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/e1bb2c191b7123fd260d008e31656d090d559d21/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), +culminating in commit +[669d2dbffc1dafb82a09d9419ea182667115df06](https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06). +Any subsequent changes have not undergone auditing. diff --git a/crypto/multiexp/src/batch.rs b/crypto/multiexp/src/batch.rs index c47e2845..2895867b 100644 --- a/crypto/multiexp/src/batch.rs +++ b/crypto/multiexp/src/batch.rs @@ -104,8 +104,9 @@ where } /// Perform a binary search to identify which statement does not equal 0, returning None if all - /// statements do. This function will only return the ID of one invalid statement, even if - /// multiple are invalid. + /// statements do. + /// + /// This function will only return the ID of one invalid statement, even if multiple are invalid. // A constant time variant may be beneficial for robust protocols pub fn blame_vartime(&self) -> Option { let mut slice = self.0.as_slice(); diff --git a/crypto/multiexp/src/lib.rs b/crypto/multiexp/src/lib.rs index 9d309637..0fc0a8c3 100644 --- a/crypto/multiexp/src/lib.rs +++ b/crypto/multiexp/src/lib.rs @@ -1,4 +1,5 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] use core::ops::DerefMut; diff --git a/crypto/schnorr/README.md b/crypto/schnorr/README.md index ad0a7554..baa0014d 100644 --- a/crypto/schnorr/README.md +++ b/crypto/schnorr/README.md @@ -7,9 +7,10 @@ with associated functions. This library provides signatures of the `R, s` form. Batch verification is supported via the multiexp crate. Half-aggregation, as defined in -https://eprint.iacr.org/2021/350, is also supported. +, is also supported. This library was -[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/74924095e1a0f266b58181b539d9e74fa35dc37a/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), -culminating in commit 669d2dbffc1dafb82a09d9419ea182667115df06. Any subsequent -changes have not undergone auditing. +[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/e1bb2c191b7123fd260d008e31656d090d559d21/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), +culminating in commit +[669d2dbffc1dafb82a09d9419ea182667115df06](https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06). +Any subsequent changes have not undergone auditing. diff --git a/crypto/schnorr/src/aggregate.rs b/crypto/schnorr/src/aggregate.rs index 592ed051..011d17f9 100644 --- a/crypto/schnorr/src/aggregate.rs +++ b/crypto/schnorr/src/aggregate.rs @@ -66,8 +66,8 @@ fn weight(digest: &mut DigestTran #[allow(non_snake_case)] #[derive(Clone, PartialEq, Eq, Debug, Zeroize)] pub struct SchnorrAggregate { - pub Rs: Vec, - pub s: C::F, + Rs: Vec, + s: C::F, } impl SchnorrAggregate { @@ -137,6 +137,7 @@ impl SchnorrAggregate { } } +/// A signature aggregator capable of consuming signatures in order to produce an aggregate. #[allow(non_snake_case)] #[derive(Clone, Debug, Zeroize)] pub struct SchnorrAggregator { diff --git a/crypto/schnorr/src/lib.rs b/crypto/schnorr/src/lib.rs index f852eee2..ab1e70c4 100644 --- a/crypto/schnorr/src/lib.rs +++ b/crypto/schnorr/src/lib.rs @@ -1,3 +1,6 @@ +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] + use core::ops::Deref; use std::io::{self, Read, Write}; @@ -14,6 +17,7 @@ use ciphersuite::{ }; use multiexp::{multiexp_vartime, BatchVerifier}; +/// Half-aggregation from . pub mod aggregate; #[cfg(test)] diff --git a/crypto/transcript/README.md b/crypto/transcript/README.md index ed8db388..3d9350cf 100644 --- a/crypto/transcript/README.md +++ b/crypto/transcript/README.md @@ -27,6 +27,7 @@ their type, and their length. compatible with existing Rust projects using `merlin`. This library was -[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/74924095e1a0f266b58181b539d9e74fa35dc37a/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), -culminating in commit 669d2dbffc1dafb82a09d9419ea182667115df06. Any subsequent -changes have not undergone auditing. +[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/e1bb2c191b7123fd260d008e31656d090d559d21/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), +culminating in commit +[669d2dbffc1dafb82a09d9419ea182667115df06](https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06). +Any subsequent changes have not undergone auditing. diff --git a/crypto/transcript/src/lib.rs b/crypto/transcript/src/lib.rs index 13bb750a..272b2c45 100644 --- a/crypto/transcript/src/lib.rs +++ b/crypto/transcript/src/lib.rs @@ -1,11 +1,14 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![no_std] +///! A transcript trait valid over a variety of transcript formats. + #[cfg(feature = "merlin")] mod merlin; #[cfg(feature = "merlin")] pub use crate::merlin::MerlinTranscript; +/// Tests for a transcript. #[cfg(any(test, feature = "tests"))] pub mod tests; @@ -16,6 +19,7 @@ use digest::{ Digest, Output, HashMarker, }; +/// A transcript trait valid over a variety of transcript formats. pub trait Transcript: Send + Clone { type Challenge: Send + Sync + Clone + AsRef<[u8]>; @@ -130,6 +134,6 @@ impl Transcript for DigestTranscript { } } -/// The recommended transcript, secure against length-extension attacks. +/// The recommended transcript, guaranteed to be secure against length-extension attacks. #[cfg(feature = "recommended")] pub type RecommendedTranscript = DigestTranscript; diff --git a/crypto/transcript/src/tests.rs b/crypto/transcript/src/tests.rs index 4a9f9dfc..93651b03 100644 --- a/crypto/transcript/src/tests.rs +++ b/crypto/transcript/src/tests.rs @@ -1,5 +1,6 @@ use crate::Transcript; +/// Test the sanity of a transcript. pub fn test_transcript() where T::Challenge: PartialEq,