Fully document crypto/

This commit is contained in:
Luke Parker 2023-03-20 20:10:00 -04:00
parent e1bb2c191b
commit 8d4d630e0f
No known key found for this signature in database
45 changed files with 335 additions and 208 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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")]

View file

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

View file

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

View file

@ -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<Scalar> 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
);

View file

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

View file

@ -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<R: Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self>;
fn write<W: Write>(&self, writer: &mut W) -> io::Result<()>;
mod sealed {
use super::*;
fn serialize(&self) -> Vec<u8> {
let mut buf = vec![];
self.write(&mut buf).unwrap();
buf
pub trait ReadWrite: Sized {
fn read<R: io::Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self>;
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()>;
fn serialize(&self) -> Vec<u8> {
let mut buf = vec![];
self.write(&mut buf).unwrap();
buf
}
}
}
pub trait Message: Clone + PartialEq + Eq + fmt::Debug + Zeroize + ReadWrite {}
impl<M: Clone + PartialEq + Eq + fmt::Debug + Zeroize + ReadWrite> Message for M {}
pub trait Message: Clone + PartialEq + Eq + fmt::Debug + Zeroize + ReadWrite {}
impl<M: Clone + PartialEq + Eq + fmt::Debug + Zeroize + ReadWrite> Message for M {}
pub trait Encryptable: Clone + AsRef<[u8]> + AsMut<[u8]> + Zeroize + ReadWrite {}
impl<E: Clone + AsRef<[u8]> + 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<C: Ciphersuite, M: Message> {
// Doesn't impl ReadWrite so that doesn't need to be imported
impl<C: Ciphersuite, M: Message> EncryptionKeyMessage<C, M> {
pub fn read<R: Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self> {
pub fn read<R: io::Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self> {
Ok(Self { msg: M::read(reader, params)?, enc_key: C::read_G(reader)? })
}
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
pub fn write<W: io::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<C: Ciphersuite, M: Message> EncryptionKeyMessage<C, M> {
}
}
pub trait Encryptable: Clone + AsRef<[u8]> + AsMut<[u8]> + Zeroize + ReadWrite {}
impl<E: Clone + AsRef<[u8]> + 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<R: RngCore + CryptoRng, C: Ciphersuite, E: Encryptable>(
}
impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
pub fn read<R: Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self> {
pub fn read<R: io::Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self> {
Ok(Self {
key: C::read_G(reader)?,
pop: SchnorrSignature::<C>::read(reader)?,
@ -174,7 +176,7 @@ impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
pub fn write<W: io::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<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
}
}
/// 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<C: Ciphersuite> {
key: Zeroizing<C::G>,
@ -262,11 +264,11 @@ pub struct EncryptionKeyProof<C: Ciphersuite> {
}
impl<C: Ciphersuite> EncryptionKeyProof<C> {
pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
pub fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
Ok(Self { key: Zeroizing::new(C::read_G(reader)?), dleq: DLEqProof::read(reader)? })
}
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
pub fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_all(self.key.to_bytes().as_ref())?;
self.dleq.write(writer)
}

View file

@ -43,11 +43,11 @@ fn challenge<C: Ciphersuite>(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<C: Ciphersuite> {
commitments: Vec<C::G>,
@ -91,13 +91,14 @@ pub struct KeyGenMachine<C: Ciphersuite> {
}
impl<C: Ciphersuite> KeyGenMachine<C> {
/// 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<C> {
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<R: RngCore + CryptoRng>(
@ -168,7 +169,9 @@ fn polynomial<F: PrimeField + Zeroize>(
/// 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<F::Repr>).
// It's unfortunately not possible as F::Repr doesn't have Zeroize as a bound.
// The encryption system also explicitly uses Zeroizing<M> so it can ensure anything being
@ -281,8 +284,10 @@ impl<C: Ciphersuite> SecretShareMachine<C> {
}
/// 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<R: RngCore + CryptoRng>(
@ -321,11 +326,11 @@ impl<C: Ciphersuite> SecretShareMachine<C> {
}
}
/// 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<C: Ciphersuite> {
params: ThresholdParams,
secret: Zeroizing<C::F>,
@ -397,8 +402,10 @@ enum BatchId {
impl<C: Ciphersuite> KeyMachine<C> {
/// 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<R: RngCore + CryptoRng>(
mut self,
rng: &mut R,
@ -473,6 +480,7 @@ impl<C: Ciphersuite> KeyMachine<C> {
}
}
/// A machine capable of handling blame proofs.
pub struct BlameMachine<C: Ciphersuite> {
commitments: HashMap<Participant, Vec<C::G>>,
encryption: Encryption<C>,
@ -574,6 +582,7 @@ impl<C: Ciphersuite> BlameMachine<C> {
}
}
/// A machine capable of handling an arbitrary amount of additional blame proofs.
#[derive(Debug, Zeroize)]
pub struct AdditionalBlameMachine<C: Ciphersuite>(BlameMachine<C>);
impl<C: Ciphersuite> AdditionalBlameMachine<C> {

View file

@ -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<Participant> {
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<B: Clone + PartialEq + Eq + Debug> {
/// 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<B> },
#[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<ThresholdParams, DkgError<()>> {
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<C: Ciphersuite> ThresholdCore<C> {
verification_shares,
}
}
/// Parameters for these keys.
pub fn params(&self) -> ThresholdParams {
self.params
}
/// Secret share for these keys.
pub fn secret_share(&self) -> &Zeroizing<C::F> {
&self.secret_share
}
/// Group key for these keys.
pub fn group_key(&self) -> C::G {
self.group_key
}
@ -253,6 +264,7 @@ impl<C: Ciphersuite> ThresholdCore<C> {
self.verification_shares.clone()
}
/// Write these keys to a type satisfying std::io::Write.
pub fn write<W: io::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<C: Ciphersuite> ThresholdCore<C> {
Ok(())
}
/// Serialize these keys to a `Vec<u8>`.
pub fn serialize(&self) -> Zeroizing<Vec<u8>> {
let mut serialized = Zeroizing::new(vec![]);
self.write::<Vec<u8>>(serialized.as_mut()).unwrap();
serialized
}
pub fn read<R: io::Read>(reader: &mut R) -> Result<ThresholdCore<C>, DkgError<()>> {
/// Read keys from a type satisfying std::io::Read.
pub fn read<R: io::Read>(reader: &mut R) -> io::Result<ThresholdCore<C>> {
{
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<u16> {
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,
<C as Ciphersuite>::read_G(reader)
.map_err(|_| DkgError::InternalError("invalid verification share"))?,
);
verification_shares.insert(l, <C as Ciphersuite>::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<C: Ciphersuite> {
pub(crate) offset: Option<C::F>,
}
/// View of keys passed to algorithm implementations.
/// View of keys, interpolated and offset for usage.
#[derive(Clone)]
pub struct ThresholdView<C: Ciphersuite> {
offset: C::F,
@ -383,13 +390,15 @@ impl<C: Ciphersuite> Zeroize for ThresholdView<C> {
}
impl<C: Ciphersuite> ThresholdKeys<C> {
/// Create a new set of ThresholdKeys from a ThresholdCore.
pub fn new(core: ThresholdCore<C>) -> ThresholdKeys<C> {
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<C> {
let mut res = self.clone();
@ -400,33 +409,38 @@ impl<C: Ciphersuite> ThresholdKeys<C> {
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<C::F> {
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<C::F> {
&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<Participant, C::G> {
self.core.verification_shares()
}
/// Serialize these keys to a `Vec<u8>`.
pub fn serialize(&self) -> Zeroizing<Vec<u8>> {
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<Participant>) -> Result<ThresholdView<C>, DkgError<()>> {
if (included.len() < self.params().t.into()) || (usize::from(self.params().n) < included.len())
{
@ -460,27 +474,39 @@ impl<C: Ciphersuite> ThresholdKeys<C> {
}
}
impl<C: Ciphersuite> From<ThresholdCore<C>> for ThresholdKeys<C> {
fn from(keys: ThresholdCore<C>) -> ThresholdKeys<C> {
ThresholdKeys::new(keys)
}
}
impl<C: Ciphersuite> ThresholdView<C> {
/// 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<C::F> {
&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]
}

View file

@ -60,9 +60,10 @@ impl<C: Ciphersuite> GeneratorProof<C> {
}
/// 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<C1: Ciphersuite, C2: Ciphersuite> {
base: ThresholdKeys<C1>,
proof: GeneratorProof<C1>,

View file

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

View file

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

View file

@ -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: Read, G: PrimeGroup>(r: &mut R) -> std::io::Result<G
Ok(point.unwrap())
}
/// A pair of generators, one committing to values (primary), one blinding (alt), for an elliptic
/// curve.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Generators<G: PrimeGroup> {
/// 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<G: PrimeGroup> Generators<G> {
pub fn new(primary: G, alt: G) -> Generators<G> {
Generators { primary, alt }
/// Create a new set of generators.
pub fn new(primary: G, alt: G) -> Option<Generators<G>> {
if primary == alt {
None?;
}
Some(Generators { primary, alt })
}
fn transcript<T: Transcript>(&self, transcript: &mut T) {
@ -81,14 +92,19 @@ impl<G: PrimeGroup> Generators<G> {
}
}
/// 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<G0, G1> = __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<R: RngCore + CryptoRng, T: Clone + Transcript>(
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<R: RngCore + CryptoRng, T: Clone + Transcript>(
&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<W: 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: Read>(r: &mut R) -> std::io::Result<Self> {
let capacity = usize::try_from(G0::Scalar::CAPACITY.min(G1::Scalar::CAPACITY)).unwrap();

View file

@ -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: Read, F: PrimeField>(r: &mut R) -> io::Result<F> {
/// Error for DLEq proofs.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DLEqError {
/// The proof was invalid.
InvalidProof,
}
@ -201,6 +205,7 @@ impl<G: PrimeGroup> DLEqProof<G> {
}
/// 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")]

View file

@ -40,14 +40,16 @@ pub(crate) fn generators() -> (Generators<G0>, Generators<G1>) {
&(hex!("0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0").into()),
)
.unwrap(),
),
)
.unwrap(),
Generators::new(
EdwardsPoint::generator(),
EdwardsPoint::from_bytes(&hex!(
"8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94"
))
.unwrap(),
),
)
.unwrap(),
)
}

View file

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

View file

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

View file

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

View file

@ -47,6 +47,7 @@ fn recover_x(y: FieldElement) -> CtOption<FieldElement> {
})
}
/// 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()
}
}

View file

@ -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())))
}

View file

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

View file

@ -1,4 +1,5 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
/// Tests for the Field trait.
pub mod field;

View file

@ -14,7 +14,7 @@ pub fn test_one<F: PrimeField>() {
assert_eq!(F::one(), F::from(1u64), "1 != 1");
}
/// Test From<u64> for F works.
/// Test `From<u64>` for F works.
pub fn test_from_u64<F: PrimeField>() {
assert_eq!(F::one().double(), F::from(2u64), "2 != 2");
}
@ -279,7 +279,7 @@ pub fn test_s<F: PrimeFieldBits>() {
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<F: PrimeFieldBits>() {
// "It can be calculated by exponentiating `Self::multiplicative_generator` by `t`, where
// `t = (modulus - 1) >> Self::S`."

View file

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

View file

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

View file

@ -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<Ed448> for IetfEd448Hram {

View file

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

View file

@ -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<R: Read>(reader: &mut R) -> io::Result<Self::G> {
let res = <Self as Ciphersuite>::read_G(reader)?;

View file

@ -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<T>(
map: &HashMap<Participant, T>,
included: &[Participant],

View file

@ -43,9 +43,9 @@ impl<T: Writable> Writable for Vec<T> {
}
}
/// 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<C: Curve, A: Algorithm<C>> {
struct Params<C: Curve, A: Algorithm<C>> {
// 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<C: Curve, A: Algorithm<C>> {
}
impl<C: Curve, A: Algorithm<C>> Params<C, A> {
pub fn new(algorithm: A, keys: ThresholdKeys<C>) -> Params<C, A> {
fn new(algorithm: A, keys: ThresholdKeys<C>) -> Params<C, A> {
Params { algorithm, keys }
}
pub fn multisig_params(&self) -> ThresholdParams {
fn multisig_params(&self) -> ThresholdParams {
self.keys.params()
}
}
@ -66,6 +66,7 @@ impl<C: Curve, A: Algorithm<C>> Params<C, A> {
#[derive(Clone, PartialEq, Eq)]
pub struct Preprocess<C: Curve, A: Addendum> {
pub(crate) commitments: Commitments<C>,
/// The addendum used by the algorithm.
pub addendum: A,
}
@ -76,9 +77,11 @@ impl<C: Curve, A: Addendum> Writable for Preprocess<C, A> {
}
}
/// 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<C: Curve, A: Algorithm<C>> SignatureMachine<A::Signature> 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");
}
}

View file

@ -203,7 +203,7 @@ pub fn test_schnorr<R: RngCore + CryptoRng, C: Curve, H: Hram<C>>(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<R: RngCore + CryptoRng, C: Curve, H: Hram<C>>(rng: &mut R) {
const MSG: &[u8] = b"Hello, World!";
@ -223,7 +223,7 @@ pub fn test_offset_schnorr<R: RngCore + CryptoRng, C: Curve, H: Hram<C>>(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<R: RngCore + CryptoRng, C: Curve, H: Hram<C>>(rng: &mut R) {
const MSG: &[u8] = b"Hello, World!";
@ -245,7 +245,7 @@ pub fn test_schnorr_blame<R: RngCore + CryptoRng, C: Curve, H: Hram<C>>(rng: &mu
}
}
// Run a variety of tests against a ciphersuite.
/// Run a variety of tests against a ciphersuite.
pub fn test_ciphersuite<R: RngCore + CryptoRng, C: Curve, H: Hram<C>>(rng: &mut R) {
test_schnorr::<R, C, H>(rng);
test_offset_schnorr::<R, C, H>(rng);

View file

@ -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<C: Curve>(vectors: &Vectors) -> HashMap<Participant,
keys
}
/// Test a Ciphersuite with its vectors.
pub fn test_with_vectors<R: RngCore + CryptoRng, C: Curve, H: Hram<C>>(
rng: &mut R,
vectors: Vectors,

View file

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

View file

@ -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<Id> {
let mut slice = self.0.as_slice();

View file

@ -1,4 +1,5 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
use core::ops::DerefMut;

View file

@ -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.
<https://eprint.iacr.org/2021/350>, 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.

View file

@ -66,8 +66,8 @@ fn weight<D: Send + Clone + SecureDigest, F: PrimeField>(digest: &mut DigestTran
#[allow(non_snake_case)]
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub struct SchnorrAggregate<C: Ciphersuite> {
pub Rs: Vec<C::G>,
pub s: C::F,
Rs: Vec<C::G>,
s: C::F,
}
impl<C: Ciphersuite> SchnorrAggregate<C> {
@ -137,6 +137,7 @@ impl<C: Ciphersuite> SchnorrAggregate<C> {
}
}
/// A signature aggregator capable of consuming signatures in order to produce an aggregate.
#[allow(non_snake_case)]
#[derive(Clone, Debug, Zeroize)]
pub struct SchnorrAggregator<C: Ciphersuite> {

View file

@ -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 <https://eprint.iacr.org/2021/350>.
pub mod aggregate;
#[cfg(test)]

View file

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

View file

@ -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<D: Send + Clone + SecureDigest> Transcript for DigestTranscript<D> {
}
}
/// 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<blake2::Blake2b512>;

View file

@ -1,5 +1,6 @@
use crate::Transcript;
/// Test the sanity of a transcript.
pub fn test_transcript<T: Transcript>()
where
T::Challenge: PartialEq,