From 959aaccae644eae2f96251e259dbb395309457e3 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 7 Apr 2021 22:21:45 -0700 Subject: [PATCH] Merkle proofs --- src/bin/bench_recursion.rs | 2 +- src/circuit_builder.rs | 24 ++++++++++++ src/gadgets/hash.rs | 41 -------------------- src/gadgets/merkle_proofs.rs | 60 +++++++++++++++++++++++++++-- src/gadgets/mod.rs | 1 - src/gadgets/split_join.rs | 31 ++++++++------- src/gates/gmimc.rs | 73 +++++++++++++++++++++++++----------- src/proof.rs | 3 +- 8 files changed, 153 insertions(+), 82 deletions(-) delete mode 100644 src/gadgets/hash.rs diff --git a/src/bin/bench_recursion.rs b/src/bin/bench_recursion.rs index 9047d15a..81409642 100644 --- a/src/bin/bench_recursion.rs +++ b/src/bin/bench_recursion.rs @@ -39,7 +39,7 @@ fn bench_prove() { let gmimc_gate = GMiMCGate::::with_automatic_constants(); let config = CircuitConfig { - num_wires: 120, + num_wires: 134, num_routed_wires: 12, security_bits: 128, rate_bits: 3, diff --git a/src/circuit_builder.rs b/src/circuit_builder.rs index 5d9d42d3..71a3d245 100644 --- a/src/circuit_builder.rs +++ b/src/circuit_builder.rs @@ -22,8 +22,13 @@ pub struct CircuitBuilder { /// The types of gates used in this circuit. gates: HashSet>, + /// The concrete placement of each gate. gate_instances: Vec>, + /// The next available index for a VirtualAdviceTarget. + virtual_target_index: usize, + + /// Generators used to generate the witness. generators: Vec>>, } @@ -33,10 +38,29 @@ impl CircuitBuilder { config, gates: HashSet::new(), gate_instances: Vec::new(), + virtual_target_index: 0, generators: Vec::new(), } } + /// Adds a new "virtual" advice target. This is not an actual wire in the witness, but just a + /// target that help facilitate witness generation. In particular, a generator can assign a + /// values to a virtual target, which can then be copied to other (virtual or concrete) targets + /// via `generate_copy`. When we generate the final witness (a grid of wire values), these + /// virtual targets will go away. + /// + /// Since virtual targets are not part of the actual permutation argument, they cannot be used + /// with `assert_equal`. + pub fn add_virtual_advice_target(&mut self) -> Target { + let index = self.virtual_target_index; + self.virtual_target_index += 1; + Target::VirtualAdviceTarget { index } + } + + pub fn add_virtual_advice_targets(&mut self, n: usize) -> Vec { + (0..n).map(|_i| self.add_virtual_advice_target()).collect() + } + pub fn add_gate_no_constants(&mut self, gate_type: GateRef) -> usize { self.add_gate(gate_type, Vec::new()) } diff --git a/src/gadgets/hash.rs b/src/gadgets/hash.rs deleted file mode 100644 index 8d0357a6..00000000 --- a/src/gadgets/hash.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::convert::TryInto; - -use crate::circuit_builder::CircuitBuilder; -use crate::field::field::Field; -use crate::gates::gmimc::GMiMCGate; -use crate::gates::noop::NoopGate; -use crate::hash::GMIMC_ROUNDS; -use crate::target::Target; -use crate::wire::Wire; - -impl CircuitBuilder { - pub fn permute(&mut self, inputs: [Target; 12]) -> [Target; 12] { - let zero = self.zero(); - self.permute_switched(inputs, zero) - } - - pub(crate) fn permute_switched(&mut self, inputs: [Target; 12], switch: Target) -> [Target; 12] { - let gate = self.add_gate_no_constants( - GMiMCGate::::with_automatic_constants()); - - let switch_wire = GMiMCGate::::WIRE_SWITCH; - let switch_wire = Target::Wire(Wire { gate, input: switch_wire }); - self.route(switch, switch_wire); - - for i in 0..12 { - let in_wire = GMiMCGate::::wire_output(i); - let in_wire = Target::Wire(Wire { gate, input: in_wire }); - self.route(inputs[i], in_wire); - } - - // Add a NoopGate just to receive the outputs. - let next_gate = self.add_gate_no_constants(NoopGate::get()); - - (0..12) - .map(|i| Target::Wire( - Wire { gate: next_gate, input: GMiMCGate::::wire_output(i) })) - .collect::>() - .try_into() - .unwrap() - } -} diff --git a/src/gadgets/merkle_proofs.rs b/src/gadgets/merkle_proofs.rs index 246406b6..307ec153 100644 --- a/src/gadgets/merkle_proofs.rs +++ b/src/gadgets/merkle_proofs.rs @@ -1,7 +1,14 @@ +use std::convert::TryInto; + use crate::circuit_builder::CircuitBuilder; use crate::field::field::Field; +use crate::gates::gmimc::GMiMCGate; +use crate::gates::noop::NoopGate; +use crate::hash::{compress, hash_or_noop}; +use crate::hash::GMIMC_ROUNDS; use crate::proof::{Hash, HashTarget}; use crate::target::Target; +use crate::wire::Wire; pub struct MerkleProof { /// The Merkle digest of each sibling subtree, staying from the bottommost layer. @@ -20,11 +27,24 @@ pub(crate) fn verify_merkle_proof( leaf_index: usize, merkle_root: Hash, proof: MerkleProof, -) { - todo!() +) -> bool { + let mut current_digest = hash_or_noop(leaf_data); + for (i, sibling_digest) in proof.siblings.into_iter().enumerate() { + let bit = (leaf_index >> i & 1) == 1; + current_digest = if bit { + compress(sibling_digest, current_digest) + } else { + compress(current_digest, sibling_digest) + } + } + current_digest == merkle_root } impl CircuitBuilder { + pub(crate) fn permute(&mut self, state: [Target; 12]) -> [Target; 12] { + todo!() + } + /// Verifies that the given leaf data is present at the given index in the Merkle tree with the /// given root. pub(crate) fn verify_merkle_proof( @@ -34,6 +54,40 @@ impl CircuitBuilder { merkle_root: HashTarget, proof: MerkleProofTarget, ) { - todo!() + let zero = self.zero(); + let height = proof.siblings.len(); + let purported_index_bits = self.split_le_virtual(leaf_index, height); + + let mut state: Vec = todo!(); // hash leaf data + + for (bit, sibling) in purported_index_bits.into_iter().zip(proof.siblings) { + let gate = self.add_gate_no_constants( + GMiMCGate::::with_automatic_constants()); + + let swap_wire = GMiMCGate::::WIRE_SWAP; + let swap_wire = Target::Wire(Wire { gate, input: swap_wire }); + self.generate_copy(bit, swap_wire); + + let input_wires = (0..12) + .map(|i| Target::Wire( + Wire { gate, input: GMiMCGate::::wire_input(i) })) + .collect::>(); + + for i in 0..4 { + self.route(state[i], input_wires[i]); + self.route(sibling.elements[i], input_wires[4 + i]); + self.route(zero, input_wires[8 + i]); + } + + state = (0..4) + .map(|i| Target::Wire( + Wire { gate, input: GMiMCGate::::wire_output(i) })) + .collect::>() + .try_into() + .unwrap(); + } + + // TODO: Verify that weighted sum of bits matches index. + // TODO: Verify that state matches merkle root. } } diff --git a/src/gadgets/mod.rs b/src/gadgets/mod.rs index 8407efe7..7af6dc9e 100644 --- a/src/gadgets/mod.rs +++ b/src/gadgets/mod.rs @@ -1,4 +1,3 @@ pub(crate) mod arithmetic; -pub(crate) mod hash; pub(crate) mod merkle_proofs; pub(crate) mod split_join; diff --git a/src/gadgets/split_join.rs b/src/gadgets/split_join.rs index a6458440..21821706 100644 --- a/src/gadgets/split_join.rs +++ b/src/gadgets/split_join.rs @@ -3,20 +3,25 @@ use crate::generator::{SimpleGenerator, WitnessGenerator}; use crate::target::Target; use crate::wire::Wire; use crate::witness::PartialWitness; +use crate::circuit_builder::CircuitBuilder; -// /// Constraints for a little-endian split. -// pub fn split_le_constraints( -// integer: ConstraintPolynomial, -// bits: &[ConstraintPolynomial], -// ) -> Vec> { -// let weighted_sum = bits.iter() -// .fold(ConstraintPolynomial::zero(), |acc, b| acc.double() + b); -// bits.iter() -// .rev() -// .map(|b| b * (b - 1)) -// .chain(iter::once(weighted_sum - integer)) -// .collect() -// } +impl CircuitBuilder { + /// Split the given integer into a list of virtual advice targets, where each one represents a + /// bit of the integer, with little-endian ordering. + /// + /// Note that this only handles witness generation; it does not enforce that the decomposition + /// is correct. The output should be treated as a "purported" decomposition which must be + /// enforced elsewhere. + pub(crate) fn split_le_virtual( + &mut self, + integer: Target, + num_bits: usize, + ) -> Vec { + let bit_targets = self.add_virtual_advice_targets(num_bits); + split_le_generator::(integer, bit_targets.clone()); + bit_targets + } +} /// Generator for a little-endian split. pub fn split_le_generator( diff --git a/src/gates/gmimc.rs b/src/gates/gmimc.rs index dce5f9bb..77c7c526 100644 --- a/src/gates/gmimc.rs +++ b/src/gates/gmimc.rs @@ -15,6 +15,11 @@ const W: usize = 12; /// Evaluates a full GMiMC permutation with 12 state elements, and writes the output to the next /// gate's first `width` wires (which could be the input of another `GMiMCGate`). +/// +/// This also has some extra features to make it suitable for efficiently verifying Merkle proofs. +/// It has a flag which can be used to swap the first four inputs with the next four, for ordering +/// sibling digests. It also has an accumulator that computes the weighted sum of these flags, for +/// computing the index of the leaf based on these swap bits. #[derive(Debug)] pub struct GMiMCGate { constants: Arc<[F; R]>, @@ -31,24 +36,27 @@ impl GMiMCGate { Self::with_constants(constants) } - /// If this is set to 1, the first four inputs will be swapped with the next four inputs. This - /// is useful for ordering hashes in Merkle proofs. Otherwise, this should be set to 0. - pub const WIRE_SWITCH: usize = W; - - /// The wire index for the i'th input to the permutation. + /// The wire index for the `i`th input to the permutation. pub fn wire_input(i: usize) -> usize { i } - /// The wire index for the i'th output to the permutation. - /// Note that outputs are written to the next gate's wires. + /// The wire index for the `i`th output to the permutation. pub fn wire_output(i: usize) -> usize { - i + W + i } + /// Used to incrementally compute the index of the leaf based on a series of swap bits. + pub const WIRE_INDEX_ACCUMULATOR_OLD: usize = 2 * W; + pub const WIRE_INDEX_ACCUMULATOR_NEW: usize = 2 * W + 1; + + /// If this is set to 1, the first four inputs will be swapped with the next four inputs. This + /// is useful for ordering hashes in Merkle proofs. Otherwise, this should be set to 0. + pub const WIRE_SWAP: usize = 2 * W + 2; + /// A wire which stores the input to the `i`th cubing. fn wire_cubing_input(i: usize) -> usize { - W + 1 + i + 2 * W + 3 + i } } @@ -61,26 +69,34 @@ impl Gate for GMiMCGate { fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { let mut constraints = Vec::with_capacity(W + R); - // Value that is implicitly added to each element. - // See https://affine.group/2020/02/starkware-challenge - let mut addition_buffer = F::ZERO; + let swap = vars.local_wires[Self::WIRE_SWAP]; + // Assert that `swap` is binary. + constraints.push(swap * (swap - F::ONE)); + + let old_index_acc = vars.local_wires[Self::WIRE_INDEX_ACCUMULATOR_OLD]; + let new_index_acc = vars.local_wires[Self::WIRE_INDEX_ACCUMULATOR_NEW]; + let computed_new_index_acc = F::TWO * old_index_acc + swap; + constraints.push(computed_new_index_acc - new_index_acc); - let switch = vars.local_wires[Self::WIRE_SWITCH]; let mut state = Vec::with_capacity(12); for i in 0..4 { let a = vars.local_wires[i]; let b = vars.local_wires[i + 4]; - state.push(a + switch * (b - a)); + state.push(a + swap * (b - a)); } for i in 0..4 { let a = vars.local_wires[i + 4]; let b = vars.local_wires[i]; - state.push(a + switch * (b - a)); + state.push(a + swap * (b - a)); } for i in 8..12 { state.push(vars.local_wires[i]); } + // Value that is implicitly added to each element. + // See https://affine.group/2020/02/starkware-challenge + let mut addition_buffer = F::ZERO; + for r in 0..R { let active = r % W; let cubing_input = state[active] + addition_buffer + self.constants[r]; @@ -93,7 +109,7 @@ impl Gate for GMiMCGate { for i in 0..W { state[i] += addition_buffer; - constraints.push(state[i] - vars.next_wires[i]); + constraints.push(state[i] - vars.local_wires[Self::wire_output(i)]); } constraints @@ -163,17 +179,30 @@ impl SimpleGenerator for GMiMCGenerator { })) .collect::>(); - let switch_value = witness.get_wire(Wire { + let swap_value = witness.get_wire(Wire { gate: self.gate_index, - input: GMiMCGate::::WIRE_SWITCH, + input: GMiMCGate::::WIRE_SWAP, }); - debug_assert!(switch_value == F::ZERO || switch_value == F::ONE); - if switch_value == F::ONE { + debug_assert!(swap_value == F::ZERO || swap_value == F::ONE); + if swap_value == F::ONE { for i in 0..4 { state.swap(i, 4 + i); } } + // Update the index accumulator. + let old_index_acc_value = witness.get_wire(Wire { + gate: self.gate_index, + input: GMiMCGate::::WIRE_INDEX_ACCUMULATOR_OLD, + }); + let new_index_acc_value = F::TWO * old_index_acc_value + swap_value; + result.set_wire( + Wire { + gate: self.gate_index, + input: GMiMCGate::::WIRE_INDEX_ACCUMULATOR_NEW, + }, + new_index_acc_value); + // Value that is implicitly added to each element. // See https://affine.group/2020/02/starkware-challenge let mut addition_buffer = F::ZERO; @@ -196,7 +225,7 @@ impl SimpleGenerator for GMiMCGenerator { state[i] += addition_buffer; result.set_wire( Wire { - gate: self.gate_index + 1, + gate: self.gate_index, input: GMiMCGate::::wire_output(i), }, state[i]); @@ -239,7 +268,7 @@ mod tests { .collect::>(); let mut witness = PartialWitness::new(); - witness.set_wire(Wire { gate: 0, input: Gate::WIRE_SWITCH }, F::ZERO); + witness.set_wire(Wire { gate: 0, input: Gate::WIRE_SWAP }, F::ZERO); for i in 0..W { witness.set_wire( Wire { gate: 0, input: Gate::wire_input(i) }, diff --git a/src/proof.rs b/src/proof.rs index d25196cb..41d29f09 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -3,7 +3,7 @@ use crate::target::Target; use crate::gadgets::merkle_proofs::{MerkleProofTarget, MerkleProof}; /// Represents a ~256 bit hash output. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Hash { pub(crate) elements: [F; 4], } @@ -18,6 +18,7 @@ impl Hash { } } +/// Represents a ~256 bit hash output. pub struct HashTarget { pub(crate) elements: Vec, }