use field_cat::FieldBytes;
use sha2::{Digest, Sha256};
use crate::error::Error;
#[derive(Debug, Clone)]
pub struct Transcript {
state: Vec<u8>,
}
impl Transcript {
#[must_use]
pub fn new(label: &[u8]) -> Self {
Self {
state: label.to_vec(),
}
}
#[must_use]
pub fn absorb_bytes(self, data: &[u8]) -> Self {
Self {
state: self.state.into_iter().chain(data.iter().copied()).collect(),
}
}
#[must_use]
pub fn absorb_field<F: FieldBytes>(self, elem: &F) -> Self {
self.absorb_bytes(&elem.to_le_bytes())
}
pub fn squeeze_challenge<F: FieldBytes>(self) -> Result<(F, Self), Error> {
let digest = Sha256::digest(&self.state);
let challenge = F::from_le_bytes(digest.as_slice())?;
let new_state = self
.state
.into_iter()
.chain(digest.iter().copied())
.collect();
Ok((challenge, Self { state: new_state }))
}
}
#[cfg(test)]
mod tests {
use super::*;
use field_cat::{BabyBear, F101};
#[test]
fn deterministic_challenges() -> Result<(), Error> {
let t1 = Transcript::new(b"test").absorb_field(&BabyBear::new(42));
let t2 = Transcript::new(b"test").absorb_field(&BabyBear::new(42));
let (c1, _) = t1.squeeze_challenge::<BabyBear>()?;
let (c2, _) = t2.squeeze_challenge::<BabyBear>()?;
assert_eq!(c1, c2);
Ok(())
}
#[test]
fn different_inputs_different_challenges() -> Result<(), Error> {
let t1 = Transcript::new(b"test").absorb_field(&BabyBear::new(1));
let t2 = Transcript::new(b"test").absorb_field(&BabyBear::new(2));
let (c1, _) = t1.squeeze_challenge::<BabyBear>()?;
let (c2, _) = t2.squeeze_challenge::<BabyBear>()?;
assert_ne!(c1, c2);
Ok(())
}
#[test]
fn absorb_order_matters() -> Result<(), Error> {
let t1 = Transcript::new(b"test")
.absorb_field(&F101::new(1))
.absorb_field(&F101::new(2));
let t2 = Transcript::new(b"test")
.absorb_field(&F101::new(2))
.absorb_field(&F101::new(1));
let (c1, _) = t1.squeeze_challenge::<F101>()?;
let (c2, _) = t2.squeeze_challenge::<F101>()?;
assert_ne!(c1, c2);
Ok(())
}
#[test]
fn label_matters() -> Result<(), Error> {
let t1 = Transcript::new(b"label_a").absorb_field(&BabyBear::new(42));
let t2 = Transcript::new(b"label_b").absorb_field(&BabyBear::new(42));
let (c1, _) = t1.squeeze_challenge::<BabyBear>()?;
let (c2, _) = t2.squeeze_challenge::<BabyBear>()?;
assert_ne!(c1, c2);
Ok(())
}
}