├── src ├── main.rs ├── lib.rs ├── circuits │ ├── flag1.rs │ ├── modulo.rs │ ├── prod.rs │ ├── tables │ │ ├── pow.rs │ │ ├── mod.rs │ │ ├── signed.rs │ │ ├── exe │ │ │ └── temp_vars.rs │ │ ├── prog.rs │ │ ├── aux │ │ │ ├── out_table.rs │ │ │ └── out.rs │ │ ├── even_bits.rs │ │ └── mem.rs │ ├── flag2.rs │ ├── flag4.rs │ ├── sprod.rs │ ├── ssum.rs │ ├── flag3.rs │ ├── changed.rs │ ├── sum.rs │ ├── mod.rs │ └── shift.rs ├── instructions │ └── opcode.rs ├── test_utils.rs ├── assign.rs └── instructions.rs ├── rustfmt.toml ├── rust-toolchain.toml ├── .gitignore ├── proptest-regressions ├── circuits │ ├── tables │ │ ├── even_bits.txt │ │ └── exe.txt │ ├── cmpa.txt │ ├── signed_sum.txt │ ├── and.txt │ ├── logic.txt │ ├── sum.txt │ ├── xor.txt │ └── shift.txt ├── gadgets │ ├── tables │ │ └── even_bits.txt │ ├── and.txt │ ├── greater_than.txt │ └── cmpa.txt └── trace.txt ├── NOTICE ├── Cargo.toml ├── README.md ├── flake.nix ├── LICENSE └── flake.lock /src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 85 2 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.65.0" 3 | components = ["rustc", "rustfmt", "rust-src", "cargo", "clippy", "rust-docs"] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | params 3 | proof 4 | verifying_key 5 | result-bin 6 | result 7 | rusty-tags.vi 8 | 9 | # generated from tests 10 | circuit.dot 11 | layout.png 12 | -------------------------------------------------------------------------------- /proptest-regressions/circuits/tables/even_bits.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc b57e0564ca9d9d7f65e13a35c8c0de7bece029c8ac807133b5b7f58d1939aff6 # shrinks to a = 16777216 8 | -------------------------------------------------------------------------------- /proptest-regressions/gadgets/tables/even_bits.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc f9a4496c5cbbef9085fed68c502b51840e7461807110e9ec069599185741923a # shrinks to a = 16777216 8 | -------------------------------------------------------------------------------- /proptest-regressions/trace.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc ccbc164895bf334adc65f0ad15ecaf49aefe4170ed8dbc6f7d66acfcefc2d850 # shrinks to s = 0 8 | cc 22f82da83b11126df68831fce8b8842e7a3fffe24a133be0f0aab7acb2768361 # shrinks to s = 0 9 | -------------------------------------------------------------------------------- /proptest-regressions/circuits/cmpa.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc cccf085f005b15dbff500959545604a3b758da0f5eda20cb3c3dc6428c765270 # shrinks to a = 0, b = 0 8 | cc e8e137796b79e8f6f834a690ffe93ae040e1c261d11c87683430b17f1256891f # shrinks to a = 0, b = 0 9 | -------------------------------------------------------------------------------- /proptest-regressions/circuits/signed_sum.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 1a6c6f6b90c959aefb782ae2cb9ca15bb03eb4427e3bd71cca5370367a327dbc # shrinks to (a, b, c, flag) = (0, 0, 0, false) 8 | cc 60668117a63a9be0b1dc40d3179bff991602b59618bb52447d1130b62b6c8256 # shrinks to (a, b, c, flag) = (0, 0, 0, false) 9 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Orbis Labs 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tiny-ram-halo2" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [profile.test] 7 | # opt-level = 3 8 | debug-assertions = true 9 | # lto = true 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [dependencies] 14 | pasta_curves = "0.3.0" 15 | rand_core = { version = "0.6", features = [ "getrandom" ] } 16 | plotters = { version = "0.3.0" } 17 | proptest = "1.0.0" 18 | lazy_static = "1.4.0" 19 | 20 | [dependencies.halo2_proofs] 21 | git = "https://github.com/Orbis-Tertius/halo2" 22 | branch = "tinyram-fork" 23 | features = ["dev-graph"] 24 | -------------------------------------------------------------------------------- /proptest-regressions/circuits/and.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc e03eb2d74f7586b54f16a38e1bc473059b414ebc95f00666c99bec3b6d1e820f # shrinks to a = 0, b = 0 8 | cc fb9b56f0552f695bdc1fdae44b1eec65b14f2eeedff7744fe27e85db1c5a20df # shrinks to a = 0, b = 0 9 | cc 452ece6ef176f90692d717f5d08049be97747533652e71b0316199b32963d807 # shrinks to a = 0, b = 38 10 | -------------------------------------------------------------------------------- /proptest-regressions/circuits/logic.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc b546e91275cde1eee7f1df504e209ccc75c7628bce230f1b75e68f56b2bbc971 # shrinks to a = 0, b = 0 8 | cc 802361d6c3724e9287b69a6917b6aa45c3c1714985ea9e1df75cc383319b0135 # shrinks to a = 0, b = 0 9 | cc 05b0175dfcc754da03caf0260dcf5148987be879519207f44bdf7f60fdd8b5ef # shrinks to a = 0, b = 85 10 | -------------------------------------------------------------------------------- /proptest-regressions/circuits/sum.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 7e211063e5363195efaf5be798f97b3b3c7ab46cb78ca2bcd2fb34ffc8c30b0e # shrinks to a = 0, b = 0 8 | cc 5f253e66accb3a501c3490dde0ec39ca4f336208e7c388e0a03c35b880eced9d # shrinks to a = 0, b = 0 9 | cc eb290e6bf69ff355a9742889d32e4b3410734d5a09da4ffdccf72ce8fbc4fa3c # shrinks to a = 0, b = 84 10 | -------------------------------------------------------------------------------- /proptest-regressions/circuits/xor.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 0939dba934df86097035df5dd1201ca7aa14585a123e36bfb4f2e0cebdae87aa # shrinks to a = 0, b = 1 8 | cc b28dbb40b4e49c1b6bb9fc1f607a23c1746cfe70c9166bcae8443176138a8919 # shrinks to a = 0, b = 1 9 | cc e22f262f91c0fc46860ce891c09d2d673f50ba51bdd7a9dbe197af83a3998568 # shrinks to a = 0, b = 1 10 | -------------------------------------------------------------------------------- /proptest-regressions/gadgets/and.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc edbd73290f8d19bbf20c5fe3b8f3f837f71a886e7ab8cc2ffa6e7777ceefa4cc # shrinks to a = 0, b = 0, c = 1 8 | cc ff39f2f26a5796799231facb18f86df03e2385e291426199a29b46e870a24f11 # shrinks to a = 0, b = 0, c = 1 9 | cc ae0a28ca96ec6d7ce8f4d22fd5f47477036b59d433ec3c61dd2cdf8ed98a762b # shrinks to a = 0, b = 21, c = 15166235 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tiny-ram-halo2 2 | 3 | This repo is in the early stages of development. 4 | It will contain an implementation of a TinyRAM Harvard architecture execution verifier using [halo2](https://github.com/zcash/halo2). 5 | 6 | ## Building or Testing 7 | 8 | You do not need `rustc`, `cargo`, or `rust-analyzer` installed on your system. They are provided by this repository's nix flake. 9 | ```bash 10 | # Enter a nix shell 11 | nix develop 12 | 13 | # Edit 14 | 15 | # Run tests 16 | cargo test 17 | ``` 18 | 19 | ### To run test's exactly as our CI does: 20 | 21 | `nix build ./#ci` 22 | 23 | 24 | ## References 25 | 26 | The TinyRAM execution constraints are based on [Nearly Linear-Time Zero-Knowledge Proofs for Correct Program Execution](https://eprint.iacr.org/2018/380). 27 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod assign; 2 | pub mod circuits; 3 | pub mod instructions; 4 | #[cfg(test)] 5 | pub mod test_utils; 6 | pub mod trace; 7 | 8 | use lazy_static::lazy_static; 9 | use std::{collections::BTreeSet, sync::RwLock}; 10 | 11 | lazy_static! { 12 | static ref LEAKED: RwLock> = RwLock::new(BTreeSet::new()); 13 | } 14 | 15 | // TODO add support for naming columns in halo2, so I can make better use of this. 16 | pub fn leak_once + AsRef + Ord>(s: S) -> &'static str { 17 | let l = { 18 | let leaked = LEAKED.read().unwrap(); 19 | leaked.get(s.as_ref()).copied() 20 | }; 21 | 22 | if let Some(l) = l { 23 | l 24 | } else { 25 | let mut leaked = LEAKED.write().unwrap(); 26 | let s = Box::leak(s.into().into_boxed_str()); 27 | leaked.insert(s); 28 | s 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /proptest-regressions/circuits/tables/exe.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 6fd596153d05d3cb9aa19a4eaa846d7569c67298b5614f69ff2e1cc99f2be9b0 # shrinks to a = 0, b = 0 8 | cc 6d9478991b41402c71fd5251ab9cb1f6cac5d405541930665042d2c63827b600 # shrinks to a = 0, b = 2 9 | cc 35aad03766443de37b9e97aa0222032156c88aa091a7a4550ed2db8a3452c9af # shrinks to a = 143, b = 0 10 | cc 4ae595a6845fc9ac6105f35f7c5e2044bfa31170cd5190e6bc3129e6b985913b # shrinks to a = Word(0), b = Word(0) 11 | cc a6e538f20d45edcb2a9c4b653615f7c8f7b2a23fedcff5fe8a45d3863021224f # shrinks to a = 0, b = 0 12 | cc aa554d3d47ce79acf09fbcfbb35cb422932afd1d53887525b69e26ddaa27c30f # shrinks to a = 8, b = 1 13 | -------------------------------------------------------------------------------- /proptest-regressions/gadgets/greater_than.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 36bd4da8007d5b56cf422706267d73abc86ed9e99015322ae4dc534ccd61911a # shrinks to a = 0, b = 0 8 | cc c7ae7d475bcdfad7eb31ae154fc31dedda52d04f0380a4d04a5eceec76038634 # shrinks to a = 79, b = 0 9 | cc 2939f52b4652277fb5b3cd8d2d88786193f33c05d5121c789ab2d945932378f9 # shrinks to a = 0, b = 0 10 | cc 006751db8fd1ba663f7e69381394e45b8a616fcde66747d41a95563b023365a4 # shrinks to a = 17641, b = 0 11 | cc 1e92067be6dd9ee17b617a5df400b7a5f7ab6b2a02f5e135913fd2e952951e29 # shrinks to a = 0, b = 121 12 | cc 53d5c3c9b0711999bd5d0e9c2a41c15a2c8eb765b0a4f660d0891b8391c6155a # shrinks to a = 15884671, b = 484 13 | -------------------------------------------------------------------------------- /proptest-regressions/circuits/shift.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 78db42c35c9ed078bba063c0b7e25f60dcec802329ba867330dba4eabbeeef9b # shrinks to (a, b, c, flag) = (0, 0, 0, false) 8 | cc 2f51aa4ffb68105c6da23d15edc6d1087fb3968df9988da6ab7d043c2e3d428b # shrinks to (a, b, c, flag) = (0, 0, 0, false) 9 | cc e55680f7edca5d159131d8c2ca248385f020ce2ebe56f1c41661afbc8ee79fc2 # shrinks to (a, b, c, flag) = (0, 0, 0, false) 10 | cc 82afe58e1c31e41a823db29a8ce77a19b4f12048452b938e718ff1ffc0b64abd # shrinks to word = 0, shift_bits = 0, c = 1, flag = false 11 | cc 74829795f0c5ce3a12faae607f6af0e051703191c8774a2ca1d667a2ab41f342 # shrinks to s = [(0, 0, 0, false), (0, 0, 0, false), (0, 0, 0, false), (0, 0, 0, false), (0, 0, 0, false), (0, 0, 0, false), (0, 0, 0, false), (0, 0, 0, false), (0, 2, 0, false), (233, 0, 233, false)] 12 | cc bba27c766958498118ed6bbd297bc6be12f0f9a250337c7ad164c31aa734cee5 13 | -------------------------------------------------------------------------------- /src/circuits/flag1.rs: -------------------------------------------------------------------------------- 1 | // TODO add tests using dead code 2 | #![allow(dead_code)] 3 | use crate::assign::ConstraintSys; 4 | use halo2_proofs::plonk::Constraints; 5 | use halo2_proofs::{ 6 | arithmetic::FieldExt, 7 | plonk::{Advice, Column, Selector}, 8 | poly::Rotation, 9 | }; 10 | 11 | #[derive(Debug, Clone, Copy)] 12 | pub struct Flag1Config { 13 | /// A Selector denoting the extent of the exe table. 14 | s_table: Selector, 15 | /// An advice columns that acts as a selector for flag1's gate. 16 | /// [`Out.flag1`](crate::circuits::tables::aux::Out) 17 | s_flag1: Column, 18 | 19 | c: Column, 20 | flag: Column, 21 | } 22 | 23 | impl Flag1Config { 24 | pub fn configure( 25 | meta: &mut impl ConstraintSys>, 26 | s_table: Selector, 27 | s_flag1: Column, 28 | 29 | c: Column, 30 | flag: Column, 31 | ) -> Self { 32 | meta.cs().create_gate("flag1", |meta| { 33 | let s_table = meta.query_selector(s_table); 34 | let s_flag1 = meta.query_advice(s_flag1, Rotation::cur()); 35 | 36 | let c = meta.query_advice(c, Rotation::cur()); 37 | let flag_n = meta.query_advice(flag, Rotation::next()); 38 | 39 | Constraints::with_selector(s_table * s_flag1, [flag_n * c]) 40 | }); 41 | 42 | Self { 43 | s_table, 44 | s_flag1, 45 | c, 46 | flag, 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 3 | inputs.dream2nix = { url = "github:nix-community/dream2nix"; inputs.nixpkgs.follows = "nixpkgs"; }; 4 | inputs.fenix = { url = "github:nix-community/fenix"; inputs.nixpkgs.follows = "nixpkgs"; }; 5 | 6 | outputs = { self, nixpkgs, dream2nix, fenix }: 7 | let 8 | pkgs = nixpkgs.legacyPackages.x86_64-linux; 9 | channelVersion = "1.65.0"; 10 | toolchain = fenix.packages.x86_64-linux.toolchainOf { 11 | channel = channelVersion; 12 | sha256 = "sha256-DzNEaW724O8/B8844tt5AVHmSjSQ3cmzlU4BP90oRlY="; 13 | }; 14 | in 15 | (dream2nix.lib.makeFlakeOutputs { 16 | systems = [ "x86_64-linux" ]; 17 | config.projectRoot = ./.; 18 | source = ./.; 19 | packageOverrides.tiny-ram-halo2 = { 20 | set-toolchain.overrideRustToolchain = old: { inherit (toolchain) cargo rustc; }; 21 | freetype-sys.nativeBuildInputs = old: old ++ [ pkgs.cmake ]; 22 | expat-sys.nativeBuildInputs = old: old ++ [ pkgs.cmake ]; 23 | servo-fontconfig-sys = { 24 | nativeBuildInputs = old: old ++ [ pkgs.pkg-config ]; 25 | buildInputs = old: old ++ [ pkgs.fontconfig ]; 26 | }; 27 | }; 28 | }) 29 | // { 30 | checks.x86_64-linux.tiny-ram-halo2 = self.packages.x86_64-linux.tiny-ram-halo2; 31 | 32 | devShells.x86_64-linux.default = 33 | let 34 | rust-toolchain = (pkgs.formats.toml { }).generate "rust-toolchain.toml" { 35 | toolchain = { 36 | channel = channelVersion; 37 | components = [ "rustc" "rustfmt" "rust-src" "cargo" "clippy" "rust-docs" ]; 38 | }; 39 | }; 40 | in 41 | pkgs.mkShell { 42 | shellHook = "cp --no-preserve=mode ${rust-toolchain} rust-toolchain.toml"; 43 | packages = [ 44 | pkgs.rustup 45 | fenix.packages.x86_64-linux.rust-analyzer 46 | pkgs.cmake 47 | pkgs.pkg-config 48 | pkgs.fontconfig 49 | ]; 50 | }; 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /src/circuits/modulo.rs: -------------------------------------------------------------------------------- 1 | // TODO add tests using dead code 2 | #![allow(dead_code)] 3 | use crate::assign::ConstraintSys; 4 | use halo2_proofs::plonk::Constraints; 5 | use halo2_proofs::poly::Rotation; 6 | use halo2_proofs::{ 7 | arithmetic::FieldExt, 8 | plonk::{Advice, Column, Selector}, 9 | }; 10 | 11 | #[derive(Debug, Clone, Copy)] 12 | pub struct ModConfig { 13 | /// A Selector denoting the extent of the exe table. 14 | s_table: Selector, 15 | /// An advice columns that acts as a selector for mod's gates. 16 | /// `Out.mod` 17 | s_mod: Column, 18 | 19 | a: Column, 20 | b: Column, 21 | c: Column, 22 | d: Column, 23 | 24 | flag: Column, 25 | } 26 | 27 | impl ModConfig { 28 | #[allow(clippy::complexity)] 29 | pub fn configure( 30 | meta: &mut impl ConstraintSys>, 31 | s_table: Selector, 32 | s_mod: Column, 33 | 34 | a: Column, 35 | b: Column, 36 | c: Column, 37 | d: Column, 38 | flag: Column, 39 | ) -> Self { 40 | meta.cs().create_gate("mod", |meta| { 41 | let s_table = meta.query_selector(s_table); 42 | let s_mod = meta.query_advice(s_mod, Rotation::cur()); 43 | 44 | let a = meta.query_advice(a, Rotation::cur()); 45 | let b = meta.query_advice(b, Rotation::cur()); 46 | let c = meta.query_advice(c, Rotation::cur()); 47 | let d = meta.query_advice(d, Rotation::cur()); 48 | let flag_next = meta.query_advice(flag, Rotation::next()); 49 | 50 | Constraints::with_selector( 51 | s_table * s_mod, 52 | [flag_next * (b.clone() - d.clone()) + d - b * c - a], 53 | ) 54 | }); 55 | 56 | Self { 57 | s_table, 58 | s_mod, 59 | a, 60 | b, 61 | c, 62 | d, 63 | flag, 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/instructions/opcode.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub trait OpCode { 4 | const OP_CODE: u64; 5 | } 6 | 7 | impl OpCode for And { 8 | const OP_CODE: u64 = 0b00000; 9 | } 10 | impl OpCode for Or { 11 | const OP_CODE: u64 = 0b00001; 12 | } 13 | impl OpCode for Xor { 14 | const OP_CODE: u64 = 0b00010; 15 | } 16 | impl OpCode for Not { 17 | const OP_CODE: u64 = 0b00011; 18 | } 19 | impl OpCode for Add { 20 | const OP_CODE: u64 = 0b00100; 21 | } 22 | impl OpCode for Sub { 23 | const OP_CODE: u64 = 0b00101; 24 | } 25 | impl OpCode for Mull { 26 | const OP_CODE: u64 = 0b00110; 27 | } 28 | impl OpCode for UMulh { 29 | const OP_CODE: u64 = 0b00111; 30 | } 31 | impl OpCode for SMulh { 32 | const OP_CODE: u64 = 0b01000; 33 | } 34 | impl OpCode for UDiv { 35 | const OP_CODE: u64 = 0b01001; 36 | } 37 | impl OpCode for UMod { 38 | const OP_CODE: u64 = 0b01010; 39 | } 40 | impl OpCode for Shl { 41 | const OP_CODE: u64 = 0b01011; 42 | } 43 | impl OpCode for Shr { 44 | const OP_CODE: u64 = 0b01100; 45 | } 46 | impl OpCode for Cmpe { 47 | const OP_CODE: u64 = 0b01101; 48 | } 49 | impl OpCode for Cmpa { 50 | const OP_CODE: u64 = 0b01110; 51 | } 52 | impl OpCode for Cmpae { 53 | const OP_CODE: u64 = 0b01111; 54 | } 55 | impl OpCode for Cmpg { 56 | const OP_CODE: u64 = 0b10000; 57 | } 58 | impl OpCode for Cmpge { 59 | const OP_CODE: u64 = 0b10001; 60 | } 61 | impl OpCode for Mov { 62 | const OP_CODE: u64 = 0b10010; 63 | } 64 | impl OpCode for CMov { 65 | const OP_CODE: u64 = 0b10011; 66 | } 67 | impl OpCode for Jmp { 68 | const OP_CODE: u64 = 0b10100; 69 | } 70 | impl OpCode for CJmp { 71 | const OP_CODE: u64 = 0b10101; 72 | } 73 | impl OpCode for CnJmp { 74 | const OP_CODE: u64 = 0b10110; 75 | } 76 | impl OpCode for StoreW { 77 | const OP_CODE: u64 = 0b11100; 78 | } 79 | impl OpCode for LoadW { 80 | const OP_CODE: u64 = 0b11101; 81 | } 82 | impl OpCode for Answer { 83 | const OP_CODE: u64 = 0b11111; 84 | } 85 | -------------------------------------------------------------------------------- /src/circuits/prod.rs: -------------------------------------------------------------------------------- 1 | use crate::assign::ConstraintSys; 2 | use halo2_proofs::plonk::{Constraints, Expression}; 3 | use halo2_proofs::poly::Rotation; 4 | use halo2_proofs::{ 5 | arithmetic::FieldExt, 6 | plonk::{Advice, Column, Selector}, 7 | }; 8 | 9 | #[derive(Debug, Clone, Copy)] 10 | pub struct ProdConfig { 11 | /// A Selector denoting the extent of the exe table. 12 | s_table: Selector, 13 | /// An advice columns that acts as a selector for prod's gates. 14 | /// `Out.prod` 15 | s_prod: Column, 16 | 17 | a: Column, 18 | b: Column, 19 | c: Column, 20 | d: Column, 21 | } 22 | 23 | impl ProdConfig { 24 | pub fn new( 25 | s_table: Selector, 26 | s_prod: Column, 27 | 28 | a: Column, 29 | b: Column, 30 | c: Column, 31 | d: Column, 32 | ) -> Self { 33 | Self { 34 | s_table, 35 | s_prod, 36 | a, 37 | b, 38 | c, 39 | d, 40 | } 41 | } 42 | 43 | pub fn configure( 44 | meta: &mut impl ConstraintSys>, 45 | s_table: Selector, 46 | s_prod: Column, 47 | 48 | a: Column, 49 | b: Column, 50 | c: Column, 51 | d: Column, 52 | ) -> Self { 53 | let conf @ Self { 54 | s_table, 55 | s_prod, 56 | a, 57 | b, 58 | c, 59 | d, 60 | } = Self::new(s_table, s_prod, a, b, c, d); 61 | 62 | meta.cs().create_gate("prod", |meta| { 63 | let max = Expression::Constant(F::from_u128(2u128.pow(WORD_BITS))); 64 | 65 | let s_table = meta.query_selector(s_table); 66 | let s_prod = meta.query_advice(s_prod, Rotation::cur()); 67 | 68 | let a = meta.query_advice(a, Rotation::cur()); 69 | let b = meta.query_advice(b, Rotation::cur()); 70 | let c = meta.query_advice(c, Rotation::cur()); 71 | let d = meta.query_advice(d, Rotation::cur()); 72 | 73 | Constraints::with_selector(s_table * s_prod, [a * b - d - max * c]) 74 | }); 75 | 76 | conf 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/circuits/tables/pow.rs: -------------------------------------------------------------------------------- 1 | use halo2_proofs::{ 2 | arithmetic::FieldExt, 3 | circuit::{Layouter, Value}, 4 | plonk::{ConstraintSystem, Error, TableColumn}, 5 | }; 6 | 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 8 | pub struct PowTable { 9 | pub values: TableColumn, 10 | pub powers: TableColumn, 11 | } 12 | 13 | impl PowTable { 14 | pub fn new(meta: &mut ConstraintSystem) -> Self { 15 | Self { 16 | values: meta.lookup_table_column(), 17 | powers: meta.lookup_table_column(), 18 | } 19 | } 20 | 21 | pub fn alloc_table( 22 | self, 23 | layouter: &mut impl Layouter, 24 | ) -> Result<(), Error> { 25 | layouter.assign_table( 26 | || "Pow table", 27 | |mut table| { 28 | for i in 0..(WORD_BITS as usize) { 29 | table 30 | .assign_cell( 31 | || format!("value {}", i), 32 | self.values, 33 | i, 34 | || Value::known(F::from(i as u64)), 35 | ) 36 | .unwrap(); 37 | 38 | let power = 2u64.pow(i as _) % 2u64.pow(WORD_BITS); 39 | table 40 | .assign_cell( 41 | || format!("power {}", power), 42 | self.powers, 43 | i, 44 | || Value::known(F::from(power)), 45 | ) 46 | .unwrap(); 47 | } 48 | 49 | table 50 | .assign_cell( 51 | || format!("value {}", WORD_BITS), 52 | self.values, 53 | WORD_BITS as _, 54 | || Value::known(F::from(WORD_BITS as u64)), 55 | ) 56 | .unwrap(); 57 | 58 | table.assign_cell( 59 | || format!("power {}", 0), 60 | self.powers, 61 | WORD_BITS as _, 62 | || Value::known(F::zero()), 63 | ) 64 | }, 65 | ) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/circuits/flag2.rs: -------------------------------------------------------------------------------- 1 | // TODO add tests using dead code 2 | #![allow(dead_code)] 3 | use crate::assign::ConstraintSys; 4 | use halo2_proofs::circuit::{Region, Value}; 5 | use halo2_proofs::plonk::{Constraints, Expression}; 6 | use halo2_proofs::{ 7 | arithmetic::FieldExt, 8 | plonk::{Advice, Column, Selector}, 9 | poly::Rotation, 10 | }; 11 | use rand_core::OsRng; 12 | 13 | #[derive(Debug, Clone, Copy)] 14 | pub struct Flag2Config { 15 | /// A Selector denoting the extent of the exe table. 16 | s_table: Selector, 17 | /// An advice columns that acts as a selector for flag2's gate. 18 | /// [`Out.flag2`](crate::circuits::tables::aux::Out) 19 | s_flag2: Column, 20 | 21 | c: Column, 22 | flag: Column, 23 | 24 | // Could we eliminate this column? 25 | a_flag: Column, 26 | } 27 | 28 | impl Flag2Config { 29 | pub fn configure( 30 | meta: &mut impl ConstraintSys>, 31 | s_table: Selector, 32 | s_flag2: Column, 33 | 34 | c: Column, 35 | flag: Column, 36 | a_flag: Column, 37 | ) -> Self { 38 | meta.cs().create_gate("flag2", |meta| { 39 | let s_table = meta.query_selector(s_table); 40 | let s_flag2 = meta.query_advice(s_flag2, Rotation::cur()); 41 | 42 | let c = meta.query_advice(c, Rotation::cur()); 43 | let flag_n = meta.query_advice(flag, Rotation::next()); 44 | let a_flag = meta.query_advice(a_flag, Rotation::cur()); 45 | 46 | Constraints::with_selector( 47 | s_table * s_flag2, 48 | [(flag_n + c) * a_flag - Expression::Constant(F::one())], 49 | ) 50 | }); 51 | 52 | Self { 53 | s_table, 54 | s_flag2, 55 | c, 56 | flag, 57 | a_flag, 58 | } 59 | } 60 | 61 | pub fn assign_flag2( 62 | &self, 63 | region: &mut Region<'_, F>, 64 | c: F, 65 | flag_next: F, 66 | offset: usize, 67 | ) { 68 | // a_flag can be anything when `c + flag_next = 0`. 69 | // TODO replace with seedable Rng to add back determinism. 70 | let a_flag = (c + flag_next).invert().unwrap_or(F::random(OsRng)); 71 | region 72 | .assign_advice(|| "a_flag", self.a_flag, offset, || Value::known(a_flag)) 73 | .unwrap(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/circuits/tables/mod.rs: -------------------------------------------------------------------------------- 1 | use halo2_proofs::{ 2 | arithmetic::FieldExt, 3 | circuit::{Region, Value}, 4 | plonk::{ 5 | Advice, Column, ConstraintSystem, Error, Expression, Selector, VirtualCells, 6 | }, 7 | poly::Rotation, 8 | }; 9 | 10 | pub mod aux; 11 | pub mod even_bits; 12 | pub mod exe; 13 | pub mod mem; 14 | pub mod pow; 15 | pub mod prog; 16 | pub mod signed; 17 | 18 | #[derive(Debug, Clone, Copy)] 19 | pub struct TableSelector { 20 | /// A Selector denoting the extent of dynamic table. 21 | /// Enabled for every row that may contain a line of the trace. 22 | /// The trace may be the Exe trace or the memory access trace. 23 | pub s_table: Selector, 24 | /// An advice selector denoting the extent of the trace. 25 | /// Enabled for every row that contains a line of the trace. 26 | pub s_trace: Column, 27 | } 28 | 29 | impl TableSelector { 30 | pub fn configure(meta: &mut ConstraintSystem) -> Self { 31 | let s_table = meta.complex_selector(); 32 | let s_trace = meta.advice_column(); 33 | Self { s_table, s_trace } 34 | } 35 | 36 | pub fn query( 37 | self: TableSelector, 38 | meta: &mut VirtualCells, 39 | ) -> Expression { 40 | let s_table = meta.query_selector(self.s_table); 41 | let s_trace = meta.query_advice(self.s_trace, Rotation::cur()); 42 | Expression::SelectorExpression(Box::new(s_table * s_trace)) 43 | } 44 | 45 | /// Query the current row of `s_table`, and the next row of s_trace. 46 | /// Useful for constraints that should not be applied on the last line of the trace. 47 | pub fn query_trace_next( 48 | self: TableSelector, 49 | meta: &mut VirtualCells, 50 | ) -> Expression { 51 | let s_table = meta.query_selector(self.s_table); 52 | let s_trace = meta.query_advice(self.s_trace, Rotation::next()); 53 | Expression::SelectorExpression(Box::new(s_table * s_trace)) 54 | } 55 | 56 | pub fn alloc_table_rows( 57 | &self, 58 | region: &mut Region, 59 | table_len: usize, 60 | ) -> Result<(), Error> { 61 | for offset in 0..table_len { 62 | self.s_table.enable(region, offset)?; 63 | } 64 | Ok(()) 65 | } 66 | 67 | /// Enable a row of an already allocated table. 68 | pub fn enable_row_of_table( 69 | &self, 70 | region: &mut Region, 71 | offset: usize, 72 | enable: bool, 73 | ) -> Result<(), Error> { 74 | region.assign_advice( 75 | || "s_trace", 76 | self.s_trace, 77 | offset, 78 | || Value::known(F::from(enable)), 79 | )?; 80 | 81 | // The table should already be allocated, but if it's not this line makes debugging easier. 82 | self.s_table.enable(region, offset)?; 83 | 84 | Ok(()) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/circuits/flag4.rs: -------------------------------------------------------------------------------- 1 | // TODO add tests using dead code 2 | #![allow(dead_code)] 3 | use crate::assign::ConstraintSys; 4 | use halo2_proofs::circuit::{Region, Value}; 5 | use halo2_proofs::plonk::{Constraints, Expression}; 6 | use halo2_proofs::{ 7 | arithmetic::FieldExt, 8 | plonk::{Advice, Column, Selector}, 9 | poly::Rotation, 10 | }; 11 | 12 | use super::tables::signed::SignedConfig; 13 | 14 | #[derive(Debug, Clone, Copy)] 15 | pub struct Flag4Config { 16 | /// A Selector denoting the extent of the exe table. 17 | s_table: Selector, 18 | /// An advice columns that acts as a selector for flag4's gate. 19 | /// [`Out.flag4`](crate::circuits::tables::aux::Out) 20 | s_flag4: Column, 21 | 22 | signed_b: SignedConfig, 23 | // ρb in the paper 24 | lsb_b: Column, 25 | b_flag: Column, 26 | flag: Column, 27 | } 28 | 29 | impl Flag4Config { 30 | pub fn configure( 31 | meta: &mut impl ConstraintSys>, 32 | s_table: Selector, 33 | s_flag4: Column, 34 | 35 | signed_b: SignedConfig, 36 | lsb_b: Column, 37 | b_flag: Column, 38 | flag: Column, 39 | ) -> Self { 40 | meta.cs().create_gate("flag4", |meta| { 41 | let one = Expression::Constant(F::one()); 42 | 43 | let s_table = meta.query_selector(s_table); 44 | let s_flag4 = meta.query_advice(s_flag4, Rotation::cur()); 45 | 46 | let b_flag = meta.query_advice(b_flag, Rotation::cur()); 47 | let msb_b = meta.query_advice(signed_b.msb, Rotation::cur()); 48 | let lsb_b = meta.query_advice(lsb_b, Rotation::cur()); 49 | let flag_n = meta.query_advice(flag, Rotation::next()); 50 | 51 | Constraints::with_selector( 52 | s_table * s_flag4, 53 | [flag_n - (b_flag.clone() * msb_b) - ((one - b_flag) * lsb_b)], 54 | ) 55 | }); 56 | 57 | Self { 58 | s_table, 59 | s_flag4, 60 | signed_b, 61 | lsb_b, 62 | b_flag, 63 | flag, 64 | } 65 | } 66 | 67 | // TODO remove `left_shift: bool,` 68 | pub fn assign_flag4( 69 | &self, 70 | region: &mut Region<'_, F>, 71 | b: u64, 72 | left_shift: bool, 73 | offset: usize, 74 | ) { 75 | let lsb_b = F::from(b & 1); 76 | region 77 | .assign_advice(|| "lsb_b", self.lsb_b, offset, || Value::known(lsb_b)) 78 | .unwrap(); 79 | 80 | region 81 | .assign_advice( 82 | || "b_flag", 83 | self.b_flag, 84 | offset, 85 | || Value::known(F::from(left_shift)), 86 | ) 87 | .unwrap(); 88 | self.signed_b.assign_signed(region, b.into(), offset); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/circuits/sprod.rs: -------------------------------------------------------------------------------- 1 | use crate::assign::ConstraintSys; 2 | use halo2_proofs::circuit::Region; 3 | use halo2_proofs::plonk::{Constraints, Expression}; 4 | use halo2_proofs::poly::Rotation; 5 | use halo2_proofs::{ 6 | arithmetic::FieldExt, 7 | plonk::{Advice, Column, Selector}, 8 | }; 9 | 10 | use super::tables::signed::SignedConfig; 11 | 12 | #[derive(Debug, Clone, Copy)] 13 | pub struct SProdConfig { 14 | /// A Selector denoting the extent of the exe table. 15 | s_table: Selector, 16 | /// An advice columns that acts as a selector for sprod's gates. 17 | /// `Out.sprod` 18 | s_sprod: Column, 19 | 20 | a: SignedConfig, 21 | b: SignedConfig, 22 | c: SignedConfig, 23 | d: Column, 24 | } 25 | 26 | impl SProdConfig { 27 | pub fn new( 28 | s_table: Selector, 29 | s_sprod: Column, 30 | 31 | a: SignedConfig, 32 | b: SignedConfig, 33 | c: SignedConfig, 34 | d: Column, 35 | ) -> Self { 36 | Self { 37 | s_table, 38 | s_sprod, 39 | a, 40 | b, 41 | c, 42 | d, 43 | } 44 | } 45 | 46 | pub fn configure( 47 | meta: &mut impl ConstraintSys>, 48 | s_table: Selector, 49 | s_sprod: Column, 50 | 51 | a: SignedConfig, 52 | b: SignedConfig, 53 | c: SignedConfig, 54 | d: Column, 55 | ) -> Self { 56 | let conf @ Self { 57 | s_table, 58 | s_sprod, 59 | a, 60 | b, 61 | c, 62 | d, 63 | } = Self::new(s_table, s_sprod, a, b, c, d); 64 | 65 | meta.cs().create_gate("sprod", |meta| { 66 | let two = Expression::Constant(F::from_u128(2)); 67 | let max = Expression::Constant(F::from_u128(2u128.pow(WORD_BITS))); 68 | 69 | let s_table = meta.query_selector(s_table); 70 | let s_sprod = meta.query_advice(s_sprod, Rotation::cur()); 71 | 72 | let a_sigma = meta.query_advice(a.word_sigma, Rotation::cur()); 73 | let a_msb = meta.query_advice(a.msb, Rotation::cur()); 74 | let a_sigma = -a_msb * two.clone() * a_sigma.clone() + a_sigma; 75 | 76 | let b_sigma = meta.query_advice(b.word_sigma, Rotation::cur()); 77 | let b_msb = meta.query_advice(b.msb, Rotation::cur()); 78 | let b_sigma = -b_msb * two.clone() * b_sigma.clone() + b_sigma; 79 | 80 | let c_sigma = meta.query_advice(c.word_sigma, Rotation::cur()); 81 | let c_msb = meta.query_advice(c.msb, Rotation::cur()); 82 | let c_sigma = -c_msb * two * c_sigma.clone() + c_sigma; 83 | 84 | let d = meta.query_advice(d, Rotation::cur()); 85 | 86 | Constraints::with_selector( 87 | s_table * s_sprod, 88 | [a_sigma * b_sigma - d - max * c_sigma], 89 | ) 90 | }); 91 | 92 | conf 93 | } 94 | 95 | pub fn assign_sprod( 96 | &self, 97 | region: &mut Region<'_, F>, 98 | a: F, 99 | b: F, 100 | c: F, 101 | offset: usize, 102 | ) { 103 | self.a.assign_signed(region, a.get_lower_128(), offset); 104 | 105 | self.b.assign_signed(region, b.get_lower_128(), offset); 106 | self.b.word.assign_decompose(region, b, offset); 107 | 108 | self.c.assign_signed(region, c.get_lower_128(), offset); 109 | self.c.word.assign_decompose(region, c, offset); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/circuits/ssum.rs: -------------------------------------------------------------------------------- 1 | use crate::assign::ConstraintSys; 2 | use halo2_proofs::circuit::Region; 3 | use halo2_proofs::plonk::{Constraints, Expression}; 4 | use halo2_proofs::poly::Rotation; 5 | use halo2_proofs::{ 6 | arithmetic::FieldExt, 7 | plonk::{Advice, Column, Selector}, 8 | }; 9 | 10 | use super::tables::signed::SignedConfig; 11 | 12 | #[derive(Debug, Clone, Copy)] 13 | pub struct SSumConfig { 14 | /// A Selector denoting the extent of the exe table. 15 | s_table: Selector, 16 | /// An advice columns that acts as a selector for ssum's gate. 17 | /// [`Out.ssum`](crate::circuits::tables::aux::Out) 18 | s_sum: Column, 19 | 20 | a: SignedConfig, 21 | b: Column, 22 | c: SignedConfig, 23 | d: Column, 24 | flag: Column, 25 | } 26 | 27 | impl SSumConfig { 28 | pub fn new( 29 | s_table: Selector, 30 | s_sum: Column, 31 | 32 | a: SignedConfig, 33 | b: Column, 34 | c: SignedConfig, 35 | d: Column, 36 | 37 | flag: Column, 38 | ) -> Self { 39 | Self { 40 | s_table, 41 | s_sum, 42 | a, 43 | b, 44 | c, 45 | d, 46 | flag, 47 | } 48 | } 49 | 50 | #[allow(clippy::complexity)] 51 | pub fn configure( 52 | meta: &mut impl ConstraintSys>, 53 | s_table: Selector, 54 | s_sum: Column, 55 | 56 | a: SignedConfig, 57 | b: Column, 58 | c: SignedConfig, 59 | d: Column, 60 | 61 | flag: Column, 62 | ) -> Self { 63 | let conf @ Self { 64 | s_table, 65 | s_sum, 66 | a, 67 | b, 68 | c, 69 | d, 70 | flag, 71 | } = Self::new(s_table, s_sum, a, b, c, d, flag); 72 | 73 | meta.cs().create_gate("ssum", |meta| { 74 | let two = Expression::Constant(F::from_u128(2)); 75 | 76 | let s_table = meta.query_selector(s_table); 77 | let s_sum = meta.query_advice(s_sum, Rotation::cur()); 78 | 79 | let a_sigma = meta.query_advice(a.word_sigma, Rotation::cur()); 80 | let a_msb = meta.query_advice(a.msb, Rotation::cur()); 81 | let a_sigma = -a_msb * two.clone() * a_sigma.clone() + a_sigma; 82 | 83 | let b = meta.query_advice(b, Rotation::cur()); 84 | 85 | let c_sigma = meta.query_advice(c.word_sigma, Rotation::cur()); 86 | let c_msb = meta.query_advice(c.msb, Rotation::cur()); 87 | let c_sigma = -c_msb * two * c_sigma.clone() + c_sigma; 88 | 89 | let d = meta.query_advice(d, Rotation::cur()); 90 | let flag_n = meta.query_advice(flag, Rotation::next()); 91 | 92 | Constraints::with_selector( 93 | s_table * s_sum, 94 | [(a_sigma) + b 95 | - c_sigma 96 | - (Expression::Constant(F::from_u128(2u128.pow(WORD_BITS))) 97 | * flag_n) 98 | + d], 99 | ) 100 | }); 101 | 102 | conf 103 | } 104 | 105 | pub fn assign_sum( 106 | &self, 107 | region: &mut Region<'_, F>, 108 | a: F, 109 | c: F, 110 | offset: usize, 111 | ) { 112 | self.a.assign_signed(region, a.get_lower_128(), offset); 113 | 114 | self.c.assign_signed(region, c.get_lower_128(), offset); 115 | self.c.word.assign_decompose(region, c, offset); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/circuits/flag3.rs: -------------------------------------------------------------------------------- 1 | // TODO add tests using dead code 2 | #![allow(dead_code)] 3 | use crate::assign::ConstraintSys; 4 | use halo2_proofs::circuit::{Region, Value}; 5 | use halo2_proofs::plonk::{Constraints, Expression}; 6 | use halo2_proofs::{ 7 | arithmetic::FieldExt, 8 | plonk::{Advice, Column, Selector}, 9 | poly::Rotation, 10 | }; 11 | 12 | use super::tables::even_bits::EvenBitsConfig; 13 | 14 | #[derive(Debug, Clone, Copy)] 15 | pub struct Flag3Config { 16 | /// A Selector denoting the extent of the exe table. 17 | s_table: Selector, 18 | /// An advice columns that acts as a selector for flag3's gate. 19 | /// [`Out.flag3`](crate::circuits::tables::aux::Out) 20 | s_flag3: Column, 21 | 22 | a: Column, 23 | b: Column, 24 | c: Column, 25 | flag: Column, 26 | 27 | r: EvenBitsConfig, 28 | } 29 | 30 | impl Flag3Config { 31 | #[allow(clippy::complexity)] 32 | pub fn configure( 33 | meta: &mut impl ConstraintSys>, 34 | s_table: Selector, 35 | s_flag3: Column, 36 | 37 | a: Column, 38 | b: Column, 39 | c: Column, 40 | flag: Column, 41 | r: EvenBitsConfig, 42 | ) -> Self { 43 | meta.cs().create_gate("flag3", |meta| { 44 | let one = Expression::Constant(F::one()); 45 | let two = Expression::Constant(F::from(2)); 46 | 47 | let s_table = meta.query_selector(s_table); 48 | let s_flag3 = meta.query_advice(s_flag3, Rotation::cur()); 49 | 50 | let a = meta.query_advice(a, Rotation::cur()); 51 | let b = meta.query_advice(b, Rotation::cur()); 52 | let c = meta.query_advice(c, Rotation::cur()); 53 | 54 | let flag_n = meta.query_advice(flag, Rotation::next()); 55 | 56 | let re = meta.query_advice(r.even, Rotation::cur()); 57 | let ro = meta.query_advice(r.odd, Rotation::cur()); 58 | let r = meta.query_advice(r.word, Rotation::cur()); 59 | 60 | // page 29 61 | Constraints::with_selector( 62 | s_table * s_flag3, 63 | [ 64 | b * flag_n.clone() 65 | + (one.clone() - flag_n) 66 | * (c.clone() - a.clone() - one.clone() - two * ro - re), 67 | // This is just to ensure the r decompose is correct. 68 | // We should experiment with decompose taking an expression rather 69 | // than a column. This would make decompose gates less reusable, but 70 | // might save a column in a few cases. 71 | // 72 | // Otherwise we should replace `c - a - one` in the constraint above with r. 73 | 74 | // r is only defined for c != 0. (page 29) 75 | c.clone() * ((c - a - one) - r), 76 | ], 77 | ) 78 | }); 79 | 80 | Self { 81 | s_table, 82 | s_flag3, 83 | c, 84 | flag, 85 | a, 86 | b, 87 | r, 88 | } 89 | } 90 | 91 | pub fn assign_flag3( 92 | &self, 93 | region: &mut Region<'_, F>, 94 | a: F, 95 | c: F, 96 | offset: usize, 97 | ) { 98 | // Used to prove `a < c` when c != 0. 99 | let r = if c == F::zero() { 100 | F::zero() 101 | } else { 102 | c - a - F::one() 103 | }; 104 | region 105 | .assign_advice(|| "r", self.r.word, offset, || Value::known(r)) 106 | .unwrap(); 107 | self.r.assign_decompose(region, r, offset); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/test_utils.rs: -------------------------------------------------------------------------------- 1 | use halo2_proofs::{ 2 | pasta::Fp, 3 | plonk::{BatchVerifier, Circuit}, 4 | }; 5 | 6 | pub fn gen_proofs_and_verify< 7 | const WORD_BITS: u32, 8 | C: Circuit + Default + Clone, 9 | >( 10 | inputs: Vec<(C, Vec>)>, 11 | ) { 12 | use halo2_proofs::pasta::{vesta, EqAffine}; 13 | use halo2_proofs::{ 14 | plonk::{create_proof, keygen_pk, keygen_vk, verify_proof, SingleVerifier}, 15 | poly::commitment::Params, 16 | transcript::{Blake2bRead, Blake2bWrite}, 17 | }; 18 | use rand_core::OsRng; 19 | 20 | let k = 2 + WORD_BITS / 2; 21 | let params: Params = halo2_proofs::poly::commitment::Params::new(k); 22 | let empty_circuit = C::default(); 23 | let vk = keygen_vk(¶ms, &empty_circuit).unwrap(); 24 | 25 | let pk = keygen_pk(¶ms, vk.clone(), &empty_circuit).unwrap(); 26 | 27 | let inputs_ref: Vec<(C, Vec<&[Fp]>)> = inputs 28 | .iter() 29 | .map(|(circuit, public_input)| { 30 | ( 31 | circuit.clone(), 32 | public_input.iter().map(|v| v.as_slice()).collect(), 33 | ) 34 | }) 35 | .collect(); 36 | 37 | let proofs: Vec<(Vec, &[&[Fp]])> = inputs_ref 38 | .iter() 39 | .map(|(circuit, c)| { 40 | let mut transcript = Blake2bWrite::<_, vesta::Affine, _>::init(vec![]); 41 | create_proof( 42 | ¶ms, 43 | &pk, 44 | &[circuit.clone()], 45 | &[c.as_slice()], 46 | &mut OsRng, 47 | &mut transcript, 48 | ) 49 | .expect("Failed to create proof"); 50 | 51 | let proof: Vec = transcript.finalize(); 52 | (proof, c.as_slice()) 53 | }) 54 | .collect(); 55 | 56 | let mut verifier = BatchVerifier::new(); 57 | for ((proof, _), (_, c)) in proofs.iter().zip(inputs.iter()) { 58 | verifier.add_proof(vec![c.clone()], proof.clone()); 59 | } 60 | 61 | let verified = verifier.finalize(¶ms, &vk); 62 | if !verified { 63 | for (proof, c) in proofs { 64 | let verifier = SingleVerifier::new(¶ms); 65 | let mut transcript = Blake2bRead::init(&proof[..]); 66 | 67 | verify_proof(¶ms, pk.get_vk(), verifier, &[c], &mut transcript) 68 | .expect("could not verify_proof"); 69 | } 70 | } 71 | } 72 | 73 | pub fn gen_proofs_and_verify_should_fail< 74 | const WORD_BITS: u32, 75 | C: Circuit + Default + Clone, 76 | >( 77 | circuit: C, 78 | public_input: Vec, 79 | ) { 80 | use halo2_proofs::pasta::{vesta, EqAffine}; 81 | use halo2_proofs::{ 82 | plonk::{create_proof, keygen_pk, keygen_vk, verify_proof, SingleVerifier}, 83 | poly::commitment::Params, 84 | transcript::{Blake2bRead, Blake2bWrite}, 85 | }; 86 | use rand_core::OsRng; 87 | 88 | let k = 1 + WORD_BITS / 2; 89 | let params: Params = halo2_proofs::poly::commitment::Params::new(k); 90 | let empty_circuit = C::default(); 91 | let vk = keygen_vk(¶ms, &empty_circuit).unwrap(); 92 | 93 | let pk = keygen_pk(¶ms, vk, &empty_circuit).unwrap(); 94 | 95 | let mut transcript = Blake2bWrite::<_, vesta::Affine, _>::init(vec![]); 96 | create_proof( 97 | ¶ms, 98 | &pk, 99 | &[circuit], 100 | &[&[&public_input]], 101 | &mut OsRng, 102 | &mut transcript, 103 | ) 104 | .expect("Failed to create proof"); 105 | 106 | let proof: Vec = transcript.finalize(); 107 | 108 | let verifier = SingleVerifier::new(¶ms); 109 | let mut transcript = Blake2bRead::init(&proof[..]); 110 | 111 | verify_proof( 112 | ¶ms, 113 | pk.get_vk(), 114 | verifier, 115 | &[&[&public_input]], 116 | &mut transcript, 117 | ) 118 | .expect_err("Erroneously verified proof"); 119 | } 120 | -------------------------------------------------------------------------------- /src/assign.rs: -------------------------------------------------------------------------------- 1 | use halo2_proofs::{ 2 | arithmetic::FieldExt, 3 | circuit::{AssignedCell, Region, Table, Value}, 4 | plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Instance, TableColumn}, 5 | }; 6 | 7 | pub trait NewColumn { 8 | fn new_column(&mut self) -> C; 9 | } 10 | 11 | impl NewColumn> for ConstraintSystem { 12 | fn new_column(&mut self) -> Column { 13 | self.advice_column() 14 | } 15 | } 16 | 17 | impl NewColumn> for ConstraintSystem { 18 | fn new_column(&mut self) -> Column { 19 | self.instance_column() 20 | } 21 | } 22 | 23 | impl NewColumn> for ConstraintSystem { 24 | fn new_column(&mut self) -> Column { 25 | self.fixed_column() 26 | } 27 | } 28 | 29 | pub struct TrackColumns<'l, F: FieldExt, C: Copy>( 30 | pub &'l mut ConstraintSystem, 31 | pub Vec, 32 | ); 33 | 34 | impl<'l, F: FieldExt, C: Copy> TrackColumns<'l, F, C> { 35 | pub fn new(meta: &'l mut ConstraintSystem) -> Self { 36 | TrackColumns(meta, Vec::new()) 37 | } 38 | } 39 | 40 | impl<'l, F: FieldExt, C: Copy> NewColumn for TrackColumns<'l, F, C> 41 | where 42 | ConstraintSystem: NewColumn, 43 | { 44 | fn new_column(&mut self) -> C { 45 | let c = self.0.new_column(); 46 | self.1.push(c); 47 | c 48 | } 49 | } 50 | 51 | pub trait ConstraintSys: NewColumn { 52 | fn cs(&mut self) -> &mut ConstraintSystem; 53 | } 54 | 55 | impl ConstraintSys for ConstraintSystem 56 | where 57 | ConstraintSystem: NewColumn, 58 | { 59 | fn cs(&mut self) -> &mut ConstraintSystem { 60 | self 61 | } 62 | } 63 | 64 | impl<'l, F: FieldExt, C: Copy> ConstraintSys for TrackColumns<'l, F, C> 65 | where 66 | ConstraintSystem: NewColumn, 67 | { 68 | fn cs(&mut self) -> &mut ConstraintSystem { 69 | self.0 70 | } 71 | } 72 | 73 | pub trait AssignCell { 74 | type AssignedRef; 75 | fn assign_cell( 76 | &mut self, 77 | column: C, 78 | offset: usize, 79 | f: F, 80 | ) -> Result; 81 | } 82 | 83 | /// An impl of push_cell for `(Region, offset)`. 84 | impl<'r, F: FieldExt> AssignCell> for Region<'r, F> { 85 | type AssignedRef = AssignedCell; 86 | fn assign_cell( 87 | &mut self, 88 | column: Column, 89 | offset: usize, 90 | f: F, 91 | ) -> Result { 92 | self.assign_advice(|| "", column, offset, &mut || Value::known(f)) 93 | } 94 | } 95 | 96 | /// An impl of push_cell for `(Region, offset)`. 97 | impl<'r, F: FieldExt> AssignCell for Table<'r, F> { 98 | type AssignedRef = (); 99 | fn assign_cell( 100 | &mut self, 101 | column: TableColumn, 102 | offset: usize, 103 | f: F, 104 | ) -> Result { 105 | self.assign_cell(|| "", column, offset, &mut || Value::known(f)) 106 | } 107 | } 108 | 109 | impl AssignCell for PseudoMeta { 110 | type AssignedRef = (PseudoColumn, usize); 111 | fn assign_cell( 112 | &mut self, 113 | column: PseudoColumn, 114 | offset: usize, 115 | f: F, 116 | ) -> Result { 117 | // We only support assigning rows in order. 118 | assert_eq!( 119 | self.0.get(column.0).expect("Column Not Allocated").len(), 120 | offset 121 | ); 122 | self.0[column.0].push(f); 123 | Ok((column, offset)) 124 | } 125 | } 126 | 127 | #[derive(Debug, Clone, Default)] 128 | pub struct PseudoMeta(pub Vec>); 129 | 130 | #[derive(Debug, Clone, Copy)] 131 | pub struct PseudoColumn(pub usize); 132 | 133 | impl NewColumn for PseudoMeta { 134 | fn new_column(&mut self) -> PseudoColumn { 135 | self.0.push(Vec::new()); 136 | PseudoColumn(self.0.len() - 1) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/circuits/changed.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use halo2_proofs::{ 4 | arithmetic::FieldExt, 5 | plonk::{self, Advice, Column, ConstraintSystem, Constraints, Expression}, 6 | poly::Rotation, 7 | }; 8 | 9 | use crate::{assign::AssignCell, trace::Registers}; 10 | 11 | use super::tables::TableSelector; 12 | 13 | /// This corresponds to sch in the paper. 14 | /// The meaning of 0, and 1 is inverted relative to the paper. 15 | /// A selector value of 0 denotes an unchanged cell. 16 | /// 17 | /// This inversion will enable use of the selection vector combining optimization. 18 | #[derive(Debug, Clone, Copy)] 19 | pub struct ChangedSelectors { 20 | pub regs: Registers, 21 | /// Unlike all the other entries, `pc: false` denotes a incremented program count. 22 | pub pc: T, 23 | pub flag: T, 24 | } 25 | 26 | impl ChangedSelectors { 27 | pub fn new(mut new_fn: impl FnMut() -> T) -> Self { 28 | ChangedSelectors { 29 | regs: Registers([0usize; REG_COUNT].map(|_| new_fn())), 30 | pc: new_fn(), 31 | flag: new_fn(), 32 | } 33 | } 34 | 35 | pub fn set_cells>( 36 | self, 37 | region: &mut R, 38 | offset: usize, 39 | vals: ChangedSelectors, 40 | ) -> Result<(), plonk::Error> 41 | where 42 | T: Debug, 43 | { 44 | let Self { regs, pc, flag } = self; 45 | 46 | for (rc, rv) in regs.0.into_iter().zip(vals.regs.0.into_iter()) { 47 | region.assign_cell(rc, offset, rv.into()).unwrap(); 48 | } 49 | region.assign_cell(pc, offset, vals.pc.into())?; 50 | region.assign_cell(flag, offset, vals.flag.into())?; 51 | 52 | Ok(()) 53 | } 54 | 55 | pub fn convert>(self) -> ChangedSelectors { 56 | ChangedSelectors { 57 | regs: self.regs.convert(), 58 | pc: self.pc.into(), 59 | flag: self.flag.into(), 60 | } 61 | } 62 | 63 | pub fn map( 64 | self, 65 | mut f: impl FnMut(T) -> B, 66 | ) -> ChangedSelectors 67 | where 68 | T: Copy, 69 | { 70 | let Self { regs, pc, flag } = self; 71 | 72 | ChangedSelectors { 73 | pc: f(pc), 74 | flag: f(flag), 75 | regs: regs.map(&mut f), 76 | } 77 | } 78 | } 79 | 80 | impl ChangedSelectors> { 81 | pub fn unchanged_gate( 82 | &self, 83 | meta: &mut ConstraintSystem, 84 | s_trace: TableSelector, 85 | 86 | // TODO Consider refactoring ChangedSelectors into a new type around `State {regs, pc, flag}` 87 | regs: Registers>, 88 | pc: Column, 89 | flag: Column, 90 | ) { 91 | meta.create_gate("unchanged", |meta| { 92 | let one = Expression::Constant(F::one()); 93 | 94 | let s_extent = s_trace.query_trace_next(meta); 95 | 96 | let ch_pc = meta.query_advice(self.pc, Rotation::cur()); 97 | let pc_n = meta.query_advice(pc, Rotation::next()); 98 | let pc = meta.query_advice(pc, Rotation::cur()); 99 | 100 | let ch_flag = meta.query_advice(self.flag, Rotation::cur()); 101 | let flag_n = meta.query_advice(flag, Rotation::next()); 102 | let flag = meta.query_advice(flag, Rotation::cur()); 103 | 104 | let mut constraints = vec![ 105 | (one.clone() - ch_pc) * (pc + one.clone() - pc_n), 106 | (one.clone() - ch_flag) * (flag - flag_n), 107 | ]; 108 | 109 | constraints.extend(self.regs.0.iter().zip(regs.0.iter()).map( 110 | |(ch_r, r)| { 111 | let ch_r = meta.query_advice(*ch_r, Rotation::cur()); 112 | let r_n = meta.query_advice(*r, Rotation::next()); 113 | let r = meta.query_advice(*r, Rotation::cur()); 114 | 115 | (one.clone() - ch_r) * (r - r_n) 116 | }, 117 | )); 118 | 119 | Constraints::with_selector(s_extent, constraints) 120 | }); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /proptest-regressions/gadgets/cmpa.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc c4fd5307a8c40084a2dc79ce1d5b7e921cc36cedde13b8ed9fcb2cbef44d7bd7 # shrinks to a = 26055, b = 0 8 | cc 9d1a88458c41552591483f208c7b009e9d5aa08dcaac1da09b651481eebe4173 # shrinks to a = 0, b = 256 9 | cc ffac2ad50cc61121b23be9cddd5f3f87f14b9b55c09c43d3cf67b228f7adcdb6 # shrinks to a = 0, b = 276 10 | cc 0dea6c838403c776e4c47a57247a5920669a80b1f16986d8570f6b9dfbc85229 # shrinks to s = [(GreaterThanCircuit { a: Some(0x0000000000000000000000000000000000000000000000000000000000000000), b: Some(0x0000000000000000000000000000000000000000000000000000000000000000) }, [0x0000000000000000000000000000000000000000000000000000000000000000]), (GreaterThanCircuit { a: Some(0x0000000000000000000000000000000000000000000000000000000000000000), b: Some(0x0000000000000000000000000000000000000000000000000000000000000000) }, [0x0000000000000000000000000000000000000000000000000000000000000000]), (GreaterThanCircuit { a: Some(0x0000000000000000000000000000000000000000000000000000000000000000), b: Some(0x00000000000000000000000000000000000000000000000000000000000000d6) }, [0x0000000000000000000000000000000000000000000000000000000000000000]), (GreaterThanCircuit { a: Some(0x0000000000000000000000000000000000000000000000000000000000005faf), b: Some(0x000000000000000000000000000000000000000000000000000000000000b1a2) }, [0x0000000000000000000000000000000000000000000000000000000000000000]), (GreaterThanCircuit { a: Some(0x000000000000000000000000000000000000000000000000000000000000ad0a), b: Some(0x00000000000000000000000000000000000000000000000000000000000008e8) }, [0x0000000000000000000000000000000000000000000000000000000000000001]), (GreaterThanCircuit { a: Some(0x0000000000000000000000000000000000000000000000000000000000000977), b: Some(0x0000000000000000000000000000000000000000000000000000000000003fc6) }, [0x0000000000000000000000000000000000000000000000000000000000000000]), (GreaterThanCircuit { a: Some(0x000000000000000000000000000000000000000000000000000000000000f056), b: Some(0x0000000000000000000000000000000000000000000000000000000000004ad2) }, [0x0000000000000000000000000000000000000000000000000000000000000001]), (GreaterThanCircuit { a: Some(0x0000000000000000000000000000000000000000000000000000000000000fa6), b: Some(0x000000000000000000000000000000000000000000000000000000000000473a) }, [0x0000000000000000000000000000000000000000000000000000000000000000]), (GreaterThanCircuit { a: Some(0x000000000000000000000000000000000000000000000000000000000000d998), b: Some(0x0000000000000000000000000000000000000000000000000000000000006873) }, [0x0000000000000000000000000000000000000000000000000000000000000001]), (GreaterThanCircuit { a: Some(0x0000000000000000000000000000000000000000000000000000000000000bac), b: Some(0x0000000000000000000000000000000000000000000000000000000000003477) }, [0x0000000000000000000000000000000000000000000000000000000000000000])] 11 | cc 088a60438f41ded3fce18a8945c18a9b8caadcf5d6497ffeaf970e4006ab355b # shrinks to a = 1552060, b = 11 12 | cc b52f9415b1f71cf620445ea4328b90c7fbb374714d2141eddaad8e9f1decae7f # shrinks to s = [(GreaterThanCircuit { a: Some(0x0000000000000000000000000000000000000000000000000000000000000000), b: Some(0x000000000000000000000000000000000000000000000000000000000000004e) }, [0x0000000000000000000000000000000000000000000000000000000000000000]), (GreaterThanCircuit { a: Some(0x0000000000000000000000000000000000000000000000000000000000903c4d), b: Some(0x000000000000000000000000000000000000000000000000000000000075534f) }, [0x0000000000000000000000000000000000000000000000000000000000000001]), (GreaterThanCircuit { a: Some(0x000000000000000000000000000000000000000000000000000000000006881d), b: Some(0x0000000000000000000000000000000000000000000000000000000000f50d8d) }, [0x0000000000000000000000000000000000000000000000000000000000000000]), (GreaterThanCircuit { a: Some(0x000000000000000000000000000000000000000000000000000000000010cc7a), b: Some(0x00000000000000000000000000000000000000000000000000000000002b964e) }, [0x0000000000000000000000000000000000000000000000000000000000000000]), (GreaterThanCircuit { a: Some(0x0000000000000000000000000000000000000000000000000000000000a81b42), b: Some(0x0000000000000000000000000000000000000000000000000000000000a4f341) }, [0x0000000000000000000000000000000000000000000000000000000000000001]), (GreaterThanCircuit { a: Some(0x00000000000000000000000000000000000000000000000000000000003f0245), b: Some(0x0000000000000000000000000000000000000000000000000000000000be480a) }, [0x0000000000000000000000000000000000000000000000000000000000000000]), (GreaterThanCircuit { a: Some(0x00000000000000000000000000000000000000000000000000000000005872c6), b: Some(0x000000000000000000000000000000000000000000000000000000000008cdac) }, [0x0000000000000000000000000000000000000000000000000000000000000001]), (GreaterThanCircuit { a: Some(0x00000000000000000000000000000000000000000000000000000000002c6c1f), b: Some(0x0000000000000000000000000000000000000000000000000000000000b8e00c) }, [0x0000000000000000000000000000000000000000000000000000000000000000]), (GreaterThanCircuit { a: Some(0x0000000000000000000000000000000000000000000000000000000000e4b381), b: Some(0x0000000000000000000000000000000000000000000000000000000000b0190e) }, [0x0000000000000000000000000000000000000000000000000000000000000001]), (GreaterThanCircuit { a: Some(0x00000000000000000000000000000000000000000000000000000000007faefa), b: Some(0x0000000000000000000000000000000000000000000000000000000000306468) }, [0x0000000000000000000000000000000000000000000000000000000000000001])] 13 | -------------------------------------------------------------------------------- /src/circuits/tables/signed.rs: -------------------------------------------------------------------------------- 1 | use crate::assign::ConstraintSys; 2 | use halo2_proofs::circuit::{Region, Value}; 3 | use halo2_proofs::plonk::{Constraints, VirtualCells}; 4 | use halo2_proofs::{ 5 | arithmetic::FieldExt, 6 | plonk::{Advice, Column, Expression, Selector}, 7 | poly::Rotation, 8 | }; 9 | 10 | use super::even_bits::{EvenBitsConfig, EvenBitsTable}; 11 | 12 | #[derive(Debug, Clone, Copy)] 13 | pub struct SignedConfig { 14 | /// A Selector denoting the extent of the exe table. 15 | pub s_table: Selector, 16 | pub word: EvenBitsConfig, 17 | /// Denoted σ_column in the paper. 18 | pub msb: Column, 19 | pub word_sigma: Column, 20 | pub check_sign: EvenBitsConfig, 21 | } 22 | 23 | impl SignedConfig { 24 | pub fn new( 25 | meta: &mut impl ConstraintSys>, 26 | s_table: Selector, 27 | s_signed: &[Column], 28 | word: EvenBitsConfig, 29 | even_bits: EvenBitsTable, 30 | ) -> Self { 31 | let msb = meta.new_column(); 32 | let word_sigma = meta.new_column(); 33 | 34 | let check_sign_exp = meta.new_column(); 35 | let check_sign = EvenBitsConfig::configure( 36 | meta, 37 | check_sign_exp, 38 | s_signed, 39 | s_table, 40 | even_bits, 41 | ); 42 | Self { 43 | s_table, 44 | word, 45 | msb, 46 | word_sigma, 47 | check_sign, 48 | } 49 | } 50 | 51 | pub fn configure( 52 | meta: &mut impl ConstraintSys>, 53 | s_table: Selector, 54 | s_signed: &[Column], 55 | word: EvenBitsConfig, 56 | even_bits: EvenBitsTable, 57 | ) -> Self { 58 | let conf @ Self { 59 | s_table, 60 | word, 61 | msb, 62 | // Don't we need to constrain word_sigma? 63 | word_sigma, 64 | check_sign, 65 | } = Self::new(meta, s_table, s_signed, word, even_bits); 66 | 67 | let s_signed = |meta: &mut VirtualCells| -> Expression { 68 | let s_table = meta.query_selector(s_table); 69 | s_signed 70 | .iter() 71 | .map(|c| meta.query_advice(*c, Rotation::cur())) 72 | .fold(None, |e, c| { 73 | e.map(|e| Some(e + c.clone())).unwrap_or(Some(c)) 74 | }) 75 | .map(|e| s_table.clone() * (e)) 76 | .unwrap_or(s_table) 77 | }; 78 | 79 | meta.cs().create_gate("signed", |meta| { 80 | let one = Expression::Constant(F::one()); 81 | let two = Expression::Constant(F::from_u128(2)); 82 | let max = Expression::Constant(F::from_u128(2u128.pow(WORD_BITS))); 83 | 84 | let word_odd = meta.query_advice(word.odd, Rotation::cur()); 85 | let msb = meta.query_advice(msb, Rotation::cur()); 86 | let word_sigma = meta.query_advice(word_sigma, Rotation::cur()); 87 | let word_sigma = 88 | -msb.clone() * two.clone() * word_sigma.clone() + word_sigma; 89 | 90 | let word = meta.query_advice(word.word, Rotation::cur()); 91 | let check_sign = meta.query_advice(check_sign.word, Rotation::cur()); 92 | 93 | // TODO Do we need to range check this? 94 | Constraints::with_selector( 95 | s_signed(meta), 96 | [ 97 | (-msb.clone() * max + word) - word_sigma, 98 | (word_odd 99 | + (one - two * msb) 100 | * Expression::Constant(F::from( 101 | 2u64.pow(WORD_BITS - 2), 102 | )) 103 | - check_sign), 104 | ], 105 | ) 106 | }); 107 | 108 | conf 109 | } 110 | 111 | pub fn assign_signed( 112 | &self, 113 | region: &mut Region<'_, F>, 114 | word: u128, 115 | offset: usize, 116 | ) { 117 | let msb = (word >> (WORD_BITS - 1)) & 1; 118 | 119 | region 120 | .assign_advice( 121 | || "msb", 122 | self.msb, 123 | offset, 124 | || Value::known(F::from_u128(msb)), 125 | ) 126 | .unwrap(); 127 | 128 | // TODO revist this, compare this approach to using `-F::from(n)`; 129 | // See page 28 130 | let word_sigma = -(msb as i128) * 2i128.pow(WORD_BITS) + word as i128; 131 | let word_sigma = if word_sigma >= 0 { 132 | word_sigma as u128 133 | } else { 134 | -word_sigma as u128 135 | }; 136 | 137 | region 138 | .assign_advice( 139 | || "word_sigma", 140 | self.word_sigma, 141 | offset, 142 | || Value::known(F::from_u128(word_sigma)), 143 | ) 144 | .unwrap(); 145 | 146 | let (_e, o) = self 147 | .word 148 | .assign_decompose(region, F::from_u128(word), offset); 149 | 150 | let cs = (o.0.get_lower_128() as i128) 151 | + (1 - 2 * (msb as i128)) * 2i128.pow(WORD_BITS - 2); 152 | assert!(cs >= 0); 153 | let cs = F::from_u128(cs as u128); 154 | 155 | self.check_sign.assign_decompose(region, cs, offset); 156 | region 157 | .assign_advice( 158 | || "check_sign", 159 | self.check_sign.word, 160 | offset, 161 | || Value::known(cs), 162 | ) 163 | .unwrap(); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/circuits/tables/exe/temp_vars.rs: -------------------------------------------------------------------------------- 1 | use halo2_proofs::{ 2 | arithmetic::FieldExt, 3 | circuit::{Region, Value}, 4 | plonk::{Advice, Column, Selector}, 5 | }; 6 | 7 | use crate::{ 8 | assign::ConstraintSys, 9 | circuits::tables::{ 10 | aux::out::Out, 11 | even_bits::{EvenBitsConfig, EvenBitsTable}, 12 | }, 13 | }; 14 | 15 | /// The temporary variables, and their decompositions which always exist (namely even bits). 16 | /// Other decompositions like `SignedConfig` only exist for lines of Exe containing an instruction 17 | /// that uses the decomposition. 18 | /// 19 | /// The even bits decomposition is enforced on all lines of Exe for every temporary variable. 20 | /// The temporary variable columns are stored at `EvenBitsConfig.word`. 21 | #[derive(Debug, Clone, Copy)] 22 | pub struct TempVars { 23 | pub a: EvenBitsConfig, 24 | pub b: EvenBitsConfig, 25 | pub c: EvenBitsConfig, 26 | pub d: EvenBitsConfig, 27 | } 28 | 29 | impl TempVars { 30 | /// `s_table` defines the maxium extent of the Exe table. 31 | pub fn configure( 32 | meta: &mut impl ConstraintSys>, 33 | // A complex selector denoting the extent in rows of the table to decompse. 34 | s_table: Selector, 35 | out: Out>, 36 | even_bits: EvenBitsTable, 37 | ) -> Self { 38 | // Temporary vars 39 | // 40 | // By using `meta.cs().advice_column()` instead of `meta.new_column()` 41 | // we exclude temporary variables columns from `TrackColumns`. 42 | let a = meta.cs().advice_column(); 43 | let b = meta.cs().advice_column(); 44 | let c = meta.cs().advice_column(); 45 | let d = meta.cs().advice_column(); 46 | 47 | // In the future we may want to optimize column count by reusing `EvenBitsConfig{even, odd}`. 48 | // The table below describes when eacj decomposition is in use. 49 | 50 | let Out { 51 | and, 52 | xor, 53 | or, 54 | sum, 55 | ssum, 56 | prod, 57 | sprod, 58 | mod_, 59 | shift, 60 | flag4, 61 | .. 62 | } = out; 63 | 64 | // Constraints which rely on a's even_bits decomposition: 65 | // MOD (UDiv non_det must be valid word) 66 | // AND, OR, XOR (`LogicConfig`) 67 | // SSUM, SPROD (`signed_a`) 68 | let a = EvenBitsConfig::configure( 69 | meta, 70 | a, 71 | &[mod_, and, or, xor, ssum, sprod], 72 | s_table, 73 | even_bits, 74 | ); 75 | 76 | // Constraints which rely on b's even_bits decomposition: 77 | // MOD (UMod non_det must be valid word) 78 | // SUM (Cmpa, Cmpae non_det must be valid word) 79 | // SSUM (Cmpge, Cmpg non_det must be valid word) 80 | // SPROD, FLAG4 (`signed_b`) 81 | // 82 | // SHIFT can be active with FLAG4, 83 | let b = EvenBitsConfig::configure( 84 | meta, 85 | b, 86 | &[mod_, sum, ssum, sprod, flag4], 87 | s_table, 88 | even_bits, 89 | ); 90 | 91 | // Constraints which rely on c's even_bits decomposition: 92 | // XOR (Cmpe non_det must be valid word) 93 | // PROD (Mull non_det must be valid word) 94 | // SHIFT (Shl non_det must be valid word) 95 | // SSUM, SPROD (`signed_c`) 96 | let c = EvenBitsConfig::configure( 97 | meta, 98 | c, 99 | &[xor, prod, shift, ssum, sprod], 100 | s_table, 101 | even_bits, 102 | ); 103 | 104 | // Constraints which rely on d's even_bits decomposition: 105 | // SHIFT (Shr non_det must be valid word) 106 | // PROD (UMulh non_det must be valid word) 107 | // SPROD (SMulh on_det must be valid word) 108 | let d = EvenBitsConfig::configure( 109 | meta, 110 | d, 111 | // FIXME enforce on shift 112 | // &[shift, prod, sprod], 113 | &[prod, sprod], 114 | s_table, 115 | even_bits, 116 | ); 117 | 118 | TempVars { a, b, c, d } 119 | } 120 | 121 | pub fn assign_temp_vars( 122 | &self, 123 | region: &mut Region<'_, F>, 124 | a: F, 125 | b: F, 126 | c: F, 127 | d: F, 128 | offset: usize, 129 | ) { 130 | region 131 | .assign_advice( 132 | || format!("a: {:?}", a), 133 | self.a.word, 134 | offset, 135 | || Value::known(a), 136 | ) 137 | .unwrap(); 138 | self.a.assign_decompose(region, a, offset); 139 | 140 | region 141 | .assign_advice( 142 | || format!("b: {:?}", b), 143 | self.b.word, 144 | offset, 145 | || Value::known(b), 146 | ) 147 | .unwrap(); 148 | self.b.assign_decompose(region, b, offset); 149 | 150 | region 151 | .assign_advice( 152 | || format!("c: {:?}", c), 153 | self.c.word, 154 | offset, 155 | || Value::known(c), 156 | ) 157 | .unwrap(); 158 | self.c.assign_decompose(region, c, offset); 159 | 160 | region 161 | .assign_advice( 162 | || format!("d: {:?}", d), 163 | self.d.word, 164 | offset, 165 | || Value::known(d), 166 | ) 167 | .unwrap(); 168 | self.d.assign_decompose(region, d, offset); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/circuits/tables/prog.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use halo2_proofs::{ 4 | arithmetic::FieldExt, 5 | circuit::{Layouter, Value}, 6 | plonk::{ 7 | Advice, Column, ConstraintSystem, DynamicTable, DynamicTableMap, Error, 8 | Fixed, Instance, Selector, 9 | }, 10 | poly::Rotation, 11 | }; 12 | 13 | use crate::{ 14 | assign::{AssignCell, NewColumn, PseudoColumn, PseudoMeta}, 15 | instructions::Instruction, 16 | trace, 17 | }; 18 | 19 | use super::aux::{TempVarSelectors, TempVarSelectorsRow}; 20 | 21 | #[derive(Debug, Clone, Copy)] 22 | pub struct ProgramLine { 23 | pub opcode: C, 24 | pub immediate: C, 25 | pub temp_var_selectors: TempVarSelectors, 26 | } 27 | 28 | #[derive(Debug, Clone, Copy)] 29 | pub struct ProgConfig { 30 | s_prog: Selector, 31 | input: ProgramLine>, 32 | 33 | dyn_table: DynamicTable, 34 | table: ProgramLine>, 35 | pc: Column, 36 | } 37 | 38 | pub fn program_instance< 39 | const WORD_BITS: u32, 40 | const REG_COUNT: usize, 41 | F: FieldExt, 42 | >( 43 | mut program: trace::Program, 44 | ) -> Vec> { 45 | let max_program_len = ProgConfig::::TABLE_LEN; 46 | let last_inst = *program.0.last().expect("Empty programs are invalid"); 47 | assert!(matches!(last_inst, Instruction::Answer(_))); 48 | assert!(program.0.len() <= max_program_len); 49 | 50 | for _ in 0..(max_program_len - program.0.len()) { 51 | // Fill the remainder of the program space by repeating the terminal instruction (Answer). 52 | program.0.push(last_inst); 53 | } 54 | 55 | let mut meta = PseudoMeta::default(); 56 | let input_cols: ProgramLine = 57 | ProgramLine::configure::(&mut meta); 58 | input_cols.assign_cells(&mut meta, &program); 59 | meta.0 60 | } 61 | 62 | impl 63 | ProgramLine 64 | { 65 | pub fn configure>( 66 | meta: &mut M, 67 | ) -> ProgramLine { 68 | let opcode = meta.new_column(); 69 | let immediate = meta.new_column(); 70 | let temp_vars = TempVarSelectors::new::(meta); 71 | 72 | ProgramLine { 73 | opcode, 74 | immediate, 75 | temp_var_selectors: temp_vars, 76 | } 77 | } 78 | 79 | pub fn assign_cells>( 80 | self, 81 | region: &mut R, 82 | program: &trace::Program, 83 | ) { 84 | let ProgramLine { 85 | opcode, 86 | immediate, 87 | temp_var_selectors, 88 | } = self; 89 | 90 | for (offset, inst) in program.0.iter().enumerate() { 91 | region 92 | .assign_cell(opcode, offset, F::from_u128(inst.opcode())) 93 | .unwrap(); 94 | region 95 | .assign_cell( 96 | immediate, 97 | offset, 98 | F::from_u128(inst.a().immediate().unwrap_or_default().into()), 99 | ) 100 | .unwrap(); 101 | temp_var_selectors.assign_cells( 102 | region, 103 | offset, 104 | TempVarSelectorsRow::from(inst), 105 | ); 106 | } 107 | } 108 | 109 | pub fn map( 110 | self, 111 | mut f: impl FnMut(C) -> B, 112 | ) -> ProgramLine { 113 | let Self { 114 | opcode, 115 | immediate, 116 | temp_var_selectors, 117 | } = self; 118 | 119 | ProgramLine { 120 | opcode: f(opcode), 121 | immediate: f(immediate), 122 | temp_var_selectors: temp_var_selectors.map(f), 123 | } 124 | } 125 | 126 | pub fn to_vec(self) -> Vec { 127 | let mut v = Vec::with_capacity(30); 128 | self.map(|c| v.push(c)); 129 | v 130 | } 131 | } 132 | 133 | impl ProgConfig { 134 | /// Currently this is the same as `ExeConfig::TABLE_LEN`. 135 | /// Programs will usually be much smaller than traces, 136 | /// so we should reduce this to allow stacking. 137 | const TABLE_LEN: usize = 2usize.pow(WORD_BITS / 2); 138 | 139 | pub fn configure(meta: &mut ConstraintSystem) -> Self { 140 | let s_prog = meta.selector(); 141 | let input = ProgramLine::configure::(meta); 142 | 143 | let table = ProgramLine::configure::(meta); 144 | let pc = meta.fixed_column(); 145 | let dyn_table = meta.create_dynamic_table( 146 | "Prog Table", 147 | &[pc], 148 | table.to_vec().as_slice(), 149 | ); 150 | 151 | input.map(|c| meta.enable_equality(c)); 152 | table.map(|c| meta.enable_equality(c)); 153 | 154 | Self { 155 | s_prog, 156 | input, 157 | dyn_table, 158 | table, 159 | pc, 160 | } 161 | } 162 | 163 | pub fn lookup( 164 | &self, 165 | meta: &mut ConstraintSystem, 166 | s_trace: Column, 167 | pc: Column, 168 | exe_line: ProgramLine>, 169 | ) { 170 | meta.lookup_dynamic(&self.dyn_table, |meta| { 171 | let s_trace = meta.query_advice(s_trace, Rotation::cur()); 172 | 173 | let pc = meta.query_advice(pc, Rotation::cur()); 174 | let mut table_map = vec![(pc, self.pc.into())]; 175 | table_map.extend( 176 | exe_line 177 | .to_vec() 178 | .into_iter() 179 | .zip(self.table.to_vec().into_iter()) 180 | .map(|(exe_col, prog_col)| { 181 | ( 182 | meta.query_advice(exe_col, Rotation::cur()), 183 | prog_col.into(), 184 | ) 185 | }), 186 | ); 187 | 188 | DynamicTableMap { 189 | selector: s_trace, 190 | table_map, 191 | } 192 | }); 193 | } 194 | 195 | pub fn assign_prog( 196 | self, 197 | layouter: &mut impl Layouter, 198 | ) -> Result<(), Error> { 199 | layouter.assign_region( 200 | || "Prog", 201 | |mut region| { 202 | let input_cols = self.input.to_vec(); 203 | let table_cols = self.table.to_vec(); 204 | 205 | for (ic, tc) in input_cols.into_iter().zip(table_cols.into_iter()) { 206 | for offset in 0..Self::TABLE_LEN { 207 | region.assign_advice_from_instance( 208 | || "", 209 | ic, 210 | offset, 211 | tc, 212 | offset, 213 | )?; 214 | } 215 | } 216 | 217 | for offset in 0..Self::TABLE_LEN { 218 | self.s_prog.enable(&mut region, offset)?; 219 | self.dyn_table.add_row(&mut region, offset)?; 220 | region.assign_fixed( 221 | || "pc", 222 | self.pc, 223 | offset, 224 | || Value::known(F::from(offset as u64)), 225 | )?; 226 | } 227 | 228 | Ok(()) 229 | }, 230 | )?; 231 | 232 | Ok(()) 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/circuits/tables/aux/out_table.rs: -------------------------------------------------------------------------------- 1 | use halo2_proofs::{ 2 | arithmetic::FieldExt, 3 | circuit::{Layouter, Table, Value}, 4 | plonk::{ 5 | Advice, Column, ConstraintSystem, Error, Expression, Selector, TableColumn, 6 | }, 7 | poly::Rotation, 8 | }; 9 | 10 | use super::out::{Out, OutPut}; 11 | use crate::instructions::{opcode::OpCode, unit::*}; 12 | 13 | #[allow(dead_code)] 14 | #[derive(Debug, Clone, Copy)] 15 | pub struct CorrectOutConfig { 16 | opcode: Column, 17 | out: Out>, 18 | out_table: OutTable, 19 | } 20 | 21 | impl CorrectOutConfig { 22 | /// configure a lookup between opcode, and Out selection vector. 23 | pub fn configure( 24 | meta: &mut ConstraintSystem, 25 | opcode: Column, 26 | out: Out>, 27 | // A advice selector denoting if a line of Exe is part of the trace. 28 | s_in_trace: Column, 29 | // A complex selector denoting the extent in rows of the table to decompose. 30 | s_table: Selector, 31 | out_table: OutTable, 32 | ) -> Self { 33 | meta.lookup(|meta| { 34 | let s_table = meta.query_selector(s_table); 35 | let s_in_trace_next = meta.query_advice(s_in_trace, Rotation::next()); 36 | let s_in_trace = meta.query_advice(s_in_trace, Rotation::cur()); 37 | let opcode = meta.query_advice(opcode, Rotation::cur()); 38 | let Out { 39 | and, 40 | xor, 41 | or, 42 | sum, 43 | ssum, 44 | prod, 45 | sprod, 46 | mod_, 47 | shift, 48 | flag1, 49 | flag2, 50 | flag3, 51 | flag4, 52 | } = out.map(|c| meta.query_advice(c, Rotation::cur())); 53 | 54 | [ 55 | (s_in_trace_next, out_table.continue_trace), 56 | // For an explanation of `opcode + 1` the comment on `OutTable`. 57 | (opcode + Expression::Constant(F::one()), out_table.opcode), 58 | (and, out_table.out.and), 59 | (xor, out_table.out.xor), 60 | (or, out_table.out.or), 61 | (sum, out_table.out.sum), 62 | (ssum, out_table.out.ssum), 63 | (prod, out_table.out.prod), 64 | (sprod, out_table.out.sprod), 65 | (mod_, out_table.out.mod_), 66 | (shift, out_table.out.shift), 67 | (flag1, out_table.out.flag1), 68 | (flag2, out_table.out.flag2), 69 | (flag3, out_table.out.flag3), 70 | (flag4, out_table.out.flag4), 71 | ] 72 | .map(|(e, t)| (s_table.clone() * s_in_trace.clone() * e, t)) 73 | .to_vec() 74 | }); 75 | 76 | Self { 77 | opcode, 78 | out, 79 | out_table, 80 | } 81 | } 82 | } 83 | 84 | /// The table maps `opcode + 1` to the opcode's `Out` selection vector. 85 | /// `opcode + 1` is used to give a default mapping `(opcode * 0, Out * 0)`. 86 | /// This increment is hidden from the user in the `configure` method. 87 | #[derive(Debug, Clone, Copy)] 88 | pub struct OutTable { 89 | opcode: TableColumn, 90 | out: Out, 91 | // 0 for Answer 1 for everything else. 92 | continue_trace: TableColumn, 93 | } 94 | 95 | impl OutTable { 96 | pub fn new(meta: &mut ConstraintSystem) -> Self { 97 | OutTable { 98 | opcode: meta.lookup_table_column(), 99 | out: Out::new(|| meta.lookup_table_column()), 100 | continue_trace: meta.lookup_table_column(), 101 | } 102 | } 103 | 104 | fn assign_row( 105 | &self, 106 | table: &mut Table<'_, F>, 107 | offset: usize, 108 | opcode: u64, 109 | out: Out, 110 | continue_trace: bool, 111 | ) { 112 | table 113 | .assign_cell( 114 | || "", 115 | self.opcode, 116 | offset, 117 | || Value::known(F::from(opcode)), 118 | ) 119 | .unwrap(); 120 | 121 | table 122 | .assign_cell( 123 | || "", 124 | self.continue_trace, 125 | offset, 126 | || Value::known(F::from(continue_trace)), 127 | ) 128 | .unwrap(); 129 | 130 | self.out.push_cells(table, offset, out).unwrap(); 131 | } 132 | 133 | pub fn alloc_table( 134 | self, 135 | layouter: &mut impl Layouter, 136 | ) -> Result<(), Error> { 137 | layouter.assign_table( 138 | || "out_table", 139 | |mut table| { 140 | self.assign_row(&mut table, 0, And::OP_CODE + 1, And::OUT, true); 141 | self.assign_row(&mut table, 1, Or::OP_CODE + 1, Or::OUT, true); 142 | self.assign_row(&mut table, 2, Xor::OP_CODE + 1, Xor::OUT, true); 143 | self.assign_row(&mut table, 3, Not::OP_CODE + 1, Not::OUT, true); 144 | self.assign_row(&mut table, 4, Add::OP_CODE + 1, Add::OUT, true); 145 | self.assign_row(&mut table, 5, Sub::OP_CODE + 1, Sub::OUT, true); 146 | self.assign_row(&mut table, 6, Mull::OP_CODE + 1, Mull::OUT, true); 147 | self.assign_row(&mut table, 7, UMulh::OP_CODE + 1, UMulh::OUT, true); 148 | self.assign_row(&mut table, 8, SMulh::OP_CODE + 1, SMulh::OUT, true); 149 | self.assign_row(&mut table, 9, UDiv::OP_CODE + 1, UDiv::OUT, true); 150 | self.assign_row(&mut table, 10, UMod::OP_CODE + 1, UMod::OUT, true); 151 | self.assign_row(&mut table, 11, Shl::OP_CODE + 1, Shl::OUT, true); 152 | self.assign_row(&mut table, 12, Shr::OP_CODE + 1, Shr::OUT, true); 153 | self.assign_row(&mut table, 13, Cmpe::OP_CODE + 1, Cmpe::OUT, true); 154 | self.assign_row(&mut table, 14, Cmpa::OP_CODE + 1, Cmpa::OUT, true); 155 | self.assign_row( 156 | &mut table, 157 | 15, 158 | Cmpae::OP_CODE + 1, 159 | Cmpae::OUT, 160 | true, 161 | ); 162 | self.assign_row(&mut table, 16, Cmpg::OP_CODE + 1, Cmpg::OUT, true); 163 | self.assign_row( 164 | &mut table, 165 | 17, 166 | Cmpge::OP_CODE + 1, 167 | Cmpge::OUT, 168 | true, 169 | ); 170 | self.assign_row(&mut table, 18, Mov::OP_CODE + 1, Mov::OUT, true); 171 | self.assign_row(&mut table, 19, CMov::OP_CODE + 1, CMov::OUT, true); 172 | self.assign_row(&mut table, 20, Jmp::OP_CODE + 1, Jmp::OUT, true); 173 | self.assign_row(&mut table, 21, CJmp::OP_CODE + 1, CJmp::OUT, true); 174 | self.assign_row( 175 | &mut table, 176 | 22, 177 | CnJmp::OP_CODE + 1, 178 | CnJmp::OUT, 179 | true, 180 | ); 181 | self.assign_row( 182 | &mut table, 183 | 23, 184 | StoreW::OP_CODE + 1, 185 | StoreW::OUT, 186 | true, 187 | ); 188 | self.assign_row( 189 | &mut table, 190 | 24, 191 | LoadW::OP_CODE + 1, 192 | LoadW::OUT, 193 | true, 194 | ); 195 | self.assign_row( 196 | &mut table, 197 | 25, 198 | Answer::OP_CODE + 1, 199 | Answer::OUT, 200 | false, 201 | ); 202 | 203 | // default value 204 | self.assign_row( 205 | &mut table, 206 | 26, 207 | 0, 208 | // Answer has a empty Out 209 | Answer::OUT, 210 | false, 211 | ); 212 | Ok(()) 213 | }, 214 | ) 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/circuits/tables/aux/out.rs: -------------------------------------------------------------------------------- 1 | use halo2_proofs::{arithmetic::FieldExt, plonk}; 2 | 3 | use crate::{assign::AssignCell, instructions::*}; 4 | 5 | pub trait OutPut { 6 | const OUT: Out; 7 | } 8 | 9 | /// This corresponds to `s_out` in the paper (page 24). 10 | #[derive(Debug, Clone, Copy)] 11 | pub struct Out { 12 | /// logical 13 | pub and: T, 14 | pub xor: T, 15 | pub or: T, 16 | 17 | /// arithmetic 18 | pub sum: T, 19 | pub ssum: T, 20 | pub prod: T, 21 | pub sprod: T, 22 | pub mod_: T, 23 | 24 | pub shift: T, 25 | 26 | pub flag1: T, 27 | pub flag2: T, 28 | pub flag3: T, 29 | pub flag4: T, 30 | } 31 | 32 | // serves as default, except it's private. 33 | const EMPTY_OUT: Out = Out { 34 | and: false, 35 | xor: false, 36 | or: false, 37 | sum: false, 38 | prod: false, 39 | ssum: false, 40 | sprod: false, 41 | mod_: false, 42 | shift: false, 43 | flag1: false, 44 | flag2: false, 45 | flag3: false, 46 | flag4: false, 47 | }; 48 | 49 | impl Out { 50 | pub fn new(mut new_fn: impl FnMut() -> T) -> Out { 51 | Out { 52 | and: new_fn(), 53 | xor: new_fn(), 54 | or: new_fn(), 55 | sum: new_fn(), 56 | prod: new_fn(), 57 | ssum: new_fn(), 58 | sprod: new_fn(), 59 | mod_: new_fn(), 60 | shift: new_fn(), 61 | flag1: new_fn(), 62 | flag2: new_fn(), 63 | flag3: new_fn(), 64 | flag4: new_fn(), 65 | } 66 | } 67 | 68 | pub fn map(self, mut f: impl FnMut(T) -> B) -> Out { 69 | Out { 70 | and: f(self.and), 71 | xor: f(self.xor), 72 | or: f(self.or), 73 | sum: f(self.sum), 74 | prod: f(self.prod), 75 | ssum: f(self.ssum), 76 | sprod: f(self.sprod), 77 | mod_: f(self.mod_), 78 | shift: f(self.shift), 79 | flag1: f(self.flag1), 80 | flag2: f(self.flag2), 81 | flag3: f(self.flag3), 82 | flag4: f(self.flag4), 83 | } 84 | } 85 | } 86 | 87 | impl Out { 88 | pub fn push_cells>( 89 | self, 90 | region: &mut R, 91 | offset: usize, 92 | vals: Out, 93 | ) -> Result<(), plonk::Error> { 94 | let Self { 95 | and, 96 | xor, 97 | or, 98 | sum, 99 | prod, 100 | ssum, 101 | sprod, 102 | mod_, 103 | shift, 104 | flag1, 105 | flag2, 106 | flag3, 107 | flag4, 108 | } = self; 109 | 110 | region.assign_cell(and, offset, vals.and.into())?; 111 | region.assign_cell(xor, offset, vals.xor.into())?; 112 | region.assign_cell(or, offset, vals.or.into())?; 113 | region.assign_cell(sum, offset, vals.sum.into())?; 114 | region.assign_cell(prod, offset, vals.prod.into())?; 115 | region.assign_cell(ssum, offset, vals.ssum.into())?; 116 | region.assign_cell(sprod, offset, vals.sprod.into())?; 117 | region.assign_cell(mod_, offset, vals.mod_.into())?; 118 | region.assign_cell(shift, offset, vals.shift.into())?; 119 | region.assign_cell(flag1, offset, vals.flag1.into())?; 120 | region.assign_cell(flag2, offset, vals.flag2.into())?; 121 | region.assign_cell(flag3, offset, vals.flag3.into())?; 122 | region.assign_cell(flag4, offset, vals.flag4.into())?; 123 | 124 | Ok(()) 125 | } 126 | } 127 | 128 | impl Out { 129 | pub fn convert>(self) -> Out { 130 | Out { 131 | and: self.and.into(), 132 | xor: self.xor.into(), 133 | or: self.or.into(), 134 | sum: self.sum.into(), 135 | prod: self.prod.into(), 136 | ssum: self.ssum.into(), 137 | sprod: self.sprod.into(), 138 | mod_: self.mod_.into(), 139 | shift: self.shift.into(), 140 | flag1: self.flag1.into(), 141 | flag2: self.flag2.into(), 142 | flag3: self.flag3.into(), 143 | flag4: self.flag4.into(), 144 | } 145 | } 146 | } 147 | 148 | impl OutPut for And { 149 | const OUT: Out = Out { 150 | and: true, 151 | flag1: true, 152 | flag2: true, 153 | ..EMPTY_OUT 154 | }; 155 | } 156 | 157 | impl OutPut for Or { 158 | const OUT: Out = Out { 159 | or: true, 160 | flag1: true, 161 | flag2: true, 162 | ..EMPTY_OUT 163 | }; 164 | } 165 | 166 | impl OutPut for Xor { 167 | const OUT: Out = Out { 168 | xor: true, 169 | flag1: true, 170 | flag2: true, 171 | ..EMPTY_OUT 172 | }; 173 | } 174 | 175 | impl OutPut for Not { 176 | const OUT: Out = Out { 177 | xor: true, 178 | flag1: true, 179 | flag2: true, 180 | ..EMPTY_OUT 181 | }; 182 | } 183 | 184 | impl OutPut for Add { 185 | const OUT: Out = Out { 186 | sum: true, 187 | ..EMPTY_OUT 188 | }; 189 | } 190 | 191 | impl OutPut for Sub { 192 | const OUT: Out = Out { 193 | sum: true, 194 | ..EMPTY_OUT 195 | }; 196 | } 197 | 198 | impl OutPut for Mull { 199 | const OUT: Out = Out { 200 | prod: true, 201 | flag1: true, 202 | flag2: true, 203 | ..EMPTY_OUT 204 | }; 205 | } 206 | 207 | impl OutPut for UMulh { 208 | const OUT: Out = Out { 209 | prod: true, 210 | flag1: true, 211 | flag2: true, 212 | ..EMPTY_OUT 213 | }; 214 | } 215 | 216 | impl OutPut for SMulh { 217 | const OUT: Out = Out { 218 | sprod: true, 219 | flag1: true, 220 | flag2: true, 221 | ..EMPTY_OUT 222 | }; 223 | } 224 | 225 | impl OutPut for UDiv { 226 | const OUT: Out = Out { 227 | mod_: true, 228 | flag1: true, 229 | flag2: true, 230 | flag3: true, 231 | ..EMPTY_OUT 232 | }; 233 | } 234 | 235 | impl OutPut for UMod { 236 | const OUT: Out = Out { 237 | mod_: true, 238 | flag1: true, 239 | flag2: true, 240 | flag3: true, 241 | ..EMPTY_OUT 242 | }; 243 | } 244 | 245 | impl OutPut for Shl { 246 | const OUT: Out = Out { 247 | shift: true, 248 | flag4: true, 249 | ..EMPTY_OUT 250 | }; 251 | } 252 | 253 | impl OutPut for Shr { 254 | const OUT: Out = Out { 255 | shift: true, 256 | flag4: true, 257 | ..EMPTY_OUT 258 | }; 259 | } 260 | 261 | impl OutPut for Cmpe { 262 | const OUT: Out = Out { 263 | xor: true, 264 | flag1: true, 265 | flag2: true, 266 | ..EMPTY_OUT 267 | }; 268 | } 269 | 270 | impl OutPut for Cmpa { 271 | const OUT: Out = Out { 272 | sum: true, 273 | ..EMPTY_OUT 274 | }; 275 | } 276 | 277 | impl OutPut for Cmpae { 278 | const OUT: Out = Out { 279 | sum: true, 280 | ..EMPTY_OUT 281 | }; 282 | } 283 | 284 | impl OutPut for Cmpg { 285 | const OUT: Out = Out { 286 | ssum: true, 287 | ..EMPTY_OUT 288 | }; 289 | } 290 | 291 | impl OutPut for Cmpge { 292 | const OUT: Out = Out { 293 | ssum: true, 294 | ..EMPTY_OUT 295 | }; 296 | } 297 | 298 | impl OutPut for Mov { 299 | const OUT: Out = Out { 300 | xor: true, 301 | ..EMPTY_OUT 302 | }; 303 | } 304 | 305 | impl OutPut for CMov { 306 | const OUT: Out = Out { 307 | mod_: true, 308 | ..EMPTY_OUT 309 | }; 310 | } 311 | 312 | impl OutPut for Jmp { 313 | const OUT: Out = Out { 314 | xor: true, 315 | ..EMPTY_OUT 316 | }; 317 | } 318 | 319 | impl OutPut for CJmp { 320 | const OUT: Out = Out { 321 | mod_: true, 322 | ..EMPTY_OUT 323 | }; 324 | } 325 | 326 | impl OutPut for CnJmp { 327 | const OUT: Out = Out { 328 | mod_: true, 329 | ..EMPTY_OUT 330 | }; 331 | } 332 | 333 | impl OutPut for LoadW { 334 | const OUT: Out = Out { 335 | // FIXME 336 | ..EMPTY_OUT 337 | }; 338 | } 339 | 340 | impl OutPut for StoreW { 341 | const OUT: Out = Out { 342 | xor: true, 343 | ..EMPTY_OUT 344 | }; 345 | } 346 | 347 | impl OutPut for Answer { 348 | const OUT: Out = EMPTY_OUT; 349 | } 350 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 16 | 17 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 18 | 19 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 20 | 21 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 22 | 23 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 24 | 25 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 26 | 27 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 28 | 29 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 30 | 31 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 32 | 33 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 34 | 35 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 36 | 37 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 38 | You must cause any modified files to carry prominent notices stating that You changed the files; and 39 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 40 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 41 | 42 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 43 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 44 | 45 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 46 | 47 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 48 | 49 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 50 | 51 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 52 | 53 | END OF TERMS AND CONDITIONS 54 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "alejandra": { 4 | "inputs": { 5 | "fenix": "fenix", 6 | "flakeCompat": "flakeCompat", 7 | "nixpkgs": [ 8 | "dream2nix", 9 | "nixpkgs" 10 | ] 11 | }, 12 | "locked": { 13 | "lastModified": 1658427149, 14 | "narHash": "sha256-ToD/1z/q5VHsLMrS2h96vjJoLho59eNRtknOUd19ey8=", 15 | "owner": "kamadorueda", 16 | "repo": "alejandra", 17 | "rev": "f5a22afd2adfb249b4e68e0b33aa1f0fb73fb1be", 18 | "type": "github" 19 | }, 20 | "original": { 21 | "owner": "kamadorueda", 22 | "repo": "alejandra", 23 | "type": "github" 24 | } 25 | }, 26 | "all-cabal-json": { 27 | "flake": false, 28 | "locked": { 29 | "lastModified": 1665552503, 30 | "narHash": "sha256-r14RmRSwzv5c+bWKUDaze6pXM7nOsiz1H8nvFHJvufc=", 31 | "owner": "nix-community", 32 | "repo": "all-cabal-json", 33 | "rev": "d7c0434eebffb305071404edcf9d5cd99703878e", 34 | "type": "github" 35 | }, 36 | "original": { 37 | "owner": "nix-community", 38 | "ref": "hackage", 39 | "repo": "all-cabal-json", 40 | "type": "github" 41 | } 42 | }, 43 | "crane": { 44 | "flake": false, 45 | "locked": { 46 | "lastModified": 1661875961, 47 | "narHash": "sha256-f1h/2c6Teeu1ofAHWzrS8TwBPcnN+EEu+z1sRVmMQTk=", 48 | "owner": "ipetkov", 49 | "repo": "crane", 50 | "rev": "d9f394e4e20e97c2a60c3ad82c2b6ef99be19e24", 51 | "type": "github" 52 | }, 53 | "original": { 54 | "owner": "ipetkov", 55 | "repo": "crane", 56 | "type": "github" 57 | } 58 | }, 59 | "devshell": { 60 | "flake": false, 61 | "locked": { 62 | "lastModified": 1663445644, 63 | "narHash": "sha256-+xVlcK60x7VY1vRJbNUEAHi17ZuoQxAIH4S4iUFUGBA=", 64 | "owner": "numtide", 65 | "repo": "devshell", 66 | "rev": "e3dc3e21594fe07bdb24bdf1c8657acaa4cb8f66", 67 | "type": "github" 68 | }, 69 | "original": { 70 | "owner": "numtide", 71 | "repo": "devshell", 72 | "type": "github" 73 | } 74 | }, 75 | "dream2nix": { 76 | "inputs": { 77 | "alejandra": "alejandra", 78 | "all-cabal-json": "all-cabal-json", 79 | "crane": "crane", 80 | "devshell": "devshell", 81 | "flake-utils-pre-commit": "flake-utils-pre-commit", 82 | "ghc-utils": "ghc-utils", 83 | "gomod2nix": "gomod2nix", 84 | "mach-nix": "mach-nix", 85 | "nixpkgs": [ 86 | "nixpkgs" 87 | ], 88 | "poetry2nix": "poetry2nix", 89 | "pre-commit-hooks": "pre-commit-hooks" 90 | }, 91 | "locked": { 92 | "lastModified": 1667429039, 93 | "narHash": "sha256-Lu6da25JioHzerkLHAHSO9suCQFzJ/XBjkcGCIbasLM=", 94 | "owner": "nix-community", 95 | "repo": "dream2nix", 96 | "rev": "5252794e58eedb02d607fa3187ffead7becc81b0", 97 | "type": "github" 98 | }, 99 | "original": { 100 | "owner": "nix-community", 101 | "repo": "dream2nix", 102 | "type": "github" 103 | } 104 | }, 105 | "fenix": { 106 | "inputs": { 107 | "nixpkgs": [ 108 | "dream2nix", 109 | "alejandra", 110 | "nixpkgs" 111 | ], 112 | "rust-analyzer-src": "rust-analyzer-src" 113 | }, 114 | "locked": { 115 | "lastModified": 1657607339, 116 | "narHash": "sha256-HaqoAwlbVVZH2n4P3jN2FFPMpVuhxDy1poNOR7kzODc=", 117 | "owner": "nix-community", 118 | "repo": "fenix", 119 | "rev": "b814c83d9e6aa5a28d0cf356ecfdafb2505ad37d", 120 | "type": "github" 121 | }, 122 | "original": { 123 | "owner": "nix-community", 124 | "repo": "fenix", 125 | "type": "github" 126 | } 127 | }, 128 | "fenix_2": { 129 | "inputs": { 130 | "nixpkgs": [ 131 | "nixpkgs" 132 | ], 133 | "rust-analyzer-src": "rust-analyzer-src_2" 134 | }, 135 | "locked": { 136 | "lastModified": 1667457007, 137 | "narHash": "sha256-u7MQfMBONp8wRo6L12lIjue5cWJFDYgxJWQRgVPYAqI=", 138 | "owner": "nix-community", 139 | "repo": "fenix", 140 | "rev": "970d53297014da010d8c9715ebeba75f1f801756", 141 | "type": "github" 142 | }, 143 | "original": { 144 | "owner": "nix-community", 145 | "repo": "fenix", 146 | "type": "github" 147 | } 148 | }, 149 | "flake-utils-pre-commit": { 150 | "locked": { 151 | "lastModified": 1644229661, 152 | "narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=", 153 | "owner": "numtide", 154 | "repo": "flake-utils", 155 | "rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797", 156 | "type": "github" 157 | }, 158 | "original": { 159 | "owner": "numtide", 160 | "repo": "flake-utils", 161 | "type": "github" 162 | } 163 | }, 164 | "flakeCompat": { 165 | "flake": false, 166 | "locked": { 167 | "lastModified": 1650374568, 168 | "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", 169 | "owner": "edolstra", 170 | "repo": "flake-compat", 171 | "rev": "b4a34015c698c7793d592d66adbab377907a2be8", 172 | "type": "github" 173 | }, 174 | "original": { 175 | "owner": "edolstra", 176 | "repo": "flake-compat", 177 | "type": "github" 178 | } 179 | }, 180 | "ghc-utils": { 181 | "flake": false, 182 | "locked": { 183 | "lastModified": 1662774800, 184 | "narHash": "sha256-1Rd2eohGUw/s1tfvkepeYpg8kCEXiIot0RijapUjAkE=", 185 | "ref": "refs/heads/master", 186 | "rev": "bb3a2d3dc52ff0253fb9c2812bd7aa2da03e0fea", 187 | "revCount": 1072, 188 | "type": "git", 189 | "url": "https://gitlab.haskell.org/bgamari/ghc-utils" 190 | }, 191 | "original": { 192 | "type": "git", 193 | "url": "https://gitlab.haskell.org/bgamari/ghc-utils" 194 | } 195 | }, 196 | "gomod2nix": { 197 | "flake": false, 198 | "locked": { 199 | "lastModified": 1627572165, 200 | "narHash": "sha256-MFpwnkvQpauj799b4QTBJQFEddbD02+Ln5k92QyHOSk=", 201 | "owner": "tweag", 202 | "repo": "gomod2nix", 203 | "rev": "67f22dd738d092c6ba88e420350ada0ed4992ae8", 204 | "type": "github" 205 | }, 206 | "original": { 207 | "owner": "tweag", 208 | "repo": "gomod2nix", 209 | "type": "github" 210 | } 211 | }, 212 | "mach-nix": { 213 | "flake": false, 214 | "locked": { 215 | "lastModified": 1634711045, 216 | "narHash": "sha256-m5A2Ty88NChLyFhXucECj6+AuiMZPHXNbw+9Kcs7F6Y=", 217 | "owner": "DavHau", 218 | "repo": "mach-nix", 219 | "rev": "4433f74a97b94b596fa6cd9b9c0402104aceef5d", 220 | "type": "github" 221 | }, 222 | "original": { 223 | "id": "mach-nix", 224 | "type": "indirect" 225 | } 226 | }, 227 | "nixpkgs": { 228 | "locked": { 229 | "lastModified": 1667292599, 230 | "narHash": "sha256-7ISOUI1aj6UKMPIL+wwthENL22L3+A9V+jS8Is3QsRo=", 231 | "owner": "NixOS", 232 | "repo": "nixpkgs", 233 | "rev": "ef2f213d9659a274985778bff4ca322f3ef3ac68", 234 | "type": "github" 235 | }, 236 | "original": { 237 | "owner": "NixOS", 238 | "ref": "nixpkgs-unstable", 239 | "repo": "nixpkgs", 240 | "type": "github" 241 | } 242 | }, 243 | "poetry2nix": { 244 | "flake": false, 245 | "locked": { 246 | "lastModified": 1632969109, 247 | "narHash": "sha256-jPDclkkiAy5m2gGLBlKgH+lQtbF7tL4XxBrbSzw+Ioc=", 248 | "owner": "nix-community", 249 | "repo": "poetry2nix", 250 | "rev": "aee8f04296c39d88155e05d25cfc59dfdd41cc77", 251 | "type": "github" 252 | }, 253 | "original": { 254 | "owner": "nix-community", 255 | "ref": "1.21.0", 256 | "repo": "poetry2nix", 257 | "type": "github" 258 | } 259 | }, 260 | "pre-commit-hooks": { 261 | "inputs": { 262 | "flake-utils": [ 263 | "dream2nix", 264 | "flake-utils-pre-commit" 265 | ], 266 | "nixpkgs": [ 267 | "dream2nix", 268 | "nixpkgs" 269 | ] 270 | }, 271 | "locked": { 272 | "lastModified": 1646153636, 273 | "narHash": "sha256-AlWHMzK+xJ1mG267FdT8dCq/HvLCA6jwmx2ZUy5O8tY=", 274 | "owner": "cachix", 275 | "repo": "pre-commit-hooks.nix", 276 | "rev": "b6bc0b21e1617e2b07d8205e7fae7224036dfa4b", 277 | "type": "github" 278 | }, 279 | "original": { 280 | "owner": "cachix", 281 | "repo": "pre-commit-hooks.nix", 282 | "type": "github" 283 | } 284 | }, 285 | "root": { 286 | "inputs": { 287 | "dream2nix": "dream2nix", 288 | "fenix": "fenix_2", 289 | "nixpkgs": "nixpkgs" 290 | } 291 | }, 292 | "rust-analyzer-src": { 293 | "flake": false, 294 | "locked": { 295 | "lastModified": 1657557289, 296 | "narHash": "sha256-PRW+nUwuqNTRAEa83SfX+7g+g8nQ+2MMbasQ9nt6+UM=", 297 | "owner": "rust-lang", 298 | "repo": "rust-analyzer", 299 | "rev": "caf23f29144b371035b864a1017dbc32573ad56d", 300 | "type": "github" 301 | }, 302 | "original": { 303 | "owner": "rust-lang", 304 | "ref": "nightly", 305 | "repo": "rust-analyzer", 306 | "type": "github" 307 | } 308 | }, 309 | "rust-analyzer-src_2": { 310 | "flake": false, 311 | "locked": { 312 | "lastModified": 1667394715, 313 | "narHash": "sha256-EeCAvp8X5INFd1mJm6CyQL9ewYxMQjJpUrDMpzPzfHM=", 314 | "owner": "rust-lang", 315 | "repo": "rust-analyzer", 316 | "rev": "56c97a8351abe090f86c45a091b49ba65416a949", 317 | "type": "github" 318 | }, 319 | "original": { 320 | "owner": "rust-lang", 321 | "ref": "nightly", 322 | "repo": "rust-analyzer", 323 | "type": "github" 324 | } 325 | } 326 | }, 327 | "root": "root", 328 | "version": 7 329 | } 330 | -------------------------------------------------------------------------------- /src/circuits/sum.rs: -------------------------------------------------------------------------------- 1 | use crate::assign::ConstraintSys; 2 | use halo2_proofs::circuit::Value; 3 | use halo2_proofs::pasta::Fp; 4 | use halo2_proofs::plonk::Constraints; 5 | use halo2_proofs::{ 6 | arithmetic::FieldExt, 7 | circuit::{Chip, Layouter, SimpleFloorPlanner}, 8 | plonk::{ 9 | Advice, Circuit, Column, ConstraintSystem, Error, Expression, Instance, 10 | Selector, 11 | }, 12 | poly::Rotation, 13 | }; 14 | use std::marker::PhantomData; 15 | 16 | #[derive(Debug, Clone, Copy)] 17 | pub struct SumConfig { 18 | /// A Selector denoting the extent of the exe table. 19 | s_table: Selector, 20 | /// An advice columns that acts as a selector for sum's gate. 21 | /// [`Out.sum`](crate::circuits::tables::aux::Out) 22 | pub s_sum: Column, 23 | 24 | a: Column, 25 | b: Column, 26 | c: Column, 27 | d: Column, 28 | 29 | flag: Column, 30 | } 31 | 32 | impl SumConfig { 33 | pub fn new( 34 | s_table: Selector, 35 | s_sum: Column, 36 | 37 | a: Column, 38 | b: Column, 39 | c: Column, 40 | d: Column, 41 | 42 | flag: Column, 43 | ) -> Self { 44 | Self { 45 | s_table, 46 | s_sum, 47 | a, 48 | b, 49 | c, 50 | d, 51 | flag, 52 | } 53 | } 54 | 55 | #[allow(clippy::complexity)] 56 | pub fn configure( 57 | meta: &mut impl ConstraintSys>, 58 | s_table: Selector, 59 | s_sum: Column, 60 | 61 | a: Column, 62 | b: Column, 63 | c: Column, 64 | d: Column, 65 | 66 | flag: Column, 67 | ) -> Self { 68 | let conf @ Self { 69 | s_table, 70 | s_sum, 71 | a, 72 | b, 73 | c, 74 | d, 75 | flag, 76 | } = Self::new(s_table, s_sum, a, b, c, d, flag); 77 | 78 | meta.cs().create_gate("sum", |meta| { 79 | let s_table = meta.query_selector(s_table); 80 | let s_sum = meta.query_advice(s_sum, Rotation::cur()); 81 | 82 | let a = meta.query_advice(a, Rotation::cur()); 83 | let b = meta.query_advice(b, Rotation::cur()); 84 | let c = meta.query_advice(c, Rotation::cur()); 85 | let d = meta.query_advice(d, Rotation::cur()); 86 | let flag_n = meta.query_advice(flag, Rotation::next()); 87 | 88 | Constraints::with_selector( 89 | s_table * s_sum, 90 | [a + b 91 | - c 92 | - (Expression::Constant(F::from_u128(2u128.pow(WORD_BITS))) 93 | * flag_n) 94 | + d], 95 | ) 96 | }); 97 | 98 | conf 99 | } 100 | } 101 | 102 | impl AndChip { 103 | pub fn construct(config: >::Config) -> Self { 104 | Self { 105 | config, 106 | _marker: PhantomData, 107 | } 108 | } 109 | } 110 | 111 | /// The chip that will implement our instructions! Chips store their own 112 | /// config, as well as type markers if necessary. 113 | pub struct AndChip { 114 | config: SumConfig, 115 | _marker: PhantomData, 116 | } 117 | 118 | impl Chip for AndChip { 119 | type Config = SumConfig; 120 | type Loaded = (); 121 | 122 | fn config(&self) -> &Self::Config { 123 | &self.config 124 | } 125 | 126 | fn loaded(&self) -> &Self::Loaded { 127 | &() 128 | } 129 | } 130 | 131 | #[derive(Default, Debug, Clone, Copy)] 132 | pub struct SumCircuit { 133 | pub a: Option, 134 | pub b: Option, 135 | } 136 | 137 | impl Circuit for SumCircuit { 138 | type Config = (SumConfig, Column); 139 | type FloorPlanner = SimpleFloorPlanner; 140 | 141 | fn without_witnesses(&self) -> Self { 142 | Self::default() 143 | } 144 | 145 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 146 | let instance = meta.instance_column(); 147 | meta.enable_equality(instance); 148 | 149 | let s_table = meta.complex_selector(); 150 | let s_sum = meta.advice_column(); 151 | 152 | let a = meta.advice_column(); 153 | let b = meta.advice_column(); 154 | let c = meta.advice_column(); 155 | meta.enable_equality(c); 156 | let d = meta.advice_column(); 157 | 158 | // Overflow flag 159 | let flag = meta.advice_column(); 160 | meta.enable_equality(flag); 161 | 162 | ( 163 | SumConfig::::configure( 164 | meta, s_table, s_sum, a, b, c, d, flag, 165 | ), 166 | instance, 167 | ) 168 | } 169 | 170 | fn synthesize( 171 | &self, 172 | config: Self::Config, 173 | mut layouter: impl Layouter, 174 | ) -> Result<(), Error> { 175 | layouter 176 | .assign_region( 177 | || "sum", 178 | |mut region| { 179 | config.0.s_table.enable(&mut region, 0).unwrap(); 180 | region 181 | .assign_advice( 182 | || "s_sum", 183 | config.0.s_sum, 184 | 0, 185 | || Value::known(Fp::one()), 186 | ) 187 | .unwrap(); 188 | 189 | // If a or b is None then we will see the error early. 190 | if self.a.is_some() || self.b.is_some() { 191 | // load private 192 | let a = self.a.unwrap(); 193 | let b = self.b.unwrap(); 194 | 195 | region 196 | .assign_advice(|| "a", config.0.a, 0, || Value::known(a)) 197 | .unwrap(); 198 | region 199 | .assign_advice(|| "b", config.0.b, 0, || Value::known(b)) 200 | .unwrap(); 201 | region 202 | .assign_advice( 203 | || "d", 204 | config.0.d, 205 | 0, 206 | || Value::known(Fp::zero()), 207 | ) 208 | .unwrap(); 209 | 210 | region 211 | .assign_advice( 212 | || "fill for the mock prover", 213 | config.0.flag, 214 | 0, 215 | || Value::known(Fp::zero()), 216 | ) 217 | .unwrap(); 218 | } 219 | 220 | region 221 | .assign_advice_from_instance( 222 | || "res/c", 223 | config.1, 224 | 0, 225 | config.0.c, 226 | 0, 227 | ) 228 | .unwrap(); 229 | 230 | region 231 | .assign_advice_from_instance( 232 | || "flag", 233 | config.1, 234 | 1, 235 | config.0.flag, 236 | 1, 237 | ) 238 | .unwrap(); 239 | 240 | Ok(()) 241 | }, 242 | ) 243 | .unwrap(); 244 | Ok(()) 245 | } 246 | } 247 | 248 | #[cfg(test)] 249 | mod tests { 250 | use super::*; 251 | use crate::test_utils::*; 252 | use halo2_proofs::dev::MockProver; 253 | use proptest::prelude::*; 254 | 255 | prop_compose! { 256 | fn valid_values(word_bits: u32) 257 | (a in 0..2u64.pow(word_bits), b in 0..2u64.pow(word_bits)) 258 | -> (u64, u64, u64, bool) { 259 | let c = a + b; 260 | let max = 2u64.pow(word_bits); 261 | let (c, flag) = if c >= max { 262 | (c - max, true) 263 | } else { 264 | (c, false) 265 | }; 266 | 267 | (a, b, c, flag) 268 | } 269 | } 270 | 271 | fn sum( 272 | v: Vec<(u64, u64, u64, bool)>, 273 | ) -> Vec<(SumCircuit, Vec>)> { 274 | v.into_iter() 275 | .map(|(a, b, c, flag)| { 276 | ( 277 | SumCircuit { 278 | a: Some(a.into()), 279 | b: Some(b.into()), 280 | }, 281 | vec![vec![c.into(), flag.into()]], 282 | ) 283 | }) 284 | .collect() 285 | } 286 | 287 | proptest! { 288 | /// proptest does not support testing const generics. 289 | #[test] 290 | fn all_8_bit_words_mock_prover_test((a, b, c, flag) in valid_values(8)) { 291 | mock_prover_test::<8>(a, b, c, flag) 292 | } 293 | } 294 | 295 | proptest! { 296 | #![proptest_config(ProptestConfig { 297 | cases: 20, .. ProptestConfig::default() 298 | })] 299 | 300 | #[test] 301 | fn all_8_bit_words_test(s in prop::collection::vec(valid_values(8), 10)) { 302 | gen_proofs_and_verify::<8, _>(sum::<8>(s)) 303 | } 304 | 305 | #[test] 306 | fn all_16_bit_words_mock_prover_test((a, b, c, flag) in valid_values(16)) { 307 | mock_prover_test::<16>(a, b, c, flag) 308 | } 309 | 310 | #[test] 311 | fn all_16_bit_words_test(s in prop::collection::vec(valid_values(16), 10)) { 312 | gen_proofs_and_verify::<16, _>(sum::<16>(s)) 313 | } 314 | 315 | #[test] 316 | fn all_8_bit_words_test_bad_proof(a in 0..2u64.pow(8), b in 0..2u64.pow(8), c in 0..2u64.pow(8), flag: bool) { 317 | let overflow_correct = flag && (a + b).checked_sub(2u64.pow(8)).map(|s| c == s).unwrap_or(false); 318 | prop_assume!((c != a + b)); 319 | prop_assume!(!overflow_correct); 320 | let circuit = SumCircuit:: {a: Some(a.into()), b: Some(b.into())}; 321 | gen_proofs_and_verify_should_fail::<8, _>(circuit, vec![c.into(), flag.into()]) 322 | } 323 | } 324 | 325 | proptest! { 326 | #![proptest_config(ProptestConfig { 327 | cases: 10, .. ProptestConfig::default() 328 | })] 329 | 330 | #[test] 331 | fn all_24_bit_words_mock_prover_test((a, b, c, flag) in valid_values(24)) { 332 | mock_prover_test::<24>(a, b, c, flag) 333 | } 334 | 335 | #[test] 336 | fn all_24_bit_words_test(s in prop::collection::vec(valid_values(24), 10)) { 337 | gen_proofs_and_verify::<24, _>(sum::<24>(s)) 338 | } 339 | } 340 | 341 | // It's used in the proptests 342 | fn mock_prover_test(a: u64, b: u64, c: u64, flag: bool) { 343 | let k = 1 + WORD_BITS / 2; 344 | let circuit: SumCircuit = SumCircuit { 345 | a: Some(Fp::from(a)), 346 | b: Some(Fp::from(b)), 347 | }; 348 | 349 | let prover = 350 | MockProver::run(k, &circuit, vec![vec![c.into(), flag.into()]]).unwrap(); 351 | assert_eq!(prover.verify(), Ok(())); 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /src/circuits/tables/even_bits.rs: -------------------------------------------------------------------------------- 1 | use std::{marker::PhantomData, ops::Deref}; 2 | 3 | use halo2_proofs::{ 4 | arithmetic::FieldExt, 5 | circuit::{Chip, Layouter, Region, Value}, 6 | plonk::{ 7 | Advice, Column, ConstraintSystem, Constraints, Error, Expression, Selector, 8 | TableColumn, VirtualCells, 9 | }, 10 | poly::Rotation, 11 | }; 12 | 13 | /// A newtype of a field element containing only bits 14 | /// that were in the even position of the decomposed element. 15 | /// All odd bits will be zero. 16 | #[derive(Clone, Copy, Debug)] 17 | pub struct EvenBits(pub W); 18 | 19 | impl Deref for EvenBits { 20 | type Target = W; 21 | 22 | fn deref(&self) -> &Self::Target { 23 | &self.0 24 | } 25 | } 26 | 27 | /// A newtype of a field element containing only bits 28 | /// that were in the odd position of the decomposed element. 29 | /// All odd bits will be right shifted by 1 into even positions. 30 | /// All odd bits will be zero. 31 | #[derive(Clone, Copy, Debug)] 32 | pub struct OddBits(pub W); 33 | 34 | impl Deref for OddBits { 35 | type Target = W; 36 | 37 | fn deref(&self) -> &Self::Target { 38 | &self.0 39 | } 40 | } 41 | 42 | pub trait HasEvenBits { 43 | fn even_bits(&self) -> EvenBitsTable; 44 | } 45 | 46 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 47 | pub struct EvenBitsTable(TableColumn); 48 | 49 | impl EvenBitsTable { 50 | pub fn configure(meta: &mut ConstraintSystem) -> Self { 51 | Self(meta.lookup_table_column()) 52 | } 53 | 54 | // Allocates all even bits in a a table for the word size WORD_BITS. 55 | // `2^(WORD_BITS/2)` rows of the constraint system. 56 | pub fn alloc_table( 57 | self, 58 | layouter: &mut impl Layouter, 59 | ) -> Result<(), Error> { 60 | layouter.assign_table( 61 | || "even bits", 62 | |mut table| { 63 | for i in 0..2usize.pow(WORD_BITS / 2) { 64 | table.assign_cell( 65 | || format!("even_bits row {}", i), 66 | self.0, 67 | i, 68 | || Value::known(F::from(even_bits_at(i) as u64)), 69 | )?; 70 | } 71 | Ok(()) 72 | }, 73 | ) 74 | } 75 | } 76 | 77 | /// Chip state is stored in a config struct. This is generated by the chip 78 | /// during configuration, and then stored inside the chip. 79 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 80 | pub struct EvenBitsConfig { 81 | pub word: Column, 82 | pub even: Column, 83 | pub odd: Column, 84 | pub even_bits: EvenBitsTable, 85 | 86 | pub s_table: Selector, 87 | } 88 | 89 | impl EvenBitsConfig { 90 | fn new( 91 | meta: &mut impl ConstraintSys>, 92 | word: Column, 93 | _s_even_bits: &[Column], 94 | // A complex selector denoting the extent in rows of the table to decompse. 95 | s_table: Selector, 96 | even_bits: EvenBitsTable, 97 | ) -> Self { 98 | let even = meta.new_column(); 99 | let odd = meta.new_column(); 100 | Self { 101 | word, 102 | even, 103 | odd, 104 | even_bits, 105 | s_table, 106 | } 107 | } 108 | 109 | // `s_even_bits` contains the advice selectors that enable this decomposition. 110 | // The sum of `s_even_bits` must be 0 or 1. 111 | // 112 | // `s_table` denotes the maxium extent of the Exe table. 113 | // 114 | // The decomposition is enforced when `s_table * (s_even_bits[0] + s_even_bits[1] + ..)` is 1. 115 | pub fn configure( 116 | meta: &mut impl ConstraintSys>, 117 | word: Column, 118 | s_even_bits: &[Column], 119 | // A complex selector denoting the extent in rows of the table to decompse. 120 | s_table: Selector, 121 | even_bits: EvenBitsTable, 122 | ) -> Self { 123 | let conf @ Self { 124 | word, 125 | even, 126 | odd, 127 | even_bits, 128 | s_table, 129 | } = Self::new(meta, word, s_even_bits, s_table, even_bits); 130 | 131 | let meta = meta.cs(); 132 | 133 | let s_even_bits = |meta: &mut VirtualCells| -> Expression { 134 | let s_table = meta.query_selector(s_table); 135 | s_even_bits 136 | .iter() 137 | .map(|c| meta.query_advice(*c, Rotation::cur())) 138 | .fold(None, |e, c| { 139 | // We sum advice selectors, since only one of them can be enabled on each row. 140 | e.map(|e| Some(e + c.clone())).unwrap_or(Some(c)) 141 | }) 142 | .map(|e| s_table.clone() * (e)) 143 | .unwrap_or(s_table) 144 | }; 145 | 146 | meta.create_gate("decompose", |meta| { 147 | let s_even_bits = s_even_bits(meta); 148 | let lhs = meta.query_advice(even, Rotation::cur()); 149 | let rhs = meta.query_advice(odd, Rotation::cur()); 150 | let out = meta.query_advice(word, Rotation::cur()); 151 | 152 | Constraints::with_selector( 153 | s_even_bits, 154 | [(lhs + Expression::Constant(F::from(2)) * rhs - out)], 155 | ) 156 | }); 157 | 158 | let _ = meta.lookup(|meta| { 159 | let s_even_bits = s_even_bits(meta); 160 | let e = meta.query_advice(even, Rotation::cur()); 161 | 162 | vec![(s_even_bits * e, even_bits.0)] 163 | }); 164 | 165 | let _ = meta.lookup(|meta| { 166 | let s_even_bits = s_even_bits(meta); 167 | let o = meta.query_advice(odd, Rotation::cur()); 168 | 169 | vec![(s_even_bits * o, even_bits.0)] 170 | }); 171 | 172 | conf 173 | } 174 | 175 | /// Assign the word's even_bits, and the word's odd bits shifted into even positions. 176 | pub fn assign_decompose( 177 | &self, 178 | region: &mut Region<'_, F>, 179 | word: F, 180 | offset: usize, 181 | ) -> (EvenBits, OddBits) { 182 | let (e, o) = decompose(word); 183 | let _ = region 184 | .assign_advice(|| "even bits", self.even, offset, || Value::known(e.0)) 185 | .map(EvenBits) 186 | .unwrap(); 187 | 188 | let _ = region 189 | .assign_advice(|| "odd bits", self.odd, offset, || Value::known(o.0)) 190 | .map(OddBits) 191 | .unwrap(); 192 | (e, o) 193 | } 194 | } 195 | 196 | #[derive(Clone, Debug)] 197 | pub struct EvenBitsChip { 198 | config: EvenBitsConfig, 199 | _marker: PhantomData, 200 | } 201 | 202 | impl EvenBitsChip { 203 | pub fn construct(config: >::Config) -> Self { 204 | Self { 205 | config, 206 | _marker: PhantomData, 207 | } 208 | } 209 | } 210 | 211 | fn even_bits_at(mut i: usize) -> usize { 212 | let mut r = 0; 213 | let mut c = 0; 214 | 215 | while i != 0 { 216 | let lower_bit = i % 2; 217 | r += lower_bit * 4usize.pow(c); 218 | i >>= 1; 219 | c += 1; 220 | } 221 | 222 | r 223 | } 224 | 225 | #[test] 226 | fn even_bits_at_test() { 227 | assert_eq!(0b0, even_bits_at(0)); 228 | assert_eq!(0b1, even_bits_at(1)); 229 | assert_eq!(0b100, even_bits_at(2)); 230 | assert_eq!(0b101, even_bits_at(3)); 231 | } 232 | 233 | impl Chip for EvenBitsChip { 234 | type Config = EvenBitsConfig; 235 | type Loaded = (); 236 | 237 | fn config(&self) -> &Self::Config { 238 | &self.config 239 | } 240 | 241 | fn loaded(&self) -> &Self::Loaded { 242 | &() 243 | } 244 | } 245 | 246 | fn decompose(word: F) -> (EvenBits, OddBits) { 247 | // FIXME re-enable assertion 248 | // assert!(word <= F::from_u128(u128::MAX)); 249 | 250 | let mut even_only = word.to_repr(); 251 | even_only.as_mut().iter_mut().for_each(|bits| { 252 | *bits &= 0b01010101; 253 | }); 254 | 255 | let mut odd_only = word.to_repr(); 256 | odd_only.as_mut().iter_mut().for_each(|bits| { 257 | *bits &= 0b10101010; 258 | }); 259 | 260 | let even_only = EvenBits(F::from_repr(even_only).unwrap()); 261 | let odd_only = F::from_repr(odd_only).unwrap(); 262 | let odds_in_even = OddBits(F::from_u128(odd_only.get_lower_128() >> 1)); 263 | (even_only, odds_in_even) 264 | } 265 | 266 | #[test] 267 | fn decompose_test_even_odd() { 268 | use halo2_proofs::pasta::Fp; 269 | let odds = 0xAAAA; 270 | let evens = 0x5555; 271 | let (e, o) = decompose(Fp::from_u128(odds)); 272 | assert_eq!(e.get_lower_128(), 0); 273 | assert_eq!(o.get_lower_128(), odds >> 1); 274 | let (e, o) = decompose(Fp::from_u128(evens)); 275 | assert_eq!(e.get_lower_128(), evens); 276 | assert_eq!(o.get_lower_128(), 0); 277 | } 278 | 279 | use proptest::prelude::*; 280 | 281 | use crate::assign::ConstraintSys; 282 | 283 | proptest! { 284 | #[test] 285 | fn decompose_test(a in 0..u128::MAX) { 286 | use halo2_proofs::pasta::Fp; 287 | let a = Fp::from_u128(a); 288 | decompose(a); 289 | } 290 | 291 | #[test] 292 | fn fp_u128_test(n in 0..u128::MAX) { 293 | use halo2_proofs::pasta::Fp; 294 | let a = Fp::from_u128(n); 295 | let b = a.get_lower_128(); 296 | assert_eq!(b, n) 297 | } 298 | } 299 | 300 | #[cfg(test)] 301 | mod mem_test { 302 | use halo2_proofs::pasta::Fp; 303 | use halo2_proofs::{ 304 | circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit, 305 | }; 306 | 307 | use super::*; 308 | 309 | // TODO take a look at how they test in src/ecc/chip/mul.rs 310 | // That looks useful. 311 | 312 | #[derive(Default)] 313 | pub struct EvenBitsTestCircuit { 314 | pub input: Option, 315 | } 316 | 317 | impl Circuit 318 | for EvenBitsTestCircuit 319 | { 320 | // Since we are using a single chip for everything, we can just reuse its config. 321 | type Config = EvenBitsConfig; 322 | type FloorPlanner = SimpleFloorPlanner; 323 | 324 | fn without_witnesses(&self) -> Self { 325 | Self::default() 326 | } 327 | 328 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 329 | let word = meta.advice_column(); 330 | let s_table = meta.complex_selector(); 331 | let even_bits = EvenBitsTable::configure(meta); 332 | 333 | EvenBitsConfig::::configure( 334 | meta, 335 | word, 336 | &[], 337 | s_table, 338 | even_bits, 339 | ) 340 | } 341 | 342 | fn synthesize( 343 | &self, 344 | config: Self::Config, 345 | mut layouter: impl Layouter, 346 | ) -> Result<(), Error> { 347 | let field_chip = EvenBitsChip::::construct(config); 348 | field_chip 349 | .config 350 | .even_bits 351 | .alloc_table(&mut layouter.namespace(|| "alloc table"))?; 352 | 353 | layouter 354 | .assign_region( 355 | || "decompose", 356 | |mut region| { 357 | config.s_table.enable(&mut region, 0)?; 358 | if let Some(word) = self.input { 359 | region 360 | .assign_advice( 361 | || "word", 362 | config.word, 363 | 0, 364 | || Value::known(word), 365 | ) 366 | .unwrap(); 367 | config.assign_decompose(&mut region, word, 0); 368 | }; 369 | Ok(()) 370 | }, 371 | ) 372 | .unwrap(); 373 | 374 | Ok(()) 375 | } 376 | } 377 | 378 | #[allow(unused)] 379 | fn mock_prover_test(a: u64) { 380 | let k = 1 + WORD_BITS / 2; 381 | let circuit = EvenBitsTestCircuit:: { 382 | input: Some(Fp::from(a)), 383 | }; 384 | 385 | // Given the correct public input, our circuit will verify. 386 | let prover = MockProver::run(k, &circuit, vec![]).unwrap(); 387 | assert_eq!(prover.verify(), Ok(())); 388 | } 389 | 390 | proptest! { 391 | // The case number was picked to run all tests in about 60 seconds on my machine. 392 | #![proptest_config(ProptestConfig { 393 | cases: 20, .. ProptestConfig::default() 394 | })] 395 | 396 | #[test] 397 | fn all_24_bit_words_mock_prover_test(a in 0..2u64.pow(24)) { 398 | mock_prover_test::<24>(a) 399 | } 400 | 401 | #[test] 402 | #[should_panic] 403 | fn all_24_bit_words_test_bad_proof(a in 2u64.pow(24)..) { 404 | mock_prover_test::<24>(a) 405 | } 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /src/instructions.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use crate::trace::{truncate, ImmediateOrRegName, RegName, Word}; 4 | 5 | pub mod opcode; 6 | 7 | /// Docs for a variant are on each variant's struct, 8 | /// They can also be found on page 9 of the TinyRAM Architecture Specification v2.000. 9 | #[derive(Debug, Clone, Copy)] 10 | pub enum Instruction { 11 | And(And), 12 | Or(Or), 13 | Xor(Xor), 14 | Not(Not), 15 | Add(Add), 16 | Sub(Sub), 17 | Mull(Mull), 18 | UMulh(UMulh), 19 | SMulh(SMulh), 20 | UDiv(UDiv), 21 | UMod(UMod), 22 | Shl(Shl), 23 | Shr(Shr), 24 | /// compare equal 25 | Cmpe(Cmpe), 26 | /// compare above, unsigned 27 | Cmpa(Cmpa), 28 | /// compare above or equal, unsigned 29 | Cmpae(Cmpae), 30 | // compare greater signed 31 | Cmpg(Cmpg), 32 | /// compare greater or equal, signed 33 | Cmpge(Cmpge), 34 | Mov(Mov), 35 | CMov(CMov), 36 | Jmp(Jmp), 37 | CJmp(CJmp), 38 | CnJmp(CnJmp), 39 | StoreW(StoreW), 40 | LoadW(LoadW), 41 | Answer(Answer), 42 | } 43 | 44 | impl Instruction { 45 | pub fn name(&self) -> &str { 46 | match self { 47 | Instruction::And(_) => "And", 48 | Instruction::LoadW(_) => "Load.w", 49 | Instruction::StoreW(_) => "Store.w", 50 | Instruction::Answer(_) => "Answer", 51 | Instruction::Or(_) => "Or", 52 | Instruction::Xor(_) => "Xor", 53 | Instruction::Not(_) => "Not", 54 | Instruction::Add(_) => "Add", 55 | Instruction::Sub(_) => "Sub", 56 | Instruction::Mull(_) => "Mull", 57 | Instruction::UMulh(_) => "UMulh", 58 | Instruction::SMulh(_) => "SMulh", 59 | Instruction::UDiv(_) => "Udiv", 60 | Instruction::UMod(_) => "UMod", 61 | Instruction::Shl(_) => "Shl", 62 | Instruction::Shr(_) => "Shr", 63 | Instruction::Cmpe(_) => "Cmpe", 64 | Instruction::Cmpa(_) => "Cmpa", 65 | Instruction::Cmpae(_) => "Cmpae", 66 | Instruction::Cmpg(_) => "Cmpg", 67 | Instruction::Cmpge(_) => "Cmpge", 68 | Instruction::Mov(_) => "Mov", 69 | Instruction::CMov(_) => "Cmov", 70 | Instruction::Jmp(_) => "Jmp", 71 | Instruction::CJmp(_) => "CJmp", 72 | Instruction::CnJmp(_) => "CnJmp", 73 | } 74 | } 75 | 76 | /// See TinyRAM 2.0 spec (page 16) 77 | /// The op code is the first field (`#1` in the table) of the binary instruction encoding. 78 | pub fn opcode(&self) -> u128 { 79 | match self { 80 | Instruction::And(_) => 0b00000, 81 | Instruction::Or(_) => 0b00001, 82 | Instruction::Xor(_) => 0b00010, 83 | Instruction::Not(_) => 0b00011, 84 | Instruction::Add(_) => 0b00100, 85 | Instruction::Sub(_) => 0b00101, 86 | Instruction::Mull(_) => 0b00110, 87 | Instruction::UMulh(_) => 0b00111, 88 | Instruction::SMulh(_) => 0b01000, 89 | Instruction::UDiv(_) => 0b01001, 90 | Instruction::UMod(_) => 0b01010, 91 | Instruction::Shl(_) => 0b01011, 92 | Instruction::Shr(_) => 0b01100, 93 | Instruction::Cmpe(_) => 0b01101, 94 | Instruction::Cmpa(_) => 0b01110, 95 | Instruction::Cmpae(_) => 0b01111, 96 | Instruction::Cmpg(_) => 0b10000, 97 | Instruction::Cmpge(_) => 0b10001, 98 | Instruction::Mov(_) => 0b10010, 99 | Instruction::CMov(_) => 0b10011, 100 | Instruction::Jmp(_) => 0b10100, 101 | Instruction::CJmp(_) => 0b10101, 102 | Instruction::CnJmp(_) => 0b10110, 103 | Instruction::StoreW(_) => 0b11100, 104 | Instruction::LoadW(_) => 0b11101, 105 | Instruction::Answer(_) => 0b11111, 106 | } 107 | } 108 | 109 | pub fn is_store(&self) -> bool { 110 | matches!(self, Instruction::StoreW(_)) 111 | } 112 | 113 | pub fn is_load(&self) -> bool { 114 | matches!(self, Instruction::LoadW(_)) 115 | } 116 | } 117 | 118 | impl Instruction { 119 | pub fn ri(&self) -> Option { 120 | match self { 121 | Instruction::And(And { ri, .. }) 122 | | Instruction::LoadW(LoadW { ri, .. }) 123 | | Instruction::StoreW(StoreW { ri, .. }) 124 | | Instruction::Or(Or { ri, .. }) 125 | | Instruction::Xor(Xor { ri, .. }) 126 | | Instruction::Not(Not { ri, .. }) 127 | | Instruction::Add(Add { ri, .. }) 128 | | Instruction::Sub(Sub { ri, .. }) 129 | | Instruction::Mull(Mull { ri, .. }) 130 | | Instruction::UMulh(UMulh { ri, .. }) 131 | | Instruction::SMulh(SMulh { ri, .. }) 132 | | Instruction::UDiv(UDiv { ri, .. }) 133 | | Instruction::UMod(UMod { ri, .. }) 134 | | Instruction::Shl(Shl { ri, .. }) 135 | | Instruction::Shr(Shr { ri, .. }) 136 | | Instruction::Cmpe(Cmpe { ri, .. }) 137 | | Instruction::Cmpa(Cmpa { ri, .. }) 138 | | Instruction::Cmpae(Cmpae { ri, .. }) 139 | | Instruction::Cmpg(Cmpg { ri, .. }) 140 | | Instruction::Cmpge(Cmpge { ri, .. }) 141 | | Instruction::Mov(Mov { ri, .. }) 142 | | Instruction::CMov(CMov { ri, .. }) => Some(*ri), 143 | Instruction::Answer(_) 144 | | Instruction::Jmp(_) 145 | | Instruction::CJmp(_) 146 | | Instruction::CnJmp(_) => None, 147 | } 148 | } 149 | 150 | pub fn rj(&self) -> Option { 151 | match self { 152 | Instruction::And(And { rj, .. }) 153 | | Instruction::Or(Or { rj, .. }) 154 | | Instruction::Xor(Xor { rj, .. }) 155 | | Instruction::Add(Add { rj, .. }) 156 | | Instruction::Sub(Sub { rj, .. }) 157 | | Instruction::Mull(Mull { rj, .. }) 158 | | Instruction::UMulh(UMulh { rj, .. }) 159 | | Instruction::SMulh(SMulh { rj, .. }) 160 | | Instruction::UDiv(UDiv { rj, .. }) 161 | | Instruction::UMod(UMod { rj, .. }) 162 | | Instruction::Shl(Shl { rj, .. }) 163 | | Instruction::Shr(Shr { rj, .. }) => Some(*rj), 164 | Instruction::Answer(_) 165 | | Instruction::Cmpe(_) 166 | | Instruction::Cmpa(_) 167 | | Instruction::Cmpae(_) 168 | | Instruction::Cmpg(_) 169 | | Instruction::Cmpge(_) 170 | | Instruction::Mov(_) 171 | | Instruction::CMov(_) 172 | | Instruction::Jmp(_) 173 | | Instruction::CJmp(_) 174 | | Instruction::CnJmp(_) 175 | | Instruction::Not(_) 176 | | Instruction::StoreW(_) 177 | | Instruction::LoadW(_) => None, 178 | } 179 | } 180 | 181 | pub fn a(&self) -> ImmediateOrRegName { 182 | match self { 183 | Instruction::And(And { a, .. }) 184 | | Instruction::LoadW(LoadW { a, .. }) 185 | | Instruction::StoreW(StoreW { a, .. }) 186 | | Instruction::Or(Or { a, .. }) 187 | | Instruction::Xor(Xor { a, .. }) 188 | | Instruction::Not(Not { a, .. }) 189 | | Instruction::Add(Add { a, .. }) 190 | | Instruction::Sub(Sub { a, .. }) 191 | | Instruction::Mull(Mull { a, .. }) 192 | | Instruction::UMulh(UMulh { a, .. }) 193 | | Instruction::SMulh(SMulh { a, .. }) 194 | | Instruction::UDiv(UDiv { a, .. }) 195 | | Instruction::UMod(UMod { a, .. }) 196 | | Instruction::Shl(Shl { a, .. }) 197 | | Instruction::Shr(Shr { a, .. }) 198 | | Instruction::Cmpe(Cmpe { a, .. }) 199 | | Instruction::Cmpa(Cmpa { a, .. }) 200 | | Instruction::Cmpae(Cmpae { a, .. }) 201 | | Instruction::Cmpg(Cmpg { a, .. }) 202 | | Instruction::Cmpge(Cmpge { a, .. }) 203 | | Instruction::Mov(Mov { a, .. }) 204 | | Instruction::CMov(CMov { a, .. }) 205 | | Instruction::Jmp(Jmp { a, .. }) 206 | | Instruction::CJmp(CJmp { a, .. }) 207 | | Instruction::CnJmp(CnJmp { a, .. }) 208 | | Instruction::Answer(Answer { a }) => *a, 209 | } 210 | } 211 | } 212 | 213 | impl Display for Instruction { 214 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 215 | write!(f, "{} ", self.name())?; 216 | match self { 217 | Instruction::And(And { ri, rj, a }) 218 | | Instruction::Or(Or { ri, rj, a }) 219 | | Instruction::Xor(Xor { ri, rj, a }) 220 | | Instruction::Add(Add { ri, rj, a }) 221 | | Instruction::Sub(Sub { ri, rj, a }) 222 | | Instruction::Mull(Mull { ri, rj, a }) 223 | | Instruction::UMulh(UMulh { ri, rj, a }) 224 | | Instruction::SMulh(SMulh { ri, rj, a }) 225 | | Instruction::UDiv(UDiv { ri, rj, a }) 226 | | Instruction::UMod(UMod { ri, rj, a }) 227 | | Instruction::Shl(Shl { ri, rj, a }) 228 | | Instruction::Shr(Shr { ri, rj, a }) => { 229 | write!(f, "r{} ", ri.0)?; 230 | write!(f, "r{} ", rj.0)?; 231 | write!(f, "{}", a) 232 | } 233 | Instruction::Not(Not { ri, a }) 234 | | Instruction::Cmpe(Cmpe { ri, a }) 235 | | Instruction::Cmpa(Cmpa { ri, a }) 236 | | Instruction::Cmpae(Cmpae { ri, a }) 237 | | Instruction::Cmpg(Cmpg { ri, a }) 238 | | Instruction::Cmpge(Cmpge { ri, a }) 239 | | Instruction::Mov(Mov { ri, a }) 240 | | Instruction::CMov(CMov { ri, a }) 241 | | Instruction::LoadW(LoadW { ri, a }) 242 | | Instruction::StoreW(StoreW { ri, a }) => { 243 | write!(f, "r{} ", ri.0)?; 244 | write!(f, "{}", a) 245 | } 246 | 247 | Instruction::Jmp(Jmp { a }) 248 | | Instruction::CJmp(CJmp { a }) 249 | | Instruction::CnJmp(CnJmp { a }) 250 | | Instruction::Answer(Answer { a }) => write!(f, "{}", a), 251 | } 252 | } 253 | } 254 | 255 | /// compute bitwise AND of `[rj]` and `[A]` and store result in ri 256 | #[derive(Debug, Clone, Copy)] 257 | pub struct And { 258 | pub ri: R, 259 | pub rj: R, 260 | pub a: A, 261 | } 262 | 263 | /// compute bitwise OR of `[rj]` and `[A]` and store result in ri 264 | #[derive(Debug, Clone, Copy)] 265 | pub struct Or { 266 | pub ri: R, 267 | pub rj: R, 268 | pub a: A, 269 | } 270 | 271 | /// compute bitwise OR of `[rj]` and `[A]` and store result in ri 272 | #[derive(Debug, Clone, Copy)] 273 | pub struct Xor { 274 | pub ri: R, 275 | pub rj: R, 276 | pub a: A, 277 | } 278 | 279 | #[derive(Debug, Clone, Copy)] 280 | pub struct Not { 281 | pub ri: R, 282 | pub a: A, 283 | } 284 | 285 | #[derive(Debug, Clone, Copy)] 286 | pub struct Add { 287 | pub ri: R, 288 | pub rj: R, 289 | pub a: A, 290 | } 291 | 292 | #[derive(Debug, Clone, Copy)] 293 | pub struct Sub { 294 | pub ri: R, 295 | pub rj: R, 296 | pub a: A, 297 | } 298 | 299 | #[derive(Debug, Clone, Copy)] 300 | pub struct Mull { 301 | pub ri: R, 302 | pub rj: R, 303 | pub a: A, 304 | } 305 | 306 | #[derive(Debug, Clone, Copy)] 307 | pub struct UMulh { 308 | pub ri: R, 309 | pub rj: R, 310 | pub a: A, 311 | } 312 | 313 | #[derive(Debug, Clone, Copy)] 314 | pub struct SMulh { 315 | pub ri: R, 316 | pub rj: R, 317 | pub a: A, 318 | } 319 | 320 | impl SMulh { 321 | /// Returns the `(upper bits, lower bits, flag)` of signed multiplication. 322 | /// The flag is set if `a * b` is out of range of the word size. 323 | pub fn eval(a: Word, b: Word) -> (Word, Word, bool) { 324 | let a = a.into_signed(WORD_BITS) as i128; 325 | let b = b.into_signed(WORD_BITS) as i128; 326 | 327 | let f = a * b; 328 | 329 | let lower = truncate::(f as u128); 330 | let upper = truncate::((f >> WORD_BITS) as u128); 331 | 332 | let m = 2i128.pow(WORD_BITS - 1); 333 | let flag = f >= m || f < -m; 334 | 335 | assert_eq!(f.is_negative(), upper.into_signed(WORD_BITS).is_negative()); 336 | (upper, lower, flag) 337 | } 338 | } 339 | 340 | #[derive(Debug, Clone, Copy)] 341 | pub struct UDiv { 342 | pub ri: R, 343 | pub rj: R, 344 | pub a: A, 345 | } 346 | 347 | #[derive(Debug, Clone, Copy)] 348 | pub struct UMod { 349 | pub ri: R, 350 | pub rj: R, 351 | pub a: A, 352 | } 353 | 354 | #[derive(Debug, Clone, Copy)] 355 | pub struct Shl { 356 | pub ri: R, 357 | pub rj: R, 358 | pub a: A, 359 | } 360 | 361 | #[derive(Debug, Clone, Copy)] 362 | pub struct Shr { 363 | pub ri: R, 364 | pub rj: R, 365 | pub a: A, 366 | } 367 | 368 | #[derive(Debug, Clone, Copy)] 369 | pub struct Cmpe { 370 | pub ri: R, 371 | pub a: A, 372 | } 373 | 374 | #[derive(Debug, Clone, Copy)] 375 | pub struct Cmpa { 376 | pub ri: R, 377 | pub a: A, 378 | } 379 | 380 | #[derive(Debug, Clone, Copy)] 381 | pub struct Cmpae { 382 | pub ri: R, 383 | pub a: A, 384 | } 385 | 386 | #[derive(Debug, Clone, Copy)] 387 | pub struct Cmpg { 388 | pub ri: R, 389 | pub a: A, 390 | } 391 | 392 | #[derive(Debug, Clone, Copy)] 393 | pub struct Cmpge { 394 | pub ri: R, 395 | pub a: A, 396 | } 397 | 398 | #[derive(Debug, Clone, Copy)] 399 | pub struct Mov { 400 | pub ri: R, 401 | pub a: A, 402 | } 403 | 404 | #[derive(Debug, Clone, Copy)] 405 | pub struct CMov { 406 | pub ri: R, 407 | pub a: A, 408 | } 409 | 410 | /// stall or halt (and the return value is `[A]u` ) 411 | #[derive(Debug, Clone, Copy)] 412 | pub struct Jmp { 413 | pub a: A, 414 | } 415 | 416 | /// stall or halt (and the return value is `[A]u` ) 417 | #[derive(Debug, Clone, Copy)] 418 | pub struct CJmp { 419 | pub a: A, 420 | } 421 | 422 | /// stall or halt (and the return value is `[A]u` ) 423 | #[derive(Debug, Clone, Copy)] 424 | pub struct CnJmp { 425 | pub a: A, 426 | } 427 | 428 | /// Store into ri the word in memory that is aligned to the `[A]w-th` byte. 429 | #[derive(Debug, Clone, Copy)] 430 | pub struct LoadW { 431 | pub ri: R, 432 | pub a: A, 433 | } 434 | 435 | /// store `[ri]` at the word in memory that is aligned to the `[A]w-th` byte 436 | #[derive(Debug, Clone, Copy)] 437 | pub struct StoreW { 438 | pub ri: R, 439 | pub a: A, 440 | } 441 | 442 | /// stall or halt (and the return value is `[A]u` ) 443 | #[derive(Debug, Clone, Copy)] 444 | pub struct Answer { 445 | pub a: A, 446 | } 447 | 448 | /// Conveniance aliases for Instructions type with unit type Arguments. 449 | pub mod unit { 450 | 451 | pub type And = super::And<(), ()>; 452 | 453 | pub type Or = super::Or<(), ()>; 454 | 455 | pub type Xor = super::Xor<(), ()>; 456 | 457 | pub type Not = super::Not<(), ()>; 458 | 459 | pub type Add = super::Add<(), ()>; 460 | 461 | pub type Sub = super::Sub<(), ()>; 462 | 463 | pub type Mull = super::Mull<(), ()>; 464 | 465 | pub type UMulh = super::UMulh<(), ()>; 466 | 467 | pub type SMulh = super::SMulh<(), ()>; 468 | 469 | pub type UDiv = super::UDiv<(), ()>; 470 | 471 | pub type UMod = super::UMod<(), ()>; 472 | 473 | pub type Shl = super::Shl<(), ()>; 474 | 475 | pub type Shr = super::Shr<(), ()>; 476 | 477 | pub type Cmpe = super::Cmpe<(), ()>; 478 | 479 | pub type Cmpa = super::Cmpa<(), ()>; 480 | 481 | pub type Cmpae = super::Cmpae<(), ()>; 482 | 483 | pub type Cmpg = super::Cmpg<(), ()>; 484 | 485 | pub type Cmpge = super::Cmpge<(), ()>; 486 | 487 | pub type Mov = super::Mov<(), ()>; 488 | 489 | pub type CMov = super::CMov<(), ()>; 490 | 491 | pub type Jmp = super::Jmp<()>; 492 | 493 | pub type CJmp = super::CJmp<()>; 494 | 495 | pub type CnJmp = super::CnJmp<()>; 496 | 497 | pub type LoadW = super::LoadW<(), ()>; 498 | 499 | pub type StoreW = super::StoreW<(), ()>; 500 | 501 | pub type Answer = super::Answer<()>; 502 | } 503 | -------------------------------------------------------------------------------- /src/circuits/tables/mem.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use halo2_proofs::{ 4 | arithmetic::FieldExt, 5 | circuit::{Chip, Layouter, Region, Value}, 6 | plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Expression}, 7 | poly::Rotation, 8 | }; 9 | 10 | use crate::trace::{Access, Mem}; 11 | 12 | use super::{ 13 | even_bits::{EvenBitsConfig, EvenBitsTable}, 14 | TableSelector, 15 | }; 16 | 17 | pub struct MemChip { 18 | config: MemConfig, 19 | _marker: PhantomData, 20 | } 21 | 22 | #[derive(Debug, Clone, Copy)] 23 | pub struct MemConfig { 24 | extent: TableSelector, 25 | address: Column, 26 | time: Column, 27 | 28 | init: Column, 29 | store: Column, 30 | load: Column, 31 | 32 | // The most recently stored value. 33 | value: Column, 34 | 35 | /// Defined as `min(address_next - address - 1, 0)`; 36 | /// The evenbits decomposition enforces that the difference is positive. 37 | address_increment: EvenBitsConfig, 38 | /// The difference between time and time on the next row. 39 | time_increment: EvenBitsConfig, 40 | 41 | even_bits: EvenBitsTable, 42 | } 43 | 44 | impl Chip for MemChip { 45 | type Config = MemConfig; 46 | type Loaded = (); 47 | 48 | fn config(&self) -> &Self::Config { 49 | &self.config 50 | } 51 | 52 | fn loaded(&self) -> &Self::Loaded { 53 | &() 54 | } 55 | } 56 | 57 | impl MemChip { 58 | /// Currently this is the same as `ExeConfig::TABLE_LEN`. 59 | /// Programs will usually be much smaller than traces, 60 | /// so we should reduce this to allow stacking. 61 | const TABLE_LEN: usize = 2usize.pow(WORD_BITS / 2); 62 | 63 | pub fn construct( 64 | layouter: &mut impl Layouter, 65 | config: MemConfig, 66 | ) -> Self { 67 | config.even_bits.alloc_table(layouter).unwrap(); 68 | 69 | Self { 70 | config, 71 | _marker: PhantomData, 72 | } 73 | } 74 | 75 | pub fn configure( 76 | meta: &mut ConstraintSystem, 77 | even_bits: EvenBitsTable, 78 | ) -> MemConfig { 79 | let extent = TableSelector::configure(meta); 80 | 81 | let address = meta.advice_column(); 82 | let time = meta.advice_column(); 83 | let init = meta.advice_column(); 84 | let store = meta.advice_column(); 85 | let load = meta.advice_column(); 86 | 87 | let value = meta.advice_column(); 88 | 89 | let address_increment = meta.advice_column(); 90 | let address_increment = EvenBitsConfig::configure( 91 | meta, 92 | address_increment, 93 | &[extent.s_trace], 94 | extent.s_table, 95 | even_bits, 96 | ); 97 | 98 | let time_increment = meta.advice_column(); 99 | let time_increment = EvenBitsConfig::configure( 100 | meta, 101 | time_increment, 102 | &[extent.s_trace], 103 | extent.s_table, 104 | even_bits, 105 | ); 106 | 107 | meta.create_gate("Mem", |meta| { 108 | let one = Expression::Constant(F::one()); 109 | 110 | // We query s_trace next since all our constraints are on the current and next row. 111 | // The last line of the trace is constrained by the prior line. 112 | let s_trace_next = extent.query_trace_next(meta); 113 | 114 | let address_next = meta.query_advice(address, Rotation::next()); 115 | let address = meta.query_advice(address, Rotation::cur()); 116 | 117 | let address_increment = 118 | meta.query_advice(address_increment.word, Rotation::next()); 119 | 120 | let time_next = meta.query_advice(time, Rotation::next()); 121 | let time = meta.query_advice(time, Rotation::cur()); 122 | 123 | let time_increment = 124 | meta.query_advice(time_increment.word, Rotation::next()); 125 | 126 | let same_cycle = address_next.clone() - address.clone(); 127 | let end_cycle = address_next - address - one - address_increment; 128 | 129 | let time_sorted = time_next - time - time_increment; 130 | 131 | let init_next = meta.query_advice(init, Rotation::next()); 132 | 133 | let load = meta.query_advice(load, Rotation::cur()); 134 | 135 | let value_next = meta.query_advice(value, Rotation::next()); 136 | let value = meta.query_advice(value, Rotation::cur()); 137 | 138 | // The table is sorted by address and then time. 139 | // `init = 0` or store can be the start of a access trace over an address. 140 | Constraints::with_selector( 141 | s_trace_next, 142 | [ 143 | // The next row may belong to a different access trace on a larger address. 144 | end_cycle.clone() * same_cycle, 145 | // The next row may occur at a latter time if it's in a different access trace. 146 | end_cycle.clone() * time_sorted, 147 | // The next row may be an initial value from the tape 148 | // if it's the start of an access trace. 149 | end_cycle * init_next, 150 | // The value cannot change on loads 151 | load * (value_next - value), 152 | ], 153 | ) 154 | }); 155 | 156 | MemConfig { 157 | extent, 158 | address, 159 | time, 160 | init, 161 | store, 162 | load, 163 | value, 164 | address_increment, 165 | time_increment, 166 | even_bits, 167 | } 168 | } 169 | 170 | pub fn assign_mem( 171 | &self, 172 | mut layouter: impl Layouter, 173 | mem: &Mem, 174 | ) -> Result<(), Error> { 175 | let config = self.config(); 176 | 177 | layouter.assign_region( 178 | || "Mem", 179 | |mut region: Region<'_, F>| { 180 | config 181 | .extent 182 | .alloc_table_rows(&mut region, Self::TABLE_LEN)?; 183 | 184 | let mut prior_address = 0; 185 | let mut offset = 0; 186 | 187 | eprintln!("mem: {:?}", mem); 188 | for (address_val, accesses) in mem.accesses.iter() { 189 | // TODO don't generate init for sequences init, store.. 190 | let mut recent_stored_value: u64 = 191 | accesses.initial_value().unwrap().0.into(); 192 | let mut prior_time = 0; 193 | 194 | for access in accesses.0.iter() { 195 | if let Access::Store { value, .. } = access { 196 | recent_stored_value = value.0.into(); 197 | } 198 | 199 | let MemConfig { 200 | extent, 201 | address, 202 | time: _, 203 | init, 204 | store, 205 | load, 206 | value, 207 | address_increment, 208 | time_increment: _, 209 | even_bits: _, 210 | } = *config; 211 | 212 | extent.enable_row_of_table(&mut region, offset, true)?; 213 | 214 | let time_val: u64 = 215 | access.time().map(|a| a.0).unwrap_or(0).into(); 216 | 217 | self.assign_time(&mut region, offset, prior_time, time_val)?; 218 | prior_time = time_val; 219 | 220 | assert_eq!(address_val, &access.address()); 221 | region.assign_advice( 222 | || format!("address: {}", address_val.0), 223 | address, 224 | offset, 225 | || Value::known(F::from(u64::from(address_val.0))), 226 | )?; 227 | 228 | let address_increment_val = 229 | (address_val.0 - prior_address).saturating_sub(1); 230 | let address_increment_val_f = 231 | F::from(u64::from(address_increment_val)); 232 | region.assign_advice( 233 | || { 234 | format!( 235 | "address_increment: {}", 236 | address_increment_val 237 | ) 238 | }, 239 | address_increment.word, 240 | offset, 241 | || Value::known(address_increment_val_f), 242 | )?; 243 | address_increment.assign_decompose( 244 | &mut region, 245 | address_increment_val_f, 246 | offset, 247 | ); 248 | 249 | let is_load = access.is_load(); 250 | region.assign_advice( 251 | || format!("load: {}", is_load), 252 | load, 253 | offset, 254 | || Value::known(F::from(is_load)), 255 | )?; 256 | 257 | let is_store = access.is_store(); 258 | region.assign_advice( 259 | || format!("store: {}", is_store), 260 | store, 261 | offset, 262 | || Value::known(F::from(is_store)), 263 | )?; 264 | 265 | let is_init = access.is_init(); 266 | region.assign_advice( 267 | || format!("init: {}", is_init), 268 | init, 269 | offset, 270 | || Value::known(F::from(is_init)), 271 | )?; 272 | 273 | region.assign_advice( 274 | || format!("value: {}", recent_stored_value), 275 | value, 276 | offset, 277 | || Value::known(F::from(recent_stored_value)), 278 | )?; 279 | 280 | offset += 1; 281 | } 282 | prior_address = address_val.0; 283 | } 284 | 285 | Ok(()) 286 | }, 287 | ) 288 | } 289 | 290 | pub fn assign_time( 291 | &self, 292 | region: &mut Region, 293 | offset: usize, 294 | prior_time: u64, 295 | time_val: u64, 296 | ) -> Result<(), Error> { 297 | region.assign_advice( 298 | || format!("time: {}", time_val), 299 | self.config.time, 300 | offset, 301 | || Value::known(F::from(time_val)), 302 | )?; 303 | 304 | let time_increment_val = time_val.saturating_sub(prior_time); 305 | let time_increment_val_f = F::from(time_increment_val); 306 | region.assign_advice( 307 | || format!("time_increment: {}", time_increment_val), 308 | self.config.time_increment.word, 309 | offset, 310 | || Value::known(time_increment_val_f), 311 | )?; 312 | self.config.time_increment.assign_decompose( 313 | region, 314 | time_increment_val_f, 315 | offset, 316 | ); 317 | 318 | Ok(()) 319 | } 320 | } 321 | 322 | #[cfg(test)] 323 | mod mem_tests { 324 | use super::*; 325 | use halo2_proofs::circuit::SimpleFloorPlanner; 326 | use halo2_proofs::pasta::Fp; 327 | use halo2_proofs::{dev::MockProver, plonk::Circuit}; 328 | use proptest::{prop_compose, proptest}; 329 | 330 | use crate::{instructions::*, test_utils::gen_proofs_and_verify, trace::*}; 331 | 332 | #[derive(Default, Debug, Clone)] 333 | pub struct TinyRamCircuit { 334 | pub trace: Option>, 335 | } 336 | 337 | impl< 338 | F: halo2_proofs::arithmetic::FieldExt, 339 | const WORD_BITS: u32, 340 | const REG_COUNT: usize, 341 | > Circuit for TinyRamCircuit 342 | { 343 | type Config = MemConfig; 344 | type FloorPlanner = SimpleFloorPlanner; 345 | 346 | fn without_witnesses(&self) -> Self { 347 | Self::default() 348 | } 349 | 350 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 351 | let even_bits = EvenBitsTable::configure(meta); 352 | MemChip::::configure(meta, even_bits) 353 | } 354 | 355 | fn synthesize( 356 | &self, 357 | config: Self::Config, 358 | mut layouter: impl Layouter, 359 | ) -> Result<(), Error> { 360 | let exe_chip = MemChip::::construct(&mut layouter, config); 361 | 362 | if let Some(trace) = &self.trace { 363 | exe_chip.assign_mem(layouter, &trace.mem)?; 364 | } 365 | Ok(()) 366 | } 367 | } 368 | 369 | fn load_and_answer( 370 | a: u32, 371 | b: u32, 372 | ) -> Trace { 373 | let prog = Program(vec![ 374 | Instruction::LoadW(LoadW { 375 | ri: RegName(0), 376 | a: ImmediateOrRegName::Immediate(Word(b)), 377 | }), 378 | Instruction::And(And { 379 | ri: RegName(1), 380 | rj: RegName(0), 381 | a: ImmediateOrRegName::Immediate(Word(a)), 382 | }), 383 | Instruction::Answer(Answer { 384 | a: ImmediateOrRegName::Immediate(Word(1)), 385 | }), 386 | ]); 387 | 388 | let trace = prog.eval::(Mem::new(&[Word(0b1)], &[])); 389 | assert_eq!(trace.ans.0, 1); 390 | trace 391 | } 392 | 393 | fn mock_prover_test( 394 | trace: Trace, 395 | ) { 396 | let k = 2 + WORD_BITS / 2; 397 | let circuit = TinyRamCircuit:: { trace: Some(trace) }; 398 | 399 | // Given the correct public input, our circuit will verify. 400 | let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); 401 | prover.assert_satisfied(); 402 | } 403 | 404 | #[test] 405 | fn two_programs() { 406 | let ans = { 407 | let ans = Program(vec![Instruction::Answer(Answer { 408 | a: ImmediateOrRegName::Immediate(Word(1)), 409 | })]); 410 | let ans = ans.eval::<8, 8>(Mem::new(&[], &[])); 411 | assert_eq!(ans.ans.0, 1); 412 | ans 413 | }; 414 | 415 | let l_and_ans = load_and_answer::<8, 8>(1, 2); 416 | assert_eq!(l_and_ans.ans.0, 1); 417 | 418 | gen_proofs_and_verify::<8, _>(vec![ 419 | (TinyRamCircuit { trace: Some(ans) }, vec![]), 420 | ( 421 | TinyRamCircuit { 422 | trace: Some(l_and_ans), 423 | }, 424 | vec![], 425 | ), 426 | ]); 427 | } 428 | 429 | prop_compose! { 430 | fn signed_word(word_bits: u32) 431 | (a in -(2i32.pow(word_bits - 1))..2i32.pow(word_bits - 1) - 1) 432 | -> Word { 433 | Word::try_from_signed(a, word_bits).unwrap() 434 | } 435 | } 436 | 437 | proptest! { 438 | #[test] 439 | fn load_and_answer_mock_prover(a in 0..2u32.pow(8), b in 0..2u32.pow(8)) { 440 | mock_prover_test::<8, 8>(load_and_answer(a, b)) 441 | } 442 | } 443 | } 444 | -------------------------------------------------------------------------------- /src/circuits/mod.rs: -------------------------------------------------------------------------------- 1 | use halo2_proofs::{ 2 | circuit::{Layouter, SimpleFloorPlanner}, 3 | plonk::{Circuit, ConstraintSystem, Error}, 4 | }; 5 | 6 | use crate::trace::Trace; 7 | 8 | use self::tables::{ 9 | exe::{ExeChip, ExeConfig}, 10 | prog::ProgConfig, 11 | }; 12 | 13 | pub mod changed; 14 | pub mod flag1; 15 | pub mod flag2; 16 | pub mod flag3; 17 | pub mod flag4; 18 | pub mod logic; 19 | pub mod modulo; 20 | pub mod prod; 21 | pub mod shift; 22 | pub mod sprod; 23 | pub mod ssum; 24 | pub mod sum; 25 | pub mod tables; 26 | 27 | #[derive(Default, Debug, Clone)] 28 | pub struct TinyRamCircuit { 29 | pub trace: Option>, 30 | } 31 | 32 | impl< 33 | F: halo2_proofs::arithmetic::FieldExt, 34 | const WORD_BITS: u32, 35 | const REG_COUNT: usize, 36 | > Circuit for TinyRamCircuit 37 | { 38 | type Config = ( 39 | ProgConfig, 40 | ExeConfig, 41 | ); 42 | type FloorPlanner = SimpleFloorPlanner; 43 | 44 | fn without_witnesses(&self) -> Self { 45 | Self::default() 46 | } 47 | 48 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 49 | let prog_config = ProgConfig::configure(meta); 50 | let exe_config = ExeChip::::configure(meta); 51 | 52 | prog_config.lookup( 53 | meta, 54 | exe_config.extent.s_trace, 55 | exe_config.pc, 56 | exe_config.program_line, 57 | ); 58 | 59 | (prog_config, exe_config) 60 | } 61 | 62 | fn synthesize( 63 | &self, 64 | config: Self::Config, 65 | mut layouter: impl Layouter, 66 | ) -> Result<(), Error> { 67 | let exe_chip = 68 | ExeChip::::construct(&mut layouter, config.1); 69 | config.0.assign_prog(&mut layouter)?; 70 | 71 | if let Some(trace) = &self.trace { 72 | exe_chip.assign_trace(layouter.namespace(|| "Trace"), trace)?; 73 | } 74 | Ok(()) 75 | } 76 | } 77 | 78 | #[cfg(test)] 79 | mod tiny_ram_circuit_tests { 80 | use halo2_proofs::dev::MockProver; 81 | use halo2_proofs::pasta::Fp; 82 | use proptest::{prop_compose, proptest}; 83 | 84 | use crate::{instructions::*, test_utils::gen_proofs_and_verify, trace::*}; 85 | 86 | use super::{tables::prog::program_instance, TinyRamCircuit}; 87 | 88 | fn load_and_answer( 89 | a: u32, 90 | b: u32, 91 | ) -> Trace { 92 | let prog = Program(vec![ 93 | Instruction::LoadW(LoadW { 94 | ri: RegName(0), 95 | a: ImmediateOrRegName::Immediate(Word(b)), 96 | }), 97 | Instruction::And(And { 98 | ri: RegName(1), 99 | rj: RegName(0), 100 | a: ImmediateOrRegName::Immediate(Word(a)), 101 | }), 102 | Instruction::Answer(Answer { 103 | a: ImmediateOrRegName::Immediate(Word(1)), 104 | }), 105 | ]); 106 | 107 | let trace = prog.eval::(Mem::new(&[Word(0b1)], &[])); 108 | assert_eq!(trace.ans.0, 1); 109 | trace 110 | } 111 | fn mov_ins_answer( 112 | ins: Instruction, 113 | b: u32, 114 | ) -> Trace { 115 | let prog = Program(vec![ 116 | Instruction::Mov(Mov { 117 | ri: RegName(0), 118 | a: ImmediateOrRegName::Immediate(Word(b)), 119 | }), 120 | ins, 121 | Instruction::Answer(Answer { 122 | a: ImmediateOrRegName::Immediate(Word(1)), 123 | }), 124 | ]); 125 | 126 | let trace = prog.eval::(Mem::new(&[Word(0b1)], &[])); 127 | assert_eq!(trace.ans.0, 1); 128 | trace 129 | } 130 | 131 | fn mov_and_answer( 132 | a: u32, 133 | b: u32, 134 | ) -> Trace { 135 | mov_ins_answer( 136 | Instruction::And(And { 137 | ri: RegName(1), 138 | rj: RegName(0), 139 | a: ImmediateOrRegName::Immediate(Word(a)), 140 | }), 141 | b, 142 | ) 143 | } 144 | 145 | fn mov_xor_answer( 146 | a: u32, 147 | b: u32, 148 | ) -> Trace { 149 | mov_ins_answer( 150 | Instruction::Xor(Xor { 151 | ri: RegName(1), 152 | rj: RegName(0), 153 | a: ImmediateOrRegName::Immediate(Word(a)), 154 | }), 155 | b, 156 | ) 157 | } 158 | 159 | fn mov_or_answer( 160 | a: u32, 161 | b: u32, 162 | ) -> Trace { 163 | mov_ins_answer( 164 | Instruction::Or(Or { 165 | ri: RegName(1), 166 | rj: RegName(0), 167 | a: ImmediateOrRegName::Immediate(Word(a)), 168 | }), 169 | b, 170 | ) 171 | } 172 | 173 | fn mov_add_answer( 174 | a: u32, 175 | b: u32, 176 | ) -> Trace { 177 | mov_ins_answer( 178 | Instruction::Add(Add { 179 | ri: RegName(1), 180 | rj: RegName(0), 181 | a: ImmediateOrRegName::Immediate(Word(a)), 182 | }), 183 | b, 184 | ) 185 | } 186 | 187 | fn mov_sub_answer( 188 | a: u32, 189 | b: u32, 190 | ) -> Trace { 191 | mov_ins_answer( 192 | Instruction::Sub(Sub { 193 | ri: RegName(1), 194 | rj: RegName(0), 195 | a: ImmediateOrRegName::Immediate(Word(a)), 196 | }), 197 | b, 198 | ) 199 | } 200 | 201 | fn mov_cmpe_answer( 202 | a: u32, 203 | b: u32, 204 | ) -> Trace { 205 | mov_ins_answer( 206 | Instruction::Cmpe(Cmpe { 207 | ri: RegName(0), 208 | a: ImmediateOrRegName::Immediate(Word(a)), 209 | }), 210 | b, 211 | ) 212 | } 213 | 214 | fn mov_cmpa_answer( 215 | a: u32, 216 | b: u32, 217 | ) -> Trace { 218 | mov_ins_answer( 219 | Instruction::Cmpa(Cmpa { 220 | ri: RegName(0), 221 | a: ImmediateOrRegName::Immediate(Word(a)), 222 | }), 223 | b, 224 | ) 225 | } 226 | 227 | fn mov_cmpae_answer( 228 | a: u32, 229 | b: u32, 230 | ) -> Trace { 231 | mov_ins_answer( 232 | Instruction::Cmpae(Cmpae { 233 | ri: RegName(0), 234 | a: ImmediateOrRegName::Immediate(Word(a)), 235 | }), 236 | b, 237 | ) 238 | } 239 | 240 | fn mov_cmpg_answer( 241 | a: Word, 242 | b: Word, 243 | ) -> Trace { 244 | mov_ins_answer( 245 | Instruction::Cmpg(Cmpg { 246 | ri: RegName(0), 247 | a: ImmediateOrRegName::Immediate(a), 248 | }), 249 | b.0, 250 | ) 251 | } 252 | 253 | fn mov_cmpge_answer( 254 | a: Word, 255 | b: Word, 256 | ) -> Trace { 257 | mov_ins_answer( 258 | Instruction::Cmpge(Cmpge { 259 | ri: RegName(0), 260 | a: ImmediateOrRegName::Immediate(a), 261 | }), 262 | b.0, 263 | ) 264 | } 265 | 266 | fn mov_mull_answer( 267 | a: Word, 268 | b: Word, 269 | ) -> Trace { 270 | mov_ins_answer( 271 | Instruction::Mull(Mull { 272 | ri: RegName(1), 273 | rj: RegName(0), 274 | a: ImmediateOrRegName::Immediate(a), 275 | }), 276 | b.0, 277 | ) 278 | } 279 | 280 | fn mov_umulh_answer( 281 | a: Word, 282 | b: Word, 283 | ) -> Trace { 284 | mov_ins_answer( 285 | Instruction::Mull(Mull { 286 | ri: RegName(1), 287 | rj: RegName(0), 288 | a: ImmediateOrRegName::Immediate(a), 289 | }), 290 | b.0, 291 | ) 292 | } 293 | 294 | fn mov_smullh_answer( 295 | a: Word, 296 | b: Word, 297 | ) -> Trace { 298 | mov_ins_answer( 299 | Instruction::SMulh(SMulh { 300 | ri: RegName(1), 301 | rj: RegName(0), 302 | a: ImmediateOrRegName::Immediate(a), 303 | }), 304 | b.0, 305 | ) 306 | } 307 | 308 | fn mov_umod_answer( 309 | a: Word, 310 | b: Word, 311 | ) -> Trace { 312 | mov_ins_answer( 313 | Instruction::UMod(UMod { 314 | ri: RegName(1), 315 | rj: RegName(0), 316 | a: ImmediateOrRegName::Immediate(a), 317 | }), 318 | b.0, 319 | ) 320 | } 321 | 322 | fn mov_udiv_answer( 323 | a: Word, 324 | b: Word, 325 | ) -> Trace { 326 | mov_ins_answer( 327 | Instruction::UDiv(UDiv { 328 | ri: RegName(1), 329 | rj: RegName(0), 330 | a: ImmediateOrRegName::Immediate(a), 331 | }), 332 | b.0, 333 | ) 334 | } 335 | 336 | fn mov_shr_answer( 337 | a: Word, 338 | b: Word, 339 | ) -> Trace { 340 | mov_ins_answer( 341 | Instruction::Shr(Shr { 342 | ri: RegName(1), 343 | rj: RegName(0), 344 | a: ImmediateOrRegName::Immediate(a), 345 | }), 346 | b.0, 347 | ) 348 | } 349 | 350 | fn mov_shl_answer( 351 | a: Word, 352 | b: Word, 353 | ) -> Trace { 354 | mov_ins_answer( 355 | Instruction::Shl(Shl { 356 | ri: RegName(1), 357 | rj: RegName(0), 358 | a: ImmediateOrRegName::Immediate(a), 359 | }), 360 | b.0, 361 | ) 362 | } 363 | 364 | fn mock_prover_test( 365 | trace: Trace, 366 | ) { 367 | let k = 2 + WORD_BITS / 2; 368 | let program_instance = 369 | program_instance::(trace.prog.clone()); 370 | let circuit = TinyRamCircuit:: { trace: Some(trace) }; 371 | 372 | // Given the correct public input, our circuit will verify. 373 | let prover = MockProver::::run(k, &circuit, program_instance).unwrap(); 374 | prover.assert_satisfied(); 375 | } 376 | 377 | #[test] 378 | fn two_programs() { 379 | let ans = { 380 | let ans = Program(vec![Instruction::Answer(Answer { 381 | a: ImmediateOrRegName::Immediate(Word(1)), 382 | })]); 383 | let ans = ans.eval::<8, 8>(Mem::new(&[], &[])); 384 | assert_eq!(ans.ans.0, 1); 385 | ans 386 | }; 387 | 388 | let l_and_ans = load_and_answer::<8, 8>(1, 2); 389 | assert_eq!(l_and_ans.ans.0, 1); 390 | 391 | gen_proofs_and_verify::<8, _>(vec![ 392 | ( 393 | TinyRamCircuit { 394 | trace: Some(ans.clone()), 395 | }, 396 | program_instance::<8, 8, Fp>(ans.prog), 397 | ), 398 | ( 399 | TinyRamCircuit { 400 | trace: Some(l_and_ans.clone()), 401 | }, 402 | program_instance::<8, 8, Fp>(l_and_ans.prog), 403 | ), 404 | ]); 405 | } 406 | 407 | prop_compose! { 408 | fn signed_word(word_bits: u32) 409 | (a in -(2i32.pow(word_bits - 1))..2i32.pow(word_bits - 1) - 1) 410 | -> Word { 411 | Word::try_from_signed(a, word_bits).unwrap() 412 | } 413 | } 414 | 415 | proptest! { 416 | #[test] 417 | fn load_and_answer_mock_prover(a in 0..2u32.pow(8), b in 0..2u32.pow(8)) { 418 | mock_prover_test::<8, 8>(load_and_answer(a, b)) 419 | } 420 | 421 | #[test] 422 | fn mov_and_answer_mock_prover(a in 0..2u32.pow(8), b in 0..2u32.pow(8)) { 423 | mock_prover_test::<8, 8>(mov_and_answer(a, b)) 424 | } 425 | 426 | #[test] 427 | fn mov_xor_answer_mock_prover(a in 0..2u32.pow(8), b in 0..2u32.pow(8)) { 428 | mock_prover_test::<8, 8>(mov_xor_answer(a, b)) 429 | } 430 | 431 | #[test] 432 | fn mov_or_answer_mock_prover(a in 0..2u32.pow(8), b in 0..2u32.pow(8)) { 433 | mock_prover_test::<8, 8>(mov_or_answer(a, b)) 434 | } 435 | 436 | #[test] 437 | fn mov_add_answer_mock_prover(a in 0..2u32.pow(8), b in 0..2u32.pow(8)) { 438 | mock_prover_test::<8, 8>(mov_add_answer(a, b)) 439 | } 440 | 441 | #[test] 442 | fn mov_sub_answer_mock_prover(a in 0..2u32.pow(8), b in 0..2u32.pow(8)) { 443 | mock_prover_test::<8, 8>(mov_sub_answer(a, b)) 444 | } 445 | 446 | #[test] 447 | fn mov_mull_answer_mock_prover(a in signed_word(8), b in signed_word(8)) { 448 | mock_prover_test::<8, 8>(mov_mull_answer(a, b)) 449 | } 450 | 451 | #[test] 452 | fn mov_umulh_answer_mock_prover(a in signed_word(8), b in signed_word(8)) { 453 | mock_prover_test::<8, 8>(mov_umulh_answer(a, b)) 454 | } 455 | 456 | #[test] 457 | fn mov_umod_answer_mock_prover(a in signed_word(8), b in signed_word(8)) { 458 | mock_prover_test::<8, 8>(mov_umod_answer(a, b)) 459 | } 460 | 461 | #[test] 462 | fn mov_udiv_answer_mock_prover(a in signed_word(8), b in signed_word(8)) { 463 | mock_prover_test::<8, 8>(mov_udiv_answer(a, b)) 464 | } 465 | 466 | #[test] 467 | fn mov_cmpe_answer_mock_prover(a in 0..2u32.pow(8), b in 0..2u32.pow(8)) { 468 | mock_prover_test::<8, 8>(mov_cmpe_answer(a, b)) 469 | } 470 | 471 | #[test] 472 | fn mov_cmpa_answer_mock_prover(a in 0..2u32.pow(8), b in 0..2u32.pow(8)) { 473 | mock_prover_test::<8, 8>(mov_cmpa_answer(a, b)) 474 | } 475 | 476 | #[test] 477 | fn mov_cmpae_answer_mock_prover(a in 0..2u32.pow(8), b in 0..2u32.pow(8)) { 478 | mock_prover_test::<8, 8>(mov_cmpae_answer(a, b)) 479 | } 480 | 481 | #[test] 482 | fn mov_cmpg_answer_mock_prover(a in signed_word(8), b in signed_word(8)) { 483 | mock_prover_test::<8, 8>(mov_cmpg_answer(a, b)) 484 | } 485 | 486 | #[test] 487 | fn mov_cmpge_answer_mock_prover(a in signed_word(8), b in signed_word(8)) { 488 | mock_prover_test::<8, 8>(mov_cmpge_answer(a, b)) 489 | } 490 | 491 | #[test] 492 | fn mov_smullh_answer_mock_prover(a in signed_word(8), b in signed_word(8)) { 493 | mock_prover_test::<8, 8>(mov_smullh_answer(a, b)) 494 | } 495 | 496 | #[test] 497 | fn mov_shl_answer_mock_prover(a in 0..8u32, b in 0..2u32.pow(8)) { 498 | mock_prover_test::<8, 8>(mov_shl_answer(Word(a), Word(b))) 499 | } 500 | 501 | #[test] 502 | fn mov_shr_answer_mock_prover(a in 0..8u32, b in 0..2u32.pow(8)) { 503 | mock_prover_test::<8, 8>(mov_shr_answer(Word(a), Word(b))) 504 | } 505 | } 506 | } 507 | -------------------------------------------------------------------------------- /src/circuits/shift.rs: -------------------------------------------------------------------------------- 1 | use crate::assign::ConstraintSys; 2 | use crate::trace::truncate; 3 | use halo2_proofs::circuit::{Region, Value}; 4 | use halo2_proofs::pasta::Fp; 5 | use halo2_proofs::plonk::Constraints; 6 | use halo2_proofs::{ 7 | arithmetic::FieldExt, 8 | circuit::{Chip, Layouter, SimpleFloorPlanner}, 9 | plonk::{ 10 | Advice, Circuit, Column, ConstraintSystem, Error, Expression, Instance, 11 | Selector, 12 | }, 13 | poly::Rotation, 14 | }; 15 | use std::marker::PhantomData; 16 | 17 | use super::tables::even_bits::{EvenBitsConfig, EvenBitsTable}; 18 | use super::tables::pow::PowTable; 19 | 20 | #[derive(Debug, Clone, Copy)] 21 | pub struct ShiftConfig { 22 | /// A Selector denoting the extent of the exe table. 23 | s_table: Selector, 24 | /// An advice columns that acts as a selector for shift's gate. 25 | /// [`Out.shift`](crate::circuits::tables::aux::Out) 26 | s_shift: Column, 27 | 28 | a: Column, 29 | b: Column, 30 | c: Column, 31 | d: Column, 32 | 33 | a_shift: Column, 34 | a_power: Column, 35 | r_decompose: EvenBitsConfig, 36 | 37 | pow: PowTable, 38 | } 39 | 40 | impl ShiftConfig { 41 | #[allow(clippy::complexity)] 42 | pub fn new( 43 | s_table: Selector, 44 | s_shift: Column, 45 | 46 | a: Column, 47 | b: Column, 48 | c: Column, 49 | d: Column, 50 | 51 | a_shift: Column, 52 | a_power: Column, 53 | r_decompose: EvenBitsConfig, 54 | 55 | pow: PowTable, 56 | ) -> Self { 57 | Self { 58 | s_table, 59 | s_shift, 60 | a, 61 | b, 62 | c, 63 | d, 64 | a_shift, 65 | a_power, 66 | r_decompose, 67 | pow, 68 | } 69 | } 70 | 71 | #[allow(clippy::complexity)] 72 | pub fn configure( 73 | meta: &mut impl ConstraintSys>, 74 | s_table: Selector, 75 | s_shift: Column, 76 | 77 | a: Column, 78 | b: Column, 79 | c: Column, 80 | d: Column, 81 | 82 | a_shift: Column, 83 | a_power: Column, 84 | r_decompose: EvenBitsConfig, 85 | 86 | pow: PowTable, 87 | ) -> Self { 88 | let conf @ Self { 89 | s_table, 90 | s_shift, 91 | a, 92 | b, 93 | c, 94 | d, 95 | a_shift, 96 | a_power, 97 | r_decompose, 98 | pow, 99 | } = Self::new( 100 | s_table, 101 | s_shift, 102 | a, 103 | b, 104 | c, 105 | d, 106 | a_shift, 107 | a_power, 108 | r_decompose, 109 | pow, 110 | ); 111 | 112 | meta.cs().create_gate("shift", |meta| { 113 | let one = Expression::Constant(F::one()); 114 | let two = Expression::Constant(F::from(2)); 115 | let word_bits = Expression::Constant(F::from(WORD_BITS as u64)); 116 | let max = Expression::Constant(F::from(2u64.pow(WORD_BITS))); 117 | 118 | let s_table = meta.query_selector(s_table); 119 | let s_shift = meta.query_advice(s_shift, Rotation::cur()); 120 | 121 | let a = meta.query_advice(a, Rotation::cur()); 122 | let b = meta.query_advice(b, Rotation::cur()); 123 | let c = meta.query_advice(c, Rotation::cur()); 124 | let d = meta.query_advice(d, Rotation::cur()); 125 | 126 | let r_o = meta.query_advice(r_decompose.odd, Rotation::cur()); 127 | let r_e = meta.query_advice(r_decompose.even, Rotation::cur()); 128 | 129 | let a_shift = meta.query_advice(a_shift, Rotation::cur()); 130 | let a_power = meta.query_advice(a_power, Rotation::cur()); 131 | 132 | Constraints::with_selector( 133 | s_table * s_shift, 134 | [ 135 | a_shift.clone() * (a_shift.clone() - one.clone()), 136 | (one - a_shift) * (word_bits - a - (two * r_o) - r_e), 137 | a_power * b - d - max * c, 138 | ], 139 | ) 140 | }); 141 | 142 | let _ = meta.cs().lookup(|meta| { 143 | let one = Expression::Constant(F::one()); 144 | let word_bits = Expression::Constant(F::from(WORD_BITS as u64)); 145 | 146 | // let s_table = meta.query_selector(s_table); 147 | let s_shift = meta.query_advice(s_shift, Rotation::cur()); 148 | let a = meta.query_advice(a, Rotation::cur()); 149 | 150 | let a_shift = meta.query_advice(a_shift, Rotation::cur()); 151 | let a_power = meta.query_advice(a_power, Rotation::cur()); 152 | 153 | vec![ 154 | ( 155 | s_shift.clone() * (a.clone() + a_shift * (word_bits - a)), 156 | pow.values, 157 | ), 158 | // (a_power, pow.powers), 159 | // When s_shift not set, we lookup (value: 0, value: 1) 160 | ( 161 | (s_shift.clone() * a_power) + one.clone() - (s_shift * one), 162 | pow.powers, 163 | ), 164 | ] 165 | }); 166 | 167 | conf 168 | } 169 | 170 | pub fn assign_shift( 171 | &self, 172 | region: &mut Region<'_, F>, 173 | shift_bits: u32, 174 | offset: usize, 175 | ) { 176 | let a_shift = WORD_BITS < shift_bits as _; 177 | region 178 | .assign_advice( 179 | || "a_shift", 180 | self.a_shift, 181 | offset, 182 | || Value::known(F::from(a_shift)), 183 | ) 184 | .unwrap(); 185 | 186 | let a_power = if shift_bits == WORD_BITS as _ { 187 | 0 188 | } else { 189 | 2u64.pow(shift_bits as _) 190 | }; 191 | region 192 | .assign_advice( 193 | || "a_power", 194 | self.a_power, 195 | offset, 196 | || Value::known(F::from(a_power)), 197 | ) 198 | .unwrap(); 199 | 200 | let r = if shift_bits > WORD_BITS as _ { 201 | F::zero() 202 | } else { 203 | F::from((WORD_BITS - shift_bits) as u64) 204 | }; 205 | region 206 | .assign_advice(|| "r", self.r_decompose.word, offset, || Value::known(r)) 207 | .unwrap(); 208 | self.r_decompose.assign_decompose(region, r, offset); 209 | } 210 | } 211 | 212 | pub fn non_det_d(a: u128, b: u128, c: u128) -> F { 213 | F::from(2u64.pow((a) as _)) * F::from_u128(b) 214 | - (F::from(2u64.pow(WORD_BITS)) * F::from_u128(c)) 215 | } 216 | 217 | pub fn non_det_c(a: i128, b: i128, d: i128) -> F { 218 | F::from_u128( 219 | ((2i128.pow(a as _) * b - d) / 2i128.pow(WORD_BITS)) 220 | .try_into() 221 | .unwrap(), 222 | ) 223 | } 224 | 225 | impl ShiftChip { 226 | pub fn construct(config: >::Config) -> Self { 227 | Self { 228 | config, 229 | _marker: PhantomData, 230 | } 231 | } 232 | } 233 | 234 | /// The chip that will implement our instructions! Chips store their own 235 | /// config, as well as type markers if necessary. 236 | pub struct ShiftChip { 237 | config: ShiftConfig, 238 | _marker: PhantomData, 239 | } 240 | 241 | impl Chip for ShiftChip { 242 | type Config = ShiftConfig; 243 | type Loaded = (); 244 | 245 | fn config(&self) -> &Self::Config { 246 | &self.config 247 | } 248 | 249 | fn loaded(&self) -> &Self::Loaded { 250 | &() 251 | } 252 | } 253 | 254 | #[derive(Default, Debug, Clone, Copy)] 255 | pub struct ShiftCircuit { 256 | pub word: Option, 257 | pub shift_bits: Option, 258 | } 259 | 260 | impl Circuit 261 | for ShiftCircuit 262 | { 263 | type Config = (ShiftConfig, Column); 264 | type FloorPlanner = SimpleFloorPlanner; 265 | 266 | fn without_witnesses(&self) -> Self { 267 | Self::default() 268 | } 269 | 270 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 271 | let instance = meta.instance_column(); 272 | meta.enable_equality(instance); 273 | 274 | let s_table = meta.complex_selector(); 275 | let s_shift = meta.advice_column(); 276 | 277 | let a = meta.advice_column(); 278 | let b = meta.advice_column(); 279 | let c = meta.advice_column(); 280 | meta.enable_equality(c); 281 | let d = meta.advice_column(); 282 | meta.enable_equality(d); 283 | 284 | // Overflow flag 285 | let flag = meta.advice_column(); 286 | meta.enable_equality(flag); 287 | 288 | let even_bits = EvenBitsTable::configure(meta); 289 | 290 | let a_shift = meta.advice_column(); 291 | let a_power = meta.advice_column(); 292 | let r_decompose = meta.advice_column(); 293 | let r_decompose = EvenBitsConfig::configure( 294 | meta, 295 | r_decompose, 296 | &[s_shift], 297 | s_table, 298 | even_bits, 299 | ); 300 | 301 | let pow = PowTable::new(meta); 302 | 303 | ( 304 | ShiftConfig::::configure( 305 | meta, 306 | s_table, 307 | s_shift, 308 | a, 309 | b, 310 | c, 311 | d, 312 | a_shift, 313 | a_power, 314 | r_decompose, 315 | pow, 316 | ), 317 | instance, 318 | ) 319 | } 320 | 321 | fn synthesize( 322 | &self, 323 | config: Self::Config, 324 | mut layouter: impl Layouter, 325 | ) -> Result<(), Error> { 326 | config.0.pow.alloc_table(&mut layouter).unwrap(); 327 | config 328 | .0 329 | .r_decompose 330 | .even_bits 331 | .alloc_table(&mut layouter) 332 | .unwrap(); 333 | layouter 334 | .assign_region( 335 | || "shift", 336 | |mut region| { 337 | config.0.s_table.enable(&mut region, 0).unwrap(); 338 | region 339 | .assign_advice( 340 | || "s_shift", 341 | config.0.s_shift, 342 | 0, 343 | || Value::known(Fp::one()), 344 | ) 345 | .unwrap(); 346 | 347 | // If a or b is None then we will see the error early. 348 | if self.word.is_some() || self.shift_bits.is_some() { 349 | // load private 350 | let a = self.shift_bits.unwrap(); 351 | let b = self.word.unwrap(); 352 | 353 | config.0.assign_shift( 354 | &mut region, 355 | a.get_lower_128().try_into().unwrap(), 356 | 0, 357 | ); 358 | 359 | region 360 | .assign_advice(|| "a", config.0.a, 0, || Value::known(a)) 361 | .unwrap(); 362 | region 363 | .assign_advice(|| "b", config.0.b, 0, || Value::known(b)) 364 | .unwrap(); 365 | 366 | { 367 | let a = a.get_lower_128(); 368 | let b = b.get_lower_128(); 369 | 370 | if SHIFT_RIGHT { 371 | let c = truncate::( 372 | b >> self.shift_bits.unwrap().get_lower_128(), 373 | ) 374 | .0 375 | .into(); 376 | region 377 | .assign_advice( 378 | || "d", 379 | config.0.d, 380 | 0, 381 | || { 382 | Value::known(non_det_d::( 383 | a, b, c, 384 | )) 385 | }, 386 | ) 387 | .unwrap(); 388 | } else { 389 | // shift left 390 | 391 | let d: u128 = truncate::( 392 | b << self.shift_bits.unwrap().get_lower_128(), 393 | ) 394 | .0 395 | .into(); 396 | region 397 | .assign_advice( 398 | || "c", 399 | config.0.c, 400 | 0, 401 | || { 402 | Value::known(non_det_c::( 403 | a.try_into().unwrap(), 404 | b.try_into().unwrap(), 405 | d.try_into().unwrap(), 406 | )) 407 | }, 408 | ) 409 | .unwrap(); 410 | } 411 | } 412 | } 413 | 414 | region 415 | .assign_advice_from_instance( 416 | || "res", 417 | config.1, 418 | 0, 419 | if SHIFT_RIGHT { config.0.c } else { config.0.d }, 420 | 0, 421 | ) 422 | .unwrap(); 423 | 424 | Ok(()) 425 | }, 426 | ) 427 | .unwrap(); 428 | Ok(()) 429 | } 430 | } 431 | 432 | #[cfg(test)] 433 | mod tests { 434 | use super::*; 435 | use crate::test_utils::*; 436 | use halo2_proofs::dev::MockProver; 437 | use proptest::prelude::*; 438 | 439 | prop_compose! { 440 | fn valid_values(word_bits: u32, right_shift: bool) 441 | (word in 0..2u64.pow(word_bits), s_bits in 0..(word_bits)) 442 | -> (u64, u64, u64, bool) { 443 | let out = if right_shift { 444 | word >> s_bits 445 | } else { 446 | (word << s_bits) & ((2u64.pow(word_bits)) - 1) 447 | }; 448 | 449 | (s_bits as _, word, out, false) 450 | } 451 | } 452 | 453 | fn shift( 454 | v: Vec<(u64, u64, u64, bool)>, 455 | ) -> Vec<(ShiftCircuit, Vec>)> { 456 | v.into_iter() 457 | .map(|(a, b, c, flag)| { 458 | ( 459 | ShiftCircuit { 460 | word: Some(b.into()), 461 | shift_bits: Some(a.into()), 462 | }, 463 | vec![vec![c.into(), flag.into()]], 464 | ) 465 | }) 466 | .collect() 467 | } 468 | 469 | mod left { 470 | use super::*; 471 | proptest! { 472 | /// proptest does not support testing const generics. 473 | #[test] 474 | fn all_8_bit_words_mock_prover_test((a, b, c, flag) in valid_values(8, false)) { 475 | mock_prover_test::<8, false>(a, b, c, flag) 476 | } 477 | } 478 | 479 | proptest! { 480 | #![proptest_config(ProptestConfig { 481 | cases: 20, .. ProptestConfig::default() 482 | })] 483 | 484 | #[test] 485 | fn all_8_bit_words_test(s in prop::collection::vec(valid_values(8, false), 10)) { 486 | gen_proofs_and_verify::<8, _>(shift::<8, false>(s)) 487 | } 488 | 489 | #[test] 490 | fn all_16_bit_words_mock_prover_test((a, b, c, flag) in valid_values(16, false)) { 491 | mock_prover_test::<16, false>(a, b, c, flag) 492 | } 493 | 494 | #[test] 495 | fn all_16_bit_words_test(s in prop::collection::vec(valid_values(16, false), 10)) { 496 | gen_proofs_and_verify::<16, _>(shift::<16, false>(s)) 497 | } 498 | 499 | #[test] 500 | fn all_8_bit_words_test_bad_proof(word in 0..2u64.pow(8), shift_bits in 0..8u64, c in 0..2u64.pow(8), flag: bool) { 501 | prop_assume!((c != word << shift_bits)); 502 | let circuit = ShiftCircuit:: {word: Some(word.into()), shift_bits: Some(shift_bits.into())}; 503 | gen_proofs_and_verify_should_fail::<8, _>(circuit, vec![c.into(), flag.into()]) 504 | } 505 | } 506 | 507 | proptest! { 508 | // The case number was picked to run all tests in about 60 seconds on my machine. 509 | #![proptest_config(ProptestConfig { 510 | cases: 10, .. ProptestConfig::default() 511 | })] 512 | 513 | #[test] 514 | fn all_24_bit_words_mock_prover_test((a, b, c, flag) in valid_values(24, false)) { 515 | mock_prover_test::<24, false>(a, b, c, flag) 516 | } 517 | 518 | #[test] 519 | fn all_24_bit_words_test(s in prop::collection::vec(valid_values(24, false), 10)) { 520 | gen_proofs_and_verify::<24, _>(shift::<24, false>(s)) 521 | } 522 | } 523 | } 524 | 525 | mod right { 526 | use super::*; 527 | proptest! { 528 | /// proptest does not support testing const generics. 529 | #[test] 530 | fn all_8_bit_words_mock_prover_test((a, b, c, flag) in valid_values(8, true)) { 531 | mock_prover_test::<8, true>(a, b, c, flag) 532 | } 533 | } 534 | 535 | proptest! { 536 | #![proptest_config(ProptestConfig { 537 | cases: 20, .. ProptestConfig::default() 538 | })] 539 | 540 | #[test] 541 | fn all_8_bit_words_test(s in prop::collection::vec(valid_values(8, true), 10)) { 542 | gen_proofs_and_verify::<8, _>(shift::<8, true>(s)) 543 | } 544 | 545 | #[test] 546 | fn all_16_bit_words_mock_prover_test((a, b, c, flag) in valid_values(16, true)) { 547 | mock_prover_test::<16, true>(a, b, c, flag) 548 | } 549 | 550 | #[test] 551 | fn all_16_bit_words_test(s in prop::collection::vec(valid_values(16, true), 10)) { 552 | gen_proofs_and_verify::<16, _>(shift::<16, true>(s)) 553 | } 554 | 555 | #[test] 556 | fn all_8_bit_words_test_bad_proof(word in 0..2u64.pow(8), shift_bits in 0..8u64, c in 0..2u64.pow(8), flag: bool) { 557 | prop_assume!((c != word >> shift_bits)); 558 | let circuit = ShiftCircuit:: {word: Some(word.into()), shift_bits: Some(shift_bits.into())}; 559 | gen_proofs_and_verify_should_fail::<8, _>(circuit, vec![c.into(), flag.into()]) 560 | } 561 | } 562 | 563 | proptest! { 564 | // The case number was picked to run all tests in about 60 seconds on my machine. 565 | #![proptest_config(ProptestConfig { 566 | cases: 10, .. ProptestConfig::default() 567 | })] 568 | 569 | #[test] 570 | fn all_24_bit_words_mock_prover_test((a, b, c, flag) in valid_values(24, true)) { 571 | mock_prover_test::<24, true>(a, b, c, flag) 572 | } 573 | 574 | #[test] 575 | fn all_24_bit_words_test(s in prop::collection::vec(valid_values(24, true), 10)) { 576 | gen_proofs_and_verify::<24, _>(shift::<24, true>(s)) 577 | } 578 | } 579 | } 580 | 581 | // It's used in the proptests 582 | fn mock_prover_test( 583 | a: u64, 584 | b: u64, 585 | c: u64, 586 | flag: bool, 587 | ) { 588 | let k = 1 + WORD_BITS / 2; 589 | let circuit: ShiftCircuit = ShiftCircuit { 590 | word: Some(Fp::from(b)), 591 | shift_bits: Some(Fp::from(a)), 592 | }; 593 | 594 | let prover = 595 | MockProver::run(k, &circuit, vec![vec![c.into(), flag.into()]]).unwrap(); 596 | assert_eq!(prover.verify(), Ok(())); 597 | } 598 | } 599 | --------------------------------------------------------------------------------