Skip to content

Commit 7c5bd69

Browse files
committed
More documentation.
1 parent 7b4baf7 commit 7c5bd69

File tree

8 files changed

+110
-45
lines changed

8 files changed

+110
-45
lines changed

src/hash/legacy.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,9 @@ impl<D: BlockSizeUser + Digest + Clone + FixedOutputReset> DuplexHash<u8> for Di
163163
// If we still have some digest not yet squeezed
164164
// from previous invocations, write it to the output.
165165
} else if !self.leftovers.is_empty() {
166-
println!("Here we are, with leftovers {:?}", self.leftovers);
167166
let len = usize::min(output.len(), self.leftovers.len());
168167
output[..len].copy_from_slice(&self.leftovers[..len]);
169-
self.leftovers.drain(len..);
168+
self.leftovers.drain(..len);
170169
self.squeeze_unchecked(&mut output[len..])
171170
// Squeeze another digest
172171
} else if let Mode::Squeeze(i) = self.mode {

src/hash/sponge.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ pub struct DuplexSponge<C: Sponge> {
4949
}
5050

5151
impl<U: Unit, C: Sponge<U = U>> DuplexHash<U> for DuplexSponge<C> {
52-
fn new(tag: [u8; 32]) -> Self {
52+
fn new(iv: [u8; 32]) -> Self {
5353
Self {
54-
state: C::new(tag),
54+
state: C::new(iv),
5555
absorb_pos: 0,
5656
squeeze_pos: 0,
5757
}

src/iopattern.rs

+32-28
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,31 @@ use super::hash::{DuplexHash, Unit};
1414
/// and as such is the only forbidden characted in labels.
1515
const SEP_BYTE: &str = "\0";
1616

17+
/// The IO Pattern of an interactive protocol.
18+
///
19+
/// An IO pattern is a string that specifies the protocol in a simple,
20+
/// non-ambiguous, human-readable format. A typical example is the following:
21+
///
22+
/// ```text
23+
/// domain-separator A32generator A32public-key R A32commitment S32challenge A32response
24+
/// ```
25+
/// The domain-separator is a user-specified string uniquely identifying the end-user application (to avoid cross-protocol attacks).
26+
/// The letter `A` indicates the absorption of a public input (an `ABSORB`), while the letter `S` indicates the squeezing (a `SQUEEZE`) of a challenge.
27+
/// The letter `R` indicates a ratcheting operation: ratcheting means invoking the hash function even on an incomplete block.
28+
/// It provides forward secrecy and allows it to start from a clean rate.
29+
/// After the operation type, is the number of elements in base 10 that are being absorbed/squeezed.
30+
/// Then, follows the label associated with the element being absorbed/squeezed. This often comes from the underlying description of the protocol. The label cannot start with a digit or contain the NULL byte.
31+
32+
#[derive(Clone)]
33+
pub struct IOPattern<H = crate::DefaultHash, U = u8>
34+
where
35+
U: Unit,
36+
H: DuplexHash<U>,
37+
{
38+
io: String,
39+
_hash: PhantomData<(H, U)>,
40+
}
41+
1742
/// Sponge operations.
1843
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1944
pub(crate) enum Op {
@@ -46,34 +71,6 @@ impl Op {
4671
}
4772
}
4873

49-
/// The IO Pattern of an interactive protocol.
50-
///
51-
/// An IO Pattern is a string denoting a
52-
/// sequence of operations to be performed on a [`crate::DuplexHash`].
53-
/// The IO Pattern is prepended by a domain separator, a NULL-terminated string
54-
/// that is used to prevent collisions between different protocols.
55-
/// Each operation (absorb, squeeze, ratchet) is identified by a
56-
/// single character, followed by the number of units (bytes, or field elements)
57-
/// to be absorbed/squeezed, and a NULL-terminated label identifying the element.
58-
/// The whole is separated by a NULL byte.
59-
///
60-
/// For example, the IO Pattern
61-
/// ```text
62-
/// iacr.org\0A32\0S64\0A32\0A32
63-
/// ```
64-
///
65-
/// Denotes a protocol absorbing 32 native elements, squeezing 64 native elements,
66-
/// and finally absorbing 64 native elements.
67-
#[derive(Clone)]
68-
pub struct IOPattern<H = crate::DefaultHash, U = u8>
69-
where
70-
U: Unit,
71-
H: DuplexHash<U>,
72-
{
73-
io: String,
74-
_hash: PhantomData<(H, U)>,
75-
}
76-
7774
impl<H: DuplexHash<U>, U: Unit> IOPattern<H, U> {
7875
fn from_string(io: String) -> Self {
7976
Self {
@@ -88,6 +85,7 @@ impl<H: DuplexHash<U>, U: Unit> IOPattern<H, U> {
8885
Self::from_string(domsep.to_string())
8986
}
9087

88+
/// Absorb `count` native elements.
9189
pub fn absorb(self, count: usize, label: &str) -> Self {
9290
assert!(count > 0, "Count must be positive");
9391
assert!(!label.contains(SEP_BYTE));
@@ -96,6 +94,7 @@ impl<H: DuplexHash<U>, U: Unit> IOPattern<H, U> {
9694
Self::from_string(self.io + SEP_BYTE + &format!("A{}", count) + label)
9795
}
9896

97+
/// Squeeze `count` native elements.
9998
pub fn squeeze(self, count: usize, label: &str) -> Self {
10099
assert!(count > 0, "Count must be positive");
101100
assert!(!label.contains(SEP_BYTE));
@@ -104,14 +103,17 @@ impl<H: DuplexHash<U>, U: Unit> IOPattern<H, U> {
104103
Self::from_string(self.io + SEP_BYTE + &format!("S{}", count) + label)
105104
}
106105

106+
/// Ratchet the state.
107107
pub fn ratchet(self) -> Self {
108108
Self::from_string(self.io + SEP_BYTE + "R")
109109
}
110110

111+
/// Return the IO Pattern as bytes.
111112
pub fn as_bytes(&self) -> &[u8] {
112113
self.io.as_bytes()
113114
}
114115

116+
/// Parse the givern IO Pattern into a sequence of [`Op`]'s.
115117
pub(crate) fn finalize(&self) -> VecDeque<Op> {
116118
// Guaranteed to succeed as instances are all valid iopatterns
117119
Self::parse_io(self.io.as_bytes())
@@ -173,10 +175,12 @@ impl<H: DuplexHash<U>, U: Unit> IOPattern<H, U> {
173175
}
174176
}
175177

178+
/// Create an [`crate::Arthur`] instance from the IO Pattern.
176179
pub fn to_arthur(&self) -> crate::Arthur<H, U, crate::DefaultRng> {
177180
crate::Arthur::new(self, crate::DefaultRng::default())
178181
}
179182

183+
/// Create a [`crate::Merlin`] instance from the IO Pattern and the protocol transcript (bytes).
180184
pub fn to_merlin<'a>(&self, transcript: &'a [u8]) -> crate::Merlin<'a, H, U> {
181185
crate::Merlin::<H, U>::new(self, transcript)
182186
}

src/lib.rs

+10-4
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,17 @@
2222
//! - **Private randomness generation**.
2323
//! It is vital to avoid providing two different challenges for the same prover message. We do our best to avoid it by tying down the prover randomness to the protocol transcript, without making the proof deterministic.
2424
//!
25-
//! # Intuition
25+
//! # Overview
26+
//!
27+
//! The library does three things:
28+
//!
29+
//! - Assist in the construction of a protocol transcript for a public-coin zero-knowledge proof ([Arthur]),
30+
//! - Assist in the deserialization and verification of a public-coin protocol ([Merlin]).
2631
//!
2732
//! The basic idea behind Nimue is that prover and verifier "commit" to the protocol before running the actual protocol.
28-
//! This preprocessing step, where the input/output of the prover, generates an "IV" that is used to initialize the hash function for the Fiat-Shamir heuristic.
29-
//! From here, prover just proceeds with concatenation, without ever worrying
33+
//! They a string encoding the sequence of messages sent from the prover and the verifier (the [IOPattern]), which is used as an "IV" to initialize the hash function for the Fiat-Shamir heuristic.
34+
//!
35+
//! There are prover just proceeds with concatenation, without ever worrying
3036
//! about encoding length and special flags to embed in the hash function.
3137
//! This allows for
3238
//! better preprocessing,
@@ -48,7 +54,7 @@
4854
//! An [`IOPattern`] is a UTF8-encoded string wrapper. Absorptions are denoted as `format!(A{}, length)` and
4955
//! squeezes as `format!(S{}, length)`. A label is added at the end of the string, meant to describe the *type* and
5056
//! *the variable* as used in the protocol. Operations are separated by a NULL byte and therefore labels cannot contain
51-
//! NULL bytes themselves, nor start with an ASCII digit.x
57+
//! NULL bytes themselves, nor start with an ASCII digit.
5258
//!
5359
//!
5460
//! # Protocol transcripts

src/plugins/ark/mod.rs

+53-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,59 @@
11
//! This module contains utilities for working with Arkworks types
22
//! and aid in the Fiat-Shamir heuristic for protocols dealing with
33
//! field elements and group elements.
4-
5-
/// Common utilities for adding public elements to the protocol transcript.
4+
//!
5+
//! # Examples
6+
//!
7+
//! Here's a protocol that does Fiat-Shamir without caring about the hash function used
8+
//! or the serialization format.
9+
//!
10+
//! ```rust
11+
//! use ark_ec::CurveGroup;
12+
//! use ark_std::UniformRand;
13+
//! use nimue::{IOPattern, Arthur, DuplexHash, ProofResult};
14+
//! use nimue::plugins::ark::*;
15+
//!
16+
//! fn prove<G>(
17+
//! arthur: &mut Arthur,
18+
//! x: G::ScalarField,
19+
//! ) -> ProofResult<&[u8]>
20+
//! where
21+
//! G: CurveGroup,
22+
//! Arthur: GroupWriter<G> + FieldChallenges<G::ScalarField>,
23+
//! {
24+
//! let k = G::ScalarField::rand(arthur.rng());
25+
//! arthur.add_points(&[G::generator() * k])?;
26+
//! let [c] = arthur.challenge_scalars()?;
27+
//! arthur.add_scalars(&[k + c * x])?;
28+
//! Ok(arthur.transcript())
29+
//! }
30+
//! ```
31+
//! The type constraint on [`crate::Arthur`] hints the compiler that we are going to be absorbing elements from the group `G` and squeezing challenges in the scalar field `G::ScalarField`. Similarly, we could have been squeezing out bytes.
32+
//!
33+
//! ```rust
34+
//! # use ark_ec::CurveGroup;
35+
//! # use ark_std::UniformRand;
36+
//! # use ark_ff::PrimeField;
37+
//! # use nimue::{IOPattern, Arthur, DuplexHash, ProofResult};
38+
//! # use nimue::plugins::ark::*;
39+
//!
40+
//! fn prove<G>(
41+
//! arthur: &mut Arthur,
42+
//! x: G::ScalarField,
43+
//! ) -> ProofResult<&[u8]>
44+
//! where
45+
//! G: CurveGroup,
46+
//! Arthur: GroupWriter<G> + ByteChallenges,
47+
//! {
48+
//! let k = G::ScalarField::rand(arthur.rng());
49+
//! arthur.add_points(&[G::generator() * k])?;
50+
//! let c_bytes = arthur.challenge_bytes::<16>()?;
51+
//! let c = G::ScalarField::from_le_bytes_mod_order(&c_bytes);
52+
//! arthur.add_scalars(&[k + c * x])?;
53+
//! Ok(arthur.transcript())
54+
//! }
55+
//! ```
56+
/// Add public elements (field or group elements) to the protocol transcript.
657
mod common;
758
/// IO Pattern utilities.
859
mod iopattern;

src/plugins/ark/poseidon.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ use ark_ff::PrimeField;
22

33
use crate::hash::sponge::Sponge;
44
use crate::hash::Unit;
5+
use crate::hash::index::impl_indexing;
56

67
#[derive(Clone)]
78
pub struct PoseidonSponge<F: PrimeField, const R: usize, const N: usize> {
8-
99
/// Number of rounds in a full-round operation.
1010
pub full_rounds: usize,
1111
/// Number of rounds in a partial-round operation.
@@ -18,11 +18,14 @@ pub struct PoseidonSponge<F: PrimeField, const R: usize, const N: usize> {
1818
/// Maximally Distance Separating (MDS) Matrix.
1919
pub mds: &'static [[F; N]],
2020

21-
// Sponge State
22-
/// Current sponge's state (current elements in the permutation block)
21+
/// Sponge state
2322
pub state: [F; N],
2423
}
2524

25+
// Indexing over PoseidonSponge is just forwarded to indexing on the state.
26+
impl_indexing!(PoseidonSponge, state, Output = F, Params = [F: PrimeField], Constants = [R, N]);
27+
28+
2629
impl<F: PrimeField, const R: usize, const N: usize> PoseidonSponge<F, R, N> {
2730
fn apply_s_box(&self, state: &mut [F], is_full_round: bool) {
2831
// Full rounds apply the S Box (x^alpha) to every element of state
@@ -57,7 +60,6 @@ impl<F: PrimeField, const R: usize, const N: usize> PoseidonSponge<F, R, N> {
5760
}
5861
}
5962

60-
crate::hash::index::impl_indexing!(PoseidonSponge, state, Output = F, Params = [F: PrimeField], Constants = [R, N]);
6163

6264
impl<F: PrimeField, const R: usize, const N: usize> zeroize::Zeroize for PoseidonSponge<F, R, N> {
6365
fn zeroize(&mut self) {

src/plugins/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ pub(super) const fn bytes_modp(modulus_bits: u32) -> usize {
2323

2424
/// Unit-tests for inter-operability among libraries.
2525
#[cfg(all(test, feature = "ark", feature = "group"))]
26-
mod tests;
26+
mod tests;

src/tests.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use crate::hash::legacy::DigestBridge;
33
use crate::{Arthur, ByteChallenges, ByteWriter, DuplexHash, IOPattern, Safe};
44

55
type Sha2 = DigestBridge<sha2::Sha256>;
6-
type Blake2 = DigestBridge<blake2::Blake2b512>;
6+
type Blake2b512 = DigestBridge<blake2::Blake2b512>;
7+
type Blake2s256 = DigestBridge<blake2::Blake2s256>;
8+
79

810
/// How should a protocol without IOPattern be handled?
911
#[test]
@@ -165,7 +167,8 @@ fn test_streaming_sha2() {
165167

166168
#[test]
167169
fn test_streaming_blake2() {
168-
test_streaming_absorb_and_squeeze::<Blake2>();
170+
test_streaming_absorb_and_squeeze::<Blake2b512>();
171+
test_streaming_absorb_and_squeeze::<Blake2s256>();
169172
}
170173

171174
#[test]

0 commit comments

Comments
 (0)