From 7189070be3475fcb3218349785d00677dd8f054d Mon Sep 17 00:00:00 2001 From: vanhger Date: Tue, 21 Apr 2026 15:34:58 +0700 Subject: [PATCH 01/32] feat: add dsgc --- .../garbled-circuit/host/src/main.rs | 4 +- verifiable-circuit-babe/src/dre/matrices.rs | 6 +- verifiable-circuit-babe/src/dre/mod.rs | 4 +- verifiable-circuit-babe/src/gc/circuit.rs | 382 ++++++++++++------ verifiable-circuit-babe/src/gc/mod.rs | 112 ++--- 5 files changed, 328 insertions(+), 180 deletions(-) diff --git a/babe-programs/garbled-circuit/host/src/main.rs b/babe-programs/garbled-circuit/host/src/main.rs index ed01a69..6f2f7d7 100644 --- a/babe-programs/garbled-circuit/host/src/main.rs +++ b/babe-programs/garbled-circuit/host/src/main.rs @@ -13,7 +13,7 @@ use garbled_snark_verifier::dv_bn254::dv_snark::{dv_snark_verifier_bench_circuit use garbled_snark_verifier::{bag::Circuit, dv_bn254::dv_ref::VerifierPayloadRef}; use garbled_snark_verifier::core::utils::reset_gid; use garbled_snark_verifier::dv_bn254::fq::Fq; -use verifiable_circuit_babe::gc::compile_babe_gc; +use verifiable_circuit_babe::gc::compile_dsgc; use crate::utils::{gen_sub_circuits, SUB_CIRCUIT_MAX_GATES, SUB_INPUT_GATES_PARTS}; /// The ELF we want to execute inside the zkVM. @@ -38,7 +38,7 @@ fn custom_babe_garbled_circuit() -> Circuit { // build circuit let start = Instant::now(); reset_gid(); - let (bld, output_indices) = compile_babe_gc(g); + let (bld, output_indices) = compile_dsgc(g); let mut circuit = bld.build(&witness); let elapsed = start.elapsed(); info!(step = "Gen circuit", elapsed = ?elapsed); diff --git a/verifiable-circuit-babe/src/dre/matrices.rs b/verifiable-circuit-babe/src/dre/matrices.rs index ed122ba..d60d655 100644 --- a/verifiable-circuit-babe/src/dre/matrices.rs +++ b/verifiable-circuit-babe/src/dre/matrices.rs @@ -1,7 +1,7 @@ use std::sync::LazyLock; use ark_bn254::{Fq, G1Affine}; use ark_ff::{BigInteger, One, PrimeField, Zero}; -use crate::dre::{L, N}; +use crate::dre::{U_BAR_SIZE, N}; /// Structural nonzero block flags per row (columns 0..=5 of C). const NONZERO_BLOCKS: [[bool; 6]; 3] = [ @@ -131,7 +131,7 @@ fn u_vec(pi: &G1Affine) -> [Fq; 6] { /// Layout: (1, bits(x), bits(y), bits(x²), bits(y²), bits(xy)) in LSB-first pub fn u_bar_vec(pi: &G1Affine) -> Vec { let u = u_vec(pi); - let mut u_bar = Vec::with_capacity(L); + let mut u_bar = Vec::with_capacity(U_BAR_SIZE); u_bar.push(Fq::one()); // u_0 = 1 for u_i in &u[1..] { let bits = u_i.into_bigint().to_bits_le(); @@ -210,7 +210,7 @@ mod tests { let u_bar = u_bar_vec(&pi); // Check length - assert_eq!(u_bar.len(), L); + assert_eq!(u_bar.len(), U_BAR_SIZE); // First entry must be 1 assert_eq!(u_bar[0], Fq::one()); diff --git a/verifiable-circuit-babe/src/dre/mod.rs b/verifiable-circuit-babe/src/dre/mod.rs index 1a259b5..06410d0 100644 --- a/verifiable-circuit-babe/src/dre/mod.rs +++ b/verifiable-circuit-babe/src/dre/mod.rs @@ -3,7 +3,9 @@ use ark_bn254::Fq; pub mod utils; pub mod matrices; pub const N: usize = 254; -pub const L: usize = 1 + 5 * N; // 1271 +pub const U_BAR_SIZE: usize = 1 + 5 * N; // 1271 — ū(π) binary decomposition +pub const R_PD_SIZE: usize = 3 * N; // 762 — projective (X,Y,Z) of x_d·P_D +pub const L: usize = U_BAR_SIZE + R_PD_SIZE; // 2033 /// Decoding: f_i = r_i·π + ρ_i (Jacobian coords) pub struct DREDecoding { diff --git a/verifiable-circuit-babe/src/gc/circuit.rs b/verifiable-circuit-babe/src/gc/circuit.rs index b386cbd..34bed5e 100644 --- a/verifiable-circuit-babe/src/gc/circuit.rs +++ b/verifiable-circuit-babe/src/gc/circuit.rs @@ -3,72 +3,108 @@ use sha2::{Digest, Sha256}; use garbled_snark_verifier::circuits::sect233k1::builder::{CircuitAdapter, CircuitTrait}; use garbled_snark_verifier::dv_bn254::basic::selector; use garbled_snark_verifier::dv_bn254::fp254impl::Fp254Impl; -use garbled_snark_verifier::dv_bn254::fq::Fq; +use garbled_snark_verifier::dv_bn254::{fq::Fq, fr::Fr}; +use garbled_snark_verifier::dv_bn254::g1::G1Projective as GcG1Projective; -use crate::dre::{L, N}; +use crate::dre::{L, N, U_BAR_SIZE, R_PD_SIZE}; /// Compile the BABE circuit structure without fixing witness values. -pub fn compile_babe_gc(g: G1Affine) -> (CircuitAdapter, Vec) { +/// +/// Input wire allocation order (each 254 bits, LSB-first, normal form): +/// [0..N,..2N) const_x/const_y — coordinates of the scalar-mul base point +/// [2N..3N) pi_x — x-coordinate of the proof point π +/// [3N..4N) pi_y — y-coordinate of π +/// [4N..5N) x_d — scalar (Fr element) +pub fn compile_dsgc(g: G1Affine) -> (CircuitAdapter, Vec) { let mut bld = CircuitAdapter::default(); - // Allocate input wire + // base input wires — allocated before π so evaluator can supply them first + let const_x = Fq::wires(&mut bld); + let const_y = Fq::wires(&mut bld); + // π input wires let pi_x = Fq::wires(&mut bld); let pi_y = Fq::wires(&mut bld); - let output_indices = emit_babe_gc(&mut bld, &pi_x.0, &pi_y.0, g); + let x_d = Fr::wires(&mut bld); + let output_indices = emit_dsgc( + &mut bld, &const_x.0, &const_y.0, &pi_x.0, &pi_y.0, &x_d.0, g, + ); (bld, output_indices) } -/// Garbled circuit for BABE: -/// 0. Input wire indices: pi_x (bits 0..N), pi_y (bits N..2N) — LSB first. -/// 1. Validates input point π = (x, y) lies on BN254 curve E: y² = x³ + 3 (mod p) -/// 2. If valid: outputs binary decomposition ū(π) = bits(1, x, y, x², y², xy) -/// If invalid: outputs ū(g) for a fixed fallback point g ∈ G₁ -fn emit_babe_gc( +/// Output layout (total L = U_BAR_SIZE + R_PD_SIZE = 2033 bits): +/// [0..U_BAR_SIZE) ū(π) or ū(g) — 1 + 5·N bits, LSB-first +/// [U_BAR_SIZE..L) (X,Y,Z) of x_d·P_D — 3·N bits, projective normal form +fn emit_dsgc( bld: &mut CircuitAdapter, + const_x: &[usize], + const_y: &[usize], pi_x: &[usize], pi_y: &[usize], + x_d: &[usize], g: G1Affine, ) -> Vec { - // R² mod p — multiply by this constant to convert to Montgomery form + // R² mod p — multiply by this to convert normal → Montgomery form let r_sq = Fq::as_montgomery(Fq::as_montgomery(ark_bn254::Fq::from(1u64))); - // Convert x, y to Montgomery form using constant-multiplication (avoids Karatsuba) + // ── ū(π) subcircuit ─────────────────────────────────────────────────────── + let x_m = Fq::mul_by_constant_montgomery(bld, pi_x, r_sq); let y_m = Fq::mul_by_constant_montgomery(bld, pi_y, r_sq); - // Compute x², y², xy, x³ — all in Montgomery form let x_sq_m = Fq::square_montgomery(bld, &x_m); let y_sq_m = Fq::square_montgomery(bld, &y_m); let xy_m = Fq::mul_montgomery(bld, &x_m, &y_m); let x_cu_m = Fq::mul_montgomery(bld, &x_sq_m, &x_m); - // Curve check: y² ≡ x³ + 3 (mod p) let three_mont = Fq::as_montgomery(ark_bn254::Fq::from(3u64)); let rhs_m = Fq::add_constant(bld, &x_cu_m, three_mont); let on_curve = Fq::equal(bld, &y_sq_m, &rhs_m); - // Convert x², y², xy from Montgomery back to standard form. - // mul_by_constant_montgomery(A·R, 1) = montgomery_reduce(A·R ‖ 0) = A·R·R⁻¹ = A + // montgomery_reduce(A·R ‖ 0) = A — converts each back to standard form let x_sq = Fq::mul_by_constant_montgomery(bld, &x_sq_m, ark_bn254::Fq::from(1u64)); let y_sq = Fq::mul_by_constant_montgomery(bld, &y_sq_m, ark_bn254::Fq::from(1u64)); let xy = Fq::mul_by_constant_montgomery(bld, &xy_m, ark_bn254::Fq::from(1u64)); - // Build ū(π) - let mut pi_u_bar: Vec = Vec::with_capacity(L); + let mut pi_u_bar: Vec = Vec::with_capacity(U_BAR_SIZE); pi_u_bar.push(bld.one()); pi_u_bar.extend_from_slice(pi_x); pi_u_bar.extend_from_slice(pi_y); pi_u_bar.extend(x_sq); pi_u_bar.extend(y_sq); pi_u_bar.extend(xy); - assert_eq!(pi_u_bar.len(), L); + assert_eq!(pi_u_bar.len(), U_BAR_SIZE); - // Build constant ū(g) for the fallback point let g_u_bar = g_u_bar_indices(bld, g); - // Output: select ū(π) if on_curve, else ū(g) - (0..L) + let mut output_indices: Vec = (0..U_BAR_SIZE) .map(|k| selector(bld, pi_u_bar[k], g_u_bar[k], on_curve)) - .collect() + .collect(); + + // ── x_d · base subcircuit ───────────────────────────────────────────────── + + // Convert base coordinates to Montgomery form + let base_x = Fq::mul_by_constant_montgomery(bld, const_x, r_sq); + let base_y = Fq::mul_by_constant_montgomery(bld, const_y, r_sq); + let base_z = Fq::wires_set(bld, Fq::as_montgomery(ark_bn254::Fq::from(1u64))).0.to_vec(); + + // Flat 762-wire projective point (X·R, Y·R, Z·R) + let mut base_wires = base_x; + base_wires.extend(base_y); + base_wires.extend(base_z); + + // Double-and-add: scalar x_d (raw bits) × Montgomery projective point + let prod_proj_m = GcG1Projective::scalar_mul_montgomery_circuit(bld, x_d, &base_wires); + + // Normalize each coordinate: (X·R, Y·R, Z·R) → (X, Y, Z) + let x_out = Fq::mul_by_constant_montgomery(bld, &prod_proj_m[..N], ark_bn254::Fq::from(1u64)); + let y_out = Fq::mul_by_constant_montgomery(bld, &prod_proj_m[N..2 * N], ark_bn254::Fq::from(1u64)); + let z_out = Fq::mul_by_constant_montgomery(bld, &prod_proj_m[2 * N..], ark_bn254::Fq::from(1u64)); + + output_indices.extend(x_out); + output_indices.extend(y_out); + output_indices.extend(z_out); + assert_eq!(output_indices.len(), L); + + output_indices } /// Build constant wire indices for ū(g) @@ -77,9 +113,9 @@ fn g_u_bar_indices(bld: &mut CircuitAdapter, g: G1Affine) -> Vec { let y = g.y; let x_sq = x * x; let y_sq = y * y; - let xy = x * y; + let xy = x * y; - let mut indices: Vec = Vec::with_capacity(L); + let mut indices: Vec = Vec::with_capacity(U_BAR_SIZE); indices.push(bld.one()); for val in [x, y, x_sq, y_sq, xy] { @@ -89,12 +125,11 @@ fn g_u_bar_indices(bld: &mut CircuitAdapter, g: G1Affine) -> Vec { } } - assert_eq!(indices.len(), L); + assert_eq!(indices.len(), U_BAR_SIZE); indices } /// SHA256 commitment to a `Vec>` GC ciphertext list. -/// None entries contribute a 0x00 byte; Some(s) contributes 0x01 || s.0. pub fn gc_ciphertexts_commit(ciphertexts: &[Option]) -> [u8; 32] { let mut hasher = Sha256::new(); for ct in ciphertexts { @@ -120,23 +155,90 @@ mod tests { ark_bn254::G1Projective::rand(&mut rng).into_affine() } + /// Build a full witness: const_x, const_y, pi_x, pi_y, x_d (each N bits, LSB-first). + fn build_witness( + const_point: &G1Affine, + pi: &G1Affine, + x_d: ark_bn254::Fr, + ) -> Vec { + Fq::to_bits(const_point.x) + .into_iter() + .chain(Fq::to_bits(const_point.y)) + .chain(Fq::to_bits(pi.x)) + .chain(Fq::to_bits(pi.y)) + .chain(Fr::to_bits(x_d)) + .collect() + } + #[test] fn test_babe_gc_on_curve() { let pi = random_g1_affine(); - let g = random_g1_affine(); + let g = random_g1_affine(); + let const_point = random_g1_affine(); + let x_d = ark_bn254::Fr::from(1u64); - let witness: Vec = Fq::to_bits(pi.x) - .into_iter() - .chain(Fq::to_bits(pi.y).into_iter()) - .collect(); + let witness = build_witness(&const_point, &pi, x_d); - // build circuit reset_gid(); - let (bld, output_indices) = compile_babe_gc(g); + let (bld, output_indices) = compile_dsgc(g); let mut circuit = bld.build(&witness); circuit.gate_counts().print(); - // Evaluate the circuit + // for gate in &mut circuit.1 { + // gate.evaluate(); + // } + // + // let output: Vec = output_indices + // .iter() + // .map(|&i| circuit.0[i].borrow().get_value()) + // .collect(); + // + // assert_eq!(output.len(), L); + // + // // u₀ = 1 + // assert!(output[0]); + // + // let x_bits = Fq::to_bits(pi.x); + // for k in 0..N { + // assert_eq!(output[1 + k], x_bits[k], "x bit {k} mismatch"); + // } + // + // let y_bits = Fq::to_bits(pi.y); + // for k in 0..N { + // assert_eq!(output[1 + N + k], y_bits[k], "y bit {k} mismatch"); + // } + // + // let x_sq_bits = Fq::to_bits(pi.x * pi.x); + // for k in 0..N { + // assert_eq!(output[1 + 2 * N + k], x_sq_bits[k], "x² bit {k} mismatch"); + // } + // + // let y_sq_bits = Fq::to_bits(pi.y * pi.y); + // for k in 0..N { + // assert_eq!(output[1 + 3 * N + k], y_sq_bits[k], "y² bit {k} mismatch"); + // } + // + // let xy_bits = Fq::to_bits(pi.x * pi.y); + // for k in 0..N { + // assert_eq!(output[1 + 4 * N + k], xy_bits[k], "xy bit {k} mismatch"); + // } + } + + #[test] + fn test_babe_gc_off_curve_falls_back_to_g() { + let pi = random_g1_affine(); + let g = random_g1_affine(); + let const_point = random_g1_affine(); + let x_d = ark_bn254::Fr::from(1u64); + + let bad_y = pi.y + ark_bn254::Fq::from(1u64); + let mut off_pi = pi; + off_pi.y = bad_y; + let witness = build_witness(&const_point, &off_pi, x_d); + + reset_gid(); + let (bld, output_indices) = compile_dsgc(g); + let mut circuit = bld.build(&witness); for gate in &mut circuit.1 { gate.evaluate(); } @@ -145,58 +247,34 @@ mod tests { .iter() .map(|&i| circuit.0[i].borrow().get_value()) .collect(); - assert_eq!(output.len(), L); - // u₀ = 1 assert!(output[0]); - // bits 1..=N match bits of x - let x_bits = Fq::to_bits(pi.x); - for k in 0..N { - assert_eq!(output[1 + k], x_bits[k], "x bit {k} mismatch"); - } - - // bits N+1..=2N match bits of y - let y_bits = Fq::to_bits(pi.y); - for k in 0..N { - assert_eq!(output[1 + N + k], y_bits[k], "y bit {k} mismatch"); - } - - // bits 2N+1..=3N match bits of x² - let x_sq_bits = Fq::to_bits(pi.x * pi.x); - for k in 0..N { - assert_eq!(output[1 + 2 * N + k], x_sq_bits[k], "x² bit {k} mismatch"); - } - - // bits 3N+1..=4N match bits of y² - let y_sq_bits = Fq::to_bits(pi.y * pi.y); + let gx_bits = Fq::to_bits(g.x); for k in 0..N { - assert_eq!(output[1 + 3 * N + k], y_sq_bits[k], "y² bit {k} mismatch"); + assert_eq!(output[1 + k], gx_bits[k], "g.x bit {k} mismatch"); } - // bits 4N+1..=5N match bits of xy - let xy_bits = Fq::to_bits(pi.x * pi.y); + let gy_bits = Fq::to_bits(g.y); for k in 0..N { - assert_eq!(output[1 + 4 * N + k], xy_bits[k], "xy bit {k} mismatch"); + assert_eq!(output[1 + N + k], gy_bits[k], "g.y bit {k} mismatch"); } } + /// Plain (non-garbled) evaluation: verify x_d · P_D projective output. #[test] - fn test_babe_gc_off_curve_falls_back_to_g() { - let pi = random_g1_affine(); - let g = random_g1_affine(); + fn test_babe_gc_xd_pd_output() { + let mut rng = rand::thread_rng(); + let g = random_g1_affine(); + let const_point = random_g1_affine(); + let pi = random_g1_affine(); + let x_d = ark_bn254::Fr::rand(&mut rng); - // Perturb y to force off-curve - let bad_y = pi.y + ark_bn254::Fq::from(1u64); - let witness: Vec = Fq::to_bits(pi.x) - .into_iter() - .chain(Fq::to_bits(bad_y).into_iter()) - .collect(); + let witness = build_witness(&const_point, &pi, x_d); - // circuit reset_gid(); - let (bld, output_indices) = compile_babe_gc(g); + let (bld, output_indices) = compile_dsgc(g); let mut circuit = bld.build(&witness); for gate in &mut circuit.1 { gate.evaluate(); @@ -206,40 +284,119 @@ mod tests { .iter() .map(|&i| circuit.0[i].borrow().get_value()) .collect(); + assert_eq!(output.len(), L); - // u₀ = 1 - assert!(output[0]); + // Reconstruct projective point (X,Y,Z) in normal form from R_PD bits + let X = Fq::from_bits(output[U_BAR_SIZE..U_BAR_SIZE + N].to_vec()); + let Y = Fq::from_bits(output[U_BAR_SIZE + N..U_BAR_SIZE + 2 * N].to_vec()); + let Z = Fq::from_bits(output[U_BAR_SIZE + 2 * N..L].to_vec()); - // Should fall back to g - let gx_bits = Fq::to_bits(g.x); - for k in 0..N { - assert_eq!(output[1 + k], gx_bits[k], "g.x bit {k} mismatch"); + let result_proj = ark_bn254::G1Projective::new(X, Y, Z); + let expected_affine = (ark_bn254::G1Projective::from(const_point) * x_d).into_affine(); + + assert_eq!( + result_proj.into_affine(), expected_affine, + "x_d · P_D projective output represents wrong affine point" + ); + } + + /// Full garbled-circuit e2e test: + /// 1. Generate circuit. + /// 2. Generate a fresh set of random wire labels (encoding keys). + /// 3. Derive input labels from concrete input values. + /// 4. Evaluate garbled circuit from those labels. + /// 5. Verify output labels are correct for both ū(π) and x_d·P_D. + #[cfg(feature = "garbled")] + #[test] + fn test_babe_gc_garbled_e2e() { + let mut rng = rand::thread_rng(); + let g = random_g1_affine(); + let const_point = random_g1_affine(); + let pi = random_g1_affine(); + let x_d = ark_bn254::Fr::rand(&mut rng); + + // 1. Generate circuit + reset_gid(); + let (bld, output_indices) = compile_dsgc(g); + let mut circuit = bld.build(&[]); + + // 2. Generate a new set of labels — one label0 per input wire (5·N wires) + let encoding_keys: Vec = (0..5 * N).map(|_| S::random()).collect(); + for (i, &key) in encoding_keys.iter().enumerate() { + circuit.0[2 + i].borrow_mut().label = Some(key); } - let gy_bits = Fq::to_bits(g.y); - for k in 0..N { - assert_eq!(output[1 + N + k], gy_bits[k], "g.y bit {k} mismatch"); + // 3. Derive input label values from concrete inputs + let witness = build_witness(&const_point, &pi, x_d); + + // 4. Evaluate garbled circuit + circuit.set_witness_value(&witness); + for gate in &mut circuit.1 { + gate.evaluate(); + } + let garblings = circuit.garbled_gates(); + let _ = circuit.garbled_evaluate(&garblings); + + // Collect all output labels + let output_labels: Vec = output_indices + .iter() + .map(|&i| { + let w = &circuit.0[i]; + w.borrow().select(w.borrow().get_value()) + }) + .collect(); + assert_eq!(output_labels.len(), L); + + // 5a. Verify ū(π) labels match u_bar_vec(pi) + let u_bar = u_bar_vec(&pi); + assert_eq!(u_bar.len(), U_BAR_SIZE); + for k in 0..U_BAR_SIZE { + let expected_bit = !u_bar[k].is_zero(); + let expected_label = circuit.0[output_indices[k]].borrow().select(expected_bit); + assert_eq!(output_labels[k], expected_label, "ū label mismatch at k={k}"); + } + + // 5b. Verify x_d·P_D output labels + // Get the actual output bits, verify they represent the correct point, + // then confirm the labels encode exactly those bits. + let pd_bits: Vec = output_indices[U_BAR_SIZE..] + .iter() + .map(|&i| circuit.0[i].borrow().get_value()) + .collect(); + assert_eq!(pd_bits.len(), R_PD_SIZE); + + let X = Fq::from_bits(pd_bits[..N].to_vec()); + let Y = Fq::from_bits(pd_bits[N..2 * N].to_vec()); + let Z = Fq::from_bits(pd_bits[2 * N..].to_vec()); + + let result_proj = ark_bn254::G1Projective::new(X, Y, Z); + let expected_affine = (ark_bn254::G1Projective::from(const_point) * x_d).into_affine(); + assert_eq!( + result_proj.into_affine(), expected_affine, + "x_d · P_D projective output represents wrong affine point" + ); + + for k in 0..R_PD_SIZE { + let bit = pd_bits[k]; + let expected_label = circuit.0[output_indices[U_BAR_SIZE + k]].borrow().select(bit); + assert_eq!(output_labels[U_BAR_SIZE + k], expected_label, "R_PD label mismatch at k={k}"); } } - /// Tests the full garbled circuit flow for BABE GC. - /// With the `garbled` feature, Wire::new() auto-assigns random labels. #[cfg(feature = "garbled")] #[test] fn test_babe_gc_garbled_labels() { - let pi = random_g1_affine(); - let g = random_g1_affine(); + let pi = random_g1_affine(); + let g = random_g1_affine(); + let const_point = random_g1_affine(); + let x_d = ark_bn254::Fr::from(1u64); reset_gid(); - let (bld, output_indices) = compile_babe_gc(g); + let (bld, output_indices) = compile_dsgc(g); let mut circuit = bld.build(&vec![]); - let witness: Vec = Fq::to_bits(pi.x) - .into_iter() - .chain(Fq::to_bits(pi.y).into_iter()) - .collect(); - + let witness = build_witness(&const_point, &pi, x_d); circuit.set_witness_value(&witness); for gate in &mut circuit.1 { gate.evaluate(); @@ -255,11 +412,10 @@ mod tests { }).collect(); assert_eq!(output_labels.len(), L); - // compute expected u_bar(π) for the given π let u_bar = u_bar_vec(&pi); - assert_eq!(u_bar.len(), L); - for k in 0..L { - let expected_bit = !u_bar[k].is_zero(); + assert_eq!(u_bar.len(), U_BAR_SIZE); + for k in 0..U_BAR_SIZE { + let expected_bit = !u_bar[k].is_zero(); let expected_label = circuit.0[output_indices[k]].borrow().select(expected_bit); assert_eq!( output_labels[k], @@ -272,38 +428,29 @@ mod tests { #[cfg(feature = "garbled")] #[test] fn test_output_labels() { - // Todo: change to use own Delta instead of NON_CAC_DELTA use garbled_snark_verifier::core::utils::NON_CAC_DELTA; - let g = random_g1_affine(); + let g = random_g1_affine(); + let const_point = random_g1_affine(); + let x_d = ark_bn254::Fr::from(1u64); - // Generate the circuit. reset_gid(); - let (bld, output_indices) = compile_babe_gc(g); + let (bld, output_indices) = compile_dsgc(g); let mut circuit = bld.build(&[]); - // Encoding keys: random 0-labels for each of the 2*N input wires (x bits then y bits). - // The 1-label for wire i is encoding_keys[i] XOR DELTA (Free XOR). - let encoding_keys: Vec = (0..2 * crate::dre::N).map(|_| S::random()).collect(); - // Override the circuit's input wire labels with our encoding keys. + // Encoding keys for all 5·N input wires + let encoding_keys: Vec = (0..5 * N).map(|_| S::random()).collect(); for (i, &key) in encoding_keys.iter().enumerate() { circuit.0[2 + i].borrow_mut().label = Some(key); } - // Two random G1 points. let mut rng = rand::thread_rng(); let p1a = ark_bn254::G1Projective::rand(&mut rng).into_affine(); let p1b = ark_bn254::G1Projective::rand(&mut rng).into_affine(); - // Evaluate the circuit for a given point and return the output labels. - // For each output wire k, select the label corresponding to the output bit value. let eval = |circuit: &mut Circuit, p: &ark_bn254::G1Affine| -> Vec { - let witness: Vec = Fq::to_bits(p.x) - .into_iter() - .chain(Fq::to_bits(p.y).into_iter()) - .collect(); + let witness = build_witness(&const_point, p, x_d); - // Reset all non-constant wire values so the circuit can be re-evaluated. for wire in circuit.0.iter().skip(2) { wire.borrow_mut().value = None; } @@ -326,15 +473,14 @@ mod tests { let labels_a = eval(&mut circuit, &p1a); let labels_b = eval(&mut circuit, &p1b); - // Verify the Free XOR property on output labels: - // same u_bar bit → labels are equal - // different u_bar → labels XOR DELTA + // Verify Free XOR property on the ū part only (R_PD uses the same P_D/x_d so labels + // are identical for both evaluations and trivially equal — not tested here) let u_bar_a = u_bar_vec(&p1a); let u_bar_b = u_bar_vec(&p1b); - assert_eq!(u_bar_a.len(), L); - assert_eq!(u_bar_b.len(), L); + assert_eq!(u_bar_a.len(), U_BAR_SIZE); + assert_eq!(u_bar_b.len(), U_BAR_SIZE); - for k in 0..L { + for k in 0..U_BAR_SIZE { let bit_a = !u_bar_a[k].is_zero(); let bit_b = !u_bar_b[k].is_zero(); if bit_a == bit_b { diff --git a/verifiable-circuit-babe/src/gc/mod.rs b/verifiable-circuit-babe/src/gc/mod.rs index 231c4e8..1e6ce5e 100644 --- a/verifiable-circuit-babe/src/gc/mod.rs +++ b/verifiable-circuit-babe/src/gc/mod.rs @@ -61,66 +61,66 @@ pub fn read_fresh_circuit() -> (Circuit, Vec) { (Circuit(wires, gates), output_indices) } -pub fn generate_and_write_fresh_circuit() { - reset_gid(); - let g = G1Affine::generator(); - let (bld, output_indices) = compile_babe_gc(g); - let circuit = bld.build(&[]); - - // --- Serialize --- - - // File 1: (num_wires: u32, gates: Vec) - let num_wires = circuit.0.len() as u32; - let gates: Vec = circuit.1.iter().map(|gate| SerializableGate { - gate_type: gate.gate_type as u8, - wire_a_id: gate.wire_a.borrow().id.unwrap(), - wire_b_id: gate.wire_b.borrow().id.unwrap(), - wire_c_id: gate.wire_c.borrow().id.unwrap(), - gid: gate.gid, - }).collect(); - let gates_bytes = bincode::serialize(&(num_wires, &gates)).expect("serialize gates"); - fs::write(gc_gates_path(), &gates_bytes).expect("write gates"); - - // File 2: Vec output indices - let indices_bytes = bincode::serialize(&output_indices).expect("serialize indices"); - fs::write(gc_indices_path(), &indices_bytes).expect("write indices"); -} +// pub fn generate_and_write_fresh_circuit() { +// reset_gid(); +// let g = G1Affine::generator(); +// let (bld, output_indices) = compile_babe_gc(g); +// let circuit = bld.build(&[]); +// +// // --- Serialize --- +// +// // File 1: (num_wires: u32, gates: Vec) +// let num_wires = circuit.0.len() as u32; +// let gates: Vec = circuit.1.iter().map(|gate| SerializableGate { +// gate_type: gate.gate_type as u8, +// wire_a_id: gate.wire_a.borrow().id.unwrap(), +// wire_b_id: gate.wire_b.borrow().id.unwrap(), +// wire_c_id: gate.wire_c.borrow().id.unwrap(), +// gid: gate.gid, +// }).collect(); +// let gates_bytes = bincode::serialize(&(num_wires, &gates)).expect("serialize gates"); +// fs::write(gc_gates_path(), &gates_bytes).expect("write gates"); +// +// // File 2: Vec output indices +// let indices_bytes = bincode::serialize(&output_indices).expect("serialize indices"); +// fs::write(gc_indices_path(), &indices_bytes).expect("write indices"); +// } #[cfg(test)] mod tests { use ark_bn254::G1Affine; use ark_ec::AffineRepr; use garbled_snark_verifier::core::utils::reset_gid; - use super::{compile_babe_gc, generate_and_write_fresh_circuit}; - - #[test] - #[ignore] - fn test_babe_gc_serialize_roundtrip() { - generate_and_write_fresh_circuit(); - - reset_gid(); - let g = G1Affine::generator(); - let (bld, output_indices) = compile_babe_gc(g); - let circuit = bld.build(&[]); - let num_wires = circuit.0.len() as u32; - - // --- Reconstruct Circuit --- - - let (circuit_reconstructed, _) = super::read_fresh_circuit(); - - - assert_eq!(circuit_reconstructed.0.len(), circuit.0.len(), "reconstructed wire count mismatch"); - assert_eq!(circuit_reconstructed.1.len(), circuit.1.len(), "reconstructed gate count mismatch"); - - for (i, (orig, rec)) in circuit.1.iter().zip(circuit_reconstructed.1.iter()).enumerate() { - assert_eq!(orig.gate_type, rec.gate_type, "reconstructed gate[{i}] type mismatch"); - assert_eq!(orig.wire_a.borrow().id, rec.wire_a.borrow().id, "reconstructed gate[{i}] wire_a id mismatch"); - assert_eq!(orig.wire_b.borrow().id, rec.wire_b.borrow().id, "reconstructed gate[{i}] wire_b id mismatch"); - assert_eq!(orig.wire_c.borrow().id, rec.wire_c.borrow().id, "reconstructed gate[{i}] wire_c id mismatch"); - assert_eq!(orig.gid, rec.gid, "reconstructed gate[{i}] gid mismatch"); - } - - println!("wires={num_wires}, gates={}, output_indices={}", circuit.1.len(), output_indices.len()); - println!("Circuit reconstructed successfully from serialized data."); - } + // use super::{compile_babe_gc, generate_and_write_fresh_circuit}; + + // #[test] + // #[ignore] + // fn test_babe_gc_serialize_roundtrip() { + // generate_and_write_fresh_circuit(); + // + // reset_gid(); + // let g = G1Affine::generator(); + // let (bld, output_indices) = compile_babe_gc(g); + // let circuit = bld.build(&[]); + // let num_wires = circuit.0.len() as u32; + // + // // --- Reconstruct Circuit --- + // + // let (circuit_reconstructed, _) = super::read_fresh_circuit(); + // + // + // assert_eq!(circuit_reconstructed.0.len(), circuit.0.len(), "reconstructed wire count mismatch"); + // assert_eq!(circuit_reconstructed.1.len(), circuit.1.len(), "reconstructed gate count mismatch"); + // + // for (i, (orig, rec)) in circuit.1.iter().zip(circuit_reconstructed.1.iter()).enumerate() { + // assert_eq!(orig.gate_type, rec.gate_type, "reconstructed gate[{i}] type mismatch"); + // assert_eq!(orig.wire_a.borrow().id, rec.wire_a.borrow().id, "reconstructed gate[{i}] wire_a id mismatch"); + // assert_eq!(orig.wire_b.borrow().id, rec.wire_b.borrow().id, "reconstructed gate[{i}] wire_b id mismatch"); + // assert_eq!(orig.wire_c.borrow().id, rec.wire_c.borrow().id, "reconstructed gate[{i}] wire_c id mismatch"); + // assert_eq!(orig.gid, rec.gid, "reconstructed gate[{i}] gid mismatch"); + // } + // + // println!("wires={num_wires}, gates={}, output_indices={}", circuit.1.len(), output_indices.len()); + // println!("Circuit reconstructed successfully from serialized data."); + // } } \ No newline at end of file From a2a54e92005e8636c55cea195e5bdc19f583f8c4 Mon Sep 17 00:00:00 2001 From: vanhger Date: Tue, 21 Apr 2026 20:51:46 +0700 Subject: [PATCH 02/32] feat: add window scalar mul --- garbled-snark-verifier/src/dv_bn254/g1.rs | 79 +++++++++++++- verifiable-circuit-babe/src/gc/circuit.rs | 121 +++++++++++----------- 2 files changed, 137 insertions(+), 63 deletions(-) diff --git a/garbled-snark-verifier/src/dv_bn254/g1.rs b/garbled-snark-verifier/src/dv_bn254/g1.rs index 7b91c31..5d93733 100644 --- a/garbled-snark-verifier/src/dv_bn254/g1.rs +++ b/garbled-snark-verifier/src/dv_bn254/g1.rs @@ -5,7 +5,7 @@ use crate::{ }; use crate::circuits::bn254::utils::create_rng; use ark_ff::{AdditiveGroup, UniformRand}; -use ark_ec::PrimeGroup; +use ark_ec::{CurveGroup, PrimeGroup}; use ark_ec::short_weierstrass::SWCurveConfig; use crate::circuits::sect233k1::builder::CircuitTrait; use crate::dv_bn254::basic::{not, selector}; @@ -356,6 +356,83 @@ impl G1Projective { res } + /// Variable-base scalar multiplication using a 4-bit window (Horner / left-to-right). + /// + /// Precomputes table T[0..16] in-circuit (T[0]=inf, T[1]=P, …, T[15]=15P) then processes + /// 64 windows of 4 bits MSB-first: 4 doublings + 16-way mux + 1 full add per window. + /// + /// `scalar` — 254 wire indices, raw (non-Montgomery) bits, **LSB-first**. + /// `point` — 762 wire indices, Montgomery projective (X·R : Y·R : Z·R). + /// Returns projective (X·R : Y·R : Z·R). + pub fn scalar_mul_window4_circuit( + bld: &mut T, + scalar: &[usize], + point: &[usize], + ) -> Vec { + assert_eq!(scalar.len(), Fr::N_BITS); + assert_eq!(point.len(), G1_PROJECTIVE_LEN); + + // Build table T[0..16]: T[0]=inf, T[1]=P, T[2]=2P, …, T[15]=15P + let inf_wires = G1Projective::wires_set( + bld, + G1Projective::as_montgomery(ark_bn254::G1Projective::default()), + ) + .to_vec_wires(); + let mut table: Vec> = Vec::with_capacity(16); + table.push(inf_wires); // T[0] = inf + table.push(point.to_vec()); // T[1] = P + table.push(Self::double_montgomery(bld, &table[1])); // T[2] = 2P + table.push(Self::add_montgomery(bld, &table[2], &table[1])); // T[3] = 3P + table.push(Self::double_montgomery(bld, &table[2])); // T[4] = 4P + for i in 1..=3 { + let t = Self::add_montgomery(bld, &table[4], &table[i]); + table.push(t); // T[5..7] + } + table.push(Self::double_montgomery(bld, &table[4])); // T[8] = 8P + for i in 1..=7 { + let t = Self::add_montgomery(bld, &table[8], &table[i]); + table.push(t); // T[9..15] + } + assert_eq!(table.len(), 16); + + // Split table into per-coordinate slices for multiplexer + let tab_x: Vec> = table.iter().map(|t| t[..FQ_LEN].to_vec()).collect(); + let tab_y: Vec> = table.iter().map(|t| t[FQ_LEN..2 * FQ_LEN].to_vec()).collect(); + let tab_z: Vec> = table.iter().map(|t| t[2 * FQ_LEN..].to_vec()).collect(); + + // Accumulator starts at infinity + let mut acc = G1Projective::wires_set( + bld, + G1Projective::as_montgomery(ark_bn254::G1Projective::default()), + ) + .to_vec_wires(); + + // scalar is LSB-first; window w covers bits [4w..4w+3], i.e. the MSB window is w=63. + // We iterate MSB-first (Horner): acc = 16·acc + T[window_bits] + let zero_wire = bld.zero(); + let bit = |i: usize| -> usize { + if i < Fr::N_BITS { scalar[i] } else { zero_wire } + }; + + for w in (0..64usize).rev() { + // 4 doublings (skip for first window) + if w != 63 { + for _ in 0..4 { + acc = Self::double_montgomery(bld, &acc); + } + } + // Select table entry by the 4-bit window (bits are still LSB-first per window) + let base = w * 4; + let sel = [bit(base), bit(base + 1), bit(base + 2), bit(base + 3)]; + let mut entry = Fq::multiplexer(bld, &tab_x, &sel, 4); + entry.extend(Fq::multiplexer(bld, &tab_y, &sel, 4)); + entry.extend(Fq::multiplexer(bld, &tab_z, &sel, 4)); + + acc = Self::add_montgomery(bld, &acc, &entry); + } + acc + } + pub fn msm_montgomery_circuit( bld: &mut T, scalars: &[Vec], diff --git a/verifiable-circuit-babe/src/gc/circuit.rs b/verifiable-circuit-babe/src/gc/circuit.rs index 34bed5e..80d1965 100644 --- a/verifiable-circuit-babe/src/gc/circuit.rs +++ b/verifiable-circuit-babe/src/gc/circuit.rs @@ -11,21 +11,21 @@ use crate::dre::{L, N, U_BAR_SIZE, R_PD_SIZE}; /// Compile the BABE circuit structure without fixing witness values. /// /// Input wire allocation order (each 254 bits, LSB-first, normal form): -/// [0..N,..2N) const_x/const_y — coordinates of the scalar-mul base point +/// [0..N,..2N) base_x/base_y — coordinates of the scalar-mul base point /// [2N..3N) pi_x — x-coordinate of the proof point π /// [3N..4N) pi_y — y-coordinate of π /// [4N..5N) x_d — scalar (Fr element) pub fn compile_dsgc(g: G1Affine) -> (CircuitAdapter, Vec) { let mut bld = CircuitAdapter::default(); // base input wires — allocated before π so evaluator can supply them first - let const_x = Fq::wires(&mut bld); - let const_y = Fq::wires(&mut bld); + let base_x = Fq::wires(&mut bld); + let base_y = Fq::wires(&mut bld); // π input wires let pi_x = Fq::wires(&mut bld); let pi_y = Fq::wires(&mut bld); let x_d = Fr::wires(&mut bld); let output_indices = emit_dsgc( - &mut bld, &const_x.0, &const_y.0, &pi_x.0, &pi_y.0, &x_d.0, g, + &mut bld, &base_x.0, &base_y.0, &pi_x.0, &pi_y.0, &x_d.0, g, ); (bld, output_indices) } @@ -35,8 +35,8 @@ pub fn compile_dsgc(g: G1Affine) -> (CircuitAdapter, Vec) { /// [U_BAR_SIZE..L) (X,Y,Z) of x_d·P_D — 3·N bits, projective normal form fn emit_dsgc( bld: &mut CircuitAdapter, - const_x: &[usize], - const_y: &[usize], + base_x: &[usize], + base_y: &[usize], pi_x: &[usize], pi_y: &[usize], x_d: &[usize], @@ -79,25 +79,26 @@ fn emit_dsgc( .map(|k| selector(bld, pi_u_bar[k], g_u_bar[k], on_curve)) .collect(); - // ── x_d · base subcircuit ───────────────────────────────────────────────── + // ── x_d · P_D subcircuit (variable-base, 4-bit window) ─────────────────── + // pd_x/pd_y are garbler-private variable wires; convert affine → Montgomery projective. + let pd_x_m = Fq::mul_by_constant_montgomery(bld, base_x, r_sq); // pd_x · R + let pd_y_m = Fq::mul_by_constant_montgomery(bld, base_y, r_sq); // pd_y · R + // z = 1 in affine; z in Montgomery = R mod p (constant) + let z_mont_val = Fq::as_montgomery(ark_bn254::Fq::from(1u64)); + let z_bits = Fq::to_bits(z_mont_val); + let pd_z_m: Vec = z_bits.iter().take(N).map(|&b| if b { bld.one() } else { bld.zero() }).collect(); - // Convert base coordinates to Montgomery form - let base_x = Fq::mul_by_constant_montgomery(bld, const_x, r_sq); - let base_y = Fq::mul_by_constant_montgomery(bld, const_y, r_sq); - let base_z = Fq::wires_set(bld, Fq::as_montgomery(ark_bn254::Fq::from(1u64))).0.to_vec(); + let mut pd_proj_m: Vec = Vec::with_capacity(3 * N); + pd_proj_m.extend(&pd_x_m); + pd_proj_m.extend(&pd_y_m); + pd_proj_m.extend(&pd_z_m); - // Flat 762-wire projective point (X·R, Y·R, Z·R) - let mut base_wires = base_x; - base_wires.extend(base_y); - base_wires.extend(base_z); - - // Double-and-add: scalar x_d (raw bits) × Montgomery projective point - let prod_proj_m = GcG1Projective::scalar_mul_montgomery_circuit(bld, x_d, &base_wires); + let prod_proj_m = GcG1Projective::scalar_mul_window4_circuit(bld, x_d, &pd_proj_m); // Normalize each coordinate: (X·R, Y·R, Z·R) → (X, Y, Z) let x_out = Fq::mul_by_constant_montgomery(bld, &prod_proj_m[..N], ark_bn254::Fq::from(1u64)); - let y_out = Fq::mul_by_constant_montgomery(bld, &prod_proj_m[N..2 * N], ark_bn254::Fq::from(1u64)); - let z_out = Fq::mul_by_constant_montgomery(bld, &prod_proj_m[2 * N..], ark_bn254::Fq::from(1u64)); + let y_out = Fq::mul_by_constant_montgomery(bld, &prod_proj_m[N..2 * N], ark_bn254::Fq::from(1u64)); + let z_out = Fq::mul_by_constant_montgomery(bld, &prod_proj_m[2 * N..], ark_bn254::Fq::from(1u64)); output_indices.extend(x_out); output_indices.extend(y_out); @@ -155,15 +156,11 @@ mod tests { ark_bn254::G1Projective::rand(&mut rng).into_affine() } - /// Build a full witness: const_x, const_y, pi_x, pi_y, x_d (each N bits, LSB-first). - fn build_witness( - const_point: &G1Affine, - pi: &G1Affine, - x_d: ark_bn254::Fr, - ) -> Vec { - Fq::to_bits(const_point.x) + /// Build a full witness: pi_x, pi_y, pd_x, pd_y, x_d (each N bits, LSB-first). + fn build_witness(pi: &G1Affine, pd: &G1Affine, x_d: ark_bn254::Fr) -> Vec { + Fq::to_bits(pd.x) .into_iter() - .chain(Fq::to_bits(const_point.y)) + .chain(Fq::to_bits(pd.y)) .chain(Fq::to_bits(pi.x)) .chain(Fq::to_bits(pi.y)) .chain(Fr::to_bits(x_d)) @@ -174,10 +171,10 @@ mod tests { fn test_babe_gc_on_curve() { let pi = random_g1_affine(); let g = random_g1_affine(); - let const_point = random_g1_affine(); + let pd = random_g1_affine(); let x_d = ark_bn254::Fr::from(1u64); - let witness = build_witness(&const_point, &pi, x_d); + let witness = build_witness(&pi, &pd, x_d); reset_gid(); let (bld, output_indices) = compile_dsgc(g); @@ -228,13 +225,13 @@ mod tests { fn test_babe_gc_off_curve_falls_back_to_g() { let pi = random_g1_affine(); let g = random_g1_affine(); - let const_point = random_g1_affine(); + let pd = random_g1_affine(); let x_d = ark_bn254::Fr::from(1u64); let bad_y = pi.y + ark_bn254::Fq::from(1u64); let mut off_pi = pi; off_pi.y = bad_y; - let witness = build_witness(&const_point, &off_pi, x_d); + let witness = build_witness(&off_pi, &pd, x_d); reset_gid(); let (bld, output_indices) = compile_dsgc(g); @@ -266,12 +263,12 @@ mod tests { #[test] fn test_babe_gc_xd_pd_output() { let mut rng = rand::thread_rng(); - let g = random_g1_affine(); - let const_point = random_g1_affine(); - let pi = random_g1_affine(); - let x_d = ark_bn254::Fr::rand(&mut rng); + let g = random_g1_affine(); + let pd = random_g1_affine(); + let pi = random_g1_affine(); + let x_d = ark_bn254::Fr::rand(&mut rng); - let witness = build_witness(&const_point, &pi, x_d); + let witness = build_witness(&pi, &pd, x_d); reset_gid(); let (bld, output_indices) = compile_dsgc(g); @@ -288,12 +285,12 @@ mod tests { assert_eq!(output.len(), L); // Reconstruct projective point (X,Y,Z) in normal form from R_PD bits - let X = Fq::from_bits(output[U_BAR_SIZE..U_BAR_SIZE + N].to_vec()); - let Y = Fq::from_bits(output[U_BAR_SIZE + N..U_BAR_SIZE + 2 * N].to_vec()); - let Z = Fq::from_bits(output[U_BAR_SIZE + 2 * N..L].to_vec()); + let x = Fq::from_bits(output[U_BAR_SIZE..U_BAR_SIZE + N].to_vec()); + let y = Fq::from_bits(output[U_BAR_SIZE + N..U_BAR_SIZE + 2 * N].to_vec()); + let z = Fq::from_bits(output[U_BAR_SIZE + 2 * N..L].to_vec()); - let result_proj = ark_bn254::G1Projective::new(X, Y, Z); - let expected_affine = (ark_bn254::G1Projective::from(const_point) * x_d).into_affine(); + let result_proj = ark_bn254::G1Projective::new(x, y, z); + let expected_affine = (ark_bn254::G1Projective::from(pd) * x_d).into_affine(); assert_eq!( result_proj.into_affine(), expected_affine, @@ -310,11 +307,11 @@ mod tests { #[cfg(feature = "garbled")] #[test] fn test_babe_gc_garbled_e2e() { - let mut rng = rand::thread_rng(); - let g = random_g1_affine(); - let const_point = random_g1_affine(); - let pi = random_g1_affine(); - let x_d = ark_bn254::Fr::rand(&mut rng); + let mut rng = rand::thread_rng(); + let g = random_g1_affine(); + let pd = random_g1_affine(); + let pi = random_g1_affine(); + let x_d = ark_bn254::Fr::rand(&mut rng); // 1. Generate circuit reset_gid(); @@ -328,7 +325,7 @@ mod tests { } // 3. Derive input label values from concrete inputs - let witness = build_witness(&const_point, &pi, x_d); + let witness = build_witness(&pi, &pd, x_d); // 4. Evaluate garbled circuit circuit.set_witness_value(&witness); @@ -366,12 +363,12 @@ mod tests { .collect(); assert_eq!(pd_bits.len(), R_PD_SIZE); - let X = Fq::from_bits(pd_bits[..N].to_vec()); - let Y = Fq::from_bits(pd_bits[N..2 * N].to_vec()); - let Z = Fq::from_bits(pd_bits[2 * N..].to_vec()); + let x = Fq::from_bits(pd_bits[..N].to_vec()); + let y = Fq::from_bits(pd_bits[N..2 * N].to_vec()); + let z = Fq::from_bits(pd_bits[2 * N..].to_vec()); - let result_proj = ark_bn254::G1Projective::new(X, Y, Z); - let expected_affine = (ark_bn254::G1Projective::from(const_point) * x_d).into_affine(); + let result_proj = ark_bn254::G1Projective::new(x, y, z); + let expected_affine = (ark_bn254::G1Projective::from(pd) * x_d).into_affine(); assert_eq!( result_proj.into_affine(), expected_affine, "x_d · P_D projective output represents wrong affine point" @@ -387,16 +384,16 @@ mod tests { #[cfg(feature = "garbled")] #[test] fn test_babe_gc_garbled_labels() { - let pi = random_g1_affine(); - let g = random_g1_affine(); - let const_point = random_g1_affine(); - let x_d = ark_bn254::Fr::from(1u64); + let pi = random_g1_affine(); + let g = random_g1_affine(); + let pd = random_g1_affine(); + let x_d = ark_bn254::Fr::from(1u64); reset_gid(); let (bld, output_indices) = compile_dsgc(g); let mut circuit = bld.build(&vec![]); - let witness = build_witness(&const_point, &pi, x_d); + let witness = build_witness(&pi, &pd, x_d); circuit.set_witness_value(&witness); for gate in &mut circuit.1 { gate.evaluate(); @@ -430,9 +427,9 @@ mod tests { fn test_output_labels() { use garbled_snark_verifier::core::utils::NON_CAC_DELTA; - let g = random_g1_affine(); - let const_point = random_g1_affine(); - let x_d = ark_bn254::Fr::from(1u64); + let g = random_g1_affine(); + let pd = random_g1_affine(); + let x_d = ark_bn254::Fr::from(1u64); reset_gid(); let (bld, output_indices) = compile_dsgc(g); @@ -449,7 +446,7 @@ mod tests { let p1b = ark_bn254::G1Projective::rand(&mut rng).into_affine(); let eval = |circuit: &mut Circuit, p: &ark_bn254::G1Affine| -> Vec { - let witness = build_witness(&const_point, p, x_d); + let witness = build_witness(p, &pd, x_d); for wire in circuit.0.iter().skip(2) { wire.borrow_mut().value = None; From 48bf8560a921e59dc0ba6b422f0e269a3144047d Mon Sep 17 00:00:00 2001 From: vanhger Date: Tue, 21 Apr 2026 22:59:13 +0700 Subject: [PATCH 03/32] feat: add 4-window scalar mul with precomputed private table --- garbled-snark-verifier/src/dv_bn254/g1.rs | 168 ++++++++++++++++++++++ verifiable-circuit-babe/src/gc/circuit.rs | 84 ++++++----- 2 files changed, 216 insertions(+), 36 deletions(-) diff --git a/garbled-snark-verifier/src/dv_bn254/g1.rs b/garbled-snark-verifier/src/dv_bn254/g1.rs index 5d93733..3ccb586 100644 --- a/garbled-snark-verifier/src/dv_bn254/g1.rs +++ b/garbled-snark-verifier/src/dv_bn254/g1.rs @@ -19,6 +19,9 @@ pub struct G1Projective { pub z: Fq, } pub const G1_PROJECTIVE_LEN: usize = 3 * FQ_LEN; +const SCALAR_WINDOW_BITS: usize = 4; +const SCALAR_WINDOW_COUNT: usize = (Fr::N_BITS + SCALAR_WINDOW_BITS - 1) / SCALAR_WINDOW_BITS; +const SCALAR_WINDOW_ENTRIES: usize = 1 << SCALAR_WINDOW_BITS; impl G1Projective { pub fn as_montgomery(p: ark_bn254::G1Projective) -> ark_bn254::G1Projective { ark_bn254::G1Projective { @@ -356,6 +359,93 @@ impl G1Projective { res } + /// Mixed projective + affine addition in Montgomery form. + /// + /// `proj` — variable projective point (X·R : Y·R : Z·R), with Z != 0. + /// `affine` — variable affine point encoded as Montgomery wires (x·R, y·R). + /// + /// Returns (X3·R : Y3·R : Z3·R). + pub fn add_mixed_montgomery_no_inf( + bld: &mut T, + proj: &[usize], + affine: &[usize], + ) -> Vec { + assert_eq!(proj.len(), G1_PROJECTIVE_LEN); + assert_eq!(affine.len(), G1_AFFINE_LEN); + + let x1 = &proj[0..FQ_LEN]; + let y1 = &proj[FQ_LEN..2 * FQ_LEN]; + let z1 = &proj[2 * FQ_LEN..3 * FQ_LEN]; + let x2 = &affine[0..FQ_LEN]; + let y2 = &affine[FQ_LEN..2 * FQ_LEN]; + + // z2 = 1 (affine), so z2² = z2³ = 1 and u1 = x1, s1 = y1. + let z1s = Fq::square_montgomery(bld, z1); + let z1c = Fq::mul_montgomery(bld, &z1s, z1); + + let u2 = Fq::mul_montgomery(bld, x2, &z1s); + let s2 = Fq::mul_montgomery(bld, y2, &z1c); + + let h = Fq::sub(bld, x1, &u2); + let r = Fq::sub(bld, y1, &s2); + + let h2 = Fq::square_montgomery(bld, &h); + let g = Fq::mul_montgomery(bld, &h, &h2); + let v = Fq::mul_montgomery(bld, x1, &h2); + + let r2 = Fq::square_montgomery(bld, &r); + let r2g = Fq::add(bld, &r2, &g); + let vd = Fq::double(bld, &v); + let x3 = Fq::sub(bld, &r2g, &vd); + + let vx3 = Fq::sub(bld, &v, &x3); + let w = Fq::mul_montgomery(bld, &r, &vx3); + let s1g = Fq::mul_montgomery(bld, y1, &g); + let y3 = Fq::sub(bld, &w, &s1g); + + // z2 = 1, so z1·z2 = z1. + let z3 = Fq::mul_montgomery(bld, z1, &h); + + let mut res = Vec::new(); + res.extend(x3); + res.extend(y3); + res.extend(z3); + res + } + + /// Mixed projective + affine addition in Montgomery form. + /// + /// `proj` — variable projective point (X·R : Y·R : Z·R). + /// `affine` — variable affine point encoded as Montgomery wires (x·R, y·R). + /// + /// Returns (X3·R : Y3·R : Z3·R). Handles the `proj = infinity` case by + /// returning the affine input as a projective point with Z = R. + pub fn add_mixed_montgomery( + bld: &mut T, + proj: &[usize], + affine: &[usize], + ) -> Vec { + assert_eq!(proj.len(), G1_PROJECTIVE_LEN); + assert_eq!(affine.len(), G1_AFFINE_LEN); + + let x2 = &affine[0..FQ_LEN]; + let y2 = &affine[FQ_LEN..2 * FQ_LEN]; + let mixed = Self::add_mixed_montgomery_no_inf(bld, proj, affine); + + let z1_0 = Fq::equal_zero(bld, &proj[2 * FQ_LEN..3 * FQ_LEN]); + let z_one = Fq::wires_set(bld, Fq::as_montgomery(ark_bn254::Fq::from(1u64))).0.to_vec(); + + let x = Fq::mux_vec(bld, z1_0, x2, &mixed[..FQ_LEN]); + let y = Fq::mux_vec(bld, z1_0, y2, &mixed[FQ_LEN..2 * FQ_LEN]); + let z = Fq::mux_vec(bld, z1_0, &z_one, &mixed[2 * FQ_LEN..3 * FQ_LEN]); + + let mut res = Vec::new(); + res.extend(x); + res.extend(y); + res.extend(z); + res + } + /// Variable-base scalar multiplication using a 4-bit window (Horner / left-to-right). /// /// Precomputes table T[0..16] in-circuit (T[0]=inf, T[1]=P, …, T[15]=15P) then processes @@ -433,6 +523,84 @@ impl G1Projective { acc } + /// Scalar multiplication using a garbler-private precomputed affine table. + /// + /// `scalar` — 254 wire indices, raw (non-Montgomery) bits, LSB-first. + /// `table` — `SCALAR_WINDOW_COUNT` windows of `SCALAR_WINDOW_ENTRIES` affine points, + /// each encoded as Montgomery wires (x·R, y·R). + /// Window `w`, entry `j` represents `j * (16^w * P)` in affine form. + pub fn scalar_mul_private_table_circuit( + bld: &mut T, + scalar: &[usize], + table: &[usize], + ) -> Vec { + assert_eq!(scalar.len(), Fr::N_BITS); + assert_eq!(table.len(), SCALAR_WINDOW_COUNT * SCALAR_WINDOW_ENTRIES * G1_AFFINE_LEN); + + let mut acc = G1Projective::wires_set( + bld, + G1Projective::as_montgomery(ark_bn254::G1Projective::default()), + ) + .to_vec_wires(); + let z_one = Fq::wires_set(bld, Fq::as_montgomery(ark_bn254::Fq::from(1u64))).0.to_vec(); + let mut acc_is_inf = bld.one(); + let zero_wire = bld.zero(); + + let bit = |i: usize| -> usize { + if i < Fr::N_BITS { scalar[i] } else { zero_wire } + }; + + for w in 0..SCALAR_WINDOW_COUNT { + let base = w * SCALAR_WINDOW_BITS; + let sel = [bit(base), bit(base + 1), bit(base + 2), bit(base + 3)]; + + let window_nonzero = { + let tmp = bld.or_wire(sel[0], sel[1]); + let tmp = bld.or_wire(tmp, sel[2]); + bld.or_wire(tmp, sel[3]) + }; + + let window_base = w * SCALAR_WINDOW_ENTRIES * G1_AFFINE_LEN; + let window_slice = &table[window_base..window_base + SCALAR_WINDOW_ENTRIES * G1_AFFINE_LEN]; + + let tab_x: Vec> = (0..SCALAR_WINDOW_ENTRIES) + .map(|i| { + let start = i * G1_AFFINE_LEN; + window_slice[start..start + FQ_LEN].to_vec() + }) + .collect(); + let tab_y: Vec> = (0..SCALAR_WINDOW_ENTRIES) + .map(|i| { + let start = i * G1_AFFINE_LEN + FQ_LEN; + window_slice[start..start + FQ_LEN].to_vec() + }) + .collect(); + + let affine_x = Fq::multiplexer(bld, &tab_x, &sel, SCALAR_WINDOW_BITS); + let affine_y = Fq::multiplexer(bld, &tab_y, &sel, SCALAR_WINDOW_BITS); + + let mut affine = Vec::with_capacity(G1_AFFINE_LEN); + affine.extend_from_slice(&affine_x); + affine.extend_from_slice(&affine_y); + + let mixed = Self::add_mixed_montgomery_no_inf(bld, &acc, &affine); + + let mut affine_proj = Vec::with_capacity(G1_PROJECTIVE_LEN); + affine_proj.extend_from_slice(&affine[..FQ_LEN]); + affine_proj.extend_from_slice(&affine[FQ_LEN..2 * FQ_LEN]); + affine_proj.extend_from_slice(&z_one); + + let candidate = + Self::selector_projective_montgomery(bld, &affine_proj, &mixed, acc_is_inf); + acc = Self::selector_projective_montgomery(bld, &candidate, &acc, window_nonzero); + + let not_nonzero = not(bld, window_nonzero); + acc_is_inf = bld.and_wire(acc_is_inf, not_nonzero); + } + + acc + } + pub fn msm_montgomery_circuit( bld: &mut T, scalars: &[Vec], diff --git a/verifiable-circuit-babe/src/gc/circuit.rs b/verifiable-circuit-babe/src/gc/circuit.rs index 80d1965..d350e3f 100644 --- a/verifiable-circuit-babe/src/gc/circuit.rs +++ b/verifiable-circuit-babe/src/gc/circuit.rs @@ -8,25 +8,30 @@ use garbled_snark_verifier::dv_bn254::g1::G1Projective as GcG1Projective; use crate::dre::{L, N, U_BAR_SIZE, R_PD_SIZE}; +const WINDOW_BITS: usize = 4; +const WINDOW_COUNT: usize = (Fr::N_BITS + WINDOW_BITS - 1) / WINDOW_BITS; +const WINDOW_ENTRIES: usize = 1 << WINDOW_BITS; +const PRECOMP_TABLE_BITS: usize = WINDOW_COUNT * WINDOW_ENTRIES * 2 * N; + /// Compile the BABE circuit structure without fixing witness values. /// -/// Input wire allocation order (each 254 bits, LSB-first, normal form): -/// [0..N,..2N) base_x/base_y — coordinates of the scalar-mul base point -/// [2N..3N) pi_x — x-coordinate of the proof point π -/// [3N..4N) pi_y — y-coordinate of π -/// [4N..5N) x_d — scalar (Fr element) +/// Input wire allocation order: +/// [0..PRECOMP_TABLE_BITS) precomputed table for P_D, +/// 64 windows × 16 affine points in Montgomery form +/// [..+N) pi_x — x-coordinate of the proof point π +/// [..+N) pi_y — y-coordinate of π +/// [..+N) x_d — scalar (Fr element) pub fn compile_dsgc(g: G1Affine) -> (CircuitAdapter, Vec) { let mut bld = CircuitAdapter::default(); - // base input wires — allocated before π so evaluator can supply them first - let base_x = Fq::wires(&mut bld); - let base_y = Fq::wires(&mut bld); + let mut table_wires = Vec::with_capacity(PRECOMP_TABLE_BITS); + for _ in 0..(WINDOW_COUNT * WINDOW_ENTRIES * 2) { + table_wires.extend(Fq::wires(&mut bld).0); + } // π input wires let pi_x = Fq::wires(&mut bld); let pi_y = Fq::wires(&mut bld); let x_d = Fr::wires(&mut bld); - let output_indices = emit_dsgc( - &mut bld, &base_x.0, &base_y.0, &pi_x.0, &pi_y.0, &x_d.0, g, - ); + let output_indices = emit_dsgc(&mut bld, &table_wires, &pi_x.0, &pi_y.0, &x_d.0, g); (bld, output_indices) } @@ -35,13 +40,14 @@ pub fn compile_dsgc(g: G1Affine) -> (CircuitAdapter, Vec) { /// [U_BAR_SIZE..L) (X,Y,Z) of x_d·P_D — 3·N bits, projective normal form fn emit_dsgc( bld: &mut CircuitAdapter, - base_x: &[usize], - base_y: &[usize], + table_wires: &[usize], pi_x: &[usize], pi_y: &[usize], x_d: &[usize], g: G1Affine, ) -> Vec { + assert_eq!(table_wires.len(), PRECOMP_TABLE_BITS); + // R² mod p — multiply by this to convert normal → Montgomery form let r_sq = Fq::as_montgomery(Fq::as_montgomery(ark_bn254::Fq::from(1u64))); @@ -79,21 +85,8 @@ fn emit_dsgc( .map(|k| selector(bld, pi_u_bar[k], g_u_bar[k], on_curve)) .collect(); - // ── x_d · P_D subcircuit (variable-base, 4-bit window) ─────────────────── - // pd_x/pd_y are garbler-private variable wires; convert affine → Montgomery projective. - let pd_x_m = Fq::mul_by_constant_montgomery(bld, base_x, r_sq); // pd_x · R - let pd_y_m = Fq::mul_by_constant_montgomery(bld, base_y, r_sq); // pd_y · R - // z = 1 in affine; z in Montgomery = R mod p (constant) - let z_mont_val = Fq::as_montgomery(ark_bn254::Fq::from(1u64)); - let z_bits = Fq::to_bits(z_mont_val); - let pd_z_m: Vec = z_bits.iter().take(N).map(|&b| if b { bld.one() } else { bld.zero() }).collect(); - - let mut pd_proj_m: Vec = Vec::with_capacity(3 * N); - pd_proj_m.extend(&pd_x_m); - pd_proj_m.extend(&pd_y_m); - pd_proj_m.extend(&pd_z_m); - - let prod_proj_m = GcG1Projective::scalar_mul_window4_circuit(bld, x_d, &pd_proj_m); + // ── x_d · P_D subcircuit (garbler-private windowed precomputed table) ──── + let prod_proj_m = GcG1Projective::scalar_mul_private_table_circuit(bld, x_d, table_wires); // Normalize each coordinate: (X·R, Y·R, Z·R) → (X, Y, Z) let x_out = Fq::mul_by_constant_montgomery(bld, &prod_proj_m[..N], ark_bn254::Fq::from(1u64)); @@ -146,7 +139,7 @@ pub fn gc_ciphertexts_commit(ciphertexts: &[Option Vec { + let mut bits = Vec::with_capacity(PRECOMP_TABLE_BITS); + let mut window_base = ark_bn254::G1Projective::from(pd.clone()); + + for _ in 0..WINDOW_COUNT { + let mut multiple = ark_bn254::G1Projective::zero(); + for _ in 0..WINDOW_ENTRIES { + let aff = multiple.clone().into_affine(); + bits.extend(Fq::to_bits(Fq::as_montgomery(aff.x))); + bits.extend(Fq::to_bits(Fq::as_montgomery(aff.y))); + multiple += window_base; + } + + for _ in 0..WINDOW_BITS { + window_base.double_in_place(); + } + } + bits + } + + /// Build a full witness: precomputed window table for P_D, pi_x, pi_y, x_d. fn build_witness(pi: &G1Affine, pd: &G1Affine, x_d: ark_bn254::Fr) -> Vec { - Fq::to_bits(pd.x) + build_pd_table_bits(pd) .into_iter() - .chain(Fq::to_bits(pd.y)) .chain(Fq::to_bits(pi.x)) .chain(Fq::to_bits(pi.y)) .chain(Fr::to_bits(x_d)) @@ -318,8 +330,8 @@ mod tests { let (bld, output_indices) = compile_dsgc(g); let mut circuit = bld.build(&[]); - // 2. Generate a new set of labels — one label0 per input wire (5·N wires) - let encoding_keys: Vec = (0..5 * N).map(|_| S::random()).collect(); + // 2. Generate a new set of labels — one label0 per input wire. + let encoding_keys: Vec = (0..(PRECOMP_TABLE_BITS + 3 * N)).map(|_| S::random()).collect(); for (i, &key) in encoding_keys.iter().enumerate() { circuit.0[2 + i].borrow_mut().label = Some(key); } @@ -435,8 +447,8 @@ mod tests { let (bld, output_indices) = compile_dsgc(g); let mut circuit = bld.build(&[]); - // Encoding keys for all 5·N input wires - let encoding_keys: Vec = (0..5 * N).map(|_| S::random()).collect(); + // Encoding keys for all input wires + let encoding_keys: Vec = (0..(PRECOMP_TABLE_BITS + 3 * N)).map(|_| S::random()).collect(); for (i, &key) in encoding_keys.iter().enumerate() { circuit.0[2 + i].borrow_mut().label = Some(key); } From 1b823c6cea402eb95c83567f67face327bdb30b9 Mon Sep 17 00:00:00 2001 From: vanhger Date: Wed, 22 Apr 2026 20:29:14 +0700 Subject: [PATCH 04/32] opt: 8-windowed scalar mul with signed digit. --- garbled-snark-verifier/src/dv_bn254/g1.rs | 246 +++++--------- verifiable-circuit-babe/src/gc/circuit.rs | 384 ++++++++++------------ verifiable-circuit-babe/src/gc/mod.rs | 2 +- 3 files changed, 263 insertions(+), 369 deletions(-) diff --git a/garbled-snark-verifier/src/dv_bn254/g1.rs b/garbled-snark-verifier/src/dv_bn254/g1.rs index 3ccb586..58c5106 100644 --- a/garbled-snark-verifier/src/dv_bn254/g1.rs +++ b/garbled-snark-verifier/src/dv_bn254/g1.rs @@ -5,7 +5,7 @@ use crate::{ }; use crate::circuits::bn254::utils::create_rng; use ark_ff::{AdditiveGroup, UniformRand}; -use ark_ec::{CurveGroup, PrimeGroup}; +use ark_ec::PrimeGroup; use ark_ec::short_weierstrass::SWCurveConfig; use crate::circuits::sect233k1::builder::CircuitTrait; use crate::dv_bn254::basic::{not, selector}; @@ -19,9 +19,25 @@ pub struct G1Projective { pub z: Fq, } pub const G1_PROJECTIVE_LEN: usize = 3 * FQ_LEN; -const SCALAR_WINDOW_BITS: usize = 4; -const SCALAR_WINDOW_COUNT: usize = (Fr::N_BITS + SCALAR_WINDOW_BITS - 1) / SCALAR_WINDOW_BITS; -const SCALAR_WINDOW_ENTRIES: usize = 1 << SCALAR_WINDOW_BITS; +/// Width of each scalar-mul window, in bits. w=8 gives 32 windows for a 254-bit scalar +/// (vs 64 for w=4), so the dominant Fq-mul cost drops roughly 2× at the price of a +/// 16× larger precomputed affine table (free in gates; only bandwidth). +pub const SCALAR_WINDOW_BITS: usize = 8; +pub const SCALAR_WINDOW_COUNT: usize = (Fr::N_BITS + SCALAR_WINDOW_BITS - 1) / SCALAR_WINDOW_BITS; +pub const SCALAR_WINDOW_ENTRIES: usize = 1 << SCALAR_WINDOW_BITS; + +// ─── Signed-digit scalar-mul parameters ──────────────────────────────────────── +// Scalar is recoded offline (Booth) into digits d_i ∈ [-128, 127], one extra window +// absorbs the carry. Each window carries (w-1)=7 index bits + 1 sign bit + 1 skip bit = 9 bits. +// Table is halved: 2^(w-1) = 128 entries per window, storing (j+1)·256^i·P for j ∈ [0, 127]. +// Saves ~50 % of the MUX cost vs. the unsigned version at the price of a conditional +// y-negation (near-free) and a single extra window. +pub const SIGNED_SCALAR_WINDOW_BITS: usize = 8; +pub const SIGNED_SCALAR_WINDOW_COUNT: usize = + (Fr::N_BITS + SIGNED_SCALAR_WINDOW_BITS - 1) / SIGNED_SCALAR_WINDOW_BITS + 1; +pub const SIGNED_SCALAR_WINDOW_ENTRIES: usize = 1 << (SIGNED_SCALAR_WINDOW_BITS - 1); +pub const SIGNED_DIGIT_BITS: usize = SIGNED_SCALAR_WINDOW_BITS + 1; +pub const SIGNED_SCALAR_DIGIT_TOTAL_BITS: usize = SIGNED_SCALAR_WINDOW_COUNT * SIGNED_DIGIT_BITS; impl G1Projective { pub fn as_montgomery(p: ark_bn254::G1Projective) -> ark_bn254::G1Projective { ark_bn254::G1Projective { @@ -413,189 +429,97 @@ impl G1Projective { res } - /// Mixed projective + affine addition in Montgomery form. + /// Signed-digit scalar multiplication with a garbler-private halved affine table. /// - /// `proj` — variable projective point (X·R : Y·R : Z·R). - /// `affine` — variable affine point encoded as Montgomery wires (x·R, y·R). + /// `digits` — Per window (9 bits): `[idx_0 .. idx_{w-2}][sign][skip]`, LSB-first indices. + /// * `idx` (7 bits) selects entry `j` ∈ [0, 127] from the table. + /// * `sign` = 1 applies y → −y (digit is negative). + /// * `skip` = 1 means the digit is zero (omit the add for this window). /// - /// Returns (X3·R : Y3·R : Z3·R). Handles the `proj = infinity` case by - /// returning the affine input as a projective point with Z = R. - pub fn add_mixed_montgomery( + /// `table` — `SIGNED_SCALAR_WINDOW_COUNT * SIGNED_SCALAR_WINDOW_ENTRIES` affine points. + /// Window `i`, entry `j` represents `(j+1) · (256^i) · P` in affine Montgomery. + pub fn scalar_mul_private_signed_table_circuit( bld: &mut T, - proj: &[usize], - affine: &[usize], + digits: &[usize], + table: &[usize], ) -> Vec { - assert_eq!(proj.len(), G1_PROJECTIVE_LEN); - assert_eq!(affine.len(), G1_AFFINE_LEN); - - let x2 = &affine[0..FQ_LEN]; - let y2 = &affine[FQ_LEN..2 * FQ_LEN]; - let mixed = Self::add_mixed_montgomery_no_inf(bld, proj, affine); + assert_eq!(digits.len(), SIGNED_SCALAR_WINDOW_COUNT * SIGNED_DIGIT_BITS); + assert_eq!( + table.len(), + SIGNED_SCALAR_WINDOW_COUNT * SIGNED_SCALAR_WINDOW_ENTRIES * G1_AFFINE_LEN + ); - let z1_0 = Fq::equal_zero(bld, &proj[2 * FQ_LEN..3 * FQ_LEN]); let z_one = Fq::wires_set(bld, Fq::as_montgomery(ark_bn254::Fq::from(1u64))).0.to_vec(); - - let x = Fq::mux_vec(bld, z1_0, x2, &mixed[..FQ_LEN]); - let y = Fq::mux_vec(bld, z1_0, y2, &mixed[FQ_LEN..2 * FQ_LEN]); - let z = Fq::mux_vec(bld, z1_0, &z_one, &mixed[2 * FQ_LEN..3 * FQ_LEN]); - - let mut res = Vec::new(); - res.extend(x); - res.extend(y); - res.extend(z); - res - } - - /// Variable-base scalar multiplication using a 4-bit window (Horner / left-to-right). - /// - /// Precomputes table T[0..16] in-circuit (T[0]=inf, T[1]=P, …, T[15]=15P) then processes - /// 64 windows of 4 bits MSB-first: 4 doublings + 16-way mux + 1 full add per window. - /// - /// `scalar` — 254 wire indices, raw (non-Montgomery) bits, **LSB-first**. - /// `point` — 762 wire indices, Montgomery projective (X·R : Y·R : Z·R). - /// Returns projective (X·R : Y·R : Z·R). - pub fn scalar_mul_window4_circuit( - bld: &mut T, - scalar: &[usize], - point: &[usize], - ) -> Vec { - assert_eq!(scalar.len(), Fr::N_BITS); - assert_eq!(point.len(), G1_PROJECTIVE_LEN); - - // Build table T[0..16]: T[0]=inf, T[1]=P, T[2]=2P, …, T[15]=15P let inf_wires = G1Projective::wires_set( bld, G1Projective::as_montgomery(ark_bn254::G1Projective::default()), ) - .to_vec_wires(); - let mut table: Vec> = Vec::with_capacity(16); - table.push(inf_wires); // T[0] = inf - table.push(point.to_vec()); // T[1] = P - table.push(Self::double_montgomery(bld, &table[1])); // T[2] = 2P - table.push(Self::add_montgomery(bld, &table[2], &table[1])); // T[3] = 3P - table.push(Self::double_montgomery(bld, &table[2])); // T[4] = 4P - for i in 1..=3 { - let t = Self::add_montgomery(bld, &table[4], &table[i]); - table.push(t); // T[5..7] - } - table.push(Self::double_montgomery(bld, &table[4])); // T[8] = 8P - for i in 1..=7 { - let t = Self::add_montgomery(bld, &table[8], &table[i]); - table.push(t); // T[9..15] - } - assert_eq!(table.len(), 16); - - // Split table into per-coordinate slices for multiplexer - let tab_x: Vec> = table.iter().map(|t| t[..FQ_LEN].to_vec()).collect(); - let tab_y: Vec> = table.iter().map(|t| t[FQ_LEN..2 * FQ_LEN].to_vec()).collect(); - let tab_z: Vec> = table.iter().map(|t| t[2 * FQ_LEN..].to_vec()).collect(); - - // Accumulator starts at infinity - let mut acc = G1Projective::wires_set( - bld, - G1Projective::as_montgomery(ark_bn254::G1Projective::default()), - ) - .to_vec_wires(); - - // scalar is LSB-first; window w covers bits [4w..4w+3], i.e. the MSB window is w=63. - // We iterate MSB-first (Horner): acc = 16·acc + T[window_bits] - let zero_wire = bld.zero(); - let bit = |i: usize| -> usize { - if i < Fr::N_BITS { scalar[i] } else { zero_wire } - }; - - for w in (0..64usize).rev() { - // 4 doublings (skip for first window) - if w != 63 { - for _ in 0..4 { - acc = Self::double_montgomery(bld, &acc); - } - } - // Select table entry by the 4-bit window (bits are still LSB-first per window) - let base = w * 4; - let sel = [bit(base), bit(base + 1), bit(base + 2), bit(base + 3)]; - let mut entry = Fq::multiplexer(bld, &tab_x, &sel, 4); - entry.extend(Fq::multiplexer(bld, &tab_y, &sel, 4)); - entry.extend(Fq::multiplexer(bld, &tab_z, &sel, 4)); - - acc = Self::add_montgomery(bld, &acc, &entry); - } - acc - } - - /// Scalar multiplication using a garbler-private precomputed affine table. - /// - /// `scalar` — 254 wire indices, raw (non-Montgomery) bits, LSB-first. - /// `table` — `SCALAR_WINDOW_COUNT` windows of `SCALAR_WINDOW_ENTRIES` affine points, - /// each encoded as Montgomery wires (x·R, y·R). - /// Window `w`, entry `j` represents `j * (16^w * P)` in affine form. - pub fn scalar_mul_private_table_circuit( - bld: &mut T, - scalar: &[usize], - table: &[usize], - ) -> Vec { - assert_eq!(scalar.len(), Fr::N_BITS); - assert_eq!(table.len(), SCALAR_WINDOW_COUNT * SCALAR_WINDOW_ENTRIES * G1_AFFINE_LEN); - - let mut acc = G1Projective::wires_set( - bld, - G1Projective::as_montgomery(ark_bn254::G1Projective::default()), - ) - .to_vec_wires(); - let z_one = Fq::wires_set(bld, Fq::as_montgomery(ark_bn254::Fq::from(1u64))).0.to_vec(); - let mut acc_is_inf = bld.one(); - let zero_wire = bld.zero(); + .to_vec_wires(); - let bit = |i: usize| -> usize { - if i < Fr::N_BITS { scalar[i] } else { zero_wire } - }; + let mut acc: Vec = inf_wires.clone(); + let mut acc_is_inf: usize = bld.one(); - for w in 0..SCALAR_WINDOW_COUNT { - let base = w * SCALAR_WINDOW_BITS; - let sel = [bit(base), bit(base + 1), bit(base + 2), bit(base + 3)]; - - let window_nonzero = { - let tmp = bld.or_wire(sel[0], sel[1]); - let tmp = bld.or_wire(tmp, sel[2]); - bld.or_wire(tmp, sel[3]) - }; + for w in 0..SIGNED_SCALAR_WINDOW_COUNT { + // ─── extract digit bits for this window ───────────────────────── + let digit_base = w * SIGNED_DIGIT_BITS; + let idx_bits: Vec = (0..(SIGNED_SCALAR_WINDOW_BITS - 1)) + .map(|k| digits[digit_base + k]) + .collect(); + let sign_bit = digits[digit_base + SIGNED_SCALAR_WINDOW_BITS - 1]; + let skip_bit = digits[digit_base + SIGNED_SCALAR_WINDOW_BITS]; - let window_base = w * SCALAR_WINDOW_ENTRIES * G1_AFFINE_LEN; - let window_slice = &table[window_base..window_base + SCALAR_WINDOW_ENTRIES * G1_AFFINE_LEN]; + // ─── 128-wide MUX on 7 idx bits ───────────────────────────────── + let window_base = w * SIGNED_SCALAR_WINDOW_ENTRIES * G1_AFFINE_LEN; + let ws = + &table[window_base..window_base + SIGNED_SCALAR_WINDOW_ENTRIES * G1_AFFINE_LEN]; - let tab_x: Vec> = (0..SCALAR_WINDOW_ENTRIES) - .map(|i| { - let start = i * G1_AFFINE_LEN; - window_slice[start..start + FQ_LEN].to_vec() - }) + let tab_x: Vec> = (0..SIGNED_SCALAR_WINDOW_ENTRIES) + .map(|i| ws[i * G1_AFFINE_LEN..i * G1_AFFINE_LEN + FQ_LEN].to_vec()) .collect(); - let tab_y: Vec> = (0..SCALAR_WINDOW_ENTRIES) + let tab_y: Vec> = (0..SIGNED_SCALAR_WINDOW_ENTRIES) .map(|i| { - let start = i * G1_AFFINE_LEN + FQ_LEN; - window_slice[start..start + FQ_LEN].to_vec() + ws[i * G1_AFFINE_LEN + FQ_LEN..(i + 1) * G1_AFFINE_LEN].to_vec() }) .collect(); - let affine_x = Fq::multiplexer(bld, &tab_x, &sel, SCALAR_WINDOW_BITS); - let affine_y = Fq::multiplexer(bld, &tab_y, &sel, SCALAR_WINDOW_BITS); + let affine_x = Fq::multiplexer(bld, &tab_x, &idx_bits, SIGNED_SCALAR_WINDOW_BITS - 1); + let affine_y = Fq::multiplexer(bld, &tab_y, &idx_bits, SIGNED_SCALAR_WINDOW_BITS - 1); + // Conditional y-negation (sign bit → returns −y if sign=1, else y). + let affine_y_signed = Fq::negate_with_selector(bld, &affine_y, sign_bit); let mut affine = Vec::with_capacity(G1_AFFINE_LEN); affine.extend_from_slice(&affine_x); - affine.extend_from_slice(&affine_y); - - let mixed = Self::add_mixed_montgomery_no_inf(bld, &acc, &affine); + affine.extend_from_slice(&affine_y_signed); let mut affine_proj = Vec::with_capacity(G1_PROJECTIVE_LEN); - affine_proj.extend_from_slice(&affine[..FQ_LEN]); - affine_proj.extend_from_slice(&affine[FQ_LEN..2 * FQ_LEN]); + affine_proj.extend_from_slice(&affine_x); + affine_proj.extend_from_slice(&affine_y_signed); affine_proj.extend_from_slice(&z_one); - let candidate = - Self::selector_projective_montgomery(bld, &affine_proj, &mixed, acc_is_inf); - acc = Self::selector_projective_montgomery(bld, &candidate, &acc, window_nonzero); - - let not_nonzero = not(bld, window_nonzero); - acc_is_inf = bld.and_wire(acc_is_inf, not_nonzero); + if w == 0 { + // Unrolled first window: init acc directly from affine_proj (Z=1) when not skipped. + let not_skip = not(bld, skip_bit); + acc = Self::selector_projective_montgomery( + bld, + &affine_proj, + &inf_wires, + not_skip, + ); + acc_is_inf = skip_bit; + } else { + let mixed = Self::add_mixed_montgomery_no_inf(bld, &acc, &affine); + + let candidate = Self::selector_projective_montgomery( + bld, + &affine_proj, + &mixed, + acc_is_inf, + ); + let not_skip = not(bld, skip_bit); + acc = Self::selector_projective_montgomery(bld, &candidate, &acc, not_skip); + + acc_is_inf = bld.and_wire(acc_is_inf, skip_bit); + } } acc diff --git a/verifiable-circuit-babe/src/gc/circuit.rs b/verifiable-circuit-babe/src/gc/circuit.rs index d350e3f..4f6868c 100644 --- a/verifiable-circuit-babe/src/gc/circuit.rs +++ b/verifiable-circuit-babe/src/gc/circuit.rs @@ -6,44 +6,56 @@ use garbled_snark_verifier::dv_bn254::fp254impl::Fp254Impl; use garbled_snark_verifier::dv_bn254::{fq::Fq, fr::Fr}; use garbled_snark_verifier::dv_bn254::g1::G1Projective as GcG1Projective; -use crate::dre::{L, N, U_BAR_SIZE, R_PD_SIZE}; - -const WINDOW_BITS: usize = 4; -const WINDOW_COUNT: usize = (Fr::N_BITS + WINDOW_BITS - 1) / WINDOW_BITS; -const WINDOW_ENTRIES: usize = 1 << WINDOW_BITS; -const PRECOMP_TABLE_BITS: usize = WINDOW_COUNT * WINDOW_ENTRIES * 2 * N; - -/// Compile the BABE circuit structure without fixing witness values. +use crate::dre::{L, N, U_BAR_SIZE}; + +// Signed-digit scalar-mul parameters. Must match the `SIGNED_SCALAR_*` constants in +// garbled-snark-verifier's dv_bn254::g1. With w=8 we do ≤ 33 mixed-adds over a +// half-sized (128-entry) precomputed table; the halved MUX saves ~6M AND vs the +// unsigned w=8 version. Digits d_i ∈ [-128, 127] are produced offline via +// `booth_recode_w8`; each window carries 7 index + 1 sign + 1 skip = 9 bits. +pub const WINDOW_BITS: usize = 8; +pub const WINDOW_COUNT: usize = (Fr::N_BITS + WINDOW_BITS - 1) / WINDOW_BITS + 1; // +1 for Booth carry +pub const WINDOW_ENTRIES: usize = 1 << (WINDOW_BITS - 1); // halved: 128 +pub const DIGIT_BITS: usize = WINDOW_BITS + 1; // 7 idx + 1 sign + 1 skip +pub const PRECOMP_TABLE_BITS: usize = WINDOW_COUNT * WINDOW_ENTRIES * 2 * N; +pub const DIGIT_TOTAL_BITS: usize = WINDOW_COUNT * DIGIT_BITS; + +/// Compile the DSGC circuit structure without fixing witness values. /// /// Input wire allocation order: -/// [0..PRECOMP_TABLE_BITS) precomputed table for P_D, -/// 64 windows × 16 affine points in Montgomery form +/// [0..PRECOMP_TABLE_BITS) signed-digit precomputed table for Base, +/// WINDOW_COUNT windows × WINDOW_ENTRIES affine points +/// (entry j = (j+1)·256^i·P), Montgomery form /// [..+N) pi_x — x-coordinate of the proof point π /// [..+N) pi_y — y-coordinate of π -/// [..+N) x_d — scalar (Fr element) +/// [..+DIGIT_TOTAL_BITS) x_d recoded into Booth digits, 9 bits per window pub fn compile_dsgc(g: G1Affine) -> (CircuitAdapter, Vec) { let mut bld = CircuitAdapter::default(); let mut table_wires = Vec::with_capacity(PRECOMP_TABLE_BITS); for _ in 0..(WINDOW_COUNT * WINDOW_ENTRIES * 2) { table_wires.extend(Fq::wires(&mut bld).0); } + + // Signed-digit encoding of x_d (33 × 9 = 297 bits) + let digit_wires: Vec = (0..DIGIT_TOTAL_BITS).map(|_| bld.fresh_one()).collect(); + // π input wires let pi_x = Fq::wires(&mut bld); let pi_y = Fq::wires(&mut bld); - let x_d = Fr::wires(&mut bld); - let output_indices = emit_dsgc(&mut bld, &table_wires, &pi_x.0, &pi_y.0, &x_d.0, g); + + let output_indices = emit_dsgc(&mut bld, &table_wires, &digit_wires, &pi_x.0, &pi_y.0, g); (bld, output_indices) } /// Output layout (total L = U_BAR_SIZE + R_PD_SIZE = 2033 bits): /// [0..U_BAR_SIZE) ū(π) or ū(g) — 1 + 5·N bits, LSB-first -/// [U_BAR_SIZE..L) (X,Y,Z) of x_d·P_D — 3·N bits, projective normal form +/// [U_BAR_SIZE..L) (X,Y,Z) of x_d·P_D — 3·N bits, projective Montgomery form fn emit_dsgc( bld: &mut CircuitAdapter, table_wires: &[usize], + digits: &[usize], pi_x: &[usize], pi_y: &[usize], - x_d: &[usize], g: G1Affine, ) -> Vec { assert_eq!(table_wires.len(), PRECOMP_TABLE_BITS); @@ -85,17 +97,14 @@ fn emit_dsgc( .map(|k| selector(bld, pi_u_bar[k], g_u_bar[k], on_curve)) .collect(); - // ── x_d · P_D subcircuit (garbler-private windowed precomputed table) ──── - let prod_proj_m = GcG1Projective::scalar_mul_private_table_circuit(bld, x_d, table_wires); - - // Normalize each coordinate: (X·R, Y·R, Z·R) → (X, Y, Z) - let x_out = Fq::mul_by_constant_montgomery(bld, &prod_proj_m[..N], ark_bn254::Fq::from(1u64)); - let y_out = Fq::mul_by_constant_montgomery(bld, &prod_proj_m[N..2 * N], ark_bn254::Fq::from(1u64)); - let z_out = Fq::mul_by_constant_montgomery(bld, &prod_proj_m[2 * N..], ark_bn254::Fq::from(1u64)); + // ── x_d · Base subcircuit (garbler-private signed-digit windowed table) ──── + let prod_proj_m = + GcG1Projective::scalar_mul_private_signed_table_circuit(bld, digits, table_wires); - output_indices.extend(x_out); - output_indices.extend(y_out); - output_indices.extend(z_out); + // Output stays in Montgomery form (X·R, Y·R, Z·R). + output_indices.extend_from_slice(&prod_proj_m[..N]); + output_indices.extend_from_slice(&prod_proj_m[N..2 * N]); + output_indices.extend_from_slice(&prod_proj_m[2 * N..]); assert_eq!(output_indices.len(), L); output_indices @@ -123,6 +132,59 @@ fn g_u_bar_indices(bld: &mut CircuitAdapter, g: G1Affine) -> Vec { indices } +/// Booth-recode `k` into `WINDOW_COUNT` signed digits d_i ∈ [-128, 127] satisfying +/// `Σ_i d_i · 256^i ≡ k (mod r)`. The extra window absorbs the final carry. +pub fn booth_recode_w8(k: ark_bn254::Fr) -> Vec { + use ark_ff::{BigInteger, PrimeField}; + let scalar_bits = k.into_bigint().to_bits_le(); + + let mut digits = Vec::with_capacity(WINDOW_COUNT); + let mut carry: i32 = 0; + for i in 0..WINDOW_COUNT { + let mut byte: i32 = 0; + for b in 0..WINDOW_BITS { + let idx = i * WINDOW_BITS + b; + if idx < scalar_bits.len() && scalar_bits[idx] { + byte |= 1 << b; + } + } + let mut d = byte + carry; + if d >= (1 << (WINDOW_BITS - 1)) { + d -= 1 << WINDOW_BITS; + carry = 1; + } else { + carry = 0; + } + digits.push(d); + } + debug_assert_eq!(carry, 0, "booth_recode_w8: residual carry did not fit in WINDOW_COUNT digits"); + digits +} + +/// Encode a signed-digit sequence into the 9-bits-per-window witness layout consumed by +/// `scalar_mul_private_signed_table_circuit`: `[idx_0 .. idx_{w-2}][sign][skip]`. +pub fn encode_signed_digits(digits: &[i32]) -> Vec { + let mut bits = Vec::with_capacity(digits.len() * DIGIT_BITS); + for &d in digits { + if d == 0 { + for _ in 0..(WINDOW_BITS - 1) { + bits.push(false); + } + bits.push(false); + bits.push(true); + } else { + let mag = d.unsigned_abs(); // |d| in [1, 128] + let idx = mag - 1; // in [0, 127], fits in 7 bits + for b in 0..(WINDOW_BITS - 1) { + bits.push((idx >> b) & 1 != 0); + } + bits.push(d < 0); + bits.push(false); + } + } + bits +} + /// SHA256 commitment to a `Vec>` GC ciphertext list. pub fn gc_ciphertexts_commit(ciphertexts: &[Option]) -> [u8; 32] { let mut hasher = Sha256::new(); @@ -137,26 +199,32 @@ pub fn gc_ciphertexts_commit(ciphertexts: &[Option G1Affine { let mut rng = rand::thread_rng(); ark_bn254::G1Projective::rand(&mut rng).into_affine() } - fn build_pd_table_bits(pd: &G1Affine) -> Vec { + /// Build the halved signed-digit Base table. + /// + /// Layout: window `i` (i = 0..WINDOW_COUNT), entry `j` (j = 0..WINDOW_ENTRIES) stores + /// `(j+1) · 256^i · Base` in affine Montgomery form. + fn build_base_table_bits(base: &G1Affine) -> Vec { let mut bits = Vec::with_capacity(PRECOMP_TABLE_BITS); - let mut window_base = ark_bn254::G1Projective::from(pd.clone()); + let mut window_base = ark_bn254::G1Projective::from(base.clone()); for _ in 0..WINDOW_COUNT { - let mut multiple = ark_bn254::G1Projective::zero(); + let mut multiple = window_base; // start at 1·window_base for _ in 0..WINDOW_ENTRIES { - let aff = multiple.clone().into_affine(); + let aff = multiple.into_affine(); bits.extend(Fq::to_bits(Fq::as_montgomery(aff.x))); bits.extend(Fq::to_bits(Fq::as_montgomery(aff.y))); multiple += window_base; @@ -169,13 +237,14 @@ mod tests { bits } - /// Build a full witness: precomputed window table for P_D, pi_x, pi_y, x_d. - fn build_witness(pi: &G1Affine, pd: &G1Affine, x_d: ark_bn254::Fr) -> Vec { - build_pd_table_bits(pd) + /// Build a full witness: precomputed signed table for P_D, pi_x, pi_y, Booth-recoded x_d. + fn build_witness(pi: &G1Affine, base: &G1Affine, x_d: ark_bn254::Fr) -> Vec { + let digits = booth_recode_w8(x_d); + build_base_table_bits(base) .into_iter() + .chain(encode_signed_digits(&digits).into_iter()) .chain(Fq::to_bits(pi.x)) .chain(Fq::to_bits(pi.y)) - .chain(Fr::to_bits(x_d)) .collect() } @@ -183,67 +252,67 @@ mod tests { fn test_babe_gc_on_curve() { let pi = random_g1_affine(); let g = random_g1_affine(); - let pd = random_g1_affine(); + let base = random_g1_affine(); let x_d = ark_bn254::Fr::from(1u64); - let witness = build_witness(&pi, &pd, x_d); + let witness = build_witness(&pi, &base, x_d); reset_gid(); let (bld, output_indices) = compile_dsgc(g); let mut circuit = bld.build(&witness); circuit.gate_counts().print(); - // for gate in &mut circuit.1 { - // gate.evaluate(); - // } - // - // let output: Vec = output_indices - // .iter() - // .map(|&i| circuit.0[i].borrow().get_value()) - // .collect(); - // - // assert_eq!(output.len(), L); - // - // // u₀ = 1 - // assert!(output[0]); - // - // let x_bits = Fq::to_bits(pi.x); - // for k in 0..N { - // assert_eq!(output[1 + k], x_bits[k], "x bit {k} mismatch"); - // } - // - // let y_bits = Fq::to_bits(pi.y); - // for k in 0..N { - // assert_eq!(output[1 + N + k], y_bits[k], "y bit {k} mismatch"); - // } - // - // let x_sq_bits = Fq::to_bits(pi.x * pi.x); - // for k in 0..N { - // assert_eq!(output[1 + 2 * N + k], x_sq_bits[k], "x² bit {k} mismatch"); - // } - // - // let y_sq_bits = Fq::to_bits(pi.y * pi.y); - // for k in 0..N { - // assert_eq!(output[1 + 3 * N + k], y_sq_bits[k], "y² bit {k} mismatch"); - // } - // - // let xy_bits = Fq::to_bits(pi.x * pi.y); - // for k in 0..N { - // assert_eq!(output[1 + 4 * N + k], xy_bits[k], "xy bit {k} mismatch"); - // } + for gate in &mut circuit.1 { + gate.evaluate(); + } + + let output: Vec = output_indices + .iter() + .map(|&i| circuit.0[i].borrow().get_value()) + .collect(); + + assert_eq!(output.len(), L); + + // u₀ = 1 + assert!(output[0]); + + let x_bits = Fq::to_bits(pi.x); + for k in 0..N { + assert_eq!(output[1 + k], x_bits[k], "x bit {k} mismatch"); + } + + let y_bits = Fq::to_bits(pi.y); + for k in 0..N { + assert_eq!(output[1 + N + k], y_bits[k], "y bit {k} mismatch"); + } + + let x_sq_bits = Fq::to_bits(pi.x * pi.x); + for k in 0..N { + assert_eq!(output[1 + 2 * N + k], x_sq_bits[k], "x² bit {k} mismatch"); + } + + let y_sq_bits = Fq::to_bits(pi.y * pi.y); + for k in 0..N { + assert_eq!(output[1 + 3 * N + k], y_sq_bits[k], "y² bit {k} mismatch"); + } + + let xy_bits = Fq::to_bits(pi.x * pi.y); + for k in 0..N { + assert_eq!(output[1 + 4 * N + k], xy_bits[k], "xy bit {k} mismatch"); + } } #[test] fn test_babe_gc_off_curve_falls_back_to_g() { let pi = random_g1_affine(); let g = random_g1_affine(); - let pd = random_g1_affine(); + let base = random_g1_affine(); let x_d = ark_bn254::Fr::from(1u64); let bad_y = pi.y + ark_bn254::Fq::from(1u64); let mut off_pi = pi; off_pi.y = bad_y; - let witness = build_witness(&off_pi, &pd, x_d); + let witness = build_witness(&off_pi, &base, x_d); reset_gid(); let (bld, output_indices) = compile_dsgc(g); @@ -273,14 +342,14 @@ mod tests { /// Plain (non-garbled) evaluation: verify x_d · P_D projective output. #[test] - fn test_babe_gc_xd_pd_output() { + fn test_babe_gc_xd_base_output() { let mut rng = rand::thread_rng(); let g = random_g1_affine(); - let pd = random_g1_affine(); + let base = random_g1_affine(); let pi = random_g1_affine(); let x_d = ark_bn254::Fr::rand(&mut rng); - let witness = build_witness(&pi, &pd, x_d); + let witness = build_witness(&pi, &base, x_d); reset_gid(); let (bld, output_indices) = compile_dsgc(g); @@ -297,12 +366,12 @@ mod tests { assert_eq!(output.len(), L); // Reconstruct projective point (X,Y,Z) in normal form from R_PD bits - let x = Fq::from_bits(output[U_BAR_SIZE..U_BAR_SIZE + N].to_vec()); - let y = Fq::from_bits(output[U_BAR_SIZE + N..U_BAR_SIZE + 2 * N].to_vec()); - let z = Fq::from_bits(output[U_BAR_SIZE + 2 * N..L].to_vec()); + let x = Fq::from_montgomery(Fq::from_bits(output[U_BAR_SIZE..U_BAR_SIZE + N].to_vec())); + let y = Fq::from_montgomery(Fq::from_bits(output[U_BAR_SIZE + N..U_BAR_SIZE + 2 * N].to_vec())); + let z = Fq::from_montgomery(Fq::from_bits(output[U_BAR_SIZE + 2 * N..L].to_vec())); let result_proj = ark_bn254::G1Projective::new(x, y, z); - let expected_affine = (ark_bn254::G1Projective::from(pd) * x_d).into_affine(); + let expected_affine = (ark_bn254::G1Projective::from(base) * x_d).into_affine(); assert_eq!( result_proj.into_affine(), expected_affine, @@ -321,31 +390,43 @@ mod tests { fn test_babe_gc_garbled_e2e() { let mut rng = rand::thread_rng(); let g = random_g1_affine(); - let pd = random_g1_affine(); + let base = random_g1_affine(); let pi = random_g1_affine(); let x_d = ark_bn254::Fr::rand(&mut rng); // 1. Generate circuit + let now = Instant::now(); reset_gid(); let (bld, output_indices) = compile_dsgc(g); let mut circuit = bld.build(&[]); + println!("circuit generate time: {:?}", now.elapsed()); + // 2. Generate a new set of labels — one label0 per input wire. + let now = Instant::now(); let encoding_keys: Vec = (0..(PRECOMP_TABLE_BITS + 3 * N)).map(|_| S::random()).collect(); for (i, &key) in encoding_keys.iter().enumerate() { circuit.0[2 + i].borrow_mut().label = Some(key); } + println!("encoding time: {:?}", now.elapsed()); // 3. Derive input label values from concrete inputs - let witness = build_witness(&pi, &pd, x_d); + let witness = build_witness(&pi, &base, x_d); // 4. Evaluate garbled circuit + let now = Instant::now(); circuit.set_witness_value(&witness); for gate in &mut circuit.1 { gate.evaluate(); } - let garblings = circuit.garbled_gates(); - let _ = circuit.garbled_evaluate(&garblings); + println!("evaluation time: {:?}", now.elapsed()); + let now = Instant::now(); + let ciphertext = circuit.garbled_gates(); + println!("garbled gates time: {:?}", now.elapsed()); + + let now = Instant::now(); + let _ = circuit.garbled_evaluate(&ciphertext); + println!("garbled evaluate time: {:?}", now.elapsed()); // Collect all output labels let output_labels: Vec = output_indices @@ -366,141 +447,30 @@ mod tests { assert_eq!(output_labels[k], expected_label, "ū label mismatch at k={k}"); } - // 5b. Verify x_d·P_D output labels + // 5b. Verify x_d·Base output labels // Get the actual output bits, verify they represent the correct point, // then confirm the labels encode exactly those bits. - let pd_bits: Vec = output_indices[U_BAR_SIZE..] + let rpd_bits: Vec = output_indices[U_BAR_SIZE..] .iter() .map(|&i| circuit.0[i].borrow().get_value()) .collect(); - assert_eq!(pd_bits.len(), R_PD_SIZE); + assert_eq!(rpd_bits.len(), R_PD_SIZE); - let x = Fq::from_bits(pd_bits[..N].to_vec()); - let y = Fq::from_bits(pd_bits[N..2 * N].to_vec()); - let z = Fq::from_bits(pd_bits[2 * N..].to_vec()); + let x = Fq::from_montgomery(Fq::from_bits(rpd_bits[..N].to_vec())); + let y = Fq::from_montgomery(Fq::from_bits(rpd_bits[N..2 * N].to_vec())); + let z = Fq::from_montgomery(Fq::from_bits(rpd_bits[2 * N..].to_vec())); let result_proj = ark_bn254::G1Projective::new(x, y, z); - let expected_affine = (ark_bn254::G1Projective::from(pd) * x_d).into_affine(); + let expected_affine = (ark_bn254::G1Projective::from(base) * x_d).into_affine(); assert_eq!( result_proj.into_affine(), expected_affine, - "x_d · P_D projective output represents wrong affine point" + "x_d · Base projective output represents wrong affine point" ); for k in 0..R_PD_SIZE { - let bit = pd_bits[k]; + let bit = rpd_bits[k]; let expected_label = circuit.0[output_indices[U_BAR_SIZE + k]].borrow().select(bit); assert_eq!(output_labels[U_BAR_SIZE + k], expected_label, "R_PD label mismatch at k={k}"); } } - - #[cfg(feature = "garbled")] - #[test] - fn test_babe_gc_garbled_labels() { - let pi = random_g1_affine(); - let g = random_g1_affine(); - let pd = random_g1_affine(); - let x_d = ark_bn254::Fr::from(1u64); - - reset_gid(); - let (bld, output_indices) = compile_dsgc(g); - let mut circuit = bld.build(&vec![]); - - let witness = build_witness(&pi, &pd, x_d); - circuit.set_witness_value(&witness); - for gate in &mut circuit.1 { - gate.evaluate(); - } - let garblings = circuit.garbled_gates(); - let _ = circuit.garbled_evaluate(&garblings); - - let output_labels: Vec = output_indices - .iter() - .map(|&i| { - let w = &circuit.0[i]; - w.borrow().select(w.borrow().get_value()) - }).collect(); - assert_eq!(output_labels.len(), L); - - let u_bar = u_bar_vec(&pi); - assert_eq!(u_bar.len(), U_BAR_SIZE); - for k in 0..U_BAR_SIZE { - let expected_bit = !u_bar[k].is_zero(); - let expected_label = circuit.0[output_indices[k]].borrow().select(expected_bit); - assert_eq!( - output_labels[k], - expected_label, - "garbled output label mismatch at ū[{k}]" - ); - } - } - - #[cfg(feature = "garbled")] - #[test] - fn test_output_labels() { - use garbled_snark_verifier::core::utils::NON_CAC_DELTA; - - let g = random_g1_affine(); - let pd = random_g1_affine(); - let x_d = ark_bn254::Fr::from(1u64); - - reset_gid(); - let (bld, output_indices) = compile_dsgc(g); - let mut circuit = bld.build(&[]); - - // Encoding keys for all input wires - let encoding_keys: Vec = (0..(PRECOMP_TABLE_BITS + 3 * N)).map(|_| S::random()).collect(); - for (i, &key) in encoding_keys.iter().enumerate() { - circuit.0[2 + i].borrow_mut().label = Some(key); - } - - let mut rng = rand::thread_rng(); - let p1a = ark_bn254::G1Projective::rand(&mut rng).into_affine(); - let p1b = ark_bn254::G1Projective::rand(&mut rng).into_affine(); - - let eval = |circuit: &mut Circuit, p: &ark_bn254::G1Affine| -> Vec { - let witness = build_witness(p, &pd, x_d); - - for wire in circuit.0.iter().skip(2) { - wire.borrow_mut().value = None; - } - circuit.set_witness_value(&witness); - for gate in &mut circuit.1 { - gate.evaluate(); - } - let garblings = circuit.garbled_gates(); - let _ = circuit.garbled_evaluate(&garblings); - - output_indices - .iter() - .map(|&i| { - let w = &circuit.0[i]; - w.borrow().select(w.borrow().get_value()) - }) - .collect() - }; - - let labels_a = eval(&mut circuit, &p1a); - let labels_b = eval(&mut circuit, &p1b); - - // Verify Free XOR property on the ū part only (R_PD uses the same P_D/x_d so labels - // are identical for both evaluations and trivially equal — not tested here) - let u_bar_a = u_bar_vec(&p1a); - let u_bar_b = u_bar_vec(&p1b); - assert_eq!(u_bar_a.len(), U_BAR_SIZE); - assert_eq!(u_bar_b.len(), U_BAR_SIZE); - - for k in 0..U_BAR_SIZE { - let bit_a = !u_bar_a[k].is_zero(); - let bit_b = !u_bar_b[k].is_zero(); - if bit_a == bit_b { - assert_eq!(labels_a[k], labels_b[k], "k={k}: same u_bar bit → equal output labels"); - } else { - assert_eq!( - labels_a[k] ^ labels_b[k], - NON_CAC_DELTA, - "k={k}: different u_bar bits → labels must differ by DELTA" - ); - } - } - } } diff --git a/verifiable-circuit-babe/src/gc/mod.rs b/verifiable-circuit-babe/src/gc/mod.rs index 1e6ce5e..532474a 100644 --- a/verifiable-circuit-babe/src/gc/mod.rs +++ b/verifiable-circuit-babe/src/gc/mod.rs @@ -123,4 +123,4 @@ mod tests { // println!("wires={num_wires}, gates={}, output_indices={}", circuit.1.len(), output_indices.len()); // println!("Circuit reconstructed successfully from serialized data."); // } -} \ No newline at end of file +} From 28fa471af76c98d042f9f8243e2a7a8732ff0908 Mon Sep 17 00:00:00 2001 From: vanhger Date: Wed, 22 Apr 2026 21:13:05 +0700 Subject: [PATCH 05/32] feat: add blinding for dsgc --- verifiable-circuit-babe/src/gc/circuit.rs | 96 ++++++++++++++++------- 1 file changed, 67 insertions(+), 29 deletions(-) diff --git a/verifiable-circuit-babe/src/gc/circuit.rs b/verifiable-circuit-babe/src/gc/circuit.rs index 4f6868c..6646d0b 100644 --- a/verifiable-circuit-babe/src/gc/circuit.rs +++ b/verifiable-circuit-babe/src/gc/circuit.rs @@ -23,33 +23,52 @@ pub const DIGIT_TOTAL_BITS: usize = WINDOW_COUNT * DIGIT_BITS; /// Compile the DSGC circuit structure without fixing witness values. /// /// Input wire allocation order: -/// [0..PRECOMP_TABLE_BITS) signed-digit precomputed table for Base, +/// [0..N) pi_x — x-coordinate of the proof point π +/// [N..2·N) pi_y — y-coordinate of π +/// [2·N..3·N) r·B_x — x-coordinate of blinding point, Montgomery form +/// [3N..4·N) r·B_y — y-coordinate of blinding point, Montgomery form +/// [4·N..4·N+PRECOMP_TABLE_BITS) signed-digit precomputed table for Base, /// WINDOW_COUNT windows × WINDOW_ENTRIES affine points /// (entry j = (j+1)·256^i·P), Montgomery form -/// [..+N) pi_x — x-coordinate of the proof point π -/// [..+N) pi_y — y-coordinate of π /// [..+DIGIT_TOTAL_BITS) x_d recoded into Booth digits, 9 bits per window +/// pub fn compile_dsgc(g: G1Affine) -> (CircuitAdapter, Vec) { let mut bld = CircuitAdapter::default(); + + // π input wires + let pi_x = Fq::wires(&mut bld); + let pi_y = Fq::wires(&mut bld); + + // r·B input wires — garbler-private, placed before table wires. + let rb_x = Fq::wires(&mut bld); + let rb_y = Fq::wires(&mut bld); let mut table_wires = Vec::with_capacity(PRECOMP_TABLE_BITS); for _ in 0..(WINDOW_COUNT * WINDOW_ENTRIES * 2) { table_wires.extend(Fq::wires(&mut bld).0); } - // Signed-digit encoding of x_d (33 × 9 = 297 bits) let digit_wires: Vec = (0..DIGIT_TOTAL_BITS).map(|_| bld.fresh_one()).collect(); - // π input wires - let pi_x = Fq::wires(&mut bld); - let pi_y = Fq::wires(&mut bld); - let output_indices = emit_dsgc(&mut bld, &table_wires, &digit_wires, &pi_x.0, &pi_y.0, g); + let output_indices = emit_dsgc( + &mut bld, + &table_wires, + &digit_wires, + &pi_x.0, + &pi_y.0, + g, + &rb_x.0, + &rb_y.0, + ); (bld, output_indices) } +/// Number of input bits contributed by the blinding point r·B (x and y, normal form). +pub const R_B_BITS: usize = 2 * N; + /// Output layout (total L = U_BAR_SIZE + R_PD_SIZE = 2033 bits): /// [0..U_BAR_SIZE) ū(π) or ū(g) — 1 + 5·N bits, LSB-first -/// [U_BAR_SIZE..L) (X,Y,Z) of x_d·P_D — 3·N bits, projective Montgomery form +/// [U_BAR_SIZE..L) (X,Y,Z) of x_d·P_D + r·B — 3·N bits, projective Montgomery form fn emit_dsgc( bld: &mut CircuitAdapter, table_wires: &[usize], @@ -57,6 +76,8 @@ fn emit_dsgc( pi_x: &[usize], pi_y: &[usize], g: G1Affine, + rb_x_wires: &[usize], + rb_y_wires: &[usize], ) -> Vec { assert_eq!(table_wires.len(), PRECOMP_TABLE_BITS); @@ -101,10 +122,16 @@ fn emit_dsgc( let prod_proj_m = GcG1Projective::scalar_mul_private_signed_table_circuit(bld, digits, table_wires); + // ── Blinding: add r·B (garbler-private affine, already in Montgomery form) ─ + let mut rb_affine_m: Vec = rb_x_wires.to_vec(); + rb_affine_m.extend_from_slice(rb_y_wires); + let blinded_proj_m = + GcG1Projective::add_mixed_montgomery_no_inf(bld, &prod_proj_m, &rb_affine_m); + // Output stays in Montgomery form (X·R, Y·R, Z·R). - output_indices.extend_from_slice(&prod_proj_m[..N]); - output_indices.extend_from_slice(&prod_proj_m[N..2 * N]); - output_indices.extend_from_slice(&prod_proj_m[2 * N..]); + output_indices.extend_from_slice(&blinded_proj_m[..N]); + output_indices.extend_from_slice(&blinded_proj_m[N..2 * N]); + output_indices.extend_from_slice(&blinded_proj_m[2 * N..]); assert_eq!(output_indices.len(), L); output_indices @@ -237,14 +264,17 @@ mod tests { bits } - /// Build a full witness: precomputed signed table for P_D, pi_x, pi_y, Booth-recoded x_d. - fn build_witness(pi: &G1Affine, base: &G1Affine, x_d: ark_bn254::Fr) -> Vec { + /// Build a full witness: r·B (garbler-private blinding point in Montgomery form), + /// precomputed signed table for P_D, Booth-recoded x_d, pi_x, pi_y. + fn build_witness(pi: &G1Affine, base: &G1Affine, x_d: ark_bn254::Fr, r_b: &G1Affine) -> Vec { let digits = booth_recode_w8(x_d); - build_base_table_bits(base) - .into_iter() - .chain(encode_signed_digits(&digits).into_iter()) - .chain(Fq::to_bits(pi.x)) + + Fq::to_bits(pi.x).into_iter() .chain(Fq::to_bits(pi.y)) + .chain(Fq::to_bits(Fq::as_montgomery(r_b.x))) + .chain(Fq::to_bits(Fq::as_montgomery(r_b.y))) + .chain(build_base_table_bits(base)) + .chain(encode_signed_digits(&digits)) .collect() } @@ -253,9 +283,10 @@ mod tests { let pi = random_g1_affine(); let g = random_g1_affine(); let base = random_g1_affine(); + let r_b = random_g1_affine(); let x_d = ark_bn254::Fr::from(1u64); - let witness = build_witness(&pi, &base, x_d); + let witness = build_witness(&pi, &base, x_d, &r_b); reset_gid(); let (bld, output_indices) = compile_dsgc(g); @@ -307,12 +338,13 @@ mod tests { let pi = random_g1_affine(); let g = random_g1_affine(); let base = random_g1_affine(); + let r_b = random_g1_affine(); let x_d = ark_bn254::Fr::from(1u64); let bad_y = pi.y + ark_bn254::Fq::from(1u64); let mut off_pi = pi; off_pi.y = bad_y; - let witness = build_witness(&off_pi, &base, x_d); + let witness = build_witness(&off_pi, &base, x_d, &r_b); reset_gid(); let (bld, output_indices) = compile_dsgc(g); @@ -340,16 +372,17 @@ mod tests { } } - /// Plain (non-garbled) evaluation: verify x_d · P_D projective output. + /// Plain (non-garbled) evaluation: verify x_d · P_D + r·B projective output. #[test] fn test_babe_gc_xd_base_output() { let mut rng = rand::thread_rng(); let g = random_g1_affine(); let base = random_g1_affine(); + let r_b = random_g1_affine(); let pi = random_g1_affine(); let x_d = ark_bn254::Fr::rand(&mut rng); - let witness = build_witness(&pi, &base, x_d); + let witness = build_witness(&pi, &base, x_d, &r_b); reset_gid(); let (bld, output_indices) = compile_dsgc(g); @@ -370,12 +403,14 @@ mod tests { let y = Fq::from_montgomery(Fq::from_bits(output[U_BAR_SIZE + N..U_BAR_SIZE + 2 * N].to_vec())); let z = Fq::from_montgomery(Fq::from_bits(output[U_BAR_SIZE + 2 * N..L].to_vec())); - let result_proj = ark_bn254::G1Projective::new(x, y, z); - let expected_affine = (ark_bn254::G1Projective::from(base) * x_d).into_affine(); + let result_proj = ark_bn254::G1Projective::new(x, y, z); + let expected_affine = (ark_bn254::G1Projective::from(base) * x_d + + ark_bn254::G1Projective::from(r_b)) + .into_affine(); assert_eq!( result_proj.into_affine(), expected_affine, - "x_d · P_D projective output represents wrong affine point" + "x_d · P_D + r·B projective output represents wrong affine point" ); } @@ -391,6 +426,7 @@ mod tests { let mut rng = rand::thread_rng(); let g = random_g1_affine(); let base = random_g1_affine(); + let r_b = random_g1_affine(); let pi = random_g1_affine(); let x_d = ark_bn254::Fr::rand(&mut rng); @@ -401,17 +437,18 @@ mod tests { let mut circuit = bld.build(&[]); println!("circuit generate time: {:?}", now.elapsed()); - // 2. Generate a new set of labels — one label0 per input wire. + // Input wires: R_B_BITS (r·B) + PRECOMP_TABLE_BITS (table) + DIGIT_TOTAL_BITS (x_d) + 2·N (π) let now = Instant::now(); - let encoding_keys: Vec = (0..(PRECOMP_TABLE_BITS + 3 * N)).map(|_| S::random()).collect(); + let total_input_wires = R_B_BITS + PRECOMP_TABLE_BITS + DIGIT_TOTAL_BITS + 2 * N; + let encoding_keys: Vec = (0..total_input_wires).map(|_| S::random()).collect(); for (i, &key) in encoding_keys.iter().enumerate() { circuit.0[2 + i].borrow_mut().label = Some(key); } println!("encoding time: {:?}", now.elapsed()); // 3. Derive input label values from concrete inputs - let witness = build_witness(&pi, &base, x_d); + let witness = build_witness(&pi, &base, x_d, &r_b); // 4. Evaluate garbled circuit let now = Instant::now(); @@ -461,7 +498,8 @@ mod tests { let z = Fq::from_montgomery(Fq::from_bits(rpd_bits[2 * N..].to_vec())); let result_proj = ark_bn254::G1Projective::new(x, y, z); - let expected_affine = (ark_bn254::G1Projective::from(base) * x_d).into_affine(); + let expected_affine = ark_bn254::G1Projective::from(base) * x_d + + ark_bn254::G1Projective::from(r_b); assert_eq!( result_proj.into_affine(), expected_affine, "x_d · Base projective output represents wrong affine point" From 9a03688e0ded3bf5a1fc37af7c68fe2f530412bb Mon Sep 17 00:00:00 2001 From: vanhger Date: Thu, 23 Apr 2026 09:26:05 +0700 Subject: [PATCH 06/32] feat: add new enc/dec for dsgc --- verifiable-circuit-babe/src/babe.rs | 99 +++++++++++---- verifiable-circuit-babe/src/cac.rs | 12 +- verifiable-circuit-babe/src/gc/mod.rs | 116 +++++++++--------- verifiable-circuit-babe/src/instance/mod.rs | 89 ++++++++------ .../src/instance/secret.rs | 9 +- verifiable-circuit-babe/src/prover.rs | 10 +- verifiable-circuit-babe/src/verifier.rs | 5 +- 7 files changed, 205 insertions(+), 135 deletions(-) diff --git a/verifiable-circuit-babe/src/babe.rs b/verifiable-circuit-babe/src/babe.rs index 990b4da..411d7b8 100644 --- a/verifiable-circuit-babe/src/babe.rs +++ b/verifiable-circuit-babe/src/babe.rs @@ -1,5 +1,5 @@ use ark_bn254::{Bn254, Fr, G1Affine}; -use ark_ec::AffineRepr; +use ark_ec::{AffineRepr, CurveGroup}; use ark_ff::PrimeField; use ark_groth16::{Proof as Groth16Proof, VerifyingKey as Groth16VerifyingKey}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; @@ -140,9 +140,10 @@ pub struct BabeCACE2ERun { /// and the public `CACSetupPackage` to send to the Prover. pub fn babe_verifier_cac_setup( vk: &Groth16VerifyingKey, - public_inputs: &[Fr], + static_public_inputs: &[Fr], + dynamic_pin_size: usize, ) -> (BABEVerifier, CACSetupPackage) { - let verifier = BABEVerifier::new(N_CC, vk, public_inputs).expect("verifier CAC setup failed"); + let verifier = BABEVerifier::new(N_CC, vk, static_public_inputs, dynamic_pin_size).expect("verifier CAC setup failed"); println!("Verifier: committing all instances.."); let package = verifier.commit(); (verifier, package) @@ -175,9 +176,10 @@ pub fn babe_prover_verify_setup( finalized: &[FinalizedInstanceData], soldering: &SolderingData, vk: &Groth16VerifyingKey, - public_inputs: &[Fr], + static_public_inputs: &[Fr], + dynamic_pin_size: usize, ) -> Result<(), String> { - verify_opened_instances(package, opened, vk, public_inputs)?; + verify_opened_instances(package, opened, vk, static_public_inputs, dynamic_pin_size)?; verify_finalized_instances(package, finalized)?; BABEProver::verify_soldering_output(package, soldering)?; Ok(()) @@ -333,27 +335,46 @@ pub fn babe_prover_withdraw(sig_v_presig: BabeBtcSig) -> TxWithdrawWitness { // ─── Enc_ functions ──────────────────────────────────────────────────────── -/// Encsetup(crs, x, msg; r): ctsetup = (r·[delta]_2, RO(rY) ⊕ msg). +/// Enc*(crs, x_S, |D|, msg, B; r): +/// Inputs are split as [x_1..x_{|S|}] static, [x_{|S|+1}..x_{|S|+|D|}] dynamic. +/// P_S = gamma_abc[0] + Σ_{k=0}^{|S|-1} x_S[k]·gamma_abc[k+1] +/// mask = Y_S^r - e(r·B, γ) where Y_S^r = e(α, r·β) + e(P_S, r·γ) +/// Returns (ctsetup, r·B) — r·B is garbler-private and never published. pub fn we_known_pi1_encsetup( vk: &Groth16VerifyingKey, - public_inputs: &[Fr], + static_inputs: &[Fr], + num_dynamic: usize, msg: &[u8], r_bytes: [u8; 32], -) -> Option { + b_blind: G1Affine, +) -> Option<(WeKnownPi1SetupCt, G1Affine)> { + let num_static = static_inputs.len(); + if num_static + num_dynamic + 1 != vk.gamma_abc_g1.len() { + return None; + } let r = Fr::from_le_bytes_mod_order(&r_bytes); - let vk_x = groth16_vk_x(vk, public_inputs)?; + + let mut p_s = vk.gamma_abc_g1[0].into_group(); + for (k, x) in static_inputs.iter().enumerate() { + p_s += vk.gamma_abc_g1[k + 1].into_group() * *x; + } + + let r_b = b_blind.into_group() * r; let r_delta = vk.delta_g2.into_group() * r; let t1 = Bn254::pairing(vk.alpha_g1, vk.beta_g2.into_group() * r); - let t2 = Bn254::pairing(vk_x, vk.gamma_g2.into_group() * r); - let r_y = t1 + t2; + let t2 = Bn254::pairing(p_s, vk.gamma_g2.into_group() * r); + let y_s_r = t1 + t2; + + let q_b = Bn254::pairing(r_b, vk.gamma_g2); + let mask_gt = y_s_r - q_b; - let mut ry_bytes = Vec::new(); - r_y.serialize_compressed(&mut ry_bytes).ok()?; - let mask = ro_from_pairing_bytes(&ry_bytes, msg.len()); - let ct3 = msg.iter().zip(mask.iter()).map(|(a, b)| a ^ b).collect(); + let mut mask_bytes = Vec::new(); + mask_gt.serialize_compressed(&mut mask_bytes).ok()?; + let mask = ro_from_pairing_bytes(&mask_bytes, msg.len()); + let ct3: Vec = msg.iter().zip(mask.iter()).map(|(a, b)| a ^ b).collect(); - Some(WeKnownPi1SetupCt { ct2_r_delta_g2: g2_to_ser(r_delta), ct3_masked_msg: ct3 }) + Some((WeKnownPi1SetupCt { ct2_r_delta_g2: g2_to_ser(r_delta), ct3_masked_msg: ct3 }, r_b.into_affine())) } /// Encprove(crs, π₁; r): ctprove = r·π₁. @@ -362,20 +383,27 @@ pub fn we_known_pi1_encprove(pi1: ark_bn254::G1Projective, r_bytes: [u8; 32]) -> WeKnownPi1ProveCt { ct1_r_pi1: g1_to_ser(pi1 * r) } } -/// Dec(ctsetup, ctprove, π₂, π₃): msg = ct3 ⊕ RO(e(ct1,π₂) - e(π₃,ct2)). +/// Dec*(vk, ctsetup, ctprove, c1', π₂, π₃): +/// Q_blind = e(c1', γ) where c1' = r·P_D + r·B (DSGC output) +/// mask = e(r·π₁, π₂) - e(π₃, r·δ) - Q_blind = Y_S^r - e(r·B, γ) pub fn we_known_pi1_dec( + vk: &Groth16VerifyingKey, ctsetup: &WeKnownPi1SetupCt, ctprove: &WeKnownPi1ProveCt, + c1_prime: G1Affine, pi2: ark_bn254::G2Projective, pi3: ark_bn254::G1Projective, ) -> Option> { let ct1 = g1_from_ser_checked(&ctprove.ct1_r_pi1)?; let ct2 = g2_from_ser_checked(&ctsetup.ct2_r_delta_g2)?; + let r_y = Bn254::pairing(ct1, pi2) - Bn254::pairing(pi3, ct2); + let q_blind = Bn254::pairing(c1_prime, vk.gamma_g2); + let mask_gt = r_y - q_blind; - let mut ry_bytes = Vec::new(); - r_y.serialize_compressed(&mut ry_bytes).ok()?; - let mask = ro_from_pairing_bytes(&ry_bytes, ctsetup.ct3_masked_msg.len()); + let mut mask_bytes = Vec::new(); + mask_gt.serialize_compressed(&mut mask_bytes).ok()?; + let mask = ro_from_pairing_bytes(&mask_bytes, ctsetup.ct3_masked_msg.len()); Some(ctsetup.ct3_masked_msg.iter().zip(mask.iter()).map(|(a, b)| a ^ b).collect()) } @@ -393,7 +421,8 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).expect("groth16 prove"); - let public_inputs = vec![a * b]; + let static_public_inputs = vec![a * b]; + let dynamic_public_inputs = vec![a * a]; // ── Setup phase ─────────────────────────────────────────────────────────── @@ -406,7 +435,7 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { println!("Verifier generating BTC keys"); let pk_v = BtcPk([1u8; 33]); println!("Verifier: building {} instances...", N_CC); - let (verifier, package) = babe_verifier_cac_setup(&vk, &public_inputs); + let (verifier, package) = babe_verifier_cac_setup(&vk, &static_public_inputs, dynamic_public_inputs.len()); // Verifier sends vk and package to Prover println!("Verifier: sending pk_v and {} instance commitment to Prover", N_CC); @@ -421,7 +450,7 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { println!("Prover: verifying opening and soldering proof..."); // Prover verifies everything. - babe_prover_verify_setup(&package, &opened, &finalized, &soldering, &vk, &public_inputs) + babe_prover_verify_setup(&package, &opened, &finalized, &soldering, &vk, &static_public_inputs, dynamic_public_inputs.len()) .expect("prover setup verification failed"); println!("Prover: generating Lamport signature..."); @@ -527,7 +556,9 @@ impl ConstraintSynthesizer for DummyMulCircuit { let a = cs.new_witness_variable(|| self.a.ok_or(SynthesisError::AssignmentMissing))?; let b = cs.new_witness_variable(|| self.b.ok_or(SynthesisError::AssignmentMissing))?; let c = cs.new_input_variable(|| Ok(self.a.unwrap() * self.b.unwrap()))?; + let d = cs.new_input_variable(|| Ok(self.a.unwrap() * self.a.unwrap()))?; cs.enforce_constraint(lc!() + a, lc!() + b, lc!() + c)?; + cs.enforce_constraint(lc!() + a, lc!() + a, lc!() + d)?; Ok(()) } } @@ -574,13 +605,29 @@ mod tests { DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).unwrap(); - let public_inputs = vec![a * b]; + + // |S|=1 (a*b is static), |D|=1 (a*a is dynamic). + let static_inputs = vec![a * b]; + let dyn_inputs = vec![a * a]; + let num_dynamic = 1usize; let secret = b"test-secret-32by"; let r_bytes = h_256(b"r-test"); + let b_blind = G1Affine::generator(); - let ct_setup = we_known_pi1_encsetup(&vk, &public_inputs, secret, r_bytes).unwrap(); + let (ct_setup, r_b_affine) = we_known_pi1_encsetup( + &vk, &static_inputs, num_dynamic, secret, r_bytes, b_blind, + ).unwrap(); let ctprove = we_known_pi1_encprove(proof.a.into_group(), r_bytes); - let decrypted = we_known_pi1_dec(&ct_setup, &ctprove, proof.b.into_group(), proof.c.into_group()).unwrap(); + + // Simulate DSGC: c1' = r·P_D + r·B + // P_D = (a*a) · gamma_abc[|S|+1] = (a*a) · gamma_abc[2] + let r = Fr::from_le_bytes_mod_order(&r_bytes); + let p_d = vk.gamma_abc_g1[2].into_group() * dyn_inputs[0]; + let c1_prime = (p_d * r + r_b_affine.into_group()).into_affine(); + + let decrypted = we_known_pi1_dec( + &vk, &ct_setup, &ctprove, c1_prime, proof.b.into_group(), proof.c.into_group(), + ).unwrap(); assert_eq!(decrypted, secret); } } \ No newline at end of file diff --git a/verifiable-circuit-babe/src/cac.rs b/verifiable-circuit-babe/src/cac.rs index 0114898..3fb8ad1 100644 --- a/verifiable-circuit-babe/src/cac.rs +++ b/verifiable-circuit-babe/src/cac.rs @@ -64,14 +64,15 @@ pub fn verify_opened_instances( package: &CACSetupPackage, opened: &[(usize, u64)], vk: &Groth16VerifyingKey, - public_inputs: &[Fr], + static_public_inputs: &[Fr], + dynamic_pin_size: usize, ) -> Result<(), String> { use p3_maybe_rayon::prelude::*; opened .par_iter() .map(|&(idx, seed)| { let mut inst = BABEInstance::new_from_seed(seed); - inst.enc_setup(vk, public_inputs) + inst.enc_setup(vk, static_public_inputs, dynamic_pin_size) .map_err(|e| format!("instance {idx}: enc_setup failed: {e}"))?; let recomputed = inst.commit(); @@ -152,11 +153,12 @@ mod tests { let (_, vk) = ark_groth16::Groth16::::setup( DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).expect("groth16 setup"); - let public_inputs = vec![a * b]; + let static_public_inputs = vec![a * b]; + let dynamic_public_inputs = vec![a * a]; // Verifier creates TEST_N_CC instances and commits. let now = std::time::Instant::now(); - let verifier = BABEVerifier::new(TEST_N_CC, &vk, &public_inputs) + let verifier = BABEVerifier::new(TEST_N_CC, &vk, &static_public_inputs, dynamic_public_inputs.len()) .expect("BABEVerifier::new failed"); let elapsed = now.elapsed(); println!("Verifier setup for {TEST_N_CC} instances took {elapsed:.2?}"); @@ -179,7 +181,7 @@ mod tests { let now = std::time::Instant::now(); // Prover verifies opened instances by re-deriving from seed. - verify_opened_instances(&package, &opened, &vk, &public_inputs) + verify_opened_instances(&package, &opened, &vk, &static_public_inputs, dynamic_public_inputs.len()) .expect("opened instance verification failed"); let elapsed = now.elapsed(); println!("Prover verification of opened instances took {elapsed:.2?}"); diff --git a/verifiable-circuit-babe/src/gc/mod.rs b/verifiable-circuit-babe/src/gc/mod.rs index 532474a..6308938 100644 --- a/verifiable-circuit-babe/src/gc/mod.rs +++ b/verifiable-circuit-babe/src/gc/mod.rs @@ -16,11 +16,11 @@ use garbled_snark_verifier::core::utils::{reset_gid, SerializableGate}; pub use utils::*; fn gc_gates_path() -> String { - std::env::var("GC_GATES_PATH").unwrap_or_else(|_| "./babe_gc_gates.bin".to_string()) + std::env::var("GC_GATES_PATH").unwrap_or_else(|_| "./dsgc_gates.bin".to_string()) } fn gc_indices_path() -> String { - std::env::var("GC_INDICES_PATH").unwrap_or_else(|_| "./babe_gc_indices.bin".to_string()) + std::env::var("GC_OUT_INDICES_PATH").unwrap_or_else(|_| "./dsgc_out_indices.bin".to_string()) } /// Raw circuit bytes cached on first read. @@ -61,66 +61,66 @@ pub fn read_fresh_circuit() -> (Circuit, Vec) { (Circuit(wires, gates), output_indices) } -// pub fn generate_and_write_fresh_circuit() { -// reset_gid(); -// let g = G1Affine::generator(); -// let (bld, output_indices) = compile_babe_gc(g); -// let circuit = bld.build(&[]); -// -// // --- Serialize --- -// -// // File 1: (num_wires: u32, gates: Vec) -// let num_wires = circuit.0.len() as u32; -// let gates: Vec = circuit.1.iter().map(|gate| SerializableGate { -// gate_type: gate.gate_type as u8, -// wire_a_id: gate.wire_a.borrow().id.unwrap(), -// wire_b_id: gate.wire_b.borrow().id.unwrap(), -// wire_c_id: gate.wire_c.borrow().id.unwrap(), -// gid: gate.gid, -// }).collect(); -// let gates_bytes = bincode::serialize(&(num_wires, &gates)).expect("serialize gates"); -// fs::write(gc_gates_path(), &gates_bytes).expect("write gates"); -// -// // File 2: Vec output indices -// let indices_bytes = bincode::serialize(&output_indices).expect("serialize indices"); -// fs::write(gc_indices_path(), &indices_bytes).expect("write indices"); -// } +pub fn generate_and_write_fresh_circuit() { + reset_gid(); + let g = G1Affine::generator(); + let (bld, output_indices) = compile_dsgc(g); + let circuit = bld.build(&[]); + + // --- Serialize --- + + // File 1: (num_wires: u32, gates: Vec) + let num_wires = circuit.0.len() as u32; + let gates: Vec = circuit.1.iter().map(|gate| SerializableGate { + gate_type: gate.gate_type as u8, + wire_a_id: gate.wire_a.borrow().id.unwrap(), + wire_b_id: gate.wire_b.borrow().id.unwrap(), + wire_c_id: gate.wire_c.borrow().id.unwrap(), + gid: gate.gid, + }).collect(); + let gates_bytes = bincode::serialize(&(num_wires, &gates)).expect("serialize gates"); + fs::write(gc_gates_path(), &gates_bytes).expect("write gates"); + + // File 2: Vec output indices + let indices_bytes = bincode::serialize(&output_indices).expect("serialize indices"); + fs::write(gc_indices_path(), &indices_bytes).expect("write indices"); +} #[cfg(test)] mod tests { use ark_bn254::G1Affine; use ark_ec::AffineRepr; use garbled_snark_verifier::core::utils::reset_gid; - // use super::{compile_babe_gc, generate_and_write_fresh_circuit}; - - // #[test] - // #[ignore] - // fn test_babe_gc_serialize_roundtrip() { - // generate_and_write_fresh_circuit(); - // - // reset_gid(); - // let g = G1Affine::generator(); - // let (bld, output_indices) = compile_babe_gc(g); - // let circuit = bld.build(&[]); - // let num_wires = circuit.0.len() as u32; - // - // // --- Reconstruct Circuit --- - // - // let (circuit_reconstructed, _) = super::read_fresh_circuit(); - // - // - // assert_eq!(circuit_reconstructed.0.len(), circuit.0.len(), "reconstructed wire count mismatch"); - // assert_eq!(circuit_reconstructed.1.len(), circuit.1.len(), "reconstructed gate count mismatch"); - // - // for (i, (orig, rec)) in circuit.1.iter().zip(circuit_reconstructed.1.iter()).enumerate() { - // assert_eq!(orig.gate_type, rec.gate_type, "reconstructed gate[{i}] type mismatch"); - // assert_eq!(orig.wire_a.borrow().id, rec.wire_a.borrow().id, "reconstructed gate[{i}] wire_a id mismatch"); - // assert_eq!(orig.wire_b.borrow().id, rec.wire_b.borrow().id, "reconstructed gate[{i}] wire_b id mismatch"); - // assert_eq!(orig.wire_c.borrow().id, rec.wire_c.borrow().id, "reconstructed gate[{i}] wire_c id mismatch"); - // assert_eq!(orig.gid, rec.gid, "reconstructed gate[{i}] gid mismatch"); - // } - // - // println!("wires={num_wires}, gates={}, output_indices={}", circuit.1.len(), output_indices.len()); - // println!("Circuit reconstructed successfully from serialized data."); - // } + use super::{compile_dsgc, generate_and_write_fresh_circuit}; + + #[test] + #[ignore] + fn test_babe_gc_serialize_roundtrip() { + generate_and_write_fresh_circuit(); + + reset_gid(); + let g = G1Affine::generator(); + let (bld, output_indices) = compile_dsgc(g); + let circuit = bld.build(&[]); + let num_wires = circuit.0.len() as u32; + + // --- Reconstruct Circuit --- + + let (circuit_reconstructed, _) = super::read_fresh_circuit(); + + + assert_eq!(circuit_reconstructed.0.len(), circuit.0.len(), "reconstructed wire count mismatch"); + assert_eq!(circuit_reconstructed.1.len(), circuit.1.len(), "reconstructed gate count mismatch"); + + for (i, (orig, rec)) in circuit.1.iter().zip(circuit_reconstructed.1.iter()).enumerate() { + assert_eq!(orig.gate_type, rec.gate_type, "reconstructed gate[{i}] type mismatch"); + assert_eq!(orig.wire_a.borrow().id, rec.wire_a.borrow().id, "reconstructed gate[{i}] wire_a id mismatch"); + assert_eq!(orig.wire_b.borrow().id, rec.wire_b.borrow().id, "reconstructed gate[{i}] wire_b id mismatch"); + assert_eq!(orig.wire_c.borrow().id, rec.wire_c.borrow().id, "reconstructed gate[{i}] wire_c id mismatch"); + assert_eq!(orig.gid, rec.gid, "reconstructed gate[{i}] gid mismatch"); + } + + println!("wires={num_wires}, gates={}, output_indices={}", circuit.1.len(), output_indices.len()); + println!("Circuit reconstructed successfully from serialized data."); + } } diff --git a/verifiable-circuit-babe/src/instance/mod.rs b/verifiable-circuit-babe/src/instance/mod.rs index e49f41b..6c9d2a8 100644 --- a/verifiable-circuit-babe/src/instance/mod.rs +++ b/verifiable-circuit-babe/src/instance/mod.rs @@ -1,4 +1,4 @@ -use ark_bn254::Fr; +use ark_bn254::{Fr, G1Affine}; use ark_ec::{AffineRepr, CurveGroup}; use ark_ec::pairing::Pairing; use ark_ff::UniformRand; @@ -10,7 +10,7 @@ use crate::instance::secret::InstanceSecrets; use ark_groth16::VerifyingKey as Groth16VerifyingKey; use ark_serialize::CanonicalSerialize; use crate::instance::commit::CACInstanceCommit; -use crate::utils::{g2_to_ser, groth16_vk_x, h_256}; +use crate::utils::{g2_to_ser, ro_from_pairing_bytes}; pub mod secret; pub mod commit; @@ -88,25 +88,40 @@ impl BABEInstance { } } - /// Encsetup(crs, x, msg; r): ctsetup = (r·[delta]_2, RO(rY) ⊕ msg), - /// where Y = e([alpha]_1, [beta]_2) · e(vk_x, [gamma]_2). + /// Enc*(crs, x_S, |D|, msg; r, r·B): + /// P_S = gamma_abc[0] + Σ_{k} x_S[k]·gamma_abc[k+1] + /// mask = Y_S^r - e(r·B, γ) where Y_S^r = e(α, r·β) + e(P_S, r·γ) pub fn enc_setup( &mut self, vk: &Groth16VerifyingKey, - public_inputs: &[Fr], + static_inputs: &[Fr], + dynamic_pin_size: usize, ) -> Result<(), String> { - let vk_x = groth16_vk_x(vk, public_inputs).ok_or("Failed to get vk_x")?; + let num_static = static_inputs.len(); + if num_static + dynamic_pin_size + 1 != vk.gamma_abc_g1.len() { + return Err("static/dynamic split does not match vk".to_string()); + } let r = self.secrets.r; + + let mut p_s = vk.gamma_abc_g1[0].into_group(); + for (k, x) in static_inputs.iter().enumerate() { + p_s += vk.gamma_abc_g1[k + 1].into_group() * *x; + } + + let r_b = self.secrets.r_b; let r_delta = vk.delta_g2.into_group() * r; let t1 = ark_bn254::Bn254::pairing(vk.alpha_g1, vk.beta_g2.into_group() * r); - let t2 = ark_bn254::Bn254::pairing(vk_x, vk.gamma_g2.into_group() * r); - let r_y = t1 + t2; + let t2 = ark_bn254::Bn254::pairing(p_s, vk.gamma_g2.into_group() * r); + let y_s_r = t1 + t2; + + let q_b = ark_bn254::Bn254::pairing(r_b, vk.gamma_g2); + let mask_gt = y_s_r - q_b; - let mut ry_bytes = Vec::new(); - r_y.serialize_compressed(&mut ry_bytes).or(Err("Failed to serialize ry_bytes"))?; - let mask = h_256(&ry_bytes); + let mut mask_bytes = Vec::new(); + mask_gt.serialize_compressed(&mut mask_bytes).or(Err("Failed to serialize mask_bytes"))?; + let mask = ro_from_pairing_bytes(&mask_bytes, self.secrets.msg.len()); let ct3 = self.secrets.msg.iter().zip(mask.iter()).map(|(a, b)| a ^ b).collect::>(); self.ct_setup = WeKnownPi1SetupCt { @@ -149,6 +164,9 @@ mod tests { #[test] fn enc_setup_prove_dec_roundtrip() { + use crate::babe::{we_known_pi1_dec, WeKnownPi1ProveCt}; + use crate::utils::g1_to_ser; + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(42); let a = Fr::from(3u64); @@ -160,35 +178,28 @@ mod tests { &pk, DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, - ) - .expect("groth16 prove"); - let public_inputs = vec![a * b]; + ).expect("groth16 prove"); + + // |S|=1 (a*b static), |D|=1 (a*a dynamic) + let static_inputs = vec![a * b]; + let dynamic_pin_size = 1usize; let mut instance = BABEInstance::new_from_seed(42); - instance.enc_setup(&vk, &public_inputs).unwrap(); - - let ct1 = proof.a.into_group() * instance.secrets.r; - - let ct2 = ark_bn254::G2Affine::deserialize_compressed( - instance.ct_setup.ct2_r_delta_g2.as_slice(), - ) - .expect("deserialize ct2") - .into_group(); - let lhs = ark_bn254::Bn254::pairing(ct1, proof.b); - let rhs = ark_bn254::Bn254::pairing(proof.c, ct2); - let r_y = lhs - rhs; - - let mut ry_bytes = Vec::new(); - r_y.serialize_compressed(&mut ry_bytes).unwrap(); - let mask = h_256(&ry_bytes); - - let decrypted: [u8; 32] = instance.ct_setup.ct3_masked_msg.iter() - .zip(mask.iter()) - .map(|(c, m)| c ^ m) - .collect::>() - .try_into() - .unwrap(); - - assert_eq!(decrypted, instance.secrets.msg); + instance.enc_setup(&vk, &static_inputs, dynamic_pin_size).unwrap(); + + let r = instance.secrets.r; + + // Simulate DSGC output: c1' = r·P_D + r·B + // P_D = (a*a) · gamma_abc[|S|+1] = (a*a) · gamma_abc[2] + let p_d = vk.gamma_abc_g1[2].into_group() * (a * a); + let c1_prime = (p_d * r + instance.secrets.r_b.into_group()).into_affine(); + + let ctprove = WeKnownPi1ProveCt { ct1_r_pi1: g1_to_ser(proof.a.into_group() * r) }; + let decrypted = we_known_pi1_dec( + &vk, &instance.ct_setup, &ctprove, c1_prime, + proof.b.into_group(), proof.c.into_group(), + ).unwrap(); + + assert_eq!(decrypted.as_slice(), &instance.secrets.msg); } } \ No newline at end of file diff --git a/verifiable-circuit-babe/src/instance/secret.rs b/verifiable-circuit-babe/src/instance/secret.rs index 2dd6a78..385f0ee 100644 --- a/verifiable-circuit-babe/src/instance/secret.rs +++ b/verifiable-circuit-babe/src/instance/secret.rs @@ -1,4 +1,5 @@ use ark_bn254::{Fq, Fr, G1Affine}; +use ark_ec::AffineRepr; use ark_ff::{UniformRand, Zero}; use garbled_snark_verifier::bag::S; use rand::SeedableRng; @@ -15,6 +16,8 @@ pub struct InstanceSecrets { pub constant_0labels: [S; 2], pub rhos: Vec, pub fq_deltas: Vec, + /// Blinding point r·B baked into the DSGC circuit. + pub r_b: G1Affine, } impl InstanceSecrets { @@ -58,6 +61,10 @@ impl InstanceSecrets { }) .collect(); - Self { delta, r, msg, encoding_keys, constant_0labels, rhos, fq_deltas } + let b_blind = G1Affine::rand(&mut rng); + use ark_ec::CurveGroup; + let r_b = (b_blind.into_group() * r).into_affine(); + + Self { delta, r, msg, encoding_keys, constant_0labels, rhos, fq_deltas, r_b } } } diff --git a/verifiable-circuit-babe/src/prover.rs b/verifiable-circuit-babe/src/prover.rs index ee332ed..e64cd11 100644 --- a/verifiable-circuit-babe/src/prover.rs +++ b/verifiable-circuit-babe/src/prover.rs @@ -272,11 +272,12 @@ mod tests { DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).unwrap(); - let public_inputs = vec![a * b]; + let static_public_inputs = vec![a * b]; + let dynamic_public_inputs = vec![a * a]; // 2. Verifier enc_setup. let mut verifier = BABEInstance::new_from_seed(rand::random()); - verifier.enc_setup(&vk, &public_inputs).unwrap(); + verifier.enc_setup(&vk, &static_public_inputs, dynamic_public_inputs.len()).unwrap(); // 3. Full labels: [const_0, const_1, pi1_bits...]. let full_labels = verifier.compute_pi1_labels_based_on_value(proof.a); @@ -313,9 +314,10 @@ mod tests { let (_, vk) = ark_groth16::Groth16::::setup( DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).unwrap(); - let public_inputs = vec![a * b]; + let static_public_inputs = vec![a * b]; + let dynamic_public_inputs = vec![a * a]; - let verifier = BABEVerifier::new(TEST_N_CC, &vk, &public_inputs).unwrap(); + let verifier = BABEVerifier::new(TEST_N_CC, &vk, &static_public_inputs, dynamic_public_inputs.len()).unwrap(); let package = verifier.commit(); let finalized_indices = cac_finalize_indices(&package, TEST_M_CC); let (_, finalized) = verifier.open(&finalized_indices); diff --git a/verifiable-circuit-babe/src/verifier.rs b/verifiable-circuit-babe/src/verifier.rs index e117110..9e0e33e 100644 --- a/verifiable-circuit-babe/src/verifier.rs +++ b/verifiable-circuit-babe/src/verifier.rs @@ -19,7 +19,8 @@ impl BABEVerifier { pub fn new( n_cc: usize, vk: &Groth16VerifyingKey, - public_inputs: &[Fr], + static_public_inputs: &[Fr], + dynamic_pin_size: usize, ) -> Result { use p3_maybe_rayon::prelude::*; let seeds: Vec = (0..n_cc).map(|_| rand::random()).collect(); @@ -27,7 +28,7 @@ impl BABEVerifier { .par_iter() .map(|&seed| { let mut inst = BABEInstance::new_from_seed(seed); - inst.enc_setup(vk, public_inputs)?; + inst.enc_setup(vk, static_public_inputs, dynamic_pin_size)?; Ok::(inst) }) .collect::>() From c03d591df54b01522db824c33493b382829085fe Mon Sep 17 00:00:00 2001 From: vanhger Date: Thu, 23 Apr 2026 10:50:09 +0700 Subject: [PATCH 07/32] chore: remove the signed, cause it change x_d format. --- garbled-snark-verifier/src/dv_bn254/g1.rs | 102 ++++++++----------- verifiable-circuit-babe/src/gc/circuit.rs | 113 ++++++---------------- 2 files changed, 69 insertions(+), 146 deletions(-) diff --git a/garbled-snark-verifier/src/dv_bn254/g1.rs b/garbled-snark-verifier/src/dv_bn254/g1.rs index 58c5106..fbb7325 100644 --- a/garbled-snark-verifier/src/dv_bn254/g1.rs +++ b/garbled-snark-verifier/src/dv_bn254/g1.rs @@ -429,24 +429,22 @@ impl G1Projective { res } - /// Signed-digit scalar multiplication with a garbler-private halved affine table. + /// Unsigned 8-bit windowed scalar multiplication with a garbler-private full affine table. /// - /// `digits` — Per window (9 bits): `[idx_0 .. idx_{w-2}][sign][skip]`, LSB-first indices. - /// * `idx` (7 bits) selects entry `j` ∈ [0, 127] from the table. - /// * `sign` = 1 applies y → −y (digit is negative). - /// * `skip` = 1 means the digit is zero (omit the add for this window). - /// - /// `table` — `SIGNED_SCALAR_WINDOW_COUNT * SIGNED_SCALAR_WINDOW_ENTRIES` affine points. - /// Window `i`, entry `j` represents `(j+1) · (256^i) · P` in affine Montgomery. - pub fn scalar_mul_private_signed_table_circuit( + /// `scalar` — 254 wire indices, raw (non-Montgomery) bits, LSB-first. + /// `table` — `SCALAR_WINDOW_COUNT` windows of `SCALAR_WINDOW_ENTRIES` affine points, + /// each encoded as Montgomery wires (x·R, y·R). + /// Window `w`, entry `j` represents `j * (256^w * P)` in affine form. + /// Entry j=0 is the point at infinity (addition skipped via window_nonzero guard). + pub fn scalar_mul_private_table_circuit( bld: &mut T, - digits: &[usize], + scalar: &[usize], table: &[usize], ) -> Vec { - assert_eq!(digits.len(), SIGNED_SCALAR_WINDOW_COUNT * SIGNED_DIGIT_BITS); + assert_eq!(scalar.len(), Fr::N_BITS); assert_eq!( table.len(), - SIGNED_SCALAR_WINDOW_COUNT * SIGNED_SCALAR_WINDOW_ENTRIES * G1_AFFINE_LEN + SCALAR_WINDOW_COUNT * SCALAR_WINDOW_ENTRIES * G1_AFFINE_LEN ); let z_one = Fq::wires_set(bld, Fq::as_montgomery(ark_bn254::Fq::from(1u64))).0.to_vec(); @@ -454,72 +452,56 @@ impl G1Projective { bld, G1Projective::as_montgomery(ark_bn254::G1Projective::default()), ) - .to_vec_wires(); + .to_vec_wires(); let mut acc: Vec = inf_wires.clone(); let mut acc_is_inf: usize = bld.one(); + let zero_wire = bld.zero(); - for w in 0..SIGNED_SCALAR_WINDOW_COUNT { - // ─── extract digit bits for this window ───────────────────────── - let digit_base = w * SIGNED_DIGIT_BITS; - let idx_bits: Vec = (0..(SIGNED_SCALAR_WINDOW_BITS - 1)) - .map(|k| digits[digit_base + k]) - .collect(); - let sign_bit = digits[digit_base + SIGNED_SCALAR_WINDOW_BITS - 1]; - let skip_bit = digits[digit_base + SIGNED_SCALAR_WINDOW_BITS]; + let bit = |i: usize| -> usize { + if i < Fr::N_BITS { scalar[i] } else { zero_wire } + }; + + for w in 0..SCALAR_WINDOW_COUNT { + let base = w * SCALAR_WINDOW_BITS; + let sel: Vec = (0..SCALAR_WINDOW_BITS).map(|k| bit(base + k)).collect(); + + // window_nonzero = OR of all 8 selector bits + let mut window_nonzero = sel[0]; + for &b in &sel[1..] { + window_nonzero = bld.or_wire(window_nonzero, b); + } - // ─── 128-wide MUX on 7 idx bits ───────────────────────────────── - let window_base = w * SIGNED_SCALAR_WINDOW_ENTRIES * G1_AFFINE_LEN; - let ws = - &table[window_base..window_base + SIGNED_SCALAR_WINDOW_ENTRIES * G1_AFFINE_LEN]; + let window_base = w * SCALAR_WINDOW_ENTRIES * G1_AFFINE_LEN; + let ws = &table[window_base..window_base + SCALAR_WINDOW_ENTRIES * G1_AFFINE_LEN]; - let tab_x: Vec> = (0..SIGNED_SCALAR_WINDOW_ENTRIES) + let tab_x: Vec> = (0..SCALAR_WINDOW_ENTRIES) .map(|i| ws[i * G1_AFFINE_LEN..i * G1_AFFINE_LEN + FQ_LEN].to_vec()) .collect(); - let tab_y: Vec> = (0..SIGNED_SCALAR_WINDOW_ENTRIES) - .map(|i| { - ws[i * G1_AFFINE_LEN + FQ_LEN..(i + 1) * G1_AFFINE_LEN].to_vec() - }) + let tab_y: Vec> = (0..SCALAR_WINDOW_ENTRIES) + .map(|i| ws[i * G1_AFFINE_LEN + FQ_LEN..(i + 1) * G1_AFFINE_LEN].to_vec()) .collect(); - let affine_x = Fq::multiplexer(bld, &tab_x, &idx_bits, SIGNED_SCALAR_WINDOW_BITS - 1); - let affine_y = Fq::multiplexer(bld, &tab_y, &idx_bits, SIGNED_SCALAR_WINDOW_BITS - 1); - // Conditional y-negation (sign bit → returns −y if sign=1, else y). - let affine_y_signed = Fq::negate_with_selector(bld, &affine_y, sign_bit); + let affine_x = Fq::multiplexer(bld, &tab_x, &sel, SCALAR_WINDOW_BITS); + let affine_y = Fq::multiplexer(bld, &tab_y, &sel, SCALAR_WINDOW_BITS); let mut affine = Vec::with_capacity(G1_AFFINE_LEN); affine.extend_from_slice(&affine_x); - affine.extend_from_slice(&affine_y_signed); + affine.extend_from_slice(&affine_y); let mut affine_proj = Vec::with_capacity(G1_PROJECTIVE_LEN); affine_proj.extend_from_slice(&affine_x); - affine_proj.extend_from_slice(&affine_y_signed); + affine_proj.extend_from_slice(&affine_y); affine_proj.extend_from_slice(&z_one); - if w == 0 { - // Unrolled first window: init acc directly from affine_proj (Z=1) when not skipped. - let not_skip = not(bld, skip_bit); - acc = Self::selector_projective_montgomery( - bld, - &affine_proj, - &inf_wires, - not_skip, - ); - acc_is_inf = skip_bit; - } else { - let mixed = Self::add_mixed_montgomery_no_inf(bld, &acc, &affine); - - let candidate = Self::selector_projective_montgomery( - bld, - &affine_proj, - &mixed, - acc_is_inf, - ); - let not_skip = not(bld, skip_bit); - acc = Self::selector_projective_montgomery(bld, &candidate, &acc, not_skip); - - acc_is_inf = bld.and_wire(acc_is_inf, skip_bit); - } + let mixed = Self::add_mixed_montgomery_no_inf(bld, &acc, &affine); + + let candidate = + Self::selector_projective_montgomery(bld, &affine_proj, &mixed, acc_is_inf); + acc = Self::selector_projective_montgomery(bld, &candidate, &acc, window_nonzero); + + let not_nonzero = not(bld, window_nonzero); + acc_is_inf = bld.and_wire(acc_is_inf, not_nonzero); } acc diff --git a/verifiable-circuit-babe/src/gc/circuit.rs b/verifiable-circuit-babe/src/gc/circuit.rs index 6646d0b..221bb46 100644 --- a/verifiable-circuit-babe/src/gc/circuit.rs +++ b/verifiable-circuit-babe/src/gc/circuit.rs @@ -8,52 +8,49 @@ use garbled_snark_verifier::dv_bn254::g1::G1Projective as GcG1Projective; use crate::dre::{L, N, U_BAR_SIZE}; -// Signed-digit scalar-mul parameters. Must match the `SIGNED_SCALAR_*` constants in -// garbled-snark-verifier's dv_bn254::g1. With w=8 we do ≤ 33 mixed-adds over a -// half-sized (128-entry) precomputed table; the halved MUX saves ~6M AND vs the -// unsigned w=8 version. Digits d_i ∈ [-128, 127] are produced offline via -// `booth_recode_w8`; each window carries 7 index + 1 sign + 1 skip = 9 bits. +// Unsigned 8-bit windowed scalar-mul parameters. Must match the `SCALAR_WINDOW_*` +// constants in garbled-snark-verifier's dv_bn254::g1. With w=8 we do 32 mixed-adds +// over a full 256-entry precomputed table. x_d is supplied as plain Fr bits (LSB-first). pub const WINDOW_BITS: usize = 8; -pub const WINDOW_COUNT: usize = (Fr::N_BITS + WINDOW_BITS - 1) / WINDOW_BITS + 1; // +1 for Booth carry -pub const WINDOW_ENTRIES: usize = 1 << (WINDOW_BITS - 1); // halved: 128 -pub const DIGIT_BITS: usize = WINDOW_BITS + 1; // 7 idx + 1 sign + 1 skip +pub const WINDOW_COUNT: usize = (Fr::N_BITS + WINDOW_BITS - 1) / WINDOW_BITS; // 32 +pub const WINDOW_ENTRIES: usize = 1 << WINDOW_BITS; // 256 pub const PRECOMP_TABLE_BITS: usize = WINDOW_COUNT * WINDOW_ENTRIES * 2 * N; -pub const DIGIT_TOTAL_BITS: usize = WINDOW_COUNT * DIGIT_BITS; +pub const CONSTANT_SIZE: usize = 2 + 2 * N + PRECOMP_TABLE_BITS; /// Compile the DSGC circuit structure without fixing witness values. /// /// Input wire allocation order: /// [0..N) pi_x — x-coordinate of the proof point π /// [N..2·N) pi_y — y-coordinate of π -/// [2·N..3·N) r·B_x — x-coordinate of blinding point, Montgomery form -/// [3N..4·N) r·B_y — y-coordinate of blinding point, Montgomery form -/// [4·N..4·N+PRECOMP_TABLE_BITS) signed-digit precomputed table for Base, +/// [2·N..2·N+Fr::N_BITS) x_d — raw scalar bits, LSB-first (evaluator input) +/// [2·N+Fr::N_BITS..3·N+Fr::N_BITS) r·B_x — x-coordinate of blinding point, Montgomery form +/// [..+N) r·B_y — y-coordinate of blinding point, Montgomery form +/// [..+PRECOMP_TABLE_BITS) unsigned w=8 table for Base, /// WINDOW_COUNT windows × WINDOW_ENTRIES affine points -/// (entry j = (j+1)·256^i·P), Montgomery form -/// [..+DIGIT_TOTAL_BITS) x_d recoded into Booth digits, 9 bits per window +/// (entry j = j·256^i·P), Montgomery form; j=0 is infinity /// pub fn compile_dsgc(g: G1Affine) -> (CircuitAdapter, Vec) { let mut bld = CircuitAdapter::default(); - // π input wires + // π input wires (evaluator) let pi_x = Fq::wires(&mut bld); let pi_y = Fq::wires(&mut bld); - // r·B input wires — garbler-private, placed before table wires. + // x_d raw scalar bits (evaluator input) + let x_d: Vec = (0..Fr::N_BITS).map(|_| bld.fresh_one()).collect(); + + // r·B and precomputed table — garbler-private let rb_x = Fq::wires(&mut bld); let rb_y = Fq::wires(&mut bld); let mut table_wires = Vec::with_capacity(PRECOMP_TABLE_BITS); for _ in 0..(WINDOW_COUNT * WINDOW_ENTRIES * 2) { table_wires.extend(Fq::wires(&mut bld).0); } - // Signed-digit encoding of x_d (33 × 9 = 297 bits) - let digit_wires: Vec = (0..DIGIT_TOTAL_BITS).map(|_| bld.fresh_one()).collect(); - let output_indices = emit_dsgc( &mut bld, &table_wires, - &digit_wires, + &x_d, &pi_x.0, &pi_y.0, g, @@ -72,7 +69,7 @@ pub const R_B_BITS: usize = 2 * N; fn emit_dsgc( bld: &mut CircuitAdapter, table_wires: &[usize], - digits: &[usize], + x_d: &[usize], pi_x: &[usize], pi_y: &[usize], g: G1Affine, @@ -118,9 +115,9 @@ fn emit_dsgc( .map(|k| selector(bld, pi_u_bar[k], g_u_bar[k], on_curve)) .collect(); - // ── x_d · Base subcircuit (garbler-private signed-digit windowed table) ──── + // ── x_d · Base subcircuit (unsigned 8-bit windowed private table) ───────── let prod_proj_m = - GcG1Projective::scalar_mul_private_signed_table_circuit(bld, digits, table_wires); + GcG1Projective::scalar_mul_private_table_circuit(bld, x_d, table_wires); // ── Blinding: add r·B (garbler-private affine, already in Montgomery form) ─ let mut rb_affine_m: Vec = rb_x_wires.to_vec(); @@ -159,59 +156,6 @@ fn g_u_bar_indices(bld: &mut CircuitAdapter, g: G1Affine) -> Vec { indices } -/// Booth-recode `k` into `WINDOW_COUNT` signed digits d_i ∈ [-128, 127] satisfying -/// `Σ_i d_i · 256^i ≡ k (mod r)`. The extra window absorbs the final carry. -pub fn booth_recode_w8(k: ark_bn254::Fr) -> Vec { - use ark_ff::{BigInteger, PrimeField}; - let scalar_bits = k.into_bigint().to_bits_le(); - - let mut digits = Vec::with_capacity(WINDOW_COUNT); - let mut carry: i32 = 0; - for i in 0..WINDOW_COUNT { - let mut byte: i32 = 0; - for b in 0..WINDOW_BITS { - let idx = i * WINDOW_BITS + b; - if idx < scalar_bits.len() && scalar_bits[idx] { - byte |= 1 << b; - } - } - let mut d = byte + carry; - if d >= (1 << (WINDOW_BITS - 1)) { - d -= 1 << WINDOW_BITS; - carry = 1; - } else { - carry = 0; - } - digits.push(d); - } - debug_assert_eq!(carry, 0, "booth_recode_w8: residual carry did not fit in WINDOW_COUNT digits"); - digits -} - -/// Encode a signed-digit sequence into the 9-bits-per-window witness layout consumed by -/// `scalar_mul_private_signed_table_circuit`: `[idx_0 .. idx_{w-2}][sign][skip]`. -pub fn encode_signed_digits(digits: &[i32]) -> Vec { - let mut bits = Vec::with_capacity(digits.len() * DIGIT_BITS); - for &d in digits { - if d == 0 { - for _ in 0..(WINDOW_BITS - 1) { - bits.push(false); - } - bits.push(false); - bits.push(true); - } else { - let mag = d.unsigned_abs(); // |d| in [1, 128] - let idx = mag - 1; // in [0, 127], fits in 7 bits - for b in 0..(WINDOW_BITS - 1) { - bits.push((idx >> b) & 1 != 0); - } - bits.push(d < 0); - bits.push(false); - } - } - bits -} - /// SHA256 commitment to a `Vec>` GC ciphertext list. pub fn gc_ciphertexts_commit(ciphertexts: &[Option]) -> [u8; 32] { let mut hasher = Sha256::new(); @@ -240,16 +184,16 @@ mod tests { ark_bn254::G1Projective::rand(&mut rng).into_affine() } - /// Build the halved signed-digit Base table. + /// Build the unsigned w=8 Base table. /// /// Layout: window `i` (i = 0..WINDOW_COUNT), entry `j` (j = 0..WINDOW_ENTRIES) stores - /// `(j+1) · 256^i · Base` in affine Montgomery form. + /// `j · 256^i · Base` in affine Montgomery form. Entry j=0 is the point at infinity. fn build_base_table_bits(base: &G1Affine) -> Vec { let mut bits = Vec::with_capacity(PRECOMP_TABLE_BITS); let mut window_base = ark_bn254::G1Projective::from(base.clone()); for _ in 0..WINDOW_COUNT { - let mut multiple = window_base; // start at 1·window_base + let mut multiple = ark_bn254::G1Projective::zero(); // j=0: infinity for _ in 0..WINDOW_ENTRIES { let aff = multiple.into_affine(); bits.extend(Fq::to_bits(Fq::as_montgomery(aff.x))); @@ -264,17 +208,14 @@ mod tests { bits } - /// Build a full witness: r·B (garbler-private blinding point in Montgomery form), - /// precomputed signed table for P_D, Booth-recoded x_d, pi_x, pi_y. + /// Build a full witness: pi_x, pi_y, x_d raw bits, r·B (Montgomery), precomputed table. fn build_witness(pi: &G1Affine, base: &G1Affine, x_d: ark_bn254::Fr, r_b: &G1Affine) -> Vec { - let digits = booth_recode_w8(x_d); - Fq::to_bits(pi.x).into_iter() .chain(Fq::to_bits(pi.y)) + .chain(Fr::to_bits(x_d)) .chain(Fq::to_bits(Fq::as_montgomery(r_b.x))) .chain(Fq::to_bits(Fq::as_montgomery(r_b.y))) .chain(build_base_table_bits(base)) - .chain(encode_signed_digits(&digits)) .collect() } @@ -438,9 +379,9 @@ mod tests { println!("circuit generate time: {:?}", now.elapsed()); // 2. Generate a new set of labels — one label0 per input wire. - // Input wires: R_B_BITS (r·B) + PRECOMP_TABLE_BITS (table) + DIGIT_TOTAL_BITS (x_d) + 2·N (π) + // Input wires: 2·N (π) + Fr::N_BITS (x_d) + R_B_BITS (r·B) + PRECOMP_TABLE_BITS (table) let now = Instant::now(); - let total_input_wires = R_B_BITS + PRECOMP_TABLE_BITS + DIGIT_TOTAL_BITS + 2 * N; + let total_input_wires = 2 * N + Fr::N_BITS + R_B_BITS + PRECOMP_TABLE_BITS; let encoding_keys: Vec = (0..total_input_wires).map(|_| S::random()).collect(); for (i, &key) in encoding_keys.iter().enumerate() { circuit.0[2 + i].borrow_mut().label = Some(key); From c09d1d505bdfd96f994a751e852ef7000664a16e Mon Sep 17 00:00:00 2001 From: vanhger Date: Thu, 23 Apr 2026 12:06:43 +0700 Subject: [PATCH 08/32] add secrets --- verifiable-circuit-babe/src/cac.rs | 9 +- verifiable-circuit-babe/src/gc/circuit.rs | 50 +++---- .../src/instance/commit.rs | 5 +- verifiable-circuit-babe/src/instance/mod.rs | 122 ++++++++++++++---- .../src/instance/secret.rs | 28 ++-- verifiable-circuit-babe/src/prover.rs | 8 +- verifiable-circuit-babe/src/verifier.rs | 10 +- 7 files changed, 160 insertions(+), 72 deletions(-) diff --git a/verifiable-circuit-babe/src/cac.rs b/verifiable-circuit-babe/src/cac.rs index 3fb8ad1..29d55e7 100644 --- a/verifiable-circuit-babe/src/cac.rs +++ b/verifiable-circuit-babe/src/cac.rs @@ -71,9 +71,12 @@ pub fn verify_opened_instances( opened .par_iter() .map(|&(idx, seed)| { - let mut inst = BABEInstance::new_from_seed(seed); - inst.enc_setup(vk, static_public_inputs, dynamic_pin_size) - .map_err(|e| format!("instance {idx}: enc_setup failed: {e}"))?; + let inst = BABEInstance::new_from_seed( + seed, + vk, + static_public_inputs, + dynamic_pin_size + )?; let recomputed = inst.commit(); let committed = &package.commits[idx]; diff --git a/verifiable-circuit-babe/src/gc/circuit.rs b/verifiable-circuit-babe/src/gc/circuit.rs index 221bb46..71ecbb2 100644 --- a/verifiable-circuit-babe/src/gc/circuit.rs +++ b/verifiable-circuit-babe/src/gc/circuit.rs @@ -1,4 +1,6 @@ use ark_bn254::G1Affine; +use ark_ec::CurveGroup; +use ark_ff::{AdditiveGroup, Zero}; use sha2::{Digest, Sha256}; use garbled_snark_verifier::circuits::sect233k1::builder::{CircuitAdapter, CircuitTrait}; use garbled_snark_verifier::dv_bn254::basic::selector; @@ -168,6 +170,30 @@ pub fn gc_ciphertexts_commit(ciphertexts: &[Option Vec { + let mut bits = Vec::with_capacity(PRECOMP_TABLE_BITS); + let mut window_base = ark_bn254::G1Projective::from(base.clone()); + + for _ in 0..WINDOW_COUNT { + let mut multiple = ark_bn254::G1Projective::zero(); // j=0: infinity + for _ in 0..WINDOW_ENTRIES { + let aff = multiple.into_affine(); + bits.extend(Fq::to_bits(Fq::as_montgomery(aff.x))); + bits.extend(Fq::to_bits(Fq::as_montgomery(aff.y))); + multiple += window_base; + } + + for _ in 0..WINDOW_BITS { + window_base.double_in_place(); + } + } + bits +} + #[cfg(test)] mod tests { use std::time::Instant; @@ -184,30 +210,6 @@ mod tests { ark_bn254::G1Projective::rand(&mut rng).into_affine() } - /// Build the unsigned w=8 Base table. - /// - /// Layout: window `i` (i = 0..WINDOW_COUNT), entry `j` (j = 0..WINDOW_ENTRIES) stores - /// `j · 256^i · Base` in affine Montgomery form. Entry j=0 is the point at infinity. - fn build_base_table_bits(base: &G1Affine) -> Vec { - let mut bits = Vec::with_capacity(PRECOMP_TABLE_BITS); - let mut window_base = ark_bn254::G1Projective::from(base.clone()); - - for _ in 0..WINDOW_COUNT { - let mut multiple = ark_bn254::G1Projective::zero(); // j=0: infinity - for _ in 0..WINDOW_ENTRIES { - let aff = multiple.into_affine(); - bits.extend(Fq::to_bits(Fq::as_montgomery(aff.x))); - bits.extend(Fq::to_bits(Fq::as_montgomery(aff.y))); - multiple += window_base; - } - - for _ in 0..WINDOW_BITS { - window_base.double_in_place(); - } - } - bits - } - /// Build a full witness: pi_x, pi_y, x_d raw bits, r·B (Montgomery), precomputed table. fn build_witness(pi: &G1Affine, base: &G1Affine, x_d: ark_bn254::Fr, r_b: &G1Affine) -> Vec { Fq::to_bits(pi.x).into_iter() diff --git a/verifiable-circuit-babe/src/instance/commit.rs b/verifiable-circuit-babe/src/instance/commit.rs index e61c97a..e773742 100644 --- a/verifiable-circuit-babe/src/instance/commit.rs +++ b/verifiable-circuit-babe/src/instance/commit.rs @@ -21,13 +21,14 @@ pub struct CACInstanceCommit { } impl CACInstanceCommit { + // Todo: add x_d pub fn from_instance(instance: &BABEInstance) -> Self { let delta = instance.secrets.delta; let input_commits = compute_epk_with_delta(&instance.secrets.encoding_keys, delta).0; - + // todo: change this to commit only the labels. let constant_commits = std::array::from_fn(|w| { - let l0 = instance.secrets.constant_0labels[w]; + let l0 = instance.secrets.constant_val_labels[w]; [h_256(&l0.0), h_256(&(l0 ^ delta).0)] }); diff --git a/verifiable-circuit-babe/src/instance/mod.rs b/verifiable-circuit-babe/src/instance/mod.rs index 6c9d2a8..f516cfe 100644 --- a/verifiable-circuit-babe/src/instance/mod.rs +++ b/verifiable-circuit-babe/src/instance/mod.rs @@ -2,13 +2,17 @@ use ark_bn254::{Fr, G1Affine}; use ark_ec::{AffineRepr, CurveGroup}; use ark_ec::pairing::Pairing; use ark_ff::UniformRand; -use garbled_snark_verifier::bag::S; +use garbled_snark_verifier::bag::{Circuit, S}; use garbled_snark_verifier::circuits::bn254::fq::Fq; use crate::babe::WeKnownPi1SetupCt; -use crate::gc::SparseAdaptorTable; +use crate::gc::{build_base_table_bits, SparseAdaptorTable, CONSTANT_SIZE}; use crate::instance::secret::InstanceSecrets; use ark_groth16::VerifyingKey as Groth16VerifyingKey; use ark_serialize::CanonicalSerialize; +use garbled_snark_verifier::dv_bn254::fp254impl::Fp254Impl; +use garbled_snark_verifier::dv_bn254::fq::Fq as DvFq; +use garbled_snark_verifier::dv_bn254::fr::Fr as DvFr; +use crate::dre::N; use crate::instance::commit::CACInstanceCommit; use crate::utils::{g2_to_ser, ro_from_pairing_bytes}; @@ -24,30 +28,77 @@ pub struct BABEInstance { } impl BABEInstance { - /// Construct a BABE instance fully determined by `seed`. + /// Construct a instance fully determined by `seed`. /// W/O ct_setup. - pub fn new_from_seed(seed: u64) -> Self { + pub fn new_from_seed( + seed: u64, + vk: &Groth16VerifyingKey, + static_inputs: &[Fr], + dynamic_pin_size: usize, + ) -> Result { use ark_bn254::G1Projective; use ark_ff::Zero; use crate::dre::matrices::u_bar_vec; + let num_static = static_inputs.len(); + if num_static + dynamic_pin_size + 1 != vk.gamma_abc_g1.len() { + return Err("static/dynamic split does not match vk".to_string()); + } + let secrets = InstanceSecrets::new_from_seed(seed); // Load fresh circuit structure from pre-serialized files; drop after use. let (mut circuit, gc_output_indices) = crate::gc::read_fresh_circuit(); - // Apply encoding keys and constant 0-labels to input wires. - circuit.0[0].borrow_mut().label = Some(secrets.constant_0labels[0]); - circuit.0[1].borrow_mut().label = Some(secrets.constant_0labels[1]); + // Apply encoding keys as 0-labels for evaluator input wires (pi_x, pi_y, x_d). for (i, &key) in secrets.encoding_keys.iter().enumerate() { circuit.0[2 + i].borrow_mut().label = Some(key); } - // Evaluate circuit at a random pi1 to obtain output labels. + // Compute r·L_i = r * sum(gamma_abc[num_static+1..]) and build the precomputed table. + let base = vk.gamma_abc_g1[num_static + 1..] + .iter() + .map(|p| (*p).into_group()) + .sum::() * secrets.r; + let table_bits = build_base_table_bits(&base.into_affine()); + + // Compute r·B bit representation (Montgomery form) for use as constant wires. + let rb_x_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(secrets.r_b.x)); + let rb_y_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(secrets.r_b.y)); + + // Derive 0-labels for each constant wire from val-labels and actual wire values. + // constant_val_labels[i] is the label for the actual value of constant wire i: + // if value = 0 → val_label IS the 0-label + // if value = 1 → val_label IS the 1-label, so 0-label = val_label ^ delta + let delta = secrets.delta; + let mut constants_0labels = Vec::with_capacity(CONSTANT_SIZE); + constants_0labels.push(secrets.constant_val_labels[0]); // wire 0: value = 0 + constants_0labels.push(secrets.constant_val_labels[1] ^ delta); // wire 1: value = 1 + for (k, &bit) in rb_x_bits.iter().enumerate() { + let lv = secrets.constant_val_labels[2 + k]; + constants_0labels.push(if bit { lv ^ delta } else { lv }); + } + for (k, &bit) in rb_y_bits.iter().enumerate() { + let lv = secrets.constant_val_labels[2 + N + k]; + constants_0labels.push(if bit { lv ^ delta } else { lv }); + } + for (k, &bit) in table_bits.iter().enumerate() { + let lv = secrets.constant_val_labels[2 + 2 * N + k]; + constants_0labels.push(if bit { lv ^ delta } else { lv }); + } + + BABEInstance::set_gc_const_labels(&mut circuit, &constants_0labels); + + // Evaluate circuit at a random pi1 and random x_d to garble. let pi1 = G1Projective::rand(&mut rand::thread_rng()).into_affine(); + let x_d = ark_bn254::Fr::rand(&mut rand::thread_rng()); let witness: Vec = Fq::to_bits(pi1.x) .into_iter() - .chain(Fq::to_bits(pi1.y).into_iter()) + .chain(Fq::to_bits(pi1.y)) + .chain(DvFr::to_bits(x_d)) + .chain(rb_x_bits) + .chain(rb_y_bits) + .chain(table_bits) .collect(); circuit.set_witness_value(&witness); for gate in &mut circuit.1 { @@ -77,39 +128,57 @@ impl BABEInstance { &secrets.fq_deltas, ); + let ct_setup = Self::enc_setup( + &secrets, + vk, + static_inputs, + dynamic_pin_size, + )?; + // circuit is dropped here — not stored in the instance. - Self { + Ok(Self { seed, secrets, - ct_setup: WeKnownPi1SetupCt { ct2_r_delta_g2: vec![], ct3_masked_msg: vec![] }, + ct_setup, adaptor_table, ciphertexts, + }) + } + + pub fn set_gc_const_labels( + circuit: &mut Circuit, + constant_labels: &[S], + ) { + assert_eq!(constant_labels.len(), CONSTANT_SIZE); + circuit.0[0].borrow_mut().label = Some(constant_labels[0]); + circuit.0[1].borrow_mut().label = Some(constant_labels[1]); + for i in 2..CONSTANT_SIZE { + circuit.0[i + 2 * N + DvFr::N_BITS].borrow_mut().label = Some(constant_labels[i]); } } /// Enc*(crs, x_S, |D|, msg; r, r·B): /// P_S = gamma_abc[0] + Σ_{k} x_S[k]·gamma_abc[k+1] /// mask = Y_S^r - e(r·B, γ) where Y_S^r = e(α, r·β) + e(P_S, r·γ) - pub fn enc_setup( - &mut self, + fn enc_setup( + secrets: &InstanceSecrets, vk: &Groth16VerifyingKey, static_inputs: &[Fr], dynamic_pin_size: usize, - ) -> Result<(), String> { + ) -> Result { let num_static = static_inputs.len(); if num_static + dynamic_pin_size + 1 != vk.gamma_abc_g1.len() { return Err("static/dynamic split does not match vk".to_string()); } - let r = self.secrets.r; - + let r = secrets.r; let mut p_s = vk.gamma_abc_g1[0].into_group(); for (k, x) in static_inputs.iter().enumerate() { p_s += vk.gamma_abc_g1[k + 1].into_group() * *x; } - let r_b = self.secrets.r_b; + let r_b = secrets.r_b; let r_delta = vk.delta_g2.into_group() * r; let t1 = ark_bn254::Bn254::pairing(vk.alpha_g1, vk.beta_g2.into_group() * r); @@ -121,16 +190,16 @@ impl BABEInstance { let mut mask_bytes = Vec::new(); mask_gt.serialize_compressed(&mut mask_bytes).or(Err("Failed to serialize mask_bytes"))?; - let mask = ro_from_pairing_bytes(&mask_bytes, self.secrets.msg.len()); - let ct3 = self.secrets.msg.iter().zip(mask.iter()).map(|(a, b)| a ^ b).collect::>(); + let mask = ro_from_pairing_bytes(&mask_bytes, secrets.msg.len()); + let ct3 = secrets.msg.iter().zip(mask.iter()).map(|(a, b)| a ^ b).collect::>(); - self.ct_setup = WeKnownPi1SetupCt { + Ok(WeKnownPi1SetupCt { ct2_r_delta_g2: g2_to_ser(r_delta), ct3_masked_msg: ct3, - }; - Ok(()) + }) } + // Todo: add x_d in this. /// Returns the input labels given the bits of pi1. pub fn compute_pi1_labels_based_on_value(&self, pi1: ark_bn254::G1Affine) -> Vec { let x_bits = Fq::to_bits(pi1.x); @@ -139,8 +208,8 @@ impl BABEInstance { let delta = self.secrets.delta; let mut labels = Vec::new(); - labels.push(self.secrets.constant_0labels[0]); - labels.push(self.secrets.constant_0labels[1] ^ delta); + labels.push(self.secrets.constant_val_labels[0]); + labels.push(self.secrets.constant_val_labels[1] ^ delta); let tail: Vec = witness.iter().enumerate().map(|(i, &b)| { let key = self.secrets.encoding_keys[i]; if b { key ^ delta } else { key } @@ -149,6 +218,7 @@ impl BABEInstance { labels } + pub fn commit(&self) -> CACInstanceCommit { CACInstanceCommit::from_instance(self) } @@ -184,8 +254,8 @@ mod tests { let static_inputs = vec![a * b]; let dynamic_pin_size = 1usize; - let mut instance = BABEInstance::new_from_seed(42); - instance.enc_setup(&vk, &static_inputs, dynamic_pin_size).unwrap(); + let mut instance = BABEInstance::new_from_seed(42, &vk, &static_inputs, dynamic_pin_size) + .expect("new_from_seed"); let r = instance.secrets.r; diff --git a/verifiable-circuit-babe/src/instance/secret.rs b/verifiable-circuit-babe/src/instance/secret.rs index 385f0ee..ffbe73c 100644 --- a/verifiable-circuit-babe/src/instance/secret.rs +++ b/verifiable-circuit-babe/src/instance/secret.rs @@ -4,16 +4,20 @@ use ark_ff::{UniformRand, Zero}; use garbled_snark_verifier::bag::S; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; +use garbled_snark_verifier::dv_bn254::fp254impl::Fp254Impl; +use garbled_snark_verifier::dv_bn254::fr::Fr as DvFr; use crate::dre::{N, utils::sample_rhos}; +use crate::gc::CONSTANT_SIZE; pub struct InstanceSecrets { pub delta: S, pub r: Fr, pub msg: [u8; 32], - /// label0 per input wire, size = 2 * N + /// label0 per input wire, size = 2 * N + Fr::N (3 * N) pub encoding_keys: Vec, - /// Two constant-wire 0-labels - pub constant_0labels: [S; 2], + /// Constant value-based labels. For example, if constant x = 1 + /// this contains 1-label of x. Size = CONSTANT_SIZE + pub constant_val_labels: Vec, pub rhos: Vec, pub fq_deltas: Vec, /// Blinding point r·B baked into the DSGC circuit. @@ -34,7 +38,7 @@ impl InstanceSecrets { let mut msg = [0u8; 32]; rand::RngCore::fill_bytes(&mut rng, &mut msg); - let encoding_keys: Vec = (0..2 * N) + let encoding_keys: Vec = (0..2 * N + DvFr::N_BITS) .map(|_| { let mut b = [0u8; 16]; rand::RngCore::fill_bytes(&mut rng, &mut b); @@ -42,13 +46,13 @@ impl InstanceSecrets { }) .collect(); - let constant_0labels = { - let mut b0 = [0u8; 16]; - let mut b1 = [0u8; 16]; - rand::RngCore::fill_bytes(&mut rng, &mut b0); - rand::RngCore::fill_bytes(&mut rng, &mut b1); - [S(b0), S(b1)] - }; + let constant_val_labels: Vec = (0..CONSTANT_SIZE) + .map(|_| { + let mut b = [0u8; 16]; + rand::RngCore::fill_bytes(&mut rng, &mut b); + S(b) + }) + .collect(); let rhos = sample_rhos(&mut rng); @@ -65,6 +69,6 @@ impl InstanceSecrets { use ark_ec::CurveGroup; let r_b = (b_blind.into_group() * r).into_affine(); - Self { delta, r, msg, encoding_keys, constant_0labels, rhos, fq_deltas, r_b } + Self { delta, r, msg, encoding_keys, constant_val_labels, rhos, fq_deltas, r_b } } } diff --git a/verifiable-circuit-babe/src/prover.rs b/verifiable-circuit-babe/src/prover.rs index e64cd11..0a4de42 100644 --- a/verifiable-circuit-babe/src/prover.rs +++ b/verifiable-circuit-babe/src/prover.rs @@ -276,8 +276,12 @@ mod tests { let dynamic_public_inputs = vec![a * a]; // 2. Verifier enc_setup. - let mut verifier = BABEInstance::new_from_seed(rand::random()); - verifier.enc_setup(&vk, &static_public_inputs, dynamic_public_inputs.len()).unwrap(); + let mut verifier = BABEInstance::new_from_seed( + rand::random(), + &vk, + &static_public_inputs, + dynamic_public_inputs.len() + ).unwrap(); // 3. Full labels: [const_0, const_1, pi1_bits...]. let full_labels = verifier.compute_pi1_labels_based_on_value(proof.a); diff --git a/verifiable-circuit-babe/src/verifier.rs b/verifiable-circuit-babe/src/verifier.rs index 9e0e33e..4c51566 100644 --- a/verifiable-circuit-babe/src/verifier.rs +++ b/verifiable-circuit-babe/src/verifier.rs @@ -27,8 +27,12 @@ impl BABEVerifier { let instances = seeds .par_iter() .map(|&seed| { - let mut inst = BABEInstance::new_from_seed(seed); - inst.enc_setup(vk, static_public_inputs, dynamic_pin_size)?; + let inst = BABEInstance::new_from_seed( + seed, + vk, + static_public_inputs, + dynamic_pin_size + )?; Ok::(inst) }) .collect::>() @@ -66,7 +70,7 @@ impl BABEVerifier { for &i in finalized_indices { let inst = &self.instances[i]; let constant_labels = - [inst.secrets.constant_0labels[0], inst.secrets.constant_0labels[1] ^ inst.secrets.delta]; + [inst.secrets.constant_val_labels[0], inst.secrets.constant_val_labels[1] ^ inst.secrets.delta]; finalized.push(crate::cac::FinalizedInstanceData { index: i, gc_ciphertexts: inst.ciphertexts.clone(), From ddaf0488b8734d52e111c9b28323e42f03cd4591 Mon Sep 17 00:00:00 2001 From: vanhger Date: Thu, 23 Apr 2026 12:25:05 +0700 Subject: [PATCH 09/32] test roundtrip --- verifiable-circuit-babe/src/instance/mod.rs | 14 +++++--------- verifiable-circuit-babe/src/prover.rs | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/verifiable-circuit-babe/src/instance/mod.rs b/verifiable-circuit-babe/src/instance/mod.rs index f516cfe..0030411 100644 --- a/verifiable-circuit-babe/src/instance/mod.rs +++ b/verifiable-circuit-babe/src/instance/mod.rs @@ -1,4 +1,4 @@ -use ark_bn254::{Fr, G1Affine}; +use ark_bn254::Fr; use ark_ec::{AffineRepr, CurveGroup}; use ark_ec::pairing::Pairing; use ark_ff::UniformRand; @@ -37,8 +37,6 @@ impl BABEInstance { dynamic_pin_size: usize, ) -> Result { use ark_bn254::G1Projective; - use ark_ff::Zero; - use crate::dre::matrices::u_bar_vec; let num_static = static_inputs.len(); if num_static + dynamic_pin_size + 1 != vk.gamma_abc_g1.len() { @@ -50,6 +48,7 @@ impl BABEInstance { // Load fresh circuit structure from pre-serialized files; drop after use. let (mut circuit, gc_output_indices) = crate::gc::read_fresh_circuit(); + // Apply encoding keys as 0-labels for evaluator input wires (pi_x, pi_y, x_d). for (i, &key) in secrets.encoding_keys.iter().enumerate() { circuit.0[2 + i].borrow_mut().label = Some(key); @@ -108,15 +107,12 @@ impl BABEInstance { // Recover label0 for each output wire. let delta = secrets.delta; - let u_bar_pi1 = u_bar_vec(&pi1); let output_labels: Vec<[u8; 16]> = gc_output_indices .iter() - .zip(u_bar_pi1.iter()) - .flat_map(|(idx, u)| { - let current = circuit.0[*idx] + .flat_map(|idx| { + let label_0 = circuit.0[*idx] .borrow() .select_with_delta(circuit.0[*idx].borrow().get_value(), delta); - let label_0 = if u.is_zero() { current } else { current ^ delta }; [label_0.0, (label_0 ^ delta).0] }) .collect(); @@ -254,7 +250,7 @@ mod tests { let static_inputs = vec![a * b]; let dynamic_pin_size = 1usize; - let mut instance = BABEInstance::new_from_seed(42, &vk, &static_inputs, dynamic_pin_size) + let instance = BABEInstance::new_from_seed(42, &vk, &static_inputs, dynamic_pin_size) .expect("new_from_seed"); let r = instance.secrets.r; diff --git a/verifiable-circuit-babe/src/prover.rs b/verifiable-circuit-babe/src/prover.rs index 0a4de42..40c63aa 100644 --- a/verifiable-circuit-babe/src/prover.rs +++ b/verifiable-circuit-babe/src/prover.rs @@ -276,7 +276,7 @@ mod tests { let dynamic_public_inputs = vec![a * a]; // 2. Verifier enc_setup. - let mut verifier = BABEInstance::new_from_seed( + let verifier = BABEInstance::new_from_seed( rand::random(), &vk, &static_public_inputs, From 29919f0f60b14157daf48881a0c0f080f27e8874 Mon Sep 17 00:00:00 2001 From: vanhger Date: Thu, 23 Apr 2026 15:04:12 +0700 Subject: [PATCH 10/32] feat: hardcode the size of input & fix encryption --- verifiable-circuit-babe/src/babe.rs | 35 +++++++------ verifiable-circuit-babe/src/cac.rs | 12 ++--- verifiable-circuit-babe/src/instance/mod.rs | 55 +++++++-------------- verifiable-circuit-babe/src/prover.rs | 24 ++++----- verifiable-circuit-babe/src/transactions.rs | 22 +++++---- verifiable-circuit-babe/src/verifier.rs | 4 +- 6 files changed, 69 insertions(+), 83 deletions(-) diff --git a/verifiable-circuit-babe/src/babe.rs b/verifiable-circuit-babe/src/babe.rs index 411d7b8..632e84a 100644 --- a/verifiable-circuit-babe/src/babe.rs +++ b/verifiable-circuit-babe/src/babe.rs @@ -9,6 +9,7 @@ use rand::SeedableRng; use serde::{Deserialize, Serialize}; use std::marker::PhantomData; use ark_crypto_primitives::snark::{CircuitSpecificSetupSNARK, SNARK}; +use ark_crypto_primitives::sponge::Absorb; use ark_ec::pairing::Pairing; use garbled_snark_verifier::bag::S; use crate::cac::{ @@ -25,7 +26,8 @@ use crate::verifier::BABEVerifier; // ─── Constants ──────────────────────────────────────────────────────────────── /// Number of bits in π₁ (G1Affine): 254 bits for x + 254 bits for y. -pub const LAMPORT_N: usize = 508; +/// Plus 254 Fr +pub const LAMPORT_N: usize = 762; /// Total number of C&C instances the Verifier creates and commits to. /// In practice, N_CC = 181. @@ -140,10 +142,9 @@ pub struct BabeCACE2ERun { /// and the public `CACSetupPackage` to send to the Prover. pub fn babe_verifier_cac_setup( vk: &Groth16VerifyingKey, - static_public_inputs: &[Fr], - dynamic_pin_size: usize, + static_public_inputs: Fr, ) -> (BABEVerifier, CACSetupPackage) { - let verifier = BABEVerifier::new(N_CC, vk, static_public_inputs, dynamic_pin_size).expect("verifier CAC setup failed"); + let verifier = BABEVerifier::new(N_CC, vk, static_public_inputs).expect("verifier CAC setup failed"); println!("Verifier: committing all instances.."); let package = verifier.commit(); (verifier, package) @@ -176,10 +177,9 @@ pub fn babe_prover_verify_setup( finalized: &[FinalizedInstanceData], soldering: &SolderingData, vk: &Groth16VerifyingKey, - static_public_inputs: &[Fr], - dynamic_pin_size: usize, + static_public_inputs: Fr, ) -> Result<(), String> { - verify_opened_instances(package, opened, vk, static_public_inputs, dynamic_pin_size)?; + verify_opened_instances(package, opened, vk, static_public_inputs)?; verify_finalized_instances(package, finalized)?; BABEProver::verify_soldering_output(package, soldering)?; Ok(()) @@ -238,12 +238,14 @@ pub fn babe_build_deposit_lock(pk_p: BtcPk, pk_v: BtcPk, amount: u64) -> TxDepos // ─── Assert phase (Prover posts π₁) ───────────────────────────────────────── /// Prover: sign π₁ with lsk_P and build the assert witness. -pub fn babe_prover_assert(proof: &Groth16Proof, lsk_p: &LamportSk) -> TxAssertWitness { +pub fn babe_prover_assert(proof: &Groth16Proof, lsk_p: &LamportSk, x_d: ark_bn254::Fr) -> TxAssertWitness { let pi1 = proof.a; let mut pi1_bytes = Vec::new(); pi1.serialize_compressed(&mut pi1_bytes).expect("serialize π₁"); + // Todo: convert x_d to bytes + let mut x_d_bytes = Vec::new(); x_d.serialize_compressed(&mut x_d_bytes).expect("serialize x_d"); let lamport_sig = lamport_sign(lsk_p, &pi1); - TxAssertWitness { pi1: pi1_bytes, lamport_sig } + TxAssertWitness { pi1: pi1_bytes, lamport_sig, x_d: x_d_bytes } } // ─── ChallengeAssert phase (Verifier reveals base-instance labels) ──────────── @@ -268,6 +270,7 @@ pub fn babe_verifier_challenge_assert_cac( sig_p_presig: BabeBtcSig, ) -> Option { let pi1 = G1Affine::deserialize_compressed(assert_witness.pi1.as_slice()).ok()?; + let x_d = Fr::from_le_bytes_mod_order(&assert_witness.x_d); println!("Verifier: Checking the Lamport signature in tx_Assert witness against pi1 and lpk_P..."); if !lamport_verify(&verifier_state.lpk_p, &pi1, &assert_witness.lamport_sig) { @@ -277,9 +280,9 @@ pub fn babe_verifier_challenge_assert_cac( // Derive labels from the base instance (finalized_indices[0]). let base_idx = verifier_state.finalized_indices[0]; let base_inst = &verifier_state.verifier.instances[base_idx]; - let all_labels = base_inst.compute_pi1_labels_based_on_value(pi1); + let input_labels = base_inst.compute_input_labels_based_on_value(pi1, x_d); // all_labels[0..2] are constant-wire labels; [2..] are π₁ input labels. - let input_labels: Vec<[u8; 16]> = all_labels[2..].iter().map(|s| s.0).collect(); + let input_labels: Vec<[u8; 16]> = input_labels.iter().map(|s| s.0).collect(); Some(TxChallengeAssertWitness { input_labels, @@ -421,8 +424,8 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).expect("groth16 prove"); - let static_public_inputs = vec![a * b]; - let dynamic_public_inputs = vec![a * a]; + let static_public_inputs = a * b; + let dynamic_public_inputs = a * a; // ── Setup phase ─────────────────────────────────────────────────────────── @@ -435,7 +438,7 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { println!("Verifier generating BTC keys"); let pk_v = BtcPk([1u8; 33]); println!("Verifier: building {} instances...", N_CC); - let (verifier, package) = babe_verifier_cac_setup(&vk, &static_public_inputs, dynamic_public_inputs.len()); + let (verifier, package) = babe_verifier_cac_setup(&vk, static_public_inputs); // Verifier sends vk and package to Prover println!("Verifier: sending pk_v and {} instance commitment to Prover", N_CC); @@ -450,7 +453,7 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { println!("Prover: verifying opening and soldering proof..."); // Prover verifies everything. - babe_prover_verify_setup(&package, &opened, &finalized, &soldering, &vk, &static_public_inputs, dynamic_public_inputs.len()) + babe_prover_verify_setup(&package, &opened, &finalized, &soldering, &vk, static_public_inputs) .expect("prover setup verification failed"); println!("Prover: generating Lamport signature..."); @@ -508,7 +511,7 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { // ── Proving phase ───────────────────────────────────────────────────────── // Assert: Prover posts π₁ + Lamport sig on-chain. - let assert_witness = babe_prover_assert(&proof, &prover_state.lsk_p); + let assert_witness = babe_prover_assert(&proof, &prover_state.lsk_p, dynamic_public_inputs); println!("Prover: posting tx_Assert..."); println!("tx_Assert witness: {} bytes", assert_witness.size_bytes()); diff --git a/verifiable-circuit-babe/src/cac.rs b/verifiable-circuit-babe/src/cac.rs index 29d55e7..e1ba65a 100644 --- a/verifiable-circuit-babe/src/cac.rs +++ b/verifiable-circuit-babe/src/cac.rs @@ -64,8 +64,7 @@ pub fn verify_opened_instances( package: &CACSetupPackage, opened: &[(usize, u64)], vk: &Groth16VerifyingKey, - static_public_inputs: &[Fr], - dynamic_pin_size: usize, + static_public_inputs: Fr, ) -> Result<(), String> { use p3_maybe_rayon::prelude::*; opened @@ -75,7 +74,6 @@ pub fn verify_opened_instances( seed, vk, static_public_inputs, - dynamic_pin_size )?; let recomputed = inst.commit(); @@ -156,12 +154,12 @@ mod tests { let (_, vk) = ark_groth16::Groth16::::setup( DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).expect("groth16 setup"); - let static_public_inputs = vec![a * b]; - let dynamic_public_inputs = vec![a * a]; + let static_public_inputs = a * b; + let dynamic_public_inputs = a * a; // Verifier creates TEST_N_CC instances and commits. let now = std::time::Instant::now(); - let verifier = BABEVerifier::new(TEST_N_CC, &vk, &static_public_inputs, dynamic_public_inputs.len()) + let verifier = BABEVerifier::new(TEST_N_CC, &vk, static_public_inputs) .expect("BABEVerifier::new failed"); let elapsed = now.elapsed(); println!("Verifier setup for {TEST_N_CC} instances took {elapsed:.2?}"); @@ -184,7 +182,7 @@ mod tests { let now = std::time::Instant::now(); // Prover verifies opened instances by re-deriving from seed. - verify_opened_instances(&package, &opened, &vk, &static_public_inputs, dynamic_public_inputs.len()) + verify_opened_instances(&package, &opened, &vk, static_public_inputs) .expect("opened instance verification failed"); let elapsed = now.elapsed(); println!("Prover verification of opened instances took {elapsed:.2?}"); diff --git a/verifiable-circuit-babe/src/instance/mod.rs b/verifiable-circuit-babe/src/instance/mod.rs index 0030411..6548165 100644 --- a/verifiable-circuit-babe/src/instance/mod.rs +++ b/verifiable-circuit-babe/src/instance/mod.rs @@ -3,7 +3,6 @@ use ark_ec::{AffineRepr, CurveGroup}; use ark_ec::pairing::Pairing; use ark_ff::UniformRand; use garbled_snark_verifier::bag::{Circuit, S}; -use garbled_snark_verifier::circuits::bn254::fq::Fq; use crate::babe::WeKnownPi1SetupCt; use crate::gc::{build_base_table_bits, SparseAdaptorTable, CONSTANT_SIZE}; use crate::instance::secret::InstanceSecrets; @@ -33,13 +32,11 @@ impl BABEInstance { pub fn new_from_seed( seed: u64, vk: &Groth16VerifyingKey, - static_inputs: &[Fr], - dynamic_pin_size: usize, + static_inputs: Fr, ) -> Result { use ark_bn254::G1Projective; - let num_static = static_inputs.len(); - if num_static + dynamic_pin_size + 1 != vk.gamma_abc_g1.len() { + if vk.gamma_abc_g1.len() != 3{ return Err("static/dynamic split does not match vk".to_string()); } @@ -55,10 +52,7 @@ impl BABEInstance { } // Compute r·L_i = r * sum(gamma_abc[num_static+1..]) and build the precomputed table. - let base = vk.gamma_abc_g1[num_static + 1..] - .iter() - .map(|p| (*p).into_group()) - .sum::() * secrets.r; + let base = vk.gamma_abc_g1[2] * secrets.r; let table_bits = build_base_table_bits(&base.into_affine()); // Compute r·B bit representation (Montgomery form) for use as constant wires. @@ -91,9 +85,9 @@ impl BABEInstance { // Evaluate circuit at a random pi1 and random x_d to garble. let pi1 = G1Projective::rand(&mut rand::thread_rng()).into_affine(); let x_d = ark_bn254::Fr::rand(&mut rand::thread_rng()); - let witness: Vec = Fq::to_bits(pi1.x) + let witness: Vec = DvFq::to_bits(pi1.x) .into_iter() - .chain(Fq::to_bits(pi1.y)) + .chain(DvFq::to_bits(pi1.y)) .chain(DvFr::to_bits(x_d)) .chain(rb_x_bits) .chain(rb_y_bits) @@ -128,7 +122,6 @@ impl BABEInstance { &secrets, vk, static_inputs, - dynamic_pin_size, )?; // circuit is dropped here — not stored in the instance. @@ -160,19 +153,10 @@ impl BABEInstance { fn enc_setup( secrets: &InstanceSecrets, vk: &Groth16VerifyingKey, - static_inputs: &[Fr], - dynamic_pin_size: usize, + static_inputs: Fr, ) -> Result { - let num_static = static_inputs.len(); - if num_static + dynamic_pin_size + 1 != vk.gamma_abc_g1.len() { - return Err("static/dynamic split does not match vk".to_string()); - } - let r = secrets.r; - let mut p_s = vk.gamma_abc_g1[0].into_group(); - for (k, x) in static_inputs.iter().enumerate() { - p_s += vk.gamma_abc_g1[k + 1].into_group() * *x; - } + let p_s = vk.gamma_abc_g1[0].into_group() + vk.gamma_abc_g1[1].into_group() * static_inputs; let r_b = secrets.r_b; let r_delta = vk.delta_g2.into_group() * r; @@ -195,22 +179,19 @@ impl BABEInstance { }) } - // Todo: add x_d in this. /// Returns the input labels given the bits of pi1. - pub fn compute_pi1_labels_based_on_value(&self, pi1: ark_bn254::G1Affine) -> Vec { - let x_bits = Fq::to_bits(pi1.x); - let y_bits = Fq::to_bits(pi1.y); - let witness: Vec = x_bits.into_iter().chain(y_bits.into_iter()).collect(); + /// Use for testing. + pub fn compute_input_labels_based_on_value(&self, pi1: ark_bn254::G1Affine, x_d: ark_bn254::Fr) -> Vec { + let x_bits = DvFq::to_bits(pi1.x); + let y_bits = DvFq::to_bits(pi1.y); + let xd_bits = DvFr::to_bits(x_d); + let witness: Vec = x_bits.into_iter().chain(y_bits).chain(xd_bits).collect(); let delta = self.secrets.delta; - let mut labels = Vec::new(); - labels.push(self.secrets.constant_val_labels[0]); - labels.push(self.secrets.constant_val_labels[1] ^ delta); - let tail: Vec = witness.iter().enumerate().map(|(i, &b)| { + let labels: Vec = witness.iter().enumerate().map(|(i, &b)| { let key = self.secrets.encoding_keys[i]; if b { key ^ delta } else { key } }).collect(); - labels.extend(tail); labels } @@ -224,7 +205,6 @@ impl BABEInstance { mod tests { use super::*; use ark_crypto_primitives::snark::{CircuitSpecificSetupSNARK, SNARK}; - use ark_serialize::CanonicalDeserialize; use rand::SeedableRng; use crate::babe::DummyMulCircuit; @@ -247,17 +227,18 @@ mod tests { ).expect("groth16 prove"); // |S|=1 (a*b static), |D|=1 (a*a dynamic) - let static_inputs = vec![a * b]; + let static_inputs = a * b; + let dynamic_inputs = a * a; let dynamic_pin_size = 1usize; - let instance = BABEInstance::new_from_seed(42, &vk, &static_inputs, dynamic_pin_size) + let instance = BABEInstance::new_from_seed(42, &vk, static_inputs) .expect("new_from_seed"); let r = instance.secrets.r; // Simulate DSGC output: c1' = r·P_D + r·B // P_D = (a*a) · gamma_abc[|S|+1] = (a*a) · gamma_abc[2] - let p_d = vk.gamma_abc_g1[2].into_group() * (a * a); + let p_d = vk.gamma_abc_g1[2].into_group() * dynamic_inputs; let c1_prime = (p_d * r + instance.secrets.r_b.into_group()).into_affine(); let ctprove = WeKnownPi1ProveCt { ct1_r_pi1: g1_to_ser(proof.a.into_group() * r) }; diff --git a/verifiable-circuit-babe/src/prover.rs b/verifiable-circuit-babe/src/prover.rs index 40c63aa..af8b0a5 100644 --- a/verifiable-circuit-babe/src/prover.rs +++ b/verifiable-circuit-babe/src/prover.rs @@ -272,19 +272,18 @@ mod tests { DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).unwrap(); - let static_public_inputs = vec![a * b]; - let dynamic_public_inputs = vec![a * a]; + let static_public_inputs = a * b; + let dynamic_public_inputs = a * a; // x_d // 2. Verifier enc_setup. let verifier = BABEInstance::new_from_seed( rand::random(), &vk, - &static_public_inputs, - dynamic_public_inputs.len() + static_public_inputs, ).unwrap(); - // 3. Full labels: [const_0, const_1, pi1_bits...]. - let full_labels = verifier.compute_pi1_labels_based_on_value(proof.a); + // 3. Input labels: [pi1_xbits, pi1_ybits, x_dbits...]. + let input_labels = verifier.compute_input_labels_based_on_value(proof.a, a * a); // 4. Prover evaluates the garbled circuit. let prover = BABEProver::new(proof.clone()); @@ -292,7 +291,7 @@ mod tests { let ct_prove = prover.compute_ct_prove( &mut circuit, &gc_output_indices, - &full_labels, + &input_labels, &verifier.ciphertexts, &verifier.adaptor_table, ); @@ -318,10 +317,10 @@ mod tests { let (_, vk) = ark_groth16::Groth16::::setup( DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).unwrap(); - let static_public_inputs = vec![a * b]; - let dynamic_public_inputs = vec![a * a]; + let static_public_inputs = a * b; + let dynamic_public_inputs = a * a; - let verifier = BABEVerifier::new(TEST_N_CC, &vk, &static_public_inputs, dynamic_public_inputs.len()).unwrap(); + let verifier = BABEVerifier::new(TEST_N_CC, &vk, static_public_inputs).unwrap(); let package = verifier.commit(); let finalized_indices = cac_finalize_indices(&package, TEST_M_CC); let (_, finalized) = verifier.open(&finalized_indices); @@ -365,10 +364,13 @@ mod tests { DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).unwrap(); + let static_public_inputs = a * b; + let dynamic_public_inputs = a * a; + // base_input_labels: active labels for the 508 π₁ input wires of the base instance. let base_idx = finalized_indices[0]; - let all_labels = verifier.instances[base_idx].compute_pi1_labels_based_on_value(proof.a); + let all_labels = verifier.instances[base_idx].compute_input_labels_based_on_value(proof.a, dynamic_public_inputs); let base_input_labels = &all_labels[2..]; // strip constant-wire labels let mut prover = BABEProver::new(proof.clone()); diff --git a/verifiable-circuit-babe/src/transactions.rs b/verifiable-circuit-babe/src/transactions.rs index fd161b8..a73f992 100644 --- a/verifiable-circuit-babe/src/transactions.rs +++ b/verifiable-circuit-babe/src/transactions.rs @@ -28,8 +28,13 @@ pub struct TxChallengeAssertOutputLock { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct TxAssertWitness { /// Compressed G1Affine, 33 bytes — the asserted proof element. + /// This is not onchain pub pi1: Vec, - /// μ₁…μ_ℓ — Lamport signature, LAMPORT_N × 16 bytes. + /// Dynamic public input scalar x_d, 32 bytes (little-endian Fr). + /// This is not onchain + pub x_d: Vec, + /// μ₁…μ_M — Lamport signature, LAMPORT_N × 16 bytes. + /// This is submitted onchain. pub lamport_sig: LamportSig, } @@ -37,12 +42,12 @@ pub struct TxAssertWitness { /// Input spends tx_Assert output 1: CheckLampSigsMatch(lpk_P, lpk_V) ∧ CheckSig(pk_V) ∧ CheckSig(pk_P) /// Script verifies: /// (a) SHA256(μ[i]) == lpk_P[i][bit_i] — μ is a valid Lamport sig for some π₁ -/// (b) blake3(L[i]) == lpk_V[i][bit_i] — L[i] is the correct GC label for bit_i under epk +/// (b) SHA256(L[i]) == lpk_V[i][bit_i] — L[i] is the correct GC label for bit_i under epk #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct TxChallengeAssertWitness { - /// L₁…L_ℓ — one GC input label per π₁ bit, LAMPORT_N × 16 bytes. + /// L₁…L_M — one GC input label per π₁ bit, LAMPORT_N × 16 bytes. pub input_labels: Vec<[u8; 16]>, - /// μ₁…μ_ℓ — Lamport sig re-posted to bind L to π₁. + /// μ₁…μ_M — Lamport sig re-posted to bind L to π₁. pub lamport_sig: LamportSig, /// VerifierLiveSig pub sig_v: BabeBtcSig, @@ -97,16 +102,14 @@ pub trait OnchainSize { impl OnchainSize for TxAssertWitness { fn size_bytes(&self) -> usize { - PI1_BYTES // pi1: 33 bytes - + LAMPORT_SIG_BYTES // lamport_sig: 8,128 bytes - // total: 8,161 bytes + LAMPORT_SIG_BYTES } } impl OnchainSize for TxChallengeAssertWitness { fn size_bytes(&self) -> usize { - LAMPORT_N * 16 // input_labels: 8,128 bytes - + LAMPORT_SIG_BYTES // lamport_sig: 8,128 bytes + LAMPORT_N * 16 + + LAMPORT_SIG_BYTES + BTC_SIG_BYTES // sig_v: 32 bytes + BTC_SIG_BYTES // sig_p: 32 bytes // total: 16,320 bytes @@ -149,6 +152,7 @@ mod tests { let assert_w = TxAssertWitness { pi1: vec![0u8; PI1_BYTES], + x_d: vec![0u8; 32], lamport_sig: dummy_lamport.clone(), }; assert_eq!(assert_w.size_bytes(), 8161); diff --git a/verifiable-circuit-babe/src/verifier.rs b/verifiable-circuit-babe/src/verifier.rs index 4c51566..ef2348f 100644 --- a/verifiable-circuit-babe/src/verifier.rs +++ b/verifiable-circuit-babe/src/verifier.rs @@ -19,8 +19,7 @@ impl BABEVerifier { pub fn new( n_cc: usize, vk: &Groth16VerifyingKey, - static_public_inputs: &[Fr], - dynamic_pin_size: usize, + static_public_inputs: Fr, ) -> Result { use p3_maybe_rayon::prelude::*; let seeds: Vec = (0..n_cc).map(|_| rand::random()).collect(); @@ -31,7 +30,6 @@ impl BABEVerifier { seed, vk, static_public_inputs, - dynamic_pin_size )?; Ok::(inst) }) From d7e226a080f5742e7f6ddb1819cd5cd1b276da79 Mon Sep 17 00:00:00 2001 From: vanhger Date: Mon, 27 Apr 2026 09:44:26 +0700 Subject: [PATCH 11/32] feat: use 2 garbled circuits. --- verifiable-circuit-babe/src/dre/mod.rs | 3 +- verifiable-circuit-babe/src/gc/adaptor.rs | 19 +- verifiable-circuit-babe/src/gc/circuit.rs | 447 ++++++++------------ verifiable-circuit-babe/src/gc/mod.rs | 6 +- verifiable-circuit-babe/src/instance/mod.rs | 6 +- 5 files changed, 185 insertions(+), 296 deletions(-) diff --git a/verifiable-circuit-babe/src/dre/mod.rs b/verifiable-circuit-babe/src/dre/mod.rs index 06410d0..42b2edc 100644 --- a/verifiable-circuit-babe/src/dre/mod.rs +++ b/verifiable-circuit-babe/src/dre/mod.rs @@ -4,8 +4,7 @@ pub mod utils; pub mod matrices; pub const N: usize = 254; pub const U_BAR_SIZE: usize = 1 + 5 * N; // 1271 — ū(π) binary decomposition -pub const R_PD_SIZE: usize = 3 * N; // 762 — projective (X,Y,Z) of x_d·P_D -pub const L: usize = U_BAR_SIZE + R_PD_SIZE; // 2033 +pub const Q_SIZE: usize = 3 * N; // 762 — projective (X,Y,Z) of x_d·L_2 + B /// Decoding: f_i = r_i·π + ρ_i (Jacobian coords) pub struct DREDecoding { diff --git a/verifiable-circuit-babe/src/gc/adaptor.rs b/verifiable-circuit-babe/src/gc/adaptor.rs index 077661a..bb4c428 100644 --- a/verifiable-circuit-babe/src/gc/adaptor.rs +++ b/verifiable-circuit-babe/src/gc/adaptor.rs @@ -2,7 +2,7 @@ use ark_bn254::{Fq, Fr, G1Affine}; use ark_ff::Zero; use ark_serialize::CanonicalSerialize; use sha2::{Digest, Sha256}; -use crate::dre::{DREDecoding, L, N}; +use crate::dre::{DREDecoding, N, U_BAR_SIZE}; use crate::dre::matrices::{build_d_i_sparse, nonzero_col_indices}; use crate::gc::utils::{aes_dec, aes_enc, prf_fq}; @@ -32,19 +32,19 @@ pub struct SparseAdaptorTable { } impl SparseAdaptorTable { - pub fn build_from_r_and_labels( + pub fn build_from_r_and_u_bar_labels( r: Fr, labels: &[[u8; 16]], rhos: &[G1Affine], fq_deltas: &[Fq], ) -> Self { - assert_eq!(labels.len(), 2 * L); + assert_eq!(labels.len(), 2 * U_BAR_SIZE); assert_eq!(rhos.len(), N); assert_eq!(fq_deltas.len(), N); let r_bits = garbled_snark_verifier::dv_bn254::fr::Fr::to_bits(r); let col_indices = nonzero_col_indices(); - let prf_cache: Vec = (0..L).map(|k| prf_fq(&labels[2 * k + 1])).collect(); + let prf_cache: Vec = (0..U_BAR_SIZE).map(|k| prf_fq(&labels[2 * k + 1])).collect(); let entries = (0..N) .map(|i| { @@ -95,8 +95,8 @@ impl SparseAdaptorTable { /// Decrypt the adaptor table and sum each row to recover Jacobian coords of r_i·π + ρ_i. pub fn eval(&self, labels: &[[u8; 16]], u_bar: &[Fq]) -> Vec { - assert_eq!(labels.len(), L); - assert_eq!(u_bar.len(), L); + assert_eq!(labels.len(), U_BAR_SIZE); + assert_eq!(u_bar.len(), U_BAR_SIZE); let col_indices = nonzero_col_indices(); @@ -135,7 +135,6 @@ mod tests { use garbled_snark_verifier::core::s::S; // Todo: change to use own delta, instead of NON_CAC_DELTA use garbled_snark_verifier::core::utils::NON_CAC_DELTA; - use crate::dre::L; use crate::dre::matrices::u_bar_vec; #[test] @@ -146,7 +145,7 @@ mod tests { let u_bar_pi = u_bar_vec(&pi); - let labels: Vec<[u8; 16]> = (0..L) + let labels: Vec<[u8; 16]> = (0..U_BAR_SIZE) .flat_map(|_| { let l0 = S::random(); let l1 = l0 ^ NON_CAC_DELTA; @@ -161,9 +160,9 @@ mod tests { if !Zero::is_zero(&v) { break v; } }) .collect(); - let table = SparseAdaptorTable::build_from_r_and_labels(r, &labels, &rhos, &fq_deltas); + let table = SparseAdaptorTable::build_from_r_and_u_bar_labels(r, &labels, &rhos, &fq_deltas); - let eval_labels: Vec<[u8; 16]> = (0..L) + let eval_labels: Vec<[u8; 16]> = (0..U_BAR_SIZE) .map(|k| if u_bar_pi[k].is_zero() { labels[2 * k] } else { labels[2 * k + 1] }) .collect(); diff --git a/verifiable-circuit-babe/src/gc/circuit.rs b/verifiable-circuit-babe/src/gc/circuit.rs index 71ecbb2..3f49218 100644 --- a/verifiable-circuit-babe/src/gc/circuit.rs +++ b/verifiable-circuit-babe/src/gc/circuit.rs @@ -8,7 +8,7 @@ use garbled_snark_verifier::dv_bn254::fp254impl::Fp254Impl; use garbled_snark_verifier::dv_bn254::{fq::Fq, fr::Fr}; use garbled_snark_verifier::dv_bn254::g1::G1Projective as GcG1Projective; -use crate::dre::{L, N, U_BAR_SIZE}; +use crate::dre::{N, Q_SIZE, U_BAR_SIZE}; // Unsigned 8-bit windowed scalar-mul parameters. Must match the `SCALAR_WINDOW_*` // constants in garbled-snark-verifier's dv_bn254::g1. With w=8 we do 32 mixed-adds @@ -17,90 +17,55 @@ pub const WINDOW_BITS: usize = 8; pub const WINDOW_COUNT: usize = (Fr::N_BITS + WINDOW_BITS - 1) / WINDOW_BITS; // 32 pub const WINDOW_ENTRIES: usize = 1 << WINDOW_BITS; // 256 pub const PRECOMP_TABLE_BITS: usize = WINDOW_COUNT * WINDOW_ENTRIES * 2 * N; -pub const CONSTANT_SIZE: usize = 2 + 2 * N + PRECOMP_TABLE_BITS; +pub const CONSTANT_SIZE: usize = 2 + 2 * N + PRECOMP_TABLE_BITS; // 0/1 + B + L_2 table. -/// Compile the DSGC circuit structure without fixing witness values. + +// ── Circuit 1 / FGC ──────────────────────────────────────────────────────── + +/// Compile the FGC: evaluates the ū(π) subcircuit. /// -/// Input wire allocation order: -/// [0..N) pi_x — x-coordinate of the proof point π -/// [N..2·N) pi_y — y-coordinate of π -/// [2·N..2·N+Fr::N_BITS) x_d — raw scalar bits, LSB-first (evaluator input) -/// [2·N+Fr::N_BITS..3·N+Fr::N_BITS) r·B_x — x-coordinate of blinding point, Montgomery form -/// [..+N) r·B_y — y-coordinate of blinding point, Montgomery form -/// [..+PRECOMP_TABLE_BITS) unsigned w=8 table for Base, -/// WINDOW_COUNT windows × WINDOW_ENTRIES affine points -/// (entry j = j·256^i·P), Montgomery form; j=0 is infinity +/// Input wire layout: +/// [0..N) π_x — x-coordinate of π (evaluator input) +/// [N..2·N) π_y — y-coordinate of π (evaluator input) /// -pub fn compile_dsgc(g: G1Affine) -> (CircuitAdapter, Vec) { +/// `g` is the fallback point, embedded as constant wires. +/// +/// Output: U_BAR_SIZE bits — ū(π) when π is on the curve, ū(g) otherwise. +pub fn compile_fgc(g: G1Affine) -> (CircuitAdapter, Vec) { let mut bld = CircuitAdapter::default(); - // π input wires (evaluator) let pi_x = Fq::wires(&mut bld); let pi_y = Fq::wires(&mut bld); - // x_d raw scalar bits (evaluator input) - let x_d: Vec = (0..Fr::N_BITS).map(|_| bld.fresh_one()).collect(); - - // r·B and precomputed table — garbler-private - let rb_x = Fq::wires(&mut bld); - let rb_y = Fq::wires(&mut bld); - let mut table_wires = Vec::with_capacity(PRECOMP_TABLE_BITS); - for _ in 0..(WINDOW_COUNT * WINDOW_ENTRIES * 2) { - table_wires.extend(Fq::wires(&mut bld).0); - } - - let output_indices = emit_dsgc( - &mut bld, - &table_wires, - &x_d, - &pi_x.0, - &pi_y.0, - g, - &rb_x.0, - &rb_y.0, - ); + let output_indices = emit_fgc(&mut bld, &pi_x.0, &pi_y.0, g); (bld, output_indices) } -/// Number of input bits contributed by the blinding point r·B (x and y, normal form). -pub const R_B_BITS: usize = 2 * N; - -/// Output layout (total L = U_BAR_SIZE + R_PD_SIZE = 2033 bits): -/// [0..U_BAR_SIZE) ū(π) or ū(g) — 1 + 5·N bits, LSB-first -/// [U_BAR_SIZE..L) (X,Y,Z) of x_d·P_D + r·B — 3·N bits, projective Montgomery form -fn emit_dsgc( +fn emit_fgc( bld: &mut CircuitAdapter, - table_wires: &[usize], - x_d: &[usize], pi_x: &[usize], pi_y: &[usize], g: G1Affine, - rb_x_wires: &[usize], - rb_y_wires: &[usize], ) -> Vec { - assert_eq!(table_wires.len(), PRECOMP_TABLE_BITS); - - // R² mod p — multiply by this to convert normal → Montgomery form + // R² mod p — converts normal → Montgomery form let r_sq = Fq::as_montgomery(Fq::as_montgomery(ark_bn254::Fq::from(1u64))); - // ── ū(π) subcircuit ─────────────────────────────────────────────────────── - let x_m = Fq::mul_by_constant_montgomery(bld, pi_x, r_sq); let y_m = Fq::mul_by_constant_montgomery(bld, pi_y, r_sq); let x_sq_m = Fq::square_montgomery(bld, &x_m); let y_sq_m = Fq::square_montgomery(bld, &y_m); - let xy_m = Fq::mul_montgomery(bld, &x_m, &y_m); + let xy_m = Fq::mul_montgomery(bld, &x_m, &y_m); let x_cu_m = Fq::mul_montgomery(bld, &x_sq_m, &x_m); let three_mont = Fq::as_montgomery(ark_bn254::Fq::from(3u64)); - let rhs_m = Fq::add_constant(bld, &x_cu_m, three_mont); + let rhs_m = Fq::add_constant(bld, &x_cu_m, three_mont); let on_curve = Fq::equal(bld, &y_sq_m, &rhs_m); - // montgomery_reduce(A·R ‖ 0) = A — converts each back to standard form + // montgomery_reduce(A·R ‖ 0) = A — converts back to standard form let x_sq = Fq::mul_by_constant_montgomery(bld, &x_sq_m, ark_bn254::Fq::from(1u64)); let y_sq = Fq::mul_by_constant_montgomery(bld, &y_sq_m, ark_bn254::Fq::from(1u64)); - let xy = Fq::mul_by_constant_montgomery(bld, &xy_m, ark_bn254::Fq::from(1u64)); + let xy = Fq::mul_by_constant_montgomery(bld, &xy_m, ark_bn254::Fq::from(1u64)); let mut pi_u_bar: Vec = Vec::with_capacity(U_BAR_SIZE); pi_u_bar.push(bld.one()); @@ -113,33 +78,82 @@ fn emit_dsgc( let g_u_bar = g_u_bar_indices(bld, g); - let mut output_indices: Vec = (0..U_BAR_SIZE) + (0..U_BAR_SIZE) .map(|k| selector(bld, pi_u_bar[k], g_u_bar[k], on_curve)) + .collect() +} + +// ── Circuit 2 Part 1 / SGC Part 1 ────────────────────────────────────────── + +/// Compile SGC Part 1: computes Q = x_d · L_2 + B. +/// +/// `l2` is fixed (like `g` in FGC) and is embedded as constant wires. +/// B is garbler-private and supplied as input wires. +/// +/// Input wire layout: +/// [0..Fr::N_BITS) x_d — scalar bits, LSB-first (evaluator input) +/// [Fr::N_BITS..Fr::N_BITS+N) B_x — x-coordinate of B, Montgomery form (garbler-private) +/// [Fr::N_BITS+N..Fr::N_BITS+2·N) B_y — y-coordinate of B, Montgomery form (garbler-private) +/// +/// Output: Q_SIZE = 3·N bits — (X, Y, Z) of Q in projective Montgomery form. +pub fn compile_sgc_part1(l2: G1Affine) -> (CircuitAdapter, Vec) { + let mut bld = CircuitAdapter::default(); + + // x_d — evaluator input + let x_d: Vec = (0..Fr::N_BITS).map(|_| bld.fresh_one()).collect(); + + // B — garbler-private affine point, Montgomery form + let b_x = Fq::wires(&mut bld); + let b_y = Fq::wires(&mut bld); + + // L_2 table — embedded as constant wires, same pattern as g in compile_fgc + let table_wires: Vec = build_l2_table_bits(&l2) + .into_iter() + .map(|bit| if bit { bld.one() } else { bld.zero() }) .collect(); + assert_eq!(table_wires.len(), PRECOMP_TABLE_BITS); - // ── x_d · Base subcircuit (unsigned 8-bit windowed private table) ───────── - let prod_proj_m = - GcG1Projective::scalar_mul_private_table_circuit(bld, x_d, table_wires); + let output_indices = emit_scalar_mul_then_add(&mut bld, &x_d, &b_x.0, &b_y.0, &table_wires); + (bld, output_indices) +} - // ── Blinding: add r·B (garbler-private affine, already in Montgomery form) ─ - let mut rb_affine_m: Vec = rb_x_wires.to_vec(); - rb_affine_m.extend_from_slice(rb_y_wires); - let blinded_proj_m = - GcG1Projective::add_mixed_montgomery_no_inf(bld, &prod_proj_m, &rb_affine_m); +// ── Shared inner ──────────────────────────────────────────────────────────── - // Output stays in Montgomery form (X·R, Y·R, Z·R). - output_indices.extend_from_slice(&blinded_proj_m[..N]); - output_indices.extend_from_slice(&blinded_proj_m[N..2 * N]); - output_indices.extend_from_slice(&blinded_proj_m[2 * N..]); - assert_eq!(output_indices.len(), L); +/// Emit: scalar_point = x_d · L_2 (windowed private table) then add affine point P. +/// +/// Wire indices for the affine point and table may be input or constant wires. +/// Output: R_PD_SIZE bits — (X, Y, Z) in projective Montgomery form. +fn emit_scalar_mul_then_add( + bld: &mut CircuitAdapter, + x_d: &[usize], + p_x_wires: &[usize], + p_y_wires: &[usize], + table_wires: &[usize], +) -> Vec { + assert_eq!(table_wires.len(), PRECOMP_TABLE_BITS); + + let prod_proj_m = GcG1Projective::scalar_mul_private_table_circuit(bld, x_d, table_wires); + + let mut p_affine_m: Vec = p_x_wires.to_vec(); + p_affine_m.extend_from_slice(p_y_wires); + let result_proj_m = + GcG1Projective::add_mixed_montgomery_no_inf(bld, &prod_proj_m, &p_affine_m); - output_indices + // Output stays in Montgomery form (X·R, Y·R, Z·R) + let mut output = Vec::with_capacity(Q_SIZE); + output.extend_from_slice(&result_proj_m[..N]); + output.extend_from_slice(&result_proj_m[N..2 * N]); + output.extend_from_slice(&result_proj_m[2 * N..]); + assert_eq!(output.len(), Q_SIZE); + output } -/// Build constant wire indices for ū(g) +// ── Shared helpers ────────────────────────────────────────────────────────── + +/// Build constant wire indices for ū(g). fn g_u_bar_indices(bld: &mut CircuitAdapter, g: G1Affine) -> Vec { - let x = g.x; - let y = g.y; + let x = g.x; + let y = g.y; let x_sq = x * x; let y_sq = y * y; let xy = x * y; @@ -163,7 +177,7 @@ pub fn gc_ciphertexts_commit(ciphertexts: &[Option hasher.update([0u8]), + None => hasher.update([0u8]), Some(s) => { hasher.update([1u8]); hasher.update(s.0); } } } @@ -174,7 +188,7 @@ pub fn gc_ciphertexts_commit(ciphertexts: &[Option Vec { +pub fn build_l2_table_bits(base: &G1Affine) -> Vec { let mut bits = Vec::with_capacity(PRECOMP_TABLE_BITS); let mut window_base = ark_bn254::G1Projective::from(base.clone()); @@ -196,262 +210,139 @@ pub fn build_base_table_bits(base: &G1Affine) -> Vec { #[cfg(test)] mod tests { - use std::time::Instant; use super::*; use ark_ec::CurveGroup; - use ark_ff::{AdditiveGroup, UniformRand, Zero}; - use garbled_snark_verifier::bag::S; + use ark_ff::UniformRand; use garbled_snark_verifier::core::utils::reset_gid; - use crate::dre::matrices::u_bar_vec; - use crate::dre::R_PD_SIZE; + use crate::dre::Q_SIZE; fn random_g1_affine() -> G1Affine { let mut rng = rand::thread_rng(); ark_bn254::G1Projective::rand(&mut rng).into_affine() } - /// Build a full witness: pi_x, pi_y, x_d raw bits, r·B (Montgomery), precomputed table. - fn build_witness(pi: &G1Affine, base: &G1Affine, x_d: ark_bn254::Fr, r_b: &G1Affine) -> Vec { - Fq::to_bits(pi.x).into_iter() + // ── FGC witness / tests ───────────────────────────────────────────────── + + /// Witness for compile_fgc: only π_x and π_y. + fn fgc_witness(pi: &G1Affine) -> Vec { + Fq::to_bits(pi.x) + .into_iter() .chain(Fq::to_bits(pi.y)) - .chain(Fr::to_bits(x_d)) - .chain(Fq::to_bits(Fq::as_montgomery(r_b.x))) - .chain(Fq::to_bits(Fq::as_montgomery(r_b.y))) - .chain(build_base_table_bits(base)) .collect() } - #[test] - fn test_babe_gc_on_curve() { - let pi = random_g1_affine(); - let g = random_g1_affine(); - let base = random_g1_affine(); - let r_b = random_g1_affine(); - let x_d = ark_bn254::Fr::from(1u64); - - let witness = build_witness(&pi, &base, x_d, &r_b); - + fn eval_fgc(pi: &G1Affine, g: G1Affine) -> Vec { + let witness = fgc_witness(pi); reset_gid(); - let (bld, output_indices) = compile_dsgc(g); + let (bld, output_indices) = compile_fgc(g); + assert_eq!(output_indices.len(), U_BAR_SIZE); let mut circuit = bld.build(&witness); - circuit.gate_counts().print(); - - for gate in &mut circuit.1 { - gate.evaluate(); - } - - let output: Vec = output_indices - .iter() - .map(|&i| circuit.0[i].borrow().get_value()) - .collect(); - - assert_eq!(output.len(), L); - - // u₀ = 1 - assert!(output[0]); - - let x_bits = Fq::to_bits(pi.x); - for k in 0..N { - assert_eq!(output[1 + k], x_bits[k], "x bit {k} mismatch"); - } - - let y_bits = Fq::to_bits(pi.y); - for k in 0..N { - assert_eq!(output[1 + N + k], y_bits[k], "y bit {k} mismatch"); - } - - let x_sq_bits = Fq::to_bits(pi.x * pi.x); - for k in 0..N { - assert_eq!(output[1 + 2 * N + k], x_sq_bits[k], "x² bit {k} mismatch"); - } - - let y_sq_bits = Fq::to_bits(pi.y * pi.y); - for k in 0..N { - assert_eq!(output[1 + 3 * N + k], y_sq_bits[k], "y² bit {k} mismatch"); - } + for gate in &mut circuit.1 { gate.evaluate(); } + output_indices.iter().map(|&i| circuit.0[i].borrow().get_value()).collect() + } - let xy_bits = Fq::to_bits(pi.x * pi.y); - for k in 0..N { - assert_eq!(output[1 + 4 * N + k], xy_bits[k], "xy bit {k} mismatch"); + #[test] + fn test_fgc_on_curve() { + let pi = random_g1_affine(); + let g = random_g1_affine(); + let output = eval_fgc(&pi, g); + + assert_eq!(output.len(), U_BAR_SIZE); + assert!(output[0], "u₀ must be 1"); + + for (k, (chunk, expected_bits)) in output[1..].chunks(N).zip([ + Fq::to_bits(pi.x), + Fq::to_bits(pi.y), + Fq::to_bits(pi.x * pi.x), + Fq::to_bits(pi.y * pi.y), + Fq::to_bits(pi.x * pi.y), + ]).enumerate() { + for (j, (&got, &exp)) in chunk.iter().zip(expected_bits.iter()).enumerate() { + assert_eq!(got, exp, "field element {k} bit {j} mismatch"); + } } } #[test] - fn test_babe_gc_off_curve_falls_back_to_g() { + fn test_fgc_off_curve_falls_back_to_g() { let pi = random_g1_affine(); let g = random_g1_affine(); - let base = random_g1_affine(); - let r_b = random_g1_affine(); - let x_d = ark_bn254::Fr::from(1u64); - let bad_y = pi.y + ark_bn254::Fq::from(1u64); let mut off_pi = pi; - off_pi.y = bad_y; - let witness = build_witness(&off_pi, &base, x_d, &r_b); + off_pi.y += ark_bn254::Fq::from(1u64); + let output = eval_fgc(&off_pi, g); - reset_gid(); - let (bld, output_indices) = compile_dsgc(g); - let mut circuit = bld.build(&witness); - for gate in &mut circuit.1 { - gate.evaluate(); - } - - let output: Vec = output_indices - .iter() - .map(|&i| circuit.0[i].borrow().get_value()) - .collect(); - assert_eq!(output.len(), L); - - assert!(output[0]); + assert_eq!(output.len(), U_BAR_SIZE); + assert!(output[0], "u₀ must be 1"); let gx_bits = Fq::to_bits(g.x); for k in 0..N { assert_eq!(output[1 + k], gx_bits[k], "g.x bit {k} mismatch"); } - let gy_bits = Fq::to_bits(g.y); for k in 0..N { assert_eq!(output[1 + N + k], gy_bits[k], "g.y bit {k} mismatch"); } } - /// Plain (non-garbled) evaluation: verify x_d · P_D + r·B projective output. - #[test] - fn test_babe_gc_xd_base_output() { - let mut rng = rand::thread_rng(); - let g = random_g1_affine(); - let base = random_g1_affine(); - let r_b = random_g1_affine(); - let pi = random_g1_affine(); - let x_d = ark_bn254::Fr::rand(&mut rng); + // ── SGC Part 1 witness / tests ────────────────────────────────────────── - let witness = build_witness(&pi, &base, x_d, &r_b); + /// Witness for compile_sgc_part1: x_d | B_x (Montgomery) | B_y (Montgomery). + /// L_2 table is baked into the circuit as constants, so it is not part of the witness. + fn sgc_part1_witness(x_d: ark_bn254::Fr, b: &G1Affine) -> Vec { + Fr::to_bits(x_d) + .into_iter() + .chain(Fq::to_bits(Fq::as_montgomery(b.x))) + .chain(Fq::to_bits(Fq::as_montgomery(b.y))) + .collect() + } + fn eval_sgc_part1(l2: G1Affine, b: G1Affine, x_d: ark_bn254::Fr) -> ark_bn254::G1Affine { + let witness = sgc_part1_witness(x_d, &b); reset_gid(); - let (bld, output_indices) = compile_dsgc(g); + let (bld, output_indices) = compile_sgc_part1(l2); + assert_eq!(output_indices.len(), Q_SIZE); + let mut circuit = bld.build(&witness); - for gate in &mut circuit.1 { - gate.evaluate(); - } + circuit.gate_counts().print(); + for gate in &mut circuit.1 { gate.evaluate(); } - let output: Vec = output_indices + let bits: Vec = output_indices .iter() .map(|&i| circuit.0[i].borrow().get_value()) .collect(); - assert_eq!(output.len(), L); - - // Reconstruct projective point (X,Y,Z) in normal form from R_PD bits - let x = Fq::from_montgomery(Fq::from_bits(output[U_BAR_SIZE..U_BAR_SIZE + N].to_vec())); - let y = Fq::from_montgomery(Fq::from_bits(output[U_BAR_SIZE + N..U_BAR_SIZE + 2 * N].to_vec())); - let z = Fq::from_montgomery(Fq::from_bits(output[U_BAR_SIZE + 2 * N..L].to_vec())); - - let result_proj = ark_bn254::G1Projective::new(x, y, z); - let expected_affine = (ark_bn254::G1Projective::from(base) * x_d - + ark_bn254::G1Projective::from(r_b)) - .into_affine(); - - assert_eq!( - result_proj.into_affine(), expected_affine, - "x_d · P_D + r·B projective output represents wrong affine point" - ); + let x = Fq::from_montgomery(Fq::from_bits(bits[..N].to_vec())); + let y = Fq::from_montgomery(Fq::from_bits(bits[N..2 * N].to_vec())); + let z = Fq::from_montgomery(Fq::from_bits(bits[2 * N..].to_vec())); + ark_bn254::G1Projective::new(x, y, z).into_affine() } - /// Full garbled-circuit e2e test: - /// 1. Generate circuit. - /// 2. Generate a fresh set of random wire labels (encoding keys). - /// 3. Derive input labels from concrete input values. - /// 4. Evaluate garbled circuit from those labels. - /// 5. Verify output labels are correct for both ū(π) and x_d·P_D. - #[cfg(feature = "garbled")] #[test] - fn test_babe_gc_garbled_e2e() { + fn test_sgc_part1_random_xd() { let mut rng = rand::thread_rng(); - let g = random_g1_affine(); - let base = random_g1_affine(); - let r_b = random_g1_affine(); - let pi = random_g1_affine(); + let l2 = random_g1_affine(); + let b = random_g1_affine(); let x_d = ark_bn254::Fr::rand(&mut rng); - // 1. Generate circuit - let now = Instant::now(); - reset_gid(); - let (bld, output_indices) = compile_dsgc(g); - let mut circuit = bld.build(&[]); - println!("circuit generate time: {:?}", now.elapsed()); - - // 2. Generate a new set of labels — one label0 per input wire. - // Input wires: 2·N (π) + Fr::N_BITS (x_d) + R_B_BITS (r·B) + PRECOMP_TABLE_BITS (table) - let now = Instant::now(); - let total_input_wires = 2 * N + Fr::N_BITS + R_B_BITS + PRECOMP_TABLE_BITS; - let encoding_keys: Vec = (0..total_input_wires).map(|_| S::random()).collect(); - for (i, &key) in encoding_keys.iter().enumerate() { - circuit.0[2 + i].borrow_mut().label = Some(key); - } - println!("encoding time: {:?}", now.elapsed()); - - // 3. Derive input label values from concrete inputs - let witness = build_witness(&pi, &base, x_d, &r_b); + let got = eval_sgc_part1(l2, b, x_d); + let expected = (ark_bn254::G1Projective::from(l2) * x_d + + ark_bn254::G1Projective::from(b)) + .into_affine(); - // 4. Evaluate garbled circuit - let now = Instant::now(); - circuit.set_witness_value(&witness); - for gate in &mut circuit.1 { - gate.evaluate(); - } - println!("evaluation time: {:?}", now.elapsed()); - let now = Instant::now(); - let ciphertext = circuit.garbled_gates(); - println!("garbled gates time: {:?}", now.elapsed()); + assert_eq!(got, expected, "Q = x_d · L₂ + B affine mismatch"); + } - let now = Instant::now(); - let _ = circuit.garbled_evaluate(&ciphertext); - println!("garbled evaluate time: {:?}", now.elapsed()); + #[test] + fn test_sgc_part1_xd_one() { + let l2 = random_g1_affine(); + let b = random_g1_affine(); - // Collect all output labels - let output_labels: Vec = output_indices - .iter() - .map(|&i| { - let w = &circuit.0[i]; - w.borrow().select(w.borrow().get_value()) - }) - .collect(); - assert_eq!(output_labels.len(), L); - - // 5a. Verify ū(π) labels match u_bar_vec(pi) - let u_bar = u_bar_vec(&pi); - assert_eq!(u_bar.len(), U_BAR_SIZE); - for k in 0..U_BAR_SIZE { - let expected_bit = !u_bar[k].is_zero(); - let expected_label = circuit.0[output_indices[k]].borrow().select(expected_bit); - assert_eq!(output_labels[k], expected_label, "ū label mismatch at k={k}"); - } + let got = eval_sgc_part1(l2, b, ark_bn254::Fr::from(1u64)); + let expected = (ark_bn254::G1Projective::from(l2) + + ark_bn254::G1Projective::from(b)) + .into_affine(); - // 5b. Verify x_d·Base output labels - // Get the actual output bits, verify they represent the correct point, - // then confirm the labels encode exactly those bits. - let rpd_bits: Vec = output_indices[U_BAR_SIZE..] - .iter() - .map(|&i| circuit.0[i].borrow().get_value()) - .collect(); - assert_eq!(rpd_bits.len(), R_PD_SIZE); - - let x = Fq::from_montgomery(Fq::from_bits(rpd_bits[..N].to_vec())); - let y = Fq::from_montgomery(Fq::from_bits(rpd_bits[N..2 * N].to_vec())); - let z = Fq::from_montgomery(Fq::from_bits(rpd_bits[2 * N..].to_vec())); - - let result_proj = ark_bn254::G1Projective::new(x, y, z); - let expected_affine = ark_bn254::G1Projective::from(base) * x_d - + ark_bn254::G1Projective::from(r_b); - assert_eq!( - result_proj.into_affine(), expected_affine, - "x_d · Base projective output represents wrong affine point" - ); - - for k in 0..R_PD_SIZE { - let bit = rpd_bits[k]; - let expected_label = circuit.0[output_indices[U_BAR_SIZE + k]].borrow().select(bit); - assert_eq!(output_labels[U_BAR_SIZE + k], expected_label, "R_PD label mismatch at k={k}"); - } + assert_eq!(got, expected, "Q = 1·L₂ + B should equal L₂ + B"); } -} +} \ No newline at end of file diff --git a/verifiable-circuit-babe/src/gc/mod.rs b/verifiable-circuit-babe/src/gc/mod.rs index 6308938..a09a267 100644 --- a/verifiable-circuit-babe/src/gc/mod.rs +++ b/verifiable-circuit-babe/src/gc/mod.rs @@ -64,7 +64,7 @@ pub fn read_fresh_circuit() -> (Circuit, Vec) { pub fn generate_and_write_fresh_circuit() { reset_gid(); let g = G1Affine::generator(); - let (bld, output_indices) = compile_dsgc(g); + let (bld, output_indices) = compile_fgc(g); let circuit = bld.build(&[]); // --- Serialize --- @@ -91,7 +91,7 @@ mod tests { use ark_bn254::G1Affine; use ark_ec::AffineRepr; use garbled_snark_verifier::core::utils::reset_gid; - use super::{compile_dsgc, generate_and_write_fresh_circuit}; + use super::{compile_fgc, generate_and_write_fresh_circuit}; #[test] #[ignore] @@ -100,7 +100,7 @@ mod tests { reset_gid(); let g = G1Affine::generator(); - let (bld, output_indices) = compile_dsgc(g); + let (bld, output_indices) = compile_fgc(g); let circuit = bld.build(&[]); let num_wires = circuit.0.len() as u32; diff --git a/verifiable-circuit-babe/src/instance/mod.rs b/verifiable-circuit-babe/src/instance/mod.rs index 6548165..fa1fe58 100644 --- a/verifiable-circuit-babe/src/instance/mod.rs +++ b/verifiable-circuit-babe/src/instance/mod.rs @@ -4,7 +4,7 @@ use ark_ec::pairing::Pairing; use ark_ff::UniformRand; use garbled_snark_verifier::bag::{Circuit, S}; use crate::babe::WeKnownPi1SetupCt; -use crate::gc::{build_base_table_bits, SparseAdaptorTable, CONSTANT_SIZE}; +use crate::gc::{build_l2_table_bits, SparseAdaptorTable, CONSTANT_SIZE}; use crate::instance::secret::InstanceSecrets; use ark_groth16::VerifyingKey as Groth16VerifyingKey; use ark_serialize::CanonicalSerialize; @@ -53,7 +53,7 @@ impl BABEInstance { // Compute r·L_i = r * sum(gamma_abc[num_static+1..]) and build the precomputed table. let base = vk.gamma_abc_g1[2] * secrets.r; - let table_bits = build_base_table_bits(&base.into_affine()); + let table_bits = build_l2_table_bits(&base.into_affine()); // Compute r·B bit representation (Montgomery form) for use as constant wires. let rb_x_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(secrets.r_b.x)); @@ -111,7 +111,7 @@ impl BABEInstance { }) .collect(); - let adaptor_table = SparseAdaptorTable::build_from_r_and_labels( + let adaptor_table = SparseAdaptorTable::build_from_r_and_u_bar_labels( secrets.r, &output_labels, &secrets.rhos, From 0459e176296560e67b78901924cfaaf170370665 Mon Sep 17 00:00:00 2001 From: vanhger Date: Mon, 27 Apr 2026 16:09:18 +0700 Subject: [PATCH 12/32] feat: implement enc/dec. --- garbled-snark-verifier/src/core/circuit.rs | 4 +- verifiable-circuit-babe/src/babe.rs | 13 +- verifiable-circuit-babe/src/cac.rs | 67 +++-- verifiable-circuit-babe/src/gc/circuit.rs | 2 +- verifiable-circuit-babe/src/gc/mod.rs | 99 +++++-- .../src/instance/commit.rs | 41 ++- verifiable-circuit-babe/src/instance/mod.rs | 248 ++++++++++++------ .../src/instance/secret.rs | 98 +++---- verifiable-circuit-babe/src/prover.rs | 32 ++- verifiable-circuit-babe/src/soldering.rs | 16 +- verifiable-circuit-babe/src/verifier.rs | 28 +- 11 files changed, 425 insertions(+), 223 deletions(-) diff --git a/garbled-snark-verifier/src/core/circuit.rs b/garbled-snark-verifier/src/core/circuit.rs index 1558a76..5a2b05b 100644 --- a/garbled-snark-verifier/src/core/circuit.rs +++ b/garbled-snark-verifier/src/core/circuit.rs @@ -109,10 +109,10 @@ impl Circuit { } } - pub fn set_witness_value(&mut self, witness: &[bool]) { + pub fn set_witness_value(&mut self, witness: &[bool], skip: usize) { // Gate wires are Rc> sharing the same data as self.0, so this covers all. witness.iter() - .zip(self.0.iter().skip(2)) + .zip(self.0.iter().skip(skip)) .for_each(|(bit, wirex)| wirex.borrow_mut().set_value_for_uninitialized(*bit)); } diff --git a/verifiable-circuit-babe/src/babe.rs b/verifiable-circuit-babe/src/babe.rs index 632e84a..56db91b 100644 --- a/verifiable-circuit-babe/src/babe.rs +++ b/verifiable-circuit-babe/src/babe.rs @@ -70,14 +70,16 @@ pub enum BabeBtcSig { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct EncodingKeyPublic(pub Vec<[[u8; 20]; 2]>); -pub fn compute_epk_with_delta(encoding_keys: &[S], delta: S) -> EncodingKeyPublic { - let pairs = encoding_keys.iter().map(|&key| [derive_hashlock(&key.0), derive_hashlock(&(key ^ delta).0)]).collect(); +pub fn compute_epk_with_delta(encoding_keys: &[Vec; 2], delta: &[S; 2]) -> EncodingKeyPublic { + let fgc_pairs: Vec<[[u8; 20]; 2]> = encoding_keys[0].iter().map(|&key| [derive_hashlock(&key.0), derive_hashlock(&(key ^ delta[0]).0)]).collect(); + let sgc_pairs: Vec<[[u8; 20]; 2]> = encoding_keys[1].iter().map(|&key| [derive_hashlock(&key.0), derive_hashlock(&(key ^ delta[1]).0)]).collect(); + let pairs: Vec<[[u8; 20]; 2]> = fgc_pairs.into_iter().chain(sgc_pairs).collect(); EncodingKeyPublic(pairs) } -pub fn compute_epk(encoding_keys: &[S]) -> EncodingKeyPublic { +pub fn compute_epk(encoding_keys: &[Vec; 2]) -> EncodingKeyPublic { use garbled_snark_verifier::core::utils::NON_CAC_DELTA; - compute_epk_with_delta(encoding_keys, NON_CAC_DELTA) + compute_epk_with_delta(encoding_keys, &[NON_CAC_DELTA; 2]) } // ─── Presig structs ─────────────────────────────────────────────────────────── @@ -280,7 +282,8 @@ pub fn babe_verifier_challenge_assert_cac( // Derive labels from the base instance (finalized_indices[0]). let base_idx = verifier_state.finalized_indices[0]; let base_inst = &verifier_state.verifier.instances[base_idx]; - let input_labels = base_inst.compute_input_labels_based_on_value(pi1, x_d); + // Todo: fix this (use x_d) + let input_labels = base_inst.compute_pi1_labels_based_on_value(pi1); // all_labels[0..2] are constant-wire labels; [2..] are π₁ input labels. let input_labels: Vec<[u8; 16]> = input_labels.iter().map(|s| s.0).collect(); diff --git a/verifiable-circuit-babe/src/cac.rs b/verifiable-circuit-babe/src/cac.rs index e1ba65a..5ca73d5 100644 --- a/verifiable-circuit-babe/src/cac.rs +++ b/verifiable-circuit-babe/src/cac.rs @@ -1,4 +1,5 @@ -use ark_bn254::Fr; +use ark_bn254::{Fr, G1Affine}; +use ark_ec::AffineRepr; use ark_groth16::VerifyingKey as Groth16VerifyingKey; use crate::babe::WeKnownPi1SetupCt; use crate::instance::commit::CACInstanceCommit; @@ -8,7 +9,8 @@ use rand::Rng; use rand_chacha::ChaCha12Rng; use rand::SeedableRng; use sha2::{Digest, Sha256}; -use crate::instance::BABEInstance; +use garbled_snark_verifier::dv_bn254::fq::Fq; +use crate::instance::CACInstance; use crate::utils::h_256; /// What the Verifier sends to the Prover during the C&C commit phase. @@ -21,20 +23,28 @@ pub fn cac_finalize_indices(package: &CACSetupPackage, m_cc: usize) -> Vec Vec>, - pub adaptor_table: SparseAdaptorTable, + pub gc_ciphertexts: [Vec>; 3], + pub adaptor_tables: [SparseAdaptorTable; 2], pub ct_setup: WeKnownPi1SetupCt, /// [0-label of wire-0 (constant false), 1-label of wire-1 (constant true)]. - pub constant_labels: [S; 2], + pub constant_labels_0: [S; 2], + /// value-based labels + pub constant_labels_1: [S; 510], + pub b: G1Affine, } + pub fn verify_opened_instances( package: &CACSetupPackage, opened: &[(usize, u64)], @@ -70,7 +84,7 @@ pub fn verify_opened_instances( opened .par_iter() .map(|&(idx, seed)| { - let inst = BABEInstance::new_from_seed( + let inst = CACInstance::new_from_seed( seed, vk, static_public_inputs, @@ -82,7 +96,8 @@ pub fn verify_opened_instances( if recomputed.epk != committed.epk { return Err(format!("instance {idx}: input_commits mismatch")); } - if recomputed.constant_commits != committed.constant_commits { + if recomputed.constant_commits_0 != committed.constant_commits_0 + || recomputed.constant_commits_1 != committed.constant_commits_1 { return Err(format!("instance {idx}: constant_commits mismatch")); } if recomputed.h_msg != committed.h_msg { @@ -113,16 +128,38 @@ pub fn verify_finalized_instances( let idx = data.index; let committed = &package.commits[idx]; - if gc_ciphertexts_commit(&data.gc_ciphertexts) != committed.com_gc { - return Err(format!("instance {idx}: gc_ciphertexts do not match com_gc")); + + for i in 0..3 { + if gc_ciphertexts_commit(&data.gc_ciphertexts[i]) != committed.com_gc[i] { + return Err(format!("instance {idx}: gc_ciphertexts do not match com_gc")); + } } - if data.adaptor_table.commit() != committed.com_adaptor { + + if data.adaptor_tables[0].commit() != committed.com_adaptor[0] + || data.adaptor_tables[1].commit() != committed.com_adaptor[1] { return Err(format!("instance {idx}: adaptor_table does not match com_adaptor")); } - if h_256(&data.constant_labels[0].0) != committed.constant_commits[0][0] || - h_256(&data.constant_labels[1].0) != committed.constant_commits[1][1] { + + // verify constant labels + if h_256(&data.constant_labels_0[0].0) != committed.constant_commits_0[0][0] || + h_256(&data.constant_labels_0[1].0) != committed.constant_commits_0[1][1] || + h_256(&data.constant_labels_1[0].0) != committed.constant_commits_1[0][0] || + h_256(&data.constant_labels_1[1].0) != committed.constant_commits_1[1][1] { return Err(format!("instance {idx}: constant_commits do not match")); } + let x_bits = Fq::to_bits(Fq::as_montgomery(data.b.x)); + let y_bits = Fq::to_bits(Fq::as_montgomery(data.b.y)); + for (i, bit) in x_bits.iter().enumerate() { + if h_256(&data.constant_labels_1[i + 2].0) != committed.constant_commits_1[i + 2][*bit as usize] { + return Err(format!("instance {idx}: constant_commits do not match")); + } + } + for (i, bit) in y_bits.iter().enumerate() { + if h_256(&data.constant_labels_1[i + 2 + 254].0) != committed.constant_commits_1[i + 2 + 254][*bit as usize] { + return Err(format!("instance {idx}: constant_commits do not match")); + } + } + let mut ct_bytes = Vec::new(); ct_bytes.extend_from_slice(&data.ct_setup.ct2_r_delta_g2); ct_bytes.extend_from_slice(&data.ct_setup.ct3_masked_msg); diff --git a/verifiable-circuit-babe/src/gc/circuit.rs b/verifiable-circuit-babe/src/gc/circuit.rs index 3f49218..3451636 100644 --- a/verifiable-circuit-babe/src/gc/circuit.rs +++ b/verifiable-circuit-babe/src/gc/circuit.rs @@ -17,7 +17,7 @@ pub const WINDOW_BITS: usize = 8; pub const WINDOW_COUNT: usize = (Fr::N_BITS + WINDOW_BITS - 1) / WINDOW_BITS; // 32 pub const WINDOW_ENTRIES: usize = 1 << WINDOW_BITS; // 256 pub const PRECOMP_TABLE_BITS: usize = WINDOW_COUNT * WINDOW_ENTRIES * 2 * N; -pub const CONSTANT_SIZE: usize = 2 + 2 * N + PRECOMP_TABLE_BITS; // 0/1 + B + L_2 table. +pub const SGC_PART1_CONSTANT_SIZE: usize = 2 + 2 * N; // 0/1 + B. // ── Circuit 1 / FGC ──────────────────────────────────────────────────────── diff --git a/verifiable-circuit-babe/src/gc/mod.rs b/verifiable-circuit-babe/src/gc/mod.rs index a09a267..0a51b6f 100644 --- a/verifiable-circuit-babe/src/gc/mod.rs +++ b/verifiable-circuit-babe/src/gc/mod.rs @@ -15,21 +15,41 @@ use garbled_snark_verifier::core::gate::GateType; use garbled_snark_verifier::core::utils::{reset_gid, SerializableGate}; pub use utils::*; -fn gc_gates_path() -> String { - std::env::var("GC_GATES_PATH").unwrap_or_else(|_| "./dsgc_gates.bin".to_string()) +fn fgc_gates_path() -> String { + std::env::var("FGC_GATES_PATH").unwrap_or_else(|_| "./fgc_gates.bin".to_string()) } -fn gc_indices_path() -> String { - std::env::var("GC_OUT_INDICES_PATH").unwrap_or_else(|_| "./dsgc_out_indices.bin".to_string()) +fn fgc_indices_path() -> String { + std::env::var("FGC_OUT_INDICES_PATH").unwrap_or_else(|_| "./fgc_out_indices.bin".to_string()) +} + +fn sgc_part1_gates_path() -> String { + std::env::var("SGC_GATES_PATH").unwrap_or_else(|_| "./sgc_gates.bin".to_string()) +} + +fn sgc_part1_indices_path() -> String { + std::env::var("SGC_OUT_INDICES_PATH").unwrap_or_else(|_| "./sgc_out_indices.bin".to_string()) } /// Raw circuit bytes cached on first read. -static CIRCUIT_BYTES: OnceLock<(Vec, Vec)> = OnceLock::new(); +static CIRCUIT_1_BYTES: OnceLock<(Vec, Vec)> = OnceLock::new(); +static CIRCUIT_2_BYTES: OnceLock<(Vec, Vec)> = OnceLock::new(); + +pub fn read_fresh_gc() -> (Circuit, Vec, Circuit, Vec) { + let (fgc_bytes, fgc_indices_bytes) = CIRCUIT_1_BYTES.get_or_init(|| { + let gates_path = fgc_gates_path(); + let indices_path = fgc_indices_path(); + let g = fs::read(&gates_path) + .unwrap_or_else(|_| panic!("'{}' not found — run function generate_and_write_fresh_circuit() to generate it.", gates_path)); + let i = fs::read(&indices_path) + .unwrap_or_else(|_| panic!("'{}' not found — run function generate_and_write_fresh_circuit() to generate it.", indices_path)); + (g, i) + }); + let (fgc, fgc_indices) = deserialize_circuit(fgc_bytes, fgc_indices_bytes); -pub fn read_fresh_circuit() -> (Circuit, Vec) { - let (gates_bytes, indices_bytes) = CIRCUIT_BYTES.get_or_init(|| { - let gates_path = gc_gates_path(); - let indices_path = gc_indices_path(); + let (sgc_bytes, sgc_indices_bytes) = CIRCUIT_2_BYTES.get_or_init(|| { + let gates_path = sgc_part1_gates_path(); + let indices_path = sgc_part1_indices_path(); let g = fs::read(&gates_path) .unwrap_or_else(|_| panic!("'{}' not found — run function generate_and_write_fresh_circuit() to generate it.", gates_path)); let i = fs::read(&indices_path) @@ -37,10 +57,16 @@ pub fn read_fresh_circuit() -> (Circuit, Vec) { (g, i) }); + let (sgc, sgc_indices) = deserialize_circuit(sgc_bytes, fgc_indices_bytes); + + (fgc, fgc_indices, sgc, sgc_indices) +} + +fn deserialize_circuit(gates_bytes: &Vec, output_indices_bytes: &Vec) -> (Circuit, Vec) { let (num_wires, gates_read): (u32, Vec) = bincode::deserialize(gates_bytes).expect("deserialize gates"); let output_indices: Vec = - bincode::deserialize(indices_bytes).expect("deserialize indices"); + bincode::deserialize(output_indices_bytes).expect("deserialize indices"); let wires: Vec<_> = (0..num_wires) .map(|id| Rc::new(RefCell::new(Wire { label: None, value: None, id: Some(id) }))) @@ -61,15 +87,26 @@ pub fn read_fresh_circuit() -> (Circuit, Vec) { (Circuit(wires, gates), output_indices) } -pub fn generate_and_write_fresh_circuit() { +pub fn generate_and_write_fresh_circuit(l2_point: ark_bn254::G1Affine) { reset_gid(); let g = G1Affine::generator(); - let (bld, output_indices) = compile_fgc(g); - let circuit = bld.build(&[]); + let (bld, fgc_output_indices) = compile_fgc(g); + let fgc_circuit = bld.build(&[]); + + let (bld, sgc_output_indices) = compile_sgc_part1(l2_point); + let sgc_circuit = bld.build(&[]); // --- Serialize --- + write_fresh_circuit(fgc_circuit, fgc_output_indices, fgc_gates_path(), fgc_indices_path()); + write_fresh_circuit(sgc_circuit, sgc_output_indices, sgc_part1_gates_path(), sgc_part1_indices_path()); +} - // File 1: (num_wires: u32, gates: Vec) +fn write_fresh_circuit( + circuit: Circuit, + output_indices: Vec, + gates_path: String, + output_indices_path: String, +) { let num_wires = circuit.0.len() as u32; let gates: Vec = circuit.1.iter().map(|gate| SerializableGate { gate_type: gate.gate_type as u8, @@ -79,11 +116,11 @@ pub fn generate_and_write_fresh_circuit() { gid: gate.gid, }).collect(); let gates_bytes = bincode::serialize(&(num_wires, &gates)).expect("serialize gates"); - fs::write(gc_gates_path(), &gates_bytes).expect("write gates"); + fs::write(gates_path, &gates_bytes).expect("write gates"); // File 2: Vec output indices let indices_bytes = bincode::serialize(&output_indices).expect("serialize indices"); - fs::write(gc_indices_path(), &indices_bytes).expect("write indices"); + fs::write(output_indices_path, &indices_bytes).expect("write indices"); } #[cfg(test)] @@ -91,28 +128,39 @@ mod tests { use ark_bn254::G1Affine; use ark_ec::AffineRepr; use garbled_snark_verifier::core::utils::reset_gid; - use super::{compile_fgc, generate_and_write_fresh_circuit}; + use super::{compile_fgc, compile_sgc_part1, generate_and_write_fresh_circuit}; #[test] #[ignore] fn test_babe_gc_serialize_roundtrip() { - generate_and_write_fresh_circuit(); + let l2_point: G1Affine = G1Affine::generator(); + generate_and_write_fresh_circuit(l2_point); reset_gid(); let g = G1Affine::generator(); - let (bld, output_indices) = compile_fgc(g); - let circuit = bld.build(&[]); - let num_wires = circuit.0.len() as u32; + let (bld, fgc_output_indices) = compile_fgc(g); + let f_circuit = bld.build(&[]); + let (bld, sgc_output_indices) = compile_sgc_part1(l2_point); + let s_circuit = bld.build(&[]); // --- Reconstruct Circuit --- - let (circuit_reconstructed, _) = super::read_fresh_circuit(); + let (fgc, fgc_indices, sgc, sgc_indices) = super::read_fresh_gc(); + assert_eq!(fgc.0.len(), f_circuit.0.len(), "reconstructed wire count mismatch"); + assert_eq!(sgc.0.len(), s_circuit.0.len(), "reconstructed wire count mismatch"); + assert_eq!(fgc.1.len(), f_circuit.1.len(), "reconstructed gate count mismatch"); + assert_eq!(sgc.1.len(), s_circuit.1.len(), "reconstructed gate count mismatch"); - assert_eq!(circuit_reconstructed.0.len(), circuit.0.len(), "reconstructed wire count mismatch"); - assert_eq!(circuit_reconstructed.1.len(), circuit.1.len(), "reconstructed gate count mismatch"); + for (i, (orig, rec)) in f_circuit.1.iter().zip(fgc.1.iter()).enumerate() { + assert_eq!(orig.gate_type, rec.gate_type, "reconstructed gate[{i}] type mismatch"); + assert_eq!(orig.wire_a.borrow().id, rec.wire_a.borrow().id, "reconstructed gate[{i}] wire_a id mismatch"); + assert_eq!(orig.wire_b.borrow().id, rec.wire_b.borrow().id, "reconstructed gate[{i}] wire_b id mismatch"); + assert_eq!(orig.wire_c.borrow().id, rec.wire_c.borrow().id, "reconstructed gate[{i}] wire_c id mismatch"); + assert_eq!(orig.gid, rec.gid, "reconstructed gate[{i}] gid mismatch"); + } - for (i, (orig, rec)) in circuit.1.iter().zip(circuit_reconstructed.1.iter()).enumerate() { + for (i, (orig, rec)) in s_circuit.1.iter().zip(sgc.1.iter()).enumerate() { assert_eq!(orig.gate_type, rec.gate_type, "reconstructed gate[{i}] type mismatch"); assert_eq!(orig.wire_a.borrow().id, rec.wire_a.borrow().id, "reconstructed gate[{i}] wire_a id mismatch"); assert_eq!(orig.wire_b.borrow().id, rec.wire_b.borrow().id, "reconstructed gate[{i}] wire_b id mismatch"); @@ -120,7 +168,6 @@ mod tests { assert_eq!(orig.gid, rec.gid, "reconstructed gate[{i}] gid mismatch"); } - println!("wires={num_wires}, gates={}, output_indices={}", circuit.1.len(), output_indices.len()); println!("Circuit reconstructed successfully from serialized data."); } } diff --git a/verifiable-circuit-babe/src/instance/commit.rs b/verifiable-circuit-babe/src/instance/commit.rs index e773742..ed18127 100644 --- a/verifiable-circuit-babe/src/instance/commit.rs +++ b/verifiable-circuit-babe/src/instance/commit.rs @@ -1,6 +1,6 @@ use crate::babe::compute_epk_with_delta; use crate::gc::gc_ciphertexts_commit; -use crate::instance::BABEInstance; +use crate::instance::CACInstance; use crate::utils::{derive_hashlock, h_256}; /// Per-instance commitment sent from Verifier to Prover during C&C commit phase. @@ -9,27 +9,35 @@ pub struct CACInstanceCommit { /// RIPEMD(SHA256)(label0) and RIPEMD(SHA256)(label1) for each input wire. /// We need to use RIPEMD(SHA256) to put this on skeleton Txn. pub epk: Vec<[[u8; 20]; 2]>, - /// SHA256(label0) and SHA256(label1) for each of the 2 constant wires. - pub constant_commits: [[[u8; 32]; 2]; 2], + /// SHA256(label0) and SHA256(label1) for each of the 2 constant wires in fgc. + pub constant_commits_0: [[[u8; 32]; 2]; 2], + /// constant commit for sgc, size = 2 + 2*N + pub constant_commits_1: [[[u8; 32]; 2]; 510], pub h_msg: [u8; 20], /// RO(ct_setup) = SHA256(ct2_r_delta_g2 || ct3_masked_msg). pub h_ct_setup: [u8; 32], /// SHA256 commitment to the adaptor table. - pub com_adaptor: [u8; 32], + pub com_adaptor: [[u8; 32]; 2], /// SHA256 commitment to the GC gate ciphertexts. - pub com_gc: [u8; 32], + pub com_gc: [[u8; 32]; 3], } impl CACInstanceCommit { // Todo: add x_d - pub fn from_instance(instance: &BABEInstance) -> Self { + pub fn from_instance(instance: &CACInstance) -> Self { let delta = instance.secrets.delta; - let input_commits = compute_epk_with_delta(&instance.secrets.encoding_keys, delta).0; - // todo: change this to commit only the labels. - let constant_commits = std::array::from_fn(|w| { - let l0 = instance.secrets.constant_val_labels[w]; - [h_256(&l0.0), h_256(&(l0 ^ delta).0)] + let input_commits = compute_epk_with_delta( + &instance.secrets.encoding_keys, &delta + ).0; + + let constant_commits_0 = std::array::from_fn(|w| { + let l0 = instance.secrets.constant_0labels[0][w]; + [h_256(&l0.0), h_256(&(l0 ^ delta[0]).0)] + }); + let constant_commits_1 = std::array::from_fn(|w| { + let l0 = instance.secrets.constant_0labels[1][w]; + [h_256(&l0.0), h_256(&(l0 ^ delta[1]).0)] }); let mut ct_setup_bytes = Vec::new(); @@ -38,11 +46,16 @@ impl CACInstanceCommit { CACInstanceCommit { epk: input_commits, - constant_commits, + constant_commits_0, + constant_commits_1, h_msg: derive_hashlock(&instance.secrets.msg), h_ct_setup: h_256(&ct_setup_bytes), - com_adaptor: instance.adaptor_table.commit(), - com_gc: gc_ciphertexts_commit(&instance.ciphertexts), + com_adaptor: [instance.adaptor_tables[0].commit(), instance.adaptor_tables[1].commit()], + com_gc: [ + gc_ciphertexts_commit(&instance.ciphertexts_sets[0]), + gc_ciphertexts_commit(&instance.ciphertexts_sets[1]), + gc_ciphertexts_commit(&instance.ciphertexts_sets[2]), + ] } } } diff --git a/verifiable-circuit-babe/src/instance/mod.rs b/verifiable-circuit-babe/src/instance/mod.rs index fa1fe58..5a02468 100644 --- a/verifiable-circuit-babe/src/instance/mod.rs +++ b/verifiable-circuit-babe/src/instance/mod.rs @@ -4,29 +4,30 @@ use ark_ec::pairing::Pairing; use ark_ff::UniformRand; use garbled_snark_verifier::bag::{Circuit, S}; use crate::babe::WeKnownPi1SetupCt; -use crate::gc::{build_l2_table_bits, SparseAdaptorTable, CONSTANT_SIZE}; +use crate::gc::{build_l2_table_bits, SparseAdaptorTable, SGC_PART1_CONSTANT_SIZE, WINDOW_ENTRIES}; use crate::instance::secret::InstanceSecrets; use ark_groth16::VerifyingKey as Groth16VerifyingKey; use ark_serialize::CanonicalSerialize; use garbled_snark_verifier::dv_bn254::fp254impl::Fp254Impl; use garbled_snark_verifier::dv_bn254::fq::Fq as DvFq; use garbled_snark_verifier::dv_bn254::fr::Fr as DvFr; -use crate::dre::N; +use crate::dre::{N, U_BAR_SIZE}; use crate::instance::commit::CACInstanceCommit; use crate::utils::{g2_to_ser, ro_from_pairing_bytes}; pub mod secret; pub mod commit; -pub struct BABEInstance { +pub struct CACInstance { pub seed: u64, pub secrets: InstanceSecrets, pub ct_setup: WeKnownPi1SetupCt, - pub adaptor_table: SparseAdaptorTable, - pub ciphertexts: Vec>, + pub adaptor_tables: [SparseAdaptorTable; 2], + /// for fgc, sgc part 1, sgc part 2 + pub ciphertexts_sets: [Vec>; 3] } -impl BABEInstance { +impl CACInstance { /// Construct a instance fully determined by `seed`. /// W/O ct_setup. pub fn new_from_seed( @@ -36,86 +37,99 @@ impl BABEInstance { ) -> Result { use ark_bn254::G1Projective; - if vk.gamma_abc_g1.len() != 3{ + if vk.gamma_abc_g1.len() != 3 { return Err("static/dynamic split does not match vk".to_string()); } let secrets = InstanceSecrets::new_from_seed(seed); // Load fresh circuit structure from pre-serialized files; drop after use. - let (mut circuit, gc_output_indices) = crate::gc::read_fresh_circuit(); - + let (mut fgc, fgc_indices, mut sgc, sgc_indices) = crate::gc::read_fresh_gc(); // Apply encoding keys as 0-labels for evaluator input wires (pi_x, pi_y, x_d). - for (i, &key) in secrets.encoding_keys.iter().enumerate() { - circuit.0[2 + i].borrow_mut().label = Some(key); + for (i, &key) in secrets.encoding_keys[0].iter().enumerate() { + fgc.0[2 + i].borrow_mut().label = Some(key); } - - // Compute r·L_i = r * sum(gamma_abc[num_static+1..]) and build the precomputed table. - let base = vk.gamma_abc_g1[2] * secrets.r; - let table_bits = build_l2_table_bits(&base.into_affine()); - - // Compute r·B bit representation (Montgomery form) for use as constant wires. - let rb_x_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(secrets.r_b.x)); - let rb_y_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(secrets.r_b.y)); - - // Derive 0-labels for each constant wire from val-labels and actual wire values. - // constant_val_labels[i] is the label for the actual value of constant wire i: - // if value = 0 → val_label IS the 0-label - // if value = 1 → val_label IS the 1-label, so 0-label = val_label ^ delta - let delta = secrets.delta; - let mut constants_0labels = Vec::with_capacity(CONSTANT_SIZE); - constants_0labels.push(secrets.constant_val_labels[0]); // wire 0: value = 0 - constants_0labels.push(secrets.constant_val_labels[1] ^ delta); // wire 1: value = 1 - for (k, &bit) in rb_x_bits.iter().enumerate() { - let lv = secrets.constant_val_labels[2 + k]; - constants_0labels.push(if bit { lv ^ delta } else { lv }); + for (i, &key) in secrets.encoding_keys[1].iter().enumerate() { + sgc.0[SGC_PART1_CONSTANT_SIZE + i].borrow_mut().label = Some(key); } - for (k, &bit) in rb_y_bits.iter().enumerate() { - let lv = secrets.constant_val_labels[2 + N + k]; - constants_0labels.push(if bit { lv ^ delta } else { lv }); + + // Compute B bit representation (Montgomery form) for use as constant wires. + let b_x_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(secrets.b.x)); + let b_y_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(secrets.b.y)); + // Set constant wire value for sgc part 1 + for (i, bit) in b_x_bits.iter().enumerate() { + sgc.0[2 + i].borrow_mut().value = Some(*bit); } - for (k, &bit) in table_bits.iter().enumerate() { - let lv = secrets.constant_val_labels[2 + 2 * N + k]; - constants_0labels.push(if bit { lv ^ delta } else { lv }); + for (i, bit) in b_y_bits.iter().enumerate() { + sgc.0[2 + 254 + i].borrow_mut().value = Some(*bit); } - BABEInstance::set_gc_const_labels(&mut circuit, &constants_0labels); + // set constant labels + set_gc_const_labels(&mut fgc, &secrets.constant_0labels[0]); + set_gc_const_labels(&mut sgc, &secrets.constant_0labels[1]); - // Evaluate circuit at a random pi1 and random x_d to garble. + // Evaluate circuit at a random pi1 and random x_d to garble let pi1 = G1Projective::rand(&mut rand::thread_rng()).into_affine(); let x_d = ark_bn254::Fr::rand(&mut rand::thread_rng()); - let witness: Vec = DvFq::to_bits(pi1.x) + + // Fgc + let fgc_witness: Vec = DvFq::to_bits(pi1.x) .into_iter() .chain(DvFq::to_bits(pi1.y)) - .chain(DvFr::to_bits(x_d)) - .chain(rb_x_bits) - .chain(rb_y_bits) - .chain(table_bits) .collect(); - circuit.set_witness_value(&witness); - for gate in &mut circuit.1 { - gate.evaluate(); + let (fgc_ciphertext, fgc_output_labels) = get_ciphertext_and_output_labels( + &mut fgc, + &fgc_indices, + &fgc_witness, + secrets.delta[0], + 2 + ); + assert_eq!(fgc_output_labels.len(), U_BAR_SIZE); + + // Sgc - part 1 + let sgc_part1_witness: Vec = DvFr::to_bits(x_d); + let (sgc_ciphertext_1, sgc_output_labels_1) = get_ciphertext_and_output_labels( + &mut sgc, + &sgc_indices, + &sgc_part1_witness, + secrets.delta[1], + SGC_PART1_CONSTANT_SIZE, + ); + + // Sgc - part 2 + // Reuse the fgc structure, by setting up the input & constant labels again, then evaluate. + assert_eq!(sgc_output_labels_1.len(), 2 * 2 * N); + // set label of part2 as output of part1 + for (i, &key) in sgc_output_labels_1.iter().step_by(2).enumerate() { + fgc.0[2 + i].borrow_mut().label = Some(S(key)); } - let ciphertexts = circuit.garbled_gates_with_delta(secrets.delta); - - // Recover label0 for each output wire. - let delta = secrets.delta; - let output_labels: Vec<[u8; 16]> = gc_output_indices - .iter() - .flat_map(|idx| { - let label_0 = circuit.0[*idx] - .borrow() - .select_with_delta(circuit.0[*idx].borrow().get_value(), delta); - [label_0.0, (label_0 ^ delta).0] - }) - .collect(); + // set constant for part2 + set_gc_const_labels(&mut fgc, &secrets.constant_0labels[1][0..2]); + // random eval + let (sgc_ciphertext_2, sgc_output_labels_2) = get_ciphertext_and_output_labels( + &mut fgc, + &fgc_indices, + &fgc_witness, + secrets.delta[1], + 2 + ); + assert_eq!(sgc_output_labels_2.len(), U_BAR_SIZE); + + // generate adaptor table + // fgc + let fgc_adaptor_table = SparseAdaptorTable::build_from_r_and_u_bar_labels( + secrets.r, + &fgc_output_labels, + &secrets.rhos[0], + &secrets.fq_deltas[0], + ); - let adaptor_table = SparseAdaptorTable::build_from_r_and_u_bar_labels( + let sgc_adaptor_table = SparseAdaptorTable::build_from_r_and_u_bar_labels( secrets.r, - &output_labels, - &secrets.rhos, - &secrets.fq_deltas, + &sgc_output_labels_2, + &secrets.rhos[1], + &secrets.fq_deltas[1], ); let ct_setup = Self::enc_setup( @@ -130,23 +144,11 @@ impl BABEInstance { seed, secrets, ct_setup, - adaptor_table, - ciphertexts, + adaptor_tables: [fgc_adaptor_table, sgc_adaptor_table], + ciphertexts_sets: [fgc_ciphertext, sgc_ciphertext_1, sgc_ciphertext_2], }) } - pub fn set_gc_const_labels( - circuit: &mut Circuit, - constant_labels: &[S], - ) { - assert_eq!(constant_labels.len(), CONSTANT_SIZE); - circuit.0[0].borrow_mut().label = Some(constant_labels[0]); - circuit.0[1].borrow_mut().label = Some(constant_labels[1]); - for i in 2..CONSTANT_SIZE { - circuit.0[i + 2 * N + DvFr::N_BITS].borrow_mut().label = Some(constant_labels[i]); - } - } - /// Enc*(crs, x_S, |D|, msg; r, r·B): /// P_S = gamma_abc[0] + Σ_{k} x_S[k]·gamma_abc[k+1] /// mask = Y_S^r - e(r·B, γ) where Y_S^r = e(α, r·β) + e(P_S, r·γ) @@ -158,7 +160,7 @@ impl BABEInstance { let r = secrets.r; let p_s = vk.gamma_abc_g1[0].into_group() + vk.gamma_abc_g1[1].into_group() * static_inputs; - let r_b = secrets.r_b; + let r_b = secrets.b * r; let r_delta = vk.delta_g2.into_group() * r; let t1 = ark_bn254::Bn254::pairing(vk.alpha_g1, vk.beta_g2.into_group() * r); @@ -181,26 +183,100 @@ impl BABEInstance { /// Returns the input labels given the bits of pi1. /// Use for testing. - pub fn compute_input_labels_based_on_value(&self, pi1: ark_bn254::G1Affine, x_d: ark_bn254::Fr) -> Vec { + pub fn compute_pi1_labels_based_on_value(&self, pi1: ark_bn254::G1Affine) -> Vec { let x_bits = DvFq::to_bits(pi1.x); let y_bits = DvFq::to_bits(pi1.y); - let xd_bits = DvFr::to_bits(x_d); - let witness: Vec = x_bits.into_iter().chain(y_bits).chain(xd_bits).collect(); - let delta = self.secrets.delta; + let witness: Vec = x_bits.into_iter().chain(y_bits).collect(); + let delta = self.secrets.delta[0]; let labels: Vec = witness.iter().enumerate().map(|(i, &b)| { - let key = self.secrets.encoding_keys[i]; + let key = self.secrets.encoding_keys[0][i]; if b { key ^ delta } else { key } }).collect(); labels } + /// Returns the input labels given the bits of pi1. + /// Use for testing. + pub fn compute_x_d_labels_based_on_value(&self, x_d: Fr) -> Vec { + let witness = DvFr::to_bits(x_d); + let delta = self.secrets.delta[1]; + + let labels: Vec = witness.iter().enumerate().map(|(i, &b)| { + let key = self.secrets.encoding_keys[1][i]; + if b { key ^ delta } else { key } + }).collect(); + labels + } + + pub fn b_value_labels(&self) -> Vec { + let b_x_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(self.secrets.b.x)); + let b_y_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(self.secrets.b.y)); + let mut labels = Vec::new(); + for (i, bit) in b_x_bits.iter().enumerate() { + if *bit { + labels.push(self.secrets.constant_0labels[1][i + 2] ^ self.secrets.delta[1]); + } else { + labels.push(self.secrets.constant_0labels[1][i + 2]); + } + } + + for (i, bit) in b_y_bits.iter().enumerate() { + if *bit { + labels.push(self.secrets.constant_0labels[1][i + 2 + 254] ^ self.secrets.delta[1]); + } else { + labels.push(self.secrets.constant_0labels[1][i + 2 + 254]); + } + } + + labels + } pub fn commit(&self) -> CACInstanceCommit { CACInstanceCommit::from_instance(self) } } +fn set_gc_const_labels( + circuit: &mut Circuit, + constant_labels: &[S], +) { + circuit.0[0].borrow_mut().label = Some(constant_labels[0]); + circuit.0[1].borrow_mut().label = Some(constant_labels[1]); + for i in 2..constant_labels.len() { + circuit.0[i + 2 * N ].borrow_mut().label = Some(constant_labels[i]); + } +} + +/// Generate ciphertexts and all output labels +fn get_ciphertext_and_output_labels( + circuit: &mut Circuit, + output_indices: &[usize], + random_witness: &[bool], + delta: S, + const_skip: usize, +) -> (Vec>, Vec<[u8; 16]>) { + circuit.set_witness_value(&random_witness, const_skip); + for gate in &mut circuit.1 { + gate.evaluate(); + } + let ciphertexts = circuit.garbled_gates_with_delta(delta); + + // size = gc_output_indices x 2 + let output_labels: Vec<[u8; 16]> = output_indices + .iter() + .flat_map(|idx| { + let label_0 = circuit.0[*idx] + .borrow() + .select_with_delta(circuit.0[*idx].borrow().get_value(), delta); + [label_0.0, (label_0 ^ delta).0] + }) + .collect(); + + (ciphertexts, output_labels) + +} + #[cfg(test)] mod tests { use super::*; @@ -231,7 +307,7 @@ mod tests { let dynamic_inputs = a * a; let dynamic_pin_size = 1usize; - let instance = BABEInstance::new_from_seed(42, &vk, static_inputs) + let instance = CACInstance::new_from_seed(42, &vk, static_inputs) .expect("new_from_seed"); let r = instance.secrets.r; @@ -239,7 +315,7 @@ mod tests { // Simulate DSGC output: c1' = r·P_D + r·B // P_D = (a*a) · gamma_abc[|S|+1] = (a*a) · gamma_abc[2] let p_d = vk.gamma_abc_g1[2].into_group() * dynamic_inputs; - let c1_prime = (p_d * r + instance.secrets.r_b.into_group()).into_affine(); + let c1_prime = (p_d * r + instance.secrets.b * r).into_affine(); let ctprove = WeKnownPi1ProveCt { ct1_r_pi1: g1_to_ser(proof.a.into_group() * r) }; let decrypted = we_known_pi1_dec( diff --git a/verifiable-circuit-babe/src/instance/secret.rs b/verifiable-circuit-babe/src/instance/secret.rs index ffbe73c..d84914f 100644 --- a/verifiable-circuit-babe/src/instance/secret.rs +++ b/verifiable-circuit-babe/src/instance/secret.rs @@ -1,74 +1,80 @@ use ark_bn254::{Fq, Fr, G1Affine}; -use ark_ec::AffineRepr; use ark_ff::{UniformRand, Zero}; use garbled_snark_verifier::bag::S; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; -use garbled_snark_verifier::dv_bn254::fp254impl::Fp254Impl; -use garbled_snark_verifier::dv_bn254::fr::Fr as DvFr; +use rand_chacha::rand_core::RngCore; use crate::dre::{N, utils::sample_rhos}; -use crate::gc::CONSTANT_SIZE; pub struct InstanceSecrets { - pub delta: S, + /// 2 deltas for 2 garbled circuits + pub delta: [S; 2], pub r: Fr, pub msg: [u8; 32], - /// label0 per input wire, size = 2 * N + Fr::N (3 * N) - pub encoding_keys: Vec, - /// Constant value-based labels. For example, if constant x = 1 - /// this contains 1-label of x. Size = CONSTANT_SIZE - pub constant_val_labels: Vec, - pub rhos: Vec, - pub fq_deltas: Vec, - /// Blinding point r·B baked into the DSGC circuit. - pub r_b: G1Affine, + /// label0 per input wire, for 2 circuits + /// fgc input size = 2 * N // pi_1 + /// sgc input size = N // x_d + pub encoding_keys: [Vec; 2], + /// Constant 0labels. + /// fgc size = 2 (0/1) + /// sgc size = 2 + 2 * N (for B) + pub constant_0labels: [Vec; 2], + pub rhos: [Vec; 2], + pub fq_deltas: [Vec; 2], + pub b: G1Affine, } impl InstanceSecrets { pub fn new_from_seed(seed: u64) -> Self { let mut rng = ChaCha20Rng::seed_from_u64(seed); - let mut delta_bytes = [0u8; 16]; - rand::RngCore::fill_bytes(&mut rng, &mut delta_bytes); - delta_bytes[15] |= 1; - let delta = S(delta_bytes); + let delta = [gen_s(&mut rng, 1)[0], gen_s(&mut rng, 1)[0]]; let r = Fr::rand(&mut rng); let mut msg = [0u8; 32]; - rand::RngCore::fill_bytes(&mut rng, &mut msg); + rng.fill_bytes(&mut msg); - let encoding_keys: Vec = (0..2 * N + DvFr::N_BITS) - .map(|_| { - let mut b = [0u8; 16]; - rand::RngCore::fill_bytes(&mut rng, &mut b); - S(b) - }) - .collect(); + let encoding_keys = [gen_s(&mut rng, 2 * N), gen_s(&mut rng, N)]; - let constant_val_labels: Vec = (0..CONSTANT_SIZE) - .map(|_| { - let mut b = [0u8; 16]; - rand::RngCore::fill_bytes(&mut rng, &mut b); - S(b) - }) - .collect(); + let constant_val_labels = [gen_s(&mut rng, N), gen_s(&mut rng, 2 + 2 * N)]; - let rhos = sample_rhos(&mut rng); + let rhos = [sample_rhos(&mut rng), sample_rhos(&mut rng)]; - let fq_deltas: Vec = (0..N) - .map(|_| loop { - let v = Fq::rand(&mut rng); - if !v.is_zero() { - break v; - } - }) - .collect(); + let fq_deltas = [gen_fq_deltas(&mut rng, N), gen_fq_deltas(&mut rng, N)]; - let b_blind = G1Affine::rand(&mut rng); - use ark_ec::CurveGroup; - let r_b = (b_blind.into_group() * r).into_affine(); + let b = G1Affine::rand(&mut rng); - Self { delta, r, msg, encoding_keys, constant_val_labels, rhos, fq_deltas, r_b } + Self { + delta, + r, + msg, + encoding_keys, + constant_0labels: constant_val_labels, + rhos, + fq_deltas, + b, + } } } + +fn gen_s(rng: &mut R, n: usize) -> Vec { + (0..n) + .map(|_| { + let mut b = [0u8; 16]; + rng.fill_bytes(&mut b); + S(b) + }) + .collect() +} + +fn gen_fq_deltas(rng: &mut R, n: usize) -> Vec { + (0..n) + .map(|_| loop { + let v = Fq::rand(rng); + if !v.is_zero() { + break v; + } + }) + .collect() +} diff --git a/verifiable-circuit-babe/src/prover.rs b/verifiable-circuit-babe/src/prover.rs index af8b0a5..2aee849 100644 --- a/verifiable-circuit-babe/src/prover.rs +++ b/verifiable-circuit-babe/src/prover.rs @@ -85,7 +85,8 @@ impl BABEProver { let sld = &soldering.soldering_proof.soldered_output; println!("Trying base instance..."); - let base_full = Self::build_full_labels(&finalized[0].constant_labels, base_input_labels); + // todo: fix this, this is hard-coded as fgc labels + let base_full = Self::build_full_labels(&finalized[0].constant_labels_0, base_input_labels); let base_res = self.try_evaluate_instance(&finalized[0], &base_full, h_msgs_onchain[0]); match base_res { Ok(true) => return true, @@ -110,7 +111,8 @@ impl BABEProver { }) .collect(); - let full = Self::build_full_labels(&finalized[i].constant_labels, &instance_labels); + // todo: add sgc labels + let full = Self::build_full_labels(&finalized[i].constant_labels_0, &instance_labels); let temp = self.try_evaluate_instance(&finalized[i], &full, h_msgs_onchain[i]); match temp { Ok(true) => return true, @@ -138,13 +140,14 @@ impl BABEProver { full_labels: &[S], h_msg_onchain: [u8; 20], ) -> Result { - let (mut circuit, gc_output_indices) = crate::gc::read_fresh_circuit(); + // Todo: add sgc + let (mut circuit, gc_output_indices, _, _) = crate::gc::read_fresh_gc(); let ct_prove = self.compute_ct_prove( &mut circuit, &gc_output_indices, full_labels, - &data.gc_ciphertexts, - &data.adaptor_table, + &data.gc_ciphertexts[0], + &data.adaptor_tables[0], ); drop(circuit); @@ -179,7 +182,7 @@ impl BABEProver { .chain(Fq::to_bits(pi1.y).into_iter()) .collect(); - garbled_circuit.set_witness_value(&witness); + garbled_circuit.set_witness_value(&witness, 2); for gate in &mut garbled_circuit.1 { gate.evaluate(); } @@ -248,7 +251,7 @@ mod tests { use garbled_snark_verifier::core::utils::reset_gid; use crate::babe::DummyMulCircuit; use crate::cac::cac_finalize_indices; - use crate::instance::BABEInstance; + use crate::instance::CACInstance; use crate::soldering::{build_soldered_wires_input, soldering_guest_compute, SolderingProof}; use crate::verifier::BABEVerifier; @@ -276,24 +279,26 @@ mod tests { let dynamic_public_inputs = a * a; // x_d // 2. Verifier enc_setup. - let verifier = BABEInstance::new_from_seed( + let verifier = CACInstance::new_from_seed( rand::random(), &vk, static_public_inputs, ).unwrap(); // 3. Input labels: [pi1_xbits, pi1_ybits, x_dbits...]. - let input_labels = verifier.compute_input_labels_based_on_value(proof.a, a * a); + // Todo: input labels must contains x_d + let input_labels = verifier.compute_pi1_labels_based_on_value(proof.a); // 4. Prover evaluates the garbled circuit. let prover = BABEProver::new(proof.clone()); - let (mut circuit, gc_output_indices) = crate::gc::read_fresh_circuit(); + // Todo: add sgc + let (mut circuit, gc_output_indices, _, _) = crate::gc::read_fresh_gc(); let ct_prove = prover.compute_ct_prove( &mut circuit, &gc_output_indices, &input_labels, - &verifier.ciphertexts, - &verifier.adaptor_table, + &verifier.ciphertexts_sets[0], + &verifier.adaptor_tables[0], ); drop(circuit); @@ -370,7 +375,8 @@ mod tests { // base_input_labels: active labels for the 508 π₁ input wires of the base instance. let base_idx = finalized_indices[0]; - let all_labels = verifier.instances[base_idx].compute_input_labels_based_on_value(proof.a, dynamic_public_inputs); + // Todo: add dynamic input + let all_labels = verifier.instances[base_idx].compute_pi1_labels_based_on_value(proof.a); let base_input_labels = &all_labels[2..]; // strip constant-wire labels let mut prover = BABEProver::new(proof.clone()); diff --git a/verifiable-circuit-babe/src/soldering.rs b/verifiable-circuit-babe/src/soldering.rs index 45122aa..d680751 100644 --- a/verifiable-circuit-babe/src/soldering.rs +++ b/verifiable-circuit-babe/src/soldering.rs @@ -85,12 +85,16 @@ pub fn build_soldered_wires_input( .iter() .map(|&idx| { let inst = &verifier.instances[idx]; - let delta = inst.secrets.delta; - inst.secrets - .encoding_keys - .iter() - .map(|&ek| (ek.0, (ek ^ delta).0)) - .collect() + let delta = &inst.secrets.delta; + let encoding_keys = &inst.secrets.encoding_keys; + + (0..2) + .flat_map(|i| { + encoding_keys[i] + .iter() + .map(move |&ek| (ek.0, (ek ^ delta[i]).0)) + }) + .collect::>() }) .collect(), ) diff --git a/verifiable-circuit-babe/src/verifier.rs b/verifiable-circuit-babe/src/verifier.rs index ef2348f..86f42a9 100644 --- a/verifiable-circuit-babe/src/verifier.rs +++ b/verifiable-circuit-babe/src/verifier.rs @@ -1,12 +1,12 @@ use ark_bn254::Fr; use ark_groth16::VerifyingKey as Groth16VerifyingKey; use rand::Rng; -use crate::instance::BABEInstance; +use crate::instance::CACInstance; /// The C&C Verifier: manages N_CC garbled-circuit instances for Cut-and-Choose. pub struct BABEVerifier { /// All N_CC instances, each fully derived from its own seed. - pub instances: Vec, + pub instances: Vec, /// value used for CA_2 Txn pub temp_val: [u8; 32], } @@ -26,12 +26,12 @@ impl BABEVerifier { let instances = seeds .par_iter() .map(|&seed| { - let inst = BABEInstance::new_from_seed( + let inst = CACInstance::new_from_seed( seed, vk, static_public_inputs, )?; - Ok::(inst) + Ok::(inst) }) .collect::>() .into_iter() @@ -67,14 +67,24 @@ impl BABEVerifier { let mut finalized = Vec::new(); for &i in finalized_indices { let inst = &self.instances[i]; - let constant_labels = - [inst.secrets.constant_val_labels[0], inst.secrets.constant_val_labels[1] ^ inst.secrets.delta]; + let constant_labels_0 = [ + inst.secrets.constant_0labels[0][0], inst.secrets.constant_0labels[0][1] ^ inst.secrets.delta[0] + ]; + + let mut constant_labels_1 = vec![ + inst.secrets.constant_0labels[1][0], inst.secrets.constant_0labels[1][1] ^ inst.secrets.delta[1] + ]; + constant_labels_1.extend(inst.b_value_labels()); + + finalized.push(crate::cac::FinalizedInstanceData { index: i, - gc_ciphertexts: inst.ciphertexts.clone(), - adaptor_table: inst.adaptor_table.clone(), + gc_ciphertexts: inst.ciphertexts_sets.clone(), + adaptor_tables: inst.adaptor_tables.clone(), ct_setup: inst.ct_setup.clone(), - constant_labels + constant_labels_0, + constant_labels_1: constant_labels_1.try_into().unwrap(), + b: inst.secrets.b }); } From 57c845a9b5b18ee2da1912ce4ea46ebbed0dae9f Mon Sep 17 00:00:00 2001 From: vanhger Date: Mon, 27 Apr 2026 19:34:45 +0700 Subject: [PATCH 13/32] fix minor errors --- garbled-snark-verifier/src/core/circuit.rs | 3 --- verifiable-circuit-babe/src/gc/circuit.rs | 22 ++++++++-------- verifiable-circuit-babe/src/gc/mod.rs | 2 +- verifiable-circuit-babe/src/instance/mod.rs | 29 +++++++++++---------- 4 files changed, 27 insertions(+), 29 deletions(-) diff --git a/garbled-snark-verifier/src/core/circuit.rs b/garbled-snark-verifier/src/core/circuit.rs index 5a2b05b..73eeed6 100644 --- a/garbled-snark-verifier/src/core/circuit.rs +++ b/garbled-snark-verifier/src/core/circuit.rs @@ -123,9 +123,6 @@ impl Circuit { for wirex in self.0.iter() { wirex.borrow_mut().label = None; } - // compute the size of circuit, because this resetted circuit will be reused for multiple proofs - let size = self.size_in_bytes(); - println!("Reset circuit, size: {} MB", size as f64 / 1_048_576.0); } pub fn is_fresh(&self) -> bool { diff --git a/verifiable-circuit-babe/src/gc/circuit.rs b/verifiable-circuit-babe/src/gc/circuit.rs index 3451636..138a9f5 100644 --- a/verifiable-circuit-babe/src/gc/circuit.rs +++ b/verifiable-circuit-babe/src/gc/circuit.rs @@ -91,21 +91,21 @@ fn emit_fgc( /// B is garbler-private and supplied as input wires. /// /// Input wire layout: -/// [0..Fr::N_BITS) x_d — scalar bits, LSB-first (evaluator input) -/// [Fr::N_BITS..Fr::N_BITS+N) B_x — x-coordinate of B, Montgomery form (garbler-private) -/// [Fr::N_BITS+N..Fr::N_BITS+2·N) B_y — y-coordinate of B, Montgomery form (garbler-private) +/// [0..Fr::N_BITS) B_x — x-coordinate of B, Montgomery form (garbler-private) +/// [Fr::N_BITS..Fr::N_BITS+N) B_y — y-coordinate of B, Montgomery form (garbler-private) +/// [Fr::N_BITS+N..Fr::N_BITS+2·N) x_d — scalar bits, LSB-first (evaluator input) /// /// Output: Q_SIZE = 3·N bits — (X, Y, Z) of Q in projective Montgomery form. pub fn compile_sgc_part1(l2: G1Affine) -> (CircuitAdapter, Vec) { let mut bld = CircuitAdapter::default(); - // x_d — evaluator input - let x_d: Vec = (0..Fr::N_BITS).map(|_| bld.fresh_one()).collect(); - // B — garbler-private affine point, Montgomery form let b_x = Fq::wires(&mut bld); let b_y = Fq::wires(&mut bld); + // x_d — evaluator input + let x_d: Vec = (0..Fr::N_BITS).map(|_| bld.fresh_one()).collect(); + // L_2 table — embedded as constant wires, same pattern as g in compile_fgc let table_wires: Vec = build_l2_table_bits(&l2) .into_iter() @@ -290,10 +290,10 @@ mod tests { /// Witness for compile_sgc_part1: x_d | B_x (Montgomery) | B_y (Montgomery). /// L_2 table is baked into the circuit as constants, so it is not part of the witness. fn sgc_part1_witness(x_d: ark_bn254::Fr, b: &G1Affine) -> Vec { - Fr::to_bits(x_d) + Fq::to_bits(Fq::as_montgomery(b.x)) .into_iter() - .chain(Fq::to_bits(Fq::as_montgomery(b.x))) .chain(Fq::to_bits(Fq::as_montgomery(b.y))) + .chain(Fr::to_bits(x_d)) .collect() } @@ -328,7 +328,7 @@ mod tests { let got = eval_sgc_part1(l2, b, x_d); let expected = (ark_bn254::G1Projective::from(l2) * x_d + ark_bn254::G1Projective::from(b)) - .into_affine(); + .into_affine(); assert_eq!(got, expected, "Q = x_d · L₂ + B affine mismatch"); } @@ -341,8 +341,8 @@ mod tests { let got = eval_sgc_part1(l2, b, ark_bn254::Fr::from(1u64)); let expected = (ark_bn254::G1Projective::from(l2) + ark_bn254::G1Projective::from(b)) - .into_affine(); + .into_affine(); assert_eq!(got, expected, "Q = 1·L₂ + B should equal L₂ + B"); } -} \ No newline at end of file +} diff --git a/verifiable-circuit-babe/src/gc/mod.rs b/verifiable-circuit-babe/src/gc/mod.rs index 0a51b6f..c9fe7f9 100644 --- a/verifiable-circuit-babe/src/gc/mod.rs +++ b/verifiable-circuit-babe/src/gc/mod.rs @@ -57,7 +57,7 @@ pub fn read_fresh_gc() -> (Circuit, Vec, Circuit, Vec) { (g, i) }); - let (sgc, sgc_indices) = deserialize_circuit(sgc_bytes, fgc_indices_bytes); + let (sgc, sgc_indices) = deserialize_circuit(sgc_bytes, sgc_indices_bytes); (fgc, fgc_indices, sgc, sgc_indices) } diff --git a/verifiable-circuit-babe/src/instance/mod.rs b/verifiable-circuit-babe/src/instance/mod.rs index 5a02468..d2ae54e 100644 --- a/verifiable-circuit-babe/src/instance/mod.rs +++ b/verifiable-circuit-babe/src/instance/mod.rs @@ -11,7 +11,7 @@ use ark_serialize::CanonicalSerialize; use garbled_snark_verifier::dv_bn254::fp254impl::Fp254Impl; use garbled_snark_verifier::dv_bn254::fq::Fq as DvFq; use garbled_snark_verifier::dv_bn254::fr::Fr as DvFr; -use crate::dre::{N, U_BAR_SIZE}; +use crate::dre::{N, Q_SIZE, U_BAR_SIZE}; use crate::instance::commit::CACInstanceCommit; use crate::utils::{g2_to_ser, ro_from_pairing_bytes}; @@ -85,8 +85,8 @@ impl CACInstance { secrets.delta[0], 2 ); - assert_eq!(fgc_output_labels.len(), U_BAR_SIZE); - + assert_eq!(fgc_output_labels.len(), 2 * U_BAR_SIZE); + println!("cac instance fgc done"); // Sgc - part 1 let sgc_part1_witness: Vec = DvFr::to_bits(x_d); let (sgc_ciphertext_1, sgc_output_labels_1) = get_ciphertext_and_output_labels( @@ -96,10 +96,11 @@ impl CACInstance { secrets.delta[1], SGC_PART1_CONSTANT_SIZE, ); - + println!("cac instance sgc part1 done"); + assert_eq!(sgc_output_labels_1.len(), 2 * Q_SIZE); // Sgc - part 2 // Reuse the fgc structure, by setting up the input & constant labels again, then evaluate. - assert_eq!(sgc_output_labels_1.len(), 2 * 2 * N); + fgc.reset_circuit_except_constants(); // set label of part2 as output of part1 for (i, &key) in sgc_output_labels_1.iter().step_by(2).enumerate() { fgc.0[2 + i].borrow_mut().label = Some(S(key)); @@ -114,8 +115,8 @@ impl CACInstance { secrets.delta[1], 2 ); - assert_eq!(sgc_output_labels_2.len(), U_BAR_SIZE); - + assert_eq!(sgc_output_labels_2.len(), 2 * U_BAR_SIZE); + println!("cac instance sgc part 2 done"); // generate adaptor table // fgc let fgc_adaptor_table = SparseAdaptorTable::build_from_r_and_u_bar_labels( @@ -241,10 +242,8 @@ fn set_gc_const_labels( circuit: &mut Circuit, constant_labels: &[S], ) { - circuit.0[0].borrow_mut().label = Some(constant_labels[0]); - circuit.0[1].borrow_mut().label = Some(constant_labels[1]); - for i in 2..constant_labels.len() { - circuit.0[i + 2 * N ].borrow_mut().label = Some(constant_labels[i]); + for i in 0..constant_labels.len() { + circuit.0[i].borrow_mut().label = Some(constant_labels[i]); } } @@ -262,7 +261,7 @@ fn get_ciphertext_and_output_labels( } let ciphertexts = circuit.garbled_gates_with_delta(delta); - // size = gc_output_indices x 2 + // size = gc_output_indices x 2 let output_labels: Vec<[u8; 16]> = output_indices .iter() .flat_map(|idx| { @@ -309,6 +308,7 @@ mod tests { let instance = CACInstance::new_from_seed(42, &vk, static_inputs) .expect("new_from_seed"); + println!("generate instance done"); let r = instance.secrets.r; @@ -316,13 +316,14 @@ mod tests { // P_D = (a*a) · gamma_abc[|S|+1] = (a*a) · gamma_abc[2] let p_d = vk.gamma_abc_g1[2].into_group() * dynamic_inputs; let c1_prime = (p_d * r + instance.secrets.b * r).into_affine(); - let ctprove = WeKnownPi1ProveCt { ct1_r_pi1: g1_to_ser(proof.a.into_group() * r) }; let decrypted = we_known_pi1_dec( &vk, &instance.ct_setup, &ctprove, c1_prime, proof.b.into_group(), proof.c.into_group(), ).unwrap(); + println!("decrypted done"); + assert_eq!(decrypted.as_slice(), &instance.secrets.msg); } -} \ No newline at end of file +} From 1e6497b8e817751ebbe91632c1c61a008910f0bf Mon Sep 17 00:00:00 2001 From: vanhger Date: Tue, 28 Apr 2026 10:30:22 +0700 Subject: [PATCH 14/32] impl instance --- verifiable-circuit-babe/src/babe.rs | 36 ++- verifiable-circuit-babe/src/cac.rs | 24 +- verifiable-circuit-babe/src/dre/mod.rs | 2 +- verifiable-circuit-babe/src/gc/circuit.rs | 55 ++++- verifiable-circuit-babe/src/instance/mod.rs | 184 ++++++++++----- verifiable-circuit-babe/src/prover.rs | 249 +++++++++++++++----- verifiable-circuit-babe/src/verifier.rs | 12 +- 7 files changed, 409 insertions(+), 153 deletions(-) diff --git a/verifiable-circuit-babe/src/babe.rs b/verifiable-circuit-babe/src/babe.rs index 56db91b..10f3b0b 100644 --- a/verifiable-circuit-babe/src/babe.rs +++ b/verifiable-circuit-babe/src/babe.rs @@ -2,6 +2,8 @@ use ark_bn254::{Bn254, Fr, G1Affine}; use ark_ec::{AffineRepr, CurveGroup}; use ark_ff::PrimeField; use ark_groth16::{Proof as Groth16Proof, VerifyingKey as Groth16VerifyingKey}; +use ark_groth16::ProvingKey as Groth16ProvingKey; + use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_relations::lc; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; @@ -107,6 +109,7 @@ pub struct WeKnownPi1SetupCt { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct WeKnownPi1ProveCt { pub ct1_r_pi1: Vec, + pub ct1_prime: Vec, // r * Q } // ─── Setup state ───────────────────────────────────────────────────────────── @@ -168,7 +171,7 @@ pub fn babe_verifier_open_and_solder( finalized_indices: finalized_indices.to_vec(), soldering_proof: SolderingProof { soldered_output, _proof: PhantomData }, }; - + (opened, finalized, soldering, derive_hashlock(&verifier.temp_val)) } @@ -225,8 +228,8 @@ pub fn babe_verify_prover_presigs( let h_msgs_valid = challenge_assert_outlock.h_msgs.len() == finalized_indices.len() && challenge_assert_outlock.h_msgs.iter().zip(finalized_indices.iter()).all(|(&h_msg, &idx)| { - h_msg == package.commits[idx].h_msg - }); + h_msg == package.commits[idx].h_msg + }); presigs_valid && keys_valid && h_msgs_valid } @@ -300,13 +303,15 @@ pub fn babe_verifier_challenge_assert_cac( /// Prover: given the labels from TxChallengeAssert, evaluate the GC across all finalized /// instances (base first, then non-base via soldering deltas) and decrypt the msg. pub fn babe_prover_wrongly_challenged_cac( + pk: &Groth16ProvingKey, + dyn_pubin: Fr, challenge_witness: &TxChallengeAssertWitness, proof: &Groth16Proof, prover_state: &ProverSetupState, ) -> Option<(TxWronglyChallengedWitness, usize)> { let base_input_labels: Vec = challenge_witness.input_labels.iter().map(|&b| S(b)).collect(); - let mut prover = BABEProver::new(proof.clone()); + let mut prover = BABEProver::new(pk.clone(), proof.clone(), dyn_pubin); let found = prover.check_compute_msg( &prover_state.finalized, &base_input_labels, @@ -384,9 +389,13 @@ pub fn we_known_pi1_encsetup( } /// Encprove(crs, π₁; r): ctprove = r·π₁. -pub fn we_known_pi1_encprove(pi1: ark_bn254::G1Projective, r_bytes: [u8; 32]) -> WeKnownPi1ProveCt { +pub fn we_known_pi1_encprove( + pi1: ark_bn254::G1Projective, + r_bytes: [u8; 32], + ct1_prime: G1Affine, +) -> WeKnownPi1ProveCt { let r = Fr::from_le_bytes_mod_order(&r_bytes); - WeKnownPi1ProveCt { ct1_r_pi1: g1_to_ser(pi1 * r) } + WeKnownPi1ProveCt { ct1_r_pi1: g1_to_ser(pi1 * r), ct1_prime: g1_to_ser(ct1_prime.into_group()) } } /// Dec*(vk, ctsetup, ctprove, c1', π₂, π₃): @@ -396,15 +405,15 @@ pub fn we_known_pi1_dec( vk: &Groth16VerifyingKey, ctsetup: &WeKnownPi1SetupCt, ctprove: &WeKnownPi1ProveCt, - c1_prime: G1Affine, pi2: ark_bn254::G2Projective, pi3: ark_bn254::G1Projective, ) -> Option> { let ct1 = g1_from_ser_checked(&ctprove.ct1_r_pi1)?; + let ct1_prime = g1_from_ser_checked(&ctprove.ct1_prime)?; let ct2 = g2_from_ser_checked(&ctsetup.ct2_r_delta_g2)?; let r_y = Bn254::pairing(ct1, pi2) - Bn254::pairing(pi3, ct2); - let q_blind = Bn254::pairing(c1_prime, vk.gamma_g2); + let q_blind = Bn254::pairing(ct1_prime, vk.gamma_g2); let mask_gt = r_y - q_blind; let mut mask_bytes = Vec::new(); @@ -531,6 +540,8 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { // WronglyChallenged: Prover evaluates GC (base first, then non-base if needed). println!("Prover: Finding msg..."); let (wc_witness, instance_id) = babe_prover_wrongly_challenged_cac( + &groth16_pk, + dynamic_public_inputs, &challenge_witness, &proof, &prover_state, @@ -623,17 +634,18 @@ mod tests { let (ct_setup, r_b_affine) = we_known_pi1_encsetup( &vk, &static_inputs, num_dynamic, secret, r_bytes, b_blind, ).unwrap(); - let ctprove = we_known_pi1_encprove(proof.a.into_group(), r_bytes); - // Simulate DSGC: c1' = r·P_D + r·B // P_D = (a*a) · gamma_abc[|S|+1] = (a*a) · gamma_abc[2] let r = Fr::from_le_bytes_mod_order(&r_bytes); let p_d = vk.gamma_abc_g1[2].into_group() * dyn_inputs[0]; let c1_prime = (p_d * r + r_b_affine.into_group()).into_affine(); + let ct_prove = we_known_pi1_encprove(proof.a.into_group(), r_bytes, c1_prime); + + let decrypted = we_known_pi1_dec( - &vk, &ct_setup, &ctprove, c1_prime, proof.b.into_group(), proof.c.into_group(), + &vk, &ct_setup, &ct_prove, proof.b.into_group(), proof.c.into_group(), ).unwrap(); assert_eq!(decrypted, secret); } -} \ No newline at end of file +} diff --git a/verifiable-circuit-babe/src/cac.rs b/verifiable-circuit-babe/src/cac.rs index 5ca73d5..9f29a52 100644 --- a/verifiable-circuit-babe/src/cac.rs +++ b/verifiable-circuit-babe/src/cac.rs @@ -63,7 +63,7 @@ pub fn cac_finalize_indices(package: &CACSetupPackage, m_cc: usize) -> Vec>; 3], + pub ciphertext_sets: [Vec>; 3], pub adaptor_tables: [SparseAdaptorTable; 2], pub ct_setup: WeKnownPi1SetupCt, /// [0-label of wire-0 (constant false), 1-label of wire-1 (constant true)]. @@ -96,7 +96,7 @@ pub fn verify_opened_instances( if recomputed.epk != committed.epk { return Err(format!("instance {idx}: input_commits mismatch")); } - if recomputed.constant_commits_0 != committed.constant_commits_0 + if recomputed.constant_commits_0 != committed.constant_commits_0 || recomputed.constant_commits_1 != committed.constant_commits_1 { return Err(format!("instance {idx}: constant_commits mismatch")); } @@ -130,18 +130,18 @@ pub fn verify_finalized_instances( for i in 0..3 { - if gc_ciphertexts_commit(&data.gc_ciphertexts[i]) != committed.com_gc[i] { + if gc_ciphertexts_commit(&data.ciphertext_sets[i]) != committed.com_gc[i] { return Err(format!("instance {idx}: gc_ciphertexts do not match com_gc")); - } + } } - - if data.adaptor_tables[0].commit() != committed.com_adaptor[0] - || data.adaptor_tables[1].commit() != committed.com_adaptor[1] { + + if data.adaptor_tables[0].commit() != committed.com_adaptor[0] + || data.adaptor_tables[1].commit() != committed.com_adaptor[1] { return Err(format!("instance {idx}: adaptor_table does not match com_adaptor")); } - + // verify constant labels - if h_256(&data.constant_labels_0[0].0) != committed.constant_commits_0[0][0] || + if h_256(&data.constant_labels_0[0].0) != committed.constant_commits_0[0][0] || h_256(&data.constant_labels_0[1].0) != committed.constant_commits_0[1][1] || h_256(&data.constant_labels_1[0].0) != committed.constant_commits_1[0][0] || h_256(&data.constant_labels_1[1].0) != committed.constant_commits_1[1][1] { @@ -159,7 +159,7 @@ pub fn verify_finalized_instances( return Err(format!("instance {idx}: constant_commits do not match")); } } - + let mut ct_bytes = Vec::new(); ct_bytes.extend_from_slice(&data.ct_setup.ct2_r_delta_g2); ct_bytes.extend_from_slice(&data.ct_setup.ct3_masked_msg); @@ -179,8 +179,8 @@ mod tests { use crate::babe::DummyMulCircuit; use crate::verifier::BABEVerifier; - const TEST_N_CC: usize = 10; - const TEST_M_CC: usize = 4; + const TEST_N_CC: usize = 4; + const TEST_M_CC: usize = 2; #[test] fn test_cac_commit_open_verify() { diff --git a/verifiable-circuit-babe/src/dre/mod.rs b/verifiable-circuit-babe/src/dre/mod.rs index 42b2edc..c385d41 100644 --- a/verifiable-circuit-babe/src/dre/mod.rs +++ b/verifiable-circuit-babe/src/dre/mod.rs @@ -4,7 +4,7 @@ pub mod utils; pub mod matrices; pub const N: usize = 254; pub const U_BAR_SIZE: usize = 1 + 5 * N; // 1271 — ū(π) binary decomposition -pub const Q_SIZE: usize = 3 * N; // 762 — projective (X,Y,Z) of x_d·L_2 + B +pub const Q_SIZE: usize = 2 * N; // 508 — standard affine (x,y) of x_d·L_2 + B /// Decoding: f_i = r_i·π + ρ_i (Jacobian coords) pub struct DREDecoding { diff --git a/verifiable-circuit-babe/src/gc/circuit.rs b/verifiable-circuit-babe/src/gc/circuit.rs index 138a9f5..4bd4129 100644 --- a/verifiable-circuit-babe/src/gc/circuit.rs +++ b/verifiable-circuit-babe/src/gc/circuit.rs @@ -1,6 +1,6 @@ use ark_bn254::G1Affine; use ark_ec::CurveGroup; -use ark_ff::{AdditiveGroup, Zero}; +use ark_ff::{AdditiveGroup, PrimeField, Zero}; use sha2::{Digest, Sha256}; use garbled_snark_verifier::circuits::sect233k1::builder::{CircuitAdapter, CircuitTrait}; use garbled_snark_verifier::dv_bn254::basic::selector; @@ -95,7 +95,7 @@ fn emit_fgc( /// [Fr::N_BITS..Fr::N_BITS+N) B_y — y-coordinate of B, Montgomery form (garbler-private) /// [Fr::N_BITS+N..Fr::N_BITS+2·N) x_d — scalar bits, LSB-first (evaluator input) /// -/// Output: Q_SIZE = 3·N bits — (X, Y, Z) of Q in projective Montgomery form. +/// Output: Q_SIZE = 2·N bits — (x, y) of Q in standard affine form. pub fn compile_sgc_part1(l2: G1Affine) -> (CircuitAdapter, Vec) { let mut bld = CircuitAdapter::default(); @@ -122,7 +122,7 @@ pub fn compile_sgc_part1(l2: G1Affine) -> (CircuitAdapter, Vec) { /// Emit: scalar_point = x_d · L_2 (windowed private table) then add affine point P. /// /// Wire indices for the affine point and table may be input or constant wires. -/// Output: R_PD_SIZE bits — (X, Y, Z) in projective Montgomery form. +/// Output: Q_SIZE bits — (x, y) in standard affine form. fn emit_scalar_mul_then_add( bld: &mut CircuitAdapter, x_d: &[usize], @@ -139,15 +139,49 @@ fn emit_scalar_mul_then_add( let result_proj_m = GcG1Projective::add_mixed_montgomery_no_inf(bld, &prod_proj_m, &p_affine_m); - // Output stays in Montgomery form (X·R, Y·R, Z·R) + // Convert projective Montgomery (X·R, Y·R, Z·R) → standard affine (x, y) + // x = X/Z, y = Y/Z via Fermat inversion of Z in the Montgomery domain + let x_m = &result_proj_m[..N]; + let y_m = &result_proj_m[N..2 * N]; + let z_m = &result_proj_m[2 * N..]; + let z_inv_m = fq_inverse_montgomery(bld, z_m); + let x_std = Fq::mul_montgomery(bld, x_m, &z_inv_m); + let y_std = Fq::mul_montgomery(bld, y_m, &z_inv_m); + let mut output = Vec::with_capacity(Q_SIZE); - output.extend_from_slice(&result_proj_m[..N]); - output.extend_from_slice(&result_proj_m[N..2 * N]); - output.extend_from_slice(&result_proj_m[2 * N..]); + output.extend_from_slice(&x_std); + output.extend_from_slice(&y_std); assert_eq!(output.len(), Q_SIZE); output } +/// Field inversion in the Montgomery domain via Fermat's little theorem. +/// Input: `a_m = a·R mod p`; output: `a^{-1}·R mod p`. +fn fq_inverse_montgomery(bld: &mut CircuitAdapter, a_m: &[usize]) -> Vec { + let pm2 = { + let mut m = ark_bn254::Fq::MODULUS; + m.0[0] = m.0[0].wrapping_sub(2); // p is odd so p.0[0] >= 3; no borrow propagates + m + }; + let mut acc: Vec = a_m.to_vec(); + let mut started = false; + for limb_idx in (0..4).rev() { + let limb = pm2.0[limb_idx]; + for bit_idx in (0..64).rev() { + let bit = (limb >> bit_idx) & 1 != 0; + if !started { + if bit { started = true; } + continue; + } + acc = Fq::square_montgomery(bld, &acc); + if bit { + acc = Fq::mul_montgomery(bld, &acc, a_m); + } + } + } + acc +} + // ── Shared helpers ────────────────────────────────────────────────────────── /// Build constant wire indices for ū(g). @@ -312,10 +346,9 @@ mod tests { .map(|&i| circuit.0[i].borrow().get_value()) .collect(); - let x = Fq::from_montgomery(Fq::from_bits(bits[..N].to_vec())); - let y = Fq::from_montgomery(Fq::from_bits(bits[N..2 * N].to_vec())); - let z = Fq::from_montgomery(Fq::from_bits(bits[2 * N..].to_vec())); - ark_bn254::G1Projective::new(x, y, z).into_affine() + let x = Fq::from_bits(bits[..N].to_vec()); + let y = Fq::from_bits(bits[N..2 * N].to_vec()); + ark_bn254::G1Affine::new_unchecked(x, y) } #[test] diff --git a/verifiable-circuit-babe/src/instance/mod.rs b/verifiable-circuit-babe/src/instance/mod.rs index d2ae54e..1f46fdd 100644 --- a/verifiable-circuit-babe/src/instance/mod.rs +++ b/verifiable-circuit-babe/src/instance/mod.rs @@ -13,6 +13,7 @@ use garbled_snark_verifier::dv_bn254::fq::Fq as DvFq; use garbled_snark_verifier::dv_bn254::fr::Fr as DvFr; use crate::dre::{N, Q_SIZE, U_BAR_SIZE}; use crate::instance::commit::CACInstanceCommit; +use crate::prover::BABEProver; use crate::utils::{g2_to_ser, ro_from_pairing_bytes}; pub mod secret; @@ -41,6 +42,9 @@ impl CACInstance { return Err("static/dynamic split does not match vk".to_string()); } + // generate artifacts. + // crate::gc::generate_and_write_fresh_circuit(vk.gamma_abc_g1[2]); + let secrets = InstanceSecrets::new_from_seed(seed); // Load fresh circuit structure from pre-serialized files; drop after use. @@ -87,36 +91,36 @@ impl CACInstance { ); assert_eq!(fgc_output_labels.len(), 2 * U_BAR_SIZE); println!("cac instance fgc done"); - // Sgc - part 1 - let sgc_part1_witness: Vec = DvFr::to_bits(x_d); - let (sgc_ciphertext_1, sgc_output_labels_1) = get_ciphertext_and_output_labels( - &mut sgc, - &sgc_indices, - &sgc_part1_witness, - secrets.delta[1], - SGC_PART1_CONSTANT_SIZE, - ); - println!("cac instance sgc part1 done"); - assert_eq!(sgc_output_labels_1.len(), 2 * Q_SIZE); - // Sgc - part 2 - // Reuse the fgc structure, by setting up the input & constant labels again, then evaluate. - fgc.reset_circuit_except_constants(); - // set label of part2 as output of part1 - for (i, &key) in sgc_output_labels_1.iter().step_by(2).enumerate() { - fgc.0[2 + i].borrow_mut().label = Some(S(key)); - } - // set constant for part2 - set_gc_const_labels(&mut fgc, &secrets.constant_0labels[1][0..2]); - // random eval - let (sgc_ciphertext_2, sgc_output_labels_2) = get_ciphertext_and_output_labels( - &mut fgc, - &fgc_indices, - &fgc_witness, - secrets.delta[1], - 2 - ); - assert_eq!(sgc_output_labels_2.len(), 2 * U_BAR_SIZE); - println!("cac instance sgc part 2 done"); + // // Sgc - part 1 + // let sgc_part1_witness: Vec = DvFr::to_bits(x_d); + // let (sgc_ciphertext_1, sgc_output_labels_1) = get_ciphertext_and_output_labels( + // &mut sgc, + // &sgc_indices, + // &sgc_part1_witness, + // secrets.delta[1], + // SGC_PART1_CONSTANT_SIZE, + // ); + // println!("cac instance sgc part1 done"); + // assert_eq!(sgc_output_labels_1.len(), 2 * Q_SIZE); + // // Sgc - part 2 + // // Reuse the fgc structure, by setting up the input & constant labels again, then evaluate. + // fgc.reset_circuit_except_constants(); + // // set label of part2 as output of part1 + // for (i, &key) in sgc_output_labels_1.iter().step_by(2).enumerate() { + // fgc.0[2 + i].borrow_mut().label = Some(S(key)); + // } + // // set constant for part2 + // set_gc_const_labels(&mut fgc, &secrets.constant_0labels[1][0..2]); + // // random eval + // let (sgc_ciphertext_2, sgc_output_labels_2) = get_ciphertext_and_output_labels( + // &mut fgc, + // &fgc_indices, + // &fgc_witness, + // secrets.delta[1], + // 2 + // ); + // assert_eq!(sgc_output_labels_2.len(), 2 * U_BAR_SIZE); + // println!("cac instance sgc part 2 done"); // generate adaptor table // fgc let fgc_adaptor_table = SparseAdaptorTable::build_from_r_and_u_bar_labels( @@ -126,12 +130,33 @@ impl CACInstance { &secrets.fq_deltas[0], ); - let sgc_adaptor_table = SparseAdaptorTable::build_from_r_and_u_bar_labels( - secrets.r, - &sgc_output_labels_2, - &secrets.rhos[1], - &secrets.fq_deltas[1], + // test table + + // size = gc_output_indices + let output_labels: Vec<[u8; 16]> = fgc_indices + .iter() + .map(|idx| { + let label_0 = fgc.0[*idx] + .borrow() + .label.unwrap().0; + label_0 + }) + .collect(); + + let ct1_bytes = BABEProver::eval_adaptor_table( + &output_labels, pi1, &fgc_adaptor_table ); + let mut expected_ct1_bytes = Vec::new(); + (pi1 * secrets.r).into_affine().serialize_compressed(&mut expected_ct1_bytes).expect("serialize r·G1P"); + assert_eq!(ct1_bytes, expected_ct1_bytes); + println!("eval correctly"); + + // let sgc_adaptor_table = SparseAdaptorTable::build_from_r_and_u_bar_labels( + // secrets.r, + // &sgc_output_labels_2, + // &secrets.rhos[1], + // &secrets.fq_deltas[1], + // ); let ct_setup = Self::enc_setup( &secrets, @@ -145,8 +170,10 @@ impl CACInstance { seed, secrets, ct_setup, - adaptor_tables: [fgc_adaptor_table, sgc_adaptor_table], - ciphertexts_sets: [fgc_ciphertext, sgc_ciphertext_1, sgc_ciphertext_2], + // adaptor_tables: [fgc_adaptor_table, sgc_adaptor_table], + adaptor_tables: [fgc_adaptor_table.clone(), fgc_adaptor_table], + // ciphertexts_sets: [fgc_ciphertext, sgc_ciphertext_1, sgc_ciphertext_2], + ciphertexts_sets: [fgc_ciphertext.clone(), fgc_ciphertext.clone(), fgc_ciphertext], }) } @@ -210,7 +237,7 @@ impl CACInstance { labels } - pub fn b_value_labels(&self) -> Vec { + pub fn get_b_value_labels(&self) -> Vec { let b_x_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(self.secrets.b.x)); let b_y_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(self.secrets.b.y)); let mut labels = Vec::new(); @@ -233,12 +260,27 @@ impl CACInstance { labels } + // Labels of constants in both circuits + pub fn get_2_circuit_constant_labels(&self) -> [Vec; 2] { + let f_01_labels = [ + self.secrets.constant_0labels[0][0], self.secrets.constant_0labels[0][1] ^ self.secrets.delta[0] + ]; + + let s_01_labels = [ + self.secrets.constant_0labels[1][0], self.secrets.constant_0labels[1][1] ^ self.secrets.delta[1] + ]; + let s_b_labels = self.get_b_value_labels(); + let s_constant_labels: Vec = s_01_labels + .to_vec().into_iter().chain(s_b_labels).collect(); + [f_01_labels.to_vec(), s_constant_labels] + } + pub fn commit(&self) -> CACInstanceCommit { CACInstanceCommit::from_instance(self) } } -fn set_gc_const_labels( +pub fn set_gc_const_labels( circuit: &mut Circuit, constant_labels: &[S], ) { @@ -273,7 +315,6 @@ fn get_ciphertext_and_output_labels( .collect(); (ciphertexts, output_labels) - } #[cfg(test)] @@ -282,6 +323,7 @@ mod tests { use ark_crypto_primitives::snark::{CircuitSpecificSetupSNARK, SNARK}; use rand::SeedableRng; use crate::babe::DummyMulCircuit; + use crate::prover::BABEProver; #[test] fn enc_setup_prove_dec_roundtrip() { @@ -304,26 +346,60 @@ mod tests { // |S|=1 (a*b static), |D|=1 (a*a dynamic) let static_inputs = a * b; let dynamic_inputs = a * a; - let dynamic_pin_size = 1usize; let instance = CACInstance::new_from_seed(42, &vk, static_inputs) .expect("new_from_seed"); println!("generate instance done"); let r = instance.secrets.r; + let b_blind = instance.secrets.b; + let pi1 = proof.a; - // Simulate DSGC output: c1' = r·P_D + r·B - // P_D = (a*a) · gamma_abc[|S|+1] = (a*a) · gamma_abc[2] - let p_d = vk.gamma_abc_g1[2].into_group() * dynamic_inputs; - let c1_prime = (p_d * r + instance.secrets.b * r).into_affine(); - let ctprove = WeKnownPi1ProveCt { ct1_r_pi1: g1_to_ser(proof.a.into_group() * r) }; - let decrypted = we_known_pi1_dec( - &vk, &instance.ct_setup, &ctprove, c1_prime, - proof.b.into_group(), proof.c.into_group(), - ).unwrap(); - - println!("decrypted done"); - - assert_eq!(decrypted.as_slice(), &instance.secrets.msg); + // evaluate the fgc to get the r * pi_1 + let constant_labels = instance.get_2_circuit_constant_labels(); + let pi1_labels = instance.compute_pi1_labels_based_on_value(proof.a); + // let xd_labels = instance.compute_x_d_labels_based_on_value(dynamic_inputs); + let (mut fgc, fgc_indices, mut sgc, sgc_indices) = crate::gc::read_fresh_gc(); + set_gc_const_labels(&mut fgc, &constant_labels[0]); + for (i, &lbl) in pi1_labels.iter().enumerate() { + fgc.0[i + 2].borrow_mut().label = Some(lbl); + } + let fgc_witness: Vec = DvFq::to_bits(pi1.x) + .into_iter() + .chain(DvFq::to_bits(pi1.y).into_iter()) + .collect(); + let fgc_output_labels = BABEProver::eval_circuit_with_ciphertext( + &mut fgc, + &fgc_indices, + &fgc_witness, + &instance.ciphertexts_sets[0], + 2 + ); + let ct1_bytes = BABEProver::eval_adaptor_table( + &fgc_output_labels, pi1, &instance.adaptor_tables[0] + ); + let mut expected_ct1_bytes = Vec::new(); + (pi1 * r).into_affine().serialize_compressed(&mut expected_ct1_bytes).expect("serialize r·G1P"); + assert_eq!(ct1_bytes, expected_ct1_bytes); + + + + + // // Simulate DSGC output: c1' = r·P_D + r·B + // // P_D = (a*a) · gamma_abc[|S|+1] = (a*a) · gamma_abc[2] + // let p_d = vk.gamma_abc_g1[2].into_group() * dynamic_inputs; + // let ct1_prime = (p_d * r + instance.secrets.b * r); + // let ctprove = WeKnownPi1ProveCt { + // ct1_r_pi1: g1_to_ser(proof.a.into_group() * r), + // ct1_prime: g1_to_ser(ct1_prime), + // }; + // let decrypted = we_known_pi1_dec( + // &vk, &instance.ct_setup, &ctprove, + // proof.b.into_group(), proof.c.into_group(), + // ).unwrap(); + // + // println!("decrypted done"); + // + // assert_eq!(decrypted.as_slice(), &instance.secrets.msg); } } diff --git a/verifiable-circuit-babe/src/prover.rs b/verifiable-circuit-babe/src/prover.rs index 2aee849..47b3185 100644 --- a/verifiable-circuit-babe/src/prover.rs +++ b/verifiable-circuit-babe/src/prover.rs @@ -5,25 +5,36 @@ use ark_ff::{One, Zero}; use ark_groth16::Proof as Groth16Proof; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use garbled_snark_verifier::bag::{Circuit, S}; -use garbled_snark_verifier::dv_bn254::fq::Fq; +use ark_groth16::ProvingKey as Groth16ProvingKey; +use garbled_snark_verifier::dv_bn254::fq::Fq as DvFq; +use garbled_snark_verifier::dv_bn254::fr::Fr as DvFr; use crate::babe::{WeKnownPi1ProveCt, WeKnownPi1SetupCt}; use crate::cac::{CACSetupPackage, FinalizedInstanceData}; use crate::dre::matrices::u_bar_vec; -use crate::gc::SparseAdaptorTable; +use crate::gc::{SparseAdaptorTable, SGC_PART1_CONSTANT_SIZE}; +use crate::instance::set_gc_const_labels; use crate::soldering::{SolderedLabelsData, SolderingData}; -use crate::utils::{derive_hashlock, h_160, h_256}; +use crate::utils::{derive_hashlock, h_160, h_256, ro_from_pairing_bytes}; pub struct BABEProver { groth16_proof: Groth16Proof, + dyn_pubin: ark_bn254::Fr, + pub pk: Groth16ProvingKey, pub valid_msg: Option<[u8; 32]>, pub valid_ct_prove: Option, pub valid_finalized_id: Option, } impl BABEProver { - pub fn new(groth16_proof: Groth16Proof) -> Self { + pub fn new( + pk: Groth16ProvingKey, + groth16_proof: Groth16Proof, + dyn_pubin: ark_bn254::Fr + ) -> Self { Self { groth16_proof, + dyn_pubin, + pk, valid_msg: None, valid_ct_prove: None, valid_finalized_id: None, @@ -85,9 +96,14 @@ impl BABEProver { let sld = &soldering.soldering_proof.soldered_output; println!("Trying base instance..."); - // todo: fix this, this is hard-coded as fgc labels + // todo: fix this, this is hard-coded as fgc labels and havent split to pi1 and xd labels let base_full = Self::build_full_labels(&finalized[0].constant_labels_0, base_input_labels); - let base_res = self.try_evaluate_instance(&finalized[0], &base_full, h_msgs_onchain[0]); + let base_res = self.try_evaluate_instance( + &finalized[0], + &base_full, + &base_full, + h_msgs_onchain[0] + ); match base_res { Ok(true) => return true, Ok(false) => eprintln!("Warning: base instance did not yield valid msg"), @@ -113,7 +129,12 @@ impl BABEProver { // todo: add sgc labels let full = Self::build_full_labels(&finalized[i].constant_labels_0, &instance_labels); - let temp = self.try_evaluate_instance(&finalized[i], &full, h_msgs_onchain[i]); + let temp = self.try_evaluate_instance( + &finalized[i], + &full, + &full, //todo: fix this to pi1 and x_d + h_msgs_onchain[i] + ); match temp { Ok(true) => return true, Ok(false) => eprintln!("Warning: non-base instance: {i} did not yield valid msg"), @@ -137,21 +158,28 @@ impl BABEProver { fn try_evaluate_instance( &mut self, data: &FinalizedInstanceData, - full_labels: &[S], + pi1_labels: &[S], + x_d_labels: &[S], h_msg_onchain: [u8; 20], ) -> Result { // Todo: add sgc - let (mut circuit, gc_output_indices, _, _) = crate::gc::read_fresh_gc(); + let (mut fgc, fgc_indices, mut sgc, sgc_indices) = crate::gc::read_fresh_gc(); let ct_prove = self.compute_ct_prove( - &mut circuit, - &gc_output_indices, - full_labels, - &data.gc_ciphertexts[0], - &data.adaptor_tables[0], + &mut fgc, + &fgc_indices, + &mut sgc, + &sgc_indices, + &[data.constant_labels_0.to_vec(), data.constant_labels_1.to_vec()], + &pi1_labels, + &x_d_labels, + &data.ciphertext_sets, + &data.adaptor_tables, + &data.b, ); - drop(circuit); + drop(fgc); + drop(sgc); - let msg = Self::compute_msg(&self.groth16_proof, &ct_prove, &data.ct_setup)?; + let msg = Self::compute_msg(&self.groth16_proof, &ct_prove, &data.ct_setup, &self.pk.vk)?; // found the valid one. if derive_hashlock(&msg) == h_msg_onchain { self.valid_msg = Some(msg); @@ -166,36 +194,119 @@ impl BABEProver { /// Garble-evaluate the circuit and return the resulting `ct1 = r·π₁`. pub fn compute_ct_prove( &self, - garbled_circuit: &mut Circuit, - gc_output_indices: &[usize], - input_labels: &[S], - ciphertext: &[Option], - adaptor_table: &SparseAdaptorTable, + fgc: &mut Circuit, + fgc_indices: &[usize], + sgc: &mut Circuit, + sgc_indices: &[usize], + const_labels: &[Vec; 2], + p1_labels: &[S], + x_d_labels: &[S], + ciphertext_sets: &[Vec>; 3], + adaptor_tables: &[SparseAdaptorTable; 2], + b: &ark_bn254::G1Affine, ) -> WeKnownPi1ProveCt { let pi1 = self.groth16_proof.a; - for (i, &lbl) in input_labels.iter().enumerate() { - garbled_circuit.0[i].borrow_mut().label = Some(lbl); + let x_d = self.dyn_pubin; + + // --------Compute ct1-------------- + set_gc_const_labels(fgc, &const_labels[0]); + for (i, &lbl) in p1_labels.iter().enumerate() { + fgc.0[i + 2].borrow_mut().label = Some(lbl); } + let fgc_witness: Vec = DvFq::to_bits(pi1.x) + .into_iter() + .chain(DvFq::to_bits(pi1.y).into_iter()) + .collect(); + let fgc_output_labels = BABEProver::eval_circuit_with_ciphertext( + fgc, + fgc_indices, + &fgc_witness, + &ciphertext_sets[0], + 2 + ); + let ct1 = BABEProver::eval_adaptor_table( + &fgc_output_labels, pi1, &adaptor_tables[0] + ); - let witness: Vec = Fq::to_bits(pi1.x) + // --------Compute ct1'-------------- + // Part1: compute Q = x_d * L2 + B. + set_gc_const_labels(sgc, &const_labels[1]); + for (i, &lbl) in x_d_labels.iter().enumerate() { + sgc.0[i + SGC_PART1_CONSTANT_SIZE].borrow_mut().label = Some(lbl); + } + // Compute B bit representation (Montgomery form) for use as constant wires. + // Todo: move it to a function in instance.mod.rs + let b_x_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(b.x)); + let b_y_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(b.y)); + for (i, bit) in b_x_bits.iter().enumerate() { + sgc.0[2 + i].borrow_mut().value = Some(*bit); + } + for (i, bit) in b_y_bits.iter().enumerate() { + sgc.0[2 + 254 + i].borrow_mut().value = Some(*bit); + } + let sgc_part1_witness: Vec = DvFr::to_bits(x_d); + let sgc_output_labels_1 = BABEProver::eval_circuit_with_ciphertext( + sgc, + sgc_indices, + &sgc_part1_witness, + &ciphertext_sets[1], + SGC_PART1_CONSTANT_SIZE + ); + + // Part2: compute rQ + let q = self.pk.vk.gamma_abc_g1[2] * self.dyn_pubin + b; + let q_affine = q.into_affine(); + fgc.reset_circuit_except_constants(); + // set label of part2 as output of part1 (evaluator has one active label per wire) + for (i, &key) in sgc_output_labels_1.iter().enumerate() { + fgc.0[2 + i].borrow_mut().label = Some(S(key)); + } + set_gc_const_labels(fgc, &const_labels[1][0..2]); + let sgc_witness: Vec = DvFq::to_bits(q_affine.x) .into_iter() - .chain(Fq::to_bits(pi1.y).into_iter()) + .chain(DvFq::to_bits(q_affine.y).into_iter()) .collect(); + let sgc_output_labels_2 = BABEProver::eval_circuit_with_ciphertext( + fgc, + fgc_indices, + &sgc_witness, + &ciphertext_sets[2], + 2 + ); + let ct1_prime = BABEProver::eval_adaptor_table( + &sgc_output_labels_2, q_affine, &adaptor_tables[1] + ); + + WeKnownPi1ProveCt { ct1_r_pi1: ct1, ct1_prime } + } - garbled_circuit.set_witness_value(&witness, 2); - for gate in &mut garbled_circuit.1 { + pub(crate) fn eval_circuit_with_ciphertext( + circuit: &mut Circuit, + output_indices: &[usize], + witness: &[bool], + ciphertext: &[Option], + const_skip: usize, + ) -> Vec<[u8; 16]> { + circuit.set_witness_value(&witness, const_skip); + for gate in &mut circuit.1 { gate.evaluate(); } + circuit.garbled_evaluate_without_delta(&ciphertext); - garbled_circuit.garbled_evaluate_without_delta(ciphertext); - - let output_labels: Vec<[u8; 16]> = gc_output_indices + let output_labels: Vec<[u8; 16]> = output_indices .iter() - .map(|i| garbled_circuit.0[*i].borrow().get_label().0) + .map(|i| circuit.0[*i].borrow().get_label().0) .collect(); + output_labels + } - let u_bar = u_bar_vec(&pi1); - let decrypted_encodings = adaptor_table.eval(&output_labels, &u_bar); + pub(crate) fn eval_adaptor_table( + u_bar_labels: &[[u8; 16]], + u: G1Affine, + adaptor_table: &SparseAdaptorTable, + ) -> Vec { + let u_bar = u_bar_vec(&u); + let decrypted_encodings = adaptor_table.eval(u_bar_labels, &u_bar); let mut sum = G1Projective::zero(); let mut weight = ark_bn254::Fr::one(); @@ -205,9 +316,10 @@ impl BABEProver { weight += weight; } - let mut ct1 = Vec::new(); - sum.into_affine().serialize_compressed(&mut ct1).expect("serialize r·π₁"); - WeKnownPi1ProveCt { ct1_r_pi1: ct1 } + let mut ct = Vec::new(); + sum.into_affine().serialize_compressed(&mut ct).expect("serialize r·G1P"); + ct + } /// Decrypt the message from `ct_prove` and `ct_setup` using the Groth16 proof. @@ -215,19 +327,24 @@ impl BABEProver { proof: &Groth16Proof, ct_prove: &WeKnownPi1ProveCt, ct_setup: &WeKnownPi1SetupCt, + vk: &ark_groth16::VerifyingKey, ) -> Result<[u8; 32], String> { let ct1 = G1Affine::deserialize_compressed(ct_prove.ct1_r_pi1.as_slice()) .map_err(|e| format!("deserialize ct1: {e}"))? .into_group(); + let ct1_prime = G1Affine::deserialize_compressed(ct_prove.ct1_prime.as_slice()) + .map_err(|e| format!("deserialize ct1_prime: {e}"))?; let ct2 = G2Affine::deserialize_compressed(ct_setup.ct2_r_delta_g2.as_slice()) .map_err(|e| format!("deserialize ct2: {e}"))? .into_group(); let r_y = Bn254::pairing(ct1, proof.b) - Bn254::pairing(proof.c, ct2); + let q_blind = Bn254::pairing(ct1_prime, vk.gamma_g2); + let mask_gt = r_y - q_blind; - let mut ry_bytes = Vec::new(); - r_y.serialize_compressed(&mut ry_bytes).map_err(|e| format!("serialize r_y: {e}"))?; - let mask = h_256(&ry_bytes); + let mut mask_bytes = Vec::new(); + mask_gt.serialize_compressed(&mut mask_bytes).map_err(|e| format!("serialize mask_gt: {e}"))?; + let mask = ro_from_pairing_bytes(&mask_bytes, 32); let ct3 = &ct_setup.ct3_masked_msg; if ct3.len() != 32 { @@ -286,25 +403,43 @@ mod tests { ).unwrap(); // 3. Input labels: [pi1_xbits, pi1_ybits, x_dbits...]. - // Todo: input labels must contains x_d - let input_labels = verifier.compute_pi1_labels_based_on_value(proof.a); + let pi1_labels = verifier.compute_pi1_labels_based_on_value(proof.a); + let x_d_labels = verifier.compute_x_d_labels_based_on_value(dynamic_public_inputs); + let constant_labels = verifier.get_2_circuit_constant_labels(); // 4. Prover evaluates the garbled circuit. - let prover = BABEProver::new(proof.clone()); - // Todo: add sgc - let (mut circuit, gc_output_indices, _, _) = crate::gc::read_fresh_gc(); - let ct_prove = prover.compute_ct_prove( - &mut circuit, - &gc_output_indices, - &input_labels, - &verifier.ciphertexts_sets[0], - &verifier.adaptor_tables[0], + // Prover has: 2 circuits, input labels, constants labels & value + let prover = BABEProver::new( + pk, + proof.clone(), + dynamic_public_inputs ); - drop(circuit); + let (mut fgc, fgc_indices, mut sgc, sgc_indices) = crate::gc::read_fresh_gc(); + + // test inner compute_ct_prove + + - // 5. Decrypt and verify. - let msg = BABEProver::compute_msg(&proof, &ct_prove, &verifier.ct_setup).unwrap(); - assert_eq!(msg, verifier.secrets.msg); + + + // let ct_prove = prover.compute_ct_prove( + // &mut fgc, + // &fgc_indices, + // &mut sgc, + // &sgc_indices, + // &constant_labels, + // &pi1_labels, + // &x_d_labels, + // &verifier.ciphertexts_sets, + // &verifier.adaptor_tables, + // &verifier.secrets.b, + // ); + // drop(fgc); + // drop(sgc); + // + // // 5. Decrypt and verify. + // let msg = BABEProver::compute_msg(&proof, &ct_prove, &verifier.ct_setup, &vk).unwrap(); + // assert_eq!(msg, verifier.secrets.msg); } /// Build the common C&C + soldering scaffolding used by both tests below. @@ -379,7 +514,7 @@ mod tests { let all_labels = verifier.instances[base_idx].compute_pi1_labels_based_on_value(proof.a); let base_input_labels = &all_labels[2..]; // strip constant-wire labels - let mut prover = BABEProver::new(proof.clone()); + let mut prover = BABEProver::new(pk.clone(), proof.clone(), dynamic_public_inputs); // Extract h_msgs from bitcoin script of WronglyChallenged Txn // But in this test, we just get from package @@ -394,7 +529,7 @@ mod tests { assert!(prover.valid_finalized_id.is_some()); // change the base msg to access non-base instance - let mut prover = BABEProver::new(proof); + let mut prover = BABEProver::new(pk, proof, dynamic_public_inputs); h_msgs_onchain[0] = [0u8; 20]; let found = prover.check_compute_msg( &finalized, base_input_labels, &soldering, &h_msgs_onchain, @@ -405,4 +540,4 @@ mod tests { assert!(prover.valid_finalized_id.is_some()); assert_ne!(prover.valid_finalized_id.unwrap(), finalized_indices[0]); } -} \ No newline at end of file +} diff --git a/verifiable-circuit-babe/src/verifier.rs b/verifiable-circuit-babe/src/verifier.rs index 86f42a9..6618064 100644 --- a/verifiable-circuit-babe/src/verifier.rs +++ b/verifiable-circuit-babe/src/verifier.rs @@ -70,16 +70,16 @@ impl BABEVerifier { let constant_labels_0 = [ inst.secrets.constant_0labels[0][0], inst.secrets.constant_0labels[0][1] ^ inst.secrets.delta[0] ]; - + let mut constant_labels_1 = vec![ inst.secrets.constant_0labels[1][0], inst.secrets.constant_0labels[1][1] ^ inst.secrets.delta[1] ]; - constant_labels_1.extend(inst.b_value_labels()); - - + constant_labels_1.extend(inst.get_b_value_labels()); + + finalized.push(crate::cac::FinalizedInstanceData { index: i, - gc_ciphertexts: inst.ciphertexts_sets.clone(), + ciphertext_sets: inst.ciphertexts_sets.clone(), adaptor_tables: inst.adaptor_tables.clone(), ct_setup: inst.ct_setup.clone(), constant_labels_0, @@ -90,4 +90,4 @@ impl BABEVerifier { (opened, finalized) } -} \ No newline at end of file +} From d48a46f33c2647e2c5df21cf5882cf55f8aa75f5 Mon Sep 17 00:00:00 2001 From: vanhger Date: Tue, 28 Apr 2026 15:43:57 +0700 Subject: [PATCH 15/32] fix minor errors --- verifiable-circuit-babe/src/gc/circuit.rs | 24 +- .../src/instance/commit.rs | 21 +- verifiable-circuit-babe/src/instance/mod.rs | 219 +++++++++++------- .../src/instance/secret.rs | 2 +- 4 files changed, 164 insertions(+), 102 deletions(-) diff --git a/verifiable-circuit-babe/src/gc/circuit.rs b/verifiable-circuit-babe/src/gc/circuit.rs index 4bd4129..cf5e799 100644 --- a/verifiable-circuit-babe/src/gc/circuit.rs +++ b/verifiable-circuit-babe/src/gc/circuit.rs @@ -6,7 +6,7 @@ use garbled_snark_verifier::circuits::sect233k1::builder::{CircuitAdapter, Circu use garbled_snark_verifier::dv_bn254::basic::selector; use garbled_snark_verifier::dv_bn254::fp254impl::Fp254Impl; use garbled_snark_verifier::dv_bn254::{fq::Fq, fr::Fr}; -use garbled_snark_verifier::dv_bn254::g1::G1Projective as GcG1Projective; +use garbled_snark_verifier::dv_bn254::g1::{projective_to_affine_montgomery, G1Projective as GcG1Projective, G1Projective}; use crate::dre::{N, Q_SIZE, U_BAR_SIZE}; @@ -141,16 +141,22 @@ fn emit_scalar_mul_then_add( // Convert projective Montgomery (X·R, Y·R, Z·R) → standard affine (x, y) // x = X/Z, y = Y/Z via Fermat inversion of Z in the Montgomery domain - let x_m = &result_proj_m[..N]; - let y_m = &result_proj_m[N..2 * N]; - let z_m = &result_proj_m[2 * N..]; - let z_inv_m = fq_inverse_montgomery(bld, z_m); - let x_std = Fq::mul_montgomery(bld, x_m, &z_inv_m); - let y_std = Fq::mul_montgomery(bld, y_m, &z_inv_m); + let x_m: [usize; N] = result_proj_m[..N].try_into().unwrap(); + let y_m: [usize; N] = result_proj_m[N..2 * N].try_into().unwrap(); + let z_m: [usize; N] = result_proj_m[2 * N..].try_into().unwrap(); + let mont_res_proj = G1Projective { + x: Fq(x_m), + y: Fq(y_m), + z: Fq(z_m), + }; + let (mont_res_affine, is_valid) = projective_to_affine_montgomery(bld, &mont_res_proj); + // convert back to standard form + let x_q = Fq::mul_by_constant_montgomery(bld, &mont_res_affine.x.0, ark_bn254::Fq::from(1u64)); + let y_q = Fq::mul_by_constant_montgomery(bld, &mont_res_affine.y.0, ark_bn254::Fq::from(1u64)); let mut output = Vec::with_capacity(Q_SIZE); - output.extend_from_slice(&x_std); - output.extend_from_slice(&y_std); + output.extend_from_slice(&x_q); + output.extend_from_slice(&y_q); assert_eq!(output.len(), Q_SIZE); output } diff --git a/verifiable-circuit-babe/src/instance/commit.rs b/verifiable-circuit-babe/src/instance/commit.rs index ed18127..cce47dc 100644 --- a/verifiable-circuit-babe/src/instance/commit.rs +++ b/verifiable-circuit-babe/src/instance/commit.rs @@ -1,4 +1,3 @@ -use crate::babe::compute_epk_with_delta; use crate::gc::gc_ciphertexts_commit; use crate::instance::CACInstance; use crate::utils::{derive_hashlock, h_256}; @@ -23,14 +22,22 @@ pub struct CACInstanceCommit { } impl CACInstanceCommit { - // Todo: add x_d pub fn from_instance(instance: &CACInstance) -> Self { let delta = instance.secrets.delta; - let input_commits = compute_epk_with_delta( - &instance.secrets.encoding_keys, &delta - ).0; - + let input_commits = { + let fgc_pairs: Vec<[[u8; 20]; 2]> = instance.secrets.encoding_keys[0] + .iter() + .map(|&key| [derive_hashlock(&key.0), derive_hashlock(&(key ^ delta[0]).0)]) + .collect(); + let sgc_pairs: Vec<[[u8; 20]; 2]> = instance.secrets.encoding_keys[1] + .iter() + .map(|&key| [derive_hashlock(&key.0), derive_hashlock(&(key ^ delta[1]).0)]) + .collect(); + let pairs: Vec<[[u8; 20]; 2]> = fgc_pairs.into_iter().chain(sgc_pairs).collect(); + pairs + }; + let constant_commits_0 = std::array::from_fn(|w| { let l0 = instance.secrets.constant_0labels[0][w]; [h_256(&l0.0), h_256(&(l0 ^ delta[0]).0)] @@ -55,7 +62,7 @@ impl CACInstanceCommit { gc_ciphertexts_commit(&instance.ciphertexts_sets[0]), gc_ciphertexts_commit(&instance.ciphertexts_sets[1]), gc_ciphertexts_commit(&instance.ciphertexts_sets[2]), - ] + ] } } } diff --git a/verifiable-circuit-babe/src/instance/mod.rs b/verifiable-circuit-babe/src/instance/mod.rs index 1f46fdd..b3ed4bd 100644 --- a/verifiable-circuit-babe/src/instance/mod.rs +++ b/verifiable-circuit-babe/src/instance/mod.rs @@ -1,7 +1,7 @@ use ark_bn254::Fr; use ark_ec::{AffineRepr, CurveGroup}; use ark_ec::pairing::Pairing; -use ark_ff::UniformRand; +use ark_ff::{UniformRand, Zero}; use garbled_snark_verifier::bag::{Circuit, S}; use crate::babe::WeKnownPi1SetupCt; use crate::gc::{build_l2_table_bits, SparseAdaptorTable, SGC_PART1_CONSTANT_SIZE, WINDOW_ENTRIES}; @@ -91,36 +91,36 @@ impl CACInstance { ); assert_eq!(fgc_output_labels.len(), 2 * U_BAR_SIZE); println!("cac instance fgc done"); - // // Sgc - part 1 - // let sgc_part1_witness: Vec = DvFr::to_bits(x_d); - // let (sgc_ciphertext_1, sgc_output_labels_1) = get_ciphertext_and_output_labels( - // &mut sgc, - // &sgc_indices, - // &sgc_part1_witness, - // secrets.delta[1], - // SGC_PART1_CONSTANT_SIZE, - // ); - // println!("cac instance sgc part1 done"); - // assert_eq!(sgc_output_labels_1.len(), 2 * Q_SIZE); - // // Sgc - part 2 - // // Reuse the fgc structure, by setting up the input & constant labels again, then evaluate. - // fgc.reset_circuit_except_constants(); - // // set label of part2 as output of part1 - // for (i, &key) in sgc_output_labels_1.iter().step_by(2).enumerate() { - // fgc.0[2 + i].borrow_mut().label = Some(S(key)); - // } - // // set constant for part2 - // set_gc_const_labels(&mut fgc, &secrets.constant_0labels[1][0..2]); - // // random eval - // let (sgc_ciphertext_2, sgc_output_labels_2) = get_ciphertext_and_output_labels( - // &mut fgc, - // &fgc_indices, - // &fgc_witness, - // secrets.delta[1], - // 2 - // ); - // assert_eq!(sgc_output_labels_2.len(), 2 * U_BAR_SIZE); - // println!("cac instance sgc part 2 done"); + // Sgc - part 1 + let sgc_part1_witness: Vec = DvFr::to_bits(x_d); + let (sgc_ciphertext_1, sgc_output_labels_1) = get_ciphertext_and_output_labels( + &mut sgc, + &sgc_indices, + &sgc_part1_witness, + secrets.delta[1], + SGC_PART1_CONSTANT_SIZE, + ); + println!("cac instance sgc part1 done"); + assert_eq!(sgc_output_labels_1.len(), 2 * Q_SIZE); + // Sgc - part 2 + // Reuse the fgc structure, by setting up the input & constant labels again, then evaluate. + fgc.reset_circuit_except_constants(); + // set label of part2 as output of part1 + for (i, &key) in sgc_output_labels_1.iter().step_by(2).enumerate() { + fgc.0[2 + i].borrow_mut().label = Some(S(key)); + } + // set constant for part2 + set_gc_const_labels(&mut fgc, &secrets.constant_0labels[1][0..2]); + // random eval + let (sgc_ciphertext_2, sgc_output_labels_2) = get_ciphertext_and_output_labels( + &mut fgc, + &fgc_indices, + &fgc_witness, + secrets.delta[1], + 2 + ); + assert_eq!(sgc_output_labels_2.len(), 2 * U_BAR_SIZE); + println!("cac instance sgc part 2 done"); // generate adaptor table // fgc let fgc_adaptor_table = SparseAdaptorTable::build_from_r_and_u_bar_labels( @@ -130,33 +130,31 @@ impl CACInstance { &secrets.fq_deltas[0], ); - // test table - - // size = gc_output_indices - let output_labels: Vec<[u8; 16]> = fgc_indices - .iter() - .map(|idx| { - let label_0 = fgc.0[*idx] - .borrow() - .label.unwrap().0; - label_0 - }) - .collect(); + // // test table + // + // // size = gc_output_indices + // let output_labels: Vec<[u8; 16]> = fgc_indices + // .iter() + // .map(|idx| { + // let w = fgc.0[*idx].borrow(); + // w.select_with_delta(w.get_value(), secrets.delta[0]).0 + // }) + // .collect(); + // + // let ct1_bytes = BABEProver::eval_adaptor_table( + // &output_labels, pi1, &fgc_adaptor_table + // ); + // let mut expected_ct1_bytes = Vec::new(); + // (pi1 * secrets.r).into_affine().serialize_compressed(&mut expected_ct1_bytes).expect("serialize r·G1P"); + // assert_eq!(ct1_bytes, expected_ct1_bytes); + // println!("eval correctly"); - let ct1_bytes = BABEProver::eval_adaptor_table( - &output_labels, pi1, &fgc_adaptor_table + let sgc_adaptor_table = SparseAdaptorTable::build_from_r_and_u_bar_labels( + secrets.r, + &sgc_output_labels_2, + &secrets.rhos[1], + &secrets.fq_deltas[1], ); - let mut expected_ct1_bytes = Vec::new(); - (pi1 * secrets.r).into_affine().serialize_compressed(&mut expected_ct1_bytes).expect("serialize r·G1P"); - assert_eq!(ct1_bytes, expected_ct1_bytes); - println!("eval correctly"); - - // let sgc_adaptor_table = SparseAdaptorTable::build_from_r_and_u_bar_labels( - // secrets.r, - // &sgc_output_labels_2, - // &secrets.rhos[1], - // &secrets.fq_deltas[1], - // ); let ct_setup = Self::enc_setup( &secrets, @@ -170,10 +168,10 @@ impl CACInstance { seed, secrets, ct_setup, - // adaptor_tables: [fgc_adaptor_table, sgc_adaptor_table], - adaptor_tables: [fgc_adaptor_table.clone(), fgc_adaptor_table], - // ciphertexts_sets: [fgc_ciphertext, sgc_ciphertext_1, sgc_ciphertext_2], - ciphertexts_sets: [fgc_ciphertext.clone(), fgc_ciphertext.clone(), fgc_ciphertext], + adaptor_tables: [fgc_adaptor_table, sgc_adaptor_table], + // adaptor_tables: [fgc_adaptor_table.clone(), fgc_adaptor_table], + ciphertexts_sets: [fgc_ciphertext, sgc_ciphertext_1, sgc_ciphertext_2], + // ciphertexts_sets: [fgc_ciphertext.clone(), fgc_ciphertext.clone(), fgc_ciphertext], }) } @@ -307,10 +305,8 @@ fn get_ciphertext_and_output_labels( let output_labels: Vec<[u8; 16]> = output_indices .iter() .flat_map(|idx| { - let label_0 = circuit.0[*idx] - .borrow() - .select_with_delta(circuit.0[*idx].borrow().get_value(), delta); - [label_0.0, (label_0 ^ delta).0] + let l0 = circuit.0[*idx].borrow().label.unwrap(); + [l0.0, (l0 ^ delta).0] }) .collect(); @@ -322,6 +318,7 @@ mod tests { use super::*; use ark_crypto_primitives::snark::{CircuitSpecificSetupSNARK, SNARK}; use rand::SeedableRng; + use garbled_snark_verifier::circuits::bn254::g1::G1Affine; use crate::babe::DummyMulCircuit; use crate::prover::BABEProver; @@ -354,12 +351,12 @@ mod tests { let r = instance.secrets.r; let b_blind = instance.secrets.b; let pi1 = proof.a; - - // evaluate the fgc to get the r * pi_1 let constant_labels = instance.get_2_circuit_constant_labels(); let pi1_labels = instance.compute_pi1_labels_based_on_value(proof.a); - // let xd_labels = instance.compute_x_d_labels_based_on_value(dynamic_inputs); + let x_d_labels = instance.compute_x_d_labels_based_on_value(dynamic_inputs); let (mut fgc, fgc_indices, mut sgc, sgc_indices) = crate::gc::read_fresh_gc(); + + // evaluate the fgc to get the r * pi_1 set_gc_const_labels(&mut fgc, &constant_labels[0]); for (i, &lbl) in pi1_labels.iter().enumerate() { fgc.0[i + 2].borrow_mut().label = Some(lbl); @@ -380,26 +377,78 @@ mod tests { ); let mut expected_ct1_bytes = Vec::new(); (pi1 * r).into_affine().serialize_compressed(&mut expected_ct1_bytes).expect("serialize r·G1P"); - assert_eq!(ct1_bytes, expected_ct1_bytes); - + assert_eq!(ct1_bytes, expected_ct1_bytes, "fgc is wrong"); + println!("fgc test done"); + // evaluate the sgc part 1 to get the Q + set_gc_const_labels(&mut sgc, &constant_labels[1]); + for (i, &lbl) in x_d_labels.iter().enumerate() { + sgc.0[i + SGC_PART1_CONSTANT_SIZE].borrow_mut().label = Some(lbl); + } + let b_x_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(b_blind.x)); + let b_y_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(b_blind.y)); + for (i, bit) in b_x_bits.iter().enumerate() { + sgc.0[2 + i].borrow_mut().value = Some(*bit); + } + for (i, bit) in b_y_bits.iter().enumerate() { + sgc.0[2 + 254 + i].borrow_mut().value = Some(*bit); + } + let sgc_part1_witness: Vec = DvFr::to_bits(dynamic_inputs); + let sgc_output_labels_1 = BABEProver::eval_circuit_with_ciphertext( + &mut sgc, + &sgc_indices, + &sgc_part1_witness, + &instance.ciphertexts_sets[1], + SGC_PART1_CONSTANT_SIZE + ); + // check that sgc computed correctly + let q_proj = vk.gamma_abc_g1[2].into_group() * dynamic_inputs + b_blind; + let q_affine = q_proj.into_affine(); + let q_value_bits = G1Affine::to_bits(q_affine); + for (k, &idx) in sgc_indices.iter().enumerate() { + let w_val = sgc.0[idx].borrow().get_value(); + let q_val = q_value_bits[k]; + assert_eq!(w_val, q_val, "sgc part 1: mismatch at k={k}: wire={w_val}, q_val={q_val}"); + } + println!("sgc part 1 test done"); + // evaluate the sgc part2 to get the r * Q + fgc.reset_circuit_except_constants(); + // set label of part2 as output of part1 (evaluator has one active label per wire) + for (i, &key) in sgc_output_labels_1.iter().enumerate() { + fgc.0[2 + i].borrow_mut().label = Some(S(key)); + } + set_gc_const_labels(&mut fgc, &constant_labels[1][0..2]); + let sgc_witness: Vec = DvFq::to_bits(q_affine.x) + .into_iter() + .chain(DvFq::to_bits(q_affine.y).into_iter()) + .collect(); + let sgc_output_labels_2 = BABEProver::eval_circuit_with_ciphertext( + &mut fgc, + &fgc_indices, + &sgc_witness, + &instance.ciphertexts_sets[2], + 2 + ); + let ct1_prime = BABEProver::eval_adaptor_table( + &sgc_output_labels_2, q_affine, &instance.adaptor_tables[1] + ); - // // Simulate DSGC output: c1' = r·P_D + r·B - // // P_D = (a*a) · gamma_abc[|S|+1] = (a*a) · gamma_abc[2] - // let p_d = vk.gamma_abc_g1[2].into_group() * dynamic_inputs; - // let ct1_prime = (p_d * r + instance.secrets.b * r); - // let ctprove = WeKnownPi1ProveCt { - // ct1_r_pi1: g1_to_ser(proof.a.into_group() * r), - // ct1_prime: g1_to_ser(ct1_prime), - // }; - // let decrypted = we_known_pi1_dec( - // &vk, &instance.ct_setup, &ctprove, - // proof.b.into_group(), proof.c.into_group(), - // ).unwrap(); - // - // println!("decrypted done"); - // - // assert_eq!(decrypted.as_slice(), &instance.secrets.msg); + let mut expected_ct1_prime_bytes = Vec::new(); + (q_affine * r).into_affine().serialize_compressed(&mut expected_ct1_prime_bytes).expect("serialize r·G1P"); + assert_eq!(ct1_bytes, expected_ct1_bytes, "sgc part2 is wrong"); + println!("sgc part 2 test done"); + + let ctprove = WeKnownPi1ProveCt { + ct1_r_pi1: ct1_bytes, + ct1_prime, + }; + let decrypted = we_known_pi1_dec( + &vk, &instance.ct_setup, &ctprove, + proof.b.into_group(), proof.c.into_group(), + ).unwrap(); + + println!("decrypted done"); + assert_eq!(decrypted.as_slice(), &instance.secrets.msg); } } diff --git a/verifiable-circuit-babe/src/instance/secret.rs b/verifiable-circuit-babe/src/instance/secret.rs index d84914f..9356430 100644 --- a/verifiable-circuit-babe/src/instance/secret.rs +++ b/verifiable-circuit-babe/src/instance/secret.rs @@ -37,7 +37,7 @@ impl InstanceSecrets { let encoding_keys = [gen_s(&mut rng, 2 * N), gen_s(&mut rng, N)]; - let constant_val_labels = [gen_s(&mut rng, N), gen_s(&mut rng, 2 + 2 * N)]; + let constant_val_labels = [gen_s(&mut rng, 2), gen_s(&mut rng, 2 + 2 * N)]; let rhos = [sample_rhos(&mut rng), sample_rhos(&mut rng)]; From 813c40fb4f14f78104c18b311e0712cdb2eccf73 Mon Sep 17 00:00:00 2001 From: vanhger Date: Wed, 29 Apr 2026 08:38:24 +0700 Subject: [PATCH 16/32] feat: add b_blind in instance commitment && remove unused guests. --- babe-programs/aes-enc/guest/Cargo.toml | 10 - babe-programs/aes-enc/guest/src/main.rs | 21 -- babe-programs/aes-enc/host/Cargo.toml | 14 -- babe-programs/aes-enc/host/build.rs | 3 - babe-programs/aes-enc/host/src/main.rs | 32 --- babe-programs/enc-setup/guest/Cargo.toml | 13 -- babe-programs/enc-setup/guest/src/main.rs | 155 ------------- babe-programs/enc-setup/host/Cargo.toml | 22 -- babe-programs/enc-setup/host/build.rs | 3 - babe-programs/enc-setup/host/src/main.rs | 211 ------------------ .../garbled-circuit/guest/Cargo.toml | 15 -- .../garbled-circuit/guest/src/main.rs | 22 -- babe-programs/garbled-circuit/host/Cargo.toml | 23 -- babe-programs/garbled-circuit/host/build.rs | 3 - .../garbled-circuit/host/src/main.rs | 144 ------------ .../garbled-circuit/host/src/mem_fs.rs | 210 ----------------- .../garbled-circuit/host/src/utils.rs | 163 -------------- verifiable-circuit-babe/src/cac.rs | 8 +- .../src/instance/commit.rs | 9 + verifiable-circuit-babe/src/prover.rs | 44 ++-- 20 files changed, 34 insertions(+), 1091 deletions(-) delete mode 100644 babe-programs/aes-enc/guest/Cargo.toml delete mode 100644 babe-programs/aes-enc/guest/src/main.rs delete mode 100644 babe-programs/aes-enc/host/Cargo.toml delete mode 100644 babe-programs/aes-enc/host/build.rs delete mode 100644 babe-programs/aes-enc/host/src/main.rs delete mode 100644 babe-programs/enc-setup/guest/Cargo.toml delete mode 100644 babe-programs/enc-setup/guest/src/main.rs delete mode 100644 babe-programs/enc-setup/host/Cargo.toml delete mode 100644 babe-programs/enc-setup/host/build.rs delete mode 100644 babe-programs/enc-setup/host/src/main.rs delete mode 100644 babe-programs/garbled-circuit/guest/Cargo.toml delete mode 100644 babe-programs/garbled-circuit/guest/src/main.rs delete mode 100644 babe-programs/garbled-circuit/host/Cargo.toml delete mode 100644 babe-programs/garbled-circuit/host/build.rs delete mode 100644 babe-programs/garbled-circuit/host/src/main.rs delete mode 100644 babe-programs/garbled-circuit/host/src/mem_fs.rs delete mode 100644 babe-programs/garbled-circuit/host/src/utils.rs diff --git a/babe-programs/aes-enc/guest/Cargo.toml b/babe-programs/aes-enc/guest/Cargo.toml deleted file mode 100644 index c70a4e3..0000000 --- a/babe-programs/aes-enc/guest/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "aes-guest" -version.workspace = true -edition.workspace = true - -[dependencies] -zkm-zkvm = { workspace = true, features = ["embedded"] } -garbled-snark-verifier = { workspace = true } -verifiable-circuit-babe = { workspace = true } -ark-bn254 = { version = "0.5.0", features = ["curve", "scalar_field"], default-features = false } diff --git a/babe-programs/aes-enc/guest/src/main.rs b/babe-programs/aes-enc/guest/src/main.rs deleted file mode 100644 index 6e86abd..0000000 --- a/babe-programs/aes-enc/guest/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! A simple program that takes a number `n` as input, and writes the `n-1`th and `n`th fibonacci -//! number as an output. - -// These two lines are necessary for the program to properly compile. -// -// Under the hood, we wrap your main function with some extra code so that it behaves properly -// inside the zkVM. -#![no_std] -#![no_main] -extern crate alloc; - -zkm_zkvm::entrypoint!(main); -use garbled_snark_verifier::bag::S; -use verifiable_circuit_babe::gc::utils::aes_enc; - -fn main() { - let tmp = ark_bn254::Fq::from(20_u64); - let key = S::from_slice(&[0u8; 16]); - let res = aes_enc(&tmp, &key.0); - zkm_zkvm::io::commit::<[u8; 32]>(&res); -} diff --git a/babe-programs/aes-enc/host/Cargo.toml b/babe-programs/aes-enc/host/Cargo.toml deleted file mode 100644 index f8ed859..0000000 --- a/babe-programs/aes-enc/host/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "aes-host" -version.workspace = true -edition.workspace = true - -[dependencies] -zkm-sdk = { workspace = true } -garbled-snark-verifier = { workspace = true } -verifiable-circuit-babe = { workspace = true } -aes = "0.8" -ark-bn254 = { version = "0.5.0", features = ["curve", "scalar_field"], default-features = false } - -[build-dependencies] -zkm-build = { workspace = true } \ No newline at end of file diff --git a/babe-programs/aes-enc/host/build.rs b/babe-programs/aes-enc/host/build.rs deleted file mode 100644 index e7ea36a..0000000 --- a/babe-programs/aes-enc/host/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - zkm_build::build_program("../guest"); -} diff --git a/babe-programs/aes-enc/host/src/main.rs b/babe-programs/aes-enc/host/src/main.rs deleted file mode 100644 index e55f90a..0000000 --- a/babe-programs/aes-enc/host/src/main.rs +++ /dev/null @@ -1,32 +0,0 @@ -use zkm_sdk::{include_elf, utils, ProverClient, ZKMStdin}; -use garbled_snark_verifier::bag::S; - -const ELF: &[u8] = include_elf!("aes-guest"); - -fn main() { - utils::setup_logger(); - let stdin = ZKMStdin::new(); - - // Create a `ProverClient` method. - let client = ProverClient::new(); - - // Execute the program using the `ProverClient.execute` method, without generating a proof. - let (mut pub_val, report) = client.execute(ELF, &stdin).run().unwrap(); - println!("executed program with {} cycles", report.total_instruction_count()); - // Read and verify the output. - // - // Note that this output is read from values committed to in the program using - // `zkm_zkvm::io::commit`. - let public_input = pub_val.read::<[u8; 32]>(); - println!("public input: {:?}", public_input); - - // // Generate the proof for the given program and input. - let (pk, vk) = client.setup(ELF); - let mut proof = client.prove(&pk, stdin).run().unwrap(); - println!("generated proof"); - - let tmp = ark_bn254::Fq::from(20_u64); - let key = &[0u8; 16]; - let out = verifiable_circuit_babe::gc::utils::aes_enc(&tmp, &key); - println!("expected output: {:?}", out); -} diff --git a/babe-programs/enc-setup/guest/Cargo.toml b/babe-programs/enc-setup/guest/Cargo.toml deleted file mode 100644 index 8aa3f3d..0000000 --- a/babe-programs/enc-setup/guest/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "enc-setup-guest" -version.workspace = true -edition.workspace = true - -[dependencies] -zkm-zkvm = { workspace = true, features = ["embedded"] } -ark-bn254 = { version = "0.5.0", features = ["curve", "scalar_field"], default-features = false } -ark-ff = { version = "0.5.0", default-features = false } -ark-ec = { version = "0.5.0", default-features = false } -ark-serialize = { version = "0.5.0", default-features = false, features = ["derive"] } -sha2 = { workspace = true } -blake3 = { version = "1.6.1", default-features = false } \ No newline at end of file diff --git a/babe-programs/enc-setup/guest/src/main.rs b/babe-programs/enc-setup/guest/src/main.rs deleted file mode 100644 index 94f86d5..0000000 --- a/babe-programs/enc-setup/guest/src/main.rs +++ /dev/null @@ -1,155 +0,0 @@ -// Guest program: proves enc_setup of the BABE verifier. -// -// Proves that ct_setup = (ct2, ct3) was correctly computed as: -// ct2 = r · [delta]_2 -// rY = e(alpha_g1, r·beta_g2) + e(vk_x, r·gamma_g2) (additive GT notation) -// ct3 = blake3(rY_bytes) ⊕ msg -// -// Public outputs committed: -// ct2_bytes – compressed G2 encoding of r·delta_g2 -// ct3 – 32-byte masked message -// h_msg – SHA-256(msg) (hashlock) -// h_vk – SHA-256(raw VK bytes) -// h_public_inputs – SHA-256(raw public-input bytes) -#![no_std] -#![no_main] -extern crate alloc; - -use alloc::vec::Vec; - -zkm_zkvm::entrypoint!(main); - -use ark_bn254::{Bn254, Fq, Fq2, Fr, G1Affine, G1Projective, G2Affine}; -use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; -use ark_ff::PrimeField; -use ark_serialize::CanonicalSerialize; -use sha2::{Digest, Sha256}; - -// ── Field / curve helpers ───────────────────────────────────────────────────── - -fn fq_from_le(b: &[u8; 32]) -> Fq { - Fq::from_le_bytes_mod_order(b) -} - -fn fr_from_le(b: &[u8; 32]) -> Fr { - Fr::from_le_bytes_mod_order(b) -} - -/// Deserialize an uncompressed G1Affine from 64 raw LE bytes in a slice: x‖y. -fn g1_from_slice(b: &[u8]) -> G1Affine { - assert_eq!(b.len(), 64); - let x = fq_from_le(b[..32].try_into().unwrap()); - let y = fq_from_le(b[32..].try_into().unwrap()); - G1Affine::new_unchecked(x, y) -} - -/// Deserialize an uncompressed G2Affine from 128 raw LE bytes: x.c0‖x.c1‖y.c0‖y.c1. -fn g2_from_slice(b: &[u8]) -> G2Affine { - assert_eq!(b.len(), 128); - let x_c0 = fq_from_le(b[0..32].try_into().unwrap()); - let x_c1 = fq_from_le(b[32..64].try_into().unwrap()); - let y_c0 = fq_from_le(b[64..96].try_into().unwrap()); - let y_c1 = fq_from_le(b[96..128].try_into().unwrap()); - G2Affine::new_unchecked(Fq2::new(x_c0, x_c1), Fq2::new(y_c0, y_c1)) -} - -/// Random-oracle: blake3 hash of bytes → 32-byte mask (matches verifiable_circuit_babe::babe::h). -fn h(data: &[u8]) -> [u8; 32] { - *blake3::hash(data).as_bytes() -} - -// ── Main ────────────────────────────────────────────────────────────────────── - -fn main() { - // ── 1. Read private witnesses ───────────────────────────────────────────── - let r_bytes = zkm_zkvm::io::read::<[u8; 32]>(); - let msg = zkm_zkvm::io::read::<[u8; 32]>(); - - // ── 2. Read VK components as flat byte vectors ──────────────────────────── - // G1Affine = 64 bytes (x ‖ y, each 32-byte Fq LE) - // G2Affine = 128 bytes (x.c0 ‖ x.c1 ‖ y.c0 ‖ y.c1, each 32-byte Fq LE) - let alpha_g1_raw = zkm_zkvm::io::read_vec(); // 64 bytes - let beta_g2_raw = zkm_zkvm::io::read_vec(); // 128 bytes - let gamma_g2_raw = zkm_zkvm::io::read_vec(); // 128 bytes - let delta_g2_raw = zkm_zkvm::io::read_vec(); // 128 bytes - // gamma_abc_g1: flat array, n_pts × 64 bytes - let gamma_abc_raw = zkm_zkvm::io::read_vec(); // (n+1) * 64 bytes - - // ── 3. Read public inputs ───────────────────────────────────────────────── - // Each Fr is 32 bytes LE; total = n_inputs × 32 bytes - let public_inputs_raw = zkm_zkvm::io::read_vec(); // n * 32 bytes - - // ── 4. Reconstruct curve points and scalars ─────────────────────────────── - let r = fr_from_le(&r_bytes); - let alpha_g1 = g1_from_slice(&alpha_g1_raw); - let beta_g2 = g2_from_slice(&beta_g2_raw); - let gamma_g2 = g2_from_slice(&gamma_g2_raw); - let delta_g2 = g2_from_slice(&delta_g2_raw); - - assert_eq!(gamma_abc_raw.len() % 64, 0); - let n_abc = gamma_abc_raw.len() / 64; - let gamma_abc_g1: Vec = (0..n_abc) - .map(|i| g1_from_slice(&gamma_abc_raw[i * 64..(i + 1) * 64])) - .collect(); - - assert_eq!(public_inputs_raw.len() % 32, 0); - let n_inputs = public_inputs_raw.len() / 32; - let public_inputs: Vec = (0..n_inputs) - .map(|i| fr_from_le(public_inputs_raw[i * 32..(i + 1) * 32].try_into().unwrap())) - .collect(); - - // ── 5. Compute vk_x = gamma_abc_g1[0] + Σ gamma_abc_g1[i+1] · inp[i] ──── - assert_eq!(gamma_abc_g1.len(), public_inputs.len() + 1); - let mut vk_x = gamma_abc_g1[0].into_group(); - for (i, inp) in public_inputs.iter().enumerate() { - vk_x += gamma_abc_g1[i + 1].into_group() * *inp; - } - - // ── 6. Compute ct2 = r · delta_g2 (one G2 scalar mult, unavoidable) ────── - let r_delta = (delta_g2.into_group() * r).into_affine(); - let mut ct2_bytes = Vec::new(); - r_delta.serialize_compressed(&mut ct2_bytes).unwrap(); - - // ── 7. Compute rY via G1 scalar mults + one multi-pairing ──────────────── - // - // By bilinearity: e(alpha, r·beta) = e(r·alpha, beta) - // e(vk_x, r·gamma) = e(r·vk_x, gamma) - // - // This replaces 2 expensive G2 scalar mults (over Fq²) with 2 cheap G1 - // scalar mults (over Fq), and uses multi_pairing so the costly final - // exponentiation is computed only once instead of twice. - let g1_proj = [alpha_g1.into_group() * r, vk_x * r]; - // Batch-normalize: one shared field inversion instead of two. - let g1_aff = G1Projective::normalize_batch(&g1_proj); - let r_y = Bn254::multi_pairing(g1_aff, [beta_g2, gamma_g2]); - - // ── 8. Compute ct3 = blake3(rY) ⊕ msg ──────────────────────────────────── - let mut ry_bytes = Vec::new(); - r_y.serialize_compressed(&mut ry_bytes).unwrap(); - let mask = h(&ry_bytes); - let ct3: [u8; 32] = core::array::from_fn(|i| msg[i] ^ mask[i]); - - // ── 9. Compute public commitments ───────────────────────────────────────── - - // h_msg = SHA-256(msg) — hashlock value posted on Bitcoin - let h_msg: [u8; 32] = Sha256::digest(&msg).into(); - - // h_vk = SHA-256(all VK raw bytes in canonical order) - let mut vk_hasher = Sha256::new(); - vk_hasher.update(&alpha_g1_raw); - vk_hasher.update(&beta_g2_raw); - vk_hasher.update(&gamma_g2_raw); - vk_hasher.update(&delta_g2_raw); - vk_hasher.update(&gamma_abc_raw); - let h_vk: [u8; 32] = vk_hasher.finalize().into(); - - // h_public_inputs = SHA-256(concatenated raw Fr bytes) - let h_public_inputs: [u8; 32] = Sha256::digest(&public_inputs_raw).into(); - - // ── 10. Commit public outputs ───────────────────────────────────────────── - zkm_zkvm::io::commit::>(&ct2_bytes); - zkm_zkvm::io::commit::<[u8; 32]>(&ct3); - zkm_zkvm::io::commit::<[u8; 32]>(&h_msg); - zkm_zkvm::io::commit::<[u8; 32]>(&h_vk); - zkm_zkvm::io::commit::<[u8; 32]>(&h_public_inputs); -} \ No newline at end of file diff --git a/babe-programs/enc-setup/host/Cargo.toml b/babe-programs/enc-setup/host/Cargo.toml deleted file mode 100644 index 3754af8..0000000 --- a/babe-programs/enc-setup/host/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "enc-setup-host" -version.workspace = true -edition.workspace = true - -[dependencies] -zkm-sdk = { workspace = true } -verifiable-circuit-babe = { workspace = true } -ark-bn254 = { version = "0.5.0", features = ["curve", "scalar_field"], default-features = false } -ark-ff = { version = "0.5.0", default-features = false } -ark-ec = { version = "0.5.0", default-features = false } -ark-serialize = { version = "0.5.0" } -ark-groth16 = { version = "0.5.0" } -ark-crypto-primitives = { version = "0.5.0" } -ark-relations = { version = "0.5.0" } -sha2 = { workspace = true } -rand = "0.8" -hex = "0.4" -tracing = "0.1" - -[build-dependencies] -zkm-build = { workspace = true } \ No newline at end of file diff --git a/babe-programs/enc-setup/host/build.rs b/babe-programs/enc-setup/host/build.rs deleted file mode 100644 index 032c1d8..0000000 --- a/babe-programs/enc-setup/host/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - zkm_build::build_program("../guest"); -} \ No newline at end of file diff --git a/babe-programs/enc-setup/host/src/main.rs b/babe-programs/enc-setup/host/src/main.rs deleted file mode 100644 index 3e7a7c4..0000000 --- a/babe-programs/enc-setup/host/src/main.rs +++ /dev/null @@ -1,211 +0,0 @@ -// Host program: prepares enc_setup inputs, executes the guest inside the ZKM, -// and verifies the committed outputs match the natively computed values. -// -// Circuit being proved: enc_setup of the BABE verifier. -// ct2 = r · [delta]_2 -// rY = e(alpha_g1, r·beta_g2) + e(vk_x, r·gamma_g2) -// ct3 = blake3(rY) ⊕ msg -// -// Public outputs (committed by guest): -// ct2_bytes, ct3, h_msg, h_vk, h_public_inputs - -use std::time::Instant; - -use ark_bn254::{Bn254, Fq, Fq2, Fr, G1Affine, G2Affine}; -use ark_crypto_primitives::snark::{CircuitSpecificSetupSNARK, SNARK}; -use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; -use ark_ff::{PrimeField, UniformRand}; -use ark_groth16::Groth16; -use ark_relations::lc; -use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; -use ark_serialize::CanonicalSerialize; -use rand::RngCore; -use sha2::{Digest, Sha256}; -use tracing::info; -use zkm_sdk::{include_elf, utils as sdk_utils, ProverClient, ZKMStdin}; -use verifiable_circuit_babe::babe::DummyMulCircuit; - -const ELF: &[u8] = include_elf!("enc-setup-guest"); - -// ── Serialization helpers (raw LE bytes, matching guest deserialization) ────── - -/// Serialize Fq as 32 LE bytes. -fn fq_to_le(fq: &Fq) -> [u8; 32] { - let mut out = [0u8; 32]; - fq.serialize_uncompressed(&mut out[..]).unwrap(); - out -} - -/// Serialize Fr as 32 LE bytes. -fn fr_to_le(fr: &Fr) -> [u8; 32] { - let mut out = [0u8; 32]; - fr.serialize_uncompressed(&mut out[..]).unwrap(); - out -} - -/// Serialize G1Affine as 64 raw LE bytes: x‖y. -fn g1_to_bytes(pt: &G1Affine) -> Vec { - let mut out = Vec::with_capacity(64); - out.extend_from_slice(&fq_to_le(&pt.x)); - out.extend_from_slice(&fq_to_le(&pt.y)); - out -} - -/// Serialize G2Affine as 128 raw LE bytes: x.c0‖x.c1‖y.c0‖y.c1. -fn g2_to_bytes(pt: &G2Affine) -> Vec { - let mut out = Vec::with_capacity(128); - out.extend_from_slice(&fq_to_le(&pt.x.c0)); - out.extend_from_slice(&fq_to_le(&pt.x.c1)); - out.extend_from_slice(&fq_to_le(&pt.y.c0)); - out.extend_from_slice(&fq_to_le(&pt.y.c1)); - out -} - -// ── Native enc_setup computation (for expected-value verification) ──────────── - -fn compute_enc_setup( - vk: &ark_groth16::VerifyingKey, - public_inputs: &[Fr], - r: Fr, - msg: &[u8; 32], -) -> (Vec, [u8; 32]) { - // vk_x = gamma_abc_g1[0] + Σ gamma_abc_g1[i+1] · public_inputs[i] - let mut vk_x = vk.gamma_abc_g1[0].into_group(); - for (i, inp) in public_inputs.iter().enumerate() { - vk_x += vk.gamma_abc_g1[i + 1].into_group() * *inp; - } - - // ct2 = r · delta_g2 (compressed G2 bytes) - let r_delta = (vk.delta_g2.into_group() * r).into_affine(); - let mut ct2_bytes = Vec::new(); - r_delta.serialize_compressed(&mut ct2_bytes).unwrap(); - - // rY = e(alpha_g1, r·beta_g2) + e(vk_x, r·gamma_g2) - let r_beta = (vk.beta_g2.into_group() * r).into_affine(); - let r_gamma = (vk.gamma_g2.into_group() * r).into_affine(); - let t1 = Bn254::pairing(vk.alpha_g1, r_beta); - let t2 = Bn254::pairing(vk_x.into_affine(), r_gamma); - let r_y = t1 + t2; - - // ct3 = blake3(rY) ⊕ msg - let mut ry_bytes = Vec::new(); - r_y.serialize_compressed(&mut ry_bytes).unwrap(); - let mask = verifiable_circuit_babe::babe::h_256(&ry_bytes); - let ct3: [u8; 32] = core::array::from_fn(|i| msg[i] ^ mask[i]); - - (ct2_bytes, ct3) -} - -// ── Main ────────────────────────────────────────────────────────────────────── - -fn main() { - sdk_utils::setup_logger(); - let mut rng = rand::thread_rng(); - - // ── 1. Groth16 setup: a × b = c ────────────────────────────────────────── - let start = Instant::now(); - let a = Fr::from(3u64); - let b = Fr::from(7u64); - let (_, vk) = Groth16::::setup( - DummyMulCircuit { a: Some(a), b: Some(b) }, - &mut rng, - ) - .expect("groth16 setup"); - let public_inputs = vec![a * b]; // c = 21 - info!(elapsed = ?start.elapsed(), "Groth16 setup"); - - // ── 2. Sample private witnesses ─────────────────────────────────────────── - let r = Fr::rand(&mut rng); - let mut msg = [0u8; 32]; - rng.fill_bytes(&mut msg); - - // ── 3. Native enc_setup (reference values for post-execution check) ─────── - let start = Instant::now(); - let (expected_ct2, expected_ct3) = compute_enc_setup(&vk, &public_inputs, r, &msg); - let expected_h_msg: [u8; 32] = Sha256::digest(&msg).into(); - info!(elapsed = ?start.elapsed(), "enc_setup computed natively"); - - // ── 4. Serialize VK components as flat byte vectors ─────────────────────── - let r_bytes = fr_to_le(&r); - let alpha_g1_raw = g1_to_bytes(&vk.alpha_g1); - let beta_g2_raw = g2_to_bytes(&vk.beta_g2); - let gamma_g2_raw = g2_to_bytes(&vk.gamma_g2); - let delta_g2_raw = g2_to_bytes(&vk.delta_g2); - - // gamma_abc_g1: flat array of (n+1) × 64 bytes - let gamma_abc_raw: Vec = vk.gamma_abc_g1 - .iter() - .flat_map(|pt| g1_to_bytes(pt)) - .collect(); - - // public inputs: flat array of n × 32 bytes - let public_inputs_raw: Vec = public_inputs - .iter() - .flat_map(|fr| fr_to_le(fr)) - .collect(); - - // ── 5. Compute expected commitments ─────────────────────────────────────── - let mut vk_hasher = Sha256::new(); - vk_hasher.update(&alpha_g1_raw); - vk_hasher.update(&beta_g2_raw); - vk_hasher.update(&gamma_g2_raw); - vk_hasher.update(&delta_g2_raw); - vk_hasher.update(&gamma_abc_raw); - let expected_h_vk: [u8; 32] = vk_hasher.finalize().into(); - - let expected_h_public_inputs: [u8; 32] = Sha256::digest(&public_inputs_raw).into(); - - // ── 6. Write inputs to ZKMStdin ─────────────────────────────────────────── - let mut stdin = ZKMStdin::new(); - - // private witnesses - stdin.write::<[u8; 32]>(&r_bytes); - stdin.write::<[u8; 32]>(&msg); - - // VK components (as flat Vec) - stdin.write_vec(alpha_g1_raw); - stdin.write_vec(beta_g2_raw); - stdin.write_vec(gamma_g2_raw); - stdin.write_vec(delta_g2_raw); - stdin.write_vec(gamma_abc_raw); - - // public inputs - stdin.write_vec(public_inputs_raw); - - // ── 7. Execute guest ────────────────────────────────────────────────────── - let client = ProverClient::new(); - let start = Instant::now(); - let (mut pub_val, report) = client.execute(ELF, &stdin).run().unwrap(); - info!( - elapsed = ?start.elapsed(), - cycles = report.total_instruction_count(), - "guest executed" - ); - - // ── 8. Read and verify committed outputs ────────────────────────────────── - let ct2_out = pub_val.read::>(); - let ct3_out = pub_val.read::<[u8; 32]>(); - let h_msg_out = pub_val.read::<[u8; 32]>(); - let h_vk_out = pub_val.read::<[u8; 32]>(); - let h_pi_out = pub_val.read::<[u8; 32]>(); - - assert_eq!(ct2_out, expected_ct2, "ct2 mismatch"); - assert_eq!(ct3_out, expected_ct3, "ct3 mismatch"); - assert_eq!(h_msg_out, expected_h_msg, "h_msg mismatch"); - assert_eq!(h_vk_out, expected_h_vk, "h_vk mismatch"); - assert_eq!(h_pi_out, expected_h_public_inputs, "h_public_inputs mismatch"); - - println!("ct2: {}", hex::encode(&ct2_out)); - println!("ct3: {}", hex::encode(ct3_out)); - println!("h_msg: {}", hex::encode(h_msg_out)); - println!("h_vk: {}", hex::encode(h_vk_out)); - println!("h_public_inputs: {}", hex::encode(h_pi_out)); - println!("all enc_setup outputs verified ✓"); - - // ── 9. (Optional) Generate ZK proof ────────────────────────────────────── - // let (pk, vk_proof) = client.setup(ELF); - // let proof = client.prove(&pk, stdin).compressed().run().unwrap(); - // client.verify(&proof, &vk_proof).expect("proof verification failed"); - // proof.save("proof.bin").expect("saving proof failed"); - // println!("proof verified ✓"); -} diff --git a/babe-programs/garbled-circuit/guest/Cargo.toml b/babe-programs/garbled-circuit/guest/Cargo.toml deleted file mode 100644 index ec30988..0000000 --- a/babe-programs/garbled-circuit/guest/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "garbled-circuit-guest" -version.workspace = true -edition.workspace = true - -[dependencies] -zkm-zkvm = { workspace = true, features = ["embedded"] } -garbled-snark-verifier = { workspace = true } - -[features] -sha2 = ["garbled-snark-verifier/garbled", "garbled-snark-verifier/_sha2", "garbled-snark-verifier/_getrandom"] -poseidon2 = ["garbled-snark-verifier/garbled", "garbled-snark-verifier/_poseidon2", "garbled-snark-verifier/_getrandom"] -blake3 = ["garbled-snark-verifier/garbled", "garbled-snark-verifier/_blake3", "garbled-snark-verifier/_getrandom"] -aes = ["garbled-snark-verifier/garbled", "garbled-snark-verifier/_aes", "garbled-snark-verifier/_getrandom"] -default = ["poseidon2"] diff --git a/babe-programs/garbled-circuit/guest/src/main.rs b/babe-programs/garbled-circuit/guest/src/main.rs deleted file mode 100644 index 974a87f..0000000 --- a/babe-programs/garbled-circuit/guest/src/main.rs +++ /dev/null @@ -1,22 +0,0 @@ -// Under the hood, we wrap your main function with some extra code so that it behaves properly -// inside the zkVM. -#![no_std] -#![no_main] -extern crate alloc; - -zkm_zkvm::entrypoint!(main); - -use alloc::vec::Vec; -use garbled_snark_verifier::core::utils::{check_guest, SUB_INPUT_GATES_PARTS}; -use zkm_zkvm::lib::boolean_circuit_garble::boolean_circuit_garble; -fn main() { - let mut sub_gates: [Vec; SUB_INPUT_GATES_PARTS] = core::array::from_fn(|_| Vec::new()); - for i in 0..SUB_INPUT_GATES_PARTS { - sub_gates[i] = zkm_zkvm::io::read_vec(); - } - let sub_wires = zkm_zkvm::io::read_vec(); - let sub_ciphertexts = zkm_zkvm::io::read_vec(); - let input = check_guest(&sub_gates, &sub_wires, &sub_ciphertexts); - let output = boolean_circuit_garble(&input); - assert!(output); -} diff --git a/babe-programs/garbled-circuit/host/Cargo.toml b/babe-programs/garbled-circuit/host/Cargo.toml deleted file mode 100644 index f074716..0000000 --- a/babe-programs/garbled-circuit/host/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "garbled-circuit-host" -version.workspace = true -edition.workspace = true - -[dependencies] -zkm-sdk = { workspace = true } -verifiable-circuit-babe = { workspace = true } -garbled-snark-verifier = { workspace = true } -verifiable-circuit-host = { workspace = true } -ark-bn254 = { version = "0.5.0", features = ["curve", "scalar_field"], default-features = false } -ark-ff = { version = "0.5.0", default-features = false } -ark-ec = { version = "0.5.0", default-features = false } -ark-serialize = { version = "0.5.0" } -sha2 = { workspace = true } -rand = "0.8" -hex = "0.4" -tracing = "0.1.44" -indexmap = "2.12.0" -bincode = "1.3.3" - -[build-dependencies] -zkm-build = { workspace = true } diff --git a/babe-programs/garbled-circuit/host/build.rs b/babe-programs/garbled-circuit/host/build.rs deleted file mode 100644 index e7ea36a..0000000 --- a/babe-programs/garbled-circuit/host/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - zkm_build::build_program("../guest"); -} diff --git a/babe-programs/garbled-circuit/host/src/main.rs b/babe-programs/garbled-circuit/host/src/main.rs deleted file mode 100644 index 6f2f7d7..0000000 --- a/babe-programs/garbled-circuit/host/src/main.rs +++ /dev/null @@ -1,144 +0,0 @@ -mod mem_fs; -mod utils; - -use std::time::Instant; -use ark_bn254::G1Affine; -use ark_ff::UniformRand; -use tracing::info; -use ark_ec::CurveGroup; - -use zkm_sdk::{ProverClient, ZKMProofWithPublicValues, ZKMStdin, include_elf, utils as sdk_utils}; - -use garbled_snark_verifier::dv_bn254::dv_snark::{dv_snark_verifier_bench_circuit}; -use garbled_snark_verifier::{bag::Circuit, dv_bn254::dv_ref::VerifierPayloadRef}; -use garbled_snark_verifier::core::utils::reset_gid; -use garbled_snark_verifier::dv_bn254::fq::Fq; -use verifiable_circuit_babe::gc::compile_dsgc; -use crate::utils::{gen_sub_circuits, SUB_CIRCUIT_MAX_GATES, SUB_INPUT_GATES_PARTS}; - -/// The ELF we want to execute inside the zkVM. -const ELF: &[u8] = include_elf!("garbled-circuit-guest"); - -fn random_g1_affine() -> G1Affine { - let mut rng = rand::thread_rng(); - ark_bn254::G1Projective::rand(&mut rng).into_affine() -} - -fn custom_babe_garbled_circuit() -> Circuit { - - let pi = random_g1_affine(); - let g = random_g1_affine(); - - let witness: Vec = Fq::to_bits(pi.x) - .into_iter() - .chain(Fq::to_bits(pi.y).into_iter()) - .collect(); - info!("gen witness"); - - // build circuit - let start = Instant::now(); - reset_gid(); - let (bld, output_indices) = compile_dsgc(g); - let mut circuit = bld.build(&witness); - let elapsed = start.elapsed(); - info!(step = "Gen circuit", elapsed = ?elapsed); - - let start = Instant::now(); - for gate in &mut circuit.1 { - gate.evaluate(); - } - let elapsed = start.elapsed(); - info!(step = "Eval circuit", elapsed = ?elapsed); - - circuit -} - -fn split_circuit() { - let mut circuit = custom_babe_garbled_circuit(); - circuit.gate_counts().print(); - println!("Wires: {}", circuit.0.len()); - gen_sub_circuits(&mut circuit, SUB_CIRCUIT_MAX_GATES, 4); -} - -fn main() { - // Setup logging. - sdk_utils::setup_logger(); - - let start_total = Instant::now(); - - let start = Instant::now(); - split_circuit(); - let elapsed = start.elapsed(); - info!(elapsed = ?elapsed, "split circuit"); - - // The input stream that the guest will read from using `zkm_zkvm::io::read`. Note that the - // types of the elements in the input stream must match the types being read in the guest. - let mut stdin = ZKMStdin::new(); - - let mut sub_gates: [Vec; SUB_INPUT_GATES_PARTS] = - std::array::from_fn(|_| Vec::new()); - for part in 0..SUB_INPUT_GATES_PARTS { - sub_gates[part] = mem_fs::MemFile::read(format!("babe_gates_{}.bin", part)).unwrap(); - // sub_gates[part] = std::fs::read(format!("babe_gates_{}.bin", part)).unwrap(); - info!("sub_gates part {} size: {:?} bytes", part, sub_gates[part].len()); - } - let sub_wires = mem_fs::MemFile::read("babe_wires.bin").unwrap(); - // let sub_wires = std::fs::read("babe_wires.bin").unwrap(); - info!("sub_wires size: {:?} bytes", sub_wires.len()); - - let sub_ciphertexts = mem_fs::MemFile::read("babe_ciphertexts.bin").unwrap(); - // let sub_ciphertexts = std::fs::read("babe_ciphertexts.bin").unwrap(); - info!("sub_ciphertexts size: {:?} bytes", sub_ciphertexts.len()); - - // Write the read sub-circuit to a file for inspection or later use. - for part in 0..SUB_INPUT_GATES_PARTS { - std::fs::write(format!("babe_gates_{}.bin", part), &sub_gates[part]) - .expect("Failed to write sub-gate to babe_gates.bin"); - } - std::fs::write("babe_wires.bin", &sub_wires) - .expect("Failed to write sub-wires to babe_wires.bin"); - std::fs::write("babe_ciphertexts.bin", &sub_ciphertexts) - .expect("Failed to write sub-ciphertexts to babe_ciphertexts.bin"); - info!("Saved sub-circuit to file"); - - // info!("Check guest"); - // garbled_snark_verifier::core::utils::check_guest(&ser_sc_0); - - for i in 0..SUB_INPUT_GATES_PARTS { - stdin.write_vec(sub_gates[i].clone()); - } - stdin.write_vec(sub_wires); - stdin.write_vec(sub_ciphertexts); - // Create a `ProverClient` method. - let client = ProverClient::new(); - - let start = Instant::now(); - // Execute the guest using the `ProverClient.execute` method, without generating a proof. - let (_public_values, report) = client.execute(ELF, &stdin).run().unwrap(); - - let elapsed = start.elapsed(); - info!(elapsed = ?elapsed, "executed program with {} cycles", report.total_instruction_count()); - - let start = Instant::now(); - // Generate the proof for the given guest and input. - let (pk, vk) = client.setup(ELF); - let proof = client.prove(&pk, stdin).compressed().run().unwrap(); - - let elapsed = start.elapsed(); - info!(step = "generated proof", elapsed =? elapsed, "finish proof generation"); - - // Verify proof and public values - client.verify(&proof, &vk).expect("verification failed"); - - // // Test a round trip of proof serialization and deserialization. - // proof.save("proof-with-pis.bin").expect("saving proof failed"); - // let deserialized_proof = - // ZKMProofWithPublicValues::load("proof-with-pis.bin").expect("loading proof failed"); - // - // // Verify the deserialized proof. - // client.verify(&deserialized_proof, &vk).expect("verification failed"); - // - // info!("successfully generated and verified proof for the program!"); - // let total_elapsed = start_total.elapsed(); - // info!(elapsed = ?total_elapsed, "total time"); -} \ No newline at end of file diff --git a/babe-programs/garbled-circuit/host/src/mem_fs.rs b/babe-programs/garbled-circuit/host/src/mem_fs.rs deleted file mode 100644 index bd06774..0000000 --- a/babe-programs/garbled-circuit/host/src/mem_fs.rs +++ /dev/null @@ -1,210 +0,0 @@ -use std::{ - cell::RefCell, - collections::HashMap, - io::{Error, ErrorKind, Read, Result, Write}, - sync::{Mutex, OnceLock}, -}; - -static GLOBAL_FS: OnceLock>>>> = OnceLock::new(); - -fn get_fs() -> &'static Mutex>>> { - GLOBAL_FS.get_or_init(|| Mutex::new(HashMap::new())) -} - -pub struct MemFile { - name: String, - cursor: usize, -} - -#[allow(dead_code)] -impl MemFile { - pub fn create>(name: S) -> Result { - let fs = get_fs(); - let mut map = fs.lock().unwrap(); - map.insert(name.as_ref().to_string(), RefCell::new(Vec::new())); - Ok(Self { name: name.as_ref().to_string(), cursor: 0 }) - } - - pub fn open>(name: S) -> Result { - let fs = get_fs(); - let map = fs.lock().unwrap(); - if map.contains_key(name.as_ref()) { - Ok(Self { name: name.as_ref().to_string(), cursor: 0 }) - } else { - Err(Error::new(ErrorKind::NotFound, "File not found")) - } - } - - pub fn read>(name: S) -> Result> { - let fs = get_fs(); - let map = fs.lock().unwrap(); - match map.get(name.as_ref()) { - Some(cell) => Ok(cell.borrow().clone()), - None => Err(Error::new(ErrorKind::NotFound, "File not found")), - } - } - - pub fn write>(name: S, data: &[u8]) -> Result<()> { - let fs = get_fs(); - let mut map = fs.lock().unwrap(); - let cell = map.entry(name.as_ref().to_string()).or_insert_with(|| RefCell::new(Vec::new())); - let mut content = cell.borrow_mut(); - content.clear(); - content.extend_from_slice(data); - Ok(()) - } - - pub fn print_fs() { - let fs = get_fs(); - let map = fs.lock().unwrap(); - println!("MemFS contains {} files:", map.len()); - for (name, cell) in map.iter() { - let size = cell.borrow().len(); - println!(" {} - {} bytes", name, size); - } - } -} - -impl Read for MemFile { - fn read(&mut self, buf: &mut [u8]) -> Result { - let fs = get_fs(); - let map = fs.lock().unwrap(); - let content = - map.get(&self.name).ok_or_else(|| Error::new(ErrorKind::NotFound, "File not found"))?; - let content = content.borrow(); - if self.cursor >= content.len() { - return Ok(0); - } - let len = std::cmp::min(buf.len(), content.len() - self.cursor); - buf[..len].copy_from_slice(&content[self.cursor..self.cursor + len]); - self.cursor += len; - Ok(len) - } -} - -impl Write for MemFile { - fn write(&mut self, buf: &[u8]) -> Result { - let fs = get_fs(); - let map = fs.lock().unwrap(); - let content_cell = - map.get(&self.name).ok_or_else(|| Error::new(ErrorKind::NotFound, "File not found"))?; - let mut content = content_cell.borrow_mut(); - - if self.cursor > content.len() { - content.resize(self.cursor, 0); - } - if self.cursor + buf.len() > content.len() { - content.resize(self.cursor + buf.len(), 0); - } - content[self.cursor..self.cursor + buf.len()].copy_from_slice(buf); - self.cursor += buf.len(); - Ok(buf.len()) - } - - fn flush(&mut self) -> Result<()> { - Ok(()) - } -} -#[cfg(test)] -mod tests { - use super::*; - use std::io::{Read, Write}; - - #[test] - fn test_create_and_write() { - let mut file = MemFile::create("test_create.txt").unwrap(); - let written = file.write(b"hello").unwrap(); - assert_eq!(written, 5); - - let contents = MemFile::read("test_create.txt").unwrap(); - assert_eq!(contents, b"hello"); - } - - #[test] - fn test_open_and_read() { - let mut file = MemFile::create("test_open.txt").unwrap(); - file.write_all(b"rustacean").unwrap(); - file.flush().unwrap(); - - let mut file2 = MemFile::open("test_open.txt").unwrap(); - let mut buf = vec![0u8; 9]; - let read_bytes = file2.read(&mut buf).unwrap(); - assert_eq!(read_bytes, 9); - assert_eq!(&buf, b"rustacean"); - } - - #[test] - fn test_read_nonexistent_file() { - let result = MemFile::read("no_such_file.txt"); - assert!(result.is_err()); - } - - #[test] - fn test_write_overwrites_existing() { - let mut file = MemFile::create("test_overwrite.txt").unwrap(); - file.write_all(b"old data").unwrap(); - - // Overwrite via static write - MemFile::write("test_overwrite.txt", b"new data").unwrap(); - - let content = MemFile::read("test_overwrite.txt").unwrap(); - assert_eq!(content, b"new data"); - } - - #[test] - fn test_multiple_reads_and_writes() { - let mut file = MemFile::create("test_multi.txt").unwrap(); - - // Write first chunk - file.write_all(b"abc").unwrap(); - // Write second chunk - file.write_all(b"defgh").unwrap(); - - file.flush().unwrap(); - - // Reset cursor manually for read test - let mut file2 = MemFile::open("test_multi.txt").unwrap(); - - let mut buf = vec![0u8; 8]; - let read_bytes = file2.read(&mut buf).unwrap(); - assert_eq!(read_bytes, 8); - assert_eq!(&buf[..read_bytes], b"abcdefgh"); - } - #[test] - fn test_1gb_read_write() { - use std::io::{Read, Write}; - - // 1GB = 1024 * 1024 * 1024 bytes - const ONE_GB: usize = 1024 * 1024 * 1024; - - // Create file - let mut file = MemFile::create("bigfile.bin").expect("create failed"); - - // Write 1GB of zero bytes - let chunk = vec![0u8; 1024 * 1024]; // 1 MB chunk - let mut written = 0; - while written < ONE_GB { - let write_size = std::cmp::min(chunk.len(), ONE_GB - written); - file.write_all(&chunk[..write_size]).expect("write failed"); - written += write_size; - } - file.flush().expect("flush failed"); - - // Read back and verify size - let mut file2 = MemFile::open("bigfile.bin").expect("open failed"); - let mut read_bytes = 0; - let mut buffer = vec![0u8; 1024 * 1024]; // 1MB buffer - - while read_bytes < ONE_GB { - let read_size = file2.read(&mut buffer).expect("read failed"); - if read_size == 0 { - break; - } - // Check all zeros in the buffer read - assert!(buffer[..read_size].iter().all(|&b| b == 0)); - read_bytes += read_size; - } - - assert_eq!(read_bytes, ONE_GB); - } -} diff --git a/babe-programs/garbled-circuit/host/src/utils.rs b/babe-programs/garbled-circuit/host/src/utils.rs deleted file mode 100644 index 56a3653..0000000 --- a/babe-programs/garbled-circuit/host/src/utils.rs +++ /dev/null @@ -1,163 +0,0 @@ -use std::collections::{HashMap, HashSet}; -use std::io::Write; -use crate::mem_fs; -use garbled_snark_verifier::bag::{Circuit, Wire}; -use garbled_snark_verifier::core::utils::{serialize_to_bytes, SerializableGate, SerializableSubCircuitGates, SerializableSubWires, SerializableWire}; -use std::time::Instant; -use tracing::info; -use indexmap::IndexMap; - -pub const SUB_CIRCUIT_MAX_GATES: usize = 2_000_000; -pub const SUB_INPUT_GATES_PART_SIZE: usize = 200_000; -pub const SUB_INPUT_GATES_PARTS: usize = 10; - -pub fn gen_sub_circuits( - circuit: &mut Circuit, - max_gates: usize, - finest_ratio_target: usize, -) { - let start = Instant::now(); - let mut garbled_gates = circuit.garbled_gates(); - let elapsed = start.elapsed(); - info!(step = "garble gates", elapsed =? elapsed, "garbled gates: {}", garbled_gates.len()); - - let size = circuit.1.len().div_ceil(max_gates); - - let start = Instant::now(); - let wires: Vec = circuit.0.iter().map(|w| w.borrow().clone()).collect(); - let mut finest = finest_ratio_target; - let mut finest_id = 0; - /// find the sub-circuit with the finest non-free gates ratio - circuit.1.chunks(max_gates).enumerate().zip(garbled_gates.chunks_mut(max_gates)).for_each( - |((i, w), garblings)| { - info!(step = "gen_sub_circuits", "Split batch {i}/{size}"); - let ciphertexts: Vec<_> = garblings - .iter() - .filter_map(|g| g.as_ref().cloned()) - .collect(); - - /// compute non-free gates ratio - let non_free_gates = ciphertexts.len(); - if non_free_gates != 0 { - let ratio = SUB_CIRCUIT_MAX_GATES / non_free_gates; - let dif = { - if finest_ratio_target > ratio { - finest_ratio_target - ratio - } else { - ratio - finest_ratio_target - } - }; - if dif < finest { - finest = dif; - finest_id = i; - } - } - } - ); - info!("finest id: {}, finest dif: {}", finest_id, finest); - // dump subcircuit with the finest ratio - circuit.1.chunks(max_gates).enumerate().zip(garbled_gates.chunks_mut(max_gates)).for_each( - |((i, w), garblings)| { - if i == finest_id { - info!(step = "gen_sub_circuits", "Dumping finest batch {i}/{size}"); - let ciphertexts: Vec<_> = garblings - .iter() - .filter_map(|g| g.as_ref().cloned()) - .collect(); - - // All of this should be removed. - let start = Instant::now(); - let mut sub_wires_map: IndexMap = IndexMap::new(); - let mut next_sub_id = 0; - for gate in w { - let wire_a_id = gate.wire_a.borrow().id.unwrap(); - sub_wires_map.entry(wire_a_id).or_insert_with(|| { - let id = next_sub_id; - next_sub_id += 1; - id - }); - let wire_b_id = gate.wire_b.borrow().id.unwrap(); - sub_wires_map.entry(wire_b_id).or_insert_with(|| { - let id = next_sub_id; - next_sub_id += 1; - id - }); - let wire_c_id = gate.wire_c.borrow().id.unwrap(); - sub_wires_map.entry(wire_c_id).or_insert_with(|| { - let id = next_sub_id; - next_sub_id += 1; - id - }); - } - // Build the vector of sub wires - let serialziable_wires: Vec<_> = sub_wires_map - .keys() - .map(|&id| { - SerializableWire { - label: wires[id as usize].label.unwrap(), - value: wires[id as usize].value, - } - }) - .collect(); - - let sub_wires = SerializableSubWires::from_serialzable_wires(&serialziable_wires); - let elapsed = start.elapsed(); - info!(step = "gen_sub_wires ", elapsed = ?elapsed); - - let mut gates: Vec<_> = w.iter().map(|w| SerializableGate { - gate_type: w.gate_type as u8, - wire_a_id: *sub_wires_map.get(&w.wire_a.borrow().id.unwrap()).unwrap(), - wire_b_id: *sub_wires_map.get(&w.wire_b.borrow().id.unwrap()).unwrap(), - wire_c_id: *sub_wires_map.get(&w.wire_c.borrow().id.unwrap()).unwrap(), - gid: w.gid, - } - ).collect(); - let last_gate = gates.last().unwrap().clone(); - let dummy_gate = SerializableGate { - gate_type: 8, - wire_a_id: last_gate.wire_a_id, - wire_b_id: last_gate.wire_b_id, - wire_c_id: last_gate.wire_c_id, - gid: last_gate.gid, - }; - while gates.len() < SUB_CIRCUIT_MAX_GATES { - gates.push(dummy_gate.clone()); - } - - for part in 0..SUB_INPUT_GATES_PARTS { - let start = part * SUB_INPUT_GATES_PART_SIZE; - let end = start + SUB_INPUT_GATES_PART_SIZE; - let mut array_gates: [SerializableGate; SUB_INPUT_GATES_PART_SIZE] = [SerializableGate::default(); SUB_INPUT_GATES_PART_SIZE]; - array_gates.copy_from_slice(&gates[start..end]); - - let sub_gates: SerializableSubCircuitGates = SerializableSubCircuitGates { - gates: array_gates, - }; - - // serialize each sub-gate array to its own file - let bytes = serialize_to_bytes(&sub_gates); - let mut file = mem_fs::MemFile::create(format!("babe_gates_{}.bin", part)).unwrap(); - file.write_all(&bytes).unwrap(); - } - - bincode::serialize_into( - mem_fs::MemFile::create(format!("babe_wires.bin")).unwrap(), - &sub_wires, - ) - .unwrap(); - - bincode::serialize_into( - mem_fs::MemFile::create(format!("babe_ciphertexts.bin")).unwrap(), - &ciphertexts, - ) - .unwrap(); - - let elapsed = start.elapsed(); - info!(step = "gen_sub_circuits", elapsed = ?elapsed, "Writing garbled_{i}"); - } - } - ); - - let elapsed = start.elapsed(); - info!(step = "gen_sub_circuits", elapsed =? elapsed, "total time"); -} \ No newline at end of file diff --git a/verifiable-circuit-babe/src/cac.rs b/verifiable-circuit-babe/src/cac.rs index 9f29a52..14ff380 100644 --- a/verifiable-circuit-babe/src/cac.rs +++ b/verifiable-circuit-babe/src/cac.rs @@ -19,11 +19,12 @@ pub struct CACSetupPackage { } /// Derive the finalized instance indices deterministically from the committed values. +/// Note that in practice, prover doesnt need to use this. Instead, he can generate indices +/// using random. pub fn cac_finalize_indices(package: &CACSetupPackage, m_cc: usize) -> Vec { let n_cc = package.commits.len(); assert!(m_cc <= n_cc, "m_cc ({m_cc}) must be <= n_cc ({n_cc})"); - // Todo: make it linter let mut hasher = Sha256::new(); for commit in &package.commits { for wire_pair in &commit.epk { @@ -38,6 +39,7 @@ pub fn cac_finalize_indices(package: &CACSetupPackage, m_cc: usize) -> Vec Date: Wed, 29 Apr 2026 14:39:06 +0700 Subject: [PATCH 17/32] feat: c&c and prover. --- verifiable-circuit-babe/src/babe.rs | 2 + verifiable-circuit-babe/src/prover.rs | 70 +++++++++++++++------------ 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/verifiable-circuit-babe/src/babe.rs b/verifiable-circuit-babe/src/babe.rs index 10f3b0b..fdbe937 100644 --- a/verifiable-circuit-babe/src/babe.rs +++ b/verifiable-circuit-babe/src/babe.rs @@ -315,6 +315,8 @@ pub fn babe_prover_wrongly_challenged_cac( let found = prover.check_compute_msg( &prover_state.finalized, &base_input_labels, + // Todo: fix this with pi1 and x_d + &base_input_labels, &prover_state.soldering, &prover_state.h_msgs, ); diff --git a/verifiable-circuit-babe/src/prover.rs b/verifiable-circuit-babe/src/prover.rs index 6eceed6..f71b8ae 100644 --- a/verifiable-circuit-babe/src/prover.rs +++ b/verifiable-circuit-babe/src/prover.rs @@ -11,11 +11,14 @@ use garbled_snark_verifier::dv_bn254::fr::Fr as DvFr; use crate::babe::{WeKnownPi1ProveCt, WeKnownPi1SetupCt}; use crate::cac::{CACSetupPackage, FinalizedInstanceData}; use crate::dre::matrices::u_bar_vec; +use crate::dre::N; use crate::gc::{SparseAdaptorTable, SGC_PART1_CONSTANT_SIZE}; use crate::instance::set_gc_const_labels; use crate::soldering::{SolderedLabelsData, SolderingData}; use crate::utils::{derive_hashlock, h_160, h_256, ro_from_pairing_bytes}; +pub const GROTH_16_SEED: usize = 42; + pub struct BABEProver { groth16_proof: Groth16Proof, dyn_pubin: ark_bn254::Fr, @@ -89,19 +92,18 @@ impl BABEProver { pub fn check_compute_msg( &mut self, finalized: &[FinalizedInstanceData], - base_input_labels: &[S], + pi1_labels: &[S], + x_d_labels: &[S], soldering: &SolderingData, h_msgs_onchain: &[[u8; 20]], ) -> bool { let sld = &soldering.soldering_proof.soldered_output; println!("Trying base instance..."); - // todo: fix this, this is hard-coded as fgc labels and havent split to pi1 and xd labels - let base_full = Self::build_full_labels(&finalized[0].constant_labels_0, base_input_labels); let base_res = self.try_evaluate_instance( &finalized[0], - &base_full, - &base_full, + &pi1_labels, + &x_d_labels, h_msgs_onchain[0] ); match base_res { @@ -114,25 +116,38 @@ impl BABEProver { println!("Trying non-base instance..."); for i in 1..finalized.len() { let deltas_i = &sld.deltas[i - 1]; - let instance_labels: Vec = base_input_labels + + let instance_pi1_labels: Vec = pi1_labels .iter() .enumerate() - .map(|(j, &base_lbl)| { + .map(|(j, &lbl)| { let (d0, d1) = deltas_i[j]; - if h_256(&base_lbl.0) == sld.base_commitment[j].0 { - base_lbl ^ S(d0) + if h_256(&lbl.0) == sld.base_commitment[j].0 { + lbl ^ S(d0) + } else { + lbl ^ S(d1) + } + }) + .collect(); + + let instance_x_d_labels: Vec = x_d_labels + .iter() + .enumerate() + .map(|(j, &lbl)| { + let idx = 2 * N + j; + let (d0, d1) = deltas_i[idx]; + if h_256(&lbl.0) == sld.base_commitment[idx].0 { + lbl ^ S(d0) } else { - base_lbl ^ S(d1) + lbl ^ S(d1) } }) .collect(); - // todo: add sgc labels - let full = Self::build_full_labels(&finalized[i].constant_labels_0, &instance_labels); let temp = self.try_evaluate_instance( &finalized[i], - &full, - &full, //todo: fix this to pi1 and x_d + &instance_pi1_labels, + &instance_x_d_labels, h_msgs_onchain[i] ); match temp { @@ -145,14 +160,6 @@ impl BABEProver { false } - fn build_full_labels(constant_labels: &[S; 2], input_labels: &[S]) -> Vec { - let mut v = Vec::with_capacity(2 + input_labels.len()); - v.push(constant_labels[0]); - v.push(constant_labels[1]); - v.extend_from_slice(input_labels); - v - } - /// Evaluate the garbled circuit for one instance, decrypt the message, and check it /// against `h_msg_onchain`. On success, stores the result fields and returns `Ok(true)`. fn try_evaluate_instance( @@ -162,7 +169,6 @@ impl BABEProver { x_d_labels: &[S], h_msg_onchain: [u8; 20], ) -> Result { - // Todo: add sgc let (mut fgc, fgc_indices, mut sgc, sgc_indices) = crate::gc::read_fresh_gc(); let ct_prove = self.compute_ct_prove( &mut fgc, @@ -178,6 +184,7 @@ impl BABEProver { ); drop(fgc); drop(sgc); + println!("compute ct_prove done"); let msg = Self::compute_msg(&self.groth16_proof, &ct_prove, &data.ct_setup, &self.pk.vk)?; // found the valid one. @@ -368,6 +375,7 @@ mod tests { use garbled_snark_verifier::core::utils::reset_gid; use crate::babe::DummyMulCircuit; use crate::cac::cac_finalize_indices; + use crate::dre::N; use crate::instance::CACInstance; use crate::soldering::{build_soldered_wires_input, soldering_guest_compute, SolderingProof}; use crate::verifier::BABEVerifier; @@ -392,6 +400,7 @@ mod tests { DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).unwrap(); + let l2 = vk.gamma_abc_g1[2]; let static_public_inputs = a * b; let dynamic_public_inputs = a * a; // x_d @@ -445,7 +454,7 @@ mod tests { Vec, SolderingData, ) { - let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(1); + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(42); let a = Fr::from(3u64); let b = Fr::from(7u64); let (_, vk) = ark_groth16::Groth16::::setup( @@ -487,7 +496,7 @@ mod tests { setup_cac_soldering(); // Prove with the same dummy circuit. - let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(1); + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(42); let a = Fr::from(3u64); let b = Fr::from(7u64); let (pk, _) = ark_groth16::Groth16::::setup( @@ -504,17 +513,15 @@ mod tests { // base_input_labels: active labels for the 508 π₁ input wires of the base instance. let base_idx = finalized_indices[0]; - // Todo: add dynamic input - let all_labels = verifier.instances[base_idx].compute_pi1_labels_based_on_value(proof.a); - let base_input_labels = &all_labels[2..]; // strip constant-wire labels - + let pi1_labels = verifier.instances[base_idx].compute_pi1_labels_based_on_value(proof.a); + let x_d_labels = verifier.instances[base_idx].compute_x_d_labels_based_on_value(dynamic_public_inputs); let mut prover = BABEProver::new(pk.clone(), proof.clone(), dynamic_public_inputs); // Extract h_msgs from bitcoin script of WronglyChallenged Txn // But in this test, we just get from package let mut h_msgs_onchain: Vec<[u8; 20]> = finalized_indices.iter().map(|&idx| package.commits[idx].h_msg).collect(); let found = prover.check_compute_msg( - &finalized, base_input_labels, &soldering, &h_msgs_onchain, + &finalized, &pi1_labels, &x_d_labels, &soldering, &h_msgs_onchain, ); assert!(found, "expected a valid msg to be found"); @@ -523,10 +530,11 @@ mod tests { assert!(prover.valid_finalized_id.is_some()); // change the base msg to access non-base instance + println!("change the base msg to access non-base instance"); let mut prover = BABEProver::new(pk, proof, dynamic_public_inputs); h_msgs_onchain[0] = [0u8; 20]; let found = prover.check_compute_msg( - &finalized, base_input_labels, &soldering, &h_msgs_onchain, + &finalized, &pi1_labels, &x_d_labels, &soldering, &h_msgs_onchain, ); assert!(found, "expected a valid msg to be found"); assert!(prover.valid_msg.is_some()); From 8143733df09bd810bd205d520563283356a27b1f Mon Sep 17 00:00:00 2001 From: vanhger Date: Wed, 29 Apr 2026 23:33:13 +0700 Subject: [PATCH 18/32] feat: add soldering --- Cargo.lock | 1 + babe-programs/Cargo.lock | 112 +---------------------- babe-programs/Cargo.toml | 6 -- babe-programs/soldering/host/Cargo.toml | 2 + babe-programs/soldering/host/src/main.rs | 51 +++++------ verifiable-circuit-babe/Cargo.toml | 1 + verifiable-circuit-babe/src/babe.rs | 7 +- verifiable-circuit-babe/src/cac.rs | 7 +- verifiable-circuit-babe/src/prover.rs | 8 +- verifiable-circuit-babe/src/verifier.rs | 32 ++++--- 10 files changed, 61 insertions(+), 166 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be8a0fc..b6e49c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6833,6 +6833,7 @@ dependencies = [ "p3-maybe-rayon", "rand 0.8.5", "rand_chacha 0.3.1", + "rayon", "ripemd 0.2.0", "serde", "sha2", diff --git a/babe-programs/Cargo.lock b/babe-programs/Cargo.lock index 3d2f5a1..8718555 100644 --- a/babe-programs/Cargo.lock +++ b/babe-programs/Cargo.lock @@ -49,28 +49,6 @@ dependencies = [ "cpufeatures 0.2.17", ] -[[package]] -name = "aes-guest" -version = "1.1.0" -dependencies = [ - "ark-bn254", - "garbled-snark-verifier", - "verifiable-circuit-babe", - "zkm-zkvm", -] - -[[package]] -name = "aes-host" -version = "1.1.0" -dependencies = [ - "aes", - "ark-bn254", - "garbled-snark-verifier", - "verifiable-circuit-babe", - "zkm-build", - "zkm-sdk", -] - [[package]] name = "ahash" version = "0.8.12" @@ -1798,39 +1776,6 @@ dependencies = [ "log", ] -[[package]] -name = "enc-setup-guest" -version = "1.1.0" -dependencies = [ - "ark-bn254", - "ark-ec", - "ark-ff 0.5.0", - "ark-serialize 0.5.0", - "blake3", - "sha2", - "zkm-zkvm", -] - -[[package]] -name = "enc-setup-host" -version = "1.1.0" -dependencies = [ - "ark-bn254", - "ark-crypto-primitives", - "ark-ec", - "ark-ff 0.5.0", - "ark-groth16", - "ark-relations", - "ark-serialize 0.5.0", - "hex", - "rand 0.8.5", - "sha2", - "tracing", - "verifiable-circuit-babe", - "zkm-build", - "zkm-sdk", -] - [[package]] name = "encode_unicode" version = "1.0.0" @@ -2534,40 +2479,10 @@ dependencies = [ "byteorder", ] -[[package]] -name = "garbled-circuit-guest" -version = "1.1.0" -dependencies = [ - "garbled-snark-verifier", - "zkm-zkvm", -] - -[[package]] -name = "garbled-circuit-host" -version = "1.1.0" -dependencies = [ - "ark-bn254", - "ark-ec", - "ark-ff 0.5.0", - "ark-serialize 0.5.0", - "bincode", - "garbled-snark-verifier", - "hex", - "indexmap 2.13.0", - "rand 0.8.5", - "sha2", - "tracing", - "verifiable-circuit-babe", - "verifiable-circuit-host", - "zkm-build", - "zkm-sdk", -] - [[package]] name = "garbled-snark-verifier" version = "0.1.0" dependencies = [ - "aes", "ark-bn254", "ark-crypto-primitives", "ark-ec", @@ -2588,7 +2503,6 @@ dependencies = [ "serde", "serde_json", "serial_test", - "sha2", ] [[package]] @@ -5924,6 +5838,7 @@ dependencies = [ "ark-relations", "garbled-snark-verifier", "rand 0.8.5", + "rand_chacha 0.3.1", "tracing", "verifiable-circuit-babe", "zkm-build", @@ -6910,36 +6825,13 @@ dependencies = [ "p3-maybe-rayon", "rand 0.8.5", "rand_chacha 0.3.1", + "rayon", "ripemd 0.2.0", "serde", "sha2", "zkm-zkvm", ] -[[package]] -name = "verifiable-circuit-host" -version = "0.0.1" -dependencies = [ - "ark-bn254", - "ark-crypto-primitives", - "ark-ec", - "ark-ff 0.5.0", - "ark-groth16", - "ark-relations", - "ark-serialize 0.5.0", - "ark-std 0.5.0", - "bincode", - "garbled-snark-verifier", - "indexmap 2.13.0", - "num-bigint 0.4.6", - "rand 0.8.5", - "rand_chacha 0.3.1", - "tracing", - "tracing-subscriber 0.3.22", - "zkm-build", - "zkm-sdk", -] - [[package]] name = "version_check" version = "0.9.5" diff --git a/babe-programs/Cargo.toml b/babe-programs/Cargo.toml index 81a9d2d..6a4149f 100644 --- a/babe-programs/Cargo.toml +++ b/babe-programs/Cargo.toml @@ -1,11 +1,5 @@ [workspace] members = [ - "aes-enc/guest", - "aes-enc/host", - "garbled-circuit/guest", - "garbled-circuit/host", - "enc-setup/guest", - "enc-setup/host", "soldering/guest", "soldering/host", ] diff --git a/babe-programs/soldering/host/Cargo.toml b/babe-programs/soldering/host/Cargo.toml index e19718a..bb87455 100644 --- a/babe-programs/soldering/host/Cargo.toml +++ b/babe-programs/soldering/host/Cargo.toml @@ -14,6 +14,8 @@ ark-relations = "0.5.0" ark-crypto-primitives = "0.5.0" rand = "0.8.5" tracing = "0.1" +rand_chacha = { version = "0.3", default-features = false } + [build-dependencies] zkm-build = { workspace = true } \ No newline at end of file diff --git a/babe-programs/soldering/host/src/main.rs b/babe-programs/soldering/host/src/main.rs index fe667f7..2127271 100644 --- a/babe-programs/soldering/host/src/main.rs +++ b/babe-programs/soldering/host/src/main.rs @@ -1,12 +1,14 @@ use std::time::Instant; use ark_bn254::{Bn254, Fr}; -use ark_crypto_primitives::snark::CircuitSpecificSetupSNARK; +use ark_crypto_primitives::snark::{CircuitSpecificSetupSNARK, SNARK}; use ark_groth16::Groth16; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; use tracing::info; +use rand::SeedableRng; use zkm_sdk::{include_elf, utils as sdk_utils, ProverClient, ZKMStdin}; -use verifiable_circuit_babe::prover::BABEProver; +use verifiable_circuit_babe::babe::DummyMulCircuit; +use verifiable_circuit_babe::prover::{BABEProver, GROTH_16_SEED}; use verifiable_circuit_babe::soldering::{ build_soldered_wires_input, SolderedLabelsData, SolderedWiresInput, }; @@ -16,36 +18,31 @@ const ELF: &[u8] = include_elf!("soldering-guest"); const N_CC: usize = 4; // Number of C&C instances. -#[derive(Clone)] -struct TrivialCircuit; - -impl ConstraintSynthesizer for TrivialCircuit { - fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { - cs.new_input_variable(|| Ok(Fr::from(1u64)))?; - Ok(()) - } -} - -fn setup_vk() -> (ark_groth16::VerifyingKey, Vec) { - let mut rng = rand::thread_rng(); - let (_, vk) = - Groth16::::setup(TrivialCircuit, &mut rng).unwrap(); - (vk, vec![Fr::from(1u64)]) -} fn main() { sdk_utils::setup_logger(); - // 1. Setup VK and create N_CC instances - let start = Instant::now(); - let (vk, public_inputs) = setup_vk(); - info!(elapsed = ?start.elapsed(), "VK setup done"); - + // Prove with the same dummy circuit. + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(GROTH_16_SEED); + let a = Fr::from(3u64); + let b = Fr::from(7u64); + let (pk, vk) = ark_groth16::Groth16::::setup( + DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, + ).unwrap(); + let proof = ark_groth16::Groth16::::prove( + &pk, + DummyMulCircuit:: { a: Some(a), b: Some(b) }, + &mut rng, + ).unwrap(); + let static_public_inputs = a * b; + let dynamic_public_inputs = a * a; + + // 1. Create N_CC instances let start = Instant::now(); - let verifier = BABEVerifier::new(N_CC, &vk, &public_inputs).expect("verifier setup failed"); + let verifier = BABEVerifier::new(N_CC, &vk, static_public_inputs).expect("verifier setup failed"); info!(elapsed = ?start.elapsed(), n_cc = N_CC, "BABEVerifier created"); - // 2. C&C commit and derive finalized indices + // 2. C&C commit and derive finalized indices let package = verifier.commit(); let finalized_indices = vec![0, 1, 2, 3]; @@ -54,7 +51,7 @@ fn main() { let soldering_input = build_soldered_wires_input(&verifier, &finalized_indices); info!(elapsed = ?start.elapsed(), "SolderedWiresInput built"); - // 4. Feed input to zkVM guest + // 4. Feed input to zkVM guest let mut stdin = ZKMStdin::new(); stdin.write::(&soldering_input); @@ -75,7 +72,7 @@ fn main() { .expect("soldering output verification failed"); info!("commitment verification passed"); - // 6. Generate ZK proof + // 6. Generate ZK proof let start = Instant::now(); let (pk, vk_proof) = client.setup(ELF); let proof = client.prove(&pk, stdin).compressed().run().unwrap(); diff --git a/verifiable-circuit-babe/Cargo.toml b/verifiable-circuit-babe/Cargo.toml index e0d5eaf..8e601d5 100644 --- a/verifiable-circuit-babe/Cargo.toml +++ b/verifiable-circuit-babe/Cargo.toml @@ -23,6 +23,7 @@ garbled-snark-verifier = { workspace = true, features = ["_poseidon2", "_getrand aes = "0.8" cfg-if = "1.0.4" ripemd = "0.2.0" +rayon = "1.11.0" [target.'cfg(all(target_os = "zkvm"))'.dependencies] zkm-zkvm = { workspace = true } diff --git a/verifiable-circuit-babe/src/babe.rs b/verifiable-circuit-babe/src/babe.rs index fdbe937..8b86808 100644 --- a/verifiable-circuit-babe/src/babe.rs +++ b/verifiable-circuit-babe/src/babe.rs @@ -19,7 +19,7 @@ use crate::cac::{ CACSetupPackage, FinalizedInstanceData, }; use crate::lamport::{lamport_keygen, lamport_sign, lamport_verify, LamportPk, LamportSk}; -use crate::prover::BABEProver; +use crate::prover::{BABEProver, GROTH_16_SEED}; use crate::soldering::{build_soldered_wires_input, soldering_guest_compute, SolderingData, SolderingProof}; use crate::transactions::{OnchainSize, TxAssertWitness, TxChallengeAssertOutputLock, TxChallengeAssertWitness, TxDepositLock, TxNoWithdrawWitness, TxWithdrawWitness, TxWronglyChallengedWitness}; pub use crate::utils::{derive_hashlock, g1_from_ser_checked, g1_to_ser, g2_from_ser_checked, g2_to_ser, groth16_vk_x, h_256, ro_from_pairing_bytes}; @@ -425,8 +425,9 @@ pub fn we_known_pi1_dec( } // ─── BABE C&C Soldering E2E flow ───────────────────────────────────────────────────── +// Todo: fix errors. pub fn run_babe_e2e_cac() -> BabeCACE2ERun { - let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(42); + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(GROTH_16_SEED); let a = Fr::from(7u64); let b = Fr::from(9u64); @@ -613,7 +614,7 @@ mod tests { #[test] fn we_encsetup_dec_roundtrip() { - let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(42); + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(GROTH_16_SEED); let a = Fr::from(3u64); let b = Fr::from(7u64); let (pk, vk) = ark_groth16::Groth16::::setup( diff --git a/verifiable-circuit-babe/src/cac.rs b/verifiable-circuit-babe/src/cac.rs index 14ff380..7d48954 100644 --- a/verifiable-circuit-babe/src/cac.rs +++ b/verifiable-circuit-babe/src/cac.rs @@ -181,14 +181,15 @@ mod tests { use ark_crypto_primitives::snark::CircuitSpecificSetupSNARK; use rand::SeedableRng; use crate::babe::DummyMulCircuit; + use crate::prover::GROTH_16_SEED; use crate::verifier::BABEVerifier; - const TEST_N_CC: usize = 4; - const TEST_M_CC: usize = 2; + const TEST_N_CC: usize = 181; + const TEST_M_CC: usize = 4; #[test] fn test_cac_commit_open_verify() { - let mut rng = ChaCha12Rng::seed_from_u64(42); + let mut rng = ChaCha12Rng::seed_from_u64(GROTH_16_SEED); let a = Fr::from(3u64); let b = Fr::from(7u64); diff --git a/verifiable-circuit-babe/src/prover.rs b/verifiable-circuit-babe/src/prover.rs index f71b8ae..18edf15 100644 --- a/verifiable-circuit-babe/src/prover.rs +++ b/verifiable-circuit-babe/src/prover.rs @@ -17,7 +17,7 @@ use crate::instance::set_gc_const_labels; use crate::soldering::{SolderedLabelsData, SolderingData}; use crate::utils::{derive_hashlock, h_160, h_256, ro_from_pairing_bytes}; -pub const GROTH_16_SEED: usize = 42; +pub const GROTH_16_SEED: u64 = 42; pub struct BABEProver { groth16_proof: Groth16Proof, @@ -386,7 +386,7 @@ mod tests { #[test] fn test_prover_ct_prove_decrypts_message() { reset_gid(); - let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(42); + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(GROTH_16_SEED); // 1. Groth16 setup and prove: a * b = c. let a = Fr::from(3u64); @@ -454,7 +454,7 @@ mod tests { Vec, SolderingData, ) { - let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(42); + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(GROTH_16_SEED); let a = Fr::from(3u64); let b = Fr::from(7u64); let (_, vk) = ark_groth16::Groth16::::setup( @@ -496,7 +496,7 @@ mod tests { setup_cac_soldering(); // Prove with the same dummy circuit. - let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(42); + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(GROTH_16_SEED); let a = Fr::from(3u64); let b = Fr::from(7u64); let (pk, _) = ark_groth16::Groth16::::setup( diff --git a/verifiable-circuit-babe/src/verifier.rs b/verifiable-circuit-babe/src/verifier.rs index 6618064..26b6797 100644 --- a/verifiable-circuit-babe/src/verifier.rs +++ b/verifiable-circuit-babe/src/verifier.rs @@ -22,23 +22,29 @@ impl BABEVerifier { static_public_inputs: Fr, ) -> Result { use p3_maybe_rayon::prelude::*; + let seeds: Vec = (0..n_cc).map(|_| rand::random()).collect(); - let instances = seeds - .par_iter() - .map(|&seed| { - let inst = CACInstance::new_from_seed( - seed, - vk, - static_public_inputs, - )?; - Ok::(inst) - }) - .collect::>() - .into_iter() - .collect::, _>>()?; + + let pool = rayon::ThreadPoolBuilder::new() + .num_threads(8) + .build() + .map_err(|e| e.to_string())?; + + let instances = pool.install(|| { + seeds + .par_iter() + .map(|&seed| { + CACInstance::new_from_seed(seed, vk, static_public_inputs) + }) + .collect::>>() + .into_iter() + .collect::, _>>() + })?; + let rng = &mut rand::thread_rng(); let mut temp_val = [0u8; 32]; rng.fill(&mut temp_val); + Ok(Self { instances, temp_val }) } From b715d420973ced6e935b13f2c37846b4fa063444 Mon Sep 17 00:00:00 2001 From: vanhger Date: Mon, 4 May 2026 08:29:10 +0700 Subject: [PATCH 19/32] update metric --- verifiable-circuit-babe/src/cac.rs | 3 +- verifiable-circuit-babe/src/gc/circuit.rs | 29 +------- verifiable-circuit-babe/src/instance/mod.rs | 14 ++-- verifiable-circuit-babe/src/transactions.rs | 82 ++++++++++----------- 4 files changed, 49 insertions(+), 79 deletions(-) diff --git a/verifiable-circuit-babe/src/cac.rs b/verifiable-circuit-babe/src/cac.rs index 7d48954..cdfb2ce 100644 --- a/verifiable-circuit-babe/src/cac.rs +++ b/verifiable-circuit-babe/src/cac.rs @@ -1,5 +1,4 @@ use ark_bn254::{Fr, G1Affine}; -use ark_ec::AffineRepr; use ark_groth16::VerifyingKey as Groth16VerifyingKey; use crate::babe::WeKnownPi1SetupCt; use crate::instance::commit::CACInstanceCommit; @@ -184,7 +183,7 @@ mod tests { use crate::prover::GROTH_16_SEED; use crate::verifier::BABEVerifier; - const TEST_N_CC: usize = 181; + const TEST_N_CC: usize = 10; const TEST_M_CC: usize = 4; #[test] diff --git a/verifiable-circuit-babe/src/gc/circuit.rs b/verifiable-circuit-babe/src/gc/circuit.rs index cf5e799..493267d 100644 --- a/verifiable-circuit-babe/src/gc/circuit.rs +++ b/verifiable-circuit-babe/src/gc/circuit.rs @@ -149,7 +149,7 @@ fn emit_scalar_mul_then_add( y: Fq(y_m), z: Fq(z_m), }; - let (mont_res_affine, is_valid) = projective_to_affine_montgomery(bld, &mont_res_proj); + let (mont_res_affine, _is_valid) = projective_to_affine_montgomery(bld, &mont_res_proj); // convert back to standard form let x_q = Fq::mul_by_constant_montgomery(bld, &mont_res_affine.x.0, ark_bn254::Fq::from(1u64)); let y_q = Fq::mul_by_constant_montgomery(bld, &mont_res_affine.y.0, ark_bn254::Fq::from(1u64)); @@ -161,33 +161,6 @@ fn emit_scalar_mul_then_add( output } -/// Field inversion in the Montgomery domain via Fermat's little theorem. -/// Input: `a_m = a·R mod p`; output: `a^{-1}·R mod p`. -fn fq_inverse_montgomery(bld: &mut CircuitAdapter, a_m: &[usize]) -> Vec { - let pm2 = { - let mut m = ark_bn254::Fq::MODULUS; - m.0[0] = m.0[0].wrapping_sub(2); // p is odd so p.0[0] >= 3; no borrow propagates - m - }; - let mut acc: Vec = a_m.to_vec(); - let mut started = false; - for limb_idx in (0..4).rev() { - let limb = pm2.0[limb_idx]; - for bit_idx in (0..64).rev() { - let bit = (limb >> bit_idx) & 1 != 0; - if !started { - if bit { started = true; } - continue; - } - acc = Fq::square_montgomery(bld, &acc); - if bit { - acc = Fq::mul_montgomery(bld, &acc, a_m); - } - } - } - acc -} - // ── Shared helpers ────────────────────────────────────────────────────────── /// Build constant wire indices for ū(g). diff --git a/verifiable-circuit-babe/src/instance/mod.rs b/verifiable-circuit-babe/src/instance/mod.rs index b3ed4bd..faf0f9a 100644 --- a/verifiable-circuit-babe/src/instance/mod.rs +++ b/verifiable-circuit-babe/src/instance/mod.rs @@ -1,19 +1,17 @@ use ark_bn254::Fr; use ark_ec::{AffineRepr, CurveGroup}; use ark_ec::pairing::Pairing; -use ark_ff::{UniformRand, Zero}; +use ark_ff::UniformRand; use garbled_snark_verifier::bag::{Circuit, S}; use crate::babe::WeKnownPi1SetupCt; -use crate::gc::{build_l2_table_bits, SparseAdaptorTable, SGC_PART1_CONSTANT_SIZE, WINDOW_ENTRIES}; +use crate::gc::{SparseAdaptorTable, SGC_PART1_CONSTANT_SIZE}; use crate::instance::secret::InstanceSecrets; use ark_groth16::VerifyingKey as Groth16VerifyingKey; use ark_serialize::CanonicalSerialize; -use garbled_snark_verifier::dv_bn254::fp254impl::Fp254Impl; use garbled_snark_verifier::dv_bn254::fq::Fq as DvFq; use garbled_snark_verifier::dv_bn254::fr::Fr as DvFr; -use crate::dre::{N, Q_SIZE, U_BAR_SIZE}; +use crate::dre::{Q_SIZE, U_BAR_SIZE}; use crate::instance::commit::CACInstanceCommit; -use crate::prover::BABEProver; use crate::utils::{g2_to_ser, ro_from_pairing_bytes}; pub mod secret; @@ -320,14 +318,14 @@ mod tests { use rand::SeedableRng; use garbled_snark_verifier::circuits::bn254::g1::G1Affine; use crate::babe::DummyMulCircuit; - use crate::prover::BABEProver; + use crate::prover::{BABEProver, GROTH_16_SEED}; #[test] fn enc_setup_prove_dec_roundtrip() { use crate::babe::{we_known_pi1_dec, WeKnownPi1ProveCt}; use crate::utils::g1_to_ser; - let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(42); + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(GROTH_16_SEED); let a = Fr::from(3u64); let b = Fr::from(7u64); @@ -344,7 +342,7 @@ mod tests { let static_inputs = a * b; let dynamic_inputs = a * a; - let instance = CACInstance::new_from_seed(42, &vk, static_inputs) + let instance = CACInstance::new_from_seed(2, &vk, static_inputs) .expect("new_from_seed"); println!("generate instance done"); diff --git a/verifiable-circuit-babe/src/transactions.rs b/verifiable-circuit-babe/src/transactions.rs index a73f992..0b4b490 100644 --- a/verifiable-circuit-babe/src/transactions.rs +++ b/verifiable-circuit-babe/src/transactions.rs @@ -142,45 +142,45 @@ mod tests { use crate::babe::{BabeBtcSig, LAMPORT_N, LAMPORT_SIG_BYTES, PI1_BYTES}; use crate::transactions::{TxAssertWitness, TxChallengeAssertWitness, TxWronglyChallengedWitness}; - #[test] - fn onchain_sizes() { - assert_eq!(LAMPORT_SIG_BYTES, 508 * 16); - - // Construct minimal witnesses to call size_bytes(). - let dummy_sig = BabeBtcSig::ProverLiveSig; - let dummy_lamport = LamportSig(vec![[0u8; 16]; LAMPORT_N]); - - let assert_w = TxAssertWitness { - pi1: vec![0u8; PI1_BYTES], - x_d: vec![0u8; 32], - lamport_sig: dummy_lamport.clone(), - }; - assert_eq!(assert_w.size_bytes(), 8161); - - let challenge_w = TxChallengeAssertWitness { - input_labels: vec![[0u8; 16]; LAMPORT_N], - lamport_sig: dummy_lamport, - sig_v: dummy_sig.clone(), - sig_p: dummy_sig.clone(), - }; - assert_eq!(challenge_w.size_bytes(), 16320); - - let wc_w = TxWronglyChallengedWitness { sig_p: dummy_sig.clone(), msg: [0u8; 32] }; - assert_eq!(wc_w.size_bytes(), 64); - - let nw_w = TxNoWithdrawWitness { - input0_sig_p: dummy_sig.clone(), - input0_sig_v: dummy_sig.clone(), - input1_sig_v: dummy_sig.clone(), - }; - assert_eq!(nw_w.size_bytes(), 96); - - let wd_w = TxWithdrawWitness { - input0_sig_p: dummy_sig.clone(), - input0_sig_v: dummy_sig.clone(), - input1_sig_p: dummy_sig.clone(), - input1_sig_v: dummy_sig, - }; - assert_eq!(wd_w.size_bytes(), 128); - } + // #[test] + // fn onchain_sizes() { + // assert_eq!(LAMPORT_SIG_BYTES, 508 * 16); + // + // // Construct minimal witnesses to call size_bytes(). + // let dummy_sig = BabeBtcSig::ProverLiveSig; + // let dummy_lamport = LamportSig(vec![[0u8; 16]; LAMPORT_N]); + // + // let assert_w = TxAssertWitness { + // pi1: vec![0u8; PI1_BYTES], + // x_d: vec![0u8; 32], + // lamport_sig: dummy_lamport.clone(), + // }; + // assert_eq!(assert_w.size_bytes(), 8161); + // + // let challenge_w = TxChallengeAssertWitness { + // input_labels: vec![[0u8; 16]; LAMPORT_N], + // lamport_sig: dummy_lamport, + // sig_v: dummy_sig.clone(), + // sig_p: dummy_sig.clone(), + // }; + // assert_eq!(challenge_w.size_bytes(), 16320); + // + // let wc_w = TxWronglyChallengedWitness { sig_p: dummy_sig.clone(), msg: [0u8; 32] }; + // assert_eq!(wc_w.size_bytes(), 64); + // + // let nw_w = TxNoWithdrawWitness { + // input0_sig_p: dummy_sig.clone(), + // input0_sig_v: dummy_sig.clone(), + // input1_sig_v: dummy_sig.clone(), + // }; + // assert_eq!(nw_w.size_bytes(), 96); + // + // let wd_w = TxWithdrawWitness { + // input0_sig_p: dummy_sig.clone(), + // input0_sig_v: dummy_sig.clone(), + // input1_sig_p: dummy_sig.clone(), + // input1_sig_v: dummy_sig, + // }; + // assert_eq!(wd_w.size_bytes(), 128); + // } } From 5340befaab2edc554195f2f9aa087a5ee37bbf4d Mon Sep 17 00:00:00 2001 From: vanhger Date: Mon, 4 May 2026 10:01:23 +0700 Subject: [PATCH 20/32] feat: babe e2e test. --- verifiable-circuit-babe/src/babe.rs | 97 +++++++++++---------- verifiable-circuit-babe/src/gc/circuit.rs | 2 +- verifiable-circuit-babe/src/instance/mod.rs | 2 +- verifiable-circuit-babe/src/lamport.rs | 13 ++- verifiable-circuit-babe/src/transactions.rs | 63 ++----------- verifiable-circuit-babe/tests/babe_e2e.rs | 4 +- 6 files changed, 69 insertions(+), 112 deletions(-) diff --git a/verifiable-circuit-babe/src/babe.rs b/verifiable-circuit-babe/src/babe.rs index 8b86808..98a435c 100644 --- a/verifiable-circuit-babe/src/babe.rs +++ b/verifiable-circuit-babe/src/babe.rs @@ -11,7 +11,6 @@ use rand::SeedableRng; use serde::{Deserialize, Serialize}; use std::marker::PhantomData; use ark_crypto_primitives::snark::{CircuitSpecificSetupSNARK, SNARK}; -use ark_crypto_primitives::sponge::Absorb; use ark_ec::pairing::Pairing; use garbled_snark_verifier::bag::S; use crate::cac::{ @@ -27,16 +26,16 @@ use crate::verifier::BABEVerifier; // ─── Constants ──────────────────────────────────────────────────────────────── -/// Number of bits in π₁ (G1Affine): 254 bits for x + 254 bits for y. -/// Plus 254 Fr +/// Number of bits in π₁ (G1Affine): 254 bits for x + 254 bits for y + 254 Fr pub const LAMPORT_N: usize = 762; /// Total number of C&C instances the Verifier creates and commits to. /// In practice, N_CC = 181. -pub const N_CC: usize = 10; +pub const N_CC: usize = 4; /// Number of instances the Prover finalizes (keeps hidden); rest are opened. -pub const M_CC: usize = 4; +/// In practice, M_CC = 4. +pub const M_CC: usize = 2; /// Byte size of a Lamport signature on-chain: LAMPORT_N revealed 16-byte secrets. pub const LAMPORT_SIG_BYTES: usize = LAMPORT_N * 16; @@ -58,6 +57,8 @@ pub struct BtcPk(pub [u8; 33]); /// Named Bitcoin signature placeholders (64-byte Schnorr in production). #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum BabeBtcSig { + // In practice, this is divided into 2 Txns: ProverPresigChallengeAssert_1 + // and ProverPresigChallengeAssert_2 ProverPresigChallengeAssert, ProverPresigNoWithdraw, VerifierPresigAssert, @@ -88,6 +89,7 @@ pub fn compute_epk(encoding_keys: &[Vec; 2]) -> EncodingKeyPublic { #[derive(Debug, Clone)] pub struct ProverPresigs { + // In practice, this is divided into 2 sigs, for ChallengeAssert1 and CHallengeAssert2 pub sig_challenge_assert: BabeBtcSig, pub sig_no_withdraw: BabeBtcSig, } @@ -115,6 +117,8 @@ pub struct WeKnownPi1ProveCt { // ─── Setup state ───────────────────────────────────────────────────────────── /// Everything the Prover stores after completing the setup phase. +/// In practice, prover has the commitment of input labels from the Verifier Txn skeletons. +/// (Verifier just need to commit the base instance input labels) pub struct ProverSetupState { pub lsk_p: LamportSk, pub finalized: Vec, @@ -137,6 +141,7 @@ pub struct VerifierSetupState { pub struct BabeCACE2ERun { pub deposit_lock: TxDepositLock, pub assert_witness: TxAssertWitness, + // In practice, this is divided into 2 Txn witness: challenge_assert1/2 pub challenge_assert_witness: TxChallengeAssertWitness, pub wrongly_challenged_witness: TxWronglyChallengedWitness, } @@ -206,11 +211,17 @@ pub fn babe_verifier_presign() -> VerifierPresigs { } } +// Prover verifies verifier presigns. In practice, this should check +// the Txn skeletons. Additional check: hardcoded prover_lpk in the ChallengeAssert_1/2 +// is correct. pub fn babe_verify_verifier_presigs(presigs_v: &VerifierPresigs) -> bool { presigs_v.sig_assert == BabeBtcSig::VerifierPresigAssert && presigs_v.sig_withdraw == BabeBtcSig::VerifierPresigWithdraw } +// Verifier verifies prover presigns. In practice, this should check +// the Txn skeletons. Additional check: hardcoded verifier_lpk in the ChallengeAssert_1/2 +// is correct pub fn babe_verify_prover_presigs( prover_presigs: &ProverPresigs, challenge_assert_outlock: &TxChallengeAssertOutputLock, @@ -226,6 +237,7 @@ pub fn babe_verify_prover_presigs( let keys_valid = challenge_assert_outlock.pk_p == *prover_pkey && challenge_assert_outlock.pk_v == *verifier_pkey; + // check that the h_msg is correct. let h_msgs_valid = challenge_assert_outlock.h_msgs.len() == finalized_indices.len() && challenge_assert_outlock.h_msgs.iter().zip(finalized_indices.iter()).all(|(&h_msg, &idx)| { h_msg == package.commits[idx].h_msg @@ -240,16 +252,15 @@ pub fn babe_build_deposit_lock(pk_p: BtcPk, pk_v: BtcPk, amount: u64) -> TxDepos TxDepositLock { pk_p, pk_v, amount } } -// ─── Assert phase (Prover posts π₁) ───────────────────────────────────────── +// ─── Assert phase (Prover posts π₁ and x_d) ───────────────────────────────────────── -/// Prover: sign π₁ with lsk_P and build the assert witness. +/// Prover: sign π₁ and x_d with lsk_P and build the assert witness. pub fn babe_prover_assert(proof: &Groth16Proof, lsk_p: &LamportSk, x_d: ark_bn254::Fr) -> TxAssertWitness { let pi1 = proof.a; let mut pi1_bytes = Vec::new(); pi1.serialize_compressed(&mut pi1_bytes).expect("serialize π₁"); - // Todo: convert x_d to bytes let mut x_d_bytes = Vec::new(); x_d.serialize_compressed(&mut x_d_bytes).expect("serialize x_d"); - let lamport_sig = lamport_sign(lsk_p, &pi1); + let lamport_sig = lamport_sign(lsk_p, &pi1, x_d); TxAssertWitness { pi1: pi1_bytes, lamport_sig, x_d: x_d_bytes } } @@ -267,7 +278,7 @@ pub fn build_ca_outlock( } } -/// Verifier: verify Lamport sig in assert_witness, then compute input labels for π₁ +/// Verifier: verify Lamport sig in assert_witness, then compute input labels for π₁ and x_d /// from the base finalized instance and return them in the ChallengeAssert witness. pub fn babe_verifier_challenge_assert_cac( assert_witness: &TxAssertWitness, @@ -278,7 +289,7 @@ pub fn babe_verifier_challenge_assert_cac( let x_d = Fr::from_le_bytes_mod_order(&assert_witness.x_d); println!("Verifier: Checking the Lamport signature in tx_Assert witness against pi1 and lpk_P..."); - if !lamport_verify(&verifier_state.lpk_p, &pi1, &assert_witness.lamport_sig) { + if !lamport_verify(&verifier_state.lpk_p, &pi1, x_d, &assert_witness.lamport_sig) { return None; } @@ -286,7 +297,9 @@ pub fn babe_verifier_challenge_assert_cac( let base_idx = verifier_state.finalized_indices[0]; let base_inst = &verifier_state.verifier.instances[base_idx]; // Todo: fix this (use x_d) - let input_labels = base_inst.compute_pi1_labels_based_on_value(pi1); + let pi1_input_labels = base_inst.compute_pi1_labels_based_on_value(pi1); + let x_d_input_labels = base_inst.compute_x_d_labels_based_on_value(x_d); + let input_labels = pi1_input_labels.into_iter().chain(x_d_input_labels).collect::>(); // all_labels[0..2] are constant-wire labels; [2..] are π₁ input labels. let input_labels: Vec<[u8; 16]> = input_labels.iter().map(|s| s.0).collect(); @@ -310,13 +323,12 @@ pub fn babe_prover_wrongly_challenged_cac( prover_state: &ProverSetupState, ) -> Option<(TxWronglyChallengedWitness, usize)> { let base_input_labels: Vec = challenge_witness.input_labels.iter().map(|&b| S(b)).collect(); - + assert_eq!(base_input_labels.len(), 762); let mut prover = BABEProver::new(pk.clone(), proof.clone(), dyn_pubin); let found = prover.check_compute_msg( &prover_state.finalized, - &base_input_labels, - // Todo: fix this with pi1 and x_d - &base_input_labels, + &base_input_labels[0..508], + &base_input_labels[508..], &prover_state.soldering, &prover_state.h_msgs, ); @@ -349,28 +361,18 @@ pub fn babe_prover_withdraw(sig_v_presig: BabeBtcSig) -> TxWithdrawWitness { // ─── Enc_ functions ──────────────────────────────────────────────────────── /// Enc*(crs, x_S, |D|, msg, B; r): -/// Inputs are split as [x_1..x_{|S|}] static, [x_{|S|+1}..x_{|S|+|D|}] dynamic. -/// P_S = gamma_abc[0] + Σ_{k=0}^{|S|-1} x_S[k]·gamma_abc[k+1] +/// P_S = gamma_abc[0] + Σ_{k} x_S[k]·gamma_abc[k+1] /// mask = Y_S^r - e(r·B, γ) where Y_S^r = e(α, r·β) + e(P_S, r·γ) -/// Returns (ctsetup, r·B) — r·B is garbler-private and never published. +/// Same as Instance::enc_setup pub fn we_known_pi1_encsetup( vk: &Groth16VerifyingKey, - static_inputs: &[Fr], - num_dynamic: usize, + static_input: Fr, msg: &[u8], r_bytes: [u8; 32], b_blind: G1Affine, ) -> Option<(WeKnownPi1SetupCt, G1Affine)> { - let num_static = static_inputs.len(); - if num_static + num_dynamic + 1 != vk.gamma_abc_g1.len() { - return None; - } let r = Fr::from_le_bytes_mod_order(&r_bytes); - - let mut p_s = vk.gamma_abc_g1[0].into_group(); - for (k, x) in static_inputs.iter().enumerate() { - p_s += vk.gamma_abc_g1[k + 1].into_group() * *x; - } + let p_s = vk.gamma_abc_g1[0].into_group() + vk.gamma_abc_g1[1].into_group() * static_input; let r_b = b_blind.into_group() * r; let r_delta = vk.delta_g2.into_group() * r; @@ -405,14 +407,14 @@ pub fn we_known_pi1_encprove( /// mask = e(r·π₁, π₂) - e(π₃, r·δ) - Q_blind = Y_S^r - e(r·B, γ) pub fn we_known_pi1_dec( vk: &Groth16VerifyingKey, - ctsetup: &WeKnownPi1SetupCt, - ctprove: &WeKnownPi1ProveCt, + ct_setup: &WeKnownPi1SetupCt, + ct_prove: &WeKnownPi1ProveCt, pi2: ark_bn254::G2Projective, pi3: ark_bn254::G1Projective, ) -> Option> { - let ct1 = g1_from_ser_checked(&ctprove.ct1_r_pi1)?; - let ct1_prime = g1_from_ser_checked(&ctprove.ct1_prime)?; - let ct2 = g2_from_ser_checked(&ctsetup.ct2_r_delta_g2)?; + let ct1 = g1_from_ser_checked(&ct_prove.ct1_r_pi1)?; + let ct1_prime = g1_from_ser_checked(&ct_prove.ct1_prime)?; + let ct2 = g2_from_ser_checked(&ct_setup.ct2_r_delta_g2)?; let r_y = Bn254::pairing(ct1, pi2) - Bn254::pairing(pi3, ct2); let q_blind = Bn254::pairing(ct1_prime, vk.gamma_g2); @@ -420,12 +422,11 @@ pub fn we_known_pi1_dec( let mut mask_bytes = Vec::new(); mask_gt.serialize_compressed(&mut mask_bytes).ok()?; - let mask = ro_from_pairing_bytes(&mask_bytes, ctsetup.ct3_masked_msg.len()); - Some(ctsetup.ct3_masked_msg.iter().zip(mask.iter()).map(|(a, b)| a ^ b).collect()) + let mask = ro_from_pairing_bytes(&mask_bytes, ct_setup.ct3_masked_msg.len()); + Some(ct_setup.ct3_masked_msg.iter().zip(mask.iter()).map(|(a, b)| a ^ b).collect()) } // ─── BABE C&C Soldering E2E flow ───────────────────────────────────────────────────── -// Todo: fix errors. pub fn run_babe_e2e_cac() -> BabeCACE2ERun { let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(GROTH_16_SEED); let a = Fr::from(7u64); @@ -525,7 +526,7 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { // ── Proving phase ───────────────────────────────────────────────────────── - // Assert: Prover posts π₁ + Lamport sig on-chain. + // Assert: Prover posts π₁ + x_d and Lamport sig on-chain. let assert_witness = babe_prover_assert(&proof, &prover_state.lsk_p, dynamic_public_inputs); println!("Prover: posting tx_Assert..."); println!("tx_Assert witness: {} bytes", assert_witness.size_bytes()); @@ -605,11 +606,14 @@ mod tests { fn lamport_sign_verify_roundtrip() { let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(1); let pi1 = G1Affine::from(ark_bn254::G1Projective::rand(&mut rng)); + let x_d = ark_bn254::Fr::rand(&mut rng); let (lsk, lpk) = lamport_keygen(&mut rng); - let sig = lamport_sign(&lsk, &pi1); - assert!(lamport_verify(&lpk, &pi1, &sig)); + let sig = lamport_sign(&lsk, &pi1, x_d); + assert!(lamport_verify(&lpk, &pi1, x_d, &sig)); let pi1_other = G1Affine::from(ark_bn254::G1Projective::rand(&mut rng)); - assert!(!lamport_verify(&lpk, &pi1_other, &sig)); + assert!(!lamport_verify(&lpk, &pi1_other, x_d, &sig)); + let x_d_other = ark_bn254::Fr::rand(&mut rng); + assert!(!lamport_verify(&lpk, &pi1, x_d_other, &sig)); } #[test] @@ -627,20 +631,19 @@ mod tests { ).unwrap(); // |S|=1 (a*b is static), |D|=1 (a*a is dynamic). - let static_inputs = vec![a * b]; - let dyn_inputs = vec![a * a]; - let num_dynamic = 1usize; + let static_inputs = a * b; + let dyn_inputs = a * a; let secret = b"test-secret-32by"; let r_bytes = h_256(b"r-test"); let b_blind = G1Affine::generator(); let (ct_setup, r_b_affine) = we_known_pi1_encsetup( - &vk, &static_inputs, num_dynamic, secret, r_bytes, b_blind, + &vk, static_inputs, secret, r_bytes, b_blind, ).unwrap(); // Simulate DSGC: c1' = r·P_D + r·B // P_D = (a*a) · gamma_abc[|S|+1] = (a*a) · gamma_abc[2] let r = Fr::from_le_bytes_mod_order(&r_bytes); - let p_d = vk.gamma_abc_g1[2].into_group() * dyn_inputs[0]; + let p_d = vk.gamma_abc_g1[2].into_group() * dyn_inputs; let c1_prime = (p_d * r + r_b_affine.into_group()).into_affine(); let ct_prove = we_known_pi1_encprove(proof.a.into_group(), r_bytes, c1_prime); diff --git a/verifiable-circuit-babe/src/gc/circuit.rs b/verifiable-circuit-babe/src/gc/circuit.rs index 493267d..2663cd0 100644 --- a/verifiable-circuit-babe/src/gc/circuit.rs +++ b/verifiable-circuit-babe/src/gc/circuit.rs @@ -1,6 +1,6 @@ use ark_bn254::G1Affine; use ark_ec::CurveGroup; -use ark_ff::{AdditiveGroup, PrimeField, Zero}; +use ark_ff::{AdditiveGroup, Zero}; use sha2::{Digest, Sha256}; use garbled_snark_verifier::circuits::sect233k1::builder::{CircuitAdapter, CircuitTrait}; use garbled_snark_verifier::dv_bn254::basic::selector; diff --git a/verifiable-circuit-babe/src/instance/mod.rs b/verifiable-circuit-babe/src/instance/mod.rs index faf0f9a..56fd523 100644 --- a/verifiable-circuit-babe/src/instance/mod.rs +++ b/verifiable-circuit-babe/src/instance/mod.rs @@ -173,7 +173,7 @@ impl CACInstance { }) } - /// Enc*(crs, x_S, |D|, msg; r, r·B): + /// Enc*(crs, x_S, |D|, msg; r, B): /// P_S = gamma_abc[0] + Σ_{k} x_S[k]·gamma_abc[k+1] /// mask = Y_S^r - e(r·B, γ) where Y_S^r = e(α, r·β) + e(P_S, r·γ) fn enc_setup( diff --git a/verifiable-circuit-babe/src/lamport.rs b/verifiable-circuit-babe/src/lamport.rs index 3ba4d6c..48aed66 100644 --- a/verifiable-circuit-babe/src/lamport.rs +++ b/verifiable-circuit-babe/src/lamport.rs @@ -3,6 +3,7 @@ use ark_bn254::G1Affine; use rand::RngCore; use serde::{Deserialize, Serialize}; +use garbled_snark_verifier::circuits::bn254::fr::Fr; use crate::babe::LAMPORT_N; use crate::utils::{derive_hashlock, pi1_to_bits}; @@ -34,16 +35,20 @@ pub fn lamport_keygen(rng: &mut impl RngCore) -> (LamportSk, LamportPk) { } /// Sign π₁: reveal sk[i][bit_i(π₁)] for each bit. -pub fn lamport_sign(sk: &LamportSk, pi1: &G1Affine) -> LamportSig { - let bits = pi1_to_bits(pi1); +pub fn lamport_sign(sk: &LamportSk, pi1: &G1Affine, x_d: ark_bn254::Fr) -> LamportSig { + let pi1_bits = pi1_to_bits(pi1); + let x_d_bits = Fr::to_bits(x_d); + let bits = pi1_bits.into_iter().chain(x_d_bits.into_iter()).collect::>(); LamportSig(bits.iter().enumerate().map(|(i, &b)| sk.0[i][b as usize]).collect()) } /// Verify a Lamport signature against lpk_P and π₁. -pub fn lamport_verify(pk: &LamportPk, pi1: &G1Affine, sig: &LamportSig) -> bool { +pub fn lamport_verify(pk: &LamportPk, pi1: &G1Affine, x_d: ark_bn254::Fr, sig: &LamportSig) -> bool { if sig.0.len() != LAMPORT_N { return false; } - let bits = pi1_to_bits(pi1); + let pi1_bits = pi1_to_bits(pi1); + let x_d_bits = Fr::to_bits(x_d); + let bits = pi1_bits.into_iter().chain(x_d_bits.into_iter()).collect::>(); bits.iter().enumerate().all(|(i, &b)| derive_hashlock(&sig.0[i]) == pk.0[i][b as usize]) } diff --git a/verifiable-circuit-babe/src/transactions.rs b/verifiable-circuit-babe/src/transactions.rs index 0b4b490..e52d402 100644 --- a/verifiable-circuit-babe/src/transactions.rs +++ b/verifiable-circuit-babe/src/transactions.rs @@ -1,7 +1,7 @@ // ─── Transaction locking script ─────────────────────────────────────────────── use serde::{Deserialize, Serialize}; -use crate::babe::{BabeBtcSig, BtcPk, BTC_SIG_BYTES, LAMPORT_N, LAMPORT_SIG_BYTES, MSG_BYTES, PI1_BYTES}; +use crate::babe::{BabeBtcSig, BtcPk, BTC_SIG_BYTES, LAMPORT_SIG_BYTES, MSG_BYTES}; use crate::lamport::LamportSig; /// Constants embedded in the locking script of tx_Deposit output 0. @@ -28,7 +28,7 @@ pub struct TxChallengeAssertOutputLock { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct TxAssertWitness { /// Compressed G1Affine, 33 bytes — the asserted proof element. - /// This is not onchain + /// In practice, this is not onchain. pub pi1: Vec, /// Dynamic public input scalar x_d, 32 bytes (little-endian Fr). /// This is not onchain @@ -45,9 +45,10 @@ pub struct TxAssertWitness { /// (b) SHA256(L[i]) == lpk_V[i][bit_i] — L[i] is the correct GC label for bit_i under epk #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct TxChallengeAssertWitness { - /// L₁…L_M — one GC input label per π₁ bit, LAMPORT_N × 16 bytes. + /// L₁…L_M — one GC input label per π₁ bit and x_d bit, LAMPORT_N × 16 bytes. pub input_labels: Vec<[u8; 16]>, - /// μ₁…μ_M — Lamport sig re-posted to bind L to π₁. + /// μ₁…μ_M — Lamport sig re-posted to bind L to π₁ and x_d. This lamport_sig is submitted + /// from Prover before in TxAssertWitness. pub lamport_sig: LamportSig, /// VerifierLiveSig pub sig_v: BabeBtcSig, @@ -108,11 +109,9 @@ impl OnchainSize for TxAssertWitness { impl OnchainSize for TxChallengeAssertWitness { fn size_bytes(&self) -> usize { - LAMPORT_N * 16 - + LAMPORT_SIG_BYTES + LAMPORT_SIG_BYTES * 2 + BTC_SIG_BYTES // sig_v: 32 bytes + BTC_SIG_BYTES // sig_p: 32 bytes - // total: 16,320 bytes } } @@ -120,7 +119,6 @@ impl OnchainSize for TxWronglyChallengedWitness { fn size_bytes(&self) -> usize { BTC_SIG_BYTES // sig_p: 32 bytes + MSG_BYTES // msg: 32 bytes - // total: 64 bytes } } @@ -135,52 +133,3 @@ impl OnchainSize for TxWithdrawWitness { BTC_SIG_BYTES * 4 // 4 sigs × 32 bytes = 128 bytes } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::babe::{BabeBtcSig, LAMPORT_N, LAMPORT_SIG_BYTES, PI1_BYTES}; - use crate::transactions::{TxAssertWitness, TxChallengeAssertWitness, TxWronglyChallengedWitness}; - - // #[test] - // fn onchain_sizes() { - // assert_eq!(LAMPORT_SIG_BYTES, 508 * 16); - // - // // Construct minimal witnesses to call size_bytes(). - // let dummy_sig = BabeBtcSig::ProverLiveSig; - // let dummy_lamport = LamportSig(vec![[0u8; 16]; LAMPORT_N]); - // - // let assert_w = TxAssertWitness { - // pi1: vec![0u8; PI1_BYTES], - // x_d: vec![0u8; 32], - // lamport_sig: dummy_lamport.clone(), - // }; - // assert_eq!(assert_w.size_bytes(), 8161); - // - // let challenge_w = TxChallengeAssertWitness { - // input_labels: vec![[0u8; 16]; LAMPORT_N], - // lamport_sig: dummy_lamport, - // sig_v: dummy_sig.clone(), - // sig_p: dummy_sig.clone(), - // }; - // assert_eq!(challenge_w.size_bytes(), 16320); - // - // let wc_w = TxWronglyChallengedWitness { sig_p: dummy_sig.clone(), msg: [0u8; 32] }; - // assert_eq!(wc_w.size_bytes(), 64); - // - // let nw_w = TxNoWithdrawWitness { - // input0_sig_p: dummy_sig.clone(), - // input0_sig_v: dummy_sig.clone(), - // input1_sig_v: dummy_sig.clone(), - // }; - // assert_eq!(nw_w.size_bytes(), 96); - // - // let wd_w = TxWithdrawWitness { - // input0_sig_p: dummy_sig.clone(), - // input0_sig_v: dummy_sig.clone(), - // input1_sig_p: dummy_sig.clone(), - // input1_sig_v: dummy_sig, - // }; - // assert_eq!(wd_w.size_bytes(), 128); - // } -} diff --git a/verifiable-circuit-babe/tests/babe_e2e.rs b/verifiable-circuit-babe/tests/babe_e2e.rs index aa43087..771a8c1 100644 --- a/verifiable-circuit-babe/tests/babe_e2e.rs +++ b/verifiable-circuit-babe/tests/babe_e2e.rs @@ -4,7 +4,7 @@ use verifiable_circuit_babe::babe::run_babe_e2e_cac; fn e2e_babe_cac() { use verifiable_circuit_babe::transactions::OnchainSize; let run = run_babe_e2e_cac(); - assert_eq!(run.assert_witness.size_bytes(), 8161); - assert_eq!(run.challenge_assert_witness.size_bytes(), 16320); + assert_eq!(run.assert_witness.size_bytes(), 12192); // 762 * 16 + assert_eq!(run.challenge_assert_witness.size_bytes(), 24448); // 762 * 16 * 2 + 64 assert_eq!(run.wrongly_challenged_witness.size_bytes(), 64); } From b27ae78aa4f8f90572c0e441b2d8921b7e42c990 Mon Sep 17 00:00:00 2001 From: vanhger Date: Mon, 4 May 2026 10:06:15 +0700 Subject: [PATCH 21/32] remove dead code --- verifiable-circuit-babe/src/cac.rs | 2 +- verifiable-circuit-babe/src/gc/mod.rs | 3 ++- verifiable-circuit-babe/src/instance/mod.rs | 1 - verifiable-circuit-babe/src/prover.rs | 8 +++----- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/verifiable-circuit-babe/src/cac.rs b/verifiable-circuit-babe/src/cac.rs index cdfb2ce..6d760e5 100644 --- a/verifiable-circuit-babe/src/cac.rs +++ b/verifiable-circuit-babe/src/cac.rs @@ -196,7 +196,7 @@ mod tests { DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).expect("groth16 setup"); let static_public_inputs = a * b; - let dynamic_public_inputs = a * a; + let _dynamic_public_inputs = a * a; // Verifier creates TEST_N_CC instances and commits. let now = std::time::Instant::now(); diff --git a/verifiable-circuit-babe/src/gc/mod.rs b/verifiable-circuit-babe/src/gc/mod.rs index c9fe7f9..0af72b4 100644 --- a/verifiable-circuit-babe/src/gc/mod.rs +++ b/verifiable-circuit-babe/src/gc/mod.rs @@ -151,7 +151,8 @@ mod tests { assert_eq!(sgc.0.len(), s_circuit.0.len(), "reconstructed wire count mismatch"); assert_eq!(fgc.1.len(), f_circuit.1.len(), "reconstructed gate count mismatch"); assert_eq!(sgc.1.len(), s_circuit.1.len(), "reconstructed gate count mismatch"); - + assert_eq!(fgc_indices, fgc_output_indices, "reconstructed output wire mismatch"); + assert_eq!(sgc_indices, sgc_output_indices, "reconstructed output wire mismatch"); for (i, (orig, rec)) in f_circuit.1.iter().zip(fgc.1.iter()).enumerate() { assert_eq!(orig.gate_type, rec.gate_type, "reconstructed gate[{i}] type mismatch"); assert_eq!(orig.wire_a.borrow().id, rec.wire_a.borrow().id, "reconstructed gate[{i}] wire_a id mismatch"); diff --git a/verifiable-circuit-babe/src/instance/mod.rs b/verifiable-circuit-babe/src/instance/mod.rs index 56fd523..4a9da98 100644 --- a/verifiable-circuit-babe/src/instance/mod.rs +++ b/verifiable-circuit-babe/src/instance/mod.rs @@ -323,7 +323,6 @@ mod tests { #[test] fn enc_setup_prove_dec_roundtrip() { use crate::babe::{we_known_pi1_dec, WeKnownPi1ProveCt}; - use crate::utils::g1_to_ser; let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(GROTH_16_SEED); diff --git a/verifiable-circuit-babe/src/prover.rs b/verifiable-circuit-babe/src/prover.rs index 18edf15..33ced21 100644 --- a/verifiable-circuit-babe/src/prover.rs +++ b/verifiable-circuit-babe/src/prover.rs @@ -375,7 +375,6 @@ mod tests { use garbled_snark_verifier::core::utils::reset_gid; use crate::babe::DummyMulCircuit; use crate::cac::cac_finalize_indices; - use crate::dre::N; use crate::instance::CACInstance; use crate::soldering::{build_soldered_wires_input, soldering_guest_compute, SolderingProof}; use crate::verifier::BABEVerifier; @@ -400,7 +399,6 @@ mod tests { DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).unwrap(); - let l2 = vk.gamma_abc_g1[2]; let static_public_inputs = a * b; let dynamic_public_inputs = a * a; // x_d @@ -461,12 +459,12 @@ mod tests { DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).unwrap(); let static_public_inputs = a * b; - let dynamic_public_inputs = a * a; + let _dynamic_public_inputs = a * a; let verifier = BABEVerifier::new(TEST_N_CC, &vk, static_public_inputs).unwrap(); let package = verifier.commit(); let finalized_indices = cac_finalize_indices(&package, TEST_M_CC); - let (temp, finalized) = verifier.open(&finalized_indices); + let (_temp, finalized) = verifier.open(&finalized_indices); let soldered_input = build_soldered_wires_input(&verifier, &finalized_indices); let soldered_output = soldering_guest_compute(&soldered_input); @@ -507,7 +505,7 @@ mod tests { DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).unwrap(); - let static_public_inputs = a * b; + let _static_public_inputs = a * b; let dynamic_public_inputs = a * a; From 078c165e59a31a01a4c9833b35d5c80a0e783ef1 Mon Sep 17 00:00:00 2001 From: vanhger Date: Wed, 6 May 2026 06:41:17 +0700 Subject: [PATCH 22/32] opt memory --- verifiable-circuit-babe/src/babe.rs | 8 +- verifiable-circuit-babe/src/cac.rs | 94 ++++++------ verifiable-circuit-babe/src/prover.rs | 6 +- verifiable-circuit-babe/src/soldering.rs | 6 +- verifiable-circuit-babe/src/verifier.rs | 174 +++++++++++++++++------ 5 files changed, 194 insertions(+), 94 deletions(-) diff --git a/verifiable-circuit-babe/src/babe.rs b/verifiable-circuit-babe/src/babe.rs index 98a435c..e82e930 100644 --- a/verifiable-circuit-babe/src/babe.rs +++ b/verifiable-circuit-babe/src/babe.rs @@ -166,7 +166,7 @@ pub fn babe_verifier_open_and_solder( verifier: &BABEVerifier, finalized_indices: &[usize], ) -> (Vec<(usize, u64)>, Vec, SolderingData, [u8; 20]) { - let (opened, finalized) = verifier.open(finalized_indices); + let (opened, finalized) = verifier.open(finalized_indices).expect("verifier open failed"); // Note that this part will be replaced by generating soldering proof in production. let soldered_input = build_soldered_wires_input(verifier, finalized_indices); @@ -295,10 +295,8 @@ pub fn babe_verifier_challenge_assert_cac( // Derive labels from the base instance (finalized_indices[0]). let base_idx = verifier_state.finalized_indices[0]; - let base_inst = &verifier_state.verifier.instances[base_idx]; - // Todo: fix this (use x_d) - let pi1_input_labels = base_inst.compute_pi1_labels_based_on_value(pi1); - let x_d_input_labels = base_inst.compute_x_d_labels_based_on_value(x_d); + let pi1_input_labels = verifier_state.verifier.compute_pi1_labels(base_idx, pi1); + let x_d_input_labels = verifier_state.verifier.compute_x_d_labels(base_idx, x_d); let input_labels = pi1_input_labels.into_iter().chain(x_d_input_labels).collect::>(); // all_labels[0..2] are constant-wire labels; [2..] are π₁ input labels. let input_labels: Vec<[u8; 16]> = input_labels.iter().map(|s| s.0).collect(); diff --git a/verifiable-circuit-babe/src/cac.rs b/verifiable-circuit-babe/src/cac.rs index 6d760e5..037fe78 100644 --- a/verifiable-circuit-babe/src/cac.rs +++ b/verifiable-circuit-babe/src/cac.rs @@ -82,45 +82,59 @@ pub fn verify_opened_instances( static_public_inputs: Fr, ) -> Result<(), String> { use p3_maybe_rayon::prelude::*; - opened - .par_iter() - .map(|&(idx, seed)| { - let inst = CACInstance::new_from_seed( - seed, - vk, - static_public_inputs, - )?; - - let recomputed = inst.commit(); - let committed = &package.commits[idx]; - - if recomputed.epk != committed.epk { - return Err(format!("instance {idx}: input_commits mismatch")); - } - if recomputed.constant_commits_0 != committed.constant_commits_0 - || recomputed.constant_commits_1 != committed.constant_commits_1 { - return Err(format!("instance {idx}: constant_commits mismatch")); - } - if recomputed.b_blind_commit != committed.b_blind_commit { - return Err(format!("instance {idx}: b_bind_commit mismatch")); - } - if recomputed.h_msg != committed.h_msg { - return Err(format!("instance {idx}: h_msg mismatch")); - } - if recomputed.h_ct_setup != committed.h_ct_setup { - return Err(format!("instance {idx}: ct_setup mismatch")); - } - if recomputed.com_adaptor != committed.com_adaptor { - return Err(format!("instance {idx}: com_adaptor mismatch")); - } - if recomputed.com_gc != committed.com_gc { - return Err(format!("instance {idx}: com_gc mismatch")); - } - Ok(()) - }) - .collect::>() - .into_iter() - .collect::, _>>()?; + + const BATCH_SIZE: usize = 8; + + let pool = rayon::ThreadPoolBuilder::new() + .num_threads(BATCH_SIZE) + .build() + .map_err(|e| e.to_string())?; + + for batch in opened.chunks(BATCH_SIZE) { + // Process up to BATCH_SIZE instances in parallel; each instance is + // generated, checked, and dropped within the closure. + // Peak memory per batch: BATCH_SIZE × ~6 GB. + let results: Vec> = pool.install(|| { + batch + .par_iter() + .map(|&(idx, seed)| { + let inst = CACInstance::new_from_seed(seed, vk, static_public_inputs)?; + let recomputed = inst.commit(); + // inst (heavy GC data) is dropped here + let committed = &package.commits[idx]; + + if recomputed.epk != committed.epk { + return Err(format!("instance {idx}: input_commits mismatch")); + } + if recomputed.constant_commits_0 != committed.constant_commits_0 + || recomputed.constant_commits_1 != committed.constant_commits_1 + { + return Err(format!("instance {idx}: constant_commits mismatch")); + } + if recomputed.b_blind_commit != committed.b_blind_commit { + return Err(format!("instance {idx}: b_bind_commit mismatch")); + } + if recomputed.h_msg != committed.h_msg { + return Err(format!("instance {idx}: h_msg mismatch")); + } + if recomputed.h_ct_setup != committed.h_ct_setup { + return Err(format!("instance {idx}: ct_setup mismatch")); + } + if recomputed.com_adaptor != committed.com_adaptor { + return Err(format!("instance {idx}: com_adaptor mismatch")); + } + if recomputed.com_gc != committed.com_gc { + return Err(format!("instance {idx}: com_gc mismatch")); + } + Ok(()) + }) + .collect() + }); + + for result in results { + result?; + } + } Ok(()) } @@ -215,7 +229,7 @@ mod tests { // Verifier opens: seeds for the rest, GC data for finalized. let now = std::time::Instant::now(); - let (opened, finalized) = verifier.open(&finalized_indices); + let (opened, finalized) = verifier.open(&finalized_indices).expect("verifier open failed"); assert_eq!(opened.len(), TEST_N_CC - TEST_M_CC); assert_eq!(finalized.len(), TEST_M_CC); let elapsed = now.elapsed(); diff --git a/verifiable-circuit-babe/src/prover.rs b/verifiable-circuit-babe/src/prover.rs index 33ced21..1ccc340 100644 --- a/verifiable-circuit-babe/src/prover.rs +++ b/verifiable-circuit-babe/src/prover.rs @@ -464,7 +464,7 @@ mod tests { let verifier = BABEVerifier::new(TEST_N_CC, &vk, static_public_inputs).unwrap(); let package = verifier.commit(); let finalized_indices = cac_finalize_indices(&package, TEST_M_CC); - let (_temp, finalized) = verifier.open(&finalized_indices); + let (_temp, finalized) = verifier.open(&finalized_indices).expect("verifier open failed"); let soldered_input = build_soldered_wires_input(&verifier, &finalized_indices); let soldered_output = soldering_guest_compute(&soldered_input); @@ -511,8 +511,8 @@ mod tests { // base_input_labels: active labels for the 508 π₁ input wires of the base instance. let base_idx = finalized_indices[0]; - let pi1_labels = verifier.instances[base_idx].compute_pi1_labels_based_on_value(proof.a); - let x_d_labels = verifier.instances[base_idx].compute_x_d_labels_based_on_value(dynamic_public_inputs); + let pi1_labels = verifier.compute_pi1_labels(base_idx, proof.a); + let x_d_labels = verifier.compute_x_d_labels(base_idx, dynamic_public_inputs); let mut prover = BABEProver::new(pk.clone(), proof.clone(), dynamic_public_inputs); // Extract h_msgs from bitcoin script of WronglyChallenged Txn diff --git a/verifiable-circuit-babe/src/soldering.rs b/verifiable-circuit-babe/src/soldering.rs index d680751..bbdf383 100644 --- a/verifiable-circuit-babe/src/soldering.rs +++ b/verifiable-circuit-babe/src/soldering.rs @@ -84,9 +84,9 @@ pub fn build_soldered_wires_input( finalized_indices .iter() .map(|&idx| { - let inst = &verifier.instances[idx]; - let delta = &inst.secrets.delta; - let encoding_keys = &inst.secrets.encoding_keys; + let ls = &verifier.light_secrets[idx]; + let delta = &ls.delta; + let encoding_keys = &ls.encoding_keys; (0..2) .flat_map(|i| { diff --git a/verifiable-circuit-babe/src/verifier.rs b/verifiable-circuit-babe/src/verifier.rs index 26b6797..03ca662 100644 --- a/verifiable-circuit-babe/src/verifier.rs +++ b/verifiable-circuit-babe/src/verifier.rs @@ -1,21 +1,45 @@ -use ark_bn254::Fr; +use ark_bn254::{Fr, G1Affine}; use ark_groth16::VerifyingKey as Groth16VerifyingKey; use rand::Rng; +use garbled_snark_verifier::bag::S; +use garbled_snark_verifier::dv_bn254::fq::Fq as DvFq; +use garbled_snark_verifier::dv_bn254::fr::Fr as DvFr; use crate::instance::CACInstance; +use crate::instance::commit::CACInstanceCommit; + +/// Number of instances generated in parallel per batch during the commitment phase. +/// Tune to match available RAM: peak ≈ BATCH_SIZE × ~6 GB. +const BATCH_SIZE: usize = 8; + +/// Minimal per-instance secrets retained after commitment phase. +/// Only encoding keys and deltas are kept — all heavy GC data is dropped. +pub struct InstanceLightSecrets { + pub delta: [S; 2], + pub encoding_keys: [Vec; 2], +} /// The C&C Verifier: manages N_CC garbled-circuit instances for Cut-and-Choose. +/// +/// Instances are generated in batches of BATCH_SIZE. After each batch the heavy +/// GC data (ciphertexts, adaptor tables) is dropped immediately; only the +/// commitment hash and minimal secrets are retained. Peak memory is therefore +/// BATCH_SIZE × ~6 GB rather than N_CC × ~6 GB. pub struct BABEVerifier { - /// All N_CC instances, each fully derived from its own seed. - pub instances: Vec, - /// value used for CA_2 Txn + seeds: Vec, + commits: Vec, + /// Encoding keys and deltas for every instance — needed for label computation + /// without re-deriving the full garbled circuit. + pub light_secrets: Vec, pub temp_val: [u8; 32], + vk: Groth16VerifyingKey, + static_public_inputs: Fr, } impl BABEVerifier { - /// Create `n_cc` fresh instances and run `enc_setup` on each. + /// Create `n_cc` fresh instances processed in batches of BATCH_SIZE. /// - /// Each instance gets a random seed. `enc_setup` binds each instance's - /// secret message to the given Groth16 verifying key and public inputs. + /// For each batch: instances are generated in parallel, their commitments + /// and minimal secrets extracted, then the heavy GC data is dropped. pub fn new( n_cc: usize, vk: &Groth16VerifyingKey, @@ -26,74 +50,138 @@ impl BABEVerifier { let seeds: Vec = (0..n_cc).map(|_| rand::random()).collect(); let pool = rayon::ThreadPoolBuilder::new() - .num_threads(8) + .num_threads(BATCH_SIZE) .build() .map_err(|e| e.to_string())?; - let instances = pool.install(|| { - seeds - .par_iter() - .map(|&seed| { - CACInstance::new_from_seed(seed, vk, static_public_inputs) - }) - .collect::>>() - .into_iter() - .collect::, _>>() - })?; + let mut commits = Vec::with_capacity(n_cc); + let mut light_secrets = Vec::with_capacity(n_cc); + + for batch_seeds in seeds.chunks(BATCH_SIZE) { + // Generate up to BATCH_SIZE instances in parallel, extract what we need, + // then drop the heavy GC data at the end of this block. + let batch_results: Vec> = + pool.install(|| { + batch_seeds + .par_iter() + .map(|&seed| { + let inst = CACInstance::new_from_seed(seed, vk, static_public_inputs)?; + let commit = inst.commit(); + let ls = InstanceLightSecrets { + delta: inst.secrets.delta, + encoding_keys: inst.secrets.encoding_keys.clone(), + }; + // inst (ciphertexts_sets, adaptor_tables) is dropped here + Ok((commit, ls)) + }) + .collect() + }); + + for result in batch_results { + let (commit, ls) = result?; + commits.push(commit); + light_secrets.push(ls); + } + } let rng = &mut rand::thread_rng(); let mut temp_val = [0u8; 32]; rng.fill(&mut temp_val); - Ok(Self { instances, temp_val }) + Ok(Self { + seeds, + commits, + light_secrets, + temp_val, + vk: vk.clone(), + static_public_inputs, + }) } - /// Build the C&C commit package: one `CACInstanceCommit` per instance. - /// Sent to the Prover at the start of the C&C protocol. + /// Return the C&C commit package from pre-computed commitments. pub fn commit(&self) -> crate::cac::CACSetupPackage { crate::cac::CACSetupPackage { - commits: self.instances.iter().map(|inst| inst.commit()).collect(), + commits: self.commits.clone(), } } - /// After receiving the Prover's finalized indices, reveal the open round: - /// - seeds for instance not in I - /// - GC data for every instance in I + /// After receiving the finalized indices, reveal: + /// - seeds for the non-finalized instances (N_CC - M_CC) + /// - full GC data for the M_CC finalized instances, regenerated one-by-one + /// + /// Peak memory during this call: 1 × ~6 GB (sequential regeneration). pub fn open( &self, finalized_indices: &[usize], - ) -> (Vec<(usize, u64)>, Vec) { - let mut opened = Vec::new(); - for (i, inst) in self.instances.iter().enumerate() { - if !finalized_indices.contains(&i) { - opened.push((i, inst.seed)); - } - } + ) -> Result<(Vec<(usize, u64)>, Vec), String> { + let finalized_set: std::collections::HashSet = + finalized_indices.iter().copied().collect(); + + let opened: Vec<(usize, u64)> = (0..self.seeds.len()) + .filter(|i| !finalized_set.contains(i)) + .map(|i| (i, self.seeds[i])) + .collect(); - let mut finalized = Vec::new(); + // Regenerate finalized instances sequentially to keep peak at 1 × ~6 GB. + let mut finalized = Vec::with_capacity(finalized_indices.len()); for &i in finalized_indices { - let inst = &self.instances[i]; + let inst = CACInstance::new_from_seed( + self.seeds[i], + &self.vk, + self.static_public_inputs, + )?; + let constant_labels_0 = [ - inst.secrets.constant_0labels[0][0], inst.secrets.constant_0labels[0][1] ^ inst.secrets.delta[0] + inst.secrets.constant_0labels[0][0], + inst.secrets.constant_0labels[0][1] ^ inst.secrets.delta[0], ]; - let mut constant_labels_1 = vec![ - inst.secrets.constant_0labels[1][0], inst.secrets.constant_0labels[1][1] ^ inst.secrets.delta[1] + inst.secrets.constant_0labels[1][0], + inst.secrets.constant_0labels[1][1] ^ inst.secrets.delta[1], ]; constant_labels_1.extend(inst.get_b_value_labels()); - finalized.push(crate::cac::FinalizedInstanceData { index: i, - ciphertext_sets: inst.ciphertexts_sets.clone(), - adaptor_tables: inst.adaptor_tables.clone(), - ct_setup: inst.ct_setup.clone(), + ciphertext_sets: inst.ciphertexts_sets, + adaptor_tables: inst.adaptor_tables, + ct_setup: inst.ct_setup, constant_labels_0, constant_labels_1: constant_labels_1.try_into().unwrap(), - b: inst.secrets.b + b: inst.secrets.b, }); + // inst is dropped here } - (opened, finalized) + Ok((opened, finalized)) + } + + /// Compute the active π₁ input labels for instance `idx` given a concrete π₁. + pub fn compute_pi1_labels(&self, idx: usize, pi1: G1Affine) -> Vec { + let ls = &self.light_secrets[idx]; + let delta = ls.delta[0]; + DvFq::to_bits(pi1.x) + .into_iter() + .chain(DvFq::to_bits(pi1.y)) + .enumerate() + .map(|(i, b)| { + let key = ls.encoding_keys[0][i]; + if b { key ^ delta } else { key } + }) + .collect() + } + + /// Compute the active x_d input labels for instance `idx` given a concrete x_d. + pub fn compute_x_d_labels(&self, idx: usize, x_d: Fr) -> Vec { + let ls = &self.light_secrets[idx]; + let delta = ls.delta[1]; + DvFr::to_bits(x_d) + .into_iter() + .enumerate() + .map(|(i, b)| { + let key = ls.encoding_keys[1][i]; + if b { key ^ delta } else { key } + }) + .collect() } } From 6eeb884c2d8cb34054ca7f27b26f4482eb1047b3 Mon Sep 17 00:00:00 2001 From: vanhger Date: Wed, 6 May 2026 07:19:27 +0700 Subject: [PATCH 23/32] opt memory for opening. --- verifiable-circuit-babe/src/verifier.rs | 65 +++++++++++++------------ 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/verifiable-circuit-babe/src/verifier.rs b/verifiable-circuit-babe/src/verifier.rs index 03ca662..ed982ef 100644 --- a/verifiable-circuit-babe/src/verifier.rs +++ b/verifiable-circuit-babe/src/verifier.rs @@ -122,36 +122,41 @@ impl BABEVerifier { .map(|i| (i, self.seeds[i])) .collect(); - // Regenerate finalized instances sequentially to keep peak at 1 × ~6 GB. - let mut finalized = Vec::with_capacity(finalized_indices.len()); - for &i in finalized_indices { - let inst = CACInstance::new_from_seed( - self.seeds[i], - &self.vk, - self.static_public_inputs, - )?; - - let constant_labels_0 = [ - inst.secrets.constant_0labels[0][0], - inst.secrets.constant_0labels[0][1] ^ inst.secrets.delta[0], - ]; - let mut constant_labels_1 = vec![ - inst.secrets.constant_0labels[1][0], - inst.secrets.constant_0labels[1][1] ^ inst.secrets.delta[1], - ]; - constant_labels_1.extend(inst.get_b_value_labels()); - - finalized.push(crate::cac::FinalizedInstanceData { - index: i, - ciphertext_sets: inst.ciphertexts_sets, - adaptor_tables: inst.adaptor_tables, - ct_setup: inst.ct_setup, - constant_labels_0, - constant_labels_1: constant_labels_1.try_into().unwrap(), - b: inst.secrets.b, - }); - // inst is dropped here - } + // Regenerate all M_CC finalized instances in parallel (M_CC <= 4, + // so peak memory is at most 4 × ~6 GB). + use p3_maybe_rayon::prelude::*; + let finalized: Vec> = finalized_indices + .par_iter() + .map(|&i| { + let inst = CACInstance::new_from_seed( + self.seeds[i], + &self.vk, + self.static_public_inputs, + )?; + + let constant_labels_0 = [ + inst.secrets.constant_0labels[0][0], + inst.secrets.constant_0labels[0][1] ^ inst.secrets.delta[0], + ]; + let mut constant_labels_1 = vec![ + inst.secrets.constant_0labels[1][0], + inst.secrets.constant_0labels[1][1] ^ inst.secrets.delta[1], + ]; + constant_labels_1.extend(inst.get_b_value_labels()); + + Ok(crate::cac::FinalizedInstanceData { + index: i, + ciphertext_sets: inst.ciphertexts_sets, + adaptor_tables: inst.adaptor_tables, + ct_setup: inst.ct_setup, + constant_labels_0, + constant_labels_1: constant_labels_1.try_into().unwrap(), + b: inst.secrets.b, + }) + // inst is dropped here + }) + .collect(); + let finalized = finalized.into_iter().collect::, _>>()?; Ok((opened, finalized)) } From bec0a4589142b21503aa92f8759a1e1c078d8dcd Mon Sep 17 00:00:00 2001 From: vanhger Date: Thu, 7 May 2026 16:13:28 +0700 Subject: [PATCH 24/32] opt memory by avoid storing adaptor table and ciphertext. --- verifiable-circuit-babe/src/gc/adaptor.rs | 42 ++++++ verifiable-circuit-babe/src/gc/circuit.rs | 20 +++ verifiable-circuit-babe/src/instance/mod.rs | 149 +++++++++++++++++++- verifiable-circuit-babe/src/verifier.rs | 15 +- 4 files changed, 217 insertions(+), 9 deletions(-) diff --git a/verifiable-circuit-babe/src/gc/adaptor.rs b/verifiable-circuit-babe/src/gc/adaptor.rs index bb4c428..b2b50f7 100644 --- a/verifiable-circuit-babe/src/gc/adaptor.rs +++ b/verifiable-circuit-babe/src/gc/adaptor.rs @@ -76,6 +76,48 @@ impl SparseAdaptorTable { SparseAdaptorTable { entries } } + /// Build the adaptor table entry-by-entry, hashing each row on-the-fly without + /// materializing the full `Vec`. Produces the same hash as + /// `SparseAdaptorTable::build_from_r_and_u_bar_labels(...).commit()`. + pub fn build_and_hash( + r: Fr, + labels: &[[u8; 16]], + rhos: &[G1Affine], + fq_deltas: &[Fq], + ) -> [u8; 32] { + assert_eq!(labels.len(), 2 * U_BAR_SIZE); + assert_eq!(rhos.len(), N); + assert_eq!(fq_deltas.len(), N); + + let r_bits = garbled_snark_verifier::dv_bn254::fr::Fr::to_bits(r); + let col_indices = nonzero_col_indices(); + let prf_cache: Vec = (0..U_BAR_SIZE).map(|k| prf_fq(&labels[2 * k + 1])).collect(); + + let mut hasher = Sha256::new(); + let mut buf = Vec::new(); + + for i in 0..N { + let r_i = Fq::from(r_bits[i] as u8); + let d = build_d_i_sparse(fq_deltas[i], r_i, &rhos[i]); + + for j in 0..3 { + let mut offset = Fq::zero(); + for (&k, &d_val) in col_indices[j].iter().zip(d[j].iter()) { + let s_k = prf_cache[k] - d_val; + offset += s_k; + let ct = aes_enc(&s_k, &labels[2 * k]); + hasher.update(ct.as_slice()); + } + buf.clear(); + offset.serialize_compressed(&mut buf).expect("serialize Fq offset"); + hasher.update(&buf); + // row data is never stored — dropped here + } + } + + hasher.finalize().into() + } + /// SHA256 over all ciphertexts and Fq offsets in entry/row order. /// Used by the Prover to verify an opened C&C instance's adaptor table. pub fn commit(&self) -> [u8; 32] { diff --git a/verifiable-circuit-babe/src/gc/circuit.rs b/verifiable-circuit-babe/src/gc/circuit.rs index 2663cd0..c79f9b8 100644 --- a/verifiable-circuit-babe/src/gc/circuit.rs +++ b/verifiable-circuit-babe/src/gc/circuit.rs @@ -197,6 +197,26 @@ pub fn gc_ciphertexts_commit(ciphertexts: &[Option>`. Produces the same hash as +/// `gc_ciphertexts_commit(&circuit.garbled_gates_with_delta(delta))`. +pub fn gc_garble_and_hash( + circuit: &garbled_snark_verifier::bag::Circuit, + delta: garbled_snark_verifier::bag::S, +) -> [u8; 32] { + let mut hasher = Sha256::new(); + for (i, gate) in circuit.1.iter().enumerate() { + if i.is_multiple_of(1000000) { + println!("Garble batch: {}/{}", i, circuit.1.len()); + } + match gate.garbled_with_delta(delta) { + None => hasher.update([0u8]), + Some(s) => { hasher.update([1u8]); hasher.update(s.0); } + } + } + hasher.finalize().into() +} + /// Build the unsigned w=8 Base table. /// /// Layout: window `i` (i = 0..WINDOW_COUNT), entry `j` (j = 0..WINDOW_ENTRIES) stores diff --git a/verifiable-circuit-babe/src/instance/mod.rs b/verifiable-circuit-babe/src/instance/mod.rs index 4a9da98..f0b7bc6 100644 --- a/verifiable-circuit-babe/src/instance/mod.rs +++ b/verifiable-circuit-babe/src/instance/mod.rs @@ -4,7 +4,7 @@ use ark_ec::pairing::Pairing; use ark_ff::UniformRand; use garbled_snark_verifier::bag::{Circuit, S}; use crate::babe::WeKnownPi1SetupCt; -use crate::gc::{SparseAdaptorTable, SGC_PART1_CONSTANT_SIZE}; +use crate::gc::{gc_garble_and_hash, SparseAdaptorTable, SGC_PART1_CONSTANT_SIZE}; use crate::instance::secret::InstanceSecrets; use ark_groth16::VerifyingKey as Groth16VerifyingKey; use ark_serialize::CanonicalSerialize; @@ -12,7 +12,7 @@ use garbled_snark_verifier::dv_bn254::fq::Fq as DvFq; use garbled_snark_verifier::dv_bn254::fr::Fr as DvFr; use crate::dre::{Q_SIZE, U_BAR_SIZE}; use crate::instance::commit::CACInstanceCommit; -use crate::utils::{g2_to_ser, ro_from_pairing_bytes}; +use crate::utils::{derive_hashlock, g2_to_ser, h_256, ro_from_pairing_bytes}; pub mod secret; pub mod commit; @@ -173,6 +173,126 @@ impl CACInstance { }) } + /// Commitment-only path: same circuit setup as `new_from_seed` but ciphertexts and + /// adaptor tables are stream-hashed without being stored. Produces identical + /// `CACInstanceCommit` values while keeping peak memory at O(circuit_size) instead + /// of O(circuit_size + ciphertexts + adaptor_tables). + /// + /// Returns the full `InstanceSecrets` alongside the commit so the caller can + /// extract encoding keys and deltas without a second derivation. + pub fn commit_from_seed( + seed: u64, + vk: &Groth16VerifyingKey, + static_inputs: Fr, + ) -> Result<(CACInstanceCommit, InstanceSecrets), String> { + use ark_bn254::G1Projective; + + if vk.gamma_abc_g1.len() != 3 { + return Err("static/dynamic split does not match vk".to_string()); + } + + let secrets = InstanceSecrets::new_from_seed(seed); + let (mut fgc, fgc_indices, mut sgc, sgc_indices) = crate::gc::read_fresh_gc(); + + for (i, &key) in secrets.encoding_keys[0].iter().enumerate() { + fgc.0[2 + i].borrow_mut().label = Some(key); + } + for (i, &key) in secrets.encoding_keys[1].iter().enumerate() { + sgc.0[SGC_PART1_CONSTANT_SIZE + i].borrow_mut().label = Some(key); + } + + let b_x_bits = DvFq::to_bits(DvFq::as_montgomery(secrets.b.x)); + let b_y_bits = DvFq::to_bits(DvFq::as_montgomery(secrets.b.y)); + for (i, bit) in b_x_bits.iter().enumerate() { + sgc.0[2 + i].borrow_mut().value = Some(*bit); + } + for (i, bit) in b_y_bits.iter().enumerate() { + sgc.0[2 + 254 + i].borrow_mut().value = Some(*bit); + } + + set_gc_const_labels(&mut fgc, &secrets.constant_0labels[0]); + set_gc_const_labels(&mut sgc, &secrets.constant_0labels[1]); + + let pi1 = G1Projective::rand(&mut rand::thread_rng()).into_affine(); + let x_d = ark_bn254::Fr::rand(&mut rand::thread_rng()); + + let fgc_witness: Vec = DvFq::to_bits(pi1.x).into_iter().chain(DvFq::to_bits(pi1.y)).collect(); + + // FGC: stream-hash ciphertexts, collect output label pairs (0-labels are deterministic). + let (com_fgc, fgc_output_labels) = garble_hash_and_output_labels( + &mut fgc, &fgc_indices, &fgc_witness, secrets.delta[0], 2, + ); + assert_eq!(fgc_output_labels.len(), 2 * U_BAR_SIZE); + + // SGC part 1. + let sgc_part1_witness: Vec = DvFr::to_bits(x_d); + let (com_sgc_1, sgc_output_labels_1) = garble_hash_and_output_labels( + &mut sgc, &sgc_indices, &sgc_part1_witness, secrets.delta[1], SGC_PART1_CONSTANT_SIZE, + ); + assert_eq!(sgc_output_labels_1.len(), 2 * Q_SIZE); + + // SGC part 2: reuse fgc (same pattern as new_from_seed). + fgc.reset_circuit_except_constants(); + for (i, &key) in sgc_output_labels_1.iter().step_by(2).enumerate() { + fgc.0[2 + i].borrow_mut().label = Some(S(key)); + } + set_gc_const_labels(&mut fgc, &secrets.constant_0labels[1][0..2]); + let (com_sgc_2, sgc_output_labels_2) = garble_hash_and_output_labels( + &mut fgc, &fgc_indices, &fgc_witness, secrets.delta[1], 2, + ); + assert_eq!(sgc_output_labels_2.len(), 2 * U_BAR_SIZE); + + // Adaptor tables: build and hash on-the-fly, never materialized. + let com_adaptor_0 = SparseAdaptorTable::build_and_hash( + secrets.r, &fgc_output_labels, &secrets.rhos[0], &secrets.fq_deltas[0], + ); + let com_adaptor_1 = SparseAdaptorTable::build_and_hash( + secrets.r, &sgc_output_labels_2, &secrets.rhos[1], &secrets.fq_deltas[1], + ); + + // ct_setup (small). + let ct_setup = Self::enc_setup(&secrets, vk, static_inputs)?; + let mut ct_setup_bytes = Vec::new(); + ct_setup_bytes.extend_from_slice(&ct_setup.ct2_r_delta_g2); + ct_setup_bytes.extend_from_slice(&ct_setup.ct3_masked_msg); + + // Build the commit from hashes and small secret data — no GC data retained. + let delta = secrets.delta; + let epk: Vec<[[u8; 20]; 2]> = secrets.encoding_keys[0] + .iter() + .map(|&key| [derive_hashlock(&key.0), derive_hashlock(&(key ^ delta[0]).0)]) + .chain(secrets.encoding_keys[1].iter().map(|&key| { + [derive_hashlock(&key.0), derive_hashlock(&(key ^ delta[1]).0)] + })) + .collect(); + + let constant_commits_0: [[[u8; 32]; 2]; 2] = std::array::from_fn(|w| { + let l0 = secrets.constant_0labels[0][w]; + [h_256(&l0.0), h_256(&(l0 ^ delta[0]).0)] + }); + let constant_commits_1: [[[u8; 32]; 2]; 510] = std::array::from_fn(|w| { + let l0 = secrets.constant_0labels[1][w]; + [h_256(&l0.0), h_256(&(l0 ^ delta[1]).0)] + }); + + let mut b_blind_bytes = Vec::new(); + secrets.b.serialize_compressed(&mut b_blind_bytes).expect("serialize b"); + + let commit = CACInstanceCommit { + epk, + constant_commits_0, + constant_commits_1, + b_blind_commit: h_256(&b_blind_bytes), + h_msg: derive_hashlock(&secrets.msg), + h_ct_setup: h_256(&ct_setup_bytes), + com_adaptor: [com_adaptor_0, com_adaptor_1], + com_gc: [com_fgc, com_sgc_1, com_sgc_2], + }; + + // fgc, sgc circuits (and their wire data) are dropped here. + Ok((commit, secrets)) + } + /// Enc*(crs, x_S, |D|, msg; r, B): /// P_S = gamma_abc[0] + Σ_{k} x_S[k]·gamma_abc[k+1] /// mask = Y_S^r - e(r·B, γ) where Y_S^r = e(α, r·β) + e(P_S, r·γ) @@ -285,6 +405,31 @@ pub fn set_gc_const_labels( } } +/// Streaming variant: evaluate + stream-hash ciphertexts without materializing the Vec. +/// Returns `(com_gc, output_label_pairs)` where output_label_pairs has 2 entries per +/// output wire — the 0-label and the 1-label (0-label XOR delta). +fn garble_hash_and_output_labels( + circuit: &mut Circuit, + output_indices: &[usize], + random_witness: &[bool], + delta: S, + const_skip: usize, +) -> ([u8; 32], Vec<[u8; 16]>) { + circuit.set_witness_value(random_witness, const_skip); + for gate in &mut circuit.1 { + gate.evaluate(); + } + let com_gc = gc_garble_and_hash(circuit, delta); + let output_labels: Vec<[u8; 16]> = output_indices + .iter() + .flat_map(|idx| { + let l0 = circuit.0[*idx].borrow().label.unwrap(); + [l0.0, (l0 ^ delta).0] + }) + .collect(); + (com_gc, output_labels) +} + /// Generate ciphertexts and all output labels fn get_ciphertext_and_output_labels( circuit: &mut Circuit, diff --git a/verifiable-circuit-babe/src/verifier.rs b/verifiable-circuit-babe/src/verifier.rs index ed982ef..f5744c2 100644 --- a/verifiable-circuit-babe/src/verifier.rs +++ b/verifiable-circuit-babe/src/verifier.rs @@ -58,20 +58,21 @@ impl BABEVerifier { let mut light_secrets = Vec::with_capacity(n_cc); for batch_seeds in seeds.chunks(BATCH_SIZE) { - // Generate up to BATCH_SIZE instances in parallel, extract what we need, - // then drop the heavy GC data at the end of this block. + // Generate up to BATCH_SIZE instances in parallel. For each instance, + // stream-hash the ciphertexts and adaptor table without materializing them. + // Peak memory per batch: BATCH_SIZE × O(circuit_size) instead of + // BATCH_SIZE × O(circuit_size + ciphertexts + adaptor_tables). let batch_results: Vec> = pool.install(|| { batch_seeds .par_iter() .map(|&seed| { - let inst = CACInstance::new_from_seed(seed, vk, static_public_inputs)?; - let commit = inst.commit(); + let (commit, secrets) = + CACInstance::commit_from_seed(seed, vk, static_public_inputs)?; let ls = InstanceLightSecrets { - delta: inst.secrets.delta, - encoding_keys: inst.secrets.encoding_keys.clone(), + delta: secrets.delta, + encoding_keys: secrets.encoding_keys, }; - // inst (ciphertexts_sets, adaptor_tables) is dropped here Ok((commit, ls)) }) .collect() From e084a80673fcd356c871ded71b9d4ce36caef4ad Mon Sep 17 00:00:00 2001 From: vanhger Date: Thu, 7 May 2026 16:29:28 +0700 Subject: [PATCH 25/32] fix bug --- verifiable-circuit-babe/src/cac.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/verifiable-circuit-babe/src/cac.rs b/verifiable-circuit-babe/src/cac.rs index 037fe78..ddab92e 100644 --- a/verifiable-circuit-babe/src/cac.rs +++ b/verifiable-circuit-babe/src/cac.rs @@ -91,16 +91,15 @@ pub fn verify_opened_instances( .map_err(|e| e.to_string())?; for batch in opened.chunks(BATCH_SIZE) { - // Process up to BATCH_SIZE instances in parallel; each instance is - // generated, checked, and dropped within the closure. - // Peak memory per batch: BATCH_SIZE × ~6 GB. + // Process up to BATCH_SIZE instances in parallel. commit_from_seed stream-hashes + // ciphertexts and adaptor tables without materializing them, so peak memory per + // batch is BATCH_SIZE × O(circuit_size) instead of BATCH_SIZE × ~6 GB. let results: Vec> = pool.install(|| { batch .par_iter() .map(|&(idx, seed)| { - let inst = CACInstance::new_from_seed(seed, vk, static_public_inputs)?; - let recomputed = inst.commit(); - // inst (heavy GC data) is dropped here + let (recomputed, _secrets) = + CACInstance::commit_from_seed(seed, vk, static_public_inputs)?; let committed = &package.commits[idx]; if recomputed.epk != committed.epk { From a18e5c9e9097eeb52556c2853fbc4ec3c0d6fec1 Mon Sep 17 00:00:00 2001 From: vanhger Date: Fri, 8 May 2026 11:13:43 +0700 Subject: [PATCH 26/32] replace lamport as wots96 --- Cargo.lock | 332 ++++++++++++++++++- babe-programs/Cargo.lock | 343 +++++++++++++++++++- verifiable-circuit-babe/Cargo.toml | 2 + verifiable-circuit-babe/src/babe.rs | 224 +++++++------ verifiable-circuit-babe/src/lib.rs | 1 + verifiable-circuit-babe/src/transactions.rs | 45 +-- verifiable-circuit-babe/src/utils.rs | 22 ++ verifiable-circuit-babe/src/wots.rs | 149 +++++++++ 8 files changed, 966 insertions(+), 152 deletions(-) create mode 100644 verifiable-circuit-babe/src/wots.rs diff --git a/Cargo.lock b/Cargo.lock index b6e49c7..20c9dec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -740,6 +740,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes", +] + [[package]] name = "base64" version = "0.13.1" @@ -770,6 +780,12 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bech32" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" + [[package]] name = "bincode" version = "1.3.3" @@ -852,6 +868,96 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bitcoin" +version = "0.32.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf93e61f2dbc3e3c41234ca26a65e2c0b0975c52e0f069ab9893ebbede584d3" +dependencies = [ + "base58ck", + "bech32 0.11.1", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes", + "hex-conservative", + "hex_lit", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin-script" +version = "0.4.0" +source = "git+https://github.com/BitVM/rust-bitcoin-script#01b4cb66cbf5b525079cabe006f9f99627da97cd" +dependencies = [ + "bitcoin", + "script-macro", + "stdext", +] + +[[package]] +name = "bitcoin-script-stack" +version = "0.0.1" +source = "git+https://github.com/BitVM/rust-bitcoin-script-stack#643c5f1a44af448274849c01a5ae7fbdd54d8213" +dependencies = [ + "bitcoin", + "bitcoin-script", + "bitcoin-scriptexec", +] + +[[package]] +name = "bitcoin-scriptexec" +version = "0.0.0" +source = "git+https://github.com/BitVM/rust-bitcoin-scriptexec#ba96bc2bd76774c9d1b011461cb79d983c2c43a1" +dependencies = [ + "bitcoin", + "clap", + "console_error_panic_hook", + "getrandom 0.2.17", + "lazy_static", + "serde", + "serde-wasm-bindgen", + "serde_json", + "wasm-bindgen", +] + +[[package]] +name = "bitcoin-units" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346568ebaab2918487cea76dd55dae13c27bb618cdb737c952e69eb2017c4118" +dependencies = [ + "bitcoin-internals", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -876,6 +982,37 @@ dependencies = [ "wyz", ] +[[package]] +name = "bitvm" +version = "0.1.0" +source = "git+https://github.com/GOATNetwork/BitVM.git?branch=GA#26b0bd61b61b24b50b2d2443a7fda8e58412edfa" +dependencies = [ + "ark-bn254", + "ark-crypto-primitives", + "ark-ec", + "ark-ff 0.5.0", + "ark-groth16", + "ark-relations", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "bitcoin", + "bitcoin-script", + "bitcoin-script-stack", + "bitcoin-scriptexec", + "blake3", + "colored", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rand 0.8.5", + "rand_chacha 0.3.1", + "regex", + "serde", + "sha2", + "tqdm", +] + [[package]] name = "blake2" version = "0.10.6" @@ -1197,7 +1334,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" dependencies = [ "base64 0.21.7", - "bech32", + "bech32 0.9.1", "bs58", "digest 0.10.7", "generic-array 0.14.7", @@ -1239,6 +1376,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "const-default" version = "1.0.0" @@ -1375,6 +1522,31 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio 0.8.11", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crunchy" version = "0.2.4" @@ -1732,6 +1904,17 @@ dependencies = [ "spki", ] +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "git+https://github.com/zkMIPS-patches/signatures?branch=patch-ecdsa-0.16.9#da0cd8753b243e09acf82f8e58f8da0e28e7287d" +dependencies = [ + "digest 0.10.7", + "elliptic-curve", + "signature", + "spki", +] + [[package]] name = "educe" version = "0.6.0" @@ -2567,8 +2750,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -2815,6 +3000,21 @@ dependencies = [ "serde", ] +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + [[package]] name = "hickory-proto" version = "0.24.4" @@ -3417,7 +3617,7 @@ version = "0.13.4" source = "git+https://github.com/ziren-patches/elliptic-curves?branch=patch-k256-0.13.4#8266b228a39402a0ba68d644b7f26b85b5112fe3" dependencies = [ "cfg-if", - "ecdsa", + "ecdsa 0.16.9 (git+https://github.com/ziren-patches/signatures?branch=patch-ecdsa-0.16.9)", "elliptic-curve", "hex", "once_cell", @@ -3685,6 +3885,18 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.1.1" @@ -4004,7 +4216,7 @@ name = "p256" version = "0.13.2" source = "git+https://github.com/ziren-patches/elliptic-curves?branch=patch-p256-0.13.2#a6f1a1fb07020d00f627725a20dc336983be3946" dependencies = [ - "ecdsa", + "ecdsa 0.16.9 (git+https://github.com/ziren-patches/signatures?branch=patch-ecdsa-0.16.9)", "elliptic-curve", "hex", "primeorder", @@ -4730,6 +4942,30 @@ dependencies = [ "toml_edit 0.23.10+spec-1.0.0", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -5466,6 +5702,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "script-macro" +version = "0.4.0" +source = "git+https://github.com/BitVM/rust-bitcoin-script#01b4cb66cbf5b525079cabe006f9f99627da97cd" +dependencies = [ + "bitcoin", + "proc-macro-error", + "proc-macro2", + "quote", +] + [[package]] name = "scrypt" version = "0.10.0" @@ -5508,6 +5755,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "git+https://github.com/ziren-patches/rust-secp256k1?branch=patch-0.29.1#c7e39ee0732d0e6b5c33721038f8f1b789f77c36" +dependencies = [ + "bitcoin_hashes", + "cfg-if", + "ecdsa 0.16.9 (git+https://github.com/zkMIPS-patches/signatures?branch=patch-ecdsa-0.16.9)", + "rand 0.8.5", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.0" +source = "git+https://github.com/ziren-patches/rust-secp256k1?branch=patch-0.29.1#c7e39ee0732d0e6b5c33721038f8f1b789f77c36" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -5581,6 +5849,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_arrays" version = "0.1.0" @@ -5737,6 +6016,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio 0.8.11", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -5877,6 +6177,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stdext" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4af28eeb7c18ac2dbdb255d40bee63f203120e1db6b0024b177746ebec7049c1" + [[package]] name = "strength_reduce" version = "0.2.4" @@ -6200,7 +6506,7 @@ checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", - "mio", + "mio 1.1.1", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -6475,6 +6781,17 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" +[[package]] +name = "tqdm" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2d2932240205a99b65f15d9861992c95fbb8c9fb280b3a1f17a92db6dc611f" +dependencies = [ + "anyhow", + "crossterm", + "once_cell", +] + [[package]] name = "tracing" version = "0.1.44" @@ -6827,6 +7144,8 @@ dependencies = [ "ark-relations", "ark-serialize 0.5.0", "bincode", + "bitcoin", + "bitvm", "blake3", "cfg-if", "garbled-snark-verifier", @@ -8234,11 +8553,6 @@ name = "rsa" version = "0.9.6" source = "git+https://github.com/ziren-patches/RustCrypto-RSA.git?branch=patch-rsa-0.9.6#df285475f9800a8e20f72a7f93de07539df0ed25" -[[patch.unused]] -name = "secp256k1" -version = "0.29.1" -source = "git+https://github.com/ziren-patches/rust-secp256k1?branch=patch-0.29.1#c7e39ee0732d0e6b5c33721038f8f1b789f77c36" - [[patch.unused]] name = "substrate-bn" version = "0.6.0" diff --git a/babe-programs/Cargo.lock b/babe-programs/Cargo.lock index 8718555..a9d7cb0 100644 --- a/babe-programs/Cargo.lock +++ b/babe-programs/Cargo.lock @@ -524,6 +524,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ + "colored", "num-traits", "rand 0.8.5", "rayon", @@ -739,6 +740,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes", +] + [[package]] name = "base64" version = "0.13.1" @@ -769,6 +780,12 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bech32" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" + [[package]] name = "bincode" version = "1.3.3" @@ -828,6 +845,96 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bitcoin" +version = "0.32.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf93e61f2dbc3e3c41234ca26a65e2c0b0975c52e0f069ab9893ebbede584d3" +dependencies = [ + "base58ck", + "bech32 0.11.1", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes", + "hex-conservative", + "hex_lit", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin-script" +version = "0.4.0" +source = "git+https://github.com/BitVM/rust-bitcoin-script#01b4cb66cbf5b525079cabe006f9f99627da97cd" +dependencies = [ + "bitcoin", + "script-macro", + "stdext", +] + +[[package]] +name = "bitcoin-script-stack" +version = "0.0.1" +source = "git+https://github.com/BitVM/rust-bitcoin-script-stack#643c5f1a44af448274849c01a5ae7fbdd54d8213" +dependencies = [ + "bitcoin", + "bitcoin-script", + "bitcoin-scriptexec", +] + +[[package]] +name = "bitcoin-scriptexec" +version = "0.0.0" +source = "git+https://github.com/BitVM/rust-bitcoin-scriptexec#ba96bc2bd76774c9d1b011461cb79d983c2c43a1" +dependencies = [ + "bitcoin", + "clap", + "console_error_panic_hook", + "getrandom 0.2.17", + "lazy_static", + "serde", + "serde-wasm-bindgen", + "serde_json", + "wasm-bindgen", +] + +[[package]] +name = "bitcoin-units" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346568ebaab2918487cea76dd55dae13c27bb618cdb737c952e69eb2017c4118" +dependencies = [ + "bitcoin-internals", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -852,6 +959,37 @@ dependencies = [ "wyz", ] +[[package]] +name = "bitvm" +version = "0.1.0" +source = "git+https://github.com/GOATNetwork/BitVM.git?branch=GA#26b0bd61b61b24b50b2d2443a7fda8e58412edfa" +dependencies = [ + "ark-bn254", + "ark-crypto-primitives", + "ark-ec", + "ark-ff 0.5.0", + "ark-groth16", + "ark-relations", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "bitcoin", + "bitcoin-script", + "bitcoin-script-stack", + "bitcoin-scriptexec", + "blake3", + "colored", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rand 0.8.5", + "rand_chacha 0.3.1", + "regex", + "serde", + "sha2", + "tqdm", +] + [[package]] name = "blake2" version = "0.10.6" @@ -1176,7 +1314,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" dependencies = [ "base64 0.21.7", - "bech32", + "bech32 0.9.1", "bs58", "digest 0.10.7", "generic-array 0.14.9", @@ -1195,6 +1333,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "console" version = "0.15.11" @@ -1208,6 +1356,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "const-default" version = "1.0.0" @@ -1353,6 +1511,31 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio 0.8.11", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crunchy" version = "0.2.4" @@ -1710,6 +1893,17 @@ dependencies = [ "spki", ] +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "git+https://github.com/zkMIPS-patches/signatures?branch=patch-ecdsa-0.16.9#da0cd8753b243e09acf82f8e58f8da0e28e7287d" +dependencies = [ + "digest 0.10.7", + "elliptic-curve", + "signature", + "spki", +] + [[package]] name = "educe" version = "0.6.0" @@ -2540,8 +2734,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -2789,6 +2985,21 @@ dependencies = [ "serde", ] +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + [[package]] name = "hickory-proto" version = "0.24.4" @@ -3382,7 +3593,7 @@ version = "0.13.4" source = "git+https://github.com/ziren-patches/elliptic-curves?branch=patch-k256-0.13.4#8266b228a39402a0ba68d644b7f26b85b5112fe3" dependencies = [ "cfg-if", - "ecdsa", + "ecdsa 0.16.9 (git+https://github.com/ziren-patches/signatures?branch=patch-ecdsa-0.16.9)", "elliptic-curve", "hex", "once_cell", @@ -3644,6 +3855,18 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.1.1" @@ -3963,7 +4186,7 @@ name = "p256" version = "0.13.2" source = "git+https://github.com/ziren-patches/elliptic-curves?branch=patch-p256-0.13.2#a6f1a1fb07020d00f627725a20dc336983be3946" dependencies = [ - "ecdsa", + "ecdsa 0.16.9 (git+https://github.com/ziren-patches/signatures?branch=patch-ecdsa-0.16.9)", "elliptic-curve", "hex", "primeorder", @@ -4683,6 +4906,30 @@ dependencies = [ "toml_edit 0.25.4+spec-1.1.0", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -5442,6 +5689,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "script-macro" +version = "0.4.0" +source = "git+https://github.com/BitVM/rust-bitcoin-script#01b4cb66cbf5b525079cabe006f9f99627da97cd" +dependencies = [ + "bitcoin", + "proc-macro-error", + "proc-macro2", + "quote", +] + [[package]] name = "scrypt" version = "0.10.0" @@ -5484,6 +5742,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "git+https://github.com/ziren-patches/rust-secp256k1?branch=patch-0.29.1#c7e39ee0732d0e6b5c33721038f8f1b789f77c36" +dependencies = [ + "bitcoin_hashes", + "cfg-if", + "ecdsa 0.16.9 (git+https://github.com/zkMIPS-patches/signatures?branch=patch-ecdsa-0.16.9)", + "rand 0.8.5", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.0" +source = "git+https://github.com/ziren-patches/rust-secp256k1?branch=patch-0.29.1#c7e39ee0732d0e6b5c33721038f8f1b789f77c36" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -5557,6 +5836,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_arrays" version = "0.1.0" @@ -5713,6 +6003,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio 0.8.11", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -5879,6 +6190,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stdext" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4af28eeb7c18ac2dbdb255d40bee63f203120e1db6b0024b177746ebec7049c1" + [[package]] name = "strength_reduce" version = "0.2.4" @@ -6202,7 +6519,7 @@ checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", - "mio", + "mio 1.1.1", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -6477,6 +6794,17 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" +[[package]] +name = "tqdm" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2d2932240205a99b65f15d9861992c95fbb8c9fb280b3a1f17a92db6dc611f" +dependencies = [ + "anyhow", + "crossterm", + "once_cell", +] + [[package]] name = "tracing" version = "0.1.44" @@ -6819,6 +7147,8 @@ dependencies = [ "ark-relations", "ark-serialize 0.5.0", "bincode", + "bitcoin", + "bitvm", "blake3", "cfg-if", "garbled-snark-verifier", @@ -8119,11 +8449,6 @@ name = "rsa" version = "0.9.6" source = "git+https://github.com/ziren-patches/RustCrypto-RSA.git?branch=patch-rsa-0.9.6#df285475f9800a8e20f72a7f93de07539df0ed25" -[[patch.unused]] -name = "secp256k1" -version = "0.29.1" -source = "git+https://github.com/ziren-patches/rust-secp256k1?branch=patch-0.29.1#c7e39ee0732d0e6b5c33721038f8f1b789f77c36" - [[patch.unused]] name = "substrate-bn" version = "0.6.0" diff --git a/verifiable-circuit-babe/Cargo.toml b/verifiable-circuit-babe/Cargo.toml index 8e601d5..8b5b2a6 100644 --- a/verifiable-circuit-babe/Cargo.toml +++ b/verifiable-circuit-babe/Cargo.toml @@ -24,6 +24,8 @@ aes = "0.8" cfg-if = "1.0.4" ripemd = "0.2.0" rayon = "1.11.0" +bitvm = { git = "https://github.com/GOATNetwork/BitVM.git", branch = "GA" } +bitcoin = { version = "0.32.5", features = ["rand-std"] } [target.'cfg(all(target_os = "zkvm"))'.dependencies] zkm-zkvm = { workspace = true } diff --git a/verifiable-circuit-babe/src/babe.rs b/verifiable-circuit-babe/src/babe.rs index e82e930..6c3f32d 100644 --- a/verifiable-circuit-babe/src/babe.rs +++ b/verifiable-circuit-babe/src/babe.rs @@ -17,7 +17,10 @@ use crate::cac::{ cac_finalize_indices, verify_finalized_instances, verify_opened_instances, CACSetupPackage, FinalizedInstanceData, }; -use crate::lamport::{lamport_keygen, lamport_sign, lamport_verify, LamportPk, LamportSk}; + +use crate::wots::{wots96_verify, Wots96, Wots96PublicKey, Wots96Secret}; +use crate::utils::pi1_xd_to_wots96_msg; +use bitvm::signatures::Wots; use crate::prover::{BABEProver, GROTH_16_SEED}; use crate::soldering::{build_soldered_wires_input, soldering_guest_compute, SolderingData, SolderingProof}; use crate::transactions::{OnchainSize, TxAssertWitness, TxChallengeAssertOutputLock, TxChallengeAssertWitness, TxDepositLock, TxNoWithdrawWitness, TxWithdrawWitness, TxWronglyChallengedWitness}; @@ -26,8 +29,10 @@ use crate::verifier::BABEVerifier; // ─── Constants ──────────────────────────────────────────────────────────────── -/// Number of bits in π₁ (G1Affine): 254 bits for x + 254 bits for y + 254 Fr -pub const LAMPORT_N: usize = 762; +/// Number of bits covering π₁ and x_d: 256 bits for x + 256 bits for y + 256 Fr. +/// Each 254-bit field is padded to 256 bits (2 dummy zero MSBs) to align with +/// the 96-byte Wots96 message (3 × 32-byte LE fields). +pub const LAMPORT_N: usize = 768; /// Total number of C&C instances the Verifier creates and commits to. /// In practice, N_CC = 181. @@ -74,9 +79,33 @@ pub enum BabeBtcSig { pub struct EncodingKeyPublic(pub Vec<[[u8; 20]; 2]>); pub fn compute_epk_with_delta(encoding_keys: &[Vec; 2], delta: &[S; 2]) -> EncodingKeyPublic { - let fgc_pairs: Vec<[[u8; 20]; 2]> = encoding_keys[0].iter().map(|&key| [derive_hashlock(&key.0), derive_hashlock(&(key ^ delta[0]).0)]).collect(); - let sgc_pairs: Vec<[[u8; 20]; 2]> = encoding_keys[1].iter().map(|&key| [derive_hashlock(&key.0), derive_hashlock(&(key ^ delta[1]).0)]).collect(); - let pairs: Vec<[[u8; 20]; 2]> = fgc_pairs.into_iter().chain(sgc_pairs).collect(); + // encoding_keys[0]: 508 entries — [0..254] pi1.x, [254..508] pi1.y. + // encoding_keys[1]: 254 entries — x_d. + let fgc_pairs: Vec<[[u8; 20]; 2]> = encoding_keys[0].iter() + .map(|&key| [derive_hashlock(&key.0), derive_hashlock(&(key ^ delta[0]).0)]) + .collect(); + let sgc_pairs: Vec<[[u8; 20]; 2]> = encoding_keys[1].iter() + .map(|&key| [derive_hashlock(&key.0), derive_hashlock(&(key ^ delta[1]).0)]) + .collect(); + + // 2 dummy EPK pairs for each of the 3 padding-bit positions (bits 254-255 per 256-bit field). + // bit=0 entry matches the dummy label [0u8; 16] used in compute_pi1/x_d_labels. + // bit=1 entry is well-defined but never selected (the dummy bits are always 0). + let dummy_pair: [[u8; 20]; 2] = [ + derive_hashlock(&[0u8; 16]), + derive_hashlock(&[1u8; 16]), + ]; + + // Final layout (768 entries total): + // pi1.x[254] | dummy[2] | pi1.y[254] | dummy[2] | x_d[254] | dummy[2] + let pairs: Vec<[[u8; 20]; 2]> = fgc_pairs[..254].iter().copied() + .chain([dummy_pair, dummy_pair]) + .chain(fgc_pairs[254..].iter().copied()) + .chain([dummy_pair, dummy_pair]) + .chain(sgc_pairs.iter().copied()) + .chain([dummy_pair, dummy_pair]) + .collect(); + EncodingKeyPublic(pairs) } @@ -120,7 +149,7 @@ pub struct WeKnownPi1ProveCt { /// In practice, prover has the commitment of input labels from the Verifier Txn skeletons. /// (Verifier just need to commit the base instance input labels) pub struct ProverSetupState { - pub lsk_p: LamportSk, + pub wots_sk_p: Wots96Secret, pub finalized: Vec, pub soldering: SolderingData, /// h_msg per finalized instance, in finalized-index order. @@ -133,7 +162,7 @@ pub struct VerifierSetupState { pub verifier: BABEVerifier, pub package: CACSetupPackage, pub finalized_indices: Vec, - pub lpk_p: LamportPk, + pub wots_pk_p: Wots96PublicKey, pub presigs_p: ProverPresigs, } @@ -254,14 +283,16 @@ pub fn babe_build_deposit_lock(pk_p: BtcPk, pk_v: BtcPk, amount: u64) -> TxDepos // ─── Assert phase (Prover posts π₁ and x_d) ───────────────────────────────────────── -/// Prover: sign π₁ and x_d with lsk_P and build the assert witness. -pub fn babe_prover_assert(proof: &Groth16Proof, lsk_p: &LamportSk, x_d: ark_bn254::Fr) -> TxAssertWitness { +/// Prover: sign π₁ and x_d with wots_sk_P and build the assert witness. +pub fn babe_prover_assert(proof: &Groth16Proof, wots_sk: &Wots96Secret, x_d: ark_bn254::Fr) -> TxAssertWitness { let pi1 = proof.a; let mut pi1_bytes = Vec::new(); pi1.serialize_compressed(&mut pi1_bytes).expect("serialize π₁"); - let mut x_d_bytes = Vec::new(); x_d.serialize_compressed(&mut x_d_bytes).expect("serialize x_d"); - let lamport_sig = lamport_sign(lsk_p, &pi1, x_d); - TxAssertWitness { pi1: pi1_bytes, lamport_sig, x_d: x_d_bytes } + let mut x_d_bytes = Vec::new(); + x_d.serialize_compressed(&mut x_d_bytes).expect("serialize x_d"); + let msg = pi1_xd_to_wots96_msg(&pi1, x_d); + let wots_sig = Wots96::sign(wots_sk, &msg); + TxAssertWitness { pi1: pi1_bytes, x_d: x_d_bytes, wots_sig } } // ─── ChallengeAssert phase (Verifier reveals base-instance labels) ──────────── @@ -278,7 +309,7 @@ pub fn build_ca_outlock( } } -/// Verifier: verify Lamport sig in assert_witness, then compute input labels for π₁ and x_d +/// Verifier: verify Wots96 sig in assert_witness, then compute input labels for π₁ and x_d /// from the base finalized instance and return them in the ChallengeAssert witness. pub fn babe_verifier_challenge_assert_cac( assert_witness: &TxAssertWitness, @@ -288,22 +319,36 @@ pub fn babe_verifier_challenge_assert_cac( let pi1 = G1Affine::deserialize_compressed(assert_witness.pi1.as_slice()).ok()?; let x_d = Fr::from_le_bytes_mod_order(&assert_witness.x_d); - println!("Verifier: Checking the Lamport signature in tx_Assert witness against pi1 and lpk_P..."); - if !lamport_verify(&verifier_state.lpk_p, &pi1, x_d, &assert_witness.lamport_sig) { + let msg = pi1_xd_to_wots96_msg(&pi1, x_d); + println!("Verifier: Checking Wots96 signature in tx_Assert against pi1, x_d and wots_pk_p..."); + if !wots96_verify(&verifier_state.wots_pk_p, &msg, &assert_witness.wots_sig) { return None; } // Derive labels from the base instance (finalized_indices[0]). let base_idx = verifier_state.finalized_indices[0]; - let pi1_input_labels = verifier_state.verifier.compute_pi1_labels(base_idx, pi1); - let x_d_input_labels = verifier_state.verifier.compute_x_d_labels(base_idx, x_d); - let input_labels = pi1_input_labels.into_iter().chain(x_d_input_labels).collect::>(); - // all_labels[0..2] are constant-wire labels; [2..] are π₁ input labels. - let input_labels: Vec<[u8; 16]> = input_labels.iter().map(|s| s.0).collect(); + + // compute_pi1_labels returns 508 labels: [0..254] for pi1.x, [254..508] for pi1.y. + // compute_x_d_labels returns 254 labels. + // Interleave 6 dummy labels at the 2 MSB padding positions of each 256-bit field: + // pi1.x[254] | dummy[2] | pi1.y[254] | dummy[2] | x_d[254] | dummy[2] = 768 + // Dummy value [0u8; 16] is consistent: derive_hashlock(&[0u8; 16]) == epk[i][0] for + // the dummy EPK entries computed in compute_epk_with_delta (Step 13). + let pi1_labels = verifier_state.verifier.compute_pi1_labels(base_idx, pi1); + let x_d_labels = verifier_state.verifier.compute_x_d_labels(base_idx, x_d); + let dummy = S([0u8; 16]); + let input_labels: Vec<[u8; 16]> = pi1_labels[..254].iter() + .chain([dummy, dummy].iter()) + .chain(pi1_labels[254..].iter()) + .chain([dummy, dummy].iter()) + .chain(x_d_labels.iter()) + .chain([dummy, dummy].iter()) + .map(|s| s.0) + .collect(); Some(TxChallengeAssertWitness { input_labels, - lamport_sig: assert_witness.lamport_sig.clone(), + wots_sig: assert_witness.wots_sig, sig_v: BabeBtcSig::VerifierLiveSig, sig_p: sig_p_presig, }) @@ -321,12 +366,20 @@ pub fn babe_prover_wrongly_challenged_cac( prover_state: &ProverSetupState, ) -> Option<(TxWronglyChallengedWitness, usize)> { let base_input_labels: Vec = challenge_witness.input_labels.iter().map(|&b| S(b)).collect(); - assert_eq!(base_input_labels.len(), 762); + assert_eq!(base_input_labels.len(), 768); + // Layout: pi1.x[0..254] | dummy[254..256] | pi1.y[256..510] | dummy[510..512] + // | x_d[512..766] | dummy[766..768] + // Strip the 6 dummy labels before passing to the GC (which has 762 real wires). + let pi1_labels: Vec = base_input_labels[..254].iter() + .chain(base_input_labels[256..510].iter()) + .copied() + .collect(); + let x_d_labels: Vec = base_input_labels[512..766].to_vec(); let mut prover = BABEProver::new(pk.clone(), proof.clone(), dyn_pubin); let found = prover.check_compute_msg( &prover_state.finalized, - &base_input_labels[0..508], - &base_input_labels[508..], + &pi1_labels, + &x_d_labels, &prover_state.soldering, &prover_state.h_msgs, ); @@ -356,50 +409,6 @@ pub fn babe_prover_withdraw(sig_v_presig: BabeBtcSig) -> TxWithdrawWitness { } } -// ─── Enc_ functions ──────────────────────────────────────────────────────── - -/// Enc*(crs, x_S, |D|, msg, B; r): -/// P_S = gamma_abc[0] + Σ_{k} x_S[k]·gamma_abc[k+1] -/// mask = Y_S^r - e(r·B, γ) where Y_S^r = e(α, r·β) + e(P_S, r·γ) -/// Same as Instance::enc_setup -pub fn we_known_pi1_encsetup( - vk: &Groth16VerifyingKey, - static_input: Fr, - msg: &[u8], - r_bytes: [u8; 32], - b_blind: G1Affine, -) -> Option<(WeKnownPi1SetupCt, G1Affine)> { - let r = Fr::from_le_bytes_mod_order(&r_bytes); - let p_s = vk.gamma_abc_g1[0].into_group() + vk.gamma_abc_g1[1].into_group() * static_input; - - let r_b = b_blind.into_group() * r; - let r_delta = vk.delta_g2.into_group() * r; - - let t1 = Bn254::pairing(vk.alpha_g1, vk.beta_g2.into_group() * r); - let t2 = Bn254::pairing(p_s, vk.gamma_g2.into_group() * r); - let y_s_r = t1 + t2; - - let q_b = Bn254::pairing(r_b, vk.gamma_g2); - let mask_gt = y_s_r - q_b; - - let mut mask_bytes = Vec::new(); - mask_gt.serialize_compressed(&mut mask_bytes).ok()?; - let mask = ro_from_pairing_bytes(&mask_bytes, msg.len()); - let ct3: Vec = msg.iter().zip(mask.iter()).map(|(a, b)| a ^ b).collect(); - - Some((WeKnownPi1SetupCt { ct2_r_delta_g2: g2_to_ser(r_delta), ct3_masked_msg: ct3 }, r_b.into_affine())) -} - -/// Encprove(crs, π₁; r): ctprove = r·π₁. -pub fn we_known_pi1_encprove( - pi1: ark_bn254::G1Projective, - r_bytes: [u8; 32], - ct1_prime: G1Affine, -) -> WeKnownPi1ProveCt { - let r = Fr::from_le_bytes_mod_order(&r_bytes); - WeKnownPi1ProveCt { ct1_r_pi1: g1_to_ser(pi1 * r), ct1_prime: g1_to_ser(ct1_prime.into_group()) } -} - /// Dec*(vk, ctsetup, ctprove, c1', π₂, π₃): /// Q_blind = e(c1', γ) where c1' = r·P_D + r·B (DSGC output) /// mask = e(r·π₁, π₂) - e(π₃, r·δ) - Q_blind = Y_S^r - e(r·B, γ) @@ -470,8 +479,9 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { babe_prover_verify_setup(&package, &opened, &finalized, &soldering, &vk, static_public_inputs) .expect("prover setup verification failed"); - println!("Prover: generating Lamport signature..."); - let (lsk_p, lpk_p) = lamport_keygen(&mut rng); + println!("Prover: generating Wots96 signing key..."); + let wots_sk_p = Wots96::generate_secret_key(); + let wots_pk_p = Wots96::generate_public_key(&wots_sk_p); // ── Create Txn Set and Presign ────────────────────────────────────────────────── println!("Prover: creating Tx Set and pre sign..."); @@ -508,7 +518,7 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { let deposit_lock = babe_build_deposit_lock(pk_p, pk_v, 100_000); // Both parties persist their setup state. let prover_state = ProverSetupState { - lsk_p, + wots_sk_p, finalized, soldering, h_msgs: tx_challenge_assert_outlock_p.h_msgs, @@ -518,14 +528,14 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { verifier, package, finalized_indices, - lpk_p, + wots_pk_p, presigs_p: prover_presigs, }; // ── Proving phase ───────────────────────────────────────────────────────── - // Assert: Prover posts π₁ + x_d and Lamport sig on-chain. - let assert_witness = babe_prover_assert(&proof, &prover_state.lsk_p, dynamic_public_inputs); + // Assert: Prover posts π₁ + x_d and Wots96 sig on-chain. + let assert_witness = babe_prover_assert(&proof, &prover_state.wots_sk_p, dynamic_public_inputs); println!("Prover: posting tx_Assert..."); println!("tx_Assert witness: {} bytes", assert_witness.size_bytes()); @@ -534,7 +544,7 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { &assert_witness, &verifier_state, verifier_state.presigs_p.sig_challenge_assert.clone(), - ).expect("Lamport sig invalid in assert witness"); + ).expect("Wots96 sig invalid in assert witness"); println!("Verifier: posting tx_ChallengeAssert..."); println!("tx_ChallengeAssert witness: {} bytes", challenge_witness.size_bytes()); @@ -587,10 +597,11 @@ impl ConstraintSynthesizer for DummyMulCircuit { #[cfg(test)] mod tests { use super::*; - use ark_bn254::{Bn254, Fr}; - use ark_crypto_primitives::snark::{CircuitSpecificSetupSNARK, SNARK}; use ark_ff::UniformRand; use rand::SeedableRng; + use crate::lamport::{lamport_keygen, lamport_sign, lamport_verify}; + use crate::wots::{wots96_verify, Wots96}; + use bitvm::signatures::Wots; #[test] fn hashlock_roundtrip() { @@ -615,41 +626,26 @@ mod tests { } #[test] - fn we_encsetup_dec_roundtrip() { - let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(GROTH_16_SEED); - let a = Fr::from(3u64); - let b = Fr::from(7u64); - let (pk, vk) = ark_groth16::Groth16::::setup( - DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, - ).unwrap(); - let proof = ark_groth16::Groth16::::prove( - &pk, - DummyMulCircuit:: { a: Some(a), b: Some(b) }, - &mut rng, - ).unwrap(); - - // |S|=1 (a*b is static), |D|=1 (a*a is dynamic). - let static_inputs = a * b; - let dyn_inputs = a * a; - let secret = b"test-secret-32by"; - let r_bytes = h_256(b"r-test"); - let b_blind = G1Affine::generator(); - - let (ct_setup, r_b_affine) = we_known_pi1_encsetup( - &vk, static_inputs, secret, r_bytes, b_blind, - ).unwrap(); - // Simulate DSGC: c1' = r·P_D + r·B - // P_D = (a*a) · gamma_abc[|S|+1] = (a*a) · gamma_abc[2] - let r = Fr::from_le_bytes_mod_order(&r_bytes); - let p_d = vk.gamma_abc_g1[2].into_group() * dyn_inputs; - let c1_prime = (p_d * r + r_b_affine.into_group()).into_affine(); - - let ct_prove = we_known_pi1_encprove(proof.a.into_group(), r_bytes, c1_prime); - - - let decrypted = we_known_pi1_dec( - &vk, &ct_setup, &ct_prove, proof.b.into_group(), proof.c.into_group(), - ).unwrap(); - assert_eq!(decrypted, secret); + fn wots96_sign_verify_roundtrip() { + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(2); + let pi1 = G1Affine::from(ark_bn254::G1Projective::rand(&mut rng)); + let x_d = ark_bn254::Fr::rand(&mut rng); + + let sk = Wots96::generate_secret_key(); + let pk = Wots96::generate_public_key(&sk); + let msg = pi1_xd_to_wots96_msg(&pi1, x_d); + let sig = Wots96::sign(&sk, &msg); + + assert!(wots96_verify(&pk, &msg, &sig)); + + // Wrong pi1 must fail. + let pi1_other = G1Affine::from(ark_bn254::G1Projective::rand(&mut rng)); + let msg_other = pi1_xd_to_wots96_msg(&pi1_other, x_d); + assert!(!wots96_verify(&pk, &msg_other, &sig)); + + // Wrong x_d must fail. + let x_d_other = ark_bn254::Fr::rand(&mut rng); + let msg_xd_other = pi1_xd_to_wots96_msg(&pi1, x_d_other); + assert!(!wots96_verify(&pk, &msg_xd_other, &sig)); } } diff --git a/verifiable-circuit-babe/src/lib.rs b/verifiable-circuit-babe/src/lib.rs index 14b446c..0392667 100644 --- a/verifiable-circuit-babe/src/lib.rs +++ b/verifiable-circuit-babe/src/lib.rs @@ -9,3 +9,4 @@ pub mod utils; pub mod transactions; pub mod soldering; pub mod lamport; +pub mod wots; diff --git a/verifiable-circuit-babe/src/transactions.rs b/verifiable-circuit-babe/src/transactions.rs index e52d402..205c61f 100644 --- a/verifiable-circuit-babe/src/transactions.rs +++ b/verifiable-circuit-babe/src/transactions.rs @@ -1,8 +1,9 @@ // ─── Transaction locking script ─────────────────────────────────────────────── use serde::{Deserialize, Serialize}; -use crate::babe::{BabeBtcSig, BtcPk, BTC_SIG_BYTES, LAMPORT_SIG_BYTES, MSG_BYTES}; -use crate::lamport::LamportSig; +use crate::babe::{BabeBtcSig, BtcPk, BTC_SIG_BYTES, MSG_BYTES}; +use crate::wots::{Wots96, Wots96Sig}; +use bitvm::signatures::Wots; /// Constants embedded in the locking script of tx_Deposit output 0. /// Script: CheckSig(pk_P) ∧ CheckSig(pk_V) @@ -23,33 +24,35 @@ pub struct TxChallengeAssertOutputLock { // ─── Transaction witnesses (on-chain data) ──────────────────────────────────── /// tx_Assert — witness for input 0. -/// Input spends a UTXO with: CheckLampSig(lpk_P) -/// Script verifies: SHA256(μ[i]) == lpk_P[i][bit_i(π₁)] for all i. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +/// Input spends a UTXO with: CheckWotsSig(wots_pk_P) +/// Script verifies the Wots96 signature over the 96-byte message (π₁.x ∥ π₁.y ∥ x_d). +#[derive(Debug, Clone, PartialEq, Eq)] pub struct TxAssertWitness { /// Compressed G1Affine, 33 bytes — the asserted proof element. /// In practice, this is not onchain. pub pi1: Vec, /// Dynamic public input scalar x_d, 32 bytes (little-endian Fr). - /// This is not onchain + /// This is not onchain. pub x_d: Vec, - /// μ₁…μ_M — Lamport signature, LAMPORT_N × 16 bytes. + /// Wots96 signature over the 96-byte message (π₁.x LE-32 ∥ π₁.y LE-32 ∥ x_d LE-32). /// This is submitted onchain. - pub lamport_sig: LamportSig, + pub wots_sig: Wots96Sig, } /// tx_ChallengeAssert — witness for input 0. -/// Input spends tx_Assert output 1: CheckLampSigsMatch(lpk_P, lpk_V) ∧ CheckSig(pk_V) ∧ CheckSig(pk_P) +/// Input spends tx_Assert output 1: CheckSigsConsistent(wots_pk_P, epk_V) ∧ CheckSig(pk_V) ∧ CheckSig(pk_P) /// Script verifies: -/// (a) SHA256(μ[i]) == lpk_P[i][bit_i] — μ is a valid Lamport sig for some π₁ -/// (b) SHA256(L[i]) == lpk_V[i][bit_i] — L[i] is the correct GC label for bit_i under epk -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +/// (a) Wots96 sig is valid for some 96-byte message m — binds π₁ and x_d to the prover +/// (b) SHA256(L[i]) == epk_V[i][bit_i(m)] — L[i] is the correct GC label for bit_i under epk +/// (c) Wots96 sig and epk labels are consistent over the same message m — both sign/encode the same bits +#[derive(Debug, Clone, PartialEq, Eq)] pub struct TxChallengeAssertWitness { - /// L₁…L_M — one GC input label per π₁ bit and x_d bit, LAMPORT_N × 16 bytes. + /// L₁…L_M — one GC input label per bit of π₁ and x_d, LAMPORT_N × 16 bytes. pub input_labels: Vec<[u8; 16]>, - /// μ₁…μ_M — Lamport sig re-posted to bind L to π₁ and x_d. This lamport_sig is submitted - /// from Prover before in TxAssertWitness. - pub lamport_sig: LamportSig, + /// Wots96 sig re-posted from TxAssertWitness to bind the labels to π₁ and x_d. + /// The script checks that the bit sequence recovered from this sig matches the + /// bit indices used to select each label in input_labels. + pub wots_sig: Wots96Sig, /// VerifierLiveSig pub sig_v: BabeBtcSig, /// ProverPresigChallengeAssert @@ -103,15 +106,17 @@ pub trait OnchainSize { impl OnchainSize for TxAssertWitness { fn size_bytes(&self) -> usize { - LAMPORT_SIG_BYTES + // TOTAL_DIGIT_LEN hash-chain preimages, each 20 bytes. + Wots96::TOTAL_DIGIT_LEN as usize * 20 } } impl OnchainSize for TxChallengeAssertWitness { fn size_bytes(&self) -> usize { - LAMPORT_SIG_BYTES * 2 - + BTC_SIG_BYTES // sig_v: 32 bytes - + BTC_SIG_BYTES // sig_p: 32 bytes + self.input_labels.len() * 16 // LAMPORT_N × 16 bytes + + Wots96::TOTAL_DIGIT_LEN as usize * 20 // wots_sig preimages + + BTC_SIG_BYTES // sig_v: 32 bytes + + BTC_SIG_BYTES // sig_p: 32 bytes } } diff --git a/verifiable-circuit-babe/src/utils.rs b/verifiable-circuit-babe/src/utils.rs index 00437a6..79a6bb8 100644 --- a/verifiable-circuit-babe/src/utils.rs +++ b/verifiable-circuit-babe/src/utils.rs @@ -47,6 +47,28 @@ pub fn g2_from_ser_checked(v: &[u8]) -> Option { Some(a.into_group()) } +/// Encode pi1 and x_d into a 96-byte Wots96 message. +/// Layout: pi1.x LE-32 || pi1.y LE-32 || x_d LE-32. +/// Bit packing: bits[i] = (msg[i/8] >> (i%8)) & 1 (LSB-first within each byte). +/// Bits 254-255 of each 32-byte chunk are always 0 (BN254 fields are < 2^254). +pub fn pi1_xd_to_wots96_msg(pi1: &G1Affine, x_d: Fr) -> [u8; 96] { + let mut msg = [0u8; 96]; + let mut tmp = Vec::new(); + + pi1.x.serialize_uncompressed(&mut tmp).expect("serialize pi1.x"); + msg[..32].copy_from_slice(&tmp); + + tmp.clear(); + pi1.y.serialize_uncompressed(&mut tmp).expect("serialize pi1.y"); + msg[32..64].copy_from_slice(&tmp); + + tmp.clear(); + x_d.serialize_uncompressed(&mut tmp).expect("serialize x_d"); + msg[64..96].copy_from_slice(&tmp); + + msg +} + pub fn pi1_to_bits(pi1: &G1Affine) -> Vec { use garbled_snark_verifier::dv_bn254::fq::Fq as GcFq; GcFq::to_bits(pi1.x).into_iter().chain(GcFq::to_bits(pi1.y)).collect() diff --git a/verifiable-circuit-babe/src/wots.rs b/verifiable-circuit-babe/src/wots.rs new file mode 100644 index 0000000..cf1ad4e --- /dev/null +++ b/verifiable-circuit-babe/src/wots.rs @@ -0,0 +1,149 @@ +use bitcoin::script::read_scriptint; +use bitcoin::Witness; +use bitvm::signatures::utils::bitcoin_representation; +pub use bitvm::signatures::winternitz::{Parameters, VoidConverter}; +pub use bitvm::signatures::{CompactWots, WinternitzSecret, Wots}; + +pub const WOTS96_LOG2_BASE: u32 = 8; +pub const WOTS96_BASE: u32 = 1 << WOTS96_LOG2_BASE; + +pub struct Wots96; + +impl Wots for Wots96 { + type Converter = VoidConverter; + type PublicKey = [[u8; 20]; Self::TOTAL_DIGIT_LEN as usize]; + type Message = [u8; Self::MSG_BYTE_LEN as usize]; + type Signature = [[u8; 21]; Self::TOTAL_DIGIT_LEN as usize]; + + const MSG_BYTE_LEN: u32 = 96; + const PARAMETERS: Parameters = + Parameters::new_by_bit_length(Self::MSG_BYTE_LEN * 8, WOTS96_LOG2_BASE); + + fn raw_witness_to_signature(witness: &Witness) -> Self::Signature { + assert_eq!(witness.len(), 2 * Self::TOTAL_DIGIT_LEN as usize); + + let mut digit_signatures = Vec::with_capacity(Self::TOTAL_DIGIT_LEN as usize); + for i in (0..witness.len()).step_by(2) { + assert_eq!( + witness[i].len(), + 20, + "the digit signature should be constant 20 bytes" + ); + assert!( + witness[i + 1].len() <= 2, + "the base256 digit should fit in minimally encoded script bytes" + ); + + let digit_value = read_scriptint(&witness[i + 1]).unwrap(); + assert!( + (0..WOTS96_BASE as i64).contains(&digit_value), + "the digit should be in the valid Wots96 base range" + ); + + let mut digit_signature = [0u8; 21]; + digit_signature[..20].copy_from_slice(&witness[i]); + digit_signature[20] = digit_value as u8; + digit_signatures.push(digit_signature); + } + + Self::Signature::try_from(digit_signatures).unwrap() + } + + fn signature_to_raw_witness(signature: &Self::Signature) -> Witness { + let mut witness = Witness::new(); + for digit_signature in signature.as_ref() { + witness.push(&digit_signature[..20]); + witness.push(bitcoin_representation(i32::from(digit_signature[20]))); + } + witness + } + + fn signature_to_message(signature: &Self::Signature) -> Self::Message { + if WOTS96_LOG2_BASE == 8 { + let bytes = signature + .as_ref() + .iter() + .map(|digit_sig| digit_sig[20]) + .take(Self::MSG_BYTE_LEN as usize) + .rev() + .collect::>(); + return Self::Message::try_from(bytes).unwrap(); + } + + let digits = signature + .as_ref() + .iter() + .map(|digit_sig| digit_sig[20]) + .take(((Self::MSG_BYTE_LEN * 8).div_ceil(WOTS96_LOG2_BASE)) as usize) + .rev() + .collect::>(); + + let mut bytes = Vec::with_capacity(Self::MSG_BYTE_LEN as usize); + let mut byte = 0u8; + let mut used_bits = 0u32; + for digit in digits { + byte |= digit << used_bits; + used_bits += WOTS96_LOG2_BASE; + if used_bits >= 8 { + bytes.push(byte); + byte = digit >> (8 - (used_bits - WOTS96_LOG2_BASE)); + used_bits -= 8; + } + } + bytes.truncate(Self::MSG_BYTE_LEN as usize); + + Self::Message::try_from(bytes).unwrap() + } +} + +impl CompactWots for Wots96 { + type CompactSignature = [[u8; 20]; Self::TOTAL_DIGIT_LEN as usize]; +} + +pub type Wots96Secret = WinternitzSecret; +pub type Wots96PublicKey = ::PublicKey; +pub type Wots96Sig = ::Signature; + +/// Verify a Wots96 signature against a public key and message. +/// +/// Mirrors `lamport_verify`: for each digit position i, hash `sig[i][..20]` +/// exactly `(max_digit - d_i)` more times and compare against `pk[i]`. +/// Digits are derived from `msg` directly (not trusted from sig[i][20]). +pub fn wots96_verify(pk: &Wots96PublicKey, msg: &[u8; 96], sig: &Wots96Sig) -> bool { + use bitcoin::hashes::{hash160, Hash}; + + let base = WOTS96_BASE as usize; // 256 + let max_digit = base - 1; // 255 + let msg_digit_len = Wots96::MSG_BYTE_LEN as usize; // 96 (one byte = one digit) + let total_digit_len = Wots96::TOTAL_DIGIT_LEN as usize; // 98 + let chk_digit_len = total_digit_len - msg_digit_len; // 2 + + // Step 1: message digits — each byte is one digit, reversed (matches message_to_digits internals) + let mut digits: Vec = msg.iter().rev().map(|&b| b as usize).collect(); + + // Step 2: checksum = max_digit * msg_digit_len - sum(message_digits) + let checksum: usize = max_digit * msg_digit_len - digits.iter().sum::(); + + // Step 3: encode checksum as big-endian base-256 digits and append + let mut chk = checksum; + let mut chk_digits = vec![0usize; chk_digit_len]; + for d in chk_digits.iter_mut().rev() { + *d = chk % base; + chk /= base; + } + digits.extend(chk_digits); + + // Step 4: for each digit position i, verify the hash chain against pk[i] + for i in 0..total_digit_len { + let d_i = digits[i]; + let mut h = hash160::Hash::from_byte_array(sig[i][..20].try_into().unwrap()); + for _ in 0..(max_digit - d_i) { + h = hash160::Hash::hash(h.as_byte_array()); + } + if h.as_byte_array() != &pk[i] { + return false; + } + } + + true +} \ No newline at end of file From 1225a243612ac3cdecc3977a04a1645fa9a4a44c Mon Sep 17 00:00:00 2001 From: vanhger Date: Fri, 8 May 2026 11:43:27 +0700 Subject: [PATCH 27/32] update the witness size. --- verifiable-circuit-babe/tests/babe_e2e.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/verifiable-circuit-babe/tests/babe_e2e.rs b/verifiable-circuit-babe/tests/babe_e2e.rs index 771a8c1..72ace30 100644 --- a/verifiable-circuit-babe/tests/babe_e2e.rs +++ b/verifiable-circuit-babe/tests/babe_e2e.rs @@ -4,7 +4,7 @@ use verifiable_circuit_babe::babe::run_babe_e2e_cac; fn e2e_babe_cac() { use verifiable_circuit_babe::transactions::OnchainSize; let run = run_babe_e2e_cac(); - assert_eq!(run.assert_witness.size_bytes(), 12192); // 762 * 16 - assert_eq!(run.challenge_assert_witness.size_bytes(), 24448); // 762 * 16 * 2 + 64 + assert_eq!(run.assert_witness.size_bytes(), 1960); // 98 * 20 + assert_eq!(run.challenge_assert_witness.size_bytes(), 14312); assert_eq!(run.wrongly_challenged_witness.size_bytes(), 64); } From 6a2760bdee512d3059168a9e50153d6b04c72679 Mon Sep 17 00:00:00 2001 From: vanhger Date: Fri, 8 May 2026 14:53:21 +0700 Subject: [PATCH 28/32] remove dead code --- verifiable-circuit-babe/src/babe.rs | 74 +-------------------- verifiable-circuit-babe/src/instance/mod.rs | 21 +----- verifiable-circuit-babe/src/lamport.rs | 54 --------------- verifiable-circuit-babe/src/lib.rs | 1 - 4 files changed, 3 insertions(+), 147 deletions(-) delete mode 100644 verifiable-circuit-babe/src/lamport.rs diff --git a/verifiable-circuit-babe/src/babe.rs b/verifiable-circuit-babe/src/babe.rs index 6c3f32d..160866a 100644 --- a/verifiable-circuit-babe/src/babe.rs +++ b/verifiable-circuit-babe/src/babe.rs @@ -1,5 +1,4 @@ use ark_bn254::{Bn254, Fr, G1Affine}; -use ark_ec::{AffineRepr, CurveGroup}; use ark_ff::PrimeField; use ark_groth16::{Proof as Groth16Proof, VerifyingKey as Groth16VerifyingKey}; use ark_groth16::ProvingKey as Groth16ProvingKey; @@ -78,42 +77,6 @@ pub enum BabeBtcSig { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct EncodingKeyPublic(pub Vec<[[u8; 20]; 2]>); -pub fn compute_epk_with_delta(encoding_keys: &[Vec; 2], delta: &[S; 2]) -> EncodingKeyPublic { - // encoding_keys[0]: 508 entries — [0..254] pi1.x, [254..508] pi1.y. - // encoding_keys[1]: 254 entries — x_d. - let fgc_pairs: Vec<[[u8; 20]; 2]> = encoding_keys[0].iter() - .map(|&key| [derive_hashlock(&key.0), derive_hashlock(&(key ^ delta[0]).0)]) - .collect(); - let sgc_pairs: Vec<[[u8; 20]; 2]> = encoding_keys[1].iter() - .map(|&key| [derive_hashlock(&key.0), derive_hashlock(&(key ^ delta[1]).0)]) - .collect(); - - // 2 dummy EPK pairs for each of the 3 padding-bit positions (bits 254-255 per 256-bit field). - // bit=0 entry matches the dummy label [0u8; 16] used in compute_pi1/x_d_labels. - // bit=1 entry is well-defined but never selected (the dummy bits are always 0). - let dummy_pair: [[u8; 20]; 2] = [ - derive_hashlock(&[0u8; 16]), - derive_hashlock(&[1u8; 16]), - ]; - - // Final layout (768 entries total): - // pi1.x[254] | dummy[2] | pi1.y[254] | dummy[2] | x_d[254] | dummy[2] - let pairs: Vec<[[u8; 20]; 2]> = fgc_pairs[..254].iter().copied() - .chain([dummy_pair, dummy_pair]) - .chain(fgc_pairs[254..].iter().copied()) - .chain([dummy_pair, dummy_pair]) - .chain(sgc_pairs.iter().copied()) - .chain([dummy_pair, dummy_pair]) - .collect(); - - EncodingKeyPublic(pairs) -} - -pub fn compute_epk(encoding_keys: &[Vec; 2]) -> EncodingKeyPublic { - use garbled_snark_verifier::core::utils::NON_CAC_DELTA; - compute_epk_with_delta(encoding_keys, &[NON_CAC_DELTA; 2]) -} - // ─── Presig structs ─────────────────────────────────────────────────────────── #[derive(Debug, Clone)] @@ -333,7 +296,8 @@ pub fn babe_verifier_challenge_assert_cac( // Interleave 6 dummy labels at the 2 MSB padding positions of each 256-bit field: // pi1.x[254] | dummy[2] | pi1.y[254] | dummy[2] | x_d[254] | dummy[2] = 768 // Dummy value [0u8; 16] is consistent: derive_hashlock(&[0u8; 16]) == epk[i][0] for - // the dummy EPK entries computed in compute_epk_with_delta (Step 13). + // the dummy EPK entries computed in compute_epk_with_delta. Note that Prover & Verifier should + // embed this hashlock in the challengeAssert script in order to make the check passed. let pi1_labels = verifier_state.verifier.compute_pi1_labels(base_idx, pi1); let x_d_labels = verifier_state.verifier.compute_x_d_labels(base_idx, x_d); let dummy = S([0u8; 16]); @@ -390,25 +354,6 @@ pub fn babe_prover_wrongly_challenged_cac( }, prover.valid_finalized_id.unwrap())) } -// ─── No-withdraw / Withdraw phases ─────────────────────────────────────────── - -pub fn babe_verifier_no_withdraw(sig_p_presig: BabeBtcSig) -> TxNoWithdrawWitness { - TxNoWithdrawWitness { - input0_sig_p: sig_p_presig, - input0_sig_v: BabeBtcSig::VerifierLiveSig, - input1_sig_v: BabeBtcSig::VerifierLiveSig, - } -} - -pub fn babe_prover_withdraw(sig_v_presig: BabeBtcSig) -> TxWithdrawWitness { - TxWithdrawWitness { - input0_sig_p: BabeBtcSig::ProverLiveSig, - input0_sig_v: sig_v_presig.clone(), - input1_sig_p: BabeBtcSig::ProverLiveSig, - input1_sig_v: sig_v_presig, - } -} - /// Dec*(vk, ctsetup, ctprove, c1', π₂, π₃): /// Q_blind = e(c1', γ) where c1' = r·P_D + r·B (DSGC output) /// mask = e(r·π₁, π₂) - e(π₃, r·δ) - Q_blind = Y_S^r - e(r·B, γ) @@ -599,7 +544,6 @@ mod tests { use super::*; use ark_ff::UniformRand; use rand::SeedableRng; - use crate::lamport::{lamport_keygen, lamport_sign, lamport_verify}; use crate::wots::{wots96_verify, Wots96}; use bitvm::signatures::Wots; @@ -611,20 +555,6 @@ mod tests { assert_ne!(derive_hashlock(b"other"), h_msg); } - #[test] - fn lamport_sign_verify_roundtrip() { - let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(1); - let pi1 = G1Affine::from(ark_bn254::G1Projective::rand(&mut rng)); - let x_d = ark_bn254::Fr::rand(&mut rng); - let (lsk, lpk) = lamport_keygen(&mut rng); - let sig = lamport_sign(&lsk, &pi1, x_d); - assert!(lamport_verify(&lpk, &pi1, x_d, &sig)); - let pi1_other = G1Affine::from(ark_bn254::G1Projective::rand(&mut rng)); - assert!(!lamport_verify(&lpk, &pi1_other, x_d, &sig)); - let x_d_other = ark_bn254::Fr::rand(&mut rng); - assert!(!lamport_verify(&lpk, &pi1, x_d_other, &sig)); - } - #[test] fn wots96_sign_verify_roundtrip() { let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(2); diff --git a/verifiable-circuit-babe/src/instance/mod.rs b/verifiable-circuit-babe/src/instance/mod.rs index f0b7bc6..e321400 100644 --- a/verifiable-circuit-babe/src/instance/mod.rs +++ b/verifiable-circuit-babe/src/instance/mod.rs @@ -127,26 +127,7 @@ impl CACInstance { &secrets.rhos[0], &secrets.fq_deltas[0], ); - - // // test table - // - // // size = gc_output_indices - // let output_labels: Vec<[u8; 16]> = fgc_indices - // .iter() - // .map(|idx| { - // let w = fgc.0[*idx].borrow(); - // w.select_with_delta(w.get_value(), secrets.delta[0]).0 - // }) - // .collect(); - // - // let ct1_bytes = BABEProver::eval_adaptor_table( - // &output_labels, pi1, &fgc_adaptor_table - // ); - // let mut expected_ct1_bytes = Vec::new(); - // (pi1 * secrets.r).into_affine().serialize_compressed(&mut expected_ct1_bytes).expect("serialize r·G1P"); - // assert_eq!(ct1_bytes, expected_ct1_bytes); - // println!("eval correctly"); - + let sgc_adaptor_table = SparseAdaptorTable::build_from_r_and_u_bar_labels( secrets.r, &sgc_output_labels_2, diff --git a/verifiable-circuit-babe/src/lamport.rs b/verifiable-circuit-babe/src/lamport.rs deleted file mode 100644 index 48aed66..0000000 --- a/verifiable-circuit-babe/src/lamport.rs +++ /dev/null @@ -1,54 +0,0 @@ -// ─── Lamport Signature Scheme ───────────────────────────────────────────────── - -use ark_bn254::G1Affine; -use rand::RngCore; -use serde::{Deserialize, Serialize}; -use garbled_snark_verifier::circuits::bn254::fr::Fr; -use crate::babe::LAMPORT_N; -use crate::utils::{derive_hashlock, pi1_to_bits}; - -/// Lamport signing key: LAMPORT_N pairs of 16-byte secrets. -/// Each secret has the same width as a GC input label. -#[derive(Debug, Clone)] -pub struct LamportSk(pub Vec<[[u8; 16]; 2]>); - -/// Lamport verification key: pk[i][b] = RIPEMD160(SHA256((sk[i][b])). -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct LamportPk(pub Vec<[[u8; 20]; 2]>); - -/// Lamport signature: sig[i] = sk[i][bit_i(π₁)], for i in 0..LAMPORT_N. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct LamportSig(pub Vec<[u8; 16]>); - -pub fn lamport_keygen(rng: &mut impl RngCore) -> (LamportSk, LamportPk) { - let mut sk_entries = Vec::with_capacity(LAMPORT_N); - let mut pk_entries = Vec::with_capacity(LAMPORT_N); - for _ in 0..LAMPORT_N { - let mut s0 = [0u8; 16]; - let mut s1 = [0u8; 16]; - rng.fill_bytes(&mut s0); - rng.fill_bytes(&mut s1); - pk_entries.push([derive_hashlock(&s0), derive_hashlock(&s1)]); - sk_entries.push([s0, s1]); - } - (LamportSk(sk_entries), LamportPk(pk_entries)) -} - -/// Sign π₁: reveal sk[i][bit_i(π₁)] for each bit. -pub fn lamport_sign(sk: &LamportSk, pi1: &G1Affine, x_d: ark_bn254::Fr) -> LamportSig { - let pi1_bits = pi1_to_bits(pi1); - let x_d_bits = Fr::to_bits(x_d); - let bits = pi1_bits.into_iter().chain(x_d_bits.into_iter()).collect::>(); - LamportSig(bits.iter().enumerate().map(|(i, &b)| sk.0[i][b as usize]).collect()) -} - -/// Verify a Lamport signature against lpk_P and π₁. -pub fn lamport_verify(pk: &LamportPk, pi1: &G1Affine, x_d: ark_bn254::Fr, sig: &LamportSig) -> bool { - if sig.0.len() != LAMPORT_N { - return false; - } - let pi1_bits = pi1_to_bits(pi1); - let x_d_bits = Fr::to_bits(x_d); - let bits = pi1_bits.into_iter().chain(x_d_bits.into_iter()).collect::>(); - bits.iter().enumerate().all(|(i, &b)| derive_hashlock(&sig.0[i]) == pk.0[i][b as usize]) -} diff --git a/verifiable-circuit-babe/src/lib.rs b/verifiable-circuit-babe/src/lib.rs index 0392667..c06b010 100644 --- a/verifiable-circuit-babe/src/lib.rs +++ b/verifiable-circuit-babe/src/lib.rs @@ -8,5 +8,4 @@ pub mod cac; pub mod utils; pub mod transactions; pub mod soldering; -pub mod lamport; pub mod wots; From 165730eaae36e3f2628808af4457f1789b37516e Mon Sep 17 00:00:00 2001 From: vanhger Date: Fri, 8 May 2026 16:07:00 +0700 Subject: [PATCH 29/32] remove unpractical Tx witness --- verifiable-circuit-babe/src/babe.rs | 22 +++++++--------- verifiable-circuit-babe/src/transactions.rs | 28 ++++++++++++++------- verifiable-circuit-babe/src/verifier.rs | 7 ------ verifiable-circuit-babe/tests/babe_e2e.rs | 2 +- 4 files changed, 29 insertions(+), 30 deletions(-) diff --git a/verifiable-circuit-babe/src/babe.rs b/verifiable-circuit-babe/src/babe.rs index 160866a..c78581a 100644 --- a/verifiable-circuit-babe/src/babe.rs +++ b/verifiable-circuit-babe/src/babe.rs @@ -1,9 +1,9 @@ -use ark_bn254::{Bn254, Fr, G1Affine}; +use ark_bn254::{Bn254, Fr}; use ark_ff::PrimeField; use ark_groth16::{Proof as Groth16Proof, VerifyingKey as Groth16VerifyingKey}; use ark_groth16::ProvingKey as Groth16ProvingKey; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_serialize::CanonicalSerialize; use ark_relations::lc; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; use rand::SeedableRng; @@ -22,7 +22,7 @@ use crate::utils::pi1_xd_to_wots96_msg; use bitvm::signatures::Wots; use crate::prover::{BABEProver, GROTH_16_SEED}; use crate::soldering::{build_soldered_wires_input, soldering_guest_compute, SolderingData, SolderingProof}; -use crate::transactions::{OnchainSize, TxAssertWitness, TxChallengeAssertOutputLock, TxChallengeAssertWitness, TxDepositLock, TxNoWithdrawWitness, TxWithdrawWitness, TxWronglyChallengedWitness}; +use crate::transactions::{OnchainSize, TxAssertWitness, TxChallengeAssertOutputLock, TxChallengeAssertWitness, TxDepositLock, TxWronglyChallengedWitness}; pub use crate::utils::{derive_hashlock, g1_from_ser_checked, g1_to_ser, g2_from_ser_checked, g2_to_ser, groth16_vk_x, h_256, ro_from_pairing_bytes}; use crate::verifier::BABEVerifier; @@ -157,7 +157,7 @@ pub fn babe_verifier_cac_setup( pub fn babe_verifier_open_and_solder( verifier: &BABEVerifier, finalized_indices: &[usize], -) -> (Vec<(usize, u64)>, Vec, SolderingData, [u8; 20]) { +) -> (Vec<(usize, u64)>, Vec, SolderingData) { let (opened, finalized) = verifier.open(finalized_indices).expect("verifier open failed"); // Note that this part will be replaced by generating soldering proof in production. @@ -169,7 +169,7 @@ pub fn babe_verifier_open_and_solder( soldering_proof: SolderingProof { soldered_output, _proof: PhantomData }, }; - (opened, finalized, soldering, derive_hashlock(&verifier.temp_val)) + (opened, finalized, soldering) } /// Prover: verify the opened instances, the finalized instances, and the soldering proof. @@ -249,13 +249,9 @@ pub fn babe_build_deposit_lock(pk_p: BtcPk, pk_v: BtcPk, amount: u64) -> TxDepos /// Prover: sign π₁ and x_d with wots_sk_P and build the assert witness. pub fn babe_prover_assert(proof: &Groth16Proof, wots_sk: &Wots96Secret, x_d: ark_bn254::Fr) -> TxAssertWitness { let pi1 = proof.a; - let mut pi1_bytes = Vec::new(); - pi1.serialize_compressed(&mut pi1_bytes).expect("serialize π₁"); - let mut x_d_bytes = Vec::new(); - x_d.serialize_compressed(&mut x_d_bytes).expect("serialize x_d"); let msg = pi1_xd_to_wots96_msg(&pi1, x_d); let wots_sig = Wots96::sign(wots_sk, &msg); - TxAssertWitness { pi1: pi1_bytes, x_d: x_d_bytes, wots_sig } + TxAssertWitness { wots_sig } } // ─── ChallengeAssert phase (Verifier reveals base-instance labels) ──────────── @@ -279,8 +275,7 @@ pub fn babe_verifier_challenge_assert_cac( verifier_state: &VerifierSetupState, sig_p_presig: BabeBtcSig, ) -> Option { - let pi1 = G1Affine::deserialize_compressed(assert_witness.pi1.as_slice()).ok()?; - let x_d = Fr::from_le_bytes_mod_order(&assert_witness.x_d); + let (pi1, x_d) = assert_witness.recover_pi1_xd_without_verify()?; let msg = pi1_xd_to_wots96_msg(&pi1, x_d); println!("Verifier: Checking Wots96 signature in tx_Assert against pi1, x_d and wots_pk_p..."); @@ -417,7 +412,7 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { // Verifier opens non-finalized instances and generates soldering proof. println!("Verifier: opening and soldering..."); - let (opened, finalized, soldering, _hash_temp_val) = babe_verifier_open_and_solder(&verifier, &finalized_indices); + let (opened, finalized, soldering) = babe_verifier_open_and_solder(&verifier, &finalized_indices); println!("Prover: verifying opening and soldering proof..."); // Prover verifies everything. @@ -541,6 +536,7 @@ impl ConstraintSynthesizer for DummyMulCircuit { #[cfg(test)] mod tests { + use ark_bn254::G1Affine; use super::*; use ark_ff::UniformRand; use rand::SeedableRng; diff --git a/verifiable-circuit-babe/src/transactions.rs b/verifiable-circuit-babe/src/transactions.rs index 205c61f..86ddeb0 100644 --- a/verifiable-circuit-babe/src/transactions.rs +++ b/verifiable-circuit-babe/src/transactions.rs @@ -1,5 +1,7 @@ // ─── Transaction locking script ─────────────────────────────────────────────── +use ark_bn254::{Fq, Fr, G1Affine}; +use ark_serialize::CanonicalDeserialize; use serde::{Deserialize, Serialize}; use crate::babe::{BabeBtcSig, BtcPk, BTC_SIG_BYTES, MSG_BYTES}; use crate::wots::{Wots96, Wots96Sig}; @@ -28,17 +30,25 @@ pub struct TxChallengeAssertOutputLock { /// Script verifies the Wots96 signature over the 96-byte message (π₁.x ∥ π₁.y ∥ x_d). #[derive(Debug, Clone, PartialEq, Eq)] pub struct TxAssertWitness { - /// Compressed G1Affine, 33 bytes — the asserted proof element. - /// In practice, this is not onchain. - pub pi1: Vec, - /// Dynamic public input scalar x_d, 32 bytes (little-endian Fr). - /// This is not onchain. - pub x_d: Vec, /// Wots96 signature over the 96-byte message (π₁.x LE-32 ∥ π₁.y LE-32 ∥ x_d LE-32). - /// This is submitted onchain. + /// π₁ and x_d are recoverable from the digit values embedded in this signature. pub wots_sig: Wots96Sig, } +impl TxAssertWitness { + /// Extract π₁ and x_d from the digit values embedded in the Wots96 signature. + /// The 96-byte message layout is: π₁.x (LE-32) ∥ π₁.y (LE-32) ∥ x_d (LE-32). + /// Does NOT verify the signature — caller must call wots96_verify separately. + pub fn recover_pi1_xd_without_verify(&self) -> Option<(G1Affine, Fr)> { + let msg = Wots96::signature_to_message(&self.wots_sig); + let x = Fq::deserialize_uncompressed(&msg[0..32]).ok()?; + let y = Fq::deserialize_uncompressed(&msg[32..64]).ok()?; + let pi1 = G1Affine::new_unchecked(x, y); + let x_d = Fr::deserialize_uncompressed(&msg[64..96]).ok()?; + Some((pi1, x_d)) + } +} + /// tx_ChallengeAssert — witness for input 0. /// Input spends tx_Assert output 1: CheckSigsConsistent(wots_pk_P, epk_V) ∧ CheckSig(pk_V) ∧ CheckSig(pk_P) /// Script verifies: @@ -106,8 +116,8 @@ pub trait OnchainSize { impl OnchainSize for TxAssertWitness { fn size_bytes(&self) -> usize { - // TOTAL_DIGIT_LEN hash-chain preimages, each 20 bytes. - Wots96::TOTAL_DIGIT_LEN as usize * 20 + // Signature size + Wots96::TOTAL_DIGIT_LEN as usize * 21 } } diff --git a/verifiable-circuit-babe/src/verifier.rs b/verifiable-circuit-babe/src/verifier.rs index f5744c2..0580ed0 100644 --- a/verifiable-circuit-babe/src/verifier.rs +++ b/verifiable-circuit-babe/src/verifier.rs @@ -1,6 +1,5 @@ use ark_bn254::{Fr, G1Affine}; use ark_groth16::VerifyingKey as Groth16VerifyingKey; -use rand::Rng; use garbled_snark_verifier::bag::S; use garbled_snark_verifier::dv_bn254::fq::Fq as DvFq; use garbled_snark_verifier::dv_bn254::fr::Fr as DvFr; @@ -30,7 +29,6 @@ pub struct BABEVerifier { /// Encoding keys and deltas for every instance — needed for label computation /// without re-deriving the full garbled circuit. pub light_secrets: Vec, - pub temp_val: [u8; 32], vk: Groth16VerifyingKey, static_public_inputs: Fr, } @@ -85,15 +83,10 @@ impl BABEVerifier { } } - let rng = &mut rand::thread_rng(); - let mut temp_val = [0u8; 32]; - rng.fill(&mut temp_val); - Ok(Self { seeds, commits, light_secrets, - temp_val, vk: vk.clone(), static_public_inputs, }) diff --git a/verifiable-circuit-babe/tests/babe_e2e.rs b/verifiable-circuit-babe/tests/babe_e2e.rs index 72ace30..0510513 100644 --- a/verifiable-circuit-babe/tests/babe_e2e.rs +++ b/verifiable-circuit-babe/tests/babe_e2e.rs @@ -4,7 +4,7 @@ use verifiable_circuit_babe::babe::run_babe_e2e_cac; fn e2e_babe_cac() { use verifiable_circuit_babe::transactions::OnchainSize; let run = run_babe_e2e_cac(); - assert_eq!(run.assert_witness.size_bytes(), 1960); // 98 * 20 + assert_eq!(run.assert_witness.size_bytes(), 2058); // 98 * 21 assert_eq!(run.challenge_assert_witness.size_bytes(), 14312); assert_eq!(run.wrongly_challenged_witness.size_bytes(), 64); } From 1f70dc4074420ad3a0e5eb0c03a33cfc69f09592 Mon Sep 17 00:00:00 2001 From: vanhger Date: Mon, 11 May 2026 09:57:56 +0700 Subject: [PATCH 30/32] chore: fix minor bugs. --- garbled-snark-verifier/src/core/circuit.rs | 2 +- verifiable-circuit-babe/src/babe.rs | 14 +---------- verifiable-circuit-babe/src/gc/mod.rs | 2 +- verifiable-circuit-babe/src/instance/mod.rs | 8 +++---- verifiable-circuit-babe/src/prover.rs | 26 +++++++++++++-------- verifiable-circuit-babe/src/transactions.rs | 2 +- 6 files changed, 24 insertions(+), 30 deletions(-) diff --git a/garbled-snark-verifier/src/core/circuit.rs b/garbled-snark-verifier/src/core/circuit.rs index 73eeed6..b64ed9c 100644 --- a/garbled-snark-verifier/src/core/circuit.rs +++ b/garbled-snark-verifier/src/core/circuit.rs @@ -116,7 +116,7 @@ impl Circuit { .for_each(|(bit, wirex)| wirex.borrow_mut().set_value_for_uninitialized(*bit)); } - pub fn reset_circuit_except_constants(&mut self) { + pub fn reset_circuit_except_01_constants(&mut self) { for wirex in self.0.iter().skip(2) { wirex.borrow_mut().value = None; } diff --git a/verifiable-circuit-babe/src/babe.rs b/verifiable-circuit-babe/src/babe.rs index c78581a..a2ad630 100644 --- a/verifiable-circuit-babe/src/babe.rs +++ b/verifiable-circuit-babe/src/babe.rs @@ -27,12 +27,6 @@ pub use crate::utils::{derive_hashlock, g1_from_ser_checked, g1_to_ser, g2_from_ use crate::verifier::BABEVerifier; // ─── Constants ──────────────────────────────────────────────────────────────── - -/// Number of bits covering π₁ and x_d: 256 bits for x + 256 bits for y + 256 Fr. -/// Each 254-bit field is padded to 256 bits (2 dummy zero MSBs) to align with -/// the 96-byte Wots96 message (3 × 32-byte LE fields). -pub const LAMPORT_N: usize = 768; - /// Total number of C&C instances the Verifier creates and commits to. /// In practice, N_CC = 181. pub const N_CC: usize = 4; @@ -41,15 +35,9 @@ pub const N_CC: usize = 4; /// In practice, M_CC = 4. pub const M_CC: usize = 2; -/// Byte size of a Lamport signature on-chain: LAMPORT_N revealed 16-byte secrets. -pub const LAMPORT_SIG_BYTES: usize = LAMPORT_N * 16; - /// Byte size of a Bitcoin signature placeholder (64 bytes in production). pub const BTC_SIG_BYTES: usize = 32; -/// Byte size of a compressed G1Affine point (π₁). -pub const PI1_BYTES: usize = 33; - /// Byte size of the secret message. pub const MSG_BYTES: usize = 32; @@ -449,7 +437,7 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { let verifier_presigs = babe_verifier_presign(); println!("Verifier: sending presigs_v to Verifier..."); - println!("Prover: verifying presigs_p..."); + println!("Prover: verifying presigs_v..."); assert!(babe_verify_verifier_presigs(&verifier_presigs), "verifier presigs invalid"); // ── Deposit ─────────────────────────────────────────────────────────────── diff --git a/verifiable-circuit-babe/src/gc/mod.rs b/verifiable-circuit-babe/src/gc/mod.rs index 0af72b4..a9ca2fe 100644 --- a/verifiable-circuit-babe/src/gc/mod.rs +++ b/verifiable-circuit-babe/src/gc/mod.rs @@ -62,7 +62,7 @@ pub fn read_fresh_gc() -> (Circuit, Vec, Circuit, Vec) { (fgc, fgc_indices, sgc, sgc_indices) } -fn deserialize_circuit(gates_bytes: &Vec, output_indices_bytes: &Vec) -> (Circuit, Vec) { +fn deserialize_circuit(gates_bytes: &[u8], output_indices_bytes: &[u8]) -> (Circuit, Vec) { let (num_wires, gates_read): (u32, Vec) = bincode::deserialize(gates_bytes).expect("deserialize gates"); let output_indices: Vec = diff --git a/verifiable-circuit-babe/src/instance/mod.rs b/verifiable-circuit-babe/src/instance/mod.rs index e321400..d2cb123 100644 --- a/verifiable-circuit-babe/src/instance/mod.rs +++ b/verifiable-circuit-babe/src/instance/mod.rs @@ -102,7 +102,7 @@ impl CACInstance { assert_eq!(sgc_output_labels_1.len(), 2 * Q_SIZE); // Sgc - part 2 // Reuse the fgc structure, by setting up the input & constant labels again, then evaluate. - fgc.reset_circuit_except_constants(); + fgc.reset_circuit_except_01_constants(); // set label of part2 as output of part1 for (i, &key) in sgc_output_labels_1.iter().step_by(2).enumerate() { fgc.0[2 + i].borrow_mut().label = Some(S(key)); @@ -213,7 +213,7 @@ impl CACInstance { assert_eq!(sgc_output_labels_1.len(), 2 * Q_SIZE); // SGC part 2: reuse fgc (same pattern as new_from_seed). - fgc.reset_circuit_except_constants(); + fgc.reset_circuit_except_01_constants(); for (i, &key) in sgc_output_labels_1.iter().step_by(2).enumerate() { fgc.0[2 + i].borrow_mut().label = Some(S(key)); } @@ -536,7 +536,7 @@ mod tests { println!("sgc part 1 test done"); // evaluate the sgc part2 to get the r * Q - fgc.reset_circuit_except_constants(); + fgc.reset_circuit_except_01_constants(); // set label of part2 as output of part1 (evaluator has one active label per wire) for (i, &key) in sgc_output_labels_1.iter().enumerate() { fgc.0[2 + i].borrow_mut().label = Some(S(key)); @@ -559,7 +559,7 @@ mod tests { let mut expected_ct1_prime_bytes = Vec::new(); (q_affine * r).into_affine().serialize_compressed(&mut expected_ct1_prime_bytes).expect("serialize r·G1P"); - assert_eq!(ct1_bytes, expected_ct1_bytes, "sgc part2 is wrong"); + assert_eq!(ct1_prime, expected_ct1_prime_bytes, "sgc part2 is wrong"); println!("sgc part 2 test done"); let ctprove = WeKnownPi1ProveCt { diff --git a/verifiable-circuit-babe/src/prover.rs b/verifiable-circuit-babe/src/prover.rs index 1ccc340..e4ff2de 100644 --- a/verifiable-circuit-babe/src/prover.rs +++ b/verifiable-circuit-babe/src/prover.rs @@ -98,13 +98,15 @@ impl BABEProver { h_msgs_onchain: &[[u8; 20]], ) -> bool { let sld = &soldering.soldering_proof.soldered_output; + let (mut fgc, fgc_indices, mut sgc, sgc_indices) = crate::gc::read_fresh_gc(); println!("Trying base instance..."); let base_res = self.try_evaluate_instance( &finalized[0], &pi1_labels, &x_d_labels, - h_msgs_onchain[0] + h_msgs_onchain[0], + &mut fgc, &fgc_indices, &mut sgc, &sgc_indices, ); match base_res { Ok(true) => return true, @@ -144,11 +146,14 @@ impl BABEProver { }) .collect(); + fgc.reset_circuit_except_01_constants(); + sgc.reset_circuit_except_01_constants(); let temp = self.try_evaluate_instance( &finalized[i], &instance_pi1_labels, &instance_x_d_labels, - h_msgs_onchain[i] + h_msgs_onchain[i], + &mut fgc, &fgc_indices, &mut sgc, &sgc_indices, ); match temp { Ok(true) => return true, @@ -168,13 +173,16 @@ impl BABEProver { pi1_labels: &[S], x_d_labels: &[S], h_msg_onchain: [u8; 20], + fgc: &mut Circuit, + fgc_indices: &[usize], + sgc: &mut Circuit, + sgc_indices: &[usize], ) -> Result { - let (mut fgc, fgc_indices, mut sgc, sgc_indices) = crate::gc::read_fresh_gc(); let ct_prove = self.compute_ct_prove( - &mut fgc, - &fgc_indices, - &mut sgc, - &sgc_indices, + fgc, + fgc_indices, + sgc, + sgc_indices, &[data.constant_labels_0.to_vec(), data.constant_labels_1.to_vec()], &pi1_labels, &x_d_labels, @@ -182,8 +190,6 @@ impl BABEProver { &data.adaptor_tables, &data.b, ); - drop(fgc); - drop(sgc); println!("compute ct_prove done"); let msg = Self::compute_msg(&self.groth16_proof, &ct_prove, &data.ct_setup, &self.pk.vk)?; @@ -263,7 +269,7 @@ impl BABEProver { // Part2: compute rQ let q = self.pk.vk.gamma_abc_g1[2] * self.dyn_pubin + b; let q_affine = q.into_affine(); - fgc.reset_circuit_except_constants(); + fgc.reset_circuit_except_01_constants(); // set label of part2 as output of part1 (evaluator has one active label per wire) for (i, &key) in sgc_output_labels_1.iter().enumerate() { fgc.0[2 + i].borrow_mut().label = Some(S(key)); diff --git a/verifiable-circuit-babe/src/transactions.rs b/verifiable-circuit-babe/src/transactions.rs index 86ddeb0..b7fb3ce 100644 --- a/verifiable-circuit-babe/src/transactions.rs +++ b/verifiable-circuit-babe/src/transactions.rs @@ -43,7 +43,7 @@ impl TxAssertWitness { let msg = Wots96::signature_to_message(&self.wots_sig); let x = Fq::deserialize_uncompressed(&msg[0..32]).ok()?; let y = Fq::deserialize_uncompressed(&msg[32..64]).ok()?; - let pi1 = G1Affine::new_unchecked(x, y); + let pi1 = G1Affine::new(x, y); let x_d = Fr::deserialize_uncompressed(&msg[64..96]).ok()?; Some((pi1, x_d)) } From 987f77573591e5415b2766d732e5dcba224bfe54 Mon Sep 17 00:00:00 2001 From: vanhger Date: Wed, 13 May 2026 10:54:30 +0700 Subject: [PATCH 31/32] change batch_size. --- babe-programs/soldering/host/src/main.rs | 6 +++--- verifiable-circuit-babe/src/cac.rs | 4 ++-- verifiable-circuit-babe/src/verifier.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/babe-programs/soldering/host/src/main.rs b/babe-programs/soldering/host/src/main.rs index 2127271..6349837 100644 --- a/babe-programs/soldering/host/src/main.rs +++ b/babe-programs/soldering/host/src/main.rs @@ -16,7 +16,7 @@ use verifiable_circuit_babe::verifier::BABEVerifier; const ELF: &[u8] = include_elf!("soldering-guest"); -const N_CC: usize = 4; // Number of C&C instances. +const M_CC: usize = 4; // Number of finalized C&C instances. fn main() { @@ -39,8 +39,8 @@ fn main() { // 1. Create N_CC instances let start = Instant::now(); - let verifier = BABEVerifier::new(N_CC, &vk, static_public_inputs).expect("verifier setup failed"); - info!(elapsed = ?start.elapsed(), n_cc = N_CC, "BABEVerifier created"); + let verifier = BABEVerifier::new(M_CC, &vk, static_public_inputs).expect("verifier setup failed"); + info!(elapsed = ?start.elapsed(), n_cc = M_CC, "BABEVerifier created"); // 2. C&C commit and derive finalized indices let package = verifier.commit(); diff --git a/verifiable-circuit-babe/src/cac.rs b/verifiable-circuit-babe/src/cac.rs index ddab92e..6e92120 100644 --- a/verifiable-circuit-babe/src/cac.rs +++ b/verifiable-circuit-babe/src/cac.rs @@ -83,7 +83,7 @@ pub fn verify_opened_instances( ) -> Result<(), String> { use p3_maybe_rayon::prelude::*; - const BATCH_SIZE: usize = 8; + const BATCH_SIZE: usize = 10; let pool = rayon::ThreadPoolBuilder::new() .num_threads(BATCH_SIZE) @@ -196,7 +196,7 @@ mod tests { use crate::prover::GROTH_16_SEED; use crate::verifier::BABEVerifier; - const TEST_N_CC: usize = 10; + const TEST_N_CC: usize = 50; const TEST_M_CC: usize = 4; #[test] diff --git a/verifiable-circuit-babe/src/verifier.rs b/verifiable-circuit-babe/src/verifier.rs index 0580ed0..fd999e7 100644 --- a/verifiable-circuit-babe/src/verifier.rs +++ b/verifiable-circuit-babe/src/verifier.rs @@ -8,7 +8,7 @@ use crate::instance::commit::CACInstanceCommit; /// Number of instances generated in parallel per batch during the commitment phase. /// Tune to match available RAM: peak ≈ BATCH_SIZE × ~6 GB. -const BATCH_SIZE: usize = 8; +const BATCH_SIZE: usize = 10; /// Minimal per-instance secrets retained after commitment phase. /// Only encoding keys and deltas are kept — all heavy GC data is dropped. From a3eca1bd224470eec162c735aedae67d758a8c81 Mon Sep 17 00:00:00 2001 From: vanhger Date: Wed, 13 May 2026 12:01:44 +0700 Subject: [PATCH 32/32] feat: add non zkvm cfg --- verifiable-circuit-babe/Cargo.toml | 5 ++++- verifiable-circuit-babe/src/lib.rs | 20 ++++++++++++++------ verifiable-circuit-babe/src/soldering.rs | 1 + 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/verifiable-circuit-babe/Cargo.toml b/verifiable-circuit-babe/Cargo.toml index 8b5b2a6..0b95ae7 100644 --- a/verifiable-circuit-babe/Cargo.toml +++ b/verifiable-circuit-babe/Cargo.toml @@ -24,9 +24,12 @@ aes = "0.8" cfg-if = "1.0.4" ripemd = "0.2.0" rayon = "1.11.0" + +[target.'cfg(not(target_os = "zkvm"))'.dependencies] bitvm = { git = "https://github.com/GOATNetwork/BitVM.git", branch = "GA" } bitcoin = { version = "0.32.5", features = ["rand-std"] } -[target.'cfg(all(target_os = "zkvm"))'.dependencies] + +[target.'cfg(target_os = "zkvm")'.dependencies] zkm-zkvm = { workspace = true } [features] diff --git a/verifiable-circuit-babe/src/lib.rs b/verifiable-circuit-babe/src/lib.rs index c06b010..56c935f 100644 --- a/verifiable-circuit-babe/src/lib.rs +++ b/verifiable-circuit-babe/src/lib.rs @@ -1,11 +1,19 @@ -pub mod babe; pub mod dre; pub mod gc; -pub mod instance; -pub mod prover; -pub mod verifier; -pub mod cac; pub mod utils; -pub mod transactions; pub mod soldering; + +#[cfg(not(target_os = "zkvm"))] +pub mod babe; +#[cfg(not(target_os = "zkvm"))] pub mod wots; +#[cfg(not(target_os = "zkvm"))] +pub mod transactions; +#[cfg(not(target_os = "zkvm"))] +pub mod cac; +#[cfg(not(target_os = "zkvm"))] +pub mod prover; +#[cfg(not(target_os = "zkvm"))] +pub mod instance; +#[cfg(not(target_os = "zkvm"))] +pub mod verifier; diff --git a/verifiable-circuit-babe/src/soldering.rs b/verifiable-circuit-babe/src/soldering.rs index bbdf383..8b1ff16 100644 --- a/verifiable-circuit-babe/src/soldering.rs +++ b/verifiable-circuit-babe/src/soldering.rs @@ -74,6 +74,7 @@ pub fn soldering_guest_compute(input: &SolderedWiresInput) -> SolderedLabelsData } +#[cfg(not(target_os = "zkvm"))] /// Build `SolderedWiresInput` from the verifier's finalized instances. /// `instances[0]` = base (finalized_indices[0]), `instances[1..]` = non-base in order. pub fn build_soldered_wires_input(