#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc = include_str!("../README.md")] use std::io::{self, Read}; use rand_core::{RngCore, CryptoRng}; use zeroize::Zeroizing; use transcript::{Transcript, MerlinTranscript}; use group::{ff::PrimeField, GroupEncoding}; use ciphersuite::{Ciphersuite, Ristretto}; use schnorr::SchnorrSignature; use ::frost::{ Participant, ThresholdKeys, ThresholdView, FrostError, algorithm::{Hram, Algorithm, Schnorr}, }; /// The [modular-frost](https://docs.rs/modular-frost) library. pub mod frost { pub use ::frost::*; } use schnorrkel::{PublicKey, Signature, context::SigningTranscript, signing_context}; type RistrettoPoint = ::G; type Scalar = ::F; #[cfg(test)] mod tests; #[derive(Clone)] struct SchnorrkelHram; impl Hram for SchnorrkelHram { #[allow(non_snake_case)] fn hram(R: &RistrettoPoint, A: &RistrettoPoint, m: &[u8]) -> Scalar { let ctx_len = usize::try_from(u32::from_le_bytes(m[0 .. 4].try_into().expect("malformed message"))) .unwrap(); let mut t = signing_context(&m[4 .. (4 + ctx_len)]).bytes(&m[(4 + ctx_len) ..]); t.proto_name(b"Schnorr-sig"); let convert = |point: &RistrettoPoint| PublicKey::from_bytes(&point.to_bytes()).unwrap().into_compressed(); t.commit_point(b"sign:pk", &convert(A)); t.commit_point(b"sign:R", &convert(R)); Scalar::from_repr(t.challenge_scalar(b"sign:c").to_bytes()).unwrap() } } /// FROST Schnorrkel algorithm. #[derive(Clone)] pub struct Schnorrkel { context: &'static [u8], schnorr: Schnorr, msg: Option>, } impl Schnorrkel { /// Create a new algorithm with the specified context. /// /// If the context is greater than or equal to 4 GB in size, this will panic. pub fn new(context: &'static [u8]) -> Schnorrkel { Schnorrkel { context, schnorr: Schnorr::new(MerlinTranscript::new(b"FROST Schnorrkel")), msg: None, } } } impl Algorithm for Schnorrkel { type Transcript = MerlinTranscript; type Addendum = (); type Signature = Signature; fn transcript(&mut self) -> &mut Self::Transcript { self.schnorr.transcript() } fn nonces(&self) -> Vec::G>> { self.schnorr.nonces() } 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], nonces: Vec>, msg: &[u8], ) -> Scalar { self.msg = Some(msg.to_vec()); self.schnorr.sign_share( params, nonce_sums, nonces, &[ &u32::try_from(self.context.len()).expect("context exceeded 2^32 bytes").to_le_bytes(), self.context, msg, ] .concat(), ) } #[must_use] fn verify( &self, group_key: RistrettoPoint, nonces: &[Vec], sum: Scalar, ) -> Option { let mut sig = (SchnorrSignature:: { R: nonces[0][0], s: sum }).serialize(); sig[63] |= 1 << 7; Some(Signature::from_bytes(&sig).unwrap()).filter(|sig| { PublicKey::from_bytes(&group_key.to_bytes()) .unwrap() .verify(&mut signing_context(self.context).bytes(self.msg.as_ref().unwrap()), sig) .is_ok() }) } fn verify_share( &self, verification_share: RistrettoPoint, nonces: &[Vec], share: Scalar, ) -> Result, ()> { self.schnorr.verify_share(verification_share, nonces, share) } }