mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-03 09:29:46 +00:00
Fully document crypto/
This commit is contained in:
parent
e1bb2c191b
commit
8d4d630e0f
45 changed files with 335 additions and 208 deletions
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
9
crypto/ciphersuite/src/lib.md
Normal file
9
crypto/ciphersuite/src/lib.md
Normal 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.
|
|
@ -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")]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())))
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
/// Tests for the Field trait.
|
||||
pub mod field;
|
||||
|
|
|
@ -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`."
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
use core::ops::DerefMut;
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::Transcript;
|
||||
|
||||
/// Test the sanity of a transcript.
|
||||
pub fn test_transcript<T: Transcript>()
|
||||
where
|
||||
T::Challenge: PartialEq,
|
||||
|
|
Loading…
Reference in a new issue