From 959aaccae644eae2f96251e259dbb395309457e3 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 7 Apr 2021 22:21:45 -0700 Subject: [PATCH 1/7] 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, } From 04f74446fad13afdadef24eaa8e1b6fe607baf7e Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 9 Apr 2021 11:40:51 -0700 Subject: [PATCH 2/7] Misc fixes to get tests green again --- src/gadgets/hash.rs | 42 ++++++++++++++++++++++++++++++++++++ src/gadgets/merkle_proofs.rs | 5 ----- src/gadgets/mod.rs | 3 ++- src/gates/gmimc.rs | 31 ++++++++++++++++++-------- src/plonk_challenger.rs | 2 +- 5 files changed, 67 insertions(+), 16 deletions(-) create mode 100644 src/gadgets/hash.rs diff --git a/src/gadgets/hash.rs b/src/gadgets/hash.rs new file mode 100644 index 00000000..cbf8f5e2 --- /dev/null +++ b/src/gadgets/hash.rs @@ -0,0 +1,42 @@ +use std::convert::TryInto; + +use crate::circuit_builder::CircuitBuilder; +use crate::field::field::Field; +use crate::gates::gmimc::GMiMCGate; +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(); + let gate = self.add_gate_no_constants( + GMiMCGate::::with_automatic_constants()); + + // We don't want to swap any inputs, so set that wire to 0. + let swap_wire = GMiMCGate::::WIRE_SWAP; + let swap_wire = Target::Wire(Wire { gate, input: swap_wire }); + self.route(zero, swap_wire); + + // The old accumulator wire doesn't matter, since we won't read the new accumulator wire. + // We do have to set it to something though, so we'll arbitrary pick 0. + let old_acc_wire = GMiMCGate::::WIRE_INDEX_ACCUMULATOR_OLD; + let old_acc_wire = Target::Wire(Wire { gate, input: old_acc_wire }); + self.route(zero, old_acc_wire); + + // Route input wires. + for i in 0..12 { + let in_wire = GMiMCGate::::wire_input(i); + let in_wire = Target::Wire(Wire { gate, input: in_wire }); + self.route(inputs[i], in_wire); + } + + // Collect output wires. + (0..12) + .map(|i| Target::Wire( + Wire { 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 307ec153..9154647b 100644 --- a/src/gadgets/merkle_proofs.rs +++ b/src/gadgets/merkle_proofs.rs @@ -3,7 +3,6 @@ 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}; @@ -41,10 +40,6 @@ pub(crate) fn verify_merkle_proof( } 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( diff --git a/src/gadgets/mod.rs b/src/gadgets/mod.rs index 7af6dc9e..c4f8dad4 100644 --- a/src/gadgets/mod.rs +++ b/src/gadgets/mod.rs @@ -1,3 +1,4 @@ -pub(crate) mod arithmetic; +pub mod arithmetic; +pub mod hash; pub(crate) mod merkle_proofs; pub(crate) mod split_join; diff --git a/src/gates/gmimc.rs b/src/gates/gmimc.rs index 77c7c526..63cf8918 100644 --- a/src/gates/gmimc.rs +++ b/src/gates/gmimc.rs @@ -1,12 +1,12 @@ use std::sync::Arc; use crate::circuit_builder::CircuitBuilder; -use crate::vars::{EvaluationTargets, EvaluationVars}; use crate::field::field::Field; use crate::gates::gate::{Gate, GateRef}; use crate::generator::{SimpleGenerator, WitnessGenerator}; use crate::gmimc::gmimc_automatic_constants; use crate::target::Target; +use crate::vars::{EvaluationTargets, EvaluationVars}; use crate::wire::Wire; use crate::witness::PartialWitness; @@ -69,8 +69,8 @@ impl Gate for GMiMCGate { fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { let mut constraints = Vec::with_capacity(W + R); - let swap = vars.local_wires[Self::WIRE_SWAP]; // Assert that `swap` is binary. + let swap = vars.local_wires[Self::WIRE_SWAP]; constraints.push(swap * (swap - F::ONE)); let old_index_acc = vars.local_wires[Self::WIRE_INDEX_ACCUMULATOR_OLD]; @@ -161,11 +161,15 @@ struct GMiMCGenerator { impl SimpleGenerator for GMiMCGenerator { fn dependencies(&self) -> Vec { - (0..W) - .map(|i| Target::Wire(Wire { - gate: self.gate_index, - input: GMiMCGate::::wire_input(i), - })) + let mut dep_input_indices = Vec::with_capacity(W + 2); + for i in 0..W { + dep_input_indices.push(GMiMCGate::::wire_input(i)); + } + dep_input_indices.push(GMiMCGate::::WIRE_SWAP); + dep_input_indices.push(GMiMCGate::::WIRE_INDEX_ACCUMULATOR_OLD); + + dep_input_indices.into_iter() + .map(|input| Target::Wire(Wire { gate: self.gate_index, input })) .collect() } @@ -268,7 +272,12 @@ mod tests { .collect::>(); let mut witness = PartialWitness::new(); - witness.set_wire(Wire { gate: 0, input: Gate::WIRE_SWAP }, F::ZERO); + witness.set_wire( + Wire { gate: 0, input: Gate::WIRE_INDEX_ACCUMULATOR_OLD }, + F::from_canonical_usize(7)); + 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) }, @@ -284,8 +293,12 @@ mod tests { for i in 0..W { let out = witness.get_wire( - Wire { gate: 1, input: Gate::wire_output(i) }); + Wire { gate: 0, input: Gate::wire_output(i) }); assert_eq!(out, expected_outputs[i]); } + + let acc_new = witness.get_wire( + Wire { gate: 0, input: Gate::WIRE_INDEX_ACCUMULATOR_NEW }); + assert_eq!(acc_new, F::from_canonical_usize(7 * 2)); } } diff --git a/src/plonk_challenger.rs b/src/plonk_challenger.rs index 8dbd2813..09276105 100644 --- a/src/plonk_challenger.rs +++ b/src/plonk_challenger.rs @@ -228,7 +228,7 @@ mod tests { let config = CircuitConfig { num_wires: 114, - num_routed_wires: 13, + num_routed_wires: 27, ..CircuitConfig::default() }; let mut builder = CircuitBuilder::::new(config); From 93b73fb89ac896bd328a319396dd2cc1000ffd70 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 9 Apr 2021 12:40:43 -0700 Subject: [PATCH 3/7] Recursive Merkle proofs --- src/gadgets/hash.rs | 1 + src/gadgets/mod.rs | 1 - src/hash.rs | 21 +++++++++++++++++++-- src/lib.rs | 1 + src/{gadgets => }/merkle_proofs.rs | 30 ++++++++++++++++++++++-------- src/proof.rs | 20 ++++++++++++++++++-- 6 files changed, 61 insertions(+), 13 deletions(-) rename src/{gadgets => }/merkle_proofs.rs (73%) diff --git a/src/gadgets/hash.rs b/src/gadgets/hash.rs index cbf8f5e2..20b26244 100644 --- a/src/gadgets/hash.rs +++ b/src/gadgets/hash.rs @@ -7,6 +7,7 @@ use crate::hash::GMIMC_ROUNDS; use crate::target::Target; use crate::wire::Wire; +// TODO: Move to be next to native `permute`? impl CircuitBuilder { pub fn permute(&mut self, inputs: [Target; 12]) -> [Target; 12] { let zero = self.zero(); diff --git a/src/gadgets/mod.rs b/src/gadgets/mod.rs index c4f8dad4..ed84207e 100644 --- a/src/gadgets/mod.rs +++ b/src/gadgets/mod.rs @@ -1,4 +1,3 @@ pub mod arithmetic; pub mod hash; -pub(crate) mod merkle_proofs; pub(crate) mod split_join; diff --git a/src/hash.rs b/src/hash.rs index 9ac7b120..da7f1765 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -6,8 +6,10 @@ use rayon::prelude::*; use crate::field::field::Field; use crate::gmimc::gmimc_permute_array; -use crate::proof::Hash; +use crate::proof::{Hash, HashTarget}; use crate::util::reverse_index_bits_in_place; +use crate::circuit_builder::CircuitBuilder; +use crate::target::Target; pub(crate) const SPONGE_RATE: usize = 8; pub(crate) const SPONGE_CAPACITY: usize = 4; @@ -25,7 +27,7 @@ const ELEMS_PER_CHUNK: usize = 1 << 8; /// Hash the vector if necessary to reduce its length to ~256 bits. If it already fits, this is a /// no-op. -pub fn hash_or_noop(mut inputs: Vec) -> Hash { +pub fn hash_or_noop(inputs: Vec) -> Hash { if inputs.len() <= 4 { Hash::from_partial(inputs) } else { @@ -33,6 +35,21 @@ pub fn hash_or_noop(mut inputs: Vec) -> Hash { } } +impl CircuitBuilder { + pub fn hash_or_noop(&mut self, inputs: Vec) -> HashTarget { + let zero = self.zero(); + if inputs.len() <= 4 { + HashTarget::from_partial(inputs, zero) + } else { + self.hash_n_to_hash(inputs, false) + } + } + + pub fn hash_n_to_hash(&mut self, inputs: Vec, pad: bool) -> HashTarget { + todo!() + } +} + /// A one-way compression function which takes two ~256 bit inputs and returns a ~256 bit output. pub fn compress(x: Hash, y: Hash) -> Hash { let mut inputs = Vec::with_capacity(8); diff --git a/src/lib.rs b/src/lib.rs index 5f840e78..89762ead 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ pub mod gates; pub mod generator; pub mod gmimc; pub mod hash; +pub mod merkle_proofs; pub mod plonk_challenger; pub mod plonk_common; pub mod polynomial; diff --git a/src/gadgets/merkle_proofs.rs b/src/merkle_proofs.rs similarity index 73% rename from src/gadgets/merkle_proofs.rs rename to src/merkle_proofs.rs index 9154647b..7e1b5ecf 100644 --- a/src/gadgets/merkle_proofs.rs +++ b/src/merkle_proofs.rs @@ -53,7 +53,8 @@ impl CircuitBuilder { let height = proof.siblings.len(); let purported_index_bits = self.split_le_virtual(leaf_index, height); - let mut state: Vec = todo!(); // hash leaf data + let mut state: HashTarget = self.hash_or_noop(leaf_data); + let mut acc_leaf_index = zero; for (bit, sibling) in purported_index_bits.into_iter().zip(proof.siblings) { let gate = self.add_gate_no_constants( @@ -63,26 +64,39 @@ impl CircuitBuilder { let swap_wire = Target::Wire(Wire { gate, input: swap_wire }); self.generate_copy(bit, swap_wire); + let old_acc_wire = GMiMCGate::::WIRE_INDEX_ACCUMULATOR_OLD; + let old_acc_wire = Target::Wire(Wire { gate, input: old_acc_wire }); + self.route(acc_leaf_index, old_acc_wire); + + let new_acc_wire = GMiMCGate::::WIRE_INDEX_ACCUMULATOR_NEW; + let new_acc_wire = Target::Wire(Wire { gate, input: new_acc_wire }); + acc_leaf_index = new_acc_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(state.elements[i], input_wires[i]); self.route(sibling.elements[i], input_wires[4 + i]); self.route(zero, input_wires[8 + i]); } - state = (0..4) + state = HashTarget::from_vec((0..4) .map(|i| Target::Wire( Wire { gate, input: GMiMCGate::::wire_output(i) })) - .collect::>() - .try_into() - .unwrap(); + .collect()) } - // TODO: Verify that weighted sum of bits matches index. - // TODO: Verify that state matches merkle root. + self.assert_equal(acc_leaf_index, leaf_index); + + self.assert_hashes_equal(state, merkle_root) + } + + pub(crate) fn assert_hashes_equal(&mut self, x: HashTarget, y: HashTarget) { + for i in 0..4 { + self.assert_equal(x.elements[i], y.elements[i]); + } } } diff --git a/src/proof.rs b/src/proof.rs index 41d29f09..88513ce8 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -1,6 +1,7 @@ use crate::field::field::Field; use crate::target::Target; -use crate::gadgets::merkle_proofs::{MerkleProofTarget, MerkleProof}; +use crate::merkle_proofs::{MerkleProofTarget, MerkleProof}; +use std::convert::TryInto; /// Represents a ~256 bit hash output. #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -20,7 +21,22 @@ impl Hash { /// Represents a ~256 bit hash output. pub struct HashTarget { - pub(crate) elements: Vec, + pub(crate) elements: [Target; 4], +} + +impl HashTarget { + pub(crate) fn from_vec(elements: Vec) -> Self { + debug_assert!(elements.len() == 4); + HashTarget { elements: elements.try_into().unwrap() } + } + + pub(crate) fn from_partial(mut elements: Vec, zero: Target) -> Self { + debug_assert!(elements.len() <= 4); + while elements.len() < 4 { + elements.push(zero); + } + Self { elements: [elements[0], elements[1], elements[2], elements[3]] } + } } pub struct Proof { From b1835798867ae64f52fc8798d4881d80945acdc2 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 9 Apr 2021 12:53:33 -0700 Subject: [PATCH 4/7] Finish up recursive Merkle proofs --- src/hash.rs | 46 ++++++++++++++++++++++++++++++++++++++--- src/merkle_proofs.rs | 2 -- src/plonk_challenger.rs | 1 + src/proof.rs | 7 ++++++- 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/hash.rs b/src/hash.rs index da7f1765..bb59957d 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -46,7 +46,48 @@ impl CircuitBuilder { } pub fn hash_n_to_hash(&mut self, inputs: Vec, pad: bool) -> HashTarget { - todo!() + HashTarget::from_vec(self.hash_n_to_m(inputs, 4, pad)) + } + + pub fn hash_n_to_m( + &mut self, + mut inputs: Vec, + num_outputs: usize, + pad: bool, + ) -> Vec { + let zero = self.zero(); + let one = self.one(); + + if pad { + inputs.push(zero); + while (inputs.len() + 1) % SPONGE_WIDTH != 0 { + inputs.push(one); + } + inputs.push(zero); + } + + let mut state = [zero; SPONGE_WIDTH]; + + // Absorb all input chunks. + for input_chunk in inputs.chunks(SPONGE_WIDTH - 1) { + for i in 0..input_chunk.len() { + // TODO: These adds are wasteful. Maybe GMiMCGate should have separates wires to be added in. + state[i] = self.add(state[i], input_chunk[i]); + } + state = self.permute(state); + } + + // Squeeze until we have the desired number of outputs. + let mut outputs = Vec::new(); + loop { + for i in 0..(SPONGE_WIDTH - 1) { + outputs.push(state[i]); + if outputs.len() == num_outputs { + return outputs; + } + } + state = self.permute(state); + } } } @@ -98,8 +139,7 @@ pub fn hash_n_to_m(mut inputs: Vec, num_outputs: usize, pad: bool) } pub fn hash_n_to_hash(inputs: Vec, pad: bool) -> Hash { - let elements = hash_n_to_m(inputs, 4, pad).try_into().unwrap(); - Hash { elements } + Hash::from_vec(hash_n_to_m(inputs, 4, pad)) } pub fn hash_n_to_1(inputs: Vec, pad: bool) -> F { diff --git a/src/merkle_proofs.rs b/src/merkle_proofs.rs index 7e1b5ecf..5f735e35 100644 --- a/src/merkle_proofs.rs +++ b/src/merkle_proofs.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use crate::circuit_builder::CircuitBuilder; use crate::field::field::Field; use crate::gates::gmimc::GMiMCGate; diff --git a/src/plonk_challenger.rs b/src/plonk_challenger.rs index 09276105..4a5a99f1 100644 --- a/src/plonk_challenger.rs +++ b/src/plonk_challenger.rs @@ -179,6 +179,7 @@ impl RecursiveChallenger { for input_chunk in self.input_buffer.chunks(SPONGE_RATE) { // Add the inputs to our sponge state. for (i, &input) in input_chunk.iter().enumerate() { + // TODO: These adds are wasteful. Maybe GMiMCGate should have separates wires to be added in. self.sponge_state[i] = builder.add(self.sponge_state[i], input); } diff --git a/src/proof.rs b/src/proof.rs index 88513ce8..a77d1683 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -10,6 +10,11 @@ pub struct Hash { } impl Hash { + pub(crate) fn from_vec(elements: Vec) -> Self { + debug_assert!(elements.len() == 4); + Self { elements: elements.try_into().unwrap() } + } + pub(crate) fn from_partial(mut elements: Vec) -> Self { debug_assert!(elements.len() <= 4); while elements.len() < 4 { @@ -27,7 +32,7 @@ pub struct HashTarget { impl HashTarget { pub(crate) fn from_vec(elements: Vec) -> Self { debug_assert!(elements.len() == 4); - HashTarget { elements: elements.try_into().unwrap() } + Self { elements: elements.try_into().unwrap() } } pub(crate) fn from_partial(mut elements: Vec, zero: Target) -> Self { From a14ddc3b03478bc1e83bd830d5ac5897a08fef29 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sat, 10 Apr 2021 14:54:46 -0700 Subject: [PATCH 5/7] Fix constraint count --- src/gates/gmimc.rs | 2 +- src/plonk_common.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gates/gmimc.rs b/src/gates/gmimc.rs index 63cf8918..dad8b078 100644 --- a/src/gates/gmimc.rs +++ b/src/gates/gmimc.rs @@ -149,7 +149,7 @@ impl Gate for GMiMCGate { } fn num_constraints(&self) -> usize { - R + W + R + W + 2 } } diff --git a/src/plonk_common.rs b/src/plonk_common.rs index c1965995..1c2deb02 100644 --- a/src/plonk_common.rs +++ b/src/plonk_common.rs @@ -18,6 +18,8 @@ pub fn evaluate_gate_constraints( for gate in gates { let gate_constraints = gate.0.eval_filtered(vars); for (i, c) in gate_constraints.into_iter().enumerate() { + debug_assert!(i < num_gate_constraints, + "num_constraints() gave too low of a number"); constraints[i] += c; } } From 7d9bb073f4ac893553c207c8f3c502b2c28a5cd4 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sat, 10 Apr 2021 21:32:11 -0700 Subject: [PATCH 6/7] Switch to "overwrite mode" sponges And fix a bug where the rate was assumed to be width - 1 (which was true in plonky 1). --- src/hash.rs | 14 ++++++++------ src/plonk_challenger.rs | 13 ++++++++----- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/hash.rs b/src/hash.rs index bb59957d..e90cdb0a 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -69,10 +69,12 @@ impl CircuitBuilder { let mut state = [zero; SPONGE_WIDTH]; // Absorb all input chunks. - for input_chunk in inputs.chunks(SPONGE_WIDTH - 1) { + for input_chunk in inputs.chunks(SPONGE_RATE) { + // Overwrite the first r elements with the inputs. This differs from a standard sponge, + // where we would xor or add in the inputs. This is a well-known variant, though, + // sometimes called "overwrite mode". for i in 0..input_chunk.len() { - // TODO: These adds are wasteful. Maybe GMiMCGate should have separates wires to be added in. - state[i] = self.add(state[i], input_chunk[i]); + state[i] = input_chunk[i]; } state = self.permute(state); } @@ -80,7 +82,7 @@ impl CircuitBuilder { // Squeeze until we have the desired number of outputs. let mut outputs = Vec::new(); loop { - for i in 0..(SPONGE_WIDTH - 1) { + for i in 0..SPONGE_RATE { outputs.push(state[i]); if outputs.len() == num_outputs { return outputs; @@ -118,7 +120,7 @@ pub fn hash_n_to_m(mut inputs: Vec, num_outputs: usize, pad: bool) let mut state = [F::ZERO; SPONGE_WIDTH]; // Absorb all input chunks. - for input_chunk in inputs.chunks(SPONGE_WIDTH - 1) { + for input_chunk in inputs.chunks(SPONGE_RATE) { for i in 0..input_chunk.len() { state[i] += input_chunk[i]; } @@ -128,7 +130,7 @@ pub fn hash_n_to_m(mut inputs: Vec, num_outputs: usize, pad: bool) // Squeeze until we have the desired number of outputs. let mut outputs = Vec::new(); loop { - for i in 0..(SPONGE_WIDTH - 1) { + for i in 0..SPONGE_RATE { outputs.push(state[i]); if outputs.len() == num_outputs { return outputs; diff --git a/src/plonk_challenger.rs b/src/plonk_challenger.rs index 4a5a99f1..b745150d 100644 --- a/src/plonk_challenger.rs +++ b/src/plonk_challenger.rs @@ -79,9 +79,11 @@ impl Challenger { /// Absorb any buffered inputs. After calling this, the input buffer will be empty. fn absorb_buffered_inputs(&mut self) { for input_chunk in self.input_buffer.chunks(SPONGE_RATE) { - // Add the inputs to our sponge state. + // Overwrite the first r elements with the inputs. This differs from a standard sponge, + // where we would xor or add in the inputs. This is a well-known variant, though, + // sometimes called "overwrite mode". for (i, &input) in input_chunk.iter().enumerate() { - self.sponge_state[i] = self.sponge_state[i] + input; + self.sponge_state[i] = input; } // Apply the permutation. @@ -177,10 +179,11 @@ impl RecursiveChallenger { builder: &mut CircuitBuilder, ) { for input_chunk in self.input_buffer.chunks(SPONGE_RATE) { - // Add the inputs to our sponge state. + // Overwrite the first r elements with the inputs. This differs from a standard sponge, + // where we would xor or add in the inputs. This is a well-known variant, though, + // sometimes called "overwrite mode". for (i, &input) in input_chunk.iter().enumerate() { - // TODO: These adds are wasteful. Maybe GMiMCGate should have separates wires to be added in. - self.sponge_state[i] = builder.add(self.sponge_state[i], input); + self.sponge_state[i] = input; } // Apply the permutation. From 30b845e6b30b3fe83ebbd85286a85794c3305d12 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 12 Apr 2021 10:18:16 -0700 Subject: [PATCH 7/7] Add generator to circuit --- src/gadgets/split_join.rs | 4 +++- src/hash.rs | 6 ++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gadgets/split_join.rs b/src/gadgets/split_join.rs index 21821706..db9d2aab 100644 --- a/src/gadgets/split_join.rs +++ b/src/gadgets/split_join.rs @@ -18,12 +18,13 @@ impl CircuitBuilder { num_bits: usize, ) -> Vec { let bit_targets = self.add_virtual_advice_targets(num_bits); - split_le_generator::(integer, bit_targets.clone()); + self.add_generator(SplitGenerator { integer, bits: bit_targets.clone() }); bit_targets } } /// Generator for a little-endian split. +#[must_use] pub fn split_le_generator( integer: Target, bits: Vec, @@ -32,6 +33,7 @@ pub fn split_le_generator( } /// Generator for a little-endian split. +#[must_use] pub fn split_le_generator_local_wires( gate: usize, integer_input_index: usize, diff --git a/src/hash.rs b/src/hash.rs index e90cdb0a..4cf30126 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -1,15 +1,13 @@ //! Concrete instantiation of a hash function. -use std::convert::TryInto; - use rayon::prelude::*; +use crate::circuit_builder::CircuitBuilder; use crate::field::field::Field; use crate::gmimc::gmimc_permute_array; use crate::proof::{Hash, HashTarget}; -use crate::util::reverse_index_bits_in_place; -use crate::circuit_builder::CircuitBuilder; use crate::target::Target; +use crate::util::reverse_index_bits_in_place; pub(crate) const SPONGE_RATE: usize = 8; pub(crate) const SPONGE_CAPACITY: usize = 4;