arnaucube
arnaucube/NovaPublic Self-hosted repository
Nova/src/provider/keccak.rs
src/provider/keccak.rsRaw
  1//! This module provides an implementation of TranscriptEngineTrait using keccak256
  2use crate::traits::PrimeFieldExt;
  3use crate::{
  4  errors::NovaError,
  5  traits::{Group, TranscriptEngineTrait, TranscriptReprTrait},
  6};
  7use core::marker::PhantomData;
  8use sha3::{Digest, Keccak256};
  9
 10const PERSONA_TAG: &[u8] = b"NoTR";
 11const DOM_SEP_TAG: &[u8] = b"NoDS";
 12const KECCAK256_STATE_SIZE: usize = 64;
 13const KECCAK256_PREFIX_CHALLENGE_LO: u8 = 0;
 14const KECCAK256_PREFIX_CHALLENGE_HI: u8 = 1;
 15
 16/// Provides an implementation of TranscriptEngine
 17#[derive(Debug, Clone)]
 18pub struct Keccak256Transcript<G: Group> {
 19  round: u16,
 20  state: [u8; KECCAK256_STATE_SIZE],
 21  transcript: Keccak256,
 22  _p: PhantomData<G>,
 23}
 24
 25fn compute_updated_state(keccak_instance: Keccak256, input: &[u8]) -> [u8; KECCAK256_STATE_SIZE] {
 26  let mut updated_instance = keccak_instance;
 27  updated_instance.update(input);
 28
 29  let input_lo = &[KECCAK256_PREFIX_CHALLENGE_LO];
 30  let input_hi = &[KECCAK256_PREFIX_CHALLENGE_HI];
 31
 32  let mut hasher_lo = updated_instance.clone();
 33  let mut hasher_hi = updated_instance;
 34
 35  hasher_lo.update(input_lo);
 36  hasher_hi.update(input_hi);
 37
 38  let output_lo = hasher_lo.finalize();
 39  let output_hi = hasher_hi.finalize();
 40
 41  [output_lo, output_hi]
 42    .concat()
 43    .as_slice()
 44    .try_into()
 45    .unwrap()
 46}
 47
 48impl<G: Group> TranscriptEngineTrait<G> for Keccak256Transcript<G> {
 49  fn new(label: &'static [u8]) -> Self {
 50    let keccak_instance = Keccak256::new();
 51    let input = [PERSONA_TAG, label].concat();
 52    let output = compute_updated_state(keccak_instance.clone(), &input);
 53
 54    Self {
 55      round: 0u16,
 56      state: output,
 57      transcript: keccak_instance,
 58      _p: Default::default(),
 59    }
 60  }
 61
 62  fn squeeze(&mut self, label: &'static [u8]) -> Result<G::Scalar, NovaError> {
 63    // we gather the full input from the round, preceded by the current state of the transcript
 64    let input = [
 65      DOM_SEP_TAG,
 66      self.round.to_le_bytes().as_ref(),
 67      self.state.as_ref(),
 68      label,
 69    ]
 70    .concat();
 71    let output = compute_updated_state(self.transcript.clone(), &input);
 72
 73    // update state
 74    self.round = {
 75      if let Some(v) = self.round.checked_add(1) {
 76        v
 77      } else {
 78        return Err(NovaError::InternalTranscriptError);
 79      }
 80    };
 81    self.state.copy_from_slice(&output);
 82    self.transcript = Keccak256::new();
 83
 84    // squeeze out a challenge
 85    Ok(G::Scalar::from_uniform(&output))
 86  }
 87
 88  fn absorb<T: TranscriptReprTrait<G>>(&mut self, label: &'static [u8], o: &T) {
 89    self.transcript.update(label);
 90    self.transcript.update(&o.to_transcript_bytes());
 91  }
 92
 93  fn dom_sep(&mut self, bytes: &'static [u8]) {
 94    self.transcript.update(DOM_SEP_TAG);
 95    self.transcript.update(bytes);
 96  }
 97}
 98
 99#[cfg(test)]
100mod tests {
101  use crate::{
102    provider::bn256_grumpkin::bn256,
103    provider::keccak::Keccak256Transcript,
104    traits::{Group, PrimeFieldExt, TranscriptEngineTrait, TranscriptReprTrait},
105  };
106  use ff::PrimeField;
107  use rand::Rng;
108  use sha3::{Digest, Keccak256};
109
110  fn test_keccak_transcript_with<G: Group>(expected_h1: &'static str, expected_h2: &'static str) {
111    let mut transcript: Keccak256Transcript<G> = Keccak256Transcript::new(b"test");
112
113    // two scalars
114    let s1 = <G as Group>::Scalar::from(2u64);
115    let s2 = <G as Group>::Scalar::from(5u64);
116
117    // add the scalars to the transcript
118    transcript.absorb(b"s1", &s1);
119    transcript.absorb(b"s2", &s2);
120
121    // make a challenge
122    let c1: <G as Group>::Scalar = transcript.squeeze(b"c1").unwrap();
123    assert_eq!(hex::encode(c1.to_repr().as_ref()), expected_h1);
124
125    // a scalar
126    let s3 = <G as Group>::Scalar::from(128u64);
127
128    // add the scalar to the transcript
129    transcript.absorb(b"s3", &s3);
130
131    // make a challenge
132    let c2: <G as Group>::Scalar = transcript.squeeze(b"c2").unwrap();
133    assert_eq!(hex::encode(c2.to_repr().as_ref()), expected_h2);
134  }
135
136  #[test]
137  fn test_keccak_transcript() {
138    test_keccak_transcript_with::<pasta_curves::pallas::Point>(
139      "5ddffa8dc091862132788b8976af88b9a2c70594727e611c7217ba4c30c8c70a",
140      "4d4bf42c065870395749fa1c4fb641df1e0d53f05309b03d5b1db7f0be3aa13d",
141    );
142
143    test_keccak_transcript_with::<bn256::Point>(
144      "9fb71e3b74bfd0b60d97349849b895595779a240b92a6fae86bd2812692b6b0e",
145      "bfd4c50b7d6317e9267d5d65c985eb455a3561129c0b3beef79bfc8461a84f18",
146    );
147  }
148
149  #[test]
150  fn test_keccak_example() {
151    let mut hasher = Keccak256::new();
152    hasher.update(0xffffffff_u32.to_le_bytes());
153    let output: [u8; 32] = hasher.finalize().try_into().unwrap();
154    assert_eq!(
155      hex::encode(output),
156      "29045a592007d0c246ef02c2223570da9522d0cf0f73282c79a1bc8f0bb2c238"
157    );
158  }
159
160  use super::{
161    DOM_SEP_TAG, KECCAK256_PREFIX_CHALLENGE_HI, KECCAK256_PREFIX_CHALLENGE_LO,
162    KECCAK256_STATE_SIZE, PERSONA_TAG,
163  };
164
165  fn compute_updated_state_for_testing(input: &[u8]) -> [u8; KECCAK256_STATE_SIZE] {
166    let input_lo = [input, &[KECCAK256_PREFIX_CHALLENGE_LO]].concat();
167    let input_hi = [input, &[KECCAK256_PREFIX_CHALLENGE_HI]].concat();
168
169    let mut hasher_lo = Keccak256::new();
170    let mut hasher_hi = Keccak256::new();
171
172    hasher_lo.update(&input_lo);
173    hasher_hi.update(&input_hi);
174
175    let output_lo = hasher_lo.finalize();
176    let output_hi = hasher_hi.finalize();
177
178    [output_lo, output_hi]
179      .concat()
180      .as_slice()
181      .try_into()
182      .unwrap()
183  }
184
185  fn squeeze_for_testing(
186    transcript: &[u8],
187    round: u16,
188    state: [u8; KECCAK256_STATE_SIZE],
189    label: &'static [u8],
190  ) -> [u8; 64] {
191    let input = [
192      transcript,
193      DOM_SEP_TAG,
194      round.to_le_bytes().as_ref(),
195      state.as_ref(),
196      label,
197    ]
198    .concat();
199    compute_updated_state_for_testing(&input)
200  }
201
202  // This test is meant to ensure compatibility between the incremental way of computing the transcript above, and
203  // the former, which materialized the entirety of the input vector before calling Keccak256 on it.
204  fn test_keccak_transcript_incremental_vs_explicit_with<G: Group>() {
205    let test_label = b"test";
206    let mut transcript: Keccak256Transcript<G> = Keccak256Transcript::new(test_label);
207    let mut rng = rand::thread_rng();
208
209    // ten scalars
210    let scalars = std::iter::from_fn(|| Some(<G as Group>::Scalar::from(rng.gen::<u64>())))
211      .take(10)
212      .collect::<Vec<_>>();
213
214    // add the scalars to the transcripts,
215    let mut manual_transcript: Vec<u8> = vec![];
216    let labels = [
217      b"s1", b"s2", b"s3", b"s4", b"s5", b"s6", b"s7", b"s8", b"s9", b"s0",
218    ];
219
220    for i in 0..10 {
221      transcript.absorb(&labels[i][..], &scalars[i]);
222      manual_transcript.extend(labels[i]);
223      manual_transcript.extend(scalars[i].to_transcript_bytes());
224    }
225
226    // compute the initial state
227    let input = [PERSONA_TAG, test_label].concat();
228    let initial_state = compute_updated_state_for_testing(&input);
229
230    // make a challenge
231    let c1: <G as Group>::Scalar = transcript.squeeze(b"c1").unwrap();
232
233    let c1_bytes = squeeze_for_testing(&manual_transcript[..], 0u16, initial_state, b"c1");
234    let to_hex = |g: G::Scalar| hex::encode(g.to_repr().as_ref());
235    assert_eq!(to_hex(c1), to_hex(G::Scalar::from_uniform(&c1_bytes)));
236  }
237
238  #[test]
239  fn test_keccak_transcript_incremental_vs_explicit() {
240    test_keccak_transcript_incremental_vs_explicit_with::<pasta_curves::pallas::Point>();
241    test_keccak_transcript_incremental_vs_explicit_with::<bn256::Point>();
242  }
243}