diff --git a/src/transcript.rs b/src/transcript.rs index 61cc99f68..f910f7c35 100644 --- a/src/transcript.rs +++ b/src/transcript.rs @@ -90,16 +90,58 @@ pub trait FieldWritableExt: FieldWritable { /// native field of `Self`, and that this native field is Limbable into `TargetField`. impl FieldWritableExt for T {} +/// Any digest that implements the `digest` core APIs implements `std::io::Write`, +/// and field elements are straightforward to convert to bytes, so we could easily define +/// any such transcript as a `FieldWriter`. But the APi above brings up the problem +/// of defining a notion of single "native" field for such a `digest::Digest`: +//// there's no inherent constraint to pick one over another. +/// +/// A variant of this problem presents itself even more acutely when the destination is a +/// general std::io::Write (e.g. a serializer) rather than a digest. +/// +/// So ee punt to the user in this case by defining a struct pairing some Writer with a field. +/// Then +pub struct FieldEquippedWriter { + writer: W, + _phantom: PhantomData, +} + +impl From for FieldEquippedWriter { + fn from(writer: W) -> Self { + Self { + writer, + _phantom: PhantomData, + } + } +} + +impl FieldWriter for FieldEquippedWriter { + type NativeField = F; + + fn write(&mut self, field_elts: &[Self::NativeField]) -> Result<(), io::Error> { + for f in field_elts.iter() { + let bytes = f.to_repr(); + self.writer.write(bytes.as_ref())?; + } + Ok(()) + } + + fn flush(&mut self) -> Result<(), io::Error> { + self.writer.flush() + } +} + #[cfg(test)] mod tests { use ff::PrimeField; use num_bigint::BigUint; use num_traits::Num; + use sha3::{self, Digest, Keccak256}; pub use halo2curves::bn256::{Fq as Base, Fr as Scalar}; - use super::{FieldWritable, FieldWritableExt, FieldWriter, Limbable}; + use super::{FieldEquippedWriter, FieldWritable, FieldWritableExt, FieldWriter, Limbable}; use crate::{ provider::{Bn256EngineKZG, GrumpkinEngine}, traits::{commitment::CommitmentTrait, Dual}, @@ -188,7 +230,7 @@ mod tests { } #[test] - fn it_works() { + fn test_limbing_works() { let commitment_native_base = Commitment::::default(); // identity element with coordinates in bn256::Base, let commitment_dual_base = Commitment::>::default(); // identity element with coordinates in GrumpkinEngine::Base, let mut writer = MockWriter { @@ -199,4 +241,32 @@ mod tests { commitment_dual_base.write(&mut writer).unwrap(); // the blanket reflexive instance of Limbable fires, this writes 2 elements assert_eq!(writer.written.len(), 6); } + + #[test] + fn test_keccak_works() { + let mut keccak_for_scalar = FieldEquippedWriter::::from(Keccak256::new()); + + let commitment_native_base = Commitment::::default(); + let commitment_dual_base = Commitment::>::default(); + commitment_native_base + .write(&mut keccak_for_scalar) + .unwrap(); + commitment_dual_base.write(&mut keccak_for_scalar).unwrap(); + + let result = keccak_for_scalar.writer.finalize(); + + // Now do it manually and compare + let (x_base, y_base, _) = Commitment::::default().to_coordinates(); + let (x_scalar, y_scalar, _) = Commitment::>::default().to_coordinates(); + let mut hasher = Keccak256::new(); + for x_limb in >::limb(x_base).iter() { + hasher.update(x_limb.to_repr().as_ref()); + } + for y_limb in >::limb(y_base).iter() { + hasher.update(y_limb.to_repr().as_ref()); + } + hasher.update(x_scalar.to_repr().as_ref()); + hasher.update(y_scalar.to_repr().as_ref()); + assert_eq!(hasher.finalize(), result); + } }