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 curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY};
use group::ff::{Field, PrimeField}; use group::ff::{Field, PrimeField};
use dalek_ff_group::field::FieldElement; use dalek_ff_group::FieldElement;
use crate::hash; use crate::hash;

View file

@ -3,9 +3,10 @@
Ciphersuites for elliptic curves premised on ff/group. Ciphersuites for elliptic curves premised on ff/group.
This library, except for the not recommended Ed448 ciphersuite, was 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), [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. Any subsequent culminating in commit
changes have not undergone auditing. [669d2dbffc1dafb82a09d9419ea182667115df06](https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06).
Any subsequent changes have not undergone auditing.
### Secp256k1/P-256 ### 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 /// 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 /// 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 sha3::Shake256;
use group::Group; use group::Group;
use minimal_ed448::{scalar::Scalar, point::Point}; use minimal_ed448::{Scalar, Point};
use crate::Ciphersuite; use crate::Ciphersuite;
@ -48,7 +48,7 @@ impl FixedOutput for Shake256_114 {
} }
impl HashMarker 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 /// 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 /// 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(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
#![doc = include_str!("lib.md")]
use core::fmt::Debug; use core::fmt::Debug;
#[cfg(feature = "std")] #[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. around modern dependencies.
This library was 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), [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. Any subsequent culminating in commit
changes have not undergone auditing. [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 MODULUS: U256 = U256::from_u8(1).shl_vartime(255).saturating_sub(&U256::from_u8(19));
const WIDE_MODULUS: U512 = U256::ZERO.concat(&MODULUS); const WIDE_MODULUS: U512 = U256::ZERO.concat(&MODULUS);
/// A constant-time implementation of the Ed25519 field.
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
pub struct FieldElement(U256); pub struct FieldElement(U256);
@ -184,11 +185,13 @@ impl PrimeFieldBits for FieldElement {
} }
impl 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 { pub fn from_square(value: [u8; 32]) -> FieldElement {
let value = U256::from_le_bytes(value); let value = U256::from_le_bytes(value);
FieldElement(value) * FieldElement(value) FieldElement(value) * FieldElement(value)
} }
/// Perform an exponentation.
pub fn pow(&self, other: FieldElement) -> FieldElement { pub fn pow(&self, other: FieldElement) -> FieldElement {
let mut table = [FieldElement::one(); 16]; let mut table = [FieldElement::one(); 16];
table[1] = *self; table[1] = *self;

View file

@ -1,5 +1,6 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![no_std] #![no_std]
#![doc = include_str!("../README.md")]
use core::{ use core::{
borrow::Borrow, borrow::Borrow,
@ -23,15 +24,10 @@ use dalek::{
constants, constants,
traits::Identity, traits::Identity,
scalar::Scalar as DScalar, scalar::Scalar as DScalar,
edwards::{ edwards::{EdwardsPoint as DEdwardsPoint, EdwardsBasepointTable, CompressedEdwardsY},
EdwardsPoint as DEdwardsPoint, EdwardsBasepointTable as DEdwardsBasepointTable, ristretto::{RistrettoPoint as DRistrettoPoint, RistrettoBasepointTable, CompressedRistretto},
CompressedEdwardsY as DCompressedEdwards,
},
ristretto::{
RistrettoPoint as DRistrettoPoint, RistrettoBasepointTable as DRistrettoBasepointTable,
CompressedRistretto as DCompressedRistretto,
},
}; };
pub use constants::{ED25519_BASEPOINT_TABLE, RISTRETTO_BASEPOINT_TABLE};
use group::{ use group::{
ff::{Field, PrimeField, FieldBits, PrimeFieldBits}, ff::{Field, PrimeField, FieldBits, PrimeFieldBits},
@ -39,7 +35,8 @@ use group::{
prime::PrimeGroup, prime::PrimeGroup,
}; };
pub mod field; mod field;
pub use field::FieldElement;
// Feature gated due to MSRV requirements // Feature gated due to MSRV requirements
#[cfg(feature = "black_box")] #[cfg(feature = "black_box")]
@ -362,7 +359,6 @@ macro_rules! dalek_group {
$torsion_free: expr, $torsion_free: expr,
$Table: ident, $Table: ident,
$DTable: ident,
$DCompressed: ident, $DCompressed: ident,
@ -376,6 +372,7 @@ macro_rules! dalek_group {
constant_time!($Point, $DPoint); constant_time!($Point, $DPoint);
math_neg!($Point, Scalar, $DPoint::add, $DPoint::sub, $DPoint::mul); math_neg!($Point, Scalar, $DPoint::add, $DPoint::sub, $DPoint::mul);
/// The basepoint for this curve.
pub const $BASEPOINT_POINT: $Point = $Point(constants::$BASEPOINT_POINT); pub const $BASEPOINT_POINT: $Point = $Point(constants::$BASEPOINT_POINT);
impl Sum<$Point> for $Point { impl Sum<$Point> for $Point {
@ -437,16 +434,10 @@ macro_rules! dalek_group {
impl PrimeGroup for $Point {} 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 { impl Mul<Scalar> for &$Table {
type Output = $Point; type Output = $Point;
fn mul(self, b: Scalar) -> $Point { fn mul(self, b: Scalar) -> $Point {
$Point(&b.0 * &self.0) $Point(&b.0 * self)
} }
} }
@ -468,8 +459,7 @@ dalek_group!(
DEdwardsPoint, DEdwardsPoint,
|point: DEdwardsPoint| point.is_torsion_free(), |point: DEdwardsPoint| point.is_torsion_free(),
EdwardsBasepointTable, EdwardsBasepointTable,
DEdwardsBasepointTable, CompressedEdwardsY,
DCompressedEdwards,
ED25519_BASEPOINT_POINT, ED25519_BASEPOINT_POINT,
ED25519_BASEPOINT_TABLE ED25519_BASEPOINT_TABLE
); );
@ -485,8 +475,7 @@ dalek_group!(
DRistrettoPoint, DRistrettoPoint,
|_| true, |_| true,
RistrettoBasepointTable, RistrettoBasepointTable,
DRistrettoBasepointTable, CompressedRistretto,
DCompressedRistretto,
RISTRETTO_BASEPOINT_POINT, RISTRETTO_BASEPOINT_POINT,
RISTRETTO_BASEPOINT_TABLE RISTRETTO_BASEPOINT_TABLE
); );

View file

@ -3,15 +3,14 @@
A collection of implementations of various distributed key generation protocols. A collection of implementations of various distributed key generation protocols.
All included protocols resolve into the provided `Threshold` types, intended to 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 Currently, the only included protocol is the two-round protocol from the
another, are also provided.
Currently included is the two-round protocol from the
[FROST paper](https://eprint.iacr.org/2020/852). [FROST paper](https://eprint.iacr.org/2020/852).
This library was 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), [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. Any subsequent culminating in commit
changes have not undergone auditing. [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 core::{ops::Deref, fmt};
use std::{ use std::{io, collections::HashMap};
io::{self, Read, Write},
collections::HashMap,
};
use thiserror::Error; use thiserror::Error;
@ -26,19 +23,27 @@ use dleq::DLEqProof;
use crate::{Participant, ThresholdParams}; use crate::{Participant, ThresholdParams};
pub trait ReadWrite: Sized { mod sealed {
fn read<R: Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self>; use super::*;
fn write<W: Write>(&self, writer: &mut W) -> io::Result<()>;
fn serialize(&self) -> Vec<u8> { pub trait ReadWrite: Sized {
let mut buf = vec![]; fn read<R: io::Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self>;
self.write(&mut buf).unwrap(); fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()>;
buf
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 {} pub trait Message: Clone + PartialEq + Eq + fmt::Debug + Zeroize + ReadWrite {}
impl<M: Clone + PartialEq + Eq + fmt::Debug + Zeroize + ReadWrite> Message for M {} 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. /// Wraps a message with a key to use for encryption in the future.
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)] #[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 // Doesn't impl ReadWrite so that doesn't need to be imported
impl<C: Ciphersuite, M: Message> EncryptionKeyMessage<C, M> { 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)? }) 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)?; self.msg.write(writer)?;
writer.write_all(self.enc_key.to_bytes().as_ref()) 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 /// An encrypted message, with a per-message encryption key enabling revealing specific messages
/// without side effects. /// without side effects.
#[derive(Clone, Zeroize)] #[derive(Clone, Zeroize)]
@ -166,7 +168,7 @@ fn encrypt<R: RngCore + CryptoRng, C: Ciphersuite, E: Encryptable>(
} }
impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> { 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 { Ok(Self {
key: C::read_G(reader)?, key: C::read_G(reader)?,
pop: SchnorrSignature::<C>::read(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())?; writer.write_all(self.key.to_bytes().as_ref())?;
self.pop.write(writer)?; self.pop.write(writer)?;
self.msg.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)] #[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub struct EncryptionKeyProof<C: Ciphersuite> { pub struct EncryptionKeyProof<C: Ciphersuite> {
key: Zeroizing<C::G>, key: Zeroizing<C::G>,
@ -262,11 +264,11 @@ pub struct EncryptionKeyProof<C: Ciphersuite> {
} }
impl<C: Ciphersuite> EncryptionKeyProof<C> { 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)? }) 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())?; writer.write_all(self.key.to_bytes().as_ref())?;
self.dleq.write(writer) 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. /// 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 /// Every participant should only provide one set of commitments to all parties. If any
/// malicious. /// participant sends multiple sets of commitments, they are faulty and should be presumed
/// As this library does not handle networking, it is also unable to detect if any participant is /// malicious. As this library does not handle networking, it is unable to detect if any
/// so faulty. That responsibility lies with the caller. /// participant is so faulty. That responsibility lies with the caller.
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)] #[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub struct Commitments<C: Ciphersuite> { pub struct Commitments<C: Ciphersuite> {
commitments: Vec<C::G>, commitments: Vec<C::G>,
@ -91,13 +91,14 @@ pub struct KeyGenMachine<C: Ciphersuite> {
} }
impl<C: Ciphersuite> KeyGenMachine<C> { 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. // The context string should be unique among multisigs.
pub fn new(params: ThresholdParams, context: String) -> KeyGenMachine<C> { pub fn new(params: ThresholdParams, context: String) -> KeyGenMachine<C> {
KeyGenMachine { params, context, _curve: PhantomData } KeyGenMachine { params, context, _curve: PhantomData }
} }
/// Start generating a key according to the FROST DKG spec. /// 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 /// 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. /// party submits multiple sets of commitments, they MUST be treated as malicious.
pub fn generate_coefficients<R: RngCore + CryptoRng>( 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 /// The secret share message, to be sent to the party it's intended for over an authenticated
/// channel. /// channel.
///
/// If any participant sends multiple secret shares to another participant, they are faulty. /// If any participant sends multiple secret shares to another participant, they are faulty.
// This should presumably be written as SecretShare(Zeroizing<F::Repr>). // 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. // 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 // 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. /// Continue generating a key.
///
/// Takes in everyone else's commitments. Returns a HashMap of encrypted secret shares to be sent /// Takes in everyone else's commitments. Returns a HashMap of encrypted secret shares to be sent
/// over authenticated channels to their relevant counterparties. /// over authenticated channels to their relevant counterparties.
///
/// If any participant sends multiple secret shares to another participant, they are faulty. /// If any participant sends multiple secret shares to another participant, they are faulty.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn generate_secret_shares<R: RngCore + CryptoRng>( 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. /// Advancement of the the secret share state machine.
/// 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 /// This machine will 'complete' the protocol, by a local perspective. In order to be secure,
/// completed the protocol (an effort out of scope to this library), yet this is modelled by one /// the parties must confirm having successfully completed the protocol (an effort out of scope to
/// more state transition. /// this library), yet this is modeled by one more state transition (BlameMachine).
pub struct KeyMachine<C: Ciphersuite> { pub struct KeyMachine<C: Ciphersuite> {
params: ThresholdParams, params: ThresholdParams,
secret: Zeroizing<C::F>, secret: Zeroizing<C::F>,
@ -397,8 +402,10 @@ enum BatchId {
impl<C: Ciphersuite> KeyMachine<C> { impl<C: Ciphersuite> KeyMachine<C> {
/// Calculate our share given the shares sent to us. /// Calculate our share given the shares sent to us.
///
/// Returns a BlameMachine usable to determine if faults in the protocol occurred. /// 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>( pub fn calculate_share<R: RngCore + CryptoRng>(
mut self, mut self,
rng: &mut R, rng: &mut R,
@ -473,6 +480,7 @@ impl<C: Ciphersuite> KeyMachine<C> {
} }
} }
/// A machine capable of handling blame proofs.
pub struct BlameMachine<C: Ciphersuite> { pub struct BlameMachine<C: Ciphersuite> {
commitments: HashMap<Participant, Vec<C::G>>, commitments: HashMap<Participant, Vec<C::G>>,
encryption: Encryption<C>, 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)] #[derive(Debug, Zeroize)]
pub struct AdditionalBlameMachine<C: Ciphersuite>(BlameMachine<C>); pub struct AdditionalBlameMachine<C: Ciphersuite>(BlameMachine<C>);
impl<C: Ciphersuite> AdditionalBlameMachine<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))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
//! 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.
use core::{ use core::{
fmt::{self, Debug}, fmt::{self, Debug},
@ -43,6 +38,7 @@ pub mod tests;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Participant(pub(crate) u16); pub struct Participant(pub(crate) u16);
impl Participant { impl Participant {
/// Create a new Participant identifier from a u16.
pub fn new(i: u16) -> Option<Participant> { pub fn new(i: u16) -> Option<Participant> {
if i == 0 { if i == 0 {
None None
@ -51,6 +47,7 @@ impl Participant {
} }
} }
/// Convert a Participant identifier to bytes.
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
pub fn to_bytes(&self) -> [u8; 2] { pub fn to_bytes(&self) -> [u8; 2] {
self.0.to_le_bytes() 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)] #[derive(Clone, PartialEq, Eq, Debug, Error)]
pub enum DkgError<B: Clone + PartialEq + Eq + Debug> { pub enum DkgError<B: Clone + PartialEq + Eq + Debug> {
/// A parameter was zero.
#[error("a parameter was 0 (threshold {0}, participants {1})")] #[error("a parameter was 0 (threshold {0}, participants {1})")]
ZeroParameter(u16, u16), ZeroParameter(u16, u16),
#[error("invalid amount of required participants (max {1}, got {0})")] /// The threshold exceeded the amount of participants.
InvalidRequiredQuantity(u16, u16), #[error("invalid threshold (max {1}, got {0})")]
InvalidThreshold(u16, u16),
/// Invalid participant identifier.
#[error("invalid participant (0 < participant <= {0}, yet participant is {1})")] #[error("invalid participant (0 < participant <= {0}, yet participant is {1})")]
InvalidParticipant(u16, Participant), InvalidParticipant(u16, Participant),
/// Invalid signing set.
#[error("invalid signing set")] #[error("invalid signing set")]
InvalidSigningSet, InvalidSigningSet,
/// Invalid amount of participants.
#[error("invalid participant quantity (expected {0}, got {1})")] #[error("invalid participant quantity (expected {0}, got {1})")]
InvalidParticipantQuantity(usize, usize), InvalidParticipantQuantity(usize, usize),
/// A participant was duplicated.
#[error("duplicated participant ({0})")] #[error("duplicated participant ({0})")]
DuplicatedParticipant(Participant), DuplicatedParticipant(Participant),
/// A participant was missing.
#[error("missing participant {0}")] #[error("missing participant {0}")]
MissingParticipant(Participant), MissingParticipant(Participant),
/// An invalid proof of knowledge was provided.
#[error("invalid proof of knowledge (participant {0})")] #[error("invalid proof of knowledge (participant {0})")]
InvalidProofOfKnowledge(Participant), InvalidProofOfKnowledge(Participant),
/// An invalid DKG share was provided.
#[error("invalid share (participant {participant}, blame {blame})")] #[error("invalid share (participant {participant}, blame {blame})")]
InvalidShare { participant: Participant, blame: Option<B> }, InvalidShare { participant: Participant, blame: Option<B> },
#[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 included participants
@ -137,6 +140,7 @@ pub struct ThresholdParams {
} }
impl ThresholdParams { impl ThresholdParams {
/// Create a new set of parameters.
pub fn new(t: u16, n: u16, i: Participant) -> Result<ThresholdParams, DkgError<()>> { pub fn new(t: u16, n: u16, i: Participant) -> Result<ThresholdParams, DkgError<()>> {
if (t == 0) || (n == 0) { if (t == 0) || (n == 0) {
Err(DkgError::ZeroParameter(t, n))?; 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), // 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 // but it's not invalid to do so
if t > n { if t > n {
Err(DkgError::InvalidRequiredQuantity(t, n))?; Err(DkgError::InvalidThreshold(t, n))?;
} }
if u16::from(i) > n { if u16::from(i) > n {
Err(DkgError::InvalidParticipant(n, i))?; Err(DkgError::InvalidParticipant(n, i))?;
@ -154,12 +158,15 @@ impl ThresholdParams {
Ok(ThresholdParams { t, n, i }) Ok(ThresholdParams { t, n, i })
} }
/// Return the threshold for a multisig with these parameters.
pub fn t(&self) -> u16 { pub fn t(&self) -> u16 {
self.t self.t
} }
/// Return the amount of participants for a multisig with these parameters.
pub fn n(&self) -> u16 { pub fn n(&self) -> u16 {
self.n self.n
} }
/// Return the participant index of the share with these parameters.
pub fn i(&self) -> Participant { pub fn i(&self) -> Participant {
self.i self.i
} }
@ -237,14 +244,18 @@ impl<C: Ciphersuite> ThresholdCore<C> {
verification_shares, verification_shares,
} }
} }
/// Parameters for these keys.
pub fn params(&self) -> ThresholdParams { pub fn params(&self) -> ThresholdParams {
self.params self.params
} }
/// Secret share for these keys.
pub fn secret_share(&self) -> &Zeroizing<C::F> { pub fn secret_share(&self) -> &Zeroizing<C::F> {
&self.secret_share &self.secret_share
} }
/// Group key for these keys.
pub fn group_key(&self) -> C::G { pub fn group_key(&self) -> C::G {
self.group_key self.group_key
} }
@ -253,6 +264,7 @@ impl<C: Ciphersuite> ThresholdCore<C> {
self.verification_shares.clone() 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<()> { 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(&u32::try_from(C::ID.len()).unwrap().to_le_bytes())?;
writer.write_all(C::ID)?; writer.write_all(C::ID)?;
@ -269,61 +281,56 @@ impl<C: Ciphersuite> ThresholdCore<C> {
Ok(()) Ok(())
} }
/// Serialize these keys to a `Vec<u8>`.
pub fn serialize(&self) -> Zeroizing<Vec<u8>> { pub fn serialize(&self) -> Zeroizing<Vec<u8>> {
let mut serialized = Zeroizing::new(vec![]); let mut serialized = Zeroizing::new(vec![]);
self.write::<Vec<u8>>(serialized.as_mut()).unwrap(); self.write::<Vec<u8>>(serialized.as_mut()).unwrap();
serialized 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 =
let different = DkgError::InternalError("deserializing ThresholdCore for another curve"); || io::Error::new(io::ErrorKind::Other, "deserializing ThresholdCore for another curve");
let mut id_len = [0; 4]; 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 { 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()]; 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 { if id != C::ID {
Err(different)?; Err(different())?;
} }
} }
let (t, n, i) = { let (t, n, i) = {
let mut read_u16 = || { let mut read_u16 = || -> io::Result<u16> {
let mut value = [0; 2]; let mut value = [0; 2];
reader reader.read_exact(&mut value)?;
.read_exact(&mut value)
.map_err(|_| DkgError::InternalError("missing participant quantities"))?;
Ok(u16::from_le_bytes(value)) Ok(u16::from_le_bytes(value))
}; };
( (
read_u16()?, read_u16()?,
read_u16()?, read_u16()?,
Participant::new(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( let secret_share = Zeroizing::new(C::read_F(reader)?);
C::read_F(reader).map_err(|_| DkgError::InternalError("invalid secret share"))?,
);
let mut verification_shares = HashMap::new(); let mut verification_shares = HashMap::new();
for l in (1 ..= n).map(Participant) { for l in (1 ..= n).map(Participant) {
verification_shares.insert( verification_shares.insert(l, <C as Ciphersuite>::read_G(reader)?);
l,
<C as Ciphersuite>::read_G(reader)
.map_err(|_| DkgError::InternalError("invalid verification share"))?,
);
} }
Ok(ThresholdCore::new( 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, secret_share,
verification_shares, verification_shares,
)) ))
@ -343,7 +350,7 @@ pub struct ThresholdKeys<C: Ciphersuite> {
pub(crate) offset: Option<C::F>, pub(crate) offset: Option<C::F>,
} }
/// View of keys passed to algorithm implementations. /// View of keys, interpolated and offset for usage.
#[derive(Clone)] #[derive(Clone)]
pub struct ThresholdView<C: Ciphersuite> { pub struct ThresholdView<C: Ciphersuite> {
offset: C::F, offset: C::F,
@ -383,13 +390,15 @@ impl<C: Ciphersuite> Zeroize for ThresholdView<C> {
} }
impl<C: Ciphersuite> ThresholdKeys<C> { impl<C: Ciphersuite> ThresholdKeys<C> {
/// Create a new set of ThresholdKeys from a ThresholdCore.
pub fn new(core: ThresholdCore<C>) -> ThresholdKeys<C> { pub fn new(core: ThresholdCore<C>) -> ThresholdKeys<C> {
ThresholdKeys { core: Arc::new(core), offset: None } ThresholdKeys { core: Arc::new(core), offset: None }
} }
/// Offset the keys by a given scalar to allow for account and privacy schemes. /// 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. ///
/// Keys offset multiple times will form a new offset of their sum. /// 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] #[must_use]
pub fn offset(&self, offset: C::F) -> ThresholdKeys<C> { pub fn offset(&self, offset: C::F) -> ThresholdKeys<C> {
let mut res = self.clone(); let mut res = self.clone();
@ -400,33 +409,38 @@ impl<C: Ciphersuite> ThresholdKeys<C> {
res 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> { pub fn current_offset(&self) -> Option<C::F> {
self.offset self.offset
} }
/// Return the parameters for these keys.
pub fn params(&self) -> ThresholdParams { pub fn params(&self) -> ThresholdParams {
self.core.params self.core.params
} }
/// Return the secret share for these keys.
pub fn secret_share(&self) -> &Zeroizing<C::F> { pub fn secret_share(&self) -> &Zeroizing<C::F> {
&self.core.secret_share &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 { pub fn group_key(&self) -> C::G {
self.core.group_key + (C::generator() * self.offset.unwrap_or_else(C::F::zero)) 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> { pub(crate) fn verification_shares(&self) -> HashMap<Participant, C::G> {
self.core.verification_shares() self.core.verification_shares()
} }
/// Serialize these keys to a `Vec<u8>`.
pub fn serialize(&self) -> Zeroizing<Vec<u8>> { pub fn serialize(&self) -> Zeroizing<Vec<u8>> {
self.core.serialize() 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<()>> { 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()) 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> { impl<C: Ciphersuite> ThresholdView<C> {
/// Return the offset for this view.
pub fn offset(&self) -> C::F { pub fn offset(&self) -> C::F {
self.offset self.offset
} }
/// Return the group key.
pub fn group_key(&self) -> C::G { pub fn group_key(&self) -> C::G {
self.group_key self.group_key
} }
/// Return the included signers.
pub fn included(&self) -> &[Participant] { pub fn included(&self) -> &[Participant] {
&self.included &self.included
} }
/// Return the interpolated, offset secret share.
pub fn secret_share(&self) -> &Zeroizing<C::F> { pub fn secret_share(&self) -> &Zeroizing<C::F> {
&self.secret_share &self.secret_share
} }
/// Return the original verification share for the specified participant.
pub fn original_verification_share(&self, l: Participant) -> C::G { pub fn original_verification_share(&self, l: Participant) -> C::G {
self.original_verification_shares[&l] self.original_verification_shares[&l]
} }
/// Return the interpolated, offset verification share for the specified participant.
pub fn verification_share(&self, l: Participant) -> C::G { pub fn verification_share(&self, l: Participant) -> C::G {
self.verification_shares[&l] 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. /// 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 /// 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 /// update the generator used with keys. This outperforms the key generation protocol which is
// exponential. /// exponential.
pub struct GeneratorPromotion<C1: Ciphersuite, C2: Ciphersuite> { pub struct GeneratorPromotion<C1: Ciphersuite, C2: Ciphersuite> {
base: ThresholdKeys<C1>, base: ThresholdKeys<C1>,
proof: GeneratorProof<C1>, proof: GeneratorProof<C1>,

View file

@ -7,7 +7,7 @@ use ciphersuite::{group::ff::Field, Ciphersuite};
use crate::{Participant, ThresholdCore, ThresholdKeys, lagrange}; use crate::{Participant, ThresholdCore, ThresholdKeys, lagrange};
/// FROST generation test. /// FROST key generation testing utility.
pub mod frost; pub mod frost;
use frost::frost_gen; use frost::frost_gen;
@ -17,7 +17,7 @@ use promote::test_generator_promotion;
/// Constant amount of participants to use when testing. /// Constant amount of participants to use when testing.
pub const PARTICIPANTS: u16 = 5; 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; pub const THRESHOLD: u16 = ((PARTICIPANTS / 3) * 2) + 1;
/// Clone a map without a specific value. /// Clone a map without a specific value.

View file

@ -1,14 +1,17 @@
# Discrete Log Equality # Discrete Log Equality
Implementation of discrete log equality proofs for curves implementing 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 the `experimental` feature, which has no formal proofs available yet is
available here regardless. available here regardless.
This library, except for the `experimental` feature, was 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), [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. Any subsequent culminating in commit
changes have not undergone auditing. [669d2dbffc1dafb82a09d9419ea182667115df06](https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06).
Any subsequent changes have not undergone auditing.
### Cross-Group DLEq ### Cross-Group DLEq

View file

@ -18,6 +18,7 @@ use group::{
}; };
use multiexp::BatchVerifier; use multiexp::BatchVerifier;
/// Scalar utilities.
pub mod scalar; pub mod scalar;
use scalar::{scalar_convert, mutual_scalar_from_bytes}; 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()) Ok(point.unwrap())
} }
/// A pair of generators, one committing to values (primary), one blinding (alt), for an elliptic
/// curve.
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
pub struct Generators<G: PrimeGroup> { pub struct Generators<G: PrimeGroup> {
/// The generator used to commit to values.
///
/// This should likely be the curve's traditional 'basepoint'.
pub primary: G, pub primary: G,
/// The generator used to blind values. This must be distinct from the primary generator.
pub alt: G, pub alt: G,
} }
impl<G: PrimeGroup> Generators<G> { impl<G: PrimeGroup> Generators<G> {
pub fn new(primary: G, alt: G) -> Generators<G> { /// Create a new set of generators.
Generators { primary, alt } 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) { 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)] #[derive(Error, PartialEq, Eq, Debug)]
pub enum DLEqError { pub enum DLEqError {
/// Invalid proof of knowledge.
#[error("invalid proof of knowledge")] #[error("invalid proof of knowledge")]
InvalidProofOfKnowledge, InvalidProofOfKnowledge,
/// Invalid proof length.
#[error("invalid proof length")] #[error("invalid proof length")]
InvalidProofLength, InvalidProofLength,
/// Invalid challenge.
#[error("invalid challenge")] #[error("invalid challenge")]
InvalidChallenge, InvalidChallenge,
/// Invalid proof.
#[error("invalid proof")] #[error("invalid proof")]
InvalidProof, InvalidProof,
} }
@ -115,7 +131,8 @@ pub struct __DLEqProof<
} }
macro_rules! dleq { 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< pub type $name<G0, G1> = __DLEqProof<
G0, G0,
G1, 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 // 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 // reference for the following DLEq proofs, all which use merged challenges, and isn't performant
// in comparison to the others // 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 // Proves for 2-bits at a time to save 3/7 elements of every other bit
// <9% smaller than CompromiseLinear, yet ~12% slower // <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 // 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 // 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 // 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 // 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 // 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 // >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< impl<
G0: PrimeGroup + Zeroize, G0: PrimeGroup + Zeroize,
@ -297,10 +343,13 @@ where
(proof, f) (proof, f)
} }
/// Prove the cross-Group Discrete Log Equality for the points derived from the scalar created as /// 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 /// the output of the passed in Digest.
/// 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. /// 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 /// 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 /// 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. /// the relationship between keys would allow breaking all swaps after just one.
@ -323,9 +372,11 @@ where
Self::prove_internal(rng, transcript, generators, f) Self::prove_internal(rng, transcript, generators, f)
} }
/// Prove the cross-Group Discrete Log Equality for the points derived from the scalar passed in, /// 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 /// failing if it's not mutually valid.
/// scalars until they're safely usable, as needed. ///
/// This allows for rejection sampling externally derived scalars until they're safely usable,
/// as needed.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn prove_without_bias<R: RngCore + CryptoRng, T: Clone + Transcript>( pub fn prove_without_bias<R: RngCore + CryptoRng, T: Clone + Transcript>(
rng: &mut R, rng: &mut R,
@ -337,7 +388,7 @@ where
.map(|f1| Self::prove_internal(rng, transcript, generators, (f0, Zeroizing::new(f1)))) .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>( pub fn verify<R: RngCore + CryptoRng, T: Clone + Transcript>(
&self, &self,
rng: &mut R, rng: &mut R,
@ -386,6 +437,7 @@ where
Ok(keys) Ok(keys)
} }
/// Write a Cross-Group Discrete Log Equality proof to a type satisfying std::io::Write.
#[cfg(feature = "serialize")] #[cfg(feature = "serialize")]
pub fn write<W: Write>(&self, w: &mut W) -> std::io::Result<()> { pub fn write<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
for bit in &self.bits { for bit in &self.bits {
@ -398,6 +450,7 @@ where
self.poks.1.write(w) self.poks.1.write(w)
} }
/// Read a Cross-Group Discrete Log Equality proof from a type satisfying std::io::Read.
#[cfg(feature = "serialize")] #[cfg(feature = "serialize")]
pub fn read<R: Read>(r: &mut R) -> std::io::Result<Self> { 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(); 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(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
#![doc = include_str!("../README.md")]
use core::ops::Deref; use core::ops::Deref;
@ -15,6 +16,8 @@ use group::prime::PrimeGroup;
#[cfg(feature = "serialize")] #[cfg(feature = "serialize")]
use std::io::{self, ErrorKind, Error, Read, Write}; 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")] #[cfg(feature = "experimental")]
pub mod cross_group; 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. /// Error for DLEq proofs.
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DLEqError { pub enum DLEqError {
/// The proof was invalid.
InvalidProof, 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. /// 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 /// 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. /// across some generators, yet with a smaller overall proof size.
#[cfg(feature = "std")] #[cfg(feature = "std")]

View file

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

View file

@ -73,6 +73,7 @@ macro_rules! field {
} }
impl $FieldName { impl $FieldName {
/// Perform an exponentation.
pub fn pow(&self, other: $FieldName) -> $FieldName { pub fn pow(&self, other: $FieldName) -> $FieldName {
let mut table = [Self(U512::ONE); 16]; let mut table = [Self(U512::ONE); 16];
table[1] = *self; table[1] = *self;

View file

@ -4,11 +4,12 @@ use crypto_bigint::{U512, U1024};
use crate::field; use crate::field;
/// Ed448 field element.
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Zeroize)] #[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Zeroize)]
pub struct FieldElement(pub(crate) U512); pub struct FieldElement(pub(crate) U512);
// 2**448 - 2**224 - 1 // 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", "00000000000000",
"00", "00",
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffe", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffe",

View file

@ -1,13 +1,14 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![no_std] #![no_std]
#![doc = include_str!("../README.md")]
mod backend; mod backend;
pub mod scalar; mod scalar;
pub use scalar::Scalar; pub use scalar::Scalar;
pub mod field; mod field;
pub use field::FieldElement; pub use field::FieldElement;
pub mod point; mod point;
pub use point::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)] #[derive(Clone, Copy, Debug, Zeroize)]
pub struct Point { pub struct Point {
x: FieldElement, x: FieldElement,
@ -270,7 +271,7 @@ impl MulAssign<&Scalar> for Point {
} }
impl Point { impl Point {
pub fn is_torsion_free(&self) -> Choice { fn is_torsion_free(&self) -> Choice {
(*self * SCALAR_MODULUS).is_identity() (*self * SCALAR_MODULUS).is_identity()
} }
} }

View file

@ -2,13 +2,14 @@ use zeroize::Zeroize;
use crypto_bigint::{U512, U1024}; use crypto_bigint::{U512, U1024};
pub use crate::field; use crate::field;
/// Ed448 Scalar field element.
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Zeroize)] #[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Zeroize)]
pub struct Scalar(pub(crate) U512); pub struct Scalar(pub(crate) U512);
// 2**446 - 13818066809895115352007386748515426880336692474882178609894547503885 // 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", "00000000000000",
"00", "00",
"3fffffffffffffffffffffffffffffffffffffffffffffffffffffff", "3fffffffffffffffffffffffffffffffffffffffffffffffffffffff",
@ -27,6 +28,7 @@ const WIDE_MODULUS: U1024 = U1024::from_be_hex(concat!(
field!(Scalar, MODULUS, WIDE_MODULUS, 446); field!(Scalar, MODULUS, WIDE_MODULUS, 446);
impl Scalar { impl Scalar {
/// Perform a wide reduction to obtain a non-biased Scalar.
pub fn wide_reduce(bytes: [u8; 114]) -> Scalar { pub fn wide_reduce(bytes: [u8; 114]) -> Scalar {
Scalar(reduce(U1024::from_le_slice(&[bytes.as_ref(), &[0; 14]].concat()))) Scalar(reduce(U1024::from_le_slice(&[bytes.as_ref(), &[0; 14]].concat())))
} }

View file

@ -1,10 +1,12 @@
# FF/Group Tests # FF/Group Tests
A series of sanity checks for implementors of the ff/group APIs. Implementors A series of sanity checks for implementors of the ff/group APIs.
are assumed to be of a non-trivial size. These tests do not attempt to check if
constant time implementations are used. 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 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), [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. Any subsequent culminating in commit
changes have not undergone auditing. [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))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
/// Tests for the Field trait. /// Tests for the Field trait.
pub mod field; pub mod field;

View file

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

View file

@ -13,9 +13,10 @@ This library offers ciphersuites compatible with the
11 is supported. 11 is supported.
This library was 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), [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. Any subsequent culminating in commit
changes have not undergone auditing. While this audit included FROST's [669d2dbffc1dafb82a09d9419ea182667115df06](https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06).
definition of Ed448, the underlying Ed448 ciphersuite (offered by the 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 ciphersuite crate) was not audited, nor was the minimal-ed448 crate implementing
the curve itself. the curve itself.

View file

@ -22,6 +22,7 @@ macro_rules! dalek_curve {
const CONTEXT: &'static [u8] = $CONTEXT; const CONTEXT: &'static [u8] = $CONTEXT;
} }
/// The challenge function for this ciphersuite.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct $Hram; pub struct $Hram;
impl Hram<$Curve> for $Hram { impl Hram<$Curve> for $Hram {

View file

@ -11,11 +11,12 @@ impl Curve for Ed448 {
const CONTEXT: &'static [u8] = CONTEXT; const CONTEXT: &'static [u8] = CONTEXT;
} }
// The RFC-8032 Ed448 challenge function.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct Ietf8032Ed448Hram; pub(crate) struct Ietf8032Ed448Hram;
impl Ietf8032Ed448Hram { impl Ietf8032Ed448Hram {
#[allow(non_snake_case)] #[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( Scalar::wide_reduce(
Shake256_114::digest( Shake256_114::digest(
[ [
@ -32,6 +33,7 @@ impl Ietf8032Ed448Hram {
} }
} }
/// The challenge function for FROST's Ed448 ciphersuite.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct IetfEd448Hram; pub struct IetfEd448Hram;
impl Hram<Ed448> for IetfEd448Hram { impl Hram<Ed448> for IetfEd448Hram {

View file

@ -17,6 +17,7 @@ macro_rules! kp_curve {
const CONTEXT: &'static [u8] = $CONTEXT; const CONTEXT: &'static [u8] = $CONTEXT;
} }
/// The challenge function for this ciphersuite.
#[derive(Clone)] #[derive(Clone)]
pub struct $Hram; pub struct $Hram;
impl Hram<$Curve> for $Hram { impl Hram<$Curve> for $Hram {

View file

@ -33,10 +33,14 @@ pub use kp256::{P256, IetfP256Hram};
#[cfg(feature = "ed448")] #[cfg(feature = "ed448")]
mod ed448; mod ed448;
#[cfg(feature = "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, /// FROST Ciphersuite.
/// its associated hash function, and the functions derived from it. ///
/// 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 { pub trait Curve: Ciphersuite {
/// Context string for this curve. /// Context string for this curve.
const CONTEXT: &'static [u8]; const CONTEXT: &'static [u8];
@ -98,6 +102,7 @@ pub trait Curve: Ciphersuite {
res res
} }
/// Read a point from a reader, rejecting identity.
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> { fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {
let res = <Self as Ciphersuite>::read_G(reader)?; 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))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
//! 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.
use core::fmt::Debug; use core::fmt::Debug;
use std::collections::HashMap; use std::collections::HashMap;
@ -53,12 +41,9 @@ pub enum FrostError {
InvalidPreprocess(Participant), InvalidPreprocess(Participant),
#[error("invalid share (participant {0})")] #[error("invalid share (participant {0})")]
InvalidShare(Participant), 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>( pub fn validate_map<T>(
map: &HashMap<Participant, T>, map: &HashMap<Participant, T>,
included: &[Participant], 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)] #[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 // Skips the algorithm due to being too large a bound to feasibly enforce on users
#[zeroize(skip)] #[zeroize(skip)]
algorithm: A, algorithm: A,
@ -53,11 +53,11 @@ pub struct Params<C: Curve, A: Algorithm<C>> {
} }
impl<C: Curve, A: Algorithm<C>> Params<C, A> { 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 } Params { algorithm, keys }
} }
pub fn multisig_params(&self) -> ThresholdParams { fn multisig_params(&self) -> ThresholdParams {
self.keys.params() self.keys.params()
} }
} }
@ -66,6 +66,7 @@ impl<C: Curve, A: Algorithm<C>> Params<C, A> {
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub struct Preprocess<C: Curve, A: Addendum> { pub struct Preprocess<C: Curve, A: Addendum> {
pub(crate) commitments: Commitments<C>, pub(crate) commitments: Commitments<C>,
/// The addendum used by the algorithm.
pub addendum: A, 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 /// A cached preprocess.
/// 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 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. // 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 // 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. // 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 // 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))); 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) { pub fn test_offset_schnorr<R: RngCore + CryptoRng, C: Curve, H: Hram<C>>(rng: &mut R) {
const MSG: &[u8] = b"Hello, World!"; 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))); 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) { pub fn test_schnorr_blame<R: RngCore + CryptoRng, C: Curve, H: Hram<C>>(rng: &mut R) {
const MSG: &[u8] = b"Hello, World!"; 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) { pub fn test_ciphersuite<R: RngCore + CryptoRng, C: Curve, H: Hram<C>>(rng: &mut R) {
test_schnorr::<R, C, H>(rng); test_schnorr::<R, C, H>(rng);
test_offset_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}, tests::{clone_without, recover_key, test_ciphersuite},
}; };
/// Vectors for a ciphersuite.
pub struct Vectors { pub struct Vectors {
pub threshold: u16, pub threshold: u16,
@ -141,6 +142,7 @@ fn vectors_to_multisig_keys<C: Curve>(vectors: &Vectors) -> HashMap<Participant,
keys keys
} }
/// Test a Ciphersuite with its vectors.
pub fn test_with_vectors<R: RngCore + CryptoRng, C: Curve, H: Hram<C>>( pub fn test_with_vectors<R: RngCore + CryptoRng, C: Curve, H: Hram<C>>(
rng: &mut R, rng: &mut R,
vectors: Vectors, 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. should sum to 0, identifying which doesn't via binary search if they don't.
This library was 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), [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. Any subsequent culminating in commit
changes have not undergone auditing. [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 /// 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 /// statements do.
/// multiple are invalid. ///
/// 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 // A constant time variant may be beneficial for robust protocols
pub fn blame_vartime(&self) -> Option<Id> { pub fn blame_vartime(&self) -> Option<Id> {
let mut slice = self.0.as_slice(); let mut slice = self.0.as_slice();

View file

@ -1,4 +1,5 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
use core::ops::DerefMut; 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 This library provides signatures of the `R, s` form. Batch verification is
supported via the multiexp crate. Half-aggregation, as defined in 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 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), [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. Any subsequent culminating in commit
changes have not undergone auditing. [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)] #[allow(non_snake_case)]
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)] #[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub struct SchnorrAggregate<C: Ciphersuite> { pub struct SchnorrAggregate<C: Ciphersuite> {
pub Rs: Vec<C::G>, Rs: Vec<C::G>,
pub s: C::F, s: C::F,
} }
impl<C: Ciphersuite> SchnorrAggregate<C> { 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)] #[allow(non_snake_case)]
#[derive(Clone, Debug, Zeroize)] #[derive(Clone, Debug, Zeroize)]
pub struct SchnorrAggregator<C: Ciphersuite> { 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 core::ops::Deref;
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
@ -14,6 +17,7 @@ use ciphersuite::{
}; };
use multiexp::{multiexp_vartime, BatchVerifier}; use multiexp::{multiexp_vartime, BatchVerifier};
/// Half-aggregation from <https://eprint.iacr.org/2021/350>.
pub mod aggregate; pub mod aggregate;
#[cfg(test)] #[cfg(test)]

View file

@ -27,6 +27,7 @@ their type, and their length.
compatible with existing Rust projects using `merlin`. compatible with existing Rust projects using `merlin`.
This library was 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), [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. Any subsequent culminating in commit
changes have not undergone auditing. [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))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![no_std] #![no_std]
///! A transcript trait valid over a variety of transcript formats.
#[cfg(feature = "merlin")] #[cfg(feature = "merlin")]
mod merlin; mod merlin;
#[cfg(feature = "merlin")] #[cfg(feature = "merlin")]
pub use crate::merlin::MerlinTranscript; pub use crate::merlin::MerlinTranscript;
/// Tests for a transcript.
#[cfg(any(test, feature = "tests"))] #[cfg(any(test, feature = "tests"))]
pub mod tests; pub mod tests;
@ -16,6 +19,7 @@ use digest::{
Digest, Output, HashMarker, Digest, Output, HashMarker,
}; };
/// A transcript trait valid over a variety of transcript formats.
pub trait Transcript: Send + Clone { pub trait Transcript: Send + Clone {
type Challenge: Send + Sync + Clone + AsRef<[u8]>; 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")] #[cfg(feature = "recommended")]
pub type RecommendedTranscript = DigestTranscript<blake2::Blake2b512>; pub type RecommendedTranscript = DigestTranscript<blake2::Blake2b512>;

View file

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