mirror of
https://github.com/saymrwulf/curve25519-dalek-source.git
synced 2026-09-04 20:24:10 +00:00
ed: vendor merlin dependency (#774)
`merlin` is currently a blocker for upgrading to `rand_core` v0.9 by way of the `transcript.build_rng().finalize()` function (which we only pass `ZeroRng` to). There is an open PR to update `rand_core` in `merlin` and I have pinged the relevant people to take a look, hopefully: zkcrypto/merlin#11 However, in the event we can't get `merlin` updated, this at least unblocks the `rand_core` upgrade, and is being opened as a contingency plan for that case. The PR has been implemented in a way that it should be easy to switch back to upstream `merlin` in the event they upgrade `rand_core`.
This commit is contained in:
parent
655992f3c2
commit
44bb8cb7c1
5 changed files with 636 additions and 4 deletions
|
|
@ -33,7 +33,7 @@ sha2 = { version = "0.10", default-features = false }
|
|||
subtle = { version = "2.3.0", default-features = false }
|
||||
|
||||
# optional features
|
||||
merlin = { version = "3", default-features = false, optional = true }
|
||||
keccak = { version = "0.1", default-features = false, optional = true }
|
||||
rand_core = { version = "0.6.4", default-features = false, optional = true }
|
||||
serde = { version = "1.0", default-features = false, optional = true }
|
||||
zeroize = { version = "1.5", default-features = false, optional = true }
|
||||
|
|
@ -50,7 +50,9 @@ criterion = { version = "0.5", features = ["html_reports"] }
|
|||
hex-literal = "0.4"
|
||||
rand = "0.8"
|
||||
rand_core = { version = "0.6.4", default-features = false }
|
||||
rand_chacha = "0.3.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
strobe-rs = "0.5"
|
||||
toml = { version = "0.7" }
|
||||
|
||||
[[bench]]
|
||||
|
|
@ -64,7 +66,7 @@ alloc = ["curve25519-dalek/alloc", "ed25519/alloc", "serde?/alloc", "zeroize/all
|
|||
std = ["alloc", "ed25519/std", "serde?/std", "sha2/std"]
|
||||
|
||||
asm = ["sha2/asm"]
|
||||
batch = ["alloc", "merlin", "rand_core"]
|
||||
batch = ["alloc", "dep:keccak", "rand_core"]
|
||||
fast = ["curve25519-dalek/precomputed-tables"]
|
||||
digest = ["signature/digest"]
|
||||
# Exposes the hazmat module
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@
|
|||
|
||||
//! Batch signature verification.
|
||||
|
||||
mod strobe;
|
||||
mod transcript;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use core::iter::once;
|
||||
|
|
@ -21,7 +24,7 @@ use curve25519_dalek::traits::VartimeMultiscalarMul;
|
|||
|
||||
pub use curve25519_dalek::digest::Digest;
|
||||
|
||||
use merlin::Transcript;
|
||||
use transcript::Transcript;
|
||||
|
||||
use rand_core::RngCore;
|
||||
|
||||
|
|
@ -32,6 +35,14 @@ use crate::errors::SignatureError;
|
|||
use crate::signature::InternalSignature;
|
||||
use crate::VerifyingKey;
|
||||
|
||||
/// Domain separation label to initialize the STROBE context.
|
||||
///
|
||||
/// This is not to be confused with the crate's semver string:
|
||||
/// the latter applies to the API, while this label defines the protocol.
|
||||
/// E.g. it is possible that crate 2.0 will have an incompatible API,
|
||||
/// but implement the same 1.0 protocol.
|
||||
const MERLIN_PROTOCOL_LABEL: &[u8] = b"Merlin v1.0";
|
||||
|
||||
/// An implementation of `rand_core::RngCore` which does nothing. This is necessary because merlin
|
||||
/// demands an `Rng` as input to `TranscriptRngBuilder::finalize()`. Using this with `finalize()`
|
||||
/// yields a PRG whose input is the hashed transcript.
|
||||
|
|
|
|||
239
ed25519-dalek/src/batch/strobe.rs
Normal file
239
ed25519-dalek/src/batch/strobe.rs
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
//! Minimal implementation of (parts of) Strobe.
|
||||
|
||||
use core::ops::{Deref, DerefMut};
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
/// Strobe R value; security level 128 is hardcoded
|
||||
const STROBE_R: u8 = 166;
|
||||
|
||||
const FLAG_I: u8 = 1;
|
||||
const FLAG_A: u8 = 1 << 1;
|
||||
const FLAG_C: u8 = 1 << 2;
|
||||
const FLAG_T: u8 = 1 << 3;
|
||||
const FLAG_M: u8 = 1 << 4;
|
||||
const FLAG_K: u8 = 1 << 5;
|
||||
|
||||
fn transmute_state(st: &mut AlignedKeccakState) -> &mut [u64; 25] {
|
||||
unsafe { &mut *(st as *mut AlignedKeccakState as *mut [u64; 25]) }
|
||||
}
|
||||
|
||||
/// This is a wrapper around 200-byte buffer that's always 8-byte aligned
|
||||
/// to make pointers to it safely convertible to pointers to [u64; 25]
|
||||
/// (since u64 words must be 8-byte aligned)
|
||||
#[derive(Clone)]
|
||||
#[repr(align(8))]
|
||||
struct AlignedKeccakState([u8; 200]);
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
impl Drop for AlignedKeccakState {
|
||||
fn drop(&mut self) {
|
||||
self.0.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
/// A Strobe context for the 128-bit security level.
|
||||
///
|
||||
/// Only `meta-AD`, `AD`, `KEY`, and `PRF` operations are supported.
|
||||
#[derive(Clone)]
|
||||
pub struct Strobe128 {
|
||||
state: AlignedKeccakState,
|
||||
pos: u8,
|
||||
pos_begin: u8,
|
||||
cur_flags: u8,
|
||||
}
|
||||
|
||||
impl ::core::fmt::Debug for Strobe128 {
|
||||
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
|
||||
// Ensure that the Strobe state isn't accidentally logged
|
||||
write!(f, "Strobe128: STATE OMITTED")
|
||||
}
|
||||
}
|
||||
|
||||
impl Strobe128 {
|
||||
pub fn new(protocol_label: &[u8]) -> Strobe128 {
|
||||
let initial_state = {
|
||||
let mut st = AlignedKeccakState([0u8; 200]);
|
||||
st[0..6].copy_from_slice(&[1, STROBE_R + 2, 1, 0, 1, 96]);
|
||||
st[6..18].copy_from_slice(b"STROBEv1.0.2");
|
||||
keccak::f1600(transmute_state(&mut st));
|
||||
|
||||
st
|
||||
};
|
||||
|
||||
let mut strobe = Strobe128 {
|
||||
state: initial_state,
|
||||
pos: 0,
|
||||
pos_begin: 0,
|
||||
cur_flags: 0,
|
||||
};
|
||||
|
||||
strobe.meta_ad(protocol_label, false);
|
||||
|
||||
strobe
|
||||
}
|
||||
|
||||
pub fn meta_ad(&mut self, data: &[u8], more: bool) {
|
||||
self.begin_op(FLAG_M | FLAG_A, more);
|
||||
self.absorb(data);
|
||||
}
|
||||
|
||||
pub fn ad(&mut self, data: &[u8], more: bool) {
|
||||
self.begin_op(FLAG_A, more);
|
||||
self.absorb(data);
|
||||
}
|
||||
|
||||
pub fn prf(&mut self, data: &mut [u8], more: bool) {
|
||||
self.begin_op(FLAG_I | FLAG_A | FLAG_C, more);
|
||||
self.squeeze(data);
|
||||
}
|
||||
|
||||
pub fn key(&mut self, data: &[u8], more: bool) {
|
||||
self.begin_op(FLAG_A | FLAG_C, more);
|
||||
self.overwrite(data);
|
||||
}
|
||||
}
|
||||
|
||||
impl Strobe128 {
|
||||
fn run_f(&mut self) {
|
||||
self.state[self.pos as usize] ^= self.pos_begin;
|
||||
self.state[(self.pos + 1) as usize] ^= 0x04;
|
||||
self.state[(STROBE_R + 1) as usize] ^= 0x80;
|
||||
keccak::f1600(transmute_state(&mut self.state));
|
||||
self.pos = 0;
|
||||
self.pos_begin = 0;
|
||||
}
|
||||
|
||||
fn absorb(&mut self, data: &[u8]) {
|
||||
for byte in data {
|
||||
self.state[self.pos as usize] ^= byte;
|
||||
self.pos += 1;
|
||||
if self.pos == STROBE_R {
|
||||
self.run_f();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn overwrite(&mut self, data: &[u8]) {
|
||||
for byte in data {
|
||||
self.state[self.pos as usize] = *byte;
|
||||
self.pos += 1;
|
||||
if self.pos == STROBE_R {
|
||||
self.run_f();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn squeeze(&mut self, data: &mut [u8]) {
|
||||
for byte in data {
|
||||
*byte = self.state[self.pos as usize];
|
||||
self.state[self.pos as usize] = 0;
|
||||
self.pos += 1;
|
||||
if self.pos == STROBE_R {
|
||||
self.run_f();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn begin_op(&mut self, flags: u8, more: bool) {
|
||||
// Check if we're continuing an operation
|
||||
if more {
|
||||
assert_eq!(
|
||||
self.cur_flags, flags,
|
||||
"You tried to continue op {:#b} but changed flags to {:#b}",
|
||||
self.cur_flags, flags,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip adjusting direction information (we just use AD, PRF)
|
||||
assert_eq!(
|
||||
flags & FLAG_T,
|
||||
0u8,
|
||||
"You used the T flag, which this implementation doesn't support"
|
||||
);
|
||||
|
||||
let old_begin = self.pos_begin;
|
||||
self.pos_begin = self.pos + 1;
|
||||
self.cur_flags = flags;
|
||||
|
||||
self.absorb(&[old_begin, flags]);
|
||||
|
||||
// Force running F if C or K is set
|
||||
let force_f = 0 != (flags & (FLAG_C | FLAG_K));
|
||||
|
||||
if force_f && self.pos != 0 {
|
||||
self.run_f();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for AlignedKeccakState {
|
||||
type Target = [u8; 200];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for AlignedKeccakState {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use strobe_rs::{self, SecParam};
|
||||
|
||||
#[test]
|
||||
fn test_conformance() {
|
||||
let mut s1 = super::Strobe128::new(b"Conformance Test Protocol");
|
||||
let mut s2 = strobe_rs::Strobe::new(b"Conformance Test Protocol", SecParam::B128);
|
||||
|
||||
// meta-AD(b"msg"); AD(msg)
|
||||
|
||||
let msg = [99u8; 1024];
|
||||
|
||||
s1.meta_ad(b"ms", false);
|
||||
s1.meta_ad(b"g", true);
|
||||
s1.ad(&msg, false);
|
||||
|
||||
s2.meta_ad(b"ms", false);
|
||||
s2.meta_ad(b"g", true);
|
||||
s2.ad(&msg, false);
|
||||
|
||||
// meta-AD(b"prf"); PRF()
|
||||
|
||||
let mut prf1 = [0u8; 32];
|
||||
s1.meta_ad(b"prf", false);
|
||||
s1.prf(&mut prf1, false);
|
||||
|
||||
let mut prf2 = [0u8; 32];
|
||||
s2.meta_ad(b"prf", false);
|
||||
s2.prf(&mut prf2, false);
|
||||
|
||||
assert_eq!(prf1, prf2);
|
||||
|
||||
// meta-AD(b"key"); KEY(prf output)
|
||||
|
||||
s1.meta_ad(b"key", false);
|
||||
s1.key(&prf1, false);
|
||||
|
||||
s2.meta_ad(b"key", false);
|
||||
s2.key(&prf2, false);
|
||||
|
||||
// meta-AD(b"prf"); PRF()
|
||||
|
||||
let mut prf1 = [0u8; 32];
|
||||
s1.meta_ad(b"prf", false);
|
||||
s1.prf(&mut prf1, false);
|
||||
|
||||
let mut prf2 = [0u8; 32];
|
||||
s2.meta_ad(b"prf", false);
|
||||
s2.prf(&mut prf2, false);
|
||||
|
||||
assert_eq!(prf1, prf2);
|
||||
}
|
||||
}
|
||||
380
ed25519-dalek/src/batch/transcript.rs
Normal file
380
ed25519-dalek/src/batch/transcript.rs
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
use super::strobe::Strobe128;
|
||||
use super::MERLIN_PROTOCOL_LABEL;
|
||||
|
||||
fn encode_usize_as_u32(x: usize) -> [u8; 4] {
|
||||
u32::try_from(x).expect("usize too large").to_le_bytes()
|
||||
}
|
||||
|
||||
/// A transcript of a public-coin argument.
|
||||
///
|
||||
/// The prover's messages are added to the transcript using
|
||||
/// [`append_message`](Transcript::append_message), and the verifier's
|
||||
/// challenges can be computed using
|
||||
/// [`challenge_bytes`](Transcript::challenge_bytes).
|
||||
///
|
||||
/// # Creating and using a Merlin transcript
|
||||
///
|
||||
/// To create a Merlin transcript, use [`Transcript::new()`]. This
|
||||
/// function takes a domain separation label which should be unique to
|
||||
/// the application.
|
||||
///
|
||||
/// To use the transcript with a Merlin-based proof implementation,
|
||||
/// the prover's side creates a Merlin transcript with an
|
||||
/// application-specific domain separation label, and passes a `&mut`
|
||||
/// reference to the transcript to the proving function(s).
|
||||
///
|
||||
/// To verify the resulting proof, the verifier creates their own
|
||||
/// Merlin transcript using the same domain separation label, then
|
||||
/// passes a `&mut` reference to the verifier's transcript to the
|
||||
/// verification function.
|
||||
///
|
||||
/// # Implementing proofs using Merlin
|
||||
///
|
||||
/// For information on the design of Merlin and how to use it to
|
||||
/// implement a proof system, see the documentation at
|
||||
/// [merlin.cool](https://merlin.cool), particularly the [Using
|
||||
/// Merlin](https://merlin.cool/use/index.html) section.
|
||||
#[derive(Clone)]
|
||||
pub struct Transcript {
|
||||
strobe: Strobe128,
|
||||
}
|
||||
|
||||
impl Transcript {
|
||||
/// Initialize a new transcript with the supplied `label`, which
|
||||
/// is used as a domain separator.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This function should be called by a proof library's API
|
||||
/// consumer (i.e., the application using the proof library), and
|
||||
/// **not by the proof implementation**. See the [Passing
|
||||
/// Transcripts](https://merlin.cool/use/passing.html) section of
|
||||
/// the Merlin website for more details on why.
|
||||
pub fn new(label: &'static [u8]) -> Transcript {
|
||||
let mut transcript = Transcript {
|
||||
strobe: Strobe128::new(MERLIN_PROTOCOL_LABEL),
|
||||
};
|
||||
transcript.append_message(b"dom-sep", label);
|
||||
|
||||
transcript
|
||||
}
|
||||
|
||||
/// Append a prover's `message` to the transcript.
|
||||
///
|
||||
/// The `label` parameter is metadata about the message, and is
|
||||
/// also appended to the transcript. See the [Transcript
|
||||
/// Protocols](https://merlin.cool/use/protocol.html) section of
|
||||
/// the Merlin website for details on labels.
|
||||
pub fn append_message(&mut self, label: &'static [u8], message: &[u8]) {
|
||||
let data_len = encode_usize_as_u32(message.len());
|
||||
self.strobe.meta_ad(label, false);
|
||||
self.strobe.meta_ad(&data_len, true);
|
||||
self.strobe.ad(message, false);
|
||||
}
|
||||
|
||||
/// Fill the supplied buffer with the verifier's challenge bytes.
|
||||
///
|
||||
/// The `label` parameter is metadata about the challenge, and is
|
||||
/// also appended to the transcript. See the [Transcript
|
||||
/// Protocols](https://merlin.cool/use/protocol.html) section of
|
||||
/// the Merlin website for details on labels.
|
||||
#[cfg(test)]
|
||||
pub fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8]) {
|
||||
let data_len = encode_usize_as_u32(dest.len());
|
||||
self.strobe.meta_ad(label, false);
|
||||
self.strobe.meta_ad(&data_len, true);
|
||||
self.strobe.prf(dest, false);
|
||||
}
|
||||
|
||||
/// Fork the current [`Transcript`] to construct an RNG whose output is bound
|
||||
/// to the current transcript state as well as prover's secrets.
|
||||
///
|
||||
/// See the [`TranscriptRngBuilder`] documentation for more details.
|
||||
pub fn build_rng(&self) -> TranscriptRngBuilder {
|
||||
TranscriptRngBuilder {
|
||||
strobe: self.strobe.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a [`TranscriptRng`] by rekeying the [`Transcript`] with
|
||||
/// prover secrets and an external RNG.
|
||||
///
|
||||
/// The prover uses a [`TranscriptRngBuilder`] to rekey with its
|
||||
/// witness data, before using an external RNG to finalize to a
|
||||
/// [`TranscriptRng`]. The resulting [`TranscriptRng`] will be a PRF
|
||||
/// of all of the entire public transcript, the prover's secret
|
||||
/// witness data, and randomness from the external RNG.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Protocols that require randomness in multiple places (e.g., to
|
||||
/// choose blinding factors for a multi-round protocol) should create
|
||||
/// a fresh [`TranscriptRng`] **each time they need randomness**,
|
||||
/// rather than reusing a single instance. This ensures that the
|
||||
/// randomness in each round is bound to the latest transcript state,
|
||||
/// rather than just the state of the transcript when randomness was
|
||||
/// first required.
|
||||
///
|
||||
/// # Typed Witness Data
|
||||
///
|
||||
/// Like the [`Transcript`], the [`TranscriptRngBuilder`] provides a
|
||||
/// minimal, byte-oriented API, and like the [`Transcript`], this API
|
||||
/// can be extended to allow rekeying with protocol-specific types
|
||||
/// using an extension trait. See the [Transcript
|
||||
/// Protocols](https://merlin.cool/use/protocol.html) section of the
|
||||
/// Merlin website for more details.
|
||||
///
|
||||
/// [rekey_with_witness_bytes]: TranscriptRngBuilder::rekey_with_witness_bytes
|
||||
/// [finalize]: TranscriptRngBuilder::finalize
|
||||
pub struct TranscriptRngBuilder {
|
||||
strobe: Strobe128,
|
||||
}
|
||||
|
||||
impl TranscriptRngBuilder {
|
||||
/// Rekey the transcript using the provided witness data.
|
||||
///
|
||||
/// The `label` parameter is metadata about `witness`.
|
||||
#[cfg(test)]
|
||||
pub fn rekey_with_witness_bytes(
|
||||
mut self,
|
||||
label: &'static [u8],
|
||||
witness: &[u8],
|
||||
) -> TranscriptRngBuilder {
|
||||
let witness_len = encode_usize_as_u32(witness.len());
|
||||
self.strobe.meta_ad(label, false);
|
||||
self.strobe.meta_ad(&witness_len, true);
|
||||
self.strobe.key(witness, false);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Use the supplied external `rng` to rekey the transcript, so
|
||||
/// that the finalized [`TranscriptRng`] is a PRF bound to
|
||||
/// randomness from the external RNG, as well as all other
|
||||
/// transcript data.
|
||||
pub fn finalize<R>(mut self, rng: &mut R) -> TranscriptRng
|
||||
where
|
||||
R: rand_core::RngCore + rand_core::CryptoRng,
|
||||
{
|
||||
let random_bytes = {
|
||||
let mut bytes = [0u8; 32];
|
||||
rng.fill_bytes(&mut bytes);
|
||||
bytes
|
||||
};
|
||||
|
||||
self.strobe.meta_ad(b"rng", false);
|
||||
self.strobe.key(&random_bytes, false);
|
||||
|
||||
TranscriptRng {
|
||||
strobe: self.strobe,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An RNG providing synthetic randomness to the prover.
|
||||
///
|
||||
/// A [`TranscriptRng`] is constructed from a [`Transcript`] using a
|
||||
/// [`TranscriptRngBuilder`]; see its documentation for details on
|
||||
/// how to construct one.
|
||||
///
|
||||
/// The transcript RNG construction is described in the [Generating
|
||||
/// Randomness](https://merlin.cool/transcript/rng.html) section of
|
||||
/// the Merlin website.
|
||||
pub struct TranscriptRng {
|
||||
strobe: Strobe128,
|
||||
}
|
||||
|
||||
impl rand_core::RngCore for TranscriptRng {
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
rand_core::impls::next_u32_via_fill(self)
|
||||
}
|
||||
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
rand_core::impls::next_u64_via_fill(self)
|
||||
}
|
||||
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||
let dest_len = encode_usize_as_u32(dest.len());
|
||||
self.strobe.meta_ad(&dest_len, false);
|
||||
self.strobe.prf(dest, false);
|
||||
}
|
||||
|
||||
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
|
||||
self.fill_bytes(dest);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl rand_core::CryptoRng for TranscriptRng {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::vec::Vec;
|
||||
use strobe_rs::SecParam;
|
||||
use strobe_rs::Strobe;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Test against a full strobe implementation to ensure we match the few
|
||||
/// operations we're interested in.
|
||||
struct TestTranscript {
|
||||
state: Strobe,
|
||||
}
|
||||
|
||||
impl TestTranscript {
|
||||
/// Strobe init; meta-AD(label)
|
||||
pub fn new(label: &[u8]) -> TestTranscript {
|
||||
let mut tt = TestTranscript {
|
||||
state: Strobe::new(MERLIN_PROTOCOL_LABEL, SecParam::B128),
|
||||
};
|
||||
tt.append_message(b"dom-sep", label);
|
||||
|
||||
tt
|
||||
}
|
||||
|
||||
/// Strobe op: meta-AD(label || len(message)); AD(message)
|
||||
pub fn append_message(&mut self, label: &[u8], message: &[u8]) {
|
||||
// metadata = label || len(message);
|
||||
let mut metadata: Vec<u8> = Vec::with_capacity(label.len() + 4);
|
||||
metadata.extend_from_slice(label);
|
||||
metadata.extend_from_slice(&encode_usize_as_u32(message.len()));
|
||||
|
||||
self.state.meta_ad(&metadata, false);
|
||||
self.state.ad(&message, false);
|
||||
}
|
||||
|
||||
/// Strobe op: meta-AD(label || len(dest)); PRF into challenge_bytes
|
||||
#[cfg(test)]
|
||||
pub fn challenge_bytes(&mut self, label: &[u8], dest: &mut [u8]) {
|
||||
let prf_len = dest.len();
|
||||
|
||||
// metadata = label || len(challenge_bytes);
|
||||
let mut metadata: Vec<u8> = Vec::with_capacity(label.len() + 4);
|
||||
metadata.extend_from_slice(label);
|
||||
metadata.extend_from_slice(&encode_usize_as_u32(prf_len));
|
||||
|
||||
self.state.meta_ad(&metadata, false);
|
||||
self.state.prf(dest, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Test a simple protocol with one message and one challenge
|
||||
#[test]
|
||||
fn equivalence_simple() {
|
||||
let mut real_transcript = Transcript::new(b"test protocol");
|
||||
let mut test_transcript = TestTranscript::new(b"test protocol");
|
||||
|
||||
real_transcript.append_message(b"some label", b"some data");
|
||||
test_transcript.append_message(b"some label", b"some data");
|
||||
|
||||
let mut real_challenge = [0u8; 32];
|
||||
let mut test_challenge = [0u8; 32];
|
||||
|
||||
real_transcript.challenge_bytes(b"challenge", &mut real_challenge);
|
||||
test_transcript.challenge_bytes(b"challenge", &mut test_challenge);
|
||||
|
||||
assert_eq!(real_challenge, test_challenge);
|
||||
}
|
||||
|
||||
/// Test a complex protocol with multiple messages and challenges,
|
||||
/// with messages long enough to wrap around the sponge state, and
|
||||
/// with multiple rounds of messages and challenges.
|
||||
#[test]
|
||||
fn equivalence_complex() {
|
||||
let mut real_transcript = Transcript::new(b"test protocol");
|
||||
let mut test_transcript = TestTranscript::new(b"test protocol");
|
||||
|
||||
let data = vec![99; 1024];
|
||||
|
||||
real_transcript.append_message(b"step1", b"some data");
|
||||
test_transcript.append_message(b"step1", b"some data");
|
||||
|
||||
let mut real_challenge = [0u8; 32];
|
||||
let mut test_challenge = [0u8; 32];
|
||||
|
||||
for _ in 0..32 {
|
||||
real_transcript.challenge_bytes(b"challenge", &mut real_challenge);
|
||||
test_transcript.challenge_bytes(b"challenge", &mut test_challenge);
|
||||
|
||||
assert_eq!(real_challenge, test_challenge);
|
||||
|
||||
real_transcript.append_message(b"bigdata", &data);
|
||||
test_transcript.append_message(b"bigdata", &data);
|
||||
|
||||
real_transcript.append_message(b"challengedata", &real_challenge);
|
||||
test_transcript.append_message(b"challengedata", &test_challenge);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transcript_rng_is_bound_to_transcript_and_witnesses() {
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
use rand_chacha::ChaChaRng;
|
||||
use rand_core::SeedableRng;
|
||||
|
||||
// Check that the TranscriptRng is bound to the transcript and
|
||||
// the witnesses. This is done by producing a sequence of
|
||||
// transcripts that diverge at different points and checking
|
||||
// that they produce different challenges.
|
||||
|
||||
let protocol_label = b"test TranscriptRng collisions";
|
||||
let commitment1 = b"commitment data 1";
|
||||
let commitment2 = b"commitment data 2";
|
||||
let witness1 = b"witness data 1";
|
||||
let witness2 = b"witness data 2";
|
||||
|
||||
let mut t1 = Transcript::new(protocol_label);
|
||||
let mut t2 = Transcript::new(protocol_label);
|
||||
let mut t3 = Transcript::new(protocol_label);
|
||||
let mut t4 = Transcript::new(protocol_label);
|
||||
|
||||
t1.append_message(b"com", commitment1);
|
||||
t2.append_message(b"com", commitment2);
|
||||
t3.append_message(b"com", commitment2);
|
||||
t4.append_message(b"com", commitment2);
|
||||
|
||||
let mut r1 = t1
|
||||
.build_rng()
|
||||
.rekey_with_witness_bytes(b"witness", witness1)
|
||||
.finalize(&mut ChaChaRng::from_seed([0; 32]));
|
||||
|
||||
let mut r2 = t2
|
||||
.build_rng()
|
||||
.rekey_with_witness_bytes(b"witness", witness1)
|
||||
.finalize(&mut ChaChaRng::from_seed([0; 32]));
|
||||
|
||||
let mut r3 = t3
|
||||
.build_rng()
|
||||
.rekey_with_witness_bytes(b"witness", witness2)
|
||||
.finalize(&mut ChaChaRng::from_seed([0; 32]));
|
||||
|
||||
let mut r4 = t4
|
||||
.build_rng()
|
||||
.rekey_with_witness_bytes(b"witness", witness2)
|
||||
.finalize(&mut ChaChaRng::from_seed([0; 32]));
|
||||
|
||||
let s1 = Scalar::random(&mut r1);
|
||||
let s2 = Scalar::random(&mut r2);
|
||||
let s3 = Scalar::random(&mut r3);
|
||||
let s4 = Scalar::random(&mut r4);
|
||||
|
||||
// Transcript t1 has different commitments than t2, t3, t4, so
|
||||
// it should produce distinct challenges from all of them.
|
||||
assert_ne!(s1, s2);
|
||||
assert_ne!(s1, s3);
|
||||
assert_ne!(s1, s4);
|
||||
|
||||
// Transcript t2 has different witness variables from t3, t4,
|
||||
// so it should produce distinct challenges from all of them.
|
||||
assert_ne!(s2, s3);
|
||||
assert_ne!(s2, s4);
|
||||
|
||||
// Transcripts t3 and t4 have the same commitments and
|
||||
// witnesses, so they should give different challenges only
|
||||
// based on the RNG. Checking that they're equal in the
|
||||
// presence of a bad RNG checks that the different challenges
|
||||
// above aren't because the RNG is accidentally different.
|
||||
assert_eq!(s3, s4);
|
||||
}
|
||||
}
|
||||
|
|
@ -242,7 +242,7 @@
|
|||
#![warn(future_incompatible, rust_2018_idioms)]
|
||||
#![deny(missing_docs)] // refuse to compile if documentation is missing
|
||||
#![deny(clippy::unwrap_used)] // don't allow unwrap
|
||||
#![cfg_attr(not(test), forbid(unsafe_code))]
|
||||
#![cfg_attr(not(any(test, feature = "batch")), forbid(unsafe_code))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg, doc_cfg_hide))]
|
||||
#![cfg_attr(docsrs, doc(cfg_hide(docsrs)))]
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue