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:
Luke Parker 2022-05-03 07:20:24 -04:00
parent 87f38cafe4
commit bf257b3a1f
No known key found for this signature in database
GPG key ID: F9F1386DB1E119B6
19 changed files with 282 additions and 129 deletions

View file

@ -1,6 +1,7 @@
[workspace]
members = [
"crypto/transcript",
"crypto/frost",
"crypto/dalek-ff-group",
"coins/monero",

View file

@ -11,13 +11,14 @@ lazy_static = "1"
thiserror = "1"
rand_core = "0.6"
rand_chacha = { version = "0.3", optional = true }
tiny-keccak = { version = "2.0", features = ["keccak"] }
blake2 = "0.10"
curve25519-dalek = { version = "3.2", features = ["std", "simd_backend"] }
transcript = { path = "../../crypto/transcript" }
ff = { version = "0.11", optional = true }
group = { version = "0.11", optional = true }
dalek-ff-group = { path = "../../crypto/dalek-ff-group", optional = true }
@ -33,7 +34,7 @@ monero-epee-bin-serde = "1.0"
reqwest = { version = "0.11", features = ["json"] }
[features]
multisig = ["ff", "group", "dalek-ff-group", "frost", "rand_chacha"]
multisig = ["ff", "group", "dalek-ff-group", "frost"]
[dev-dependencies]
rand = "0.8"

View file

@ -1,5 +1,5 @@
use rand_core::{RngCore, CryptoRng};
use thiserror::Error;
use rand_core::{RngCore, CryptoRng};
use curve25519_dalek::{
constants::ED25519_BASEPOINT_TABLE,
@ -40,8 +40,8 @@ pub enum Error {
pub struct Input {
// Ring, the index we're signing for, and the actual commitment behind it
pub ring: Vec<[EdwardsPoint; 2]>,
pub i: usize,
pub commitment: Commitment,
pub i: u8,
pub commitment: Commitment
}
impl Input {
@ -49,7 +49,7 @@ impl Input {
ring: Vec<[EdwardsPoint; 2]>,
i: u8,
commitment: Commitment
) -> Result<Input, Error> {
) -> Result<Input, Error> {
let n = ring.len();
if n > u8::MAX.into() {
Err(Error::InternalError("max ring size in this library is u8 max".to_string()))?;
@ -57,29 +57,14 @@ impl Input {
if i >= (n as u8) {
Err(Error::InvalidRingMember(i, n as u8))?;
}
let i: usize = i.into();
// Validate the commitment matches
if ring[i][1] != commitment.calculate() {
if ring[usize::from(i)][1] != commitment.calculate() {
Err(Error::InvalidCommitment)?;
}
Ok(Input { ring, i, commitment })
}
#[cfg(feature = "multisig")]
pub fn context(&self) -> Vec<u8> {
// Ring index
let mut context = u8::try_from(self.i).unwrap().to_le_bytes().to_vec();
// Ring
for pair in &self.ring {
// Doesn't include key offsets as CLSAG doesn't care and won't be affected by it
context.extend(&pair[0].compress().to_bytes());
context.extend(&pair[1].compress().to_bytes());
}
// Doesn't include commitment as the above ring + index includes the commitment
context
}
}
#[allow(non_snake_case)]
@ -233,7 +218,7 @@ pub fn sign<R: RngCore + CryptoRng>(
&inputs[i].1,
&inputs[i].2,
mask,
&nonce * &ED25519_BASEPOINT_TABLE, nonce * hash_to_point(&inputs[i].1.ring[inputs[i].1.i][0])
&nonce * &ED25519_BASEPOINT_TABLE, nonce * hash_to_point(&inputs[i].1.ring[usize::from(inputs[i].1.i)][0])
);
clsag.s[inputs[i].1.i as usize] = Key {
key: (nonce - (c * ((mu_C * z) + (mu_P * inputs[i].0)))).to_bytes()

View file

@ -1,10 +1,7 @@
use core::fmt::Debug;
use std::{rc::Rc, cell::RefCell};
use rand_core::{RngCore, CryptoRng, SeedableRng};
use rand_chacha::ChaCha12Rng;
use blake2::{Digest, Blake2b512};
use rand_core::{RngCore, CryptoRng};
use curve25519_dalek::{
constants::ED25519_BASEPOINT_TABLE,
@ -13,19 +10,43 @@ use curve25519_dalek::{
edwards::EdwardsPoint
};
use group::Group;
use dalek_ff_group as dfg;
use frost::{Curve, FrostError, algorithm::Algorithm, MultisigView};
use monero::util::ringct::{Key, Clsag};
use group::Group;
use dalek_ff_group as dfg;
use transcript::Transcript as TranscriptTrait;
use frost::{Curve, FrostError, algorithm::Algorithm, MultisigView};
use crate::{
Transcript,
hash_to_point,
frost::{MultisigError, Ed25519, DLEqProof},
key_image,
clsag::{Input, sign_core, verify}
};
impl Input {
pub fn transcript<T: TranscriptTrait>(&self, transcript: &mut T) {
// Ring index
transcript.append_message(b"ring_index", &[self.i]);
// Ring
let mut ring = vec![];
for pair in &self.ring {
// Doesn't include global output indexes as CLSAG doesn't care and won't be affected by it
// They're just a mutable reference to this data
ring.extend(&pair[0].compress().to_bytes());
ring.extend(&pair[1].compress().to_bytes());
}
transcript.append_message(b"ring", &ring);
// Doesn't include the commitment's parts as the above ring + index includes the commitment
// The only potential malleability would be if the G/H relationship is known breaking the
// discrete log problem, which breaks everything already
}
}
#[allow(non_snake_case)]
#[derive(Clone, Debug)]
struct ClsagSignInterim {
@ -39,15 +60,14 @@ struct ClsagSignInterim {
#[allow(non_snake_case)]
#[derive(Clone, Debug)]
pub struct Multisig {
entropy: Vec<u8>,
commitments_H: Vec<u8>,
image: EdwardsPoint,
AH: (dfg::EdwardsPoint, dfg::EdwardsPoint),
input: Input,
image: EdwardsPoint,
msg: Rc<RefCell<[u8; 32]>>,
mask_sum: Rc<RefCell<Scalar>>,
mask: Rc<RefCell<Scalar>>,
interim: Option<ClsagSignInterim>
}
@ -56,19 +76,18 @@ impl Multisig {
pub fn new(
input: Input,
msg: Rc<RefCell<[u8; 32]>>,
mask_sum: Rc<RefCell<Scalar>>,
mask: Rc<RefCell<Scalar>>,
) -> Result<Multisig, MultisigError> {
Ok(
Multisig {
entropy: vec![],
commitments_H: vec![],
image: EdwardsPoint::identity(),
AH: (dfg::EdwardsPoint::identity(), dfg::EdwardsPoint::identity()),
input,
image: EdwardsPoint::identity(),
msg,
mask_sum,
mask,
interim: None
}
@ -81,16 +100,9 @@ impl Multisig {
}
impl Algorithm<Ed25519> for Multisig {
type Transcript = Transcript;
type Signature = (Clsag, EdwardsPoint);
// We arguably don't have to commit to the nonces at all thanks to xG and yG being committed to,
// both of those being proven to have the same scalar as xH and yH, yet it doesn't hurt
// As for the image, that should be committed to by the msg, yet putting it here as well ensures
// the security bounds of this
fn addendum_commit_len() -> usize {
3 * 32
}
fn preprocess_addendum<R: RngCore + CryptoRng>(
rng: &mut R,
view: &MultisigView<Ed25519>,
@ -125,15 +137,14 @@ impl Algorithm<Ed25519> for Multisig {
Err(FrostError::InvalidCommitmentQuantity(l, 9, serialized.len() / 32))?;
}
// Use everyone's commitments to derive a random source all signers can agree upon
// Cannot be manipulated to effect and all signers must, and will, know this
self.entropy.extend(&l.to_le_bytes());
self.entropy.extend(&serialized[0 .. Multisig::addendum_commit_len()]);
let (share, serialized) = key_image::verify_share(view, l, serialized).map_err(|_| FrostError::InvalidShare(l))?;
self.image += share;
let alt = &hash_to_point(&self.input.ring[self.input.i][0]);
let alt = &hash_to_point(&self.input.ring[usize::from(self.input.i)][0]);
// Uses the same format FROST does for the expected commitments (nonce * G where this is nonce * H)
self.commitments_H.extend(&u64::try_from(l).unwrap().to_le_bytes());
self.commitments_H.extend(&serialized[0 .. 64]);
#[allow(non_snake_case)]
let H = (
@ -159,12 +170,20 @@ impl Algorithm<Ed25519> for Multisig {
Ok(())
}
fn context(&self) -> Vec<u8> {
let mut context = Vec::with_capacity(32 + 32 + 1 + (2 * 11 * 32));
context.extend(&*self.msg.borrow());
context.extend(&self.mask_sum.borrow().to_bytes());
context.extend(&self.input.context());
context
fn transcript(&self) -> Option<Self::Transcript> {
let mut transcript = Self::Transcript::new(b"CLSAG");
self.input.transcript(&mut transcript);
// Given the fact there's only ever one possible value for this, this may technically not need
// to be committed to. If signing a TX, it's be double committed to thanks to the message
// It doesn't hurt to have though and ensures security boundaries are well formed
transcript.append_message(b"image", &self.image.compress().to_bytes());
// Given this is guaranteed to match commitments, which FROST commits to, this also technically
// doesn't need to be committed to if a canonical serialization is guaranteed
// It, again, doesn't hurt to include and ensures security boundaries are well formed
transcript.append_message(b"commitments_H", &self.commitments_H);
transcript.append_message(b"message", &*self.msg.borrow());
transcript.append_message(b"mask", &self.mask.borrow().to_bytes());
Some(transcript)
}
fn sign_share(
@ -178,13 +197,12 @@ impl Algorithm<Ed25519> for Multisig {
// Apply the binding factor to the H variant of the nonce
self.AH.0 += self.AH.1 * b;
// Use the context with the entropy to prevent passive observers of messages from being able to
// break privacy, as the context includes the index of the output in the ring, which can only
// be known if you have the view key and know which of the wallet's TXOs is being spent
let mut seed = b"CLSAG_randomness".to_vec();
seed.extend(&self.context());
seed.extend(&self.entropy);
let mut rng = ChaCha12Rng::from_seed(Blake2b512::digest(seed)[0 .. 32].try_into().unwrap());
// Use the transcript to get a seeded random number generator
// The transcript contains private data, preventing passive adversaries from recreating this
// process even if they have access to commitments (specifically, the ring index being signed
// for, along with the mask which should not only require knowing the shared keys yet also the
// input commitment mask)
let mut rng = self.transcript().unwrap().seeded_rng(b"decoy_responses", None);
#[allow(non_snake_case)]
let (clsag, c, mu_C, z, mu_P, C_out) = sign_core(
@ -192,7 +210,7 @@ impl Algorithm<Ed25519> for Multisig {
&self.msg.borrow(),
&self.input,
&self.image,
*self.mask_sum.borrow(),
*self.mask.borrow(),
nonce_sum.0,
self.AH.0.0
);
@ -212,7 +230,7 @@ impl Algorithm<Ed25519> for Multisig {
let interim = self.interim.as_ref().unwrap();
let mut clsag = interim.clsag.clone();
clsag.s[self.input.i] = Key { key: (sum.0 - interim.s).to_bytes() };
clsag.s[usize::from(self.input.i)] = Key { key: (sum.0 - interim.s).to_bytes() };
if verify(&clsag, &self.msg.borrow(), self.image, &self.input.ring, interim.C_out) {
return Some((clsag, interim.C_out));
}

View file

@ -1,7 +1,7 @@
use core::convert::TryInto;
use rand_core::{RngCore, CryptoRng};
use thiserror::Error;
use rand_core::{RngCore, CryptoRng};
use blake2::{digest::Update, Digest, Blake2b512};
@ -12,7 +12,6 @@ use curve25519_dalek::{
edwards::EdwardsPoint as DPoint
};
use dalek_ff_group::EdwardsPoint;
use ff::PrimeField;
use group::Group;
@ -56,7 +55,7 @@ impl Curve for Ed25519 {
}
fn multiexp_vartime(scalars: &[Self::F], points: &[Self::G]) -> Self::G {
EdwardsPoint(DPoint::vartime_multiscalar_mul(scalars, points))
dfg::EdwardsPoint(DPoint::vartime_multiscalar_mul(scalars, points))
}
fn hash_msg(msg: &[u8]) -> Vec<u8> {

View file

@ -1,6 +1,7 @@
use rand_core::{RngCore, CryptoRng};
use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY};
use frost::MultisigView;
use crate::{hash_to_point, frost::{MultisigError, Ed25519, DLEqProof}};

View file

@ -12,6 +12,8 @@ use curve25519_dalek::{
use monero::util::key::H;
use transcript::DigestTranscript;
#[cfg(feature = "multisig")]
pub mod frost;
@ -48,6 +50,8 @@ lazy_static! {
static ref H_TABLE: EdwardsBasepointTable = EdwardsBasepointTable::create(&H.point.decompress().unwrap());
}
pub(crate) type Transcript = DigestTranscript::<blake2::Blake2b512>;
#[allow(non_snake_case)]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Commitment {

View file

@ -1,8 +1,6 @@
use rand_core::{RngCore, CryptoRng, SeedableRng};
use rand_chacha::ChaCha12Rng;
use thiserror::Error;
use blake2::{Digest, Blake2b512};
use rand_core::{RngCore, CryptoRng};
use curve25519_dalek::{
constants::ED25519_BASEPOINT_TABLE,
@ -26,10 +24,13 @@ use monero::{
}
};
use transcript::Transcript as TranscriptTrait;
#[cfg(feature = "multisig")]
use frost::FrostError;
use crate::{
Transcript,
Commitment,
random_scalar,
hash, hash_to_scalar,
@ -264,6 +265,9 @@ impl SignableTransaction {
)
}
// This could be refactored so prep, a multisig-required variable, is used only by multisig
// Not shimmed by the single signer API as well
// This would enable moving Transcript as a whole to the multisig feature
fn prepare_outputs<'a, R: RngCore + CryptoRng>(
&self,
prep: &mut Preparation<'a, R>
@ -289,6 +293,7 @@ impl SignableTransaction {
match prep {
Preparation::Leader(ref mut rng) => {
// The Leader generates the entropy for the one time keys and the bulletproof
// This prevents de-anonymization via recalculation of the randomness which is deterministic
rng.fill_bytes(&mut entropy);
},
Preparation::Follower(e, b) => {
@ -297,16 +302,14 @@ impl SignableTransaction {
}
}
let mut seed = b"StealthAddress_randomness".to_vec();
// Leader selected entropy to prevent de-anonymization via recalculation of randomness
seed.extend(&entropy);
let mut transcript = Transcript::new(b"StealthAddress");
// This output can only be spent once. Therefore, it forces all one time keys used here to be
// unique, even if the leader reuses entropy. While another transaction could use a different
// input ordering to swap which 0 is, that input set can't contain this input without being a
// double spend
seed.extend(&self.inputs[0].tx.0);
seed.extend(&self.inputs[0].o.to_le_bytes());
let mut rng = ChaCha12Rng::from_seed(Blake2b512::digest(seed)[0 .. 32].try_into().unwrap());
transcript.append_message(b"hash", &self.inputs[0].tx.0);
transcript.append_message(b"index", &u64::try_from(self.inputs[0].o).unwrap().to_le_bytes());
let mut rng = transcript.seeded_rng(b"tx_keys", Some(entropy));
let mut outputs = Vec::with_capacity(payments.len());
let mut commitments = Vec::with_capacity(payments.len());

View file

@ -1,12 +1,9 @@
use std::{rc::Rc, cell::RefCell};
use rand_core::{RngCore, CryptoRng};
use rand_chacha::ChaCha12Rng;
use curve25519_dalek::{scalar::Scalar, edwards::{EdwardsPoint, CompressedEdwardsY}};
use frost::{FrostError, MultisigKeys, MultisigParams, sign::{State, StateMachine, AlgorithmMachine}};
use monero::{
Hash, VarInt,
consensus::deserialize,
@ -14,7 +11,11 @@ use monero::{
blockdata::transaction::{KeyImage, TxIn, Transaction}
};
use transcript::Transcript as TranscriptTrait;
use frost::{FrostError, MultisigKeys, MultisigParams, sign::{State, StateMachine, AlgorithmMachine}};
use crate::{
Transcript,
frost::Ed25519,
key_image,
clsag,
@ -150,7 +151,7 @@ impl StateMachine for TransactionMachine {
let prep = prep.as_ref().unwrap();
// Handle the prep with a seeded RNG type to make rustc happy
let (_, mask_sum, tx_inner) = self.signable.prepare_outputs::<ChaCha12Rng>(
let (_, mask_sum, tx_inner) = self.signable.prepare_outputs::<<Transcript as TranscriptTrait>::SeededRng>(
&mut Preparation::Follower(
prep[clsag_lens .. (clsag_lens + 32)].try_into().map_err(|_| FrostError::InvalidCommitment(l))?,
deserialize(&prep[(clsag_lens + 32) .. prep.len()]).map_err(|_| FrostError::InvalidCommitment(l))?

View file

@ -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"

View file

@ -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(

View file

@ -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;

View file

@ -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(&params.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(&params.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);

View file

@ -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},

View file

@ -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,

View 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
View 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.

View 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)
}
}

View 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)
}
}