|
| 1 | +//! Generate and verify the Poseidon example with Midnight's EVM transcript. |
| 2 | +//! Run with: |
| 3 | +//! cargo run --example midnight_poseidon_evm --features midnight,loader_evm,revm -p snark-verifier-sdk |
| 4 | +use ff::Field; |
| 5 | +use midnight_circuits::{ |
| 6 | + hash::poseidon::PoseidonChip, |
| 7 | + instructions::{hash::HashCPU, AssignmentInstructions, PublicInputInstructions}, |
| 8 | +}; |
| 9 | +use midnight_curves::Bls12; |
| 10 | +use midnight_proofs::poly::kzg::params::ParamsKZG; |
| 11 | +use midnight_proofs::{ |
| 12 | + circuit::{Layouter, Value}, |
| 13 | + plonk::Error, |
| 14 | +}; |
| 15 | +use midnight_zk_stdlib::{Relation, ZkStdLib, ZkStdLibArch}; |
| 16 | +use rand::{rngs::OsRng, SeedableRng}; |
| 17 | +use rand_chacha::ChaCha8Rng; |
| 18 | +use snark_verifier_sdk::midnight_adapter::{MidnightBundleOptions, MidnightProofBundle}; |
| 19 | +use std::path::PathBuf; |
| 20 | + |
| 21 | +#[path = "support/midnight_evm_transcript.rs"] |
| 22 | +mod midnight_evm_transcript; |
| 23 | +use midnight_evm_transcript::MidnightEvmHash; |
| 24 | + |
| 25 | +type F = midnight_curves::Fq; |
| 26 | + |
| 27 | +#[derive(Clone, Default)] |
| 28 | +pub struct PoseidonExample; |
| 29 | + |
| 30 | +impl Relation for PoseidonExample { |
| 31 | + type Instance = F; |
| 32 | + |
| 33 | + type Witness = [F; 3]; |
| 34 | + |
| 35 | + // Expose one public output column containing the Poseidon hash value. |
| 36 | + fn format_instance(instance: &Self::Instance) -> Result<Vec<F>, Error> { |
| 37 | + Ok(vec![*instance]) |
| 38 | + } |
| 39 | + |
| 40 | + // Build the relation: hash witness values with Poseidon and constrain as public output. |
| 41 | + fn circuit( |
| 42 | + &self, |
| 43 | + std_lib: &ZkStdLib, |
| 44 | + layouter: &mut impl Layouter<F>, |
| 45 | + _instance: Value<Self::Instance>, |
| 46 | + witness: Value<Self::Witness>, |
| 47 | + ) -> Result<(), Error> { |
| 48 | + let assigned_message = std_lib.assign_many(layouter, &witness.transpose_array())?; |
| 49 | + let output = std_lib.poseidon(layouter, &assigned_message)?; |
| 50 | + std_lib.constrain_as_public_input(layouter, &output) |
| 51 | + } |
| 52 | + |
| 53 | + // Enable only Poseidon chip plumbing for this example circuit. |
| 54 | + fn used_chips(&self) -> ZkStdLibArch { |
| 55 | + ZkStdLibArch { poseidon: true, ..ZkStdLibArch::default() } |
| 56 | + } |
| 57 | + |
| 58 | + // No custom relation payload is needed for this stateless demo relation. |
| 59 | + fn write_relation<W: std::io::Write>(&self, _writer: &mut W) -> std::io::Result<()> { |
| 60 | + Ok(()) |
| 61 | + } |
| 62 | + |
| 63 | + // No custom relation payload is needed for this stateless demo relation. |
| 64 | + fn read_relation<R: std::io::Read>(_reader: &mut R) -> std::io::Result<Self> { |
| 65 | + Ok(PoseidonExample) |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +fn main() { |
| 70 | + const K: u32 = 6; |
| 71 | + let srs = ParamsKZG::<Bls12>::unsafe_setup(K, OsRng); |
| 72 | + |
| 73 | + // Build proving/verifying keys for the Poseidon relation. |
| 74 | + let relation = PoseidonExample; |
| 75 | + let vk = midnight_zk_stdlib::setup_vk(&srs, &relation); |
| 76 | + let pk = midnight_zk_stdlib::setup_pk(&relation, &vk); |
| 77 | + |
| 78 | + // Sample random witness and derive the expected public output. |
| 79 | + let mut rng = ChaCha8Rng::from_entropy(); |
| 80 | + let witness: [F; 3] = core::array::from_fn(|_| F::random(&mut rng)); |
| 81 | + let instance = <PoseidonChip<F> as HashCPU<F, F>>::hash(&witness); |
| 82 | + |
| 83 | + // Produce and verify an EVM-transcript proof using MidnightEvmHash. |
| 84 | + let proof = midnight_zk_stdlib::prove::<PoseidonExample, MidnightEvmHash>( |
| 85 | + &srs, &pk, &relation, &instance, witness, OsRng, |
| 86 | + ) |
| 87 | + .expect("EVM-transcript proof generation should not fail"); |
| 88 | + |
| 89 | + midnight_zk_stdlib::verify::<PoseidonExample, MidnightEvmHash>( |
| 90 | + &srs.verifier_params(), |
| 91 | + &vk, |
| 92 | + &instance, |
| 93 | + None, |
| 94 | + &proof, |
| 95 | + ) |
| 96 | + .expect("EVM-transcript native verification should succeed"); |
| 97 | + |
| 98 | + // Build adapter bundle used for Solidity/codegen bridge. |
| 99 | + let bundle = MidnightProofBundle::from_vk( |
| 100 | + srs.verifier_params(), |
| 101 | + vk.vk().clone(), |
| 102 | + proof.clone(), |
| 103 | + vec![vec![instance]], |
| 104 | + MidnightBundleOptions::default(), |
| 105 | + ) |
| 106 | + .expect("Bundle creation should succeed"); |
| 107 | + |
| 108 | + // Generate Solidity source, compile bytecode, and encode calldata artifacts. |
| 109 | + let solidity = bundle |
| 110 | + .generate_evm_verifier_solidity() |
| 111 | + .expect("failed to generate Solidity verifier source"); |
| 112 | + let bytecode = |
| 113 | + bundle.generate_evm_verifier_bytecode().expect("failed to compile Solidity verifier"); |
| 114 | + let calldata = bundle.encode_evm_calldata().expect("failed to encode EVM calldata"); |
| 115 | + |
| 116 | + let out_dir = |
| 117 | + std::env::var_os("MIDNIGHT_ARTIFACT_DIR").map(PathBuf::from).unwrap_or_else(|| { |
| 118 | + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("target").join("midnight") |
| 119 | + }); |
| 120 | + std::fs::create_dir_all(&out_dir).expect("failed to create output directory"); |
| 121 | + let solidity_path = out_dir.join("MidnightPoseidonVerifier.sol"); |
| 122 | + let bytecode_path = out_dir.join("midnight_poseidon.bytecode"); |
| 123 | + let calldata_path = out_dir.join("midnight_poseidon.calldata"); |
| 124 | + |
| 125 | + // Persist generated artifacts to disk (default: target/midnight). |
| 126 | + std::fs::write(&solidity_path, &solidity).expect("failed to write Solidity verifier"); |
| 127 | + std::fs::write(&bytecode_path, format!("0x{}", hex::encode(&bytecode))) |
| 128 | + .expect("failed to write verifier bytecode"); |
| 129 | + std::fs::write(&calldata_path, hex::encode(&calldata)).expect("failed to write calldata"); |
| 130 | + |
| 131 | + println!("proof bytes: {}", proof.len()); |
| 132 | + println!("deployment code bytes: {}", bytecode.len()); |
| 133 | + println!("calldata bytes: {}", calldata.len()); |
| 134 | + println!("wrote {}", solidity_path.display()); |
| 135 | + println!("wrote {}", bytecode_path.display()); |
| 136 | + println!("wrote {}", calldata_path.display()); |
| 137 | + |
| 138 | + #[cfg(feature = "revm")] |
| 139 | + { |
| 140 | + // Optional local revm simulation for end-to-end gas measurement. |
| 141 | + if std::env::var("RUN_REVM").ok().as_deref() == Some("1") { |
| 142 | + let gas = bundle |
| 143 | + .verify_with_generated_solidity_revm() |
| 144 | + .expect("revm verification should succeed"); |
| 145 | + println!("revm gas: {gas}"); |
| 146 | + } else { |
| 147 | + println!("revm verification skipped (set RUN_REVM=1 to run local revm simulation)"); |
| 148 | + } |
| 149 | + } |
| 150 | +} |
0 commit comments