mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Transcript crate with both a merlin backend and a basic label len value backend
Moves binding factor/seeded RNGs over to the transcripts.
This commit is contained in:
@@ -3,18 +3,19 @@ name = "frost"
|
||||
version = "0.1.0"
|
||||
description = "Implementation of FROST over ff/group"
|
||||
license = "MIT"
|
||||
authors = ["kayabaNerve (Luke Parker) <lukeparker5132@gmail.com>"]
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
digest = "0.10"
|
||||
thiserror = "1"
|
||||
|
||||
rand_core = "0.6"
|
||||
|
||||
ff = "0.11"
|
||||
group = "0.11"
|
||||
|
||||
thiserror = "1"
|
||||
blake2 = "0.10"
|
||||
transcript = { path = "../transcript" }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8"
|
||||
|
||||
@@ -4,16 +4,16 @@ use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use group::Group;
|
||||
|
||||
use transcript::{Transcript, DigestTranscript};
|
||||
|
||||
use crate::{Curve, FrostError, MultisigView};
|
||||
|
||||
/// Algorithm to use FROST with
|
||||
pub trait Algorithm<C: Curve>: Clone {
|
||||
type Transcript: Transcript + Clone + Debug;
|
||||
/// The resulting type of the signatures this algorithm will produce
|
||||
type Signature: Clone + Debug;
|
||||
|
||||
/// The amount of bytes from each participant's addendum to commit to
|
||||
fn addendum_commit_len() -> usize;
|
||||
|
||||
/// Generate an addendum to FROST"s preprocessing stage
|
||||
fn preprocess_addendum<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
@@ -30,8 +30,8 @@ pub trait Algorithm<C: Curve>: Clone {
|
||||
serialized: &[u8],
|
||||
) -> Result<(), FrostError>;
|
||||
|
||||
/// Context for this algorithm to be hashed into b, and therefore committed to
|
||||
fn context(&self) -> Vec<u8>;
|
||||
/// 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
|
||||
@@ -90,12 +90,11 @@ 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 Signature = SchnorrSignature<C>;
|
||||
|
||||
fn addendum_commit_len() -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn preprocess_addendum<R: RngCore + CryptoRng>(
|
||||
_: &mut R,
|
||||
_: &MultisigView<C>,
|
||||
@@ -114,8 +113,8 @@ impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn context(&self) -> Vec<u8> {
|
||||
vec![]
|
||||
fn transcript(&self) -> Option<DigestTranscript::<blake2::Blake2b512>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn sign_share(
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use core::{ops::Mul, fmt::Debug};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use ff::{Field, PrimeField};
|
||||
use group::{Group, GroupOps, ScalarMul};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod key_gen;
|
||||
pub mod algorithm;
|
||||
pub mod sign;
|
||||
|
||||
@@ -6,6 +6,8 @@ use rand_core::{RngCore, CryptoRng};
|
||||
use ff::{Field, PrimeField};
|
||||
use group::Group;
|
||||
|
||||
use transcript::Transcript;
|
||||
|
||||
use crate::{Curve, FrostError, MultisigParams, MultisigKeys, MultisigView, algorithm::Algorithm};
|
||||
|
||||
/// Calculate the lagrange coefficient
|
||||
@@ -142,17 +144,13 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
|
||||
Err(FrostError::NonEmptyParticipantZero)?;
|
||||
}
|
||||
|
||||
let commitments_len = C::G_len() * 2;
|
||||
// Allow algorithms to commit to more data than just the included nonces
|
||||
// Not IETF draft compliant yet it doesn't prevent a compliant Schnorr algorithm from being used
|
||||
// with this library, which does ship one
|
||||
let commit_len = commitments_len + A::addendum_commit_len();
|
||||
#[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 mut b: Vec<u8> = Vec::with_capacity((multisig_params.t * 2 * C::G_len()) + 32);
|
||||
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 {
|
||||
@@ -163,7 +161,7 @@ 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 .. commit_len]);
|
||||
b.extend(&our_preprocess.serialized[0 .. (C::G_len() * 2)]);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -193,7 +191,7 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
|
||||
.map_err(|_| FrostError::InvalidCommitment(l))?;
|
||||
B.push(Some([D, E]));
|
||||
b.extend(&u16::try_from(l).unwrap().to_le_bytes());
|
||||
b.extend(&commitments[0 .. commit_len]);
|
||||
b.extend(&commitments[0 .. commitments_len]);
|
||||
}
|
||||
|
||||
// Process the commitments and addendums
|
||||
@@ -216,26 +214,27 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
|
||||
// Finish the binding factor
|
||||
b.extend(&C::hash_msg(&msg));
|
||||
|
||||
// If the following are used with certain lengths, it is possible to craft distinct
|
||||
// commitments/messages/contexts with the same binding factor. While we can't length prefix the
|
||||
// commitments, unfortunately, we can tag and length prefix the following
|
||||
// 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
|
||||
let mut transcript = params.algorithm.transcript();
|
||||
|
||||
// If the offset functionality provided by this library is in use, include it in the binding
|
||||
// factor. Not compliant with the IETF spec which doesn't have a concept of offsets
|
||||
// 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() {
|
||||
b.extend(b"offset");
|
||||
b.extend(u64::try_from(C::F_len()).unwrap().to_le_bytes());
|
||||
b.extend(&C::F_to_le_bytes(¶ms.keys.offset.unwrap()));
|
||||
let mut offset_transcript = transcript.unwrap_or(A::Transcript::new(b"FROST_offset"));
|
||||
offset_transcript.append_message(b"offset", &C::F_to_le_bytes(¶ms.keys.offset.unwrap()));
|
||||
transcript = Some(offset_transcript);
|
||||
}
|
||||
|
||||
// Also include any context the algorithm may want to specify. Again not compliant with the IETF
|
||||
// spec which doesn't considered there may be signatures other than Schnorr being generated with
|
||||
// FROST
|
||||
let context = params.algorithm.context();
|
||||
if context.len() != 0 {
|
||||
b.extend(b"context");
|
||||
b.extend(u64::try_from(context.len()).unwrap().to_le_bytes());
|
||||
b.extend(&context);
|
||||
// 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);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use core::convert::TryInto;
|
||||
|
||||
use digest::Digest;
|
||||
use ff::PrimeField;
|
||||
use group::GroupEncoding;
|
||||
|
||||
use sha2::{Sha256, Sha512};
|
||||
use sha2::{Digest, Sha256, Sha512};
|
||||
|
||||
use k256::{
|
||||
elliptic_curve::{generic_array::GenericArray, bigint::{ArrayEncoding, U512}, ops::Reduce},
|
||||
|
||||
@@ -2,8 +2,7 @@ use std::rc::Rc;
|
||||
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
|
||||
use digest::Digest;
|
||||
use sha2::Sha256;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use frost::{
|
||||
Curve,
|
||||
|
||||
18
crypto/transcript/Cargo.toml
Normal file
18
crypto/transcript/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "transcript"
|
||||
version = "0.1.0"
|
||||
description = "A simple transcript definition"
|
||||
license = "MIT"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
rand_core = "0.6"
|
||||
rand_chacha = "0.3"
|
||||
|
||||
digest = "0.10"
|
||||
|
||||
merlin = { version = "3", optional = true }
|
||||
|
||||
[features]
|
||||
merlin = ["dep:merlin"]
|
||||
21
crypto/transcript/LICENSE
Normal file
21
crypto/transcript/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Luke Parker
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
62
crypto/transcript/src/lib.rs
Normal file
62
crypto/transcript/src/lib.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use core::{marker::PhantomData, fmt::Debug};
|
||||
|
||||
#[cfg(features = "merlin")]
|
||||
mod merlin;
|
||||
#[cfg(features = "merlin")]
|
||||
pub use merlin::MerlinTranscript;
|
||||
|
||||
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
||||
use rand_chacha::ChaCha12Rng;
|
||||
|
||||
use digest::Digest;
|
||||
|
||||
pub trait Transcript {
|
||||
type SeededRng: RngCore + CryptoRng;
|
||||
|
||||
fn new(label: &'static [u8]) -> Self;
|
||||
fn append_message(&mut self, label: &'static [u8], message: &[u8]);
|
||||
fn challenge(&mut self, label: &'static [u8], len: usize) -> Vec<u8>;
|
||||
fn seeded_rng(&self, label: &'static [u8], additional_entropy: Option<[u8; 32]>) -> Self::SeededRng;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DigestTranscript<D: Digest>(Vec<u8>, PhantomData<D>);
|
||||
impl<D: Digest> Transcript for DigestTranscript<D> {
|
||||
// Uses ChaCha12 as even ChaCha8 should be secure yet 12 is considered a sane middleground
|
||||
type SeededRng = ChaCha12Rng;
|
||||
|
||||
fn new(label: &'static [u8]) -> Self {
|
||||
DigestTranscript(label.to_vec(), PhantomData)
|
||||
}
|
||||
|
||||
fn append_message(&mut self, label: &'static [u8], message: &[u8]) {
|
||||
self.0.extend(label);
|
||||
// Assumes messages don't exceed 16 exabytes
|
||||
self.0.extend(u64::try_from(message.len()).unwrap().to_le_bytes());
|
||||
self.0.extend(message);
|
||||
}
|
||||
|
||||
fn challenge(&mut self, label: &'static [u8], len: usize) -> Vec<u8> {
|
||||
self.0.extend(label);
|
||||
|
||||
let mut challenge = Vec::with_capacity(len);
|
||||
challenge.extend(&D::new().chain_update(&self.0).chain_update(&0u64.to_le_bytes()).finalize());
|
||||
for i in 0 .. (len / challenge.len()) {
|
||||
challenge.extend(&D::new().chain_update(&self.0).chain_update(&u64::try_from(i).unwrap().to_le_bytes()).finalize());
|
||||
}
|
||||
challenge.truncate(len);
|
||||
challenge
|
||||
}
|
||||
|
||||
fn seeded_rng(&self, label: &'static [u8], additional_entropy: Option<[u8; 32]>) -> Self::SeededRng {
|
||||
let mut transcript = DigestTranscript::<D>(self.0.clone(), PhantomData);
|
||||
if additional_entropy.is_some() {
|
||||
transcript.append_message(b"additional_entropy", &additional_entropy.unwrap());
|
||||
}
|
||||
transcript.0.extend(label);
|
||||
|
||||
let mut seed = [0; 32];
|
||||
seed.copy_from_slice(&D::digest(&transcript.0)[0 .. 32]);
|
||||
ChaCha12Rng::from_seed(seed)
|
||||
}
|
||||
}
|
||||
42
crypto/transcript/src/merlin.rs
Normal file
42
crypto/transcript/src/merlin.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use core::{marker::PhantomData, fmt::{Debug, Formatter}};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
||||
use rand_chacha::ChaCha12Rng;
|
||||
|
||||
use digest::Digest;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MerlinTranscript(merlin::Transcript);
|
||||
// Merlin doesn't implement Debug so provide a stub which won't panic
|
||||
impl Debug for MerlinTranscript {
|
||||
fn fmt(&self, _: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { Ok(()) }
|
||||
}
|
||||
|
||||
impl Transcript for MerlinTranscript {
|
||||
type SeededRng = ChaCha12Rng;
|
||||
|
||||
fn new(label: &'static [u8]) -> Self {
|
||||
MerlinTranscript(merlin::Transcript::new(label))
|
||||
}
|
||||
|
||||
fn append_message(&mut self, label: &'static [u8], message: &[u8]) {
|
||||
self.0.append_message(label, message);
|
||||
}
|
||||
|
||||
fn challenge(&mut self, label: &'static [u8], len: usize) -> Vec<u8> {
|
||||
let mut challenge = vec![];
|
||||
challenge.resize(len, 0);
|
||||
self.0.challenge_bytes(label, &mut challenge);
|
||||
challenge
|
||||
}
|
||||
|
||||
fn seeded_rng(&self, label: &'static [u8], additional_entropy: Option<[u8; 32]>) -> ChaCha12Rng {
|
||||
let mut transcript = self.0.clone();
|
||||
if additional_entropy.is_some() {
|
||||
transcript.append_message(b"additional_entropy", &additional_entropy.unwrap());
|
||||
}
|
||||
let mut seed = [0; 32];
|
||||
transcript.challenge_bytes(label, &mut seed);
|
||||
ChaCha12Rng::from_seed(seed)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user