mirror of
https://github.com/serai-dex/serai.git
synced 2025-03-24 08:08:51 +00:00
Consolidate Schnorr code in FROST
This commit is contained in:
parent
d67d6f2f98
commit
1eaf2f897b
4 changed files with 132 additions and 71 deletions
|
@ -2,11 +2,10 @@ use core::{marker::PhantomData, fmt::Debug};
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use group::Group;
|
|
||||||
|
|
||||||
use transcript::Transcript;
|
use transcript::Transcript;
|
||||||
|
|
||||||
use crate::{Curve, FrostError, MultisigView};
|
use crate::{Curve, FrostError, MultisigView, schnorr};
|
||||||
|
pub use schnorr::SchnorrSignature;
|
||||||
|
|
||||||
/// Algorithm to use FROST with
|
/// Algorithm to use FROST with
|
||||||
pub trait Algorithm<C: Curve>: Clone {
|
pub trait Algorithm<C: Curve>: Clone {
|
||||||
|
@ -103,13 +102,6 @@ impl<C: Curve, H: Hram<C>> Schnorr<C, H> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
|
||||||
pub struct SchnorrSignature<C: Curve> {
|
|
||||||
pub R: C::G,
|
|
||||||
pub s: C::F,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implementation of Schnorr signatures for use with FROST
|
/// Implementation of Schnorr signatures for use with FROST
|
||||||
impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
|
impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
|
||||||
type Transcript = IetfTranscript;
|
type Transcript = IetfTranscript;
|
||||||
|
@ -148,13 +140,13 @@ impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
|
||||||
) -> C::F {
|
) -> C::F {
|
||||||
let c = H::hram(&nonce_sum, ¶ms.group_key(), msg);
|
let c = H::hram(&nonce_sum, ¶ms.group_key(), msg);
|
||||||
self.c = Some(c);
|
self.c = Some(c);
|
||||||
|
schnorr::sign::<C>(params.secret_share(), nonce, c).s
|
||||||
nonce + (params.secret_share() * c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(&self, group_key: C::G, nonce: C::G, sum: C::F) -> Option<Self::Signature> {
|
fn verify(&self, group_key: C::G, nonce: C::G, sum: C::F) -> Option<Self::Signature> {
|
||||||
if (C::generator_table() * sum) + (C::G::identity() - (group_key * self.c.unwrap())) == nonce {
|
let sig = SchnorrSignature { R: nonce, s: sum };
|
||||||
Some(SchnorrSignature { R: nonce, s: sum })
|
if schnorr::verify::<C>(group_key, self.c.unwrap(), &sig) {
|
||||||
|
Some(sig)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -166,6 +158,10 @@ impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
|
||||||
nonce: C::G,
|
nonce: C::G,
|
||||||
share: C::F,
|
share: C::F,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
(C::generator_table() * share) == (nonce + (verification_share * self.c.unwrap()))
|
schnorr::verify::<C>(
|
||||||
|
verification_share,
|
||||||
|
self.c.unwrap(),
|
||||||
|
&SchnorrSignature { R: nonce, s: share}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,12 @@ use std::collections::HashMap;
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use ff::{Field, PrimeField};
|
use ff::{Field, PrimeField};
|
||||||
use group::Group;
|
|
||||||
|
|
||||||
use crate::{Curve, MultisigParams, MultisigKeys, FrostError, validate_map};
|
use crate::{
|
||||||
|
Curve, MultisigParams, MultisigKeys, FrostError,
|
||||||
|
schnorr::{self, SchnorrSignature},
|
||||||
|
validate_map
|
||||||
|
};
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn challenge<C: Curve>(l: u16, context: &str, R: &[u8], Am: &[u8]) -> C::F {
|
fn challenge<C: Curve>(l: u16, context: &str, R: &[u8], Am: &[u8]) -> C::F {
|
||||||
|
@ -42,19 +45,24 @@ fn generate_key_r1<R: RngCore + CryptoRng, C: Curve>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Provide a proof of knowledge
|
// Step 2: Provide a proof of knowledge
|
||||||
// This can be deterministic as the PoK is a singleton never opened up to cooperative discussion
|
|
||||||
// There's also no reason to spend the time and effort to make this deterministic besides a
|
|
||||||
// general obsession with canonicity and determinism
|
|
||||||
let r = C::F::random(rng);
|
let r = C::F::random(rng);
|
||||||
#[allow(non_snake_case)]
|
serialized.extend(
|
||||||
let R = C::generator_table() * r;
|
schnorr::sign::<C>(
|
||||||
let s = r + (
|
coefficients[0],
|
||||||
coefficients[0] * challenge::<C>(params.i(), context, &C::G_to_bytes(&R), &serialized)
|
// This could be deterministic as the PoK is a singleton never opened up to cooperative
|
||||||
|
// discussion
|
||||||
|
// There's no reason to spend the time and effort to make this deterministic besides a
|
||||||
|
// general obsession with canonicity and determinism though
|
||||||
|
r,
|
||||||
|
challenge::<C>(
|
||||||
|
params.i(),
|
||||||
|
context,
|
||||||
|
&C::G_to_bytes(&(C::generator_table() * r)),
|
||||||
|
&serialized
|
||||||
|
)
|
||||||
|
).serialize()
|
||||||
);
|
);
|
||||||
|
|
||||||
serialized.extend(&C::G_to_bytes(&R));
|
|
||||||
serialized.extend(&C::F_to_bytes(&s));
|
|
||||||
|
|
||||||
// Step 4: Broadcast
|
// Step 4: Broadcast
|
||||||
(coefficients, serialized)
|
(coefficients, serialized)
|
||||||
}
|
}
|
||||||
|
@ -88,9 +96,7 @@ fn verify_r1<R: RngCore + CryptoRng, C: Curve>(
|
||||||
&serialized[&l][commitments_len + C::G_len() ..]
|
&serialized[&l][commitments_len + C::G_len() ..]
|
||||||
).map_err(|_| FrostError::InvalidProofOfKnowledge(l));
|
).map_err(|_| FrostError::InvalidProofOfKnowledge(l));
|
||||||
|
|
||||||
let mut first = true;
|
let mut signatures = Vec::with_capacity(usize::from(params.n() - 1));
|
||||||
let mut scalars = Vec::with_capacity((usize::from(params.n()) - 1) * 3);
|
|
||||||
let mut points = Vec::with_capacity((usize::from(params.n()) - 1) * 3);
|
|
||||||
for l in 1 ..= params.n() {
|
for l in 1 ..= params.n() {
|
||||||
let mut these_commitments = vec![];
|
let mut these_commitments = vec![];
|
||||||
for c in 0 .. usize::from(params.t()) {
|
for c in 0 .. usize::from(params.t()) {
|
||||||
|
@ -100,54 +106,29 @@ fn verify_r1<R: RngCore + CryptoRng, C: Curve>(
|
||||||
).map_err(|_| FrostError::InvalidCommitment(l.try_into().unwrap()))?
|
).map_err(|_| FrostError::InvalidCommitment(l.try_into().unwrap()))?
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
commitments.insert(l, these_commitments);
|
|
||||||
|
|
||||||
// Don't bother validating our own proof of knowledge
|
// Don't bother validating our own proof of knowledge
|
||||||
if l == params.i() {
|
if l != params.i() {
|
||||||
continue;
|
// Step 5: Validate each proof of knowledge
|
||||||
|
// This is solely the prep step for the latter batch verification
|
||||||
|
signatures.push((
|
||||||
|
l,
|
||||||
|
these_commitments[0],
|
||||||
|
challenge::<C>(l, context, R_bytes(l), Am(l)),
|
||||||
|
SchnorrSignature::<C> { R: R(l)?, s: s(l)? }
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 5: Validate each proof of knowledge (prep)
|
commitments.insert(l, these_commitments);
|
||||||
let mut u = C::F::one();
|
|
||||||
if !first {
|
|
||||||
u = C::F::random(&mut *rng);
|
|
||||||
}
|
|
||||||
|
|
||||||
// uR
|
|
||||||
scalars.push(u);
|
|
||||||
points.push(R(l)?);
|
|
||||||
|
|
||||||
// -usG
|
|
||||||
scalars.push(-s(l)? * u);
|
|
||||||
points.push(C::generator());
|
|
||||||
|
|
||||||
// ucA
|
|
||||||
let c = challenge::<C>(l, context, R_bytes(l), Am(l));
|
|
||||||
scalars.push(if first { first = false; c } else { c * u});
|
|
||||||
points.push(commitments[&l][0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 5: Implementation
|
schnorr::batch_verify(rng, &signatures).map_err(
|
||||||
// Uses batch verification to optimize the success case dramatically
|
|l| if l == 0 {
|
||||||
// On failure, the cost is now this + blame, yet that should happen infrequently
|
FrostError::InternalError("batch validation is broken".to_string())
|
||||||
// s = r + ca
|
} else {
|
||||||
// sG == R + cA
|
FrostError::InvalidProofOfKnowledge(l)
|
||||||
// R + cA - sG == 0
|
|
||||||
if C::multiexp_vartime(&scalars, &points) != C::G::identity() {
|
|
||||||
for l in 1 ..= params.n() {
|
|
||||||
if l == params.i() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (C::generator_table() * s(l)?) != (
|
|
||||||
R(l)? + (commitments[&l][0] * challenge::<C>(l, context, R_bytes(l), Am(l)))
|
|
||||||
) {
|
|
||||||
Err(FrostError::InvalidProofOfKnowledge(l))?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
)?;
|
||||||
Err(FrostError::InternalError("batch validation is broken".to_string()))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(commitments)
|
Ok(commitments)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ use group::{Group, GroupOps, ScalarMul};
|
||||||
|
|
||||||
pub use multiexp::multiexp_vartime;
|
pub use multiexp::multiexp_vartime;
|
||||||
|
|
||||||
|
mod schnorr;
|
||||||
|
|
||||||
pub mod key_gen;
|
pub mod key_gen;
|
||||||
pub mod algorithm;
|
pub mod algorithm;
|
||||||
pub mod sign;
|
pub mod sign;
|
||||||
|
|
82
crypto/frost/src/schnorr.rs
Normal file
82
crypto/frost/src/schnorr.rs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
|
use ff::Field;
|
||||||
|
use group::Group;
|
||||||
|
|
||||||
|
use crate::Curve;
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub struct SchnorrSignature<C: Curve> {
|
||||||
|
pub R: C::G,
|
||||||
|
pub s: C::F,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Curve> SchnorrSignature<C> {
|
||||||
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
|
let mut res = Vec::with_capacity(C::G_len() + C::F_len());
|
||||||
|
res.extend(C::G_to_bytes(&self.R));
|
||||||
|
res.extend(C::F_to_bytes(&self.s));
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn sign<C: Curve>(
|
||||||
|
private_key: C::F,
|
||||||
|
nonce: C::F,
|
||||||
|
challenge: C::F
|
||||||
|
) -> SchnorrSignature<C> {
|
||||||
|
SchnorrSignature {
|
||||||
|
R: C::generator_table() * nonce,
|
||||||
|
s: nonce + (private_key * challenge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn verify<C: Curve>(
|
||||||
|
public_key: C::G,
|
||||||
|
challenge: C::F,
|
||||||
|
signature: &SchnorrSignature<C>
|
||||||
|
) -> bool {
|
||||||
|
(C::generator_table() * signature.s) == (signature.R + (public_key * challenge))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn batch_verify<C: Curve, R: RngCore + CryptoRng>(
|
||||||
|
rng: &mut R,
|
||||||
|
triplets: &[(u16, C::G, C::F, SchnorrSignature<C>)]
|
||||||
|
) -> Result<(), u16> {
|
||||||
|
let mut first = true;
|
||||||
|
let mut scalars = Vec::with_capacity(triplets.len() * 3);
|
||||||
|
let mut points = Vec::with_capacity(triplets.len() * 3);
|
||||||
|
for triple in triplets {
|
||||||
|
let mut u = C::F::one();
|
||||||
|
if !first {
|
||||||
|
u = C::F::random(&mut *rng);
|
||||||
|
}
|
||||||
|
|
||||||
|
// uR
|
||||||
|
scalars.push(u);
|
||||||
|
points.push(triple.3.R);
|
||||||
|
|
||||||
|
// -usG
|
||||||
|
scalars.push(-triple.3.s * u);
|
||||||
|
points.push(C::generator());
|
||||||
|
|
||||||
|
// ucA
|
||||||
|
scalars.push(if first { first = false; triple.2 } else { triple.2 * u});
|
||||||
|
points.push(triple.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// s = r + ca
|
||||||
|
// sG == R + cA
|
||||||
|
// R + cA - sG == 0
|
||||||
|
if C::multiexp_vartime(&scalars, &points) == C::G::identity() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
for triple in triplets {
|
||||||
|
if !verify::<C>(triple.1, triple.2, &triple.3) {
|
||||||
|
Err(triple.0)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(0)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue