use alloc::vec::Vec;
use std::io;
use ff::Field;
use group::{Curve, Group};
use halo2_proofs::plonk::fingerprint::{
capture_proof_fingerprint, ChallengeRecorder, TranscriptEvent,
};
use halo2_proofs::transcript::{
Blake2bWrite, Challenge255, EncodedChallenge, Transcript, TranscriptRead, TranscriptWrite,
};
use pasta_curves::vesta;
use rand_chacha::ChaCha20Rng;
use super::super::{OrchardCircuitVersion, VerifyingKey, K};
use super::{assert_pinned_verifying_key, fixture_rng, raw_instance_refs};
const INSTANCE_ROWS: usize = 10;
struct RandomizingTranscript {
inner: Blake2bWrite<Vec<u8>, vesta::Affine, Challenge255<vesta::Affine>>,
rng: ChaCha20Rng,
events: Vec<TranscriptEvent<vesta::Affine>>,
challenges: Vec<vesta::Scalar>,
}
impl RandomizingTranscript {
fn new(rng: ChaCha20Rng) -> Self {
RandomizingTranscript {
inner: Blake2bWrite::init(vec![]),
rng,
events: Vec::new(),
challenges: Vec::new(),
}
}
fn sample_nonzero_scalar(&mut self) -> vesta::Scalar {
loop {
let t = vesta::Scalar::random(&mut self.rng);
if !bool::from(t.is_zero()) {
return t;
}
}
}
fn finalize(
self,
) -> (
Vec<u8>,
Vec<TranscriptEvent<vesta::Affine>>,
Vec<vesta::Scalar>,
) {
(self.inner.finalize(), self.events, self.challenges)
}
}
impl Transcript<vesta::Affine, Challenge255<vesta::Affine>> for RandomizingTranscript {
fn squeeze_challenge(&mut self) -> Challenge255<vesta::Affine> {
let challenge = self.inner.squeeze_challenge();
let scalar = challenge.get_scalar();
self.challenges.push(scalar);
self.events.push(TranscriptEvent::Squeeze(scalar));
challenge
}
fn common_point(&mut self, point: vesta::Affine) -> io::Result<()> {
self.events.push(TranscriptEvent::CommonPoint(point));
self.inner.common_point(point)
}
fn common_scalar(&mut self, scalar: vesta::Scalar) -> io::Result<()> {
self.events.push(TranscriptEvent::CommonScalar(scalar));
self.inner.common_scalar(scalar)
}
}
impl TranscriptRead<vesta::Affine, Challenge255<vesta::Affine>> for RandomizingTranscript {
fn read_point(&mut self) -> io::Result<vesta::Affine> {
let t = self.sample_nonzero_scalar();
let point = (vesta::Point::generator() * t).to_affine();
self.inner.write_point(point)?;
self.events.push(TranscriptEvent::ReadPoint(point));
Ok(point)
}
fn read_scalar(&mut self) -> io::Result<vesta::Scalar> {
let scalar = vesta::Scalar::random(&mut self.rng);
self.inner.write_scalar(scalar)?;
self.events.push(TranscriptEvent::ReadScalar(scalar));
Ok(scalar)
}
}
fn assert_transcript_events_eq(
fabricated: &[TranscriptEvent<vesta::Affine>],
replayed: &[TranscriptEvent<vesta::Affine>],
) {
assert_eq!(
fabricated.len(),
replayed.len(),
"transcript event counts diverged between fabrication and replay"
);
for (i, (fab, rep)) in fabricated.iter().zip(replayed.iter()).enumerate() {
let same = match (fab, rep) {
(TranscriptEvent::CommonPoint(a), TranscriptEvent::CommonPoint(b)) => a == b,
(TranscriptEvent::CommonScalar(a), TranscriptEvent::CommonScalar(b)) => a == b,
(TranscriptEvent::ReadPoint(a), TranscriptEvent::ReadPoint(b)) => a == b,
(TranscriptEvent::ReadScalar(a), TranscriptEvent::ReadScalar(b)) => a == b,
(TranscriptEvent::Squeeze(a), TranscriptEvent::Squeeze(b)) => a == b,
_ => false,
};
assert!(
same,
"transcript event {i} diverged between fabrication and replay: {fab:?} vs {rep:?}"
);
}
}
pub(super) fn capture_random_fixture(
seed: u8,
num_actions: u8,
namespace: &str,
fixture_output_var: &str,
proof_output_var: &str,
) {
let mut rng = fixture_rng(seed);
let vk = VerifyingKey::build(OrchardCircuitVersion::PostNu6_3);
assert!(vk.supports_cross_address_restriction());
assert_pinned_verifying_key(&vk);
let raw_instances: Vec<Vec<Vec<vesta::Scalar>>> = (0..num_actions)
.map(|_| {
vec![(0..INSTANCE_ROWS)
.map(|_| vesta::Scalar::random(&mut rng))
.collect()]
})
.collect();
let raw_instance_refs = raw_instance_refs(&raw_instances);
let raw_instance_refs: Vec<_> = raw_instance_refs
.iter()
.map(|instance| &instance[..])
.collect();
let mut fabricate = RandomizingTranscript::new(rng);
let fabricated_msm =
capture_proof_fingerprint(&vk.params, &vk.vk, &raw_instance_refs, &mut fabricate)
.expect("deployed verifier must run to completion on the random proof string");
assert!(
!fabricated_msm.eval(),
"random capture must not assemble the identity MSM"
);
let (proof_bytes, fabricated_events, fabricated_challenges) = fabricate.finalize();
let mut replay = ChallengeRecorder::<_, _, Challenge255<_>>::init(&proof_bytes[..]);
let msm = capture_proof_fingerprint(&vk.params, &vk.vk, &raw_instance_refs, &mut replay)
.expect("replay of the fabricated proof bytes must parse and run to completion");
assert_eq!(
replay.challenges, fabricated_challenges,
"replay challenges diverged from fabrication"
);
assert_transcript_events_eq(&fabricated_events, &replay.events);
assert!(
!msm.clone().eval(),
"replayed random capture must not assemble the identity MSM"
);
std::eprintln!(
"Captured {num_actions}-action random match-only Orchard fingerprint at k={K} with {} challenges and {} proof bytes",
replay.challenges.len(),
proof_bytes.len(),
);
let fixture = vk.vk.dump_vesta_lean_fixture_match_only(
namespace,
"PostNu6_3",
K,
&raw_instance_refs,
&replay,
&msm,
);
if let Some(path) = std::env::var_os(fixture_output_var) {
std::fs::write(std::path::PathBuf::from(path), fixture).unwrap();
}
if let Some(path) = std::env::var_os(proof_output_var) {
let mut encoded = hex::encode(&proof_bytes);
encoded.push('\n');
std::fs::write(std::path::PathBuf::from(path), encoded).unwrap();
}
}