diff --git a/Cargo.toml b/Cargo.toml index 0f2269ca..f82cc1de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ authors = ["Daniel Lubarov "] readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/mir-protocol/plonky2" -keywords = ["cryptography", "SNARK"] +keywords = ["cryptography", "SNARK", "FRI"] categories = ["cryptography"] edition = "2018" default-run = "bench_recursion" diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 00000000..57bc88a1 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 00000000..6220a485 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2021 Predicate Labs Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md index f33850e4..e844ca77 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,25 @@ # plonky2 TODO: Write a readme... + + +## Disclaimer + +This code has not been thoroughly reviewed or tested, and should not be used in any production systems. + + +## License + +Licensed under either of + + * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. 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/gadgets/hash.rs b/src/gadgets/hash.rs index 8d0357a6..20b26244 100644 --- a/src/gadgets/hash.rs +++ b/src/gadgets/hash.rs @@ -3,37 +3,39 @@ 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; +// TODO: Move to be next to native `permute`? 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); + // 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_output(i); + let in_wire = GMiMCGate::::wire_input(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()); - + // Collect output wires. (0..12) .map(|i| Target::Wire( - Wire { gate: next_gate, input: GMiMCGate::::wire_output(i) })) + Wire { gate, input: GMiMCGate::::wire_output(i) })) .collect::>() .try_into() .unwrap() diff --git a/src/gadgets/mod.rs b/src/gadgets/mod.rs index 8407efe7..ed84207e 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 mod arithmetic; +pub mod hash; 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..dad8b078 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; @@ -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; + // 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]; + 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 @@ -133,7 +149,7 @@ impl Gate for GMiMCGate { } fn num_constraints(&self) -> usize { - R + W + R + W + 2 } } @@ -145,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() } @@ -163,17 +183,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 +229,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 +272,12 @@ 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_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) }, @@ -255,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/hash.rs b/src/hash.rs index 9ac7b120..e90cdb0a 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,64 @@ 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 { + 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_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() { + 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_RATE { + outputs.push(state[i]); + if outputs.len() == num_outputs { + return outputs; + } + } + state = self.permute(state); + } + } +} + /// 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); @@ -60,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]; } @@ -70,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; @@ -81,8 +141,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/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/merkle_proofs.rs b/src/merkle_proofs.rs new file mode 100644 index 00000000..5f735e35 --- /dev/null +++ b/src/merkle_proofs.rs @@ -0,0 +1,100 @@ +use crate::circuit_builder::CircuitBuilder; +use crate::field::field::Field; +use crate::gates::gmimc::GMiMCGate; +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. + pub siblings: Vec>, +} + +pub struct MerkleProofTarget { + /// The Merkle digest of each sibling subtree, staying from the bottommost layer. + pub siblings: Vec, +} + +/// 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( + leaf_data: Vec, + leaf_index: usize, + merkle_root: Hash, + proof: MerkleProof, +) -> 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 { + /// 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( + &mut self, + leaf_data: Vec, + leaf_index: Target, + merkle_root: HashTarget, + proof: MerkleProofTarget, + ) { + let zero = self.zero(); + let height = proof.siblings.len(); + let purported_index_bits = self.split_le_virtual(leaf_index, height); + + 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( + 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 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.elements[i], input_wires[i]); + self.route(sibling.elements[i], input_wires[4 + i]); + self.route(zero, input_wires[8 + i]); + } + + state = HashTarget::from_vec((0..4) + .map(|i| Target::Wire( + Wire { gate, input: GMiMCGate::::wire_output(i) })) + .collect()) + } + + 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/plonk_challenger.rs b/src/plonk_challenger.rs index 8dbd2813..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,9 +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() { - self.sponge_state[i] = builder.add(self.sponge_state[i], input); + self.sponge_state[i] = input; } // Apply the permutation. @@ -228,7 +232,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); 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; } }