├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── binary ├── Cargo.toml └── src │ ├── errors.rs │ ├── lib.rs │ └── utils.rs ├── bootloader-proof.bin ├── builtins ├── Cargo.toml └── src │ ├── bitwise │ └── mod.rs │ ├── ec_op │ └── mod.rs │ ├── ecdsa │ ├── mod.rs │ └── periodic.rs │ ├── lib.rs │ ├── pedersen │ ├── constants.rs │ ├── mod.rs │ └── periodic.rs │ ├── poseidon │ ├── mod.rs │ ├── params.rs │ └── periodic.rs │ ├── range_check │ └── mod.rs │ └── utils.rs ├── cli ├── Cargo.toml └── src │ └── main.rs ├── crypto ├── Cargo.toml ├── benches │ └── public_coin.rs └── src │ ├── hash │ ├── blake2s.rs │ ├── keccak.rs │ ├── mod.rs │ └── pedersen.rs │ ├── lib.rs │ ├── merkle │ ├── mixed.rs │ ├── mod.rs │ └── utils.rs │ ├── public_coin │ ├── cairo.rs │ ├── mod.rs │ └── solidity.rs │ └── utils.rs ├── darude.jpeg ├── example ├── air-input.json ├── air-private-input.json ├── air-public-input.json ├── array-sum.cairo ├── array-sum.json ├── array-sum.proof.saved ├── bootloader │ ├── air-private-input.json │ ├── air-public-input.json │ ├── bootloader-proof.bin │ ├── bootloader_compiled.json │ ├── memory.bin │ └── trace.bin ├── gen_poly.py ├── memory.bin ├── output │ ├── air-private-input.json │ ├── air-public-input.json │ ├── main.cairo │ ├── main_compiled.json │ ├── memory.bin │ └── trace.bin ├── pedersen │ ├── air-private-input-OLD.json │ ├── air-private-input.json │ ├── air-public-input-OLD.json │ ├── air-public-input.json │ ├── main.cairo │ ├── main_compiled.json │ ├── memory.bin │ └── trace.bin ├── test.py └── trace.bin ├── layouts ├── Cargo.toml ├── README.md └── src │ ├── lib.rs │ ├── plain │ ├── air.rs │ ├── mod.rs │ └── trace.rs │ ├── recursive │ ├── air.rs │ ├── mod.rs │ └── trace.rs │ ├── starknet │ ├── air.rs │ ├── mod.rs │ └── trace.rs │ └── utils.rs ├── prover.gif ├── rustfmt.toml ├── src ├── claims.rs ├── input.rs └── lib.rs └── verifier.gif /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /tmp 3 | *.proof -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sandstorm" 3 | description = "SHARP compatible Cairo prover" 4 | authors = ["Andrew Milson "] 5 | version = "0.2.0" 6 | edition = "2021" 7 | 8 | [workspace] 9 | members = ["layouts", "binary", "builtins", "crypto", "cli"] 10 | 11 | [features] 12 | default = [] 13 | asm = ["ministark/asm", "crypto/asm"] 14 | gpu = ["ministark/gpu"] 15 | parallel = [ 16 | "dep:rayon", 17 | "ark-std/parallel", 18 | "ark-ff/parallel", 19 | "ark-poly/parallel", 20 | "ministark/parallel", 21 | "ministark-gpu/parallel", 22 | "crypto/parallel", 23 | "layouts/parallel", 24 | ] 25 | 26 | [dependencies] 27 | ark-ff = "0.4" 28 | ark-std = "0.4" 29 | layouts = { path = "./layouts", package = "sandstorm-layouts" } 30 | crypto = { path = "./crypto", package = "sandstorm-crypto" } 31 | builtins = { path = "./builtins", package = "sandstorm-builtins" } 32 | binary = { path = "./binary", package = "sandstorm-binary" } 33 | ministark-gpu = { version = "0.3", git = "https://github.com/andrewmilson/ministark" } 34 | ministark = { git = "https://github.com/andrewmilson/ministark" } 35 | ruint = { version = "1.7", features = ["serde", "num-bigint"] } 36 | sha2 = "0.10" 37 | sha3 = "0.10" 38 | blake2 = "0.10" 39 | rand = "0.8" 40 | num-bigint = "0.4" 41 | ark-serialize = "0.4" 42 | ark-poly = "0.4" 43 | digest = "0.10" 44 | rayon = { version = "1.5", optional = true } 45 | 46 | [dev-dependencies] 47 | serde_json = "1.0" 48 | 49 | # taken from https://github.com/recmo/uint 50 | # Compilation profile for any non-workspace member. 51 | # Dependencies are optimized, even in a dev build. This improves dev performance 52 | # while having neglible impact on incremental build times. 53 | [profile.dev.package."*"] 54 | opt-level = 3 55 | 56 | [profile.release] 57 | codegen-units = 1 58 | lto = true 59 | 60 | [profile.bench] 61 | codegen-units = 1 62 | lto = true 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![Sandstorm](./darude.jpeg) 4 | 5 | # sandstorm 6 | 7 | **Cairo prover powered by [miniSTARK](https://github.com/andrewmilson/ministark/)** 8 | 9 | [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/andrewmilson/sandstorm/blob/main/LICENSE) 10 | [![stability-experimental](https://img.shields.io/badge/stability-experimental-orange.svg)](https://github.com/mkenney/software-guides/blob/master/STABILITY-BADGES.md#experimental) 11 | 12 |
13 | 14 | Sandstorm is a Cairo prover built on top of miniSTARK. The prover was built by reverse engineering [StarkWare's open source verifier](https://github.com/starkware-libs/starkex-contracts) and was used to submit the first independent proof to StarkWare's Ethereum verifier (see tweet [here](https://twitter.com/andrewmilson/status/1686292241990692864)). 15 | 16 | ## Demo - proving Cairo programs 17 | 18 | | ![Generating a proof](prover.gif) | ![Verifying a proof](verifier.gif) | 19 | |:--:|:--:| 20 | | *Generating the proof* | *Verifying the proof* 21 | 22 | In this demo, the prover has a Cairo program that appears to sum the values of an array. The prover runs the program with `cairo-run` to generate `trace.bin` (stores register values at each VM cycle) and `memory.bin` (stores memory address value pairs). The prover then runs `sandstorm prove` which builds a STARK execution trace and proof from `trace.bin`, `memory.bin` and the compiled program. 23 | 24 | 25 | The verifier, supplied with this proof and the original code, can run `sandstorm verify` to assert the program was executed correctly without having to run the program themselves. This is a small program for demonstration purposes and it'd probably be faster for the verifier to run the program themselves. Sandstorm is capable of generating proofs for much larger programs, where proof verification would run orders of magnitude faster than running the program. To run this demo locally: 26 | 27 | ```bash 28 | # 1. (optional) install Cairo and activate the venv 29 | # https://www.cairo-lang.org/docs/quickstart.html 30 | source ~/cairo_venv/bin/activate 31 | 32 | # 2. (optional) compile and run the Cairo program 33 | cairo-compile example/array-sum.cairo --proof_mode --output example/array-sum.json 34 | cairo-run --program example/array-sum.json \ 35 | --air_private_input example/air-private-input.json \ 36 | --air_public_input example/air-public-input.json \ 37 | --trace_file example/trace.bin \ 38 | --memory_file example/memory.bin \ 39 | --min_steps 128 \ 40 | --layout recursive \ 41 | --proof_mode 42 | 43 | # 3. generate the proof 44 | cargo +nightly run -p sandstorm-cli -r -F parallel -- \ 45 | --program example/array-sum.json \ 46 | --air-public-input example/air-public-input.json \ 47 | prove --air-private-input example/air-private-input.json \ 48 | --output example/array-sum.proof 49 | 50 | # 4. verify the proof 51 | cargo +nightly run -p sandstorm-cli -r -F parallel -- \ 52 | --program example/array-sum.json \ 53 | --air-public-input example/air-public-input.json \ 54 | verify --proof example/array-sum.proof 55 | ``` 56 | 57 |
58 | Proving Cairo programs with Goldilocks field 59 | 60 | ## Work in Progress - ~~Proving Cairo programs with Goldilocks field~~ 61 | 62 | ~~The goldilocks field is a magical 64-bit prime field that has very fast arithmetic. This field was discovered after StarkWare built their Solidity verifier for Cairo programs. As a result Cairo uses a much larger 252-bit prime field by default. Arithmetic in this 252-bit field is slow and it can be hard to practically utilize the storage provided by each field element.~~ 63 | 64 | ~~Sandstorm recently supported proving Cairo programs with the 64-bit Goldilocks field instead of StarkWare's default 252-bit field. On a M1 Max proof generation is 5x faster using the 64-bit Goldilocks field and only uses 1/4 of the overall memory when compared against Cairo's default 252-bit field. To run and prove with Goldilocks field locally:~~ 65 | 66 | ```bash 67 | # 1. install Cairo and activate the venv 68 | # https://www.cairo-lang.org/docs/quickstart.html 69 | source ~/cairo_venv/bin/ 70 | 71 | # 2. compile the Cairo program with Goldilocks field 72 | cairo-compile example/array-sum.cairo \ 73 | --prime 18446744069414584321 \ 74 | --output example/array-sum.json \ 75 | --proof_mode 76 | 77 | # 3. modify the Cairo runner to support Goldilocks 78 | # there are a few overly protective asserts that need to be commented out to get 79 | # things working. The location of these files is based on where you installed Cairo. 80 | # For me they were in `~/cairo_venv/lib/python3.9/site-packages/starkware/cairo/`. 81 | # Remove or comment out the following asserts: 82 | # - lang/vm/relocatable.py line 84 `assert value < 2 ** (8 * n_bytes - 1)` 83 | # - lang/compiler/encode.py line 38 `assert prime > 2 ** (3 * OFFSET_BITS + 16)` 84 | 85 | # 4. run the Cairo program 86 | cairo-run --program example/array-sum.json \ 87 | --trace_file example/trace.bin \ 88 | --memory_file example/memory.bin \ 89 | --min_steps 128 \ 90 | --proof_mode 91 | 92 | # 5. generate the proof 93 | cargo +nightly run -r -F parallel,asm -- \ 94 | --program example/array-sum.json \ 95 | prove --trace example/trace.bin \ 96 | --memory example/memory.bin \ 97 | --output example/array-sum.proof 98 | 99 | # 6. verify the proof 100 | cargo +nightly run -r -F parallel,asm -- \ 101 | --program example/array-sum.json \ 102 | verify --proof example/array-sum.proof 103 | ``` 104 |
105 | 106 |
107 | How Sandstorm works 108 | 109 | ## How Sandstorm works 110 | 111 | Those curious about the inner workings of Sandstorm can read the comments in [air.rs](layouts/src/starknet/air.rs#115). The comments expect some understanding of how STARK proofs are generated - if you need some background on this then [Anatomy of a STARK (part 4)](https://aszepieniec.github.io/stark-anatomy/) by [Alan Szepieniec](https://twitter.com/aszepieniec) is a great resource. The pseudo code in section 4.5 of the [Cairo whitepaper](https://eprint.iacr.org/2021/1063.pdf) provides a nice high level overview of how some pieces fit together. 112 |
113 | 114 |
115 | Troubleshooting 116 | 117 | ## Running on ARM64 118 | 119 | When running on ARM64 (i.e. Mac M1/M2), remove the `asm` feature from the example cli calls. 120 |
-------------------------------------------------------------------------------- /binary/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sandstorm-binary" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ark-ff = "0.4" 8 | serde = { version = "1.0", features = ["derive"] } 9 | serde_json = { version = "1.0", features = ["arbitrary_precision"] } 10 | ruint = { version = "1.7", features = ["serde", "num-bigint"] } 11 | num-bigint = { version = "0.4" } 12 | ministark-gpu = { version = "0.3", git = "https://github.com/andrewmilson/ministark" } 13 | ark-serialize = "0.4" 14 | num-traits = "0.2" 15 | ark-ec = "0.4" 16 | bincode = "1.2" 17 | 18 | [dev-dependencies] 19 | sha3 = "0.10" 20 | -------------------------------------------------------------------------------- /binary/src/errors.rs: -------------------------------------------------------------------------------- 1 | use ruint::aliases::U256; 2 | use std::error::Error; 3 | use std::fmt::Display; 4 | 5 | #[derive(Debug, Clone, Copy)] 6 | pub struct InvalidFieldElementError { 7 | pub value: U256, 8 | pub modulus: U256, 9 | } 10 | 11 | impl Display for InvalidFieldElementError { 12 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 13 | write!( 14 | f, 15 | "Invalid value: {}, must be less than the field modulus {}", 16 | self.value, self.modulus 17 | ) 18 | } 19 | } 20 | 21 | impl Error for InvalidFieldElementError {} 22 | -------------------------------------------------------------------------------- /binary/src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::InvalidFieldElementError; 2 | use crate::MemoryEntry; 3 | use alloc::vec::Vec; 4 | use ark_ff::PrimeField; 5 | use num_bigint::BigUint; 6 | use ruint::aliases::U256; 7 | use serde::de; 8 | use serde::Deserialize; 9 | use serde::Deserializer; 10 | use serde_json::value::Number; 11 | 12 | fn try_felt_from_u256(value: U256) -> Result { 13 | let modulus = U256::from::(F::MODULUS.into()); 14 | if value < modulus { 15 | Ok(From::::from(value.into())) 16 | } else { 17 | Err(InvalidFieldElementError { value, modulus }) 18 | } 19 | } 20 | 21 | /// Deserializes a hex string into a field element 22 | pub fn deserialize_hex_str_as_field_element<'de, D: Deserializer<'de>, F: PrimeField>( 23 | deserializer: D, 24 | ) -> Result { 25 | let num = deserialize_hex_str(deserializer)?; 26 | try_felt_from_u256(num).map_err(de::Error::custom) 27 | } 28 | 29 | /// Deserializes a hex string into a big integer 30 | pub fn deserialize_hex_str<'de, D: Deserializer<'de>>(deserializer: D) -> Result { 31 | let hex_str = String::deserialize(deserializer)?; 32 | hex_str.parse::().map_err(de::Error::custom) 33 | } 34 | 35 | /// Deserializes a list of memory entries of the form 36 | /// `{value: "0x...", address: ...}` 37 | pub fn deserialize_hex_str_memory_entries<'de, D: Deserializer<'de>, F: PrimeField>( 38 | deserializer: D, 39 | ) -> Result>, D::Error> { 40 | #[derive(Deserialize)] 41 | struct Entry { 42 | #[serde(deserialize_with = "deserialize_hex_str_as_field_element")] 43 | pub value: F, 44 | pub address: u32, 45 | } 46 | let v = Vec::deserialize(deserializer)?; 47 | Ok(v.into_iter() 48 | .map(|Entry { address, value }| MemoryEntry { address, value }) 49 | .collect()) 50 | } 51 | 52 | /// Deserializes a list of hex strings into a list of big integers 53 | pub fn deserialize_vec_hex_str<'de, D: Deserializer<'de>, F: PrimeField>( 54 | deserializer: D, 55 | ) -> Result, D::Error> { 56 | #[derive(Deserialize)] 57 | struct Wrapper( 58 | #[serde(deserialize_with = "deserialize_hex_str_as_field_element")] F, 59 | ); 60 | let v = Vec::deserialize(deserializer)?; 61 | Ok(v.into_iter().map(|Wrapper(a)| a).collect()) 62 | } 63 | 64 | /// Deserializes a JSON big integer 65 | /// This deserializer uses serde_json's arbitrary precision features to convert 66 | /// large numbers to a string and then converts that string to a [U256]. Note 67 | /// that you can't just deserialize a [U256] because it deserializes a large 68 | /// number from smaller 32 bit number chunks. TODO: check 69 | pub fn deserialize_big_uint<'de, D: Deserializer<'de>>(deserializer: D) -> Result { 70 | let num = Number::deserialize(deserializer)?.to_string(); 71 | num.parse::().map_err(de::Error::custom) 72 | } 73 | 74 | /// Deserializes a JSON list of big integers 75 | /// See docs for [deserialize_big_uint] to understand why this is needed. 76 | // TODO: consider removing 77 | pub fn _deserialize_vec_big_uint<'de, D: Deserializer<'de>>( 78 | deserializer: D, 79 | ) -> Result, D::Error> { 80 | #[derive(Deserialize)] 81 | struct Wrapper(#[serde(deserialize_with = "deserialize_big_uint")] U256); 82 | let v = Vec::deserialize(deserializer)?; 83 | Ok(v.into_iter().map(|Wrapper(a)| a).collect()) 84 | } 85 | 86 | /// Calculates the number of bytes per field element the 87 | /// same way as StarkWare's runner 88 | pub const fn field_bytes() -> usize { 89 | F::MODULUS_BIT_SIZE.next_multiple_of(8) as usize / 8 90 | } 91 | -------------------------------------------------------------------------------- /bootloader-proof.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewmilson/sandstorm/bf871325c12a05e8b51c9625b318ee50087234b3/bootloader-proof.bin -------------------------------------------------------------------------------- /builtins/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sandstorm-builtins" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | ark-ff = "0.4" 10 | ark-ec = "0.4" 11 | starknet-crypto = "0.6" 12 | ministark-gpu = { version = "0.3", git = "https://github.com/andrewmilson/ministark" } 13 | ministark = { git = "https://github.com/andrewmilson/ministark" } 14 | ark-poly = "0.4" 15 | num-bigint = "0.4" 16 | ruint = { version = "1.7", features = ["serde", "num-bigint"] } 17 | binary = { path = "../binary", package = "sandstorm-binary" } 18 | -------------------------------------------------------------------------------- /builtins/src/bitwise/mod.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | 3 | use binary::BitwiseInstance; 4 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 5 | use num_bigint::BigUint; 6 | use ruint::aliases::U256; 7 | 8 | #[derive(Clone, Debug)] 9 | pub struct InstanceTrace { 10 | pub instance: BitwiseInstance, 11 | pub x: Fp, 12 | pub y: Fp, 13 | pub x_and_y: Fp, 14 | pub x_xor_y: Fp, 15 | pub x_or_y: Fp, 16 | pub x_partition: Partition256, 17 | pub y_partition: Partition256, 18 | pub x_and_y_partition: Partition256, 19 | pub x_xor_y_partition: Partition256, 20 | } 21 | 22 | impl InstanceTrace { 23 | pub fn new(instance: BitwiseInstance) -> Self { 24 | let BitwiseInstance { x, y, .. } = instance; 25 | let x_and_y = x & y; 26 | let x_xor_y = x ^ y; 27 | let x_or_y = x | y; 28 | 29 | let x_partition = Partition256::new(x); 30 | let y_partition = Partition256::new(y); 31 | let x_and_y_partition = Partition256::new(x_and_y); 32 | let x_xor_y_partition = Partition256::new(x_xor_y); 33 | 34 | let x = BigUint::from(x).into(); 35 | let y = BigUint::from(y).into(); 36 | let x_and_y = BigUint::from(x_and_y).into(); 37 | let x_xor_y = BigUint::from(x_xor_y).into(); 38 | let x_or_y = BigUint::from(x_or_y).into(); 39 | 40 | Self { 41 | instance, 42 | x, 43 | y, 44 | x_and_y, 45 | x_xor_y, 46 | x_or_y, 47 | x_partition, 48 | y_partition, 49 | x_and_y_partition, 50 | x_xor_y_partition, 51 | } 52 | } 53 | } 54 | 55 | /// Partitions of a 64 bit integer 56 | /// For example to break up the 64 bit binary integer `v` with spacing 4: 57 | /// ```text 58 | /// v = 0b1100_1010_0110_1001_0101_0100_0100_0000_0100_0010_0001_0010_1111_0111_1100 59 | /// s0 = 0b0000_0000_0000_0001_0001_0000_0000_0000_0000_0000_0001_0000_0001_0001_0000 60 | /// s1 = 0b0000_0001_0001_0000_0000_0000_0000_0000_0000_0001_0000_0001_0001_0001_0000 61 | /// s2 = 0b0001_0000_0001_0000_0001_0001_0001_0000_0001_0000_0000_0000_0001_0001_0001 62 | /// s3 = 0b0001_0001_0000_0001_0000_0000_0000_0000_0000_0000_0000_0000_0001_0000_0001 63 | /// ``` 64 | /// note that `v = s0 * 2^0 + s1 * 2^1 + s2 * 2^2 + s3 * 2^3`. 65 | #[derive(Clone, Copy, Debug)] 66 | pub struct Partition64 { 67 | segments: [u64; SPACING], 68 | } 69 | 70 | impl Partition64 { 71 | const N_BITS: usize = u64::BITS as usize / SPACING; 72 | 73 | pub fn new(v: u64) -> Self { 74 | let mut segments = [0; SPACING]; 75 | for b in 0..Self::N_BITS { 76 | for (s, segment) in segments.iter_mut().enumerate() { 77 | let bit = (v >> (b * SPACING + s)) & 1; 78 | *segment |= bit << (b * SPACING); 79 | } 80 | } 81 | Self { segments } 82 | } 83 | } 84 | 85 | impl Deref for Partition64 { 86 | type Target = [u64; SPACING]; 87 | 88 | fn deref(&self) -> &Self::Target { 89 | &self.segments 90 | } 91 | } 92 | 93 | #[derive(Clone, Copy, Debug)] 94 | pub struct Partition128 { 95 | pub low: Partition64, 96 | pub high: Partition64, 97 | } 98 | 99 | impl Partition128 { 100 | pub fn new(v: u128) -> Self { 101 | Self { 102 | low: Partition64::new(v as u64), 103 | high: Partition64::new((v >> 64) as u64), 104 | } 105 | } 106 | } 107 | 108 | #[derive(Clone, Copy, Debug)] 109 | pub struct Partition256 { 110 | pub low: Partition128, 111 | pub high: Partition128, 112 | } 113 | 114 | impl Partition256 { 115 | pub fn new(v: U256) -> Self { 116 | // least significant first 117 | let [l0, l1, l2, l3] = v.into_limbs(); 118 | Self { 119 | low: Partition128::new(((l1 as u128) << 64) + l0 as u128), 120 | high: Partition128::new(((l3 as u128) << 64) + l2 as u128), 121 | } 122 | } 123 | } 124 | 125 | /// Dilutes input v by interspersing `SPACING - 1` many 0s between bits 126 | /// E.g. `SPACING=4, v=0b1111, diluted_v=0001000100010001` 127 | pub fn dilute(v: U256) -> U256 { 128 | let mut res = U256::ZERO; 129 | for i in 0..U256::BITS / SPACING { 130 | res.set_bit(i * SPACING, v.bit(i)); 131 | } 132 | res 133 | } 134 | 135 | #[cfg(test)] 136 | mod tests { 137 | use crate::bitwise::dilute; 138 | use ruint::aliases::U256; 139 | 140 | #[test] 141 | fn dilute_works() { 142 | let input = U256::from(0b101u32); 143 | 144 | assert_eq!(U256::from(0b0001_0000_0001u32), dilute::<4>(input)) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /builtins/src/ec_op/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::OnceLock; 2 | 3 | use crate::utils::curve::StarkwareCurve; 4 | use crate::utils::curve::calculate_slope; 5 | use crate::ecdsa::doubling_steps; 6 | use crate::ecdsa::DoublingStep; 7 | use crate::ecdsa::EcMadPartialStep; 8 | use ark_ec::short_weierstrass::Affine; 9 | use ark_ec::short_weierstrass::SWCurveConfig; 10 | use ark_ec::short_weierstrass::Projective; 11 | use ark_ec::CurveGroup; 12 | use ark_ec::Group; 13 | use binary::EcOpInstance; 14 | use ark_ff::Field; 15 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 16 | use num_bigint::BigUint; 17 | use ruint::aliases::U256; 18 | use ruint::uint; 19 | 20 | /// An ECDSA trace for a dummy instance 21 | /// Created once since creating new instance traces each time is expensive. 22 | static DUMMY_INSTANCE_TRACE: OnceLock = OnceLock::new(); 23 | 24 | /// Elliptic Curve operation instance trace for `r = p + m * q` with scalar `m` 25 | /// and points `p`, `q` and `r` on an elliptic curve 26 | #[derive(Clone, Debug)] 27 | pub struct InstanceTrace { 28 | pub instance: EcOpInstance, 29 | pub p: Affine, 30 | pub q: Affine, 31 | pub q_doubling_steps: Vec, 32 | pub r: Affine, 33 | pub r_steps: Vec, 34 | pub m: Fp, 35 | pub m_bit251_and_bit196_and_bit192: bool, 36 | pub m_bit251_and_bit196: bool, 37 | } 38 | 39 | impl InstanceTrace { 40 | pub fn new(instance: EcOpInstance) -> Self { 41 | let p_x = BigUint::from(instance.p_x).into(); 42 | let p_y = BigUint::from(instance.p_y).into(); 43 | let p = Affine::new(p_x, p_y); 44 | 45 | let q_x = BigUint::from(instance.q_x).into(); 46 | let q_y = BigUint::from(instance.q_y).into(); 47 | let q = Affine::new(q_x, q_y); 48 | let q_doubling_steps = doubling_steps(256, q.into()); 49 | 50 | let m = Fp::from(BigUint::from(instance.m)); 51 | let m_bit251 = instance.m.bit(251); 52 | let m_bit196 = instance.m.bit(196); 53 | let m_bit192 = instance.m.bit(192); 54 | let m_bit251_and_bit196_and_bit192 = m_bit251 && m_bit196 && m_bit192; 55 | let m_bit251_and_bit196 = m_bit251 && m_bit196; 56 | 57 | let r = mimic_ec_mad_air(m, q.into(), p.into()).unwrap().into(); 58 | let r_steps = gen_ec_mad_steps(m, q.into(), p.into()); 59 | assert_eq!(r, r_steps.last().unwrap().partial_sum); 60 | 61 | Self { 62 | instance, 63 | p, 64 | q, 65 | q_doubling_steps, 66 | m, 67 | m_bit251_and_bit196_and_bit192, 68 | m_bit251_and_bit196, 69 | r, 70 | r_steps, 71 | } 72 | } 73 | 74 | /// Creates a new dummy instance. 75 | /// Can be used for filling holes in an execution trace 76 | pub fn new_dummy(index: u32) -> Self { 77 | let mut dummy_trace = DUMMY_INSTANCE_TRACE 78 | .get_or_init(|| { 79 | let dummy_instance = gen_dummy_instance(0); 80 | Self::new(dummy_instance) 81 | }) 82 | .clone(); 83 | dummy_trace.instance.index = index; 84 | dummy_trace 85 | } 86 | } 87 | 88 | /// Generates a dummy EC op instance using `private_key = 1` 89 | fn gen_dummy_instance(index: u32) -> EcOpInstance { 90 | let p = crate::pedersen::constants::P0; 91 | let q = StarkwareCurve::GENERATOR; 92 | EcOpInstance { 93 | index, 94 | p_x: U256::from(BigUint::from(p.x)), 95 | p_y: U256::from(BigUint::from(p.y)), 96 | q_x: U256::from(BigUint::from(q.x)), 97 | q_y: U256::from(BigUint::from(q.y)), 98 | m: uint!(1_U256), 99 | } 100 | } 101 | 102 | /// Generates a list of the steps involved with `p + m * q` 103 | /// Different failure cases to [crate::ecdsa::gen_ec_mad_steps] 104 | fn gen_ec_mad_steps( 105 | m: Fp, 106 | mut q: Projective, 107 | p: Projective, 108 | ) -> Vec { 109 | let m = U256::from(BigUint::from(m)); 110 | let mut partial_sum = p; 111 | let mut res = Vec::new(); 112 | for i in 0..256 { 113 | let suffix = m >> i; 114 | let bit = suffix & uint!(1_U256); 115 | 116 | let mut slope = Fp::ZERO; 117 | let mut partial_sum_next = partial_sum; 118 | let partial_sum_affine = partial_sum.into_affine(); 119 | let q_affine = q.into_affine(); 120 | if bit == uint!(1_U256) { 121 | slope = calculate_slope(q_affine, partial_sum_affine).unwrap(); 122 | partial_sum_next += q; 123 | } 124 | 125 | res.push(EcMadPartialStep { 126 | partial_sum: partial_sum_affine, 127 | fixed_point: q_affine, 128 | suffix: Fp::from(BigUint::from(suffix)), 129 | x_diff_inv: (partial_sum_affine.x - q_affine.x).inverse().unwrap(), 130 | slope, 131 | }); 132 | 133 | partial_sum = partial_sum_next; 134 | q.double_in_place(); 135 | } 136 | res 137 | } 138 | 139 | /// Computes `p + m * q` using the same steps as the AIR 140 | /// Returns None if and only if the AIR errors. 141 | pub(crate) fn mimic_ec_mad_air( 142 | m: Fp, 143 | mut q: Projective, 144 | p: Projective, 145 | ) -> Option> { 146 | let mut m = U256::from(BigUint::from(m)); 147 | let mut partial_sum = p; 148 | #[allow(clippy::needless_range_loop)] 149 | while m != U256::ZERO { 150 | if Affine::from(partial_sum).x == Affine::from(q).x { 151 | return None; 152 | } 153 | let bit = m & uint!(1_U256); 154 | if bit == uint!(1_U256) { 155 | partial_sum += q; 156 | } 157 | q.double_in_place(); 158 | m >>= 1; 159 | } 160 | Some(partial_sum) 161 | } 162 | -------------------------------------------------------------------------------- /builtins/src/ecdsa/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::OnceLock; 2 | use ark_ec::CurveGroup; 3 | use ark_ec::Group; 4 | use ark_ec::short_weierstrass::Projective; 5 | use ark_ec::short_weierstrass::SWCurveConfig; 6 | use ark_ff::Zero; 7 | use binary::EcdsaInstance; 8 | use binary::Signature; 9 | use num_bigint::BigUint; 10 | use ruint::aliases::U256; 11 | use ruint::uint; 12 | use ark_ff::Field; 13 | use crate::pedersen::pedersen_hash; 14 | use crate::utils::curve::Fr; 15 | use crate::utils::curve::StarkwareCurve; 16 | use crate::utils::curve::calculate_slope; 17 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 18 | use ark_ec::short_weierstrass::Affine; 19 | use ark_ff::PrimeField; 20 | 21 | pub mod periodic; 22 | 23 | pub const SHIFT_POINT: Affine = super::pedersen::constants::P0; 24 | 25 | /// An ECDSA trace for a dummy instance 26 | /// Created once since creating new instance traces each time is expensive. 27 | static DUMMY_INSTANCE_TRACE: OnceLock = OnceLock::new(); 28 | 29 | /// Elliptic Curve multilpy-add (MAD) partial step 30 | #[derive(Clone, Debug)] 31 | pub struct EcMadPartialStep { 32 | pub partial_sum: Affine, 33 | pub fixed_point: Affine, 34 | pub suffix: Fp, 35 | pub slope: Fp, 36 | pub x_diff_inv: Fp, 37 | } 38 | 39 | #[derive(Clone, Copy, Debug)] 40 | pub struct DoublingStep { 41 | pub point: Affine, 42 | pub slope: Fp, 43 | } 44 | 45 | #[derive(Clone, Debug)] 46 | pub struct InstanceTrace { 47 | pub instance: EcdsaInstance, 48 | /// pubkey `Q` 49 | pub pubkey: Affine, 50 | pub pubkey_doubling_steps: Vec, 51 | pub w: Fp, 52 | /// Inverse of `w` in the base field 53 | pub w_inv: Fp, 54 | pub r: Fp, 55 | /// Inverse of `r` in the base field 56 | pub r_inv: Fp, 57 | pub r_point_slope: Fp, 58 | pub r_point_x_diff_inv: Fp, 59 | /// Message hash `z` 60 | pub message: Fp, 61 | pub message_inv: Fp, 62 | /// Point `B = z * G + r * Q` 63 | pub b: Affine, 64 | /// Slope between points `z * G` and `r * Q` 65 | pub b_slope: Fp, 66 | pub b_x_diff_inv: Fp, 67 | pub b_doubling_steps: Vec, 68 | /// steps for `z * G` where 69 | /// `G` is the elliptic curve generator point and 70 | /// `z` is the message hash 71 | pub zg_steps: Vec, 72 | /// steps for the scalar multiplication `r * Q` where 73 | /// `Q` is the pubkey point and 74 | /// `r` is the signature's `r` value 75 | pub rq_steps: Vec, 76 | /// steps for the scalar multiplication `w * B` where 77 | /// `B = z * G + r * Q` and 78 | /// `w` is the inverse of the signature's `s` value (NOTE: that's the 79 | /// inverse in the curve's scalar field) 80 | pub wb_steps: Vec, 81 | } 82 | 83 | impl InstanceTrace { 84 | // TODO: error handling 85 | pub fn new(instance: EcdsaInstance) -> Self { 86 | let message = Fp::from(BigUint::from(instance.message)); 87 | let pubkey_x = Fp::from(BigUint::from(instance.pubkey_x)); 88 | let r = Fp::from(BigUint::from(instance.signature.r)); 89 | let w = Fr::from(BigUint::from(instance.signature.w)); 90 | let s = w.inverse().unwrap(); 91 | let pubkey = verify(message, r, s, pubkey_x).expect("signature is invalid"); 92 | 93 | let shift_point = Projective::from(SHIFT_POINT); 94 | let generator = Projective::from(StarkwareCurve::GENERATOR); 95 | 96 | let zg = Affine::from(mimic_ec_mad_air(message.into(), generator, -shift_point).unwrap()); 97 | let qr = Affine::from(mimic_ec_mad_air(r.into(), pubkey.into(), shift_point).unwrap()); 98 | 99 | let b = (zg + qr).into_affine(); 100 | let b_slope = calculate_slope(zg, qr).unwrap(); 101 | let b_x_diff_inv = (zg.x - qr.x).inverse().unwrap(); 102 | let b_doubling_steps = doubling_steps(256, b.into()); 103 | let wb = Affine::from(mimic_ec_mad_air(w.into(), b.into(), shift_point).unwrap()); 104 | 105 | // Restrict generator max doublings to 250 to match the 106 | // periodic column used by AIR. 107 | let zg_steps = gen_ec_mad_steps::<250>(message.into(), generator, -shift_point); 108 | let rq_steps = gen_ec_mad_steps::<255>(r.into(), pubkey.into(), shift_point); 109 | let wb_steps = gen_ec_mad_steps::<255>(w.into(), b.into(), shift_point); 110 | 111 | assert_eq!(zg, zg_steps.last().unwrap().partial_sum); 112 | assert_eq!(qr, rq_steps.last().unwrap().partial_sum); 113 | assert_eq!(wb, wb_steps.last().unwrap().partial_sum); 114 | 115 | let w = Fp::from(BigUint::from(w)); 116 | let w_inv = w.inverse().unwrap(); 117 | let r_inv = r.inverse().unwrap(); 118 | let message_inv = message.inverse().unwrap(); 119 | 120 | let pubkey_doubling_steps = doubling_steps(256, pubkey.into()); 121 | 122 | let shift_point = Affine::from(shift_point); 123 | let r_point_slope = calculate_slope(wb, -shift_point).unwrap(); 124 | let r_point_x_diff_inv = (wb.x - (-shift_point).x).inverse().unwrap(); 125 | assert_eq!(r, (wb - shift_point).into_affine().x); 126 | 127 | Self { 128 | instance, 129 | pubkey, 130 | pubkey_doubling_steps, 131 | w, 132 | w_inv, 133 | r, 134 | r_inv, 135 | r_point_slope, 136 | r_point_x_diff_inv, 137 | message, 138 | message_inv, 139 | b, 140 | b_slope, 141 | b_x_diff_inv, 142 | b_doubling_steps, 143 | zg_steps, 144 | rq_steps, 145 | wb_steps, 146 | } 147 | } 148 | 149 | /// Creates a new dummy instance. 150 | /// Can be used for filling holes in an execution trace 151 | pub fn new_dummy(index: u32) -> Self { 152 | let mut dummy_trace = DUMMY_INSTANCE_TRACE 153 | .get_or_init(|| { 154 | let dummy_instance = gen_dummy_instance(0); 155 | Self::new(dummy_instance) 156 | }) 157 | .clone(); 158 | dummy_trace.instance.index = index; 159 | dummy_trace 160 | } 161 | } 162 | 163 | /// Generates a list of the steps involved with an EC multiply-add 164 | // TODO: NOTE: MAX_POINT_DOUBLINGS is a little decoupled but this is to do with 165 | // the periodic column construction. If this is done for i>251 the AIR with 166 | // error. 167 | fn gen_ec_mad_steps( 168 | x: BigUint, 169 | mut point: Projective, 170 | shift_point: Projective, 171 | ) -> Vec { 172 | let x = U256::from(x); 173 | // Assertions fail if the AIR will error 174 | assert!(x != U256::ZERO); 175 | assert!(x < uint!(2_U256).pow(uint!(251_U256))); 176 | let mut partial_sum = shift_point; 177 | let mut res = Vec::new(); 178 | for i in 0..256 { 179 | let suffix = x >> i; 180 | let bit = suffix & uint!(1_U256); 181 | 182 | let mut slope = Fp::ZERO; 183 | let mut partial_sum_next = partial_sum; 184 | let partial_sum_affine = partial_sum.into_affine(); 185 | let point_affine = point.into_affine(); 186 | if bit == uint!(1_U256) { 187 | slope = calculate_slope(point_affine, partial_sum_affine).unwrap(); 188 | partial_sum_next += point; 189 | } 190 | 191 | res.push(EcMadPartialStep { 192 | partial_sum: partial_sum_affine, 193 | fixed_point: point_affine, 194 | suffix: Fp::from(BigUint::from(suffix)), 195 | x_diff_inv: (partial_sum_affine.x - point_affine.x).inverse().unwrap(), 196 | slope, 197 | }); 198 | 199 | partial_sum = partial_sum_next; 200 | if i < MAX_POINT_DOUBLINGS { 201 | point.double_in_place(); 202 | } 203 | } 204 | res 205 | } 206 | 207 | pub fn doubling_steps(num_steps: usize, mut p: Projective) -> Vec { 208 | let mut res = Vec::new(); 209 | #[allow(clippy::needless_range_loop)] 210 | for _ in 0..num_steps { 211 | let p_affine = p.into_affine(); 212 | let slope = calculate_slope(p_affine, p_affine).unwrap(); 213 | res.push(DoublingStep { 214 | point: p_affine, 215 | slope, 216 | }); 217 | p.double_in_place(); 218 | } 219 | res 220 | } 221 | 222 | /// Generates a dummy signature using `private_key = 1` 223 | fn gen_dummy_instance(index: u32) -> EcdsaInstance { 224 | let privkey = Fr::ONE; 225 | let message_hash = BigUint::from(pedersen_hash(Fp::ONE, Fp::ZERO)); 226 | assert!(!message_hash.is_zero()); 227 | assert!(message_hash < BigUint::from(2u32).pow(251)); 228 | let message_hash = Fr::from(message_hash); 229 | 230 | for i in 1u64.. { 231 | let k = Fr::from(i); 232 | 233 | // Cannot fail because 0 < k < EC_ORDER and EC_ORDER is prime. 234 | let x = (StarkwareCurve::GENERATOR * k).into_affine().x; 235 | 236 | let r = BigUint::from(x); 237 | if r.is_zero() || r >= BigUint::from(2u32).pow(251) { 238 | // Bad value. This fails with negligible probability. 239 | continue; 240 | } 241 | 242 | let r = Fr::from(r); 243 | if (message_hash + r * privkey).is_zero() { 244 | // Bad value. This fails with negligible probability. 245 | continue; 246 | } 247 | 248 | let w = k / (message_hash + r * privkey); 249 | let w_int = BigUint::from(w); 250 | if w_int.is_zero() || w_int >= BigUint::from(2u32).pow(251) { 251 | // Bad value. This fails with negligible probability. 252 | continue; 253 | } 254 | 255 | let pubkey = (StarkwareCurve::GENERATOR * privkey).into_affine(); 256 | 257 | return EcdsaInstance { 258 | index, 259 | pubkey_x: U256::from(BigUint::from(pubkey.x)), 260 | message: U256::from(BigUint::from(message_hash)), 261 | signature: Signature { 262 | r: U256::from(BigUint::from(r)), 263 | w: U256::from(w_int), 264 | }, 265 | }; 266 | } 267 | 268 | unreachable!() 269 | } 270 | 271 | /// Verifies a signature 272 | /// Returns the associated public key if the signature is valid 273 | /// Returns None if the signature is invalid 274 | /// based on: https://github.com/starkware-libs/starkex-resources/blob/844ac3dcb1f735451457f7eecc6e37cd96d1cb2d/crypto/starkware/crypto/signature/signature.py#L192 275 | fn verify(msg_hash: Fp, r: Fp, s: Fr, pubkey_x: Fp) -> Option> { 276 | let w = s.inverse().unwrap(); 277 | let (y1, y0) = 278 | Affine::::get_ys_from_x_unchecked(pubkey_x).expect("not on the curve"); 279 | 280 | #[allow(clippy::tuple_array_conversions)] 281 | for pubkey_y in [y1, y0] { 282 | let pubkey = Affine::::new_unchecked(pubkey_x, pubkey_y); 283 | // Signature validation. 284 | // DIFF: original formula is: 285 | // x = (w*msg_hash)*EC_GEN + (w*r)*public_key 286 | // While what we implement is: 287 | // x = w*(msg_hash*EC_GEN + r*public_key). 288 | // While both mathematically equivalent, one might error while the other 289 | // doesn't, given the current implementation. 290 | // This formula ensures that if the verification errors in our AIR, it 291 | // errors here as well. 292 | let shift_point = Projective::from(SHIFT_POINT); 293 | let generator = StarkwareCurve::GENERATOR.into(); 294 | let zg = mimic_ec_mad_air(msg_hash.into(), generator, -shift_point).unwrap(); 295 | let rq = mimic_ec_mad_air(r.into(), pubkey.into(), shift_point).unwrap(); 296 | let wb = mimic_ec_mad_air(w.into(), zg + rq, shift_point).unwrap(); 297 | let x = (wb - shift_point).into_affine().x; 298 | if r == x { 299 | return Some(pubkey); 300 | } 301 | } 302 | 303 | None 304 | } 305 | 306 | /// Computes `m * point + shift_point` using the same steps like the AIR and 307 | /// Returns None if and only if the AIR errors. 308 | pub(crate) fn mimic_ec_mad_air( 309 | m: BigUint, 310 | mut point: Projective, 311 | shift_point: Projective, 312 | ) -> Option> { 313 | if !(1..Fp::MODULUS_BIT_SIZE).contains(&(m.bits() as u32)) { 314 | return None; 315 | } 316 | let mut m = U256::from(m); 317 | let mut partial_sum = shift_point; 318 | #[allow(clippy::needless_range_loop)] 319 | while m != U256::ZERO { 320 | if Affine::from(partial_sum).x == Affine::from(point).x { 321 | return None; 322 | } 323 | let bit = m & uint!(1_U256); 324 | if bit == uint!(1_U256) { 325 | partial_sum += point; 326 | } 327 | point.double_in_place(); 328 | m >>= 1; 329 | } 330 | Some(partial_sum) 331 | } 332 | -------------------------------------------------------------------------------- /builtins/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod bitwise; 2 | pub mod ec_op; 3 | pub mod ecdsa; 4 | pub mod pedersen; 5 | pub mod poseidon; 6 | pub mod range_check; 7 | pub mod utils; 8 | -------------------------------------------------------------------------------- /builtins/src/pedersen/mod.rs: -------------------------------------------------------------------------------- 1 | use ark_ec::CurveGroup; 2 | use ark_ec::Group; 3 | use ark_ec::short_weierstrass::Affine; 4 | use ark_ec::short_weierstrass::Projective; 5 | use ark_ff::BigInt; 6 | use ark_ff::Field; 7 | use ark_ff::PrimeField; 8 | use binary::PedersenInstance; 9 | use constants::P0; 10 | use constants::P1; 11 | use constants::P2; 12 | use constants::P3; 13 | use constants::P4; 14 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 15 | use num_bigint::BigUint; 16 | use ruint::aliases::U256; 17 | use ruint::uint; 18 | use crate::utils::curve::Fr; 19 | use crate::utils::curve::StarkwareCurve; 20 | use crate::utils::curve::calculate_slope; 21 | 22 | pub mod constants; 23 | pub mod periodic; 24 | 25 | /// Computes the Pedersen hash of a and b using StarkWare's parameters. 26 | /// The hash is defined by: 27 | /// shift_point + x_low * P_0 + x_high * P1 + y_low * P2 + y_high * P3 28 | /// where x_low is the 248 low bits of x, x_high is the 4 high bits of x and 29 | /// similarly for y. shift_point, P_0, P_1, P_2, P_3 are constant points 30 | /// generated from the digits of pi. 31 | pub fn pedersen_hash(a: Fp, b: Fp) -> Fp { 32 | let a = starknet_crypto::FieldElement::from_mont((a.0).0); 33 | let b = starknet_crypto::FieldElement::from_mont((b.0).0); 34 | let res = starknet_crypto::pedersen_hash(&a, &b); 35 | Fp::new_unchecked(BigInt(res.into_mont())) 36 | } 37 | 38 | /// Based on StarkWare's Python reference implementation: 39 | // TODO: remove 40 | #[deprecated] 41 | pub fn pedersen_hash_slow(a: Fp, b: Fp) -> Fp { 42 | let a = starknet_crypto::FieldElement::from_mont((a.0).0); 43 | let b = starknet_crypto::FieldElement::from_mont((b.0).0); 44 | let res = starknet_crypto::pedersen_hash(&a, &b); 45 | Fp::new_unchecked(BigInt(res.into_mont())) 46 | } 47 | 48 | fn process_element( 49 | x: Fp, 50 | p1: Projective, 51 | p2: Projective, 52 | ) -> Projective { 53 | assert_eq!(252, Fp::MODULUS_BIT_SIZE); 54 | let x: BigUint = x.into_bigint().into(); 55 | let shift = 252 - 4; 56 | let high_part = &x >> shift; 57 | let low_part = x - (&high_part << shift); 58 | let x_high = Fr::from(high_part); 59 | let x_low = Fr::from(low_part); 60 | p1 * x_low + p2 * x_high 61 | } 62 | 63 | #[derive(Clone, Copy, Debug)] 64 | pub struct ElementPartialStep { 65 | pub point: Affine, 66 | pub suffix: Fp, 67 | pub slope: Fp, 68 | } 69 | 70 | #[derive(Clone, Debug)] 71 | pub struct InstanceTrace { 72 | pub instance: PedersenInstance, 73 | pub output: Fp, 74 | pub a_steps: Vec, 75 | pub b_steps: Vec, 76 | pub a_bit251_and_bit196_and_bit192: bool, 77 | pub a_bit251_and_bit196: bool, 78 | pub b_bit251_and_bit196_and_bit192: bool, 79 | pub b_bit251_and_bit196: bool, 80 | } 81 | 82 | impl InstanceTrace { 83 | pub fn new(instance: PedersenInstance) -> Self { 84 | let PedersenInstance { a, b, .. } = instance; 85 | let a = Fp::from(BigUint::from(a)); 86 | let b = Fp::from(BigUint::from(b)); 87 | 88 | let a_p0 = P0; 89 | let a_p1 = P1; 90 | let a_p2 = P2; 91 | let a_steps = gen_element_steps(a, a_p0, a_p1, a_p2); 92 | 93 | let b_p0 = (a_p0 + process_element(a, a_p1.into(), a_p2.into())).into(); 94 | let b_p1 = P3; 95 | let b_p2 = P4; 96 | // check out initial value for the second input is correct 97 | assert_eq!(a_steps.last().unwrap().point, b_p0); 98 | let b_steps = gen_element_steps(b, b_p0, b_p1, b_p2); 99 | 100 | // check the expected output matches 101 | let output = pedersen_hash(a, b); 102 | assert_eq!(output, b_steps.last().unwrap().point.x); 103 | 104 | let a_bit251 = instance.a.bit(251); 105 | let a_bit196 = instance.a.bit(196); 106 | let a_bit192 = instance.a.bit(192); 107 | let a_bit251_and_bit196_and_bit192 = a_bit251 && a_bit196 && a_bit192; 108 | let a_bit251_and_bit196 = a_bit251 && a_bit196; 109 | 110 | let b_bit251 = instance.b.bit(251); 111 | let b_bit196 = instance.b.bit(196); 112 | let b_bit192 = instance.b.bit(192); 113 | let b_bit251_and_bit196_and_bit192 = b_bit251 && b_bit196 && b_bit192; 114 | let b_bit251_and_bit196 = b_bit251 && b_bit196; 115 | 116 | Self { 117 | instance, 118 | output, 119 | a_steps, 120 | b_steps, 121 | a_bit251_and_bit196_and_bit192, 122 | a_bit251_and_bit196, 123 | b_bit251_and_bit196_and_bit192, 124 | b_bit251_and_bit196, 125 | } 126 | } 127 | } 128 | 129 | fn gen_element_steps( 130 | x: Fp, 131 | p0: Affine, 132 | p1: Affine, 133 | p2: Affine, 134 | ) -> Vec { 135 | // generate our constant points 136 | let mut constant_points = Vec::new(); 137 | let mut p1_acc = Projective::from(p1); 138 | for _ in 0..252 - 4 { 139 | constant_points.push(p1_acc); 140 | p1_acc.double_in_place(); 141 | } 142 | let mut p2_acc = Projective::from(p2); 143 | for _ in 0..4 { 144 | constant_points.push(p2_acc); 145 | p2_acc.double_in_place(); 146 | } 147 | 148 | // generate partial sums 149 | let x_int = U256::from::(x.into()); 150 | let mut partial_point = Projective::from(p0); 151 | let mut res = Vec::new(); 152 | #[allow(clippy::needless_range_loop)] 153 | for i in 0..256 { 154 | let suffix = x_int >> i; 155 | let bit = suffix & uint!(1_U256); 156 | 157 | let mut slope: Fp = Fp::ZERO; 158 | let mut partial_point_next = partial_point; 159 | let partial_point_affine = partial_point.into_affine(); 160 | if bit == uint!(1_U256) { 161 | let constant_point = constant_points[i]; 162 | slope = calculate_slope(constant_point.into(), partial_point_affine).unwrap(); 163 | partial_point_next += constant_point; 164 | } 165 | 166 | res.push(ElementPartialStep { 167 | point: partial_point_affine, 168 | suffix: Fp::from(BigUint::from(suffix)), 169 | slope, 170 | }); 171 | 172 | partial_point = partial_point_next; 173 | } 174 | 175 | res 176 | } 177 | 178 | #[cfg(test)] 179 | mod tests { 180 | use crate::pedersen::pedersen_hash; 181 | use ark_ff::MontFp as Fp; 182 | 183 | #[test] 184 | fn hash_example0_works() { 185 | // Example source: 186 | // https://github.com/starkware-libs/starkex-resources/blob/master/crypto/starkware/crypto/signature/signature_test_data.json#L87 187 | let a = Fp!("1740729136829561885683894917751815192814966525555656371386868611731128807883"); 188 | let b = Fp!("919869093895560023824014392670608914007817594969197822578496829435657368346"); 189 | 190 | let output = pedersen_hash(a, b); 191 | 192 | assert_eq!( 193 | Fp!("1382171651951541052082654537810074813456022260470662576358627909045455537762"), 194 | output 195 | ) 196 | } 197 | 198 | #[test] 199 | fn hash_example1_works() { 200 | // Example source: 201 | // https://github.com/starkware-libs/starkex-resources/blob/master/crypto/starkware/crypto/signature/signature_test_data.json#L92 202 | let a = Fp!("2514830971251288745316508723959465399194546626755475650431255835704887319877"); 203 | let b = Fp!("3405079826265633459083097571806844574925613129801245865843963067353416465931"); 204 | 205 | let output = pedersen_hash(a, b); 206 | 207 | assert_eq!( 208 | Fp!("2962565761002374879415469392216379291665599807391815720833106117558254791559"), 209 | output 210 | ) 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /builtins/src/poseidon/mod.rs: -------------------------------------------------------------------------------- 1 | //! Poseidon builtin. Reference implementation (used by StarkWare): 2 | //! 3 | 4 | use std::iter::zip; 5 | use ark_ff::MontFp as Fp; 6 | use binary::PoseidonInstance; 7 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 8 | pub mod params; 9 | pub mod periodic; 10 | use self::params::FULL_ROUND_KEYS_1ST_HALF; 11 | use self::params::MDS_MATRIX; 12 | use self::params::NUM_FULL_ROUNDS; 13 | use self::params::NUM_PARTIAL_ROUNDS; 14 | use self::params::PARTIAL_ROUND_KEYS; 15 | use self::params::ROUND_KEYS; 16 | use crate::poseidon::params::FULL_ROUND_KEYS_2ND_HALF; 17 | use crate::poseidon::params::PARTIAL_ROUND_KEYS_OPTIMIZED; 18 | use crate::utils::Mat3x3; 19 | use ark_ff::Field; 20 | use num_bigint::BigUint; 21 | 22 | /// Stores the states within a full round 23 | #[derive(Clone, Copy, Debug)] 24 | pub struct FullRoundStates { 25 | /// State after adding round keys 26 | pub after_add_round_keys: [Fp; 3], 27 | /// State after applying the S-Box function 28 | pub after_apply_s_box: [Fp; 3], 29 | /// State after multiplication by the MDS matrix 30 | pub after_mds_mul: [Fp; 3], 31 | } 32 | 33 | /// Stores the states within a partial round 34 | #[derive(Clone, Copy, Debug)] 35 | pub struct PartialRoundStates { 36 | /// State after adding round keys 37 | pub after_add_round_key: Fp, 38 | } 39 | 40 | #[derive(Clone, Debug)] 41 | pub struct PartialRoundsState { 42 | pub margin_full_to_partial0: Fp, 43 | pub margin_full_to_partial1: Fp, 44 | pub margin_full_to_partial2: Fp, 45 | } 46 | 47 | #[derive(Clone, Debug)] 48 | pub struct InstanceTrace { 49 | pub instance: PoseidonInstance, 50 | pub input0: Fp, 51 | pub input1: Fp, 52 | pub input2: Fp, 53 | pub output0: Fp, 54 | pub output1: Fp, 55 | pub output2: Fp, 56 | pub full_round_states_1st_half: Vec, 57 | pub full_round_states_2nd_half: Vec, 58 | pub partial_round_states: Vec, 59 | // pub partial_rounds_state: PartialRoundsState, 60 | } 61 | 62 | impl InstanceTrace { 63 | pub fn new(instance: PoseidonInstance) -> Self { 64 | let input0 = Fp::from(BigUint::from(instance.input0)); 65 | let input1 = Fp::from(BigUint::from(instance.input1)); 66 | let input2 = Fp::from(BigUint::from(instance.input2)); 67 | 68 | let state = [input0, input1, input2]; 69 | let full_round_states_1st_half = 70 | gen_half_full_round_states(state, FULL_ROUND_KEYS_1ST_HALF); 71 | 72 | // set state to last state of 1st full rounds (aka after the MDS multiplication) 73 | let mut state = full_round_states_1st_half.last().unwrap().after_mds_mul; 74 | 75 | let mut partial_round_states = Vec::new(); 76 | for round_key in PARTIAL_ROUND_KEYS_OPTIMIZED { 77 | // Source: https://github.com/CryptoExperts/poseidon/blob/main/sage/poseidon_variant.sage#L127 78 | state[2] += round_key; 79 | partial_round_states.push(PartialRoundStates { 80 | after_add_round_key: state[2], 81 | }); 82 | state[2] = state[2].pow([3]); 83 | state = Mat3x3(MDS_MATRIX) * state; 84 | } 85 | 86 | // modify first full round keys to optimized variant 87 | // TODO: this is a bit random having these constants here. 88 | // TODO: improve docs and document optimized version. Poseidon paper section B? 89 | let mut full_round_keys_2nd_half = FULL_ROUND_KEYS_2ND_HALF; 90 | full_round_keys_2nd_half[0] = [ 91 | Fp!("2841653098167170594677968593255398661749780759922623066311132183067080032372"), 92 | Fp!("3013664908435951456462052676857400233978317167153628607151632758126998548956"), 93 | Fp!("1580909581709481477620907470438960344056357690169203419381231226301063390430"), 94 | ]; 95 | 96 | let full_round_states_2nd_half = 97 | gen_half_full_round_states(state, full_round_keys_2nd_half); 98 | // set state to last state of the last round (aka after the MDS multiplication) 99 | let final_state = full_round_states_2nd_half.last().unwrap().after_mds_mul; 100 | assert_eq!(permute([input0, input1, input2]), final_state); 101 | let [output0, output1, output2] = final_state; 102 | 103 | Self { 104 | instance, 105 | input0, 106 | input1, 107 | input2, 108 | output0, 109 | output1, 110 | output2, 111 | full_round_states_1st_half, 112 | full_round_states_2nd_half, 113 | partial_round_states, 114 | } 115 | } 116 | } 117 | 118 | fn gen_half_full_round_states( 119 | mut state: [Fp; 3], 120 | round_keys: [[Fp; 3]; NUM_FULL_ROUNDS / 2], 121 | ) -> Vec { 122 | let mut rounds = Vec::new(); 123 | for rks in round_keys { 124 | // keep track of the state after adding round keys 125 | for (s, rk) in zip(&mut state, rks) { 126 | *s += rk; 127 | } 128 | let after_add_round_keys = state; 129 | 130 | // keep track of the state after applying the S-Box function 131 | for s in &mut state { 132 | *s = s.pow([3]); 133 | } 134 | let after_apply_s_box = state; 135 | 136 | // keep track of the state after multiplying my the MDS matrix 137 | state = Mat3x3(MDS_MATRIX) * state; 138 | let after_mds_mul = state; 139 | 140 | // append round 141 | rounds.push(FullRoundStates { 142 | after_add_round_keys, 143 | after_apply_s_box, 144 | after_mds_mul, 145 | }) 146 | } 147 | rounds 148 | } 149 | 150 | /// Computes the Poseidon hash using StarkWare's parameters. Source: 151 | /// 152 | fn permute(input: [Fp; 3]) -> [Fp; 3] { 153 | let mut state = input; 154 | let mut round = 0; 155 | // first full rounds 156 | for _ in 0..NUM_FULL_ROUNDS / 2 { 157 | // round constants, nonlinear layer, matrix multiplication 158 | for (s, round_key) in zip(&mut state, ROUND_KEYS[round]) { 159 | *s = (*s + round_key).pow([3]); 160 | } 161 | state = Mat3x3(MDS_MATRIX) * state; 162 | round += 1; 163 | } 164 | // Middle partial rounds 165 | for _ in 0..NUM_PARTIAL_ROUNDS { 166 | // round constants, nonlinear layer, matrix multiplication 167 | for (s, round_key) in zip(&mut state, ROUND_KEYS[round]) { 168 | *s += round_key; 169 | } 170 | state[2] = state[2].pow([3]); 171 | state = Mat3x3(MDS_MATRIX) * state; 172 | round += 1; 173 | } 174 | // last full rounds 175 | for _ in 0..NUM_FULL_ROUNDS / 2 { 176 | // round constants, nonlinear layer, matrix multiplication 177 | for (s, round_key) in zip(&mut state, ROUND_KEYS[round]) { 178 | *s = (*s + round_key).pow([3]); 179 | } 180 | state = Mat3x3(MDS_MATRIX) * state; 181 | round += 1; 182 | } 183 | state 184 | } 185 | 186 | /// Computes the Poseidon hash using StarkWare's parameters. Source: 187 | /// 188 | // TODO: docs for optimized version 189 | fn _permute_optimized(input: [Fp; 3]) -> [Fp; 3] { 190 | let mut state = input; 191 | let mut round = 0; 192 | // first full rounds 193 | for _ in 0..NUM_FULL_ROUNDS / 2 { 194 | // round constants, nonlinear layer, matrix multiplication 195 | for (s, round_key) in zip(&mut state, ROUND_KEYS[round]) { 196 | *s = (*s + round_key).pow([3]); 197 | } 198 | state = Mat3x3(MDS_MATRIX) * state; 199 | round += 1; 200 | } 201 | 202 | // partial rounds - initial constants addition 203 | for (s, round_key) in zip(&mut state, ROUND_KEYS[round]) { 204 | *s += round_key 205 | } 206 | todo!(); 207 | 208 | // // last full rounds 209 | // for _ in 0..NUM_FULL_ROUNDS / 2 { 210 | // // round constants, nonlinear layer, matrix multiplication 211 | // for (s, round_key) in zip(&mut state, ROUND_KEYS[round]) { 212 | // *s = (*s + round_key).pow([3]); 213 | // } 214 | // state = Mat3x3(MDS_MATRIX) * state; 215 | // round += 1; 216 | // } 217 | // state 218 | } 219 | 220 | // This is mentioned in section B of the Poseidon paper. Sources: 221 | // * 222 | // * 223 | // * 224 | // TODO: consider removing 225 | fn _calc_optimized_partial_round_keys() -> [[Fp; 3]; NUM_PARTIAL_ROUNDS] { 226 | let mds_matrix_transpose = Mat3x3(MDS_MATRIX).transpose(); 227 | let mds_matrix_transpose_inv = mds_matrix_transpose.inverse().unwrap(); 228 | // Start moving round constants up 229 | // Calculate c_i' = M^(-1) * c_(i+1) 230 | // Split c_i': Add c_i'[0] AFTER the S-box, add the rest to c_i 231 | // I.e.: Store c_i'[0] for each of the partial rounds, and make c_i = c_i 232 | // + c_i' (where now c_i'[0] = 0) num_rounds = R_F + R_P 233 | // R_f = R_F / 2 234 | let mut res = PARTIAL_ROUND_KEYS; 235 | for i in (0..NUM_PARTIAL_ROUNDS - 1).rev() { 236 | let c_i_prime = mds_matrix_transpose_inv * res[i + 1]; 237 | res[i][1] += c_i_prime[1]; 238 | res[i][2] += c_i_prime[2]; 239 | res[i + 1] = [c_i_prime[0], Fp::ZERO, Fp::ZERO]; 240 | } 241 | todo!() 242 | } 243 | 244 | #[cfg(test)] 245 | mod tests { 246 | use crate::poseidon::permute; 247 | use ark_ff::MontFp as Fp; 248 | use ark_ff::Field; 249 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 250 | 251 | #[test] 252 | fn zero_hash_matches_starkware_example() { 253 | // Example from https://github.com/starkware-industries/poseidon 254 | let expected = [ 255 | Fp!("3446325744004048536138401612021367625846492093718951375866996507163446763827"), 256 | Fp!("1590252087433376791875644726012779423683501236913937337746052470473806035332"), 257 | Fp!("867921192302518434283879514999422690776342565400001269945778456016268852423"), 258 | ]; 259 | 260 | assert_eq!(expected, permute([Fp::ZERO, Fp::ZERO, Fp::ZERO])); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /builtins/src/poseidon/periodic.rs: -------------------------------------------------------------------------------- 1 | //! Periodic columns used by AIR 2 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 3 | use ark_ff::MontFp as Fp; 4 | 5 | /// A periodic column, encoded as a polynomial, comprising of the Poseidon hash 6 | /// round keys. This polynomial evaluates to the `i + 1`th row of full round 7 | /// keys (for `state[0]`) when evaluated on the `i`th power of the 8th root of 8 | /// unity. e.g. 9 | /// 10 | /// ```text 11 | /// ┌───────────┬────────────────────────────────────┐ 12 | /// │ X │ P(X) │ 13 | /// ├───────────┼────────────────────────────────────┤ 14 | /// │ ω^0 │ FULL_ROUND_KEYS_1ST_HALF[1][0] │ 15 | /// ├───────────┼────────────────────────────────────┤ 16 | /// │ ω^1 │ FULL_ROUND_KEYS_1ST_HALF[2][0] │ 17 | /// ├───────────┼────────────────────────────────────┤ 18 | /// │ ω^2 │ FULL_ROUND_KEYS_1ST_HALF[3][0] │ 19 | /// ├───────────┼────────────────────────────────────┤ 20 | /// │ ω^3 │ 0 │ 21 | /// ├───────────┼────────────────────────────────────┤ 22 | /// │ ω^4 │ FULL_ROUND_KEYS_2ND_HALF[1][0] │ 23 | /// ├───────────┼────────────────────────────────────┤ 24 | /// │ ω^5 │ FULL_ROUND_KEYS_2ND_HALF[2][0] │ 25 | /// ├───────────┼────────────────────────────────────┤ 26 | /// │ ω^6 │ FULL_ROUND_KEYS_2ND_HALF[3][0] │ 27 | /// ├───────────┼────────────────────────────────────┤ 28 | /// │ ω^7 │ 0 │ 29 | /// └───────────┴────────────────────────────────────┘ 30 | /// ``` 31 | /// 32 | /// NOTE: exact polynomial from StarkWare's solidity verifier: 33 | /// https://etherscan.io/address/0x37070Fd8051f63E5A6D7E87026e086Cc19db1aBe#code 34 | pub const FULL_ROUND_KEY_0_COEFFS: [Fp; 8] = [ 35 | Fp!("2031256392032510728323540713732249591121305006506648848985382400586597610737"), 36 | Fp!("1058884251835032902902959934847754981443976379105821846535521887778003815056"), 37 | Fp!("3199116735214460673119860668797791295709497126247935323420345647237191227032"), 38 | Fp!("3403593029026780926333265226464133854272305860522439300723126785401542560911"), 39 | Fp!("778805400360822368207731183437512943743151741287620774689794956671172832290"), 40 | Fp!("506510838109096059220730854778109913863437083975464167557121102677882315441"), 41 | Fp!("2692520644938914523392225523713543975571066502545988597642254010798055841422"), 42 | Fp!("3585697187696786468041264729514461885587375226952972025977220400366736024776"), 43 | ]; 44 | 45 | /// A periodic column, encoded as a polynomial, comprising of the Poseidon hash 46 | /// round keys. This polynomial evaluates to the `i + 1`th row of full round 47 | /// keys (for `state[1]`) when evaluated on the `i`th power of the 8th root of 48 | /// unity. e.g. 49 | /// 50 | /// ```text 51 | /// ┌───────────┬────────────────────────────────────┐ 52 | /// │ X │ P(X) │ 53 | /// ├───────────┼────────────────────────────────────┤ 54 | /// │ ω^0 │ FULL_ROUND_KEYS_1ST_HALF[1][1] │ 55 | /// ├───────────┼────────────────────────────────────┤ 56 | /// │ ω^1 │ FULL_ROUND_KEYS_1ST_HALF[2][1] │ 57 | /// ├───────────┼────────────────────────────────────┤ 58 | /// │ ω^2 │ FULL_ROUND_KEYS_1ST_HALF[3][1] │ 59 | /// ├───────────┼────────────────────────────────────┤ 60 | /// │ ω^3 │ 0 │ 61 | /// ├───────────┼────────────────────────────────────┤ 62 | /// │ ω^4 │ FULL_ROUND_KEYS_2ND_HALF[1][1] │ 63 | /// ├───────────┼────────────────────────────────────┤ 64 | /// │ ω^5 │ FULL_ROUND_KEYS_2ND_HALF[2][1] │ 65 | /// ├───────────┼────────────────────────────────────┤ 66 | /// │ ω^6 │ FULL_ROUND_KEYS_2ND_HALF[3][1] │ 67 | /// ├───────────┼────────────────────────────────────┤ 68 | /// │ ω^7 │ 0 │ 69 | /// └───────────┴────────────────────────────────────┘ 70 | /// ``` 71 | /// 72 | /// NOTE: exact polynomial from StarkWare's solidity verifier: 73 | /// https://etherscan.io/address/0xb4711a4614368516529d6118C97905aB4B28e267#code 74 | pub const FULL_ROUND_KEY_1_COEFFS: [Fp; 8] = [ 75 | Fp!("2500698040461080674773139952381198839638778571438636910046275161256801675904"), 76 | Fp!("652964806634297091011052233362268594934430242798313942867095931791011310695"), 77 | Fp!("2939414448332072724875253100414078308043974631371534529978201600425249181396"), 78 | Fp!("933462664633036044541055050313690761446142252651413906931929096455205671368"), 79 | Fp!("1079694764967229138366396911064796417614767939275741410725217249097399097433"), 80 | Fp!("40157082147030768552107673477087688599112273433463576858312217366627733685"), 81 | Fp!("1927672438903793236031198529848894371333243975830731128569985457311697349568"), 82 | Fp!("572113961278945390624169048852605746055969372192753638936252529960127728215"), 83 | ]; 84 | 85 | /// A periodic column, encoded as a polynomial, comprising of the Poseidon hash 86 | /// round keys. This polynomial evaluates to the `i + 1`th row of full round 87 | /// keys (for `state[2]`) when evaluated on the `i`th power of the 8th root of 88 | /// unity. e.g. 89 | /// 90 | /// ```text 91 | /// ┌───────────┬────────────────────────────────────┐ 92 | /// │ X │ P(X) │ 93 | /// ├───────────┼────────────────────────────────────┤ 94 | /// │ ω^0 │ FULL_ROUND_KEYS_1ST_HALF[1][2] │ 95 | /// ├───────────┼────────────────────────────────────┤ 96 | /// │ ω^1 │ FULL_ROUND_KEYS_1ST_HALF[2][2] │ 97 | /// ├───────────┼────────────────────────────────────┤ 98 | /// │ ω^2 │ FULL_ROUND_KEYS_1ST_HALF[3][2] │ 99 | /// ├───────────┼────────────────────────────────────┤ 100 | /// │ ω^3 │ 0 │ 101 | /// ├───────────┼────────────────────────────────────┤ 102 | /// │ ω^4 │ FULL_ROUND_KEYS_2ND_HALF[1][2] │ 103 | /// ├───────────┼────────────────────────────────────┤ 104 | /// │ ω^5 │ FULL_ROUND_KEYS_2ND_HALF[2][2] │ 105 | /// ├───────────┼────────────────────────────────────┤ 106 | /// │ ω^6 │ FULL_ROUND_KEYS_2ND_HALF[3][2] │ 107 | /// ├───────────┼────────────────────────────────────┤ 108 | /// │ ω^7 │ 0 │ 109 | /// └───────────┴────────────────────────────────────┘ 110 | /// ``` 111 | /// 112 | /// NOTE: exact polynomial from StarkWare's solidity verifier: 113 | /// https://etherscan.io/address/0x4FB05b7CC348C5a72C59a3f307baf66e3CA1F835#code 114 | pub const FULL_ROUND_KEY_2_COEFFS: [Fp; 8] = [ 115 | Fp!("3539912415782779599122645682228323725845348737630462191562757603420971269882"), 116 | Fp!("2837558332314068247341924887451449731336046919722520548593080808680890892730"), 117 | Fp!("3212814682131296835268738489319086132340321330913282582597217989594356418299"), 118 | Fp!("1347425782113004596959884742203547293270903553049680363963920329314389743311"), 119 | Fp!("3198787996468404797054919162695519790615152785742672594891965171169724870950"), 120 | Fp!("1120844515109881529308595795213214924647761105714342715524064761247549779006"), 121 | Fp!("2773044165244186651230214283470825130260620864240013282273579979977860129690"), 122 | Fp!("2441179170663939450438781661794771519027802813305140163656909544306356385672"), 123 | ]; 124 | 125 | /// TODO 126 | pub const PARTIAL_ROUND_KEY_0_COEFFS: [Fp; 64] = [ 127 | Fp!("2011058453588713720249123693775956922878707212487922032862264007270822126798"), 128 | Fp!("2201400638612229879162360768067398810413954949723023944021920055901410060413"), 129 | Fp!("1691168953466643624865245449195988054903726721572677745282185974497468294783"), 130 | Fp!("837752204021605327715160395330797487102481208539228487427947956029869630090"), 131 | Fp!("2380263194087140445105962283337933941330390837238505085577084716209620735203"), 132 | Fp!("2231171705584461626219509193605920937154699735111651166324180876986249969427"), 133 | Fp!("691023582400368151425499578644682002993815109358501180253153836927178952202"), 134 | Fp!("815712857098752335465822874251324712683954559804455434201967167181304297010"), 135 | Fp!("2200896946459156009846999085508750344518635263077243168217378202438594399713"), 136 | Fp!("3311504242880559186906289974222556212177173749439946052400816630613769891545"), 137 | Fp!("3281626193282231458894797057713120383292667019437105138719321469870604870959"), 138 | Fp!("2595542047312967823969139776095094215792736848354262072270364139034663772927"), 139 | Fp!("2494470255641204693373951627990755478939566816743530326817044130885076025300"), 140 | Fp!("1839024952517621240148375577443119360154339660198916304905856489632607403977"), 141 | Fp!("1360423116113108511511497884143773970748909336603644522105658835738197459855"), 142 | Fp!("1857009051935306160156068085116029921162702946378227598456855109159215348445"), 143 | Fp!("3600950696686588582476467502787179945215441888071387185774270376638527649148"), 144 | Fp!("131042599369273776049953022479346094604946968292827874807385621310020684720"), 145 | Fp!("3579213573733953387118074698596977327254071670778683287052996808576059180984"), 146 | Fp!("1187946733693976194680866767164361748278602816744612270201356897672845548051"), 147 | Fp!("1668920906183966718844909027436064328219511118180304667818990347333708376180"), 148 | Fp!("2512864962690964498518583493949462908552960782966303680210470792426995794470"), 149 | Fp!("3130975011033880246968733267409908092405714799218937413525105832416250686257"), 150 | Fp!("255429598784696064225798137849386054805272155656354071337626616328297217212"), 151 | Fp!("2568709998230648299478741631398126812268414080300466333973067163918513337880"), 152 | Fp!("247015030310741953830313401436147267923682996291128770579600739705064846946"), 153 | Fp!("703286827763390949543255191325140556123276201303350118564927150503707388045"), 154 | Fp!("2283833088401962940862491743337449926009876591639572950835450509414336917837"), 155 | Fp!("1165070839594609882231448434734576346131360204877699424190750181455879108601"), 156 | Fp!("628772092455570545634696948020074778619306970147697868698037435933629562865"), 157 | Fp!("2052855298369150222140895326223812853776817706882609748443636410350664071785"), 158 | Fp!("3143094298659700197468180571237029106493115704312009498966004520694515163024"), 159 | Fp!("1881887943842872994654544963993447050594380908093260031674969742778453918936"), 160 | Fp!("1197071412697646109932927118826948403758665168613087916893460643944415079674"), 161 | Fp!("317078510010863808711921435221323999995766710781080621703998431703493327141"), 162 | Fp!("3101160684179089998010485418744819435886550128325017664383233963733629553353"), 163 | Fp!("2186046537954612104058961354922357721563971378723505054389774170636091386609"), 164 | Fp!("1422160250234177761995064844685640629304074194008588974227878184853642320777"), 165 | Fp!("1653804945057406761664299960167559592652054181513275033282602974300244876420"), 166 | Fp!("1626866096214457447353131310595846121199682496375598478019302407996384140132"), 167 | Fp!("1219544134201476223608458198059663034072645073160261956712213627034699364179"), 168 | Fp!("2624722405021981927284628209472631505272477323263035288175552820883694876232"), 169 | Fp!("1705440197452178348031493305623047936665439567174750229763897038569453695369"), 170 | Fp!("7303794513927036150443526957467421187171381334229685273052173015499219494"), 171 | Fp!("2831581540247649530344309751279789484933757764180014236421382117214379616287"), 172 | Fp!("2181555584533462839506660165587102437288422319970987885731920777554390795228"), 173 | Fp!("1039399290065386888059670728989329530711068617616029629109914913851840078782"), 174 | Fp!("342589009230579100869655403449881228285514990926465815232434050390798969857"), 175 | Fp!("2395697661217587689960413117415101752342752392813229403589615155648100550125"), 176 | Fp!("3365752314385391825538688991497681317826270293686345335138725158762212400100"), 177 | Fp!("3614967852738983401943551688676402047460348453115672964108326419370951919162"), 178 | Fp!("484746741610260848805047653419797384523026975808170816357775138125394263293"), 179 | Fp!("863733279204969122578485458230775871077735743688346649390956030361991441174"), 180 | Fp!("301231648953226191687796737757800634493022525241985058737339210813981579119"), 181 | Fp!("2520827245004312951028763345126579130423272832294677441491626515661846251399"), 182 | Fp!("2493814524510849468472603754000003895776214459806037625542524562549093906520"), 183 | Fp!("1947342058568602493578731388026571104173560600163872855912734417815497144430"), 184 | Fp!("145106130279481286282841880720188329803589955317433265140585959745746887597"), 185 | Fp!("2830139199320765337361906216820940731535745103903732116715972175533579991067"), 186 | Fp!("3158512911697354645267907609611608675370361599838845963888630757176317382130"), 187 | Fp!("703838833471174089158455984460528878306827610165194726969716803706729926365"), 188 | Fp!("303255456835421199388721782102376723575438809947380810971746663123581314666"), 189 | Fp!("2778710226175958524264360794670288591259207376969629073233667082388335595108"), 190 | Fp!("1472197238932273074617280345699913126639197451199647801548108788096142113621"), 191 | ]; 192 | 193 | pub const PARTIAL_ROUND_KEY_1_COEFFS: [Fp; 32] = [ 194 | Fp!("2134335647277236274172649243940335615164845847046811650862308089928274309142"), 195 | Fp!("2913860402982745297192222836556238327676350387704743365206163352943615939695"), 196 | Fp!("1568267719665639329068021097686449424596763578883734209116294890374401775542"), 197 | Fp!("1438832787030345458159564327372021276446704853049296657680414856659331461798"), 198 | Fp!("3400058820156868235423381073658840048586987919336376284621360292676805371218"), 199 | Fp!("2097252063762419661158195314647303504235218184214541115244166149117675347187"), 200 | Fp!("662355037613787361248520866919088369589127966654473231732832401906009112883"), 201 | Fp!("624725934977501428908126150949682646058306899152541041649324164767007671842"), 202 | Fp!("3406201849572533639242164999792192263169824072501379198799385503000569132892"), 203 | Fp!("551414799926945114935931957038676882674275258651094086893483468013116998666"), 204 | Fp!("1001129977779070338184465124492598129855598351649956917265470863964279880205"), 205 | Fp!("989707767477736519138567160031823854690440421522390350577615124351647587320"), 206 | Fp!("1671348076294883574173629413733381569234769582548940048836611998536819209530"), 207 | Fp!("1278091775458215357337740799277013101497153998886847902142053204572133154739"), 208 | Fp!("1403310372422924025511615925475740554489460975480439543509413849788758552442"), 209 | Fp!("1163860326430000718046134830422603091806053130321254448183955127128856564594"), 210 | Fp!("1176869189607295923833069625214293402348267208207586729072901074552450908402"), 211 | Fp!("2566730401717775950645324738467461257934959667889273733557224286834682203471"), 212 | Fp!("3022199364693918131568583135019020773935797752643072673410681091087385332563"), 213 | Fp!("2467107418574549863183112932087925494933901313922495113400766347022199588819"), 214 | Fp!("1091161948796597579795135900067697303554663397335240530598707491663288873237"), 215 | Fp!("1381870550230811989378850214588398558335671987688920999343514478753433779237"), 216 | Fp!("3280018111039722005715710254587103942170658383725019294422846454704244683575"), 217 | Fp!("674372138400372680741447662832556892494101211344401445609878547300002401286"), 218 | Fp!("238812867981604900242730730962904790833095078985847249523658401722016961926"), 219 | Fp!("2049960815341355063267765714253140081115130937556658598531520181157671117871"), 220 | Fp!("3428405366212921189464692166285931213219252511553063377650769767569797458285"), 221 | Fp!("1609002408988616425227773974819821829315526569077095807693412587743519537681"), 222 | Fp!("3345616520536764426311449776963625327065910414283226943957811553324037662878"), 223 | Fp!("3309346525029019614570826948798124563777478018649485974112340052591392984375"), 224 | Fp!("3226324998128064023550162643984510276265270254846795904002410473231727358829"), 225 | Fp!("1101040016726595443223915982531692887649984393932412609031414028907016010174"), 226 | ]; 227 | 228 | #[cfg(test)] 229 | mod tests { 230 | use super::FULL_ROUND_KEY_0_COEFFS; 231 | use crate::poseidon::params::FULL_ROUND_KEYS_1ST_HALF; 232 | use crate::poseidon::params::FULL_ROUND_KEYS_2ND_HALF; 233 | use crate::poseidon::params::NUM_FULL_ROUNDS; 234 | use crate::poseidon::periodic::FULL_ROUND_KEY_1_COEFFS; 235 | use crate::poseidon::periodic::FULL_ROUND_KEY_2_COEFFS; 236 | use ark_ff::Field; 237 | use ark_poly::EvaluationDomain; 238 | use ark_poly::Radix2EvaluationDomain; 239 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 240 | 241 | #[test] 242 | fn full_round_keys0_match() { 243 | let domain = Radix2EvaluationDomain::::new(NUM_FULL_ROUNDS).unwrap(); 244 | let mut full_round_keys_1st_half = FULL_ROUND_KEYS_1ST_HALF.map(|r| r[0]); 245 | let mut full_round_keys_2nd_half = FULL_ROUND_KEYS_2ND_HALF.map(|r| r[0]); 246 | full_round_keys_1st_half.rotate_left(1); 247 | full_round_keys_2nd_half.rotate_left(1); 248 | *full_round_keys_1st_half.last_mut().unwrap() = Fp::ZERO; 249 | *full_round_keys_2nd_half.last_mut().unwrap() = Fp::ZERO; 250 | 251 | let evals = domain.fft(&FULL_ROUND_KEY_0_COEFFS); 252 | let (evals_1st_half, evals_2nd_half) = evals.split_at(NUM_FULL_ROUNDS / 2); 253 | 254 | assert_eq!(&full_round_keys_1st_half, evals_1st_half); 255 | assert_eq!(&full_round_keys_2nd_half, evals_2nd_half); 256 | } 257 | 258 | #[test] 259 | fn full_round_keys1_match() { 260 | let domain = Radix2EvaluationDomain::::new(NUM_FULL_ROUNDS).unwrap(); 261 | let mut full_round_keys_1st_half = FULL_ROUND_KEYS_1ST_HALF.map(|r| r[1]); 262 | let mut full_round_keys_2nd_half = FULL_ROUND_KEYS_2ND_HALF.map(|r| r[1]); 263 | full_round_keys_1st_half.rotate_left(1); 264 | full_round_keys_2nd_half.rotate_left(1); 265 | *full_round_keys_1st_half.last_mut().unwrap() = Fp::ZERO; 266 | *full_round_keys_2nd_half.last_mut().unwrap() = Fp::ZERO; 267 | 268 | let evals = domain.fft(&FULL_ROUND_KEY_1_COEFFS); 269 | let (evals_1st_half, evals_2nd_half) = evals.split_at(NUM_FULL_ROUNDS / 2); 270 | 271 | assert_eq!(&full_round_keys_1st_half, evals_1st_half); 272 | assert_eq!(&full_round_keys_2nd_half, evals_2nd_half); 273 | } 274 | 275 | #[test] 276 | fn full_round_keys2_match() { 277 | let domain = Radix2EvaluationDomain::::new(NUM_FULL_ROUNDS).unwrap(); 278 | let mut full_round_keys_1st_half = FULL_ROUND_KEYS_1ST_HALF.map(|r| r[2]); 279 | let mut full_round_keys_2nd_half = FULL_ROUND_KEYS_2ND_HALF.map(|r| r[2]); 280 | full_round_keys_1st_half.rotate_left(1); 281 | full_round_keys_2nd_half.rotate_left(1); 282 | *full_round_keys_1st_half.last_mut().unwrap() = Fp::ZERO; 283 | *full_round_keys_2nd_half.last_mut().unwrap() = Fp::ZERO; 284 | 285 | let evals = domain.fft(&FULL_ROUND_KEY_2_COEFFS); 286 | let (evals_1st_half, evals_2nd_half) = evals.split_at(NUM_FULL_ROUNDS / 2); 287 | 288 | assert_eq!(&full_round_keys_1st_half, evals_1st_half); 289 | assert_eq!(&full_round_keys_2nd_half, evals_2nd_half); 290 | } 291 | 292 | // TODO: partial round keys 293 | } 294 | -------------------------------------------------------------------------------- /builtins/src/range_check/mod.rs: -------------------------------------------------------------------------------- 1 | use binary::RangeCheckInstance; 2 | use ruint::aliases::U256; 3 | use ruint::uint; 4 | 5 | #[derive(Clone, Debug)] 6 | pub struct InstanceTrace { 7 | pub instance: RangeCheckInstance, 8 | pub parts: [u16; NUM_PARTS], 9 | } 10 | 11 | impl InstanceTrace { 12 | pub fn new(instance: RangeCheckInstance) -> Self { 13 | let value = instance.value; 14 | assert!(value < uint!(1_U256) << (NUM_PARTS * 16)); 15 | 16 | // decompose value into u16 parts 17 | let mask = U256::from(u16::MAX); 18 | let mut parts = [0; NUM_PARTS]; 19 | for (i, part) in parts.iter_mut().enumerate() { 20 | *part = ((value >> ((NUM_PARTS - i - 1) * 16)) & mask) 21 | .try_into() 22 | .unwrap(); 23 | } 24 | 25 | Self { instance, parts } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /builtins/src/utils.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::FftField; 2 | use ark_ff::Field; 3 | use ark_poly::univariate::DensePolynomial; 4 | use ark_poly::EvaluationDomain; 5 | use ark_poly::Evaluations; 6 | use ark_poly::Radix2EvaluationDomain; 7 | use std::ops::Mul; 8 | 9 | /// Generates a periodic table comprising of values in the matrix. 10 | /// The columns of the periodic table are are represented by polynomials that 11 | /// evaluate to the `i`th row when evaluated on the `i`th power of the `n`th 12 | /// root of unity where n is the power-of-2 height of the table. For example a 13 | /// matrix with 4 rows and 2 columns would be represented by two columns 14 | /// `P_0(X)` and `P_1(X)`: 15 | /// 16 | /// ```text 17 | /// ┌───────────┬────────────────────┬────────────────────┐ 18 | /// │ X │ P_0(X) │ P_1(X) │ 19 | /// ├───────────┼────────────────────┼────────────────────┤ 20 | /// │ ω^0 │ matrix_0_0 │ matrix_0_1 │ 21 | /// ├───────────┼────────────────────┼────────────────────┤ 22 | /// │ ω^1 │ matrix_1_0 │ matrix_1_1 │ 23 | /// ├───────────┼────────────────────┼────────────────────┤ 24 | /// │ ω^0 │ matrix_2_0 │ matrix_2_1 │ 25 | /// ├───────────┼────────────────────┼────────────────────┤ 26 | /// │ ω^1 │ matrix_3_0 │ matrix_3_1 │ 27 | /// └───────────┴────────────────────┴────────────────────┘ 28 | /// ``` 29 | /// 30 | /// Input and output matrix are to be represented in column-major. 31 | // TODO: consider deleting 32 | pub fn gen_periodic_table(matrix: Vec>) -> Vec> { 33 | if matrix.is_empty() { 34 | return Vec::new(); 35 | } 36 | 37 | let num_rows = matrix[0].len(); 38 | assert!(num_rows.is_power_of_two()); 39 | assert!(matrix.iter().all(|col| col.len() == num_rows)); 40 | 41 | let domain = Radix2EvaluationDomain::new(num_rows).unwrap(); 42 | matrix 43 | .into_iter() 44 | .map(|col| Evaluations::from_vec_and_domain(col, domain).interpolate()) 45 | .collect() 46 | } 47 | 48 | #[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Ord)] 49 | pub struct Mat3x3(pub [[T; 3]; 3]); 50 | 51 | impl Mat3x3 { 52 | pub fn transpose(self) -> Self { 53 | let [[a, b, c], [d, e, f], [g, h, i]] = self.0; 54 | Mat3x3([[a, d, g], [b, e, h], [c, f, i]]) 55 | } 56 | } 57 | 58 | impl Mat3x3 { 59 | pub fn identity() -> Self { 60 | Self([ 61 | [F::one(), F::zero(), F::zero()], 62 | [F::zero(), F::one(), F::zero()], 63 | [F::zero(), F::zero(), F::one()], 64 | ]) 65 | } 66 | 67 | pub fn inverse(self) -> Option { 68 | let [[a, b, c], [d, e, f], [g, h, i]] = self.0; 69 | let a_prime = e * i - f * h; 70 | let b_prime = -(b * i - c * h); 71 | let c_prime = b * f - c * e; 72 | let d_prime = -(d * i - f * g); 73 | let e_prime = a * i - c * g; 74 | let f_prime = -(a * f - c * d); 75 | let g_prime = d * h - e * g; 76 | let h_prime = -(a * h - b * g); 77 | let i_prime = a * e - b * d; 78 | let determinant = a * a_prime + b * d_prime + c * g_prime; 79 | let inv = Self([ 80 | [a_prime, b_prime, c_prime], 81 | [d_prime, e_prime, f_prime], 82 | [g_prime, h_prime, i_prime], 83 | ]) * determinant.inverse()?; 84 | debug_assert_eq!(self * inv, Self::identity()); 85 | Some(inv) 86 | } 87 | } 88 | 89 | impl Mul for Mat3x3 { 90 | type Output = Self; 91 | 92 | /// Multiplies the matrix by a scalar 93 | fn mul(self, rhs: F) -> Self { 94 | Self(self.0.map(|row| row.map(|cell| cell * rhs))) 95 | } 96 | } 97 | 98 | impl Mul for Mat3x3 { 99 | type Output = Self; 100 | 101 | /// Multiplies the matrix by another matrix 102 | fn mul(self, rhs: Self) -> Self { 103 | let [v0, v1, v2] = rhs.transpose().0; 104 | Mat3x3([self * v0, self * v1, self * v2]).transpose() 105 | } 106 | } 107 | 108 | impl Mul<[F; 3]> for Mat3x3 { 109 | type Output = [F; 3]; 110 | 111 | /// Multiplies the matrix by a vector 112 | fn mul(self, [x, y, z]: [F; 3]) -> [F; 3] { 113 | let [[a, b, c], [d, e, f], [g, h, i]] = self.0; 114 | [ 115 | x * a + y * b + z * c, 116 | x * d + y * e + z * f, 117 | x * g + y * h + z * i, 118 | ] 119 | } 120 | } 121 | 122 | pub mod curve { 123 | use ark_ec::CurveConfig; 124 | use ark_ec::short_weierstrass::SWCurveConfig; 125 | use ark_ff::Fp256; 126 | use ark_ff::Field; 127 | use ark_ff::MontBackend; 128 | use ark_ff::MontConfig; 129 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 130 | use ark_ec::short_weierstrass::Affine; 131 | use ark_ff::MontFp as Fp; 132 | 133 | #[derive(MontConfig)] 134 | #[modulus = "3618502788666131213697322783095070105526743751716087489154079457884512865583"] 135 | #[generator = "3"] 136 | pub struct FrConfig; 137 | pub type Fr = Fp256>; 138 | 139 | // StarkWare's Cairo curve params: https://docs.starkware.co/starkex/crypto/pedersen-hash-function.html 140 | pub struct StarkwareCurve; 141 | 142 | impl CurveConfig for StarkwareCurve { 143 | type BaseField = Fp; 144 | type ScalarField = Fr; 145 | 146 | const COFACTOR: &'static [u64] = &[1]; 147 | const COFACTOR_INV: Self::ScalarField = Fr::ONE; 148 | } 149 | 150 | impl SWCurveConfig for StarkwareCurve { 151 | const COEFF_A: Self::BaseField = Fp::ONE; 152 | const COEFF_B: Self::BaseField = 153 | Fp!("3141592653589793238462643383279502884197169399375105820974944592307816406665"); 154 | 155 | const GENERATOR: Affine = Affine::new_unchecked( 156 | Fp!("874739451078007766457464989774322083649278607533249481151382481072868806602"), 157 | Fp!("152666792071518830868575557812948353041420400780739481342941381225525861407"), 158 | ); 159 | } 160 | 161 | /// calculates the slope between points `p1` and `p2` 162 | /// Returns None if one of the points is the point at infinity 163 | pub fn calculate_slope(p1: Affine, p2: Affine) -> Option { 164 | if p1.infinity || p2.infinity || (p1.x == p2.x && p1.y != p2.y) { 165 | return None; 166 | } 167 | 168 | let y1 = p1.y; 169 | let y2 = p2.y; 170 | let x1 = p1.x; 171 | let x2 = p2.x; 172 | 173 | Some(if x1 == x2 { 174 | // use tangent line 175 | assert_eq!(y1, y2); 176 | let xx = x1.square(); 177 | (xx + xx + xx + StarkwareCurve::COEFF_A) / (y1 + y1) 178 | } else { 179 | // use slope 180 | (y2 - y1) / (x2 - x1) 181 | }) 182 | } 183 | } 184 | 185 | #[cfg(test)] 186 | mod tests { 187 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 188 | 189 | use super::Mat3x3; 190 | 191 | #[test] 192 | fn matrix_multiplication() { 193 | let a = Fp::from(37u8); 194 | let b = Fp::from(29u8); 195 | let c = Fp::from(13u8); 196 | let d = Fp::from(89u8); 197 | let e = Fp::from(67u8); 198 | let f = Fp::from(45u8); 199 | let g = Fp::from(5u8); 200 | let h = Fp::from(9u8); 201 | let i = Fp::from(2u8); 202 | let m = Mat3x3([[a, b, c], [d, e, f], [g, h, i]]); 203 | 204 | let mm = m * m; 205 | 206 | let [row0, row1, row2] = mm.0; 207 | assert_eq!( 208 | row0, 209 | [ 210 | a * a + b * d + c * g, 211 | a * b + b * e + c * h, 212 | a * c + b * f + c * i, 213 | ] 214 | ); 215 | assert_eq!( 216 | row1, 217 | [ 218 | d * a + e * d + f * g, 219 | d * b + e * e + f * h, 220 | d * c + e * f + f * i, 221 | ] 222 | ); 223 | assert_eq!( 224 | row2, 225 | [ 226 | g * a + h * d + i * g, 227 | g * b + h * e + i * h, 228 | g * c + h * f + i * i, 229 | ] 230 | ); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sandstorm-cli" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | [features] 7 | default = [] 8 | # enables support for experimental provers and verifiers 9 | # e.g. proving cairo programs over the Goldilocks field 10 | experimental_claims = [] 11 | asm = ["ministark/asm", "sandstorm/asm"] 12 | gpu = ["ministark/gpu"] 13 | parallel = [ 14 | "dep:rayon", 15 | "ark-std/parallel", 16 | "ark-ff/parallel", 17 | "ark-poly/parallel", 18 | "ministark/parallel", 19 | "ministark-gpu/parallel", 20 | "sandstorm/parallel", 21 | "layouts/parallel", 22 | ] 23 | 24 | [dependencies] 25 | sandstorm = { path = "../" } 26 | layouts = { path = "../layouts", package = "sandstorm-layouts" } 27 | binary = { path = "../binary", package = "sandstorm-binary" } 28 | ministark-gpu = { version = "0.3", git = "https://github.com/andrewmilson/ministark" } 29 | ministark = { git = "https://github.com/andrewmilson/ministark" } 30 | ark-poly = "0.4" 31 | ark-std = "0.4" 32 | ark-ff = "0.4" 33 | ark-serialize = "0.4" 34 | structopt = "0.3" 35 | sha2 = "0.10" 36 | sha3 = "0.10" 37 | serde_json = "1.0" 38 | num-bigint = "0.4" 39 | num-traits = "0.2" 40 | pollster = "0.2" 41 | rayon = { version = "1.5", optional = true } 42 | -------------------------------------------------------------------------------- /cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::Field; 2 | use ark_ff::PrimeField; 3 | use ark_serialize::CanonicalDeserialize; 4 | use ark_serialize::CanonicalSerialize; 5 | use binary::AirPrivateInput; 6 | use binary::AirPublicInput; 7 | use binary::CompiledProgram; 8 | use binary::Layout; 9 | use binary::Memory; 10 | use binary::RegisterStates; 11 | use layouts::CairoWitness; 12 | use ministark::stark::Stark; 13 | use ministark::Proof; 14 | use ministark::ProofOptions; 15 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481; 16 | use sandstorm::claims; 17 | use std::fs; 18 | use std::fs::File; 19 | use std::io::Write; 20 | use std::path::PathBuf; 21 | use std::time::Instant; 22 | use structopt::StructOpt; 23 | 24 | /// Modulus of Starkware's 252-bit prime field used for Cairo 25 | const STARKWARE_PRIME_HEX_STR: &str = 26 | "0x800000000000011000000000000000000000000000000000000000000000001"; 27 | 28 | /// Modulus of 64-bit goldilocks field 29 | #[cfg(feature = "experimental_claims")] 30 | const GOLDILOCKS_PRIME_HEX_STR: &str = "0xffffffff00000001"; 31 | 32 | #[derive(StructOpt, Debug)] 33 | #[structopt(name = "sandstorm", about = "cairo prover")] 34 | struct SandstormOptions { 35 | #[structopt(long, parse(from_os_str))] 36 | program: PathBuf, 37 | #[structopt(long, parse(from_os_str))] 38 | air_public_input: PathBuf, 39 | #[structopt(subcommand)] 40 | command: Command, 41 | } 42 | 43 | #[derive(StructOpt, Debug)] 44 | enum Command { 45 | Prove { 46 | #[structopt(long, parse(from_os_str))] 47 | output: PathBuf, 48 | #[structopt(long, parse(from_os_str))] 49 | air_private_input: PathBuf, 50 | // TODO: add validation to the proof options 51 | #[structopt(long, default_value = "65")] 52 | num_queries: u8, 53 | #[structopt(long, default_value = "2")] 54 | lde_blowup_factor: u8, 55 | #[structopt(long, default_value = "16")] 56 | proof_of_work_bits: u8, 57 | #[structopt(long, default_value = "8")] 58 | fri_folding_factor: u8, 59 | #[structopt(long, default_value = "16")] 60 | fri_max_remainder_coeffs: u8, 61 | }, 62 | Verify { 63 | #[structopt(long, parse(from_os_str))] 64 | proof: PathBuf, 65 | #[structopt(long, default_value = "80")] 66 | required_security_bits: u8, 67 | }, 68 | } 69 | 70 | fn main() { 71 | // read command-line args 72 | let SandstormOptions { 73 | program, 74 | air_public_input, 75 | command, 76 | } = SandstormOptions::from_args(); 77 | 78 | let program_file = File::open(program).expect("could not open program file"); 79 | let air_public_input_file = File::open(air_public_input).expect("could not open public input"); 80 | let program_json: serde_json::Value = serde_json::from_reader(program_file).unwrap(); 81 | let prime: String = serde_json::from_value(program_json["prime"].clone()).unwrap(); 82 | 83 | match prime.to_lowercase().as_str() { 84 | STARKWARE_PRIME_HEX_STR => { 85 | use p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 86 | let program: CompiledProgram = serde_json::from_value(program_json).unwrap(); 87 | let air_public_input: AirPublicInput = 88 | serde_json::from_reader(air_public_input_file).unwrap(); 89 | match air_public_input.layout { 90 | Layout::Starknet => { 91 | use claims::starknet::EthVerifierClaim; 92 | let claim = EthVerifierClaim::new(program, air_public_input); 93 | execute_command(command, claim); 94 | } 95 | Layout::Recursive => { 96 | use claims::recursive::CairoVerifierClaim; 97 | let claim = CairoVerifierClaim::new(program, air_public_input); 98 | execute_command(command, claim); 99 | } 100 | _ => unimplemented!(), 101 | } 102 | } 103 | #[cfg(feature = "experimental_claims")] 104 | GOLDILOCKS_PRIME_HEX_STR => { 105 | use ministark::hash::Sha256HashFn; 106 | use ministark::merkle::MatrixMerkleTreeImpl; 107 | use ministark::random::PublicCoinImpl; 108 | use ministark_gpu::fields::p18446744069414584321; 109 | use p18446744069414584321::ark::Fp; 110 | use p18446744069414584321::ark::Fq3; 111 | use sandstorm::CairoClaim; 112 | let program: CompiledProgram = serde_json::from_value(program_json).unwrap(); 113 | let air_public_input: AirPublicInput = 114 | serde_json::from_reader(air_public_input_file).unwrap(); 115 | match air_public_input.layout { 116 | Layout::Plain => { 117 | type A = layouts::plain::AirConfig; 118 | type T = layouts::plain::ExecutionTrace; 119 | type M = MatrixMerkleTreeImpl; 120 | type P = PublicCoinImpl; 121 | type C = CairoClaim; 122 | let claim = C::new(program, air_public_input); 123 | execute_command(command, claim); 124 | } 125 | Layout::Starknet => { 126 | unimplemented!("'starknet' layout does not support Goldilocks field") 127 | } 128 | Layout::Recursive => { 129 | unimplemented!("'recursive' layout does not support Goldilocks field") 130 | } 131 | layout => unimplemented!("layout {layout} is not supported yet"), 132 | } 133 | } 134 | prime => unimplemented!("prime field p={prime} is not supported yet. Consider enabling the \"experimental_claims\" feature."), 135 | } 136 | } 137 | 138 | fn execute_command>>( 139 | command: Command, 140 | claim: Claim, 141 | ) { 142 | match command { 143 | Command::Prove { 144 | output, 145 | air_private_input, 146 | num_queries, 147 | lde_blowup_factor, 148 | proof_of_work_bits, 149 | fri_folding_factor, 150 | fri_max_remainder_coeffs, 151 | } => { 152 | let options = ProofOptions::new( 153 | num_queries, 154 | lde_blowup_factor, 155 | proof_of_work_bits, 156 | fri_folding_factor, 157 | fri_max_remainder_coeffs, 158 | ); 159 | prove(options, &air_private_input, &output, claim) 160 | } 161 | Command::Verify { 162 | proof, 163 | required_security_bits, 164 | } => verify(required_security_bits, &proof, claim), 165 | } 166 | } 167 | 168 | fn verify>( 169 | required_security_bits: u8, 170 | proof_path: &PathBuf, 171 | claim: Claim, 172 | ) { 173 | let proof_bytes = fs::read(proof_path).unwrap(); 174 | let proof = Proof::::deserialize_compressed(&*proof_bytes).unwrap(); 175 | let now = Instant::now(); 176 | claim.verify(proof, required_security_bits.into()).unwrap(); 177 | println!("Proof verified in: {:?}", now.elapsed()); 178 | } 179 | 180 | fn prove>>( 181 | options: ProofOptions, 182 | private_input_path: &PathBuf, 183 | output_path: &PathBuf, 184 | claim: Claim, 185 | ) { 186 | let private_input_file = 187 | File::open(private_input_path).expect("could not open private input file"); 188 | let private_input: AirPrivateInput = serde_json::from_reader(private_input_file).unwrap(); 189 | 190 | let trace_path = &private_input.trace_path; 191 | let trace_file = File::open(trace_path).expect("could not open trace file"); 192 | let register_states = RegisterStates::from_reader(trace_file); 193 | 194 | let memory_path = &private_input.memory_path; 195 | let memory_file = File::open(memory_path).expect("could not open memory file"); 196 | let memory = Memory::from_reader(memory_file); 197 | 198 | let witness = CairoWitness::new(private_input, register_states, memory); 199 | 200 | let now = Instant::now(); 201 | let proof = pollster::block_on(claim.prove(options, witness)).unwrap(); 202 | println!("Proof generated in: {:?}", now.elapsed()); 203 | let security_level_bits = proof.security_level_bits(); 204 | println!("Proof security (conjectured): {security_level_bits}bit"); 205 | 206 | let mut proof_bytes = Vec::new(); 207 | proof.serialize_compressed(&mut proof_bytes).unwrap(); 208 | println!("Proof size: {:?}KB", proof_bytes.len() / 1024); 209 | let mut f = File::create(output_path).unwrap(); 210 | f.write_all(proof_bytes.as_slice()).unwrap(); 211 | f.flush().unwrap(); 212 | println!("Proof written to {}", output_path.as_path().display()); 213 | } 214 | -------------------------------------------------------------------------------- /crypto/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sandstorm-crypto" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | [features] 7 | asm = ["blake2/simd_asm"] 8 | parallel = [ 9 | "dep:rayon", 10 | "ark-std/parallel", 11 | "ministark/parallel", 12 | "ministark-gpu/parallel", 13 | ] 14 | 15 | [dependencies] 16 | ark-ff = "0.4" 17 | ark-std = "0.4" 18 | binary = { path = "../binary", package = "sandstorm-binary" } 19 | builtins = { path = "../builtins", package = "sandstorm-builtins" } 20 | ministark-gpu = { version = "0.3", git = "https://github.com/andrewmilson/ministark" } 21 | ministark = { git = "https://github.com/andrewmilson/ministark" } 22 | ruint = { version = "1.7", features = ["serde", "num-bigint"] } 23 | sha3 = "0.10" 24 | blake2 = "0.10" 25 | rand = "0.8" 26 | num-bigint = "0.4" 27 | ark-serialize = "0.4" 28 | ark-poly = "0.4" 29 | digest = "0.10" 30 | rayon = { version = "1.5", optional = true } 31 | 32 | [dev-dependencies] 33 | serde_json = "1.0" 34 | criterion = "0.5.1" 35 | 36 | [[bench]] 37 | name = "public_coin" 38 | harness = false 39 | -------------------------------------------------------------------------------- /crypto/benches/public_coin.rs: -------------------------------------------------------------------------------- 1 | use criterion::criterion_group; 2 | use criterion::criterion_main; 3 | use criterion::Criterion; 4 | use ministark::hash::HashFn; 5 | use ministark::random::PublicCoin; 6 | use sandstorm_crypto::hash::blake2s::Blake2sHashFn; 7 | use sandstorm_crypto::hash::keccak::Keccak256HashFn; 8 | use sandstorm_crypto::merkle::mixed::MixedMerkleDigest; 9 | use sandstorm_crypto::public_coin::cairo::CairoVerifierPublicCoin; 10 | use sandstorm_crypto::public_coin::solidity::SolidityVerifierPublicCoin; 11 | 12 | const PROOF_OF_WORK_BITS: u8 = 22; 13 | 14 | fn bench_proof_of_work(c: &mut Criterion, p: P, id: &str) { 15 | c.bench_function(&format!("{id}/PoW/{PROOF_OF_WORK_BITS}_bits"), |b| { 16 | b.iter(|| p.grind_proof_of_work(PROOF_OF_WORK_BITS).unwrap()) 17 | }); 18 | } 19 | 20 | fn proof_of_work_benches(c: &mut Criterion) { 21 | { 22 | let seed = Keccak256HashFn::hash(*b"Hello World!"); 23 | let public_coin = SolidityVerifierPublicCoin::new(seed); 24 | bench_proof_of_work(c, public_coin, "public_coin/solidity_verifier"); 25 | } 26 | { 27 | let seed = MixedMerkleDigest::LowLevel(Blake2sHashFn::hash(*b"Hello World!")); 28 | let public_coin = CairoVerifierPublicCoin::new(seed); 29 | bench_proof_of_work(c, public_coin, "public_coin/cairo_verifier"); 30 | } 31 | } 32 | 33 | criterion_group!(benches, proof_of_work_benches); 34 | criterion_main!(benches); 35 | -------------------------------------------------------------------------------- /crypto/src/hash/blake2s.rs: -------------------------------------------------------------------------------- 1 | use blake2::Blake2s256; 2 | use ministark::hash::ElementHashFn; 3 | use digest::Digest as _; 4 | use ministark::hash::HashFn; 5 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 6 | use ministark::utils::SerdeOutput; 7 | use crate::utils::to_montgomery; 8 | use super::mask_most_significant_bytes; 9 | 10 | pub struct Blake2sHashFn; 11 | 12 | impl HashFn for Blake2sHashFn { 13 | type Digest = SerdeOutput; 14 | const COLLISION_RESISTANCE: u32 = 128; 15 | 16 | fn hash(bytes: impl IntoIterator) -> SerdeOutput { 17 | let mut hasher = Blake2s256::new(); 18 | for byte in bytes { 19 | hasher.update([byte]); 20 | } 21 | SerdeOutput::new(hasher.finalize()) 22 | } 23 | 24 | fn hash_chunks<'a>(chunks: impl IntoIterator) -> SerdeOutput { 25 | let mut hasher = Blake2s256::new(); 26 | for chunk in chunks { 27 | hasher.update(chunk); 28 | } 29 | SerdeOutput::new(hasher.finalize()) 30 | } 31 | 32 | fn merge( 33 | v0: &SerdeOutput, 34 | v1: &SerdeOutput, 35 | ) -> SerdeOutput { 36 | let mut hasher = Blake2s256::new(); 37 | hasher.update(**v0); 38 | hasher.update(**v1); 39 | SerdeOutput::new(hasher.finalize()) 40 | } 41 | 42 | fn merge_with_int(seed: &SerdeOutput, value: u64) -> SerdeOutput { 43 | let mut hasher = Blake2s256::new(); 44 | hasher.update(**seed); 45 | hasher.update(value.to_be_bytes()); 46 | SerdeOutput::new(hasher.finalize()) 47 | } 48 | } 49 | 50 | impl ElementHashFn for Blake2sHashFn { 51 | fn hash_elements(elements: impl IntoIterator) -> SerdeOutput { 52 | let mut hasher = Blake2s256::new(); 53 | for element in elements { 54 | hasher.update(to_montgomery(element).to_be_bytes::<32>()); 55 | // for limb in (element.0).0.into_iter().rev() { 56 | // hasher.update(limb.to_be_bytes()); 57 | // } 58 | } 59 | SerdeOutput::new(hasher.finalize()) 60 | } 61 | } 62 | 63 | pub struct MaskedBlake2sHashFn; 64 | 65 | impl HashFn for MaskedBlake2sHashFn { 66 | type Digest = SerdeOutput; 67 | const COLLISION_RESISTANCE: u32 = N_UNMASKED_BYTES * 8 / 2; 68 | 69 | fn hash(bytes: impl IntoIterator) -> Self::Digest { 70 | let mut hash = Blake2sHashFn::hash(bytes); 71 | mask_most_significant_bytes::(&mut hash); 72 | hash 73 | } 74 | 75 | fn merge(v0: &Self::Digest, v1: &Self::Digest) -> Self::Digest { 76 | let mut hash = Blake2sHashFn::merge(v0, v1); 77 | mask_most_significant_bytes::(&mut hash); 78 | hash 79 | } 80 | 81 | fn merge_with_int(seed: &Self::Digest, value: u64) -> Self::Digest { 82 | let mut hash = Blake2sHashFn::merge_with_int(seed, value); 83 | mask_most_significant_bytes::(&mut hash); 84 | hash 85 | } 86 | 87 | fn hash_chunks<'a>(chunks: impl IntoIterator) -> Self::Digest { 88 | let mut hash = Blake2sHashFn::hash_chunks(chunks); 89 | mask_most_significant_bytes::(&mut hash); 90 | hash 91 | } 92 | } 93 | 94 | impl ElementHashFn for MaskedBlake2sHashFn { 95 | fn hash_elements(elements: impl IntoIterator) -> Self::Digest { 96 | let mut hash = Blake2sHashFn::hash_elements(elements); 97 | mask_most_significant_bytes::(&mut hash); 98 | hash 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /crypto/src/hash/keccak.rs: -------------------------------------------------------------------------------- 1 | use ministark::hash::ElementHashFn; 2 | use ministark::hash::HashFn; 3 | use ministark::utils::SerdeOutput; 4 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 5 | use digest::Digest as _; 6 | use ruint::aliases::U256; 7 | use ark_ff::PrimeField; 8 | use super::mask_least_significant_bytes; 9 | use crate::utils::to_montgomery; 10 | use sha3::Keccak256; 11 | 12 | /// Hash function used by StarkWare's Solidity verifier 13 | pub struct Keccak256HashFn; 14 | 15 | impl HashFn for Keccak256HashFn { 16 | type Digest = SerdeOutput; 17 | const COLLISION_RESISTANCE: u32 = 128; 18 | 19 | fn hash(bytes: impl IntoIterator) -> SerdeOutput { 20 | let mut hasher = Keccak256::new(); 21 | for byte in bytes { 22 | hasher.update([byte]); 23 | } 24 | SerdeOutput::new(hasher.finalize()) 25 | } 26 | 27 | fn merge(v0: &SerdeOutput, v1: &SerdeOutput) -> SerdeOutput { 28 | let mut hasher = Keccak256::new(); 29 | hasher.update(**v0); 30 | hasher.update(**v1); 31 | SerdeOutput::new(hasher.finalize()) 32 | } 33 | 34 | fn merge_with_int(seed: &SerdeOutput, value: u64) -> SerdeOutput { 35 | let mut hasher = Keccak256::new(); 36 | hasher.update(**seed); 37 | hasher.update(value.to_be_bytes()); 38 | SerdeOutput::new(hasher.finalize()) 39 | } 40 | 41 | fn hash_chunks<'a>(chunks: impl IntoIterator) -> Self::Digest { 42 | let mut hasher = Keccak256::new(); 43 | for chunk in chunks { 44 | hasher.update(chunk); 45 | } 46 | SerdeOutput::new(hasher.finalize()) 47 | } 48 | } 49 | 50 | impl ElementHashFn for Keccak256HashFn { 51 | fn hash_elements(elements: impl IntoIterator) -> SerdeOutput { 52 | let mut hasher = Keccak256::new(); 53 | for element in elements { 54 | hasher.update(to_montgomery(element).to_be_bytes::<32>()); 55 | } 56 | SerdeOutput::new(hasher.finalize()) 57 | } 58 | } 59 | 60 | pub struct MaskedKeccak256HashFn; 61 | 62 | impl HashFn for MaskedKeccak256HashFn { 63 | type Digest = SerdeOutput; 64 | const COLLISION_RESISTANCE: u32 = N_UNMASKED_BYTES * 8 / 2; 65 | 66 | fn hash(bytes: impl IntoIterator) -> Self::Digest { 67 | let mut hash = Keccak256HashFn::hash(bytes); 68 | mask_least_significant_bytes::(&mut hash); 69 | hash 70 | } 71 | 72 | fn merge(v0: &Self::Digest, v1: &Self::Digest) -> Self::Digest { 73 | let mut hash = Keccak256HashFn::merge(v0, v1); 74 | mask_least_significant_bytes::(&mut hash); 75 | hash 76 | } 77 | 78 | fn merge_with_int(seed: &Self::Digest, value: u64) -> Self::Digest { 79 | let mut hash = Keccak256HashFn::merge_with_int(seed, value); 80 | mask_least_significant_bytes::(&mut hash); 81 | hash 82 | } 83 | 84 | fn hash_chunks<'a>(chunks: impl IntoIterator) -> Self::Digest { 85 | let mut hash = Keccak256HashFn::hash_chunks(chunks); 86 | mask_least_significant_bytes::(&mut hash); 87 | hash 88 | } 89 | } 90 | 91 | impl ElementHashFn for MaskedKeccak256HashFn { 92 | fn hash_elements(elements: impl IntoIterator) -> Self::Digest { 93 | let mut hash = Keccak256HashFn::hash_elements(elements); 94 | mask_least_significant_bytes::(&mut hash); 95 | hash 96 | } 97 | } 98 | 99 | /// Hashes field elements in their canonical domain. This is different to 100 | /// [Keccak256HashFn] that hashes field elements in Montgomery domain. 101 | pub struct CanonicalKeccak256HashFn; 102 | 103 | impl HashFn for CanonicalKeccak256HashFn { 104 | type Digest = ::Digest; 105 | const COLLISION_RESISTANCE: u32 = ::COLLISION_RESISTANCE; 106 | 107 | fn hash(bytes: impl IntoIterator) -> Self::Digest { 108 | Keccak256HashFn::hash(bytes) 109 | } 110 | 111 | fn merge(v0: &Self::Digest, v1: &Self::Digest) -> Self::Digest { 112 | Keccak256HashFn::merge(v0, v1) 113 | } 114 | 115 | fn merge_with_int(seed: &Self::Digest, value: u64) -> Self::Digest { 116 | Keccak256HashFn::merge_with_int(seed, value) 117 | } 118 | 119 | fn hash_chunks<'a>(chunks: impl IntoIterator) -> Self::Digest { 120 | Keccak256HashFn::hash_chunks(chunks) 121 | } 122 | } 123 | 124 | impl ElementHashFn for CanonicalKeccak256HashFn { 125 | fn hash_elements(elements: impl IntoIterator) -> Self::Digest { 126 | let mut hasher = Keccak256::new(); 127 | for element in elements { 128 | let num = U256::from_limbs(element.into_bigint().0); 129 | let bytes = num.to_be_bytes::<32>(); 130 | hasher.update(bytes); 131 | } 132 | SerdeOutput::new(hasher.finalize()) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /crypto/src/hash/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod blake2s; 2 | pub mod keccak; 3 | pub mod pedersen; 4 | 5 | #[inline] 6 | pub fn mask_least_significant_bytes(bytes: &mut [u8]) { 7 | let n = bytes.len(); 8 | let mut i = N_UNMASKED_BYTES as usize; 9 | while i < n { 10 | bytes[i] = 0; 11 | i += 1; 12 | } 13 | } 14 | 15 | #[inline] 16 | pub fn mask_most_significant_bytes(bytes: &mut [u8]) { 17 | let n = bytes.len(); 18 | let mut i = 0; 19 | while i < n - N_UNMASKED_BYTES as usize { 20 | bytes[i] = 0; 21 | i += 1; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /crypto/src/hash/pedersen.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use std::ops::Deref; 3 | use ark_serialize::CanonicalDeserialize; 4 | use ark_serialize::CanonicalSerialize; 5 | use builtins::pedersen::pedersen_hash; 6 | use ministark::hash::Digest; 7 | use ministark::hash::ElementHashFn; 8 | use ark_ff::Field; 9 | use ministark::hash::HashFn; 10 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 11 | use num_bigint::BigUint; 12 | use ruint::aliases::U256; 13 | 14 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, CanonicalDeserialize, CanonicalSerialize)] 15 | pub struct PedersenDigest(pub Fp); 16 | 17 | impl Display for PedersenDigest { 18 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 19 | self.0.fmt(f) 20 | } 21 | } 22 | 23 | impl Digest for PedersenDigest { 24 | fn as_bytes(&self) -> [u8; 32] { 25 | let num = U256::from(BigUint::from(self.0)); 26 | num.to_be_bytes::<32>() 27 | } 28 | } 29 | 30 | impl Deref for PedersenDigest { 31 | type Target = Fp; 32 | 33 | fn deref(&self) -> &Self::Target { 34 | &self.0 35 | } 36 | } 37 | 38 | impl From for PedersenDigest { 39 | fn from(value: Fp) -> Self { 40 | PedersenDigest(value) 41 | } 42 | } 43 | 44 | pub struct PedersenHashFn; 45 | 46 | impl HashFn for PedersenHashFn { 47 | type Digest = PedersenDigest; 48 | const COLLISION_RESISTANCE: u32 = 125; 49 | 50 | fn hash(_bytes: impl IntoIterator) -> PedersenDigest { 51 | unreachable!() 52 | } 53 | 54 | fn hash_chunks<'a>(_chunks: impl IntoIterator) -> Self::Digest { 55 | unreachable!() 56 | } 57 | 58 | fn merge(v0: &PedersenDigest, v1: &PedersenDigest) -> PedersenDigest { 59 | PedersenDigest(pedersen_hash(**v0, **v1)) 60 | } 61 | 62 | fn merge_with_int(seed: &PedersenDigest, value: u64) -> PedersenDigest { 63 | PedersenDigest(pedersen_hash(**seed, value.into())) 64 | } 65 | } 66 | 67 | impl ElementHashFn for PedersenHashFn { 68 | fn hash_elements(elements: impl IntoIterator) -> PedersenDigest { 69 | let mut num_items = 0u64; 70 | let mut curr_hash = Fp::ZERO; 71 | for v in elements.into_iter() { 72 | curr_hash = pedersen_hash(curr_hash, v); 73 | num_items += 1; 74 | } 75 | PedersenDigest(pedersen_hash(curr_hash, num_items.into())) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /crypto/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(allocator_api, int_roundings)] 2 | 3 | pub mod hash; 4 | pub mod merkle; 5 | pub mod public_coin; 6 | pub mod utils; 7 | -------------------------------------------------------------------------------- /crypto/src/merkle/mixed.rs: -------------------------------------------------------------------------------- 1 | use ark_serialize::CanonicalDeserialize; 2 | use ark_serialize::CanonicalSerialize; 3 | use ark_serialize::SerializationError; 4 | use ark_serialize::Valid; 5 | use blake2::Blake2s256; 6 | use ministark::hash::Digest; 7 | use ministark::hash::ElementHashFn; 8 | use ministark::hash::HashFn; 9 | use ministark::merkle::MerkleTreeConfig; 10 | use ministark::merkle::MerkleTreeImpl; 11 | use ministark::utils::SerdeOutput; 12 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 13 | use num_bigint::BigUint; 14 | use std::marker::PhantomData; 15 | use crate::hash::blake2s::MaskedBlake2sHashFn; 16 | 17 | pub trait MixedHashMerkleTreeConfig: Send + Sync + Sized + 'static { 18 | const TRANSITION_DEPTH: u32; 19 | 20 | type HighLevelsDigest: Digest; 21 | 22 | type HighLevelsHashFn: HashFn; 23 | 24 | type LowLevelsDigest: Digest; 25 | 26 | type LowLevelsHashFn: HashFn; 27 | 28 | fn hash_boundary( 29 | n0: &Self::LowLevelsDigest, 30 | n1: &Self::LowLevelsDigest, 31 | ) -> Self::HighLevelsDigest; 32 | } 33 | 34 | #[derive(Debug, Clone, Eq, PartialEq)] 35 | pub enum MixedMerkleDigest { 36 | HighLevel(HighLevelsDigest), 37 | LowLevel(LowLevelsDigest), 38 | } 39 | 40 | impl Default for MixedMerkleDigest { 41 | fn default() -> Self { 42 | Self::HighLevel(HLD::default()) 43 | } 44 | } 45 | 46 | impl CanonicalSerialize for MixedMerkleDigest { 47 | fn serialize_with_mode( 48 | &self, 49 | mut writer: W, 50 | compress: ark_serialize::Compress, 51 | ) -> Result<(), SerializationError> { 52 | match self { 53 | Self::HighLevel(d) => { 54 | 0u8.serialize_with_mode(&mut writer, compress)?; 55 | d.serialize_with_mode(writer, compress) 56 | } 57 | Self::LowLevel(d) => { 58 | 1u8.serialize_with_mode(&mut writer, compress)?; 59 | d.serialize_with_mode(writer, compress) 60 | } 61 | } 62 | } 63 | 64 | fn serialized_size(&self, compress: ark_serialize::Compress) -> usize { 65 | 0u8.serialized_size(compress) 66 | + match self { 67 | Self::HighLevel(d) => d.serialized_size(compress), 68 | Self::LowLevel(d) => d.serialized_size(compress), 69 | } 70 | } 71 | } 72 | 73 | impl Valid for MixedMerkleDigest { 74 | fn check(&self) -> Result<(), SerializationError> { 75 | Ok(()) 76 | } 77 | } 78 | 79 | impl Digest for MixedMerkleDigest { 80 | fn as_bytes(&self) -> [u8; 32] { 81 | match self { 82 | Self::HighLevel(d) => d.as_bytes(), 83 | Self::LowLevel(d) => d.as_bytes(), 84 | } 85 | } 86 | } 87 | 88 | impl CanonicalDeserialize for MixedMerkleDigest { 89 | fn deserialize_with_mode( 90 | mut reader: R, 91 | compress: ark_serialize::Compress, 92 | validate: ark_serialize::Validate, 93 | ) -> Result { 94 | Ok( 95 | match u8::deserialize_with_mode(&mut reader, compress, validate)? { 96 | 0 => Self::HighLevel(<_>::deserialize_with_mode(reader, compress, validate)?), 97 | 1 => Self::LowLevel(<_>::deserialize_with_mode(reader, compress, validate)?), 98 | _ => Err(SerializationError::InvalidData)?, 99 | }, 100 | ) 101 | } 102 | } 103 | 104 | pub struct MixedHashMerkleTreeConfigImpl(PhantomData); 105 | 106 | impl MerkleTreeConfig for MixedHashMerkleTreeConfigImpl { 107 | type Digest = MixedMerkleDigest; 108 | type Leaf = C::LowLevelsDigest; 109 | 110 | fn hash_leaves(depth: u32, l0: &Self::Leaf, l1: &Self::Leaf) -> Self::Digest { 111 | match depth < C::TRANSITION_DEPTH { 112 | true => MixedMerkleDigest::HighLevel(C::hash_boundary(l0, l1)), 113 | false => MixedMerkleDigest::LowLevel(C::LowLevelsHashFn::merge(l0, l1)), 114 | } 115 | } 116 | 117 | fn hash_nodes(depth: u32, n0: &Self::Digest, n1: &Self::Digest) -> Self::Digest { 118 | use MixedMerkleDigest::*; 119 | match (depth < C::TRANSITION_DEPTH, n0, n1) { 120 | (false, LowLevel(n0), LowLevel(n1)) => LowLevel(C::LowLevelsHashFn::merge(n0, n1)), 121 | (true, LowLevel(n0), LowLevel(n1)) => HighLevel(C::hash_boundary(n0, n1)), 122 | (true, HighLevel(n0), HighLevel(n1)) => HighLevel(C::HighLevelsHashFn::merge(n0, n1)), 123 | _ => unreachable!(), 124 | } 125 | } 126 | 127 | fn security_level_bits() -> u32 { 128 | C::LowLevelsHashFn::COLLISION_RESISTANCE.min(C::HighLevelsHashFn::COLLISION_RESISTANCE) 129 | } 130 | } 131 | 132 | /// Friendly merkle tree config comprises of an algebraically friendly hash 133 | /// function for higher layers (efficient for verifier) and the Blake2s hash 134 | /// function for lower layers (>100x faster to compute for prover). 135 | pub struct FriendlyMerkleTreeConfig(PhantomData); 136 | 137 | impl> MixedHashMerkleTreeConfig 138 | for FriendlyMerkleTreeConfig 139 | where 140 | FriendlyHashFn::Digest: From, 141 | { 142 | type HighLevelsDigest = FriendlyHashFn::Digest; 143 | type HighLevelsHashFn = FriendlyHashFn; 144 | type LowLevelsDigest = SerdeOutput; 145 | type LowLevelsHashFn = MaskedBlake2sHashFn<20>; 146 | const TRANSITION_DEPTH: u32 = N_FRIENDLY_LAYERS; 147 | 148 | fn hash_boundary( 149 | n0: &SerdeOutput, 150 | n1: &SerdeOutput, 151 | ) -> FriendlyHashFn::Digest { 152 | let n0 = Fp::from(BigUint::from_bytes_be(n0)).into(); 153 | let n1 = Fp::from(BigUint::from_bytes_be(n1)).into(); 154 | FriendlyHashFn::merge(&n0, &n1) 155 | } 156 | } 157 | 158 | pub type MixedHashMerkleTreeImpl = 159 | MerkleTreeImpl>; 160 | -------------------------------------------------------------------------------- /crypto/src/merkle/utils.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::Field; 2 | use ministark::Matrix; 3 | use ministark::hash::ElementHashFn; 4 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 5 | #[cfg(feature = "parallel")] 6 | use rayon::prelude::*; 7 | 8 | #[inline] 9 | pub(crate) fn hash_row>(row: &[Fp]) -> H::Digest { 10 | H::hash_elements(row.iter().copied()) 11 | // let mut hasher = D::new(); 12 | // for v in row { 13 | // let v = U256::from(to_montgomery(*v)); 14 | // hasher.update(v.to_be_bytes::<32>()) 15 | // } 16 | // hasher.finalize() 17 | } 18 | 19 | pub(crate) fn hash_rows>(matrix: &Matrix) -> Vec { 20 | let num_rows = matrix.num_rows(); 21 | let mut row_hashes = vec![H::Digest::default(); num_rows]; 22 | 23 | // #[cfg(not(feature = "parallel"))] 24 | // let chunk_size = row_hashes.len(); 25 | // #[cfg(feature = "parallel")] 26 | // let chunk_size = core::cmp::max( 27 | // row_hashes.len() / rayon::current_num_threads().next_power_of_two(), 28 | // 128, 29 | // ); 30 | const CHUNK_SIZE: usize = 10; 31 | 32 | ark_std::cfg_chunks_mut!(row_hashes, CHUNK_SIZE) 33 | .enumerate() 34 | .for_each(|(chunk_offset, chunk)| { 35 | let offset = CHUNK_SIZE * chunk_offset; 36 | 37 | let mut row_buffer = vec![Fp::ZERO; matrix.num_cols()]; 38 | 39 | for (i, row_hash) in chunk.iter_mut().enumerate() { 40 | matrix.read_row(offset + i, &mut row_buffer); 41 | *row_hash = hash_row::(&row_buffer); 42 | } 43 | }); 44 | 45 | row_hashes 46 | } 47 | -------------------------------------------------------------------------------- /crypto/src/public_coin/cairo.rs: -------------------------------------------------------------------------------- 1 | use crate::hash::blake2s::Blake2sHashFn; 2 | use crate::hash::pedersen::PedersenDigest; 3 | use crate::hash::pedersen::PedersenHashFn; 4 | use crate::merkle::mixed::MixedMerkleDigest; 5 | use crate::utils::from_montgomery; 6 | use crate::utils::to_montgomery; 7 | use blake2::Blake2s256; 8 | use ministark::hash::Digest; 9 | use ministark::hash::ElementHashFn; 10 | use ministark::hash::HashFn; 11 | use ministark::random::PublicCoin; 12 | use ministark::random::leading_zeros; 13 | use ministark::utils::SerdeOutput; 14 | use num_bigint::BigUint; 15 | use ruint::aliases::U256; 16 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 17 | use ruint::uint; 18 | use ark_ff::PrimeField; 19 | use digest::Digest as _; 20 | use std::collections::BTreeSet; 21 | use std::fmt::Debug; 22 | use std::iter; 23 | #[cfg(feature = "parallel")] 24 | use rayon::prelude::*; 25 | 26 | /// Public coin based off of StarkWare's cairo verifier 27 | pub struct CairoVerifierPublicCoin { 28 | digest: SerdeOutput, 29 | counter: usize, 30 | } 31 | 32 | impl Debug for CairoVerifierPublicCoin { 33 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 34 | f.debug_struct("PublicCoinImpl") 35 | .field("digest", &self.digest) 36 | .field("counter", &self.counter) 37 | .finish() 38 | } 39 | } 40 | 41 | impl CairoVerifierPublicCoin { 42 | fn reseed_with_bytes(&mut self, bytes: impl AsRef<[u8]>) { 43 | let digest = U256::try_from_be_slice(&self.digest).unwrap(); 44 | let mut hasher = Blake2s256::new(); 45 | hasher.update((digest + uint!(1_U256)).to_be_bytes::<32>()); 46 | hasher.update(bytes); 47 | self.digest = SerdeOutput::new(hasher.finalize()); 48 | self.counter = 0; 49 | } 50 | 51 | fn draw_bytes(&mut self) -> [u8; 32] { 52 | let mut hasher = Blake2s256::new(); 53 | hasher.update(*self.digest); 54 | hasher.update(U256::from(self.counter).to_be_bytes::<32>()); 55 | self.counter += 1; 56 | (*hasher.finalize()).try_into().unwrap() 57 | } 58 | } 59 | 60 | impl PublicCoin for CairoVerifierPublicCoin { 61 | type Digest = MixedMerkleDigest>; 62 | type Field = Fp; 63 | 64 | fn new(digest: Self::Digest) -> Self { 65 | if let MixedMerkleDigest::LowLevel(digest) = digest { 66 | Self { digest, counter: 0 } 67 | } else { 68 | unreachable!() 69 | } 70 | } 71 | 72 | fn reseed_with_digest(&mut self, val: &Self::Digest) { 73 | self.reseed_with_bytes(val.as_bytes()); 74 | } 75 | 76 | fn reseed_with_field_elements(&mut self, vals: &[Self::Field]) { 77 | let hash_felt = PedersenHashFn::hash_elements(vals.iter().copied()); 78 | let bytes = U256::from(BigUint::from(*hash_felt)).to_be_bytes::<32>(); 79 | self.reseed_with_bytes(bytes); 80 | } 81 | 82 | fn reseed_with_field_element_vector(&mut self, vector: &[Self::Field]) { 83 | let mut bytes = Vec::new(); 84 | for val in vector { 85 | let val = to_montgomery(*val); 86 | let val_bytes = val.to_be_bytes::<32>(); 87 | bytes.extend_from_slice(&val_bytes) 88 | } 89 | self.reseed_with_bytes(bytes); 90 | } 91 | 92 | fn reseed_with_int(&mut self, val: u64) { 93 | let bytes = val.to_be_bytes(); 94 | self.reseed_with_bytes(bytes); 95 | } 96 | 97 | fn draw(&mut self) -> Fp { 98 | const MODULUS: U256 = U256::from_limbs(Fp::MODULUS.0); 99 | let bound = MODULUS * uint!(31_U256); 100 | loop { 101 | let field_element = U256::from_be_bytes::<32>(self.draw_bytes()); 102 | if field_element < bound { 103 | return from_montgomery(field_element); 104 | } 105 | } 106 | } 107 | 108 | fn draw_queries(&mut self, max_n: usize, domain_size: usize) -> BTreeSet { 109 | let mut bytes = iter::from_fn(|| Some(self.draw_bytes())).flatten(); 110 | let ints = iter::from_fn(|| { 111 | Some(u64::from_be_bytes([ 112 | bytes.next()?, 113 | bytes.next()?, 114 | bytes.next()?, 115 | bytes.next()?, 116 | bytes.next()?, 117 | bytes.next()?, 118 | bytes.next()?, 119 | bytes.next()?, 120 | ])) 121 | }); 122 | 123 | let domain_size = domain_size as u64; 124 | // NOTE: the cairo verifier samples batches of 4 queries at once 125 | let mut res = ints 126 | .take(max_n.next_multiple_of(4)) 127 | .map(|v| (v % domain_size).try_into().unwrap()) 128 | .collect::>(); 129 | res.truncate(max_n); 130 | res.into_iter().collect() 131 | } 132 | 133 | fn grind_proof_of_work(&self, proof_of_work_bits: u8) -> Option { 134 | let mut prefix_hasher = Blake2s256::new(); 135 | prefix_hasher.update(0x0123456789ABCDEDu64.to_be_bytes()); 136 | prefix_hasher.update(*self.digest); 137 | prefix_hasher.update([proof_of_work_bits]); 138 | let prefix_hash = prefix_hasher.finalize(); 139 | 140 | let mut proof_of_work_hasher = Blake2s256::new(); 141 | proof_of_work_hasher.update(prefix_hash); 142 | 143 | let is_valid = |nonce: &u64| { 144 | let mut proof_of_work_hasher = proof_of_work_hasher.clone(); 145 | proof_of_work_hasher.update(nonce.to_be_bytes()); 146 | let proof_of_work_hash = proof_of_work_hasher.finalize(); 147 | leading_zeros(&proof_of_work_hash) >= u32::from(proof_of_work_bits) 148 | }; 149 | 150 | #[cfg(not(feature = "parallel"))] 151 | return (1..u64::MAX).find(is_valid); 152 | #[cfg(feature = "parallel")] 153 | return (1..u64::MAX).into_par_iter().find_any(is_valid); 154 | } 155 | 156 | fn verify_proof_of_work(&self, proof_of_work_bits: u8, nonce: u64) -> bool { 157 | let mut prefix_hasher = Blake2s256::new(); 158 | prefix_hasher.update(0x0123456789ABCDEDu64.to_be_bytes()); 159 | prefix_hasher.update(*self.digest); 160 | prefix_hasher.update([proof_of_work_bits]); 161 | let prefix_hash = prefix_hasher.finalize(); 162 | 163 | let mut proof_of_work_hasher = Blake2s256::new(); 164 | proof_of_work_hasher.update(prefix_hash); 165 | proof_of_work_hasher.update(nonce.to_be_bytes()); 166 | let proof_of_work_hash = proof_of_work_hasher.finalize(); 167 | 168 | leading_zeros(&proof_of_work_hash) >= u32::from(proof_of_work_bits) 169 | } 170 | 171 | fn security_level_bits() -> u32 { 172 | Blake2sHashFn::COLLISION_RESISTANCE 173 | } 174 | } 175 | 176 | #[cfg(test)] 177 | mod tests { 178 | use super::CairoVerifierPublicCoin; 179 | use ark_ff::MontFp as Fp; 180 | use blake2::Blake2s256; 181 | use crate::merkle::mixed::MixedMerkleDigest; 182 | use digest::Output; 183 | use ministark::random::PublicCoin; 184 | use ministark::utils::SerdeOutput; 185 | use num_bigint::BigUint; 186 | use ruint::aliases::U256; 187 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 188 | 189 | #[test] 190 | fn reseed_with_field_element() { 191 | let seed = SerdeOutput::new(Output::::from_iter([ 192 | 0x1f, 0x9c, 0x7b, 0xc9, 0xad, 0x41, 0xb8, 0xa6, 0x92, 0x36, 0x00, 0x6e, 0x7e, 0xea, 193 | 0x80, 0x38, 0xae, 0xa4, 0x32, 0x96, 0x07, 0x41, 0xb8, 0x19, 0x79, 0x16, 0x36, 0xf8, 194 | 0x2c, 0xc2, 0xd2, 0x5d, 195 | ])); 196 | let mut public_coin = CairoVerifierPublicCoin::new(MixedMerkleDigest::LowLevel(seed)); 197 | 198 | let element: Fp = Fp!("941210603170996043151108091873286171552595656949"); 199 | let element_bytes = U256::from(BigUint::from(element)); 200 | public_coin.reseed_with_bytes(element_bytes.to_be_bytes::<32>()); 201 | 202 | let expected_digest = [ 203 | 0x60, 0x57, 0x79, 0xf6, 0xc9, 0xae, 0x87, 0x1e, 0xd7, 0x30, 0x56, 0xb4, 0xeb, 0xaa, 204 | 0x61, 0xa7, 0x7e, 0x7f, 0xb5, 0x09, 0xbc, 0x08, 0xc1, 0x93, 0xf1, 0x3a, 0xdc, 0xbf, 205 | 0x0c, 0x0b, 0xed, 0xc0, 206 | ]; 207 | assert_eq!(expected_digest, **public_coin.digest); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /crypto/src/public_coin/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cairo; 2 | pub mod solidity; 3 | -------------------------------------------------------------------------------- /crypto/src/public_coin/solidity.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | use std::fmt::Debug; 3 | use std::iter; 4 | use ministark::hash::HashFn; 5 | use ministark::random::PublicCoin; 6 | use ministark::utils::SerdeOutput; 7 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 8 | use ruint::aliases::U256; 9 | use ruint::uint; 10 | use ark_ff::PrimeField; 11 | use ministark::random::leading_zeros; 12 | use sha3::Digest; 13 | use sha3::Keccak256; 14 | use crate::hash::keccak::Keccak256HashFn; 15 | use crate::utils::to_montgomery; 16 | use crate::utils::from_montgomery; 17 | #[cfg(feature = "parallel")] 18 | use rayon::prelude::*; 19 | 20 | /// Public coin based off of StarkWare's solidity verifier 21 | pub struct SolidityVerifierPublicCoin { 22 | digest: SerdeOutput, 23 | counter: usize, 24 | } 25 | 26 | impl Debug for SolidityVerifierPublicCoin { 27 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 28 | f.debug_struct("PublicCoinImpl") 29 | .field("digest", &self.digest) 30 | .field("counter", &self.counter) 31 | .finish() 32 | } 33 | } 34 | 35 | impl SolidityVerifierPublicCoin { 36 | fn reseed_with_bytes(&mut self, bytes: impl AsRef<[u8]>) { 37 | let digest = U256::try_from_be_slice(&self.digest).unwrap(); 38 | let mut hasher = Keccak256::new(); 39 | hasher.update((digest + uint!(1_U256)).to_be_bytes::<32>()); 40 | hasher.update(bytes); 41 | self.digest = SerdeOutput::new(hasher.finalize()); 42 | self.counter = 0; 43 | } 44 | 45 | fn draw_bytes(&mut self) -> [u8; 32] { 46 | let mut hasher = Keccak256::new(); 47 | hasher.update(*self.digest); 48 | hasher.update(U256::from(self.counter).to_be_bytes::<32>()); 49 | self.counter += 1; 50 | (*hasher.finalize()).try_into().unwrap() 51 | } 52 | } 53 | 54 | impl PublicCoin for SolidityVerifierPublicCoin { 55 | type Digest = SerdeOutput; 56 | type Field = Fp; 57 | 58 | fn new(digest: SerdeOutput) -> Self { 59 | Self { digest, counter: 0 } 60 | } 61 | 62 | fn reseed_with_digest(&mut self, val: &SerdeOutput) { 63 | self.reseed_with_bytes(**val); 64 | } 65 | 66 | fn reseed_with_field_elements(&mut self, vals: &[Fp]) { 67 | for v in vals { 68 | let bytes = to_montgomery(*v).to_be_bytes::<32>(); 69 | self.reseed_with_bytes(bytes); 70 | } 71 | } 72 | 73 | fn reseed_with_field_element_vector(&mut self, vector: &[Self::Field]) { 74 | let mut bytes = Vec::new(); 75 | for val in vector { 76 | let val = to_montgomery(*val); 77 | let val_bytes = val.to_be_bytes::<32>(); 78 | bytes.extend_from_slice(&val_bytes) 79 | } 80 | self.reseed_with_bytes(bytes); 81 | } 82 | 83 | fn reseed_with_int(&mut self, val: u64) { 84 | let bytes = val.to_be_bytes(); 85 | self.reseed_with_bytes(bytes); 86 | } 87 | 88 | fn draw(&mut self) -> Fp { 89 | const MODULUS: U256 = U256::from_limbs(Fp::MODULUS.0); 90 | let bound = MODULUS * uint!(31_U256); 91 | loop { 92 | let field_element = U256::from_be_bytes::<32>(self.draw_bytes()); 93 | if field_element < bound { 94 | return from_montgomery(field_element); 95 | } 96 | } 97 | } 98 | 99 | fn draw_queries(&mut self, max_n: usize, domain_size: usize) -> BTreeSet { 100 | let mut bytes = iter::from_fn(|| Some(self.draw_bytes())).flatten(); 101 | let ints = iter::from_fn(|| { 102 | Some(u64::from_be_bytes([ 103 | bytes.next()?, 104 | bytes.next()?, 105 | bytes.next()?, 106 | bytes.next()?, 107 | bytes.next()?, 108 | bytes.next()?, 109 | bytes.next()?, 110 | bytes.next()?, 111 | ])) 112 | }); 113 | 114 | let domain_size = domain_size as u64; 115 | ints.take(max_n) 116 | .map(|v| (v % domain_size).try_into().unwrap()) 117 | .collect() 118 | } 119 | 120 | fn grind_proof_of_work(&self, proof_of_work_bits: u8) -> Option { 121 | let mut prefix_hasher = Keccak256::new(); 122 | prefix_hasher.update(0x0123456789ABCDEDu64.to_be_bytes()); 123 | prefix_hasher.update(*self.digest); 124 | prefix_hasher.update([proof_of_work_bits]); 125 | let prefix_hash = prefix_hasher.finalize(); 126 | 127 | let mut proof_of_work_hasher = Keccak256::new(); 128 | proof_of_work_hasher.update(prefix_hash); 129 | 130 | let is_valid = |nonce: &u64| { 131 | let mut proof_of_work_hasher = proof_of_work_hasher.clone(); 132 | proof_of_work_hasher.update(nonce.to_be_bytes()); 133 | let proof_of_work_hash = proof_of_work_hasher.finalize(); 134 | leading_zeros(&proof_of_work_hash) >= u32::from(proof_of_work_bits) 135 | }; 136 | 137 | #[cfg(not(feature = "parallel"))] 138 | return (1..u64::MAX).find(is_valid); 139 | #[cfg(feature = "parallel")] 140 | return (1..u64::MAX).into_par_iter().find_any(is_valid); 141 | } 142 | 143 | fn verify_proof_of_work(&self, proof_of_work_bits: u8, nonce: u64) -> bool { 144 | let mut prefix_hasher = Keccak256::new(); 145 | prefix_hasher.update(0x0123456789ABCDEDu64.to_be_bytes()); 146 | prefix_hasher.update(*self.digest); 147 | prefix_hasher.update([proof_of_work_bits]); 148 | let prefix_hash = prefix_hasher.finalize(); 149 | 150 | let mut proof_of_work_hasher = Keccak256::new(); 151 | proof_of_work_hasher.update(prefix_hash); 152 | proof_of_work_hasher.update(nonce.to_be_bytes()); 153 | let proof_of_work_hash = proof_of_work_hasher.finalize(); 154 | 155 | leading_zeros(&proof_of_work_hash) >= u32::from(proof_of_work_bits) 156 | } 157 | 158 | fn security_level_bits() -> u32 { 159 | Keccak256HashFn::COLLISION_RESISTANCE 160 | } 161 | } 162 | 163 | #[cfg(test)] 164 | mod tests { 165 | use super::SolidityVerifierPublicCoin; 166 | use ark_ff::MontFp as Fp; 167 | use digest::Output; 168 | use ministark::random::PublicCoin; 169 | use ministark::utils::SerdeOutput; 170 | use sha3::Keccak256; 171 | 172 | #[test] 173 | fn draw_matches_solidity_verifier() { 174 | let pub_input_hash = SerdeOutput::new(Output::::default()); 175 | let mut public_coin = SolidityVerifierPublicCoin::new(pub_input_hash); 176 | 177 | assert_eq!( 178 | Fp!("914053382091189896561965228399096618375831658573140010954888220151670628653"), 179 | public_coin.draw() 180 | ); 181 | assert_eq!( 182 | Fp!("3496720894051083870907112578962849417100085660158534559258626637026506475074"), 183 | public_coin.draw() 184 | ); 185 | assert_eq!( 186 | Fp!("1568281537905787801632546124130153362941104398120976544423901633300198530772"), 187 | public_coin.draw() 188 | ); 189 | assert_eq!( 190 | Fp!("539395842685339476048032152056539303790683868668644006005689195830492067187"), 191 | public_coin.draw() 192 | ); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /crypto/src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use ark_ff::BigInt; 4 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 5 | use ark_ff::PrimeField; 6 | use ruint::aliases::U256; 7 | 8 | #[inline] 9 | pub fn from_montgomery(v: U256) -> Fp { 10 | const MODULUS: U256 = U256::from_limbs(Fp::MODULUS.0); 11 | ark_ff::Fp(BigInt((v % MODULUS).into_limbs()), PhantomData) 12 | } 13 | 14 | #[inline] 15 | pub fn to_montgomery(v: Fp) -> U256 { 16 | assert!(v.0 < Fp::MODULUS); 17 | U256::from_limbs((v.0).0) 18 | 19 | // const MONTGOMERY_R: Fp = 20 | // Fp!("3618502788666127798953978732740734578953660990361066340291730267701097005025"); 21 | // BigUint::from(MONTGOMERY_R * v) 22 | } 23 | -------------------------------------------------------------------------------- /darude.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewmilson/sandstorm/bf871325c12a05e8b51c9625b318ee50087234b3/darude.jpeg -------------------------------------------------------------------------------- /example/air-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "trace_path": "example/trace.bin", 3 | "memory_path": "example/memory.bin", 4 | "pedersen": [], 5 | "range_check": [], 6 | "ecdsa": [], 7 | "bitwise": [], 8 | "ec_op": [] 9 | } 10 | -------------------------------------------------------------------------------- /example/air-private-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "trace_path": "example/trace.bin", 3 | "memory_path": "example/memory.bin", 4 | "pedersen": [], 5 | "range_check": [], 6 | "bitwise": [] 7 | } 8 | -------------------------------------------------------------------------------- /example/air-public-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "layout": "recursive", 3 | "rc_min": 32764, 4 | "rc_max": 32770, 5 | "n_steps": 16384, 6 | "memory_segments": { 7 | "program": { 8 | "begin_addr": 1, 9 | "stop_ptr": 5 10 | }, 11 | "execution": { 12 | "begin_addr": 45, 13 | "stop_ptr": 76 14 | }, 15 | "output": { 16 | "begin_addr": 76, 17 | "stop_ptr": 76 18 | }, 19 | "pedersen": { 20 | "begin_addr": 76, 21 | "stop_ptr": 76 22 | }, 23 | "range_check": { 24 | "begin_addr": 460, 25 | "stop_ptr": 460 26 | }, 27 | "bitwise": { 28 | "begin_addr": 2508, 29 | "stop_ptr": 2508 30 | } 31 | }, 32 | "public_memory": [ 33 | { 34 | "address": 1, 35 | "value": "0x40780017fff7fff", 36 | "page": 0 37 | }, 38 | { 39 | "address": 2, 40 | "value": "0x0", 41 | "page": 0 42 | }, 43 | { 44 | "address": 3, 45 | "value": "0x1104800180018000", 46 | "page": 0 47 | }, 48 | { 49 | "address": 4, 50 | "value": "0x15", 51 | "page": 0 52 | }, 53 | { 54 | "address": 5, 55 | "value": "0x10780017fff7fff", 56 | "page": 0 57 | }, 58 | { 59 | "address": 6, 60 | "value": "0x0", 61 | "page": 0 62 | }, 63 | { 64 | "address": 7, 65 | "value": "0x40780017fff7fff", 66 | "page": 0 67 | }, 68 | { 69 | "address": 8, 70 | "value": "0x1", 71 | "page": 0 72 | }, 73 | { 74 | "address": 9, 75 | "value": "0x208b7fff7fff7ffe", 76 | "page": 0 77 | }, 78 | { 79 | "address": 10, 80 | "value": "0x20780017fff7ffd", 81 | "page": 0 82 | }, 83 | { 84 | "address": 11, 85 | "value": "0x5", 86 | "page": 0 87 | }, 88 | { 89 | "address": 12, 90 | "value": "0x480680017fff8000", 91 | "page": 0 92 | }, 93 | { 94 | "address": 13, 95 | "value": "0x0", 96 | "page": 0 97 | }, 98 | { 99 | "address": 14, 100 | "value": "0x208b7fff7fff7ffe", 101 | "page": 0 102 | }, 103 | { 104 | "address": 15, 105 | "value": "0x482680017ffc8000", 106 | "page": 0 107 | }, 108 | { 109 | "address": 16, 110 | "value": "0x1", 111 | "page": 0 112 | }, 113 | { 114 | "address": 17, 115 | "value": "0x482680017ffd8000", 116 | "page": 0 117 | }, 118 | { 119 | "address": 18, 120 | "value": "0x800000000000011000000000000000000000000000000000000000000000000", 121 | "page": 0 122 | }, 123 | { 124 | "address": 19, 125 | "value": "0x1104800180018000", 126 | "page": 0 127 | }, 128 | { 129 | "address": 20, 130 | "value": "0x800000000000010fffffffffffffffffffffffffffffffffffffffffffffff8", 131 | "page": 0 132 | }, 133 | { 134 | "address": 21, 135 | "value": "0x480280007ffc8000", 136 | "page": 0 137 | }, 138 | { 139 | "address": 22, 140 | "value": "0x48307ffe7fff8000", 141 | "page": 0 142 | }, 143 | { 144 | "address": 23, 145 | "value": "0x208b7fff7fff7ffe", 146 | "page": 0 147 | }, 148 | { 149 | "address": 24, 150 | "value": "0x1104800180018000", 151 | "page": 0 152 | }, 153 | { 154 | "address": 25, 155 | "value": "0x800000000000010fffffffffffffffffffffffffffffffffffffffffffffff0", 156 | "page": 0 157 | }, 158 | { 159 | "address": 26, 160 | "value": "0x480680017fff8000", 161 | "page": 0 162 | }, 163 | { 164 | "address": 27, 165 | "value": "0x9", 166 | "page": 0 167 | }, 168 | { 169 | "address": 28, 170 | "value": "0x400080007ffe7fff", 171 | "page": 0 172 | }, 173 | { 174 | "address": 29, 175 | "value": "0x480680017fff8000", 176 | "page": 0 177 | }, 178 | { 179 | "address": 30, 180 | "value": "0xb", 181 | "page": 0 182 | }, 183 | { 184 | "address": 31, 185 | "value": "0x400080017ffd7fff", 186 | "page": 0 187 | }, 188 | { 189 | "address": 32, 190 | "value": "0x480680017fff8000", 191 | "page": 0 192 | }, 193 | { 194 | "address": 33, 195 | "value": "0x5", 196 | "page": 0 197 | }, 198 | { 199 | "address": 34, 200 | "value": "0x400080027ffc7fff", 201 | "page": 0 202 | }, 203 | { 204 | "address": 35, 205 | "value": "0x48127ffc7fff8000", 206 | "page": 0 207 | }, 208 | { 209 | "address": 36, 210 | "value": "0x480680017fff8000", 211 | "page": 0 212 | }, 213 | { 214 | "address": 37, 215 | "value": "0x3", 216 | "page": 0 217 | }, 218 | { 219 | "address": 38, 220 | "value": "0x1104800180018000", 221 | "page": 0 222 | }, 223 | { 224 | "address": 39, 225 | "value": "0x800000000000010ffffffffffffffffffffffffffffffffffffffffffffffe5", 226 | "page": 0 227 | }, 228 | { 229 | "address": 40, 230 | "value": "0x400680017fff7fff", 231 | "page": 0 232 | }, 233 | { 234 | "address": 41, 235 | "value": "0x19", 236 | "page": 0 237 | }, 238 | { 239 | "address": 42, 240 | "value": "0x208b7fff7fff7ffe", 241 | "page": 0 242 | }, 243 | { 244 | "address": 43, 245 | "value": "0x2d", 246 | "page": 0 247 | }, 248 | { 249 | "address": 44, 250 | "value": "0x0", 251 | "page": 0 252 | } 253 | ], 254 | "dynamic_params": null 255 | } 256 | -------------------------------------------------------------------------------- /example/array-sum.cairo: -------------------------------------------------------------------------------- 1 | from starkware.cairo.common.alloc import alloc 2 | 3 | func array_sum(arr: felt*, n) -> felt { 4 | if (n == 0) { 5 | return 0; 6 | } 7 | let remainder = array_sum(arr=arr + 1, n=n - 1); 8 | return arr[0] + remainder; 9 | } 10 | 11 | func main() { 12 | // allocate an array 13 | const ARRAY_SIZE = 3; 14 | let (ptr) = alloc(); 15 | 16 | // populate array values 17 | assert [ptr] = 9; 18 | assert [ptr + 1] = 11; 19 | assert [ptr + 2] = 5; 20 | 21 | // Compute and check the sum 22 | let sum = array_sum(arr=ptr, n=ARRAY_SIZE); 23 | assert sum = 25; 24 | return (); 25 | } -------------------------------------------------------------------------------- /example/array-sum.proof.saved: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewmilson/sandstorm/bf871325c12a05e8b51c9625b318ee50087234b3/example/array-sum.proof.saved -------------------------------------------------------------------------------- /example/bootloader/air-private-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "trace_path": "/Users/andrewmilson/projects/cairo-lang-bootloader-testing/trace.bin", 3 | "memory_path": "/Users/andrewmilson/projects/cairo-lang-bootloader-testing/memory.bin", 4 | "pedersen": [ 5 | { 6 | "index": 0, 7 | "x": "0x0", 8 | "y": "0x706bd57414b57145b118dd7b92e0d1f040e1a6b6987b842ffb08135699a5ae4" 9 | }, 10 | { 11 | "index": 1, 12 | "x": "0x3bf6a6baa7dad79b6ec242e70a2524309ae0c108c280943859a8b61562cb167", 13 | "y": "0x1" 14 | } 15 | ], 16 | "range_check": [], 17 | "ecdsa": [], 18 | "bitwise": [], 19 | "ec_op": [], 20 | "poseidon": [] 21 | } 22 | -------------------------------------------------------------------------------- /example/bootloader/bootloader-proof.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewmilson/sandstorm/bf871325c12a05e8b51c9625b318ee50087234b3/example/bootloader/bootloader-proof.bin -------------------------------------------------------------------------------- /example/bootloader/memory.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewmilson/sandstorm/bf871325c12a05e8b51c9625b318ee50087234b3/example/bootloader/memory.bin -------------------------------------------------------------------------------- /example/bootloader/trace.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewmilson/sandstorm/bf871325c12a05e8b51c9625b318ee50087234b3/example/bootloader/trace.bin -------------------------------------------------------------------------------- /example/memory.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewmilson/sandstorm/bf871325c12a05e8b51c9625b318ee50087234b3/example/memory.bin -------------------------------------------------------------------------------- /example/output/air-private-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "trace_path": "/Users/andrewmilson/projects/sandstorm/example/output/trace.bin", 3 | "memory_path": "/Users/andrewmilson/projects/sandstorm/example/output/memory.bin", 4 | "pedersen": [], 5 | "range_check": [], 6 | "ecdsa": [] 7 | } 8 | -------------------------------------------------------------------------------- /example/output/air-public-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "rc_max": 32769, 3 | "layout_params": null, 4 | "memory_segments": { 5 | "program": { 6 | "stop_ptr": 5, 7 | "begin_addr": 1 8 | }, 9 | "execution": { 10 | "stop_ptr": 20, 11 | "begin_addr": 15 12 | }, 13 | "output": { 14 | "stop_ptr": 21, 15 | "begin_addr": 20 16 | }, 17 | "pedersen": { 18 | "stop_ptr": 21, 19 | "begin_addr": 21 20 | }, 21 | "range_check": { 22 | "stop_ptr": 213, 23 | "begin_addr": 213 24 | }, 25 | "ecdsa": { 26 | "stop_ptr": 341, 27 | "begin_addr": 341 28 | } 29 | }, 30 | "n_steps": 2048, 31 | "layout": "layout6", 32 | "rc_min": 32765, 33 | "public_memory": [ 34 | { 35 | "value": "0x40780017fff7fff", 36 | "page": 0, 37 | "address": 1 38 | }, 39 | { 40 | "value": "0x1", 41 | "page": 0, 42 | "address": 2 43 | }, 44 | { 45 | "value": "0x1104800180018000", 46 | "page": 0, 47 | "address": 3 48 | }, 49 | { 50 | "value": "0x4", 51 | "page": 0, 52 | "address": 4 53 | }, 54 | { 55 | "value": "0x10780017fff7fff", 56 | "page": 0, 57 | "address": 5 58 | }, 59 | { 60 | "value": "0x0", 61 | "page": 0, 62 | "address": 6 63 | }, 64 | { 65 | "value": "0x480680017fff8000", 66 | "page": 0, 67 | "address": 7 68 | }, 69 | { 70 | "value": "0x9", 71 | "page": 0, 72 | "address": 8 73 | }, 74 | { 75 | "value": "0x400280007ffd7fff", 76 | "page": 0, 77 | "address": 9 78 | }, 79 | { 80 | "value": "0x482680017ffd8000", 81 | "page": 0, 82 | "address": 10 83 | }, 84 | { 85 | "value": "0x1", 86 | "page": 0, 87 | "address": 11 88 | }, 89 | { 90 | "value": "0x208b7fff7fff7ffe", 91 | "page": 0, 92 | "address": 12 93 | }, 94 | { 95 | "value": "0xf", 96 | "page": 0, 97 | "address": 13 98 | }, 99 | { 100 | "value": "0x0", 101 | "page": 0, 102 | "address": 14 103 | }, 104 | { 105 | "value": "0x14", 106 | "page": 0, 107 | "address": 15 108 | }, 109 | { 110 | "value": "0x15", 111 | "page": 0, 112 | "address": 19 113 | }, 114 | { 115 | "value": "0x9", 116 | "page": 0, 117 | "address": 20 118 | } 119 | ] 120 | } 121 | -------------------------------------------------------------------------------- /example/output/main.cairo: -------------------------------------------------------------------------------- 1 | %builtins output 2 | 3 | // Implicit arguments: addresses of the output and pedersen 4 | // builtins. 5 | func main{output_ptr}() { 6 | assert [output_ptr] = 9; 7 | 8 | // Manually update the output builtin pointer. 9 | let output_ptr = output_ptr + 1; 10 | 11 | // output_ptr and pedersen_ptr will be implicitly returned. 12 | return (); 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/output/main_compiled.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": [], 3 | "builtins": [ 4 | "output" 5 | ], 6 | "compiler_version": "0.10.2", 7 | "data": [ 8 | "0x40780017fff7fff", 9 | "0x1", 10 | "0x1104800180018000", 11 | "0x4", 12 | "0x10780017fff7fff", 13 | "0x0", 14 | "0x480680017fff8000", 15 | "0x9", 16 | "0x400280007ffd7fff", 17 | "0x482680017ffd8000", 18 | "0x1", 19 | "0x208b7fff7fff7ffe" 20 | ], 21 | "debug_info": { 22 | "file_contents": { 23 | "": "__start__:\nap += main.Args.SIZE + main.ImplicitArgs.SIZE;\ncall main;\n\n__end__:\njmp rel 0;\n" 24 | }, 25 | "instruction_locations": { 26 | "0": { 27 | "accessible_scopes": [ 28 | "__main__" 29 | ], 30 | "flow_tracking_data": { 31 | "ap_tracking": { 32 | "group": 0, 33 | "offset": 0 34 | }, 35 | "reference_ids": {} 36 | }, 37 | "hints": [], 38 | "inst": { 39 | "end_col": 46, 40 | "end_line": 2, 41 | "input_file": { 42 | "filename": "" 43 | }, 44 | "start_col": 1, 45 | "start_line": 2 46 | } 47 | }, 48 | "2": { 49 | "accessible_scopes": [ 50 | "__main__" 51 | ], 52 | "flow_tracking_data": { 53 | "ap_tracking": { 54 | "group": 0, 55 | "offset": 1 56 | }, 57 | "reference_ids": {} 58 | }, 59 | "hints": [], 60 | "inst": { 61 | "end_col": 10, 62 | "end_line": 3, 63 | "input_file": { 64 | "filename": "" 65 | }, 66 | "start_col": 1, 67 | "start_line": 3 68 | } 69 | }, 70 | "4": { 71 | "accessible_scopes": [ 72 | "__main__" 73 | ], 74 | "flow_tracking_data": { 75 | "ap_tracking": { 76 | "group": 1, 77 | "offset": 0 78 | }, 79 | "reference_ids": {} 80 | }, 81 | "hints": [], 82 | "inst": { 83 | "end_col": 10, 84 | "end_line": 6, 85 | "input_file": { 86 | "filename": "" 87 | }, 88 | "start_col": 1, 89 | "start_line": 6 90 | } 91 | }, 92 | "6": { 93 | "accessible_scopes": [ 94 | "__main__", 95 | "__main__.main" 96 | ], 97 | "flow_tracking_data": { 98 | "ap_tracking": { 99 | "group": 2, 100 | "offset": 0 101 | }, 102 | "reference_ids": { 103 | "__main__.main.output_ptr": 0 104 | } 105 | }, 106 | "hints": [], 107 | "inst": { 108 | "end_col": 28, 109 | "end_line": 6, 110 | "input_file": { 111 | "filename": "example/output/main.cairo" 112 | }, 113 | "start_col": 27, 114 | "start_line": 6 115 | } 116 | }, 117 | "8": { 118 | "accessible_scopes": [ 119 | "__main__", 120 | "__main__.main" 121 | ], 122 | "flow_tracking_data": { 123 | "ap_tracking": { 124 | "group": 2, 125 | "offset": 1 126 | }, 127 | "reference_ids": { 128 | "__main__.main.__temp0": 1, 129 | "__main__.main.output_ptr": 0 130 | } 131 | }, 132 | "hints": [], 133 | "inst": { 134 | "end_col": 29, 135 | "end_line": 6, 136 | "input_file": { 137 | "filename": "example/output/main.cairo" 138 | }, 139 | "start_col": 5, 140 | "start_line": 6 141 | } 142 | }, 143 | "9": { 144 | "accessible_scopes": [ 145 | "__main__", 146 | "__main__.main" 147 | ], 148 | "flow_tracking_data": { 149 | "ap_tracking": { 150 | "group": 2, 151 | "offset": 1 152 | }, 153 | "reference_ids": { 154 | "__main__.main.__temp0": 1, 155 | "__main__.main.output_ptr": 2 156 | } 157 | }, 158 | "hints": [], 159 | "inst": { 160 | "end_col": 36, 161 | "end_line": 9, 162 | "input_file": { 163 | "filename": "example/output/main.cairo" 164 | }, 165 | "parent_location": [ 166 | { 167 | "end_col": 21, 168 | "end_line": 5, 169 | "input_file": { 170 | "filename": "example/output/main.cairo" 171 | }, 172 | "parent_location": [ 173 | { 174 | "end_col": 15, 175 | "end_line": 12, 176 | "input_file": { 177 | "filename": "example/output/main.cairo" 178 | }, 179 | "start_col": 5, 180 | "start_line": 12 181 | }, 182 | "While trying to retrieve the implicit argument 'output_ptr' in:" 183 | ], 184 | "start_col": 11, 185 | "start_line": 5 186 | }, 187 | "While expanding the reference 'output_ptr' in:" 188 | ], 189 | "start_col": 22, 190 | "start_line": 9 191 | } 192 | }, 193 | "11": { 194 | "accessible_scopes": [ 195 | "__main__", 196 | "__main__.main" 197 | ], 198 | "flow_tracking_data": { 199 | "ap_tracking": { 200 | "group": 2, 201 | "offset": 2 202 | }, 203 | "reference_ids": { 204 | "__main__.main.__temp0": 1, 205 | "__main__.main.output_ptr": 2 206 | } 207 | }, 208 | "hints": [], 209 | "inst": { 210 | "end_col": 15, 211 | "end_line": 12, 212 | "input_file": { 213 | "filename": "example/output/main.cairo" 214 | }, 215 | "start_col": 5, 216 | "start_line": 12 217 | } 218 | } 219 | } 220 | }, 221 | "hints": {}, 222 | "identifiers": { 223 | "__main__.__end__": { 224 | "pc": 4, 225 | "type": "label" 226 | }, 227 | "__main__.__start__": { 228 | "pc": 0, 229 | "type": "label" 230 | }, 231 | "__main__.main": { 232 | "decorators": [], 233 | "pc": 6, 234 | "type": "function" 235 | }, 236 | "__main__.main.Args": { 237 | "full_name": "__main__.main.Args", 238 | "members": {}, 239 | "size": 0, 240 | "type": "struct" 241 | }, 242 | "__main__.main.ImplicitArgs": { 243 | "full_name": "__main__.main.ImplicitArgs", 244 | "members": { 245 | "output_ptr": { 246 | "cairo_type": "felt", 247 | "offset": 0 248 | } 249 | }, 250 | "size": 1, 251 | "type": "struct" 252 | }, 253 | "__main__.main.Return": { 254 | "cairo_type": "()", 255 | "type": "type_definition" 256 | }, 257 | "__main__.main.SIZEOF_LOCALS": { 258 | "type": "const", 259 | "value": 0 260 | }, 261 | "__main__.main.__temp0": { 262 | "cairo_type": "felt", 263 | "full_name": "__main__.main.__temp0", 264 | "references": [ 265 | { 266 | "ap_tracking_data": { 267 | "group": 2, 268 | "offset": 1 269 | }, 270 | "pc": 8, 271 | "value": "[cast(ap + (-1), felt*)]" 272 | } 273 | ], 274 | "type": "reference" 275 | }, 276 | "__main__.main.output_ptr": { 277 | "cairo_type": "felt", 278 | "full_name": "__main__.main.output_ptr", 279 | "references": [ 280 | { 281 | "ap_tracking_data": { 282 | "group": 2, 283 | "offset": 0 284 | }, 285 | "pc": 6, 286 | "value": "[cast(fp + (-3), felt*)]" 287 | }, 288 | { 289 | "ap_tracking_data": { 290 | "group": 2, 291 | "offset": 1 292 | }, 293 | "pc": 9, 294 | "value": "cast([fp + (-3)] + 1, felt)" 295 | } 296 | ], 297 | "type": "reference" 298 | } 299 | }, 300 | "main_scope": "__main__", 301 | "prime": "0x800000000000011000000000000000000000000000000000000000000000001", 302 | "reference_manager": { 303 | "references": [ 304 | { 305 | "ap_tracking_data": { 306 | "group": 2, 307 | "offset": 0 308 | }, 309 | "pc": 6, 310 | "value": "[cast(fp + (-3), felt*)]" 311 | }, 312 | { 313 | "ap_tracking_data": { 314 | "group": 2, 315 | "offset": 1 316 | }, 317 | "pc": 8, 318 | "value": "[cast(ap + (-1), felt*)]" 319 | }, 320 | { 321 | "ap_tracking_data": { 322 | "group": 2, 323 | "offset": 1 324 | }, 325 | "pc": 9, 326 | "value": "cast([fp + (-3)] + 1, felt)" 327 | } 328 | ] 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /example/output/memory.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewmilson/sandstorm/bf871325c12a05e8b51c9625b318ee50087234b3/example/output/memory.bin -------------------------------------------------------------------------------- /example/pedersen/air-private-input-OLD.json: -------------------------------------------------------------------------------- 1 | { 2 | "trace_path": "/Users/andrewmilson/projects/sandstorm/example/pedersen/trace.bin", 3 | "memory_path": "/Users/andrewmilson/projects/sandstorm/example/pedersen/memory.bin", 4 | "pedersen": [ 5 | { 6 | "index": 0, 7 | "x": "0x1", 8 | "y": "0x2" 9 | }, 10 | { 11 | "index": 1, 12 | "x": "0x10e1", 13 | "y": "0x0" 14 | } 15 | ], 16 | "range_check": [ 17 | { 18 | "index": 0, 19 | "value": "0x7d007d007d007d007d007d007d007d00" 20 | } 21 | ], 22 | "ecdsa": [ 23 | { 24 | "index": 0, 25 | "pubkey": "0x399ab58e2d17603eeccae95933c81d504ce475eb1bd0080d2316b84232e133c", 26 | "msg": "0x4be8d5e6a89581244171bb35108b89ccb781dd31284e5eb37855eddea4b1581", 27 | "signature_input": { 28 | "r": "0x2b5a729dad1940dcd89abe05e6755a92dc322758cf962eff13c943f9a9b056c", 29 | "w": "0x776622a379f0c039ed8ba1b6be959eb24e6f8172782e5b300ef43c90d05ddb4" 30 | } 31 | } 32 | ], 33 | "bitwise": [ 34 | { 35 | "index": 0, 36 | "x": "0xc", 37 | "y": "0xa" 38 | }, 39 | { 40 | "index": 1, 41 | "x": "0x2b5a729dad1940dcd89abe05e6755a92dc322758cf962eff13c943f9a9b056c", 42 | "y": "0x7e3dfe8113341ad16dfbb9e5e4d425cee0685adcd4697d4364c746107077d63" 43 | }, 44 | { 45 | "index": 2, 46 | "x": "0x0", 47 | "y": "0x0" 48 | }, 49 | { 50 | "index": 3, 51 | "x": "0x0", 52 | "y": "0x0" 53 | }, 54 | { 55 | "index": 4, 56 | "x": "0x0", 57 | "y": "0x0" 58 | }, 59 | { 60 | "index": 5, 61 | "x": "0x0", 62 | "y": "0x0" 63 | }, 64 | { 65 | "index": 6, 66 | "x": "0x0", 67 | "y": "0x0" 68 | } 69 | ], 70 | "ec_op": [ 71 | { 72 | "index": 0, 73 | "p_x": "0x2f59f202bc803b15260caea833f1d77b01f819b83e7bca04441cbe2fb99ebaa", 74 | "p_y": "0x2f09f1db80a70686619d18de76994148ef9868c0d869121d3f99839ac000cb5", 75 | "m": "0x4be8d5e6a89581244171bb35108b89ccb781dd31284e5eb37855eddea4b1581", 76 | "q_x": "0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca", 77 | "q_y": "0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f" 78 | }, 79 | { 80 | "index": 1, 81 | "p_x": "0x4636d8ee1636e249320195428d54d8eae784d6ad4e6b2da89cc5bc637ccaab9", 82 | "p_y": "0x262d4c3a97706076835cc90bb9847c84462d96d98034453e4a874fc6c1cf32c", 83 | "m": "0x2b5a729dad1940dcd89abe05e6755a92dc322758cf962eff13c943f9a9b056c", 84 | "q_x": "0x399ab58e2d17603eeccae95933c81d504ce475eb1bd0080d2316b84232e133c", 85 | "q_y": "0x74016fc280f03b37d4368670e2cc3c662eb5ce0d291ea199f85f1dc992c01d" 86 | }, 87 | { 88 | "index": 2, 89 | "p_x": "0x30206d3c80cbfc65c09332b1d0b05f5e98675338b62ae4d497f3816dadc0b31", 90 | "p_y": "0x2553e52bf6263e6f0a89cb0e40882f719bdf5195798bd38adf338dea1fda4a8", 91 | "m": "0x7e3dfe8113341ad16dfbb9e5e4d425cee0685adcd4697d4364c746107077d63", 92 | "q_x": "0x2b5a729dad1940dcd89abe05e6755a92dc322758cf962eff13c943f9a9b056c", 93 | "q_y": "0x3e6442a66525965a4ecf83be433a68f21068e1954c33170c0ba0cad14337ce8" 94 | } 95 | ], 96 | "poseidon": [ 97 | { 98 | "index": 0, 99 | "input_s0": "0x1a0dad6", 100 | "input_s1": "0x0", 101 | "input_s2": "0x1" 102 | } 103 | ] 104 | } 105 | -------------------------------------------------------------------------------- /example/pedersen/air-private-input.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewmilson/sandstorm/bf871325c12a05e8b51c9625b318ee50087234b3/example/pedersen/air-private-input.json -------------------------------------------------------------------------------- /example/pedersen/air-public-input.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewmilson/sandstorm/bf871325c12a05e8b51c9625b318ee50087234b3/example/pedersen/air-public-input.json -------------------------------------------------------------------------------- /example/pedersen/main.cairo: -------------------------------------------------------------------------------- 1 | %builtins output pedersen range_check ecdsa bitwise ec_op poseidon 2 | 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin, SignatureBuiltin, BitwiseBuiltin, EcOpBuiltin, PoseidonBuiltin 4 | from starkware.cairo.common.hash import hash2 5 | from starkware.cairo.common.ec_point import EcPoint 6 | from starkware.cairo.common.ec import StarkCurve 7 | from starkware.cairo.common.signature import check_ecdsa_signature 8 | from starkware.cairo.common.builtin_poseidon.poseidon import poseidon_hash_single 9 | // from starkware.cairo.common.signature import 10 | from starkware.cairo.common.bitwise import bitwise_and 11 | from starkware.cairo.common.signature import ( 12 | verify_ecdsa_signature, 13 | ) 14 | 15 | 16 | // // Implicit arguments: addresses of the output and pedersen 17 | // // builtins. 18 | // func main{output_ptr, pedersen_ptr: HashBuiltin*, range_check_ptr, ecdsa_ptr: SignatureBuiltin*, bitwise_ptr: BitwiseBuiltin*, ec_op_ptr: EcOpBuiltin*}() { 19 | // alloc_locals; 20 | // // The following line implicitly updates the pedersen_ptr 21 | // // reference to pedersen_ptr + 3. 22 | // local pedersen_ptr: HashBuiltin* = pedersen_ptr; 23 | // let (res) = hash2{hash_ptr=pedersen_ptr}(1, 2); 24 | // assert [output_ptr] = res; 25 | 26 | // // Manually update the output builtin pointer. 27 | // let output_ptr = output_ptr + 1; 28 | 29 | // // assert [range_check_ptr] = 340282366920938463463374607431768211455; 30 | // // let range_check_ptr = range_check_ptr + 1; 31 | 32 | // assert [range_check_ptr] = 166156034813001157104264704933494816000; 33 | // let range_check_ptr = range_check_ptr + 1; 34 | 35 | // let user = 1628448741648245036800002906075225705100596136133912895015035902954123957052; 36 | // let amount = 4321; 37 | // let sig = (1225578735933442828068102633747590437426782890965066746429241472187377583468, 3568809569741913715045370357918125425757114920266578211811626257903121825123); 38 | // let (amount_hash) = hash2{hash_ptr=pedersen_ptr}(amount, 0); 39 | 40 | // verify_ecdsa_signature( 41 | // message=amount_hash, 42 | // public_key=user, 43 | // signature_r=sig[0], 44 | // signature_s=sig[1], 45 | // ); 46 | 47 | // let (res) = check_ecdsa_signature( 48 | // message=amount_hash, 49 | // public_key=user, 50 | // signature_r=sig[0], 51 | // signature_s=sig[1], 52 | // ); 53 | // assert res = 1; 54 | 55 | // // output_ptr and pedersen_ptr will be implicitly returned. 56 | // return (); 57 | // } 58 | 59 | 60 | // Implicit arguments: addresses of the output and pedersen 61 | // builtins. 62 | func main{output_ptr, pedersen_ptr: HashBuiltin*, range_check_ptr, ecdsa_ptr: SignatureBuiltin*, bitwise_ptr: BitwiseBuiltin*, ec_op_ptr: EcOpBuiltin*, poseidon_ptr: PoseidonBuiltin*}() { 63 | alloc_locals; 64 | // The following line implicitly updates the pedersen_ptr 65 | // reference to pedersen_ptr + 3. 66 | local pedersen_ptr: HashBuiltin* = pedersen_ptr; 67 | let (res) = hash2{hash_ptr=pedersen_ptr}(1, 2); 68 | // assert [output_ptr] = res; 69 | 70 | let myval = 0x666; 71 | assert [output_ptr] = myval; 72 | 73 | // Manually update the output builtin pointer. 74 | let output_ptr = output_ptr + 1; 75 | 76 | 77 | let myval = 0x987; 78 | assert [output_ptr] = myval; 79 | 80 | let output_ptr = output_ptr + 1; 81 | // assert [range_check_ptr] = 340282366920938463463374607431768211455; 82 | // let range_check_ptr = range_check_ptr + 1; 83 | 84 | assert [range_check_ptr] = 166156034813001157104264704933494816000; 85 | let range_check_ptr = range_check_ptr + 1; 86 | 87 | let user = 1628448741648245036800002906075225705100596136133912895015035902954123957052; 88 | let amount = 4321; 89 | let sig = (1225578735933442828068102633747590437426782890965066746429241472187377583468, 3568809569741913715045370357918125425757114920266578211811626257903121825123); 90 | let (amount_hash) = hash2{hash_ptr=pedersen_ptr}(amount, 0); 91 | 92 | verify_ecdsa_signature( 93 | message=amount_hash, 94 | public_key=user, 95 | signature_r=sig[0], 96 | signature_s=sig[1], 97 | ); 98 | 99 | let (res) = check_ecdsa_signature( 100 | message=amount_hash, 101 | public_key=user, 102 | signature_r=sig[0], 103 | signature_s=sig[1], 104 | ); 105 | assert res = 1; 106 | 107 | let (prez) = bitwise_and(12, 10); // Binary (1100, 1010). 108 | assert prez = 8; // Binary 1000. 109 | 110 | let (yoyo) = bitwise_and( 111 | 1225578735933442828068102633747590437426782890965066746429241472187377583468, 112 | 3568809569741913715045370357918125425757114920266578211811626257903121825123 113 | ); 114 | assert yoyo = 1190020890442526208725573243584847930292605552660038159110459769316048045408; 115 | 116 | // let (zG: EcPoint) = ec_mul(m=3189, p=EcPoint(x=StarkCurve.GEN_X, y=StarkCurve.GEN_Y)); 117 | let (simple_hash) = poseidon_hash_single(27318998); 118 | 119 | 120 | let (yo1) = bitwise_and(0, 0); 121 | assert yo1 = 0; 122 | let (yo2) = bitwise_and(0, 0); 123 | assert yo2 = 0; 124 | let (yo3) = bitwise_and(0, 0); 125 | assert yo3 = 0; 126 | let (yo4) = bitwise_and(0, 0); 127 | assert yo4 = 0; 128 | let (yo5) = bitwise_and(0, 0); 129 | assert yo5 = 0; 130 | 131 | // output_ptr and pedersen_ptr will be implicitly returned. 132 | return (); 133 | } 134 | 135 | // cairo-run --program bootloader_compiled.json \ 136 | // --air_private_input ./air-private-input.json \ 137 | // --air_public_input ./air-public-input.json \ 138 | // --trace_file ./trace.bin \ 139 | // --memory_file ./memory.bin \ 140 | // --layout starknet \ 141 | // --min_steps 128 \ 142 | // --proof_mode --print_info -------------------------------------------------------------------------------- /example/pedersen/memory.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewmilson/sandstorm/bf871325c12a05e8b51c9625b318ee50087234b3/example/pedersen/memory.bin -------------------------------------------------------------------------------- /example/pedersen/trace.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewmilson/sandstorm/bf871325c12a05e8b51c9625b318ee50087234b3/example/pedersen/trace.bin -------------------------------------------------------------------------------- /example/test.py: -------------------------------------------------------------------------------- 1 | NUM_PARTIAL = 83 2 | NUM_FULL = 8 3 | NUM_ROUNDS = NUM_PARTIAL + NUM_FULL 4 | R_f = NUM_FULL // 2 5 | for i in range(NUM_PARTIAL - 2, -1, -1): 6 | print(i) 7 | -------------------------------------------------------------------------------- /layouts/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sandstorm-layouts" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | [features] 7 | default = [] 8 | parallel = [ 9 | "dep:rayon", 10 | "ark-std/parallel", 11 | "ark-ff/parallel", 12 | "ark-ec/parallel", 13 | "ark-poly/parallel", 14 | "ministark/parallel", 15 | "ministark-gpu/parallel", 16 | ] 17 | 18 | [dependencies] 19 | ministark-gpu = { version = "0.3", git = "https://github.com/andrewmilson/ministark" } 20 | ministark = { git = "https://github.com/andrewmilson/ministark" } 21 | binary = { path = "../binary", package = "sandstorm-binary" } 22 | builtins = { path = "../builtins", package = "sandstorm-builtins" } 23 | ark-poly = "0.4" 24 | ark-serialize = "0.4" 25 | num-traits = "0.2" 26 | strum = "0.24" 27 | ark-ec = "0.4" 28 | ark-ff = "0.4" 29 | num-bigint = "0.4" 30 | ark-std = "0.4" 31 | ruint = { version = "1.7", features = ["serde", "num-bigint"] } 32 | strum_macros = "0.24" 33 | rayon = { version = "1.5", optional = true } 34 | -------------------------------------------------------------------------------- /layouts/README.md: -------------------------------------------------------------------------------- 1 | # Layout 2 | 3 | Crate with implementations of different execution trace and constraint layouts for proving Cairo programs. For now only it implements the "plain" and "starknet" layouts. 4 | -------------------------------------------------------------------------------- /layouts/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature( 2 | allocator_api, 3 | slice_flatten, 4 | array_windows, 5 | array_chunks, 6 | slice_as_chunks 7 | )] 8 | 9 | extern crate alloc; 10 | 11 | use ark_ff::Field; 12 | use binary::AirPrivateInput; 13 | use binary::AirPublicInput; 14 | use binary::CompiledProgram; 15 | use binary::Memory; 16 | use binary::RegisterStates; 17 | use ministark::air::AirConfig; 18 | use ministark::challenges::Challenges; 19 | use ministark::hints::Hints; 20 | use ministark::Trace; 21 | 22 | pub mod plain; 23 | pub mod recursive; 24 | pub mod starknet; 25 | pub mod utils; 26 | 27 | pub trait CairoAirConfig: AirConfig { 28 | /// Public memory permutation challenges 29 | /// Output is of the form: (z, alpha) 30 | fn public_memory_challenges(challenges: &Challenges) -> (Self::Fq, Self::Fq); 31 | 32 | /// Public memory quotient 33 | // TODO: docs 34 | fn public_memory_quotient(hints: &Hints) -> Self::Fq; 35 | } 36 | 37 | #[derive(Debug)] 38 | pub struct CairoWitness { 39 | air_private_input: AirPrivateInput, 40 | register_states: RegisterStates, 41 | memory: Memory, 42 | } 43 | 44 | impl CairoWitness { 45 | pub fn new( 46 | air_private_input: AirPrivateInput, 47 | register_states: RegisterStates, 48 | memory: Memory, 49 | ) -> Self { 50 | Self { 51 | air_private_input, 52 | register_states, 53 | memory, 54 | } 55 | } 56 | } 57 | 58 | pub trait CairoTrace: Trace { 59 | fn new( 60 | program: CompiledProgram, 61 | public_input: AirPublicInput, 62 | witness: CairoWitness, 63 | ) -> Self; 64 | } 65 | -------------------------------------------------------------------------------- /layouts/src/plain/mod.rs: -------------------------------------------------------------------------------- 1 | //! Uses the mimimum set of constraints for proving cairo prorams (no builtins) 2 | //! 3 | 4 | mod air; 5 | mod trace; 6 | 7 | pub use air::AirConfig; 8 | pub use trace::ExecutionTrace; 9 | 10 | // must be a power-of-two 11 | pub const CYCLE_HEIGHT: usize = 16; 12 | pub const PUBLIC_MEMORY_STEP: usize = 8; 13 | pub const MEMORY_STEP: usize = 2; 14 | pub const RANGE_CHECK_STEP: usize = 4; 15 | 16 | pub const NUM_BASE_COLUMNS: usize = 5; 17 | pub const NUM_EXTENSION_COLUMNS: usize = 1; 18 | -------------------------------------------------------------------------------- /layouts/src/plain/trace.rs: -------------------------------------------------------------------------------- 1 | use super::air::Auxiliary; 2 | use super::air::Flag; 3 | use super::air::MemoryPermutation; 4 | use super::air::Npc; 5 | use super::air::Permutation; 6 | use super::air::RangeCheck; 7 | use super::air::RangeCheckPermutation; 8 | use super::CYCLE_HEIGHT; 9 | use super::MEMORY_STEP; 10 | use super::PUBLIC_MEMORY_STEP; 11 | use super::RANGE_CHECK_STEP; 12 | use crate::utils::get_ordered_memory_accesses; 13 | use crate::utils::RangeCheckPool; 14 | use crate::CairoTrace; 15 | use crate::CairoWitness; 16 | use alloc::vec; 17 | use alloc::vec::Vec; 18 | use ark_ff::batch_inversion; 19 | use ark_ff::FftField; 20 | use ark_ff::Field; 21 | use ark_ff::PrimeField; 22 | use binary::AirPublicInput; 23 | use binary::CompiledProgram; 24 | use binary::Memory; 25 | use binary::MemoryEntry; 26 | use binary::RegisterState; 27 | use binary::RegisterStates; 28 | use core::iter::zip; 29 | use ministark::challenges::Challenges; 30 | use ministark::utils::GpuAllocator; 31 | use ministark::utils::GpuVec; 32 | use ministark::Matrix; 33 | use ministark::StarkExtensionOf; 34 | use ministark::Trace; 35 | use ministark_gpu::GpuFftField; 36 | use num_bigint::BigUint; 37 | #[cfg(feature = "parallel")] 38 | use rayon::prelude::*; 39 | use std::marker::PhantomData; 40 | use strum::IntoEnumIterator; 41 | 42 | pub struct ExecutionTrace { 43 | pub air_public_input: AirPublicInput, 44 | pub initial_registers: RegisterState, 45 | pub final_registers: RegisterState, 46 | pub program: CompiledProgram, 47 | _register_states: RegisterStates, 48 | _memory: Memory, 49 | _flags_column: GpuVec, 50 | npc_column: GpuVec, 51 | memory_column: GpuVec, 52 | range_check_column: GpuVec, 53 | _auxiliary_column: GpuVec, 54 | base_trace: Matrix, 55 | _marker: PhantomData, 56 | } 57 | 58 | impl> CairoTrace for ExecutionTrace { 59 | fn new( 60 | program: CompiledProgram, 61 | air_public_input: AirPublicInput, 62 | witness: CairoWitness, 63 | ) -> Self { 64 | let CairoWitness { 65 | air_private_input: _, 66 | register_states, 67 | memory, 68 | } = witness; 69 | 70 | let num_cycles = register_states.len(); 71 | assert!(num_cycles.is_power_of_two()); 72 | let trace_len = num_cycles * CYCLE_HEIGHT; 73 | 74 | let mut flags_column = Vec::new_in(GpuAllocator); 75 | flags_column.resize(trace_len, Fp::zero()); 76 | 77 | let padding_entry = air_public_input.public_memory_padding(); 78 | let mut npc_column = Vec::new_in(GpuAllocator); 79 | npc_column.resize(trace_len, Fp::zero()); 80 | { 81 | // default all memory items to our padding entry 82 | // TODO: this is a little hacky. not good 83 | let padding_address = padding_entry.address.into(); 84 | let padding_value = padding_entry.value; 85 | for [address, value] in npc_column.array_chunks_mut() { 86 | *address = padding_address; 87 | *value = padding_value; 88 | } 89 | } 90 | 91 | // fill memory gaps to make memory "continuous" 92 | // skip the memory at address 0 - this is a special memory address in Cairo 93 | // TODO: a little brittle. investigate more. 94 | let mut npc_gap_iter = npc_column.array_chunks_mut().skip(7).step_by(8); 95 | for (a, v) in memory.iter().enumerate().skip(1) { 96 | if v.is_none() { 97 | *npc_gap_iter.next().unwrap() = [(a as u64).into(), Fp::zero()]; 98 | } 99 | } 100 | 101 | // add offsets to the range check pool 102 | let mut rc_pool = RangeCheckPool::new(); 103 | for &RegisterState { pc, .. } in register_states.iter() { 104 | let word = memory[pc].unwrap(); 105 | rc_pool.push(word.get_off_dst()); 106 | rc_pool.push(word.get_off_op0()); 107 | rc_pool.push(word.get_off_op1()); 108 | } 109 | 110 | let (ordered_rc_vals, ordered_rc_padding_vals) = rc_pool.get_ordered_values_with_padding(); 111 | let range_check_max = rc_pool.max().unwrap(); 112 | let range_check_padding_value = Fp::from(range_check_max as u64); 113 | let mut ordered_rc_vals = ordered_rc_vals.into_iter(); 114 | let mut ordered_rc_padding_vals = ordered_rc_padding_vals.into_iter(); 115 | let mut range_check_column = Vec::new_in(GpuAllocator); 116 | range_check_column.resize(trace_len, range_check_padding_value); 117 | 118 | let mut auxiliary_column = Vec::new_in(GpuAllocator); 119 | auxiliary_column.resize(trace_len, Fp::zero()); 120 | 121 | let (range_check_cycles, _) = range_check_column.as_chunks_mut::(); 122 | let (auxiliary_cycles, _) = auxiliary_column.as_chunks_mut::(); 123 | let (npc_cycles, _) = npc_column.as_chunks_mut::(); 124 | let (flag_cycles, _) = flags_column.as_chunks_mut::(); 125 | 126 | ark_std::cfg_iter_mut!(range_check_cycles) 127 | .zip(auxiliary_cycles) 128 | .zip(npc_cycles) 129 | .zip(flag_cycles) 130 | .zip(&*register_states) 131 | .for_each( 132 | |((((rc_cycle, aux_cycle), npc_cycle), flag_cycle), registers)| { 133 | let &RegisterState { pc, ap, fp } = registers; 134 | let word = memory[pc].unwrap(); 135 | debug_assert!(!word.get_flag(Flag::Zero.into())); 136 | 137 | // range check all offset values 138 | let off_dst = (word.get_off_dst() as u64).into(); 139 | let off_op0 = (word.get_off_op0() as u64).into(); 140 | let off_op1 = (word.get_off_op1() as u64).into(); 141 | let dst_addr = (word.get_dst_addr(ap, fp) as u64).into(); 142 | let op0_addr = (word.get_op0_addr(ap, fp) as u64).into(); 143 | let op1_addr = (word.get_op1_addr(pc, ap, fp, &memory) as u64).into(); 144 | let dst = word.get_dst(ap, fp, &memory); 145 | let op0 = word.get_op0(ap, fp, &memory); 146 | let op1 = word.get_op1(pc, ap, fp, &memory); 147 | let res = word.get_res(pc, ap, fp, &memory); 148 | let tmp0 = word.get_tmp0(ap, fp, &memory); 149 | let tmp1 = word.get_tmp1(pc, ap, fp, &memory); 150 | 151 | // FLAGS 152 | for flag in Flag::iter() { 153 | flag_cycle[flag as usize] = word.get_flag_prefix(flag.into()).into(); 154 | } 155 | 156 | // NPC 157 | npc_cycle[Npc::Pc as usize] = (pc as u64).into(); 158 | npc_cycle[Npc::Instruction as usize] = word.into_felt(); 159 | npc_cycle[Npc::MemOp0Addr as usize] = op0_addr; 160 | npc_cycle[Npc::MemOp0 as usize] = op0; 161 | npc_cycle[Npc::MemDstAddr as usize] = dst_addr; 162 | npc_cycle[Npc::MemDst as usize] = dst; 163 | npc_cycle[Npc::MemOp1Addr as usize] = op1_addr; 164 | npc_cycle[Npc::MemOp1 as usize] = op1; 165 | for offset in (0..CYCLE_HEIGHT).step_by(PUBLIC_MEMORY_STEP) { 166 | npc_cycle[offset + Npc::PubMemAddr as usize] = Fp::zero(); 167 | npc_cycle[offset + Npc::PubMemVal as usize] = Fp::zero(); 168 | } 169 | 170 | // MEMORY 171 | // handled after this loop 172 | 173 | // RANGE CHECK 174 | rc_cycle[RangeCheck::OffDst as usize] = off_dst; 175 | rc_cycle[RangeCheck::Ap as usize] = (ap as u64).into(); 176 | rc_cycle[RangeCheck::OffOp1 as usize] = off_op1; 177 | rc_cycle[RangeCheck::Op0MulOp1 as usize] = op0 * op1; 178 | rc_cycle[RangeCheck::OffOp0 as usize] = off_op0; 179 | rc_cycle[RangeCheck::Fp as usize] = (fp as u64).into(); 180 | rc_cycle[RangeCheck::Res as usize] = res; 181 | // RangeCheck::Ordered and RangeCheck::Unused are handled after cycle padding 182 | 183 | // COL8 - TODO: better name 184 | aux_cycle[Auxiliary::Tmp0 as usize] = tmp0; 185 | aux_cycle[Auxiliary::Tmp1 as usize] = tmp1; 186 | }, 187 | ); 188 | 189 | for cycle_offset in (0..trace_len).step_by(CYCLE_HEIGHT) { 190 | let rc_virtual_row = &mut range_check_column[cycle_offset..cycle_offset + CYCLE_HEIGHT]; 191 | 192 | // overwrite the range check padding cell with remaining padding values 193 | // TODO: this might not be enough 194 | rc_virtual_row[RangeCheck::Unused as usize] = 195 | if let Some(val) = ordered_rc_padding_vals.next() { 196 | // Last range check is currently unused so stuff in the padding values there 197 | (val as u64).into() 198 | } else { 199 | range_check_padding_value 200 | }; 201 | 202 | // add remaining ordered range check values 203 | for offset in (0..CYCLE_HEIGHT).step_by(RANGE_CHECK_STEP) { 204 | rc_virtual_row[offset + RangeCheck::Ordered as usize] = 205 | if let Some(val) = ordered_rc_vals.next() { 206 | (val as u64).into() 207 | } else { 208 | range_check_padding_value 209 | }; 210 | } 211 | } 212 | 213 | // ensure range check values have been fully consumed 214 | assert!(ordered_rc_padding_vals.next().is_none()); 215 | assert!(ordered_rc_vals.next().is_none()); 216 | 217 | // generate the memory column by ordering memory accesses 218 | let memory_accesses: Vec> = npc_column 219 | .array_chunks() 220 | .map(|&[address_felt, value_felt]| { 221 | let address: BigUint = address_felt.into_bigint().into(); 222 | MemoryEntry { 223 | address: address.try_into().unwrap(), 224 | value: value_felt, 225 | } 226 | }) 227 | .collect(); 228 | let ordered_memory_accesses = get_ordered_memory_accesses::( 229 | trace_len, 230 | &memory_accesses, 231 | &air_public_input.public_memory, 232 | padding_entry, 233 | ); 234 | let memory_column = ordered_memory_accesses 235 | .into_iter() 236 | .flat_map(|e| [e.address.into(), e.value]) 237 | .collect::>() 238 | .to_vec_in(GpuAllocator); 239 | 240 | let base_trace = Matrix::new(vec![ 241 | flags_column.to_vec_in(GpuAllocator), 242 | npc_column.to_vec_in(GpuAllocator), 243 | memory_column.to_vec_in(GpuAllocator), 244 | range_check_column.to_vec_in(GpuAllocator), 245 | auxiliary_column.to_vec_in(GpuAllocator), 246 | ]); 247 | 248 | let initial_registers = *register_states.first().unwrap(); 249 | let final_registers = *register_states.last().unwrap(); 250 | 251 | ExecutionTrace { 252 | air_public_input, 253 | initial_registers, 254 | final_registers, 255 | npc_column, 256 | memory_column, 257 | range_check_column, 258 | base_trace, 259 | program, 260 | _flags_column: flags_column, 261 | _auxiliary_column: auxiliary_column, 262 | _memory: memory, 263 | _register_states: register_states, 264 | _marker: PhantomData, 265 | } 266 | } 267 | } 268 | 269 | impl> Trace for ExecutionTrace { 270 | type Fp = Fp; 271 | type Fq = Fq; 272 | 273 | fn base_columns(&self) -> &Matrix { 274 | &self.base_trace 275 | } 276 | 277 | fn build_extension_columns(&self, challenges: &Challenges) -> Option> { 278 | // TODO: multithread 279 | // Generate memory permutation product 280 | // =================================== 281 | // see distinction between (a', v') and (a, v) in the Cairo paper. 282 | let z = challenges[MemoryPermutation::Z]; 283 | let alpha = challenges[MemoryPermutation::A]; 284 | let program_order_accesses = self.npc_column.array_chunks::(); 285 | let address_order_accesses = self.memory_column.array_chunks::(); 286 | let mut mem_perm_numerators = Vec::new(); 287 | let mut mem_perm_denominators = Vec::new(); 288 | let mut numerator_acc = Fq::one(); 289 | let mut denominator_acc = Fq::one(); 290 | for (&[a, v], &[a_prime, v_prime]) in program_order_accesses.zip(address_order_accesses) { 291 | numerator_acc *= z - (alpha * v + a); 292 | denominator_acc *= z - (alpha * v_prime + a_prime); 293 | mem_perm_numerators.push(numerator_acc); 294 | mem_perm_denominators.push(denominator_acc); 295 | } 296 | batch_inversion(&mut mem_perm_denominators); 297 | 298 | // Generate range check permutation product 299 | // ======================================== 300 | let z = challenges[RangeCheckPermutation::Z]; 301 | let range_check_chunks = self.range_check_column.array_chunks::(); 302 | let mut rc_perm_numerators = Vec::new(); 303 | let mut rc_perm_denominators = Vec::new(); 304 | let mut numerator_acc = Fq::one(); 305 | let mut denominator_acc = Fq::one(); 306 | for chunk in range_check_chunks { 307 | numerator_acc *= z - chunk[RangeCheck::OffDst as usize]; 308 | denominator_acc *= z - chunk[RangeCheck::Ordered as usize]; 309 | rc_perm_numerators.push(numerator_acc); 310 | rc_perm_denominators.push(denominator_acc); 311 | } 312 | batch_inversion(&mut rc_perm_denominators); 313 | debug_assert!((numerator_acc / denominator_acc).is_one()); 314 | 315 | let mut permutation_column = Vec::new_in(GpuAllocator); 316 | permutation_column.resize(self.base_columns().num_rows(), Fq::zero()); 317 | 318 | // Insert intermediate memory permutation results 319 | for (i, (n, d)) in zip(mem_perm_numerators, mem_perm_denominators).enumerate() { 320 | permutation_column[i * MEMORY_STEP + Permutation::Memory as usize] = n * d; 321 | } 322 | 323 | // Insert intermediate range check results 324 | for (i, (n, d)) in zip(rc_perm_numerators, rc_perm_denominators).enumerate() { 325 | permutation_column[i * RANGE_CHECK_STEP + Permutation::RangeCheck as usize] = n * d; 326 | } 327 | 328 | Some(Matrix::new(vec![permutation_column])) 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /layouts/src/recursive/mod.rs: -------------------------------------------------------------------------------- 1 | //! Matches `bitwise` layout from StarkWare's open source verifier 2 | //! 3 | 4 | //TODO This is still the starknet layout and has to be updated to the recursive 5 | // parameters. 6 | 7 | pub mod air; 8 | pub mod trace; 9 | 10 | pub use air::AirConfig; 11 | pub use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 12 | pub use trace::ExecutionTrace; 13 | 14 | // TODO Are these correct? 15 | // must be a power-of-two 16 | pub const CYCLE_HEIGHT: usize = 16; 17 | pub const PUBLIC_MEMORY_STEP: usize = 16; 18 | pub const MEMORY_STEP: usize = 2; 19 | pub const RANGE_CHECK_STEP: usize = 4; 20 | pub const DILUTED_CHECK_STEP: usize = 1; //TODO is that correct? 21 | 22 | /// How many cycles per pedersen hash 23 | pub const PEDERSEN_BUILTIN_RATIO: usize = 128; 24 | 25 | /// How many cycles per 128 bit range check 26 | pub const RANGE_CHECK_BUILTIN_RATIO: usize = 8; 27 | pub const RANGE_CHECK_BUILTIN_PARTS: usize = 8; 28 | 29 | pub const NUM_BASE_COLUMNS: usize = 9; 30 | pub const NUM_EXTENSION_COLUMNS: usize = 1; 31 | 32 | pub const DILUTED_CHECK_N_BITS: usize = 16; 33 | pub const DILUTED_CHECK_SPACING: usize = 4; 34 | 35 | pub const BITWISE_RATIO: usize = 8; 36 | -------------------------------------------------------------------------------- /layouts/src/starknet/mod.rs: -------------------------------------------------------------------------------- 1 | //! Matches `starknet` layout from StarkWare's open source verifier 2 | //! 3 | 4 | pub mod air; 5 | pub mod trace; 6 | 7 | pub use air::AirConfig; 8 | use builtins::{utils::curve::StarkwareCurve, pedersen}; 9 | pub use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 10 | pub use trace::ExecutionTrace; 11 | use ark_ec::models::short_weierstrass::SWCurveConfig; 12 | 13 | // must be a power-of-two 14 | pub const CYCLE_HEIGHT: usize = 16; 15 | pub const PUBLIC_MEMORY_STEP: usize = 8; 16 | pub const MEMORY_STEP: usize = 2; 17 | pub const RANGE_CHECK_STEP: usize = 4; 18 | pub const DILUTED_CHECK_STEP: usize = 8; 19 | 20 | /// How many cycles per pedersen hash 21 | pub const PEDERSEN_BUILTIN_RATIO: usize = 32; 22 | 23 | /// How many cycles per 128 bit range check 24 | pub const RANGE_CHECK_BUILTIN_RATIO: usize = 16; 25 | pub const RANGE_CHECK_BUILTIN_PARTS: usize = 8; 26 | 27 | pub const NUM_BASE_COLUMNS: usize = 9; 28 | pub const NUM_EXTENSION_COLUMNS: usize = 1; 29 | 30 | pub const DILUTED_CHECK_N_BITS: usize = 16; 31 | pub const DILUTED_CHECK_SPACING: usize = 4; 32 | 33 | pub const BITWISE_RATIO: usize = 64; 34 | 35 | pub const ECDSA_BUILTIN_RATIO: usize = 2048; 36 | pub const ECDSA_BUILTIN_REPETITIONS: usize = 1; 37 | pub const EC_OP_BUILTIN_RATIO: usize = 1024; 38 | pub const EC_OP_SCALAR_HEIGHT: usize = 256; 39 | pub const EC_OP_N_BITS: usize = 252; 40 | // TODO: take from curve config 41 | pub const ECDSA_SIG_CONFIG_ALPHA: Fp = StarkwareCurve::COEFF_A; 42 | pub const ECDSA_SIG_CONFIG_BETA: Fp = StarkwareCurve::COEFF_B; 43 | pub const ECDSA_SIG_CONFIG_SHIFT_POINT_X: Fp = pedersen::constants::P0.x; 44 | pub const ECDSA_SIG_CONFIG_SHIFT_POINT_Y: Fp = pedersen::constants::P0.y; 45 | 46 | pub const POSEIDON_RATIO: usize = 32; 47 | pub const POSEIDON_M: usize = 3; 48 | pub const POSEIDON_ROUNDS_FULL: usize = 8; 49 | pub const POSEIDON_ROUNDS_PARTIAL: usize = 83; 50 | -------------------------------------------------------------------------------- /layouts/src/utils.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::Field; 2 | use ark_ff::PrimeField; 3 | use binary::MemoryEntry; 4 | use ministark::utils::FieldVariant; 5 | use ministark::StarkExtensionOf; 6 | use ministark_gpu::GpuFftField; 7 | use num_traits::One; 8 | use num_traits::Zero; 9 | use ruint::aliases::U256; 10 | use ruint::uint; 11 | 12 | /// Computes the value of the public memory quotient: 13 | /// Adapted from https://github.com/starkware-libs/starkex-contracts 14 | pub fn compute_public_memory_quotient< 15 | const PUBLIC_MEMORY_STEP: usize, 16 | Fp: GpuFftField + PrimeField, 17 | Fq: StarkExtensionOf, 18 | >( 19 | z: Fq, 20 | alpha: Fq, 21 | trace_len: usize, 22 | public_memory: &[MemoryEntry], 23 | public_memory_padding: MemoryEntry, 24 | ) -> Fq { 25 | // the actual number of public memory cells 26 | let n = public_memory.len(); 27 | // the num of cells allocated for the pub mem (include padding) 28 | let s = trace_len / PUBLIC_MEMORY_STEP; 29 | 30 | // numerator = (z - (0 + alpha * 0))^S, 31 | let numerator = z.pow([s as u64]); 32 | // denominator = \prod_i( z - (addr_i + alpha * value_i) ), 33 | let denominator = public_memory 34 | .iter() 35 | .map(|e| z - (alpha * e.value + Fp::from(e.address))) 36 | .product::(); 37 | let padding = { 38 | // padding = (z - (padding_addr + alpha * padding_value))^(S - N), 39 | let padding_address = Fp::from(public_memory_padding.address); 40 | let padding_value = public_memory_padding.value; 41 | (z - (alpha * padding_value + padding_address)).pow([(s - n) as u64]) 42 | }; 43 | 44 | // numerator / (denominator * padding) 45 | numerator / (denominator * padding) 46 | } 47 | 48 | /// Source: https://github.com/starkware-libs/starkex-contracts 49 | /// 50 | /// # Context 51 | /// The cumulative value is defined using the following recursive formula: 52 | /// 53 | /// $$r_1 = 1$$ 54 | /// $$r_{j+1} = r_j * (1 + z * u_j) + \alpha * u_j^2$$ 55 | /// 56 | /// (for $j >= 1$) where $u_j = Dilute(j, spacing, n_{bits}) - Dilute(j-1, 57 | /// spacing, n_{bits})$ and we want to compute the final value 58 | /// $r_{2^{n_{bits}}}$. Note that $u_j$ depends only on the number of trailing 59 | /// zeros in the binary representation of $j$. Specifically, 60 | /// 61 | /// $$u_{(1 + 2k) * 2^i} = u_{2^i} = 62 | /// u_{2^{i - 1}} + 2^{i * spacing} - 2^{(i - 1) * spacing + 1}.$$ 63 | /// 64 | /// The recursive formula can be reduced to a nonrecursive form: 65 | /// 66 | /// $$r_j = \prod_{i=1}^{j-1}(1 + z*u_i) + \alpha 67 | /// * \sum_{i=1}^{j-1}(u_i^2 * \prod_{k=i + 1}^{j-1}(1 + z * u_m))$$ 68 | /// 69 | /// We rewrite this equation to generate a recursive formula that converges 70 | /// in $log(j)$ steps: Denote: 71 | /// 72 | /// $$p_i = \prod_{n=1}^{2^i - 1}(1 + z * u_n)$$ 73 | /// $$q_i = \sum_{n=1}^{2^i - 1}(u_n^2 * \prod_{m=n + 1}^{2^i-1}(1 + z * u_m))$$ 74 | /// $$x_i = u_{2^i}$$ 75 | /// 76 | /// Clearly $r_{2^i} = p_i + \alpha * q_i$. Moreover, due to the symmetry of the 77 | /// sequence $$u_j, p_i = p_{i - 1} * (1 + z * x_{i - 1}) * p_{i - 1} 78 | /// q_i = q_{i - 1} * (1 + z * x_{i - 1}) * p_{i - 1} 79 | /// + x_{i - 1}^2 * p_{i - 1} + q_{i - 1}$$ 80 | /// 81 | /// Now we can compute $p_{n_{bits}}$ and $q_{n_{bits}}$ in '$n_{bits}$' steps 82 | /// and we are done. 83 | pub fn compute_diluted_cumulative_value< 84 | Fp: GpuFftField + PrimeField, 85 | Fq: StarkExtensionOf, 86 | const N_BITS: usize, 87 | const SPACING: usize, 88 | >( 89 | z: Fq, 90 | alpha: Fq, 91 | ) -> Fq { 92 | assert!(SPACING * N_BITS < Fp::MODULUS_BIT_SIZE as usize); 93 | assert!(SPACING < u64::BITS as usize); 94 | let diff_multiplier = Fp::from(1u64 << SPACING); 95 | let mut diff_x = Fp::from((1u64 << SPACING) - 2); 96 | // Initialize p, q and x to p_1, q_1 and x_0 respectively. 97 | let mut p = z + Fq::ONE; 98 | let mut q = Fq::ONE; 99 | let mut x = Fq::ONE; 100 | for _ in 1..N_BITS { 101 | x += diff_x; 102 | diff_x *= diff_multiplier; 103 | // store intermediate values to save multiplications 104 | let xp = x * p; 105 | let y = p + z * xp; 106 | q += q * y + x * xp; 107 | p *= y; 108 | } 109 | p + q * alpha 110 | } 111 | 112 | /// Orders memory accesses 113 | /// Accesses must be of the form (address, value) 114 | /// Output is of the form (address, value) 115 | // TODO: make sure supports input, output and builtins 116 | pub fn get_ordered_memory_accesses( 117 | trace_len: usize, 118 | accesses: &[MemoryEntry], 119 | public_memory: &[MemoryEntry], 120 | public_memory_padding: MemoryEntry, 121 | ) -> Vec> { 122 | // the number of cells allocated for the public memory 123 | let num_pub_mem_cells = trace_len / PUBLIC_MEMORY_STEP; 124 | 125 | // order all memory accesses by address 126 | // memory accesses are of the form [address, value] 127 | let mut ordered_accesses = accesses 128 | .iter() 129 | .copied() 130 | .chain((0..num_pub_mem_cells - public_memory.len()).map(|_| public_memory_padding)) 131 | .chain(public_memory.iter().copied()) 132 | .collect::>>(); 133 | 134 | ordered_accesses.sort_unstable_by_key(|e| e.address); 135 | 136 | // justification for this is explained in section 9.8 of the Cairo paper https://eprint.iacr.org/2021/1063.pdf. 137 | // SHARP starts the first address at address 1 138 | let (zeros, ordered_accesses) = ordered_accesses.split_at(num_pub_mem_cells); 139 | assert!(zeros.iter().all(|e| e.address.is_zero())); 140 | assert!(ordered_accesses[0].address.is_one()); 141 | 142 | // check memory is "continuous" and "single valued" 143 | ordered_accesses 144 | .array_windows() 145 | .enumerate() 146 | .for_each(|(i, &[curr, next])| { 147 | assert!( 148 | curr == next || curr.address == next.address - 1, 149 | "mismatch at {i}: curr=({curr:?}), next=({next:?})" 150 | ); 151 | }); 152 | 153 | ordered_accesses.to_vec() 154 | } 155 | 156 | pub struct MemoryPool(Vec>); 157 | 158 | impl MemoryPool { 159 | pub fn new() -> Self { 160 | Self(Vec::new()) 161 | } 162 | 163 | /// Pushes a memory access to the pool 164 | pub fn push(&mut self, entry: MemoryEntry) { 165 | self.0.push(entry); 166 | } 167 | 168 | pub fn get_ordered_accesses_with_padding( 169 | &self, 170 | trace_len: usize, 171 | public_memory: Vec>, 172 | padding_entry: MemoryEntry, 173 | ) -> (Vec>, Vec>) { 174 | // order all memory accesses by address 175 | // memory accesses are of the form (address, value) 176 | let mut ordered_accesses = self 177 | .0 178 | .iter() 179 | .copied() 180 | .chain(public_memory) 181 | .collect::>>(); 182 | ordered_accesses.sort_unstable_by_key(|a| a.address); 183 | 184 | // memory values need to be continuos therefore any gaps 185 | // e.g. [..., (a:4, v:..), (a:7, v:..), ...] need to 186 | // be filled with [(a:5, v:..), (a:6, v:..)] as padding. 187 | let mut padding_accesses = Vec::new(); 188 | for &[a, b] in ordered_accesses.array_windows() { 189 | for padding_addr in a.address.saturating_add(1)..b.address { 190 | padding_accesses.push(if a.address == padding_entry.address { 191 | padding_entry 192 | } else { 193 | MemoryEntry { 194 | address: padding_addr, 195 | value: F::ZERO, 196 | } 197 | }) 198 | } 199 | } 200 | 201 | while padding_accesses.len() + ordered_accesses.len() != trace_len { 202 | padding_accesses.push(padding_entry); 203 | } 204 | 205 | // Add padding to the ordered vals 206 | for v in &padding_accesses { 207 | ordered_accesses.push(*v); 208 | } 209 | 210 | // re-sort the accesses. 211 | ordered_accesses.sort(); 212 | // assert_eq!(trace_len, ordered_accesses.len()); 213 | println!("ol: {}", ordered_accesses.len()); 214 | println!("ol: {}", padding_accesses.len()); 215 | // assert_eq!(trace_len / 8, padding_accesses.len()); 216 | 217 | // double check memory is "continuous" and "single valued" 218 | ordered_accesses 219 | .array_windows() 220 | .enumerate() 221 | .for_each(|(i, &[curr, next])| { 222 | assert!( 223 | curr == next || curr.address == next.address - 1, 224 | "mismatch at {i}: curr=({curr:?}), next=({next:?})" 225 | ); 226 | }); 227 | 228 | (ordered_accesses, padding_accesses) 229 | } 230 | } 231 | 232 | impl Default for MemoryPool { 233 | fn default() -> Self { 234 | Self::new() 235 | } 236 | } 237 | 238 | #[derive(Default, Debug, Clone)] 239 | pub struct DilutedCheckPool(Vec); 240 | 241 | impl DilutedCheckPool { 242 | pub fn new() -> Self { 243 | assert!(N_BITS > 0 && N_BITS <= 128); 244 | assert!(SPACING * N_BITS <= 256); 245 | Self(Vec::new()) 246 | } 247 | 248 | /// Pushes a binary value without dilution to the pool 249 | pub fn push(&mut self, v: u128) { 250 | let bit_len = u128::BITS - v.leading_zeros(); 251 | assert!(bit_len <= N_BITS as u32); 252 | self.0.push(v) 253 | } 254 | 255 | /// Pushes a binary value with dilution to the pool 256 | pub fn push_diluted(&mut self, v: U256) { 257 | assert!(v.bit_len() <= (N_BITS - 1) * SPACING + 1); 258 | debug_assert!({ 259 | // check all non diluted bits are zero 260 | let mut res = v; 261 | for i in 0..N_BITS { 262 | res &= !(uint!(1_U256) << (i * SPACING)); 263 | } 264 | res == U256::ZERO 265 | }); 266 | let mut res = 0; 267 | for i in 0..N_BITS { 268 | let bit: u128 = v.bit(i * SPACING).into(); 269 | res |= bit << i; 270 | } 271 | self.push(res) 272 | } 273 | 274 | /// Returns an ordered list of diluted check values with padding. 275 | /// Diluted check values, in their regular form, need to be continuos. 276 | /// For example with SPACING=4: 277 | /// ```text 278 | /// [ 279 | /// 0b0000_0000_0001_0001 -> regular form = 3 (0b0011) 280 | /// 0b0000_0001_0000_0000 -> regular form = 4 (0b0100) 281 | /// 0b0000_0001_0001_0001 -> regular form = 7 (0b0111) 282 | /// 0b0001_0000_0000_0000 -> regular form = 8 (0b1000) 283 | /// ] 284 | /// ``` 285 | /// needs to be filled with 286 | /// ```text 287 | /// [ 288 | /// 0b0000_0001_0000_0001 -> regular form = 5 (0b0101) 289 | /// 0b0000_0001_0001_0000 -> regular form = 6 (0b0110) 290 | /// ] 291 | /// ``` 292 | /// as padding. This padding is added to the ordered list of values and the 293 | /// padding used is also provided. Output is of the form `(ordered_vals, 294 | /// padding_vals)` in their regular form without dilution. 295 | pub fn get_ordered_values_with_padding(&self, min: u128, max: u128) -> (Vec, Vec) { 296 | if self.0.is_empty() { 297 | return (Vec::new(), (min..=max).collect()); 298 | } 299 | 300 | let mut ordered_vals = self.0.clone(); 301 | ordered_vals.sort_unstable(); 302 | 303 | // diluted check values need to be continuos therefore any gaps 304 | // e.g. [..., 3, 4, 7, 8, ...] need to be filled with [5, 6] as padding. 305 | let mut padding_vals = Vec::new(); 306 | 307 | let first = *ordered_vals.first().unwrap(); 308 | assert!(first >= min); 309 | for v in min..first { 310 | padding_vals.push(v); 311 | } 312 | 313 | let last = *ordered_vals.last().unwrap(); 314 | assert!(last <= max); 315 | for v in last + 1..=max { 316 | padding_vals.push(v); 317 | } 318 | 319 | for &[a, b] in ordered_vals.array_windows() { 320 | for v in u128::saturating_add(a, 1)..b { 321 | padding_vals.push(v); 322 | } 323 | } 324 | 325 | // Add padding to the ordered vals (res) 326 | for v in &padding_vals { 327 | ordered_vals.push(*v); 328 | } 329 | 330 | // re-sort the values. 331 | ordered_vals.sort_unstable(); 332 | 333 | (ordered_vals, padding_vals) 334 | } 335 | 336 | pub fn min(&self) -> Option { 337 | self.0.iter().min().copied() 338 | } 339 | 340 | pub fn max(&self) -> Option { 341 | self.0.iter().max().copied() 342 | } 343 | } 344 | 345 | #[derive(Default, Debug, Clone)] 346 | pub struct RangeCheckPool(Vec); 347 | 348 | impl RangeCheckPool { 349 | pub fn new() -> Self { 350 | Self(Vec::new()) 351 | } 352 | 353 | pub fn push(&mut self, v: u16) { 354 | self.0.push(v) 355 | } 356 | 357 | /// Returns an ordered list of the range check values with padding. 358 | /// Range check values need to be continuos therefore any gaps e.g. 359 | /// [..., 3, 4, 7, 8, ...] needs to be filled with [5, 6] as padding. This 360 | /// padding is added to the ordered list of values and the padding used is 361 | /// also provided. Output is of the form `(ordered_vals, padding_vals)`. 362 | pub fn get_ordered_values_with_padding(&self) -> (Vec, Vec) { 363 | let mut ordered_vals = self.0.clone(); 364 | ordered_vals.sort_unstable(); 365 | 366 | // range check values need to be continuos therefore any gaps 367 | // e.g. [..., 3, 4, 7, 8, ...] need to be filled with [5, 6] as padding. 368 | let mut padding_vals = Vec::new(); 369 | for &[a, b] in ordered_vals.array_windows() { 370 | for v in u16::saturating_add(a, 1)..b { 371 | padding_vals.push(v); 372 | } 373 | } 374 | 375 | // Add padding to the ordered vals (res) 376 | for v in &padding_vals { 377 | ordered_vals.push(*v); 378 | } 379 | 380 | // re-sort the values. 381 | ordered_vals.sort_unstable(); 382 | 383 | (ordered_vals, padding_vals) 384 | } 385 | 386 | pub fn min(&self) -> Option { 387 | self.0.iter().min().copied() 388 | } 389 | 390 | pub fn max(&self) -> Option { 391 | self.0.iter().max().copied() 392 | } 393 | } 394 | 395 | /// Maps array items into `FieldVariant::Fp` 396 | // TODO: remove. need for const fn. 397 | pub const fn map_into_fp_array( 398 | arr: [Fp; N], 399 | ) -> [FieldVariant; N] { 400 | let mut res = [FieldVariant::Fp(Fp::ZERO); N]; 401 | let mut i = 0; 402 | while i < N { 403 | res[i] = FieldVariant::Fp(arr[i]); 404 | i += 1; 405 | } 406 | res 407 | } 408 | -------------------------------------------------------------------------------- /prover.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewmilson/sandstorm/bf871325c12a05e8b51c9625b318ee50087234b3/prover.gif -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # See: https://github.com/rust-lang/rustfmt/blob/master/Configurations.md 2 | edition = "2021" 3 | imports_granularity = "Item" 4 | group_imports = "One" 5 | wrap_comments = true 6 | format_macro_matchers = true 7 | format_macro_bodies = true 8 | use_field_init_shorthand = true -------------------------------------------------------------------------------- /src/claims.rs: -------------------------------------------------------------------------------- 1 | use crypto::public_coin::solidity::SolidityVerifierPublicCoin; 2 | use crate::CairoClaim; 3 | use crypto::merkle::LeafVariantMerkleTree; 4 | use crypto::merkle::FriendlyMerkleTree; 5 | use crypto::hash::pedersen::PedersenHashFn; 6 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 7 | use crypto::hash::keccak::Keccak256HashFn; 8 | use crypto::public_coin::cairo::CairoVerifierPublicCoin; 9 | 10 | pub const NUM_FRIENDLY_COMMITMENT_LAYERS: u32 = 22; 11 | 12 | pub mod starknet { 13 | use super::*; 14 | use crypto::hash::keccak::MaskedKeccak256HashFn; 15 | use layouts::starknet::AirConfig; 16 | use layouts::starknet::ExecutionTrace; 17 | 18 | pub type EthVerifierClaim = 19 | CairoClaim>, SolidityVerifierPublicCoin>; 20 | pub type CairoVerifierClaim = 21 | CairoClaim, CairoVerifierPublicCoin>; 22 | } 23 | 24 | pub mod recursive { 25 | use super::*; 26 | use layouts::recursive::AirConfig; 27 | use layouts::recursive::ExecutionTrace; 28 | 29 | pub type EthVerifierClaim = 30 | CairoClaim, SolidityVerifierPublicCoin>; 31 | pub type CairoVerifierClaim = 32 | CairoClaim, CairoVerifierPublicCoin>; 33 | } -------------------------------------------------------------------------------- /src/input.rs: -------------------------------------------------------------------------------- 1 | use binary::{AirPublicInput, Layout}; 2 | use ministark::hash::{ElementHashFn, Digest}; 3 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 4 | use num_bigint::BigUint; 5 | use ruint::{aliases::U256, uint}; 6 | 7 | pub struct CairoAuxInput<'a>(pub &'a AirPublicInput); 8 | 9 | impl<'a> CairoAuxInput<'a> { 10 | fn base_values(&self) -> Vec { 11 | const OFFSET_LOG_N_STEPS: usize = 0; 12 | const OFFSET_RC_MIN: usize = 1; 13 | const OFFSET_RC_MAX: usize = 2; 14 | const OFFSET_LAYOUT_CODE: usize = 3; 15 | const OFFSET_PROGRAM_BEGIN_ADDR: usize = 4; 16 | const OFFSET_PROGRAM_STOP_PTR: usize = 5; 17 | const OFFSET_EXECUTION_BEGIN_ADDR: usize = 6; 18 | const OFFSET_EXECUTION_STOP_PTR: usize = 7; 19 | const OFFSET_OUTPUT_BEGIN_ADDR: usize = 8; 20 | const OFFSET_OUTPUT_STOP_PTR: usize = 9; 21 | const OFFSET_PEDERSEN_BEGIN_ADDR: usize = 10; 22 | const OFFSET_PEDERSEN_STOP_PTR: usize = 11; 23 | const OFFSET_RANGE_CHECK_BEGIN_ADDR: usize = 12; 24 | const OFFSET_RANGE_CHECK_STOP_PTR: usize = 13; 25 | 26 | let segments = self.0.memory_segments; 27 | 28 | const NUM_VALS: usize = OFFSET_RANGE_CHECK_STOP_PTR + 1; 29 | let mut vals = [None; NUM_VALS]; 30 | vals[OFFSET_LOG_N_STEPS] = Some(U256::from(self.0.n_steps.ilog2())); 31 | vals[OFFSET_RC_MIN] = Some(U256::from(self.0.rc_min)); 32 | vals[OFFSET_RC_MAX] = Some(U256::from(self.0.rc_max)); 33 | vals[OFFSET_LAYOUT_CODE] = Some(U256::from(self.0.layout.sharp_code())); 34 | vals[OFFSET_PROGRAM_BEGIN_ADDR] = Some(U256::from(segments.program.begin_addr)); 35 | vals[OFFSET_PROGRAM_STOP_PTR] = Some(U256::from(segments.program.stop_ptr)); 36 | vals[OFFSET_EXECUTION_BEGIN_ADDR] = Some(U256::from(segments.execution.begin_addr)); 37 | vals[OFFSET_EXECUTION_STOP_PTR] = Some(U256::from(segments.execution.stop_ptr)); 38 | vals[OFFSET_OUTPUT_BEGIN_ADDR] = segments.output.map(|s| U256::from(s.begin_addr)); 39 | vals[OFFSET_OUTPUT_STOP_PTR] = segments.output.map(|s| U256::from(s.stop_ptr)); 40 | vals[OFFSET_PEDERSEN_BEGIN_ADDR] = segments.pedersen.map(|s| U256::from(s.begin_addr)); 41 | vals[OFFSET_PEDERSEN_STOP_PTR] = segments.pedersen.map(|s| U256::from(s.stop_ptr)); 42 | vals[OFFSET_RANGE_CHECK_BEGIN_ADDR] = 43 | segments.range_check.map(|s| U256::from(s.begin_addr)); 44 | vals[OFFSET_RANGE_CHECK_STOP_PTR] = segments.range_check.map(|s| U256::from(s.stop_ptr)); 45 | vals.map(Option::unwrap).to_vec() 46 | } 47 | 48 | fn layout_specific_values(&self) -> Vec { 49 | let segments = self.0.memory_segments; 50 | let public_memory_padding = self.0.public_memory_padding(); 51 | 52 | match self.0.layout { 53 | Layout::Starknet => { 54 | const OFFSET_ECDSA_BEGIN_ADDR: usize = 0; 55 | const OFFSET_ECDSA_STOP_PTR: usize = 1; 56 | const OFFSET_BITWISE_BEGIN_ADDR: usize = 2; 57 | const OFFSET_BITWISE_STOP_ADDR: usize = 3; 58 | const OFFSET_EC_OP_BEGIN_ADDR: usize = 4; 59 | const OFFSET_EC_OP_STOP_ADDR: usize = 5; 60 | const OFFSET_POSEIDON_BEGIN_ADDR: usize = 6; 61 | const OFFSET_POSEIDON_STOP_PTR: usize = 7; 62 | const OFFSET_PUBLIC_MEMORY_PADDING_ADDR: usize = 8; 63 | const OFFSET_PUBLIC_MEMORY_PADDING_VALUE: usize = 9; 64 | const OFFSET_N_PUBLIC_MEMORY_PAGES: usize = 10; 65 | 66 | const NUM_VALS: usize = OFFSET_N_PUBLIC_MEMORY_PAGES + 1; 67 | let mut vals = [None; NUM_VALS]; 68 | vals[OFFSET_ECDSA_BEGIN_ADDR] = segments.ecdsa.map(|s| U256::from(s.begin_addr)); 69 | vals[OFFSET_ECDSA_STOP_PTR] = segments.ecdsa.map(|s| U256::from(s.stop_ptr)); 70 | vals[OFFSET_BITWISE_BEGIN_ADDR] = 71 | segments.bitwise.map(|s| U256::from(s.begin_addr)); 72 | vals[OFFSET_BITWISE_STOP_ADDR] = segments.bitwise.map(|s| U256::from(s.stop_ptr)); 73 | vals[OFFSET_EC_OP_BEGIN_ADDR] = segments.ec_op.map(|s| U256::from(s.begin_addr)); 74 | vals[OFFSET_EC_OP_STOP_ADDR] = segments.ec_op.map(|s| U256::from(s.stop_ptr)); 75 | vals[OFFSET_POSEIDON_BEGIN_ADDR] = 76 | segments.poseidon.map(|s| U256::from(s.begin_addr)); 77 | vals[OFFSET_POSEIDON_STOP_PTR] = segments.poseidon.map(|s| U256::from(s.stop_ptr)); 78 | vals[OFFSET_PUBLIC_MEMORY_PADDING_ADDR] = 79 | Some(U256::from(public_memory_padding.address)); 80 | vals[OFFSET_PUBLIC_MEMORY_PADDING_VALUE] = 81 | Some(U256::from::(public_memory_padding.value.into())); 82 | // Only 1 memory page currently for the main memory page 83 | // TODO: support more memory pages 84 | vals[OFFSET_N_PUBLIC_MEMORY_PAGES] = Some(uint!(1_U256)); 85 | vals.map(Option::unwrap).to_vec() 86 | } 87 | Layout::Recursive => { 88 | const OFFSET_BITWISE_BEGIN_ADDR: usize = 0; 89 | const OFFSET_BITWISE_STOP_ADDR: usize = 1; 90 | const OFFSET_PUBLIC_MEMORY_PADDING_ADDR: usize = 2; 91 | const OFFSET_PUBLIC_MEMORY_PADDING_VALUE: usize = 3; 92 | const OFFSET_N_PUBLIC_MEMORY_PAGES: usize = 4; 93 | 94 | const NUM_VALS: usize = OFFSET_N_PUBLIC_MEMORY_PAGES + 1; 95 | let mut vals = [None; NUM_VALS]; 96 | 97 | vals[OFFSET_BITWISE_BEGIN_ADDR] = 98 | segments.bitwise.map(|s| U256::from(s.begin_addr)); 99 | vals[OFFSET_BITWISE_STOP_ADDR] = segments.bitwise.map(|s| U256::from(s.stop_ptr)); 100 | vals[OFFSET_PUBLIC_MEMORY_PADDING_ADDR] = 101 | Some(U256::from(public_memory_padding.address)); 102 | vals[OFFSET_PUBLIC_MEMORY_PADDING_VALUE] = 103 | Some(U256::from::(public_memory_padding.value.into())); 104 | // Only 1 memory page currently for the main memory page 105 | // TODO: support more memory pages 106 | vals[OFFSET_N_PUBLIC_MEMORY_PAGES] = Some(uint!(1_U256)); 107 | vals.map(Option::unwrap).to_vec() 108 | } 109 | _ => unimplemented!(), 110 | } 111 | } 112 | 113 | fn memory_page_values>(&self) -> Vec { 114 | // The public memory consists of individual memory pages. 115 | // The first page is for main memory. 116 | // For each page: 117 | // * First address in the page (this field is not included for the first page). 118 | // * Page size. (number of memory pairs) 119 | // * Page hash (hash of memory pairs) 120 | // TODO: support other memory pages 121 | const _PAGE_INFO_ADDRESS_OFFSET: usize = 0; 122 | const _PAGE_INFO_SIZE_OFFSET: usize = 1; 123 | const _PAGE_INFO_HASH_OFFSET: usize = 2; 124 | 125 | // Hash the address value pairs of the main memory page 126 | let main_page_hash: [u8; 32] = { 127 | let memory_elements = self 128 | .0 129 | .public_memory 130 | .iter() 131 | .flat_map(|e| [e.address.into(), e.value]); 132 | H::hash_elements(memory_elements).as_bytes() 133 | }; 134 | 135 | // NOTE: no address main memory page because It's implicitly "1". 136 | let mut main_page = [None; 2]; 137 | main_page[0] = Some(U256::from(self.0.public_memory.len())); 138 | main_page[1] = Some(U256::try_from_be_slice(&main_page_hash).unwrap()); 139 | 140 | main_page.map(Option::unwrap).to_vec() 141 | } 142 | 143 | pub fn public_input_elements>(&self) -> Vec { 144 | [ 145 | self.base_values(), 146 | self.layout_specific_values(), 147 | self.memory_page_values::(), 148 | ] 149 | .concat() 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(allocator_api, array_chunks, int_roundings)] 2 | 3 | use ark_ff::Field; 4 | use ark_ff::PrimeField; 5 | use ark_serialize::CanonicalSerialize; 6 | use binary::AirPublicInput; 7 | use binary::CompiledProgram; 8 | use crypto::hash::blake2s::Blake2sHashFn; 9 | use crypto::hash::keccak::CanonicalKeccak256HashFn; 10 | use crypto::hash::pedersen::PedersenHashFn; 11 | use crypto::merkle::mixed::MixedMerkleDigest; 12 | use crypto::public_coin::cairo::CairoVerifierPublicCoin; 13 | use crypto::public_coin::solidity::SolidityVerifierPublicCoin; 14 | use input::CairoAuxInput; 15 | use layouts::CairoTrace; 16 | use layouts::CairoWitness; 17 | use ministark::air::AirConfig; 18 | use ministark::composer::DeepCompositionCoeffs; 19 | use ministark::hash::ElementHashFn; 20 | use ministark::hash::HashFn; 21 | use ministark::merkle::MatrixMerkleTree; 22 | use ministark::merkle::MerkleTree; 23 | use ministark::random::PublicCoin; 24 | use ministark::random::PublicCoinImpl; 25 | use ministark::stark::Stark; 26 | use ministark::Air; 27 | use ministark_gpu::GpuFftField; 28 | use ministark_gpu::fields::p3618502788666131213697322783095070105623107215331596699973092056135872020481::ark::Fp; 29 | use std::marker::PhantomData; 30 | 31 | pub mod claims; 32 | pub mod input; 33 | 34 | pub struct CairoClaim< 35 | Fp: GpuFftField + PrimeField, 36 | A: AirConfig>, 37 | T: CairoTrace, 38 | M: MerkleTree + MatrixMerkleTree + MatrixMerkleTree, 39 | P: CairoPublicCoin, 40 | > where 41 | A::Fp: PrimeField, 42 | { 43 | cairo_program: CompiledProgram, 44 | air_public_input: AirPublicInput, 45 | _phantom: PhantomData<(Fp, A, T, M, P)>, 46 | } 47 | 48 | impl< 49 | Fp: GpuFftField + PrimeField, 50 | A: AirConfig>, 51 | T: CairoTrace, 52 | M: MerkleTree + MatrixMerkleTree + MatrixMerkleTree, 53 | P: CairoPublicCoin, 54 | > CairoClaim 55 | where 56 | A::Fp: PrimeField, 57 | { 58 | pub fn new(cairo_program: CompiledProgram, air_public_input: AirPublicInput) -> Self { 59 | Self { 60 | cairo_program, 61 | air_public_input, 62 | _phantom: PhantomData, 63 | } 64 | } 65 | 66 | pub fn public_input(&self) -> &AirPublicInput { 67 | &self.air_public_input 68 | } 69 | 70 | pub fn program(&self) -> &CompiledProgram { 71 | &self.cairo_program 72 | } 73 | } 74 | 75 | impl< 76 | Fp: GpuFftField + PrimeField, 77 | A: AirConfig>, 78 | T: CairoTrace, 79 | M: MerkleTree + MatrixMerkleTree + MatrixMerkleTree, 80 | P: CairoPublicCoin, 81 | > Stark for CairoClaim 82 | where 83 | A::Fp: PrimeField, 84 | { 85 | type Fp = Fp; 86 | type Fq = A::Fq; 87 | type AirConfig = A; 88 | type Digest = M::Root; 89 | type PublicCoin = P; 90 | type Witness = CairoWitness; 91 | type MerkleTree = M; 92 | type Trace = T; 93 | 94 | fn generate_trace(&self, witness: CairoWitness) -> T { 95 | T::new( 96 | self.cairo_program.clone(), 97 | self.get_public_inputs(), 98 | witness, 99 | ) 100 | } 101 | 102 | fn gen_deep_coeffs( 103 | &self, 104 | public_coin: &mut Self::PublicCoin, 105 | air: &Air, 106 | ) -> DeepCompositionCoeffs { 107 | let alpha = public_coin.draw(); 108 | let mut coeff_iter = (0..).map(|i| alpha.pow([i])); 109 | let num_execution_trace = air.trace_arguments().len(); 110 | let num_composition_trace = air.ce_blowup_factor(); 111 | DeepCompositionCoeffs { 112 | execution_trace: (&mut coeff_iter).take(num_execution_trace).collect(), 113 | composition_trace: (&mut coeff_iter).take(num_composition_trace).collect(), 114 | degree: (Self::Fq::ONE, Self::Fq::ZERO), 115 | } 116 | } 117 | 118 | fn gen_public_coin(&self, air: &ministark::Air) -> Self::PublicCoin { 119 | P::from_public_input(air.public_inputs()) 120 | } 121 | 122 | fn get_public_inputs(&self) -> AirPublicInput { 123 | self.air_public_input.clone() 124 | } 125 | } 126 | 127 | pub trait CairoPublicCoin: PublicCoin { 128 | fn from_public_input( 129 | public_input: &AirPublicInput<::BasePrimeField>, 130 | ) -> Self; 131 | } 132 | 133 | impl> CairoPublicCoin for PublicCoinImpl { 134 | fn from_public_input( 135 | air_public_input: &AirPublicInput<::BasePrimeField>, 136 | ) -> Self { 137 | // NOTE: this generic implementation is only intended for experimentation so the 138 | // implementation is rather strange 139 | let mut bytes = Vec::new(); 140 | air_public_input.serialize_compressed(&mut bytes).unwrap(); 141 | Self::new(H::hash_chunks([&*bytes])) 142 | } 143 | } 144 | 145 | impl CairoPublicCoin for SolidityVerifierPublicCoin { 146 | fn from_public_input(public_input: &AirPublicInput) -> Self { 147 | let aux_input = CairoAuxInput(public_input); 148 | let mut seed = Vec::new(); 149 | for element in aux_input.public_input_elements::() { 150 | seed.extend_from_slice(&element.to_be_bytes::<32>()) 151 | } 152 | Self::new(CanonicalKeccak256HashFn::hash_chunks([&*seed])) 153 | } 154 | } 155 | 156 | impl CairoPublicCoin for CairoVerifierPublicCoin { 157 | fn from_public_input(public_input: &AirPublicInput) -> Self { 158 | let aux_input = CairoAuxInput(public_input); 159 | let mut seed = Vec::new(); 160 | for element in aux_input.public_input_elements::() { 161 | seed.extend_from_slice(&element.to_be_bytes::<32>()) 162 | } 163 | Self::new(MixedMerkleDigest::LowLevel(Blake2sHashFn::hash_chunks([ 164 | &*seed, 165 | ]))) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /verifier.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewmilson/sandstorm/bf871325c12a05e8b51c9625b318ee50087234b3/verifier.gif --------------------------------------------------------------------------------