use crate::{
errors::NovaError,
traits::{Engine, PrimeFieldExt, TranscriptEngineTrait, TranscriptReprTrait},
};
use core::marker::PhantomData;
use ff::PrimeField;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use sha3::{Digest, Keccak256};
const PERSONA_TAG: &[u8] = b"NoTR";
const DOM_SEP_TAG: &[u8] = b"NoDS";
const KECCAK256_STATE_SIZE: usize = 64;
const KECCAK256_PREFIX_CHALLENGE_LO: u8 = 0;
const KECCAK256_PREFIX_CHALLENGE_HI: u8 = 1;
#[derive(Debug, Clone)]
pub struct Keccak256Transcript<E: Engine> {
round: u16,
state: [u8; KECCAK256_STATE_SIZE],
transcript: Keccak256,
transcript_buffer: Vec<u8>,
_p: PhantomData<E>,
}
impl<E: Engine> Serialize for Keccak256Transcript<E> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeStruct;
let mut s = serializer.serialize_struct("Keccak256Transcript", 3)?;
s.serialize_field("round", &self.round)?;
s.serialize_field("state", &self.state.as_slice())?;
s.serialize_field("transcript_buffer", &self.transcript_buffer)?;
s.end()
}
}
impl<'de, E: Engine> Deserialize<'de> for Keccak256Transcript<E> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
struct Helper {
round: u16,
state: Vec<u8>,
#[serde(default)]
transcript_buffer: Vec<u8>,
}
let h = Helper::deserialize(deserializer)?;
let state: [u8; KECCAK256_STATE_SIZE] = h
.state
.try_into()
.map_err(|_| serde::de::Error::custom("invalid state length"))?;
let mut transcript = Keccak256::new();
transcript.update(&h.transcript_buffer);
Ok(Keccak256Transcript {
round: h.round,
state,
transcript,
transcript_buffer: h.transcript_buffer,
_p: PhantomData,
})
}
}
fn compute_updated_state(keccak_instance: Keccak256, input: &[u8]) -> [u8; KECCAK256_STATE_SIZE] {
let mut updated_instance = keccak_instance;
updated_instance.update(input);
let input_lo = &[KECCAK256_PREFIX_CHALLENGE_LO];
let input_hi = &[KECCAK256_PREFIX_CHALLENGE_HI];
let mut hasher_lo = updated_instance.clone();
let mut hasher_hi = updated_instance;
hasher_lo.update(input_lo);
hasher_hi.update(input_hi);
let output_lo = hasher_lo.finalize();
let output_hi = hasher_hi.finalize();
#[cfg(not(feature = "evm"))]
return [output_lo, output_hi]
.concat()
.as_slice()
.try_into()
.unwrap();
#[cfg(feature = "evm")]
return [output_hi, output_lo]
.concat()
.as_slice()
.try_into()
.unwrap();
}
impl<E: Engine> Keccak256Transcript<E> {
fn squeeze_raw(&mut self, label: &'static [u8]) -> Result<[u8; KECCAK256_STATE_SIZE], NovaError> {
#[cfg(not(feature = "evm"))]
let round_bytes = self.round.to_le_bytes();
#[cfg(feature = "evm")]
let round_bytes = self.round.to_be_bytes();
let input = [
DOM_SEP_TAG,
round_bytes.as_ref(),
self.state.as_ref(),
label,
]
.concat();
#[allow(unused_mut)]
let mut output = compute_updated_state(self.transcript.clone(), &input);
self.round = self
.round
.checked_add(1)
.ok_or(NovaError::InternalTranscriptError)?;
self.state.copy_from_slice(&output);
self.transcript = Keccak256::new();
self.transcript_buffer.clear();
#[cfg(feature = "evm")]
output.reverse();
Ok(output)
}
}
impl<E: Engine> TranscriptEngineTrait<E> for Keccak256Transcript<E> {
fn new(label: &'static [u8]) -> Self {
let keccak_instance = Keccak256::new();
let input = [PERSONA_TAG, label].concat();
let output = compute_updated_state(keccak_instance.clone(), &input);
Self {
round: 0u16,
state: output,
transcript: keccak_instance,
transcript_buffer: Vec::new(),
_p: PhantomData,
}
}
fn squeeze(&mut self, label: &'static [u8]) -> Result<E::Scalar, NovaError> {
let output = self.squeeze_raw(label)?;
Ok(E::Scalar::from_uniform(&output))
}
fn absorb<T: TranscriptReprTrait<E::GE>>(&mut self, label: &'static [u8], o: &T) {
self.transcript.update(label);
self.transcript_buffer.extend_from_slice(label);
let repr = o.to_transcript_bytes();
self.transcript.update(&repr);
self.transcript_buffer.extend_from_slice(&repr);
}
fn dom_sep(&mut self, bytes: &'static [u8]) {
self.transcript.update(DOM_SEP_TAG);
self.transcript_buffer.extend_from_slice(DOM_SEP_TAG);
self.transcript.update(bytes);
self.transcript_buffer.extend_from_slice(bytes);
}
fn squeeze_bits(
&mut self,
label: &'static [u8],
num_bits: usize,
start_with_one: bool,
) -> Result<E::Scalar, NovaError> {
assert!(num_bits >= 2);
assert!(
num_bits <= (E::Scalar::NUM_BITS - 1) as usize,
"num_bits must be < field bit-width to avoid overflow when setting MSB"
);
let output = self.squeeze_raw(label)?;
let mut repr = <E::Scalar as PrimeField>::Repr::default();
let repr_bytes = repr.as_mut();
let num_full_bytes = num_bits / 8;
let remaining_bits = num_bits % 8;
repr_bytes[..num_full_bytes].copy_from_slice(&output[..num_full_bytes]);
if remaining_bits > 0 {
repr_bytes[num_full_bytes] = output[num_full_bytes] & ((1u8 << remaining_bits) - 1);
}
if start_with_one {
let msb_byte = (num_bits - 1) / 8;
let msb_bit = (num_bits - 1) % 8;
repr_bytes[msb_byte] |= 1u8 << msb_bit;
}
Ok(E::Scalar::from_repr(repr).unwrap())
}
}
#[cfg(test)]
mod tests {
use crate::{
provider::{
keccak::Keccak256Transcript, Bn256EngineKZG, GrumpkinEngine, PallasEngine, Secp256k1Engine,
Secq256k1Engine, VestaEngine,
},
traits::{Engine, PrimeFieldExt, TranscriptEngineTrait, TranscriptReprTrait},
};
use ff::PrimeField;
use rand::Rng;
use sha3::{Digest, Keccak256};
fn test_keccak_transcript_with<E: Engine>(expected_h1: &'static str, expected_h2: &'static str) {
let mut transcript: Keccak256Transcript<E> = Keccak256Transcript::new(b"test");
let s1 = <E as Engine>::Scalar::from(2u64);
let s2 = <E as Engine>::Scalar::from(5u64);
transcript.absorb(b"s1", &s1);
transcript.absorb(b"s2", &s2);
let c1: <E as Engine>::Scalar = transcript.squeeze(b"c1").unwrap();
assert_eq!(hex::encode(c1.to_repr().as_ref()), expected_h1);
let s3 = <E as Engine>::Scalar::from(128u64);
transcript.absorb(b"s3", &s3);
let c2: <E as Engine>::Scalar = transcript.squeeze(b"c2").unwrap();
assert_eq!(hex::encode(c2.to_repr().as_ref()), expected_h2);
}
#[test]
#[cfg(not(feature = "evm"))]
fn test_keccak_transcript() {
test_keccak_transcript_with::<PallasEngine>(
"5ddffa8dc091862132788b8976af88b9a2c70594727e611c7217ba4c30c8c70a",
"4d4bf42c065870395749fa1c4fb641df1e0d53f05309b03d5b1db7f0be3aa13d",
);
test_keccak_transcript_with::<Bn256EngineKZG>(
"9fb71e3b74bfd0b60d97349849b895595779a240b92a6fae86bd2812692b6b0e",
"bfd4c50b7d6317e9267d5d65c985eb455a3561129c0b3beef79bfc8461a84f18",
);
test_keccak_transcript_with::<Secp256k1Engine>(
"9723aafb69ec8f0e9c7de756df0993247d98cf2b2f72fa353e3de654a177e310",
"a6a90fcb6e1b1a2a2f84c950ef1510d369aea8e42085f5c629bfa66d00255f25",
);
}
#[test]
#[cfg(feature = "evm")]
fn test_keccak_transcript() {
test_keccak_transcript_with::<PallasEngine>(
"cc8899e834968eae4269b223856518ab44d04bd381b95fda44969eedcd233710",
"21dd7314db7c10274ef62a25175704ac9dc86dd3be2e87cad2fe44f95ec4271b",
);
test_keccak_transcript_with::<Bn256EngineKZG>(
"4bcc2d614503c68d32df47c87c7698bfdebb71bcd9c76b5a463f806ff7bec212",
"d7ee88576da267c5c38cc39063e98c517aacc7581cdb6e5924b19de8218b8f01",
);
test_keccak_transcript_with::<Secp256k1Engine>(
"788f8ff3cf50d6d1509dd923abd427f16cb934721a5c8883c47cd1cc228a0e71",
"011ffa1afa7d3342100ec63101f6be02ed784e503e36f74f0e74a28b30dc85fb",
);
}
#[test]
fn test_keccak_example() {
let mut hasher = Keccak256::new();
hasher.update(0xffffffff_u32.to_le_bytes());
let output: [u8; 32] = hasher.finalize().into();
assert_eq!(
hex::encode(output),
"29045a592007d0c246ef02c2223570da9522d0cf0f73282c79a1bc8f0bb2c238"
);
}
use super::{
DOM_SEP_TAG, KECCAK256_PREFIX_CHALLENGE_HI, KECCAK256_PREFIX_CHALLENGE_LO,
KECCAK256_STATE_SIZE, PERSONA_TAG,
};
fn compute_updated_state_for_testing(input: &[u8]) -> [u8; KECCAK256_STATE_SIZE] {
let input_lo = [input, &[KECCAK256_PREFIX_CHALLENGE_LO]].concat();
let input_hi = [input, &[KECCAK256_PREFIX_CHALLENGE_HI]].concat();
let mut hasher_lo = Keccak256::new();
let mut hasher_hi = Keccak256::new();
hasher_lo.update(&input_lo);
hasher_hi.update(&input_hi);
let output_lo = hasher_lo.finalize();
let output_hi = hasher_hi.finalize();
#[cfg(not(feature = "evm"))]
return [output_lo, output_hi]
.concat()
.as_slice()
.try_into()
.unwrap();
#[cfg(feature = "evm")]
return [output_hi, output_lo]
.concat()
.as_slice()
.try_into()
.unwrap();
}
#[cfg(not(feature = "evm"))]
fn squeeze_for_testing(
transcript: &[u8],
round: u16,
state: [u8; KECCAK256_STATE_SIZE],
label: &'static [u8],
) -> [u8; 64] {
let input = [
transcript,
DOM_SEP_TAG,
round.to_le_bytes().as_ref(),
state.as_ref(),
label,
]
.concat();
compute_updated_state_for_testing(&input)
}
#[cfg(feature = "evm")]
fn squeeze_for_testing(
transcript: &[u8],
round: u16,
state: [u8; KECCAK256_STATE_SIZE],
label: &'static [u8],
) -> [u8; 64] {
let input = [
transcript,
DOM_SEP_TAG,
round.to_be_bytes().as_ref(),
state.as_ref(),
label,
]
.concat();
let mut output = compute_updated_state_for_testing(&input);
output.reverse();
output
}
fn test_keccak_transcript_incremental_vs_explicit_with<E: Engine>() {
let test_label = b"test";
let mut transcript: Keccak256Transcript<E> = Keccak256Transcript::new(test_label);
let mut rng = rand::thread_rng();
let scalars = std::iter::from_fn(|| Some(<E as Engine>::Scalar::from(rng.gen::<u64>())))
.take(10)
.collect::<Vec<_>>();
let mut manual_transcript: Vec<u8> = vec![];
let labels = [
b"s1", b"s2", b"s3", b"s4", b"s5", b"s6", b"s7", b"s8", b"s9", b"s0",
];
for i in 0..10 {
transcript.absorb(&labels[i][..], &scalars[i]);
manual_transcript.extend(labels[i]);
manual_transcript.extend(scalars[i].to_transcript_bytes());
}
let input = [PERSONA_TAG, test_label].concat();
let initial_state = compute_updated_state_for_testing(&input);
let c1: <E as Engine>::Scalar = transcript.squeeze(b"c1").unwrap();
let c1_bytes = squeeze_for_testing(&manual_transcript[..], 0u16, initial_state, b"c1");
let to_hex = |g: E::Scalar| hex::encode(g.to_repr().as_ref());
assert_eq!(to_hex(c1), to_hex(E::Scalar::from_uniform(&c1_bytes)));
}
#[test]
fn test_keccak_transcript_incremental_vs_explicit() {
test_keccak_transcript_incremental_vs_explicit_with::<PallasEngine>();
test_keccak_transcript_incremental_vs_explicit_with::<VestaEngine>();
test_keccak_transcript_incremental_vs_explicit_with::<Bn256EngineKZG>();
test_keccak_transcript_incremental_vs_explicit_with::<GrumpkinEngine>();
test_keccak_transcript_incremental_vs_explicit_with::<Secp256k1Engine>();
test_keccak_transcript_incremental_vs_explicit_with::<Secq256k1Engine>();
}
fn test_keccak_transcript_serde_fresh_with<E: Engine>() {
let transcript: Keccak256Transcript<E> = Keccak256Transcript::new(b"serde_test");
let json = serde_json::to_string(&transcript).unwrap();
let restored: Keccak256Transcript<E> = serde_json::from_str(&json).unwrap();
assert_eq!(transcript.round, restored.round);
assert_eq!(transcript.state, restored.state);
}
fn test_keccak_transcript_serde_after_ops_with<E: Engine>() {
let mut transcript: Keccak256Transcript<E> = Keccak256Transcript::new(b"serde_test");
let s1 = <E as Engine>::Scalar::from(42u64);
let s2 = <E as Engine>::Scalar::from(99u64);
transcript.absorb(b"s1", &s1);
transcript.absorb(b"s2", &s2);
let _c1: <E as Engine>::Scalar = transcript.squeeze(b"c1").unwrap();
let round_before = transcript.round;
let state_before = transcript.state;
let json = serde_json::to_string(&transcript).unwrap();
let restored: Keccak256Transcript<E> = serde_json::from_str(&json).unwrap();
assert_eq!(round_before, restored.round);
assert_eq!(state_before, restored.state);
}
fn test_keccak_transcript_serde_mid_absorb_with<E: Engine>() {
let mut t1: Keccak256Transcript<E> = Keccak256Transcript::new(b"mid_absorb");
let s1 = <E as Engine>::Scalar::from(11u64);
let s2 = <E as Engine>::Scalar::from(22u64);
t1.absorb(b"a", &s1);
t1.absorb(b"b", &s2);
let json = serde_json::to_string(&t1).unwrap();
let mut t2: Keccak256Transcript<E> = serde_json::from_str(&json).unwrap();
let c1: <E as Engine>::Scalar = t1.squeeze(b"ch").unwrap();
let c2: <E as Engine>::Scalar = t2.squeeze(b"ch").unwrap();
assert_eq!(c1, c2);
}
fn test_keccak_transcript_serde_bad_state_with<E: Engine>() {
let bad_json = r#"{"round":0,"state":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"transcript_buffer":[]}"#;
let result = serde_json::from_str::<Keccak256Transcript<E>>(bad_json);
assert!(result.is_err());
}
fn test_keccak_transcript_serde_bincode_with<E: Engine>() {
let mut transcript: Keccak256Transcript<E> = Keccak256Transcript::new(b"bincode_test");
let s = <E as Engine>::Scalar::from(7u64);
transcript.absorb(b"val", &s);
let _c: <E as Engine>::Scalar = transcript.squeeze(b"ch").unwrap();
let encoded = bincode::serde::encode_to_vec(&transcript, bincode::config::standard()).unwrap();
let (restored, _): (Keccak256Transcript<E>, _) =
bincode::serde::decode_from_slice(&encoded, bincode::config::standard()).unwrap();
assert_eq!(transcript.round, restored.round);
assert_eq!(transcript.state, restored.state);
}
#[test]
fn test_keccak_transcript_serde_fresh() {
test_keccak_transcript_serde_fresh_with::<PallasEngine>();
test_keccak_transcript_serde_fresh_with::<VestaEngine>();
test_keccak_transcript_serde_fresh_with::<Bn256EngineKZG>();
test_keccak_transcript_serde_fresh_with::<GrumpkinEngine>();
test_keccak_transcript_serde_fresh_with::<Secp256k1Engine>();
test_keccak_transcript_serde_fresh_with::<Secq256k1Engine>();
}
#[test]
fn test_keccak_transcript_serde_after_ops() {
test_keccak_transcript_serde_after_ops_with::<PallasEngine>();
test_keccak_transcript_serde_after_ops_with::<VestaEngine>();
test_keccak_transcript_serde_after_ops_with::<Bn256EngineKZG>();
test_keccak_transcript_serde_after_ops_with::<GrumpkinEngine>();
test_keccak_transcript_serde_after_ops_with::<Secp256k1Engine>();
test_keccak_transcript_serde_after_ops_with::<Secq256k1Engine>();
}
#[test]
fn test_keccak_transcript_serde_mid_absorb() {
test_keccak_transcript_serde_mid_absorb_with::<PallasEngine>();
test_keccak_transcript_serde_mid_absorb_with::<VestaEngine>();
test_keccak_transcript_serde_mid_absorb_with::<Bn256EngineKZG>();
test_keccak_transcript_serde_mid_absorb_with::<GrumpkinEngine>();
test_keccak_transcript_serde_mid_absorb_with::<Secp256k1Engine>();
test_keccak_transcript_serde_mid_absorb_with::<Secq256k1Engine>();
}
#[test]
fn test_keccak_transcript_serde_bad_state() {
test_keccak_transcript_serde_bad_state_with::<PallasEngine>();
test_keccak_transcript_serde_bad_state_with::<Bn256EngineKZG>();
test_keccak_transcript_serde_bad_state_with::<Secp256k1Engine>();
}
#[test]
fn test_keccak_transcript_serde_bincode() {
test_keccak_transcript_serde_bincode_with::<PallasEngine>();
test_keccak_transcript_serde_bincode_with::<VestaEngine>();
test_keccak_transcript_serde_bincode_with::<Bn256EngineKZG>();
test_keccak_transcript_serde_bincode_with::<GrumpkinEngine>();
test_keccak_transcript_serde_bincode_with::<Secp256k1Engine>();
test_keccak_transcript_serde_bincode_with::<Secq256k1Engine>();
}
}