use core::{marker::PhantomData, fmt::Debug}; use std::io::{self, Read, Write}; use zeroize::Zeroizing; use rand_core::{RngCore, CryptoRng}; use transcript::Transcript; use crate::{Participant, ThresholdKeys, ThresholdView, Curve, FrostError}; pub use schnorr::SchnorrSignature; /// Write an addendum to a writer. pub trait WriteAddendum { fn write(&self, writer: &mut W) -> io::Result<()>; } impl WriteAddendum for () { fn write(&self, _: &mut W) -> io::Result<()> { Ok(()) } } /// Trait alias for the requirements to be used as an addendum. pub trait Addendum: Send + Clone + PartialEq + Debug + WriteAddendum {} impl Addendum for A {} /// Algorithm trait usable by the FROST signing machine to produce signatures.. pub trait Algorithm: Send + Clone { /// The transcript format this algorithm uses. This likely should NOT be the IETF-compatible /// transcript included in this crate. type Transcript: Clone + Debug + Transcript; /// Serializable addendum, used in algorithms requiring more data than just the nonces. type Addendum: Addendum; /// The resulting type of the signatures this algorithm will produce. type Signature: Clone + PartialEq + Debug; /// Obtain a mutable borrow of the underlying transcript. fn transcript(&mut self) -> &mut Self::Transcript; /// Obtain the list of nonces to generate, as specified by the generators to create commitments /// against per-nonce. fn nonces(&self) -> Vec>; /// Generate an addendum to FROST"s preprocessing stage. fn preprocess_addendum( &mut self, rng: &mut R, keys: &ThresholdKeys, ) -> Self::Addendum; /// Read an addendum from a reader. fn read_addendum(&self, reader: &mut R) -> io::Result; /// Proccess the addendum for the specified participant. Guaranteed to be called in order. fn process_addendum( &mut self, params: &ThresholdView, l: Participant, reader: Self::Addendum, ) -> Result<(), FrostError>; /// Sign a share with the given secret/nonce. /// The secret will already have been its lagrange coefficient applied so it is the necessary /// key share. /// The nonce will already have been processed into the combined form d + (e * p). fn sign_share( &mut self, params: &ThresholdView, nonce_sums: &[Vec], nonces: Vec>, msg: &[u8], ) -> C::F; /// Verify a signature. #[must_use] fn verify(&self, group_key: C::G, nonces: &[Vec], sum: C::F) -> Option; /// Verify a specific share given as a response. /// This function should return a series of pairs whose products should sum to zero for a valid /// share. Any error raised is treated as the share being invalid. #[allow(clippy::type_complexity, clippy::result_unit_err)] fn verify_share( &self, verification_share: C::G, nonces: &[Vec], share: C::F, ) -> Result, ()>; } mod sealed { pub use super::*; /// IETF-compliant transcript. This is incredibly naive and should not be used within larger /// protocols. #[derive(Clone, Debug)] pub struct IetfTranscript(pub(crate) Vec); impl Transcript for IetfTranscript { type Challenge = Vec; fn new(_: &'static [u8]) -> IetfTranscript { IetfTranscript(vec![]) } fn domain_separate(&mut self, _: &[u8]) {} fn append_message>(&mut self, _: &'static [u8], message: M) { self.0.extend(message.as_ref()); } fn challenge(&mut self, _: &'static [u8]) -> Vec { self.0.clone() } // FROST won't use this and this shouldn't be used outside of FROST fn rng_seed(&mut self, _: &'static [u8]) -> [u8; 32] { unimplemented!() } } } pub(crate) use sealed::IetfTranscript; /// HRAm usable by the included Schnorr signature algorithm to generate challenges. pub trait Hram: Send + Clone { /// HRAm function to generate a challenge. /// H2 from the IETF draft, despite having a different argument set (not being pre-formatted). #[allow(non_snake_case)] fn hram(R: &C::G, A: &C::G, m: &[u8]) -> C::F; } /// IETF-compliant Schnorr signature algorithm ((R, s) where s = r + cx). #[derive(Clone)] pub struct Schnorr> { transcript: IetfTranscript, c: Option, _hram: PhantomData, } impl> Default for Schnorr { fn default() -> Self { Self::new() } } impl> Schnorr { pub fn new() -> Schnorr { Schnorr { transcript: IetfTranscript(vec![]), c: None, _hram: PhantomData } } } impl> Algorithm for Schnorr { type Transcript = IetfTranscript; type Addendum = (); type Signature = SchnorrSignature; fn transcript(&mut self) -> &mut Self::Transcript { &mut self.transcript } fn nonces(&self) -> Vec> { vec![vec![C::generator()]] } fn preprocess_addendum(&mut self, _: &mut R, _: &ThresholdKeys) {} fn read_addendum(&self, _: &mut R) -> io::Result { Ok(()) } fn process_addendum( &mut self, _: &ThresholdView, _: Participant, _: (), ) -> Result<(), FrostError> { Ok(()) } fn sign_share( &mut self, params: &ThresholdView, nonce_sums: &[Vec], mut nonces: Vec>, msg: &[u8], ) -> C::F { let c = H::hram(&nonce_sums[0][0], ¶ms.group_key(), msg); self.c = Some(c); SchnorrSignature::::sign(params.secret_share(), nonces.swap_remove(0), c).s } #[must_use] fn verify(&self, group_key: C::G, nonces: &[Vec], sum: C::F) -> Option { let sig = SchnorrSignature { R: nonces[0][0], s: sum }; Some(sig).filter(|sig| sig.verify(group_key, self.c.unwrap())) } fn verify_share( &self, verification_share: C::G, nonces: &[Vec], share: C::F, ) -> Result, ()> { Ok( SchnorrSignature:: { R: nonces[0][0], s: share } .batch_statements(verification_share, self.c.unwrap()) .to_vec(), ) } }