mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
* Remove the explicit included participants from FROST Now, whoever submits preprocesses becomes the signing set. Better separates preprocess from sign, at the cost of slightly more annoying integrations (Monero needs to now independently lagrange/offset its key images). * Support caching preprocesses Closes https://github.com/serai-dex/serai/issues/40. I *could* have added a serialization trait to Algorithm and written a ton of data to disk, while requiring Algorithm implementors also accept such work. Instead, I moved preprocess to a seeded RNG (Chacha20) which should be as secure as the regular RNG. Rebuilding from cache simply loads the previously used Chacha seed, making the Algorithm oblivious to the fact it's being rebuilt from a cache. This removes any requirements for it to be modified while guaranteeing equivalency. This builds on the last commit which delayed determining the signing set till post-preprocess acquisition. Unfortunately, that commit did force preprocess from ThresholdView to ThresholdKeys which had visible effects on Monero. Serai will actually need delayed set determination for #163, and overall, it remains better, hence it's inclusion. * Document FROST preprocess caching * Update ethereum to new FROST * Fix bug in Monero offset calculation and update processor
184 lines
5.6 KiB
Rust
184 lines
5.6 KiB
Rust
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::{Curve, FrostError, ThresholdKeys, ThresholdView};
|
|
pub use schnorr::SchnorrSignature;
|
|
|
|
/// Write an addendum to a writer.
|
|
pub trait WriteAddendum {
|
|
fn write<W: Write>(&self, writer: &mut W) -> io::Result<()>;
|
|
}
|
|
|
|
impl WriteAddendum for () {
|
|
fn write<W: Write>(&self, _: &mut W) -> io::Result<()> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Trait alias for the requirements to be used as an addendum.
|
|
pub trait Addendum: Clone + PartialEq + Debug + WriteAddendum {}
|
|
impl<A: Clone + PartialEq + Debug + WriteAddendum> Addendum for A {}
|
|
|
|
/// Algorithm trait usable by the FROST signing machine to produce signatures..
|
|
pub trait Algorithm<C: Curve>: 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<Vec<C::G>>;
|
|
|
|
/// Generate an addendum to FROST"s preprocessing stage.
|
|
fn preprocess_addendum<R: RngCore + CryptoRng>(
|
|
&mut self,
|
|
rng: &mut R,
|
|
keys: &ThresholdKeys<C>,
|
|
) -> Self::Addendum;
|
|
|
|
/// Read an addendum from a reader.
|
|
fn read_addendum<R: Read>(&self, reader: &mut R) -> io::Result<Self::Addendum>;
|
|
|
|
/// Proccess the addendum for the specified participant. Guaranteed to be called in order.
|
|
fn process_addendum(
|
|
&mut self,
|
|
params: &ThresholdView<C>,
|
|
l: u16,
|
|
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<C>,
|
|
nonce_sums: &[Vec<C::G>],
|
|
nonces: Vec<Zeroizing<C::F>>,
|
|
msg: &[u8],
|
|
) -> C::F;
|
|
|
|
/// Verify a signature.
|
|
#[must_use]
|
|
fn verify(&self, group_key: C::G, nonces: &[Vec<C::G>], sum: C::F) -> Option<Self::Signature>;
|
|
|
|
/// Verify a specific share given as a response. Used to determine blame if signature
|
|
/// verification fails.
|
|
#[must_use]
|
|
fn verify_share(&self, verification_share: C::G, nonces: &[Vec<C::G>], share: C::F) -> bool;
|
|
}
|
|
|
|
/// IETF-compliant transcript. This is incredibly naive and should not be used within larger
|
|
/// protocols.
|
|
#[derive(Clone, Debug)]
|
|
pub struct IetfTranscript(Vec<u8>);
|
|
impl Transcript for IetfTranscript {
|
|
type Challenge = Vec<u8>;
|
|
|
|
fn new(_: &'static [u8]) -> IetfTranscript {
|
|
IetfTranscript(vec![])
|
|
}
|
|
|
|
fn domain_separate(&mut self, _: &[u8]) {}
|
|
|
|
fn append_message<M: AsRef<[u8]>>(&mut self, _: &'static [u8], message: M) {
|
|
self.0.extend(message.as_ref());
|
|
}
|
|
|
|
fn challenge(&mut self, _: &'static [u8]) -> Vec<u8> {
|
|
self.0.clone()
|
|
}
|
|
|
|
fn rng_seed(&mut self, _: &'static [u8]) -> [u8; 32] {
|
|
unimplemented!()
|
|
}
|
|
}
|
|
|
|
/// HRAm usable by the included Schnorr signature algorithm to generate challenges.
|
|
pub trait Hram<C: Curve>: 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<C: Curve, H: Hram<C>> {
|
|
transcript: IetfTranscript,
|
|
c: Option<C::F>,
|
|
_hram: PhantomData<H>,
|
|
}
|
|
|
|
impl<C: Curve, H: Hram<C>> Default for Schnorr<C, H> {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl<C: Curve, H: Hram<C>> Schnorr<C, H> {
|
|
pub fn new() -> Schnorr<C, H> {
|
|
Schnorr { transcript: IetfTranscript(vec![]), c: None, _hram: PhantomData }
|
|
}
|
|
}
|
|
|
|
impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
|
|
type Transcript = IetfTranscript;
|
|
type Addendum = ();
|
|
type Signature = SchnorrSignature<C>;
|
|
|
|
fn transcript(&mut self) -> &mut Self::Transcript {
|
|
&mut self.transcript
|
|
}
|
|
|
|
fn nonces(&self) -> Vec<Vec<C::G>> {
|
|
vec![vec![C::generator()]]
|
|
}
|
|
|
|
fn preprocess_addendum<R: RngCore + CryptoRng>(&mut self, _: &mut R, _: &ThresholdKeys<C>) {}
|
|
|
|
fn read_addendum<R: Read>(&self, _: &mut R) -> io::Result<Self::Addendum> {
|
|
Ok(())
|
|
}
|
|
|
|
fn process_addendum(&mut self, _: &ThresholdView<C>, _: u16, _: ()) -> Result<(), FrostError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn sign_share(
|
|
&mut self,
|
|
params: &ThresholdView<C>,
|
|
nonce_sums: &[Vec<C::G>],
|
|
mut nonces: Vec<Zeroizing<C::F>>,
|
|
msg: &[u8],
|
|
) -> C::F {
|
|
let c = H::hram(&nonce_sums[0][0], ¶ms.group_key(), msg);
|
|
self.c = Some(c);
|
|
SchnorrSignature::<C>::sign(params.secret_share(), nonces.swap_remove(0), c).s
|
|
}
|
|
|
|
#[must_use]
|
|
fn verify(&self, group_key: C::G, nonces: &[Vec<C::G>], sum: C::F) -> Option<Self::Signature> {
|
|
let sig = SchnorrSignature { R: nonces[0][0], s: sum };
|
|
Some(sig).filter(|sig| sig.verify(group_key, self.c.unwrap()))
|
|
}
|
|
|
|
#[must_use]
|
|
fn verify_share(&self, verification_share: C::G, nonces: &[Vec<C::G>], share: C::F) -> bool {
|
|
SchnorrSignature::<C> { R: nonces[0][0], s: share }.verify(verification_share, self.c.unwrap())
|
|
}
|
|
}
|