├── noir_example ├── Prover.toml ├── src │ └── main.nr └── Nargo.toml ├── plonky2-backend ├── example_programs │ ├── fibonacci │ │ ├── Prover.toml │ │ ├── Nargo.toml │ │ └── src │ │ │ └── main.nr │ ├── basic_assert_zero │ │ ├── Prover.toml │ │ ├── src │ │ │ └── main.nr │ │ └── Nargo.toml │ ├── basic_div │ │ ├── Prover.toml │ │ ├── src │ │ │ └── main.nr │ │ └── Nargo.toml │ └── basic_if │ │ ├── Prover.toml │ │ ├── Nargo.toml │ │ └── src │ │ └── main.nr ├── src │ ├── plonky2_ecdsa │ │ ├── curve │ │ │ ├── mod.rs │ │ │ └── gadgets │ │ │ │ ├── mod.rs │ │ │ │ └── glv.rs │ │ ├── mod.rs │ │ ├── biguint │ │ │ ├── mod.rs │ │ │ ├── gadgets │ │ │ │ ├── mod.rs │ │ │ │ ├── range_check_u32.rs │ │ │ │ ├── split_nonnative.rs │ │ │ │ └── multiple_comparison.rs │ │ │ ├── gates │ │ │ │ ├── mod.rs │ │ │ │ ├── gate_testing.rs │ │ │ │ └── range_check_u32.rs │ │ │ ├── serialization.rs │ │ │ └── witness.rs │ │ └── modifications.md │ ├── circuit_translation │ │ ├── tests │ │ │ ├── factories │ │ │ │ ├── noir_circuits_for_testing │ │ │ │ │ ├── sha256_long │ │ │ │ │ │ ├── Prover.toml │ │ │ │ │ │ ├── Nargo.toml │ │ │ │ │ │ └── src │ │ │ │ │ │ │ └── main.nr │ │ │ │ │ ├── assert_x_equals_5 │ │ │ │ │ │ ├── Prover.toml │ │ │ │ │ │ ├── src │ │ │ │ │ │ │ └── main.nr │ │ │ │ │ │ └── Nargo.toml │ │ │ │ │ ├── 3_add │ │ │ │ │ │ ├── Prover.toml │ │ │ │ │ │ ├── Nargo.toml │ │ │ │ │ │ └── src │ │ │ │ │ │ │ └── main.nr │ │ │ │ │ ├── 1_mul │ │ │ │ │ │ ├── Prover.toml │ │ │ │ │ │ ├── Nargo.toml │ │ │ │ │ │ └── src │ │ │ │ │ │ │ └── main.nr │ │ │ │ │ ├── 5_over │ │ │ │ │ │ ├── Prover.toml │ │ │ │ │ │ ├── Nargo.toml │ │ │ │ │ │ └── src │ │ │ │ │ │ │ └── main.nr │ │ │ │ │ ├── sha256_4 │ │ │ │ │ │ ├── Prover.toml │ │ │ │ │ │ ├── src │ │ │ │ │ │ │ └── main.nr │ │ │ │ │ │ └── Nargo.toml │ │ │ │ │ ├── basic_memory_write │ │ │ │ │ │ ├── Prover.toml │ │ │ │ │ │ ├── Nargo.toml │ │ │ │ │ │ └── src │ │ │ │ │ │ │ └── main.nr │ │ │ │ │ ├── 7_function │ │ │ │ │ │ ├── Nargo.toml │ │ │ │ │ │ ├── Prover.toml │ │ │ │ │ │ └── src │ │ │ │ │ │ │ └── main.nr │ │ │ │ │ ├── array_dynamic │ │ │ │ │ │ ├── Nargo.toml │ │ │ │ │ │ ├── Prover.toml │ │ │ │ │ │ └── src │ │ │ │ │ │ │ └── main.nr │ │ │ │ │ ├── ecdsa_secp256k1 │ │ │ │ │ │ ├── Nargo.toml │ │ │ │ │ │ ├── src │ │ │ │ │ │ │ └── main.nr │ │ │ │ │ │ └── Prover.toml │ │ │ │ │ └── node_guardians_example │ │ │ │ │ │ ├── Nargo.toml │ │ │ │ │ │ ├── Prover.toml │ │ │ │ │ │ └── src │ │ │ │ │ │ └── main.nr │ │ │ │ ├── mod.rs │ │ │ │ ├── circuit_parser.rs │ │ │ │ ├── utils.rs │ │ │ │ └── circuit_factory.rs │ │ │ ├── mod.rs │ │ │ ├── test_precompiled.rs │ │ │ ├── test_blackbox.rs │ │ │ └── test_assert_zero.rs │ │ ├── assert_zero_translator.rs │ │ ├── ecdsa_secp256k1_translator.rs │ │ ├── memory_translator.rs │ │ ├── sha256_translator.rs │ │ └── mod.rs │ ├── actions │ │ ├── mod.rs │ │ ├── verify_action.rs │ │ ├── write_vk_action.rs │ │ └── prove_action.rs │ ├── main.rs │ ├── lib.rs │ ├── noir_and_plonky2_serialization.rs │ ├── binary_digits_target.rs │ └── argument_parsing.rs ├── Cargo.toml └── run_examples.py ├── .gitignore ├── docs ├── foreword.md ├── opcodes │ ├── foreword.md │ ├── brilligCall.md │ ├── blackBoxFunctions.md │ ├── assertZero.md │ └── memoryOperations.md ├── SUMMARY.md └── general │ ├── equivalence_witness_target.md │ └── general_workflow.md ├── book.toml ├── LICENSE ├── Dockerfile ├── .github └── workflows │ └── mdbook.yml ├── prepare_compiled_noir_test_programs.py ├── Makefile └── README.md /noir_example/Prover.toml: -------------------------------------------------------------------------------- 1 | x = 1 2 | y = 1 -------------------------------------------------------------------------------- /plonky2-backend/example_programs/fibonacci/Prover.toml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plonky2-backend/src/plonky2_ecdsa/curve/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod gadgets; 2 | -------------------------------------------------------------------------------- /plonky2-backend/example_programs/basic_assert_zero/Prover.toml: -------------------------------------------------------------------------------- 1 | x = 0 2 | -------------------------------------------------------------------------------- /plonky2-backend/example_programs/basic_div/Prover.toml: -------------------------------------------------------------------------------- 1 | x = 2 2 | y = 1 3 | -------------------------------------------------------------------------------- /plonky2-backend/src/plonky2_ecdsa/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod curve; 2 | pub mod biguint; 3 | -------------------------------------------------------------------------------- /plonky2-backend/example_programs/basic_if/Prover.toml: -------------------------------------------------------------------------------- 1 | a = 4 2 | b = 2 3 | cond = 1 4 | -------------------------------------------------------------------------------- /noir_example/src/main.nr: -------------------------------------------------------------------------------- 1 | fn main(x: Field, y: pub Field) { 2 | assert(x==y); 3 | } 4 | -------------------------------------------------------------------------------- /plonky2-backend/src/plonky2_ecdsa/curve/gadgets/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod curve; 2 | pub mod glv; 3 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/sha256_long/Prover.toml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | plonky2-backend/target/ 2 | .idea 3 | noir_example/target/ 4 | noir/ 5 | notes/ 6 | plonky2/ 7 | 8 | -------------------------------------------------------------------------------- /noir_example/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "noir_example" 3 | type = "bin" 4 | authors = [""] 5 | [dependencies] 6 | -------------------------------------------------------------------------------- /plonky2-backend/example_programs/basic_assert_zero/src/main.nr: -------------------------------------------------------------------------------- 1 | fn main(x: Field) { 2 | assert(x == 0); 3 | } 4 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/assert_x_equals_5/Prover.toml: -------------------------------------------------------------------------------- 1 | x = 5 2 | -------------------------------------------------------------------------------- /plonky2-backend/example_programs/basic_div/src/main.nr: -------------------------------------------------------------------------------- 1 | fn main(x: Field, y: pub Field) -> pub Field{ 2 | y/x 3 | } 4 | 5 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/3_add/Prover.toml: -------------------------------------------------------------------------------- 1 | x = "3" 2 | y = "4" 3 | z = "7" -------------------------------------------------------------------------------- /plonky2-backend/src/actions/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub mod prove_action; 4 | pub mod verify_action; 5 | pub mod write_vk_action; 6 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/1_mul/Prover.toml: -------------------------------------------------------------------------------- 1 | x = "3" 2 | y = "4" 3 | z = "429981696" 4 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/5_over/Prover.toml: -------------------------------------------------------------------------------- 1 | x = "43046721" 2 | y = "3793632897" 3 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/sha256_4/Prover.toml: -------------------------------------------------------------------------------- 1 | hash_input = [104, 111, 108, 97] 2 | -------------------------------------------------------------------------------- /plonky2-backend/src/main.rs: -------------------------------------------------------------------------------- 1 | use plonky2_backend; 2 | 3 | fn main() { 4 | plonky2_backend::argument_parsing::parse_and_execute_commands(); 5 | } 6 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/basic_memory_write/Prover.toml: -------------------------------------------------------------------------------- 1 | x = [10, 11] 2 | y = 0 3 | v = 1 4 | 5 | -------------------------------------------------------------------------------- /plonky2-backend/src/plonky2_ecdsa/biguint/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod biguint; 2 | pub mod gadgets; 3 | pub mod gates; 4 | pub mod serialization; 5 | pub mod witness; 6 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub mod circuit_factory; 4 | pub mod circuit_parser; 5 | pub mod utils; 6 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/assert_x_equals_5/src/main.nr: -------------------------------------------------------------------------------- 1 | fn main(x: Field) { 2 | assert(x == 5); 3 | } -------------------------------------------------------------------------------- /plonky2-backend/example_programs/basic_if/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "basic_if" 3 | type = "bin" 4 | authors = [""] 5 | compiler_version = ">=0.30.0" 6 | 7 | [dependencies] -------------------------------------------------------------------------------- /plonky2-backend/example_programs/basic_div/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "basic_div" 3 | type = "bin" 4 | authors = [""] 5 | compiler_version = ">=0.30.0" 6 | 7 | [dependencies] -------------------------------------------------------------------------------- /plonky2-backend/example_programs/basic_if/src/main.nr: -------------------------------------------------------------------------------- 1 | fn main(a: Field, b: Field, cond: bool) -> pub Field{ 2 | if cond { 3 | a 4 | } else{ 5 | b 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /plonky2-backend/example_programs/fibonacci/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fibonacci" 3 | type = "bin" 4 | authors = [""] 5 | compiler_version = ">=0.30.0" 6 | 7 | [dependencies] -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/3_add/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "3_add" 3 | type = "bin" 4 | authors = [""] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/5_over/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "5_over" 3 | type = "bin" 4 | authors = [""] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /plonky2-backend/example_programs/basic_assert_zero/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "basic_assert_zero" 3 | type = "bin" 4 | authors = [""] 5 | compiler_version = ">=0.31.0" 6 | 7 | [dependencies] -------------------------------------------------------------------------------- /plonky2-backend/src/plonky2_ecdsa/biguint/gadgets/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod arithmetic_u32; 2 | pub mod multiple_comparison; 3 | pub mod nonnative; 4 | pub mod range_check_u32; 5 | pub mod split_nonnative; 6 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/7_function/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "7_function" 3 | type = "bin" 4 | authors = [""] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/array_dynamic/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "array_dynamic" 3 | type = "bin" 4 | authors = [""] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/sha256_4/src/main.nr: -------------------------------------------------------------------------------- 1 | fn main(hash_input: pub [u8; 4]) -> pub [u8; 32]{ 2 | std::sha256::sha256_var(hash_input, 4) 3 | } -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/1_mul/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "1_mul" 3 | version = "0.1.0" 4 | type = "bin" 5 | authors = [""] 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /plonky2-backend/src/plonky2_ecdsa/biguint/gates/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod add_many_u32; 2 | pub mod arithmetic_u32; 3 | pub mod comparison; 4 | pub mod range_check_u32; 5 | pub mod subtraction_u32; 6 | 7 | pub mod gate_testing; 8 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/sha256_4/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sha256_4" 3 | type = "bin" 4 | authors = [""] 5 | compiler_version = ">=0.31.0" 6 | 7 | [dependencies] -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/sha256_long/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sha256_long" 3 | type = "bin" 4 | authors = [""] 5 | compiler_version = ">=0.31.0" 6 | 7 | [dependencies] -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/ecdsa_secp256k1/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ecdsa_secp256k1" 3 | type = "bin" 4 | authors = [""] 5 | compiler_version = ">=0.31.0" 6 | 7 | [dependencies] -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/assert_x_equals_5/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "assert_x_equals_5" 3 | type = "bin" 4 | authors = [""] 5 | compiler_version = ">=0.31.0" 6 | 7 | [dependencies] -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/basic_memory_write/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "basic_memory_write" 3 | type = "bin" 4 | authors = [""] 5 | compiler_version = ">=0.31.0" 6 | 7 | [dependencies] -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/basic_memory_write/src/main.nr: -------------------------------------------------------------------------------- 1 | fn main(mut x: pub [Field; 2], y: pub Field, v: pub Field){ 2 | x[y] = v; 3 | assert(x[0] == 1); 4 | assert(x[1] == 11); 5 | } -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub mod factories; 4 | 5 | mod test_assert_zero; 6 | mod test_blackbox; 7 | mod test_memory_operations; 8 | mod test_precompiled; 9 | mod test_sha256_internal; 10 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/node_guardians_example/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "node_guardians_example" 3 | type = "bin" 4 | authors = [""] 5 | compiler_version = ">=0.31.0" 6 | 7 | [dependencies] -------------------------------------------------------------------------------- /plonky2-backend/example_programs/fibonacci/src/main.nr: -------------------------------------------------------------------------------- 1 | fn main() -> pub Field{ 2 | let mut a = 0; 3 | let mut b = 1; 4 | for _ in 0..13 { 5 | let aux = a + b; 6 | a = b; 7 | b = aux; 8 | } 9 | b 10 | } 11 | -------------------------------------------------------------------------------- /docs/foreword.md: -------------------------------------------------------------------------------- 1 | # Foreword 2 | These docs aim to give a brief overview of what this repo is for, what it solves, and how it does so. For a deeper understanding, we recommend reading the source code: we hope it is declarative enough and that the comments are helpful. -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/array_dynamic/Prover.toml: -------------------------------------------------------------------------------- 1 | x = [104, 101, 108, 108, 111] 2 | z = "59" 3 | t = "10" 4 | index = [0,1,2,3,4] 5 | index2 = [0,1,2,3,4] 6 | offset = 1 7 | sublen = 2 8 | 9 | 10 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/3_add/src/main.nr: -------------------------------------------------------------------------------- 1 | // Test integer addition: 3 + 4 = 7 2 | fn main(mut x: u32, y: u32, z: u32) { 3 | x += y; 4 | assert(x == z); 5 | 6 | x *= 8; 7 | assert(x > 9); 8 | } 9 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/7_function/Prover.toml: -------------------------------------------------------------------------------- 1 | x = "59" 2 | y = "5" 3 | a = "1" 4 | 5 | arr1=[3320379920, 1938147428, 1942509796, 1795943184, 24853, 0, 0, 0, 0] 6 | arr2=[2912727897, 3590519536, 1687587470, 3896107618, 1092831095, 0, 0, 0, 0] -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/5_over/src/main.nr: -------------------------------------------------------------------------------- 1 | // Test unsafe integer arithmetic 2 | // Test odd bits integer 3 | fn main(mut x: u32, y: u32) { 4 | x = std::wrapping_mul(x,x); 5 | assert(y == x); 6 | 7 | let c: u1 = 0; 8 | assert(x as u1 > c); 9 | } 10 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/ecdsa_secp256k1/src/main.nr: -------------------------------------------------------------------------------- 1 | fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], signature : [u8;64]) { 2 | let valid_signature = std::ecdsa_secp256k1::verify_signature(pub_key_x, pub_key_y, signature, hashed_message); 3 | assert(valid_signature); 4 | } -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/1_mul/src/main.nr: -------------------------------------------------------------------------------- 1 | // Test unsafe integer multiplication with overflow: 12^8 = 429 981 696 2 | // The circuit should handle properly the growth of the bit size 3 | fn main(mut x: u32, y: u32, z: u32) { 4 | x *= y; 5 | x *= x; //144 6 | x *= x; //20736 7 | x *= x; //429 981 696 8 | assert(x == z); 9 | } 10 | -------------------------------------------------------------------------------- /docs/opcodes/foreword.md: -------------------------------------------------------------------------------- 1 | ## Opcodes 2 | Opcodes are the basic elements that make the ACIR abstraction. An Abstract Circuit Intermediate Representation (ACIR) is a set of opcodes of different kinds (AssertZero, MemoryInit, MemoryOp, BlackBoxFunction, BrilligCall). These opcodes take different forms and talk about witnesses (inmutable variables in the circuit) and their main function is to establish restrictions over these witnesses. -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [General Ideas of this repo](./foreword.md) 4 | - [Proving workflow](./general/general_workflow.md) 5 | - [Witness and Target](./general/equivalence_witness_target.md) 6 | - [Opcodes](./opcodes/foreword.md) 7 | - [AssertZero](./opcodes/assertZero.md) 8 | - [Memory Operations](./opcodes/memoryOperations.md) 9 | - [Brillig Call](./opcodes/brilligCall.md) 10 | - [BlackBox Functions](./opcodes/blackBoxFunctions.md) -------------------------------------------------------------------------------- /plonky2-backend/src/plonky2_ecdsa/modifications.md: -------------------------------------------------------------------------------- 1 | - We removed usages of class ByteTarget, because we implemented the same class (BinaryDigitsTarget) but with a variable number of BoolTargets. 2 | - We added convenience functions to trait CircuitBuilderU32 to operate with BinaryDigitsTarget instances: 3 | - fn constant_byte(&mut self, byte: u8) -> BinaryDigitsTarget; 4 | - fn connect_byte(&mut self, x: BinaryDigitsTarget, y: BinaryDigitsTarget); 5 | - fn connect_bit(&mut self, x: BoolTarget, y: BoolTarget); -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Eryx Coop"] 3 | language = "en" 4 | multilingual = false 5 | src = "docs" 6 | title = "Docs" 7 | 8 | [preprocessor.katex] 9 | after = ["links"] 10 | # KaTeX options. 11 | output = "html" 12 | leqno = false 13 | fleqn = false 14 | throw-on-error = true 15 | error-color = "#cc0000" 16 | min-rule-thickness = -1.0 17 | max-size = "Infinity" 18 | max-expand = 1000 19 | trust = false 20 | # Extra options. 21 | no-css = false 22 | include-src = false 23 | block-delimiter = { left = "$$", right = "$$" } 24 | inline-delimiter = { left = "$", right = "$" } -------------------------------------------------------------------------------- /plonky2-backend/src/actions/verify_action.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// The verify action will receive a proof path and a verification key path, and verify the program 4 | /// execution using those values. 5 | pub struct VerifyAction { 6 | pub proof_path: String, 7 | pub vk_path: String, 8 | } 9 | 10 | impl VerifyAction { 11 | pub fn run(&self) { 12 | let verifier_data = deserialize_verifying_key_within_file_path(&self.vk_path); 13 | let compressed_proof = deserialize_proof_within_file_path(&self.proof_path, &verifier_data); 14 | verifier_data 15 | .verify_compressed(compressed_proof) 16 | .expect("Verification failed"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /plonky2-backend/src/plonky2_ecdsa/biguint/serialization.rs: -------------------------------------------------------------------------------- 1 | use crate::plonky2_ecdsa::biguint::gadgets::arithmetic_u32::U32Target; 2 | use plonky2::util::serialization::{Buffer, IoResult, Read, Write}; 3 | 4 | pub trait WriteU32 { 5 | fn write_target_u32(&mut self, x: U32Target) -> IoResult<()>; 6 | } 7 | 8 | impl WriteU32 for Vec { 9 | #[inline] 10 | fn write_target_u32(&mut self, x: U32Target) -> IoResult<()> { 11 | self.write_target(x.0) 12 | } 13 | } 14 | 15 | pub trait ReadU32 { 16 | fn read_target_u32(&mut self) -> IoResult; 17 | } 18 | 19 | impl ReadU32 for Buffer<'_> { 20 | #[inline] 21 | fn read_target_u32(&mut self) -> IoResult { 22 | Ok(U32Target(self.read_target()?)) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /plonky2-backend/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate core; 2 | 3 | use jemallocator::Jemalloc; 4 | 5 | use circuit_translation::*; 6 | use noir_and_plonky2_serialization::*; 7 | use plonky2::plonk::circuit_data::VerifierCircuitData; 8 | use plonky2::plonk::config::{GenericConfig, KeccakGoldilocksConfig}; 9 | use plonky2::plonk::proof::CompressedProofWithPublicInputs; 10 | 11 | const D: usize = 2; 12 | 13 | pub type C = KeccakGoldilocksConfig; 14 | pub(crate) type F = >::F; 15 | 16 | pub mod actions; 17 | pub mod argument_parsing; 18 | pub mod circuit_translation; 19 | pub mod noir_and_plonky2_serialization; 20 | pub mod plonky2_ecdsa; 21 | pub mod binary_digits_target; 22 | 23 | #[global_allocator] // This is a plonky2 recommendation 24 | static GLOBAL: Jemalloc = Jemalloc; 25 | -------------------------------------------------------------------------------- /plonky2-backend/src/plonky2_ecdsa/biguint/gadgets/range_check_u32.rs: -------------------------------------------------------------------------------- 1 | use plonky2::field::extension::Extendable; 2 | use plonky2::hash::hash_types::RichField; 3 | use plonky2::iop::target::Target; 4 | use plonky2::plonk::circuit_builder::CircuitBuilder; 5 | 6 | use crate::plonky2_ecdsa::biguint::gadgets::arithmetic_u32::U32Target; 7 | use crate::plonky2_ecdsa::biguint::gates::range_check_u32::U32RangeCheckGate; 8 | 9 | pub fn range_check_u32_circuit, const D: usize>( 10 | builder: &mut CircuitBuilder, 11 | vals: Vec, 12 | ) { 13 | let num_input_limbs = vals.len(); 14 | let gate = U32RangeCheckGate::::new(num_input_limbs); 15 | let row = builder.add_gate(gate, vec![]); 16 | 17 | for i in 0..num_input_limbs { 18 | builder.connect(Target::wire(row, gate.wire_ith_input_limb(i)), vals[i].0); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/node_guardians_example/Prover.toml: -------------------------------------------------------------------------------- 1 | watcher_map = [ 2 | ["0", "0", "0", "0", "0", "0", "0", "0"], 3 | ["0", "0", "0", "0", "0", "0", "0", "0"], 4 | ["0", "0", "0", "0", "0", "0", "0", "0"], 5 | ["0", "0", "0", "0", "0", "0", "0", "0"], 6 | ["0", "0", "0", "0", "0", "0", "0", "0"], 7 | ["0", "0", "0", "1", "0", "0", "0", "0"], 8 | ["0", "0", "0", "0", "0", "0", "0", "0"], 9 | ["0", "1", "0", "0", "1", "1", "0", "0"] 10 | ] 11 | 12 | [dagger] 13 | x = "5" 14 | y = "4" 15 | 16 | [[path]] 17 | x = 0 18 | y = 0 19 | 20 | [[path]] 21 | x = 2 22 | y = 1 23 | 24 | [[path]] 25 | x = 1 26 | y = 3 27 | 28 | [[path]] 29 | x = 3 30 | y = 2 31 | 32 | [[path]] 33 | x = 1 34 | y = 3 35 | 36 | [[path]] 37 | x = 2 38 | y = 5 39 | 40 | [[path]] 41 | x = 3 42 | y = 3 43 | 44 | [[path]] 45 | x = 5 46 | y = 4 -------------------------------------------------------------------------------- /plonky2-backend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plonky2-backend" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "plonky2_backend" 8 | 9 | [dependencies] 10 | serde = { version = "1.0", features = ["derive"] } 11 | serde_json = "1.0" 12 | acir = "0.47.0" 13 | acir_field = "0.47.0" 14 | plonky2 = { version = "0.2.2", default-features = false, features = ["std", "parallel"]} 15 | num-bigint = "0.4" 16 | jemallocator = "0.5.0" 17 | sha2 = "0.10.8" 18 | base64 = "0.22.1" 19 | flate2 = "1.0.30" 20 | tar = "0.4.41" 21 | parameterized = "2.0.0" 22 | clap = { version = "4.5.9", features = ["derive", "cargo"] } 23 | criterion = "0.3" 24 | num = "0.4.3" 25 | itertools = "0.10.5" 26 | anyhow = "1.0.83" 27 | rand = "0.8.5" 28 | 29 | [patch.crates-io] 30 | acir = { path = "../noir/acvm-repo/acir"} 31 | acir_field = { path = "../noir/acvm-repo/acir_field"} 32 | plonky2 = { path = "../plonky2/plonky2"} 33 | -------------------------------------------------------------------------------- /docs/opcodes/brilligCall.md: -------------------------------------------------------------------------------- 1 | ### BrilligCall Opcode 2 | In Noir there are explicit unconstrained functions like the ones we define with the ```unconstrained``` keyword. This means that the return values of this functions will not be constrained, they will be like clues to the circuit provided through the Witness values. There are also implicit brillig calls like when we try to find the inverse of a field element. We can ignore these opcodes because their only purpose is to notify that some calculations were made that will not be constrained on the same way they were performed. The implicit brillig calls usually come with some assertZero opcodes that assert that the RESULT of the calculation is correct, like when we make a division. 3 | 4 | It's important to remark that the "clues" provided in the witness as a result of a BrilligCall should be passed over to the prover (just like we do with the input values) since there is no way the Plonky2 execution can figure out these values on its own. -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/array_dynamic/src/main.nr: -------------------------------------------------------------------------------- 1 | fn main( 2 | x: [u32; 5], 3 | mut z: u32, 4 | t: u32, 5 | index: [Field; 5], 6 | index2: [Field; 5], 7 | offset: Field, 8 | sublen: Field 9 | ) { 10 | let idx = (z - 5 * t - 5) as Field; 11 | //dynamic array test 12 | dyn_array(x, idx, idx - 3); 13 | //regression for issue 1283 14 | let mut s = 0; 15 | let x3 = [246, 159, 32, 176, 8]; 16 | for i in 0..5 { 17 | s += x3[index[i]]; 18 | } 19 | assert(s != 0); 20 | 21 | if 3 < (sublen as u32) { 22 | assert(index[offset + 3] == index2[3]); 23 | } 24 | } 25 | 26 | fn dyn_array(mut x: [u32; 5], y: Field, z: Field) { 27 | assert(x[y] == 111); 28 | assert(x[z] == 101); 29 | x[z] = 0; 30 | assert(x[y] == 111); 31 | assert(x[1] == 0); 32 | if y as u32 < 10 { 33 | x[y] = x[y] - 2; 34 | } else { 35 | x[y] = 0; 36 | } 37 | assert(x[4] == 109); 38 | } 39 | -------------------------------------------------------------------------------- /docs/general/equivalence_witness_target.md: -------------------------------------------------------------------------------- 1 | ### Equivalence between ACIR Witnesses and Plonky2 Targets 2 | In an ACIR circuit we have the concept of Witness. We can think of a witness as an inmutable variable in the circuit. All the opcodes operate over these witnesses, and they are numbered from 0 to N. On the other hand, Plonky2 circuits operate over the concept of Targets. You can think of Targets as the input and output wires that are connected to the operations in the arithmetic circuit. These targets are the skeleton of the circuit, and we cannot talk about this targets holding values while building the circuit, since the Targets will only be associated to values once the circuit is executing. 3 | 4 | Throughout the construction of the Plonky2 circuit we'll need a mapping between Witnesses and some Targets, more specifically we'll need an injective function F: Witness -> Targets. Why? Because two different opcodes can refer to the same witness, and in those cases we'll want to refer to the same targets while we're building the circuit. Besides, to generate the Plonky2 proof we need to provide some concrete values to the input targets. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Eryx 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/opcodes/blackBoxFunctions.md: -------------------------------------------------------------------------------- 1 | ### BlackBoxFunction opcode 2 | This opcode is different from Brillig Opcode. The idea is similar, but in this case we do want to constrain the calculations, and that's the key difference. The idea behind blackbox functions is to delegate to the proving backend the responsibility of how that calculation should be represented within its paradigm, so it can produce an optimal circuit. 3 | There are lots of blackbox functions. The ones done so far for Plonky2 are: 4 | 5 | #### RangeCheck 6 | The purpose is to constrain a value to be in certain range [0, 2^x), in other words, we want to make sure some value can be represented with x bits. For this we used the CircuitBuilder's ```range_check()```, which ultimately uses the ```BaseSumGate```. 7 | 8 | #### AND and XOR 9 | Performs a bitwise AND or XOR operation. For this we need to split some target into bits and then perform a traditional operation between bits. This also ends up using the ```BaseSumGate```. 10 | 11 | #### Sha256Compression 12 | This opcode represents a step in the hash function SHA256. Is not the whole hashing function, but one iteration of the main loop. 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | from python:3.13.0rc2-slim-bookworm as initial_build 2 | run apt upgrade -y 3 | run apt update -y 4 | run apt install -y git build-essential make curl 5 | 6 | run curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y 7 | env PATH="/root/.cargo/bin:${PATH}" 8 | 9 | arg CACHEBUST=1 10 | run git clone https://github.com/eryxcoop/acvm-backend-plonky2.git 11 | workdir acvm-backend-plonky2 12 | run make build_external 13 | run make build_backend 14 | 15 | from python:3.13.0rc2-slim-bookworm as final_build 16 | copy --from=initial_build /acvm-backend-plonky2/noir/target/release/nargo /acvm-backend-plonky2/noir/target/release/nargo 17 | copy --from=initial_build /acvm-backend-plonky2/plonky2-backend /acvm-backend-plonky2/plonky2-backend 18 | copy --from=initial_build /acvm-backend-plonky2/Makefile /acvm-backend-plonky2/Makefile 19 | copy --from=initial_build /acvm-backend-plonky2/prepare_compiled_noir_test_programs.py /acvm-backend-plonky2/prepare_compiled_noir_test_programs.py 20 | 21 | run apt upgrade -y 22 | run apt update -y 23 | run apt install -y build-essential make 24 | workdir acvm-backend-plonky2 25 | 26 | run make precompile_tests 27 | -------------------------------------------------------------------------------- /docs/general/general_workflow.md: -------------------------------------------------------------------------------- 1 | ## General overview 2 | The path from writing a Noir program and verifying a Plonky2 proof has many steps. This repo aims to touch only some of those, since the modularity provided by Aztec's stack allows it. We want to create a new backend for Noir using Plonky2 prover instead of Barretenberg. Essentially, these are the steps: 3 | * Read the ACIR circuit and the trace that solves it. These are the outputs of compiling and executing a Noir program with ```nargo execute```. 4 | * Create a Plonky2 circuit equivalent to the ACIR circuit. 5 | * Provide concrete values to the Plonky2 circuit and generate the proof. 6 | 7 | The ACIR circuit is composed by Opcodes, a set of abstract operations over variables. These variables are called witnesses in this context. 8 | 9 | As for now, the backend as an executable has 3 operations: 10 | * prove 11 | * write_vk 12 | * verify 13 | 14 | All the mentioned steps are performed in the ```prove``` command. The ```write_vk``` and ```verify``` mimics Barretenberg's behaviour: 15 | * ```write_vk``` should generate, serialize and persist the verification key so that the verify command can consume it. 16 | * ```verify``` should read the verification key and the proof and use the Plonky2 API to verify the proof. -------------------------------------------------------------------------------- /plonky2-backend/src/plonky2_ecdsa/biguint/witness.rs: -------------------------------------------------------------------------------- 1 | use plonky2::field::types::{Field, PrimeField64}; 2 | use plonky2::iop::generator::GeneratedValues; 3 | use plonky2::iop::witness::{Witness, WitnessWrite}; 4 | 5 | use crate::plonky2_ecdsa::biguint::gadgets::arithmetic_u32::U32Target; 6 | 7 | pub trait WitnessU32: Witness { 8 | fn set_u32_target(&mut self, target: U32Target, value: u32); 9 | fn get_u32_target(&self, target: U32Target) -> (u32, u32); 10 | } 11 | 12 | impl, F: PrimeField64> WitnessU32 for T { 13 | fn set_u32_target(&mut self, target: U32Target, value: u32) { 14 | self.set_target(target.0, F::from_canonical_u32(value)); 15 | } 16 | 17 | fn get_u32_target(&self, target: U32Target) -> (u32, u32) { 18 | let x_u64 = self.get_target(target.0).to_canonical_u64(); 19 | let low = x_u64 as u32; 20 | let high = (x_u64 >> 32) as u32; 21 | (low, high) 22 | } 23 | } 24 | 25 | pub trait GeneratedValuesU32 { 26 | fn set_u32_target(&mut self, target: U32Target, value: u32); 27 | } 28 | 29 | impl GeneratedValuesU32 for GeneratedValues { 30 | fn set_u32_target(&mut self, target: U32Target, value: u32) { 31 | self.set_target(target.0, F::from_canonical_u32(value)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/mdbook.yml: -------------------------------------------------------------------------------- 1 | name: Deploy mdBook site to Pages 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | pages: write 12 | id-token: write 13 | 14 | concurrency: 15 | group: "pages" 16 | cancel-in-progress: false 17 | 18 | jobs: 19 | build: 20 | runs-on: ubuntu-latest 21 | env: 22 | MDBOOK_VERSION: 0.4.36 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Install mdBook 26 | run: | 27 | curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf -y | sh 28 | rustup update 29 | cargo install --version ${MDBOOK_VERSION} mdbook 30 | cargo install mdbook-katex 31 | - name: Setup Pages 32 | id: pages 33 | uses: actions/configure-pages@v4 34 | - name: Build with mdBook 35 | run: mdbook build 36 | - name: Upload artifact 37 | uses: actions/upload-pages-artifact@v3 38 | with: 39 | path: ./book 40 | 41 | deploy: 42 | environment: 43 | name: github-pages 44 | url: ${{ steps.deployment.outputs.page_url }} 45 | runs-on: ubuntu-latest 46 | needs: build 47 | steps: 48 | - name: Deploy to GitHub Pages 49 | id: deployment 50 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/ecdsa_secp256k1/Prover.toml: -------------------------------------------------------------------------------- 1 | hashed_message = [ 2 | 0xce, 0x7d, 0xf6, 0xb1, 0xb2, 0x85, 0x2c, 0x5c, 3 | 0x15, 0x6b, 0x68, 0x3a, 0x9f, 0x8d, 0x4a, 0x8d, 4 | 0xae, 0xda, 0x2f, 0x35, 0xf0, 0x25, 0xcb, 0x0c, 5 | 0xf3, 0x49, 0x43, 0xdc, 0xac, 0x70, 0xd6, 0xa3 6 | ] 7 | 8 | pub_key_x = [ 9 | 0x7b, 0x83, 0xad, 0x6a, 0xfb, 0x12, 0x09, 0xf3, 10 | 0xc8, 0x2e, 0xbe, 0xb0, 0x8c, 0x0c, 0x5f, 0xa9, 11 | 0xbf, 0x67, 0x24, 0x54, 0x85, 0x06, 0xf2, 0xfb, 12 | 0x4f, 0x99, 0x1e, 0x22, 0x87, 0xa7, 0x70, 0x90, 13 | ] 14 | 15 | pub_key_y = [ 16 | 0x17, 0x73, 0x16, 0xca, 0x82, 0xb0, 0xbd, 0xf7, 17 | 0x0c, 0xd9, 0xde, 0xe1, 0x45, 0xc3, 0x00, 0x2c, 18 | 0x0d, 0xa1, 0xd9, 0x26, 0x26, 0x44, 0x98, 0x75, 19 | 0x97, 0x2a, 0x27, 0x80, 0x7b, 0x73, 0xb4, 0x2e 20 | ] 21 | 22 | signature = [ 23 | 0x6f, 0x01, 0x56, 0x09, 0x1c, 0xbe, 0x91, 0x2f, 24 | 0x2d, 0x5d, 0x12, 0x15, 0xcc, 0x3c, 0xd8, 0x1c, 25 | 0x09, 0x63, 0xc8, 0x83, 0x9b, 0x93, 0xaf, 0x60, 26 | 0xe0, 0x92, 0x1b, 0x61, 0xa1, 0x9c, 0x54, 0x30, 27 | 0x0c, 0x71, 0x00, 0x6d, 0xd9, 0x3f, 0x35, 0x08, 28 | 0xc4, 0x32, 0xda, 0xca, 0x21, 0xdb, 0x00, 0x95, 29 | 0xf4, 0xb1, 0x65, 0x42, 0x78, 0x2b, 0x79, 0x86, 30 | 0xf4, 0x8a, 0x5d, 0x0a, 0xe3, 0xc5, 0x83, 0xd4 31 | ] 32 | -------------------------------------------------------------------------------- /prepare_compiled_noir_test_programs.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | origin_path = os.path.abspath(os.getcwd()) 5 | custom_nargo_path = origin_path + "/noir/target/release/nargo" 6 | custom_backend_path = origin_path + "/plonky2-backend/target/release/plonky2-backend" 7 | base_test_programs_path = origin_path + "/plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/" 8 | print(origin_path) 9 | 10 | def execute_noir_project(noir_project_name): 11 | cur_dir = base_test_programs_path + noir_project_name 12 | os.chdir(cur_dir) 13 | try: 14 | command = f"{custom_nargo_path} execute witness" 15 | result = subprocess.check_output(command, shell=True, text=True) 16 | rename_circuit_command = f"mv {cur_dir}/target/{noir_project_name}.json {cur_dir}/target/circuit.json" 17 | subprocess.check_output(rename_circuit_command, shell=True, text=True) 18 | print(result) 19 | except Exception as e: 20 | print(f"An error has occurred while trying to execute the Noir program {noir_project_name}: {e}") 21 | return 22 | 23 | for noir_project_name in os.listdir(base_test_programs_path): 24 | subdir_path = os.path.join(base_test_programs_path, noir_project_name) 25 | if os.path.isdir(subdir_path): 26 | execute_noir_project(noir_project_name) -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/circuit_parser.rs: -------------------------------------------------------------------------------- 1 | use super::{Circuit, WitnessStack, Program}; 2 | use crate::noir_and_plonky2_serialization; 3 | 4 | fn parse_circuit_and_witnesses( 5 | circuit_path: &String, 6 | witness_path: String, 7 | ) -> (Circuit, WitnessStack) { 8 | let acir_program: Program = 9 | noir_and_plonky2_serialization::deserialize_program_within_file_path(circuit_path); 10 | let circuit = acir_program.functions[0].clone(); 11 | let witness = 12 | noir_and_plonky2_serialization::deserialize_witnesses_within_file_path(witness_path); 13 | (circuit, witness) 14 | } 15 | 16 | fn _path_for_circuit(nargo_project_name: &str) -> String { 17 | String::from(format!( 18 | "src/circuit_translation/tests/factories/noir_circuits_for_testing/{}/target/circuit.json", 19 | nargo_project_name)) 20 | } 21 | 22 | fn _path_for_witnesses(nargo_project_name: &str) -> String { 23 | String::from(format!( 24 | "src/circuit_translation/tests/factories/noir_circuits_for_testing/{}/target/witness", 25 | nargo_project_name 26 | )) 27 | } 28 | 29 | pub fn precompiled_circuit_and_withesses_with_name(program_name: &str) -> (Circuit, WitnessStack) { 30 | let circuit_path = _path_for_circuit(program_name); 31 | let witness_path = _path_for_witnesses(program_name); 32 | parse_circuit_and_witnesses(&circuit_path, witness_path) 33 | } 34 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/test_precompiled.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::circuit_translation::tests::factories::{circuit_parser, utils}; 3 | use parameterized::parameterized; 4 | 5 | /// Tests for compiled Noir programs 6 | 7 | #[parameterized(program_name = { 8 | "basic_memory_write", 9 | "assert_x_equals_5", 10 | "node_guardians_example", 11 | "array_dynamic", 12 | "1_mul", 13 | "3_add", 14 | "5_over", 15 | "7_function", 16 | "sha256_4", 17 | "ecdsa_secp256k1", 18 | })] 19 | fn test_noir_program(program_name: &str) { 20 | let (circuit, mut witnesses) = 21 | circuit_parser::precompiled_circuit_and_withesses_with_name(program_name); 22 | let witness_mapping = witnesses.pop().unwrap().witness; 23 | 24 | // print!("{:?}", circuit); 25 | 26 | // When 27 | let (circuit_data, witness_target_map) = 28 | utils::generate_plonky2_circuit_from_acir_circuit(&circuit); 29 | 30 | //Then 31 | let mut witness_assignment: Vec<(Witness, F)> = vec![]; 32 | for (witness, value) in witness_mapping { 33 | witness_assignment.push((witness, F::from_canonical_u64(value.try_to_u64().unwrap()))); 34 | } 35 | 36 | // utils::check_linked_output_targets_property(&circuit, &witness_target_map); 37 | let proof = utils::generate_plonky2_proof_using_witness_values( 38 | witness_assignment, 39 | &witness_target_map, 40 | &circuit_data, 41 | ); 42 | 43 | assert!(circuit_data.verify(proof).is_ok()); 44 | } 45 | -------------------------------------------------------------------------------- /docs/opcodes/assertZero.md: -------------------------------------------------------------------------------- 1 | ### AssertZero 2 | AssertZero is the main opcode in almost any circuit: it allows arithmetic operations over an arbitrary set of variables. This arithmetic operations are represented through equations that must hold throughout the program. For example, if we want to say 3 | 4 | ```x := y*y + z - 2``` 5 | 6 | we should express it like an equation 7 | 8 | ```y*y + z - x - 2 == 0``` 9 | 10 | that ultimately will translate into an AssertZero opcode of the form 11 | 12 | ```rust 13 | { 14 | quadratic_terms: [(1,y,y)] 15 | linear_combinations: [(1,z), (-1, x)] 16 | constant: -2 17 | } 18 | ``` 19 | 20 | Any assignment of a variable done in Noir, or any comparison by equality will end up being an AssertZero opcode. 21 | 22 | #### Equivalence in Plonky2 23 | 24 | Plonky2 has an ```ArithmeticGate``` that we can access through the CircuitBuilder API. There are some methods that we can use to facilitate the translation without the need of using the gate directly: 25 | * ```add(t1: Target, t2: Target)``` 26 | * ```mul(t1: Target, t2: Target)``` 27 | * ```mul_const(t1: Target, c: FieldElement)``` 28 | * ```assert_zero(t: Target)``` -> Restricts the value being held by the target to equal 0. 29 | 30 | The ArithmeticGate accepts equations of the form 31 | 32 | ```k_0 * x * y + k_1 * z``` 33 | 34 | so to translate a single AssertZero we need to generate a Plonky2 circuit that potentially uses many Arithmetic Gates. So, to sum it up, a single AssertZero opcode might be translated to many arithmetic operations. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: build_external build_backend precompile_tests 2 | 3 | # Cloning and building external resources 4 | 5 | build_external: 6 | $(MAKE) clone_custom_noir 7 | $(MAKE) build_noir 8 | $(MAKE) clone_custom_plonky2 9 | $(MAKE) build_plonky2 10 | 11 | clone_custom_noir: 12 | git clone https://github.com/brweisz/noir 13 | 14 | build_noir: 15 | cd noir && cargo build --release 16 | 17 | clone_custom_plonky2: 18 | git clone https://github.com/brweisz/plonky2 19 | 20 | build_plonky2: 21 | rustup override set nightly && cd plonky2 && cargo build --release 22 | 23 | 24 | # Building plonky2-backend 25 | 26 | build_backend: 27 | cd plonky2-backend && cargo build --release 28 | 29 | 30 | # Tests 31 | 32 | precompile_tests: 33 | python prepare_compiled_noir_test_programs.py 34 | 35 | 36 | # Execution 37 | 38 | verification_happy_path: 39 | $(MAKE) nargo_execute 40 | $(MAKE) prove 41 | $(MAKE) write_vk 42 | $(MAKE) verify 43 | 44 | nargo_execute: 45 | cd noir_example && ../noir/target/release/nargo execute witness --print-acir 46 | 47 | prove: 48 | cd plonky2-backend && ./target/release/plonky2-backend prove -b ../noir_example/target/noir_example.json -w ../noir_example/target/witness -o ../noir_example/proof 49 | 50 | write_vk: 51 | cd plonky2-backend && ./target/release/plonky2-backend write_vk -b ../noir_example/target/noir_example.json -o ../noir_example/target/vk 52 | 53 | verify: 54 | cd plonky2-backend && ./target/release/plonky2-backend verify -k ../noir_example/target/vk -p ../noir_example/proof 55 | 56 | .PHONY: all clone_custom_noir build_noir clone_custom_plonky2 build_plonky2 build_backend -------------------------------------------------------------------------------- /plonky2-backend/src/plonky2_ecdsa/biguint/gadgets/split_nonnative.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use plonky2::field::extension::Extendable; 3 | use plonky2::field::types::Field; 4 | use plonky2::hash::hash_types::RichField; 5 | use plonky2::iop::target::Target; 6 | use plonky2::plonk::circuit_builder::CircuitBuilder; 7 | 8 | use super::arithmetic_u32::U32Target; 9 | use super::nonnative::NonNativeTarget; 10 | 11 | pub trait CircuitBuilderSplit, const D: usize> { 12 | fn split_u32_to_4_bit_limbs(&mut self, val: U32Target) -> Vec; 13 | 14 | fn split_nonnative_to_4_bit_limbs( 15 | &mut self, 16 | val: &NonNativeTarget, 17 | ) -> Vec; 18 | 19 | fn split_nonnative_to_2_bit_limbs( 20 | &mut self, 21 | val: &NonNativeTarget, 22 | ) -> Vec; 23 | } 24 | 25 | impl, const D: usize> CircuitBuilderSplit 26 | for CircuitBuilder 27 | { 28 | fn split_u32_to_4_bit_limbs(&mut self, val: U32Target) -> Vec { 29 | let two_bit_limbs = self.split_le_base::<4>(val.0, 16); 30 | let four = self.constant(F::from_canonical_usize(4)); 31 | let combined_limbs = two_bit_limbs 32 | .iter() 33 | .tuples() 34 | .map(|(&a, &b)| self.mul_add(b, four, a)) 35 | .collect(); 36 | 37 | combined_limbs 38 | } 39 | 40 | fn split_nonnative_to_4_bit_limbs( 41 | &mut self, 42 | val: &NonNativeTarget, 43 | ) -> Vec { 44 | val.value 45 | .limbs 46 | .iter() 47 | .flat_map(|&l| self.split_u32_to_4_bit_limbs(l)) 48 | .collect() 49 | } 50 | 51 | fn split_nonnative_to_2_bit_limbs( 52 | &mut self, 53 | val: &NonNativeTarget, 54 | ) -> Vec { 55 | val.value 56 | .limbs 57 | .iter() 58 | .flat_map(|&l| self.split_le_base::<4>(l.0, 16)) 59 | .collect() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /plonky2-backend/run_examples.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import subprocess 4 | 5 | def main(argc, argv): 6 | example_name = argv[1] 7 | custom_nargo_path = "../../../noir/target/debug/nargo" 8 | custom_backend_path = "../../target/debug/plonky2-backend" 9 | 10 | os.chdir(f"example_programs/{example_name}") 11 | try: 12 | command = f"{custom_nargo_path} execute witness" 13 | result = subprocess.check_output(command, shell=True, text=True) 14 | except Exception as e: 15 | print(f"An error has occurred while trying to execute the Noir program: {e}") 16 | return 17 | 18 | try: 19 | command = f"{custom_backend_path} prove -c ./target/{example_name}.json -w ./target/witness -o ./proof" 20 | result = subprocess.check_output(command, shell=True, text=True) 21 | print("Proof generated successfully") 22 | except Exception as e: 23 | print(f"An error has occurred while trying to generate the plonky2 proof: {e}") 24 | return 25 | 26 | # try: 27 | # with open("proof", 'rt') as f: 28 | # hex_str = f.read() 29 | # hex_str = ''.join(hex_str.split()) 30 | # bytes_data = bytes.fromhex(hex_str) 31 | # print(bytes_data) 32 | # 33 | # except Exception as e: 34 | # print(f"An error has occurred while trying to read the proof: {e}") 35 | 36 | try: 37 | command = f"{custom_backend_path} write_vk -b ./target/{example_name}.json -o ./target/vk" 38 | result = subprocess.check_output(command, shell=True, text=True) 39 | print("Verification key generated successfully") 40 | except Exception as e: 41 | print(f"An error has occurred while trying to generate verification key: {e}") 42 | return 43 | 44 | try: 45 | command = f"{custom_backend_path} verify -k ./target/vk -p ./proof" 46 | result = subprocess.check_output(command, shell=True, text=True) 47 | print("Verification terminated successfully") 48 | except Exception as e: 49 | print(f"An error has occurred while trying to verify: {e}") 50 | return 51 | 52 | 53 | if __name__ == '__main__': 54 | main(len(sys.argv), sys.argv) 55 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/utils.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::circuit_translation; 3 | use plonky2::field::goldilocks_field::GoldilocksField; 4 | use plonky2::iop::witness::PartialWitness; 5 | use plonky2::iop::witness::WitnessWrite; 6 | use plonky2::plonk::proof::ProofWithPublicInputs; 7 | 8 | pub fn generate_plonky2_circuit_from_acir_circuit( 9 | circuit: &Circuit, 10 | ) -> (CircuitData, HashMap) { 11 | let mut translator = circuit_translation::CircuitBuilderFromAcirToPlonky2::new(); 12 | translator.translate_circuit(circuit); 13 | translator.unpack() 14 | } 15 | 16 | pub fn generate_plonky2_proof_using_witness_values( 17 | witness_assignment: Vec<(Witness, F)>, 18 | witness_target_map: &HashMap, 19 | circuit_data: &CircuitData, 20 | ) -> ProofWithPublicInputs { 21 | let mut witnesses = PartialWitness::::new(); 22 | for (witness, value) in witness_assignment { 23 | let plonky2_target = witness_target_map.get(&witness).unwrap(); 24 | witnesses.set_target(*plonky2_target, value); 25 | } 26 | circuit_data.prove(witnesses).unwrap() 27 | } 28 | 29 | pub fn check_linked_output_targets_property( 30 | circuit: &Circuit, 31 | witness_target_map: &HashMap, 32 | ) { 33 | // We must make sure that all targets linked to output witness exist and are actual Wires 34 | // (instead of VirtualTargets). Otherwise, it means that te circuit is not doing what we 35 | // expect and might fall into false positive tests. 36 | for witness_index in circuit.return_values.indices() { 37 | match witness_target_map.get(&Witness(witness_index)) { 38 | Some(target) => match target { 39 | Target::VirtualTarget { index: _ } => { 40 | panic!( 41 | "{}", 42 | format!( 43 | "Target corresponding to witness {} is not linked to the circuit", 44 | witness_index 45 | ) 46 | ) 47 | } 48 | Target::Wire(_wire) => {} 49 | }, 50 | None => panic!("An output witness has not an associated target"), 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/sha256_long/src/main.nr: -------------------------------------------------------------------------------- 1 | fn main() -> pub [u8; 32]{ 2 | let x = [ 3 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 4 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 5 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 6 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 7 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 8 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 9 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 10 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 11 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 12 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 13 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 14 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 15 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 16 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 17 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 18 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 19 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 20 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 21 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 22 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 23 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 24 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 25 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 26 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 27 | 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 163, 117, 178, 149, 28 | ]; 29 | std::sha256::sha256_var(x, x.len() as u64) 30 | } -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/node_guardians_example/src/main.nr: -------------------------------------------------------------------------------- 1 | struct Square { 2 | x: Field, 3 | y: Field 4 | } 5 | 6 | fn main( 7 | watcher_map: pub [[bool; 8]; 8], 8 | dagger: pub Square, 9 | path: [Square; 8] 10 | ) { 11 | is_valid_path(path, dagger); 12 | is_safe_path(path, watcher_map); 13 | } 14 | 15 | fn in_board_limits(elem: Field) -> bool { 16 | (elem == 0) | (elem == 1) | (elem == 2) | (elem == 3) | (elem == 4) | (elem == 5) | (elem == 6) | (elem == 7) 17 | } 18 | 19 | fn is_valid_square(sq: Square) -> bool { 20 | in_board_limits(sq.x) & in_board_limits(sq.y) 21 | } 22 | 23 | fn is_valid_jump(sq1: Square, sq2: Square) -> bool { 24 | let mut dx = sq1.x - sq2.x; 25 | if !in_board_limits(dx) { 26 | dx = sq2.x - sq1.x; 27 | } 28 | let mut dy = sq1.y - sq2.y; 29 | if !in_board_limits(dy) { 30 | dy = sq2.y - sq1.y; 31 | } 32 | 33 | let a = (dx == 1) & (dy == 2); 34 | let b = (dx == 2) & (dy == 1); 35 | a | b 36 | } 37 | 38 | fn is_initial_square(sq: Square) -> bool { 39 | (sq.x == 0) & (sq.y == 0) 40 | } 41 | 42 | pub fn is_valid_path(path: [Square; 8], dagger: Square) { 43 | let mut is_valid: bool = is_initial_square(path[0]); 44 | 45 | for i in 1..8 { 46 | is_valid &= is_valid_square(path[i]); 47 | is_valid &= is_valid_jump(path[i], path[i-1]); 48 | } 49 | is_valid &= (dagger.x == path[7].x) & (dagger.y == path[7].y); 50 | assert(is_valid); 51 | } 52 | 53 | fn prohibited_map_from(wm: [[bool; 8]; 8]) -> [[bool; 8]; 8] { 54 | let mut prohibited_map: [[bool; 8]; 8] = [[false; 8]; 8]; 55 | for i in 0..8 { 56 | for j in 0..8 { 57 | if wm[i][j] { 58 | for k in 0..8 { 59 | let i: Field = i as Field; 60 | let j: Field = j as Field; 61 | let k: Field = k as Field; 62 | if in_board_limits(i-k) & in_board_limits(j-k) { 63 | prohibited_map[i-k][j-k] = true; 64 | } 65 | if in_board_limits(i-k) & in_board_limits(j+k) { 66 | prohibited_map[i-k][j+k] = true; 67 | } 68 | if in_board_limits(i+k) & in_board_limits(j-k) { 69 | prohibited_map[i+k][j-k] = true; 70 | } 71 | if in_board_limits(i+k) & in_board_limits(j+k) { 72 | prohibited_map[i+k][j+k] = true; 73 | } 74 | } 75 | } 76 | } 77 | } 78 | prohibited_map 79 | } 80 | 81 | pub fn is_safe_path(path: [Square; 8], watcher_map: [[bool; 8]; 8]) { 82 | let prohibited_map: [[bool; 8]; 8] = prohibited_map_from(watcher_map); 83 | let mut unsafe: bool = false; 84 | for i in 0..8 { 85 | if prohibited_map[path[i].x][path[i].y] { 86 | unsafe = true; 87 | } 88 | } 89 | assert(!unsafe); 90 | } -------------------------------------------------------------------------------- /plonky2-backend/src/noir_and_plonky2_serialization.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | use base64; 4 | use base64::Engine; 5 | use serde_json; 6 | use std::fs::File; 7 | use std::io::{Read, Write}; 8 | use std::vec::Vec; 9 | use crate::actions::write_vk_action::BackendGateSerializer; 10 | 11 | /// Since Nargo is decoupled from the backend (they don't even have to be written in the same 12 | /// languaje) the communication between them is done through files, so there's a lot of serializing 13 | /// and deserializing in very specific formats. The fact that this backend is written in Rust is 14 | /// accidental, but also very usefull. 15 | 16 | pub fn deserialize_verifying_key_within_file_path( 17 | verifying_key_path: &String, 18 | ) -> VerifierCircuitData { 19 | let buffer = read_file_to_bytes(verifying_key_path); 20 | let gate_serializer = BackendGateSerializer; 21 | VerifierCircuitData::from_bytes(buffer, &gate_serializer).unwrap() 22 | } 23 | 24 | pub fn deserialize_proof_within_file_path( 25 | proof_path: &String, 26 | verifier_data: &VerifierCircuitData, 27 | ) -> CompressedProofWithPublicInputs { 28 | let buffer = read_file_to_bytes(proof_path); 29 | let common_circuit_data = &verifier_data.common; 30 | let proof: CompressedProofWithPublicInputs = 31 | CompressedProofWithPublicInputs::from_bytes(buffer, common_circuit_data).unwrap(); 32 | proof 33 | } 34 | 35 | pub fn read_file_to_bytes(file_path: &String) -> Vec { 36 | let mut file = File::open(file_path).expect("There was a problem reading the file"); 37 | let mut buffer: Vec = Vec::new(); 38 | let _ = file.read_to_end(&mut buffer); 39 | return buffer; 40 | } 41 | 42 | pub fn deserialize_program_within_file_path(acir_program_path: &String) -> Program { 43 | let mut file = File::open(acir_program_path).expect("There was a problem opening the file"); 44 | let mut json_string = String::new(); 45 | file.read_to_string(&mut json_string) 46 | .expect("There was a problem reading the file content"); 47 | let json_str: &str = &json_string; 48 | let json: serde_json::Value = 49 | serde_json::from_str(json_str).expect("There was a problem parsing the json program"); 50 | let Some(bytecode_str) = json["bytecode"].as_str() else { 51 | panic!("Expected a different circuit format") 52 | }; 53 | let bytecode: &[u8] = &base64::prelude::BASE64_STANDARD 54 | .decode(bytecode_str) 55 | .expect("There was a problem decoding the program from base 64"); 56 | let program = Program::deserialize_program(bytecode); 57 | program.unwrap() 58 | } 59 | 60 | pub fn deserialize_witnesses_within_file_path(mut witnesses_path: String) -> WitnessStack { 61 | let file_content: &[u8] = &read_file_to_bytes(&witnesses_path); 62 | let witness_stack = WitnessStack::try_from(file_content); 63 | witness_stack.unwrap() 64 | } 65 | 66 | pub fn write_bytes_to_file_path(bytes: Vec, path: &String) { 67 | let mut file = File::create(path).expect("Failed to create file"); 68 | file.write_all(&bytes).expect("Failed to write file"); 69 | } 70 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/noir_circuits_for_testing/7_function/src/main.nr: -------------------------------------------------------------------------------- 1 | //Tests for function calling 2 | fn f1(mut x: Field) -> Field { 3 | x = x + 1; 4 | x = f2(x); 5 | x 6 | } 7 | 8 | fn f2(mut x: Field) -> Field { 9 | x += 2; 10 | x 11 | } 12 | // Simple example 13 | fn test0(mut a: Field) { 14 | a = f2(a); 15 | assert(a == 3); 16 | } 17 | // Nested call 18 | fn test1(mut a: Field) { 19 | a = f1(a); 20 | assert(a == 4); 21 | } 22 | 23 | fn test2(z: Field, t: u32) { 24 | let a = z + t as Field; 25 | assert(a == 64); 26 | let e = pow(z, t as Field); 27 | assert(e == 714924299); 28 | } 29 | 30 | fn pow(base: Field, exponent: Field) -> Field { 31 | let mut r = 1 as Field; 32 | let b = exponent.to_le_bits(32 as u32); 33 | for i in 1..33 { 34 | r = r*r; 35 | r = (b[32-i] as Field) * (r * base) + (1 - b[32-i] as Field) * r; 36 | } 37 | r 38 | } 39 | 40 | fn test3(x: [u8; 3]) -> [u8; 3] { 41 | let mut buffer = [0 as u8; 3]; 42 | for i in 0..3 { 43 | buffer[i] = x[i]; 44 | } 45 | assert(buffer == x); 46 | buffer 47 | } 48 | 49 | fn test_multiple(x: u32, y: u32) -> (u32, u32) { 50 | (y, x) 51 | } 52 | 53 | fn test_multiple2() -> my_struct { 54 | my_struct { a: 5 as u32, b: 7 as u32 } 55 | } 56 | 57 | fn test_multiple3(x: u32, y: u32) { 58 | assert(x == y); 59 | } 60 | 61 | struct my_struct { 62 | a: u32, 63 | b: u32, 64 | } 65 | 66 | struct my2 { 67 | aa: my_struct, 68 | bb: my_struct, 69 | } 70 | 71 | fn test_multiple4(s: my_struct) { 72 | assert(s.a == s.b + 2); 73 | } 74 | 75 | fn test_multiple5(a: (u32, u32)) { 76 | assert(a.0 == a.1 + 2); 77 | } 78 | 79 | fn test_multiple6(a: my2, b: my_struct, c: (my2, my_struct)) { 80 | test_multiple4(a.aa); 81 | test_multiple5((b.a, b.b)); 82 | assert(c.0.aa.a == c.1.a); 83 | } 84 | 85 | fn foo(a: [Field; N]) -> [Field; N] { 86 | a 87 | } 88 | 89 | fn bar() -> [Field; 1] { 90 | foo([0]) 91 | } 92 | 93 | fn main(x: u32, y: u32, a: Field, arr1: [u32; 9], arr2: [u32; 9]) { 94 | let mut ss: my_struct = my_struct { b: x, a: x + 2 }; 95 | test_multiple4(ss); 96 | test_multiple5((ss.a, ss.b)); 97 | let my = my2 { aa: ss, bb: ss }; 98 | ss.a = 61; 99 | test_multiple6(my, ss, (my, ss)); 100 | 101 | let my_block = { 102 | let mut ab = f2(a); 103 | ab = ab + a; 104 | (x, ab) 105 | }; 106 | assert(my_block.1 == 4); 107 | 108 | test0(a); 109 | test1(a); 110 | test2(x as Field, y); 111 | assert(bar()[0] == 0); 112 | 113 | let mut b = [0 as u8, 5 as u8, 2 as u8]; 114 | let c = test3(b); 115 | assert(b == c); 116 | b[0] = 1 as u8; 117 | let cc = test3(b); 118 | assert(c != cc); 119 | let e = test_multiple(x, y); 120 | assert(e.1 == e.0 + 54 as u32); 121 | let d = test_multiple2(); 122 | assert(d.b == d.a + 2 as u32); 123 | test_multiple3(y, y); 124 | //Regression test for issue #628: 125 | let result = first(arr_to_field(arr1), arr_to_field(arr2)); 126 | assert(result[0] == arr1[0] as Field); 127 | } 128 | // Issue #628 129 | fn arr_to_field(arr: [u32; 9]) -> [Field; 9] { 130 | let mut as_field: [Field; 9] = [0 as Field; 9]; 131 | for i in 0..9 { 132 | as_field[i] = arr[i] as Field; 133 | } 134 | as_field 135 | } 136 | 137 | fn first(a: [Field; 9], _b: [Field; 9]) -> [Field; 9] { 138 | a 139 | } 140 | -------------------------------------------------------------------------------- /plonky2-backend/src/actions/write_vk_action.rs: -------------------------------------------------------------------------------- 1 | use plonky2::field::extension::Extendable; 2 | use plonky2::hash::hash_types::RichField; 3 | use plonky2::{impl_gate_serializer, read_gate_impl, get_gate_tag_impl}; 4 | use plonky2::gates::arithmetic_base::ArithmeticGate; 5 | use plonky2::gates::arithmetic_extension::ArithmeticExtensionGate; 6 | use plonky2::gates::base_sum::BaseSumGate; 7 | use plonky2::gates::constant::ConstantGate; 8 | use plonky2::gates::coset_interpolation::CosetInterpolationGate; 9 | use plonky2::gates::exponentiation::ExponentiationGate; 10 | use plonky2::gates::lookup::LookupGate; 11 | use plonky2::gates::lookup_table::LookupTableGate; 12 | use plonky2::gates::multiplication_extension::MulExtensionGate; 13 | use plonky2::gates::noop::NoopGate; 14 | use plonky2::gates::poseidon::PoseidonGate; 15 | use plonky2::gates::poseidon_mds::PoseidonMdsGate; 16 | use plonky2::gates::public_input::PublicInputGate; 17 | use plonky2::gates::random_access::RandomAccessGate; 18 | use plonky2::gates::reducing::ReducingGate; 19 | use plonky2::gates::reducing_extension::ReducingExtensionGate; 20 | use plonky2::util::serialization::GateSerializer; 21 | use crate::plonky2_ecdsa::biguint::gates::add_many_u32::U32AddManyGate; 22 | use crate::plonky2_ecdsa::biguint::gates::arithmetic_u32::U32ArithmeticGate; 23 | use crate::plonky2_ecdsa::biguint::gates::comparison::ComparisonGate; 24 | use crate::plonky2_ecdsa::biguint::gates::range_check_u32::U32RangeCheckGate; 25 | use crate::plonky2_ecdsa::biguint::gates::subtraction_u32::U32SubtractionGate; 26 | use super::*; 27 | 28 | /// The Write Verification Key Action will translate the ACIR circuit into the Plonky2 circuit 29 | /// (again) and write the necessary data for the Verifier to verify the computation. 30 | pub struct WriteVKAction { 31 | pub acir_program_json_path: String, 32 | pub vk_path_output: String, 33 | } 34 | 35 | pub struct BackendGateSerializer; 36 | impl, const D: usize> GateSerializer for BackendGateSerializer { 37 | impl_gate_serializer! { 38 | DefaultGateSerializer, 39 | ArithmeticGate, 40 | ArithmeticExtensionGate, 41 | BaseSumGate<2>, 42 | BaseSumGate<4>, 43 | ConstantGate, 44 | CosetInterpolationGate, 45 | ExponentiationGate, 46 | LookupGate, 47 | LookupTableGate, 48 | MulExtensionGate, 49 | NoopGate, 50 | PoseidonMdsGate, 51 | PoseidonGate, 52 | PublicInputGate, 53 | RandomAccessGate, 54 | ReducingExtensionGate, 55 | ReducingGate, 56 | ComparisonGate, 57 | U32AddManyGate, 58 | U32ArithmeticGate, 59 | U32RangeCheckGate, 60 | U32SubtractionGate 61 | } 62 | } 63 | 64 | impl WriteVKAction { 65 | pub fn run(&self) { 66 | let acir_program: Program = 67 | deserialize_program_within_file_path(&self.acir_program_json_path); 68 | let acir_circuit = &acir_program.functions[0]; 69 | let mut translator = CircuitBuilderFromAcirToPlonky2::new(); 70 | translator.translate_circuit(acir_circuit); 71 | let CircuitBuilderFromAcirToPlonky2 { 72 | builder, 73 | witness_target_map: _, 74 | memory_blocks: _, 75 | } = translator; 76 | let plonky2_circuit = builder.build::(); 77 | let verifier_data = plonky2_circuit.verifier_data(); 78 | let gate_serializer = BackendGateSerializer; 79 | let serialized_verifier_data = verifier_data.to_bytes(&gate_serializer).unwrap(); 80 | write_bytes_to_file_path(serialized_verifier_data, &self.vk_path_output); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /docs/opcodes/memoryOperations.md: -------------------------------------------------------------------------------- 1 | ### Memory Operations 2 | There are 2 memory operations: MemoryInit and MemoryOp (read or write). This opcode exists to handle the case where we read or write values from arrays with an unknown position in circuit-building time. For example, the Noir program... 3 | 4 | ```rust 5 | fn main(array: [Field; 5]) -> pub Field { 6 | array[3] 7 | } 8 | ``` 9 | ...resolves to AssertZero opcodes, since the input Witness are w0, w1, w2, w3 & w4, the output witness is w5, and the code just makes an AssertZero to ensure that w3 is equal to w5. Instead, when the Noir code looks like this... 10 | 11 | ```rust 12 | fn main(array: [Field; 5], index: Field) -> pub Field { 13 | array[index] 14 | } 15 | ``` 16 | ...then we're making a random access into an array in a position unknown at the time the circuit is being built. This requires the concept of Memory blocks (or memory arrays). 17 | 18 | 19 | #### MemoryInit 20 | It represents the creation of an array in memory. What does this mean for a prover? Well, it depends on the prover. This opcode will be translated into a set of constraints that will be used to generate the Plonky2 circuit. It has 2 fields: 21 | * block_id: the memory block index that is being created. This index will be global within our program. It's numbered from 0 to N. 22 | * witnesses: an array of the witnesses that hold the initial values of the memory block. 23 | 24 | Our way of translating this to Plonky2 was simply creating a global mapping between indexes and arrays of Plonky2 targets, like ```HashMap>```. 25 | 26 | #### MemoryOp 27 | It represents either a memory read operation or a memory write operation. 28 | 29 | ##### Memory Read 30 | The opcode has the following fields: 31 | * block_id: the index of the memory block we're reading from. 32 | * index: witness that holds the value of the index where we want to read from. It's like ```array[index]```. 33 | * value: witness where the value of the memory read will be stored. It's like ```value = array[index]```. 34 | 35 | To implement this we used the Plonky2 RandomAccessMemory gate through the CircuitBuilder's ```random_access()``` method. 36 | 37 | ##### Memory Write 38 | The opcode has the following fields: 39 | * block_id: the memory block index we're writing into. 40 | * index: witness that holds the value of the index we want to write into. 41 | * value: witness that holds the value we want to write. Assume we have a value = w_1. 42 | 43 | This would be equivalent as doing ```array[index] = value```. 44 | 45 | Now, this operation is a bit tricky since we don't know the position we're writing while building the circuit. During the circuit construction, we have arrays of targets representing our memory blocks, and these targets have unchangeable values during circuit execution. We need to create a static circuit, but any slot's value could change. Therefore, we need to create an entirely new array and constrain its values to be the same as the previous ones, except for the position we're changing. As you can imagine, this operation is rather expensive. 46 | 47 | To visualize this: imagine we need to write position 2 with some value v. Then we'll need to create a new set of targets and attach them to the corresponding value. 48 | 49 | Previous memory block: | t0 | t1 | t2 | t3 | t4 | 50 | ↓ ↓ ↓ ↓ 51 | New memory block: | t0' | t1' | t2' | t3' | t4' | 52 | ↑ 53 | New value: v 54 | 55 | 56 | We iterate over all the targets, using the CircuitBuilder's ```is_equal()``` method to figure out which position are we changing. If the position doesn't match the index, we link it to the target in the previous version of the memory block on the same position. If the position matches the index, then we create a new target with the value we want to write and link it to the new memory array. -------------------------------------------------------------------------------- /plonky2-backend/src/actions/prove_action.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use num_bigint::BigUint; 4 | 5 | use plonky2::field::goldilocks_field::GoldilocksField; 6 | use plonky2::field::types::Field; 7 | use plonky2::iop::target::Target; 8 | use plonky2::iop::witness::{PartialWitness, WitnessWrite}; 9 | use plonky2::plonk::circuit_data::CircuitData; 10 | use plonky2::plonk::proof::ProofWithPublicInputs; 11 | 12 | use super::*; 13 | 14 | /// The Prove Action will read the ACIR circuit and the witnesses generated by Nargo, translate the 15 | /// circuit to Plonky2 and then use the provided witnesses to generate the proof. Lastly, te proof 16 | /// will be saved. 17 | 18 | /// This action could be split into 1) the translation of the circuit and 2) the generation of the 19 | /// proof since the latter can be performed multiple times with different inputs for the same circuit 20 | pub struct ProveAction { 21 | pub acir_program_json_path: String, 22 | pub witness_stack_zip_path: String, 23 | pub resulting_proof_file_path: String, 24 | } 25 | 26 | impl ProveAction { 27 | pub fn run(&self) { 28 | let acir_program: Program = 29 | deserialize_program_within_file_path(&self.acir_program_json_path); 30 | let witness_stack: WitnessStack = 31 | deserialize_witnesses_within_file_path(self.witness_stack_zip_path.clone()); 32 | 33 | let circuit = &acir_program.functions[0]; 34 | let (circuit_data, witness_target_map) = 35 | self.generate_plonky2_circuit_from_acir_circuit(circuit); 36 | let proof = self.generate_serialized_plonky2_proof( 37 | witness_stack, 38 | &witness_target_map, 39 | &circuit_data, 40 | ); 41 | 42 | self._write_proof_into_file(proof, &self.resulting_proof_file_path); 43 | } 44 | 45 | fn _write_proof_into_file(&self, proof: Vec, proof_path: &String) { 46 | write_bytes_to_file_path(proof, proof_path) 47 | } 48 | 49 | pub fn generate_plonky2_circuit_from_acir_circuit( 50 | &self, 51 | circuit: &Circuit, 52 | ) -> (CircuitData, HashMap) { 53 | let mut translator = CircuitBuilderFromAcirToPlonky2::new(); 54 | translator.translate_circuit(circuit); 55 | translator.unpack() 56 | } 57 | 58 | /// This is just a transformation from the Noir's FieldElement to the Plonky2 GoldilocksField 59 | fn _field_element_to_goldilocks_field(&self, fe: &FieldElement) -> F { 60 | let fe_as_big_uint = BigUint::from_bytes_be(&fe.to_be_bytes() as &[u8]); 61 | F::from_noncanonical_biguint(fe_as_big_uint) 62 | } 63 | 64 | fn generate_serialized_plonky2_proof( 65 | &self, 66 | mut witness_stack: WitnessStack, 67 | witness_target_map: &HashMap, 68 | circuit_data: &CircuitData, 69 | ) -> Vec { 70 | let proof = self.generate_plonky2_proof_from_witness_stack( 71 | &mut witness_stack, 72 | witness_target_map, 73 | circuit_data, 74 | ); 75 | let verifier_data_digest = &circuit_data.verifier_only.circuit_digest; 76 | let common = &circuit_data.common; 77 | let compressed_proof = proof.compress(verifier_data_digest, common).unwrap(); 78 | compressed_proof.to_bytes() 79 | } 80 | 81 | pub fn generate_plonky2_proof_from_witness_stack( 82 | &self, 83 | mut witness_stack: &mut WitnessStack, 84 | witness_target_map: &HashMap, 85 | circuit_data: &CircuitData, 86 | ) -> ProofWithPublicInputs { 87 | let witnesses = self._extract_witnesses(&mut witness_stack, witness_target_map); 88 | self.generate_plonky2_proof_from_partial_witnesses(circuit_data, witnesses) 89 | } 90 | 91 | pub fn generate_plonky2_proof_from_partial_witnesses( 92 | &self, 93 | circuit_data: &CircuitData, 94 | witnesses: PartialWitness, 95 | ) -> ProofWithPublicInputs { 96 | circuit_data.prove(witnesses).unwrap() 97 | } 98 | 99 | /// All the witnesses used in the ACIR code have an "equivalent" Target in the Plonky2 100 | /// circuit. During the translation process we maintain an injective mapping between those, in 101 | /// part for assigning the values to the targets when generating the proof. 102 | fn _extract_witnesses( 103 | &self, 104 | witness_stack: &mut WitnessStack, 105 | witness_target_map: &HashMap, 106 | ) -> PartialWitness { 107 | let mut witnesses = PartialWitness::::new(); 108 | let witness_map = witness_stack.pop().unwrap().witness; 109 | for (witness, value) in witness_map.into_iter() { 110 | let plonky2_target = witness_target_map.get(&witness).unwrap(); 111 | witnesses.set_target( 112 | *plonky2_target, 113 | self._field_element_to_goldilocks_field(&value), 114 | ); 115 | } 116 | witnesses 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # acvm-backend-plonky2 2 | This is an open source backend for the ACIR standard as implemented in the Noir programming languaje, written in Rust. Check out [the docs](https://eryxcoop.github.io/acvm-backend-plonky2/foreword.html) for more detail on what this repo does. 3 | 4 | ## How to generate Plonky2 proofs for your Noir circuit with docker 5 | 1. You need to install docker, if you haven't already 6 | 2. From a terminal, run ```docker pull bweisz/acvm-backend-plonky2:0.3``` to pull the docker image from dockerhub 7 | 8 | Fast version (execute, prove, write_vk and verify all at once) 9 | 3. Run ```docker run -v /full/path/to/your/noir/project:/acvm-backend-plonky2/noir_example bweisz/acvm-backend-plonky2 make verification_happy_path```. This will create a container from the image 'bweisz/acvm-backend-plonky2', copy the contents of your noir project into the 'noir_example' folder inside the container and run a complete workflow of executing the circuit, generating the proof, writing the vk and verifying the proof. 10 | 11 | 12 | Alternatively, if you want to run the commands separately and know something about docker (or not), follow the following steps. 13 | 3. Run ```docker run -d --name noir_with_plonky2 -v /full/path/to/your/noir/project:/acvm-backend-plonky2/noir_example bweisz/acvm-backend-plonky2 tail -f /dev/null```. This will create a container named 'noir_with_plonky2' 14 | 4. Run ```docker exec -it noir_with_plonky2 bash```. This will allow you to execute commands in the container. 15 | 5. From the container terminal, run separately: 16 | 1. ```make nargo_execute``` 17 | 2. ```make prove``` 18 | 3. ```make write_vk``` 19 | 4. ```make verify ``` 20 | 21 | 22 | ## How to set up the project locally (without docker) 23 | 24 | For the setup, run ```make``` on the root directory. This will do the following: 25 | 26 | For now, until the corresponding PRs are made in the Plonky2 and the Noir repositories, the command will clone these repositories inside the project root. 27 | * https://github.com/brweisz/noir 28 | * https://github.com/brweisz/plonky2 29 | 30 | Those are forks of the official [Noir](https://github.com/noir-lang/noir) and [Plonky2](https://github.com/0xPolygonZero/plonky2) repositories respectively, with a couple modifications. 31 | The resulting project tree must therefore look like: 32 | 33 | ``` 34 | plonky-2-backend-for-acir 35 | |_ noir 36 | |_ noir_example 37 | |_ plonky2 38 | |_ plonky2-backend 39 | |_ Makefile 40 | ``` 41 | 42 | Then it'll build noir and plonky2. The latter with the nightly toolchain. Lastly, it'll build the custom plonky2 backend. 43 | 44 | ## Running some predefined examples 45 | If you want to try out some Noir examples, execute the python script ```run_examples.py``` with the name of the example as the only parameter from the ```plonky2-backend``` directory: 46 | * ```basic_if``` 47 | * ```fibonacci``` 48 | * ```basic_div``` 49 | 50 | 51 | ## Manual testing 52 | 53 | For some manual testing (local), the workflow is as follows: in the ```noir_example``` folder there's a Noir project. In the ```noir_example/src/main.nr``` file you can write the main function of any noir program you want to prove. 54 | Run ```make run_noir_example``` from the root directory. The following explanation is similar to the official [Noir docs](https://noir-lang.org/docs/getting_started/hello_noir/#4-execute-the-noir-program), but using the custom plonky2 backend instead of barretenberg, and it's what the command will execute. 55 | 56 | 1) From the ```noir_example``` directory run: 57 | * ```../noir/target/release/nargo execute witness```. This will execute the noir program through the nargo acvm, generating: 58 | * The ACIR circuit in ```target/noir_example.json``` 59 | * The witness in ```target/witness.gz``` 60 | 2) From the ```plonky2-backend``` directory run: 61 | * ```./target/release/plonky2-backend prove -b ../noir_example/target/noir_example.json -w ../noir_example/target/witness.gz -o ../noir_example/proof```. This will create a Plonky2 proof in ```../noir_example/proof```. 62 | * ```./target/release/plonky2-backend write_vk -b ../noir_example/target/noir_example.json -o ../noir_example/target/vk```. This will create the verification key in ```../noir_example/target/vk``` 63 | * ```./target/release/plonky2-backend verify -k ../noir_example/target/vk -p ../noir_example/proof```. This will verify the Plonky2 proof. An empty output is sign of verification success. 64 | 65 | ## Contact Us 66 | Feel free to join our telegram group for suggestions, report bugs or any question you might have! 67 | https://t.me/+HeUDkQPX_w0yMDQx 68 | 69 | ## Things already implemented in this version 70 | The Plonky2 backend for ACIR is still in a development phase. As for now, these are the implemented functionalities: 71 | * AssertZero Opcode ✓ 72 | * MemoryInit Opcode ✓ 73 | * MemoryOp Opcode ✓ 74 | * BrilligCall Opcode ✓ 75 | * BlackBoxFunction ✓ 76 | * RANGE (up to 33 bits) ✓ 77 | * XOR ✓ 78 | * AND ✓ 79 | * SHA256 ✓ 80 | * EcdsaSecp256k1 ✓ 81 | 82 | Things not implemented yet are mostly BlackBoxFunctions. 83 | 84 | ## Credits 85 | We used some code from repos for the implementation of ECDSA verification and made some modifications to them: 86 | * https://github.com/0xPolygonZero/plonky2-ecdsa 87 | * https://github.com/0xPolygonZero/plonky2-u32 88 | 89 | If you want to try this backend with a medium size Noir project here's an example of a [context free grammar checker](https://codeberg.org/pdallegri/zk-grammar) by Pablo Dallegri, the first Plonky2 backend user. -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/assert_zero_translator.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// Module in charge of translating each AssertZero operation. Currently, the Plonky2 feature used 4 | /// to deal with systems of equations is the ArithmeticGate, which the CircuitBuilder uses 5 | /// extensively (through methods like add, sub, mul, mul_const, etc.) 6 | pub struct AssertZeroTranslator<'a> { 7 | builder: &'a mut CircuitBuilder, 8 | witness_target_map: &'a mut HashMap, 9 | expression: &'a Expression, 10 | } 11 | 12 | impl<'a> AssertZeroTranslator<'a> { 13 | pub fn new_for( 14 | builder: &'a mut CircuitBuilder, 15 | witness_target_map: &'a mut HashMap, 16 | expression: &'a Expression, 17 | ) -> AssertZeroTranslator<'a> { 18 | Self { 19 | builder, 20 | witness_target_map, 21 | expression, 22 | } 23 | } 24 | 25 | pub fn translate(&mut self) { 26 | self._register_intermediate_witnesses_for_assert_zero(); 27 | self._translate_assert_zero(); 28 | } 29 | 30 | fn _translate_assert_zero(self: &mut Self) { 31 | let g_constant = self._field_element_to_goldilocks_field(&self.expression.q_c); 32 | 33 | let constant_target = self.builder.constant(g_constant); 34 | let mut current_acc_target = constant_target; 35 | current_acc_target = self._add_linear_combinations(current_acc_target); 36 | current_acc_target = self._add_cuadratic_combinations(current_acc_target); 37 | self.builder.assert_zero(current_acc_target); 38 | } 39 | 40 | fn _get_or_create_target_for_witness(self: &mut Self, witness: Witness) -> Target { 41 | match self.witness_target_map.get(&witness) { 42 | Some(target) => *target, 43 | None => { 44 | let target = self.builder.add_virtual_target(); 45 | self.witness_target_map.insert(witness, target); 46 | target 47 | } 48 | } 49 | } 50 | 51 | /// Witnesses that weren't registered previously because it's its first appearance. 52 | fn _register_intermediate_witnesses_for_assert_zero(&mut self) { 53 | for (_, witness_1, witness_2) in &self.expression.mul_terms { 54 | self._get_or_create_target_for_witness(*witness_1); 55 | self._get_or_create_target_for_witness(*witness_2); 56 | } 57 | for (_, witness) in &self.expression.linear_combinations { 58 | self._get_or_create_target_for_witness(*witness); 59 | } 60 | } 61 | 62 | fn _add_cuadratic_combinations(self: &mut Self, mut current_acc_target: Target) -> Target { 63 | let mul_terms = &self.expression.mul_terms; 64 | for mul_term in mul_terms { 65 | let (f_cuadratic_factor, public_input_witness_1, public_input_witness_2) = mul_term; 66 | let cuadratic_target = self._compute_cuadratic_term_target( 67 | f_cuadratic_factor, 68 | public_input_witness_1, 69 | public_input_witness_2, 70 | ); 71 | let new_target = self.builder.add(cuadratic_target, current_acc_target); 72 | current_acc_target = new_target; 73 | } 74 | current_acc_target 75 | } 76 | 77 | fn _add_linear_combinations(self: &mut Self, mut current_acc_target: Target) -> Target { 78 | let linear_combinations = &self.expression.linear_combinations; 79 | for (f_multiply_factor, public_input_witness) in linear_combinations { 80 | let linear_combination_target = 81 | self._compute_linear_combination_target(f_multiply_factor, public_input_witness); 82 | let new_target = self 83 | .builder 84 | .add(linear_combination_target, current_acc_target); 85 | current_acc_target = new_target; 86 | } 87 | current_acc_target 88 | } 89 | 90 | fn _compute_linear_combination_target( 91 | self: &mut Self, 92 | f_multiply_constant_factor: &FieldElement, 93 | public_input_witness: &Witness, 94 | ) -> Target { 95 | let factor_target = *self.witness_target_map.get(public_input_witness).unwrap(); 96 | let g_first_pi_factor = self._field_element_to_goldilocks_field(f_multiply_constant_factor); 97 | self.builder.mul_const(g_first_pi_factor, factor_target) 98 | } 99 | 100 | fn _compute_cuadratic_term_target( 101 | self: &mut Self, 102 | f_cuadratic_factor: &FieldElement, 103 | public_input_witness_1: &Witness, 104 | public_input_witness_2: &Witness, 105 | ) -> Target { 106 | let g_cuadratic_factor = self._field_element_to_goldilocks_field(f_cuadratic_factor); 107 | let first_public_input_target = 108 | *self.witness_target_map.get(public_input_witness_1).unwrap(); 109 | let second_public_input_target = 110 | *self.witness_target_map.get(public_input_witness_2).unwrap(); 111 | 112 | let cuadratic_target = self 113 | .builder 114 | .mul(first_public_input_target, second_public_input_target); 115 | self.builder.mul_const(g_cuadratic_factor, cuadratic_target) 116 | } 117 | 118 | fn _field_element_to_goldilocks_field(self: &mut Self, fe: &FieldElement) -> F { 119 | let fe_as_big_uint = BigUint::from_bytes_be(&fe.to_be_bytes() as &[u8]); 120 | F::from_noncanonical_biguint(fe_as_big_uint) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /plonky2-backend/src/plonky2_ecdsa/biguint/gadgets/multiple_comparison.rs: -------------------------------------------------------------------------------- 1 | use plonky2::field::extension::Extendable; 2 | use plonky2::hash::hash_types::RichField; 3 | use plonky2::iop::target::{BoolTarget, Target}; 4 | use plonky2::plonk::circuit_builder::CircuitBuilder; 5 | 6 | use crate::plonky2_ecdsa::biguint::gadgets::arithmetic_u32::U32Target; 7 | use crate::plonky2_ecdsa::biguint::gates::comparison::ComparisonGate; 8 | 9 | pub const fn ceil_div_usize(a: usize, b: usize) -> usize { 10 | (a + b - 1) / b 11 | } 12 | 13 | /// Returns true if a is less than or equal to b, considered as base-`2^num_bits` limbs of a large value. 14 | /// This range-checks its inputs. 15 | pub fn list_le_circuit, const D: usize>( 16 | builder: &mut CircuitBuilder, 17 | a: Vec, 18 | b: Vec, 19 | num_bits: usize, 20 | ) -> BoolTarget { 21 | assert_eq!( 22 | a.len(), 23 | b.len(), 24 | "Comparison must be between same number of inputs and outputs" 25 | ); 26 | let n = a.len(); 27 | 28 | let chunk_bits = 2; 29 | let num_chunks = ceil_div_usize(num_bits, chunk_bits); 30 | 31 | let one = builder.one(); 32 | let mut result = one; 33 | for i in 0..n { 34 | let a_le_b_gate = ComparisonGate::new(num_bits, num_chunks); 35 | let a_le_b_row = builder.add_gate(a_le_b_gate.clone(), vec![]); 36 | builder.connect( 37 | Target::wire(a_le_b_row, a_le_b_gate.wire_first_input()), 38 | a[i], 39 | ); 40 | builder.connect( 41 | Target::wire(a_le_b_row, a_le_b_gate.wire_second_input()), 42 | b[i], 43 | ); 44 | let a_le_b_result = Target::wire(a_le_b_row, a_le_b_gate.wire_result_bool()); 45 | 46 | let b_le_a_gate = ComparisonGate::new(num_bits, num_chunks); 47 | let b_le_a_row = builder.add_gate(b_le_a_gate.clone(), vec![]); 48 | builder.connect( 49 | Target::wire(b_le_a_row, b_le_a_gate.wire_first_input()), 50 | b[i], 51 | ); 52 | builder.connect( 53 | Target::wire(b_le_a_row, b_le_a_gate.wire_second_input()), 54 | a[i], 55 | ); 56 | let b_le_a_result = Target::wire(b_le_a_row, b_le_a_gate.wire_result_bool()); 57 | 58 | let these_limbs_equal = builder.mul(a_le_b_result, b_le_a_result); 59 | let these_limbs_less_than = builder.sub(one, b_le_a_result); 60 | result = builder.mul_add(these_limbs_equal, result, these_limbs_less_than); 61 | } 62 | 63 | // `result` being boolean is an invariant, maintained because its new value is always 64 | // `x * result + y`, where `x` and `y` are booleans that are not simultaneously true. 65 | BoolTarget::new_unsafe(result) 66 | } 67 | 68 | /// Helper function for comparing, specifically, lists of `U32Target`s. 69 | pub fn list_le_u32_circuit, const D: usize>( 70 | builder: &mut CircuitBuilder, 71 | a: Vec, 72 | b: Vec, 73 | ) -> BoolTarget { 74 | let a_targets: Vec = a.iter().map(|&t| t.0).collect(); 75 | let b_targets: Vec = b.iter().map(|&t| t.0).collect(); 76 | 77 | list_le_circuit(builder, a_targets, b_targets, 32) 78 | } 79 | 80 | #[cfg(test)] 81 | mod tests { 82 | use anyhow::Result; 83 | use num::BigUint; 84 | use plonky2::field::types::Field; 85 | use plonky2::iop::witness::PartialWitness; 86 | use plonky2::plonk::circuit_data::CircuitConfig; 87 | use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; 88 | use rand::rngs::OsRng; 89 | use rand::Rng; 90 | 91 | use super::*; 92 | 93 | fn test_list_le(size: usize, num_bits: usize) -> Result<()> { 94 | const D: usize = 2; 95 | type C = PoseidonGoldilocksConfig; 96 | type F = >::F; 97 | let config = CircuitConfig::standard_recursion_config(); 98 | let pw = PartialWitness::new(); 99 | let mut builder = CircuitBuilder::::new(config); 100 | 101 | let mut rng = OsRng; 102 | 103 | let lst1: Vec = (0..size) 104 | .map(|_| rng.gen_range(0..(1 << num_bits))) 105 | .collect(); 106 | let lst2: Vec = (0..size) 107 | .map(|_| rng.gen_range(0..(1 << num_bits))) 108 | .collect(); 109 | 110 | let a_biguint = BigUint::from_slice( 111 | &lst1 112 | .iter() 113 | .flat_map(|&x| [x as u32, (x >> 32) as u32]) 114 | .collect::>(), 115 | ); 116 | let b_biguint = BigUint::from_slice( 117 | &lst2 118 | .iter() 119 | .flat_map(|&x| [x as u32, (x >> 32) as u32]) 120 | .collect::>(), 121 | ); 122 | 123 | let a = lst1 124 | .iter() 125 | .map(|&x| builder.constant(F::from_canonical_u64(x))) 126 | .collect(); 127 | let b = lst2 128 | .iter() 129 | .map(|&x| builder.constant(F::from_canonical_u64(x))) 130 | .collect(); 131 | 132 | let result = list_le_circuit(&mut builder, a, b, num_bits); 133 | 134 | let expected_result = builder.constant_bool(a_biguint <= b_biguint); 135 | builder.connect(result.target, expected_result.target); 136 | 137 | let data = builder.build::(); 138 | let proof = data.prove(pw).unwrap(); 139 | data.verify(proof) 140 | } 141 | 142 | #[test] 143 | fn test_multiple_comparison() -> Result<()> { 144 | for size in [1, 3, 6] { 145 | for num_bits in [20, 32, 40, 44] { 146 | test_list_le(size, num_bits).unwrap(); 147 | } 148 | } 149 | 150 | Ok(()) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /plonky2-backend/src/plonky2_ecdsa/biguint/gates/gate_testing.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{ensure, Result}; 2 | 3 | use plonky2::field::extension::{Extendable, FieldExtension}; 4 | use plonky2::field::polynomial::{PolynomialCoeffs, PolynomialValues}; 5 | use plonky2::field::types::{Field, Sample}; 6 | use plonky2::gates::gate::Gate; 7 | use plonky2::hash::hash_types::{HashOut, RichField}; 8 | use plonky2::iop::witness::{PartialWitness, WitnessWrite}; 9 | use plonky2::plonk::circuit_builder::CircuitBuilder; 10 | use plonky2::plonk::circuit_data::CircuitConfig; 11 | use plonky2::plonk::config::GenericConfig; 12 | use plonky2::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBaseBatch}; 13 | use plonky2::util::{log2_ceil, transpose}; 14 | 15 | const WITNESS_SIZE: usize = 1 << 5; 16 | const WITNESS_DEGREE: usize = WITNESS_SIZE - 1; 17 | 18 | /// Tests that the constraints imposed by the given gate are low-degree by applying them to random 19 | /// low-degree witness polynomials. 20 | pub fn test_low_degree, G: Gate, const D: usize>(gate: G) { 21 | let rate_bits = log2_ceil(gate.degree() + 1); 22 | 23 | let wire_ldes = random_low_degree_matrix::(gate.num_wires(), rate_bits); 24 | let constant_ldes = random_low_degree_matrix::(gate.num_constants(), rate_bits); 25 | assert_eq!(wire_ldes.len(), constant_ldes.len()); 26 | let public_inputs_hash = &HashOut::rand(); 27 | 28 | let constraint_evals = wire_ldes 29 | .iter() 30 | .zip(constant_ldes.iter()) 31 | .map(|(local_wires, local_constants)| EvaluationVars { 32 | local_constants, 33 | local_wires, 34 | public_inputs_hash, 35 | }) 36 | .map(|vars| gate.eval_unfiltered(vars)) 37 | .collect::>(); 38 | 39 | let constraint_eval_degrees = transpose(&constraint_evals) 40 | .into_iter() 41 | .map(PolynomialValues::new) 42 | .map(|p| p.degree()) 43 | .collect::>(); 44 | 45 | assert_eq!( 46 | constraint_eval_degrees.len(), 47 | gate.num_constraints(), 48 | "eval should return num_constraints() constraints" 49 | ); 50 | 51 | let expected_eval_degree = WITNESS_DEGREE * gate.degree(); 52 | 53 | assert!( 54 | constraint_eval_degrees 55 | .iter() 56 | .all(|°| deg <= expected_eval_degree), 57 | "Expected degrees at most {} * {} = {}, actual {:?}", 58 | WITNESS_SIZE, 59 | gate.degree(), 60 | expected_eval_degree, 61 | constraint_eval_degrees 62 | ); 63 | } 64 | 65 | fn random_low_degree_matrix(num_polys: usize, rate_bits: usize) -> Vec> { 66 | let polys = (0..num_polys) 67 | .map(|_| random_low_degree_values(rate_bits)) 68 | .collect::>(); 69 | 70 | if polys.is_empty() { 71 | // We want a Vec of many empty Vecs, whereas transpose would just give an empty Vec. 72 | vec![Vec::new(); WITNESS_SIZE << rate_bits] 73 | } else { 74 | transpose(&polys) 75 | } 76 | } 77 | 78 | fn random_low_degree_values(rate_bits: usize) -> Vec { 79 | PolynomialCoeffs::new(F::rand_vec(WITNESS_SIZE)) 80 | .lde(rate_bits) 81 | .fft() 82 | .values 83 | } 84 | 85 | pub fn test_eval_fns< 86 | F: RichField + Extendable, 87 | C: GenericConfig, 88 | G: Gate, 89 | const D: usize, 90 | >( 91 | gate: G, 92 | ) -> Result<()> { 93 | // Test that `eval_unfiltered` and `eval_unfiltered_base` are coherent. 94 | let wires_base = F::rand_vec(gate.num_wires()); 95 | let constants_base = F::rand_vec(gate.num_constants()); 96 | let wires = wires_base 97 | .iter() 98 | .map(|&x| F::Extension::from_basefield(x)) 99 | .collect::>(); 100 | let constants = constants_base 101 | .iter() 102 | .map(|&x| F::Extension::from_basefield(x)) 103 | .collect::>(); 104 | let public_inputs_hash = HashOut::rand(); 105 | 106 | // Batch of 1. 107 | let vars_base_batch = 108 | EvaluationVarsBaseBatch::new(1, &constants_base, &wires_base, &public_inputs_hash); 109 | let vars = EvaluationVars { 110 | local_constants: &constants, 111 | local_wires: &wires, 112 | public_inputs_hash: &public_inputs_hash, 113 | }; 114 | 115 | let evals_base = gate.eval_unfiltered_base_batch(vars_base_batch); 116 | let evals = gate.eval_unfiltered(vars); 117 | // This works because we have a batch of 1. 118 | ensure!( 119 | evals 120 | == evals_base 121 | .into_iter() 122 | .map(F::Extension::from_basefield) 123 | .collect::>() 124 | ); 125 | 126 | // Test that `eval_unfiltered` and `eval_unfiltered_recursively` are coherent. 127 | let wires = F::Extension::rand_vec(gate.num_wires()); 128 | let constants = F::Extension::rand_vec(gate.num_constants()); 129 | 130 | let config = CircuitConfig::standard_recursion_config(); 131 | let mut pw = PartialWitness::new(); 132 | let mut builder = CircuitBuilder::::new(config); 133 | 134 | let wires_t = builder.add_virtual_extension_targets(wires.len()); 135 | let constants_t = builder.add_virtual_extension_targets(constants.len()); 136 | pw.set_extension_targets(&wires_t, &wires); 137 | pw.set_extension_targets(&constants_t, &constants); 138 | let public_inputs_hash_t = builder.add_virtual_hash(); 139 | pw.set_hash_target(public_inputs_hash_t, public_inputs_hash); 140 | 141 | let vars = EvaluationVars { 142 | local_constants: &constants, 143 | local_wires: &wires, 144 | public_inputs_hash: &public_inputs_hash, 145 | }; 146 | let evals = gate.eval_unfiltered(vars); 147 | 148 | let vars_t = EvaluationTargets { 149 | local_constants: &constants_t, 150 | local_wires: &wires_t, 151 | public_inputs_hash: &public_inputs_hash_t, 152 | }; 153 | let evals_t = gate.eval_unfiltered_circuit(&mut builder, vars_t); 154 | pw.set_extension_targets(&evals_t, &evals); 155 | 156 | let data = builder.build::(); 157 | let proof = data.prove(pw)?; 158 | data.verify(proof) 159 | } 160 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/ecdsa_secp256k1_translator.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::plonky2_ecdsa::biguint::biguint::{BigUintTarget, CircuitBuilderBiguint}; 3 | use crate::plonky2_ecdsa::biguint::gadgets::nonnative::{CircuitBuilderNonNative, NonNativeTarget}; 4 | use crate::plonky2_ecdsa::curve::gadgets::curve::{AffinePointTarget, CircuitBuilderCurve}; 5 | use plonky2::field::secp256k1_base::Secp256K1Base; 6 | use plonky2::field::secp256k1_scalar::Secp256K1Scalar; 7 | use crate::plonky2_ecdsa::biguint::gadgets::arithmetic_u32::U32Target; 8 | use crate::plonky2_ecdsa::curve::gadgets::glv::CircuitBuilderGlv; 9 | 10 | pub struct EcdsaSecp256k1Translator<'a> { 11 | circuit_builder: &'a mut CircuitBuilderFromAcirToPlonky2, 12 | hashed_msg: &'a Box<[FunctionInput; 32]>, 13 | public_key_x: &'a Box<[FunctionInput; 32]>, 14 | public_key_y: &'a Box<[FunctionInput; 32]>, 15 | signature: &'a Box<[FunctionInput; 64]>, 16 | output: Witness, 17 | } 18 | 19 | impl<'a> EcdsaSecp256k1Translator<'a> { 20 | pub fn new_for( 21 | circuit_builder: &'a mut CircuitBuilderFromAcirToPlonky2, 22 | hashed_msg: &'a Box<[FunctionInput; 32]>, 23 | public_key_x: &'a Box<[FunctionInput; 32]>, 24 | public_key_y: &'a Box<[FunctionInput; 32]>, 25 | signature: &'a Box<[FunctionInput; 64]>, 26 | output: Witness, 27 | ) -> EcdsaSecp256k1Translator<'a> { 28 | Self { 29 | circuit_builder, 30 | hashed_msg, 31 | public_key_x, 32 | public_key_y, 33 | signature, 34 | output, 35 | } 36 | } 37 | 38 | pub fn translate(&mut self) { 39 | let public_key = self._public_key_target(); 40 | let r = self._32_bytes_to_field_element::( 41 | self.signature.as_ref()[0..32].to_vec() 42 | ); 43 | 44 | let s = self._32_bytes_to_field_element::( 45 | self.signature[32..64].to_vec() 46 | ); 47 | let h = self._32_bytes_to_field_element::( 48 | self.hashed_msg.to_vec() 49 | ); 50 | 51 | let r_point = self._calculate_r(&public_key, &r, &s, &h); 52 | 53 | let does_signature_verify = self.circuit_builder.builder.cmp_biguint( 54 | &r.value, &r_point.x.value, 55 | ); 56 | 57 | let output_target = self.circuit_builder.target_for_witness(self.output); 58 | self.circuit_builder.builder.connect(does_signature_verify.target, output_target); 59 | } 60 | 61 | fn _calculate_r( 62 | &mut self, 63 | public_key: &AffinePointTarget, 64 | r: &NonNativeTarget, 65 | s: &NonNativeTarget, 66 | h: &NonNativeTarget, 67 | ) -> AffinePointTarget { 68 | let s1 = self.circuit_builder.builder.inv_nonnative(&s); 69 | // let s1 = self.circuit_builder.builder.add_virtual_nonnative_target(); 70 | 71 | let u_1 = self.circuit_builder.builder.mul_nonnative(&h, &s1); 72 | let u_2 = self.circuit_builder.builder.mul_nonnative(&r, &s1); 73 | 74 | let generator = self.circuit_builder.builder.curve_generator_constant(); 75 | let r_factor_1 = self.circuit_builder.builder.glv_mul( 76 | &generator, &u_1, 77 | ); 78 | 79 | let r_factor_2 = self.circuit_builder.builder.glv_mul( 80 | &public_key, &u_2, 81 | ); 82 | 83 | let r_point = self.circuit_builder.builder.curve_add( 84 | &r_factor_1, &r_factor_2, 85 | ); 86 | r_point 87 | } 88 | 89 | fn _public_key_target(&mut self) -> AffinePointTarget { 90 | let x = self._32_bytes_to_field_element::(self.public_key_x.to_vec()); 91 | let y = self._32_bytes_to_field_element::(self.public_key_y.to_vec()); 92 | AffinePointTarget { x, y } 93 | } 94 | 95 | fn _32_bytes_to_field_element( 96 | &mut self, byte_inputs: Vec, 97 | ) -> NonNativeTarget { 98 | let byte_targets: Vec = byte_inputs.iter().map(|i| { 99 | self.circuit_builder._get_or_create_target_for_witness(i.witness) 100 | }).collect(); 101 | 102 | let mut u32_limbs: Vec = Vec::new(); 103 | for u32_target_index in 0..8 { 104 | let target_0 = byte_targets[4*u32_target_index]; 105 | let target_1 = byte_targets[4*u32_target_index+1]; 106 | let target_1_papota = self.circuit_builder.builder.mul_const(F::from_canonical_u64(1<<8), target_1); 107 | let target_2 = byte_targets[4*u32_target_index+2]; 108 | let target_2_papota = self.circuit_builder.builder.mul_const(F::from_canonical_u64(1<<16), target_2); 109 | let target_3 = byte_targets[4*u32_target_index+3]; 110 | let target_3_papota = self.circuit_builder.builder.mul_const(F::from_canonical_u64(1<<24), target_3); 111 | 112 | let target = self.circuit_builder.builder.add_many([ 113 | target_0, target_1_papota, target_2_papota, target_3_papota 114 | ]); 115 | let u32_target = U32Target(target); 116 | u32_limbs.push(u32_target); 117 | } 118 | let bui_target = BigUintTarget{ limbs: u32_limbs }; 119 | let nonnative_target = self.circuit_builder.builder.biguint_to_nonnative(&bui_target); 120 | nonnative_target 121 | } 122 | 123 | fn _connect_32_bytes_with_256_bits(&mut self, bytes_targets: Vec, bits_targets: Vec) { 124 | for index_byte in 0..32 { 125 | for index_bit in 0..8 { 126 | let index_bit_in_biguint = 8 * index_byte + index_bit; 127 | self.circuit_builder.builder.connect( 128 | bits_targets[index_bit_in_biguint].target, 129 | bytes_targets[index_byte].bits[index_bit].target, 130 | ); 131 | } 132 | } 133 | } 134 | 135 | fn _inputs_to_targets(&mut self, inputs: Vec) -> Vec { 136 | inputs 137 | .iter() 138 | .map(|input| { 139 | self.circuit_builder.binary_number_target_for_witness(input.witness, 8) 140 | }) 141 | .collect() 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /plonky2-backend/src/binary_digits_target.rs: -------------------------------------------------------------------------------- 1 | use plonky2::field::types::Field; 2 | use plonky2::iop::target::BoolTarget; 3 | 4 | use crate::circuit_translation::CB; 5 | use crate::F; 6 | 7 | /// This module provides a BinaryDigitsTarget object. It's main goal is to represent numbers as 8 | /// its bit decomposition, so we can perform bitwise operations. 9 | /// TODO: Some operations might be optimized (https://github.com/JumpCrypto/plonky2-crypto/tree/main/src/u32/gates) 10 | #[derive(Clone, Debug)] 11 | pub struct BinaryDigitsTarget { 12 | pub bits: Vec, 13 | } 14 | 15 | impl BinaryDigitsTarget { 16 | pub(crate) fn number_of_digits(&self) -> usize { 17 | self.bits.len() 18 | } 19 | 20 | pub fn rotate_right( 21 | binary_target: &BinaryDigitsTarget, 22 | times: usize, 23 | builder: &mut CB, 24 | ) -> BinaryDigitsTarget { 25 | let mut new_bits = Vec::new(); 26 | 27 | for i in (binary_target.number_of_digits() - times)..binary_target.number_of_digits() { 28 | let new_bool_target = builder.add_virtual_bool_target_safe(); 29 | builder.connect(binary_target.bits[i].target, new_bool_target.target); 30 | new_bits.push(new_bool_target); 31 | } 32 | 33 | for i in 0..(binary_target.number_of_digits() - times) { 34 | let new_bool_target = builder.add_virtual_bool_target_safe(); 35 | builder.connect(binary_target.bits[i].target, new_bool_target.target); 36 | new_bits.push(new_bool_target); 37 | } 38 | 39 | BinaryDigitsTarget { bits: new_bits } 40 | } 41 | 42 | pub fn shift_right( 43 | target: &BinaryDigitsTarget, 44 | times: usize, 45 | builder: &mut CB, 46 | ) -> BinaryDigitsTarget { 47 | let mut new_bits = Vec::new(); 48 | 49 | for _ in 0..times { 50 | new_bits.push(BoolTarget::new_unsafe( 51 | builder.constant(F::from_canonical_u8(0)), 52 | )); 53 | } 54 | for i in 0..target.number_of_digits() - times { 55 | let new_bool_target = builder.add_virtual_bool_target_safe(); 56 | builder.connect(target.bits[i].target, new_bool_target.target); 57 | new_bits.push(new_bool_target); 58 | } 59 | // Fill zero bits 60 | 61 | 62 | BinaryDigitsTarget { bits: new_bits } 63 | } 64 | 65 | pub fn choose( 66 | chooser: &BinaryDigitsTarget, 67 | on_true: &BinaryDigitsTarget, 68 | on_false: &BinaryDigitsTarget, 69 | builder: &mut CB, 70 | ) -> BinaryDigitsTarget { 71 | let bit_pairs_iter = on_true.bits.iter().zip(on_false.bits.iter()); 72 | 73 | let chosen_bits = chooser 74 | .bits 75 | .iter() 76 | .zip(bit_pairs_iter) 77 | .map(|(c, (t, f))| BinaryDigitsTarget::select_bool_target(c, t, f, builder)) 78 | .collect(); 79 | 80 | BinaryDigitsTarget { bits: chosen_bits } 81 | } 82 | 83 | pub fn majority( 84 | a: &BinaryDigitsTarget, 85 | b: &BinaryDigitsTarget, 86 | c: &BinaryDigitsTarget, 87 | builder: &mut CB, 88 | ) -> BinaryDigitsTarget { 89 | let bit_pairs_iter = a.bits.iter().zip(b.bits.iter()); 90 | 91 | let majority_bits = c 92 | .bits 93 | .iter() 94 | .zip(bit_pairs_iter) 95 | .map(|(b0, (b1, b2))| { 96 | let on_true = BinaryDigitsTarget::bit_or(*b1, *b2, builder); 97 | let on_false = BinaryDigitsTarget::bit_and(*b1, *b2, builder); 98 | BinaryDigitsTarget::select_bool_target(b0, &on_true, &on_false, builder) 99 | }) 100 | .collect(); 101 | BinaryDigitsTarget { 102 | bits: majority_bits, 103 | } 104 | } 105 | 106 | pub fn select_bool_target( 107 | chooser: &BoolTarget, 108 | on_true: &BoolTarget, 109 | on_false: &BoolTarget, 110 | builder: &mut CB, 111 | ) -> BoolTarget { 112 | let target = builder.select(*chooser, on_true.target, on_false.target); 113 | BoolTarget::new_unsafe(target) 114 | } 115 | 116 | pub fn xor( 117 | b1: BinaryDigitsTarget, 118 | b2: BinaryDigitsTarget, 119 | builder: &mut CB, 120 | ) -> BinaryDigitsTarget { 121 | BinaryDigitsTarget::apply_bitwise_to_binary_digits_target( 122 | b1, 123 | b2, 124 | builder, 125 | BinaryDigitsTarget::bit_xor, 126 | ) 127 | } 128 | 129 | pub fn and( 130 | b1: BinaryDigitsTarget, 131 | b2: BinaryDigitsTarget, 132 | builder: &mut CB, 133 | ) -> BinaryDigitsTarget { 134 | BinaryDigitsTarget::apply_bitwise_to_binary_digits_target( 135 | b1, 136 | b2, 137 | builder, 138 | BinaryDigitsTarget::bit_and, 139 | ) 140 | } 141 | 142 | pub fn apply_bitwise_to_binary_digits_target( 143 | b1: BinaryDigitsTarget, 144 | b2: BinaryDigitsTarget, 145 | builder: &mut CB, 146 | op: fn(BoolTarget, BoolTarget, &mut CB) -> BoolTarget, 147 | ) -> BinaryDigitsTarget { 148 | BinaryDigitsTarget { 149 | bits: BinaryDigitsTarget::apply_bitwise_and_output_bool_targets(&b1, &b2, builder, op), 150 | } 151 | } 152 | 153 | pub fn apply_bitwise_and_output_bool_targets( 154 | b1: &BinaryDigitsTarget, 155 | b2: &BinaryDigitsTarget, 156 | builder: &mut CB, 157 | op: fn(BoolTarget, BoolTarget, &mut CB) -> BoolTarget, 158 | ) -> Vec { 159 | b1.bits 160 | .iter() 161 | .zip(b2.bits.iter()) 162 | .map(|(bit1, bit2)| op(*bit1, *bit2, builder)) 163 | .collect() 164 | } 165 | 166 | pub fn bit_and(b1: BoolTarget, b2: BoolTarget, builder: &mut CB) -> BoolTarget { 167 | builder.and(b1, b2) 168 | } 169 | 170 | pub fn bit_or(b1: BoolTarget, b2: BoolTarget, builder: &mut CB) -> BoolTarget { 171 | builder.or(b1, b2) 172 | } 173 | 174 | pub fn bit_xor(b1: BoolTarget, b2: BoolTarget, builder: &mut CB) -> BoolTarget { 175 | // a xor b = (a or b) and (not (a and b)) 176 | let b1_or_b2 = builder.or(b1, b2); 177 | let b1_and_b2 = builder.and(b1, b2); 178 | let not_b1_and_b2 = builder.not(b1_and_b2); 179 | builder.and(b1_or_b2, not_b1_and_b2) 180 | } 181 | 182 | pub fn add_module_32_bits( 183 | b1: &BinaryDigitsTarget, 184 | b2: &BinaryDigitsTarget, 185 | builder: &mut CB, 186 | ) -> BinaryDigitsTarget { 187 | assert_eq!(b1.number_of_digits(), b2.number_of_digits()); 188 | let partial_sum = BinaryDigitsTarget::apply_bitwise_and_output_bool_targets( 189 | &b1, 190 | &b2, 191 | builder, 192 | BinaryDigitsTarget::bit_xor, 193 | ); 194 | let partial_carries = BinaryDigitsTarget::apply_bitwise_and_output_bool_targets( 195 | &b1, 196 | &b2, 197 | builder, 198 | BinaryDigitsTarget::bit_and, 199 | ); 200 | 201 | let mut carry_in = builder._false(); 202 | let mut sum: Vec = Vec::new(); 203 | for idx_bit in (0..b1.number_of_digits()).rev() { 204 | let sum_with_carry_in = 205 | BinaryDigitsTarget::bit_xor(partial_sum[idx_bit], carry_in, builder); 206 | let pair_sum = BinaryDigitsTarget::bit_and(carry_in, partial_sum[idx_bit], builder); 207 | let carry_out = 208 | BinaryDigitsTarget::bit_or(partial_carries[idx_bit], pair_sum, builder); 209 | carry_in = carry_out; // The new carry_in is the current carry_out 210 | sum.push(sum_with_carry_in); 211 | } 212 | sum.reverse(); 213 | BinaryDigitsTarget { bits: sum } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/memory_translator.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// This module performs memory operations such as creating blocks of memory, reading in a specific 4 | /// position known in prove time and write a value in a position both known at prove time. The 5 | /// length of the blocks is fixed and known in circuit building time. 6 | /// 7 | /// The desired length of a memory block may not coincide with the length of the associated vector 8 | /// of targets representation. This is because we must append some zeroes at the end for making the 9 | /// length a power of 2, therefore the memory_blocks collaborator must hold the length of each 10 | /// memory block. 11 | pub struct MemoryOperationsTranslator<'a> { 12 | builder: &'a mut CircuitBuilder, 13 | witness_target_map: &'a mut HashMap, 14 | memory_blocks: &'a mut HashMap, usize)>, 15 | } 16 | 17 | impl<'a> MemoryOperationsTranslator<'a> { 18 | pub fn new_for( 19 | builder: &'a mut CircuitBuilder, 20 | witness_target_map: &'a mut HashMap, 21 | memory_blocks: &'a mut HashMap, usize)>, 22 | ) -> Self { 23 | Self { 24 | builder, 25 | witness_target_map, 26 | memory_blocks, 27 | } 28 | } 29 | 30 | pub fn translate_memory_op(&mut self, block_id: &BlockId, op: &MemOp) { 31 | self._register_intermediate_witnesses_for_memory_op(&op); 32 | 33 | let witness_index_to_access = op.index.to_witness().unwrap(); 34 | let target_index_to_access = 35 | self._get_or_create_target_for_witness(witness_index_to_access); 36 | MemoryOperationsTranslator::add_restrictions_to_assert_target_is_less_or_equal_to( 37 | self.memory_blocks.get(block_id).unwrap().1 - 1, 38 | target_index_to_access, 39 | &mut self.builder, 40 | ); 41 | 42 | let is_memory_read = op.clone().operation.to_const().unwrap().is_zero(); 43 | let is_memory_write = op.clone().operation.to_const().unwrap().is_one(); 44 | if is_memory_read { 45 | self._translate_memory_read(block_id, op); 46 | } else if is_memory_write { 47 | self._translate_memory_write(&block_id, op); 48 | } else { 49 | panic!("Backend encountered unknown memory operation code (nor 0 or 1)"); 50 | } 51 | } 52 | 53 | /// We use this algorithm to validate in-range access and restrain access to one of the padded 54 | /// positions in the target vector. 55 | pub fn add_restrictions_to_assert_target_is_less_or_equal_to( 56 | max_allowed_value: usize, 57 | target_index: Target, 58 | builder: &mut CB, 59 | ) { 60 | let binary_representation: Vec = format!("{:b}", max_allowed_value) 61 | .chars() 62 | .map(|c| c.to_digit(2).unwrap() as u8) 63 | .collect(); 64 | 65 | let binary_target_index = BinaryDigitsTarget { 66 | bits: builder 67 | .split_le(target_index, binary_representation.len()) 68 | .into_iter() 69 | .rev() 70 | .collect(), 71 | }; 72 | 73 | let mut acc_target = builder.one(); 74 | for i in 0..binary_target_index.number_of_digits() { 75 | if binary_representation[i] == 0 { 76 | let aux = builder.mul(binary_target_index.bits[i].target, acc_target); 77 | builder.assert_zero(aux); 78 | } else if binary_representation[i] == 1 { 79 | let new_acc_target = builder.mul(acc_target, binary_target_index.bits[i].target); 80 | acc_target = new_acc_target; 81 | } 82 | } 83 | } 84 | 85 | /// The problem is that we cannot know which target is going to be replaced in circuit-building 86 | /// time. The solution, replacing all the targets, connecting all the values except for the 87 | /// one modified. To know what position is being modified, we use the Plonky2 EqualGate. 88 | /// The key is that the circuit has to be symmetrical for all possible values. 89 | fn _translate_memory_write(&mut self, block_id: &BlockId, op: &MemOp) { 90 | let witness_idx_to_write = op.index.to_witness().unwrap(); 91 | let target_idx_to_write = self._get_or_create_target_for_witness(witness_idx_to_write); 92 | let witness_holding_new_value = op.value.to_witness().unwrap(); 93 | let target_holding_new_value = 94 | self._get_or_create_target_for_witness(witness_holding_new_value); 95 | 96 | let memory_block_length = (&self.memory_blocks[block_id].0).len(); 97 | for position in 0..memory_block_length { 98 | let target_with_position = self.builder.constant(F::from_canonical_usize(position)); 99 | let is_current_position_being_modified = self 100 | .builder 101 | .is_equal(target_idx_to_write, target_with_position); 102 | 103 | let current_target_in_position = self.memory_blocks[block_id].0[position]; 104 | let new_target_in_array = self.builder._if( 105 | is_current_position_being_modified, 106 | target_holding_new_value, 107 | current_target_in_position, 108 | ); 109 | 110 | self.memory_blocks.get_mut(block_id).unwrap().0[position] = new_target_in_array; 111 | } 112 | } 113 | 114 | /// For this Plonky2 uses the RandomAccessGate 115 | fn _translate_memory_read(&mut self, block_id: &BlockId, op: &MemOp) { 116 | let witness_idx_to_read = op.index.to_witness().unwrap(); 117 | let target_idx_to_read = self._get_or_create_target_for_witness(witness_idx_to_read); 118 | let witness_to_save_result = op.value.to_witness().unwrap(); 119 | let block_of_memory = self.memory_blocks[block_id].0.clone(); 120 | let target_to_save_result = self 121 | .builder 122 | .random_access(target_idx_to_read, block_of_memory); 123 | self.witness_target_map 124 | .insert(witness_to_save_result, target_to_save_result); 125 | } 126 | 127 | /// Creates a new block of memory with the associated id 128 | pub fn translate_memory_init(&mut self, init: &Vec, block_id: &BlockId) { 129 | let mut vector_targets: Vec = init 130 | .into_iter() 131 | .map(|w| self._get_or_create_target_for_witness(*w)) 132 | .collect(); 133 | let real_memory_block_size = vector_targets.len(); 134 | self._extend_block_with_zeroes_to_have_a_power_of_two_length(&mut vector_targets); 135 | self.memory_blocks 136 | .insert(*block_id, (vector_targets, real_memory_block_size)); 137 | } 138 | 139 | /// This is necessary because plonky2 can only perform a random_access operation 140 | /// on vectors with a length that is a power of two. 141 | fn _extend_block_with_zeroes_to_have_a_power_of_two_length( 142 | &mut self, 143 | vector_targets: &mut Vec, 144 | ) { 145 | let length_of_block = vector_targets.len(); 146 | let targets_to_add = (length_of_block as u32) 147 | .checked_next_power_of_two() 148 | .unwrap_or(0) 149 | - length_of_block as u32; 150 | vector_targets.extend((0..targets_to_add).into_iter().map(|_| self.builder.zero())); 151 | } 152 | 153 | fn _register_intermediate_witnesses_for_memory_op(self: &mut Self, op: &MemOp) { 154 | let at = &op.index.linear_combinations[0].1; 155 | self._get_or_create_target_for_witness(*at); 156 | 157 | let value = &op.value.linear_combinations[0].1; 158 | self._get_or_create_target_for_witness(*value); 159 | } 160 | 161 | fn _get_or_create_target_for_witness(&mut self, witness: Witness) -> Target { 162 | match self.witness_target_map.get(&witness) { 163 | Some(target) => *target, 164 | None => { 165 | let target = self.builder.add_virtual_target(); 166 | self.witness_target_map.insert(witness, target); 167 | target 168 | } 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /plonky2-backend/src/argument_parsing.rs: -------------------------------------------------------------------------------- 1 | use crate::actions; 2 | use clap::{value_parser, Arg, ArgMatches, Command}; 3 | use std::path::PathBuf; 4 | 5 | /// Commands: prove, write_vk, verify. 6 | /// prove -b circuit/path -w witness/path.gz -o output/proof/path 7 | /// write_vk -b circuit/path -o output/verification/key/path 8 | /// verify -k verification/key/path -p proof/path 9 | 10 | pub fn parse_and_execute_commands() { 11 | let prove_command = _create_prove_command(); 12 | let write_vk_command = _create_write_vk_command(); 13 | let verify_command = _create_verify_command(); 14 | 15 | let main_command = Command::new("plonky2_backend") 16 | .subcommand_required(true) 17 | .subcommand(prove_command.clone()) 18 | .subcommand(write_vk_command.clone()) 19 | .subcommand(verify_command.clone()); 20 | 21 | _match_command_values( 22 | prove_command, 23 | write_vk_command, 24 | verify_command, 25 | main_command, 26 | ); 27 | } 28 | 29 | fn _match_command_values( 30 | prove_command: Command, 31 | write_vk_command: Command, 32 | verify_command: Command, 33 | main_command: Command, 34 | ) { 35 | let matches = main_command.get_matches(); 36 | if let Some(subcommand_matches) = matches.subcommand_matches(prove_command.get_name()) { 37 | let circuit_path = _get_argument_value(subcommand_matches, _prove_argument_circuit_path()); 38 | let witness_path = _get_argument_value(subcommand_matches, _prove_argument_witness_path()); 39 | let output_path = _get_argument_value(subcommand_matches, _prove_argument_output_path()); 40 | 41 | _execute_prove_command(circuit_path, witness_path, output_path); 42 | } else if let Some(subcommand_matches) = matches.subcommand_matches(write_vk_command.get_name()) 43 | { 44 | let circuit_path = 45 | _get_argument_value(subcommand_matches, _write_vk_argument_circuit_path()); 46 | let output_path = _get_argument_value(subcommand_matches, _write_vk_argument_output_path()); 47 | 48 | _execute_write_vk_command(circuit_path, output_path); 49 | } else if let Some(subcommand_matches) = matches.subcommand_matches(verify_command.get_name()) { 50 | let vk_path = _get_argument_value(subcommand_matches, _verify_argument_vk_path()); 51 | let proof_path = _get_argument_value(subcommand_matches, _verify_argument_proof()); 52 | 53 | _execute_verify_command(vk_path, proof_path); 54 | } 55 | } 56 | 57 | fn create_command_argument( 58 | argument_id: &'static str, 59 | short_identifier: char, 60 | long_identifier: &'static str, 61 | short_help: &'static str, 62 | long_help: &'static str, 63 | ) -> Arg { 64 | Arg::new(argument_id) 65 | .help(short_help) 66 | .long_help(long_help) 67 | .short(short_identifier) 68 | .long(long_identifier) 69 | .required(true) 70 | .action(clap::ArgAction::Set) 71 | .value_parser(value_parser!(PathBuf)) 72 | } 73 | 74 | fn create_command_from_arguments(command_name: &'static str, args: Vec) -> Command { 75 | args.iter() 76 | .fold(Command::new(command_name), |acc_command, arg| { 77 | acc_command.arg(arg) 78 | }) 79 | } 80 | 81 | fn _get_argument_value(subcommand_matches: &ArgMatches, argument: Arg) -> &PathBuf { 82 | subcommand_matches 83 | .get_one::(argument.get_id().to_string().as_str()) 84 | .expect("Value for command not found") 85 | } 86 | 87 | fn _create_prove_command() -> Command { 88 | let prove_command_name = "prove"; 89 | let prove_command = create_command_from_arguments( 90 | prove_command_name, 91 | vec![ 92 | _prove_argument_circuit_path(), 93 | _prove_argument_witness_path(), 94 | _prove_argument_output_path(), 95 | ], 96 | ); 97 | prove_command 98 | } 99 | 100 | fn _create_write_vk_command() -> Command { 101 | let write_vk_command_name = "write_vk"; 102 | let prove_command = create_command_from_arguments( 103 | write_vk_command_name, 104 | vec![ 105 | _write_vk_argument_circuit_path(), 106 | _write_vk_argument_output_path(), 107 | ], 108 | ); 109 | prove_command 110 | } 111 | 112 | fn _create_verify_command() -> Command { 113 | let verify_command_name = "verify"; 114 | let prove_command = create_command_from_arguments( 115 | verify_command_name, 116 | vec![_verify_argument_vk_path(), _verify_argument_proof()], 117 | ); 118 | prove_command 119 | } 120 | 121 | fn _prove_argument_circuit_path() -> Arg { 122 | let argument_id = "circuit_path"; 123 | let short_command_identifier = 'b'; 124 | let long_command_identifier = "circuit-path"; 125 | let short_help = "Path to the generated ACIR circuit"; 126 | let long_help = ""; 127 | create_command_argument( 128 | argument_id, 129 | short_command_identifier, 130 | long_command_identifier, 131 | short_help, 132 | long_help, 133 | ) 134 | } 135 | 136 | fn _prove_argument_witness_path() -> Arg { 137 | let argument_id = "witness_path"; 138 | let short_command_identifier = 'w'; 139 | let long_command_identifier = "witness-path"; 140 | let short_help = "Path to the generated witness values"; 141 | let long_help = ""; 142 | create_command_argument( 143 | argument_id, 144 | short_command_identifier, 145 | long_command_identifier, 146 | short_help, 147 | long_help, 148 | ) 149 | } 150 | 151 | fn _prove_argument_output_path() -> Arg { 152 | let argument_id = "output_path"; 153 | let short_command_identifier = 'o'; 154 | let long_command_identifier = "output-path"; 155 | let short_help = "Path where the generated proof is to be stored"; 156 | let long_help = ""; 157 | create_command_argument( 158 | argument_id, 159 | short_command_identifier, 160 | long_command_identifier, 161 | short_help, 162 | long_help, 163 | ) 164 | } 165 | 166 | fn _write_vk_argument_circuit_path() -> Arg { 167 | let argument_id = "circuit_path"; 168 | let short_command_identifier = 'b'; 169 | let long_command_identifier = "circuit-path"; 170 | let short_help = "Path to the generated ACIR circuit"; 171 | let long_help = ""; 172 | create_command_argument( 173 | argument_id, 174 | short_command_identifier, 175 | long_command_identifier, 176 | short_help, 177 | long_help, 178 | ) 179 | } 180 | 181 | fn _write_vk_argument_output_path() -> Arg { 182 | let argument_id = "output_path"; 183 | let short_command_identifier = 'o'; 184 | let long_command_identifier = "output-path"; 185 | let short_help = "Path to the generated verification key"; 186 | let long_help = ""; 187 | create_command_argument( 188 | argument_id, 189 | short_command_identifier, 190 | long_command_identifier, 191 | short_help, 192 | long_help, 193 | ) 194 | } 195 | 196 | fn _verify_argument_vk_path() -> Arg { 197 | let argument_id = "vk_path"; 198 | let short_command_identifier = 'k'; 199 | let long_command_identifier = "vk-path"; 200 | let short_help = "Path to the verification key"; 201 | let long_help = ""; 202 | create_command_argument( 203 | argument_id, 204 | short_command_identifier, 205 | long_command_identifier, 206 | short_help, 207 | long_help, 208 | ) 209 | } 210 | 211 | fn _verify_argument_proof() -> Arg { 212 | let argument_id = "proof_path"; 213 | let short_command_identifier = 'p'; 214 | let long_command_identifier = "proof-path"; 215 | let short_help = "Path to the proof"; 216 | let long_help = ""; 217 | create_command_argument( 218 | argument_id, 219 | short_command_identifier, 220 | long_command_identifier, 221 | short_help, 222 | long_help, 223 | ) 224 | } 225 | 226 | fn _execute_prove_command(circuit_path: &PathBuf, witness_path: &PathBuf, output_path: &PathBuf) { 227 | actions::prove_action::ProveAction { 228 | acir_program_json_path: String::from(circuit_path.to_str().unwrap()), 229 | witness_stack_zip_path: String::from(witness_path.to_str().unwrap()), 230 | resulting_proof_file_path: String::from(output_path.to_str().unwrap()), 231 | } 232 | .run(); 233 | } 234 | 235 | fn _execute_write_vk_command(circuit_path: &PathBuf, output_path: &PathBuf) { 236 | actions::write_vk_action::WriteVKAction { 237 | acir_program_json_path: String::from(circuit_path.to_str().unwrap()), 238 | vk_path_output: String::from(output_path.to_str().unwrap()), 239 | } 240 | .run() 241 | } 242 | 243 | fn _execute_verify_command(vk_path: &PathBuf, proof_path: &PathBuf) { 244 | actions::verify_action::VerifyAction { 245 | proof_path: String::from(proof_path.to_str().unwrap()), 246 | vk_path: String::from(vk_path.to_str().unwrap()), 247 | } 248 | .run() 249 | } 250 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/test_blackbox.rs: -------------------------------------------------------------------------------- 1 | use plonky2::field::goldilocks_field::GoldilocksField; 2 | 3 | use crate::circuit_translation::tests::factories::{circuit_factory, utils}; 4 | 5 | use super::*; 6 | 7 | #[test] 8 | fn test_backend_can_translate_blackbox_func_call_range_check_u8() { 9 | let max_num_bits = 8; 10 | let max_allowed_witness_value = 2u16.pow(max_num_bits.clone()) - 1; 11 | let max_allowed_witness_value_field = 12 | F::from_noncanonical_u64(max_allowed_witness_value.into()); 13 | test_range_check_with_witness_value(max_allowed_witness_value_field, max_num_bits); 14 | } 15 | 16 | #[test] 17 | #[should_panic] 18 | fn test_backend_cannot_provide_witness_value_bigger_than_u8_for_u8_range_check() { 19 | let max_num_bits = 8; 20 | let min_not_allowed_witness_value = 2u16.pow(max_num_bits.clone()); 21 | let min_not_allowed_witness_value_field = 22 | F::from_noncanonical_u64(min_not_allowed_witness_value.into()); 23 | test_range_check_with_witness_value(min_not_allowed_witness_value_field, max_num_bits); 24 | } 25 | 26 | #[test] 27 | fn test_backend_can_translate_blackbox_func_call_range_check_u16() { 28 | let max_num_bits = 16; 29 | let max_allowed_witness_value = 2u32.pow(max_num_bits.clone()) - 1; 30 | let max_allowed_witness_value_field = 31 | F::from_noncanonical_u64(max_allowed_witness_value.into()); 32 | test_range_check_with_witness_value(max_allowed_witness_value_field, max_num_bits); 33 | } 34 | 35 | #[test] 36 | #[should_panic] 37 | fn test_backend_cannot_provide_witness_value_bigger_than_u16_for_u16_range_check() { 38 | let max_num_bits = 16; 39 | let min_not_allowed_witness_value = 2u32.pow(max_num_bits.clone()); 40 | let min_not_allowed_witness_value_field = 41 | F::from_noncanonical_u64(min_not_allowed_witness_value.into()); 42 | test_range_check_with_witness_value(min_not_allowed_witness_value_field, max_num_bits); 43 | } 44 | 45 | #[test] 46 | fn test_backend_can_translate_blackbox_func_call_range_check_u32() { 47 | let max_num_bits = 32; 48 | let max_allowed_witness_value = 2u64.pow(max_num_bits.clone()) - 1; 49 | let max_allowed_witness_value_field = 50 | F::from_noncanonical_u64(max_allowed_witness_value.into()); 51 | test_range_check_with_witness_value(max_allowed_witness_value_field, max_num_bits); 52 | } 53 | 54 | #[test] 55 | #[should_panic] 56 | fn test_backend_cannot_provide_witness_value_bigger_than_u32_for_u32_range_check() { 57 | let max_num_bits = 32; 58 | let min_not_allowed_witness_value = 2u64.pow(max_num_bits.clone()); 59 | let min_not_allowed_witness_value_field = 60 | F::from_noncanonical_u64(min_not_allowed_witness_value.into()); 61 | test_range_check_with_witness_value(min_not_allowed_witness_value_field, max_num_bits); 62 | } 63 | 64 | #[test] 65 | fn test_backend_can_translate_blackbox_func_call_range_check_u33() { 66 | // This was needed for the std sha256 noir function. 67 | let max_num_bits = 33; 68 | let max_allowed_witness_value = 2u64.pow(max_num_bits.clone()) - 1; 69 | let max_allowed_witness_value_field = 70 | F::from_noncanonical_u64(max_allowed_witness_value.into()); 71 | test_range_check_with_witness_value(max_allowed_witness_value_field, max_num_bits); 72 | } 73 | 74 | #[test] 75 | #[should_panic( 76 | expected = "Range checks with more than 33 bits are not allowed yet while using Plonky2 prover" 77 | )] 78 | fn test_backend_does_not_support_range_check_for_u64_or_bigger() { 79 | let max_num_bits = 64; 80 | let goldilocks_max_value = (2u128.pow(64) - 2u128.pow(32)) as u64; 81 | let goldilocks_max_value_field = F::from_noncanonical_u64(goldilocks_max_value.into()); 82 | test_range_check_with_witness_value(goldilocks_max_value_field, max_num_bits); 83 | } 84 | 85 | fn test_range_check_with_witness_value(witness_value: F, max_num_bits: u32) { 86 | //Given 87 | let public_input_witness = Witness(0); 88 | let black_box_range_8_opcode = 89 | circuit_factory::black_box_range_opcode(public_input_witness, max_num_bits); 90 | let circuit = circuit_factory::circuit_with_single_opcode( 91 | black_box_range_8_opcode, 92 | vec![public_input_witness], 93 | ); 94 | 95 | // When 96 | let (circuit_data, witness_target_map) = 97 | utils::generate_plonky2_circuit_from_acir_circuit(&circuit); 98 | 99 | //Then 100 | utils::check_linked_output_targets_property(&circuit, &witness_target_map); 101 | let proof = utils::generate_plonky2_proof_using_witness_values( 102 | vec![(public_input_witness, witness_value)], 103 | &witness_target_map, 104 | &circuit_data, 105 | ); 106 | assert!(circuit_data.verify(proof).is_ok()); 107 | } 108 | 109 | // ---------------- BITWISE OPERATIONS ------------------ // 110 | 111 | #[test] 112 | fn test_backend_supports_bitwise_and_up_to_8_bits() { 113 | // fn main(mut x: u8, y: u8) -> pub u8{ 114 | // x & y 115 | // } 116 | 117 | let one = F::from_canonical_u8(1); 118 | let three = F::from_canonical_u8(3); 119 | let five = F::from_canonical_u8(5); 120 | _assert_backend_supports_bitwise_operation( 121 | circuit_factory::bitwise_and_circuit, 122 | 8, 123 | five, 124 | three, 125 | one, 126 | ); 127 | } 128 | 129 | #[test] 130 | fn test_backend_supports_bitwise_and_up_to_16_bits() { 131 | // fn main(mut x: u16, y: u16) -> pub u16{ 132 | // x & y 133 | // } 134 | 135 | let a = F::from_canonical_u16(0xFF00); 136 | let b = F::from_canonical_u16(0xF0F0); 137 | let output = F::from_canonical_u16(0xF000); 138 | _assert_backend_supports_bitwise_operation( 139 | circuit_factory::bitwise_and_circuit, 140 | 16, 141 | a, 142 | b, 143 | output, 144 | ); 145 | } 146 | 147 | #[test] 148 | fn test_backend_supports_bitwise_and_up_to_32_bits() { 149 | // fn main(mut x: u32, y: u32) -> pub u32{ 150 | // x & y 151 | // } 152 | 153 | let a = F::from_canonical_u32(0xFF00FF00); 154 | let b = F::from_canonical_u32(0xF0F0F0F0); 155 | let output = F::from_canonical_u32(0xF000F000); 156 | _assert_backend_supports_bitwise_operation( 157 | circuit_factory::bitwise_and_circuit, 158 | 32, 159 | a, 160 | b, 161 | output, 162 | ); 163 | } 164 | 165 | #[test] 166 | fn test_backend_supports_bitwise_xor_up_to_8_bits() { 167 | // fn main(mut x: u8, y: u8) -> pub u8{ 168 | // x ^ y 169 | // } 170 | 171 | let three = F::from_canonical_u8(3); 172 | let five = F::from_canonical_u8(5); 173 | let six = F::from_canonical_u8(6); 174 | _assert_backend_supports_bitwise_operation( 175 | circuit_factory::bitwise_xor_circuit, 176 | 8, 177 | three, 178 | five, 179 | six, 180 | ); 181 | } 182 | 183 | #[test] 184 | fn test_backend_supports_bitwise_xor_up_to_16_bits() { 185 | // fn main(mut x: u16, y: u16) -> pub u16{ 186 | // x ^ y 187 | // } 188 | 189 | let a = F::from_canonical_u16(0xFF00); 190 | let b = F::from_canonical_u16(0xF0F0); 191 | let output = F::from_canonical_u16(0x0FF0); 192 | _assert_backend_supports_bitwise_operation( 193 | circuit_factory::bitwise_xor_circuit, 194 | 16, 195 | a, 196 | b, 197 | output, 198 | ); 199 | } 200 | 201 | #[test] 202 | fn test_backend_supports_bitwise_xor_up_to_32_bits() { 203 | // fn main(mut x: u32, y: u32) -> pub u32{ 204 | // x ^ y 205 | // } 206 | 207 | let a = F::from_canonical_u32(0xFF00FF00); 208 | let b = F::from_canonical_u32(0xF0F0F0F0); 209 | let output = F::from_canonical_u32(0x0FF00FF0); 210 | _assert_backend_supports_bitwise_operation( 211 | circuit_factory::bitwise_xor_circuit, 212 | 32, 213 | a, 214 | b, 215 | output, 216 | ); 217 | } 218 | 219 | fn _assert_backend_supports_bitwise_operation( 220 | operation: fn(Witness, Witness, Witness, u32) -> Circuit, 221 | bit_size: u32, 222 | a: GoldilocksField, 223 | b: GoldilocksField, 224 | output: GoldilocksField, 225 | ) { 226 | // fn main(mut x: u_maxbits, y: u_maxbits) -> pub u_maxbits{ 227 | // x (operation) y 228 | // } 229 | 230 | // Given 231 | let public_input_witness_0 = Witness(0); 232 | let public_input_witness_1 = Witness(1); 233 | let output_witness_2 = Witness(2); 234 | let circuit = operation( 235 | public_input_witness_0, 236 | public_input_witness_1, 237 | output_witness_2, 238 | bit_size, 239 | ); 240 | 241 | // When 242 | let (circuit_data, witness_target_map) = 243 | utils::generate_plonky2_circuit_from_acir_circuit(&circuit); 244 | 245 | //Then 246 | let witness_assignment = vec![ 247 | (public_input_witness_0, a), 248 | (public_input_witness_1, b), 249 | (output_witness_2, output), 250 | ]; 251 | 252 | utils::check_linked_output_targets_property(&circuit, &witness_target_map); 253 | let proof = utils::generate_plonky2_proof_using_witness_values( 254 | witness_assignment, 255 | &witness_target_map, 256 | &circuit_data, 257 | ); 258 | 259 | assert!(circuit_data.verify(proof).is_ok()); 260 | } 261 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/test_assert_zero.rs: -------------------------------------------------------------------------------- 1 | use super::factories::circuit_factory::*; 2 | use super::factories::utils; 3 | use super::*; 4 | 5 | #[test] 6 | fn test_plonky2_vm_can_traslate_the_assert_x_equals_zero_program() { 7 | // Given 8 | let public_input_witness = Witness(0); 9 | let only_opcode = x_equals_0_opcode(public_input_witness); 10 | let circuit = circuit_with_single_opcode(only_opcode, vec![public_input_witness]); 11 | 12 | // When 13 | let (circuit_data, witness_target_map) = 14 | utils::generate_plonky2_circuit_from_acir_circuit(&circuit); 15 | 16 | // Then 17 | let g_zero = F::default(); 18 | let proof = utils::generate_plonky2_proof_using_witness_values( 19 | vec![(public_input_witness, g_zero)], 20 | &witness_target_map, 21 | &circuit_data, 22 | ); 23 | assert_eq!(g_zero, proof.public_inputs[0]); 24 | assert!(circuit_data.verify(proof).is_ok()); 25 | } 26 | 27 | #[test] 28 | fn test_plonky2_vm_can_traslate_the_assert_x_equals_constant_program() { 29 | // Given 30 | let public_input_witness = Witness(0); 31 | let only_opcode = x_equals_4_opcode(public_input_witness); 32 | let circuit = circuit_with_single_opcode(only_opcode, vec![public_input_witness]); 33 | 34 | // When 35 | let (circuit_data, witness_target_map) = 36 | utils::generate_plonky2_circuit_from_acir_circuit(&circuit); 37 | 38 | // Then 39 | let four = F::from_canonical_u64(4); 40 | let proof = utils::generate_plonky2_proof_using_witness_values( 41 | vec![(public_input_witness, four)], 42 | &witness_target_map, 43 | &circuit_data, 44 | ); 45 | assert_eq!(four, proof.public_inputs[0]); 46 | assert!(circuit_data.verify(proof).is_ok()); 47 | } 48 | 49 | #[test] 50 | fn test_plonky2_vm_can_traslate_the_assert_c_times_x_equals_constant_program() { 51 | // Given 52 | let public_input_witness = Witness(0); 53 | let only_opcode = x_times_3_equals_12_opcode(public_input_witness); 54 | let circuit = circuit_with_single_opcode(only_opcode, vec![public_input_witness]); 55 | 56 | // When 57 | let (circuit_data, witness_target_map) = 58 | utils::generate_plonky2_circuit_from_acir_circuit(&circuit); 59 | 60 | // Then 61 | let four = F::from_canonical_u64(4); 62 | let proof = utils::generate_plonky2_proof_using_witness_values( 63 | vec![(public_input_witness, four)], 64 | &witness_target_map, 65 | &circuit_data, 66 | ); 67 | assert_eq!(four, proof.public_inputs[0]); 68 | assert!(circuit_data.verify(proof).is_ok()); 69 | } 70 | 71 | #[test] 72 | fn test_plonky2_vm_can_traslate_the_x_times_3_plus_y_times_4_equals_constant_program() { 73 | // Given 74 | let first_public_input_witness = Witness(0); 75 | let second_public_input_witness = Witness(1); 76 | let only_opcode = x_times_3_plus_y_times_4_equals_constant( 77 | first_public_input_witness, 78 | second_public_input_witness, 79 | ); 80 | let circuit = circuit_with_single_opcode( 81 | only_opcode, 82 | vec![first_public_input_witness, second_public_input_witness], 83 | ); 84 | 85 | // When 86 | let (circuit_data, witness_target_map) = 87 | utils::generate_plonky2_circuit_from_acir_circuit(&circuit); 88 | 89 | // Then 90 | let one = F::from_canonical_u64(1); 91 | let proof = utils::generate_plonky2_proof_using_witness_values( 92 | vec![ 93 | (first_public_input_witness, one), 94 | (second_public_input_witness, one), 95 | ], 96 | &witness_target_map, 97 | &circuit_data, 98 | ); 99 | 100 | assert_eq!(one, proof.public_inputs[0]); 101 | assert_eq!(one, proof.public_inputs[1]); 102 | assert!(circuit_data.verify(proof).is_ok()); 103 | } 104 | 105 | #[test] 106 | fn test_plonky2_vm_can_traslate_multiple_linear_combinations() { 107 | // Given 108 | let public_inputs = vec![Witness(0), Witness(1), Witness(2), Witness(3)]; 109 | let only_opcode = multiple_linear_combinations_opcode(&public_inputs); 110 | let circuit = circuit_with_single_opcode(only_opcode, public_inputs.clone()); 111 | 112 | // When 113 | let (circuit_data, witness_target_map) = 114 | utils::generate_plonky2_circuit_from_acir_circuit(&circuit); 115 | 116 | // Then 117 | let one = F::from_canonical_u64(1); 118 | let proof = utils::generate_plonky2_proof_using_witness_values( 119 | vec![ 120 | (public_inputs[0], one), 121 | (public_inputs[1], one), 122 | (public_inputs[2], one), 123 | (public_inputs[3], one), 124 | ], 125 | &witness_target_map, 126 | &circuit_data, 127 | ); 128 | 129 | assert_eq!(one, proof.public_inputs[0]); 130 | assert_eq!(one, proof.public_inputs[1]); 131 | assert_eq!(one, proof.public_inputs[2]); 132 | assert_eq!(one, proof.public_inputs[3]); 133 | assert!(circuit_data.verify(proof).is_ok()); 134 | } 135 | 136 | #[test] 137 | fn test_plonky2_vm_can_traslate_the_x_times_x_program_equals_constant() { 138 | // Given 139 | let public_input = Witness(0); 140 | let only_opcode = two_times_x_times_x_opcode(public_input); 141 | let circuit = circuit_with_single_opcode(only_opcode, vec![public_input]); 142 | 143 | // When 144 | let (circuit_data, witness_target_map) = 145 | utils::generate_plonky2_circuit_from_acir_circuit(&circuit); 146 | 147 | // Then 148 | let four = F::from_canonical_u64(4); 149 | let proof = utils::generate_plonky2_proof_using_witness_values( 150 | vec![(public_input, four)], 151 | &witness_target_map, 152 | &circuit_data, 153 | ); 154 | 155 | assert_eq!(four, proof.public_inputs[0]); 156 | assert!(circuit_data.verify(proof).is_ok()); 157 | } 158 | 159 | #[test] 160 | fn test_plonky2_vm_can_traslate_the_c_times_x_times_y_program_equals_constant() { 161 | // Given 162 | let public_input_1 = Witness(0); 163 | let public_input_2 = Witness(1); 164 | let only_opcode = two_times_x_times_y_opcode(public_input_1, public_input_2); 165 | let circuit = circuit_with_single_opcode(only_opcode, vec![public_input_1, public_input_2]); 166 | 167 | // When 168 | let (circuit_data, witness_target_map) = 169 | utils::generate_plonky2_circuit_from_acir_circuit(&circuit); 170 | 171 | // Then 172 | let four = F::from_canonical_u64(4); 173 | let five = F::from_canonical_u64(5); 174 | let proof = utils::generate_plonky2_proof_using_witness_values( 175 | vec![(public_input_1, four), (public_input_2, five)], 176 | &witness_target_map, 177 | &circuit_data, 178 | ); 179 | 180 | assert_eq!(four, proof.public_inputs[0]); 181 | assert_eq!(five, proof.public_inputs[1]); 182 | assert!(circuit_data.verify(proof).is_ok()); 183 | } 184 | 185 | #[test] 186 | fn test_plonky2_vm_can_traslate_multiple_cuadratic_terms() { 187 | // Given 188 | let public_inputs = vec![Witness(0), Witness(1), Witness(2), Witness(3)]; 189 | let only_opcode = multiple_cuadratic_terms_opcode(&public_inputs); 190 | let circuit = circuit_with_single_opcode(only_opcode, public_inputs.clone()); 191 | 192 | // When 193 | let (circuit_data, witness_target_map) = 194 | utils::generate_plonky2_circuit_from_acir_circuit(&circuit); 195 | 196 | // Then 197 | let two = F::from_canonical_u64(2); 198 | let proof = utils::generate_plonky2_proof_using_witness_values( 199 | vec![ 200 | (public_inputs[0], two), 201 | (public_inputs[1], two), 202 | (public_inputs[2], two), 203 | (public_inputs[3], two), 204 | ], 205 | &witness_target_map, 206 | &circuit_data, 207 | ); 208 | 209 | assert_eq!(two, proof.public_inputs[0]); 210 | assert_eq!(two, proof.public_inputs[1]); 211 | assert_eq!(two, proof.public_inputs[2]); 212 | assert_eq!(two, proof.public_inputs[3]); 213 | assert!(circuit_data.verify(proof).is_ok()); 214 | } 215 | 216 | #[test] 217 | fn test_plonky2_vm_can_traslate_multiple_cuadratic_terms_and_linear_combinations() { 218 | // Given 219 | let public_inputs = vec![Witness(0), Witness(1), Witness(2), Witness(3)]; 220 | let only_opcode = multiple_cuadratic_terms_and_linear_combinations_opcode(&public_inputs); 221 | let circuit = circuit_with_single_opcode(only_opcode, public_inputs.clone()); 222 | 223 | // When 224 | let (circuit_data, witness_target_map) = 225 | utils::generate_plonky2_circuit_from_acir_circuit(&circuit); 226 | 227 | // Then 228 | let two = F::from_canonical_u64(2); 229 | let proof = utils::generate_plonky2_proof_using_witness_values( 230 | vec![ 231 | (public_inputs[0], two), 232 | (public_inputs[1], two), 233 | (public_inputs[2], two), 234 | (public_inputs[3], two), 235 | ], 236 | &witness_target_map, 237 | &circuit_data, 238 | ); 239 | 240 | assert_eq!(two, proof.public_inputs[0]); 241 | assert_eq!(two, proof.public_inputs[1]); 242 | assert_eq!(two, proof.public_inputs[2]); 243 | assert_eq!(two, proof.public_inputs[3]); 244 | assert!(circuit_data.verify(proof).is_ok()); 245 | } 246 | 247 | #[test] 248 | fn test_plonky2_vm_can_translate_circuits_with_2_assert_zero_opcodes() { 249 | // Given 250 | let public_input_witness = Witness(0); 251 | let intermediate_witness = Witness(1); 252 | let circuit = circuit_with_a_public_input_and_two_assert_zero_operands( 253 | public_input_witness, 254 | intermediate_witness, 255 | ); 256 | 257 | // When 258 | let (circuit_data, witness_target_map) = 259 | utils::generate_plonky2_circuit_from_acir_circuit(&circuit); 260 | 261 | // Then 262 | let one = F::from_canonical_u64(1); 263 | let five = F::from_canonical_u64(5); 264 | let proof = utils::generate_plonky2_proof_using_witness_values( 265 | vec![(public_input_witness, one), (intermediate_witness, five)], 266 | &witness_target_map, 267 | &circuit_data, 268 | ); 269 | 270 | assert_eq!(one, proof.public_inputs[0]); 271 | assert!(circuit_data.verify(proof).is_ok()); 272 | } 273 | 274 | #[test] 275 | fn test_current_noir_is_using_goldilocks_field() { 276 | use super::FieldElement; 277 | let goldilocks_prime: u64 = (2u128.pow(64) - 2u128.pow(32) + 1u128) as u64; 278 | let field_element_one = FieldElement::one(); 279 | let field_element_minus_one = -field_element_one; 280 | 281 | let obtained_one = field_element_one.try_to_u64().unwrap(); 282 | let obtained_minus_one = field_element_minus_one.try_to_u64().unwrap(); 283 | assert_eq!(1, obtained_one); 284 | assert_eq!(goldilocks_prime - 1, obtained_minus_one); 285 | } 286 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/sha256_translator.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// This struct represents the outputs of each iteration in the 64-loop of the sha256 algorithm. 4 | #[derive(Clone)] 5 | struct CompressionIterationState { 6 | a: BinaryDigitsTarget, 7 | b: BinaryDigitsTarget, 8 | c: BinaryDigitsTarget, 9 | d: BinaryDigitsTarget, 10 | e: BinaryDigitsTarget, 11 | f: BinaryDigitsTarget, 12 | g: BinaryDigitsTarget, 13 | h: BinaryDigitsTarget, 14 | } 15 | 16 | impl CompressionIterationState { 17 | pub fn from_vec(vec: Vec) -> Self { 18 | Self { 19 | a: vec[0].clone(), 20 | b: vec[1].clone(), 21 | c: vec[2].clone(), 22 | d: vec[3].clone(), 23 | e: vec[4].clone(), 24 | f: vec[5].clone(), 25 | g: vec[6].clone(), 26 | h: vec[7].clone(), 27 | } 28 | } 29 | 30 | pub fn unpack(self) -> [BinaryDigitsTarget; 8] { 31 | [ 32 | self.a, self.b, self.c, self.d, self.e, self.f, self.g, self.h, 33 | ] 34 | } 35 | } 36 | 37 | /// This module translates the sha256 compression function. 38 | /// TODO: it should support inputs bigger than 512 bits 39 | pub struct Sha256CompressionTranslator<'a> { 40 | circuit_builder: &'a mut CircuitBuilderFromAcirToPlonky2, 41 | inputs: &'a Box<[FunctionInput; 16]>, 42 | hash_values: &'a Box<[FunctionInput; 8]>, 43 | outputs: &'a Box<[Witness; 8]>, 44 | } 45 | 46 | impl<'a> Sha256CompressionTranslator<'a> { 47 | pub fn new_for( 48 | circuit_builder: &'a mut CircuitBuilderFromAcirToPlonky2, 49 | inputs: &'a Box<[FunctionInput; 16]>, 50 | hash_values: &'a Box<[FunctionInput; 8]>, 51 | outputs: &'a Box<[Witness; 8]>, 52 | ) -> Sha256CompressionTranslator<'a> { 53 | Self { 54 | circuit_builder, 55 | inputs, 56 | hash_values, 57 | outputs, 58 | } 59 | } 60 | 61 | pub fn translate(&mut self) { 62 | let mut binary_input_targets: Vec = self 63 | .inputs 64 | .into_iter() 65 | .map(|input| { 66 | self.circuit_builder 67 | .binary_number_target_for_witness(input.witness, 32) 68 | }) 69 | .collect(); 70 | 71 | for t in 16..64 { 72 | let new_w_t = self.calculate_w_t( 73 | &binary_input_targets[t - 2], 74 | &binary_input_targets[t - 7], 75 | &binary_input_targets[t - 15], 76 | &binary_input_targets[t - 16], 77 | ); 78 | binary_input_targets.push(new_w_t); 79 | } 80 | 81 | let constant_k_digit_target_values = self.initial_k(); 82 | let initial_h = self.initial_h(); 83 | let mut iteration_states: Vec = 84 | vec![CompressionIterationState::from_vec(initial_h.clone())]; 85 | for t in 0..64 { 86 | let prev_iteration_state = iteration_states[t].clone(); 87 | let next_state = self.compression_function_iteration( 88 | prev_iteration_state, 89 | &binary_input_targets[t], 90 | &constant_k_digit_target_values[t], 91 | ); 92 | iteration_states.push(next_state); 93 | } 94 | // Link all the binary digits target outputs into the corresponding output targets 95 | let last_iteration_state = iteration_states.last().unwrap().clone(); 96 | let output_binary_targets = last_iteration_state.unpack(); 97 | 98 | let mut final_h: Vec = Vec::new(); 99 | for i in 0..8 { 100 | final_h.push(BinaryDigitsTarget::add_module_32_bits( 101 | &initial_h[i], 102 | &output_binary_targets[i], 103 | &mut self.circuit_builder.builder, 104 | )) 105 | } 106 | 107 | for (output_witness, output_binary_target) in 108 | self.outputs.iter().zip(final_h.iter()) 109 | { 110 | let new_output_target = self 111 | .circuit_builder 112 | .convert_binary_number_to_number(output_binary_target.clone()); 113 | self.circuit_builder 114 | .witness_target_map 115 | .insert(*output_witness, new_output_target); 116 | } 117 | } 118 | 119 | fn sigma_0(&mut self, target: &BinaryDigitsTarget) -> BinaryDigitsTarget { 120 | let x1 = BinaryDigitsTarget::rotate_right(target, 7, &mut self.circuit_builder.builder); 121 | let x2 = BinaryDigitsTarget::rotate_right(target, 18, &mut self.circuit_builder.builder); 122 | let x3 = BinaryDigitsTarget::shift_right(target, 3, &mut self.circuit_builder.builder); 123 | 124 | let y1 = BinaryDigitsTarget::xor(x1, x2, &mut self.circuit_builder.builder); 125 | let y2 = BinaryDigitsTarget::xor(y1, x3, &mut self.circuit_builder.builder); 126 | 127 | y2 128 | } 129 | 130 | fn sigma_1(&mut self, target: &BinaryDigitsTarget) -> BinaryDigitsTarget { 131 | let x1 = BinaryDigitsTarget::rotate_right(target, 17, &mut self.circuit_builder.builder); 132 | let x2 = BinaryDigitsTarget::rotate_right(target, 19, &mut self.circuit_builder.builder); 133 | let x3 = BinaryDigitsTarget::shift_right(target, 10, &mut self.circuit_builder.builder); 134 | 135 | let y1 = BinaryDigitsTarget::xor(x1, x2, &mut self.circuit_builder.builder); 136 | let y2 = BinaryDigitsTarget::xor(y1, x3, &mut self.circuit_builder.builder); 137 | 138 | y2 139 | } 140 | 141 | fn big_sigma_0(&mut self, target: &BinaryDigitsTarget) -> BinaryDigitsTarget { 142 | let x1 = BinaryDigitsTarget::rotate_right(target, 2, &mut self.circuit_builder.builder); 143 | let x2 = BinaryDigitsTarget::rotate_right(target, 13, &mut self.circuit_builder.builder); 144 | let x3 = BinaryDigitsTarget::rotate_right(target, 22, &mut self.circuit_builder.builder); 145 | 146 | let y1 = BinaryDigitsTarget::xor(x1, x2, &mut self.circuit_builder.builder); 147 | let y2 = BinaryDigitsTarget::xor(y1, x3, &mut self.circuit_builder.builder); 148 | y2 149 | } 150 | 151 | fn big_sigma_1(&mut self, target: &BinaryDigitsTarget) -> BinaryDigitsTarget { 152 | let x1 = BinaryDigitsTarget::rotate_right(target, 6, &mut self.circuit_builder.builder); 153 | let x2 = BinaryDigitsTarget::rotate_right(target, 11, &mut self.circuit_builder.builder); 154 | let x3 = BinaryDigitsTarget::rotate_right(target, 25, &mut self.circuit_builder.builder); 155 | 156 | let y1 = BinaryDigitsTarget::xor(x1, x2, &mut self.circuit_builder.builder); 157 | let y2 = BinaryDigitsTarget::xor(y1, x3, &mut self.circuit_builder.builder); 158 | y2 159 | } 160 | 161 | fn initial_h(&mut self) -> Vec { 162 | let binary_inputs: Vec = self 163 | .hash_values 164 | .into_iter() 165 | .map(|input| { 166 | self.circuit_builder 167 | .binary_number_target_for_witness(input.witness, 32) 168 | }) 169 | .collect(); 170 | binary_inputs 171 | } 172 | 173 | fn initial_k(&mut self) -> Vec { 174 | let sha256_k_constants: Vec = vec![ 175 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 176 | 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 177 | 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 178 | 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 179 | 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 180 | 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 181 | 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 182 | 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 183 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 184 | 0xc67178f2, 185 | ]; 186 | let binary_constants = sha256_k_constants 187 | .iter() 188 | .map(|n| { 189 | self.circuit_builder 190 | .binary_number_target_for_constant(*n as usize, 32) 191 | }) 192 | .collect(); 193 | binary_constants 194 | } 195 | 196 | fn calculate_w_t( 197 | &mut self, 198 | w_t_2: &BinaryDigitsTarget, 199 | w_t_7: &BinaryDigitsTarget, 200 | w_t_15: &BinaryDigitsTarget, 201 | w_t_16: &BinaryDigitsTarget, 202 | ) -> BinaryDigitsTarget { 203 | let sigma_1 = self.sigma_1(w_t_2); 204 | let sumand_1 = BinaryDigitsTarget::add_module_32_bits( 205 | &sigma_1, 206 | w_t_7, 207 | &mut self.circuit_builder.builder, 208 | ); 209 | let sigma_0 = self.sigma_0(w_t_15); 210 | let sumand_2 = BinaryDigitsTarget::add_module_32_bits( 211 | &sigma_0, 212 | w_t_16, 213 | &mut self.circuit_builder.builder, 214 | ); 215 | BinaryDigitsTarget::add_module_32_bits( 216 | &sumand_1, 217 | &sumand_2, 218 | &mut self.circuit_builder.builder, 219 | ) 220 | } 221 | 222 | fn compression_function_iteration( 223 | &mut self, 224 | s: CompressionIterationState, 225 | w_t: &BinaryDigitsTarget, 226 | k_t: &BinaryDigitsTarget, 227 | ) -> CompressionIterationState { 228 | let [a, b, c, d, e, f, g, h] = s.unpack(); 229 | let big_sigma_1 = self.big_sigma_1(&e); 230 | let choose_e_f_g = 231 | BinaryDigitsTarget::choose(&e, &f, &g, &mut self.circuit_builder.builder); 232 | let sumand_0 = 233 | BinaryDigitsTarget::add_module_32_bits(k_t, w_t, &mut self.circuit_builder.builder); 234 | let sumand_1 = BinaryDigitsTarget::add_module_32_bits( 235 | &h, 236 | &big_sigma_1, 237 | &mut self.circuit_builder.builder, 238 | ); 239 | let sumand_2 = BinaryDigitsTarget::add_module_32_bits( 240 | &choose_e_f_g, 241 | &sumand_0, 242 | &mut self.circuit_builder.builder, 243 | ); 244 | let t_1 = BinaryDigitsTarget::add_module_32_bits( 245 | &sumand_1, 246 | &sumand_2, 247 | &mut self.circuit_builder.builder, 248 | ); 249 | 250 | let big_sigma_0 = self.big_sigma_0(&a); 251 | let majority_a_b_c = 252 | BinaryDigitsTarget::majority(&a, &b, &c, &mut self.circuit_builder.builder); 253 | let t_2 = BinaryDigitsTarget::add_module_32_bits( 254 | &big_sigma_0, 255 | &majority_a_b_c, 256 | &mut self.circuit_builder.builder, 257 | ); 258 | CompressionIterationState { 259 | a: BinaryDigitsTarget::add_module_32_bits( 260 | &t_1, 261 | &t_2, 262 | &mut self.circuit_builder.builder, 263 | ), 264 | b: a, 265 | c: b, 266 | d: c, 267 | e: BinaryDigitsTarget::add_module_32_bits(&d, &t_1, &mut self.circuit_builder.builder), 268 | f: e, 269 | g: f, 270 | h: g, 271 | } 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/tests/factories/circuit_factory.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use acir::circuit::opcodes::FunctionInput; 3 | use acir::circuit::{opcodes, ExpressionWidth, PublicInputs}; 4 | use std::collections::BTreeSet; 5 | 6 | pub fn x_equals_0_opcode(public_input_witness: Witness) -> Opcode { 7 | Opcode::AssertZero(Expression { 8 | mul_terms: Vec::new(), 9 | linear_combinations: vec![(FieldElement::one(), public_input_witness)], 10 | q_c: FieldElement::zero(), 11 | }) 12 | } 13 | 14 | pub fn circuit_with_single_opcode( 15 | only_expr: Opcode, 16 | public_input_witnesses: Vec, 17 | ) -> Circuit { 18 | Circuit { 19 | current_witness_index: 0, 20 | expression_width: ExpressionWidth::Unbounded, 21 | opcodes: vec![only_expr], 22 | private_parameters: BTreeSet::new(), 23 | public_parameters: PublicInputs(BTreeSet::from_iter(public_input_witnesses)), 24 | return_values: PublicInputs(BTreeSet::new()), 25 | assert_messages: Default::default(), 26 | recursive: false, 27 | } 28 | } 29 | 30 | pub fn x_equals_4_opcode(public_input_witness: Witness) -> Opcode { 31 | Opcode::AssertZero(Expression { 32 | mul_terms: Vec::new(), 33 | linear_combinations: vec![(FieldElement::one(), public_input_witness)], 34 | q_c: -FieldElement::from_hex("0x04").unwrap(), 35 | }) 36 | } 37 | 38 | pub fn x_times_3_equals_12_opcode(public_input_witness: Witness) -> Opcode { 39 | Opcode::AssertZero(Expression { 40 | mul_terms: Vec::new(), 41 | linear_combinations: vec![( 42 | FieldElement::from_hex("0x03").unwrap(), 43 | public_input_witness, 44 | )], 45 | q_c: -FieldElement::from_hex("0x0C").unwrap(), 46 | }) 47 | } 48 | 49 | pub fn x_times_3_plus_y_times_4_equals_constant( 50 | first_public_input_witness: Witness, 51 | second_public_input_witness: Witness, 52 | ) -> Opcode { 53 | Opcode::AssertZero(Expression { 54 | mul_terms: vec![], 55 | linear_combinations: vec![ 56 | ( 57 | FieldElement::from_hex("0x03").unwrap(), 58 | first_public_input_witness, 59 | ), 60 | ( 61 | FieldElement::from_hex("0x09").unwrap(), 62 | second_public_input_witness, 63 | ), 64 | ], 65 | q_c: -FieldElement::from_hex("0x0c").unwrap(), 66 | }) 67 | } 68 | 69 | pub fn multiple_linear_combinations_opcode(public_inputs: &Vec) -> Opcode { 70 | Opcode::AssertZero(Expression { 71 | mul_terms: vec![], 72 | linear_combinations: public_inputs 73 | .iter() 74 | .map(|a_witness| (FieldElement::from_hex("0x03").unwrap(), *a_witness)) 75 | .rev() 76 | .collect(), 77 | q_c: -FieldElement::from_hex("0x0c").unwrap(), 78 | }) 79 | } 80 | 81 | pub fn two_times_x_times_x_opcode(public_input: Witness) -> Opcode { 82 | Opcode::AssertZero(Expression { 83 | mul_terms: vec![( 84 | FieldElement::from_hex("0x02").unwrap(), 85 | public_input, 86 | public_input, 87 | )], 88 | linear_combinations: vec![], 89 | q_c: -FieldElement::from_hex("0x20").unwrap(), 90 | }) 91 | } 92 | 93 | pub fn two_times_x_times_y_opcode(public_input_1: Witness, public_input_2: Witness) -> Opcode { 94 | Opcode::AssertZero(Expression { 95 | mul_terms: vec![( 96 | FieldElement::from_hex("0x02").unwrap(), 97 | public_input_1, 98 | public_input_2, 99 | )], 100 | linear_combinations: vec![], 101 | q_c: -FieldElement::from_hex("0x28").unwrap(), 102 | }) 103 | } 104 | 105 | pub fn multiple_cuadratic_terms_opcode(public_inputs: &Vec) -> Opcode { 106 | Opcode::AssertZero(Expression { 107 | mul_terms: vec![ 108 | ( 109 | FieldElement::from_hex("0x02").unwrap(), 110 | public_inputs[0], 111 | public_inputs[0], 112 | ), 113 | ( 114 | FieldElement::from_hex("0x03").unwrap(), 115 | public_inputs[0], 116 | public_inputs[1], 117 | ), 118 | ( 119 | FieldElement::from_hex("0x04").unwrap(), 120 | public_inputs[1], 121 | public_inputs[2], 122 | ), 123 | ( 124 | FieldElement::from_hex("0x05").unwrap(), 125 | public_inputs[2], 126 | public_inputs[3], 127 | ), 128 | ( 129 | FieldElement::from_hex("0x06").unwrap(), 130 | public_inputs[3], 131 | public_inputs[3], 132 | ), 133 | ( 134 | FieldElement::from_hex("0x07").unwrap(), 135 | public_inputs[1], 136 | public_inputs[1], 137 | ), 138 | ], 139 | linear_combinations: vec![], 140 | q_c: -FieldElement::from_hex("0x6c").unwrap(), 141 | }) 142 | } 143 | 144 | pub fn multiple_cuadratic_terms_and_linear_combinations_opcode( 145 | public_inputs: &Vec, 146 | ) -> Opcode { 147 | Opcode::AssertZero(Expression { 148 | mul_terms: vec![ 149 | ( 150 | FieldElement::from_hex("0x02").unwrap(), 151 | public_inputs[0], 152 | public_inputs[0], 153 | ), 154 | ( 155 | FieldElement::from_hex("0x03").unwrap(), 156 | public_inputs[0], 157 | public_inputs[1], 158 | ), 159 | ( 160 | FieldElement::from_hex("0x04").unwrap(), 161 | public_inputs[1], 162 | public_inputs[2], 163 | ), 164 | ( 165 | FieldElement::from_hex("0x05").unwrap(), 166 | public_inputs[2], 167 | public_inputs[3], 168 | ), 169 | ( 170 | FieldElement::from_hex("0x06").unwrap(), 171 | public_inputs[3], 172 | public_inputs[3], 173 | ), 174 | ( 175 | FieldElement::from_hex("0x07").unwrap(), 176 | public_inputs[1], 177 | public_inputs[1], 178 | ), 179 | ], 180 | linear_combinations: vec![ 181 | (FieldElement::from_hex("0x01").unwrap(), public_inputs[0]), 182 | (FieldElement::from_hex("0x02").unwrap(), public_inputs[1]), 183 | (FieldElement::from_hex("0x03").unwrap(), public_inputs[2]), 184 | (FieldElement::from_hex("0x04").unwrap(), public_inputs[3]), 185 | ], 186 | q_c: -FieldElement::from_hex("0x80").unwrap(), 187 | }) 188 | } 189 | 190 | pub fn black_box_range_opcode(public_input: Witness, max_bits: u32) -> Opcode { 191 | let input = FunctionInput { 192 | witness: public_input, 193 | num_bits: max_bits, 194 | }; 195 | Opcode::BlackBoxFuncCall(opcodes::BlackBoxFuncCall::RANGE { input }) 196 | } 197 | 198 | pub fn circuit_with_a_public_input_and_two_assert_zero_operands( 199 | public_input_witness: Witness, 200 | intermediate_witness: Witness, 201 | ) -> Circuit { 202 | Circuit { 203 | current_witness_index: 0, 204 | expression_width: ExpressionWidth::Unbounded, 205 | opcodes: vec![ 206 | Opcode::AssertZero(Expression { 207 | mul_terms: vec![], 208 | linear_combinations: vec![ 209 | (FieldElement::one(), public_input_witness), 210 | (-FieldElement::one(), intermediate_witness), 211 | ], 212 | q_c: FieldElement::from_hex("0x04").unwrap(), 213 | }), 214 | Opcode::AssertZero(Expression { 215 | mul_terms: vec![( 216 | FieldElement::one(), 217 | intermediate_witness, 218 | intermediate_witness, 219 | )], 220 | linear_combinations: vec![], 221 | q_c: -FieldElement::from_hex("0x19").unwrap(), 222 | }), 223 | ], 224 | private_parameters: BTreeSet::new(), 225 | public_parameters: PublicInputs(BTreeSet::from_iter(vec![public_input_witness])), 226 | return_values: PublicInputs(BTreeSet::new()), 227 | assert_messages: Default::default(), 228 | recursive: false, 229 | } 230 | } 231 | 232 | pub fn bitwise_and_circuit( 233 | input_1: Witness, 234 | input_2: Witness, 235 | output: Witness, 236 | bit_size: u32, 237 | ) -> Circuit { 238 | // BLACKBOX::RANGE [(_0, num_bits: max_bits)] [ ] 239 | // BLACKBOX::RANGE [(_1, num_bits: max_bits)] [ ] 240 | // BLACKBOX::AND [(_0, num_bits: max_bits), (_1, num_bits: max_bits)] [ _2] 241 | 242 | _circuit_with_bitwise_operation( 243 | input_1, 244 | input_2, 245 | _bitwise_and_acir_opcode(output, input_1, input_2, bit_size), 246 | bit_size, 247 | ) 248 | } 249 | 250 | pub fn bitwise_xor_circuit( 251 | input_1: Witness, 252 | input_2: Witness, 253 | output: Witness, 254 | bit_size: u32, 255 | ) -> Circuit { 256 | // BLACKBOX::RANGE [(_0, num_bits: max_bits)] [ ] 257 | // BLACKBOX::RANGE [(_1, num_bits: max_bits)] [ ] 258 | // BLACKBOX::XOR [(_0, num_bits: max_bits), (_1, num_bits: max_bits)] [ _2] 259 | 260 | _circuit_with_bitwise_operation( 261 | input_1, 262 | input_2, 263 | _bitwise_xor_acir_opcode(output, input_1, input_2, bit_size), 264 | bit_size, 265 | ) 266 | } 267 | 268 | fn _circuit_with_bitwise_operation( 269 | input_1: Witness, 270 | input_2: Witness, 271 | opcode: Opcode, 272 | bit_size: u32, 273 | ) -> Circuit { 274 | // BLACKBOX::RANGE [(_0, num_bits: max_bits)] [ ] 275 | // BLACKBOX::RANGE [(_1, num_bits: max_bits)] [ ] 276 | // BLACKBOX::OPCODE [(_0, num_bits: max_bits), (_1, num_bits: max_bits)] [ _2] 277 | 278 | Circuit { 279 | current_witness_index: 0, 280 | expression_width: ExpressionWidth::Unbounded, 281 | opcodes: vec![ 282 | black_box_range_opcode(input_1, bit_size), 283 | black_box_range_opcode(input_2, bit_size), 284 | opcode, 285 | ], 286 | private_parameters: BTreeSet::new(), 287 | public_parameters: PublicInputs(BTreeSet::from_iter(vec![input_1, input_2])), 288 | return_values: PublicInputs(BTreeSet::from_iter([Witness(2)])), 289 | assert_messages: Default::default(), 290 | recursive: false, 291 | } 292 | } 293 | 294 | fn _bitwise_and_acir_opcode( 295 | output: Witness, 296 | input_1: Witness, 297 | input_2: Witness, 298 | bit_size: u32, 299 | ) -> Opcode { 300 | let and_lhs = FunctionInput { 301 | witness: input_1, 302 | num_bits: bit_size, 303 | }; 304 | let and_rhs = FunctionInput { 305 | witness: input_2, 306 | num_bits: bit_size, 307 | }; 308 | 309 | Opcode::BlackBoxFuncCall(opcodes::BlackBoxFuncCall::AND { 310 | lhs: and_lhs, 311 | rhs: and_rhs, 312 | output, 313 | }) 314 | } 315 | 316 | fn _bitwise_xor_acir_opcode( 317 | output: Witness, 318 | input_1: Witness, 319 | input_2: Witness, 320 | max_bits: u32, 321 | ) -> Opcode { 322 | let and_lhs = FunctionInput { 323 | witness: input_1, 324 | num_bits: max_bits, 325 | }; 326 | let and_rhs = FunctionInput { 327 | witness: input_2, 328 | num_bits: max_bits, 329 | }; 330 | 331 | Opcode::BlackBoxFuncCall(opcodes::BlackBoxFuncCall::XOR { 332 | lhs: and_lhs, 333 | rhs: and_rhs, 334 | output, 335 | }) 336 | } 337 | -------------------------------------------------------------------------------- /plonky2-backend/src/plonky2_ecdsa/biguint/gates/range_check_u32.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | use plonky2::plonk::circuit_data::CommonCircuitData; 3 | use plonky2::util::serialization::{Buffer, IoResult, Read, Write}; 4 | 5 | use plonky2::field::extension::Extendable; 6 | use plonky2::field::types::Field; 7 | use plonky2::gates::gate::Gate; 8 | use plonky2::gates::util::StridedConstraintConsumer; 9 | use plonky2::hash::hash_types::RichField; 10 | use plonky2::iop::ext_target::ExtensionTarget; 11 | use plonky2::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGeneratorRef}; 12 | use plonky2::iop::target::Target; 13 | use plonky2::iop::witness::{PartitionWitness, Witness, WitnessWrite}; 14 | use plonky2::plonk::circuit_builder::CircuitBuilder; 15 | use plonky2::plonk::plonk_common::{reduce_with_powers, reduce_with_powers_ext_circuit}; 16 | use plonky2::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; 17 | 18 | pub const fn ceil_div_usize(a: usize, b: usize) -> usize { 19 | (a + b - 1) / b 20 | } 21 | 22 | /// A gate which can decompose a number into base B little-endian limbs. 23 | #[derive(Copy, Clone, Debug)] 24 | pub struct U32RangeCheckGate, const D: usize> { 25 | pub num_input_limbs: usize, 26 | _phantom: PhantomData, 27 | } 28 | 29 | impl, const D: usize> U32RangeCheckGate { 30 | pub fn new(num_input_limbs: usize) -> Self { 31 | Self { 32 | num_input_limbs, 33 | _phantom: PhantomData, 34 | } 35 | } 36 | 37 | pub const AUX_LIMB_BITS: usize = 2; 38 | pub const BASE: usize = 1 << Self::AUX_LIMB_BITS; 39 | 40 | fn aux_limbs_per_input_limb(&self) -> usize { 41 | ceil_div_usize(32, Self::AUX_LIMB_BITS) 42 | } 43 | pub fn wire_ith_input_limb(&self, i: usize) -> usize { 44 | debug_assert!(i < self.num_input_limbs); 45 | i 46 | } 47 | pub fn wire_ith_input_limb_jth_aux_limb(&self, i: usize, j: usize) -> usize { 48 | debug_assert!(i < self.num_input_limbs); 49 | debug_assert!(j < self.aux_limbs_per_input_limb()); 50 | self.num_input_limbs + self.aux_limbs_per_input_limb() * i + j 51 | } 52 | } 53 | 54 | impl, const D: usize> Gate for U32RangeCheckGate { 55 | fn id(&self) -> String { 56 | format!("{self:?}") 57 | } 58 | 59 | fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { 60 | dst.write_usize(self.num_input_limbs) 61 | } 62 | 63 | fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { 64 | let num_input_limbs = src.read_usize()?; 65 | Ok(Self { 66 | num_input_limbs, 67 | _phantom: PhantomData, 68 | }) 69 | } 70 | 71 | fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { 72 | let mut constraints = Vec::with_capacity(self.num_constraints()); 73 | 74 | let base = F::Extension::from_canonical_usize(Self::BASE); 75 | for i in 0..self.num_input_limbs { 76 | let input_limb = vars.local_wires[self.wire_ith_input_limb(i)]; 77 | let aux_limbs: Vec<_> = (0..self.aux_limbs_per_input_limb()) 78 | .map(|j| vars.local_wires[self.wire_ith_input_limb_jth_aux_limb(i, j)]) 79 | .collect(); 80 | let computed_sum = reduce_with_powers(&aux_limbs, base); 81 | 82 | constraints.push(computed_sum - input_limb); 83 | for aux_limb in aux_limbs { 84 | constraints.push( 85 | (0..Self::BASE) 86 | .map(|i| aux_limb - F::Extension::from_canonical_usize(i)) 87 | .product(), 88 | ); 89 | } 90 | } 91 | 92 | constraints 93 | } 94 | 95 | fn eval_unfiltered_base_one( 96 | &self, 97 | vars: EvaluationVarsBase, 98 | mut yield_constr: StridedConstraintConsumer, 99 | ) { 100 | let base = F::from_canonical_usize(Self::BASE); 101 | for i in 0..self.num_input_limbs { 102 | let input_limb = vars.local_wires[self.wire_ith_input_limb(i)]; 103 | let aux_limbs: Vec<_> = (0..self.aux_limbs_per_input_limb()) 104 | .map(|j| vars.local_wires[self.wire_ith_input_limb_jth_aux_limb(i, j)]) 105 | .collect(); 106 | let computed_sum = reduce_with_powers(&aux_limbs, base); 107 | 108 | yield_constr.one(computed_sum - input_limb); 109 | for aux_limb in aux_limbs { 110 | yield_constr.one( 111 | (0..Self::BASE) 112 | .map(|i| aux_limb - F::from_canonical_usize(i)) 113 | .product(), 114 | ); 115 | } 116 | } 117 | } 118 | 119 | fn eval_unfiltered_circuit( 120 | &self, 121 | builder: &mut CircuitBuilder, 122 | vars: EvaluationTargets, 123 | ) -> Vec> { 124 | let mut constraints = Vec::with_capacity(self.num_constraints()); 125 | 126 | let base = builder.constant(F::from_canonical_usize(Self::BASE)); 127 | for i in 0..self.num_input_limbs { 128 | let input_limb = vars.local_wires[self.wire_ith_input_limb(i)]; 129 | let aux_limbs: Vec<_> = (0..self.aux_limbs_per_input_limb()) 130 | .map(|j| vars.local_wires[self.wire_ith_input_limb_jth_aux_limb(i, j)]) 131 | .collect(); 132 | let computed_sum = reduce_with_powers_ext_circuit(builder, &aux_limbs, base); 133 | 134 | constraints.push(builder.sub_extension(computed_sum, input_limb)); 135 | for aux_limb in aux_limbs { 136 | constraints.push({ 137 | let mut acc = builder.one_extension(); 138 | (0..Self::BASE).for_each(|i| { 139 | // We update our accumulator as: 140 | // acc' = acc (x - i) 141 | // = acc x + (-i) acc 142 | // Since -i is constant, we can do this in one arithmetic_extension call. 143 | let neg_i = -F::from_canonical_usize(i); 144 | acc = builder.arithmetic_extension(F::ONE, neg_i, acc, aux_limb, acc) 145 | }); 146 | acc 147 | }); 148 | } 149 | } 150 | 151 | constraints 152 | } 153 | 154 | fn generators(&self, row: usize, _local_constants: &[F]) -> Vec> { 155 | let gen = U32RangeCheckGenerator { gate: *self, row }; 156 | vec![WitnessGeneratorRef::new(gen.adapter())] 157 | } 158 | 159 | fn num_wires(&self) -> usize { 160 | self.num_input_limbs * (1 + self.aux_limbs_per_input_limb()) 161 | } 162 | 163 | fn num_constants(&self) -> usize { 164 | 0 165 | } 166 | 167 | // Bounded by the range-check (x-0)*(x-1)*...*(x-BASE+1). 168 | fn degree(&self) -> usize { 169 | Self::BASE 170 | } 171 | 172 | // 1 for checking the each sum of aux limbs, plus a range check for each aux limb. 173 | fn num_constraints(&self) -> usize { 174 | self.num_input_limbs * (1 + self.aux_limbs_per_input_limb()) 175 | } 176 | } 177 | 178 | #[derive(Debug)] 179 | pub struct U32RangeCheckGenerator, const D: usize> { 180 | gate: U32RangeCheckGate, 181 | row: usize, 182 | } 183 | 184 | impl, const D: usize> SimpleGenerator 185 | for U32RangeCheckGenerator 186 | { 187 | fn id(&self) -> String { 188 | "U32RangeCheckGenerator".to_string() 189 | } 190 | 191 | fn dependencies(&self) -> Vec { 192 | let num_input_limbs = self.gate.num_input_limbs; 193 | (0..num_input_limbs) 194 | .map(|i| Target::wire(self.row, self.gate.wire_ith_input_limb(i))) 195 | .collect() 196 | } 197 | 198 | fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { 199 | let num_input_limbs = self.gate.num_input_limbs; 200 | for i in 0..num_input_limbs { 201 | let sum_value = witness 202 | .get_target(Target::wire(self.row, self.gate.wire_ith_input_limb(i))) 203 | .to_canonical_u64() as u32; 204 | 205 | let base = U32RangeCheckGate::::BASE as u32; 206 | let limbs = (0..self.gate.aux_limbs_per_input_limb()) 207 | .map(|j| Target::wire(self.row, self.gate.wire_ith_input_limb_jth_aux_limb(i, j))); 208 | let limbs_value = (0..self.gate.aux_limbs_per_input_limb()) 209 | .scan(sum_value, |acc, _| { 210 | let tmp = *acc % base; 211 | *acc /= base; 212 | Some(F::from_canonical_u32(tmp)) 213 | }) 214 | .collect::>(); 215 | 216 | for (b, b_value) in limbs.zip(limbs_value) { 217 | out_buffer.set_target(b, b_value); 218 | } 219 | } 220 | } 221 | 222 | fn serialize(&self, dst: &mut Vec, common_data: &CommonCircuitData) -> IoResult<()> { 223 | self.gate.serialize(dst, common_data)?; 224 | dst.write_usize(self.row) 225 | } 226 | 227 | fn deserialize(src: &mut Buffer, common_data: &CommonCircuitData) -> IoResult { 228 | let gate = U32RangeCheckGate::deserialize(src, common_data)?; 229 | let row = src.read_usize()?; 230 | Ok(Self { row, gate }) 231 | } 232 | } 233 | 234 | #[cfg(test)] 235 | mod tests { 236 | use crate::plonky2_ecdsa::biguint::gates::gate_testing::{test_eval_fns, test_low_degree}; 237 | use anyhow::Result; 238 | use itertools::unfold; 239 | use plonky2::field::extension::quartic::QuarticExtension; 240 | use plonky2::field::goldilocks_field::GoldilocksField; 241 | use plonky2::field::types::{Field, Sample}; 242 | use plonky2::hash::hash_types::HashOut; 243 | use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; 244 | use rand::rngs::OsRng; 245 | use rand::Rng; 246 | 247 | use super::*; 248 | 249 | #[test] 250 | fn low_degree() { 251 | test_low_degree::(U32RangeCheckGate::new(8)) 252 | } 253 | 254 | #[test] 255 | fn eval_fns() -> Result<()> { 256 | const D: usize = 2; 257 | type C = PoseidonGoldilocksConfig; 258 | type F = >::F; 259 | test_eval_fns::(U32RangeCheckGate::new(8)) 260 | } 261 | 262 | fn test_gate_constraint(input_limbs: Vec) { 263 | type F = GoldilocksField; 264 | type FF = QuarticExtension; 265 | const D: usize = 4; 266 | const AUX_LIMB_BITS: usize = 2; 267 | const BASE: usize = 1 << AUX_LIMB_BITS; 268 | const AUX_LIMBS_PER_INPUT_LIMB: usize = ceil_div_usize(32, AUX_LIMB_BITS); 269 | 270 | fn get_wires(input_limbs: Vec) -> Vec { 271 | let num_input_limbs = input_limbs.len(); 272 | let mut v = Vec::new(); 273 | 274 | for i in 0..num_input_limbs { 275 | let input_limb = input_limbs[i]; 276 | 277 | let split_to_limbs = |mut val, num| { 278 | unfold((), move |_| { 279 | let ret = val % (BASE as u64); 280 | val /= BASE as u64; 281 | Some(ret) 282 | }) 283 | .take(num) 284 | .map(F::from_canonical_u64) 285 | }; 286 | 287 | let mut aux_limbs: Vec<_> = 288 | split_to_limbs(input_limb, AUX_LIMBS_PER_INPUT_LIMB).collect(); 289 | 290 | v.append(&mut aux_limbs); 291 | } 292 | 293 | input_limbs 294 | .iter() 295 | .cloned() 296 | .map(F::from_canonical_u64) 297 | .chain(v.iter().cloned()) 298 | .map(|x| x.into()) 299 | .collect() 300 | } 301 | 302 | let gate = U32RangeCheckGate:: { 303 | num_input_limbs: 8, 304 | _phantom: PhantomData, 305 | }; 306 | 307 | let vars = EvaluationVars { 308 | local_constants: &[], 309 | local_wires: &get_wires(input_limbs), 310 | public_inputs_hash: &HashOut::rand(), 311 | }; 312 | 313 | assert!( 314 | gate.eval_unfiltered(vars).iter().all(|x| x.is_zero()), 315 | "Gate constraints are not satisfied." 316 | ); 317 | } 318 | 319 | #[test] 320 | fn test_gate_constraint_good() { 321 | let mut rng = OsRng; 322 | let input_limbs: Vec<_> = (0..8).map(|_| rng.gen::() as u64).collect(); 323 | 324 | test_gate_constraint(input_limbs); 325 | } 326 | 327 | #[test] 328 | #[should_panic] 329 | fn test_gate_constraint_bad() { 330 | let mut rng = OsRng; 331 | let input_limbs: Vec<_> = (0..8).map(|_| rng.gen()).collect(); 332 | 333 | test_gate_constraint(input_limbs); 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /plonky2-backend/src/circuit_translation/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use acir::circuit::opcodes; 3 | use acir::circuit::opcodes::MemOp as GenericMemOp; 4 | use acir::circuit::opcodes::{BlockId, FunctionInput}; 5 | use acir::circuit::Circuit as GenericCircuit; 6 | use acir::circuit::Opcode as GenericOpcode; 7 | use acir::circuit::Program as GenericProgram; 8 | use acir::native_types::Expression as GenericExpression; 9 | pub use acir::native_types::Witness; 10 | use acir::native_types::WitnessStack as GenericWitnessStack; 11 | use num_bigint::BigUint; 12 | use std::collections::HashMap; 13 | 14 | // Generics 15 | pub use acir_field::AcirField; 16 | pub use acir_field::FieldElement; 17 | use plonky2::field::types::Field; 18 | use plonky2::iop::target::{BoolTarget, Target}; 19 | use plonky2::plonk::circuit_builder::CircuitBuilder; 20 | use plonky2::plonk::circuit_data::CircuitConfig; 21 | use plonky2::plonk::circuit_data::CircuitData; 22 | 23 | mod memory_translator; 24 | mod sha256_translator; 25 | 26 | use crate::binary_digits_target::BinaryDigitsTarget; 27 | use memory_translator::MemoryOperationsTranslator; 28 | use sha256_translator::Sha256CompressionTranslator; 29 | use crate::circuit_translation::ecdsa_secp256k1_translator::EcdsaSecp256k1Translator; 30 | 31 | #[cfg(test)] 32 | mod tests; 33 | 34 | pub mod assert_zero_translator; 35 | mod ecdsa_secp256k1_translator; 36 | 37 | pub(crate) type CB = CircuitBuilder; 38 | 39 | /// The FieldElement is imported from the Noir library, but for this backend to work the 40 | /// GoldilocksField should be used (and the witnesses generated accordingly). 41 | 42 | pub type Opcode = GenericOpcode; 43 | pub type Circuit = GenericCircuit; 44 | pub type Program = GenericProgram; 45 | pub type Expression = GenericExpression; 46 | pub type MemOp = GenericMemOp; 47 | pub type WitnessStack = GenericWitnessStack; 48 | 49 | /// This is the most important part of the backend. The CircuitBuilderFromAcirToPlonky2 translates 50 | /// the ACIR Circuit into an equivalent Plonky2 circuit. Besides the Plonky2 circuit, the output 51 | /// contains a mapping from ACIR Witnesses to Plonky2 Targets, which is not only for internal use 52 | /// but for assigning values to the targets when generating the proof. 53 | /// 54 | /// The opcodes suported are: AssertZero, MemoryInit, MemoryOp, BrilligCall, Directive(ToLeRadix), 55 | /// and the BlackboxFunctions: Range, And, Xor, SHA256Compression. 56 | /// 57 | /// Internally it uses a Plonky2 CircuitBuilder for generating the circuit, a mapping of memory 58 | /// blocks for the memory operations and the witness to targets mapping to retain the information 59 | /// about which target is which. 60 | 61 | pub struct CircuitBuilderFromAcirToPlonky2 { 62 | pub builder: CB, 63 | pub witness_target_map: HashMap, 64 | pub memory_blocks: HashMap, usize)>, 65 | } 66 | 67 | impl CircuitBuilderFromAcirToPlonky2 { 68 | pub fn new() -> Self { 69 | let config = CircuitConfig::wide_ecc_config(); 70 | let builder = CB::new(config); 71 | let witness_target_map: HashMap = HashMap::new(); 72 | let memory_blocks: HashMap, usize)> = HashMap::new(); 73 | Self { 74 | builder, 75 | witness_target_map, 76 | memory_blocks, 77 | } 78 | } 79 | 80 | pub fn unpack(self) -> (CircuitData, HashMap) { 81 | (self.builder.build::(), self.witness_target_map) 82 | } 83 | 84 | /// Main function of the module. It sequentially parses the ACIR opcodes, applying changes 85 | /// in the CircuitBuilder accordingly. 86 | pub fn translate_circuit(self: &mut Self, circuit: &Circuit) { 87 | self._register_witnesses_from_acir_circuit(circuit); 88 | for opcode in &circuit.opcodes { 89 | match opcode { 90 | Opcode::AssertZero(expr) => { 91 | let mut translator = assert_zero_translator::AssertZeroTranslator::new_for( 92 | &mut self.builder, 93 | &mut self.witness_target_map, 94 | &expr, 95 | ); 96 | translator.translate(); 97 | } 98 | Opcode::BrilligCall { 99 | id: _, 100 | inputs: _, 101 | outputs: _, 102 | predicate: _, 103 | } => {} // The brillig call is ignored since it has no impact in the circuit 104 | Opcode::Directive(_directive) => {} // The same happens with the Directive 105 | Opcode::MemoryInit { 106 | block_id, 107 | init, 108 | block_type: _, 109 | } => { 110 | MemoryOperationsTranslator::new_for( 111 | &mut self.builder, 112 | &mut self.witness_target_map, 113 | &mut self.memory_blocks, 114 | ) 115 | .translate_memory_init(init, block_id); 116 | } 117 | Opcode::MemoryOp { 118 | block_id, 119 | op, 120 | predicate: _, 121 | } => { 122 | MemoryOperationsTranslator::new_for( 123 | &mut self.builder, 124 | &mut self.witness_target_map, 125 | &mut self.memory_blocks, 126 | ) 127 | .translate_memory_op(block_id, op); 128 | } 129 | Opcode::BlackBoxFuncCall(func_call) => { 130 | match func_call { 131 | opcodes::BlackBoxFuncCall::RANGE { input } => { 132 | let long_max_bits = input.num_bits.clone() as usize; 133 | assert!(long_max_bits <= 33, 134 | "Range checks with more than 33 bits are not allowed yet while using Plonky2 prover"); 135 | let witness = input.witness; 136 | let target = self._get_or_create_target_for_witness(witness); 137 | self.builder.range_check(target, long_max_bits) 138 | } 139 | opcodes::BlackBoxFuncCall::AND { lhs, rhs, output } => { 140 | self._extend_circuit_with_bitwise_operation( 141 | lhs, 142 | rhs, 143 | output, 144 | BinaryDigitsTarget::and, 145 | ); 146 | } 147 | opcodes::BlackBoxFuncCall::XOR { lhs, rhs, output } => { 148 | self._extend_circuit_with_bitwise_operation( 149 | lhs, 150 | rhs, 151 | output, 152 | BinaryDigitsTarget::xor, 153 | ); 154 | } 155 | opcodes::BlackBoxFuncCall::Sha256Compression { 156 | inputs, 157 | hash_values, 158 | outputs, 159 | } => { 160 | self._extend_circuit_with_sha256_compression_operation( 161 | inputs, 162 | hash_values, 163 | outputs, 164 | ); 165 | } 166 | opcodes::BlackBoxFuncCall::EcdsaSecp256k1 { 167 | public_key_x, 168 | public_key_y, 169 | signature, 170 | hashed_message, 171 | output, 172 | } => { 173 | self._extend_circuit_with_ecdsa_secp256k1_operation( 174 | public_key_x, 175 | public_key_y, 176 | signature, 177 | hashed_message, 178 | *output 179 | ); 180 | } 181 | blackbox_func => { 182 | panic!("Blackbox func not supported yet: {:?}", blackbox_func); 183 | } 184 | }; 185 | } 186 | 187 | opcode => { 188 | panic!("Opcode not supported yet: {:?}", opcode); 189 | } 190 | } 191 | } 192 | } 193 | 194 | fn _extend_circuit_with_sha256_compression_operation( 195 | &mut self, 196 | inputs: &Box<[FunctionInput; 16]>, 197 | hash_values: &Box<[FunctionInput; 8]>, 198 | outputs: &Box<[Witness; 8]>, 199 | ) { 200 | let mut sha256_compression_translator = 201 | Sha256CompressionTranslator::new_for(self, inputs, hash_values, outputs); 202 | sha256_compression_translator.translate(); 203 | } 204 | 205 | fn _extend_circuit_with_ecdsa_secp256k1_operation( 206 | &mut self, 207 | public_key_x: &Box<[FunctionInput; 32]>, 208 | public_key_y: &Box<[FunctionInput; 32]>, 209 | signature: &Box<[FunctionInput; 64]>, 210 | hashed_message: &Box<[FunctionInput; 32]>, 211 | output: Witness, 212 | ) { 213 | let mut ecdsa_secp256k1_translator = 214 | EcdsaSecp256k1Translator::new_for(self, hashed_message, public_key_x, public_key_y, signature, output); 215 | ecdsa_secp256k1_translator.translate(); 216 | } 217 | 218 | fn _extend_circuit_with_bitwise_operation( 219 | self: &mut Self, 220 | lhs: &FunctionInput, 221 | rhs: &FunctionInput, 222 | output: &Witness, 223 | operation: fn(BinaryDigitsTarget, BinaryDigitsTarget, &mut CB) -> BinaryDigitsTarget, 224 | ) { 225 | assert_eq!(lhs.num_bits, rhs.num_bits); 226 | let binary_digits = lhs.num_bits as usize; 227 | let lhs_binary_target = self.binary_number_target_for_witness(lhs.witness, binary_digits); 228 | let rhs_binary_target = self.binary_number_target_for_witness(rhs.witness, binary_digits); 229 | 230 | let output_binary_target = 231 | operation(lhs_binary_target, rhs_binary_target, &mut self.builder); 232 | 233 | let output_target = self.convert_binary_number_to_number(output_binary_target); 234 | self.witness_target_map.insert(*output, output_target); 235 | } 236 | 237 | pub fn target_for_witness(&mut self, w: Witness) -> Target { 238 | self._get_or_create_target_for_witness(w) 239 | } 240 | 241 | pub fn binary_number_target_for_witness( 242 | &mut self, 243 | w: Witness, 244 | digits: usize, 245 | ) -> BinaryDigitsTarget { 246 | let target = self._get_or_create_target_for_witness(w); 247 | self.convert_number_to_binary_number(target, digits) 248 | } 249 | 250 | pub fn binary_number_target_for_constant( 251 | &mut self, 252 | constant: usize, 253 | digits: usize, 254 | ) -> BinaryDigitsTarget { 255 | let bit_targets = (0..digits) 256 | .map(|bit_position| self._constant_bool_target_for_bit(constant, bit_position)) 257 | .rev() 258 | .collect(); 259 | BinaryDigitsTarget { bits: bit_targets } 260 | } 261 | 262 | fn convert_number_to_binary_number( 263 | &mut self, 264 | number_target: Target, 265 | digits: usize, 266 | ) -> BinaryDigitsTarget { 267 | BinaryDigitsTarget { 268 | bits: self 269 | .builder 270 | .split_le(number_target, digits) 271 | .into_iter() 272 | .rev() 273 | .collect(), 274 | } 275 | } 276 | 277 | fn convert_binary_number_to_number(&mut self, a: BinaryDigitsTarget) -> Target { 278 | self.builder.le_sum(a.bits.into_iter().rev()) 279 | } 280 | 281 | fn _constant_bool_target_for_bit( 282 | &mut self, 283 | constant_value: usize, 284 | bit_position: usize, 285 | ) -> BoolTarget { 286 | let cond = (constant_value & (1 << bit_position)) != 0; 287 | self.builder.constant_bool(cond) 288 | } 289 | 290 | fn _register_witnesses_from_acir_circuit(self: &mut Self, circuit: &Circuit) { 291 | // Public parameters 292 | let public_parameters_as_list: Vec = 293 | circuit.public_parameters.0.iter().cloned().collect(); 294 | for public_parameter_witness in public_parameters_as_list { 295 | self._register_new_public_input_from_witness(public_parameter_witness); 296 | } 297 | // Private parameters 298 | let private_parameters_as_list: Vec = 299 | circuit.private_parameters.iter().cloned().collect(); 300 | for private_parameter_witness in private_parameters_as_list { 301 | self._register_new_private_input_from_witness(private_parameter_witness); 302 | } 303 | } 304 | 305 | fn _register_new_public_input_from_witness(self: &mut Self, public_input_witness: Witness) { 306 | let public_input_target = self.builder.add_virtual_target(); 307 | self.builder.register_public_input(public_input_target); 308 | self.witness_target_map 309 | .insert(public_input_witness, public_input_target); 310 | } 311 | 312 | fn _register_new_private_input_from_witness(self: &mut Self, private_input_witness: Witness) { 313 | self._get_or_create_target_for_witness(private_input_witness); 314 | } 315 | 316 | /// This method is key. The ACIR Opcodes talk about witnesses, while Plonky2 operates with 317 | /// targets, so when we want to know which target corresponds to a certain witness we might 318 | /// encounter that there isn't a target yet in the builder for the witness, so we need to 319 | /// create it. 320 | fn _get_or_create_target_for_witness(self: &mut Self, witness: Witness) -> Target { 321 | match self.witness_target_map.get(&witness) { 322 | Some(target) => *target, 323 | None => { 324 | let target = self.builder.add_virtual_target(); 325 | self.witness_target_map.insert(witness, target); 326 | target 327 | } 328 | } 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /plonky2-backend/src/plonky2_ecdsa/curve/gadgets/glv.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | 3 | use num::rational::Ratio; 4 | use num::traits::FromBytes; 5 | use num::BigUint; 6 | use plonky2::field::extension::Extendable; 7 | use plonky2::field::secp256k1_base::Secp256K1Base; 8 | use plonky2::field::secp256k1_scalar::Secp256K1Scalar; 9 | use plonky2::field::types::{Field, PrimeField}; 10 | use plonky2::hash::hash_types::RichField; 11 | use plonky2::iop::generator::{GeneratedValues, SimpleGenerator}; 12 | use plonky2::iop::target::{BoolTarget, Target}; 13 | use plonky2::iop::witness::{PartitionWitness, WitnessWrite}; 14 | use plonky2::plonk::circuit_builder::CircuitBuilder; 15 | 16 | use crate::plonky2_ecdsa::biguint::biguint::{BigUintTarget, GeneratedValuesBigUint, WitnessBigUint}; 17 | use crate::plonky2_ecdsa::biguint::gadgets::arithmetic_u32::{CircuitBuilderU32, U32Target}; 18 | use crate::plonky2_ecdsa::biguint::gadgets::nonnative::{CircuitBuilderNonNative, NonNativeTarget}; 19 | use crate::plonky2_ecdsa::biguint::gadgets::split_nonnative::CircuitBuilderSplit; 20 | 21 | use super::curve::{generate_random_point, AffinePointTarget, CircuitBuilderCurve}; 22 | 23 | pub const GLV_BETA: Secp256K1Base = Secp256K1Base([ 24 | 13923278643952681454, 25 | 11308619431505398165, 26 | 7954561588662645993, 27 | 8856726876819556112, 28 | ]); 29 | 30 | pub const GLV_S: Secp256K1Scalar = Secp256K1Scalar([ 31 | 16069571880186789234, 32 | 1310022930574435960, 33 | 11900229862571533402, 34 | 6008836872998760672, 35 | ]); 36 | 37 | const A1: Secp256K1Scalar = Secp256K1Scalar([16747920425669159701, 3496713202691238861, 0, 0]); 38 | 39 | const MINUS_B1: Secp256K1Scalar = 40 | Secp256K1Scalar([8022177200260244675, 16448129721693014056, 0, 0]); 41 | 42 | const A2: Secp256K1Scalar = Secp256K1Scalar([6323353552219852760, 1498098850674701302, 1, 0]); 43 | 44 | const B2: Secp256K1Scalar = Secp256K1Scalar([16747920425669159701, 3496713202691238861, 0, 0]); 45 | 46 | /// Algorithm 15.41 in Handbook of Elliptic and Hyperelliptic Curve Cryptography. 47 | /// Decompose a scalar `k` into two small scalars `k1, k2` with `|k1|, |k2| < √p` that satisfy 48 | /// `k1 + s * k2 = k`. 49 | /// Returns `(|k1|, |k2|, k1 < 0, k2 < 0)`. 50 | pub fn decompose_secp256k1_scalar( 51 | k: Secp256K1Scalar, 52 | ) -> (Secp256K1Scalar, Secp256K1Scalar, bool, bool) { 53 | let p = Secp256K1Scalar::order(); 54 | let c1_biguint = Ratio::new( 55 | B2.to_canonical_biguint() * k.to_canonical_biguint(), 56 | p.clone(), 57 | ) 58 | .round() 59 | .to_integer(); 60 | let c1 = Secp256K1Scalar::from_noncanonical_biguint(c1_biguint); 61 | let c2_biguint = Ratio::new( 62 | MINUS_B1.to_canonical_biguint() * k.to_canonical_biguint(), 63 | p.clone(), 64 | ) 65 | .round() 66 | .to_integer(); 67 | let c2 = Secp256K1Scalar::from_noncanonical_biguint(c2_biguint); 68 | 69 | let k1_raw = k - c1 * A1 - c2 * A2; 70 | let k2_raw = c1 * MINUS_B1 - c2 * B2; 71 | debug_assert!(k1_raw + GLV_S * k2_raw == k); 72 | 73 | let two = BigUint::from_slice(&[2]); 74 | let k1_neg = k1_raw.to_canonical_biguint() > p.clone() / two.clone(); 75 | let k1 = if k1_neg { 76 | Secp256K1Scalar::from_noncanonical_biguint(p.clone() - k1_raw.to_canonical_biguint()) 77 | } else { 78 | k1_raw 79 | }; 80 | let k2_neg = k2_raw.to_canonical_biguint() > p.clone() / two; 81 | let k2 = if k2_neg { 82 | Secp256K1Scalar::from_noncanonical_biguint(p - k2_raw.to_canonical_biguint()) 83 | } else { 84 | k2_raw 85 | }; 86 | 87 | (k1, k2, k1_neg, k2_neg) 88 | } 89 | 90 | pub trait CircuitBuilderGlv, const D: usize> { 91 | fn secp256k1_glv_beta(&mut self) -> NonNativeTarget; 92 | 93 | fn decompose_secp256k1_scalar( 94 | &mut self, 95 | k: &NonNativeTarget, 96 | ) -> ( 97 | NonNativeTarget, 98 | NonNativeTarget, 99 | BoolTarget, 100 | BoolTarget, 101 | ); 102 | 103 | fn glv_mul( 104 | &mut self, 105 | p: &AffinePointTarget, 106 | k: &NonNativeTarget, 107 | ) -> AffinePointTarget; 108 | } 109 | 110 | impl, const D: usize> CircuitBuilderGlv 111 | for CircuitBuilder 112 | { 113 | fn secp256k1_glv_beta(&mut self) -> NonNativeTarget { 114 | self.constant_nonnative(GLV_BETA) 115 | } 116 | 117 | fn decompose_secp256k1_scalar( 118 | &mut self, 119 | k: &NonNativeTarget, 120 | ) -> ( 121 | NonNativeTarget, 122 | NonNativeTarget, 123 | BoolTarget, 124 | BoolTarget, 125 | ) { 126 | let k1 = self.add_virtual_nonnative_target_sized::(4); 127 | let k2 = self.add_virtual_nonnative_target_sized::(4); 128 | let k1_neg = self.add_virtual_bool_target_unsafe(); 129 | let k2_neg = self.add_virtual_bool_target_unsafe(); 130 | 131 | self.add_simple_generator(GLVDecompositionGenerator:: { 132 | k: k.clone(), 133 | k1: k1.clone(), 134 | k2: k2.clone(), 135 | k1_neg, 136 | k2_neg, 137 | _phantom: PhantomData, 138 | }); 139 | 140 | // Check that `k1_raw + GLV_S * k2_raw == k`. 141 | let k1_raw = self.nonnative_conditional_neg(&k1, k1_neg); 142 | let k2_raw = self.nonnative_conditional_neg(&k2, k2_neg); 143 | let s = self.constant_nonnative(GLV_S); 144 | let mut should_be_k = self.mul_nonnative(&s, &k2_raw); 145 | should_be_k = self.add_nonnative(&should_be_k, &k1_raw); 146 | self.connect_nonnative(&should_be_k, k); 147 | 148 | (k1, k2, k1_neg, k2_neg) 149 | } 150 | 151 | fn glv_mul( 152 | &mut self, 153 | p: &AffinePointTarget, 154 | k: &NonNativeTarget, 155 | ) -> AffinePointTarget { 156 | let (k1, k2, k1_neg, k2_neg) = self.decompose_secp256k1_scalar(k); 157 | 158 | let beta = self.secp256k1_glv_beta(); 159 | let beta_px = self.mul_nonnative(&beta, &p.x); 160 | let sp = AffinePointTarget { 161 | x: beta_px, 162 | y: p.y.clone(), 163 | }; 164 | 165 | let p_neg = self.curve_conditional_neg(p, k1_neg); 166 | let sp_neg = self.curve_conditional_neg(&sp, k2_neg); 167 | curve_msm_circuit(self, &p_neg, &sp_neg, &k1, &k2) 168 | } 169 | } 170 | 171 | /// Computes `n*p + m*q` using windowed MSM, with a 2-bit window. 172 | /// See Algorithm 9.23 in Handbook of Elliptic and Hyperelliptic Curve Cryptography for a 173 | /// description. 174 | /// Note: Doesn't work if `p == q`. 175 | pub fn curve_msm_circuit, const D: usize>( 176 | builder: &mut CircuitBuilder, 177 | p: &AffinePointTarget, 178 | q: &AffinePointTarget, 179 | n: &NonNativeTarget, 180 | m: &NonNativeTarget, 181 | ) -> AffinePointTarget { 182 | let limbs_n = builder.split_nonnative_to_2_bit_limbs(n); 183 | let limbs_m = builder.split_nonnative_to_2_bit_limbs(m); 184 | assert_eq!(limbs_n.len(), limbs_m.len()); 185 | 186 | let rando_t = builder.constant_affine_point( 187 | Secp256K1Base::from_noncanonical_biguint(BigUint::from_be_bytes(&[ 188 | 168, 108, 112, 254, 40, 235, 44, 180, 232, 129, 170, 129, 151, 26, 229, 18, 19, 137, 189 | 245, 62, 139, 130, 119, 30, 84, 53, 9, 156, 170, 172, 160, 15, 190 | ])), 191 | Secp256K1Base::from_noncanonical_biguint(BigUint::from_be_bytes(&[ 192 | 60, 32, 167, 79, 44, 197, 157, 125, 248, 190, 148, 181, 142, 227, 95, 8, 136, 133, 192, 193 | 43, 110, 22, 130, 29, 171, 221, 92, 43, 9, 1, 185, 27, 194 | ])), 195 | ); 196 | 197 | let neg_rando = builder.constant_affine_point( 198 | Secp256K1Base::from_noncanonical_biguint(BigUint::from_be_bytes(&[ 199 | 168, 108, 112, 254, 40, 235, 44, 180, 232, 129, 170, 129, 151, 26, 229, 18, 19, 137, 200 | 245, 62, 139, 130, 119, 30, 84, 53, 9, 156, 170, 172, 160, 15, 201 | ])), 202 | Secp256K1Base::from_noncanonical_biguint(BigUint::from_be_bytes(&[ 203 | 195, 223, 88, 176, 211, 58, 98, 130, 7, 65, 107, 74, 113, 28, 160, 247, 119, 122, 63, 204 | 212, 145, 233, 125, 226, 84, 34, 163, 211, 246, 254, 67, 20, 205 | ])), 206 | ); 207 | 208 | // Precomputes `precomputation[i + 4*j] = i*p + j*q` for `i,j=0..4`. 209 | let mut precomputation = vec![p.clone(); 16]; 210 | let mut cur_p = rando_t.clone(); 211 | let mut cur_q = rando_t.clone(); 212 | for i in 0..4 { 213 | precomputation[i] = cur_p.clone(); 214 | precomputation[4 * i] = cur_q.clone(); 215 | cur_p = builder.curve_add(&cur_p, p); 216 | cur_q = builder.curve_add(&cur_q, q); 217 | } 218 | for i in 1..4 { 219 | precomputation[i] = builder.curve_add(&precomputation[i], &neg_rando); 220 | precomputation[4 * i] = builder.curve_add(&precomputation[4 * i], &neg_rando); 221 | } 222 | for i in 1..4 { 223 | for j in 1..4 { 224 | precomputation[i + 4 * j] = 225 | builder.curve_add(&precomputation[i], &precomputation[4 * j]); 226 | } 227 | } 228 | 229 | let four = builder.constant(F::from_canonical_usize(4)); 230 | 231 | let zero = builder.zero(); 232 | let mut result = rando_t; 233 | for (limb_n, limb_m) in limbs_n.into_iter().zip(limbs_m).rev() { 234 | result = builder.curve_repeated_double(&result, 2); 235 | let index = builder.mul_add(four, limb_m, limb_n); 236 | let r = builder.random_access_curve_points(index, precomputation.clone()); 237 | let is_zero = builder.is_equal(index, zero); 238 | let should_add = builder.not(is_zero); 239 | result = builder.curve_conditional_add(&result, &r, should_add); 240 | } 241 | let to_add = builder.constant_affine_point( 242 | Secp256K1Base::from_noncanonical_biguint(BigUint::from_be_bytes(&[ 243 | 4, 240, 116, 128, 2, 142, 26, 67, 121, 228, 15, 172, 125, 56, 178, 55, 220, 178, 31, 244 | 194, 90, 168, 40, 127, 59, 193, 0, 121, 236, 178, 130, 29, 245 | ])), 246 | Secp256K1Base::from_noncanonical_biguint(BigUint::from_be_bytes(&[ 247 | 195, 20, 74, 65, 215, 167, 153, 201, 235, 110, 231, 40, 207, 121, 30, 55, 18, 16, 205, 248 | 138, 169, 66, 20, 253, 49, 54, 35, 152, 247, 117, 246, 155, 249 | ])), 250 | ); 251 | 252 | result = builder.curve_add(&result, &to_add); 253 | 254 | result 255 | } 256 | 257 | #[derive(Debug)] 258 | struct GLVDecompositionGenerator, const D: usize> { 259 | k: NonNativeTarget, 260 | k1: NonNativeTarget, 261 | k2: NonNativeTarget, 262 | k1_neg: BoolTarget, 263 | k2_neg: BoolTarget, 264 | _phantom: PhantomData, 265 | } 266 | 267 | impl, const D: usize> SimpleGenerator 268 | for GLVDecompositionGenerator 269 | { 270 | fn dependencies(&self) -> Vec { 271 | self.k.value.limbs.iter().map(|l| l.0).collect() 272 | } 273 | 274 | fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { 275 | let k = Secp256K1Scalar::from_noncanonical_biguint( 276 | witness.get_biguint_target(self.k.value.clone()), 277 | ); 278 | 279 | let (k1, k2, k1_neg, k2_neg) = decompose_secp256k1_scalar(k); 280 | 281 | out_buffer.set_biguint_target(&self.k1.value, &k1.to_canonical_biguint()); 282 | out_buffer.set_biguint_target(&self.k2.value, &k2.to_canonical_biguint()); 283 | out_buffer.set_bool_target(self.k1_neg, k1_neg); 284 | out_buffer.set_bool_target(self.k2_neg, k2_neg); 285 | } 286 | 287 | fn id(&self) -> String { 288 | todo!() 289 | } 290 | 291 | fn serialize( 292 | &self, 293 | _dst: &mut Vec, 294 | _common_data: &plonky2::plonk::circuit_data::CommonCircuitData, 295 | ) -> plonky2::util::serialization::IoResult<()> { 296 | todo!() 297 | } 298 | 299 | fn deserialize( 300 | _src: &mut plonky2::util::serialization::Buffer, 301 | _common_data: &plonky2::plonk::circuit_data::CommonCircuitData, 302 | ) -> plonky2::util::serialization::IoResult 303 | where 304 | Self: Sized, 305 | { 306 | todo!() 307 | } 308 | } 309 | 310 | const WINDOW_SIZE: usize = 4; 311 | 312 | pub trait CircuitBuilderWindowedMul, const D: usize> { 313 | fn precompute_window(&mut self, p: &AffinePointTarget) -> Vec; 314 | 315 | fn random_access_curve_points( 316 | &mut self, 317 | access_index: Target, 318 | v: Vec, 319 | ) -> AffinePointTarget; 320 | } 321 | 322 | impl, const D: usize> CircuitBuilderWindowedMul 323 | for CircuitBuilder 324 | { 325 | fn precompute_window(&mut self, p: &AffinePointTarget) -> Vec { 326 | let (x, y) = generate_random_point(self); 327 | let g = self.constant_affine_point(x, y); 328 | let neg = self.constant_affine_point(x, -y); 329 | let mut multiples = vec![g]; 330 | for i in 1..1 << WINDOW_SIZE { 331 | multiples.push(self.curve_add(p, &multiples[i - 1])); 332 | } 333 | for i in 1..1 << WINDOW_SIZE { 334 | multiples[i] = self.curve_add(&neg, &multiples[i]); 335 | } 336 | multiples 337 | } 338 | 339 | fn random_access_curve_points( 340 | &mut self, 341 | access_index: Target, 342 | v: Vec, 343 | ) -> AffinePointTarget { 344 | let num_limbs = 8; 345 | let zero = self.zero_u32(); 346 | let x_limbs: Vec> = (0..num_limbs) 347 | .map(|i| { 348 | v.iter() 349 | .map(|p| p.x.value.limbs.get(i).unwrap_or(&zero).0) 350 | .collect() 351 | }) 352 | .collect(); 353 | let y_limbs: Vec> = (0..num_limbs) 354 | .map(|i| { 355 | v.iter() 356 | .map(|p| p.y.value.limbs.get(i).unwrap_or(&zero).0) 357 | .collect() 358 | }) 359 | .collect(); 360 | 361 | let selected_x_limbs: Vec<_> = x_limbs 362 | .iter() 363 | .map(|limbs| U32Target(self.random_access(access_index, limbs.clone()))) 364 | .collect(); 365 | let selected_y_limbs: Vec<_> = y_limbs 366 | .iter() 367 | .map(|limbs| U32Target(self.random_access(access_index, limbs.clone()))) 368 | .collect(); 369 | 370 | let x = NonNativeTarget { 371 | value: BigUintTarget { 372 | limbs: selected_x_limbs, 373 | }, 374 | _phantom: PhantomData, 375 | }; 376 | let y = NonNativeTarget { 377 | value: BigUintTarget { 378 | limbs: selected_y_limbs, 379 | }, 380 | _phantom: PhantomData, 381 | }; 382 | AffinePointTarget { x, y } 383 | } 384 | } 385 | --------------------------------------------------------------------------------