Skip to content

Commit cf8ba18

Browse files
mmakernougzarm
andauthored
chore(keccak): slicker code and more stable dependencies (#45)
Move to the keccak crate Change visibility and implementation of some structures Add from_iv for codecs to initialize directly the sponge --- Co-Authored-By: nougzarm <[email protected]>
1 parent a198c0e commit cf8ba18

File tree

7 files changed

+90
-87
lines changed

7 files changed

+90
-87
lines changed

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ num-traits = "0.2.19"
2727
rand = "0.8.5"
2828
sha3 = "0.10.8"
2929
thiserror = "1"
30-
tiny-keccak = { version = "2.0.2", features = ["fips202"] }
30+
keccak = "0.1.5"
31+
zerocopy = "0.8"
32+
zeroize = "1.8.1"
3133

3234
[dev-dependencies]
3335
bls12_381 = "0.8.0"

src/codec.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ pub trait Codec {
2424
/// Generates an empty codec that can be identified by a domain separator.
2525
fn new(domain_sep: &[u8]) -> Self;
2626

27+
/// Allows for precomputed initialization of the codec with a specific IV.
28+
fn from_iv(iv: [u8; 32]) -> Self;
29+
2730
/// Absorbs data into the codec.
2831
fn prover_message(&mut self, data: &[u8]);
2932

@@ -58,10 +61,19 @@ where
5861
type Challenge = <G as Group>::Scalar;
5962

6063
fn new(domain_sep: &[u8]) -> Self {
61-
let hasher = H::new(domain_sep);
64+
let iv = {
65+
let mut tmp = H::new([0u8; 32]);
66+
tmp.absorb(domain_sep);
67+
tmp.squeeze(32).try_into().unwrap()
68+
};
69+
70+
Self::from_iv(iv)
71+
}
72+
73+
fn from_iv(iv: [u8; 32]) -> Self {
6274
Self {
63-
hasher,
64-
_marker: Default::default(),
75+
hasher: H::new(iv),
76+
_marker: core::marker::PhantomData,
6577
}
6678
}
6779

src/duplex_sponge/keccak.rs

Lines changed: 48 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,125 +4,93 @@
44
//! It is designed to match test vectors from the original Sage implementation.
55
66
use crate::duplex_sponge::DuplexSpongeInterface;
7-
use std::convert::TryInto;
8-
use tiny_keccak::keccakf;
7+
use zerocopy::IntoBytes;
98

10-
const R: usize = 136;
11-
const N: usize = 136 + 64;
9+
const RATE: usize = 136;
10+
const LENGTH: usize = 136 + 64;
1211

1312
/// Low-level Keccak-f[1600] state representation.
14-
#[derive(Clone)]
15-
pub struct KeccakPermutationState {
16-
pub state: [u8; 200],
17-
pub rate: usize,
18-
pub capacity: usize,
19-
}
20-
21-
impl Default for KeccakPermutationState {
22-
fn default() -> Self {
23-
Self::new([0u8; 32])
24-
}
25-
}
13+
#[derive(Clone, Default)]
14+
pub struct KeccakPermutationState([u64; LENGTH / 8]);
2615

2716
impl KeccakPermutationState {
2817
pub fn new(iv: [u8; 32]) -> Self {
29-
let rate = 136;
30-
let mut state = [0u8; N];
31-
state[rate..rate + 32].copy_from_slice(&iv);
32-
33-
KeccakPermutationState {
34-
state,
35-
rate,
36-
capacity: 64,
37-
}
18+
let mut state = Self::default();
19+
state.as_mut()[RATE..RATE + 32].copy_from_slice(&iv);
20+
state
3821
}
3922

40-
fn bytes_to_flat_state(&self) -> [u64; 25] {
41-
let mut flat = [0u64; 25];
42-
for (i, item) in flat.iter_mut().enumerate() {
43-
let start = i * 8;
44-
*item = u64::from_le_bytes(self.state[start..start + 8].try_into().unwrap());
45-
}
46-
flat
23+
pub fn permute(&mut self) {
24+
keccak::f1600(&mut self.0);
4725
}
26+
}
4827

49-
fn flat_state_to_bytes(&mut self, flat: [u64; 25]) {
50-
for (i, item) in flat.iter().enumerate() {
51-
let bytes = item.to_le_bytes();
52-
let start = i * 8;
53-
self.state[start..start + 8].copy_from_slice(&bytes);
54-
}
28+
impl AsRef<[u8]> for KeccakPermutationState {
29+
fn as_ref(&self) -> &[u8] {
30+
self.0.as_bytes()
5531
}
32+
}
5633

57-
pub fn permute(&mut self) {
58-
let mut flat = self.bytes_to_flat_state();
59-
keccakf(&mut flat);
60-
self.flat_state_to_bytes(flat);
34+
impl AsMut<[u8]> for KeccakPermutationState {
35+
fn as_mut(&mut self) -> &mut [u8] {
36+
self.0.as_mut_bytes()
6137
}
6238
}
6339

6440
/// Duplex sponge construction using Keccak-f[1600].
6541
#[derive(Clone)]
6642
pub struct KeccakDuplexSponge {
67-
pub state: KeccakPermutationState,
68-
pub rate: usize,
69-
pub capacity: usize,
43+
state: KeccakPermutationState,
7044
absorb_index: usize,
7145
squeeze_index: usize,
7246
}
7347

7448
impl KeccakDuplexSponge {
75-
pub fn new(iv: &[u8]) -> Self {
76-
assert_eq!(iv.len(), 32);
77-
78-
let state = KeccakPermutationState::new(iv.try_into().unwrap());
79-
let rate = R;
80-
let capacity = N - R;
49+
pub fn new(iv: [u8; 32]) -> Self {
50+
let state = KeccakPermutationState::new(iv);
8151
KeccakDuplexSponge {
8252
state,
83-
rate,
84-
capacity,
8553
absorb_index: 0,
86-
squeeze_index: rate,
54+
squeeze_index: RATE,
8755
}
8856
}
8957
}
9058

9159
impl DuplexSpongeInterface for KeccakDuplexSponge {
92-
fn new(iv: &[u8]) -> Self {
60+
fn new(iv: [u8; 32]) -> Self {
9361
KeccakDuplexSponge::new(iv)
9462
}
9563

9664
fn absorb(&mut self, mut input: &[u8]) {
97-
self.squeeze_index = self.rate;
65+
self.squeeze_index = RATE;
9866

9967
while !input.is_empty() {
100-
if self.absorb_index == self.rate {
68+
if self.absorb_index == RATE {
10169
self.state.permute();
10270
self.absorb_index = 0;
10371
}
10472

105-
let chunk_size = usize::min(self.rate - self.absorb_index, input.len());
106-
let dest = &mut self.state.state[self.absorb_index..self.absorb_index + chunk_size];
73+
let chunk_size = usize::min(RATE - self.absorb_index, input.len());
74+
let dest = &mut self.state.as_mut()[self.absorb_index..self.absorb_index + chunk_size];
10775
dest.copy_from_slice(&input[..chunk_size]);
10876
self.absorb_index += chunk_size;
10977
input = &input[chunk_size..];
11078
}
11179
}
11280

11381
fn squeeze(&mut self, mut length: usize) -> Vec<u8> {
114-
self.absorb_index = self.rate;
82+
self.absorb_index = RATE;
11583

11684
let mut output = Vec::new();
11785
while length != 0 {
118-
if self.squeeze_index == self.rate {
86+
if self.squeeze_index == RATE {
11987
self.state.permute();
12088
self.squeeze_index = 0;
12189
}
12290

123-
let chunk_size = usize::min(self.rate - self.squeeze_index, length);
91+
let chunk_size = usize::min(RATE - self.squeeze_index, length);
12492
output.extend_from_slice(
125-
&self.state.state[self.squeeze_index..self.squeeze_index + chunk_size],
93+
&self.state.as_mut()[self.squeeze_index..self.squeeze_index + chunk_size],
12694
);
12795
self.squeeze_index += chunk_size;
12896
length -= chunk_size;
@@ -131,3 +99,20 @@ impl DuplexSpongeInterface for KeccakDuplexSponge {
13199
output
132100
}
133101
}
102+
103+
#[cfg(test)]
104+
mod tests {
105+
use super::*;
106+
use crate::duplex_sponge::DuplexSpongeInterface;
107+
108+
#[test]
109+
fn test_keccak_duplex_sponge() {
110+
let mut sponge = KeccakDuplexSponge::new([0u8; 32]);
111+
112+
let input = b"Hello, World!";
113+
sponge.absorb(input);
114+
let output = sponge.squeeze(64);
115+
116+
assert_eq!(output, hex::decode("30b74a98221dd643d0814095c212d663a67945c6a582ef8f71bd2a14607ebade3f16e5975ad13d313d9aa0aa97ad29f7df5cff249fa633d3a7ac70d8587bec90").unwrap());
117+
}
118+
}

src/duplex_sponge/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub mod shake;
1616
/// This is the core primitive used for building cryptographic codecs.
1717
pub trait DuplexSpongeInterface {
1818
/// Creates a new sponge instance with an initialization vector.
19-
fn new(iv: &[u8]) -> Self;
19+
fn new(iv: [u8; 32]) -> Self;
2020

2121
/// Absorbs input data into the sponge state.
2222
fn absorb(&mut self, input: &[u8]);

src/duplex_sponge/shake.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,22 @@ use sha3::{
1010

1111
/// Duplex sponge construction using SHAKE128.
1212
#[derive(Clone, Debug)]
13-
pub struct ShakeDuplexSponge {
14-
/// Internal SHAKE128 hasher state.
15-
hasher: Shake128,
16-
}
13+
pub struct ShakeDuplexSponge(Shake128);
1714

1815
impl DuplexSpongeInterface for ShakeDuplexSponge {
19-
fn new(iv: &[u8]) -> Self {
16+
fn new(iv: [u8; 32]) -> Self {
2017
let mut hasher = Shake128::default();
21-
hasher.update(iv);
22-
Self { hasher }
18+
hasher.update(&iv);
19+
Self(hasher)
2320
}
2421

2522
fn absorb(&mut self, input: &[u8]) {
26-
self.hasher.update(input);
23+
self.0.update(input);
2724
}
2825

2926
fn squeeze(&mut self, length: usize) -> Vec<u8> {
3027
let mut output = vec![0u8; length];
31-
self.hasher.clone().finalize_xof_into(&mut output);
28+
self.0.clone().finalize_xof_into(&mut output);
3229
output
3330
}
3431
}

src/fiat_shamir.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ where
7070
}
7171
}
7272

73+
pub fn from_iv(iv: [u8; 32], instance: P) -> Self {
74+
let hash_state = C::from_iv(iv);
75+
Self {
76+
hash_state,
77+
ip: instance,
78+
}
79+
}
80+
7381
/// Generates a non-interactive proof for a witness.
7482
///
7583
/// Executes the interactive protocol steps (commit, derive challenge via hash, respond),

src/tests/spec/test_vectors.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,12 @@ type NISigmaP = NISigmaProtocol<SigmaP, Codec>;
2222
macro_rules! generate_ni_function {
2323
($name:ident, $test_fn:ident, $($param:tt),*) => {
2424
#[allow(non_snake_case)]
25-
fn $name(seed: &[u8], context: &[u8]) -> (Vec<Scalar>, Vec<u8>) {
25+
fn $name(seed: &[u8], iv: [u8; 32]) -> (Vec<Scalar>, Vec<u8>) {
2626
let mut rng = TestDRNG::new(seed);
27-
let (morphismp, witness) = $test_fn($(generate_ni_function!(@arg rng, $param)),*);
27+
let (instance, witness) = $test_fn($(generate_ni_function!(@arg rng, $param)),*);
2828

29-
let protocol = SchnorrProtocolCustom(morphismp);
30-
let domain_sep: Vec<u8> = context.to_vec();
31-
let nizk = NISigmaP::new(&domain_sep, protocol);
29+
let protocol = SchnorrProtocolCustom(instance);
30+
let nizk = NISigmaP::from_iv(iv, protocol);
3231

3332
let proof_bytes = nizk.prove_batchable(&witness, &mut rng).unwrap();
3433
let verified = nizk.verify_batchable(&proof_bytes).is_ok();
@@ -73,11 +72,11 @@ generate_ni_function!(
7372
#[test]
7473
fn sage_test_vectors() {
7574
let seed = b"hello world";
76-
let context = b"yellow submarineyellow submarine";
75+
let iv = *b"yellow submarineyellow submarine";
7776

7877
let vectors = extract_vectors("src/tests/spec/allVectors.json").unwrap();
7978

80-
let functions: [fn(&[u8], &[u8]) -> (Vec<Scalar>, Vec<u8>); 5] = [
79+
let functions: [fn(&[u8], [u8; 32]) -> (Vec<Scalar>, Vec<u8>); 5] = [
8180
NI_discrete_logarithm,
8281
NI_dleq,
8382
NI_pedersen_commitment,
@@ -86,10 +85,10 @@ fn sage_test_vectors() {
8685
];
8786

8887
for (i, f) in functions.iter().enumerate() {
89-
let (_, proof_bytes) = f(seed, context);
88+
let (_, proof_bytes) = f(seed, iv);
9089
assert_eq!(
91-
context.to_vec(),
92-
vectors[i].0,
90+
iv.as_slice(),
91+
vectors[i].0.as_slice(),
9392
"context for test vector {i} does not match"
9493
);
9594
assert_eq!(

0 commit comments

Comments
 (0)