Repository: maxgillett/giza Branch: master Commit: 5794cc93b6ec Files: 47 Total size: 143.3 KB Directory structure: gitextract_qfi_vi0c/ ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── air/ │ ├── Cargo.toml │ └── src/ │ ├── constraints.rs │ ├── frame.rs │ ├── lib.rs │ └── options.rs ├── cli/ │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── cmd/ │ │ ├── mod.rs │ │ ├── prove/ │ │ │ ├── args.rs │ │ │ ├── mod.rs │ │ │ └── prove.rs │ │ └── verify.rs │ ├── giza.rs │ └── utils.rs ├── core/ │ ├── Cargo.toml │ └── src/ │ ├── field/ │ │ ├── f252/ │ │ │ ├── mod.rs │ │ │ └── tests.rs │ │ └── mod.rs │ ├── flags/ │ │ └── mod.rs │ ├── inputs/ │ │ └── mod.rs │ ├── lib.rs │ └── word/ │ ├── helpers.rs │ └── mod.rs ├── examples/ │ ├── Cargo.toml │ └── src/ │ └── main.rs ├── program.json ├── prover/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── runner/ │ ├── Cargo.toml │ └── src/ │ ├── cairo_interop.rs │ ├── errors.rs │ ├── hints.rs │ ├── lib.rs │ ├── memory.rs │ ├── runner.rs │ └── trace.rs ├── rust-toolchain.toml └── wasm/ ├── .gitignore ├── Cargo.toml ├── index.js ├── package.json ├── src/ │ └── lib.rs └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ tmp/ target/ Cargo.lock *.sw* .DS_Store ================================================ FILE: Cargo.toml ================================================ [workspace] members = [ "air", "core", "prover", "runner", "examples", "cli", "wasm" ] ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ ## Overview Giza leverages the Winterfell library to prove and verify the execution of programs running on the Cairo VM. ## Usage instructions Giza offers two modes of usage. In the first mode, an execution trace created by an external Cairo runner is supplied to the CLI to output a proof. The provided trace consists of binary files containing the record of register and memory states visited during the run of a Cairo program. To prove execution, additional auxiliary trace values must be reconstructed, and the built-in Rust runner is used to re-execute the trace in order to compute these values. The second usage mode accepts only a Cairo program and initial register state, and uses the runner to construct all necessary trace information (including trace and memory values). Unlike the first mode, Python hint support and program input are not yet fully supported. This is not the preferred mode of interacting with Giza, and is not currently exposed through the CLI. ### Mode 1: Supply a trace to the CLI Assuming a compiled Cairo program `program.json`, the following steps can be taken to construct a proof: 1. Install the Giza CLI using nightly Rust: `cargo install --path cli` 2. Generate the partial trace using an external runner, for example: `cairo-run --program=program.json --layout=all --memory_file=memory.bin --trace_file=trace.bin`. Note that the Starkware runner may only be used for purposes that fall within its [license](https://github.com/starkware-libs/cairo-lang/blob/master/LICENSE.txt). 3. Construct the proof: `giza prove --trace=trace.bin --memory=memory.bin --program=program.json --output=output.bin` 4. Verify the proof: `giza verify --proof=output.bin` ### Mode 2: Supply a program To prove and verify the execution of the program found in `examples/src/main.rs`, one can run the following after completing step 1 from the previous section. `cargo run --release --bin giza-examples` ## Acknowledgments - The Cairo virtual machine and programming language is developed by [Starkware](https://starkware.co/). - The STARK prover and verifier is built using the [Winterfell](https://github.com/novifinancial/winterfell) project. - The current Rust runner is a fork of the implementation written by Anaïs Querol of O(1) Labs. ================================================ FILE: air/Cargo.toml ================================================ [package] name = "giza-air" version = "0.1.0" edition = "2018" [dependencies] winter-air = { package = "winter-air", git = "https://github.com/maxgillett/winterfell", rev = "0aad6a5", default-features = false } winter-utils = { package = "winter-utils", git = "https://github.com/maxgillett/winterfell", rev = "0aad6a5", default-features = false } giza_core = { package = "giza-core", path = "../core", version = "0.1", default-features = false } serde = "1.0.137" ================================================ FILE: air/src/constraints.rs ================================================ use super::{AuxEvaluationFrame, AuxTraceRandElements, MainEvaluationFrame}; use giza_core::{ range, ExtensionOf, Felt, FieldElement, FlagDecomposition, OffsetDecomposition, Range, }; pub trait EvaluationResult { fn evaluate_instr_constraints(&mut self, frame: &MainEvaluationFrame); fn evaluate_operand_constraints(&mut self, frame: &MainEvaluationFrame); fn evaluate_register_constraints(&mut self, frame: &MainEvaluationFrame); fn evaluate_opcode_constraints(&mut self, frame: &MainEvaluationFrame); fn enforce_selector(&mut self, frame: &MainEvaluationFrame); } pub trait AuxEvaluationResult> { fn evaluate_memory_constraints( &mut self, main_frame: &MainEvaluationFrame, aux_frame: &AuxEvaluationFrame, aux_rand_elements: &AuxTraceRandElements, ); fn evaluate_range_check_constraints( &mut self, main_frame: &MainEvaluationFrame, aux_frame: &AuxEvaluationFrame, aux_rand_elements: &AuxTraceRandElements, ); } /// Main constraint identifiers const INST: usize = 16; const DST_ADDR: usize = 17; const OP0_ADDR: usize = 18; const OP1_ADDR: usize = 19; const NEXT_AP: usize = 20; const NEXT_FP: usize = 21; const NEXT_PC_1: usize = 22; const NEXT_PC_2: usize = 23; const T0: usize = 24; const T1: usize = 25; const MUL_1: usize = 26; const MUL_2: usize = 27; const CALL_1: usize = 28; const CALL_2: usize = 29; const ASSERT_EQ: usize = 30; /// Aux constraint identifiers const A_M_PRIME: Range = range(0, 4); const V_M_PRIME: Range = range(4, 4); const P_M: Range = range(8, 4); const A_RC_PRIME: Range = range(12, 3); const P_RC: Range = range(15, 3); const TWO: Felt = Felt::TWO; impl> EvaluationResult for [E] { fn evaluate_instr_constraints(&mut self, frame: &MainEvaluationFrame) { let curr = frame.current(); // Bit constraints for (n, flag) in curr.flags().into_iter().enumerate() { self[n] = match n { 0..=14 => flag * (flag - Felt::ONE.into()), 15 => flag, _ => panic!("Unknown flag offset"), }; } // Instruction unpacking let b15: E = TWO.exp(15u32.into()).into(); let b16: E = TWO.exp(16u32.into()).into(); let b32: E = TWO.exp(32u32.into()).into(); let b48: E = TWO.exp(48u32.into()).into(); let a: E = curr .flags() .into_iter() .enumerate() .take(15) .fold(Felt::ZERO.into(), |acc, (n, flag)| { acc + E::from(2u128.pow(n as u32)) * flag }); self[INST] = (curr.off_dst() + b15) + b16 * (curr.off_op0() + b15) + b32 * (curr.off_op1() + b15) + b48 * a - curr.inst(); } fn evaluate_operand_constraints(&mut self, frame: &MainEvaluationFrame) { let curr = frame.current(); let ap = curr.ap(); let fp = curr.fp(); let pc = curr.pc(); let one: E = Felt::ONE.into(); self[DST_ADDR] = curr.f_dst_fp() * fp + (one - curr.f_dst_fp()) * ap + curr.off_dst() - curr.dst_addr(); self[OP0_ADDR] = curr.f_op0_fp() * fp + (one - curr.f_op0_fp()) * ap + curr.off_op0() - curr.op0_addr(); self[OP1_ADDR] = curr.f_op1_val() * pc + curr.f_op1_ap() * ap + curr.f_op1_fp() * fp + (one - curr.f_op1_val() - curr.f_op1_ap() - curr.f_op1_fp()) * curr.op0() + curr.off_op1() - curr.op1_addr(); } fn evaluate_register_constraints(&mut self, frame: &MainEvaluationFrame) { let curr = frame.current(); let next = frame.next(); let one: E = Felt::ONE.into(); // ap and fp constraints self[NEXT_AP] = curr.ap() + curr.f_ap_add() * curr.res() + curr.f_ap_one() + curr.f_opc_call() * TWO.into() - next.ap(); self[NEXT_FP] = curr.f_opc_ret() * curr.dst() + curr.f_opc_call() * (curr.ap() + TWO.into()) + (one - curr.f_opc_ret() - curr.f_opc_call()) * curr.fp() - next.fp(); // pc constraints self[NEXT_PC_1] = (curr.t1() - curr.f_pc_jnz()) * (next.pc() - (curr.pc() + curr.inst_size())); self[NEXT_PC_2] = curr.t0() * (next.pc() - (curr.pc() + curr.op1())) + (one - curr.f_pc_jnz()) * next.pc() - ((one - curr.f_pc_abs() - curr.f_pc_rel() - curr.f_pc_jnz()) * (curr.pc() + curr.inst_size()) + curr.f_pc_abs() * curr.res() + curr.f_pc_rel() * (curr.pc() + curr.res())); self[T0] = curr.f_pc_jnz() * curr.dst() - curr.t0(); self[T1] = curr.t0() * curr.res() - curr.t1(); } fn evaluate_opcode_constraints(&mut self, frame: &MainEvaluationFrame) { let curr = frame.current(); let one: E = Felt::ONE.into(); self[MUL_1] = curr.mul() - (curr.op0() * curr.op1()); self[MUL_2] = curr.f_res_add() * (curr.op0() + curr.op1()) + curr.f_res_mul() * curr.mul() + (one - curr.f_res_add() - curr.f_res_mul() - curr.f_pc_jnz()) * curr.op1() - (one - curr.f_pc_jnz()) * curr.res(); self[CALL_1] = curr.f_opc_call() * (curr.dst() - curr.fp()); self[CALL_2] = curr.f_opc_call() * (curr.op0() - (curr.pc() + curr.inst_size())); self[ASSERT_EQ] = curr.f_opc_aeq() * (curr.dst() - curr.res()); } fn enforce_selector(&mut self, frame: &MainEvaluationFrame) { let curr = frame.current(); for n in INST..=ASSERT_EQ { self[n] *= curr.selector(); } } } impl AuxEvaluationResult for [F] where E: FieldElement + From, F: FieldElement + From + ExtensionOf, { fn evaluate_memory_constraints( &mut self, main_frame: &MainEvaluationFrame, aux_frame: &AuxEvaluationFrame, aux_rand_elements: &AuxTraceRandElements, ) { let curr = main_frame.segment(); let aux = aux_frame.segment(); let random_elements = aux_rand_elements.get_segment_elements(0); let z = random_elements[0]; let alpha = random_elements[1]; // Continuity constraint for (i, n) in A_M_PRIME.enumerate() { self[n] = (aux.a_m_prime(i + 1) - aux.a_m_prime(i)) * (aux.a_m_prime(i + 1) - aux.a_m_prime(i) - F::ONE); } // Single-valued constraint for (i, n) in V_M_PRIME.enumerate() { self[n] = (aux.v_m_prime(i + 1) - aux.v_m_prime(i)) * (aux.a_m_prime(i + 1) - aux.a_m_prime(i) - F::ONE); } // Cumulative product step for (i, n) in P_M.enumerate() { let a_m: F = curr.a_m(i + 1).into(); let v_m: F = curr.v_m(i + 1).into(); self[n] = (z - (aux.a_m_prime(i + 1) + alpha * aux.v_m_prime(i + 1))) * aux.p_m(i + 1) - (z - (a_m + alpha * v_m)) * aux.p_m(i); } } fn evaluate_range_check_constraints( &mut self, main_frame: &MainEvaluationFrame, aux_frame: &AuxEvaluationFrame, aux_rand_elements: &AuxTraceRandElements, ) { let curr = main_frame.segment(); let aux = aux_frame.segment(); let random_elements = aux_rand_elements.get_segment_elements(1); let z = random_elements[0]; // Continuity constraint for (i, n) in A_RC_PRIME.enumerate() { self[n] = (aux.a_rc_prime(i + 1) - aux.a_rc_prime(i)) * (aux.a_rc_prime(i + 1) - aux.a_rc_prime(i) - F::ONE); } // Cumulative product step for (i, n) in P_RC.enumerate() { self[n] = (z - aux.a_rc_prime(i + 1)) * aux.p_rc(i + 1) - (z - curr.a_rc(i + 1).into()) * aux.p_rc(i) } } } ================================================ FILE: air/src/frame.rs ================================================ use super::FieldElement; use giza_core::{flags::*, *}; use winter_air::{Air, EvaluationFrame, Table}; use winter_utils::TableReader; // MAIN FRAME // -------------------------------------------------------------------------------------------- #[derive(Debug, Clone)] pub struct MainEvaluationFrame { table: Table, // row-major indexing } impl EvaluationFrame for MainEvaluationFrame { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- fn new(air: &A) -> Self { let num_cols = air.trace_layout().main_trace_width(); let num_rows = Self::num_rows(); MainEvaluationFrame { table: Table::new(num_rows, num_cols), } } fn from_table(table: Table) -> Self { Self { table } } // ROW MUTATORS // -------------------------------------------------------------------------------------------- fn read_from>( &mut self, data: R, step: usize, _offset: usize, blowup: usize, ) { let trace_len = data.num_rows(); for (row, row_idx) in self.table.rows_mut().zip(Self::offsets().into_iter()) { for col_idx in 0..data.num_cols() { row[col_idx] = data.get(col_idx, (step + row_idx * blowup) % trace_len); } } } // ROW ACCESSORS // -------------------------------------------------------------------------------------------- fn row<'a>(&'a self, row_idx: usize) -> &'a [E] { &self.table.get_row(row_idx) } fn to_table(&self) -> Table { self.table.clone() } fn offsets() -> &'static [usize] { &[0, 1] } } impl<'a, E: FieldElement> MainEvaluationFrame { pub fn current(&'a self) -> MainFrameSegment<'a, E> { MainFrameSegment::new(&self.table, 0) } pub fn next(&'a self) -> MainFrameSegment<'a, E> { MainFrameSegment::new(&self.table, 1) } pub fn segment(&'a self) -> MainFrameSegment<'a, E> { MainFrameSegment::new(&self.table, 0) } } pub struct MainFrameSegment<'a, E: FieldElement> { table: &'a Table, row_start: usize, } enum DataSegment { Flags, ResValue, TempMemoryPointer, MemoryAddress, MemoryValues, Offsets, TempValues, Selector, } impl<'a, E: FieldElement> MainFrameSegment<'a, E> { fn new(table: &'a Table, row_start: usize) -> Self { Self { table, row_start } } fn get(&self, pos: usize, data_type: DataSegment) -> E { // Should this function be inlined? let offset = match data_type { DataSegment::Flags => FLAG_TRACE_OFFSET, DataSegment::ResValue => RES_TRACE_OFFSET, DataSegment::TempMemoryPointer => MEM_P_TRACE_OFFSET, DataSegment::MemoryAddress => MEM_A_TRACE_OFFSET, DataSegment::MemoryValues => MEM_V_TRACE_OFFSET, DataSegment::Offsets => OFF_X_TRACE_OFFSET, DataSegment::TempValues => DERIVED_TRACE_OFFSET, DataSegment::Selector => SELECTOR_TRACE_OFFSET, }; self.table.get_row(self.row_start)[offset + pos] } fn get_virtual(&self, idx: usize, offset: usize, width: usize) -> E { if (0..width).contains(&idx) { self.table.get_row(0)[offset + idx] } else if (width..width * 2).contains(&idx) { self.table.get_row(1)[offset + idx - width] } else { panic!() } } } impl<'a, E: FieldElement + From> MainFrameSegment<'a, E> { /// Result pub fn res(&self) -> E { self.get(0, DataSegment::ResValue) } /// Registers pub fn pc(&self) -> E { self.get(0, DataSegment::MemoryAddress) } pub fn ap(&self) -> E { self.get(0, DataSegment::TempMemoryPointer) } pub fn fp(&self) -> E { self.get(1, DataSegment::TempMemoryPointer) } /// Memory addresses pub fn dst_addr(&self) -> E { self.get(1, DataSegment::MemoryAddress) } pub fn op0_addr(&self) -> E { self.get(2, DataSegment::MemoryAddress) } pub fn op1_addr(&self) -> E { self.get(3, DataSegment::MemoryAddress) } /// Memory values pub fn inst(&self) -> E { self.get(0, DataSegment::MemoryValues) } pub fn dst(&self) -> E { self.get(1, DataSegment::MemoryValues) } pub fn op0(&self) -> E { self.get(2, DataSegment::MemoryValues) } pub fn op1(&self) -> E { self.get(3, DataSegment::MemoryValues) } /// Instruction size pub fn inst_size(&self) -> E { self.f_op1_val() + Felt::ONE.into() } /// Derived trace values pub fn t0(&self) -> E { self.get(0, DataSegment::TempValues) } pub fn t1(&self) -> E { self.get(1, DataSegment::TempValues) } pub fn mul(&self) -> E { self.get(2, DataSegment::TempValues) } /// Virtual columns of memory addreses and values pub fn a_m(&self, idx: usize) -> E { self.get_virtual(idx, MEM_A_TRACE_OFFSET, MEM_A_TRACE_WIDTH) } pub fn v_m(&self, idx: usize) -> E { self.get_virtual(idx, MEM_V_TRACE_OFFSET, MEM_V_TRACE_WIDTH) } /// Virtual columns of offsets pub fn a_rc(&self, idx: usize) -> E { self.get_virtual(idx, OFF_X_TRACE_OFFSET, OFF_X_TRACE_WIDTH) } /// Selector pub fn selector(&self) -> E { self.get(0, DataSegment::Selector) } } impl<'a, E: FieldElement + From> OffsetDecomposition for MainFrameSegment<'a, E> { fn off_dst(&self) -> E { bias(self.get(0, DataSegment::Offsets)) } fn off_op0(&self) -> E { bias(self.get(1, DataSegment::Offsets)) } fn off_op1(&self) -> E { bias(self.get(2, DataSegment::Offsets)) } } impl<'a, E: FieldElement + From> FlagDecomposition for MainFrameSegment<'a, E> { fn flags(&self) -> Vec { let mut flags = Vec::with_capacity(NUM_FLAGS); for i in 0..NUM_FLAGS { flags.push(self.flag_at(i)); } flags } fn flag_at(&self, pos: usize) -> E { self.get(pos, DataSegment::Flags) } } // AUX FRAME // -------------------------------------------------------------------------------------------- #[derive(Debug, Clone)] pub struct AuxEvaluationFrame { table: Table, // row-major indexing } impl EvaluationFrame for AuxEvaluationFrame { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- fn new(air: &A) -> Self { let num_rows = Self::num_rows(); let num_cols = air.trace_layout().aux_trace_width(); AuxEvaluationFrame { table: Table::new(num_rows, num_cols), } } fn from_table(table: Table) -> Self { Self { table } } // ROW MUTATORS // -------------------------------------------------------------------------------------------- fn read_from>(&mut self, data: R, step: usize, offset: usize, blowup: usize) { let trace_len = data.num_rows(); for (row, row_idx) in self.table.rows_mut().zip(Self::offsets().into_iter()) { for col_idx in 0..data.num_cols() { row[col_idx + offset] = data.get(col_idx, (step + row_idx * blowup) % trace_len); } } } // ROW ACCESSORS // -------------------------------------------------------------------------------------------- fn row<'a>(&'a self, row_idx: usize) -> &'a [E] { &self.table.get_row(row_idx) } fn to_table(&self) -> Table { self.table.clone() } fn offsets() -> &'static [usize] { &[0, 1] } } impl<'a, E: FieldElement> AuxEvaluationFrame { pub fn segment(&'a self) -> AuxFrameSegment<'a, E> { AuxFrameSegment::new(&self.table, 0) } } pub struct AuxFrameSegment<'a, E: FieldElement> { curr_row: &'a [E], next_row: &'a [E], } impl<'a, E: FieldElement> AuxFrameSegment<'a, E> { fn new(table: &'a Table, row_idx: usize) -> Self { let curr_row = table.get_row(row_idx); let next_row = table.get_row(row_idx + 1); Self { curr_row, next_row } } fn get_virtual(&self, idx: usize, offset: usize, width: usize) -> E { if (0..width).contains(&idx) { self.curr_row[offset + idx] } else if (width..width * 2).contains(&idx) { self.next_row[offset + idx - width] } else { panic!() } } /// Memory pub fn a_m_prime(&self, idx: usize) -> E { self.get_virtual(idx, A_M_PRIME_OFFSET, A_M_PRIME_WIDTH) } pub fn v_m_prime(&self, idx: usize) -> E { self.get_virtual(idx, V_M_PRIME_OFFSET, V_M_PRIME_WIDTH) } pub fn p_m(&self, idx: usize) -> E { self.get_virtual(idx, P_M_OFFSET, P_M_WIDTH) } /// Permutation range check pub fn a_rc_prime(&self, idx: usize) -> E { self.get_virtual(idx, A_RC_PRIME_OFFSET, A_RC_PRIME_WIDTH) } pub fn p_rc(&self, idx: usize) -> E { self.get_virtual(idx, P_RC_OFFSET, P_RC_WIDTH) } } ================================================ FILE: air/src/lib.rs ================================================ #![feature(generic_associated_types)] use giza_core::{ Builtin, ExtensionOf, Felt, FieldElement, RegisterState, Word, A_RC_PRIME_FIRST, A_RC_PRIME_LAST, MEM_A_TRACE_OFFSET, MEM_P_TRACE_OFFSET, P_M_LAST, }; use winter_air::{ Air, AirContext, Assertion, AuxTraceRandElements, ProofOptions as WinterProofOptions, TraceInfo, TransitionConstraintDegree, }; use winter_utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; // EXPORTS // ================================================================================================ pub use winter_air::{EvaluationFrame, FieldExtension, HashFunction}; mod options; pub use options::ProofOptions; mod constraints; use constraints::{AuxEvaluationResult, EvaluationResult}; mod frame; pub use frame::{AuxEvaluationFrame, MainEvaluationFrame}; // PROCESSOR AIR // ================================================================================================ pub struct ProcessorAir { context: AirContext, pub_inputs: PublicInputs, } impl Air for ProcessorAir { type BaseField = Felt; type PublicInputs = PublicInputs; type Frame = MainEvaluationFrame; type AuxFrame = AuxEvaluationFrame; fn new(trace_info: TraceInfo, pub_inputs: PublicInputs, options: WinterProofOptions) -> Self { let mut main_degrees = vec![]; // Instruction constraints for _ in 0..=14 { main_degrees.push(TransitionConstraintDegree::new(2)); // F0-F14 } main_degrees.push(TransitionConstraintDegree::new(1)); // F15 // Operand constraints main_degrees.push(TransitionConstraintDegree::new(4)); // INST main_degrees.push(TransitionConstraintDegree::new(4)); // DST_ADDR main_degrees.push(TransitionConstraintDegree::new(4)); // OP0_ADDR main_degrees.push(TransitionConstraintDegree::new(4)); // OP1_ADDR // Register constraints main_degrees.push(TransitionConstraintDegree::new(4)); // NEXT_AP main_degrees.push(TransitionConstraintDegree::new(4)); // NEXT_FP main_degrees.push(TransitionConstraintDegree::new(4)); // NEXT_PC_1 main_degrees.push(TransitionConstraintDegree::new(4)); // NEXT_PC_2 main_degrees.push(TransitionConstraintDegree::new(4)); // T0 main_degrees.push(TransitionConstraintDegree::new(4)); // T1 // Opcode constraints main_degrees.push(TransitionConstraintDegree::new(4)); // MUL_1 main_degrees.push(TransitionConstraintDegree::new(4)); // MUL_2 main_degrees.push(TransitionConstraintDegree::new(4)); // CALL_1 main_degrees.push(TransitionConstraintDegree::new(4)); // CALL_2 main_degrees.push(TransitionConstraintDegree::new(4)); // ASSERT_EQ let aux_degrees = vec![ // Memory constraints TransitionConstraintDegree::new(2), // A_M_PRIME 0 TransitionConstraintDegree::new(2), // " 1 TransitionConstraintDegree::new(2), // " 2 TransitionConstraintDegree::new(2), // " 3 TransitionConstraintDegree::new(2), // V_M_PRIME 0 TransitionConstraintDegree::new(2), // " 1 TransitionConstraintDegree::new(2), // " 2 TransitionConstraintDegree::new(2), // " 3 TransitionConstraintDegree::new(2), // P_M 0 TransitionConstraintDegree::new(2), // " 1 TransitionConstraintDegree::new(2), // " 2 TransitionConstraintDegree::new(2), // " 3 // Range check constraints TransitionConstraintDegree::new(2), // A_RC_PRIME 0 TransitionConstraintDegree::new(2), // " 1 TransitionConstraintDegree::new(2), // " 2 TransitionConstraintDegree::new(2), // P_RC 0 TransitionConstraintDegree::new(2), // " 1 TransitionConstraintDegree::new(2), // " 2 ]; let mut transition_exemptions = vec![]; transition_exemptions.extend(vec![1; main_degrees.len()]); transition_exemptions.extend(vec![1; aux_degrees.len()]); let mut context = AirContext::new_multi_segment(trace_info, main_degrees, aux_degrees, 4, 3, options); context.set_transition_exemptions(transition_exemptions); Self { context, pub_inputs, } } fn get_assertions(&self) -> Vec> { let last_step = self.pub_inputs.num_steps - 1; vec![ // Initial and final 'pc' register Assertion::single(MEM_A_TRACE_OFFSET, 0, self.pub_inputs.init.pc), Assertion::single(MEM_A_TRACE_OFFSET, last_step, self.pub_inputs.fin.pc), // Initial and final 'ap' register Assertion::single(MEM_P_TRACE_OFFSET, 0, self.pub_inputs.init.ap), Assertion::single(MEM_P_TRACE_OFFSET, last_step, self.pub_inputs.fin.ap), ] } fn get_aux_assertions>( &self, aux_rand_elements: &AuxTraceRandElements, ) -> Vec> { let last_step = self.trace_length() - 1; let random_elements = aux_rand_elements.get_segment_elements(0); let mem = &self.pub_inputs.mem; let z = random_elements[0]; let alpha = random_elements[1]; let num = z.exp((mem.0.len() as u64).into()); let den = mem .0 .iter() .zip(&mem.1) .map(|(a, v)| z - (E::from(*a as u64) + alpha * E::from(v.unwrap().word()))) .reduce(|a, b| a * b) .unwrap(); vec![ // Public memory Assertion::single(P_M_LAST, last_step, num / den), // Minimum range check value Assertion::single(A_RC_PRIME_FIRST, 0, E::from(self.pub_inputs.rc_min)), // Maximum range check value Assertion::single(A_RC_PRIME_LAST, last_step, E::from(self.pub_inputs.rc_max)), ] } fn evaluate_transition>( &self, frame: &MainEvaluationFrame, _periodic_values: &[E], result: &mut [E], ) { result.evaluate_instr_constraints(frame); result.evaluate_operand_constraints(frame); result.evaluate_register_constraints(frame); result.evaluate_opcode_constraints(frame); result.enforce_selector(frame); } fn evaluate_aux_transition< E: FieldElement + From, F: FieldElement + From + ExtensionOf, >( &self, main_frame: &MainEvaluationFrame, aux_frame: &AuxEvaluationFrame, _periodic_values: &[E], aux_rand_elements: &AuxTraceRandElements, result: &mut [F], ) { result.evaluate_memory_constraints(main_frame, aux_frame, aux_rand_elements); result.evaluate_range_check_constraints(main_frame, aux_frame, aux_rand_elements); } fn context(&self) -> &AirContext { &self.context } } // PUBLIC INPUTS // ================================================================================================ pub struct PublicInputs { pub init: RegisterState, // initial register state pub fin: RegisterState, // final register state pub rc_min: u16, // minimum range check value (0 < rc_min < rc_max < 2^16) pub rc_max: u16, // maximum range check value pub mem: (Vec, Vec>), // public memory pub num_steps: usize, // number of execution steps pub builtins: Vec, // list of builtins } impl PublicInputs { pub fn new( init: RegisterState, fin: RegisterState, rc_min: u16, rc_max: u16, mem: (Vec, Vec>), num_steps: usize, builtins: Vec, ) -> Self { Self { init, fin, rc_min, rc_max, mem, num_steps, builtins, } } } // TODO: Implement Serializable/Deserializable traits in RegisterState and Memory // structs instead of manually managing it here impl Serializable for PublicInputs { fn write_into(&self, target: &mut W) { target.write(self.init.pc); target.write(self.init.ap); target.write(self.init.fp); target.write(self.fin.pc); target.write(self.fin.ap); target.write(self.fin.fp); target.write_u16(self.rc_min); target.write_u16(self.rc_max); target.write_u64(self.mem.1.len() as u64); for i in 0..self.mem.1.len() as usize { target.write_u64(self.mem.0[i]); } target.write( self.mem .1 .iter() .map(|x| x.unwrap().word()) .collect::>(), ); target.write_u64(self.num_steps as u64); // TODO: Use bit representation once multiple builtins are supported for builtin in self.builtins.iter() { if let Builtin::Output(_) = builtin { target.write_u8(1); } else { target.write_u8(0); } } } } impl Deserializable for PublicInputs { fn read_from(source: &mut R) -> Result { let init = RegisterState::new( Felt::read_from(source)?, Felt::read_from(source)?, Felt::read_from(source)?, ); let fin = RegisterState::new( Felt::read_from(source)?, Felt::read_from(source)?, Felt::read_from(source)?, ); let rc_min = source.read_u16()?; let rc_max = source.read_u16()?; let mem_len = source.read_u64()?; let mut mem_a = vec![0u64; mem_len as usize]; for i in 0..mem_len as usize { mem_a[i] = source.read_u64()?; } let mem_v = Felt::read_batch_from(source, mem_len as usize)? .into_iter() .map(|x| Some(Word::new(x))) .collect::>(); let num_steps = source.read_u64()?; // TODO: Interpret as bits once multiple builtins are supported let builtins = match source.read_u8()? { 1 => vec![Builtin::Output(0)], _ => vec![], }; Ok(PublicInputs::new( init, fin, rc_min, rc_max, (mem_a, mem_v), num_steps as usize, builtins, )) } } ================================================ FILE: air/src/options.rs ================================================ use core::ops::Deref; use winter_air::{FieldExtension, HashFunction, ProofOptions as WinterProofOptions}; /// TODO: add docs #[derive(Clone)] pub struct ProofOptions(WinterProofOptions); impl ProofOptions { pub fn new( num_queries: usize, blowup_factor: usize, grinding_factor: u32, hash_fn: HashFunction, field_extension: FieldExtension, fri_folding_factor: usize, fri_max_remainder_size: usize, ) -> Self { Self(WinterProofOptions::new( num_queries, blowup_factor, grinding_factor, hash_fn, field_extension, fri_folding_factor, fri_max_remainder_size, )) } pub fn with_proof_options( num_queries: Option, blowup_factor: Option, grinding_factor: Option, fri_folding_factor: Option, fri_max_remainder_size: Option, ) -> Self { Self(WinterProofOptions::new( num_queries.unwrap_or(54), // 27 blowup_factor.unwrap_or(4), //8, grinding_factor.unwrap_or(16), HashFunction::Blake3_192, FieldExtension::None, fri_folding_factor.unwrap_or(8), fri_max_remainder_size.unwrap_or(256), )) } pub fn into_inner(self) -> WinterProofOptions { self.0 } } impl Default for ProofOptions { fn default() -> Self { Self::with_proof_options(None, None, None, None, None) } } impl Deref for ProofOptions { type Target = WinterProofOptions; fn deref(&self) -> &Self::Target { &self.0 } } ================================================ FILE: cli/Cargo.toml ================================================ [package] name = "giza-cli" version = "0.1.0" edition = "2021" rust-version = "1.57" [dependencies] clap = { version = "3.1.18", features = ["derive"] } air = { package = "giza-air", path = "../air", version = "0.1", default-features = false } prover = { package = "giza-prover", path = "../prover", version = "0.1", default-features = false } runner = { package = "giza-runner", path = "../runner", version = "0.1", default-features = false } winterfell = { package = "winter-verifier", git = "https://github.com/maxgillett/winterfell", rev = "0aad6a5", features = ["std"], default-features = false } winter-utils = { package = "winter-utils", git = "https://github.com/maxgillett/winterfell", rev = "0aad6a5", default-features = false } serde = "1.0.137" bincode = "1.3.3" [[bin]] name = "giza" path = "src/giza.rs" doc = false ================================================ FILE: cli/README.md ================================================ giza-cli ======== ## Commands ``` # Generate a proof giza prove --trace ./trace.bin --output proof.bin ``` ================================================ FILE: cli/src/cmd/mod.rs ================================================ use serde::{Deserialize, Serialize}; pub mod prove; pub mod verify; #[derive(Serialize, Deserialize)] struct ProofData { input_bytes: Vec, proof_bytes: Vec, } ================================================ FILE: cli/src/cmd/prove/args.rs ================================================ use clap::{Error, ErrorKind, Parser, ValueHint}; use std::path::PathBuf; #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] pub struct ProveArgs { #[clap( help = "Path to the compiled Cairo program JSON file", long, value_hint = ValueHint::FilePath )] pub program: PathBuf, #[clap( help = "Path to the execution trace output file", long, value_hint = ValueHint::FilePath )] pub trace: PathBuf, #[clap( help = "Path to the memory output file", long, value_hint = ValueHint::FilePath )] pub memory: PathBuf, #[clap( help = "Path to write the STARK proof", long, value_hint = ValueHint::FilePath )] pub output: PathBuf, #[clap(help = "Number of serialized outputs", long)] pub num_outputs: Option, #[clap( help = "Number of queries for a STARK proof", long, value_parser(clap::builder::ValueParser::new(parse_num_queries)) )] pub num_queries: Option, #[clap( help = "Blowup factor for a STARK proof", long, value_parser(clap::builder::ValueParser::new(parse_blowup_factor)) )] pub blowup_factor: Option, #[clap( help = "Query seed grinding factor for a STARK proof", long, value_parser(clap::value_parser!(u32).range(..33)) )] pub grinding_factor: Option, #[clap( help = "Factor by which the degree of a polynomial is reduced with each FRI layer", long, value_parser(clap::builder::ValueParser::new(parse_fri_folding_factor)) )] pub fri_folding_factor: Option, #[clap( help = "Maximum allowed remainder (last FRI layer) size", long, value_parser(clap::builder::ValueParser::new(parse_fri_max_remainder_size)) )] pub fri_max_remainder_size: Option, } fn parse_num_queries(value: &str) -> Result { let value = value .parse::() .map_err(|e| Error::raw(ErrorKind::InvalidValue, format!("{}", e)))?; match value { 0 => Err(Error::raw(ErrorKind::ValueValidation, "cannot be 0")), 129.. => Err(Error::raw( ErrorKind::ValueValidation, "cannot be more than 128", )), _ => Ok(value), } } fn parse_blowup_factor(value: &str) -> Result { let value = value .parse::() .map_err(|e| Error::raw(ErrorKind::InvalidValue, format!("{}", e)))?; if !value.is_power_of_two() { return Err(Error::raw( ErrorKind::ValueValidation, "must be a power of two", )); } match value { 0..=3 => Err(Error::raw( ErrorKind::ValueValidation, "cannot be smaller than 4", )), 257.. => Err(Error::raw( ErrorKind::ValueValidation, "cannot be more than 256", )), _ => Ok(value), } } fn parse_fri_folding_factor(value: &str) -> Result { let value = value .parse::() .map_err(|e| Error::raw(ErrorKind::InvalidValue, format!("{}", e)))?; if value != 4 && value != 8 && value != 16 { Err(Error::raw(ErrorKind::ValueValidation, "must be 4, 8 or 16")) } else { Ok(value) } } fn parse_fri_max_remainder_size(value: &str) -> Result { let value = value .parse::() .map_err(|e| Error::raw(ErrorKind::InvalidValue, format!("{}", e)))?; if !value.is_power_of_two() { return Err(Error::raw( ErrorKind::ValueValidation, "must be a power of two", )); } match value { 0..=31 => Err(Error::raw( ErrorKind::ValueValidation, "cannot be smaller than 32", )), 1025.. => Err(Error::raw( ErrorKind::ValueValidation, "cannot be more than 1024", )), _ => Ok(value), } } ================================================ FILE: cli/src/cmd/prove/mod.rs ================================================ mod args; mod prove; pub use args::ProveArgs; ================================================ FILE: cli/src/cmd/prove/prove.rs ================================================ use std::fs::File; use std::io::Write; use super::ProveArgs; use crate::{cmd::ProofData, utils::Cmd}; use air::ProofOptions; use runner::ExecutionTrace; use winter_utils::Serializable; pub struct ProveOutput {} #[derive(Debug)] pub enum Error {} impl Cmd for ProveArgs { type Output = Result; fn run(self) -> Self::Output { // Load trace from file let trace = ExecutionTrace::from_file(self.program, self.trace, self.memory, self.num_outputs); // Generate proof let proof_options = ProofOptions::with_proof_options( self.num_queries, self.blowup_factor, self.grinding_factor, self.fri_folding_factor, self.fri_max_remainder_size, ); let (proof, pub_inputs) = prover::prove_trace(trace, &proof_options).unwrap(); let input_bytes = pub_inputs.to_bytes(); let proof_bytes = proof.to_bytes(); println!("Proof size: {:.1} KB", proof_bytes.len() as f64 / 1024f64); // Write proof to disk let data = ProofData { input_bytes, proof_bytes, }; let b = bincode::serialize(&data).unwrap(); let mut f = File::create(self.output).unwrap(); f.write_all(&b).unwrap(); Ok(ProveOutput {}) } } ================================================ FILE: cli/src/cmd/verify.rs ================================================ use std::fs::File; use std::io::Read; use std::path::PathBuf; use super::ProofData; use crate::utils::Cmd; use air::{ProcessorAir, PublicInputs}; use clap::{Parser, ValueHint}; use winter_utils::{Deserializable, SliceReader}; use winterfell::StarkProof; pub struct VerifyOutput {} #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] pub struct VerifyArgs { #[clap( help = "Path to the STARK proof", long, value_hint = ValueHint::FilePath )] pub proof: PathBuf, } #[derive(Debug)] pub enum Error {} impl Cmd for VerifyArgs { type Output = Result; fn run(self) -> Self::Output { // Load proof and public inputs from file let mut b = Vec::new(); let mut f = File::open(self.proof).unwrap(); f.read_to_end(&mut b).unwrap(); let data: ProofData = bincode::deserialize(&b).unwrap(); let pub_inputs = PublicInputs::read_from(&mut SliceReader::new(&data.input_bytes[..])).unwrap(); let proof = StarkProof::from_bytes(&data.proof_bytes).unwrap(); // Verify execution match winterfell::verify::(proof, pub_inputs) { Ok(_) => println!("Execution verified"), Err(err) => println!("Failed to verify execution: {}", err), } Ok(VerifyOutput {}) } } ================================================ FILE: cli/src/giza.rs ================================================ pub mod cmd; mod utils; use crate::utils::Cmd; use clap::{Parser, Subcommand}; use cmd::{prove::ProveArgs, verify::VerifyArgs}; #[derive(Debug, Parser)] #[clap(name = "giza")] pub struct Opts { #[clap(subcommand)] pub sub: Subcommands, } #[derive(Debug, Subcommand)] #[allow(clippy::large_enum_variant)] pub enum Subcommands { Prove(ProveArgs), Verify(VerifyArgs), } fn main() { let opts = Opts::parse(); match opts.sub { Subcommands::Prove(cmd) => { cmd.run().unwrap(); } Subcommands::Verify(cmd) => { cmd.run().unwrap(); } } // TODO: consider returning Result for error codes. // Ok(()) } ================================================ FILE: cli/src/utils.rs ================================================ /// Common trait for all cli commands pub trait Cmd: clap::Parser + Sized { type Output; fn run(self) -> Self::Output; } ================================================ FILE: core/Cargo.toml ================================================ [package] name = "giza-core" version = "0.1.0" edition = "2021" rust-version = "1.57" [dependencies] math = { package = "winter-math", git = "https://github.com/maxgillett/winterfell", rev = "0aad6a5", version = "0.4", default-features = false } winter-utils = { package = "winter-utils", git = "https://github.com/maxgillett/winterfell", rev = "0aad6a5", version = "0.4", default-features = false } ff = { version = "0.12", features = ["derive"] } hex = "0.4" ================================================ FILE: core/src/field/f252/mod.rs ================================================ //! An implementation of the 252-bit STARK-friendly prime field chosen by Starkware //! with modulus $2^{251} + 17 \cdot 2^{192} + 1$. //! TODO: Worth switching to Barrett reduction for efficiency? use core::{ convert::{TryFrom, TryInto}, fmt::{Debug, Display, Formatter, LowerHex}, ops::{ Add, AddAssign, BitAnd, Div, DivAssign, Mul, MulAssign, Neg, Shl, Shr, ShrAssign, Sub, SubAssign, }, slice, }; pub use math::{ExtensibleField, FieldElement, StarkField}; use winter_utils::{ collections::Vec, string::String, AsBytes, ByteReader, ByteWriter, Deserializable, DeserializationError, Randomizable, Serializable, }; use ff::{Field, PrimeField}; #[cfg(test)] mod tests; // FIELD ELEMENT // ================================================================================================ // Note that the internal representation of Fr is assumed to be in Montgomery form with R=2^256 #[derive(PrimeField)] #[PrimeFieldModulus = "3618502788666131213697322783095070105623107215331596699973092056135872020481"] #[PrimeFieldGenerator = "3"] #[PrimeFieldReprEndianness = "little"] struct Fr([u64; 4]); // Number of bytes needed to represent field element const ELEMENT_BYTES: usize = core::mem::size_of::(); // A wrapper around the internal representation of Fr for non-finite field integer manipulation #[derive(PartialOrd, Ord, PartialEq, Eq, Copy, Clone, Debug)] pub struct BigInt(pub [u64; 4]); // Represents a base field element, using Fr as the backing type. #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] pub struct BaseElement(Fr); impl FieldElement for BaseElement { type PositiveInteger = BigInt; type BaseField = Self; const ZERO: Self = BaseElement(Fr([0, 0, 0, 0])); const ONE: Self = BaseElement(Fr(R.0)); // equal to 2^256 mod M const ELEMENT_BYTES: usize = ELEMENT_BYTES; const IS_CANONICAL: bool = true; fn inv(self) -> Self { Self(self.0.invert().unwrap()) } fn conjugate(&self) -> Self { Self(self.0) } fn elements_as_bytes(elements: &[Self]) -> &[u8] { let p = elements.as_ptr(); let len = elements.len() * Self::ELEMENT_BYTES; unsafe { slice::from_raw_parts(p as *const u8, len) } } unsafe fn bytes_as_elements(_bytes: &[u8]) -> Result<&[Self], DeserializationError> { unimplemented!() } fn zeroed_vector(n: usize) -> Vec { // TODO: use more efficient initialization let result = vec![Self::ZERO.0; n]; // translate a zero-filled vector of Fr into a vector of base field elements let mut v = core::mem::ManuallyDrop::new(result); let p = v.as_mut_ptr(); let len = v.len(); let cap = v.capacity(); unsafe { Vec::from_raw_parts(p as *mut Self, len, cap) } } fn as_base_elements(elements: &[Self]) -> &[Self::BaseField] { elements } } impl BaseElement { // Equal to 2*2^256 mod M (R is derived from the macro) pub const TWO: Self = BaseElement(Fr([ 0xffff_ffff_ffff_ffc1, 0xffff_ffff_ffff_ffff, 0xffff_ffff_ffff_ffff, 0x7fff_ffff_ffff_bd0, ])); pub fn from_raw(value: [u64; 4]) -> Self { BaseElement(Fr::from_raw(value)) } pub fn to_raw(&self) -> BigInt { self.0.to_raw() } } impl StarkField for BaseElement { /// sage: MODULUS = 2^251 + 17 * 2^192 + 1 \ /// sage: GF(MODULUS).is_prime_field() \ /// True \ /// sage: GF(MODULUS).order() \ /// 3618502788666131213697322783095070105623107215331596699973092056135872020481 const MODULUS: Self::PositiveInteger = BigInt([0x1, 0x0, 0x0, 0x8000_0000_0000_011]); const MODULUS_BITS: u32 = 252; /// sage: GF(MODULUS).primitive_element() \ /// 3 const GENERATOR: Self = BaseElement(GENERATOR); /// sage: is_odd((MODULUS - 1) / 2^192) \ /// True const TWO_ADICITY: u32 = 192; /// sage: k = (MODULUS - 1) / 2^192 \ /// sage: GF(MODULUS).primitive_element()^k \ /// 145784604816374866144131285430889962727208297722245411306711449302875041684 const TWO_ADIC_ROOT_OF_UNITY: Self = BaseElement(ROOT_OF_UNITY); fn get_modulus_le_bytes() -> Vec { Self::MODULUS.to_le_bytes() } /// Convert from Montgomery form #[inline] fn as_int(&self) -> Self::PositiveInteger { self.0.to_raw() } } impl Randomizable for BaseElement { const VALUE_SIZE: usize = Self::ELEMENT_BYTES; fn from_random_bytes(bytes: &[u8]) -> Option { Self::try_from(bytes).ok() } } impl Display for BaseElement { fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { write!(f, "{}", self.to_raw()) } } // OVERLOADED OPERATORS // ================================================================================================ impl Add for BaseElement { type Output = Self; fn add(self, rhs: Self) -> Self { Self(self.0 + rhs.0) } } impl AddAssign for BaseElement { fn add_assign(&mut self, rhs: Self) { *self = *self + rhs } } impl Sub for BaseElement { type Output = Self; fn sub(self, rhs: Self) -> Self { Self(self.0 - rhs.0) } } impl SubAssign for BaseElement { fn sub_assign(&mut self, rhs: Self) { *self = *self - rhs; } } impl Mul for BaseElement { type Output = Self; fn mul(self, rhs: Self) -> Self { Self(self.0 * rhs.0) } } impl MulAssign for BaseElement { fn mul_assign(&mut self, rhs: Self) { *self = *self * rhs } } impl Div for BaseElement { type Output = Self; fn div(self, rhs: Self) -> Self { Self(self.0 * rhs.0.invert().unwrap()) } } impl DivAssign for BaseElement { fn div_assign(&mut self, rhs: Self) { *self = *self / rhs } } impl Neg for BaseElement { type Output = Self; fn neg(self) -> Self { Self(-self.0) } } // QUADRATIC EXTENSION // ================================================================================================ /// Defines a quadratic extension of the base field over an irreducible polynomial x2 - /// x - 1. Thus, an extension element is defined as α + β * φ, where φ is a root of this polynomial, /// and α and β are base field elements. impl ExtensibleField<2> for BaseElement { #[inline(always)] fn mul(a: [Self; 2], b: [Self; 2]) -> [Self; 2] { let z = a[0] * b[0]; [z + a[1] * b[1], (a[0] + a[1]) * (b[0] + b[1]) - z] } #[inline(always)] fn mul_base(a: [Self; 2], b: Self) -> [Self; 2] { [a[0] * b, a[1] * b] } #[inline(always)] fn frobenius(x: [Self; 2]) -> [Self; 2] { [x[0] + x[1], Self::ZERO - x[1]] } } // CUBIC EXTENSION // ================================================================================================ /// Cubic extension for this field is not implemented as quadratic extension already provides /// sufficient security level. impl ExtensibleField<3> for BaseElement { fn mul(_a: [Self; 3], _b: [Self; 3]) -> [Self; 3] { unimplemented!() } #[inline(always)] fn mul_base(_a: [Self; 3], _b: Self) -> [Self; 3] { unimplemented!() } #[inline(always)] fn frobenius(_x: [Self; 3]) -> [Self; 3] { unimplemented!() } fn is_supported() -> bool { false } } // TYPE CONVERSIONS // ================================================================================================ impl From for BaseElement { /// Converts a 128-bit value into a field element. fn from(value: u128) -> Self { let hi: u64 = (value >> 64) as u64; let lo: u64 = value as u64; Self(Fr::from_raw([lo, hi, 0, 0])) } } impl From for BaseElement { /// Converts a 64-bit value into a field element. fn from(value: u64) -> Self { Self(Fr::from_raw([value, 0, 0, 0])) } } impl From for BaseElement { /// Converts a 32-bit value into a field element. fn from(value: u32) -> Self { Self(Fr::from_raw([value as u64, 0, 0, 0])) } } impl From for BaseElement { /// Converts a 16-bit value into a field element. fn from(value: u16) -> Self { Self(Fr::from_raw([value as u64, 0, 0, 0])) } } impl From for BaseElement { /// Converts an 8-bit value into a field element. fn from(value: u8) -> Self { Self(Fr::from_raw([value as u64, 0, 0, 0])) } } impl From<[u64; 4]> for BaseElement { /// Converts the value encoded in an array of 4 64-bit words into a field element. The bytes /// are assumed to be in little-endian byte order. If the value is greater than or equal /// to the field modulus, modular reduction is silently performed. fn from(bytes: [u64; 4]) -> Self { Self(Fr::from_raw(bytes)) } } impl From<[u8; 32]> for BaseElement { /// Converts the value encoded in an array of 32 bytes into a field element. The bytes /// are assumed to be in little-endian byte order. If the value is greater than or equal /// to the field modulus, modular reduction is silently performed. fn from(bytes: [u8; 32]) -> Self { let value: [u64; 4] = bytes .array_chunks::<8>() .map(|c| u64::from_le_bytes(*c)) .collect::>() .try_into() .unwrap(); Self(Fr::from_raw(value)) } } impl<'a> TryFrom<&'a [u8]> for BaseElement { type Error = String; /// Converts a slice of bytes into a field element. The bytes are assumed to be in /// little-endian byte order. If the value is greater than or equal to the field modulus, /// modular reduction is silently performed. fn try_from(bytes: &[u8]) -> Result { let mut value: [u64; 4] = [0; 4]; for (i, c) in bytes.chunks(8).enumerate() { value[i] = u64::from_le_bytes(TryInto::<[u8; 8]>::try_into(c).unwrap()); } Ok(Self(Fr::from_raw(value))) } } // TODO: Why is this method not working? See commented lines in core/src/word/helpers.rs impl AsBytes for BaseElement { fn as_bytes(&self) -> &[u8] { //let ptr: *const Vec = &self.as_int().to_le_bytes(); //unsafe { slice::from_raw_parts(ptr as *const u8, ELEMENT_BYTES) } let ptr: *const BigInt = &self.to_raw(); unsafe { slice::from_raw_parts(ptr as *const u8, ELEMENT_BYTES) } } } // SERIALIZATION / DESERIALIZATION // ------------------------------------------------------------------------------------------------ impl Serializable for BaseElement { fn write_into(&self, target: &mut W) { target.write_u8_slice(&self.0.to_le_bytes()); } } impl Deserializable for BaseElement { fn read_from(source: &mut R) -> Result { let bytes: [u8; 32] = source.read_u8_array()?; let value: [u64; 4] = bytes .array_chunks::<8>() .map(|c| u64::from_le_bytes(*c)) .collect::>() .try_into() .unwrap(); Ok(BaseElement(Fr(value))) } } // OVERLOADED OPERATORS (BIGINT) // ================================================================================================ impl Shr for BigInt { type Output = BigInt; fn shr(self, rhs: u32) -> BigInt { shr_vartime(&self, rhs as usize) } } impl Shr for &BigInt { type Output = BigInt; fn shr(self, rhs: u32) -> BigInt { shr_vartime(&self, rhs as usize) } } impl ShrAssign for BigInt { fn shr_assign(&mut self, rhs: BigInt) { let shift: u64 = rhs.try_into().unwrap(); *self = shr_vartime(&self, shift as usize); } } impl Shl for BigInt { type Output = BigInt; fn shl(self, rhs: u32) -> BigInt { shl_vartime(&self, rhs as usize) } } impl Shl for &BigInt { type Output = BigInt; fn shl(self, rhs: u32) -> BigInt { shl_vartime(&self, rhs as usize) } } impl BitAnd for BigInt { type Output = Self; fn bitand(self, Self(rhs): Self) -> Self::Output { let mut limbs = [0u64; 4]; for i in 0..4 { limbs[i] = self.0[i] & rhs[i]; } Self(limbs) } } // Modified from https://github.com/RustCrypto/crypto-bigint/blob/master/src/uint/shr.rs fn shr_vartime(value: &BigInt, shift: usize) -> BigInt { let full_shifts = shift / 64; let small_shift = shift & (64 - 1); let mut limbs = [0u64; 4]; if shift > 64 * 4 { return BigInt(limbs); } let n = 4 - full_shifts; let mut i = 0; if small_shift == 0 { while i < n { limbs[i] = value.0[i + full_shifts]; i += 1; } } else { while i < n { let mut lo = value.0[i + full_shifts] >> small_shift; if i < (4 - 1) - full_shifts { lo |= value.0[i + full_shifts + 1] << (64 - small_shift); } limbs[i] = lo; i += 1; } } BigInt(limbs) } // Modified from https://github.com/RustCrypto/crypto-bigint/blob/171f6745b98b6dccf05f7d25263981949967f398/src/uint/shl.rs fn shl_vartime(value: &BigInt, n: usize) -> BigInt { let mut limbs = [0u64; 4]; if n >= 64 * 4 { return BigInt(limbs); } let shift_num = n / 64; let lshift_rem = n % 64; let nz = lshift_rem == 0; let rshift_rem = if nz { 0 } else { 64 - lshift_rem }; let mut i = 4 - 1; while i > shift_num { let mut limb = value.0[i - shift_num] << lshift_rem; let hi = value.0[i - shift_num - 1] >> rshift_rem; limb |= hi & nz as u64; limbs[i] = limb; i -= 1 } limbs[shift_num] = value.0[0] << lshift_rem; BigInt(limbs) } // TYPE CONVERSIONS (BIGINT, FR) // ------------------------------------------------------------------------------------------------ impl From for BigInt { /// Converts a 128-bit value into a field element. fn from(value: u128) -> Self { let hi: u64 = (value >> 64) as u64; let lo: u64 = value as u64; BigInt([lo, hi, 0, 0]) } } impl From for BigInt { /// Converts a 64-bit value into a field element. fn from(value: u64) -> Self { BigInt([value, 0, 0, 0]) } } impl From for BigInt { /// Converts a 32-bit value into a field element. fn from(value: u32) -> Self { BigInt([value as u64, 0, 0, 0]) } } impl From for BigInt { /// Converts a 16-bit value into a field element. fn from(value: u16) -> Self { BigInt([value as u64, 0, 0, 0]) } } impl From for BigInt { /// Converts an 8-bit value into a field element. fn from(value: u8) -> Self { BigInt([value as u64, 0, 0, 0]) } } impl TryInto for BigInt { type Error = (); fn try_into(self) -> Result { Ok(self.0[0]) } } impl TryInto for BigInt { type Error = (); fn try_into(self) -> Result { Ok(self.0[0] as u16) } } impl BigInt { pub fn to_le_bytes(&self) -> Vec { let mut result = [0u8; 32]; write_le_bytes(self.0, &mut result); result.to_vec() } } impl Display for BigInt { fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { for i in (0..4).rev() { LowerHex::fmt(&self.0[i], f)?; } Ok(()) } } impl Fr { pub fn from_raw(value: [u64; 4]) -> Self { Fr(value).mul(&R2) } pub fn to_raw(&self) -> BigInt { let limbs = self.0; let mut val = self.clone(); val.mont_reduce(limbs[0], limbs[1], limbs[2], limbs[3], 0, 0, 0, 0); BigInt(val.0) } pub fn to_le_bytes(&self) -> Vec { let mut result = [0u8; 32]; write_le_bytes(self.0, &mut result); result.to_vec() } } // Modified from https://github.com/RustCrypto/crypto-bigint/blob/171f6745b98b6dccf05f7d25263981949967f398/src/uint/encoding.rs fn write_le_bytes(value: [u64; 4], out: &mut [u8]) { for (src, dst) in value.iter().cloned().zip(out.chunks_exact_mut(8)) { dst.copy_from_slice(&src.to_le_bytes()); } } #[test] fn as_int() { let a = BaseElement::from_raw([3, 0, 0, 0]); let b: u64 = a.as_int().try_into().unwrap(); assert_eq!(b, 3); } ================================================ FILE: core/src/field/f252/tests.rs ================================================ ================================================ FILE: core/src/field/mod.rs ================================================ pub mod f252; ================================================ FILE: core/src/flags/mod.rs ================================================ // Modified from https://github.com/o1-labs/proof-systems //! Definition of some constants for easier readability of the steps. //! When they refer to single bit flagsets, only one constant is needed. /// Number of Cairo flags pub const NUM_FLAGS: usize = 16; /// Position of destination offset of 16 bits within instruction decomposition pub const POS_DST: usize = 0; /// Position of first operand offset of 16 bits within instruction decomposition pub const POS_OP0: usize = 1; /// Position of second operand offset of 16 bits within instruction decomposition pub const POS_OP1: usize = 2; /// Bit position of the beginning of the flags in a Cairo instruction pub const POS_FLAGS: usize = 48; /// Destination refers to ap register pub const DST_AP: u8 = 0; /// First operand refers to ap register pub const OP0_AP: u8 = 0; /// Second operand is double indexing pub const OP1_DBL: u8 = 0; /// Second operand is immediate value pub const OP1_VAL: u8 = 1; /// Second operand refers to fp register pub const OP1_FP: u8 = 2; /// Second operand refers to ap register pub const OP1_AP: u8 = 4; /// Result is a single operand pub const RES_ONE: u8 = 0; /// Result is an addition pub const RES_ADD: u8 = 1; /// Result is a multiplication pub const RES_MUL: u8 = 2; /// Default increase of pc by adding instruction size pub const PC_SIZ: u8 = 0; /// Update pc by an absolute jump pub const PC_ABS: u8 = 1; /// Update pc by a relative jump pub const PC_REL: u8 = 2; /// Update pc by a conditional relative jump pub const PC_JNZ: u8 = 4; /// Update by 2 in call instructions or zero behaviour for other instructions pub const AP_Z2: u8 = 0; /// Update ap by adding a number of positions pub const AP_ADD: u8 = 1; /// Update ap by self increment pub const AP_ONE: u8 = 2; /// Operation code is a jump or an increment pub const OPC_JMP_INC: u8 = 0; /// Operation code is a call pub const OPC_CALL: u8 = 1; /// Operation code is a return pub const OPC_RET: u8 = 2; /// Operation code is an assert-equal pub const OPC_AEQ: u8 = 4; ================================================ FILE: core/src/inputs/mod.rs ================================================ #[derive(Default)] pub struct ProgramInputs {} ================================================ FILE: core/src/lib.rs ================================================ #![feature(array_chunks)] pub use core::ops::Range; pub use math::{ExtensionOf, FieldElement, StarkField}; pub mod word; pub use word::{ bias, FieldHelpers, FlagDecomposition, FlagGroupDecomposition, OffsetDecomposition, Word, }; // TODO: Make the field element configurable in the CLI //pub use math::fields::f128::BaseElement as Felt; pub mod field; pub use field::f252::{BaseElement as Felt, BigInt}; pub mod inputs; pub use inputs::ProgramInputs; pub mod flags; // MAIN TRACE LAYOUT // ----------------------------------------------------------------------------------------- // A. flags (16) : Decoded instruction flags // B. res (1) : Res value // C. mem_p (2) : Temporary memory pointers (ap and fp) // D. mem_a (4) : Memory addresses (pc, dst_addr, op0_addr, op1_addr) // E. mem_v (4) : Memory values (inst, dst, op0, op1) // F. offsets (3) : (off_dst, off_op0, off_op1) // G. derived (3) : (t0, t1, mul) // // A B C D E F G // ├xxxxxxxxxxxxxxxx|x|xx|xxxx|xxxx|xxx|xxx┤ // pub const FLAG_TRACE_OFFSET: usize = 0; pub const FLAG_TRACE_WIDTH: usize = 16; pub const FLAG_TRACE_RANGE: Range = range(FLAG_TRACE_OFFSET, FLAG_TRACE_WIDTH); pub const RES_TRACE_OFFSET: usize = 16; pub const RES_TRACE_WIDTH: usize = 1; pub const RES_TRACE_RANGE: Range = range(RES_TRACE_OFFSET, RES_TRACE_WIDTH); pub const MEM_P_TRACE_OFFSET: usize = 17; pub const MEM_P_TRACE_WIDTH: usize = 2; pub const MEM_P_TRACE_RANGE: Range = range(MEM_P_TRACE_OFFSET, MEM_P_TRACE_WIDTH); pub const MEM_A_TRACE_OFFSET: usize = 19; pub const MEM_A_TRACE_WIDTH: usize = 4; pub const MEM_A_TRACE_RANGE: Range = range(MEM_A_TRACE_OFFSET, MEM_A_TRACE_WIDTH); pub const MEM_V_TRACE_OFFSET: usize = 23; pub const MEM_V_TRACE_WIDTH: usize = 4; pub const MEM_V_TRACE_RANGE: Range = range(MEM_V_TRACE_OFFSET, MEM_V_TRACE_WIDTH); pub const OFF_X_TRACE_OFFSET: usize = 27; pub const OFF_X_TRACE_WIDTH: usize = 3; pub const OFF_X_TRACE_RANGE: Range = range(OFF_X_TRACE_OFFSET, OFF_X_TRACE_WIDTH); pub const DERIVED_TRACE_OFFSET: usize = 30; pub const DERIVED_TRACE_WIDTH: usize = 3; pub const DERIVED_TRACE_RANGE: Range = range(DERIVED_TRACE_OFFSET, DERIVED_TRACE_WIDTH); pub const SELECTOR_TRACE_OFFSET: usize = 33; pub const SELECTOR_TRACE_WIDTH: usize = 1; pub const SELECTOR_TRACE_RANGE: Range = range(SELECTOR_TRACE_OFFSET, SELECTOR_TRACE_WIDTH); pub const TRACE_WIDTH: usize = 34; // AUX TRACE LAYOUT (Memory) // ----------------------------------------------------------------------------------------- // A. a_m_prime (4) : Sorted memory address // B. v_m_prime (4) : Sorted memory values // C. p_m (4) : Permutation product (memory) // // A B C // ├xxxx|xxxx|xxxx┤ pub const A_M_PRIME_OFFSET: usize = 0; pub const A_M_PRIME_WIDTH: usize = 4; pub const V_M_PRIME_OFFSET: usize = 4; pub const V_M_PRIME_WIDTH: usize = 4; pub const P_M_OFFSET: usize = 8; pub const P_M_WIDTH: usize = 4; // AUX TRACE LAYOUT (Range check) // ----------------------------------------------------------------------------------------- // D. a_rc_prime (3) : Sorted offset values // E. p_rc (3) : Permutation product (range check) // // D E // ├xxx|xxx┤ // pub const A_RC_PRIME_OFFSET: usize = 12; pub const A_RC_PRIME_WIDTH: usize = 3; pub const P_RC_OFFSET: usize = 15; pub const P_RC_WIDTH: usize = 3; // Main column indices pub const AP: usize = MEM_P_TRACE_OFFSET; // Aux column indices pub const P_M_LAST: usize = P_M_OFFSET + P_M_WIDTH - 1; pub const A_RC_PRIME_FIRST: usize = A_RC_PRIME_OFFSET; pub const A_RC_PRIME_LAST: usize = A_RC_PRIME_OFFSET + 2; /// Returns a [Range] initialized with the specified `start` and with `end` set to `start` + `len`. pub const fn range(start: usize, len: usize) -> Range { Range { start, end: start + len, } } /// A structure to store program counter, allocation pointer and frame pointer #[derive(Clone, Copy, Debug)] pub struct RegisterState { /// Program counter: points to address in memory pub pc: Felt, /// Allocation pointer: points to first free space in memory pub ap: Felt, /// Frame pointer: points to the beginning of the stack in memory (for arguments) pub fp: Felt, } pub struct InstructionState { /// Instruction pub inst: Word, pub inst_size: Felt, /// Addresses pub dst_addr: Felt, pub op0_addr: Felt, pub op1_addr: Felt, /// Values pub dst: Option, pub op0: Option, pub op1: Option, /// Result pub res: Option, } impl RegisterState { /// Creates a new triple of pointers pub fn new>(pc: T, ap: T, fp: T) -> Self { RegisterState { pc: pc.into(), ap: ap.into(), fp: fp.into(), } } } impl InstructionState { /// Creates a new set instruction word and operand state pub fn new( inst: Word, inst_size: Felt, dst: Option, op0: Option, op1: Option, res: Option, dst_addr: Felt, op0_addr: Felt, op1_addr: Felt, ) -> Self { InstructionState { inst, inst_size, dst, op0, op1, res, dst_addr, op0_addr, op1_addr, } } } #[derive(PartialEq, Eq, Copy, Clone, Debug)] pub enum Builtin { Output(u64), RangeCheck, } ================================================ FILE: core/src/word/helpers.rs ================================================ // Modified from https://github.com/o1-labs/proof-systems use super::{super::StarkField, Felt}; //use winter_utils::AsBytes; pub trait FieldHelpers { /// Return field element as byte, if it fits. Otherwise returns least significant byte fn lsb(self) -> u8; /// Return pos-th 16-bit chunk as another field element fn chunk_u16(self, pos: usize) -> Felt; /// Return first 64 bits of the field element fn to_u64(self) -> u64; /// Return a field element in hexadecimal in little endian fn to_hex_le(self) -> String; /// Return a vector of field elements from a vector of i128 fn vec_to_field(vec: &[i128]) -> Vec; /// Return a vector of bits fn to_bits(self) -> Vec; } impl FieldHelpers for Felt { fn lsb(self) -> u8 { //self.as_bytes()[0] self.as_int().to_le_bytes()[0] } fn chunk_u16(self, pos: usize) -> Felt { //let bytes = self.as_bytes(); let bytes = self.as_int().to_le_bytes(); let chunk = u16::from(bytes[2 * pos]) + u16::from(bytes[2 * pos + 1]) * 2u16.pow(8); Felt::from(chunk) } fn to_u64(self) -> u64 { //let bytes = self.as_bytes(); let bytes = self.as_int().to_le_bytes(); let mut acc: u64 = 0; for i in 0..8 { acc += 2u64.pow(i * 8) * (bytes[i as usize] as u64); } acc } fn to_hex_le(self) -> String { let mut bytes = self.as_int().to_le_bytes(); bytes.reverse(); hex::encode(bytes) } fn vec_to_field(vec: &[i128]) -> Vec { vec.iter() .map(|i| { if *i < 0 { -Felt::from((-(*i)) as u64) } else { Felt::from((*i) as u64) } }) .collect() } fn to_bits(self) -> Vec { //self.as_bytes().iter().fold(vec![], |mut bits, byte| { let bytes = self.as_int().to_le_bytes(); bytes.iter().fold(vec![], |mut bits, byte| { let mut byte = *byte; for _ in 0..8 { bits.push(byte & 0x01 == 0x01); byte >>= 1; } bits }) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_field_to_bits() { let fe = Felt::from(256u32); let bits = fe.to_bits(); println!("{:?}", &bits[0..16]); } #[test] fn test_field_to_chunks() { let fe = Felt::from(0x480680017fff8000u64); let chunk = fe.chunk_u16(1); println!("chunk {:?}", chunk); println!("chunk2 {:?}", Felt::from(0x7fffu64)); assert_eq!(chunk, Felt::from(0x7fffu64)); } } ================================================ FILE: core/src/word/mod.rs ================================================ // Modified from https://github.com/o1-labs/proof-systems use super::{Felt, FieldElement}; use crate::flags::*; mod helpers; pub use helpers::FieldHelpers; /// A word for the runner. Some words are instructions (which fit inside a `u64`). Others are immediate values (any `F` element). #[derive(Clone, Copy, Debug)] pub struct Word(Felt); /// Returns an offset of 16 bits to its biased representation in the interval `[-2^15,2^15)` as a field element pub fn bias(offset: E) -> E { offset - E::from(2u16.pow(15u32)) // -2^15 + sum_(i=0..15) b_i * 2^i } impl Word { /// Creates a [Word] from a field element pub fn new(word: Felt) -> Word { Word(word) } /// Returns the content of the word as a field element pub fn word(&self) -> Felt { self.0 } } pub trait OffsetDecomposition { /// Returns the destination offset in biased representation fn off_dst(&self) -> F; /// Returns the first operand offset in biased representation fn off_op0(&self) -> F; /// Returns the second operand offset in biased representation fn off_op1(&self) -> F; } /// This trait contains methods that decompose a field element into [Word] components pub trait FlagDecomposition { /// Returns vector of 16 flags fn flags(&self) -> Vec; /// Returns i-th bit-flag fn flag_at(&self, pos: usize) -> F; /// Returns bit-flag for destination register as `F` fn f_dst_fp(&self) -> F { self.flag_at(0) } /// Returns bit-flag for first operand register as `F` fn f_op0_fp(&self) -> F { self.flag_at(1) } /// Returns bit-flag for immediate value for second register as `F` fn f_op1_val(&self) -> F { self.flag_at(2) } /// Returns bit-flag for frame pointer for second register as `F` fn f_op1_fp(&self) -> F { self.flag_at(3) } /// Returns bit-flag for allocation pointer for second regsiter as `F` fn f_op1_ap(&self) -> F { self.flag_at(4) } /// Returns bit-flag for addition operation in right side as `F` fn f_res_add(&self) -> F { self.flag_at(5) } /// Returns bit-flag for multiplication operation in right side as `F` fn f_res_mul(&self) -> F { self.flag_at(6) } /// Returns bit-flag for program counter update being absolute jump as `F` fn f_pc_abs(&self) -> F { self.flag_at(7) } /// Returns bit-flag for program counter update being relative jump as `F` fn f_pc_rel(&self) -> F { self.flag_at(8) } /// Returns bit-flag for program counter update being conditional jump as `F` fn f_pc_jnz(&self) -> F { self.flag_at(9) } /// Returns bit-flag for allocation counter update being a manual addition as `F` fn f_ap_add(&self) -> F { self.flag_at(10) } /// Returns bit-flag for allocation counter update being a self increment as `F` fn f_ap_one(&self) -> F { self.flag_at(11) } /// Returns bit-flag for operation being a call as `F` fn f_opc_call(&self) -> F { self.flag_at(12) } /// Returns bit-flag for operation being a return as `F` fn f_opc_ret(&self) -> F { self.flag_at(13) } /// Returns bit-flag for operation being an assert-equal as `F` fn f_opc_aeq(&self) -> F { self.flag_at(14) } /// Returns bit-flag for 16th position fn f15(&self) -> F { self.flag_at(15) } } pub trait FlagGroupDecomposition { /// Returns flagset for destination register fn dst_reg(&self) -> u8; /// Returns flagset for first operand register fn op0_reg(&self) -> u8; /// Returns flagset for second operand register fn op1_src(&self) -> u8; /// Returns flagset for result logics fn res_log(&self) -> u8; /// Returns flagset for program counter update fn pc_up(&self) -> u8; /// Returns flagset for allocation pointer update fn ap_up(&self) -> u8; /// Returns flagset for operation code fn opcode(&self) -> u8; } impl OffsetDecomposition for Word { fn off_dst(&self) -> Felt { // The least significant 16 bits bias(self.word().chunk_u16(POS_DST)) } fn off_op0(&self) -> Felt { // From the 32nd bit to the 17th bias(self.word().chunk_u16(POS_OP0)) } fn off_op1(&self) -> Felt { // From the 48th bit to the 33rd bias(self.word().chunk_u16(POS_OP1)) } } impl FlagDecomposition for Word { fn flags(&self) -> Vec { let mut flags = Vec::with_capacity(NUM_FLAGS); // The most significant 16 bits for i in 0..NUM_FLAGS { flags.push(self.flag_at(i)); } flags } fn flag_at(&self, pos: usize) -> Felt { Felt::from(self.word().to_bits()[POS_FLAGS + pos] as u32) } } impl FlagGroupDecomposition for Word { fn dst_reg(&self) -> u8 { // dst_reg = fDST_REG self.f_dst_fp().lsb() } fn op0_reg(&self) -> u8 { // op0_reg = fOP0_REG self.f_op0_fp().lsb() } fn op1_src(&self) -> u8 { // op1_src = 4*fOP1_AP + 2*fOP1_FP + fOP1_VAL 2 * (2 * self.f_op1_ap().lsb() + self.f_op1_fp().lsb()) + self.f_op1_val().lsb() } fn res_log(&self) -> u8 { // res_log = 2*fRES_MUL + fRES_ADD 2 * self.f_res_mul().lsb() + self.f_res_add().lsb() } fn pc_up(&self) -> u8 { // pc_up = 4*fPC_JNZ + 2*fPC_REL + fPC_ABS 2 * (2 * self.f_pc_jnz().lsb() + self.f_pc_rel().lsb()) + self.f_pc_abs().lsb() } fn ap_up(&self) -> u8 { // ap_up = 2*fAP_ONE + fAP_ADD 2 * self.f_ap_one().lsb() + self.f_ap_add().lsb() } fn opcode(&self) -> u8 { // opcode = 4*fOPC_AEQ + 2*fOPC_RET + fOPC_CALL 2 * (2 * self.f_opc_aeq().lsb() + self.f_opc_ret().lsb()) + self.f_opc_call().lsb() } } #[cfg(test)] mod tests { use super::Felt as F; #[test] fn test_biased() { assert_eq!(F::from(1u32), super::bias(F::from(0x8001u32))); assert_eq!(F::from(0u32), super::bias(F::from(0x8000u32))); println!("{:?} {:?}", -F::from(1u32), super::bias(F::from(0x7fffu32))); assert_eq!(-F::from(1u32), super::bias(F::from(0x7fffu32))); } } ================================================ FILE: examples/Cargo.toml ================================================ [package] name = "examples" version = "0.1.0" edition = "2021" rust-version = "1.57" [[bin]] name = "giza-examples" path = "src/main.rs" bench = false doctest = false [dependencies] air = { package = "giza-air", path = "../air", version = "0.1", default-features = false } prover = { package = "giza-prover", path = "../prover", version = "0.1", default-features = false } runner = { package = "giza-runner", path = "../runner", version = "0.1", default-features = false } giza_core = { package = "giza-core", path = "../core", version = "0.1", default-features = false } winterfell = { package = "winter-verifier", git = "https://github.com/maxgillett/winterfell", rev = "0aad6a5", features = ["std"], version = "0.4", default-features = false } ================================================ FILE: examples/src/main.rs ================================================ use air::{ProcessorAir, ProofOptions}; use giza_core::Felt; use runner::{Memory, Program}; fn main() { // %builtins output // from starkware.cairo.common.serialize import serialize_word // func main{output_ptr : felt*}(): // tempvar x = 10 // tempvar y = x + x // tempvar z = y * y + x // serialize_word(x) // serialize_word(y) // serialize_word(z) // return () // end // */ let instrs: Vec = vec![ Felt::from(0x400380007ffc7ffdu64), Felt::from(0x482680017ffc8000u64), Felt::from(1u64), Felt::from(0x208b7fff7fff7ffeu64), Felt::from(0x480680017fff8000u64), Felt::from(10u64), Felt::from(0x48307fff7fff8000u64), Felt::from(0x48507fff7fff8000u64), Felt::from(0x48307ffd7fff8000u64), Felt::from(0x480a7ffd7fff8000u64), Felt::from(0x48127ffb7fff8000u64), Felt::from(0x1104800180018000u64), -Felt::from(11u64), Felt::from(0x48127ff87fff8000u64), Felt::from(0x1104800180018000u64), -Felt::from(14u64), Felt::from(0x48127ff67fff8000u64), Felt::from(0x1104800180018000u64), -Felt::from(17u64), //Felt::from(0x208b7fff7fff7ffeu64), Felt::from(0x10780017fff7fffu64), // infinite loop ]; let mut mem = Memory::new(instrs); mem.write_pub(Felt::from(21u32), Felt::from(41u32)); // beginning of output mem.write_pub(Felt::from(22u32), Felt::from(44u32)); // end of output mem.write_pub(Felt::from(23u32), Felt::from(44u32)); // end of program // run the program to create an execution trace let mut program = Program::new(&mut mem, 5, 24); let trace = program.execute().unwrap(); // generate the proof of execution let proof_options = ProofOptions::with_proof_options(None, None, None, None, None); let (proof, pub_inputs) = prover::prove_trace(trace, &proof_options).unwrap(); let proof_bytes = proof.to_bytes(); println!("Proof size: {:.1} KB", proof_bytes.len() as f64 / 1024f64); // verify correct program execution match winterfell::verify::(proof, pub_inputs) { Ok(_) => println!("Execution verified"), Err(err) => println!("Failed to verify execution: {}", err), } } ================================================ FILE: program.json ================================================ ================================================ FILE: prover/Cargo.toml ================================================ [package] name = "giza-prover" version = "0.1.0" edition = "2021" rust-version = "1.57" [dependencies] air = { package = "giza-air", path = "../air", version = "0.1", default-features = false } prover = { package = "winter-prover", git = "https://github.com/maxgillett/winterfell", rev = "0aad6a5", version = "0.4", features = ["concurrent"], default-features = false } runner = { package = "giza-runner", path = "../runner", version = "0.1", default-features = false } giza_core = { package = "giza-core", path = "../core", version = "0.1", default-features = false } ================================================ FILE: prover/src/lib.rs ================================================ use air::{ProcessorAir, PublicInputs}; use giza_core::{Felt, RegisterState, MEM_A_TRACE_OFFSET, MEM_P_TRACE_OFFSET}; use prover::{Prover, Trace}; use runner::{ExecutionError, ExecutionTrace}; // EXPORTS // ================================================================================================ pub use air::{FieldExtension, HashFunction, ProofOptions}; pub use prover::StarkProof; // EXECUTOR // ================================================================================================ /// Proves an execution trace and returns the result together with a STARK-based proof /// of execution. pub fn prove_trace( trace: ExecutionTrace, options: &ProofOptions, ) -> Result<(StarkProof, PublicInputs), ExecutionError> { let prover = ExecutionProver::new(options.clone()); let public_inputs = prover.get_pub_inputs(&trace); let proof = prover.prove(trace).map_err(ExecutionError::ProverError)?; Ok((proof, public_inputs)) } // PROVER // ================================================================================================ pub struct ExecutionProver { options: ProofOptions, } impl ExecutionProver { pub fn new(options: ProofOptions) -> Self { Self { options } } } impl Prover for ExecutionProver { type BaseField = Felt; type Air = ProcessorAir; type Trace = ExecutionTrace; fn options(&self) -> &prover::ProofOptions { &self.options } fn get_pub_inputs(&self, trace: &ExecutionTrace) -> PublicInputs { let last_step = trace.num_steps - 1; let pc_init = trace.main_segment().get(MEM_A_TRACE_OFFSET, 0); let ap_init = trace.main_segment().get(MEM_P_TRACE_OFFSET, 0); let init = RegisterState::new(pc_init, ap_init, ap_init); let pc_fin = trace.main_segment().get(MEM_A_TRACE_OFFSET, last_step); let ap_fin = trace.main_segment().get(MEM_P_TRACE_OFFSET, last_step); let fin = RegisterState::new(pc_fin, ap_fin, ap_fin); let rc_min = trace.rc_min; let rc_max = trace.rc_max; let mem = trace.get_public_mem(); PublicInputs::new( init, fin, rc_min, rc_max, mem, trace.num_steps, trace.builtins.clone(), ) } } ================================================ FILE: runner/Cargo.toml ================================================ [package] name = "giza-runner" version = "0.1.0" edition = "2021" rust-version = "1.57" [lib] bench = false doctest = false [dependencies] air = { package = "giza-air", path = "../air", version = "0.1", default-features = false } giza_core = { package = "giza-core", path = "../core", version = "0.1", default-features = false } winterfell = { package = "winter-prover", git = "https://github.com/maxgillett/winterfell", rev = "0aad6a5", version = "0.4", features = ["concurrent"], default-features = false } serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" itertools = "0.10.3" hex = "0.4" pyo3 = { package = "pyo3", version = "0.16.3", features = ["auto-initialize"], optional = true } indicatif = {version = "*", features = ["rayon"]} rayon = "1.5.3" [features] hints = ["dep:pyo3"] ================================================ FILE: runner/src/cairo_interop.rs ================================================ /// Code for parsing the outputs of Starkware's cairo-runner. /// Note the following: /// - Field elements are encoded in little-endian byte order. /// - Cairo serializes field elements as 32 bytes (the program /// prime is assumed to be equal to the 252-bit Starkware prime). /// use crate::memory::Memory; use giza_core::{Builtin, Felt, RegisterState, Word}; use serde::{Deserialize, Serialize}; use std::fs::{metadata, File}; use std::io::{BufReader, Read}; use std::path::PathBuf; #[derive(Serialize, Deserialize)] struct CompiledProgram { builtins: Vec, data: Vec, prime: String, } /// Parses an execution trace outputted by the cairo-runner. /// e.g. cairo-runner --trace_file out/trace.bin pub fn read_trace_bin(path: &PathBuf) -> Vec { let mut f = File::open(&path).expect("no file found"); let metadata = metadata(&path).expect("unable to read metadata"); let length = metadata.len() as usize; // Buffer for register values let mut pc: [u8; 8] = Default::default(); let mut ap: [u8; 8] = Default::default(); let mut fp: [u8; 8] = Default::default(); let mut ptrs: Vec = vec![]; let mut bytes_read = 0; while bytes_read < length { bytes_read += f.read(&mut ap).unwrap(); bytes_read += f.read(&mut fp).unwrap(); bytes_read += f.read(&mut pc).unwrap(); let reg = RegisterState::new( u64::from_le_bytes(pc), u64::from_le_bytes(ap), u64::from_le_bytes(fp), ); ptrs.push(reg); } //print_registers(&ptrs); ptrs } /// Parses a memory dump outputted by the cairo-runner. /// e.g. cairo-runner --memory_file out/memory.bin pub fn read_memory_bin(mem_path: &PathBuf, program_path: &PathBuf) -> Memory { // Read memory trace let mut f = File::open(&mem_path).expect("Memory trace file not found"); let metadata = metadata(&mem_path).expect("Unable to read metadata"); let length = metadata.len() as usize; // Buffer for memory accesses let mut address: [u8; 8] = Default::default(); let mut value: [u8; 32] = Default::default(); let mut mem = Memory::new(vec![]).clone(); let mut bytes_read = 0; while bytes_read < length { bytes_read += f.read(&mut address).unwrap(); bytes_read += f.read(&mut value).unwrap(); mem.write( Felt::try_from(u64::from_le_bytes(address)).unwrap(), Felt::try_from(value).unwrap(), ); } // Read compiled program and set memory codelen (the length of the public memory) let file = File::open(&program_path).expect("Compiled program file not found"); let reader = BufReader::new(file); let p: CompiledProgram = serde_json::from_reader(reader).unwrap(); mem.set_codelen(p.data.len()); //print_memory(&mem); mem } pub fn read_builtins(program_path: &PathBuf, output_len: Option) -> Vec { // Read compiled program and set memory codelen (the length of the public memory) let file = File::open(&program_path).expect("Compiled program file not found"); let reader = BufReader::new(file); let p: CompiledProgram = serde_json::from_reader(reader).unwrap(); let builtins = p .builtins .iter() .filter_map(|b| match b.as_str() { "output" => Some(Builtin::Output(output_len.unwrap())), _ => None, }) .collect::>(); builtins } fn print_registers(reg: &[RegisterState]) { for (n, r) in reg.iter().enumerate() { println!("{} {} {} {}", n, r.pc, r.ap, r.fp,); } } fn print_memory(mem: &Memory) { for n in 0..mem.size() as usize { println!( "{} {}", n, mem.data[n].unwrap_or(Word::new(Felt::from(0u8))).word() ); } } #[cfg(test)] mod tests { use super::*; #[test] fn test_trace_bin() { let trace = read_trace_bin(&PathBuf::from("../tmp/trace.bin")); println!("{:?}", trace); } #[test] fn test_memory_bin() { let mem = read_memory_bin( &PathBuf::from("../tmp/memory.bin"), &PathBuf::from("../tmp/program.json"), ); println!("{:?}", mem.data); } } ================================================ FILE: runner/src/errors.rs ================================================ use winterfell::ProverError; #[derive(Debug)] pub enum ExecutionError { ProverError(ProverError), } ================================================ FILE: runner/src/hints.rs ================================================ use crate::memory::Memory; use crate::runner::Step; use giza_core::{Felt, StarkField, Word}; use pyo3::conversion::{FromPyObject, ToPyObject}; use pyo3::prelude::*; use pyo3::types::PyDict; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::convert::TryInto; #[derive(Default)] pub struct HintManager { pub hints: HashMap>, } impl HintManager { pub fn push_hint(&mut self, pc: u64, hint: Hint) { self.hints.entry(pc).or_default().push(hint); } pub fn get_hints(&self, pc: Felt) -> Option<&Vec> { let pc: u64 = pc.as_int().try_into().unwrap(); self.hints.get(&pc) } } #[derive(Serialize, Deserialize)] pub struct Hint { code: String, accessible_scopes: Vec, flow_tracking_data: Option, } impl Hint { pub fn new( code: String, accessible_scopes: Vec, flow_tracking_data: Option, ) -> Self { Hint { code, accessible_scopes, flow_tracking_data, } } } #[derive(Serialize, Deserialize)] pub struct FlowTrackingData { ap_tracking: ApTracking, reference_ids: HashMap, } #[derive(Serialize, Deserialize)] pub struct ApTracking { group: u64, offset: u64, } #[derive(Default, Debug)] pub struct MemoryUpdate(pub Vec<(u64, Word)>); /// Data structure containing all register and memory updates effected by hint execution #[derive(Default, Debug)] pub struct ExecutionEffect { pub pc: Felt, pub ap: Felt, pub fp: Felt, pub mem_updates: Option, } impl Hint { /// Run hint code in a Python environment, and return the aggregated effect /// on program state pub fn exec(&self, step: &Step) -> PyResult { // TODO: Import Cairo toolchain and monkey patch methods // (e.g. reference manager setter method) to track memory updates Python::with_gil(|py| { let locals = PyDict::new(py); locals.set_item( "pc", TryInto::::try_into(step.curr.pc.as_int()).unwrap(), )?; locals.set_item( "ap", TryInto::::try_into(step.curr.ap.as_int()).unwrap(), )?; locals.set_item( "fp", TryInto::::try_into(step.curr.fp.as_int()).unwrap(), )?; locals.set_item("memory", &*step.mem)?; locals.set_item("memory_updates", PyDict::new(py))?; py.run(self.code.as_str(), None, Some(&locals)) .expect("error executing hint code"); ExecutionEffect::from_locals(locals) }) } } impl ExecutionEffect { fn from_locals(locals: &PyDict) -> PyResult { let pc = locals.get_item("pc").unwrap().extract::()?; let ap = locals.get_item("ap").unwrap().extract::()?; let fp = locals.get_item("fp").unwrap().extract::()?; let mem_updates: Option = locals .get_item("memory_updates") .unwrap() .extract::() .ok(); Ok(ExecutionEffect { pc: Felt::from(pc), ap: Felt::from(ap), fp: Felt::from(fp), mem_updates, }) } } impl<'a> FromPyObject<'a> for MemoryUpdate { fn extract(dict: &PyAny) -> PyResult { let mut mem_update = MemoryUpdate::default(); for (key, val) in dict.downcast::()?.iter() { mem_update.0.push(( key.extract::()?, Word::new(Felt::from(val.extract::()?)), )); } Ok(mem_update) } } impl ToPyObject for Memory { fn to_object(&self, py: Python) -> PyObject { let dict = PyDict::new(py); dict.into() } } #[cfg(test)] mod tests { use super::*; use giza_core::{Felt, RegisterState}; #[test] fn test_hint_execution() { let mut memory = Memory::new(vec![]); memory.write(Felt::from(memory.size()), Felt::from(1u64)); memory.write(Felt::from(memory.size()), Felt::from(2u64)); println!("{}", memory); let step = Step::new( &mut memory, None, RegisterState::new(Felt::from(1u64), Felt::from(1u64), Felt::from(1u64)), ); let hint = Hint::new(String::from("pc = 2; ap = 5; memory[1] = 10"), vec![], None); let res = hint.exec(&step); println!("res {:?}", res); } } ================================================ FILE: runner/src/lib.rs ================================================ pub mod memory; pub use memory::Memory; pub mod runner; pub use runner::Program; #[cfg(feature = "hints")] pub mod hints; mod trace; pub use trace::ExecutionTrace; mod errors; pub use errors::ExecutionError; mod cairo_interop; ================================================ FILE: runner/src/memory.rs ================================================ // Modified from https://github.com/o1-labs/proof-systems use std::convert::TryInto; use std::fmt::{Display, Formatter, Result}; use std::ops::{Index, IndexMut}; use core::iter::repeat; use giza_core::{Felt, FieldHelpers, StarkField, Word}; /// This data structure stores the memory of the program #[derive(Clone)] pub struct Memory { /// length of the public memory codelen: usize, /// full memory vector, None if non initialized pub data: Vec>, } impl Index for Memory { type Output = Option; fn index(&self, idx: Felt) -> &Self::Output { let addr: u64 = idx.to_u64(); &self.data[addr as usize] } } impl IndexMut for Memory { fn index_mut(&mut self, idx: Felt) -> &mut Self::Output { let addr: u64 = idx.to_u64(); self.resize(addr); &mut self.data[addr as usize] } } impl Display for Memory { fn fmt(&self, f: &mut Formatter<'_>) -> Result { for i in 1..self.size() { // Visualize content of memory if let Some(elem) = self[Felt::from(i as u64)] { if writeln!(f, "{0:>6}: 0x{1:}", i, elem.word().to_hex_le()).is_err() { println!("Error while writing") } } else if writeln!(f, "{0:>6}: None", i).is_err() { println!("Error while writing") } } Ok(()) } } impl Memory { /// Create a new memory structure from a vector of field elements pub fn new(input: Vec) -> Memory { // Initialized with the public memory (compiled instructions only) let mut aux = vec![Felt::from(0u8)]; aux.extend(input); Memory { codelen: aux.len(), data: aux.into_iter().map(|i| Some(Word::new(i))).collect(), } } /// Get size of the public memory pub fn get_codelen(&self) -> usize { self.codelen } /// Set size of the public memory pub fn set_codelen(&mut self, len: usize) { self.codelen = len; } /// Get size of the full memory pub fn size(&self) -> u64 { self.data.len() as u64 } /// Resizes memory with enough additional None slots if necessary before writing or reading fn resize(&mut self, addr: u64) { if let Some(additional) = addr.checked_sub(self.size() - 1) { self.data.extend(repeat(None).take(additional as usize)); } } /// Write u64 element in memory address pub fn write(&mut self, addr: Felt, elem: Felt) { self[addr] = Some(Word::new(elem)); } /// Write u64 element in memory address pub fn write_pub(&mut self, addr: Felt, elem: Felt) { self.write(addr, elem); self.codelen += 1; } /// Read element in memory address pub fn read(&self, addr: Felt) -> Option { //self.resize(addr.to_u64()); // Resize if necessary self[addr].map(|x| x.word()) } /// Returns a list of all memory holes (defined as missing private memory /// accesses from the provided trace vec) /// TODO: Memory should be stored as a BTreeMap in data, not a Vec. pub fn get_holes(&self, vec: Vec) -> Vec { let mut accesses = vec .iter() .map(|x| TryInto::::try_into(x.as_int()).unwrap()) .collect::>(); accesses.sort_unstable(); let mut holes = vec![]; for s in accesses.windows(2) { match s[1] - s[0] { 0 | 1 => {} _ => { if s[0] > self.codelen as u64 { holes.extend((s[0] + 1..s[1]).map(|x| Felt::from(x)).collect::>()); } } } } holes } } #[cfg(test)] mod tests { use super::Felt as F; use super::*; use giza_core::{Felt, FieldHelpers, Word}; #[test] fn test_cairo_bytecode() { // This test starts with the public memory corresponding to a simple program // func main{}(): // tempvar x = 10; // return() // end // And checks that memory writing and reading works as expected by completing // the total memory of executing the program let instrs = vec![ F::from(0x480680017fff8000u64), F::from(10u64), F::from(0x208b7fff7fff7ffeu64), ]; let mut memory = Memory::new(instrs); memory.write(F::from(memory.size() as u64), F::from(7u64)); memory.write(F::from(memory.size() as u64), F::from(7u64)); memory.write(F::from(memory.size() as u64), F::from(10u64)); println!("{}", memory); // Check content of an address assert_eq!( memory.read(F::from(1u32)).unwrap(), F::from(0x480680017fff8000u64) ); // Check that the program contained 3 words assert_eq!(3, memory.get_codelen()); // Check we have 6 words, excluding the dummy entry assert_eq!(6, memory.size() - 1); memory.read(F::from(10u32)); } } ================================================ FILE: runner/src/runner.rs ================================================ // Modified from https://github.com/o1-labs/proof-systems use crate::errors::ExecutionError; use crate::memory::Memory; use crate::trace::ExecutionTrace; use giza_core::{flags::*, *}; #[cfg(feature = "hints")] use crate::hints::{ExecutionEffect as HintExecutionEffect, HintManager}; /// A data structure to store a current step of computation pub struct Step<'a> { pub mem: &'a Memory, pub curr: RegisterState, pub next: Option, #[cfg(feature = "hints")] hints: Option<&'a HintManager>, } impl<'a> Step<'a> { /// Creates a new execution step from a step index, a word, and current pointers pub fn new(mem: &'a Memory, ptrs: RegisterState) -> Step<'a> { Step { mem, curr: ptrs, next: None, } } /// Executes a step from the current registers and returns the instruction state pub fn execute(&mut self, write: bool) -> InstructionState { // Execute hints and apply changes #[cfg(feature = "hints")] self.execute_hints(); // Execute instruction let (op0_addr, mut op0) = self.set_op0(); let (op1_addr, mut op1, size) = self.set_op1(op0); let (dst_addr, mut dst) = self.set_dst(); let mut res = self.set_res(op0, op1, dst); let next_pc = self.next_pc(size, res, dst, op1); let (next_ap, next_fp, op0_update, op1_update, res_update, dst_update) = self.next_apfp(size, res, dst, dst_addr, op1_addr, write); if op0_update.is_some() { op0 = op0_update; } if op1_update.is_some() { op1 = op1_update; } if res_update.is_some() { res = res_update; } if dst_update.is_some() { dst = dst_update; } self.next = Some(RegisterState::new( next_pc.expect("Empty next program counter"), next_ap.expect("Empty next allocation pointer"), next_fp.expect("Empty next frame pointer"), )); InstructionState::new( self.inst(), size, dst, op0, op1, res, dst_addr, op0_addr, op1_addr, ) } #[cfg(feature = "hints")] fn set_hint_manager(&mut self, hints: &'a HintManager) { self.hints = hints; } #[cfg(feature = "hints")] fn execute_hints(&mut self) { if let Some(manager) = self.hints { for hint in manager.get_hints(self.curr.pc).into_iter().flatten() { let changes = hint.exec(&self).unwrap(); self.apply_hint_effects(changes); } } } #[cfg(feature = "hints")] fn apply_hint_effects(&mut self, res: HintExecutionEffect) { self.curr.pc = res.pc; self.curr.ap = res.ap; self.curr.fp = res.fp; if let Some(updates) = res.mem_updates { for (addr, elem) in updates.0.iter() { self.mem.write(Felt::from(*addr), elem.word()); } } } /// This function returns the current word instruction being executed fn inst(&mut self) -> Word { Word::new(self.mem.read(self.curr.pc).expect("pc points to None cell")) } /// This function computes the first operand address. /// Outputs: `(op0_addr, op0)` fn set_op0(&mut self) -> (Felt, Option) { let reg = match self.inst().op0_reg() { /*0*/ OP0_AP => self.curr.ap, // reads first word from allocated memory /*1*/ _ => self.curr.fp, // reads first word from input stack }; let op0_addr = reg + self.inst().off_op0(); let op0 = self.mem.read(op0_addr); (op0_addr, op0) } /// This function computes the second operand address and content and the instruction size /// Panics if the flagset `OP1_SRC` has more than 1 nonzero bit /// Inputs: `op0` /// Outputs: `(op1_addr, op1, size)` fn set_op1(&mut self, op0: Option) -> (Felt, Option, Felt) { let (reg, size) = match self.inst().op1_src() { /*0*/ OP1_DBL => (op0.expect("None op0 for OP1_DBL"), Felt::ONE), // double indexing, op0 should be positive for address /*1*/ OP1_VAL => (self.curr.pc, Felt::TWO), // off_op1 will be 1 and then op1 contains an immediate value /*2*/ OP1_FP => (self.curr.fp, Felt::ONE), /*4*/ OP1_AP => (self.curr.ap, Felt::ONE), _ => panic!("Invalid op1_src flagset"), }; let op1_addr = reg + self.inst().off_op1(); // apply second offset to corresponding register let op1 = self.mem.read(op1_addr); (op1_addr, op1, size) } /// This function computes the value of the result of the arithmetic operation /// Panics if a `jnz` instruction is used with an invalid format /// or if the flagset `RES_LOG` has more than 1 nonzero bit /// Inputs: `op0`, `op1` /// Outputs: `res` fn set_res(&mut self, op0: Option, op1: Option, dst: Option) -> Option { let res; if self.inst().pc_up() == PC_JNZ { /*4*/ // jnz instruction if self.inst().res_log() == RES_ONE /*0*/ && self.inst().opcode() == OPC_JMP_INC /*0*/ && self.inst().ap_up() != AP_ADD /* not 1*/ { // in the context of a jnz instruction, the res register is unused, so we repurpose // it to hold the value v = dst^(-1), which is used in the pc update constraint let dst = dst.expect("None dst after JNZ"); if dst == Felt::ZERO { res = Some(dst) } else { res = Some(dst.inv()); } } else { panic!("Invalid JNZ instruction"); } } else if self.inst().pc_up() == PC_SIZ /*0*/ || self.inst().pc_up() == PC_ABS /*1*/ || self.inst().pc_up() == PC_REL /*2*/ { // rest of types of updates // common increase || absolute jump || relative jump res = { match self.inst().res_log() { /*0*/ RES_ONE => op1, // right part is single operand /*1*/ RES_ADD => Some( op0.expect("None op0 after RES_ADD") + op1.expect("None op1 after RES_ADD"), ), // right part is addition /*2*/ RES_MUL => Some( op0.expect("None op0 after RES_MUL") * op1.expect("None op1 after RES_MUL"), ), // right part is multiplication _ => panic!("Invalid res_log flagset"), } }; } else { // multiple bits take value 1 panic!("Invalid pc_up flagset"); } res } /// This function computes the destination address /// Outputs: `(dst_addr, dst)` fn set_dst(&mut self) -> (Felt, Option) { let reg = match self.inst().dst_reg() { /*0*/ DST_AP => self.curr.ap, // read from stack /*1*/ _ => self.curr.fp, // read from parameters }; let dst_addr = reg + self.inst().off_dst(); let dst = self.mem.read(dst_addr); (dst_addr, dst) } /// This function computes the next program counter /// Panics if the flagset `PC_UP` has more than 1 nonzero bit /// Inputs: `size`, `res`, `dst`, `op1`, /// Outputs: `next_pc` fn next_pc( &mut self, size: Felt, res: Option, dst: Option, op1: Option, ) -> Option { match self.inst().pc_up() { /*0*/ PC_SIZ => Some(self.curr.pc + size), // common case, next instruction is right after the current one /*1*/ PC_ABS => Some(res.expect("None res after PC_ABS")), // absolute jump, next instruction is in res, /*2*/ PC_REL => Some(self.curr.pc + res.expect("None res after PC_REL")), // relative jump, go to some address relative to pc /*4*/ PC_JNZ => { // conditional relative jump (jnz) if dst == Some(Felt::ZERO) { // if condition false, common case Some(self.curr.pc + size) } else { // if condition true, relative jump with second operand Some(self.curr.pc + op1.expect("None op1 after PC_JNZ")) } } _ => panic!("Invalid pc_up flagset"), } } /// This function computes the next values of the allocation and frame pointers /// Panics if in a `call` instruction the flagset [AP_UP] is incorrect /// or if in any other instruction the flagset AP_UP has more than 1 nonzero bit /// or if the flagset `OPCODE` has more than 1 nonzero bit /// Inputs: `size`, `res`, `dst`, `dst_addr`, `op1_addr` /// Outputs: `(next_ap, next_fp, op0_update, op1_update, res_update, dst_update)` fn next_apfp( &mut self, size: Felt, res: Option, dst: Option, dst_addr: Felt, op1_addr: Felt, write: bool, ) -> ( Option, Option, Option, Option, Option, Option, ) { let (next_ap, next_fp); let mut op0_update = None; let mut op1_update = None; let mut res_update = None; let mut dst_update = None; if self.inst().opcode() == OPC_CALL { /*1*/ // "call" instruction if write { //self.mem.write(self.curr.ap, self.curr.fp); //self.mem // .write(self.curr.ap + Felt::ONE, self.curr.pc + size); } else { let expected_a = self.mem.read(self.curr.ap).unwrap(); let expected_b = self.mem.read(self.curr.ap + Felt::ONE).unwrap(); assert_eq!(expected_a, self.curr.fp); assert_eq!(expected_b, self.curr.pc + size); } dst_update = self.mem.read(self.curr.ap); op0_update = self.mem.read(self.curr.ap + Felt::ONE); // Update fp // pointer for next frame is after current fp and instruction after call next_fp = Some(self.curr.ap + Felt::TWO); // Update ap match self.inst().ap_up() { /*0*/ AP_Z2 => next_ap = Some(self.curr.ap + Felt::TWO), // two words were written so advance 2 positions _ => panic!("ap increment in call instruction"), }; } else if self.inst().opcode() == OPC_JMP_INC /*0*/ || self.inst().opcode() == OPC_RET /*2*/ || self.inst().opcode() == OPC_AEQ /*4*/ { // rest of types of instruction // jumps and increments || return || assert equal match self.inst().ap_up() { /*0*/ AP_Z2 => next_ap = Some(self.curr.ap), // no modification on ap /*1*/ AP_ADD => { // ap += should be larger than current ap next_ap = Some(self.curr.ap + res.expect("None res after AP_ADD")) } /*2*/ AP_ONE => next_ap = Some(self.curr.ap + Felt::ONE), // ap++ _ => panic!("Invalid ap_up flagset"), } match self.inst().opcode() { /*0*/ OPC_JMP_INC => next_fp = Some(self.curr.fp), // no modification on fp /*2*/ OPC_RET => next_fp = Some(dst.expect("None dst after OPC_RET")), // ret sets fp to previous fp that was in [ap-2] /*4*/ OPC_AEQ => { // The following conditional is a fix that is not explained in the whitepaper // The goal is to distinguish two types of ASSERT_EQUAL where one checks that // dst = res , but in order for this to be true, one sometimes needs to write // the res in mem(dst_addr) and sometimes write dst in mem(res_dir). The only // case where res can be None is when res = op1 and thus res_dir = op1_addr if res.is_none() { // res = dst if write { //self.mem // .write(op1_addr, dst.expect("None dst after OPC_AEQ")); } else { let expected_a = self.mem.read(op1_addr).unwrap(); assert_eq!(expected_a, dst.unwrap()); } op1_update = self.mem.read(op1_addr); res_update = self.mem.read(op1_addr); } else { // dst = res if write { //self.mem // .write(dst_addr, res.expect("None res after OPC_AEQ")); } else { let expected_a = self.mem.read(dst_addr).unwrap(); assert_eq!(expected_a, res.unwrap()); } dst_update = self.mem.read(dst_addr); } next_fp = Some(self.curr.fp); // no modification on fp } _ => { panic!("This case must never happen") } } } else { panic!("Invalid opcode flagset"); } ( next_ap, next_fp, op0_update, op1_update, res_update, dst_update, ) } } /// Trace-friendly record of registers and instruction state across /// all program execution steps pub struct State { pub flags: [Vec; FLAG_TRACE_WIDTH], pub res: [Vec; RES_TRACE_WIDTH], pub mem_p: [Vec; MEM_P_TRACE_WIDTH], pub mem_a: [Vec; MEM_A_TRACE_WIDTH], pub mem_v: [Vec; MEM_V_TRACE_WIDTH], pub offsets: [Vec; OFF_X_TRACE_WIDTH], } impl State { pub fn new(init_trace_len: usize) -> Self { let mut flags: Vec> = Vec::with_capacity(FLAG_TRACE_WIDTH); let mut res: Vec> = Vec::with_capacity(RES_TRACE_WIDTH); let mut mem_p: Vec> = Vec::with_capacity(MEM_P_TRACE_WIDTH); let mut mem_a: Vec> = Vec::with_capacity(MEM_A_TRACE_WIDTH); let mut mem_v: Vec> = Vec::with_capacity(MEM_V_TRACE_WIDTH); let mut offsets: Vec> = Vec::with_capacity(OFF_X_TRACE_WIDTH); for _ in 0..FLAG_TRACE_WIDTH { let column = Felt::zeroed_vector(init_trace_len); flags.push(column); } for _ in 0..RES_TRACE_WIDTH { let column = Felt::zeroed_vector(init_trace_len); res.push(column); } for _ in 0..MEM_P_TRACE_WIDTH { let column = Felt::zeroed_vector(init_trace_len); mem_p.push(column); } for _ in 0..MEM_A_TRACE_WIDTH { let column = Felt::zeroed_vector(init_trace_len); mem_a.push(column); } for _ in 0..MEM_V_TRACE_WIDTH { let column = Felt::zeroed_vector(init_trace_len); mem_v.push(column); } for _ in 0..OFF_X_TRACE_WIDTH { let column = Felt::zeroed_vector(init_trace_len); offsets.push(column); } State { flags: flags.try_into().unwrap(), res: res.try_into().unwrap(), mem_p: mem_p.try_into().unwrap(), mem_a: mem_a.try_into().unwrap(), mem_v: mem_v.try_into().unwrap(), offsets: offsets.try_into().unwrap(), } } pub fn set_register_state(&mut self, step: usize, s: RegisterState) { self.mem_a[0][step] = s.pc; self.mem_p[0][step] = s.ap; self.mem_p[1][step] = s.fp; } pub fn set_instruction_state(&mut self, step: usize, s: InstructionState) { // Flags let flags = s.inst.flags(); for i in 0..=15 { self.flags[i][step] = flags[i]; } // Result self.res[0][step] = s.res.unwrap_or(Felt::ZERO); // Instruction self.mem_v[0][step] = s.inst.word(); // Auxiliary values self.mem_v[1][step] = s.dst.unwrap_or(Felt::ZERO); self.mem_v[2][step] = s.op0.unwrap_or(Felt::ZERO); self.mem_v[3][step] = s.op1.unwrap_or(Felt::ZERO); // Operands self.mem_a[1][step] = s.dst_addr; self.mem_a[2][step] = s.op0_addr; self.mem_a[3][step] = s.op1_addr; // Offsets self.offsets[0][step] = s.inst.off_dst(); self.offsets[1][step] = s.inst.off_op0(); self.offsets[2][step] = s.inst.off_op1(); } } /// Stores all information needed to run a program pub struct Program<'a> { /// total number of steps steps: usize, /// full execution memory mem: &'a mut Memory, /// initial register state init: RegisterState, /// final register state fin: RegisterState, /// requested builtins builtins: Vec, /// hints #[cfg(feature = "hints")] hints: Option, } impl<'a> Program<'a> { /// Creates an execution from the public information (memory and initial pointers) #[cfg(feature = "hints")] pub fn new(mem: &mut Memory, pc: u64, ap: u64, hints: Option) -> Program { Program { steps: 0, mem, init: RegisterState::new(Felt::from(pc), Felt::from(ap), Felt::from(ap)), fin: RegisterState::new(Felt::ZERO, Felt::ZERO, Felt::ZERO), builtins: vec![], hints, } } #[cfg(not(feature = "hints"))] pub fn new(mem: &mut Memory, pc: u64, ap: u64) -> Program { Program { steps: 0, mem, init: RegisterState::new(Felt::from(pc), Felt::from(ap), Felt::from(ap)), fin: RegisterState::new(Felt::ZERO, Felt::ZERO, Felt::ZERO), builtins: vec![], } } /// Outputs the total number of steps of the execution carried out by the runner pub fn get_steps(&self) -> usize { self.steps } /// Outputs the final value of the pointers after the execution carried out by the runner pub fn get_final(&self) -> RegisterState { self.fin } /// This function simulates an execution of the program received as input /// and returns an execution trace pub fn execute(&mut self) -> Result { let mut state = State::new(self.mem.size() as usize); let mut n: usize = 0; let mut end = false; let mut curr = self.init; let mut next = curr; // keep executing steps until the end is reached while !end { // create current step of computation let mut step = Step::new(self.mem, next); curr = step.curr; #[cfg(feature = "hints")] step.set_hint_manager(self.hints.as_ref()); // execute current step and save state let inst_state = step.execute(true); state.set_register_state(n, curr); state.set_instruction_state(n, inst_state); n += 1; match step.next { None => end = true, _ => { next = step.next.expect("Empty next pointers"); if curr.ap.as_int() <= next.pc.as_int() { // if reading from unallocated memory, end end = true; } } } } self.fin = curr; self.steps = n; Ok(ExecutionTrace::new( n, &mut state, &self.mem, self.builtins.clone(), )) } } ================================================ FILE: runner/src/trace.rs ================================================ use crate::cairo_interop::{read_builtins, read_memory_bin, read_trace_bin}; use crate::memory::Memory; use crate::runner::{State, Step}; use giza_core::{ Builtin, Felt, FieldElement, StarkField, Word, AP, A_M_PRIME_WIDTH, A_RC_PRIME_WIDTH, MEM_A_TRACE_RANGE, MEM_A_TRACE_WIDTH, MEM_V_TRACE_RANGE, OFF_X_TRACE_RANGE, OFF_X_TRACE_WIDTH, P_M_WIDTH, P_RC_WIDTH, TRACE_WIDTH, V_M_PRIME_WIDTH, }; use winterfell::{Matrix, Trace, TraceLayout}; use indicatif::ParallelProgressIterator; use indicatif::ProgressIterator; use itertools::Itertools; use rayon::prelude::*; use std::path::PathBuf; pub struct ExecutionTrace { layout: TraceLayout, meta: Vec, trace: Matrix, pub memory: Memory, pub rc_min: u16, pub rc_max: u16, pub num_steps: usize, pub builtins: Vec, } /// A virtual column is composed of one or more subcolumns. struct VirtualColumn<'a, E: FieldElement> { subcols: &'a [Vec], } impl<'a, E: FieldElement> VirtualColumn<'a, E> { fn new(subcols: &'a [Vec]) -> Self { Self { subcols } } /// Pack subcolumns into a single output column: cycle through each subcolumn, appending /// a single value to the output column for each iteration step until exhausted. fn to_column(&self) -> Vec { let mut col: Vec = vec![]; for n in 0..self.subcols[0].len() { for subcol in self.subcols { col.push(subcol[n]); } } col } /// Split subcolumns into multiple output columns: for each subcolumn, output a single /// value to each output column, cycling through each output column until exhuasted. fn to_columns(&self, num_rows: &[usize]) -> Vec> { let mut n = 0; let mut cols: Vec> = vec![vec![]; num_rows.iter().sum()]; for (subcol, width) in self.subcols.iter().zip(num_rows) { for (elem, idx) in subcol.iter().zip((0..*width).cycle()) { cols[idx + n].push(*elem); } n += width; } cols } } struct Layouter<'a, E: FieldElement> { columns: &'a mut Vec>, frame_len: usize, } impl<'a, E: FieldElement> Layouter<'a, E> { fn new(columns: &'a mut Vec>, frame_len: usize) -> Self { Self { columns, frame_len } } /// Add one or more columns to the trace. The chunk size determines the number /// of subcolumn elements to place within each frame chunk (defaults to 1) /// starting from the top most row of the chunk. fn add_columns(&mut self, subcols: &[Vec], chunk_size: Option) { for subcol in subcols.iter() { let mut col = E::zeroed_vector(subcol.len()); for (col_chunk, subcol_chunk) in col .chunks_mut(self.frame_len) .zip(subcol.chunks(chunk_size.unwrap_or(1))) { for (n, elem) in subcol_chunk.iter().enumerate() { col_chunk[n] = *elem } } self.columns.push(col); } } /// Resize columns to next power of two fn resize_all(&mut self) { resize_to_pow2(&mut self.columns); } } impl ExecutionTrace { /// Builds an execution trace pub(super) fn new( num_steps: usize, state: &mut State, memory: &Memory, builtins: Vec, ) -> Self { // Compute the derived ("auxiliary") trace values: t0, t1, and mul. // Note that in a conditional jump instruction we substitute res with dst^{-1} // (see page 53 of the whitepaper). let mut t0 = vec![]; let mut t1 = vec![]; let mut mul = vec![]; for step in 0..num_steps { // TODO: Don't hardcode index values let f_pc_jnz = state.flags[9][step]; let dst = state.mem_v[1][step]; let res = state.res[0][step]; t0.push(f_pc_jnz * dst); // f_pc_jnz * dst t1.push(t0[step] * res); // t_0 * res mul.push(state.mem_v[2][step] * state.mem_v[3][step]); // op0 * op1 } // 1. Append dummy artificial accesses to mem_a and mem_v to fill memory holes. // These gaps are due to interaction with builtins, and they still need to be handled // elsewhere in the code for soundness. // 2. Append dummy (0,0) public memory values to mem_a and mem_v. // Note that we don't need to worry about precise placement (i.e. ensuring that they are // the final n entries in the columns), because these dummy values will extend into the // resized column cells. // TODO: We should also append dummy output (not just program) public memory, in case the // trace length is not already long enough to contain these values. let mut col_extension = memory.get_holes(VirtualColumn::new(&state.mem_a).to_column()); col_extension.extend(vec![Felt::ZERO; memory.get_codelen()]); for (n, col) in VirtualColumn::new(&[col_extension]) .to_columns(&[MEM_A_TRACE_WIDTH]) .iter() .enumerate() { state.mem_a[n].extend(col); state.mem_v[n].extend(Felt::zeroed_vector(col.len())); } // 1. Convert offsets into an unbiased representation by adding 2^15, so that values are // within [0, 2^16]. // 2. Fill gaps between sorted offsets so that we can compute the proper permutation // product column in the range check auxiliary segment (if we implemented Ord for Felt // we could achieve a speedup here) let b15 = Felt::from(2u8).exp(15u32.into()); let mut rc_column: Vec = VirtualColumn::new(&state.offsets) .to_column() .into_iter() .map(|x| x + b15) .collect(); let mut rc_sorted: Vec = rc_column .iter() .map(|x| x.as_int().try_into().unwrap()) .collect(); rc_sorted.sort_unstable(); let rc_min = rc_sorted.first().unwrap().clone(); let rc_max = rc_sorted.last().unwrap().clone(); for s in rc_sorted.windows(2).progress() { match s[1] - s[0] { 0 | 1 => {} _ => { rc_column.extend((s[0] + 1..s[1]).map(|x| Felt::from(x)).collect::>()); } } } let offsets = VirtualColumn::new(&[rc_column]).to_columns(&[3]); // This is hacky... We're adding a selector to the main trace to disable the Cairo // transition constraints for public memory (and any extended trace cells that were added // to ensure that that length is a power of two). If we instead used transition // exemptions, then proving/verifying time would be too slow for programs with a large // number of instructions. // // There are two methods that can be combined to avoid selectors: // - Transform traces so that they end in an inifite loop (use the instruction // 0x10780017fff7fffu64). // - Use a short bootloader program so that the number of transition exemptions is small // and doesn't harm performance. This bootloader will compute and expose a hash of the // private memory containing the program instructions to be run. let mut selector = vec![Felt::ONE; num_steps]; selector[num_steps - 1] = Felt::ZERO; // Layout the trace let mut columns: Vec> = Vec::with_capacity(TRACE_WIDTH); let mut layouter = Layouter::new(&mut columns, 1); layouter.add_columns(&state.flags, None); layouter.add_columns(&state.res, None); layouter.add_columns(&state.mem_p, None); layouter.add_columns(&state.mem_a, None); layouter.add_columns(&state.mem_v, None); layouter.add_columns(&offsets, None); layouter.add_columns(&[t0, t1, mul], None); layouter.add_columns(&[selector], None); layouter.resize_all(); Self { layout: TraceLayout::new( TRACE_WIDTH, &[12, 6], // aux_segment widths &[2, 1], // aux_segment rands ), meta: Vec::new(), trace: Matrix::new(columns), memory: memory.clone(), rc_min, rc_max, num_steps, builtins, } } /// Reconstructs the execution trace from file pub fn from_file( program_path: PathBuf, trace_path: PathBuf, memory_path: PathBuf, output_len: Option, ) -> ExecutionTrace { let mem = read_memory_bin(&memory_path, &program_path); let registers = read_trace_bin(&trace_path); let builtins = read_builtins(&program_path, output_len); let num_steps = registers.len(); let inst_states = registers .par_iter() .progress() .map(|ptrs| { let mut step = Step::new(&mem, *ptrs); step.execute(false) }) .collect::>(); let mut state = State::new(registers.len() + 1); for (n, (reg_state, inst_state)) in registers.iter().zip(inst_states).enumerate() { state.set_register_state(n, *reg_state); state.set_instruction_state(n, inst_state); } Self::new(num_steps, &mut state, &mem, builtins) } /// Return the program public memory pub fn get_program_mem(&self) -> (Vec, Vec>) { let addrs = (0..self.memory.get_codelen() as u64).collect::>(); let vals = self.memory.data[..self.memory.get_codelen()].to_vec(); (addrs, vals) } /// Return the output public memory pub fn get_output_mem(&self) -> (Vec, Vec>) { for builtin in self.builtins.iter() { if let Builtin::Output(len) = builtin { let ptr_start: u64 = self.main_segment().get_column(AP)[self.num_steps - 1] .as_int() .try_into() .unwrap(); let ptr_end = ptr_start + len; let addrs = (ptr_start..ptr_end).collect::>(); let vals = addrs .iter() .map(|i| self.memory.data[*i as usize]) .collect::>(); return (addrs, vals); } } return (vec![], vec![]); } /// Return the combined public memory pub fn get_public_mem(&self) -> (Vec, Vec>) { let (mut a, mut v) = self.get_program_mem(); let (out_a, out_v) = self.get_output_mem(); a.extend(out_a); v.extend(out_v); (a, v) } } impl Trace for ExecutionTrace { type BaseField = Felt; fn layout(&self) -> &TraceLayout { &self.layout } fn length(&self) -> usize { self.trace.num_rows() } fn meta(&self) -> &[u8] { &self.meta } fn main_segment(&self) -> &Matrix { &self.trace } fn build_aux_segment( &mut self, aux_segments: &[Matrix], rand_elements: &[E], ) -> Option> where E: FieldElement, { match aux_segments.len() { 0 => build_aux_segment_mem(self, rand_elements), 1 => build_aux_segment_rc(self, rand_elements), _ => None, } } } /// Write documentation fn build_aux_segment_mem(trace: &ExecutionTrace, rand_elements: &[E]) -> Option> where E: FieldElement + From, { let z = rand_elements[0]; let alpha = rand_elements[1]; // Pack main memory access trace columns into two virtual columns let main = trace.main_segment(); let (a, v) = [MEM_A_TRACE_RANGE, MEM_V_TRACE_RANGE] .iter() .map(|range| { VirtualColumn::new( &range .clone() .map(|i| main.get_column(i).to_vec()) .collect::>()[..], ) .to_column() }) .collect_tuple() .unwrap(); // Construct duplicate virtual columns sorted by memory access, with dummy public // memory addresses/values replaced by their true values let mut a_prime = vec![E::ZERO; a.len()]; let mut v_prime = vec![E::ZERO; a.len()]; let mut a_replaced = a.clone(); let mut v_replaced = v.clone(); let (pub_a, pub_v) = trace.get_public_mem(); let l = a.len() - pub_a.len() - 1; for (i, (n, x)) in pub_a.iter().copied().zip(pub_v).enumerate() { a_replaced[l + i] = Felt::from(n); v_replaced[l + i] = x.unwrap().word().into(); } let mut indices = (0..a.len()).collect::>(); indices.sort_by_key(|&i| a_replaced[i].as_int()); for (i, j) in indices.iter().copied().enumerate() { a_prime[i] = a_replaced[j].into(); v_prime[i] = v_replaced[j].into(); } // Construct virtual column of computed permutation products let mut p = vec![E::ZERO; trace.length() * MEM_A_TRACE_WIDTH]; let a_0: E = a[0].into(); let v_0: E = v[0].into(); p[0] = (z - (a_0 + alpha * v_0).into()) / (z - (a_prime[0] + alpha * v_prime[0]).into()); for i in (1..p.len()).progress() { let a_i: E = a[i].into(); let v_i: E = v[i].into(); p[i] = (z - (a_i + alpha * v_i).into()) * p[i - 1] / (z - (a_prime[i] + alpha * v_prime[i]).into()); } // Split virtual columns into separate auxiliary columns let mut aux_columns = VirtualColumn::new(&[a_prime, v_prime, p]).to_columns(&[ A_M_PRIME_WIDTH, V_M_PRIME_WIDTH, P_M_WIDTH, ]); resize_to_pow2(&mut aux_columns); Some(Matrix::new(aux_columns)) } /// Write documentation fn build_aux_segment_rc(trace: &ExecutionTrace, rand_elements: &[E]) -> Option> where E: FieldElement + From, { let z = rand_elements[0]; // Pack main offset trace columns into a single virtual column let main = trace.main_segment(); let a = VirtualColumn::new( &OFF_X_TRACE_RANGE .map(|i| main.get_column(i).to_vec()) .collect::>()[..], ) .to_column(); // Construct duplicate virtual column sorted by offset value let mut indices = (0..a.len()).collect::>(); indices.sort_by_key(|&i| a[i].as_int()); let a_prime = indices.iter().map(|x| a[*x].into()).collect::>(); // Construct virtual column of computed permutation products let mut p = vec![E::ZERO; trace.length() * OFF_X_TRACE_WIDTH]; let a_0: E = a[0].into(); p[0] = (z - a_0) / (z - a_prime[0]); for i in (1..p.len()).progress() { let a_i: E = a[i].into(); p[i] = (z - a_i) * p[i - 1] / (z - a_prime[i]); } // Split virtual columns into separate auxiliary columns let mut aux_columns = VirtualColumn::new(&[a_prime, p]).to_columns(&[A_RC_PRIME_WIDTH, P_RC_WIDTH]); resize_to_pow2(&mut aux_columns); Some(Matrix::new(aux_columns)) } /// Resize columns to next power of two fn resize_to_pow2(columns: &mut [Vec]) { let trace_len_pow2 = columns .iter() .map(|x| x.len().next_power_of_two()) .max() .unwrap(); for column in columns.iter_mut() { let last_value = column.last().copied().unwrap(); column.resize(trace_len_pow2, last_value); } } ================================================ FILE: rust-toolchain.toml ================================================ [toolchain] channel = "nightly" ================================================ FILE: wasm/.gitignore ================================================ node_modules/ pkg/ dist/ output.bin ================================================ FILE: wasm/Cargo.toml ================================================ [package] name = "giza-wasm" version = "0.1.0" edition = "2021" rust-version = "1.57" [lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "0.2.74" js-sys = "0.3.57" air = { package = "giza-air", path = "../air", version = "0.1", default-features = false } giza_core = { package = "giza-core", path = "../core", version = "0.1", default-features = false } winterfell = { package = "winter-verifier", git = "https://github.com/maxgillett/winterfell", rev = "0aad6a5", version = "0.4", default-features = false } winter-utils = { package = "winter-utils", git = "https://github.com/maxgillett/winterfell", rev = "0aad6a5", version = "0.4", default-features = false } bincode = "1.3.3" serde = { version = "1.0.136", features = ["derive"] } [profile.release] lto = true opt-level = 's' [package.metadata.wasm-pack.profile.release] wasm-opt = false ================================================ FILE: wasm/index.js ================================================ // Note that a dynamic `import` statement here is required due to // webpack/webpack#6615, but in theory `import { greet } from './pkg';` // will work here one day as well! const rust = import('./pkg'); // Load a Cairo proof import data from './output.bin'; rust .then(m => m.verify(data.data)) .catch(console.error); ================================================ FILE: wasm/package.json ================================================ { "scripts": { "build": "webpack", "serve": "webpack-dev-server" }, "devDependencies": { "@wasm-tool/wasm-pack-plugin": "1.6.0", "html-webpack-plugin": "^5.5.0", "text-encoding": "^0.7.0", "webpack": "^5.73.0", "webpack-cli": "^4.9.2", "webpack-dev-server": "^4.9.1" } } ================================================ FILE: wasm/src/lib.rs ================================================ use air::{ProcessorAir, PublicInputs}; use serde::{Deserialize, Serialize}; use winter_utils::{Deserializable, SliceReader}; use winterfell::StarkProof; use js_sys::Uint8Array; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = console)] fn log(s: &str); } #[wasm_bindgen] pub fn verify(buffer: &Uint8Array) { // Load proof and public inputs let b = buffer.to_vec(); let data: ProofData = bincode::deserialize(&b).unwrap(); let pub_inputs = PublicInputs::read_from(&mut SliceReader::new(&data.input_bytes[..])).unwrap(); let proof = StarkProof::from_bytes(&data.proof_bytes).unwrap(); // Verify execution match winterfell::verify::(proof, pub_inputs) { Ok(_) => log("Execution verified"), Err(err) => log(format!("Failed to verify execution: {}", err).as_str()), } } #[derive(Serialize, Deserialize)] struct ProofData { input_bytes: Vec, proof_bytes: Vec, } ================================================ FILE: wasm/webpack.config.js ================================================ const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const webpack = require('webpack'); const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin"); module.exports = { entry: './index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'index.js', }, plugins: [ new HtmlWebpackPlugin(), new WasmPackPlugin({ crateDirectory: path.resolve(__dirname, ".") }), // Have this example work in Edge which doesn't ship `TextEncoder` or // `TextDecoder` at this time. new webpack.ProvidePlugin({ TextDecoder: ['text-encoding', 'TextDecoder'], TextEncoder: ['text-encoding', 'TextEncoder'] }) ], experiments: { asyncWebAssembly: true, }, module: { rules: [ { test: /\.bin/, type: 'asset/inline', generator: { dataUrl: content => { return content; } } } ], }, mode: 'production', performance: { maxEntrypointSize: 2000000, maxAssetSize: 2000000, } };