mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Use a global transcript
This commit is contained in:
@@ -12,6 +12,7 @@ thiserror = "1"
|
|||||||
|
|
||||||
rand_core = "0.6"
|
rand_core = "0.6"
|
||||||
rand_distr = "0.4"
|
rand_distr = "0.4"
|
||||||
|
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"
|
||||||
@@ -34,7 +35,7 @@ monero-epee-bin-serde = "1.0"
|
|||||||
reqwest = { version = "0.11", features = ["json"] }
|
reqwest = { version = "0.11", features = ["json"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
multisig = ["ff", "group", "transcript", "frost", "dalek-ff-group"]
|
multisig = ["ff", "group", "rand_chacha", "transcript", "frost", "dalek-ff-group"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
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};
|
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
||||||
|
use rand_chacha::ChaCha12Rng;
|
||||||
|
|
||||||
use curve25519_dalek::{
|
use curve25519_dalek::{
|
||||||
constants::ED25519_BASEPOINT_TABLE,
|
constants::ED25519_BASEPOINT_TABLE,
|
||||||
@@ -27,7 +28,7 @@ use crate::{
|
|||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
fn transcript<T: TranscriptTrait>(&self, transcript: &mut T) {
|
fn transcript<T: TranscriptTrait>(&self, transcript: &mut T) {
|
||||||
// Doesn't dom-sep as this is considered part of the larger input signing proof
|
// Doesn't domain separate as this is considered part of the larger CLSAG proof
|
||||||
|
|
||||||
// Ring index
|
// Ring index
|
||||||
transcript.append_message(b"ring_index", &[self.i]);
|
transcript.append_message(b"ring_index", &[self.i]);
|
||||||
@@ -61,12 +62,13 @@ struct ClsagSignInterim {
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Multisig {
|
pub struct Multisig {
|
||||||
commitments_H: Vec<u8>,
|
transcript: Transcript,
|
||||||
image: EdwardsPoint,
|
|
||||||
AH: (dfg::EdwardsPoint, dfg::EdwardsPoint),
|
|
||||||
|
|
||||||
input: Input,
|
input: Input,
|
||||||
|
|
||||||
|
image: EdwardsPoint,
|
||||||
|
commitments_H: Vec<u8>,
|
||||||
|
AH: (dfg::EdwardsPoint, dfg::EdwardsPoint),
|
||||||
|
|
||||||
msg: Rc<RefCell<[u8; 32]>>,
|
msg: Rc<RefCell<[u8; 32]>>,
|
||||||
mask: Rc<RefCell<Scalar>>,
|
mask: Rc<RefCell<Scalar>>,
|
||||||
|
|
||||||
@@ -75,18 +77,20 @@ pub struct Multisig {
|
|||||||
|
|
||||||
impl Multisig {
|
impl Multisig {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
transcript: Transcript,
|
||||||
input: Input,
|
input: Input,
|
||||||
msg: Rc<RefCell<[u8; 32]>>,
|
msg: Rc<RefCell<[u8; 32]>>,
|
||||||
mask: Rc<RefCell<Scalar>>,
|
mask: Rc<RefCell<Scalar>>,
|
||||||
) -> Result<Multisig, MultisigError> {
|
) -> Result<Multisig, MultisigError> {
|
||||||
Ok(
|
Ok(
|
||||||
Multisig {
|
Multisig {
|
||||||
commitments_H: vec![],
|
transcript,
|
||||||
image: EdwardsPoint::identity(),
|
|
||||||
AH: (dfg::EdwardsPoint::identity(), dfg::EdwardsPoint::identity()),
|
|
||||||
|
|
||||||
input,
|
input,
|
||||||
|
|
||||||
|
image: EdwardsPoint::identity(),
|
||||||
|
commitments_H: vec![],
|
||||||
|
AH: (dfg::EdwardsPoint::identity(), dfg::EdwardsPoint::identity()),
|
||||||
|
|
||||||
msg,
|
msg,
|
||||||
mask,
|
mask,
|
||||||
|
|
||||||
@@ -138,14 +142,28 @@ impl Algorithm<Ed25519> for Multisig {
|
|||||||
Err(FrostError::InvalidCommitmentQuantity(l, 9, serialized.len() / 32))?;
|
Err(FrostError::InvalidCommitmentQuantity(l, 9, serialized.len() / 32))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.commitments_H.len() == 0 {
|
||||||
|
self.transcript.domain_separate(b"CLSAG");
|
||||||
|
self.input.transcript(&mut self.transcript);
|
||||||
|
self.transcript.append_message(b"message", &*self.msg.borrow());
|
||||||
|
self.transcript.append_message(b"mask", &self.mask.borrow().to_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
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))?;
|
||||||
|
// 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'll be double committed to thanks to the message
|
||||||
|
// It doesn't hurt to have though and ensures security boundaries are well formed
|
||||||
|
self.transcript.append_message(b"image_share", &share.compress().to_bytes());
|
||||||
self.image += share;
|
self.image += share;
|
||||||
|
|
||||||
let alt = &hash_to_point(&self.input.ring[usize::from(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)
|
// 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());
|
// Given this is guaranteed to match commitments, which FROST commits to, this also technically
|
||||||
self.commitments_H.extend(&serialized[0 .. 64]);
|
// 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
|
||||||
|
self.transcript.append_message(b"participant", &u64::try_from(l).unwrap().to_le_bytes());
|
||||||
|
self.transcript.append_message(b"commitments_H", &serialized[0 .. 64]);
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
let H = (
|
let H = (
|
||||||
@@ -171,21 +189,8 @@ impl Algorithm<Ed25519> for Multisig {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transcript(&self) -> Option<Self::Transcript> {
|
fn transcript(&mut self) -> &mut Self::Transcript {
|
||||||
let mut transcript = Self::Transcript::new(b"Monero Multisig");
|
&mut self.transcript
|
||||||
self.input.transcript(&mut transcript);
|
|
||||||
transcript.append_message(b"dom-sep", b"CLSAG");
|
|
||||||
// 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(
|
fn sign_share(
|
||||||
@@ -203,8 +208,8 @@ impl Algorithm<Ed25519> for Multisig {
|
|||||||
// The transcript contains private data, preventing passive adversaries from recreating this
|
// 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
|
// 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
|
// for, along with the mask which should not only require knowing the shared keys yet also the
|
||||||
// input commitment mask)
|
// input commitment masks)
|
||||||
let mut rng = self.transcript().unwrap().seeded_rng(b"decoy_responses", None);
|
let mut rng = ChaCha12Rng::from_seed(self.transcript.rng_seed(b"decoy_responses", None));
|
||||||
|
|
||||||
#[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(
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use dalek_ff_group as dfg;
|
|||||||
|
|
||||||
use crate::random_scalar;
|
use crate::random_scalar;
|
||||||
|
|
||||||
pub(crate) type Transcript = DigestTranscript::<blake2::Blake2b512>;
|
pub type Transcript = DigestTranscript::<blake2::Blake2b512>;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum MultisigError {
|
pub enum MultisigError {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::{rc::Rc, cell::RefCell};
|
use std::{rc::Rc, cell::RefCell};
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
||||||
|
use rand_chacha::ChaCha12Rng;
|
||||||
|
|
||||||
use curve25519_dalek::{scalar::Scalar, edwards::{EdwardsPoint, CompressedEdwardsY}};
|
use curve25519_dalek::{scalar::Scalar, edwards::{EdwardsPoint, CompressedEdwardsY}};
|
||||||
|
|
||||||
@@ -24,6 +25,8 @@ use crate::{
|
|||||||
pub struct TransactionMachine {
|
pub struct TransactionMachine {
|
||||||
leader: bool,
|
leader: bool,
|
||||||
signable: SignableTransaction,
|
signable: SignableTransaction,
|
||||||
|
transcript: Transcript,
|
||||||
|
|
||||||
our_images: Vec<EdwardsPoint>,
|
our_images: Vec<EdwardsPoint>,
|
||||||
mask_sum: Rc<RefCell<Scalar>>,
|
mask_sum: Rc<RefCell<Scalar>>,
|
||||||
msg: Rc<RefCell<[u8; 32]>>,
|
msg: Rc<RefCell<[u8; 32]>>,
|
||||||
@@ -35,6 +38,7 @@ pub struct TransactionMachine {
|
|||||||
impl SignableTransaction {
|
impl SignableTransaction {
|
||||||
pub async fn multisig<R: RngCore + CryptoRng>(
|
pub async fn multisig<R: RngCore + CryptoRng>(
|
||||||
mut self,
|
mut self,
|
||||||
|
label: Vec<u8>,
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
rpc: &Rpc,
|
rpc: &Rpc,
|
||||||
keys: Rc<MultisigKeys<Ed25519>>,
|
keys: Rc<MultisigKeys<Ed25519>>,
|
||||||
@@ -51,25 +55,30 @@ impl SignableTransaction {
|
|||||||
|
|
||||||
// Create a RNG out of the input shared keys, which either requires the view key or being every
|
// Create a RNG out of the input shared keys, which either requires the view key or being every
|
||||||
// sender, and the payments (address and amount), which a passive adversary may be able to know
|
// sender, and the payments (address and amount), which a passive adversary may be able to know
|
||||||
// The use of input shared keys technically makes this one time given a competent wallet which
|
// depending on how these transactions are coordinated
|
||||||
// can withstand the burning attack (and has a static spend key? TODO visit bounds)
|
|
||||||
// The lack of dedicated entropy here is frustrating. We can probably provide entropy inclusion
|
// The lack of dedicated entropy here is frustrating. We can probably provide entropy inclusion
|
||||||
// if we move CLSAG ring to a Rc RefCell like msg and mask? TODO
|
// if we move CLSAG ring to a Rc RefCell like msg and mask? TODO
|
||||||
// For the above TODO, also consider FROST's TODO of a global transcript instance
|
let mut transcript = Transcript::new(label);
|
||||||
let mut transcript = Transcript::new(b"Input Mixins");
|
|
||||||
// Does dom-sep despite not being a proof because it's a unique section (and we have no dom-sep yet)
|
|
||||||
transcript.append_message("dom-sep", "inputs_outputs");
|
|
||||||
for input in &self.inputs {
|
for input in &self.inputs {
|
||||||
|
// These outputs can only be spent once. Therefore, it forces all RNGs derived from this
|
||||||
|
// transcript (such as the one used to create one time keys) to be unique
|
||||||
|
transcript.append_message(b"input_hash", &input.tx.0);
|
||||||
|
transcript.append_message(b"input_output_index", &u64::try_from(input.o).unwrap().to_le_bytes());
|
||||||
|
// Not including this, with a doxxed list of payments, would allow brute forcing the inputs
|
||||||
|
// to determine RNG seeds and therefore the true spends
|
||||||
transcript.append_message(b"input_shared_key", &input.key_offset.to_bytes());
|
transcript.append_message(b"input_shared_key", &input.key_offset.to_bytes());
|
||||||
}
|
}
|
||||||
for payment in &self.payments {
|
for payment in &self.payments {
|
||||||
transcript.append_message(b"payment_address", &payment.0.as_bytes());
|
transcript.append_message(b"payment_address", &payment.0.as_bytes());
|
||||||
transcript.append_message(b"payment_amount", &payment.1.to_le_bytes());
|
transcript.append_message(b"payment_amount", &payment.1.to_le_bytes());
|
||||||
}
|
}
|
||||||
|
// Not only is this an output, but this locks to the base keys to be complete with the above key offsets
|
||||||
|
transcript.append_message(b"change", &self.change.as_bytes());
|
||||||
|
|
||||||
// Select mixins
|
// Select mixins
|
||||||
let mixins = mixins::select(
|
let mixins = mixins::select(
|
||||||
&mut transcript.seeded_rng(b"mixins", None),
|
&mut ChaCha12Rng::from_seed(transcript.rng_seed(b"mixins", None)),
|
||||||
rpc,
|
rpc,
|
||||||
height,
|
height,
|
||||||
&self.inputs
|
&self.inputs
|
||||||
@@ -86,6 +95,7 @@ impl SignableTransaction {
|
|||||||
clsags.push(
|
clsags.push(
|
||||||
AlgorithmMachine::new(
|
AlgorithmMachine::new(
|
||||||
clsag::Multisig::new(
|
clsag::Multisig::new(
|
||||||
|
transcript.clone(),
|
||||||
clsag::Input::new(
|
clsag::Input::new(
|
||||||
mixins[i].2.clone(),
|
mixins[i].2.clone(),
|
||||||
mixins[i].1,
|
mixins[i].1,
|
||||||
@@ -112,6 +122,7 @@ impl SignableTransaction {
|
|||||||
Ok(TransactionMachine {
|
Ok(TransactionMachine {
|
||||||
leader: keys.params().i() == included[0],
|
leader: keys.params().i() == included[0],
|
||||||
signable: self,
|
signable: self,
|
||||||
|
transcript,
|
||||||
our_images,
|
our_images,
|
||||||
mask_sum,
|
mask_sum,
|
||||||
msg,
|
msg,
|
||||||
@@ -122,19 +133,6 @@ impl SignableTransaction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seeded RNG so multisig participants agree on one time keys to use, preventing burning attacks
|
|
||||||
fn outputs_rng(tx: &SignableTransaction, entropy: [u8; 32]) -> <Transcript as TranscriptTrait>::SeededRng {
|
|
||||||
let mut transcript = Transcript::new(b"Stealth Addresses");
|
|
||||||
// This output can only be spent once. Therefore, it forces all one time keys used here to be
|
|
||||||
// unique, even if the entropy is reused. 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
|
|
||||||
transcript.append_message(b"dom-sep", b"input_0");
|
|
||||||
transcript.append_message(b"hash", &tx.inputs[0].tx.0);
|
|
||||||
transcript.append_message(b"index", &u64::try_from(tx.inputs[0].o).unwrap().to_le_bytes());
|
|
||||||
transcript.seeded_rng(b"tx_keys", Some(entropy))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StateMachine for TransactionMachine {
|
impl StateMachine for TransactionMachine {
|
||||||
type Signature = Transaction;
|
type Signature = Transaction;
|
||||||
|
|
||||||
@@ -157,7 +155,7 @@ impl StateMachine for TransactionMachine {
|
|||||||
rng.fill_bytes(&mut entropy);
|
rng.fill_bytes(&mut entropy);
|
||||||
serialized.extend(&entropy);
|
serialized.extend(&entropy);
|
||||||
|
|
||||||
let mut rng = outputs_rng(&self.signable, entropy);
|
let mut rng = ChaCha12Rng::from_seed(self.transcript.rng_seed(b"tx_keys", Some(entropy)));
|
||||||
// Safe to unwrap thanks to the dummy prepare
|
// Safe to unwrap thanks to the dummy prepare
|
||||||
let (commitments, mask_sum) = self.signable.prepare_outputs(&mut rng).unwrap();
|
let (commitments, mask_sum) = self.signable.prepare_outputs(&mut rng).unwrap();
|
||||||
self.mask_sum.replace(mask_sum);
|
self.mask_sum.replace(mask_sum);
|
||||||
@@ -196,9 +194,11 @@ impl StateMachine for TransactionMachine {
|
|||||||
}
|
}
|
||||||
let prep = prep.as_ref().unwrap();
|
let prep = prep.as_ref().unwrap();
|
||||||
|
|
||||||
let mut rng = outputs_rng(
|
let mut rng = ChaCha12Rng::from_seed(
|
||||||
&self.signable,
|
self.transcript.rng_seed(
|
||||||
prep[clsag_lens .. (clsag_lens + 32)].try_into().map_err(|_| FrostError::InvalidShare(l))?
|
b"tx_keys",
|
||||||
|
Some(prep[clsag_lens .. (clsag_lens + 32)].try_into().map_err(|_| FrostError::InvalidShare(l))?)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
// Not invalid outputs due to doing a dummy prep as leader
|
// Not invalid outputs due to doing a dummy prep as leader
|
||||||
let (commitments, mask_sum) = self.signable.prepare_outputs(&mut rng).map_err(|_| FrostError::InvalidShare(l))?;
|
let (commitments, mask_sum) = self.signable.prepare_outputs(&mut rng).map_err(|_| FrostError::InvalidShare(l))?;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
|
|||||||
|
|
||||||
use monero_serai::{random_scalar, Commitment, key_image, clsag};
|
use monero_serai::{random_scalar, Commitment, key_image, clsag};
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
use monero_serai::frost::MultisigError;
|
use monero_serai::frost::{MultisigError, Transcript};
|
||||||
|
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
mod frost;
|
mod frost;
|
||||||
@@ -84,6 +84,7 @@ fn test_multisig() -> Result<(), MultisigError> {
|
|||||||
machines.push(
|
machines.push(
|
||||||
sign::AlgorithmMachine::new(
|
sign::AlgorithmMachine::new(
|
||||||
clsag::Multisig::new(
|
clsag::Multisig::new(
|
||||||
|
Transcript::new(b"Monero Serai CLSAG Test".to_vec()),
|
||||||
clsag::Input::new(ring.clone(), RING_INDEX, Commitment::new(randomness, AMOUNT)).unwrap(),
|
clsag::Input::new(ring.clone(), RING_INDEX, Commitment::new(randomness, AMOUNT)).unwrap(),
|
||||||
Rc::new(RefCell::new([1; 32])),
|
Rc::new(RefCell::new([1; 32])),
|
||||||
Rc::new(RefCell::new(Scalar::from(42u64)))
|
Rc::new(RefCell::new(Scalar::from(42u64)))
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ pub async fn send_multisig() {
|
|||||||
let t = keys[0].params().t();
|
let t = keys[0].params().t();
|
||||||
|
|
||||||
// Generate an address
|
// Generate an address
|
||||||
let view = Scalar::from_hash(Blake2b512::new().chain("Serai DEX")).0;
|
let view = Scalar::from_hash(Blake2b512::new().chain("Monero Serai Transaction Test")).0;
|
||||||
let spend = keys[0].group_key().0;
|
let spend = keys[0].group_key().0;
|
||||||
let addr = Address::standard(
|
let addr = Address::standard(
|
||||||
Network::Mainnet,
|
Network::Mainnet,
|
||||||
@@ -57,6 +57,7 @@ pub async fn send_multisig() {
|
|||||||
SignableTransaction::new(
|
SignableTransaction::new(
|
||||||
vec![output.clone()], vec![(addr, amount)], addr, fee_per_byte
|
vec![output.clone()], vec![(addr, amount)], addr, fee_per_byte
|
||||||
).unwrap().multisig(
|
).unwrap().multisig(
|
||||||
|
b"Monero Serai Test Transaction".to_vec(),
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
&rpc,
|
&rpc,
|
||||||
keys[i - 1].clone(),
|
keys[i - 1].clone(),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use rand_core::{RngCore, CryptoRng};
|
|||||||
|
|
||||||
use group::Group;
|
use group::Group;
|
||||||
|
|
||||||
use transcript::{Transcript, DigestTranscript};
|
use transcript::Transcript;
|
||||||
|
|
||||||
use crate::{Curve, FrostError, MultisigView};
|
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
|
/// The resulting type of the signatures this algorithm will produce
|
||||||
type Signature: Clone + Debug;
|
type Signature: Clone + Debug;
|
||||||
|
|
||||||
|
fn transcript(&mut self) -> &mut Self::Transcript;
|
||||||
|
|
||||||
/// 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,9 +32,6 @@ pub trait Algorithm<C: Curve>: Clone {
|
|||||||
serialized: &[u8],
|
serialized: &[u8],
|
||||||
) -> Result<(), FrostError>;
|
) -> 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
|
/// 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
|
||||||
/// key share
|
/// key share
|
||||||
@@ -41,7 +40,7 @@ pub trait Algorithm<C: Curve>: Clone {
|
|||||||
&mut self,
|
&mut self,
|
||||||
params: &MultisigView<C>,
|
params: &MultisigView<C>,
|
||||||
nonce_sum: C::G,
|
nonce_sum: C::G,
|
||||||
b: C::F,
|
binding: C::F,
|
||||||
nonce: C::F,
|
nonce: C::F,
|
||||||
msg: &[u8],
|
msg: &[u8],
|
||||||
) -> C::F;
|
) -> C::F;
|
||||||
@@ -59,6 +58,26 @@ pub trait Algorithm<C: Curve>: Clone {
|
|||||||
) -> bool;
|
) -> 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 {
|
pub trait Hram<C: Curve>: Clone {
|
||||||
/// HRAM function to generate a challenge
|
/// HRAM function to generate a challenge
|
||||||
/// H2 from the IETF draft despite having a different argument set (not pre-formatted)
|
/// 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)]
|
#[derive(Clone)]
|
||||||
pub struct Schnorr<C: Curve, H: Hram<C>> {
|
pub struct Schnorr<C: Curve, H: Hram<C>> {
|
||||||
|
transcript: IetfTranscript,
|
||||||
c: Option<C::F>,
|
c: Option<C::F>,
|
||||||
_hram: PhantomData<H>,
|
_hram: PhantomData<H>,
|
||||||
}
|
}
|
||||||
@@ -75,6 +95,7 @@ pub struct Schnorr<C: Curve, H: Hram<C>> {
|
|||||||
impl<C: Curve, H: Hram<C>> Schnorr<C, H> {
|
impl<C: Curve, H: Hram<C>> Schnorr<C, H> {
|
||||||
pub fn new() -> Schnorr<C, H> {
|
pub fn new() -> Schnorr<C, H> {
|
||||||
Schnorr {
|
Schnorr {
|
||||||
|
transcript: IetfTranscript(vec![]),
|
||||||
c: None,
|
c: None,
|
||||||
_hram: PhantomData
|
_hram: PhantomData
|
||||||
}
|
}
|
||||||
@@ -90,11 +111,13 @@ 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
|
type Transcript = IetfTranscript;
|
||||||
// is accordingly solid
|
|
||||||
type Transcript = DigestTranscript::<blake2::Blake2b512>;
|
|
||||||
type Signature = SchnorrSignature<C>;
|
type Signature = SchnorrSignature<C>;
|
||||||
|
|
||||||
|
fn transcript(&mut self) -> &mut Self::Transcript {
|
||||||
|
&mut self.transcript
|
||||||
|
}
|
||||||
|
|
||||||
fn preprocess_addendum<R: RngCore + CryptoRng>(
|
fn preprocess_addendum<R: RngCore + CryptoRng>(
|
||||||
_: &mut R,
|
_: &mut R,
|
||||||
_: &MultisigView<C>,
|
_: &MultisigView<C>,
|
||||||
@@ -113,10 +136,6 @@ impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transcript(&self) -> Option<DigestTranscript::<blake2::Blake2b512>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sign_share(
|
fn sign_share(
|
||||||
&mut self,
|
&mut self,
|
||||||
params: &MultisigView<C>,
|
params: &MultisigView<C>,
|
||||||
|
|||||||
@@ -93,14 +93,14 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug {
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn G_len() -> usize;
|
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
|
// 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
|
// While they do technically exist, their usage of Self::Repr breaks all potential library usage
|
||||||
// without helper functions like this
|
// without helper functions like this
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn F_from_le_slice(slice: &[u8]) -> Result<Self::F, CurveError>;
|
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)]
|
#[allow(non_snake_case)]
|
||||||
fn G_from_slice(slice: &[u8]) -> Result<Self::G, CurveError>;
|
fn G_from_slice(slice: &[u8]) -> Result<Self::G, CurveError>;
|
||||||
|
|
||||||
|
|||||||
@@ -144,13 +144,21 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
|
|||||||
Err(FrostError::NonEmptyParticipantZero)?;
|
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(¶ms.keys.offset.unwrap()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[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 commitments_len = 2 * C::G_len();
|
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 {
|
||||||
@@ -160,8 +168,14 @@ 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(&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;
|
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])
|
let E = C::G_from_slice(&commitments[C::G_len() .. commitments_len])
|
||||||
.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(&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
|
// Process the commitments and addendums
|
||||||
let view = ¶ms.view;
|
let view = ¶ms.view;
|
||||||
for l in ¶ms.view.included {
|
for l in ¶ms.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(¶ms.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)]
|
#[allow(non_snake_case)]
|
||||||
let mut Ris = vec![];
|
let mut Ris = vec![];
|
||||||
#[allow(non_snake_case)]
|
#[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() {
|
for i in 0 .. params.view.included.len() {
|
||||||
let commitments = B[params.view.included[i]].unwrap();
|
let commitments = B[params.view.included[i]].unwrap();
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
let this_R = commitments[0] + (commitments[1] * b);
|
let this_R = commitments[0] + (commitments[1] * binding);
|
||||||
Ris.push(this_R);
|
Ris.push(this_R);
|
||||||
R += this_R;
|
R += this_R;
|
||||||
}
|
}
|
||||||
@@ -266,8 +251,8 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
|
|||||||
let share = params.algorithm.sign_share(
|
let share = params.algorithm.sign_share(
|
||||||
view,
|
view,
|
||||||
R,
|
R,
|
||||||
b,
|
binding,
|
||||||
our_preprocess.nonces[0] + (our_preprocess.nonces[1] * b),
|
our_preprocess.nonces[0] + (our_preprocess.nonces[1] * binding),
|
||||||
msg
|
msg
|
||||||
);
|
);
|
||||||
Ok((Package { Ris, R, share }, C::F_to_le_bytes(&share)))
|
Ok((Package { Ris, R, share }, C::F_to_le_bytes(&share)))
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rand_core = "0.6"
|
|
||||||
rand_chacha = "0.3"
|
|
||||||
|
|
||||||
digest = "0.10"
|
digest = "0.10"
|
||||||
|
|
||||||
merlin = { version = "3", optional = true }
|
merlin = { version = "3", optional = true }
|
||||||
|
|||||||
@@ -5,34 +5,30 @@ mod merlin;
|
|||||||
#[cfg(features = "merlin")]
|
#[cfg(features = "merlin")]
|
||||||
pub use merlin::MerlinTranscript;
|
pub use merlin::MerlinTranscript;
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
|
||||||
use rand_chacha::ChaCha12Rng;
|
|
||||||
|
|
||||||
use digest::Digest;
|
use digest::Digest;
|
||||||
|
|
||||||
pub trait Transcript {
|
pub trait Transcript {
|
||||||
type SeededRng: RngCore + CryptoRng;
|
fn domain_separate(&mut self, label: &[u8]);
|
||||||
|
|
||||||
fn new(label: &'static [u8]) -> Self;
|
|
||||||
fn append_message(&mut self, label: &'static [u8], message: &[u8]);
|
fn append_message(&mut self, label: &'static [u8], message: &[u8]);
|
||||||
fn challenge(&mut self, label: &'static [u8], len: usize) -> Vec<u8>;
|
fn challenge(&mut self, label: &'static [u8]) -> Vec<u8>;
|
||||||
fn seeded_rng(
|
fn rng_seed(&mut self, label: &'static [u8], additional_entropy: Option<[u8; 32]>) -> [u8; 32];
|
||||||
&self,
|
|
||||||
label: &'static [u8],
|
|
||||||
additional_entropy: Option<[u8; 32]>
|
|
||||||
) -> Self::SeededRng;
|
|
||||||
|
|
||||||
// TODO: Consider a domain_separate function
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DigestTranscript<D: Digest>(Vec<u8>, PhantomData<D>);
|
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 {
|
impl<D: Digest> DigestTranscript<D> {
|
||||||
DigestTranscript(label.to_vec(), PhantomData)
|
pub fn new(label: Vec<u8>) -> Self {
|
||||||
|
DigestTranscript(label, PhantomData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: Digest> Transcript for DigestTranscript<D> {
|
||||||
|
// It may be beneficial for each domain to be a nested transcript which is itself length prefixed
|
||||||
|
// This would go further than Merlin though and require an accurate end_domain function which has
|
||||||
|
// frustrations not worth bothering with when this shouldn't actually be meaningful
|
||||||
|
fn domain_separate(&mut self, label: &[u8]) {
|
||||||
|
self.append_message(b"domain", label);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn append_message(&mut self, label: &'static [u8], message: &[u8]) {
|
fn append_message(&mut self, label: &'static [u8], message: &[u8]) {
|
||||||
@@ -42,40 +38,18 @@ impl<D: Digest> Transcript for DigestTranscript<D> {
|
|||||||
self.0.extend(message);
|
self.0.extend(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn challenge(&mut self, label: &'static [u8], len: usize) -> Vec<u8> {
|
fn challenge(&mut self, label: &'static [u8]) -> Vec<u8> {
|
||||||
self.0.extend(label);
|
self.0.extend(label);
|
||||||
|
D::new().chain_update(&self.0).finalize().to_vec()
|
||||||
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(
|
fn rng_seed(&mut self, label: &'static [u8], additional_entropy: Option<[u8; 32]>) -> [u8; 32] {
|
||||||
&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() {
|
if additional_entropy.is_some() {
|
||||||
transcript.append_message(b"additional_entropy", &additional_entropy.unwrap());
|
self.append_message(b"additional_entropy", &additional_entropy.unwrap());
|
||||||
}
|
}
|
||||||
transcript.0.extend(label);
|
|
||||||
|
|
||||||
let mut seed = [0; 32];
|
let mut seed = [0; 32];
|
||||||
seed.copy_from_slice(&D::digest(&transcript.0)[0 .. 32]);
|
seed.copy_from_slice(&self.challenge(label)[0 .. 32]);
|
||||||
ChaCha12Rng::from_seed(seed)
|
seed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,42 @@
|
|||||||
use core::{marker::PhantomData, fmt::{Debug, Formatter}};
|
use core::{marker::PhantomData, fmt::{Debug, Formatter}};
|
||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
|
||||||
use rand_chacha::ChaCha12Rng;
|
|
||||||
|
|
||||||
use digest::Digest;
|
use digest::Digest;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct MerlinTranscript(merlin::Transcript);
|
pub struct MerlinTranscript(pub merlin::Transcript);
|
||||||
// Merlin doesn't implement Debug so provide a stub which won't panic
|
// Merlin doesn't implement Debug so provide a stub which won't panic
|
||||||
impl Debug for MerlinTranscript {
|
impl Debug for MerlinTranscript {
|
||||||
fn fmt(&self, _: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { Ok(()) }
|
fn fmt(&self, _: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { Ok(()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transcript for MerlinTranscript {
|
impl Transcript for MerlinTranscript {
|
||||||
type SeededRng = ChaCha12Rng;
|
fn domain_separate(&mut self, label: &[u8]) {
|
||||||
|
self.append_message(b"dom-sep", label);
|
||||||
fn new(label: &'static [u8]) -> Self {
|
|
||||||
MerlinTranscript(merlin::Transcript::new(label))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn append_message(&mut self, label: &'static [u8], message: &[u8]) {
|
fn append_message(&mut self, label: &'static [u8], message: &[u8]) {
|
||||||
self.0.append_message(label, message);
|
self.0.append_message(label, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn challenge(&mut self, label: &'static [u8], len: usize) -> Vec<u8> {
|
fn challenge(&mut self, label: &'static [u8]) -> Vec<u8> {
|
||||||
let mut challenge = vec![];
|
let mut challenge = vec![];
|
||||||
challenge.resize(len, 0);
|
// Uses a challenge length of 64 bytes to support wide reduction on generated scalars
|
||||||
|
// From a security level standpoint, this should just be 32 bytes
|
||||||
|
// From a Merlin standpoint, this should be variable per call
|
||||||
|
// From a practical standpoint, this is a demo file not planned to be used and anything using
|
||||||
|
// this wrapper is fine without any settings it uses
|
||||||
|
challenge.resize(64, 0);
|
||||||
self.0.challenge_bytes(label, &mut challenge);
|
self.0.challenge_bytes(label, &mut challenge);
|
||||||
challenge
|
challenge
|
||||||
}
|
}
|
||||||
|
|
||||||
fn seeded_rng(&self, label: &'static [u8], additional_entropy: Option<[u8; 32]>) -> ChaCha12Rng {
|
fn rng_seed(&mut self, label: &'static [u8], additional_entropy: Option<[u8; 32]>) -> [u8; 32] {
|
||||||
let mut transcript = self.0.clone();
|
|
||||||
if additional_entropy.is_some() {
|
if additional_entropy.is_some() {
|
||||||
transcript.append_message(b"additional_entropy", &additional_entropy.unwrap());
|
transcript.append_message(b"additional_entropy", &additional_entropy.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut seed = [0; 32];
|
let mut seed = [0; 32];
|
||||||
transcript.challenge_bytes(label, &mut seed);
|
transcript.challenge_bytes(label, &mut seed);
|
||||||
ChaCha12Rng::from_seed(seed)
|
seed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user