Luke Parker
2022-07-12 01:28:01 -04:00
parent cf28967754
commit 5eb61f3a87
11 changed files with 198 additions and 123 deletions

View File

@@ -13,14 +13,18 @@ pub trait Algorithm<C: Curve>: Clone {
/// 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 basepoints to create commitments
/// against per-nonce. These are not committed to by FROST on the underlying transcript
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,
params: &FrostView<C>,
nonces: &[C::F; 2],
) -> Vec<u8>;
/// Proccess the addendum for the specified participant. Guaranteed to be ordered
@@ -28,7 +32,6 @@ pub trait Algorithm<C: Curve>: Clone {
&mut self,
params: &FrostView<C>,
l: u16,
commitments: &[C::G; 2],
serialized: &[u8],
) -> Result<(), FrostError>;
@@ -39,15 +42,14 @@ pub trait Algorithm<C: Curve>: Clone {
fn sign_share(
&mut self,
params: &FrostView<C>,
nonce_sum: C::G,
binding: C::F,
nonce: C::F,
nonce_sums: &[Vec<C::G>],
nonces: &[C::F],
msg: &[u8],
) -> C::F;
/// Verify a signature
#[must_use]
fn verify(&self, group_key: C::G, nonce: C::G, sum: C::F) -> Option<Self::Signature>;
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
@@ -55,7 +57,7 @@ pub trait Algorithm<C: Curve>: Clone {
fn verify_share(
&self,
verification_share: C::G,
nonce: C::G,
nonces: &[Vec<C::G>],
share: C::F,
) -> bool;
}
@@ -66,6 +68,10 @@ pub struct IetfTranscript(Vec<u8>);
impl Transcript for IetfTranscript {
type Challenge = Vec<u8>;
fn new(_: &'static [u8]) -> IetfTranscript {
unimplemented!("IetfTranscript should not be used with multiple nonce protocols");
}
fn domain_separate(&mut self, _: &[u8]) {}
fn append_message(&mut self, _: &'static [u8], message: &[u8]) {
@@ -115,11 +121,14 @@ impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
&mut self.transcript
}
fn nonces(&self) -> Vec<Vec<C::G>> {
vec![vec![C::GENERATOR]]
}
fn preprocess_addendum<R: RngCore + CryptoRng>(
&mut self,
_: &mut R,
_: &FrostView<C>,
_: &[C::F; 2],
) -> Vec<u8> {
vec![]
}
@@ -128,7 +137,6 @@ impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
&mut self,
_: &FrostView<C>,
_: u16,
_: &[C::G; 2],
_: &[u8],
) -> Result<(), FrostError> {
Ok(())
@@ -137,19 +145,18 @@ impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
fn sign_share(
&mut self,
params: &FrostView<C>,
nonce_sum: C::G,
_: C::F,
nonce: C::F,
nonce_sums: &[Vec<C::G>],
nonces: &[C::F],
msg: &[u8],
) -> C::F {
let c = H::hram(&nonce_sum, &params.group_key(), msg);
let c = H::hram(&nonce_sums[0][0], &params.group_key(), msg);
self.c = Some(c);
schnorr::sign::<C>(params.secret_share(), nonce, c).s
schnorr::sign::<C>(params.secret_share(), nonces[0], c).s
}
#[must_use]
fn verify(&self, group_key: C::G, nonce: C::G, sum: C::F) -> Option<Self::Signature> {
let sig = SchnorrSignature { R: nonce, s: sum };
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 };
if schnorr::verify::<C>(group_key, self.c.unwrap(), &sig) {
Some(sig)
} else {
@@ -161,13 +168,13 @@ impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
fn verify_share(
&self,
verification_share: C::G,
nonce: C::G,
nonces: &[Vec<C::G>],
share: C::F,
) -> bool {
schnorr::verify::<C>(
verification_share,
self.c.unwrap(),
&SchnorrSignature { R: nonce, s: share}
&SchnorrSignature { R: nonces[0][0], s: share}
)
}
}

View File

@@ -3,12 +3,14 @@ use std::{sync::Arc, collections::HashMap};
use rand_core::{RngCore, CryptoRng};
use group::{ff::{Field, PrimeField}, GroupEncoding};
use group::{ff::{Field, PrimeField}, Group, GroupEncoding};
use transcript::Transcript;
use dleq::{Generators, DLEqProof};
use crate::{
curve::{Curve, G_len, F_from_slice, G_from_slice},
curve::{Curve, F_len, G_len, F_from_slice, G_from_slice},
FrostError,
FrostParams, FrostKeys, FrostView,
algorithm::Algorithm,
@@ -69,8 +71,12 @@ impl<C: Curve, A: Algorithm<C>> Params<C, A> {
}
}
fn nonce_transcript<T: Transcript>() -> T {
T::new(b"FROST_nonce_dleq")
}
pub(crate) struct PreprocessPackage<C: Curve> {
pub(crate) nonces: [C::F; 2],
pub(crate) nonces: Vec<[C::F; 2]>,
pub(crate) serialized: Vec<u8>,
}
@@ -80,30 +86,53 @@ fn preprocess<R: RngCore + CryptoRng, C: Curve, A: Algorithm<C>>(
rng: &mut R,
params: &mut Params<C, A>,
) -> PreprocessPackage<C> {
let nonces = [
C::random_nonce(params.view().secret_share(), &mut *rng),
C::random_nonce(params.view().secret_share(), &mut *rng)
];
let commitments = [C::GENERATOR * nonces[0], C::GENERATOR * nonces[1]];
let mut serialized = commitments[0].to_bytes().as_ref().to_vec();
serialized.extend(commitments[1].to_bytes().as_ref());
let mut serialized = Vec::with_capacity(2 * G_len::<C>());
let nonces = params.algorithm.nonces().iter().cloned().map(
|mut generators| {
let nonces = [
C::random_nonce(params.view().secret_share(), &mut *rng),
C::random_nonce(params.view().secret_share(), &mut *rng)
];
serialized.extend(
&params.algorithm.preprocess_addendum(
rng,
&params.view,
&nonces
)
);
let commit = |generator: C::G| {
let commitments = [generator * nonces[0], generator * nonces[1]];
[commitments[0].to_bytes().as_ref(), commitments[1].to_bytes().as_ref()].concat().to_vec()
};
let first = generators.remove(0);
serialized.extend(commit(first));
// Iterate over the rest
for generator in generators.iter() {
serialized.extend(commit(*generator));
// Provide a DLEq to verify these commitments are for the same nonce
// TODO: Provide a single DLEq. See https://github.com/serai-dex/serai/issues/34
for nonce in nonces {
DLEqProof::prove(
&mut *rng,
// Uses an independent transcript as each signer must do this now, yet we validate them
// sequentially by the global order. Avoids needing to clone the transcript around
&mut nonce_transcript::<A::Transcript>(),
Generators::new(first, *generator),
nonce
).serialize(&mut serialized).unwrap();
}
}
nonces
}
).collect::<Vec<_>>();
serialized.extend(&params.algorithm.preprocess_addendum(rng, &params.view));
PreprocessPackage { nonces, serialized }
}
#[allow(non_snake_case)]
struct Package<C: Curve> {
B: HashMap<u16, [C::G; 2]>,
B: HashMap<u16, Vec<Vec<[C::G; 2]>>>,
binding: C::F,
R: C::G,
Rs: Vec<Vec<C::G>>,
share: Vec<u8>
}
@@ -137,27 +166,59 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
let mut B = HashMap::<u16, _>::with_capacity(params.view.included.len());
// Get the binding factor
let nonces = params.algorithm.nonces();
let mut addendums = HashMap::new();
let binding = {
let transcript = params.algorithm.transcript();
// Parse the commitments
for l in &params.view.included {
transcript.append_message(b"participant", &l.to_be_bytes());
let serialized = commitments.remove(l).unwrap();
let commitments = commitments.remove(l).unwrap();
let mut read_commitment = |c, label| {
let commitment = &commitments[c .. (c + G_len::<C>())];
let commitment = &serialized[c .. (c + G_len::<C>())];
transcript.append_message(label, commitment);
G_from_slice::<C::G>(commitment).map_err(|_| FrostError::InvalidCommitment(*l))
};
// While this doesn't note which nonce/basepoint this is for, those are expected to be
// static. Beyond that, they're committed to in the DLEq proof transcripts, ensuring
// consistency. While this is suboptimal, it maintains IETF compliance, and Algorithm is
// documented accordingly
#[allow(non_snake_case)]
let mut read_D_E = || Ok(
[read_commitment(0, b"commitment_D")?, read_commitment(G_len::<C>(), b"commitment_E")?]
);
let mut read_D_E = |c| Ok([
read_commitment(c, b"commitment_D")?,
read_commitment(c + G_len::<C>(), b"commitment_E")?
]);
B.insert(*l, read_D_E()?);
addendums.insert(*l, commitments[(G_len::<C>() * 2) ..].to_vec());
let mut c = 0;
let mut commitments = Vec::with_capacity(nonces.len());
for (n, nonce_generators) in nonces.clone().iter_mut().enumerate() {
commitments.push(Vec::with_capacity(nonce_generators.len()));
let first = nonce_generators.remove(0);
commitments[n].push(read_D_E(c)?);
c += 2 * G_len::<C>();
let mut c = 2 * G_len::<C>();
for generator in nonce_generators {
commitments[n].push(read_D_E(c)?);
c += 2 * G_len::<C>();
for de in 0 .. 2 {
DLEqProof::deserialize(
&mut std::io::Cursor::new(&serialized[c .. (c + (2 * F_len::<C>()))])
).map_err(|_| FrostError::InvalidCommitment(*l))?.verify(
&mut nonce_transcript::<A::Transcript>(),
Generators::new(first, *generator),
(commitments[n][0][de], commitments[n][commitments[n].len() - 1][de])
).map_err(|_| FrostError::InvalidCommitment(*l))?;
c += 2 * F_len::<C>();
}
}
addendums.insert(*l, serialized[c ..].to_vec());
}
B.insert(*l, commitments);
}
// Append the message to the transcript
@@ -169,22 +230,32 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
// Process the addendums
for l in &params.view.included {
params.algorithm.process_addendum(&params.view, *l, &B[l], &addendums[l])?;
params.algorithm.process_addendum(&params.view, *l, &addendums[l])?;
}
#[allow(non_snake_case)]
let R = {
B.values().map(|B| B[0]).sum::<C::G>() + (B.values().map(|B| B[1]).sum::<C::G>() * binding)
};
let mut Rs = Vec::with_capacity(nonces.len());
for n in 0 .. nonces.len() {
Rs.push(vec![C::G::identity(); nonces[n].len()]);
#[allow(non_snake_case)]
for g in 0 .. nonces[n].len() {
Rs[n][g] = {
B.values().map(|B| B[n][g][0]).sum::<C::G>() +
(B.values().map(|B| B[n][g][1]).sum::<C::G>() * binding)
};
}
}
let share = params.algorithm.sign_share(
&params.view,
R,
binding,
our_preprocess.nonces[0] + (our_preprocess.nonces[1] * binding),
&Rs,
&our_preprocess.nonces.iter().map(
|nonces| nonces[0] + (nonces[1] * binding)
).collect::<Vec<_>>(),
msg
).to_repr().as_ref().to_vec();
Ok((Package { B, binding, R, share: share.clone() }, share))
Ok((Package { B, binding, Rs, share: share.clone() }, share))
}
fn complete<C: Curve, A: Algorithm<C>>(
@@ -206,7 +277,7 @@ fn complete<C: Curve, A: Algorithm<C>>(
// Perform signature validation instead of individual share validation
// For the success route, which should be much more frequent, this should be faster
// It also acts as an integrity check of this library's signing function
let res = sign_params.algorithm.verify(sign_params.view.group_key, sign.R, sum);
let res = sign_params.algorithm.verify(sign_params.view.group_key, &sign.Rs, sum);
if let Some(res) = res {
return Ok(res);
}
@@ -216,7 +287,11 @@ fn complete<C: Curve, A: Algorithm<C>>(
for l in &sign_params.view.included {
if !sign_params.algorithm.verify_share(
sign_params.view.verification_share(*l),
sign.B[l][0] + (sign.B[l][1] * sign.binding),
&sign.B[l].iter().map(
|nonces| nonces.iter().map(
|commitments| commitments[0] + (commitments[1] * sign.binding)
).collect()
).collect::<Vec<_>>(),
responses[l]
) {
Err(FrostError::InvalidShare(*l))?;

View File

@@ -105,7 +105,7 @@ pub fn test_with_vectors<
serialized.extend((C::GENERATOR * nonces[1]).to_bytes().as_ref());
let (machine, serialized) = machine.unsafe_override_preprocess(
PreprocessPackage { nonces, serialized: serialized.clone() }
PreprocessPackage { nonces: vec![nonces], serialized: serialized.clone() }
);
commitments.insert(i, serialized);