mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-05 10:29:40 +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:
parent
87f38cafe4
commit
bf257b3a1f
19 changed files with 282 additions and 129 deletions
|
@ -1,6 +1,7 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
|
|
||||||
members = [
|
members = [
|
||||||
|
"crypto/transcript",
|
||||||
"crypto/frost",
|
"crypto/frost",
|
||||||
"crypto/dalek-ff-group",
|
"crypto/dalek-ff-group",
|
||||||
"coins/monero",
|
"coins/monero",
|
||||||
|
|
|
@ -11,13 +11,14 @@ lazy_static = "1"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
|
||||||
rand_core = "0.6"
|
rand_core = "0.6"
|
||||||
rand_chacha = { version = "0.3", optional = true }
|
|
||||||
|
|
||||||
tiny-keccak = { version = "2.0", features = ["keccak"] }
|
tiny-keccak = { version = "2.0", features = ["keccak"] }
|
||||||
blake2 = "0.10"
|
blake2 = "0.10"
|
||||||
|
|
||||||
curve25519-dalek = { version = "3.2", features = ["std", "simd_backend"] }
|
curve25519-dalek = { version = "3.2", features = ["std", "simd_backend"] }
|
||||||
|
|
||||||
|
transcript = { path = "../../crypto/transcript" }
|
||||||
|
|
||||||
ff = { version = "0.11", optional = true }
|
ff = { version = "0.11", optional = true }
|
||||||
group = { version = "0.11", optional = true }
|
group = { version = "0.11", optional = true }
|
||||||
dalek-ff-group = { path = "../../crypto/dalek-ff-group", 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"] }
|
reqwest = { version = "0.11", features = ["json"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
multisig = ["ff", "group", "dalek-ff-group", "frost", "rand_chacha"]
|
multisig = ["ff", "group", "dalek-ff-group", "frost"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use rand_core::{RngCore, CryptoRng};
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use curve25519_dalek::{
|
use curve25519_dalek::{
|
||||||
constants::ED25519_BASEPOINT_TABLE,
|
constants::ED25519_BASEPOINT_TABLE,
|
||||||
|
@ -40,8 +40,8 @@ pub enum Error {
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
// Ring, the index we're signing for, and the actual commitment behind it
|
// Ring, the index we're signing for, and the actual commitment behind it
|
||||||
pub ring: Vec<[EdwardsPoint; 2]>,
|
pub ring: Vec<[EdwardsPoint; 2]>,
|
||||||
pub i: usize,
|
pub i: u8,
|
||||||
pub commitment: Commitment,
|
pub commitment: Commitment
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
|
@ -49,7 +49,7 @@ impl Input {
|
||||||
ring: Vec<[EdwardsPoint; 2]>,
|
ring: Vec<[EdwardsPoint; 2]>,
|
||||||
i: u8,
|
i: u8,
|
||||||
commitment: Commitment
|
commitment: Commitment
|
||||||
) -> Result<Input, Error> {
|
) -> Result<Input, Error> {
|
||||||
let n = ring.len();
|
let n = ring.len();
|
||||||
if n > u8::MAX.into() {
|
if n > u8::MAX.into() {
|
||||||
Err(Error::InternalError("max ring size in this library is u8 max".to_string()))?;
|
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) {
|
if i >= (n as u8) {
|
||||||
Err(Error::InvalidRingMember(i, n as u8))?;
|
Err(Error::InvalidRingMember(i, n as u8))?;
|
||||||
}
|
}
|
||||||
let i: usize = i.into();
|
|
||||||
|
|
||||||
// Validate the commitment matches
|
// Validate the commitment matches
|
||||||
if ring[i][1] != commitment.calculate() {
|
if ring[usize::from(i)][1] != commitment.calculate() {
|
||||||
Err(Error::InvalidCommitment)?;
|
Err(Error::InvalidCommitment)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Input { ring, i, commitment })
|
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)]
|
#[allow(non_snake_case)]
|
||||||
|
@ -233,7 +218,7 @@ pub fn sign<R: RngCore + CryptoRng>(
|
||||||
&inputs[i].1,
|
&inputs[i].1,
|
||||||
&inputs[i].2,
|
&inputs[i].2,
|
||||||
mask,
|
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 {
|
clsag.s[inputs[i].1.i as usize] = Key {
|
||||||
key: (nonce - (c * ((mu_C * z) + (mu_P * inputs[i].0)))).to_bytes()
|
key: (nonce - (c * ((mu_C * z) + (mu_P * inputs[i].0)))).to_bytes()
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use std::{rc::Rc, cell::RefCell};
|
use std::{rc::Rc, cell::RefCell};
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
use rand_chacha::ChaCha12Rng;
|
|
||||||
|
|
||||||
use blake2::{Digest, Blake2b512};
|
|
||||||
|
|
||||||
use curve25519_dalek::{
|
use curve25519_dalek::{
|
||||||
constants::ED25519_BASEPOINT_TABLE,
|
constants::ED25519_BASEPOINT_TABLE,
|
||||||
|
@ -13,19 +10,43 @@ use curve25519_dalek::{
|
||||||
edwards::EdwardsPoint
|
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 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::{
|
use crate::{
|
||||||
|
Transcript,
|
||||||
hash_to_point,
|
hash_to_point,
|
||||||
frost::{MultisigError, Ed25519, DLEqProof},
|
frost::{MultisigError, Ed25519, DLEqProof},
|
||||||
key_image,
|
key_image,
|
||||||
clsag::{Input, sign_core, verify}
|
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)]
|
#[allow(non_snake_case)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct ClsagSignInterim {
|
struct ClsagSignInterim {
|
||||||
|
@ -39,15 +60,14 @@ struct ClsagSignInterim {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Multisig {
|
pub struct Multisig {
|
||||||
entropy: Vec<u8>,
|
commitments_H: Vec<u8>,
|
||||||
|
image: EdwardsPoint,
|
||||||
AH: (dfg::EdwardsPoint, dfg::EdwardsPoint),
|
AH: (dfg::EdwardsPoint, dfg::EdwardsPoint),
|
||||||
|
|
||||||
input: Input,
|
input: Input,
|
||||||
|
|
||||||
image: EdwardsPoint,
|
|
||||||
|
|
||||||
msg: Rc<RefCell<[u8; 32]>>,
|
msg: Rc<RefCell<[u8; 32]>>,
|
||||||
mask_sum: Rc<RefCell<Scalar>>,
|
mask: Rc<RefCell<Scalar>>,
|
||||||
|
|
||||||
interim: Option<ClsagSignInterim>
|
interim: Option<ClsagSignInterim>
|
||||||
}
|
}
|
||||||
|
@ -56,19 +76,18 @@ impl Multisig {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
input: Input,
|
input: Input,
|
||||||
msg: Rc<RefCell<[u8; 32]>>,
|
msg: Rc<RefCell<[u8; 32]>>,
|
||||||
mask_sum: Rc<RefCell<Scalar>>,
|
mask: Rc<RefCell<Scalar>>,
|
||||||
) -> Result<Multisig, MultisigError> {
|
) -> Result<Multisig, MultisigError> {
|
||||||
Ok(
|
Ok(
|
||||||
Multisig {
|
Multisig {
|
||||||
entropy: vec![],
|
commitments_H: vec![],
|
||||||
|
image: EdwardsPoint::identity(),
|
||||||
AH: (dfg::EdwardsPoint::identity(), dfg::EdwardsPoint::identity()),
|
AH: (dfg::EdwardsPoint::identity(), dfg::EdwardsPoint::identity()),
|
||||||
|
|
||||||
input,
|
input,
|
||||||
|
|
||||||
image: EdwardsPoint::identity(),
|
|
||||||
|
|
||||||
msg,
|
msg,
|
||||||
mask_sum,
|
mask,
|
||||||
|
|
||||||
interim: None
|
interim: None
|
||||||
}
|
}
|
||||||
|
@ -81,16 +100,9 @@ impl Multisig {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Algorithm<Ed25519> for Multisig {
|
impl Algorithm<Ed25519> for Multisig {
|
||||||
|
type Transcript = Transcript;
|
||||||
type Signature = (Clsag, EdwardsPoint);
|
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>(
|
fn preprocess_addendum<R: RngCore + CryptoRng>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
view: &MultisigView<Ed25519>,
|
view: &MultisigView<Ed25519>,
|
||||||
|
@ -125,15 +137,14 @@ impl Algorithm<Ed25519> for Multisig {
|
||||||
Err(FrostError::InvalidCommitmentQuantity(l, 9, serialized.len() / 32))?;
|
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))?;
|
let (share, serialized) = key_image::verify_share(view, l, serialized).map_err(|_| FrostError::InvalidShare(l))?;
|
||||||
self.image += share;
|
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)]
|
#[allow(non_snake_case)]
|
||||||
let H = (
|
let H = (
|
||||||
|
@ -159,12 +170,20 @@ impl Algorithm<Ed25519> for Multisig {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn context(&self) -> Vec<u8> {
|
fn transcript(&self) -> Option<Self::Transcript> {
|
||||||
let mut context = Vec::with_capacity(32 + 32 + 1 + (2 * 11 * 32));
|
let mut transcript = Self::Transcript::new(b"CLSAG");
|
||||||
context.extend(&*self.msg.borrow());
|
self.input.transcript(&mut transcript);
|
||||||
context.extend(&self.mask_sum.borrow().to_bytes());
|
// Given the fact there's only ever one possible value for this, this may technically not need
|
||||||
context.extend(&self.input.context());
|
// to be committed to. If signing a TX, it's be double committed to thanks to the message
|
||||||
context
|
// 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(
|
fn sign_share(
|
||||||
|
@ -178,13 +197,12 @@ impl Algorithm<Ed25519> for Multisig {
|
||||||
// Apply the binding factor to the H variant of the nonce
|
// Apply the binding factor to the H variant of the nonce
|
||||||
self.AH.0 += self.AH.1 * b;
|
self.AH.0 += self.AH.1 * b;
|
||||||
|
|
||||||
// Use the context with the entropy to prevent passive observers of messages from being able to
|
// Use the transcript to get a seeded random number generator
|
||||||
// break privacy, as the context includes the index of the output in the ring, which can only
|
// The transcript contains private data, preventing passive adversaries from recreating this
|
||||||
// be known if you have the view key and know which of the wallet's TXOs is being spent
|
// process even if they have access to commitments (specifically, the ring index being signed
|
||||||
let mut seed = b"CLSAG_randomness".to_vec();
|
// for, along with the mask which should not only require knowing the shared keys yet also the
|
||||||
seed.extend(&self.context());
|
// input commitment mask)
|
||||||
seed.extend(&self.entropy);
|
let mut rng = self.transcript().unwrap().seeded_rng(b"decoy_responses", None);
|
||||||
let mut rng = ChaCha12Rng::from_seed(Blake2b512::digest(seed)[0 .. 32].try_into().unwrap());
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
let (clsag, c, mu_C, z, mu_P, C_out) = sign_core(
|
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.msg.borrow(),
|
||||||
&self.input,
|
&self.input,
|
||||||
&self.image,
|
&self.image,
|
||||||
*self.mask_sum.borrow(),
|
*self.mask.borrow(),
|
||||||
nonce_sum.0,
|
nonce_sum.0,
|
||||||
self.AH.0.0
|
self.AH.0.0
|
||||||
);
|
);
|
||||||
|
@ -212,7 +230,7 @@ impl Algorithm<Ed25519> for Multisig {
|
||||||
let interim = self.interim.as_ref().unwrap();
|
let interim = self.interim.as_ref().unwrap();
|
||||||
|
|
||||||
let mut clsag = interim.clsag.clone();
|
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) {
|
if verify(&clsag, &self.msg.borrow(), self.image, &self.input.ring, interim.C_out) {
|
||||||
return Some((clsag, interim.C_out));
|
return Some((clsag, interim.C_out));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use core::convert::TryInto;
|
use core::convert::TryInto;
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use blake2::{digest::Update, Digest, Blake2b512};
|
use blake2::{digest::Update, Digest, Blake2b512};
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ use curve25519_dalek::{
|
||||||
edwards::EdwardsPoint as DPoint
|
edwards::EdwardsPoint as DPoint
|
||||||
};
|
};
|
||||||
|
|
||||||
use dalek_ff_group::EdwardsPoint;
|
|
||||||
|
|
||||||
use ff::PrimeField;
|
use ff::PrimeField;
|
||||||
use group::Group;
|
use group::Group;
|
||||||
|
@ -56,7 +55,7 @@ impl Curve for Ed25519 {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn multiexp_vartime(scalars: &[Self::F], points: &[Self::G]) -> Self::G {
|
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> {
|
fn hash_msg(msg: &[u8]) -> Vec<u8> {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY};
|
use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY};
|
||||||
|
|
||||||
use frost::MultisigView;
|
use frost::MultisigView;
|
||||||
|
|
||||||
use crate::{hash_to_point, frost::{MultisigError, Ed25519, DLEqProof}};
|
use crate::{hash_to_point, frost::{MultisigError, Ed25519, DLEqProof}};
|
||||||
|
|
|
@ -12,6 +12,8 @@ use curve25519_dalek::{
|
||||||
|
|
||||||
use monero::util::key::H;
|
use monero::util::key::H;
|
||||||
|
|
||||||
|
use transcript::DigestTranscript;
|
||||||
|
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
pub mod frost;
|
pub mod frost;
|
||||||
|
|
||||||
|
@ -48,6 +50,8 @@ lazy_static! {
|
||||||
static ref H_TABLE: EdwardsBasepointTable = EdwardsBasepointTable::create(&H.point.decompress().unwrap());
|
static ref H_TABLE: EdwardsBasepointTable = EdwardsBasepointTable::create(&H.point.decompress().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) type Transcript = DigestTranscript::<blake2::Blake2b512>;
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Commitment {
|
pub struct Commitment {
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
|
||||||
use rand_chacha::ChaCha12Rng;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use blake2::{Digest, Blake2b512};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use curve25519_dalek::{
|
use curve25519_dalek::{
|
||||||
constants::ED25519_BASEPOINT_TABLE,
|
constants::ED25519_BASEPOINT_TABLE,
|
||||||
|
@ -26,10 +24,13 @@ use monero::{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use transcript::Transcript as TranscriptTrait;
|
||||||
|
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
use frost::FrostError;
|
use frost::FrostError;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
Transcript,
|
||||||
Commitment,
|
Commitment,
|
||||||
random_scalar,
|
random_scalar,
|
||||||
hash, hash_to_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>(
|
fn prepare_outputs<'a, R: RngCore + CryptoRng>(
|
||||||
&self,
|
&self,
|
||||||
prep: &mut Preparation<'a, R>
|
prep: &mut Preparation<'a, R>
|
||||||
|
@ -289,6 +293,7 @@ impl SignableTransaction {
|
||||||
match prep {
|
match prep {
|
||||||
Preparation::Leader(ref mut rng) => {
|
Preparation::Leader(ref mut rng) => {
|
||||||
// The Leader generates the entropy for the one time keys and the bulletproof
|
// 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);
|
rng.fill_bytes(&mut entropy);
|
||||||
},
|
},
|
||||||
Preparation::Follower(e, b) => {
|
Preparation::Follower(e, b) => {
|
||||||
|
@ -297,16 +302,14 @@ impl SignableTransaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut seed = b"StealthAddress_randomness".to_vec();
|
let mut transcript = Transcript::new(b"StealthAddress");
|
||||||
// Leader selected entropy to prevent de-anonymization via recalculation of randomness
|
|
||||||
seed.extend(&entropy);
|
|
||||||
// This output can only be spent once. Therefore, it forces all one time keys used here to be
|
// 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
|
// 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
|
// input ordering to swap which 0 is, that input set can't contain this input without being a
|
||||||
// double spend
|
// double spend
|
||||||
seed.extend(&self.inputs[0].tx.0);
|
transcript.append_message(b"hash", &self.inputs[0].tx.0);
|
||||||
seed.extend(&self.inputs[0].o.to_le_bytes());
|
transcript.append_message(b"index", &u64::try_from(self.inputs[0].o).unwrap().to_le_bytes());
|
||||||
let mut rng = ChaCha12Rng::from_seed(Blake2b512::digest(seed)[0 .. 32].try_into().unwrap());
|
let mut rng = transcript.seeded_rng(b"tx_keys", Some(entropy));
|
||||||
|
|
||||||
let mut outputs = Vec::with_capacity(payments.len());
|
let mut outputs = Vec::with_capacity(payments.len());
|
||||||
let mut commitments = Vec::with_capacity(payments.len());
|
let mut commitments = Vec::with_capacity(payments.len());
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
use std::{rc::Rc, cell::RefCell};
|
use std::{rc::Rc, cell::RefCell};
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
use rand_chacha::ChaCha12Rng;
|
|
||||||
|
|
||||||
use curve25519_dalek::{scalar::Scalar, edwards::{EdwardsPoint, CompressedEdwardsY}};
|
use curve25519_dalek::{scalar::Scalar, edwards::{EdwardsPoint, CompressedEdwardsY}};
|
||||||
|
|
||||||
use frost::{FrostError, MultisigKeys, MultisigParams, sign::{State, StateMachine, AlgorithmMachine}};
|
|
||||||
|
|
||||||
use monero::{
|
use monero::{
|
||||||
Hash, VarInt,
|
Hash, VarInt,
|
||||||
consensus::deserialize,
|
consensus::deserialize,
|
||||||
|
@ -14,7 +11,11 @@ use monero::{
|
||||||
blockdata::transaction::{KeyImage, TxIn, Transaction}
|
blockdata::transaction::{KeyImage, TxIn, Transaction}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use transcript::Transcript as TranscriptTrait;
|
||||||
|
use frost::{FrostError, MultisigKeys, MultisigParams, sign::{State, StateMachine, AlgorithmMachine}};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
Transcript,
|
||||||
frost::Ed25519,
|
frost::Ed25519,
|
||||||
key_image,
|
key_image,
|
||||||
clsag,
|
clsag,
|
||||||
|
@ -150,7 +151,7 @@ impl StateMachine for TransactionMachine {
|
||||||
let prep = prep.as_ref().unwrap();
|
let prep = prep.as_ref().unwrap();
|
||||||
|
|
||||||
// Handle the prep with a seeded RNG type to make rustc happy
|
// 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(
|
&mut Preparation::Follower(
|
||||||
prep[clsag_lens .. (clsag_lens + 32)].try_into().map_err(|_| FrostError::InvalidCommitment(l))?,
|
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))?
|
deserialize(&prep[(clsag_lens + 32) .. prep.len()]).map_err(|_| FrostError::InvalidCommitment(l))?
|
||||||
|
|
|
@ -3,18 +3,19 @@ name = "frost"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Implementation of FROST over ff/group"
|
description = "Implementation of FROST over ff/group"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors = ["kayabaNerve (Luke Parker) <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
digest = "0.10"
|
thiserror = "1"
|
||||||
|
|
||||||
rand_core = "0.6"
|
rand_core = "0.6"
|
||||||
|
|
||||||
ff = "0.11"
|
ff = "0.11"
|
||||||
group = "0.11"
|
group = "0.11"
|
||||||
|
|
||||||
thiserror = "1"
|
blake2 = "0.10"
|
||||||
|
transcript = { path = "../transcript" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
|
|
@ -4,16 +4,16 @@ use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use group::Group;
|
use group::Group;
|
||||||
|
|
||||||
|
use transcript::{Transcript, DigestTranscript};
|
||||||
|
|
||||||
use crate::{Curve, FrostError, MultisigView};
|
use crate::{Curve, FrostError, MultisigView};
|
||||||
|
|
||||||
/// Algorithm to use FROST with
|
/// Algorithm to use FROST with
|
||||||
pub trait Algorithm<C: Curve>: Clone {
|
pub trait Algorithm<C: Curve>: Clone {
|
||||||
|
type Transcript: Transcript + Clone + Debug;
|
||||||
/// The resulting type of the signatures this algorithm will produce
|
/// The resulting type of the signatures this algorithm will produce
|
||||||
type Signature: Clone + Debug;
|
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
|
/// Generate an addendum to FROST"s preprocessing stage
|
||||||
fn preprocess_addendum<R: RngCore + CryptoRng>(
|
fn preprocess_addendum<R: RngCore + CryptoRng>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
|
@ -30,8 +30,8 @@ pub trait Algorithm<C: Curve>: Clone {
|
||||||
serialized: &[u8],
|
serialized: &[u8],
|
||||||
) -> Result<(), FrostError>;
|
) -> Result<(), FrostError>;
|
||||||
|
|
||||||
/// Context for this algorithm to be hashed into b, and therefore committed to
|
/// Transcript for this algorithm to be used to create the binding factor
|
||||||
fn context(&self) -> Vec<u8>;
|
fn transcript(&self) -> Option<Self::Transcript>;
|
||||||
|
|
||||||
/// Sign a share with the given secret/nonce
|
/// Sign a share with the given secret/nonce
|
||||||
/// The secret will already have been its lagrange coefficient applied so it is the necessary
|
/// 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
|
/// 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> {
|
||||||
|
// 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>;
|
type Signature = SchnorrSignature<C>;
|
||||||
|
|
||||||
fn addendum_commit_len() -> usize {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn preprocess_addendum<R: RngCore + CryptoRng>(
|
fn preprocess_addendum<R: RngCore + CryptoRng>(
|
||||||
_: &mut R,
|
_: &mut R,
|
||||||
_: &MultisigView<C>,
|
_: &MultisigView<C>,
|
||||||
|
@ -114,8 +113,8 @@ impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn context(&self) -> Vec<u8> {
|
fn transcript(&self) -> Option<DigestTranscript::<blake2::Blake2b512>> {
|
||||||
vec![]
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign_share(
|
fn sign_share(
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use core::{ops::Mul, fmt::Debug};
|
use core::{ops::Mul, fmt::Debug};
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
use ff::{Field, PrimeField};
|
use ff::{Field, PrimeField};
|
||||||
use group::{Group, GroupOps, ScalarMul};
|
use group::{Group, GroupOps, ScalarMul};
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
pub mod key_gen;
|
pub mod key_gen;
|
||||||
pub mod algorithm;
|
pub mod algorithm;
|
||||||
pub mod sign;
|
pub mod sign;
|
||||||
|
|
|
@ -6,6 +6,8 @@ use rand_core::{RngCore, CryptoRng};
|
||||||
use ff::{Field, PrimeField};
|
use ff::{Field, PrimeField};
|
||||||
use group::Group;
|
use group::Group;
|
||||||
|
|
||||||
|
use transcript::Transcript;
|
||||||
|
|
||||||
use crate::{Curve, FrostError, MultisigParams, MultisigKeys, MultisigView, algorithm::Algorithm};
|
use crate::{Curve, FrostError, MultisigParams, MultisigKeys, MultisigView, algorithm::Algorithm};
|
||||||
|
|
||||||
/// Calculate the lagrange coefficient
|
/// Calculate the lagrange coefficient
|
||||||
|
@ -142,17 +144,13 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
|
||||||
Err(FrostError::NonEmptyParticipantZero)?;
|
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)]
|
#[allow(non_snake_case)]
|
||||||
let mut B = Vec::with_capacity(multisig_params.n + 1);
|
let mut B = Vec::with_capacity(multisig_params.n + 1);
|
||||||
B.push(None);
|
B.push(None);
|
||||||
|
|
||||||
// Commitments + a presumed 32-byte hash of the message
|
// 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
|
// Parse the commitments and prepare the binding factor
|
||||||
for l in 1 ..= multisig_params.n {
|
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.push(Some(our_preprocess.commitments));
|
||||||
b.extend(&u16::try_from(l).unwrap().to_le_bytes());
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +191,7 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
|
||||||
.map_err(|_| FrostError::InvalidCommitment(l))?;
|
.map_err(|_| FrostError::InvalidCommitment(l))?;
|
||||||
B.push(Some([D, E]));
|
B.push(Some([D, E]));
|
||||||
b.extend(&u16::try_from(l).unwrap().to_le_bytes());
|
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
|
// Process the commitments and addendums
|
||||||
|
@ -216,26 +214,27 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
|
||||||
// Finish the binding factor
|
// Finish the binding factor
|
||||||
b.extend(&C::hash_msg(&msg));
|
b.extend(&C::hash_msg(&msg));
|
||||||
|
|
||||||
// If the following are used with certain lengths, it is possible to craft distinct
|
// Let the algorithm provide a transcript of its variables
|
||||||
// commitments/messages/contexts with the same binding factor. While we can't length prefix the
|
// While Merlin, which may or may not be the transcript used here, wants application level
|
||||||
// commitments, unfortunately, we can tag and length prefix the following
|
// 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
|
// If the offset functionality provided by this library is in use, include it in the transcript.
|
||||||
// factor. Not compliant with the IETF spec which doesn't have a concept of offsets
|
// 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() {
|
if params.keys.offset.is_some() {
|
||||||
b.extend(b"offset");
|
let mut offset_transcript = transcript.unwrap_or(A::Transcript::new(b"FROST_offset"));
|
||||||
b.extend(u64::try_from(C::F_len()).unwrap().to_le_bytes());
|
offset_transcript.append_message(b"offset", &C::F_to_le_bytes(¶ms.keys.offset.unwrap()));
|
||||||
b.extend(&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
|
// If a transcript was defined, move the commitments used for the binding factor into it
|
||||||
// spec which doesn't considered there may be signatures other than Schnorr being generated with
|
// Then, obtain its sum and use that as the binding factor
|
||||||
// FROST
|
if transcript.is_some() {
|
||||||
let context = params.algorithm.context();
|
let mut transcript = transcript.unwrap();
|
||||||
if context.len() != 0 {
|
transcript.append_message(b"commitments", &b);
|
||||||
b.extend(b"context");
|
b = transcript.challenge(b"binding", 64);
|
||||||
b.extend(u64::try_from(context.len()).unwrap().to_le_bytes());
|
|
||||||
b.extend(&context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let b = C::hash_to_F(&b);
|
let b = C::hash_to_F(&b);
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use core::convert::TryInto;
|
use core::convert::TryInto;
|
||||||
|
|
||||||
use digest::Digest;
|
|
||||||
use ff::PrimeField;
|
use ff::PrimeField;
|
||||||
use group::GroupEncoding;
|
use group::GroupEncoding;
|
||||||
|
|
||||||
use sha2::{Sha256, Sha512};
|
use sha2::{Digest, Sha256, Sha512};
|
||||||
|
|
||||||
use k256::{
|
use k256::{
|
||||||
elliptic_curve::{generic_array::GenericArray, bigint::{ArrayEncoding, U512}, ops::Reduce},
|
elliptic_curve::{generic_array::GenericArray, bigint::{ArrayEncoding, U512}, ops::Reduce},
|
||||||
|
|
|
@ -2,8 +2,7 @@ use std::rc::Rc;
|
||||||
|
|
||||||
use rand::{RngCore, rngs::OsRng};
|
use rand::{RngCore, rngs::OsRng};
|
||||||
|
|
||||||
use digest::Digest;
|
use sha2::{Digest, Sha256};
|
||||||
use sha2::Sha256;
|
|
||||||
|
|
||||||
use frost::{
|
use frost::{
|
||||||
Curve,
|
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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue