Skip to content

Commit 0911345

Browse files
committed
Creation of KeccakTranscript, which is completely separate from ShakeTranscript, both of which implement TranscriptCodec and are therefore usable in NISigmaProtocol.
- KeccakTranscript aims to approximate the test vectors of the Sage implementation.
1 parent e6851da commit 0911345

File tree

12 files changed

+265
-27
lines changed

12 files changed

+265
-27
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ sha2 = "0.10"
3838
subtle = "2.6.1"
3939
num-bigint = "0.4.6"
4040
num-traits = "0.2.19"
41+
tiny-keccak = { version = "2.0.2", features = ["keccak"] }
4142

4243
[dev-dependencies]
4344
bincode = "1"

src/toolbox/sigma/sage_test/BLS12_381.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use crate::toolbox::sigma::sage_test::{SInput, SRandom};
1+
use crate::toolbox::sigma::{sage_test::{SInput, SRandom}, transcript::keccak_transcript::Modulable};
22
use group::Group;
33
use ff::PrimeField;
4-
use bls12_381::G1Projective;
4+
use bls12_381::{G1Projective, Scalar};
55
use rand::{Rng, CryptoRng};
66
use subtle::CtOption;
77
use num_bigint::BigUint;
@@ -64,4 +64,10 @@ impl SRandom for G1Projective {
6464
}
6565
G1Projective::scalar_from_hex_be(&hex_string).unwrap()
6666
}
67+
}
68+
69+
impl Modulable for Scalar {
70+
fn cardinal() -> BigUint {
71+
BigUint::parse_bytes(b"111001111101101101001110101001100101001100111010111110101001000001100110011100111011000000010000000100110100001110110000000010101010011101111011010010000000010111111111111111001011011111111101111111111111111111111111111111100000000000000000000000000000001", 2).unwrap()
72+
}
6773
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
use tiny_keccak::keccakf;
2+
use std::convert::TryInto;
3+
use group::{Group, GroupEncoding};
4+
use ff::PrimeField;
5+
use crate::toolbox::sigma::transcript::r#trait::{TranscriptCodec, DuplexSpongeInterface};
6+
use crate::toolbox::sigma::GroupSerialisation;
7+
use num_bigint::BigUint;
8+
9+
const R: usize = 136;
10+
const N: usize = 136 + 64;
11+
12+
pub struct KeccakPermutationState {
13+
pub state: [u8; 200],
14+
pub rate: usize,
15+
pub capacity: usize,
16+
}
17+
18+
impl KeccakPermutationState {
19+
pub fn new() -> Self {
20+
KeccakPermutationState {
21+
state: [0u8; 200],
22+
rate: 136,
23+
capacity: 64,
24+
}
25+
}
26+
27+
fn _bytes_to_keccak_state(&self) -> [[u64; 5]; 5] {
28+
let mut flat: [u64; 25] = [0u64; 25];
29+
for i in 0..25 {
30+
let start = i * 8;
31+
flat[i] = u64::from_le_bytes(self.state[start..start + 8].try_into().unwrap());
32+
}
33+
let mut matrix = [[0u64; 5]; 5];
34+
for y in 0..5 {
35+
for x in 0..5 {
36+
matrix[x][y] = flat[5 * y + x];
37+
}
38+
}
39+
matrix
40+
}
41+
42+
fn _keccak_state_to_bytes(&mut self, state: [[u64; 5]; 5]) {
43+
let mut flat: [u64; 25] = [0; 25];
44+
for y in 0..5 {
45+
for x in 0..5 {
46+
flat[5 * y + x] = state[x][y];
47+
}
48+
}
49+
for i in 0..25 {
50+
let bytes = flat[i].to_le_bytes();
51+
let start = i * 8;
52+
self.state[start..start + 8].copy_from_slice(&bytes);
53+
}
54+
}
55+
56+
fn bytes_to_flat_state(&self) -> [u64; 25] {
57+
let mut flat = [0u64; 25];
58+
for i in 0..25 {
59+
let start = i * 8;
60+
flat[i] = u64::from_le_bytes(self.state[start..start + 8].try_into().unwrap());
61+
}
62+
flat
63+
}
64+
65+
fn flat_state_to_bytes(&mut self, flat: [u64; 25]) {
66+
for i in 0..25 {
67+
let bytes = flat[i].to_le_bytes();
68+
let start = i * 8;
69+
self.state[start..start + 8].copy_from_slice(&bytes);
70+
}
71+
}
72+
73+
pub fn permute(&mut self) {
74+
let mut flat = self.bytes_to_flat_state();
75+
keccakf(&mut flat);
76+
self.flat_state_to_bytes(flat);
77+
}
78+
}
79+
80+
pub struct KeccakDuplexSponge {
81+
pub state: KeccakPermutationState,
82+
pub rate: usize,
83+
pub capacity: usize,
84+
absorb_index: usize,
85+
squeeze_index: usize,
86+
}
87+
88+
impl KeccakDuplexSponge {
89+
pub fn new(iv: &[u8]) -> Self {
90+
assert_eq!(iv.len(), 32);
91+
let state = KeccakPermutationState::new();
92+
let rate = R;
93+
let capacity = N - R;
94+
KeccakDuplexSponge {
95+
state,
96+
rate,
97+
capacity,
98+
absorb_index: 0,
99+
squeeze_index: 0,
100+
}
101+
}
102+
}
103+
104+
impl DuplexSpongeInterface for KeccakDuplexSponge {
105+
fn new(iv: &[u8]) -> Self {
106+
KeccakDuplexSponge::new(iv)
107+
}
108+
109+
fn absorb(&mut self, mut input: &[u8]) {
110+
self.squeeze_index = self.rate;
111+
112+
while !input.is_empty() {
113+
if self.absorb_index == self.rate {
114+
self.state.permute();
115+
self.absorb_index = 0;
116+
}
117+
118+
let chunk_size = usize::min(self.rate - self.absorb_index, input.len());
119+
let dest = &mut self.state.state[self.absorb_index..self.absorb_index + chunk_size];
120+
dest.copy_from_slice(&input[..chunk_size]);
121+
self.absorb_index += chunk_size;
122+
input = &input[chunk_size..];
123+
}
124+
}
125+
126+
fn squeeze(&mut self, mut length: usize) -> Vec<u8> {
127+
self.absorb_index = self.rate;
128+
129+
let mut output = Vec::new();
130+
while length != 0 {
131+
if self.squeeze_index == self.rate {
132+
self.state.permute();
133+
self.squeeze_index = 0;
134+
}
135+
136+
let chunk_size = usize::min(self.rate - self.squeeze_index, length);
137+
output.extend_from_slice(&self.state.state[self.squeeze_index..self.squeeze_index + chunk_size]);
138+
self.squeeze_index += chunk_size;
139+
length -= chunk_size;
140+
}
141+
142+
output
143+
}
144+
}
145+
146+
pub trait Modulable: PrimeField {
147+
fn cardinal() -> BigUint;
148+
}
149+
150+
pub struct ByteSchnorrCodec<G, H>
151+
where
152+
G: Group + GroupEncoding + GroupSerialisation,
153+
G::Scalar: Modulable,
154+
H: DuplexSpongeInterface
155+
{
156+
order: BigUint,
157+
hasher: H,
158+
_marker: core::marker::PhantomData<G>,
159+
}
160+
161+
impl<G, H> TranscriptCodec<G> for ByteSchnorrCodec<G, H>
162+
where
163+
G: Group + GroupEncoding + GroupSerialisation,
164+
G::Scalar: Modulable,
165+
H: DuplexSpongeInterface
166+
{
167+
fn new(domain_sep: &[u8]) -> Self {
168+
let mut hasher = H::new(domain_sep);
169+
let order = G::Scalar::cardinal();
170+
Self { order, hasher, _marker: Default::default() }
171+
}
172+
173+
fn prover_message(&mut self, elems: &[G]) -> &mut Self {
174+
for elem in elems {
175+
self.hasher.absorb(&G::serialize_element(elem));
176+
}
177+
self
178+
}
179+
180+
fn verifier_challenge(&mut self) -> G::Scalar {
181+
let scalar_byte_length = <<G as Group>::Scalar as PrimeField>::Repr::default().as_ref().len();
182+
183+
let uniform_bytes = self.hasher.squeeze(scalar_byte_length + 16);
184+
println!("big : {:?}", &self.order);
185+
let scalar = BigUint::from_bytes_be(&uniform_bytes);
186+
let reduced = scalar % self.order.clone();
187+
188+
let mut bytes = vec![0u8; scalar_byte_length];
189+
let reduced_bytes = reduced.to_bytes_be();
190+
let start = bytes.len() - reduced_bytes.len();
191+
bytes[start..].copy_from_slice(&reduced_bytes);
192+
193+
let mut repr = <<G as Group>::Scalar as PrimeField>::Repr::default();
194+
repr.as_mut().copy_from_slice(&bytes);
195+
196+
<<G as Group>::Scalar as PrimeField>::from_repr(repr).expect("Error")
197+
}
198+
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
pub mod r#trait;
2-
pub mod transcriptcodec;
2+
pub mod shake_transcript;
3+
pub mod keccak_transcript;
34

45
pub use r#trait::TranscriptCodec;
5-
pub use transcriptcodec::KeccakTranscript;
6+
pub use shake_transcript::ShakeTranscript;
7+
pub use keccak_transcript::{KeccakDuplexSponge, Modulable, ByteSchnorrCodec};

src/toolbox/sigma/transcript/transcriptcodec.rs renamed to src/toolbox/sigma/transcript/shake_transcript.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ use crate::toolbox::sigma::transcript::r#trait::TranscriptCodec;
2424
///
2525
/// The transcript is initialized with a domain separator and absorbs serialized
2626
/// group elements. It outputs challenges compatible with the group’s scalar field.
27-
pub struct KeccakTranscript<G: Group> {
27+
pub struct ShakeTranscript<G: Group> {
2828
/// Internal SHAKE128 hasher state.
2929
hasher: Shake128,
3030
/// Marker to bind this transcript to a specific group `G`.
3131
_marker: core::marker::PhantomData<G>,
3232
}
3333

34-
impl<G> TranscriptCodec<G> for KeccakTranscript<G>
34+
impl<G> TranscriptCodec<G> for ShakeTranscript<G>
3535
where
3636
G: Group + GroupEncoding,
3737
G::Scalar: PrimeField,

src/toolbox/sigma/transcript/trait.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44
55
use group::Group;
66

7+
pub trait DuplexSpongeInterface {
8+
fn new(iv: &[u8]) -> Self;
9+
10+
fn absorb(&mut self, input: &[u8]);
11+
12+
fn squeeze(&mut self, length: usize) -> Vec<u8>;
13+
}
714
/// A trait defining the behavior of a domain-separated transcript hashing, which is typically used for Sigma Protocols.
815
///
916
/// A domain-separated hashing transcript is a transcript, identified by a domain, which is incremented with successive messages ("absorb"). The transcript can then output a bit stream of any length, which is typically used to generate a challenge unique to the given transcript ("squeeze"). (See Sponge Construction).

tests/interactive_codec.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use rand::rngs::OsRng;
22
use curve25519_dalek::ristretto::RistrettoPoint;
33

4-
use sigma_rs::toolbox::sigma::transcript::{r#trait::TranscriptCodec, transcriptcodec::KeccakTranscript};
4+
use sigma_rs::toolbox::sigma::transcript::{r#trait::TranscriptCodec, shake_transcript::ShakeTranscript};
55

6-
pub type KeccakTranscriptRistretto = KeccakTranscript<curve25519_dalek::ristretto::RistrettoPoint>;
6+
pub type KeccakTranscriptRistretto = ShakeTranscript<curve25519_dalek::ristretto::RistrettoPoint>;
77

88
#[allow(non_snake_case)]
99
#[test]

tests/non_interactive_protocol.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use curve25519_dalek::scalar::Scalar;
44

55
use sigma_rs::toolbox::sigma::group_morphism::GroupMorphismPreimage;
66
use sigma_rs::toolbox::sigma::schnorr_proof::SchnorrProof;
7-
use sigma_rs::toolbox::sigma::transcript::transcriptcodec::KeccakTranscript;
7+
use sigma_rs::toolbox::sigma::transcript::shake_transcript::ShakeTranscript;
88
use sigma_rs::toolbox::sigma::fiat_shamir::NISigmaProtocol;
99

1010
type G = RistrettoPoint;
@@ -39,7 +39,7 @@ fn fiat_shamir_schnorr_proof_ristretto() {
3939
let protocol = SchnorrProof { morphismp };
4040

4141
// Fiat-Shamir wrapper
42-
let mut nizk = NISigmaProtocol::<SchnorrProof<G>, KeccakTranscript<G>, G>::new(domain_sep, protocol);
42+
let mut nizk = NISigmaProtocol::<SchnorrProof<G>, ShakeTranscript<G>, G>::new(domain_sep, protocol);
4343

4444
// Prove
4545
let proof_bytes = nizk.prove(&witness, &mut rng);

tests/sage_test_vectors.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use bls12_381::G1Projective;
22
use rand::{Rng, CryptoRng};
33
use group::{Group, GroupEncoding, ff::Field};
4-
use sigma_rs::toolbox::sigma::sage_test::TestDRNG;
4+
use sigma_rs::toolbox::sigma::sage_test::{SRandom, TestDRNG};
55
use sigma_rs::toolbox::sigma::sage_test::custom_schnorr_proof::SchnorrProofCustom;
66

7+
use sigma_rs::toolbox::sigma::transcript::KeccakDuplexSponge;
78
use sigma_rs::toolbox::sigma::{
89
GroupMorphismPreimage,
9-
transcript::KeccakTranscript,
10+
transcript::{ShakeTranscript, ByteSchnorrCodec},
1011
NISigmaProtocol,
1112
};
1213

@@ -22,7 +23,7 @@ fn msm_pr<G: Group>(scalars: &[G::Scalar], bases: &[G]) -> G {
2223

2324

2425
#[allow(non_snake_case)]
25-
fn discrete_logarithm<G: Group + GroupEncoding>(
26+
fn discrete_logarithm<G: SRandom + Group + GroupEncoding>(
2627
rng: &mut (impl Rng + CryptoRng)
2728
) -> (GroupMorphismPreimage<G>, Vec<G::Scalar>) {
2829
let mut morphismp: GroupMorphismPreimage<G> = GroupMorphismPreimage::new();
@@ -36,7 +37,7 @@ fn discrete_logarithm<G: Group + GroupEncoding>(
3637
let G = G::generator();
3738
morphismp.set_elements(&[(var_G, G)]);
3839

39-
let x = G::Scalar::random(&mut *rng);
40+
let x = G::srandom(&mut *rng);
4041
let X = G * x;
4142
assert!(vec![X] == morphismp.morphism.evaluate(&[x]));
4243
morphismp.set_elements(&[(var_X, X)]);
@@ -173,14 +174,14 @@ fn bbs_blind_commitment_computation<G: Group + GroupEncoding>(
173174
#[allow(non_snake_case)]
174175
#[test]
175176
fn NI_discrete_logarithm() {
176-
let mut rng = TestDRNG::new(b"79656c6c6f77207375626d6172696e6579656c6c6f77207375626d6172696e65");
177+
let mut rng = TestDRNG::new(b"hello world");
177178
let (morphismp, witness) = discrete_logarithm::<Gp>(&mut rng);
178179

179180
println!("witness: {:?}", witness);
180181

181182
let protocol = SchnorrProofCustom { morphismp };
182183
let domain_sep: Vec<u8> = b"yellow submarineyellow submarine".to_vec();
183-
let mut nizk = NISigmaProtocol::<SchnorrProofCustom<Gp>, KeccakTranscript<Gp>, Gp>::new(&domain_sep, protocol);
184+
let mut nizk = NISigmaProtocol::<SchnorrProofCustom<Gp>, ByteSchnorrCodec::<Gp, KeccakDuplexSponge>, Gp>::new(&domain_sep, protocol);
184185

185186
let proof_bytes = nizk.prove(&witness, &mut rng);
186187
let verified = nizk.verify(&proof_bytes).is_ok();
@@ -198,7 +199,7 @@ fn NI_dleq() {
198199

199200
let protocol = SchnorrProofCustom { morphismp };
200201
let domain_sep: Vec<u8> = b"yellow submarineyellow submarine".to_vec();
201-
let mut nizk = NISigmaProtocol::<SchnorrProofCustom<Gp>, KeccakTranscript<Gp>, Gp>::new(&domain_sep, protocol);
202+
let mut nizk = NISigmaProtocol::<SchnorrProofCustom<Gp>, ShakeTranscript<Gp>, Gp>::new(&domain_sep, protocol);
202203

203204
let proof_bytes = nizk.prove(&witness, &mut rng);
204205
let verified = nizk.verify(&proof_bytes).is_ok();
@@ -216,7 +217,7 @@ fn NI_pedersen_commitment() {
216217

217218
let protocol = SchnorrProofCustom { morphismp };
218219
let domain_sep: Vec<u8> = b"yellow submarineyellow submarine".to_vec();
219-
let mut nizk = NISigmaProtocol::<SchnorrProofCustom<Gp>, KeccakTranscript<Gp>, Gp>::new(&domain_sep, protocol);
220+
let mut nizk = NISigmaProtocol::<SchnorrProofCustom<Gp>, ShakeTranscript<Gp>, Gp>::new(&domain_sep, protocol);
220221

221222
let proof_bytes = nizk.prove(&witness, &mut rng);
222223
let verified = nizk.verify(&proof_bytes).is_ok();
@@ -234,7 +235,7 @@ fn NI_pedersen_commitment_dleq() {
234235

235236
let protocol = SchnorrProofCustom { morphismp };
236237
let domain_sep: Vec<u8> = b"yellow submarineyellow submarine".to_vec();
237-
let mut nizk = NISigmaProtocol::<SchnorrProofCustom<Gp>, KeccakTranscript<Gp>, Gp>::new(&domain_sep, protocol);
238+
let mut nizk = NISigmaProtocol::<SchnorrProofCustom<Gp>, ShakeTranscript<Gp>, Gp>::new(&domain_sep, protocol);
238239

239240
let proof_bytes = nizk.prove(&witness, &mut rng);
240241
let verified = nizk.verify(&proof_bytes).is_ok();
@@ -252,7 +253,7 @@ fn NI_bbs_blind_commitment_computation() {
252253

253254
let protocol = SchnorrProofCustom { morphismp };
254255
let domain_sep: Vec<u8> = b"yellow submarineyellow submarine".to_vec();
255-
let mut nizk = NISigmaProtocol::<SchnorrProofCustom<Gp>, KeccakTranscript<Gp>, Gp>::new(&domain_sep, protocol);
256+
let mut nizk = NISigmaProtocol::<SchnorrProofCustom<Gp>, ShakeTranscript<Gp>, Gp>::new(&domain_sep, protocol);
256257

257258
let proof_bytes = nizk.prove(&witness, &mut rng);
258259
let verified = nizk.verify(&proof_bytes).is_ok();

0 commit comments

Comments
 (0)