Use a global transcript

This commit is contained in:
Luke Parker
2022-05-06 07:33:08 -04:00
parent cc9c2e0d40
commit 964cb357e6
12 changed files with 165 additions and 182 deletions

View File

@@ -4,7 +4,7 @@ use rand_core::{RngCore, CryptoRng};
use group::Group;
use transcript::{Transcript, DigestTranscript};
use transcript::Transcript;
use crate::{Curve, FrostError, MultisigView};
@@ -14,6 +14,8 @@ pub trait Algorithm<C: Curve>: Clone {
/// The resulting type of the signatures this algorithm will produce
type Signature: Clone + Debug;
fn transcript(&mut self) -> &mut Self::Transcript;
/// Generate an addendum to FROST"s preprocessing stage
fn preprocess_addendum<R: RngCore + CryptoRng>(
rng: &mut R,
@@ -30,9 +32,6 @@ pub trait Algorithm<C: Curve>: Clone {
serialized: &[u8],
) -> Result<(), FrostError>;
/// Transcript for this algorithm to be used to create the binding factor
fn transcript(&self) -> Option<Self::Transcript>;
/// 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
@@ -41,7 +40,7 @@ pub trait Algorithm<C: Curve>: Clone {
&mut self,
params: &MultisigView<C>,
nonce_sum: C::G,
b: C::F,
binding: C::F,
nonce: C::F,
msg: &[u8],
) -> C::F;
@@ -59,6 +58,26 @@ pub trait Algorithm<C: Curve>: Clone {
) -> bool;
}
// Transcript which will create an IETF compliant serialization for the binding factor
#[derive(Clone, Debug)]
pub struct IetfTranscript(Vec<u8>);
impl Transcript for IetfTranscript {
fn domain_separate(&mut self, _: &[u8]) {}
fn append_message(&mut self, _: &'static [u8], message: &[u8]) {
self.0.extend(message);
}
fn challenge(&mut self, _: &'static [u8]) -> Vec<u8> {
self.0.clone()
}
fn rng_seed(&mut self, _: &'static [u8], _: Option<[u8; 32]>) -> [u8; 32] {
unimplemented!()
}
}
pub trait Hram<C: Curve>: Clone {
/// HRAM function to generate a challenge
/// H2 from the IETF draft despite having a different argument set (not pre-formatted)
@@ -68,6 +87,7 @@ pub trait Hram<C: Curve>: Clone {
#[derive(Clone)]
pub struct Schnorr<C: Curve, H: Hram<C>> {
transcript: IetfTranscript,
c: Option<C::F>,
_hram: PhantomData<H>,
}
@@ -75,6 +95,7 @@ pub struct Schnorr<C: Curve, H: Hram<C>> {
impl<C: Curve, H: Hram<C>> Schnorr<C, H> {
pub fn new() -> Schnorr<C, H> {
Schnorr {
transcript: IetfTranscript(vec![]),
c: None,
_hram: PhantomData
}
@@ -90,11 +111,13 @@ pub struct SchnorrSignature<C: Curve> {
/// Implementation of Schnorr signatures for use with FROST
impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
// Specify a firm type which either won't matter as it won't be used or will be used (offset) and
// is accordingly solid
type Transcript = DigestTranscript::<blake2::Blake2b512>;
type Transcript = IetfTranscript;
type Signature = SchnorrSignature<C>;
fn transcript(&mut self) -> &mut Self::Transcript {
&mut self.transcript
}
fn preprocess_addendum<R: RngCore + CryptoRng>(
_: &mut R,
_: &MultisigView<C>,
@@ -113,10 +136,6 @@ impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
Ok(())
}
fn transcript(&self) -> Option<DigestTranscript::<blake2::Blake2b512>> {
None
}
fn sign_share(
&mut self,
params: &MultisigView<C>,

View File

@@ -93,14 +93,14 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug {
#[allow(non_snake_case)]
fn G_len() -> usize;
/// Field element from slice. Should be canonical
/// Field element from slice. Preferred to be canonical yet does not have to be
// Required due to the lack of standardized encoding functions provided by ff/group
// While they do technically exist, their usage of Self::Repr breaks all potential library usage
// without helper functions like this
#[allow(non_snake_case)]
fn F_from_le_slice(slice: &[u8]) -> Result<Self::F, CurveError>;
/// Group element from slice. Should be canonical
/// Group element from slice. Must require canonicity or risks differing binding factors
#[allow(non_snake_case)]
fn G_from_slice(slice: &[u8]) -> Result<Self::G, CurveError>;

View File

@@ -144,13 +144,21 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
Err(FrostError::NonEmptyParticipantZero)?;
}
// Domain separate FROST
{
let transcript = params.algorithm.transcript();
transcript.domain_separate(b"FROST");
if params.keys.offset.is_some() {
transcript.append_message(b"offset", &C::F_to_le_bytes(&params.keys.offset.unwrap()));
}
}
#[allow(non_snake_case)]
let mut B = Vec::with_capacity(multisig_params.n + 1);
B.push(None);
// Commitments + a presumed 32-byte hash of the message
let commitments_len = 2 * C::G_len();
let mut b: Vec<u8> = Vec::with_capacity((multisig_params.t * commitments_len) + 32);
// Parse the commitments and prepare the binding factor
for l in 1 ..= multisig_params.n {
@@ -160,8 +168,14 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
}
B.push(Some(our_preprocess.commitments));
b.extend(&u16::try_from(l).unwrap().to_le_bytes());
b.extend(&our_preprocess.serialized[0 .. (C::G_len() * 2)]);
{
let transcript = params.algorithm.transcript();
transcript.append_message(b"participant", &u16::try_from(l).unwrap().to_le_bytes());
transcript.append_message(
b"commitments",
&our_preprocess.serialized[0 .. (C::G_len() * 2)]
);
}
continue;
}
@@ -190,10 +204,20 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
let E = C::G_from_slice(&commitments[C::G_len() .. commitments_len])
.map_err(|_| FrostError::InvalidCommitment(l))?;
B.push(Some([D, E]));
b.extend(&u16::try_from(l).unwrap().to_le_bytes());
b.extend(&commitments[0 .. commitments_len]);
{
let transcript = params.algorithm.transcript();
transcript.append_message(b"participant", &u16::try_from(l).unwrap().to_le_bytes());
transcript.append_message(b"commitments", &commitments[0 .. commitments_len]);
}
}
// Add the message to the binding factor
let binding = {
let transcript = params.algorithm.transcript();
transcript.append_message(b"message", &C::hash_msg(&msg));
C::hash_to_F(&transcript.challenge(b"binding"))
};
// Process the commitments and addendums
let view = &params.view;
for l in &params.view.included {
@@ -211,45 +235,6 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
)?;
}
// Finish the binding factor
b.extend(&C::hash_msg(&msg));
// Let the algorithm provide a transcript of its variables
// While Merlin, which may or may not be the transcript used here, wants application level
// transcripts passed around to proof systems, this maintains a desired level of abstraction and
// works without issue
// Not to mention, mandating a global transcript would conflict with the IETF draft UNLESS an
// IetfTranscript was declared which ignores field names and solely does their values, with a
// fresh instantiation per sign round. That could likely be made to align without issue
// TODO: Consider Option<Transcript>?
let mut transcript = params.algorithm.transcript();
if params.keys.offset.is_some() && transcript.is_none() {
transcript = Some(A::Transcript::new(b"FROST Offset"));
}
if transcript.is_some() {
// https://github.com/rust-lang/rust/issues/91345
transcript = transcript.map(|mut t| { t.append_message(b"dom-sep", b"FROST"); t });
}
// If the offset functionality provided by this library is in use, include it in the transcript.
// Not compliant with the IETF spec which doesn't have a concept of offsets, nor does it use
// transcripts
if params.keys.offset.is_some() {
transcript = transcript.map(
|mut t| { t.append_message(b"offset", &C::F_to_le_bytes(&params.keys.offset.unwrap())); t }
);
}
// If a transcript was defined, move the commitments used for the binding factor into it
// Then, obtain its sum and use that as the binding factor
if transcript.is_some() {
let mut transcript = transcript.unwrap();
transcript.append_message(b"commitments", &b);
b = transcript.challenge(b"binding", 64);
}
let b = C::hash_to_F(&b);
#[allow(non_snake_case)]
let mut Ris = vec![];
#[allow(non_snake_case)]
@@ -257,7 +242,7 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
for i in 0 .. params.view.included.len() {
let commitments = B[params.view.included[i]].unwrap();
#[allow(non_snake_case)]
let this_R = commitments[0] + (commitments[1] * b);
let this_R = commitments[0] + (commitments[1] * binding);
Ris.push(this_R);
R += this_R;
}
@@ -266,8 +251,8 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
let share = params.algorithm.sign_share(
view,
R,
b,
our_preprocess.nonces[0] + (our_preprocess.nonces[1] * b),
binding,
our_preprocess.nonces[0] + (our_preprocess.nonces[1] * binding),
msg
);
Ok((Package { Ris, R, share }, C::F_to_le_bytes(&share)))