From f21a2e53cc2ad3c580b26a8b1d19ec3f0fc35481 Mon Sep 17 00:00:00 2001 From: alv-around <8678242+alv-around@users.noreply.github.com> Date: Fri, 20 Dec 2024 15:50:21 +0100 Subject: [PATCH 1/8] book: sdk usage correction (#1120) --- book/src/advanced-usage/sdk.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/book/src/advanced-usage/sdk.md b/book/src/advanced-usage/sdk.md index 50baf12a86..2f0fb973d0 100644 --- a/book/src/advanced-usage/sdk.md +++ b/book/src/advanced-usage/sdk.md @@ -40,13 +40,18 @@ let sdk = Sdk; let vm_config = SdkVmConfig::builder() .system(Default::default()) .rv32i(Default::default()) + .rv32m(Default::default()) .io(Default::default()) .build(); // 2a. Build the ELF with guest options and a target filter. -let guest_opts = GuestOptions::default().with_features(vec!["parallel"]); -let target_filter = TargetFilter::default().with_kind("bin".to_string()); -let elf = sdk.build(guest_opts, "your_path_project_root", &target_filter)?; +let target_path = "your_path_project_root"; +let guest_opts = GuestOptions::default(); +let target_filter = TargetFilter { + name: target_path.to_string(), + kind: "bin".to_string(), +}; +let elf = sdk.build(guest_opts, target_path, &Some(target_filter))?; // 2b. Load the ELF from a file let elf_bytes = fs::read("your_path_to_elf")?; let elf = Elf::decode(&elf_bytes, MEM_SIZE as u32)?; @@ -60,6 +65,7 @@ let exe = sdk.transpile(elf, vm_config.transpiler())?; The `SdkVmConfig` struct allows you to specify the extensions and system configuration your VM will use. To customize your own configuration, you can use the `SdkVmConfig::builder()` method and set the extensions and system configuration you want. ## Running a Program + To run your program and see the public value output, you can do the following: ```rust @@ -105,6 +111,7 @@ let proof = app_prover.generate_app_proof(stdin.clone()); ``` ## Verifying Proofs + After generating a proof, you can verify it. To do so, you need your verifying key (which you can get from your `AppProvingKey`) and the output of your `generate_app_proof` call. ```rust From e88d71b81bc426bd899a084af2b46a3d4fd44cae Mon Sep 17 00:00:00 2001 From: stephenh-axiom-xyz Date: Sun, 22 Dec 2024 12:16:27 -0500 Subject: [PATCH 2/8] feat: use Plonky3 Poseidon2Air, split aggregation and system chips (#1122) * feat: use Plonky3 Poseidon2Air, Native extension Poseidon2 chip doesn't use interactions * feat: interaction-only Poseidon2 chip for VM * fix: delete patch config toml * fix: use alternative commit for stark-sdk to run tests * fix: accidental typo * fix: Poseidon2 traces cannot be 0 * chore: update stark-backend * chore: update `stark-backend` to v0.1.2-alpha * chore: example not in workspace * chore: remove old poseidon2-air * chore: add comments clarifying its a wrapper * chore: reorganize folder * chore: remove HlMat4 and other code savings * chore: allocation * chore: add todo * fix: switch verify_fibair bench to volatile * Revert "fix: switch verify_fibair bench to volatile" This reverts commit e6cb959ffebbc3d98679a448a93aa40012825b7a. The SDK requires continations on in a bunch of places. --------- Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> --- Cargo.lock | 44 +- Cargo.toml | 5 +- crates/circuits/poseidon2-air/Cargo.toml | 4 + crates/circuits/poseidon2-air/src/air.rs | 69 +++ crates/circuits/poseidon2-air/src/babybear.rs | 64 +++ crates/circuits/poseidon2-air/src/config.rs | 69 +++ crates/circuits/poseidon2-air/src/lib.rs | 119 +++++- crates/circuits/poseidon2-air/src/permute.rs | 143 +++++++ .../poseidon2-air/src/poseidon2/air.rs | 363 ---------------- .../poseidon2-air/src/poseidon2/bridge.rs | 16 - .../poseidon2-air/src/poseidon2/columns.rs | 281 ------------ .../poseidon2-air/src/poseidon2/mod.rs | 137 ------ .../poseidon2-air/src/poseidon2/tests.rs | 260 ----------- .../poseidon2-air/src/poseidon2/trace.rs | 165 ------- crates/circuits/poseidon2-air/src/tests.rs | 98 +++++ crates/cli/Cargo.toml | 3 - crates/sdk/Cargo.toml | 3 - crates/sdk/example/Cargo.toml | 1 + crates/vm/src/arch/config.rs | 6 +- crates/vm/src/arch/extensions.rs | 20 +- crates/vm/src/arch/hasher/poseidon2.rs | 10 +- crates/vm/src/arch/segment.rs | 6 +- crates/vm/src/arch/testing/mod.rs | 4 +- crates/vm/src/system/memory/tests.rs | 31 +- crates/vm/src/system/poseidon2/air.rs | 179 ++------ crates/vm/src/system/poseidon2/bridge.rs | 45 -- crates/vm/src/system/poseidon2/chip.rs | 73 ++++ crates/vm/src/system/poseidon2/columns.rs | 242 +---------- crates/vm/src/system/poseidon2/mod.rs | 403 ++++-------------- crates/vm/src/system/poseidon2/tests.rs | 322 +++++--------- crates/vm/src/system/poseidon2/trace.rs | 79 ++-- extensions/native/circuit/Cargo.toml | 1 + extensions/native/circuit/src/extension.rs | 21 +- extensions/native/circuit/src/lib.rs | 2 + .../native/circuit/src/poseidon2/air.rs | 179 ++++++++ .../native/circuit/src/poseidon2/chip.rs | 207 +++++++++ .../native/circuit/src/poseidon2/columns.rs | 60 +++ .../native/circuit/src/poseidon2/mod.rs | 125 ++++++ .../native/circuit/src/poseidon2/tests.rs | 135 ++++++ .../native/circuit/src/poseidon2/trace.rs | 80 ++++ 40 files changed, 1766 insertions(+), 2308 deletions(-) create mode 100644 crates/circuits/poseidon2-air/src/air.rs create mode 100644 crates/circuits/poseidon2-air/src/babybear.rs create mode 100644 crates/circuits/poseidon2-air/src/config.rs create mode 100644 crates/circuits/poseidon2-air/src/permute.rs delete mode 100644 crates/circuits/poseidon2-air/src/poseidon2/air.rs delete mode 100644 crates/circuits/poseidon2-air/src/poseidon2/bridge.rs delete mode 100644 crates/circuits/poseidon2-air/src/poseidon2/columns.rs delete mode 100644 crates/circuits/poseidon2-air/src/poseidon2/mod.rs delete mode 100644 crates/circuits/poseidon2-air/src/poseidon2/tests.rs delete mode 100644 crates/circuits/poseidon2-air/src/poseidon2/trace.rs create mode 100644 crates/circuits/poseidon2-air/src/tests.rs delete mode 100644 crates/vm/src/system/poseidon2/bridge.rs create mode 100644 crates/vm/src/system/poseidon2/chip.rs create mode 100644 extensions/native/circuit/src/poseidon2/air.rs create mode 100644 extensions/native/circuit/src/poseidon2/chip.rs create mode 100644 extensions/native/circuit/src/poseidon2/columns.rs create mode 100644 extensions/native/circuit/src/poseidon2/mod.rs create mode 100644 extensions/native/circuit/src/poseidon2/tests.rs create mode 100644 extensions/native/circuit/src/poseidon2/trace.rs diff --git a/Cargo.lock b/Cargo.lock index 6763958aa1..180e5531dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1106,7 +1106,6 @@ dependencies = [ "num-bigint-dig", "openvm-build", "openvm-circuit", - "openvm-cli-example-test", "openvm-keccak256-circuit", "openvm-keccak256-transpiler", "openvm-native-recursion", @@ -3716,13 +3715,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "openvm-cli-example-test" -version = "0.0.0" -dependencies = [ - "openvm", -] - [[package]] name = "openvm-ecc-circuit" version = "0.1.0-alpha" @@ -3972,6 +3964,7 @@ dependencies = [ "openvm-stark-sdk", "parking_lot", "rand", + "rayon", "serde", "strum", "test-case", @@ -4172,6 +4165,7 @@ name = "openvm-poseidon2-air" version = "0.1.0-alpha" dependencies = [ "ark-ff 0.4.2", + "derivative", "itertools 0.13.0", "lazy_static", "openvm-circuit-primitives", @@ -4179,9 +4173,11 @@ dependencies = [ "openvm-stark-sdk", "p3-monty-31", "p3-poseidon2", + "p3-poseidon2-air", "p3-symmetric", "rand", "test-case", + "tracing", "zkhash", ] @@ -4306,7 +4302,6 @@ dependencies = [ "openvm-pairing-transpiler", "openvm-rv32im-circuit", "openvm-rv32im-transpiler", - "openvm-sdk-example-test", "openvm-stark-backend", "openvm-stark-sdk", "openvm-transpiler", @@ -4315,17 +4310,10 @@ dependencies = [ "tracing", ] -[[package]] -name = "openvm-sdk-example-test" -version = "0.0.0" -dependencies = [ - "openvm", -] - [[package]] name = "openvm-stark-backend" -version = "0.1.0-alpha" -source = "git+https://github.com/openvm-org/stark-backend.git?tag=v0.1.1-alpha#a995f8e03662e0a18c89edbe4424933b99c42e52" +version = "0.1.2-alpha" +source = "git+https://github.com/openvm-org/stark-backend.git?tag=v0.1.2-alpha#a97c6664ac29808662b5709f4e4462ce7fb53af0" dependencies = [ "async-trait", "cfg-if", @@ -4351,8 +4339,8 @@ dependencies = [ [[package]] name = "openvm-stark-sdk" -version = "0.1.0-alpha" -source = "git+https://github.com/openvm-org/stark-backend.git?tag=v0.1.1-alpha#a995f8e03662e0a18c89edbe4424933b99c42e52" +version = "0.1.2-alpha" +source = "git+https://github.com/openvm-org/stark-backend.git?tag=v0.1.2-alpha#a97c6664ac29808662b5709f4e4462ce7fb53af0" dependencies = [ "derive_more 0.99.18", "ff 0.13.0", @@ -4752,6 +4740,22 @@ dependencies = [ "rand", ] +[[package]] +name = "p3-poseidon2-air" +version = "0.1.0" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=9b267c4#9b267c411cc8965c709b27d7b0ef81e225603c59" +dependencies = [ + "p3-air", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-poseidon2", + "p3-util", + "rand", + "tikv-jemallocator", + "tracing", +] + [[package]] name = "p3-symmetric" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 720e26efde..2cecbd2b66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,8 +112,8 @@ openvm-platform = { path = "crates/toolchain/platform", default-features = false openvm-transpiler = { path = "crates/toolchain/transpiler", default-features = false } openvm-circuit = { path = "crates/vm", default-features = false } openvm-circuit-derive = { path = "crates/vm/derive", default-features = false } -openvm-stark-backend = { git = "https://github.com/openvm-org/stark-backend.git", tag = "v0.1.1-alpha", default-features = false } -openvm-stark-sdk = { git = "https://github.com/openvm-org/stark-backend.git", tag = "v0.1.1-alpha", default-features = false } +openvm-stark-backend = { git = "https://github.com/openvm-org/stark-backend.git", tag = "v0.1.2-alpha", default-features = false } +openvm-stark-sdk = { git = "https://github.com/openvm-org/stark-backend.git", tag = "v0.1.2-alpha", default-features = false } # Extensions openvm-algebra-circuit = { path = "extensions/algebra/circuit", default-features = false } @@ -164,6 +164,7 @@ p3-merkle-tree = { git = "https://github.com/Plonky3/Plonky3.git", rev = "9b267c p3-monty-31 = { git = "https://github.com/Plonky3/Plonky3.git", rev = "9b267c4" } p3-poseidon = { git = "https://github.com/Plonky3/Plonky3.git", rev = "9b267c4" } p3-poseidon2 = { git = "https://github.com/Plonky3/Plonky3.git", rev = "9b267c4" } +p3-poseidon2-air = { git = "https://github.com/Plonky3/Plonky3.git", rev = "9b267c4" } p3-symmetric = { git = "https://github.com/Plonky3/Plonky3.git", rev = "9b267c4" } p3-uni-stark = { git = "https://github.com/Plonky3/Plonky3.git", rev = "9b267c4" } p3-maybe-rayon = { git = "https://github.com/Plonky3/Plonky3.git", rev = "9b267c4" } # the "parallel" feature is NOT on by default to allow single-threaded benchmarking diff --git a/crates/circuits/poseidon2-air/Cargo.toml b/crates/circuits/poseidon2-air/Cargo.toml index b2058a737e..69e9faa0a0 100644 --- a/crates/circuits/poseidon2-air/Cargo.toml +++ b/crates/circuits/poseidon2-air/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "openvm-poseidon2-air" +description = "Wrapper for the Plonky3 Poseidon2 AIR." version.workspace = true authors.workspace = true edition.workspace = true @@ -11,6 +12,7 @@ license.workspace = true p3-monty-31 = { workspace = true } p3-symmetric = { workspace = true } p3-poseidon2 = { workspace = true } +p3-poseidon2-air = { workspace = true } zkhash = { workspace = true } openvm-circuit-primitives = { workspace = true } @@ -20,6 +22,8 @@ openvm-stark-sdk = { workspace = true } rand.workspace = true lazy_static.workspace = true itertools.workspace = true +tracing.workspace = true +derivative.workspace = true [dev-dependencies] p3-symmetric = { workspace = true } diff --git a/crates/circuits/poseidon2-air/src/air.rs b/crates/circuits/poseidon2-air/src/air.rs new file mode 100644 index 0000000000..777d933ac3 --- /dev/null +++ b/crates/circuits/poseidon2-air/src/air.rs @@ -0,0 +1,69 @@ +use openvm_stark_backend::{ + p3_air::{Air, AirBuilder, BaseAir}, + p3_field::Field, + rap::{BaseAirWithPublicValues, PartitionedBaseAir}, +}; +use p3_poseidon2_air::{Poseidon2Air, Poseidon2Cols}; + +use super::{ + BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS, BABY_BEAR_POSEIDON2_PARTIAL_ROUNDS, + BABY_BEAR_POSEIDON2_SBOX_DEGREE, POSEIDON2_WIDTH, +}; +use crate::{BabyBearPoseidon2LinearLayers, Plonky3RoundConstants}; + +pub type Poseidon2SubCols = Poseidon2Cols< + F, + POSEIDON2_WIDTH, + BABY_BEAR_POSEIDON2_SBOX_DEGREE, + SBOX_REGISTERS, + BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS, + BABY_BEAR_POSEIDON2_PARTIAL_ROUNDS, +>; + +pub type Plonky3Poseidon2Air = Poseidon2Air< + F, + LinearLayers, + POSEIDON2_WIDTH, + BABY_BEAR_POSEIDON2_SBOX_DEGREE, + SBOX_REGISTERS, + BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS, + BABY_BEAR_POSEIDON2_PARTIAL_ROUNDS, +>; + +#[derive(Debug)] +pub enum Poseidon2SubAir { + BabyBearMds(Plonky3Poseidon2Air), +} + +impl Poseidon2SubAir { + pub fn new(constants: Plonky3RoundConstants) -> Self { + Self::BabyBearMds(Plonky3Poseidon2Air::new(constants)) + } +} + +impl BaseAir for Poseidon2SubAir { + fn width(&self) -> usize { + match self { + Self::BabyBearMds(air) => air.width(), + } + } +} + +impl BaseAirWithPublicValues + for Poseidon2SubAir +{ +} +impl PartitionedBaseAir + for Poseidon2SubAir +{ +} + +impl Air + for Poseidon2SubAir +{ + fn eval(&self, builder: &mut AB) { + match self { + Self::BabyBearMds(air) => air.eval(builder), + } + } +} diff --git a/crates/circuits/poseidon2-air/src/babybear.rs b/crates/circuits/poseidon2-air/src/babybear.rs new file mode 100644 index 0000000000..e46db07e62 --- /dev/null +++ b/crates/circuits/poseidon2-air/src/babybear.rs @@ -0,0 +1,64 @@ +use std::array::from_fn; + +use lazy_static::lazy_static; +use openvm_stark_backend::p3_field::AbstractField; +use openvm_stark_sdk::p3_baby_bear::BabyBear; +use zkhash::{ + ark_ff::PrimeField as _, fields::babybear::FpBabyBear as HorizenBabyBear, + poseidon2::poseidon2_instance_babybear::RC16, +}; + +use super::{ + Poseidon2Constants, BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS, BABY_BEAR_POSEIDON2_PARTIAL_ROUNDS, + POSEIDON2_WIDTH, +}; + +pub(crate) fn horizen_to_p3_babybear(horizen_babybear: HorizenBabyBear) -> BabyBear { + BabyBear::from_canonical_u64(horizen_babybear.into_bigint().0[0]) +} + +pub(crate) fn horizen_round_consts() -> Poseidon2Constants { + let p3_rc16: Vec> = RC16 + .iter() + .map(|round| { + round + .iter() + .map(|babybear| horizen_to_p3_babybear(*babybear)) + .collect() + }) + .collect(); + let p_end = BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS + BABY_BEAR_POSEIDON2_PARTIAL_ROUNDS; + + let beginning_full_round_constants: [[BabyBear; POSEIDON2_WIDTH]; + BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS] = from_fn(|i| p3_rc16[i].clone().try_into().unwrap()); + let partial_round_constants: [BabyBear; BABY_BEAR_POSEIDON2_PARTIAL_ROUNDS] = + from_fn(|i| p3_rc16[i + BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS][0]); + let ending_full_round_constants: [[BabyBear; POSEIDON2_WIDTH]; + BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS] = + from_fn(|i| p3_rc16[i + p_end].clone().try_into().unwrap()); + + Poseidon2Constants { + beginning_full_round_constants, + partial_round_constants, + ending_full_round_constants, + } +} + +lazy_static! { + pub static ref BABYBEAR_BEGIN_EXT_CONSTS: [[BabyBear; POSEIDON2_WIDTH]; BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS] = + horizen_round_consts().beginning_full_round_constants; + pub static ref BABYBEAR_PARTIAL_CONSTS: [BabyBear; BABY_BEAR_POSEIDON2_PARTIAL_ROUNDS] = + horizen_round_consts().partial_round_constants; + pub static ref BABYBEAR_END_EXT_CONSTS: [[BabyBear; POSEIDON2_WIDTH]; BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS] = + horizen_round_consts().ending_full_round_constants; +} + +pub(crate) fn babybear_internal_linear_layer( + state: &mut [FA; WIDTH], + int_diag_m1_matrix: &[FA::F; WIDTH], +) { + let sum = state.iter().cloned().sum::(); + for (input, diag_m1) in state.iter_mut().zip(int_diag_m1_matrix) { + *input = sum.clone() + FA::from_f(*diag_m1) * input.clone(); + } +} diff --git a/crates/circuits/poseidon2-air/src/config.rs b/crates/circuits/poseidon2-air/src/config.rs new file mode 100644 index 0000000000..0992e4b3d1 --- /dev/null +++ b/crates/circuits/poseidon2-air/src/config.rs @@ -0,0 +1,69 @@ +use openvm_stark_backend::p3_field::{Field, PrimeField32}; +use openvm_stark_sdk::p3_baby_bear::BabyBear; +use p3_poseidon2::ExternalLayerConstants; +use p3_poseidon2_air::RoundConstants; + +use super::{ + BABYBEAR_BEGIN_EXT_CONSTS, BABYBEAR_END_EXT_CONSTS, BABYBEAR_PARTIAL_CONSTS, + BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS, BABY_BEAR_POSEIDON2_PARTIAL_ROUNDS, POSEIDON2_WIDTH, +}; + +// Currently only contains round constants, but this struct may contain other configuration parameters in the future. +#[derive(Clone, Copy, Debug)] +pub struct Poseidon2Config { + pub constants: Poseidon2Constants, +} + +impl Default for Poseidon2Config { + fn default() -> Self { + Self { + constants: default_baby_bear_rc(), + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct Poseidon2Constants { + pub beginning_full_round_constants: + [[F; POSEIDON2_WIDTH]; BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS], + pub partial_round_constants: [F; BABY_BEAR_POSEIDON2_PARTIAL_ROUNDS], + pub ending_full_round_constants: [[F; POSEIDON2_WIDTH]; BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS], +} + +impl Poseidon2Constants { + // FIXME[stephenh]: PR https://github.com/Plonky3/Plonky3/pull/588 is now merged. + // We can remove Poseidon2Constants and use Plonky3RoundConstants directly after updating the plonky3 commit. + pub fn to_round_constants(&self) -> Plonky3RoundConstants { + unsafe { std::mem::transmute_copy(self) } + } + + pub fn to_external_internal_constants( + &self, + ) -> (ExternalLayerConstants, Vec) { + ( + ExternalLayerConstants::new( + self.beginning_full_round_constants.to_vec(), + self.ending_full_round_constants.to_vec(), + ), + self.partial_round_constants.to_vec(), + ) + } +} + +// Round constants for only BabyBear, but we convert to `F` due to some annoyances with generics. +// This should only be used concretely when `F = BabyBear`. +fn default_baby_bear_rc() -> Poseidon2Constants { + let convert_field = |f: BabyBear| F::from_canonical_u32(f.as_canonical_u32()); + Poseidon2Constants { + beginning_full_round_constants: BABYBEAR_BEGIN_EXT_CONSTS.map(|x| x.map(convert_field)), + partial_round_constants: BABYBEAR_PARTIAL_CONSTS.map(convert_field), + ending_full_round_constants: BABYBEAR_END_EXT_CONSTS.map(|x| x.map(convert_field)), + } +} + +pub type Plonky3RoundConstants = RoundConstants< + F, + POSEIDON2_WIDTH, + BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS, + BABY_BEAR_POSEIDON2_PARTIAL_ROUNDS, +>; diff --git a/crates/circuits/poseidon2-air/src/lib.rs b/crates/circuits/poseidon2-air/src/lib.rs index f160dec20f..7022a85e26 100644 --- a/crates/circuits/poseidon2-air/src/lib.rs +++ b/crates/circuits/poseidon2-air/src/lib.rs @@ -1,7 +1,120 @@ -//! Standalone AIR implementation of the Poseidon2 permutation. +//! This is a wrapper around the Plonky3 [p3_poseidon2_air] used only for integration convenience to +//! get around some complications with field-specific generics associated with Poseidon2. +//! Currently it is only intended for use in OpenVM with BabyBear. +//! +//! We do not recommend external use of this crate, and suggest using the [p3_poseidon2_air] crate directly. +use std::sync::Arc; + +use openvm_stark_backend::{ + p3_field::{Field, PrimeField}, + p3_matrix::dense::RowMajorMatrix, +}; pub use openvm_stark_sdk::p3_baby_bear; pub use p3_poseidon2; -pub use p3_symmetric; +use p3_poseidon2::Poseidon2; +use p3_poseidon2_air::generate_trace_rows; +pub use p3_poseidon2_air::{self, Poseidon2Air}; +pub use p3_symmetric::{self, Permutation}; + +mod air; +mod babybear; +mod config; +mod permute; + +pub use air::*; +pub use babybear::*; +pub use config::*; +pub use permute::*; + +#[cfg(test)] +mod tests; + +pub const POSEIDON2_WIDTH: usize = 16; +// NOTE: these constants are for BabyBear only. +pub const BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS: usize = 4; +pub const BABY_BEAR_POSEIDON2_FULL_ROUNDS: usize = 8; +pub const BABY_BEAR_POSEIDON2_PARTIAL_ROUNDS: usize = 13; + +// Currently we only support SBOX_DEGREE = 7 +pub const BABY_BEAR_POSEIDON2_SBOX_DEGREE: u64 = 7; + +/// `SBOX_REGISTERS` affects the max constraint degree of the AIR. See [p3_poseidon2_air] for more details. +#[derive(Debug)] +pub struct Poseidon2SubChip { + // This is Arc purely because Poseidon2Air cannot derive Clone + pub air: Arc>, + pub(crate) executor: Poseidon2Executor, + pub(crate) constants: Plonky3RoundConstants, +} + +impl Poseidon2SubChip { + pub fn new(config: Poseidon2Config) -> Self { + Self { + air: Arc::new(Poseidon2SubAir::new(config.constants.to_round_constants())), + executor: Poseidon2Executor::new(config.constants), + constants: config.constants.to_round_constants(), + } + } + + pub fn permute(&self, input_state: [F; POSEIDON2_WIDTH]) -> [F; POSEIDON2_WIDTH] { + match &self.executor { + Poseidon2Executor::BabyBearMds(permuter) => permuter.permute(input_state), + } + } + + pub fn permute_mut(&self, input_state: &mut [F; POSEIDON2_WIDTH]) { + match &self.executor { + Poseidon2Executor::BabyBearMds(permuter) => permuter.permute_mut(input_state), + }; + } + + pub fn generate_trace(&self, inputs: Vec<[F; POSEIDON2_WIDTH]>) -> RowMajorMatrix + where + F: PrimeField, + { + match self.air.as_ref() { + Poseidon2SubAir::BabyBearMds(_) => generate_trace_rows::< + F, + BabyBearPoseidon2LinearLayers, + POSEIDON2_WIDTH, + BABY_BEAR_POSEIDON2_SBOX_DEGREE, + SBOX_REGISTERS, + BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS, + BABY_BEAR_POSEIDON2_PARTIAL_ROUNDS, + >(inputs, &self.constants), + } + } +} + +pub fn from_config( + config: Poseidon2Config, +) -> (Poseidon2SubAir, Poseidon2Executor) { + ( + Poseidon2SubAir::new(config.constants.to_round_constants()), + Poseidon2Executor::new(config.constants), + ) +} + +#[derive(Clone, Debug)] +pub enum Poseidon2Executor { + BabyBearMds(Plonky3Poseidon2Executor), +} + +impl Poseidon2Executor { + pub fn new(constants: Poseidon2Constants) -> Self { + let (external_constants, internal_constants) = constants.to_external_internal_constants(); + Self::BabyBearMds(Plonky3Poseidon2Executor::new( + external_constants, + internal_constants, + )) + } +} -pub mod poseidon2; +pub type Plonky3Poseidon2Executor = Poseidon2< + ::Packing, + Poseidon2ExternalLayer, + Poseidon2InternalLayer, + POSEIDON2_WIDTH, + BABY_BEAR_POSEIDON2_SBOX_DEGREE, +>; diff --git a/crates/circuits/poseidon2-air/src/permute.rs b/crates/circuits/poseidon2-air/src/permute.rs new file mode 100644 index 0000000000..5846cad2fa --- /dev/null +++ b/crates/circuits/poseidon2-air/src/permute.rs @@ -0,0 +1,143 @@ +use std::{any::TypeId, marker::PhantomData}; + +use derivative::Derivative; +use openvm_stark_backend::p3_field::AbstractField; +use openvm_stark_sdk::p3_baby_bear::{BabyBear, BabyBearInternalLayerParameters}; +use p3_monty_31::InternalLayerBaseParameters; +use p3_poseidon2::{ + add_rc_and_sbox_generic, mds_light_permutation, ExternalLayer, ExternalLayerConstants, + ExternalLayerConstructor, GenericPoseidon2LinearLayers, InternalLayer, + InternalLayerConstructor, MDSMat4, +}; + +use super::{babybear_internal_linear_layer, BABY_BEAR_POSEIDON2_SBOX_DEGREE}; + +const WIDTH: usize = crate::POSEIDON2_WIDTH; + +pub trait Poseidon2MatrixConfig: Clone + Sync { + fn int_diag_m1_matrix() -> [F; WIDTH]; +} + +/// This type needs to implement GenericPoseidon2LinearLayers generic in F so that our Poseidon2SubAir can also +/// be generic in F, but in reality each implementation of this struct's functions should be field specific. To +/// circumvent this, Poseidon2LinearLayers is generic in F but **currently requires** that F is BabyBear. +#[derive(Debug, Clone)] +pub struct BabyBearPoseidon2LinearLayers; + +// This is the same as the implementation for GenericPoseidon2LinearLayersMonty31 except that we drop the +// clause that FA needs be multipliable by BabyBear. +// TODO[jpw/stephen]: This is clearly not the best way to do this, but it would +// require some reworking in plonky3 to get around the generics. +impl GenericPoseidon2LinearLayers for BabyBearPoseidon2LinearLayers { + fn internal_linear_layer(state: &mut [FA; WIDTH]) { + let diag_m1_matrix = &>::INTERNAL_DIAG_MONTY; + assert_eq!( + TypeId::of::(), + TypeId::of::(), + "BabyBear is the only supported field type" + ); + let diag_m1_matrix = + unsafe { std::mem::transmute::<&[BabyBear; WIDTH], &[FA::F; WIDTH]>(diag_m1_matrix) }; + babybear_internal_linear_layer(state, diag_m1_matrix); + } + + fn external_linear_layer(state: &mut [FA; WIDTH]) { + mds_light_permutation(state, &MDSMat4); + } +} + +// Below are generic implementations of the Poseidon2 Internal and External Layers +// generic in the field. These are currently used for the runtime poseidon2 +// execution even though they are less optimized than the Monty31 specific +// implementations in Plonky3. We could use those more optimized implementations, +// but it would require many unsafe transmutes. + +#[derive(Debug, Derivative)] +#[derivative(Clone)] +pub struct Poseidon2InternalLayer { + pub internal_constants: Vec, + _marker: PhantomData, +} + +impl InternalLayerConstructor + for Poseidon2InternalLayer +{ + fn new_from_constants(internal_constants: Vec) -> Self { + Self { + internal_constants, + _marker: PhantomData, + } + } +} + +impl + InternalLayer + for Poseidon2InternalLayer +where + LinearLayers: GenericPoseidon2LinearLayers, +{ + /// Perform the internal layers of the Poseidon2 permutation on the given state. + fn permute_state(&self, state: &mut [FA; WIDTH]) { + self.internal_constants.iter().for_each(|&rc| { + add_rc_and_sbox_generic::<_, BABY_BEAR_POSEIDON2_SBOX_DEGREE>(&mut state[0], rc); + LinearLayers::internal_linear_layer(state); + }) + } +} + +#[derive(Debug, Derivative)] +#[derivative(Clone)] +pub struct Poseidon2ExternalLayer { + pub constants: ExternalLayerConstants, + _marker: PhantomData, +} + +impl ExternalLayerConstructor + for Poseidon2ExternalLayer +{ + fn new_from_constants(external_layer_constants: ExternalLayerConstants) -> Self { + Self { + constants: external_layer_constants, + _marker: PhantomData, + } + } +} + +impl + ExternalLayer + for Poseidon2ExternalLayer +where + LinearLayers: GenericPoseidon2LinearLayers, +{ + fn permute_state_initial(&self, state: &mut [FA; WIDTH]) { + LinearLayers::external_linear_layer(state); + external_permute_state::( + state, + self.constants.get_initial_constants(), + ); + } + + fn permute_state_terminal(&self, state: &mut [FA; WIDTH]) { + external_permute_state::( + state, + self.constants.get_terminal_constants(), + ); + } +} + +fn external_permute_state( + state: &mut [FA; WIDTH], + constants: &[[FA::F; WIDTH]], +) where + LinearLayers: GenericPoseidon2LinearLayers, +{ + for elem in constants.iter() { + state.iter_mut().zip(elem.iter()).for_each(|(s, &rc)| { + add_rc_and_sbox_generic::<_, BABY_BEAR_POSEIDON2_SBOX_DEGREE>(s, rc) + }); + LinearLayers::external_linear_layer(state); + } +} diff --git a/crates/circuits/poseidon2-air/src/poseidon2/air.rs b/crates/circuits/poseidon2-air/src/poseidon2/air.rs deleted file mode 100644 index 25a0f324d1..0000000000 --- a/crates/circuits/poseidon2-air/src/poseidon2/air.rs +++ /dev/null @@ -1,363 +0,0 @@ -use std::borrow::Borrow; - -use openvm_stark_backend::{ - interaction::InteractionBuilder, - p3_air::{Air, AirBuilder, BaseAir}, - p3_field::{AbstractField, Field}, - p3_matrix::Matrix, - rap::{BaseAirWithPublicValues, PartitionedBaseAir}, -}; -use openvm_stark_sdk::p3_baby_bear::BabyBear; -use zkhash::{ - ark_ff::PrimeField as _, - fields::babybear::FpBabyBear as HorizenBabyBear, - poseidon2::poseidon2_instance_babybear::{MAT_DIAG16_M_1, RC16}, -}; - -use super::{ - columns::{Poseidon2AuxCols, Poseidon2Cols, Poseidon2IoCols}, - Poseidon2Config, -}; - -pub const SBOX_DEGREE: usize = 7; - -/// Air for Poseidon2. Performs a single permutation of the state. -/// Permutation consists of external rounds (linear map combined with nonlinearity), -/// internal rounds, and then the remainder of external rounds. -/// -/// This AIR only supports: -/// - sbox of degree 7 -/// - WIDTH is multiple of 4 and >= 8 -/// -/// Spec is at https://hackmd.io/_I1lx-6GROWbKbDi_Vz-pw?view . -#[derive(Clone, Debug)] -pub struct Poseidon2Air { - pub rounds_f: usize, - pub external_constants: Vec<[F; WIDTH]>, - pub rounds_p: usize, - pub internal_constants: Vec, - /// The M_4 matrix to use for external linear layers. - pub ext_mds_matrix: [[F; 4]; 4], - /// The internal linear layers consist of multiplying by matrix of all 1s + diag(int_diag_m1_matrix) - pub int_diag_m1_matrix: [F; WIDTH], - /// Sometimes the constants are tuned with a reduction factor corresponding to montgomery reduction. - pub reduction_factor: F, - // Maximum constraint degree for the AIR. Must be 3, 5, or 7. - pub max_constraint_degree: usize, - pub bus_index: usize, -} - -impl Poseidon2Air { - pub fn new( - external_constants: Vec<[F; WIDTH]>, - internal_constants: Vec, - ext_mds_matrix: [[u32; 4]; 4], - int_diag_m1_matrix: [F; WIDTH], - reduction_factor: F, - max_constraint_degree: usize, - bus_index: usize, - ) -> Self { - assert!( - max_constraint_degree == 3 || max_constraint_degree == 5 || max_constraint_degree == 7 - ); - - Self { - rounds_f: external_constants.len(), - external_constants, - rounds_p: internal_constants.len(), - internal_constants, - ext_mds_matrix: ext_mds_matrix.map(|row| row.map(F::from_canonical_u32)), - int_diag_m1_matrix, - reduction_factor, - max_constraint_degree, - bus_index, - } - } - - pub fn from_config( - config: Poseidon2Config, - max_constraint_degree: usize, - bus_index: usize, - ) -> Self { - Self::new( - config.external_constants, - config.internal_constants, - config.ext_mds_matrix, - config.int_diag_m1_matrix, - config.reduction_factor, - max_constraint_degree, - bus_index, - ) - } - - pub fn get_width(&self) -> usize { - Poseidon2Cols::::width(self) - } - - // The following are generic in T: AbstractField + From because they are used in the AIR constraints: - - // TODO: allow custom implementations of this via generic DiffusionMatrix: DiffusionPermutation for faster trace generation - pub(crate) fn int_lin_layer>(&self, input: &mut [T; WIDTH]) { - let sum = input.iter().cloned().sum::(); - for (input, diag_m1) in input.iter_mut().zip(&self.int_diag_m1_matrix) { - *input = (sum.clone() + T::from(diag_m1.clone()) * input.clone()) - * self.reduction_factor.clone().into(); - } - } - - // TODO: add back custom implementations for faster trace generation - pub(crate) fn ext_lin_layer>(&self, input: &mut [T; WIDTH]) { - let mut new_state: [T; WIDTH] = core::array::from_fn(|_| T::ZERO); - for i in (0..WIDTH).step_by(4) { - for index1 in 0..4 { - for index2 in 0..4 { - new_state[i + index1] += T::from(self.ext_mds_matrix[index1][index2].clone()) - * input[i + index2].clone(); - } - } - } - - let sums: [T; 4] = core::array::from_fn(|j| { - (0..WIDTH) - .step_by(4) - .map(|i| new_state[i + j].clone()) - .sum() - }); - - for i in 0..WIDTH { - new_state[i] += sums[i % 4].clone(); - } - - input.clone_from_slice(&new_state); - } - - pub(crate) fn horizen_to_p3(horizen_babybear: HorizenBabyBear) -> BabyBear { - BabyBear::from_canonical_u64(horizen_babybear.into_bigint().0[0]) - } - - pub(crate) fn horizen_round_consts_16() -> (Vec<[BabyBear; 16]>, Vec, [BabyBear; 16]) - { - let p3_rc16: Vec> = RC16 - .iter() - .map(|round| { - round - .iter() - .map(|babybear| Self::horizen_to_p3(*babybear)) - .collect() - }) - .collect(); - - let rounds_f = 8; - let rounds_p = 13; - let rounds_f_beginning = rounds_f / 2; - let p_end = rounds_f_beginning + rounds_p; - let external_round_constants: Vec<[BabyBear; 16]> = p3_rc16[..rounds_f_beginning] - .iter() - .chain(p3_rc16[p_end..].iter()) - .cloned() - .map(|round| round.try_into().unwrap()) - .collect(); - let internal_round_constants: Vec = p3_rc16[rounds_f_beginning..p_end] - .iter() - .map(|round| round[0]) - .collect(); - let horizen_int_diag: [BabyBear; 16] = { - let mut array = [BabyBear::ZERO; 16]; - for (i, elem) in MAT_DIAG16_M_1.iter().enumerate() { - array[i] = BabyBear::from_canonical_u32(elem.into_bigint().0[0] as u32); - } - array - }; - ( - external_round_constants, - internal_round_constants, - horizen_int_diag, - ) - } -} - -impl Default for Poseidon2Air<16, BabyBear> { - fn default() -> Self { - Self::from_config(Poseidon2Config::<16, BabyBear>::default(), 7, 0) - } -} - -impl BaseAirWithPublicValues for Poseidon2Air {} -impl PartitionedBaseAir for Poseidon2Air {} -impl BaseAir for Poseidon2Air { - fn width(&self) -> usize { - self.get_width() - } -} - -impl Air for Poseidon2Air { - fn eval(&self, builder: &mut AB) { - let main = builder.main(); - let local = main.row_slice(0); - let local: &[AB::Var] = (*local).borrow(); - - let poseidon2_cols = Poseidon2Cols::from_slice(local, self); - let Poseidon2Cols { io, aux } = poseidon2_cols; - - self.eval_interactions(builder, io); - self.eval_without_interactions(builder, io, aux.into_expr::()); - } -} - -impl Poseidon2Air { - pub fn eval_without_interactions>( - &self, - builder: &mut AB, - io: Poseidon2IoCols, - aux: Poseidon2AuxCols, - ) { - let half_ext_rounds = self.rounds_f / 2; - for phase1_index in 0..half_ext_rounds { - // regenerate state as Expr from trace variables on each round - let mut state = if phase1_index == 0 { - core::array::from_fn(|i| io.input[i].into()) - } else { - core::array::from_fn(|i| aux.phase1[phase1_index - 1].round_output[i].clone()) - }; - self.ext_lin_layer(&mut state); - state = add_ext_consts::(state, phase1_index, &self.external_constants); - state = self.sbox_air( - builder, - state, - aux.phase1[phase1_index].intermediate_sbox_powers.clone(), - ); - for (state_index, state_elem) in state.iter().enumerate() { - builder.assert_eq( - state_elem.clone(), - aux.phase1[phase1_index].round_output[state_index].clone(), - ); - } - } - - for phase2_index in 0..self.rounds_p { - // regenerate state as Expr from trace variables on each round - let mut state = if phase2_index == 0 { - let mut state: [AB::Expr; WIDTH] = core::array::from_fn(|i| { - aux.phase1[half_ext_rounds - 1].round_output[i].clone() - }); - self.ext_lin_layer(&mut state); - state - } else { - let mut state = - core::array::from_fn(|i| aux.phase2[phase2_index - 1].round_output[i].clone()); - self.int_lin_layer(&mut state); - state - }; - state[0] += self.internal_constants[phase2_index].into(); - state[0] = self.sbox_p_air( - builder, - state[0].clone(), - aux.phase2[phase2_index].intermediate_sbox_power.clone(), - ); - for (state_index, state_elem) in state.iter().enumerate() { - builder.assert_eq( - state_elem.clone(), - aux.phase2[phase2_index].round_output[state_index].clone(), - ); - } - } - - for phase3_index in 0..(self.rounds_f - half_ext_rounds) { - // regenerate state as Expr from trace variables on each round - let mut state = if phase3_index == 0 { - let mut state = - core::array::from_fn(|i| aux.phase2[self.rounds_p - 1].round_output[i].clone()); - self.int_lin_layer(&mut state); - state - } else { - let mut state = - core::array::from_fn(|i| aux.phase3[phase3_index - 1].round_output[i].clone()); - self.ext_lin_layer(&mut state); - state - }; - state = add_ext_consts::( - state, - phase3_index + half_ext_rounds, - &self.external_constants, - ); - state = self.sbox_air( - builder, - state, - aux.phase3[phase3_index].intermediate_sbox_powers.clone(), - ); - - for (state_index, state_elem) in state.iter().enumerate() { - builder.assert_eq( - state_elem.clone(), - aux.phase3[phase3_index].round_output[state_index].clone(), - ); - } - } - - let mut state: [AB::Expr; WIDTH] = core::array::from_fn(|i| { - aux.phase3[self.rounds_f - half_ext_rounds - 1].round_output[i].clone() - }); - self.ext_lin_layer(&mut state); - for (state_index, state_elem) in state.iter().enumerate() { - builder.assert_eq(state_elem.clone(), io.output[state_index]); - } - } - - /// Returns value^SBOX_DEGREE - fn sbox_p_air>( - &self, - builder: &mut AB, - value: AB::Expr, - intermediate_power: Option, - ) -> AB::Expr { - // When SBOX_DEGREE <= self.max_constraint_degree, we simply compute the SBOX power - // by repeated multiplication. - // Otherwise, we make use of the intermediate_power (which is value^self.max_constraint_degree - // in that case) to reduce the degree used for computing value^SBOX_DEGREE. - - if intermediate_power.is_some() { - // Ensuring that intermediate_power is value^self.max_constraint_degree - let mut val_p = AB::Expr::ONE; - for _ in 0..self.max_constraint_degree { - val_p *= value.clone(); - } - builder.assert_eq(val_p, intermediate_power.clone().unwrap()); - } - - let mut ret = AB::Expr::ONE; - for _ in 0..(SBOX_DEGREE - 1) / self.max_constraint_degree { - ret *= intermediate_power.clone().unwrap(); - } - for _ in 0..(SBOX_DEGREE - 1) % self.max_constraint_degree + 1 { - ret *= value.clone(); - } - ret - } - - /// Returns elementwise 7th power of vector field element input - fn sbox_air>( - &self, - builder: &mut AB, - state: [AB::Expr; WIDTH], - intermediate_powers: [Option; WIDTH], - ) -> [AB::Expr; WIDTH] { - state - .into_iter() - .zip(intermediate_powers) - .map(|(state_elem, intermediate_power)| { - self.sbox_p_air(builder, state_elem, intermediate_power) - }) - .collect::>() - .try_into() - .unwrap() - } -} - -/// Adds external constants elementwise to state, indexed from [[F]] -fn add_ext_consts( - state: [AB::Expr; WIDTH], - index: usize, - external_constants: &[[AB::F; WIDTH]], -) -> [AB::Expr; WIDTH] { - core::array::from_fn(|i| state[i].clone() + external_constants[index][i]) -} diff --git a/crates/circuits/poseidon2-air/src/poseidon2/bridge.rs b/crates/circuits/poseidon2-air/src/poseidon2/bridge.rs deleted file mode 100644 index 04b9bbe1ad..0000000000 --- a/crates/circuits/poseidon2-air/src/poseidon2/bridge.rs +++ /dev/null @@ -1,16 +0,0 @@ -use openvm_stark_backend::{interaction::InteractionBuilder, p3_field::Field}; - -use super::columns::Poseidon2IoCols; -use crate::poseidon2::Poseidon2Air; - -// Receives input and output columns in one interaction -impl Poseidon2Air { - pub fn eval_interactions>( - &self, - builder: &mut AB, - io: Poseidon2IoCols, - ) { - let fields = io.input.into_iter().chain(io.output); - builder.push_receive(self.bus_index, fields, F::ONE); - } -} diff --git a/crates/circuits/poseidon2-air/src/poseidon2/columns.rs b/crates/circuits/poseidon2-air/src/poseidon2/columns.rs deleted file mode 100644 index 1c846126a0..0000000000 --- a/crates/circuits/poseidon2-air/src/poseidon2/columns.rs +++ /dev/null @@ -1,281 +0,0 @@ -use openvm_stark_backend::{p3_air::AirBuilder, p3_field::Field}; - -use super::air::SBOX_DEGREE; -use crate::poseidon2::Poseidon2Air; - -/// Composed of IO and Aux columns, which are disjoint -/// Aux columns composed of Vec>, one for each phase -#[derive(Clone, Debug)] -pub struct Poseidon2Cols { - pub io: Poseidon2IoCols, - pub aux: Poseidon2AuxCols, -} - -#[derive(Clone, Copy, Debug)] -#[repr(C)] -pub struct Poseidon2IoCols { - pub input: [T; WIDTH], - pub output: [T; WIDTH], -} - -#[derive(Clone, Debug)] -pub struct Poseidon2AuxCols { - // contains one state (array of length WIDTH) for each round of phase1, of which there are `rounds_f/2` - pub phase1: Vec>, - // contains one state (array of length WIDTH) for each round of phase2, of which there are `rounds_p` - pub phase2: Vec>, - // contains one state (array of length WIDTH) for each round of phase3, of which there are `rounds_f - rounds_f/2` - pub phase3: Vec>, -} - -#[derive(Clone, Debug)] -pub struct Poseidon2ExternalRoundCols { - // Those are helper columns to store intermediate powers to reduce the degree - // for the SBOX constraints. When max_constraint_degree (in the AIR) is less than SBOX_DEGREE, - // those columns are set to the value^max_constraint_degree. Otherwise, they are set to None. - pub intermediate_sbox_powers: [Option; WIDTH], - // The output of the round - pub round_output: [T; WIDTH], -} - -#[derive(Clone, Debug)] -pub struct Poseidon2InternalRoundCols { - // This is a helper column to store the intermediate sbox power to reduce the degree - // for the SBOX constraints. When max_constraint_degree (in the AIR) is less than SBOX_DEGREE, - // this columns is set to the value^max_constraint_degree. Otherwise, it is set to None. - pub intermediate_sbox_power: Option, - // The output of the round - pub round_output: [T; WIDTH], -} - -impl Poseidon2Cols { - pub fn blank_row(p2_air: &Poseidon2Air) -> Self { - let zero_row = [F::ZERO; WIDTH]; - p2_air.generate_trace_row(zero_row) - } -} - -/// Returns true iff we need to store intermediate powers for the SBOX constraints -fn need_intermediate_sbox_powers(p2_air: &Poseidon2Air) -> bool { - p2_air.max_constraint_degree < SBOX_DEGREE -} - -// Straightforward implementation for the functions from_slice, flatten, and width, into_expr below - -impl Poseidon2ExternalRoundCols { - fn from_slice(slice: &[T], p2_air: &Poseidon2Air) -> Self { - assert!(slice.len() == Poseidon2ExternalRoundCols::::width(p2_air)); - - if need_intermediate_sbox_powers(p2_air) { - Self { - intermediate_sbox_powers: core::array::from_fn(|i| Some(slice[i].clone())), - round_output: core::array::from_fn(|i| slice[WIDTH + i].clone()), - } - } else { - Self { - intermediate_sbox_powers: core::array::from_fn(|_| None), - round_output: core::array::from_fn(|i| slice[i].clone()), - } - } - } -} - -impl Poseidon2ExternalRoundCols { - fn flatten(self) -> Vec { - self.intermediate_sbox_powers - .into_iter() - .flatten() - .chain(self.round_output) - .collect() - } - - fn width(p2_air: &Poseidon2Air) -> usize { - if need_intermediate_sbox_powers(p2_air) { - 2 * WIDTH - } else { - WIDTH - } - } -} - -impl Poseidon2InternalRoundCols { - fn from_slice(slice: &[T], p2_air: &Poseidon2Air) -> Self { - if need_intermediate_sbox_powers(p2_air) { - Self { - intermediate_sbox_power: Some(slice[0].clone()), - round_output: core::array::from_fn(|i| slice[1 + i].clone()), - } - } else { - Self { - intermediate_sbox_power: None, - round_output: core::array::from_fn(|i| slice[i].clone()), - } - } - } - - fn flatten(self) -> Vec { - self.intermediate_sbox_power - .into_iter() - .chain(self.round_output) - .collect() - } - - fn width(p2_air: &Poseidon2Air) -> usize { - if need_intermediate_sbox_powers(p2_air) { - 1 + WIDTH - } else { - WIDTH - } - } -} - -impl Poseidon2Cols { - pub fn width(poseidon2_air: &Poseidon2Air) -> usize { - Poseidon2IoCols::::width() + Poseidon2AuxCols::::width(poseidon2_air) - } - - pub fn from_slice(slice: &[T], p2_air: &Poseidon2Air) -> Self { - Self { - io: Poseidon2IoCols::from_slice(&slice[0..2 * WIDTH]), - aux: Poseidon2AuxCols::from_slice(&slice[2 * WIDTH..], p2_air), - } - } - - pub fn flatten(self) -> Vec { - self.io - .flatten() - .into_iter() - .chain(self.aux.flatten()) - .collect() - } -} - -impl Poseidon2IoCols { - fn from_slice(slice: &[T]) -> Self { - Self { - input: core::array::from_fn(|i| slice[i].clone()), - output: core::array::from_fn(|i| slice[WIDTH + i].clone()), - } - } -} - -impl Poseidon2AuxCols { - fn from_slice(slice: &[T], p2_air: &Poseidon2Air) -> Self { - let external_round_width = Poseidon2ExternalRoundCols::::width(p2_air); - let internal_round_width = Poseidon2InternalRoundCols::::width(p2_air); - - let mut phase1 = vec![]; - let mut phase2 = vec![]; - let mut phase3 = vec![]; - - let mut start = 0; - let mut end = start; - - for _ in 0..p2_air.rounds_f / 2 { - end += external_round_width; - phase1.push(Poseidon2ExternalRoundCols::from_slice( - &slice[start..end], - p2_air, - )); - start = end; - } - - for _ in 0..p2_air.rounds_p { - end += internal_round_width; - phase2.push(Poseidon2InternalRoundCols::from_slice( - &slice[start..end], - p2_air, - )); - start = end; - } - - for _ in 0..p2_air.rounds_f - p2_air.rounds_f / 2 { - end += external_round_width; - phase3.push(Poseidon2ExternalRoundCols::from_slice( - &slice[start..end], - p2_air, - )); - start = end; - } - - Self { - phase1, - phase2, - phase3, - } - } -} - -impl Poseidon2IoCols { - pub fn width() -> usize { - 2 * WIDTH - } - - pub fn flatten(self) -> Vec { - self.input.into_iter().chain(self.output).collect() - } -} - -impl Poseidon2AuxCols { - pub fn width(p2_air: &Poseidon2Air) -> usize { - p2_air.rounds_f * Poseidon2ExternalRoundCols::::width(p2_air) - + p2_air.rounds_p * Poseidon2InternalRoundCols::::width(p2_air) - } - - pub fn flatten(self) -> Vec { - let mut flattened = vec![]; - flattened.extend(self.phase1.into_iter().flat_map(|s| s.flatten())); - flattened.extend(self.phase2.into_iter().flat_map(|s| s.flatten())); - flattened.extend(self.phase3.into_iter().flat_map(|s| s.flatten())); - flattened - } -} - -impl Poseidon2InternalRoundCols { - pub fn into_expr(self) -> Poseidon2InternalRoundCols - where - T: Into, - { - Poseidon2InternalRoundCols { - intermediate_sbox_power: self.intermediate_sbox_power.map(Into::into), - round_output: self.round_output.map(Into::into), - } - } -} - -impl Poseidon2ExternalRoundCols { - pub fn into_expr(self) -> Poseidon2ExternalRoundCols - where - T: Into, - { - Poseidon2ExternalRoundCols { - intermediate_sbox_powers: self.intermediate_sbox_powers.map(|op| op.map(Into::into)), - round_output: self.round_output.map(Into::into), - } - } -} - -impl Poseidon2AuxCols { - pub fn into_expr(self) -> Poseidon2AuxCols - where - T: Into, - { - Poseidon2AuxCols { - phase1: self - .phase1 - .into_iter() - .map(|p| p.into_expr::()) - .collect(), - phase2: self - .phase2 - .into_iter() - .map(|p| p.into_expr::()) - .collect(), - phase3: self - .phase3 - .into_iter() - .map(|p| p.into_expr::()) - .collect(), - } - } -} diff --git a/crates/circuits/poseidon2-air/src/poseidon2/mod.rs b/crates/circuits/poseidon2-air/src/poseidon2/mod.rs deleted file mode 100644 index 687a22a322..0000000000 --- a/crates/circuits/poseidon2-air/src/poseidon2/mod.rs +++ /dev/null @@ -1,137 +0,0 @@ -pub mod air; -pub mod bridge; -pub mod columns; -pub mod trace; - -#[cfg(test)] -pub mod tests; - -use lazy_static::lazy_static; -use openvm_stark_backend::p3_field::{AbstractField, PrimeField32}; -use openvm_stark_sdk::p3_baby_bear::{BabyBear, BabyBearInternalLayerParameters}; -use p3_monty_31::InternalLayerBaseParameters; - -pub use self::{air::Poseidon2Air, columns::Poseidon2Cols}; - -#[derive(Clone)] -pub struct Poseidon2Config { - pub external_constants: Vec<[F; WIDTH]>, - pub internal_constants: Vec, - pub ext_mds_matrix: [[u32; 4]; 4], - pub int_diag_m1_matrix: [F; WIDTH], - pub reduction_factor: F, -} - -impl Poseidon2Config { - pub fn rounds_f(&self) -> usize { - self.external_constants.len() - } - pub fn rounds_p(&self) -> usize { - self.internal_constants.len() - } -} -/// MDSMat4 from Plonky3 -/// [ 2 3 1 1 ] -/// [ 1 2 3 1 ] -/// [ 1 1 2 3 ] -/// [ 3 1 1 2 ]. -/// -pub const MDS_MAT_4: [[u32; 4]; 4] = [[2, 3, 1, 1], [1, 2, 3, 1], [1, 1, 2, 3], [3, 1, 1, 2]]; -// Multiply a 4-element vector x by -// [ 5 7 1 3 ] -// [ 4 6 1 1 ] -// [ 1 3 5 7 ] -// [ 1 1 4 6 ]. -// This uses the formula from the start of Appendix B in the Poseidon2 paper, with multiplications unrolled into additions. -// It is also the matrix used by the Horizon Labs implementation. -pub const HL_MDS_MAT_4: [[u32; 4]; 4] = [[5, 7, 1, 3], [4, 6, 1, 1], [1, 3, 5, 7], [1, 1, 4, 6]]; - -impl Poseidon2Config<16, BabyBear> { - pub fn horizen_config() -> Self { - Self { - external_constants: HL_BABYBEAR_EXT_CONST_16.to_vec(), - internal_constants: HL_BABYBEAR_INT_CONST_16.to_vec(), - ext_mds_matrix: HL_MDS_MAT_4, - int_diag_m1_matrix: *HL_BABYBEAR_INT_DIAG_16, - reduction_factor: BabyBear::ONE, - } - } -} - -impl Poseidon2Config<16, F> { - /// Using HorizenLab's round constants: https://github.com/HorizenLabs/poseidon2 - pub fn new_hl_baby_bear_16() -> Self { - let external_round_constants_f: Vec<[F; 16]> = HL_BABYBEAR_EXT_CONST_16 - .iter() - .map(|round| { - round - .iter() - .map(|babybear| F::from_canonical_u32(babybear.as_canonical_u32())) - .collect::>() - .try_into() - .unwrap() - }) - .collect(); - - let internal_round_constants_f: Vec = HL_BABYBEAR_INT_CONST_16 - .iter() - .map(|babybear| F::from_canonical_u32(babybear.as_canonical_u32())) - .collect(); - - let horizen_int_diag_f: [F; 16] = HL_BABYBEAR_INT_DIAG_16 - .map(|babybear| F::from_canonical_u32(babybear.as_canonical_u32())); - - Self { - external_constants: external_round_constants_f, - internal_constants: internal_round_constants_f, - ext_mds_matrix: HL_MDS_MAT_4, - int_diag_m1_matrix: horizen_int_diag_f, - reduction_factor: F::ONE, - } - } - - pub fn new_p3_baby_bear_16() -> Self { - let external_round_constants_f: Vec<[F; 16]> = HL_BABYBEAR_EXT_CONST_16 - .iter() - .map(|round| { - round - .iter() - .map(|babybear| F::from_canonical_u32(babybear.as_canonical_u32())) - .collect::>() - .try_into() - .unwrap() - }) - .collect(); - - let internal_round_constants_f: Vec = HL_BABYBEAR_INT_CONST_16 - .iter() - .map(|babybear| F::from_canonical_u32(babybear.as_canonical_u32())) - .collect(); - - let p3_int_diag_f = BabyBearInternalLayerParameters::INTERNAL_DIAG_MONTY - .map(|babybear| F::from_canonical_u32(babybear.as_canonical_u32())); - - Self { - external_constants: external_round_constants_f, - internal_constants: internal_round_constants_f, - ext_mds_matrix: MDS_MAT_4, - int_diag_m1_matrix: p3_int_diag_f, - reduction_factor: F::ONE, - } - } -} - -impl Default for Poseidon2Config<16, BabyBear> { - fn default() -> Self { - Self::new_p3_baby_bear_16() - } -} - -lazy_static! { - pub static ref HL_BABYBEAR_EXT_CONST_16: Vec<[BabyBear; 16]> = - Poseidon2Air::<16, BabyBear>::horizen_round_consts_16().0; - pub static ref HL_BABYBEAR_INT_CONST_16: Vec = - Poseidon2Air::<16, BabyBear>::horizen_round_consts_16().1; - pub static ref HL_BABYBEAR_INT_DIAG_16: [BabyBear; 16] = - Poseidon2Air::<16, BabyBear>::horizen_round_consts_16().2; -} diff --git a/crates/circuits/poseidon2-air/src/poseidon2/tests.rs b/crates/circuits/poseidon2-air/src/poseidon2/tests.rs deleted file mode 100644 index e8373b08ce..0000000000 --- a/crates/circuits/poseidon2-air/src/poseidon2/tests.rs +++ /dev/null @@ -1,260 +0,0 @@ -use ark_ff::PrimeField as _; -use openvm_stark_backend::{ - p3_field::{AbstractField, PrimeField32}, - p3_matrix::dense::RowMajorMatrix, - utils::disable_debug_builder, - verifier::VerificationError, -}; -use openvm_stark_sdk::{ - any_rap_arc_vec, - config::{ - baby_bear_poseidon2::{engine_from_perm, random_perm}, - fri_params::standard_fri_params_with_100_bits_conjectured_security, - }, - dummy_airs::interaction::dummy_interaction_air::DummyInteractionAir, - engine::StarkEngine, - p3_baby_bear::{BabyBear, BabyBearInternalLayerParameters, Poseidon2BabyBear}, - utils::create_seeded_rng, -}; -use p3_monty_31::InternalLayerBaseParameters; -use p3_poseidon2::{ExternalLayerConstants, Poseidon2}; -use p3_symmetric::Permutation; -use rand::{Rng, RngCore}; -use zkhash::{ - fields::babybear::FpBabyBear as HorizenBabyBear, - poseidon2::{ - poseidon2::Poseidon2 as HorizenPoseidon2, - poseidon2_instance_babybear::POSEIDON2_BABYBEAR_16_PARAMS, - }, -}; - -use super::{HL_BABYBEAR_EXT_CONST_16, HL_BABYBEAR_INT_CONST_16, HL_MDS_MAT_4, MDS_MAT_4}; -use crate::poseidon2::Poseidon2Air; - -#[test] -fn test_poseidon2_default() { - // config - let num_rows = 1 << 4; - let num_ext_rounds = 8; - - // random constants, state generation - let mut rng = create_seeded_rng(); - let states: Vec<[BabyBear; 16]> = (0..num_rows) - .map(|_| { - let vec: Vec = (0..16) - .map(|_| BabyBear::from_canonical_u32(rng.next_u32() % (1 << 30))) - .collect(); - vec.try_into().unwrap() - }) - .collect(); - - // air and trace generation - let poseidon2_air = Poseidon2Air::<16, BabyBear>::default(); // max constraint degree = 7 - - let mut poseidon2_trace = poseidon2_air.generate_trace(states.clone()); - let mut outputs = states.clone(); - let poseidon2: Poseidon2BabyBear<16> = Poseidon2::new( - ExternalLayerConstants::new( - HL_BABYBEAR_EXT_CONST_16[..num_ext_rounds / 2].to_vec(), - HL_BABYBEAR_EXT_CONST_16[num_ext_rounds / 2..].to_vec(), - ), - HL_BABYBEAR_INT_CONST_16.to_vec(), - ); - for output in outputs.iter_mut() { - poseidon2.permute_mut(output); - } - - // dummy interaction air and trace generation - let page_requester = DummyInteractionAir::new(2 * 16, true, poseidon2_air.bus_index); - let dummy_trace = RowMajorMatrix::new( - states - .into_iter() - .zip(outputs.iter()) - .flat_map(|(state, output)| { - [BabyBear::ONE] - .into_iter() - .chain(state.to_vec()) - .chain(output.to_vec()) - .collect::>() - }) - .collect(), - 2 * 16 + 1, - ); - - let traces = vec![poseidon2_trace.clone(), dummy_trace.clone()]; - - // engine generation - let perm = random_perm(); - let fri_params = standard_fri_params_with_100_bits_conjectured_security(3); // max constraint degree = 7 requires log blowup = 3 - let engine = engine_from_perm(perm, fri_params); - - // positive test - engine - .run_simple_test_impl( - any_rap_arc_vec![poseidon2_air.clone(), page_requester], - traces, - vec![vec![]; 2], - ) - .expect("Verification failed"); - - // negative test - disable_debug_builder(); - for _ in 0..10 { - let width = rng.gen_range(0..poseidon2_air.get_width()); - let height = rng.gen_range(0..num_rows); - let rand = BabyBear::from_canonical_u32(rng.gen_range(1..=1 << 27)); - poseidon2_trace.row_mut(height)[width] += rand; - assert_eq!( - engine - .run_simple_test_impl( - any_rap_arc_vec![poseidon2_air.clone(), page_requester], - vec![poseidon2_trace.clone(), dummy_trace.clone()], - vec![vec![]; 2], - ) - .err(), - Some(VerificationError::OodEvaluationMismatch), - "Expected constraint to fail" - ); - poseidon2_trace.row_mut(height)[width] -= rand; - } -} - -// Attention: if this test fails, it may be because plonky3 changed their constants. -// Check the reduction factor, which is either 1 or BabyBear::from_wrapped_u64(1u64 << 32).inverse(), // 943718400 -#[test] -fn test_poseidon2() { - // config - let num_rows = 1 << 4; - let num_ext_rounds = 8; - let num_int_rounds = 13; - - // random constants, state generation - let mut rng = create_seeded_rng(); - let external_constants = ExternalLayerConstants::new_from_rng(num_ext_rounds, &mut rng); - let internal_constants: Vec = (0..num_int_rounds) - .map(|_| BabyBear::from_wrapped_u32(rng.next_u32())) - .collect(); - let states: Vec<[BabyBear; 16]> = (0..num_rows) - .map(|_| { - let vec: Vec = (0..16) - .map(|_| BabyBear::from_canonical_u32(rng.next_u32() % (1 << 30))) - .collect(); - vec.try_into().unwrap() - }) - .collect(); - - // air and trace generation - let poseidon2_air = Poseidon2Air::<16, BabyBear>::new( - [ - &external_constants.get_initial_constants()[..], - &external_constants.get_terminal_constants()[..], - ] - .concat(), - internal_constants.clone(), - MDS_MAT_4, - BabyBearInternalLayerParameters::INTERNAL_DIAG_MONTY, - BabyBear::ONE, - 3, - 0, - ); - let mut poseidon2_trace = poseidon2_air.generate_trace(states.clone()); - let mut outputs = states.clone(); - let poseidon2: Poseidon2BabyBear<16> = Poseidon2::new(external_constants, internal_constants); - for output in outputs.iter_mut() { - poseidon2.permute_mut(output); - } - - // dummy interaction air and trace generation - let page_requester = DummyInteractionAir::new(2 * 16, true, poseidon2_air.bus_index); - let dummy_trace = RowMajorMatrix::new( - states - .into_iter() - .zip(outputs.iter()) - .flat_map(|(state, output)| { - [BabyBear::ONE] - .into_iter() - .chain(state.to_vec()) - .chain(output.to_vec()) - .collect::>() - }) - .collect(), - 2 * 16 + 1, - ); - - let traces = vec![poseidon2_trace.clone(), dummy_trace.clone()]; - - // engine generation - let perm = random_perm(); - let fri_params = standard_fri_params_with_100_bits_conjectured_security(3); - let engine = engine_from_perm(perm, fri_params); - - // positive test - engine - .run_simple_test_impl( - any_rap_arc_vec![poseidon2_air.clone(), page_requester], - traces, - vec![vec![]; 2], - ) - .expect("Verification failed"); - - // negative test - disable_debug_builder(); - for _ in 0..10 { - let width = rng.gen_range(0..poseidon2_air.get_width()); - let height = rng.gen_range(0..num_rows); - let rand = BabyBear::from_canonical_u32(rng.gen_range(1..=1 << 27)); - poseidon2_trace.row_mut(height)[width] += rand; - assert_eq!( - engine - .run_simple_test_impl( - any_rap_arc_vec![poseidon2_air.clone(), page_requester], - vec![poseidon2_trace.clone(), dummy_trace.clone()], - vec![vec![]; 2], - ) - .err(), - Some(VerificationError::OodEvaluationMismatch), - "Expected constraint to fail" - ); - poseidon2_trace.row_mut(height)[width] -= rand; - } -} - -#[test] -fn test_horizen_poseidon2() { - let horizen_permut = HorizenPoseidon2::new(&POSEIDON2_BABYBEAR_16_PARAMS); - let mut rng = create_seeded_rng(); - let (external_round_constants, internal_round_constants, horizen_int_diag) = - Poseidon2Air::<16, BabyBear>::horizen_round_consts_16(); - let mut air_permut = Poseidon2Air::<16, BabyBear>::new( - external_round_constants, - internal_round_constants, - HL_MDS_MAT_4, - horizen_int_diag, - BabyBear::ONE, - 3, - 0, - ); - let u32state = (0..16) - .map(|_| rng.gen_range(1..=1 << 27)) - .collect::>(); - let horizen_state: Vec = - u32state.into_iter().map(HorizenBabyBear::from).collect(); - let p3_state: [BabyBear; 16] = horizen_state - .iter() - .copied() - .map(Poseidon2Air::<16, BabyBear>::horizen_to_p3) - .collect::>() - .try_into() - .unwrap(); - let air_result: Vec = air_permut.request_trace(&[p3_state])[0].clone(); - let horizen_result = horizen_permut.permutation(&horizen_state); - let air_u32_result = air_result - .iter() - .map(BabyBear::as_canonical_u32) - .collect::>(); - let horizen_u32_result = horizen_result - .into_iter() - .map(|elem| elem.into_bigint().0[0] as u32) - .collect::>(); - assert_eq!(air_u32_result, horizen_u32_result); -} diff --git a/crates/circuits/poseidon2-air/src/poseidon2/trace.rs b/crates/circuits/poseidon2-air/src/poseidon2/trace.rs deleted file mode 100644 index 50ca902a50..0000000000 --- a/crates/circuits/poseidon2-air/src/poseidon2/trace.rs +++ /dev/null @@ -1,165 +0,0 @@ -use itertools::izip; -use openvm_stark_backend::{ - p3_field::{AbstractField, Field}, - p3_matrix::dense::RowMajorMatrix, -}; - -use super::{ - air::SBOX_DEGREE, - columns::{Poseidon2AuxCols, Poseidon2Cols, Poseidon2IoCols}, - Poseidon2Air, -}; -use crate::poseidon2::columns::{Poseidon2ExternalRoundCols, Poseidon2InternalRoundCols}; - -impl Poseidon2Air { - /// Return cached state trace if it exists (input is ignored), otherwise generate trace and return - /// - /// TODO: For more efficient trace generation, a custom `DiffusionMatrix` and `ExternalMatrix` should - /// be provided. - pub fn generate_trace(&self, input_states: Vec<[F; WIDTH]>) -> RowMajorMatrix { - RowMajorMatrix::new( - input_states - .into_iter() - .flat_map(|input_state| self.generate_trace_row(input_state).flatten().into_iter()) - .collect(), - self.get_width(), - ) - } - - /// Cache the trace as a state variable, return the outputs - pub fn request_trace(&mut self, states: &[[F; WIDTH]]) -> Vec> { - states - .iter() - .map(|s| self.generate_trace_row(*s).io.output.to_vec()) - .collect() - } - - /// Perform entire nonlinear external layer operation on state - pub fn ext_layer( - &self, - state: &mut [F; WIDTH], - constants: &[F; WIDTH], - intermediate_powers: &mut [Option; WIDTH], - ) { - self.ext_lin_layer(state); - for ((s, c), ip) in state.iter_mut().zip(constants).zip(intermediate_powers) { - *s = self.sbox_p_gen(*s + *c, ip); - } - } - - /// Perform entire nonlinear internal layer operation on state - pub fn int_layer( - &self, - state: &mut [F; WIDTH], - constant: F, - intermediate_power: &mut Option, - ) { - self.int_lin_layer(state); - state[0] += constant; - state[0] = self.sbox_p_gen(state[0], intermediate_power); - } - - /// Generate one row of trace from the input state. - pub fn generate_trace_row(&self, input_state: [F; WIDTH]) -> Poseidon2Cols { - let mut state = input_state; - - // The first half of the external rounds. - let rounds_f_beginning = self.rounds_f / 2; - let mut phase1 = Vec::with_capacity(rounds_f_beginning); - for r in 0..rounds_f_beginning { - let mut intermediate_powers = core::array::from_fn(|_| None); - self.ext_layer( - &mut state, - &self.external_constants[r], - &mut intermediate_powers, - ); - phase1.push(Poseidon2ExternalRoundCols { - intermediate_sbox_powers: intermediate_powers, - round_output: state, - }); - } - - // The internal rounds. - let mut phase2 = Vec::with_capacity(self.rounds_p); - for r in 0..self.rounds_p { - let mut intermediate_power = None; - if r == 0 { - self.ext_lin_layer(&mut state); - state[0] += self.internal_constants[0]; - state[0] = self.sbox_p_gen(state[0], &mut intermediate_power); - } else { - self.int_layer( - &mut state, - self.internal_constants[r], - &mut intermediate_power, - ); - } - - phase2.push(Poseidon2InternalRoundCols { - intermediate_sbox_power: intermediate_power, - round_output: state, - }); - } - - // The second half of the external rounds. - let mut phase3 = Vec::with_capacity(self.rounds_f - rounds_f_beginning); - for r in rounds_f_beginning..self.rounds_f { - let mut intermediate_powers = core::array::from_fn(|_| None); - if r == rounds_f_beginning { - self.int_lin_layer(&mut state); - for (s, c, ip) in izip!( - state.iter_mut(), - &self.external_constants[rounds_f_beginning], - intermediate_powers.iter_mut(), - ) { - *s = self.sbox_p_gen(*s + *c, ip); - } - } else { - self.ext_layer( - &mut state, - &self.external_constants[r], - &mut intermediate_powers, - ); - } - - phase3.push(Poseidon2ExternalRoundCols { - intermediate_sbox_powers: intermediate_powers, - round_output: state, - }); - } - self.ext_lin_layer(&mut state); - let output_state = state; - - Poseidon2Cols { - io: Poseidon2IoCols { - input: input_state, - output: output_state, - }, - aux: Poseidon2AuxCols { - phase1, - phase2, - phase3, - }, - } - } - - fn sbox_p_gen(&self, value: T, intermediate_power: &mut Option) -> T { - if self.max_constraint_degree < SBOX_DEGREE { - // In this case, we compute and set intermediate_power to value^max_constraint_degree - let mut val_p = T::ONE; - for _ in 0..self.max_constraint_degree { - val_p *= value.clone(); - } - *intermediate_power = Some(val_p); - } - - let mut ret = T::ONE; - for _ in 0..(SBOX_DEGREE - 1) / self.max_constraint_degree { - ret *= intermediate_power.clone().unwrap(); - } - for _ in 0..(SBOX_DEGREE - 1) % self.max_constraint_degree + 1 { - ret *= value.clone(); - } - ret - } -} diff --git a/crates/circuits/poseidon2-air/src/tests.rs b/crates/circuits/poseidon2-air/src/tests.rs new file mode 100644 index 0000000000..c9a567962e --- /dev/null +++ b/crates/circuits/poseidon2-air/src/tests.rs @@ -0,0 +1,98 @@ +use std::{array::from_fn, sync::Arc}; + +use openvm_stark_backend::{ + p3_air::BaseAir, p3_field::AbstractField, utils::disable_debug_builder, + verifier::VerificationError, +}; +use openvm_stark_sdk::{ + config::{ + baby_bear_poseidon2::{engine_from_perm, random_perm}, + fri_params::standard_fri_params_with_100_bits_conjectured_security, + }, + engine::StarkEngine, + p3_baby_bear::BabyBear, + utils::create_seeded_rng, +}; +use p3_poseidon2::ExternalLayerConstants; +use rand::{rngs::StdRng, Rng, RngCore}; + +use super::{Poseidon2Config, Poseidon2Constants, Poseidon2SubChip}; +use crate::BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS; + +fn run_poseidon2_subchip_test(subchip: Arc>, rng: &mut StdRng) { + // random state and trace generation + let num_rows = 1 << 4; + let states: Vec<[BabyBear; 16]> = (0..num_rows) + .map(|_| { + let vec: Vec = (0..16) + .map(|_| BabyBear::from_canonical_u32(rng.next_u32() % (1 << 30))) + .collect(); + vec.try_into().unwrap() + }) + .collect(); + let mut poseidon2_trace = subchip.generate_trace(states.clone()); + + // engine generation + let perm = random_perm(); + let fri_params = standard_fri_params_with_100_bits_conjectured_security(3); // max constraint degree = 7 requires log blowup = 3 + let engine = engine_from_perm(perm, fri_params); + + // positive test + engine + .run_simple_test_impl( + vec![subchip.air.clone()], + vec![poseidon2_trace.clone()], + vec![vec![]], + ) + .expect("Verification failed"); + + // negative test + disable_debug_builder(); + for _ in 0..10 { + let rand_idx = rng.gen_range(0..subchip.air.width()); + let rand_inc = BabyBear::from_canonical_u32(rng.gen_range(1..=1 << 27)); + poseidon2_trace.row_mut((1 << 4) - 1)[rand_idx] += rand_inc; + assert_eq!( + engine + .run_simple_test_impl( + vec![subchip.air.clone()], + vec![poseidon2_trace.clone()], + vec![vec![]], + ) + .err(), + Some(VerificationError::OodEvaluationMismatch), + "Expected constraint to fail" + ); + poseidon2_trace.row_mut((1 << 4) - 1)[rand_idx] -= rand_inc; + } +} + +#[test] +fn test_poseidon2_default() { + let mut rng = create_seeded_rng(); + let poseidon2_config = Poseidon2Config::default(); + let poseidon2_subchip = Arc::new(Poseidon2SubChip::::new(poseidon2_config)); + run_poseidon2_subchip_test(poseidon2_subchip, &mut rng); +} + +#[test] +fn test_poseidon2_random_constants() { + let mut rng = create_seeded_rng(); + let external_constants = + ExternalLayerConstants::new_from_rng(2 * BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS, &mut rng); + let poseidon2_config = Poseidon2Config { + constants: Poseidon2Constants { + beginning_full_round_constants: { + let vec = external_constants.get_initial_constants(); + from_fn(|i| vec[i]) + }, + ending_full_round_constants: { + let vec = external_constants.get_terminal_constants(); + from_fn(|i| vec[i]) + }, + partial_round_constants: from_fn(|_| BabyBear::from_wrapped_u32(rng.next_u32())), + }, + }; + let poseidon2_subchip = Arc::new(Poseidon2SubChip::::new(poseidon2_config)); + run_poseidon2_subchip_test(poseidon2_subchip, &mut rng); +} diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 528cdf6a94..0b61a7062a 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -53,9 +53,6 @@ ctrlc = "3.4.2" toml = { workspace = true } num-bigint-dig = { workspace = true, features = ["serde"] } -[dev-dependencies] -openvm-cli-example-test = { path = "example" } - [features] default = ["parallel", "mimalloc"] parallel = ["openvm-circuit/parallel"] diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index d538593a70..140a78df3c 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -42,9 +42,6 @@ metrics.workspace = true tracing.workspace = true itertools.workspace = true -[dev-dependencies] -openvm-sdk-example-test = { path = "example" } - [features] default = ["parallel"] bench-metrics = ["openvm-native-recursion/bench-metrics"] diff --git a/crates/sdk/example/Cargo.toml b/crates/sdk/example/Cargo.toml index 4dd4d3591b..1ca1917784 100644 --- a/crates/sdk/example/Cargo.toml +++ b/crates/sdk/example/Cargo.toml @@ -1,3 +1,4 @@ +[workspace] [package] name = "openvm-sdk-example-test" version = "0.0.0" diff --git a/crates/vm/src/arch/config.rs b/crates/vm/src/arch/config.rs index 41a926d92b..99b555f16a 100644 --- a/crates/vm/src/arch/config.rs +++ b/crates/vm/src/arch/config.rs @@ -1,7 +1,7 @@ use derive_new::new; use openvm_circuit::system::memory::MemoryTraceHeights; use openvm_instructions::program::DEFAULT_MAX_NUM_PUBLIC_VALUES; -use openvm_poseidon2_air::poseidon2::Poseidon2Config; +use openvm_poseidon2_air::Poseidon2Config; use openvm_stark_backend::{p3_field::PrimeField32, ChipUsageGetter}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -25,8 +25,8 @@ const DEFAULT_POSEIDON2_MAX_CONSTRAINT_DEGREE: usize = 3; /// Width of Poseidon2 VM uses. pub const POSEIDON2_WIDTH: usize = 16; /// Returns a Poseidon2 config for the VM. -pub fn vm_poseidon2_config() -> Poseidon2Config { - Poseidon2Config::::new_p3_baby_bear_16() +pub fn vm_poseidon2_config() -> Poseidon2Config { + Poseidon2Config::default() } pub trait VmConfig: Clone + Serialize + DeserializeOwned { diff --git a/crates/vm/src/arch/extensions.rs b/crates/vm/src/arch/extensions.rs index 38b199a591..f41406805a 100644 --- a/crates/vm/src/arch/extensions.rs +++ b/crates/vm/src/arch/extensions.rs @@ -9,10 +9,8 @@ use openvm_circuit_primitives::{ }; use openvm_circuit_primitives_derive::{Chip, ChipUsageGetter}; use openvm_instructions::{ - program::Program, PhantomDiscriminant, Poseidon2Opcode, PublishOpcode, SystemOpcode, - UsizeOpcode, VmOpcode, + program::Program, PhantomDiscriminant, PublishOpcode, SystemOpcode, UsizeOpcode, VmOpcode, }; -use openvm_poseidon2_air::poseidon2::air::SBOX_DEGREE; use openvm_stark_backend::{ config::{Domain, StarkGenericConfig}, p3_commit::PolynomialSpace, @@ -40,7 +38,7 @@ use crate::system::{ }, native_adapter::NativeAdapterChip, phantom::PhantomChip, - poseidon2::Poseidon2Chip, + poseidon2::Poseidon2PeripheryChip, program::{ProgramBus, ProgramChip}, public_values::{core::PublicValuesCoreChip, PublicValuesChip}, }; @@ -492,7 +490,7 @@ pub enum SystemExecutor { #[derive(ChipUsageGetter, Chip, AnyEnum, From)] pub enum SystemPeriphery { /// Poseidon2 chip with direct compression interactions - Poseidon2(Poseidon2Chip), + Poseidon2(Poseidon2PeripheryChip), } impl SystemComplex { @@ -555,14 +553,10 @@ impl SystemComplex { .compression_bus() .unwrap() .0; - let chip = Poseidon2Chip::from_poseidon2_config( + let chip = Poseidon2PeripheryChip::new( vm_poseidon2_config(), - config.max_constraint_degree.min(SBOX_DEGREE), - EXECUTION_BUS, - PROGRAM_BUS, - memory_controller.clone(), direct_bus_idx, - Poseidon2Opcode::default_offset(), + config.max_constraint_degree, ); inventory.add_periphery_chip(chip); } @@ -698,7 +692,7 @@ impl VmChipComplex { chip.as_any_kind().downcast_ref() } - pub fn poseidon2_chip(&self) -> Option<&Poseidon2Chip> + pub fn poseidon2_chip(&self) -> Option<&Poseidon2PeripheryChip> where P: AnyEnum, { @@ -709,7 +703,7 @@ impl VmChipComplex { chip.as_any_kind().downcast_ref() } - pub fn poseidon2_chip_mut(&mut self) -> Option<&mut Poseidon2Chip> + pub fn poseidon2_chip_mut(&mut self) -> Option<&mut Poseidon2PeripheryChip> where P: AnyEnum, { diff --git a/crates/vm/src/arch/hasher/poseidon2.rs b/crates/vm/src/arch/hasher/poseidon2.rs index f2191b0f9a..278553f038 100644 --- a/crates/vm/src/arch/hasher/poseidon2.rs +++ b/crates/vm/src/arch/hasher/poseidon2.rs @@ -3,7 +3,7 @@ use std::{ marker::PhantomData, }; -use openvm_poseidon2_air::{p3_poseidon2::ExternalLayerConstants, p3_symmetric::Permutation}; +use openvm_poseidon2_air::p3_symmetric::Permutation; use openvm_stark_backend::p3_field::{AbstractField, PrimeField32}; use p3_baby_bear::{BabyBear, Poseidon2BabyBear}; @@ -15,12 +15,10 @@ use crate::{ pub fn vm_poseidon2_hasher() -> Poseidon2Hasher { assert_eq!(F::ORDER_U32, BabyBear::ORDER_U32, "F must be BabyBear"); let config = vm_poseidon2_config::(); - let external_constants = ExternalLayerConstants::new( - config.external_constants[..config.rounds_f() / 2].to_vec(), - config.external_constants[config.rounds_f() / 2..].to_vec(), - ); + let (external_constants, internal_constants) = + config.constants.to_external_internal_constants(); Poseidon2Hasher { - poseidon2: Poseidon2BabyBear::new(external_constants, config.internal_constants), + poseidon2: Poseidon2BabyBear::new(external_constants, internal_constants), _marker: PhantomData, } } diff --git a/crates/vm/src/arch/segment.rs b/crates/vm/src/arch/segment.rs index 6f7cb813fa..5f508f1b37 100644 --- a/crates/vm/src/arch/segment.rs +++ b/crates/vm/src/arch/segment.rs @@ -20,7 +20,7 @@ use crate::{ metrics::cycle_tracker::CycleTracker, system::{ memory::{Equipartition, CHUNK}, - poseidon2::Poseidon2Chip, + poseidon2::Poseidon2PeripheryChip, }, }; @@ -267,13 +267,13 @@ impl> ExecutionSegment { VmChipComplex::::POSEIDON2_PERIPHERY_IDX, ) .expect("Poseidon2 chip required for persistent memory"); - let hasher: &mut Poseidon2Chip = chip + let hasher: &mut Poseidon2PeripheryChip = chip .as_any_kind_mut() .downcast_mut() .expect("Poseidon2 chip required for persistent memory"); memory_controller.finalize(Some(hasher)) } else { - memory_controller.finalize(None::<&mut Poseidon2Chip>) + memory_controller.finalize(None::<&mut Poseidon2PeripheryChip>) }; } #[cfg(feature = "bench-metrics")] diff --git a/crates/vm/src/arch/testing/mod.rs b/crates/vm/src/arch/testing/mod.rs index abf90d24db..ebc6831714 100644 --- a/crates/vm/src/arch/testing/mod.rs +++ b/crates/vm/src/arch/testing/mod.rs @@ -41,7 +41,7 @@ pub use memory::MemoryTester; pub use test_adapter::TestAdapterChip; use super::{ExecutionBus, InstructionExecutor}; -use crate::system::{memory::MemoryControllerRef, poseidon2::Poseidon2Chip}; +use crate::system::{memory::MemoryControllerRef, poseidon2::Poseidon2PeripheryChip}; pub const EXECUTION_BUS: usize = 0; pub const MEMORY_BUS: usize = 1; @@ -202,7 +202,7 @@ impl VmChipTestBuilder { self.memory .controller .borrow_mut() - .finalize(None::<&mut Poseidon2Chip>); + .finalize(None::<&mut Poseidon2PeripheryChip>); let tester = VmChipTester { memory: Some(self.memory), ..Default::default() diff --git a/crates/vm/src/system/memory/tests.rs b/crates/vm/src/system/memory/tests.rs index 9fb0018855..ea601c070b 100644 --- a/crates/vm/src/system/memory/tests.rs +++ b/crates/vm/src/system/memory/tests.rs @@ -1,15 +1,13 @@ use std::{ array, borrow::{Borrow, BorrowMut}, - cell::RefCell, - rc::Rc, sync::Arc, }; use itertools::Itertools; use openvm_circuit_primitives::var_range::{VariableRangeCheckerBus, VariableRangeCheckerChip}; use openvm_circuit_primitives_derive::AlignedBorrow; -use openvm_poseidon2_air::poseidon2::Poseidon2Config; +use openvm_poseidon2_air::Poseidon2Config; use openvm_stark_backend::{ interaction::InteractionBuilder, p3_air::{Air, BaseAir}, @@ -34,8 +32,8 @@ use super::{ }; use crate::{ arch::{ - testing::memory::gen_pointer, ExecutionBus, MemoryConfig, EXECUTION_BUS, MEMORY_BUS, - MEMORY_MERKLE_BUS, POSEIDON2_DIRECT_BUS, READ_INSTRUCTION_BUS, + testing::memory::gen_pointer, MemoryConfig, MEMORY_BUS, MEMORY_MERKLE_BUS, + POSEIDON2_DIRECT_BUS, }, system::{ memory::{ @@ -43,8 +41,7 @@ use crate::{ offline_checker::{MemoryBridge, MemoryBus, MemoryReadAuxCols, MemoryWriteAuxCols}, MemoryAddress, MemoryWriteRecord, }, - poseidon2::Poseidon2Chip, - program::ProgramBus, + poseidon2::Poseidon2PeripheryChip, }, }; @@ -244,7 +241,7 @@ fn test_memory_controller() { }); let memory_requester_trace = generate_trace(records, aux_factory); - memory_controller.finalize(None::<&mut Poseidon2Chip>); + memory_controller.finalize(None::<&mut Poseidon2PeripheryChip>); let mut air_proof_inputs = memory_controller.generate_air_proof_inputs(); air_proof_inputs.push(AirProofInput::simple_no_pis( @@ -283,22 +280,8 @@ fn test_memory_controller_persistent() { memory_bridge: memory_controller.memory_bridge(), }; - // This never gets used because poseido2_chip will only have direct compression interactions - let dummy_memory_controller = MemoryController::with_volatile_memory( - MemoryBus(MEMORY_BUS), - MemoryConfig::default(), - range_checker.clone(), - ); - - let mut poseidon_chip = Poseidon2Chip::from_poseidon2_config( - Poseidon2Config::<16, BabyBear>::new_p3_baby_bear_16(), - 3, - ExecutionBus(EXECUTION_BUS), - ProgramBus(READ_INSTRUCTION_BUS), - Rc::new(RefCell::new(dummy_memory_controller)), - POSEIDON2_DIRECT_BUS, - 0, - ); + let mut poseidon_chip = + Poseidon2PeripheryChip::new(Poseidon2Config::default(), POSEIDON2_DIRECT_BUS, 3); memory_controller.finalize(Some(&mut poseidon_chip)); let mut air_proof_inputs = memory_controller.generate_air_proof_inputs(); diff --git a/crates/vm/src/system/poseidon2/air.rs b/crates/vm/src/system/poseidon2/air.rs index 50d98aa896..834653d8cf 100644 --- a/crates/vm/src/system/poseidon2/air.rs +++ b/crates/vm/src/system/poseidon2/air.rs @@ -1,158 +1,71 @@ -use std::borrow::Borrow; +use std::{array::from_fn, borrow::Borrow, sync::Arc}; use derive_new::new; -use itertools::izip; -use openvm_poseidon2_air::poseidon2::Poseidon2Air; +use openvm_poseidon2_air::{ + Poseidon2SubAir, BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS, POSEIDON2_WIDTH, +}; use openvm_stark_backend::{ + air_builders::sub::SubAirBuilder, interaction::InteractionBuilder, - p3_air::{Air, AirBuilder, BaseAir}, - p3_field::{AbstractField, Field}, + p3_air::{Air, BaseAir}, + p3_field::Field, p3_matrix::Matrix, rap::{BaseAirWithPublicValues, PartitionedBaseAir}, }; -use super::{columns::Poseidon2VmCols, CHUNK, WIDTH}; -use crate::{ - arch::ExecutionBridge, - system::memory::{offline_checker::MemoryBridge, MemoryAddress}, -}; +use super::columns::Poseidon2PeripheryCols; /// Poseidon2 Air, VM version. /// /// Carries the subair for subtrace generation. Sticking to the conventions, this struct carries no state. /// `direct` determines whether direct interactions are enabled. By default they are on. #[derive(Clone, new, Debug)] -pub struct Poseidon2VmAir { - pub inner: Poseidon2Air, - pub execution_bridge: ExecutionBridge, - pub memory_bridge: MemoryBridge, - pub direct_bus: Option, // Whether direct interactions are enabled. - - pub(super) offset: usize, +pub struct Poseidon2PeripheryAir { + pub(super) subair: Arc>, + pub(super) bus: usize, } -impl BaseAirWithPublicValues for Poseidon2VmAir {} -impl PartitionedBaseAir for Poseidon2VmAir {} -impl BaseAir for Poseidon2VmAir { +impl BaseAirWithPublicValues + for Poseidon2PeripheryAir +{ +} +impl PartitionedBaseAir + for Poseidon2PeripheryAir +{ +} +impl BaseAir + for Poseidon2PeripheryAir +{ fn width(&self) -> usize { - Poseidon2VmCols::::width(self) + Poseidon2PeripheryCols::::width() } } -impl Air for Poseidon2VmAir { - /// Checks and constrains multiplicity indicators, and does subair evaluation +impl Air + for Poseidon2PeripheryAir +{ fn eval(&self, builder: &mut AB) { + let mut sub_builder = + SubAirBuilder::, AB::F>::new( + builder, + 0..self.subair.width(), + ); + self.subair.eval(&mut sub_builder); + let main = builder.main(); let local = main.row_slice(0); - let local: &[::Var] = (*local).borrow(); - - let cols = Poseidon2VmCols::::from_slice(local, self); - let internal_io = cols.aux.internal.io; - - self.inner.eval_without_interactions( - builder, - cols.aux.internal.io, - cols.aux.internal.aux.into_expr::(), - ); - - builder.assert_bool(cols.io.is_opcode); - builder.assert_bool(cols.io.is_compress_opcode); - builder.assert_bool(cols.io.is_compress_direct); - - // Both opcode and compress_direct cannot be true - builder.assert_zero(cols.io.is_opcode * cols.io.is_compress_direct); - - builder - .when(cols.io.is_compress_opcode) - .assert_one(cols.io.is_opcode); - let is_permute_opcode = cols.io.is_opcode - cols.io.is_compress_opcode; - - // if permute instruction, the rhs_ptr should be contiguous with lhs_ptr - builder.when(is_permute_opcode.clone()).assert_eq( - cols.aux.rhs_ptr, - cols.aux.lhs_ptr + AB::F::from_canonical_usize(CHUNK), - ); - - // Memory access constraints - let timestamp = cols.io.timestamp; - let mut timestamp_delta = 0; - let mut timestamp_pp = || { - timestamp_delta += 1; - timestamp + AB::F::from_canonical_usize(timestamp_delta - 1) - }; - - // read addresses when is_opcode: - // dst <- [a]_d, lhs <- [b]_d - // Only when opcode is COMPRESS is rhs <- [c]_d read - for (io_addr, aux_addr, count, mem_aux) in izip!( - [cols.io.a, cols.io.b, cols.io.c], - [cols.aux.dst_ptr, cols.aux.lhs_ptr, cols.aux.rhs_ptr], - [ - cols.io.is_opcode, - cols.io.is_opcode, - cols.io.is_compress_opcode - ], - &cols.aux.ptr_aux_cols, - ) { - self.memory_bridge - .read( - MemoryAddress::new(cols.io.d, io_addr), - [aux_addr], - timestamp_pp(), - mem_aux, - ) - .eval(builder, count); - } - - let [input1_aux_cols, input2_aux_cols] = cols.aux.input_aux_cols; - let [output1_aux_cols, output2_aux_cols] = cols.aux.output_aux_cols; - - // First input chunk. - self.memory_bridge - .read( - MemoryAddress::new(cols.io.e, cols.aux.lhs_ptr), - cols.aux.internal.io.input[..CHUNK].try_into().unwrap(), - timestamp_pp(), - &input1_aux_cols, - ) - .eval(builder, cols.io.is_opcode); - - // Second input chunk. - self.memory_bridge - .read( - MemoryAddress::new(cols.io.e, cols.aux.rhs_ptr), - cols.aux.internal.io.input[CHUNK..].try_into().unwrap(), - timestamp_pp(), - &input2_aux_cols, - ) - .eval(builder, cols.io.is_opcode); - - // First output chunk. - self.memory_bridge - .write( - MemoryAddress::new(cols.io.e, cols.aux.dst_ptr), - cols.aux.internal.io.output[..CHUNK].try_into().unwrap(), - timestamp_pp(), - &output1_aux_cols, - ) - .eval(builder, cols.io.is_opcode); - - // Second output chunk. - let pointer = cols.aux.dst_ptr + AB::F::from_canonical_usize(CHUNK); - self.memory_bridge - .write( - MemoryAddress::new(cols.io.e, pointer), - cols.aux.internal.io.output[CHUNK..].try_into().unwrap(), - timestamp_pp(), - &output2_aux_cols, - ) - .eval(builder, is_permute_opcode); - - self.eval_interactions( - builder, - cols.io, - internal_io, - AB::Expr::from_canonical_usize(timestamp_delta), - ); + let cols: &Poseidon2PeripheryCols = (*local).borrow(); + + let input: [AB::Var; POSEIDON2_WIDTH] = cols.inner.inputs; + let output: [AB::Var; POSEIDON2_WIDTH] = + cols.inner.ending_full_rounds[BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS - 1].post; + let fields: [_; POSEIDON2_WIDTH + POSEIDON2_WIDTH / 2] = from_fn(|i| { + if i < POSEIDON2_WIDTH { + input[i] + } else { + output[i - POSEIDON2_WIDTH] + } + }); + builder.push_receive(self.bus, fields, cols.mult); } } diff --git a/crates/vm/src/system/poseidon2/bridge.rs b/crates/vm/src/system/poseidon2/bridge.rs deleted file mode 100644 index 24736a8bd3..0000000000 --- a/crates/vm/src/system/poseidon2/bridge.rs +++ /dev/null @@ -1,45 +0,0 @@ -use openvm_poseidon2_air::poseidon2::columns::Poseidon2IoCols; -use openvm_stark_backend::{ - interaction::InteractionBuilder, - p3_field::{AbstractField, Field}, -}; - -use super::{air::Poseidon2VmAir, columns::Poseidon2VmIoCols, WIDTH}; -use crate::arch::{instructions::Poseidon2Opcode::PERM_POS2, ExecutionState}; - -impl Poseidon2VmAir { - /// Receives instructions from the Core on the designated `POSEIDON2_BUS` (opcodes) or `POSEIDON2_DIRECT_BUS` (direct), and sends both read and write requests to the memory chip. - /// - /// Receives (clk, a, b, c, d, e, cmp) for opcodes, width exposed in `opcode_interaction_width()` - /// - /// Receives (hash_in.0, hash_in.1, hash_out) for direct, width exposed in `direct_interaction_width()` - pub fn eval_interactions>( - &self, - builder: &mut AB, - io: Poseidon2VmIoCols, - internal_io: Poseidon2IoCols, - timestamp_delta: AB::Expr, - ) { - let opcode = AB::Expr::from_canonical_usize(PERM_POS2 as usize) + io.is_compress_opcode; - - self.execution_bridge - .execute_and_increment_pc( - opcode + AB::Expr::from_canonical_usize(self.offset), - [io.a, io.b, io.c, io.d, io.e], - ExecutionState::new(io.pc, io.timestamp), - timestamp_delta, - ) - .eval(builder, io.is_opcode); - - // DIRECT - if let Some(direct_bus) = self.direct_bus { - let fields = internal_io - .flatten() - .into_iter() - .take(WIDTH + WIDTH / 2) - .collect::>(); - - builder.push_receive(direct_bus, fields, io.is_compress_direct); - } - } -} diff --git a/crates/vm/src/system/poseidon2/chip.rs b/crates/vm/src/system/poseidon2/chip.rs new file mode 100644 index 0000000000..0be1cab19a --- /dev/null +++ b/crates/vm/src/system/poseidon2/chip.rs @@ -0,0 +1,73 @@ +use std::{ + array, + sync::{atomic::AtomicU32, Arc}, +}; + +use openvm_poseidon2_air::{Poseidon2Config, Poseidon2SubChip}; +use openvm_stark_backend::p3_field::PrimeField32; +use rustc_hash::FxHashMap; + +use super::{ + air::Poseidon2PeripheryAir, PERIPHERY_POSEIDON2_CHUNK_SIZE, PERIPHERY_POSEIDON2_WIDTH, +}; +use crate::arch::hasher::{Hasher, HasherChip}; + +#[derive(Debug)] +pub struct Poseidon2PeripheryBaseChip { + pub air: Arc>, + pub subchip: Poseidon2SubChip, + pub records: FxHashMap<[F; PERIPHERY_POSEIDON2_WIDTH], AtomicU32>, +} + +impl Poseidon2PeripheryBaseChip { + pub fn new(poseidon2_config: Poseidon2Config, bus_idx: usize) -> Self { + let subchip = Poseidon2SubChip::new(poseidon2_config); + Self { + air: Arc::new(Poseidon2PeripheryAir::new(subchip.air.clone(), bus_idx)), + subchip, + records: FxHashMap::default(), + } + } +} + +impl Hasher + for Poseidon2PeripheryBaseChip +{ + fn compress( + &self, + lhs: &[F; PERIPHERY_POSEIDON2_CHUNK_SIZE], + rhs: &[F; PERIPHERY_POSEIDON2_CHUNK_SIZE], + ) -> [F; PERIPHERY_POSEIDON2_CHUNK_SIZE] { + let mut input_state = [F::ZERO; PERIPHERY_POSEIDON2_WIDTH]; + input_state[..PERIPHERY_POSEIDON2_CHUNK_SIZE].copy_from_slice(lhs); + input_state[PERIPHERY_POSEIDON2_CHUNK_SIZE..].copy_from_slice(rhs); + + let output = self.subchip.permute(input_state); + array::from_fn(|i| output[i]) + } +} + +impl HasherChip + for Poseidon2PeripheryBaseChip +{ + /// Key method for Hasher trait. + /// + /// Takes two chunks, hashes them, and returns the result. Total width 3 * CHUNK, exposed in `direct_interaction_width()`. + /// + /// No interactions with other chips. + fn compress_and_record( + &mut self, + lhs: &[F; PERIPHERY_POSEIDON2_CHUNK_SIZE], + rhs: &[F; PERIPHERY_POSEIDON2_CHUNK_SIZE], + ) -> [F; PERIPHERY_POSEIDON2_CHUNK_SIZE] { + let mut input = [F::ZERO; PERIPHERY_POSEIDON2_WIDTH]; + input[..PERIPHERY_POSEIDON2_CHUNK_SIZE].copy_from_slice(lhs); + input[PERIPHERY_POSEIDON2_CHUNK_SIZE..].copy_from_slice(rhs); + + let count = self.records.entry(input).or_insert(AtomicU32::new(0)); + count.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + + let output = self.subchip.permute(input); + array::from_fn(|i| output[i]) + } +} diff --git a/crates/vm/src/system/poseidon2/columns.rs b/crates/vm/src/system/poseidon2/columns.rs index 5e68e3af80..f45e8523fe 100644 --- a/crates/vm/src/system/poseidon2/columns.rs +++ b/crates/vm/src/system/poseidon2/columns.rs @@ -1,238 +1,10 @@ -use std::array; - -use openvm_poseidon2_air::poseidon2::columns::Poseidon2Cols; -use openvm_stark_backend::p3_field::Field; - -use super::air::Poseidon2VmAir; -use crate::system::{ - memory::offline_checker::{MemoryReadAuxCols, MemoryWriteAuxCols}, - poseidon2::{CHUNK, WIDTH}, -}; +use openvm_circuit_primitives::AlignedBorrow; +use openvm_poseidon2_air::Poseidon2SubCols; /// Columns for Poseidon2Vm AIR. -#[derive(Clone, Debug)] -pub struct Poseidon2VmCols { - pub io: Poseidon2VmIoCols, - pub aux: Poseidon2VmAuxCols, -} - -/// IO columns for Poseidon2Chip. -/// * `is_opcode`: whether the row is for an opcode (either COMPRESS or PERMUTE) -/// * `is_direct`: whether the row is for a direct hash -/// * `clk`: the clock cycle (NOT timestamp) -/// * `a`, `b`, `c`: addresses -/// * `d`, `e`: address spaces -/// * `cmp`: boolean for compression vs. permutation -#[derive(Clone, Copy, Debug)] -pub struct Poseidon2VmIoCols { - pub is_opcode: T, - pub is_compress_opcode: T, - pub is_compress_direct: T, - pub pc: T, - pub timestamp: T, - pub a: T, - pub b: T, - pub c: T, - pub d: T, - pub e: T, -} - -/// Auxiliary columns for Poseidon2Chip. -/// * `addresses`: addresses where inputs/outputs for Poseidon2 are located -/// * `internal`: auxiliary columns used by Poseidon2Air for interpreting opcode, evaluating indicators, inverse, and explicit computations. -#[derive(Clone, Debug)] -pub struct Poseidon2VmAuxCols { - pub dst_ptr: T, - pub lhs_ptr: T, - pub rhs_ptr: T, - pub internal: Poseidon2Cols, - pub ptr_aux_cols: [MemoryReadAuxCols; 3], - pub input_aux_cols: [MemoryReadAuxCols; 2], - pub output_aux_cols: [MemoryWriteAuxCols; 2], -} - -impl Poseidon2VmCols { - pub fn width(p2_air: &Poseidon2VmAir) -> usize { - Poseidon2VmIoCols::::get_width() + Poseidon2VmAuxCols::::width(p2_air) - } - - pub fn flatten(self) -> Vec { - let mut result = self.io.flatten(); - result.extend(self.aux.flatten()); - result - } - - pub fn from_slice(slice: &[T], air: &Poseidon2VmAir) -> Poseidon2VmCols { - let io_width = Poseidon2VmIoCols::::get_width(); - Self { - io: Poseidon2VmIoCols::::from_slice(&slice[..io_width]), - aux: Poseidon2VmAuxCols::::from_slice(&slice[io_width..], air), - } - } -} - -impl Poseidon2VmCols { - /// Blank row with all zero input (poseidon2 internal hash values are nonzero) - /// and `is_alloc` set to 0. - /// - /// Due to how memory timestamps are currently managed, even blank rows must have consistent timestamps. - /// - /// Warning: the aux memory columns have capacity reserved but are not initialized. - pub fn blank_row(air: &Poseidon2VmAir) -> Self { - Self { - io: Poseidon2VmIoCols::::blank_row(), - aux: Poseidon2VmAuxCols::::blank_row(air), - } - } -} - -impl Poseidon2VmIoCols { - pub fn get_width() -> usize { - 10 - } - - pub fn flatten(&self) -> Vec { - vec![ - self.is_opcode.clone(), - self.is_compress_direct.clone(), - self.pc.clone(), - self.timestamp.clone(), - self.a.clone(), - self.b.clone(), - self.c.clone(), - self.d.clone(), - self.e.clone(), - self.is_compress_opcode.clone(), - ] - } - - pub fn from_slice(slice: &[T]) -> Self { - Self { - is_opcode: slice[0].clone(), - is_compress_direct: slice[1].clone(), - pc: slice[2].clone(), - timestamp: slice[3].clone(), - a: slice[4].clone(), - b: slice[5].clone(), - c: slice[6].clone(), - d: slice[7].clone(), - e: slice[8].clone(), - is_compress_opcode: slice[9].clone(), - } - } -} -impl Poseidon2VmIoCols { - pub fn blank_row() -> Self { - Self { - is_opcode: F::ZERO, - is_compress_direct: F::ZERO, - pc: F::ZERO, - timestamp: F::ZERO, - a: F::ZERO, - b: F::ZERO, - c: F::ZERO, - d: F::ONE, - e: F::ONE, - is_compress_opcode: F::ZERO, - } - } - - pub fn direct_io_cols(timestamp: F) -> Self { - Self { - is_opcode: F::ZERO, - is_compress_direct: F::ONE, - pc: F::ZERO, - timestamp, - a: F::ZERO, - b: F::ZERO, - c: F::ZERO, - d: F::ONE, - e: F::ONE, - is_compress_opcode: F::ZERO, - } - } -} - -impl Poseidon2VmAuxCols { - pub fn width(air: &Poseidon2VmAir) -> usize { - 3 + Poseidon2Cols::::width(&air.inner) - + 3 * MemoryReadAuxCols::::width() - + 2 * MemoryReadAuxCols::::width() - + 2 * MemoryWriteAuxCols::::width() - } - - pub fn flatten(self) -> Vec { - let mut result = vec![ - self.dst_ptr.clone(), - self.lhs_ptr.clone(), - self.rhs_ptr.clone(), - ]; - result.extend(self.internal.flatten()); - result.extend( - self.ptr_aux_cols - .iter() - .flat_map(|col| col.clone().flatten()), - ); - result.extend( - self.input_aux_cols - .iter() - .flat_map(|col| col.clone().flatten()), - ); - result.extend( - self.output_aux_cols - .iter() - .flat_map(|col| col.clone().flatten()), - ); - result - } - - pub fn from_slice(slc: &[T], air: &Poseidon2VmAir) -> Self { - let dst = slc[0].clone(); - let lhs = slc[1].clone(); - let rhs = slc[2].clone(); - - let mut start = 3; - let mut end = start + Poseidon2Cols::::width(&air.inner); - let internal = Poseidon2Cols::from_slice(&slc[start..end], &air.inner); - - let ptr_aux_cols = array::from_fn(|_| { - start = end; - end += MemoryReadAuxCols::::width(); - MemoryReadAuxCols::from_slice(&slc[start..end]) - }); - let input_aux_cols = array::from_fn(|_| { - start = end; - end += MemoryReadAuxCols::::width(); - MemoryReadAuxCols::from_slice(&slc[start..end]) - }); - let output_aux_cols = array::from_fn(|_| { - start = end; - end += MemoryWriteAuxCols::::width(); - MemoryWriteAuxCols::from_slice(&slc[start..end]) - }); - - Self { - dst_ptr: dst, - lhs_ptr: lhs, - rhs_ptr: rhs, - internal, - ptr_aux_cols, - input_aux_cols, - output_aux_cols, - } - } -} - -impl Poseidon2VmAuxCols { - pub fn blank_row(air: &Poseidon2VmAir) -> Self { - Self { - dst_ptr: F::default(), - lhs_ptr: F::default(), - rhs_ptr: F::default(), - internal: Poseidon2Cols::blank_row(&air.inner), - ptr_aux_cols: array::from_fn(|_| MemoryReadAuxCols::disabled()), - input_aux_cols: array::from_fn(|_| MemoryReadAuxCols::disabled()), - output_aux_cols: array::from_fn(|_| MemoryWriteAuxCols::disabled()), - } - } +#[repr(C)] +#[derive(AlignedBorrow)] +pub struct Poseidon2PeripheryCols { + pub inner: Poseidon2SubCols, + pub mult: F, } diff --git a/crates/vm/src/system/poseidon2/mod.rs b/crates/vm/src/system/poseidon2/mod.rs index f1e3e78ee3..3d8aa84a90 100644 --- a/crates/vm/src/system/poseidon2/mod.rs +++ b/crates/vm/src/system/poseidon2/mod.rs @@ -7,366 +7,113 @@ //! into a hash by applying a sponge construction. `compress` can be used as a hash in the //! internal leaves of a Merkle tree but **not** as the leaf hash because `compress` does not //! add any padding. -use std::array; - -use columns::*; -use openvm_instructions::{instruction::Instruction, program::DEFAULT_PC_STEP, VmOpcode}; -use openvm_poseidon2_air::poseidon2::{Poseidon2Air, Poseidon2Cols, Poseidon2Config}; -use openvm_stark_backend::p3_field::PrimeField32; - -use self::air::Poseidon2VmAir; -use crate::{ - arch::{ - hasher::{Hasher, HasherChip}, - instructions::{ - Poseidon2Opcode::{self, *}, - UsizeOpcode, - }, - ExecutionBridge, ExecutionBus, ExecutionError, ExecutionState, InstructionExecutor, - }, - system::{ - memory::{ - offline_checker::{MemoryBridge, MemoryReadAuxCols, MemoryWriteAuxCols}, - MemoryAuxColsFactory, MemoryControllerRef, MemoryReadRecord, MemoryWriteRecord, - }, - program::ProgramBus, - }, +use std::sync::Arc; + +use openvm_poseidon2_air::Poseidon2Config; +use openvm_stark_backend::{ + config::{StarkGenericConfig, Val}, + p3_field::PrimeField32, + prover::types::AirProofInput, + rap::AnyRap, + Chip, ChipUsageGetter, }; #[cfg(test)] pub mod tests; pub mod air; -pub mod bridge; +mod chip; +pub use chip::*; + +use crate::arch::hasher::{Hasher, HasherChip}; pub mod columns; pub mod trace; -pub const WIDTH: usize = 16; -pub const CHUNK: usize = 8; +pub const PERIPHERY_POSEIDON2_WIDTH: usize = 16; +pub const PERIPHERY_POSEIDON2_CHUNK_SIZE: usize = 8; -/// Poseidon2 Chip. -/// -/// Carries the Poseidon2VmAir for constraints, and cached state for trace generation. -#[derive(Debug)] -pub struct Poseidon2Chip { - pub air: Poseidon2VmAir, - pub memory_controller: MemoryControllerRef, - - records: Vec>, - - offset: usize, +pub enum Poseidon2PeripheryChip { + Register0(Poseidon2PeripheryBaseChip), + Register1(Poseidon2PeripheryBaseChip), } - -impl Poseidon2VmAir { - /// Construct from Poseidon2 config and bus index. - pub fn from_poseidon2_config( - config: Poseidon2Config, +impl Poseidon2PeripheryChip { + pub fn new( + poseidon2_config: Poseidon2Config, + bus_idx: usize, max_constraint_degree: usize, - execution_bus: ExecutionBus, - program_bus: ProgramBus, - memory_bridge: MemoryBridge, - direct_bus: usize, - offset: usize, ) -> Self { - let inner = Poseidon2Air::::from_config(config, max_constraint_degree, 0); - Self { - inner, - execution_bridge: ExecutionBridge::new(execution_bus, program_bus), - memory_bridge, - direct_bus: Some(direct_bus), - offset, + if max_constraint_degree >= 7 { + Self::Register0(Poseidon2PeripheryBaseChip::new(poseidon2_config, bus_idx)) + } else { + Self::Register1(Poseidon2PeripheryBaseChip::new(poseidon2_config, bus_idx)) } } - - /// By default direct bus is on. If `continuations = OFF`, this should be called. - pub fn set_direct(&mut self, direct_bus: Option) { - self.direct_bus = direct_bus; - } - - /// By default direct bus is on. If `continuations = OFF`, this should be called. - pub fn disable_direct(&mut self) { - self.direct_bus = None; - } - - /// Number of interactions through opcode bus. - pub fn opcode_interaction_width() -> usize { - 7 - } - - /// Number of interactions through direct bus. - pub fn direct_interaction_width() -> usize { - WIDTH + WIDTH / 2 - } } -impl Poseidon2Chip { - /// Construct from Poseidon2 config and bus index. - pub fn from_poseidon2_config( - p2_config: Poseidon2Config, - max_constraint_degree: usize, - execution_bus: ExecutionBus, - program_bus: ProgramBus, - memory_controller: MemoryControllerRef, - direct_bus_idx: usize, - offset: usize, - ) -> Self { - let air = Poseidon2VmAir::::from_poseidon2_config( - p2_config, - max_constraint_degree, - execution_bus, - program_bus, - memory_controller.borrow().memory_bridge(), - direct_bus_idx, - offset, - ); - Self { - air, - records: vec![], - memory_controller, - offset, +impl Chip for Poseidon2PeripheryChip> +where + Val: PrimeField32, +{ + fn air(&self) -> Arc> { + match self { + Poseidon2PeripheryChip::Register0(chip) => chip.air(), + Poseidon2PeripheryChip::Register1(chip) => chip.air(), } } - fn record_to_cols( - aux_cols_factory: &MemoryAuxColsFactory, - record: Poseidon2Record, - ) -> Poseidon2VmCols { - match record { - Poseidon2Record::FromInstruction { - instruction, - from_state, - internal_cols, - dst_ptr_read, - lhs_ptr_read, - rhs_ptr_read, - rhs_ptr, - lhs_read, - rhs_read, - output1_write, - output2_write, - } => { - let dst_ptr = dst_ptr_read.value(); - let lhs_ptr = lhs_ptr_read.value(); - - let ptr_aux_cols = - [Some(dst_ptr_read), Some(lhs_ptr_read), rhs_ptr_read].map(|maybe_read| { - maybe_read.map_or_else(MemoryReadAuxCols::disabled, |read| { - aux_cols_factory.make_read_aux_cols(read) - }) - }); - - let input_aux_cols = - [lhs_read, rhs_read].map(|read| aux_cols_factory.make_read_aux_cols(read)); - - let output_aux_cols = [Some(output1_write), output2_write].map(|maybe_write| { - maybe_write.map_or_else(MemoryWriteAuxCols::disabled, |write| { - aux_cols_factory.make_write_aux_cols(write) - }) - }); - - Poseidon2VmCols { - io: Poseidon2VmIoCols { - is_opcode: F::ONE, - is_compress_direct: F::ZERO, - pc: F::from_canonical_u32(from_state.pc), - timestamp: F::from_canonical_u32(from_state.timestamp), - a: instruction.a, - b: instruction.b, - c: instruction.c, - d: instruction.d, - e: instruction.e, - is_compress_opcode: F::from_bool( - instruction.opcode == VmOpcode::from_usize(COMP_POS2 as usize), - ), - }, - aux: Poseidon2VmAuxCols { - dst_ptr, - lhs_ptr, - rhs_ptr, - internal: internal_cols, - ptr_aux_cols, - input_aux_cols, - output_aux_cols, - }, - } - } - Poseidon2Record::DirectCompress { inner_cols } => Poseidon2VmCols { - io: Poseidon2VmIoCols { - is_opcode: F::ZERO, - is_compress_direct: F::ONE, - pc: F::ZERO, - timestamp: F::ZERO, - a: F::ZERO, - b: F::ZERO, - c: F::ZERO, - d: F::ZERO, - e: F::ZERO, - is_compress_opcode: F::ZERO, - }, - aux: Poseidon2VmAuxCols { - dst_ptr: F::ZERO, - lhs_ptr: F::ZERO, - rhs_ptr: F::ZERO, - internal: inner_cols, - ptr_aux_cols: array::from_fn(|_| MemoryReadAuxCols::disabled()), - input_aux_cols: array::from_fn(|_| MemoryReadAuxCols::disabled()), - output_aux_cols: array::from_fn(|_| MemoryWriteAuxCols::disabled()), - }, - }, + fn generate_air_proof_input(self) -> AirProofInput { + match self { + Poseidon2PeripheryChip::Register0(chip) => chip.generate_air_proof_input(), + Poseidon2PeripheryChip::Register1(chip) => chip.generate_air_proof_input(), } } } -#[derive(Debug)] -enum Poseidon2Record { - FromInstruction { - instruction: Instruction, - from_state: ExecutionState, - internal_cols: Poseidon2Cols, - dst_ptr_read: MemoryReadRecord, - lhs_ptr_read: MemoryReadRecord, - // None for permute (since rhs_ptr is computed from lhs_ptr). - rhs_ptr_read: Option>, - rhs_ptr: F, - lhs_read: MemoryReadRecord, - rhs_read: MemoryReadRecord, - output1_write: MemoryWriteRecord, - // None for compress (since output is of size CHUNK). - output2_write: Option>, - }, - DirectCompress { - inner_cols: Poseidon2Cols, - }, -} - -impl InstructionExecutor for Poseidon2Chip { - /// Reads two chunks from memory and generates a trace row for - /// the given instruction using the subair, storing it in `rows`. Then, writes output to memory, - /// truncating if the instruction is a compression. - /// - /// Used for both compression and permutation. - fn execute( - &mut self, - instruction: Instruction, - from_state: ExecutionState, - ) -> Result, ExecutionError> { - let mut memory_controller = self.memory_controller.borrow_mut(); - - let Instruction { - opcode, - a, - b, - c, - d, - e, - .. - } = instruction; - let local_opcode_idx = opcode.local_opcode_idx(self.offset); - - let local_opcode = Poseidon2Opcode::from_usize(local_opcode_idx); - - assert!(matches!(local_opcode, COMP_POS2 | PERM_POS2)); - debug_assert_eq!(WIDTH, CHUNK * 2); - - let chunk_f = F::from_canonical_usize(CHUNK); - - let dst_ptr_read = memory_controller.read_cell(d, a); - let dst_ptr = dst_ptr_read.value(); - - let lhs_ptr_read = memory_controller.read_cell(d, b); - let lhs_ptr = lhs_ptr_read.value(); - - let (rhs_ptr, rhs_ptr_read) = match local_opcode { - COMP_POS2 => { - let rhs_ptr_read = memory_controller.read_cell(d, c); - (rhs_ptr_read.value(), Some(rhs_ptr_read)) - } - PERM_POS2 => { - memory_controller.increment_timestamp(); - (lhs_ptr + chunk_f, None) - } - }; - - let lhs_read = memory_controller.read(e, lhs_ptr); - let rhs_read = memory_controller.read(e, rhs_ptr); - let input_state: [F; WIDTH] = array::from_fn(|i| { - if i < CHUNK { - lhs_read.data[i] - } else { - rhs_read.data[i - CHUNK] - } - }); - - let internal_cols = self.air.inner.generate_trace_row(input_state); - let output = internal_cols.io.output; - - let output1: [F; CHUNK] = array::from_fn(|i| output[i]); - let output2: [F; CHUNK] = array::from_fn(|i| output[CHUNK + i]); - - let output1_write = memory_controller.write(e, dst_ptr, output1); - let output2_write = match local_opcode { - COMP_POS2 => { - memory_controller.increment_timestamp(); - None - } - PERM_POS2 => Some(memory_controller.write(e, dst_ptr + chunk_f, output2)), - }; - - self.records.push(Poseidon2Record::FromInstruction { - instruction: Instruction { - opcode: VmOpcode::from_usize(local_opcode as usize), - ..instruction - }, - from_state, - internal_cols, - dst_ptr_read, - lhs_ptr_read, - rhs_ptr_read, - rhs_ptr, - lhs_read, - rhs_read, - output1_write, - output2_write, - }); +impl ChipUsageGetter for Poseidon2PeripheryChip { + fn air_name(&self) -> String { + match self { + Poseidon2PeripheryChip::Register0(chip) => chip.air_name(), + Poseidon2PeripheryChip::Register1(chip) => chip.air_name(), + } + } - Ok(ExecutionState { - pc: from_state.pc + DEFAULT_PC_STEP, - timestamp: memory_controller.timestamp(), - }) + fn current_trace_height(&self) -> usize { + match self { + Poseidon2PeripheryChip::Register0(chip) => chip.current_trace_height(), + Poseidon2PeripheryChip::Register1(chip) => chip.current_trace_height(), + } } - fn get_opcode_name(&self, opcode: usize) -> String { - let local_opcode = Poseidon2Opcode::from_usize(opcode - self.offset); - format!("{local_opcode:?}") + fn trace_width(&self) -> usize { + match self { + Poseidon2PeripheryChip::Register0(chip) => chip.trace_width(), + Poseidon2PeripheryChip::Register1(chip) => chip.trace_width(), + } } } -impl Hasher for Poseidon2Chip { - fn compress(&self, lhs: &[F; CHUNK], rhs: &[F; CHUNK]) -> [F; CHUNK] { - let mut input_state = [F::ZERO; WIDTH]; - input_state[..CHUNK].copy_from_slice(lhs); - input_state[CHUNK..].copy_from_slice(rhs); - let inner_cols = self.air.inner.generate_trace_row(input_state); - array::from_fn(|i| inner_cols.io.output[i]) +impl Hasher for Poseidon2PeripheryChip { + fn compress( + &self, + lhs: &[F; PERIPHERY_POSEIDON2_CHUNK_SIZE], + rhs: &[F; PERIPHERY_POSEIDON2_CHUNK_SIZE], + ) -> [F; PERIPHERY_POSEIDON2_CHUNK_SIZE] { + match self { + Poseidon2PeripheryChip::Register0(chip) => chip.compress(lhs, rhs), + Poseidon2PeripheryChip::Register1(chip) => chip.compress(lhs, rhs), + } } } -impl HasherChip for Poseidon2Chip { - /// Key method for Hasher trait. - /// - /// Takes two chunks, hashes them, and returns the result. Total width 3 * CHUNK, exposed in `direct_interaction_width()`. - /// - /// No interactions with other chips. - fn compress_and_record(&mut self, lhs: &[F; CHUNK], rhs: &[F; CHUNK]) -> [F; CHUNK] { - let mut input_state = [F::ZERO; WIDTH]; - input_state[..CHUNK].copy_from_slice(lhs); - input_state[CHUNK..].copy_from_slice(rhs); - - let inner_cols = self.air.inner.generate_trace_row(input_state); - let output = array::from_fn(|i| inner_cols.io.output[i]); - - self.records - .push(Poseidon2Record::DirectCompress { inner_cols }); - output +impl HasherChip for Poseidon2PeripheryChip { + fn compress_and_record( + &mut self, + lhs: &[F; PERIPHERY_POSEIDON2_CHUNK_SIZE], + rhs: &[F; PERIPHERY_POSEIDON2_CHUNK_SIZE], + ) -> [F; PERIPHERY_POSEIDON2_CHUNK_SIZE] { + match self { + Poseidon2PeripheryChip::Register0(chip) => chip.compress_and_record(lhs, rhs), + Poseidon2PeripheryChip::Register1(chip) => chip.compress_and_record(lhs, rhs), + } } } diff --git a/crates/vm/src/system/poseidon2/tests.rs b/crates/vm/src/system/poseidon2/tests.rs index fccf2c3baa..91018c0a18 100644 --- a/crates/vm/src/system/poseidon2/tests.rs +++ b/crates/vm/src/system/poseidon2/tests.rs @@ -1,232 +1,130 @@ -use openvm_instructions::{instruction::Instruction, VmOpcode}; -use openvm_poseidon2_air::poseidon2::{Poseidon2Air, Poseidon2Config}; -use openvm_stark_backend::{ - p3_field::{AbstractField, PrimeField64}, - utils::disable_debug_builder, - verifier::VerificationError, -}; +use openvm_poseidon2_air::Poseidon2Config; +use openvm_stark_backend::p3_field::{AbstractField, PrimeField32}; use openvm_stark_sdk::{ - config::{ - baby_bear_blake3::{BabyBearBlake3Config, BabyBearBlake3Engine}, - fri_params::standard_fri_params_with_100_bits_conjectured_security, - }, - engine::StarkFriEngine, + dummy_airs::interaction::dummy_interaction_air::{DummyInteractionChip, DummyInteractionData}, p3_baby_bear::BabyBear, utils::create_seeded_rng, }; -use rand::Rng; +use rand::RngCore; -use super::{Poseidon2Chip, Poseidon2VmIoCols, CHUNK, WIDTH}; -use crate::arch::{ - instructions::{ - Poseidon2Opcode::{self, *}, - UsizeOpcode, +use crate::{ + arch::{ + hasher::{Hasher, HasherChip}, + testing::VmChipTestBuilder, + POSEIDON2_DIRECT_BUS, + }, + system::poseidon2::{ + Poseidon2PeripheryChip, PERIPHERY_POSEIDON2_CHUNK_SIZE, PERIPHERY_POSEIDON2_WIDTH, }, - testing::{memory::gen_pointer, VmChipTestBuilder, VmChipTester}, - POSEIDON2_DIRECT_BUS, }; -/// Create random instructions for the poseidon2 chip. -fn random_instructions(num_ops: usize) -> Vec> { +/// Test that the direct bus interactions work. +#[test] +fn poseidon2_periphery_direct_test() { let mut rng = create_seeded_rng(); - (0..num_ops) - .map(|_| { - let [a, b, c] = - std::array::from_fn(|_| BabyBear::from_canonical_usize(gen_pointer(&mut rng, 1))); - Instruction { - opcode: if rng.gen_bool(0.5) { - VmOpcode::from_usize(PERM_POS2 as usize) - } else { - VmOpcode::from_usize(COMP_POS2 as usize) - }, - a, - b, - c, - d: BabyBear::ONE, - e: BabyBear::TWO, - f: BabyBear::ZERO, - g: BabyBear::ZERO, - } - }) - .collect() -} - -fn tester_with_random_poseidon2_ops(num_ops: usize) -> VmChipTester { - let elem_range = || 1..=100; - - let mut tester = VmChipTestBuilder::default(); - let mut chip = Poseidon2Chip::from_poseidon2_config( - Poseidon2Config::<16, _>::new_p3_baby_bear_16(), - 7, - tester.execution_bus(), - tester.program_bus(), - tester.memory_controller(), + const NUM_OPS: usize = 50; + let hashes: [( + [BabyBear; PERIPHERY_POSEIDON2_CHUNK_SIZE], + [BabyBear; PERIPHERY_POSEIDON2_CHUNK_SIZE], + ); NUM_OPS] = std::array::from_fn(|_| { + ( + std::array::from_fn(|_| BabyBear::from_canonical_u32(rng.next_u32() % (1 << 30))), + std::array::from_fn(|_| BabyBear::from_canonical_u32(rng.next_u32() % (1 << 30))), + ) + }); + + let mut chip = Poseidon2PeripheryChip::::new( + Poseidon2Config::default(), POSEIDON2_DIRECT_BUS, - 0, + 3, ); - let mut rng = create_seeded_rng(); - - for instruction in random_instructions(num_ops) { - let opcode = Poseidon2Opcode::from_usize(instruction.opcode.as_usize()); - let [a, b, c, d, e] = [ - instruction.a, - instruction.b, - instruction.c, - instruction.d, - instruction.e, - ] - .map(|elem| elem.as_canonical_u64() as usize); - - let dst = gen_pointer(&mut rng, CHUNK); - let lhs = gen_pointer(&mut rng, CHUNK); - let rhs = gen_pointer(&mut rng, CHUNK); - - let data: [_; WIDTH] = - std::array::from_fn(|_| BabyBear::from_canonical_usize(rng.gen_range(elem_range()))); - - let hash = chip.air.inner.generate_trace_row(data).io.output; - - tester.write_cell(d, a, BabyBear::from_canonical_usize(dst)); - tester.write_cell(d, b, BabyBear::from_canonical_usize(lhs)); - if opcode == COMP_POS2 { - tester.write_cell(d, c, BabyBear::from_canonical_usize(rhs)); - } - - match opcode { - COMP_POS2 => { - let data_left: [_; CHUNK] = std::array::from_fn(|i| data[i]); - let data_right: [_; CHUNK] = std::array::from_fn(|i| data[CHUNK + i]); - tester.write(e, lhs, data_left); - tester.write(e, rhs, data_right); - } - PERM_POS2 => { - tester.write(e, lhs, data); - } - } - - tester.execute(&mut chip, instruction); - - match opcode { - COMP_POS2 => { - let expected: [_; CHUNK] = std::array::from_fn(|i| hash[i]); - let actual = tester.read::(e, dst); - assert_eq!(expected, actual); - } - PERM_POS2 => { - let actual = tester.read::(e, dst); - assert_eq!(hash, actual); - } - } - } - tester.build().load(chip).finalize() -} + let outs: [[BabyBear; PERIPHERY_POSEIDON2_CHUNK_SIZE]; NUM_OPS] = + std::array::from_fn(|i| chip.compress_and_record(&hashes[i].0, &hashes[i].1)); -fn get_engine() -> BabyBearBlake3Engine { - BabyBearBlake3Engine::new(standard_fri_params_with_100_bits_conjectured_security(3)) -} - -/// Checking that 50 random instructions pass. -#[test] -fn poseidon2_chip_random_50_test_new() { - let tester = tester_with_random_poseidon2_ops(50); - tester.test(get_engine).expect("Verification failed"); + let mut dummy_interaction_chip = DummyInteractionChip::new_without_partition( + PERIPHERY_POSEIDON2_WIDTH + PERIPHERY_POSEIDON2_WIDTH / 2, + true, + POSEIDON2_DIRECT_BUS, + ); + let count = vec![1; NUM_OPS]; + let fields = hashes + .iter() + .zip(outs) + .map(|((hash1, hash2), out)| { + hash1 + .iter() + .chain(hash2.iter()) + .chain(out.iter()) + .map(|y| y.as_canonical_u32()) + .collect::>() + }) + .collect::>(); + dummy_interaction_chip.load_data(DummyInteractionData { count, fields }); + + // engine generation + let tester = VmChipTestBuilder::default(); + let tester = tester + .build() + .load(dummy_interaction_chip) + .load(chip) + .finalize(); + tester.simple_test().expect("Verification failed"); } -/// Negative test, pranking internal poseidon2 trace values. #[test] -#[ignore = "slow"] -fn poseidon2_negative_test() { +fn poseidon2_periphery_duplicate_hashes_test() { let mut rng = create_seeded_rng(); - let num_ops = 1; - let mut tester = tester_with_random_poseidon2_ops(num_ops); - - tester.simple_test().expect("Verification failed"); - - disable_debug_builder(); - // test is slow, avoid too many repetitions - for _ in 0..5 { - // TODO: better way to modify existing traces in tester - let trace = tester.air_proof_inputs[2].raw.common_main.as_mut().unwrap(); - let original_trace = trace.clone(); - - // avoid pranking IO cols or dst,lhs,rhs - let start_prank_col = Poseidon2VmIoCols::::get_width() + 3; - let end_prank_col = start_prank_col + Poseidon2Air::<16, BabyBear>::default().get_width(); - let width = rng.gen_range(start_prank_col..end_prank_col); - let height = rng.gen_range(0..num_ops); - let rand = BabyBear::from_canonical_u32(rng.gen_range(1..=1 << 27)); - println!("Pranking row {height} column {width}"); - trace.row_mut(height)[width] += rand; + const NUM_OPS: usize = 50; + let hashes: [( + [BabyBear; PERIPHERY_POSEIDON2_CHUNK_SIZE], + [BabyBear; PERIPHERY_POSEIDON2_CHUNK_SIZE], + ); NUM_OPS] = std::array::from_fn(|_| { + ( + std::array::from_fn(|_| BabyBear::from_canonical_u32(rng.next_u32() % (1 << 30))), + std::array::from_fn(|_| BabyBear::from_canonical_u32(rng.next_u32() % (1 << 30))), + ) + }); + let counts: [u32; NUM_OPS] = std::array::from_fn(|_| rng.next_u32() % 20); + + let mut chip = Poseidon2PeripheryChip::::new( + Poseidon2Config::default(), + POSEIDON2_DIRECT_BUS, + 3, + ); - let test_result = tester.test(get_engine); + let outs: [[BabyBear; PERIPHERY_POSEIDON2_CHUNK_SIZE]; NUM_OPS] = std::array::from_fn(|i| { + for _ in 0..counts[i] { + chip.compress_and_record(&hashes[i].0, &hashes[i].1); + } + chip.compress(&hashes[i].0, &hashes[i].1) + }); - assert_eq!( - test_result.err(), - Some(VerificationError::OodEvaluationMismatch), - "Expected constraint to fail" - ); - tester.air_proof_inputs[2].raw.common_main = Some(original_trace); - } + let mut dummy_interaction_chip = DummyInteractionChip::new_without_partition( + PERIPHERY_POSEIDON2_WIDTH + PERIPHERY_POSEIDON2_WIDTH / 2, + true, + POSEIDON2_DIRECT_BUS, + ); + let count = counts.to_vec(); + let fields = hashes + .iter() + .zip(outs) + .map(|((hash1, hash2), out)| { + hash1 + .iter() + .chain(hash2.iter()) + .chain(out.iter()) + .map(|y| y.as_canonical_u32()) + .collect::>() + }) + .collect::>(); + dummy_interaction_chip.load_data(DummyInteractionData { count, fields }); + + // engine generation + let tester = VmChipTestBuilder::default(); + tester + .build() + .load(chip) + .load(dummy_interaction_chip) + .finalize(); } - -// /// Test that the direct bus interactions work. -// #[test] -// fn poseidon2_direct_test() { -// let mut rng = create_seeded_rng(); -// const NUM_OPS: usize = 50; -// const CHUNKS: usize = 8; -// let correct_height = NUM_OPS.next_power_of_two(); -// let hashes: [([BabyBear; CHUNKS], [BabyBear; CHUNKS]); NUM_OPS] = std::array::from_fn(|_| { -// ( -// std::array::from_fn(|_| BabyBear::from_canonical_u32(rng.next_u32() % (1 << 30))), -// std::array::from_fn(|_| BabyBear::from_canonical_u32(rng.next_u32() % (1 << 30))), -// ) -// }); -// -// let mut chip = Poseidon2Chip::<16, BabyBear>::from_poseidon2_config( -// Poseidon2Config::default(), -// ExecutionBus(EXECUTION_BUS), -// MemoryTester::new(MemoryBus(MEMORY_BUS)).chip(), -// ); -// -// let outs: [[BabyBear; CHUNKS]; NUM_OPS] = -// std::array::from_fn(|i| chip.hash(hashes[i].0, hashes[i].1)); -// -// let width = Poseidon2VmAir::<16, BabyBear>::direct_interaction_width(); -// -// let dummy_direct_cpu = DummyInteractionAir::new(width, true, POSEIDON2_DIRECT_BUS); -// -// let mut dummy_direct_cpu_trace = RowMajorMatrix::new( -// outs.iter() -// .enumerate() -// .flat_map(|(i, out)| { -// vec![BabyBear::ONE] -// .into_iter() -// .chain(hashes[i].0) -// .chain(hashes[i].1) -// .chain(out.iter().cloned()) -// }) -// .collect::>(), -// width + 1, -// ); -// dummy_direct_cpu_trace.values.extend(vec![ -// BabyBear::ZERO; -// (width + 1) * (correct_height - NUM_OPS) -// ]); -// -// let chip_trace = chip.generate_trace(); -// -// // engine generation -// let max_trace_height = chip_trace.height(); -// let engine = get_engine(max_trace_height); -// -// // positive test -// engine -// .run_simple_test( -// vec![&dummy_direct_cpu, &chip.air], -// vec![dummy_direct_cpu_trace, chip_trace], -// vec![vec![]; 2], -// ) -// .expect("Verification failed"); -// } diff --git a/crates/vm/src/system/poseidon2/trace.rs b/crates/vm/src/system/poseidon2/trace.rs index 0e6ad93ccd..2e171eaff6 100644 --- a/crates/vm/src/system/poseidon2/trace.rs +++ b/crates/vm/src/system/poseidon2/trace.rs @@ -1,73 +1,76 @@ -use std::sync::Arc; +use std::{borrow::BorrowMut, sync::Arc}; use openvm_circuit_primitives::utils::next_power_of_two_or_zero; use openvm_stark_backend::{ config::{StarkGenericConfig, Val}, p3_air::BaseAir, - p3_field::PrimeField32, + p3_field::{AbstractField, PrimeField32}, p3_matrix::dense::RowMajorMatrix, p3_maybe_rayon::prelude::*, prover::types::AirProofInput, rap::{get_air_name, AnyRap}, Chip, ChipUsageGetter, }; -#[cfg(feature = "parallel")] -use rayon::iter::ParallelExtend; -use super::{columns::*, Poseidon2Chip}; +use super::{columns::*, Poseidon2PeripheryBaseChip, PERIPHERY_POSEIDON2_WIDTH}; -impl Chip for Poseidon2Chip> +impl Chip + for Poseidon2PeripheryBaseChip, SBOX_REGISTERS> where Val: PrimeField32, { fn air(&self) -> Arc> { - Arc::new(self.air.clone()) + self.air.clone() } fn generate_air_proof_input(self) -> AirProofInput { - let Self { - air, - memory_controller, - records, - offset: _, - } = self; + let air = self.air(); + let height = next_power_of_two_or_zero(self.current_trace_height()); + let width = self.trace_width(); - let row_len = records.len(); - let correct_len = next_power_of_two_or_zero(row_len); - let diff = correct_len - row_len; - - let aux_cols_factory = memory_controller.borrow().aux_cols_factory(); - let mut flat_rows: Vec<_> = records + let mut inputs = Vec::with_capacity(height); + let mut multiplicities = Vec::with_capacity(height); + let (actual_inputs, actual_multiplicities): (Vec<_>, Vec<_>) = self + .records .into_par_iter() - .flat_map(|record| Self::record_to_cols(&aux_cols_factory, record).flatten()) - .collect(); - #[cfg(feature = "parallel")] - flat_rows.par_extend( - vec![Poseidon2VmCols::>::blank_row(&air).flatten(); diff] - .into_par_iter() - .flatten(), - ); - #[cfg(not(feature = "parallel"))] - flat_rows.extend( - vec![Poseidon2VmCols::>::blank_row(&air).flatten(); diff] - .into_iter() - .flatten(), - ); + .map(|(input, mult)| (input, mult.load(std::sync::atomic::Ordering::Relaxed))) + .unzip(); + inputs.extend(actual_inputs); + multiplicities.extend(actual_multiplicities); + inputs.resize(height, [Val::::ZERO; PERIPHERY_POSEIDON2_WIDTH]); + multiplicities.resize(height, 0); + + // TODO: this would be more optimal if plonky3 made the generate_trace_row function public + let inner_trace = self.subchip.generate_trace(inputs); + let inner_width = self.air.subair.width(); + + let mut values = Val::::zero_vec(height * width); + values + .par_chunks_mut(width) + .zip(inner_trace.values.par_chunks(inner_width)) + .zip(multiplicities) + .for_each(|((row, inner_row), mult)| { + // WARNING: Poseidon2SubCols must be the first field in Poseidon2PeripheryCols + row[..inner_width].copy_from_slice(inner_row); + let cols: &mut Poseidon2PeripheryCols, SBOX_REGISTERS> = row.borrow_mut(); + cols.mult = Val::::from_canonical_u32(mult); + }); - AirProofInput::simple_no_pis( - Arc::new(air.clone()), - RowMajorMatrix::new(flat_rows, air.width()), - ) + AirProofInput::simple_no_pis(air, RowMajorMatrix::new(values, width)) } } -impl ChipUsageGetter for Poseidon2Chip { +impl ChipUsageGetter + for Poseidon2PeripheryBaseChip +{ fn air_name(&self) -> String { get_air_name(&self.air) } + fn current_trace_height(&self) -> usize { self.records.len() } + fn trace_width(&self) -> usize { self.air.width() } diff --git a/extensions/native/circuit/Cargo.toml b/extensions/native/circuit/Cargo.toml index ede74d78b8..5a7a1b03f1 100644 --- a/extensions/native/circuit/Cargo.toml +++ b/extensions/native/circuit/Cargo.toml @@ -28,6 +28,7 @@ derive_more = { workspace = true, features = ["from"] } rand.workspace = true eyre.workspace = true serde.workspace = true +rayon.workspace = true [dev-dependencies] openvm-stark-sdk = { workspace = true } diff --git a/extensions/native/circuit/src/extension.rs b/extensions/native/circuit/src/extension.rs index 4ce096bbcc..a07d473c89 100644 --- a/extensions/native/circuit/src/extension.rs +++ b/extensions/native/circuit/src/extension.rs @@ -5,11 +5,10 @@ use loadstore_native_adapter::NativeLoadStoreAdapterChip; use native_vectorized_adapter::NativeVectorizedAdapterChip; use openvm_circuit::{ arch::{ - vm_poseidon2_config, MemoryConfig, SystemConfig, SystemExecutor, SystemPeriphery, - SystemPort, VmChipComplex, VmConfig, VmExtension, VmInventory, VmInventoryBuilder, - VmInventoryError, + MemoryConfig, SystemConfig, SystemExecutor, SystemPeriphery, SystemPort, VmChipComplex, + VmConfig, VmExtension, VmInventory, VmInventoryBuilder, VmInventoryError, }, - system::{native_adapter::NativeAdapterChip, phantom::PhantomChip, poseidon2::Poseidon2Chip}, + system::{native_adapter::NativeAdapterChip, phantom::PhantomChip}, }; use openvm_circuit_derive::{AnyEnum, InstructionExecutor, VmConfig}; use openvm_circuit_primitives_derive::{Chip, ChipUsageGetter}; @@ -20,7 +19,7 @@ use openvm_native_compiler::{ FieldArithmeticOpcode, FieldExtensionOpcode, FriOpcode, NativeBranchEqualOpcode, NativeJalOpcode, NativeLoadStoreOpcode, NativePhantom, }; -use openvm_poseidon2_air::poseidon2::air::SBOX_DEGREE; +use openvm_poseidon2_air::Poseidon2Config; use openvm_rv32im_circuit::BranchEqualCoreChip; use openvm_stark_backend::p3_field::PrimeField32; use serde::{Deserialize, Serialize}; @@ -77,7 +76,7 @@ pub enum NativeExecutor { Jal(NativeJalChip), FieldArithmetic(FieldArithmeticChip), FieldExtension(FieldExtensionChip), - Poseidon2(Poseidon2Chip), + Poseidon2(NativePoseidon2Chip), FriReducedOpening(FriReducedOpeningChip), } @@ -177,17 +176,13 @@ impl VmExtension for Native { FriOpcode::iter().map(VmOpcode::with_default_offset), )?; - let poseidon2_chip = Poseidon2Chip::from_poseidon2_config( - vm_poseidon2_config(), - builder - .system_config() - .max_constraint_degree - .min(SBOX_DEGREE), + let poseidon2_chip = NativePoseidon2Chip::new( execution_bus, program_bus, memory_controller.clone(), - builder.new_bus_idx(), + Poseidon2Config::default(), Poseidon2Opcode::default_offset(), + builder.system_config().max_constraint_degree, ); inventory.add_executor( poseidon2_chip, diff --git a/extensions/native/circuit/src/lib.rs b/extensions/native/circuit/src/lib.rs index 688b3ef56f..46c6bc890f 100644 --- a/extensions/native/circuit/src/lib.rs +++ b/extensions/native/circuit/src/lib.rs @@ -7,6 +7,7 @@ mod field_extension; mod fri; mod jal; mod loadstore; +mod poseidon2; pub use branch_eq::*; pub use castf::*; @@ -15,6 +16,7 @@ pub use field_extension::*; pub use fri::*; pub use jal::*; pub use loadstore::*; +pub use poseidon2::*; mod extension; pub use extension::*; diff --git a/extensions/native/circuit/src/poseidon2/air.rs b/extensions/native/circuit/src/poseidon2/air.rs new file mode 100644 index 0000000000..473fd70bdd --- /dev/null +++ b/extensions/native/circuit/src/poseidon2/air.rs @@ -0,0 +1,179 @@ +use std::{array::from_fn, borrow::Borrow, sync::Arc}; + +use derive_new::new; +use itertools::izip; +use openvm_circuit::{ + arch::ExecutionBridge, + system::memory::{offline_checker::MemoryBridge, MemoryAddress}, +}; +use openvm_poseidon2_air::{Poseidon2SubAir, BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS}; +use openvm_stark_backend::{ + air_builders::sub::SubAirBuilder, + interaction::InteractionBuilder, + p3_air::{Air, AirBuilder, BaseAir}, + p3_field::{AbstractField, Field}, + p3_matrix::Matrix, + rap::{BaseAirWithPublicValues, PartitionedBaseAir}, +}; + +use super::{NativePoseidon2Cols, NATIVE_POSEIDON2_CHUNK_SIZE}; + +#[derive(Debug, new)] +pub struct NativePoseidon2Air { + pub(super) execution_bridge: ExecutionBridge, + pub(super) memory_bridge: MemoryBridge, + pub(super) subair: Arc>, + pub(super) offset: usize, +} + +impl BaseAir for NativePoseidon2Air { + fn width(&self) -> usize { + NativePoseidon2Cols::::width() + } +} +impl BaseAirWithPublicValues + for NativePoseidon2Air +{ +} +impl PartitionedBaseAir + for NativePoseidon2Air +{ +} + +impl Air + for NativePoseidon2Air +{ + fn eval(&self, builder: &mut AB) { + let mut sub_builder = + SubAirBuilder::, AB::F>::new( + builder, + 0..self.subair.width(), + ); + self.subair.eval(&mut sub_builder); + self.eval_memory_and_execution(builder); + } +} + +impl NativePoseidon2Air { + fn eval_memory_and_execution(&self, builder: &mut AB) { + let main = builder.main(); + let local = main.row_slice(0); + let cols: &NativePoseidon2Cols = (*local).borrow(); + + let timestamp = cols.memory.from_state.timestamp; + let mut timestamp_delta: usize = 0; + let mut timestamp_pp = || { + timestamp_delta += 1; + timestamp + AB::F::from_canonical_usize(timestamp_delta - 1) + }; + + // Because there are only two opcodes this adapter needs to handle, we make it so that + // is_valid == 1 if PERMUTE, is_valid == 2 if COMPRESS, and is_valid != 0 if valid. + let permute = -cols.memory.opcode_flag.into() * (cols.memory.opcode_flag - AB::F::TWO); + let compress = cols.memory.opcode_flag.into() + * (cols.memory.opcode_flag - AB::F::ONE) + * (AB::F::TWO.inverse()); + let is_valid = permute.clone() + compress.clone(); + builder.assert_zero( + cols.memory.opcode_flag + * (cols.memory.opcode_flag - AB::F::ONE) + * (cols.memory.opcode_flag - AB::F::TWO), + ); + builder + .when_ne(permute.clone(), AB::F::ONE) + .assert_eq(cols.memory.c, cols.memory.rs_ptr[1]); + + for (ptr, val, aux, count) in izip!( + [ + cols.memory.rd_ptr, + cols.memory.rs_ptr[0], + cols.memory.rs_ptr[1] + ], + [ + cols.memory.rd_val, + cols.memory.rs_val[0], + cols.memory.rs_val[1] + ], + &[ + cols.memory.rd_read_aux, + cols.memory.rs_read_aux[0], + cols.memory.rs_read_aux[1], + ], + [is_valid.clone(), is_valid.clone(), compress], + ) { + self.memory_bridge + .read( + MemoryAddress::new(cols.memory.ptr_as, ptr), + [val], + timestamp_pp(), + aux, + ) + .eval(builder, count); + } + + let read_chunk_1: [_; NATIVE_POSEIDON2_CHUNK_SIZE] = from_fn(|i| cols.inner.inputs[i]); + let read_chunk_2: [_; NATIVE_POSEIDON2_CHUNK_SIZE] = + from_fn(|i| cols.inner.inputs[i + NATIVE_POSEIDON2_CHUNK_SIZE]); + + for (ptr, data, aux, count) in izip!( + [cols.memory.rs_val[0], cols.memory.rs_val[1]], + [read_chunk_1, read_chunk_2], + &[cols.memory.chunk_read_aux[0], cols.memory.chunk_read_aux[1]], + [is_valid.clone(), is_valid.clone()], + ) { + self.memory_bridge + .read( + MemoryAddress::new(cols.memory.chunk_as, ptr), + data, + timestamp_pp(), + aux, + ) + .eval(builder, count); + } + + let write_chunk_1: [_; NATIVE_POSEIDON2_CHUNK_SIZE] = from_fn(|i| { + cols.inner.ending_full_rounds[BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS - 1].post[i] + }); + let write_chunk_2: [_; NATIVE_POSEIDON2_CHUNK_SIZE] = from_fn(|i| { + cols.inner.ending_full_rounds[BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS - 1].post + [i + NATIVE_POSEIDON2_CHUNK_SIZE] + }); + + for (ptr, data, aux, count) in izip!( + [ + cols.memory.rd_val.into(), + cols.memory.rd_val + AB::F::from_canonical_usize(NATIVE_POSEIDON2_CHUNK_SIZE) + ], + [write_chunk_1, write_chunk_2], + &[ + cols.memory.chunk_write_aux[0], + cols.memory.chunk_write_aux[1] + ], + [is_valid.clone(), permute], + ) { + self.memory_bridge + .write( + MemoryAddress::new(cols.memory.chunk_as, ptr), + data, + timestamp_pp(), + aux, + ) + .eval(builder, count); + } + + self.execution_bridge + .execute_and_increment_pc( + cols.memory.opcode_flag - AB::F::ONE + AB::F::from_canonical_usize(self.offset), + [ + cols.memory.rd_ptr, + cols.memory.rs_ptr[0], + cols.memory.c, + cols.memory.ptr_as, + cols.memory.chunk_as, + ], + cols.memory.from_state, + AB::F::from_canonical_usize(timestamp_delta), + ) + .eval(builder, is_valid); + } +} diff --git a/extensions/native/circuit/src/poseidon2/chip.rs b/extensions/native/circuit/src/poseidon2/chip.rs new file mode 100644 index 0000000000..4efadb8bb0 --- /dev/null +++ b/extensions/native/circuit/src/poseidon2/chip.rs @@ -0,0 +1,207 @@ +use std::{array::from_fn, sync::Arc}; + +use openvm_circuit::{ + arch::{ExecutionBridge, ExecutionBus, ExecutionError, ExecutionState, InstructionExecutor}, + system::{ + memory::{ + offline_checker::{MemoryReadAuxCols, MemoryWriteAuxCols}, + MemoryAuxColsFactory, MemoryControllerRef, MemoryReadRecord, MemoryWriteRecord, + }, + program::ProgramBus, + }, +}; +use openvm_instructions::{ + instruction::Instruction, program::DEFAULT_PC_STEP, Poseidon2Opcode, UsizeOpcode, +}; +use openvm_poseidon2_air::{Poseidon2Config, Poseidon2SubChip}; +use openvm_stark_backend::p3_field::{Field, PrimeField32}; + +use super::{ + NativePoseidon2Air, NativePoseidon2MemoryCols, NATIVE_POSEIDON2_CHUNK_SIZE, + NATIVE_POSEIDON2_WIDTH, +}; + +#[derive(Debug)] +pub struct NativePoseidon2BaseChip { + pub air: Arc>, + pub subchip: Poseidon2SubChip, + pub memory_controller: MemoryControllerRef, + pub records: Vec>>, +} + +#[derive(Clone, Debug)] +pub struct NativePoseidon2ChipRecord { + pub from_state: ExecutionState, + pub opcode: Poseidon2Opcode, + pub input: [F; NATIVE_POSEIDON2_WIDTH], + pub c: F, + pub rd: MemoryReadRecord, + pub rs1: MemoryReadRecord, + pub rs2: Option>, + + pub read1: MemoryReadRecord, + pub read2: MemoryReadRecord, + + pub write1: MemoryWriteRecord, + pub write2: Option>, +} + +impl NativePoseidon2BaseChip { + pub fn new( + execution_bus: ExecutionBus, + program_bus: ProgramBus, + memory_controller: MemoryControllerRef, + poseidon2_config: Poseidon2Config, + offset: usize, + ) -> Self { + let memory_bridge = memory_controller.borrow().memory_bridge(); + let subchip = Poseidon2SubChip::new(poseidon2_config); + Self { + air: Arc::new(NativePoseidon2Air::new( + ExecutionBridge::new(execution_bus, program_bus), + memory_bridge, + subchip.air.clone(), + offset, + )), + subchip, + memory_controller, + records: vec![], + } + } +} + +impl InstructionExecutor + for NativePoseidon2BaseChip +{ + fn execute( + &mut self, + instruction: Instruction, + from_state: ExecutionState, + ) -> Result, ExecutionError> { + let Instruction { + opcode, + a, + b, + c, + d, + e, + .. + } = instruction; + let local_opcode = Poseidon2Opcode::from_usize(opcode.local_opcode_idx(self.air.offset)); + let mut memory = self.memory_controller.borrow_mut(); + + let rd = memory.read_cell(d, a); + let rs1 = memory.read_cell(d, b); + let rs2 = match local_opcode { + Poseidon2Opcode::PERM_POS2 => { + memory.increment_timestamp(); + None + } + Poseidon2Opcode::COMP_POS2 => Some(memory.read_cell(d, c)), + }; + + let read1 = memory.read::(e, rs1.data[0]); + let read2 = memory.read::( + e, + match rs2 { + Some(rs2) => rs2.data[0], + None => rs1.data[0] + F::from_canonical_usize(NATIVE_POSEIDON2_CHUNK_SIZE), + }, + ); + + let mut input_state: [F; NATIVE_POSEIDON2_WIDTH] = [F::ZERO; NATIVE_POSEIDON2_WIDTH]; + input_state[..NATIVE_POSEIDON2_CHUNK_SIZE].copy_from_slice(&read1.data); + input_state[NATIVE_POSEIDON2_CHUNK_SIZE..].copy_from_slice(&read2.data); + + let output_state = self.subchip.permute(input_state); + + let output1: [F; NATIVE_POSEIDON2_CHUNK_SIZE] = from_fn(|i| output_state[i]); + let output2: [F; NATIVE_POSEIDON2_CHUNK_SIZE] = + from_fn(|i| output_state[NATIVE_POSEIDON2_CHUNK_SIZE + i]); + + let write1 = memory.write::(e, rd.data[0], output1); + let write2 = match local_opcode { + Poseidon2Opcode::PERM_POS2 => Some(memory.write::( + e, + rd.data[0] + F::from_canonical_usize(NATIVE_POSEIDON2_CHUNK_SIZE), + output2, + )), + Poseidon2Opcode::COMP_POS2 => { + memory.increment_timestamp(); + None + } + }; + + self.records.push(Some(NativePoseidon2ChipRecord { + from_state, + opcode: local_opcode, + input: input_state, + c, + rd, + rs1, + rs2, + read1, + read2, + write1, + write2, + })); + + Ok(ExecutionState { + pc: from_state.pc + DEFAULT_PC_STEP, + timestamp: memory.timestamp(), + }) + } + + fn get_opcode_name(&self, opcode: usize) -> String { + format!( + "{:?}", + Poseidon2Opcode::from_usize(opcode - self.air.offset) + ) + } +} + +impl NativePoseidon2ChipRecord { + pub fn to_memory_cols( + &self, + aux_cols_factory: &MemoryAuxColsFactory, + ) -> NativePoseidon2MemoryCols { + let (rs2_ptr, rs2_val, rs2_read_aux) = match self.rs2 { + Some(rs2) => ( + rs2.pointer, + rs2.data[0], + aux_cols_factory.make_read_aux_cols(rs2), + ), + None => ( + F::ZERO, + self.rs1.data[0] + F::from_canonical_usize(NATIVE_POSEIDON2_CHUNK_SIZE), + MemoryReadAuxCols::disabled(), + ), + }; + NativePoseidon2MemoryCols { + from_state: self.from_state.map(F::from_canonical_u32), + opcode_flag: match self.opcode { + Poseidon2Opcode::PERM_POS2 => F::ONE, + Poseidon2Opcode::COMP_POS2 => F::TWO, + }, + ptr_as: self.rd.address_space, + chunk_as: self.read1.address_space, + c: self.c, + rs_ptr: [self.rs1.pointer, rs2_ptr], + rd_ptr: self.rd.pointer, + rs_val: [self.rs1.data[0], rs2_val], + rd_val: self.rd.data[0], + rs_read_aux: [aux_cols_factory.make_read_aux_cols(self.rs1), rs2_read_aux], + rd_read_aux: aux_cols_factory.make_read_aux_cols(self.rd), + chunk_read_aux: [ + aux_cols_factory.make_read_aux_cols(self.read1), + aux_cols_factory.make_read_aux_cols(self.read2), + ], + chunk_write_aux: [ + aux_cols_factory.make_write_aux_cols(self.write1), + self.write2.map_or(MemoryWriteAuxCols::disabled(), |w| { + aux_cols_factory.make_write_aux_cols(w) + }), + ], + } + } +} diff --git a/extensions/native/circuit/src/poseidon2/columns.rs b/extensions/native/circuit/src/poseidon2/columns.rs new file mode 100644 index 0000000000..084860f7c3 --- /dev/null +++ b/extensions/native/circuit/src/poseidon2/columns.rs @@ -0,0 +1,60 @@ +use openvm_circuit::{ + arch::ExecutionState, + system::memory::offline_checker::{MemoryReadAuxCols, MemoryWriteAuxCols}, +}; +use openvm_circuit_primitives::AlignedBorrow; +use openvm_poseidon2_air::Poseidon2SubCols; +use openvm_stark_backend::p3_field::AbstractField; + +use super::NATIVE_POSEIDON2_CHUNK_SIZE; + +#[repr(C)] +#[derive(AlignedBorrow)] +pub struct NativePoseidon2Cols { + pub inner: Poseidon2SubCols, + pub memory: NativePoseidon2MemoryCols, +} + +#[repr(C)] +#[derive(Clone, Debug, AlignedBorrow)] +pub struct NativePoseidon2MemoryCols { + pub from_state: ExecutionState, + // 1 if PERMUTE, 2 if COMPRESS, 0 otherwise + pub opcode_flag: T, + + pub ptr_as: T, + pub chunk_as: T, + + // rs_ptr[1] if COMPRESS, original value of instruction field c if PERMUTE + pub c: T, + + pub rs_ptr: [T; 2], + pub rd_ptr: T, + pub rs_val: [T; 2], + pub rd_val: T, + pub rs_read_aux: [MemoryReadAuxCols; 2], + pub rd_read_aux: MemoryReadAuxCols, + + pub chunk_read_aux: [MemoryReadAuxCols; 2], + pub chunk_write_aux: [MemoryWriteAuxCols; 2], +} + +impl NativePoseidon2MemoryCols { + pub fn blank() -> Self { + Self { + from_state: ExecutionState::default(), + opcode_flag: F::ZERO, + ptr_as: F::ZERO, + chunk_as: F::ZERO, + c: F::ZERO, + rs_ptr: [F::ZERO; 2], + rd_ptr: F::ZERO, + rs_val: [F::ZERO; 2], + rd_val: F::ZERO, + rs_read_aux: [MemoryReadAuxCols::disabled(); 2], + rd_read_aux: MemoryReadAuxCols::disabled(), + chunk_read_aux: [MemoryReadAuxCols::disabled(); 2], + chunk_write_aux: [MemoryWriteAuxCols::disabled(); 2], + } + } +} diff --git a/extensions/native/circuit/src/poseidon2/mod.rs b/extensions/native/circuit/src/poseidon2/mod.rs new file mode 100644 index 0000000000..db36573b03 --- /dev/null +++ b/extensions/native/circuit/src/poseidon2/mod.rs @@ -0,0 +1,125 @@ +use std::sync::Arc; + +use openvm_circuit::{ + arch::{ExecutionBus, ExecutionError, ExecutionState, InstructionExecutor}, + system::{memory::MemoryControllerRef, program::ProgramBus}, +}; +use openvm_instructions::instruction::Instruction; +use openvm_poseidon2_air::Poseidon2Config; +use openvm_stark_backend::{ + config::{StarkGenericConfig, Val}, + p3_field::{Field, PrimeField32}, + prover::types::AirProofInput, + rap::AnyRap, + Chip, ChipUsageGetter, +}; + +mod air; +pub use air::*; +mod chip; +pub use chip::*; +mod columns; +pub use columns::*; +mod trace; + +#[cfg(test)] +mod tests; + +pub const NATIVE_POSEIDON2_WIDTH: usize = 16; +pub const NATIVE_POSEIDON2_CHUNK_SIZE: usize = 8; + +pub enum NativePoseidon2Chip { + Register0(NativePoseidon2BaseChip), + Register1(NativePoseidon2BaseChip), +} + +impl NativePoseidon2Chip { + pub fn new( + execution_bus: ExecutionBus, + program_bus: ProgramBus, + memory_controller: MemoryControllerRef, + poseidon2_config: Poseidon2Config, + offset: usize, + max_constraint_degree: usize, + ) -> Self { + if max_constraint_degree >= 7 { + Self::Register0(NativePoseidon2BaseChip::new( + execution_bus, + program_bus, + memory_controller, + poseidon2_config, + offset, + )) + } else { + Self::Register1(NativePoseidon2BaseChip::new( + execution_bus, + program_bus, + memory_controller, + poseidon2_config, + offset, + )) + } + } +} + +impl InstructionExecutor for NativePoseidon2Chip { + fn execute( + &mut self, + instruction: Instruction, + from_state: ExecutionState, + ) -> Result, ExecutionError> { + match self { + NativePoseidon2Chip::Register0(chip) => chip.execute(instruction, from_state), + NativePoseidon2Chip::Register1(chip) => chip.execute(instruction, from_state), + } + } + + fn get_opcode_name(&self, opcode: usize) -> String { + match self { + NativePoseidon2Chip::Register0(chip) => chip.get_opcode_name(opcode), + NativePoseidon2Chip::Register1(chip) => chip.get_opcode_name(opcode), + } + } +} + +impl Chip for NativePoseidon2Chip> +where + Val: PrimeField32, +{ + fn air(&self) -> Arc> { + match self { + NativePoseidon2Chip::Register0(chip) => chip.air(), + NativePoseidon2Chip::Register1(chip) => chip.air(), + } + } + + fn generate_air_proof_input(self) -> AirProofInput { + match self { + NativePoseidon2Chip::Register0(chip) => chip.generate_air_proof_input(), + NativePoseidon2Chip::Register1(chip) => chip.generate_air_proof_input(), + } + } +} + +impl ChipUsageGetter for NativePoseidon2Chip { + fn air_name(&self) -> String { + match self { + NativePoseidon2Chip::Register0(chip) => chip.air_name(), + NativePoseidon2Chip::Register1(chip) => chip.air_name(), + } + } + + fn current_trace_height(&self) -> usize { + match self { + NativePoseidon2Chip::Register0(chip) => chip.current_trace_height(), + NativePoseidon2Chip::Register1(chip) => chip.current_trace_height(), + } + } + + fn trace_width(&self) -> usize { + match self { + NativePoseidon2Chip::Register0(chip) => chip.trace_width(), + NativePoseidon2Chip::Register1(chip) => chip.trace_width(), + } + } +} diff --git a/extensions/native/circuit/src/poseidon2/tests.rs b/extensions/native/circuit/src/poseidon2/tests.rs new file mode 100644 index 0000000000..e7dd2bdf42 --- /dev/null +++ b/extensions/native/circuit/src/poseidon2/tests.rs @@ -0,0 +1,135 @@ +use openvm_circuit::arch::testing::{memory::gen_pointer, VmChipTestBuilder, VmChipTester}; +use openvm_instructions::{instruction::Instruction, Poseidon2Opcode, UsizeOpcode, VmOpcode}; +use openvm_poseidon2_air::Poseidon2Config; +use openvm_stark_backend::p3_field::{AbstractField, PrimeField64}; +use openvm_stark_sdk::{ + config::{ + baby_bear_blake3::{BabyBearBlake3Config, BabyBearBlake3Engine}, + fri_params::standard_fri_params_with_100_bits_conjectured_security, + }, + engine::StarkFriEngine, + p3_baby_bear::BabyBear, + utils::create_seeded_rng, +}; +use rand::Rng; + +use super::{NativePoseidon2Chip, NATIVE_POSEIDON2_CHUNK_SIZE, NATIVE_POSEIDON2_WIDTH}; + +/// Create random instructions for the poseidon2 chip. +fn random_instructions(num_ops: usize) -> Vec> { + let mut rng = create_seeded_rng(); + (0..num_ops) + .map(|_| { + let [a, b, c] = + std::array::from_fn(|_| BabyBear::from_canonical_usize(gen_pointer(&mut rng, 1))); + Instruction { + opcode: if rng.gen_bool(0.5) { + VmOpcode::from_usize(Poseidon2Opcode::PERM_POS2 as usize) + } else { + VmOpcode::from_usize(Poseidon2Opcode::COMP_POS2 as usize) + }, + a, + b, + c, + d: BabyBear::ONE, + e: BabyBear::TWO, + f: BabyBear::ZERO, + g: BabyBear::ZERO, + } + }) + .collect() +} + +fn tester_with_random_poseidon2_ops( + num_ops: usize, + max_constraint_degree: usize, +) -> VmChipTester { + let elem_range = || 1..=100; + + let mut tester = VmChipTestBuilder::default(); + let mut chip = NativePoseidon2Chip::new( + tester.execution_bus(), + tester.program_bus(), + tester.memory_controller(), + Poseidon2Config::default(), + 0, + max_constraint_degree, + ); + + let mut rng = create_seeded_rng(); + + for instruction in random_instructions(num_ops) { + let opcode = Poseidon2Opcode::from_usize(instruction.opcode.as_usize()); + let [a, b, c, d, e] = [ + instruction.a, + instruction.b, + instruction.c, + instruction.d, + instruction.e, + ] + .map(|elem| elem.as_canonical_u64() as usize); + + let dst = gen_pointer(&mut rng, NATIVE_POSEIDON2_CHUNK_SIZE); + let lhs = gen_pointer(&mut rng, NATIVE_POSEIDON2_CHUNK_SIZE); + let rhs = gen_pointer(&mut rng, NATIVE_POSEIDON2_CHUNK_SIZE); + + let data: [_; NATIVE_POSEIDON2_WIDTH] = + std::array::from_fn(|_| BabyBear::from_canonical_usize(rng.gen_range(elem_range()))); + + let hash = match &chip { + NativePoseidon2Chip::Register0(chip) => chip.subchip.permute(data), + NativePoseidon2Chip::Register1(chip) => chip.subchip.permute(data), + }; + + tester.write_cell(d, a, BabyBear::from_canonical_usize(dst)); + tester.write_cell(d, b, BabyBear::from_canonical_usize(lhs)); + if opcode == Poseidon2Opcode::COMP_POS2 { + tester.write_cell(d, c, BabyBear::from_canonical_usize(rhs)); + } + + match opcode { + Poseidon2Opcode::COMP_POS2 => { + let data_left: [_; NATIVE_POSEIDON2_CHUNK_SIZE] = std::array::from_fn(|i| data[i]); + let data_right: [_; NATIVE_POSEIDON2_CHUNK_SIZE] = + std::array::from_fn(|i| data[NATIVE_POSEIDON2_CHUNK_SIZE + i]); + tester.write(e, lhs, data_left); + tester.write(e, rhs, data_right); + } + Poseidon2Opcode::PERM_POS2 => { + tester.write(e, lhs, data); + } + } + + tester.execute(&mut chip, instruction); + + match opcode { + Poseidon2Opcode::COMP_POS2 => { + let expected: [_; NATIVE_POSEIDON2_CHUNK_SIZE] = std::array::from_fn(|i| hash[i]); + let actual = tester.read::(e, dst); + assert_eq!(expected, actual); + } + Poseidon2Opcode::PERM_POS2 => { + let actual = tester.read::(e, dst); + assert_eq!(hash, actual); + } + } + } + tester.build().load(chip).finalize() +} + +fn get_engine() -> BabyBearBlake3Engine { + BabyBearBlake3Engine::new(standard_fri_params_with_100_bits_conjectured_security(3)) +} + +/// Checking that 50 random instructions pass. +#[test] +fn poseidon2_chip_random_max_constraint_degree_7() { + let tester = tester_with_random_poseidon2_ops(50, 7); + tester.test(get_engine).expect("Verification failed"); +} + +#[test] +fn poseidon2_chip_random_max_constraint_degree_3() { + let tester = tester_with_random_poseidon2_ops(50, 3); + tester.test(get_engine).expect("Verification failed"); +} diff --git a/extensions/native/circuit/src/poseidon2/trace.rs b/extensions/native/circuit/src/poseidon2/trace.rs new file mode 100644 index 0000000000..f24ab38361 --- /dev/null +++ b/extensions/native/circuit/src/poseidon2/trace.rs @@ -0,0 +1,80 @@ +use std::{borrow::BorrowMut, iter::repeat, sync::Arc}; + +use openvm_stark_backend::{ + config::{StarkGenericConfig, Val}, + p3_air::BaseAir, + p3_field::{AbstractField, PrimeField32}, + p3_matrix::dense::RowMajorMatrix, + p3_maybe_rayon::prelude::*, + prover::types::AirProofInput, + rap::{get_air_name, AnyRap}, + Chip, ChipUsageGetter, +}; + +use super::{ + NativePoseidon2BaseChip, NativePoseidon2Cols, NativePoseidon2MemoryCols, NATIVE_POSEIDON2_WIDTH, +}; + +impl Chip + for NativePoseidon2BaseChip, SBOX_REGISTERS> +where + Val: PrimeField32, +{ + fn air(&self) -> Arc> { + self.air.clone() + } + + fn generate_air_proof_input(self) -> AirProofInput { + let air = self.air(); + let height = self.current_trace_height().next_power_of_two(); + let width = self.trace_width(); + let mut records = self.records; + records.extend(repeat(None).take(height - records.len())); + + let inputs = records + .par_iter() + .map(|record| match record { + Some(record) => record.input, + None => [Val::::ZERO; NATIVE_POSEIDON2_WIDTH], + }) + .collect(); + let inner_trace = self.subchip.generate_trace(inputs); + let inner_width = self.air.subair.width(); + + let aux_cols_factory = self.memory_controller.borrow().aux_cols_factory(); + let memory_cols = records.par_iter().map(|record| match record { + Some(record) => record.to_memory_cols(&aux_cols_factory), + None => NativePoseidon2MemoryCols::blank(), + }); + + let mut values = Val::::zero_vec(height * width); + values + .par_chunks_mut(width) + .zip(inner_trace.values.par_chunks(inner_width)) + .zip(memory_cols) + .for_each(|((row, inner_row), memory_cols)| { + // WARNING: Poseidon2SubCols must be the first field in NativePoseidon2Cols + row[..inner_width].copy_from_slice(inner_row); + let cols: &mut NativePoseidon2Cols, SBOX_REGISTERS> = row.borrow_mut(); + cols.memory = memory_cols; + }); + + AirProofInput::simple_no_pis(air, RowMajorMatrix::new(values, width)) + } +} + +impl ChipUsageGetter + for NativePoseidon2BaseChip +{ + fn air_name(&self) -> String { + get_air_name(&self.air) + } + + fn current_trace_height(&self) -> usize { + self.records.len() + } + + fn trace_width(&self) -> usize { + self.air.width() + } +} From 2ef5f76d16a02afa7ff29a4db15a2283367517c2 Mon Sep 17 00:00:00 2001 From: Kero Date: Tue, 24 Dec 2024 11:13:22 +0800 Subject: [PATCH 3/8] refactor(toolchain): consolidate env vars and cleanup code (#1124) * refactor(toolchain): consolidate env vars and cleanup code * chore: apply review suggestions --- crates/toolchain/build/src/lib.rs | 37 ++++++++++++++++--------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/crates/toolchain/build/src/lib.rs b/crates/toolchain/build/src/lib.rs index b8e0c8a36f..716496c899 100644 --- a/crates/toolchain/build/src/lib.rs +++ b/crates/toolchain/build/src/lib.rs @@ -19,8 +19,11 @@ pub use self::config::GuestOptions; mod config; -#[allow(dead_code)] const RUSTUP_TOOLCHAIN_NAME: &str = "nightly-2024-10-30"; +const BUILD_LOCKED_ENV: &str = "OPENVM_BUILD_LOCKED"; +const BUILD_DEBUG_ENV: &str = "OPENVM_BUILD_DEBUG"; +const SKIP_BUILD_ENV: &str = "OPENVM_SKIP_BUILD"; +const GUEST_LOGFILE_ENV: &str = "OPENVM_GUEST_LOGFILE"; /// Returns the given cargo Package from the metadata in the Cargo.toml manifest /// within the provided `manifest_dir`. @@ -92,12 +95,12 @@ pub fn current_package() -> Package { /// Reads the value of the environment variable `OPENVM_BUILD_DEBUG` and returns true if it is set to 1. pub fn is_debug() -> bool { - get_env_var("OPENVM_BUILD_DEBUG") == "1" + get_env_var(BUILD_DEBUG_ENV) == "1" } /// Reads the value of the environment variable `OPENVM_SKIP_BUILD` and returns true if it is set to 1. pub fn is_skip_build() -> bool { - !get_env_var("OPENVM_SKIP_BUILD").is_empty() + !get_env_var(SKIP_BUILD_ENV).is_empty() } fn get_env_var(name: &str) -> String { @@ -151,8 +154,10 @@ fn sanitized_cmd(tool: &str) -> Command { /// Creates a std::process::Command to execute the given cargo /// command in an environment suitable for targeting the zkvm guest. pub fn cargo_command(subcmd: &str, rust_flags: &[&str]) -> Command { + let toolchain = format!("+{RUSTUP_TOOLCHAIN_NAME}"); + let rustc = sanitized_cmd("rustup") - .args(["+nightly-2024-10-30", "which", "rustc"]) // TODO: switch +nightly to +openvm once we make a toolchain + .args([&toolchain, "which", "rustc"]) .output() .expect("rustup failed to find nightly toolchain") .stdout; @@ -162,25 +167,21 @@ pub fn cargo_command(subcmd: &str, rust_flags: &[&str]) -> Command { println!("Using rustc: {rustc}"); let mut cmd = sanitized_cmd("cargo"); - // TODO[jpw]: remove +nightly - let mut args = vec![ - "+nightly-2024-10-30", - subcmd, - "--target", - "riscv32im-risc0-zkvm-elf", - ]; - - if std::env::var("OPENVM_BUILD_LOCKED").is_ok() { + let mut args = vec![&toolchain, subcmd, "--target", "riscv32im-risc0-zkvm-elf"]; + + if std::env::var(BUILD_LOCKED_ENV).is_ok() { args.push("--locked"); } // let rust_src = get_env_var("OPENVM_RUST_SRC"); // if !rust_src.is_empty() { // TODO[jpw]: only do this for custom src once we make openvm toolchain - args.push("-Z"); - args.push("build-std=alloc,core,proc_macro,panic_abort,std"); - args.push("-Z"); - args.push("build-std-features=compiler-builtins-mem"); + args.extend_from_slice(&[ + "-Z", + "build-std=alloc,core,proc_macro,panic_abort,std", + "-Z", + "build-std-features=compiler-builtins-mem", + ]); // cmd.env("__CARGO_TESTS_ONLY_SRC_ROOT", rust_src); // } @@ -227,7 +228,7 @@ pub(crate) fn encode_rust_flags(rustc_flags: &[&str]) -> String { // progress messages from the inner cargo so the user doesn't // think it's just hanging. fn tty_println(msg: &str) { - let tty_file = env::var("OPENVM_GUEST_LOGFILE").unwrap_or_else(|_| "/dev/tty".to_string()); + let tty_file = env::var(GUEST_LOGFILE_ENV).unwrap_or_else(|_| "/dev/tty".to_string()); let mut tty = fs::OpenOptions::new() .read(true) From e4eaff480c480221a1bc5f5bc2d0beda80fc6f03 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Tue, 24 Dec 2024 00:31:20 -0500 Subject: [PATCH 4/8] feat(ecc): Weierstrass affine curves over complex fields (#1127) * chore: add invert to Field trait * chore: generalize WeierstrassPoint trait * working version of G2Affine * chore: make into macro * chore: split out FromCompress trait * chore: refactor macros for weierstrass curves * chore: fixes * add unit tests --- extensions/algebra/guest/src/field/mod.rs | 10 +- extensions/ecc/guest/src/affine_point.rs | 3 +- extensions/ecc/guest/src/ecdsa.rs | 7 +- extensions/ecc/guest/src/group.rs | 4 +- extensions/ecc/guest/src/k256/mod.rs | 25 +- extensions/ecc/guest/src/weierstrass.rs | 362 ++++++++++++++++-- extensions/ecc/sw-setup/src/lib.rs | 199 +++------- .../ecc/tests/programs/examples/decompress.rs | 2 +- .../ecc/tests/programs/examples/ecdsa.rs | 13 +- extensions/pairing/guest/src/bls12_381/mod.rs | 35 +- .../pairing/guest/src/bls12_381/tests.rs | 45 ++- extensions/pairing/guest/src/bn254/mod.rs | 111 ++++-- extensions/pairing/guest/src/bn254/tests.rs | 45 ++- .../guest/src/pairing/sextic_ext_field.rs | 1 + 14 files changed, 607 insertions(+), 255 deletions(-) diff --git a/extensions/algebra/guest/src/field/mod.rs b/extensions/algebra/guest/src/field/mod.rs index eda026c5af..ffc5c710aa 100644 --- a/extensions/algebra/guest/src/field/mod.rs +++ b/extensions/algebra/guest/src/field/mod.rs @@ -6,7 +6,7 @@ use core::{ use crate::{DivAssignUnsafe, DivUnsafe}; -// TODO: this can now extend IntMod trait +// TODO: the shared parts of Field and IntMod should be moved into a new `IntegralDomain` trait. /// This is a simplified trait for field elements. pub trait Field: Sized @@ -48,6 +48,14 @@ pub trait Field: /// Square `self` in-place fn square_assign(&mut self); + + /// Unchecked inversion. See [DivUnsafe]. + /// + /// ## Panics + /// If `self` is zero. + fn invert(&self) -> Self { + Self::ONE.div_unsafe(self) + } } /// Field extension trait. BaseField is the base field of the extension field. diff --git a/extensions/ecc/guest/src/affine_point.rs b/extensions/ecc/guest/src/affine_point.rs index 7d84afa3f7..b4dd8f1798 100644 --- a/extensions/ecc/guest/src/affine_point.rs +++ b/extensions/ecc/guest/src/affine_point.rs @@ -10,7 +10,7 @@ pub struct AffinePoint { } impl AffinePoint { - pub fn new(x: F, y: F) -> Self { + pub const fn new(x: F, y: F) -> Self { Self { x, y } } @@ -29,6 +29,7 @@ impl AffinePoint { } } +// Note: this is true for weierstrass curves but maybe not in general impl Neg for AffinePoint where F: Neg, diff --git a/extensions/ecc/guest/src/ecdsa.rs b/extensions/ecc/guest/src/ecdsa.rs index 095ef9d628..ad3a148687 100644 --- a/extensions/ecc/guest/src/ecdsa.rs +++ b/extensions/ecc/guest/src/ecdsa.rs @@ -5,7 +5,7 @@ use elliptic_curve::PrimeCurve; use openvm_algebra_guest::{DivUnsafe, IntMod, Reduce}; use crate::{ - weierstrass::{IntrinsicCurve, WeierstrassPoint}, + weierstrass::{FromCompressed, IntrinsicCurve, WeierstrassPoint}, CyclicGroup, Group, }; @@ -40,6 +40,9 @@ impl VerifyingKey { impl VerifyingKey where C: PrimeCurve + IntrinsicCurve, + C::Point: WeierstrassPoint + CyclicGroup + FromCompressed>, + Coordinate: IntMod, + C::Scalar: IntMod + Reduce, { /// Ref: /// @@ -84,7 +87,7 @@ where } let rec_id = recovery_id.to_byte(); // The point R decompressed from x-coordinate `r` - let R: C::Point = WeierstrassPoint::decompress(x, &rec_id); + let R: C::Point = FromCompressed::decompress(x, &rec_id); let neg_u1 = z.div_unsafe(&r); let u2 = s.div_unsafe(&r); diff --git a/extensions/ecc/guest/src/group.rs b/extensions/ecc/guest/src/group.rs index 860d884d5c..c2f8cde146 100644 --- a/extensions/ecc/guest/src/group.rs +++ b/extensions/ecc/guest/src/group.rs @@ -24,7 +24,9 @@ pub trait Group: const IDENTITY: Self; - fn is_identity(&self) -> bool; + fn is_identity(&self) -> bool { + self == &Self::IDENTITY + } fn double(&self) -> Self; fn double_assign(&mut self); diff --git a/extensions/ecc/guest/src/k256/mod.rs b/extensions/ecc/guest/src/k256/mod.rs index 8ce61eb1f3..25a162c0ab 100644 --- a/extensions/ecc/guest/src/k256/mod.rs +++ b/extensions/ecc/guest/src/k256/mod.rs @@ -1,11 +1,13 @@ -use core::ops::{Add, AddAssign, Neg}; +use core::ops::{Add, Neg}; use hex_literal::hex; #[cfg(not(target_os = "zkvm"))] use lazy_static::lazy_static; #[cfg(not(target_os = "zkvm"))] use num_bigint_dig::BigUint; -use openvm_algebra_guest::IntMod; +use openvm_algebra_guest::{Field, IntMod}; +use openvm_algebra_moduli_setup::moduli_declare; +use openvm_ecc_sw_setup::sw_declare; use super::group::{CyclicGroup, Group}; use crate::weierstrass::{CachedMulTable, IntrinsicCurve}; @@ -30,15 +32,30 @@ const fn seven_le() -> [u8; 32] { buf } -openvm_algebra_moduli_setup::moduli_declare! { +moduli_declare! { Secp256k1Coord { modulus = "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F" }, Secp256k1Scalar { modulus = "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141" }, } -openvm_ecc_sw_setup::sw_declare! { +sw_declare! { Secp256k1Point { mod_type = Secp256k1Coord, b = CURVE_B }, } +impl Field for Secp256k1Coord { + const ZERO: Self = ::ZERO; + const ONE: Self = ::ONE; + + type SelfRef<'a> = &'a Self; + + fn double_assign(&mut self) { + IntMod::double_assign(self); + } + + fn square_assign(&mut self) { + IntMod::square_assign(self); + } +} + impl CyclicGroup for Secp256k1Point { const GENERATOR: Self = Secp256k1Point { x: Secp256k1Coord::from_const_bytes(hex!( diff --git a/extensions/ecc/guest/src/weierstrass.rs b/extensions/ecc/guest/src/weierstrass.rs index d4f912c219..d91ec01fe5 100644 --- a/extensions/ecc/guest/src/weierstrass.rs +++ b/extensions/ecc/guest/src/weierstrass.rs @@ -1,16 +1,17 @@ use alloc::vec::Vec; -use core::ops::{Add, AddAssign, Mul}; +use core::ops::{AddAssign, Mul}; -use openvm_algebra_guest::{IntMod, Reduce}; +use openvm_algebra_guest::{Field, IntMod}; -use super::group::{CyclicGroup, Group}; +use super::group::Group; /// Short Weierstrass curve affine point. -pub trait WeierstrassPoint: Group { +pub trait WeierstrassPoint: Sized { /// The `b` coefficient in the Weierstrass curve equation `y^2 = x^3 + a x + b`. const CURVE_B: Self::Coordinate; + const IDENTITY: Self; - type Coordinate: IntMod; + type Coordinate: Field; /// The concatenated `x, y` coordinates of the affine point, where /// coordinates are in little endian. @@ -27,12 +28,16 @@ pub trait WeierstrassPoint: Group { fn x_mut(&mut self) -> &mut Self::Coordinate; fn y_mut(&mut self) -> &mut Self::Coordinate; - /// Hazmat: Assumes p1 != +- p2 and p != identity and p2 != identity. - fn add_ne_nonidentity(p1: &Self, p2: &Self) -> Self; + /// Hazmat: Assumes self != +- p2 and self != identity and p2 != identity. + fn add_ne_nonidentity(&self, p2: &Self) -> Self; /// Hazmat: Assumes self != +- p2 and self != identity and p2 != identity. fn add_ne_assign_nonidentity(&mut self, p2: &Self); - /// Hazmat: Assumes p != identity and 2 * p != identity. - fn double_nonidentity(p: &Self) -> Self; + /// Hazmat: Assumes self != +- p2 and self != identity and p2 != identity. + fn sub_ne_nonidentity(&self, p2: &Self) -> Self; + /// Hazmat: Assumes self != +- p2 and self != identity and p2 != identity. + fn sub_ne_assign_nonidentity(&mut self, p2: &Self); + /// Hazmat: Assumes self != identity and 2 * self != identity. + fn double_nonidentity(&self) -> Self; /// Hazmat: Assumes self != identity and 2 * self != identity. fn double_assign_nonidentity(&mut self); @@ -58,7 +63,9 @@ pub trait WeierstrassPoint: Group { } Some(Self::from_xy_unchecked(x, y)) } +} +pub trait FromCompressed { /// Given `x`-coordinate, /// /// ## Panics @@ -71,16 +78,7 @@ pub trait WeierstrassPoint: Group { // and then constrain its correctness. A malicious prover could hint // incorrectly, so there is no way to use a hint to prove that the input // **cannot** be decompressed. - fn decompress(x: Self::Coordinate, rec_id: &u8) -> Self - where - for<'a> &'a Self::Coordinate: Mul<&'a Self::Coordinate, Output = Self::Coordinate>, - { - let y = Self::hint_decompress(&x, rec_id); - // Must assert unique so we can check the parity - y.assert_unique(); - assert_eq!(y.as_le_bytes()[0] & 1, *rec_id & 1); - Self::from_xy_nonidentity(x, y).expect("decompressed point not on curve") - } + fn decompress(x: Coordinate, rec_id: &u8) -> Self; /// If it exists, hints the unique `y` coordinate that is less than `Coordinate::MODULUS` /// such that `(x, y)` is a point on the curve and `y` has parity equal to `rec_id`. @@ -88,24 +86,19 @@ pub trait WeierstrassPoint: Group { /// /// This is only a hint, and the returned `y` does not guarantee any of the above properties. /// They must be checked separately. Normal users should use `decompress` directly. - fn hint_decompress(x: &Self::Coordinate, rec_id: &u8) -> Self::Coordinate; + fn hint_decompress(x: &Coordinate, rec_id: &u8) -> Coordinate; } /// A trait for elliptic curves that bridges the openvm types and external types with CurveArithmetic etc. /// Implement this for external curves with corresponding openvm point and scalar types. pub trait IntrinsicCurve { - type Scalar: IntMod + Reduce; - type Point: WeierstrassPoint + CyclicGroup; + type Scalar: Clone; + type Point: Clone; - /// Multi-scalar multiplication. The default implementation may be - /// replaced by specialized implementations that use properties of the curve + /// Multi-scalar multiplication. + /// The implementation may be specialized to use properties of the curve /// (e.g., if the curve order is prime). - fn msm(coeffs: &[Self::Scalar], bases: &[Self::Point]) -> Self::Point - where - for<'a> &'a Self::Point: Add<&'a Self::Point, Output = Self::Point>, - { - super::msm(coeffs, bases) - } + fn msm(coeffs: &[Self::Scalar], bases: &[Self::Point]) -> Self::Point; } // MSM using preprocessed table (windowed method) @@ -128,11 +121,15 @@ pub struct CachedMulTable<'a, C: IntrinsicCurve> { identity: C::Point, } -impl<'a, C: IntrinsicCurve> CachedMulTable<'a, C> { - /// Constructor when the curve order is prime (so the group of curve points forms the scalar prime field). +impl<'a, C: IntrinsicCurve> CachedMulTable<'a, C> +where + C::Point: WeierstrassPoint + Group, + C::Scalar: IntMod, +{ + /// Constructor when each element of `bases` has prime torsion or is identity. /// - /// Assumes that `window_bits` is less than number of bits - 1 in the modulus - /// of `C::Scalar`. + /// Assumes that `window_bits` is less than (number of bits - 1) of the order of + /// subgroup generated by each non-identity `base`. pub fn new_with_prime_order(bases: &'a [C::Point], window_bits: usize) -> Self { assert!(window_bits > 0); let window_size = 1 << window_bits; @@ -140,18 +137,18 @@ impl<'a, C: IntrinsicCurve> CachedMulTable<'a, C> { .iter() .map(|base| { if base.is_identity() { - vec![C::Point::IDENTITY; window_size - 2] + vec![::IDENTITY; window_size - 2] } else { let mut multiples = Vec::with_capacity(window_size - 2); for _ in 0..window_size - 2 { - // Because curve order is prime, we are guaranteed that + // Because the order of `base` is prime, we are guaranteed that // j * base != identity, // j * base != +- base for j > 1, // j * base + base != identity let multiple = multiples .last() - .map(|last| C::Point::add_ne_nonidentity(last, base)) - .unwrap_or_else(|| C::Point::double_nonidentity(base)); + .map(|last| WeierstrassPoint::add_ne_nonidentity(last, base)) + .unwrap_or_else(|| base.double_nonidentity()); multiples.push(multiple); } multiples @@ -163,7 +160,7 @@ impl<'a, C: IntrinsicCurve> CachedMulTable<'a, C> { window_bits, bases, table, - identity: C::Point::IDENTITY, + identity: ::IDENTITY, } } @@ -197,7 +194,7 @@ impl<'a, C: IntrinsicCurve> CachedMulTable<'a, C> { // bit_idx will always be in range [0, 8) let mut bit_idx = 0; - let mut res = C::Point::IDENTITY; + let mut res = ::IDENTITY; for outer in 0..num_windows { if bit_idx == 0 { limb_idx -= 1; @@ -222,3 +219,288 @@ impl<'a, C: IntrinsicCurve> CachedMulTable<'a, C> { res } } + +/// Macro to generate a newtype wrapper for [AffinePoint](crate::AffinePoint) +/// that implements elliptic curve operations by using the underlying field operations according to the +/// [formulas](https://www.hyperelliptic.org/EFD/g1p/auto-shortw.html) for short Weierstrass curves. +/// +/// The following imports are required: +/// ```rust +/// use core::ops::AddAssign; +/// +/// use openvm_algebra_guest::{DivUnsafe, Field}; +/// use openvm_ecc_guest::{AffinePoint, Group, weierstrass::WeierstrassPoint}; +/// ``` +#[macro_export] +macro_rules! impl_sw_affine { + // Assumes `a = 0` in curve equation. `$three` should be a constant expression for `3` of type `$field`. + ($struct_name:ident, $field:ty, $three:expr, $b:expr) => { + /// A newtype wrapper for [AffinePoint] that implements elliptic curve operations + /// by using the underlying field operations according to the [formulas](https://www.hyperelliptic.org/EFD/g1p/auto-shortw.html) for short Weierstrass curves. + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] + #[repr(transparent)] + pub struct $struct_name(AffinePoint<$field>); + + impl WeierstrassPoint for $struct_name { + const CURVE_B: $field = $b; + const IDENTITY: Self = Self(AffinePoint::new(<$field>::ZERO, <$field>::ZERO)); + + type Coordinate = $field; + + /// SAFETY: assumes that [$field] has internal representation in little-endian. + fn as_le_bytes(&self) -> &[u8] { + unsafe { + &*core::ptr::slice_from_raw_parts( + self as *const Self as *const u8, + core::mem::size_of::(), + ) + } + } + fn from_xy_unchecked(x: Self::Coordinate, y: Self::Coordinate) -> Self { + Self(AffinePoint::new(x, y)) + } + fn into_coords(self) -> (Self::Coordinate, Self::Coordinate) { + (self.0.x, self.0.y) + } + fn x(&self) -> &Self::Coordinate { + &self.0.x + } + fn y(&self) -> &Self::Coordinate { + &self.0.y + } + fn x_mut(&mut self) -> &mut Self::Coordinate { + &mut self.0.x + } + fn y_mut(&mut self) -> &mut Self::Coordinate { + &mut self.0.y + } + + fn double_nonidentity(&self) -> Self { + use ::openvm_algebra_guest::DivUnsafe; + // lambda = (3*x1^2+a)/(2*y1) + // for bls12-381, a = 0 + let lambda = (&THREE * self.x() * self.x()).div_unsafe(self.y() + self.y()); + // x3 = lambda^2-x1-x1 + let x3 = &lambda * &lambda - self.x() - self.x(); + // y3 = lambda * (x1-x3) - y1 + let y3 = lambda * (self.x() - &x3) - self.y(); + Self(AffinePoint::new(x3, y3)) + } + + fn double_assign_nonidentity(&mut self) { + // TODO: revisit if there are possible optimizations + *self = self.double_nonidentity(); + } + + fn add_ne_nonidentity(&self, p2: &Self) -> Self { + use ::openvm_algebra_guest::DivUnsafe; + // lambda = (y2-y1)/(x2-x1) + // x3 = lambda^2-x1-x2 + // y3 = lambda*(x1-x3)-y1 + let lambda = (p2.y() - self.y()).div_unsafe(p2.x() - self.x()); + let x3 = &lambda * &lambda - self.x() - p2.x(); + let y3 = lambda * (self.x() - &x3) - self.y(); + Self(AffinePoint::new(x3, y3)) + } + + fn add_ne_assign_nonidentity(&mut self, p2: &Self) { + // TODO: revisit if there are possible optimizations + *self = self.add_ne_nonidentity(p2); + } + + fn sub_ne_nonidentity(&self, p2: &Self) -> Self { + use ::openvm_algebra_guest::DivUnsafe; + // lambda = (y2+y1)/(x1-x2) + // x3 = lambda^2-x1-x2 + // y3 = lambda*(x1-x3)-y1 + let lambda = (p2.y() + self.y()).div_unsafe(self.x() - p2.x()); + let x3 = &lambda * &lambda - self.x() - p2.x(); + let y3 = lambda * (self.x() - &x3) - self.y(); + Self(AffinePoint::new(x3, y3)) + } + + fn sub_ne_assign_nonidentity(&mut self, p2: &Self) { + // TODO: revisit if there are possible optimizations + *self = self.sub_ne_nonidentity(p2); + } + } + + impl core::ops::Neg for $struct_name { + type Output = Self; + + fn neg(mut self) -> Self::Output { + self.0.y.neg_assign(); + self + } + } + + impl core::ops::Neg for &$struct_name { + type Output = $struct_name; + + fn neg(self) -> Self::Output { + self.clone().neg() + } + } + + impl From<$struct_name> for AffinePoint<$field> { + fn from(value: $struct_name) -> Self { + value.0 + } + } + + impl From> for $struct_name { + fn from(value: AffinePoint<$field>) -> Self { + Self(value) + } + } + }; +} + +/// Implements `Group` on `$struct_name` assuming that `$struct_name` implements `WeierstrassPoint`. +/// Assumes that `Neg` is implemented for `&$struct_name`. +#[macro_export] +macro_rules! impl_sw_group_ops { + ($struct_name:ident, $field:ty) => { + impl Group for $struct_name { + type SelfRef<'a> = &'a Self; + + const IDENTITY: Self = ::IDENTITY; + + fn double(&self) -> Self { + if self.is_identity() { + self.clone() + } else { + self.double_nonidentity() + } + } + + fn double_assign(&mut self) { + if !self.is_identity() { + self.double_assign_nonidentity(); + } + } + } + + impl core::ops::Add<&$struct_name> for $struct_name { + type Output = Self; + + fn add(mut self, p2: &$struct_name) -> Self::Output { + use core::ops::AddAssign; + self.add_assign(p2); + self + } + } + + impl core::ops::Add for $struct_name { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + self.add(&rhs) + } + } + + impl core::ops::Add<&$struct_name> for &$struct_name { + type Output = $struct_name; + + fn add(self, p2: &$struct_name) -> Self::Output { + if self.is_identity() { + p2.clone() + } else if p2.is_identity() { + self.clone() + } else if self.x() == p2.x() { + if self.y() + p2.y() == <$field as Field>::ZERO { + <$struct_name as WeierstrassPoint>::IDENTITY + } else { + self.double_nonidentity() + } + } else { + self.add_ne_nonidentity(p2) + } + } + } + + impl core::ops::AddAssign<&$struct_name> for $struct_name { + fn add_assign(&mut self, p2: &$struct_name) { + if self.is_identity() { + *self = p2.clone(); + } else if p2.is_identity() { + // do nothing + } else if self.x() == p2.x() { + if self.y() + p2.y() == <$field as Field>::ZERO { + *self = <$struct_name as WeierstrassPoint>::IDENTITY; + } else { + self.double_assign_nonidentity(); + } + } else { + self.add_ne_assign_nonidentity(p2); + } + } + } + + impl core::ops::AddAssign for $struct_name { + fn add_assign(&mut self, rhs: Self) { + self.add_assign(&rhs); + } + } + + impl core::ops::Sub<&$struct_name> for $struct_name { + type Output = Self; + + fn sub(self, rhs: &$struct_name) -> Self::Output { + core::ops::Sub::sub(&self, rhs) + } + } + + impl core::ops::Sub for $struct_name { + type Output = $struct_name; + + fn sub(self, rhs: Self) -> Self::Output { + self.sub(&rhs) + } + } + + impl core::ops::Sub<&$struct_name> for &$struct_name { + type Output = $struct_name; + + fn sub(self, p2: &$struct_name) -> Self::Output { + if p2.is_identity() { + self.clone() + } else if self.is_identity() { + core::ops::Neg::neg(p2) + } else if self.x() == p2.x() { + if self.y() == p2.y() { + <$struct_name as WeierstrassPoint>::IDENTITY + } else { + self.double_nonidentity() + } + } else { + self.sub_ne_nonidentity(p2) + } + } + } + + impl core::ops::SubAssign<&$struct_name> for $struct_name { + fn sub_assign(&mut self, p2: &$struct_name) { + if p2.is_identity() { + // do nothing + } else if self.is_identity() { + *self = core::ops::Neg::neg(p2); + } else if self.x() == p2.x() { + if self.y() == p2.y() { + *self = <$struct_name as WeierstrassPoint>::IDENTITY + } else { + self.double_assign_nonidentity(); + } + } else { + self.sub_ne_assign_nonidentity(p2); + } + } + } + + impl core::ops::SubAssign for $struct_name { + fn sub_assign(&mut self, rhs: Self) { + self.sub_assign(&rhs); + } + } + }; +} diff --git a/extensions/ecc/sw-setup/src/lib.rs b/extensions/ecc/sw-setup/src/lib.rs index d1b04247e4..e9fc22c677 100644 --- a/extensions/ecc/sw-setup/src/lib.rs +++ b/extensions/ecc/sw-setup/src/lib.rs @@ -4,6 +4,7 @@ extern crate proc_macro; use openvm_macros_common::MacroArgs; use proc_macro::TokenStream; +use quote::format_ident; use syn::{ parse::{Parse, ParseStream}, parse_macro_input, Expr, ExprPath, Path, Token, @@ -76,6 +77,8 @@ pub fn sw_declare(input: TokenStream) -> TokenStream { create_extern_func!(sw_double_extern_func); create_extern_func!(hint_decompress_extern_func); + let group_ops_mod_name = format_ident!("{}_ops", struct_name.to_string().to_lowercase()); + let result = TokenStream::from(quote::quote_spanned! { span.into() => extern "C" { fn #sw_add_ne_extern_func(rd: usize, rs1: usize, rs2: usize); @@ -175,13 +178,7 @@ pub fn sw_declare(input: TokenStream) -> TokenStream { fn double_assign_impl(&mut self) { #[cfg(not(target_os = "zkvm"))] { - use openvm_algebra_guest::DivUnsafe; - let two = #intmod_type::from_u8(2); - let lambda = &self.x * &self.x * #intmod_type::from_u8(3).div_unsafe(&self.y * &two); - let x3 = &lambda * &lambda - &self.x * &two; - let y3 = &lambda * &(&self.x - &x3) - &self.y; - self.x = x3; - self.y = y3; + *self = Self::double_impl(self); } #[cfg(target_os = "zkvm")] { @@ -197,6 +194,7 @@ pub fn sw_declare(input: TokenStream) -> TokenStream { impl ::openvm_ecc_guest::weierstrass::WeierstrassPoint for #struct_name { const CURVE_B: #intmod_type = #const_b; + const IDENTITY: Self = Self::identity(); type Coordinate = #intmod_type; /// SAFETY: assumes that #intmod_type has a memory representation @@ -229,128 +227,28 @@ pub fn sw_declare(input: TokenStream) -> TokenStream { (self.x, self.y) } - fn add_ne_nonidentity(p1: &Self, p2: &Self) -> Self { - Self::add_ne(p1, p2) + fn add_ne_nonidentity(&self, p2: &Self) -> Self { + Self::add_ne(self, p2) } fn add_ne_assign_nonidentity(&mut self, p2: &Self) { Self::add_ne_assign(self, p2); } - fn double_nonidentity(p: &Self) -> Self { - Self::double_impl(p) - } - - fn double_assign_nonidentity(&mut self) { - Self::double_assign_impl(self); - } - - fn hint_decompress(x: &Self::Coordinate, rec_id: &u8) -> Self::Coordinate { - #[cfg(not(target_os = "zkvm"))] - { - unimplemented!() - } - #[cfg(target_os = "zkvm")] - { - use openvm::platform as openvm_platform; // needed for hint_store_u32! - - let y = core::mem::MaybeUninit::::uninit(); - unsafe { - #hint_decompress_extern_func(x as *const Self::Coordinate as usize, rec_id as *const u8 as usize); - let mut ptr = y.as_ptr() as *const u8; - // NOTE[jpw]: this loop could be unrolled using seq_macro and hint_store_u32(ptr, $imm) - for _ in (0..::NUM_LIMBS).step_by(4) { - openvm_rv32im_guest::hint_store_u32!(ptr, 0); - ptr = ptr.add(4); - } - y.assume_init() - } - } - } - } - - impl Group for #struct_name { - type SelfRef<'a> = &'a Self; - - const IDENTITY: Self = Self::identity(); - - fn is_identity(&self) -> bool { - self.x == <#intmod_type as openvm_algebra_guest::IntMod>::ZERO && self.y == <#intmod_type as openvm_algebra_guest::IntMod>::ZERO - } - - fn double(&self) -> Self { - if self.is_identity() { - self.clone() - } else { - Self::double_impl(self) - } - } - - fn double_assign(&mut self) { - if !self.is_identity() { - Self::double_assign_impl(self); - } - } - } - - impl core::ops::Add<&#struct_name> for #struct_name { - type Output = Self; - - fn add(mut self, p2: &#struct_name) -> Self::Output { - self.add_assign(p2); - self + fn sub_ne_nonidentity(&self, p2: &Self) -> Self { + Self::add_ne(self, &p2.clone().neg()) } - } - - impl core::ops::Add for #struct_name { - type Output = Self; - fn add(self, rhs: Self) -> Self::Output { - self.add(&rhs) + fn sub_ne_assign_nonidentity(&mut self, p2: &Self) { + Self::add_ne_assign(self, &p2.clone().neg()); } - } - impl core::ops::Add<&#struct_name> for &#struct_name { - type Output = #struct_name; - - fn add(self, p2: &#struct_name) -> Self::Output { - if self.is_identity() { - p2.clone() - } else if p2.is_identity() { - self.clone() - } else if self.x == p2.x { - if &self.y + &p2.y == <#intmod_type as openvm_algebra_guest::IntMod>::ZERO { - #struct_name::identity() - } else { - #struct_name::double_impl(self) - } - } else { - #struct_name::add_ne(self, p2) - } + fn double_nonidentity(&self) -> Self { + Self::double_impl(self) } - } - - impl core::ops::AddAssign<&#struct_name> for #struct_name { - fn add_assign(&mut self, p2: &#struct_name) { - if self.is_identity() { - *self = p2.clone(); - } else if p2.is_identity() { - // do nothing - } else if self.x == p2.x { - if &self.y + &p2.y == <#intmod_type as openvm_algebra_guest::IntMod>::ZERO { - *self = Self::identity(); - } else { - Self::double_assign_impl(self); - } - } else { - Self::add_ne_assign(self, p2); - } - } - } - impl core::ops::AddAssign for #struct_name { - fn add_assign(&mut self, rhs: Self) { - self.add_assign(&rhs); + fn double_assign_nonidentity(&mut self) { + Self::double_assign_impl(self); } } @@ -358,46 +256,61 @@ pub fn sw_declare(input: TokenStream) -> TokenStream { type Output = Self; fn neg(self) -> Self::Output { - Self { + #struct_name { x: self.x, y: -self.y, } } } - impl core::ops::Sub<&#struct_name> for #struct_name { - type Output = Self; - - fn sub(self, rhs: &#struct_name) -> Self::Output { - self.sub(rhs.clone()) - } - } - - impl core::ops::Sub for #struct_name { + impl core::ops::Neg for &#struct_name { type Output = #struct_name; - fn sub(self, rhs: Self) -> Self::Output { - self.add(rhs.neg()) + fn neg(self) -> #struct_name { + #struct_name { + x: self.x.clone(), + y: core::ops::Neg::neg(&self.y), + } } } - impl core::ops::Sub<&#struct_name> for &#struct_name { - type Output = #struct_name; + mod #group_ops_mod_name { + use ::openvm_ecc_guest::{weierstrass::{WeierstrassPoint, FromCompressed}, impl_sw_group_ops}; + use super::*; - fn sub(self, p2: &#struct_name) -> Self::Output { - self.add(&p2.clone().neg()) - } - } + impl_sw_group_ops!(#struct_name, #intmod_type); - impl core::ops::SubAssign<&#struct_name> for #struct_name { - fn sub_assign(&mut self, p2: &#struct_name) { - self.sub_assign(p2.clone()); - } - } + impl FromCompressed<#intmod_type> for #struct_name { + fn decompress(x: #intmod_type, rec_id: &u8) -> Self { + let y = <#struct_name as FromCompressed<#intmod_type>>::hint_decompress(&x, rec_id); + // Must assert unique so we can check the parity + y.assert_unique(); + assert_eq!(y.as_le_bytes()[0] & 1, *rec_id & 1); + <#struct_name as ::openvm_ecc_guest::weierstrass::WeierstrassPoint>::from_xy_nonidentity(x, y).expect("decompressed point not on curve") + } - impl core::ops::SubAssign for #struct_name { - fn sub_assign(&mut self, rhs: Self) { - self.add_assign(rhs.neg()); + fn hint_decompress(x: &#intmod_type, rec_id: &u8) -> #intmod_type { + #[cfg(not(target_os = "zkvm"))] + { + unimplemented!() + } + #[cfg(target_os = "zkvm")] + { + use openvm::platform as openvm_platform; // needed for hint_store_u32! + + let y = core::mem::MaybeUninit::<#intmod_type>::uninit(); + unsafe { + #hint_decompress_extern_func(x as *const _ as usize, rec_id as *const u8 as usize); + let mut ptr = y.as_ptr() as *const u8; + // NOTE[jpw]: this loop could be unrolled using seq_macro and hint_store_u32(ptr, $imm) + for _ in (0..<#intmod_type as openvm_algebra_guest::IntMod>::NUM_LIMBS).step_by(4) { + openvm_rv32im_guest::hint_store_u32!(ptr, 0); + ptr = ptr.add(4); + } + y.assume_init() + } + } + } } } }); diff --git a/extensions/ecc/tests/programs/examples/decompress.rs b/extensions/ecc/tests/programs/examples/decompress.rs index 7e2110840f..39fca6727c 100644 --- a/extensions/ecc/tests/programs/examples/decompress.rs +++ b/extensions/ecc/tests/programs/examples/decompress.rs @@ -5,7 +5,7 @@ use openvm::io::read_vec; use openvm_ecc_guest::{ algebra::IntMod, k256::{Secp256k1Coord, Secp256k1Point}, - weierstrass::WeierstrassPoint, + weierstrass::FromCompressed, }; openvm::entry!(main); diff --git a/extensions/ecc/tests/programs/examples/ecdsa.rs b/extensions/ecc/tests/programs/examples/ecdsa.rs index 5fbae6409e..79378c4cc9 100644 --- a/extensions/ecc/tests/programs/examples/ecdsa.rs +++ b/extensions/ecc/tests/programs/examples/ecdsa.rs @@ -1,20 +1,17 @@ #![cfg_attr(not(feature = "std"), no_main)] #![cfg_attr(not(feature = "std"), no_std)] -use core::{hint::black_box, ptr::slice_from_raw_parts}; +use core::hint::black_box; -use openvm_ecc_guest::{ - algebra::IntMod, - ecdsa::VerifyingKey, - k256::{Secp256k1Coord, Secp256k1Point}, - weierstrass::WeierstrassPoint, -}; -use openvm_keccak256_guest::keccak256; use hex_literal::hex; use k256::{ ecdsa::{self, RecoveryId}, Secp256k1, }; +use openvm_ecc_guest::{ + algebra::IntMod, ecdsa::VerifyingKey, k256::Secp256k1Coord, weierstrass::WeierstrassPoint, +}; +use openvm_keccak256_guest::keccak256; openvm::entry!(main); openvm_algebra_moduli_setup::moduli_init! { diff --git a/extensions/pairing/guest/src/bls12_381/mod.rs b/extensions/pairing/guest/src/bls12_381/mod.rs index eb908dc9b8..252acc173c 100644 --- a/extensions/pairing/guest/src/bls12_381/mod.rs +++ b/extensions/pairing/guest/src/bls12_381/mod.rs @@ -1,4 +1,4 @@ -use core::ops::{Add, AddAssign, Neg}; +use core::ops::Neg; use openvm_algebra_guest::{Field, IntMod}; use openvm_algebra_moduli_setup::moduli_declare; @@ -15,11 +15,10 @@ use hex_literal::hex; use lazy_static::lazy_static; #[cfg(not(target_os = "zkvm"))] use num_bigint_dig::BigUint; +use openvm_ecc_sw_setup::sw_declare; use crate::pairing::PairingIntrinsics; -pub struct Bls12_381; - #[cfg(all(test, feature = "halo2curves", not(target_os = "zkvm")))] mod tests; @@ -49,11 +48,9 @@ moduli_declare! { Bls12_381Scalar { modulus = "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" }, } -const CURVE_B: Bls12_381Fp = Bls12_381Fp::from_const_bytes(hex!( - "040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" -)); +const CURVE_B: Bls12_381Fp = Bls12_381Fp::from_const_u8(4); -openvm_ecc_sw_setup::sw_declare! { +sw_declare! { Bls12_381G1Affine { mod_type = Bls12_381Fp, b = CURVE_B }, } @@ -64,6 +61,7 @@ pub type Scalar = Bls12_381Scalar; /// on the curve but not necessarily in the prime order subgroup /// because the group has cofactors. pub type G1Affine = Bls12_381G1Affine; +pub use g2::G2Affine; impl Field for Fp { type SelfRef<'a> = &'a Self; @@ -116,11 +114,32 @@ impl CyclicGroup for G1Affine { }; } +pub struct Bls12_381; + impl IntrinsicCurve for Bls12_381 { type Scalar = Scalar; type Point = G1Affine; - // TODO: msm optimization + fn msm(coeffs: &[Self::Scalar], bases: &[Self::Point]) -> Self::Point { + // TODO: msm optimization + openvm_ecc_guest::msm(coeffs, bases) + } +} + +// Define a G2Affine struct that implements curve operations using `Fp2` intrinsics +// but not special E(Fp2) intrinsics. +mod g2 { + use openvm_algebra_guest::Field; + use openvm_ecc_guest::{ + impl_sw_affine, impl_sw_group_ops, weierstrass::WeierstrassPoint, AffinePoint, Group, + }; + + use super::{Fp, Fp2}; + + const THREE: Fp2 = Fp2::new(Fp::from_const_u8(3), Fp::ZERO); + const B: Fp2 = Fp2::new(Fp::from_const_u8(4), Fp::from_const_u8(4)); + impl_sw_affine!(G2Affine, Fp2, THREE, B); + impl_sw_group_ops!(G2Affine, Fp2); } impl PairingIntrinsics for Bls12_381 { diff --git a/extensions/pairing/guest/src/bls12_381/tests.rs b/extensions/pairing/guest/src/bls12_381/tests.rs index 665a0f41a3..60963e19b1 100644 --- a/extensions/pairing/guest/src/bls12_381/tests.rs +++ b/extensions/pairing/guest/src/bls12_381/tests.rs @@ -5,12 +5,12 @@ use halo2curves_axiom::bls12_381::{ Fq, Fq12, Fq2, Fq6, G1Affine, G2Affine, G2Prepared, MillerLoopResult, FROBENIUS_COEFF_FQ12_C1, }; use openvm_algebra_guest::{field::FieldExtension, IntMod}; -use openvm_ecc_guest::AffinePoint; +use openvm_ecc_guest::{weierstrass::WeierstrassPoint, AffinePoint}; use rand::{rngs::StdRng, SeedableRng}; use super::{Fp, Fp12, Fp2}; use crate::{ - bls12_381::Bls12_381, + bls12_381::{Bls12_381, G2Affine as OpenVmG2Affine}, pairing::{ fp2_invert_assign, fp6_invert_assign, fp6_square_assign, MultiMillerLoop, PairingIntrinsics, }, @@ -56,6 +56,13 @@ fn convert_bls12381_fp12_to_halo2_fq12(x: Fp12) -> Fq12 { Fq12::from_coeffs(c.map(convert_bls12381_fp2_to_halo2_fq2)) } +fn convert_g2_affine_halo2_to_openvm(p: G2Affine) -> OpenVmG2Affine { + OpenVmG2Affine::from_xy_unchecked( + convert_bls12381_halo2_fq2_to_fp2(p.x), + convert_bls12381_halo2_fq2_to_fp2(p.y), + ) +} + #[test] fn test_bls12381_frobenius_coeffs() { #[allow(clippy::needless_range_loop)] @@ -259,3 +266,37 @@ fn test_bls12381_miller_loop_identity_2() { halo2curves_axiom::bls12_381::multi_miller_loop(&[(&h2c_p, &h2c_q_prepared)]); assert_miller_results_eq(compare_miller, f); } + +// test on host is enough since we are testing the curve formulas and not anything +// about intrinsic functions +#[test] +fn test_bls12381_g2_affine() { + let mut rng = StdRng::seed_from_u64(34); + for _ in 0..10 { + let p = G2Affine::random(&mut rng); + let q = G2Affine::random(&mut rng); + let expected_add = G2Affine::from(p + q); + let expected_sub = G2Affine::from(p - q); + let expected_neg = -p; + let expected_double = G2Affine::from(p + p); + let [p, q] = [p, q].map(|p| { + let x = convert_bls12381_halo2_fq2_to_fp2(p.x); + let y = convert_bls12381_halo2_fq2_to_fp2(p.y); + // check on curve + OpenVmG2Affine::from_xy(x, y).unwrap() + }); + let r_add = &p + &q; + let r_sub = &p - &q; + let r_neg = -&p; + let r_double = &p + &p; + + for (expected, actual) in [ + (expected_add, r_add), + (expected_sub, r_sub), + (expected_neg, r_neg), + (expected_double, r_double), + ] { + assert_eq!(convert_g2_affine_halo2_to_openvm(expected), actual); + } + } +} diff --git a/extensions/pairing/guest/src/bn254/mod.rs b/extensions/pairing/guest/src/bn254/mod.rs index f9643d88ef..0de071e893 100644 --- a/extensions/pairing/guest/src/bn254/mod.rs +++ b/extensions/pairing/guest/src/bn254/mod.rs @@ -1,4 +1,4 @@ -use core::ops::{Add, AddAssign, Neg}; +use core::ops::{Add, Neg}; use hex_literal::hex; #[cfg(not(target_os = "zkvm"))] @@ -11,6 +11,7 @@ use openvm_ecc_guest::{ weierstrass::{CachedMulTable, IntrinsicCurve}, CyclicGroup, Group, }; +use openvm_ecc_sw_setup::sw_declare; use crate::pairing::PairingIntrinsics; @@ -46,46 +47,6 @@ pub const BN254_PSEUDO_BINARY_ENCODING: [i8; 66] = [ 0, 0, 1, 0, -1, 0, 1, ]; -pub struct Bn254; - -impl Bn254 { - pub const FROBENIUS_COEFF_FQ6_C1: [Fp2; 3] = [ - Fp2 { - c0: Bn254Fp(hex!( - "9d0d8fc58d435dd33d0bc7f528eb780a2c4679786fa36e662fdf079ac1770a0e" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "3d556f175795e3990c33c3c210c38cb743b159f53cec0b4cf711794f9847b32f" - )), - c1: Bn254Fp(hex!( - "a2cb0f641cd56516ce9d7c0b1d2aae3294075ad78bcca44b20aeeb6150e5c916" - )), - }, - Fp2 { - c0: Bn254Fp(hex!( - "48fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" - )), - c1: Bn254Fp(hex!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - }, - ]; - - pub const XI_TO_Q_MINUS_1_OVER_2: Fp2 = Fp2 { - c0: Bn254Fp(hex!( - "5a13a071460154dc9859c9a9ede0aadbb9f9e2b698c65edcdcf59a4805f33c06" - )), - c1: Bn254Fp(hex!( - "e3b02326637fd382d25ba28fc97d80212b6f79eca7b504079a0441acbc3cc007" - )), - }; -} - moduli_declare! { Bn254Fp { modulus = "21888242871839275222246405745257275088696311157297823662689037894645226208583" }, Bn254Scalar { modulus = "21888242871839275222246405745257275088548364400416034343698204186575808495617" }, @@ -95,13 +56,14 @@ const CURVE_B: Bn254Fp = Bn254Fp::from_const_bytes(hex!( "0300000000000000000000000000000000000000000000000000000000000000" )); -openvm_ecc_sw_setup::sw_declare! { +sw_declare! { Bn254G1Affine { mod_type = Bn254Fp, b = CURVE_B }, } pub type Fp = Bn254Fp; pub type Scalar = Bn254Scalar; pub type G1Affine = Bn254G1Affine; +pub use g2::G2Affine; impl Field for Fp { type SelfRef<'a> = &'a Self; @@ -145,6 +107,71 @@ impl CyclicGroup for G1Affine { }; } +// Define a G2Affine struct that implements curve operations using `Fp2` intrinsics +// but not special E(Fp2) intrinsics. +mod g2 { + use hex_literal::hex; + use openvm_algebra_guest::Field; + use openvm_ecc_guest::{ + impl_sw_affine, impl_sw_group_ops, weierstrass::WeierstrassPoint, AffinePoint, Group, + }; + + use super::{Fp, Fp2}; + + const THREE: Fp2 = Fp2::new(Fp::from_const_u8(3), Fp::ZERO); + // 3 / (9 + u) + const B: Fp2 = Fp2::new( + Fp::from_const_bytes(hex!( + "e538a124dce66732a3efdb59e5c5b4b5c36ae01b9918be81aeaab8ce409d142b" + )), + Fp::from_const_bytes(hex!( + "d215c38506bda2e452182de584a04fa7f4fdd8eeadaf2ccdd4fef03ab0139700" + )), + ); + impl_sw_affine!(G2Affine, Fp2, THREE, B); + impl_sw_group_ops!(G2Affine, Fp2); +} + +pub struct Bn254; + +impl Bn254 { + pub const FROBENIUS_COEFF_FQ6_C1: [Fp2; 3] = [ + Fp2 { + c0: Bn254Fp(hex!( + "9d0d8fc58d435dd33d0bc7f528eb780a2c4679786fa36e662fdf079ac1770a0e" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "3d556f175795e3990c33c3c210c38cb743b159f53cec0b4cf711794f9847b32f" + )), + c1: Bn254Fp(hex!( + "a2cb0f641cd56516ce9d7c0b1d2aae3294075ad78bcca44b20aeeb6150e5c916" + )), + }, + Fp2 { + c0: Bn254Fp(hex!( + "48fd7c60e544bde43d6e96bb9f068fc2b0ccace0e7d96d5e29a031e1724e6430" + )), + c1: Bn254Fp(hex!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + }, + ]; + + pub const XI_TO_Q_MINUS_1_OVER_2: Fp2 = Fp2 { + c0: Bn254Fp(hex!( + "5a13a071460154dc9859c9a9ede0aadbb9f9e2b698c65edcdcf59a4805f33c06" + )), + c1: Bn254Fp(hex!( + "e3b02326637fd382d25ba28fc97d80212b6f79eca7b504079a0441acbc3cc007" + )), + }; +} + impl IntrinsicCurve for Bn254 { type Scalar = Scalar; type Point = G1Affine; diff --git a/extensions/pairing/guest/src/bn254/tests.rs b/extensions/pairing/guest/src/bn254/tests.rs index 5baabb4ac1..8b1ffea3fe 100644 --- a/extensions/pairing/guest/src/bn254/tests.rs +++ b/extensions/pairing/guest/src/bn254/tests.rs @@ -5,12 +5,12 @@ use halo2curves_axiom::bn256::{ Fq, Fq12, Fq2, Fq6, G1Affine, G2Affine, G2Prepared, Gt, FROBENIUS_COEFF_FQ12_C1, }; use openvm_algebra_guest::{field::FieldExtension, IntMod}; -use openvm_ecc_guest::AffinePoint; +use openvm_ecc_guest::{weierstrass::WeierstrassPoint, AffinePoint}; use rand::{rngs::StdRng, SeedableRng}; use super::{Fp, Fp12, Fp2}; use crate::{ - bn254::Bn254, + bn254::{Bn254, G2Affine as OpenVmG2Affine}, pairing::{ fp2_invert_assign, fp6_invert_assign, fp6_square_assign, MultiMillerLoop, PairingIntrinsics, }, @@ -56,6 +56,13 @@ fn convert_bn254_fp12_to_halo2_fq12(x: Fp12) -> Fq12 { Fq12::from_coeffs(c.map(convert_bn254_fp2_to_halo2_fq2)) } +fn convert_g2_affine_halo2_to_openvm(p: G2Affine) -> OpenVmG2Affine { + OpenVmG2Affine::from_xy_unchecked( + convert_bn254_halo2_fq2_to_fp2(p.x), + convert_bn254_halo2_fq2_to_fp2(p.y), + ) +} + #[test] fn test_bn254_frobenius_coeffs() { #[allow(clippy::needless_range_loop)] @@ -245,3 +252,37 @@ fn test_bn254_miller_loop_identity_2() { let compare_miller = halo2curves_axiom::bn256::multi_miller_loop(&[(&h2c_p, &h2c_q_prepared)]); assert_miller_results_eq(compare_miller, f); } + +// test on host is enough since we are testing the curve formulas and not anything +// about intrinsic functions +#[test] +fn test_bn254_g2_affine() { + let mut rng = StdRng::seed_from_u64(34); + for _ in 0..10 { + let p = G2Affine::random(&mut rng); + let q = G2Affine::random(&mut rng); + let expected_add = G2Affine::from(p + q); + let expected_sub = G2Affine::from(p - q); + let expected_neg = -p; + let expected_double = G2Affine::from(p + p); + let [p, q] = [p, q].map(|p| { + let x = convert_bn254_halo2_fq2_to_fp2(p.x); + let y = convert_bn254_halo2_fq2_to_fp2(p.y); + // check on curve + OpenVmG2Affine::from_xy(x, y).unwrap() + }); + let r_add = &p + &q; + let r_sub = &p - &q; + let r_neg = -&p; + let r_double = &p + &p; + + for (expected, actual) in [ + (expected_add, r_add), + (expected_sub, r_sub), + (expected_neg, r_neg), + (expected_double, r_double), + ] { + assert_eq!(convert_g2_affine_halo2_to_openvm(expected), actual); + } + } +} diff --git a/extensions/pairing/guest/src/pairing/sextic_ext_field.rs b/extensions/pairing/guest/src/pairing/sextic_ext_field.rs index fdadde2127..c3318eed46 100644 --- a/extensions/pairing/guest/src/pairing/sextic_ext_field.rs +++ b/extensions/pairing/guest/src/pairing/sextic_ext_field.rs @@ -87,6 +87,7 @@ pub(crate) fn sextic_tower_mul_intrinsic( ); } +#[allow(dead_code)] #[cfg(not(target_os = "zkvm"))] pub(crate) fn sextic_tower_mul_host( lhs: &SexticExtField, From 1942aef9493a0e123bbf5ea00d0cf43fb3efe430 Mon Sep 17 00:00:00 2001 From: Kero Date: Tue, 24 Dec 2024 13:42:19 +0800 Subject: [PATCH 5/8] chore: simplify test code by reusing helper functions (#1128) * chore: reuse get_elf helper function * chore: simplify test using matches! --- crates/sdk/tests/integration_test.rs | 20 +++++++++---------- .../toolchain/tests/tests/transpiler_tests.rs | 8 ++------ 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/crates/sdk/tests/integration_test.rs b/crates/sdk/tests/integration_test.rs index c162a61517..7f174552e8 100644 --- a/crates/sdk/tests/integration_test.rs +++ b/crates/sdk/tests/integration_test.rs @@ -225,12 +225,11 @@ fn test_public_values_and_leaf_verification() { public_values_root_proof: Some(wrong_pv_root_proof), }, ); - match execution_result.err().unwrap() { - ExecutionError::Fail { .. } => {} - _ => { - panic!("Expected failure: the public value root proof has a wrong pv commit") - } - } + assert!( + matches!(execution_result, Err(ExecutionError::Fail { .. })), + "Expected failure: the public value root proof has a wrong pv commit: {:?}", + execution_result + ); } // Failure: The public value root proof has a wrong path proof. @@ -245,10 +244,11 @@ fn test_public_values_and_leaf_verification() { public_values_root_proof: Some(wrong_pv_root_proof), }, ); - match execution_result.err().unwrap() { - ExecutionError::Fail { .. } => {} - _ => panic!("Expected failure: the public value root proof has a wrong path proof"), - } + assert!( + matches!(execution_result, Err(ExecutionError::Fail { .. })), + "Expected failure: the public value root proof has a wrong path proof: {:?}", + execution_result + ); } } diff --git a/crates/toolchain/tests/tests/transpiler_tests.rs b/crates/toolchain/tests/tests/transpiler_tests.rs index da224dd14b..783b023ad3 100644 --- a/crates/toolchain/tests/tests/transpiler_tests.rs +++ b/crates/toolchain/tests/tests/transpiler_tests.rs @@ -49,9 +49,7 @@ fn get_elf(elf_path: impl AsRef) -> Result { // An "eyeball test" only: prints the decoded ELF for eyeball inspection #[test] fn test_decode_elf() -> Result<()> { - let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let data = read(dir.join("tests/data/rv32im-empty-program-elf"))?; - let elf = Elf::decode(&data, MEM_SIZE as u32)?; + let elf = get_elf("tests/data/rv32im-empty-program-elf")?; dbg!(elf); Ok(()) } @@ -62,9 +60,7 @@ fn test_decode_elf() -> Result<()> { #[test_case("tests/data/rv32im-fib-from-as")] #[test_case("tests/data/rv32im-intrin-from-as")] fn test_generate_program(elf_path: &str) -> Result<()> { - let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let data = read(dir.join(elf_path))?; - let elf = Elf::decode(&data, MEM_SIZE as u32)?; + let elf = get_elf(elf_path)?; let program = Transpiler::::default() .with_extension(Rv32ITranspilerExtension) .with_extension(Rv32MTranspilerExtension) From 62e0741d3cd0555d4ee5bf770d356b1cb158c766 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Tue, 24 Dec 2024 11:36:41 -0500 Subject: [PATCH 6/8] chore: fix bench workflow for forks (#1129) * chore: fix bench workflow for forks * Revert "chore: fix bench workflow for forks" We do want the PR commit and not the default rebased commit. This reverts commit affbf723707662df00d96589c21d5fe5ac582c3d. * chore: add repo name * fix: no benchmark_id --- .github/workflows/benchmark-call.yml | 5 +++-- .github/workflows/benchmarks.yml | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/benchmark-call.yml b/.github/workflows/benchmark-call.yml index 2d74817d8f..edd494878b 100644 --- a/.github/workflows/benchmark-call.yml +++ b/.github/workflows/benchmark-call.yml @@ -131,7 +131,8 @@ jobs: - uses: actions/checkout@v4 with: - ref: ${{ github.head_ref || github.ref }} + ref: ${{ github.event.pull_request.head.sha || github.sha }} + repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - uses: dtolnay/rust-toolchain@nightly - uses: Swatinem/rust-cache@v2 @@ -186,7 +187,7 @@ jobs: # When uploading to S3, use ${METRIC_NAME}-${current_sha}.[md/json] - name: Set metric name run: | - METRIC_NAME=${{ inputs.benchmark_id }} + METRIC_NAME=${{ inputs.benchmark_id || inputs.benchmark_name }} echo "METRIC_NAME=${METRIC_NAME}" >> $GITHUB_ENV METRIC_PATH=".bench_metrics/${METRIC_NAME}.json" echo "METRIC_PATH=${METRIC_PATH}" >> $GITHUB_ENV diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 9c6f912c15..0200eee9fe 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -23,6 +23,7 @@ env: CARGO_TERM_COLOR: always OPENVM_FAST_TEST: "1" CURRENT_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + REPO: ${{ github.event.pull_request.head.repo.full_name || github.repository }} CARGO_NET_GIT_FETCH_WITH_CLI: "true" permissions: @@ -39,7 +40,8 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ github.head_ref || github.ref }} + ref: ${{ CURRENT_SHA }} + repository: ${{ REPO }} - name: Create benchmark matrix from JSON id: create-matrix @@ -133,7 +135,8 @@ jobs: ########################################################################## - uses: actions/checkout@v4 with: - ref: ${{ github.head_ref || github.ref }} + ref: ${{ CURRENT_SHA }} + repository: ${{ REPO }} - name: Set github pages path for PR if: github.event_name == 'pull_request' From 78f572d9a0ead0d0d3893948f40065ebc634e5c0 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Tue, 24 Dec 2024 11:41:44 -0500 Subject: [PATCH 7/8] chore: add regex_execute binary for profiling (#1130) Updated readme with new ways to profile --- .gitignore | 4 +++ benchmarks/README.md | 33 ++++++++++++++++++++---- benchmarks/benches/fibonacci_execute.rs | 7 +---- benchmarks/benches/regex_execute.rs | 7 +---- benchmarks/examples/regex-elf | Bin 0 -> 643332 bytes benchmarks/examples/regex_execute.rs | 33 ++++++++++++++++++++++++ benchmarks/programs/regex/src/main.rs | 4 +-- 7 files changed, 69 insertions(+), 19 deletions(-) create mode 100755 benchmarks/examples/regex-elf create mode 100644 benchmarks/examples/regex_execute.rs diff --git a/.gitignore b/.gitignore index 21499d49aa..6c53953538 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,7 @@ __pycache__/ # Javascript **/node_modules/ + +# Profiling +**/flamegraph.svg +**/profile.json diff --git a/benchmarks/README.md b/benchmarks/README.md index d3bd2ba55a..2cf38918a1 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -90,18 +90,41 @@ Different labels can be added to provide more granularity on the metrics, but th Most benchmarks are binaries that run once since proving benchmarks take longer. For smaller benchmarks, such as to benchmark VM runtime, we use Criterion. These are in the `benches` directory. -### Usage - ```bash cargo bench --bench fibonacci_execute +cargo bench --bench regex_execute ``` will run the normal criterion benchmark. +## Profiling + +We profile using executables without criterion in [`examples`](./examples). To prevent the ELF build time from being included in the benchmark, we pre-build the ELF using the CLI. Check that the included ELF file in `examples` is up to date before proceeding. + +### Flamegraph + +To generate flamegraphs, install `cargo-flamegraph` and run: + +```bash +cargo flamegraph --example regex_execute --profile=profiling +``` + +will generate a flamegraph at `flamegraph.svg` without running any criterion analysis. +On MacOS, you will need to run the above command with `sudo`. + +### Samply + +To use [samply](https://github.com/mstange/samply), install it and then we must first build the executable. + ```bash -cargo bench --bench fibonacci_execute -- --profile-time=30 +cargo build --example regex_execute --profile=profiling ``` -will generate a flamegraph report without running any criterion analysis. +Then, run: + +```bash +samply record ../target/profiling/examples/regex_execute +``` -Flamegraph reports can be found in `target/criterion/fibonacci/execute/profile/flamegraph.svg` of the repo root directory. +It will open an interactive UI in your browser (currently only Firefox and Chrome are supported). +See the samply github page for more information. diff --git a/benchmarks/benches/fibonacci_execute.rs b/benchmarks/benches/fibonacci_execute.rs index 087bae0c73..7c7fdd1309 100644 --- a/benchmarks/benches/fibonacci_execute.rs +++ b/benchmarks/benches/fibonacci_execute.rs @@ -8,7 +8,6 @@ use openvm_rv32im_transpiler::{ use openvm_sdk::StdIn; use openvm_stark_sdk::p3_baby_bear::BabyBear; use openvm_transpiler::{transpiler::Transpiler, FromElf}; -use pprof::criterion::{Output, PProfProfiler}; fn benchmark_function(c: &mut Criterion) { let elf = build_bench_program("fibonacci").unwrap(); @@ -37,9 +36,5 @@ fn benchmark_function(c: &mut Criterion) { group.finish(); } -criterion_group! { - name = benches; - config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); - targets = benchmark_function -} +criterion_group!(benches, benchmark_function); criterion_main!(benches); diff --git a/benchmarks/benches/regex_execute.rs b/benchmarks/benches/regex_execute.rs index b4250d46ed..b6ae4dcd8d 100644 --- a/benchmarks/benches/regex_execute.rs +++ b/benchmarks/benches/regex_execute.rs @@ -9,7 +9,6 @@ use openvm_rv32im_transpiler::{ use openvm_sdk::StdIn; use openvm_stark_sdk::p3_baby_bear::BabyBear; use openvm_transpiler::{transpiler::Transpiler, FromElf}; -use pprof::criterion::{Output, PProfProfiler}; fn benchmark_function(c: &mut Criterion) { let elf = build_bench_program("regex").unwrap(); @@ -42,9 +41,5 @@ fn benchmark_function(c: &mut Criterion) { group.finish(); } -criterion_group! { - name = benches; - config = Criterion::default().with_profiler(PProfProfiler::new(10, Output::Flamegraph(None))); - targets = benchmark_function -} +criterion_group!(benches, benchmark_function); criterion_main!(benches); diff --git a/benchmarks/examples/regex-elf b/benchmarks/examples/regex-elf new file mode 100755 index 0000000000000000000000000000000000000000..277a0de283f406ffa10883ddb5b4182fedc71a8d GIT binary patch literal 643332 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

$-z>Wo6XF6#!9|lu~we87XOH!9tAEr5CswL@X~P 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::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(Keccak256TranspilerExtension), + ) + .unwrap(); + + let config = Keccak256Rv32Config::default(); + let executor = VmExecutor::::new(config); + + let data = include_str!("../programs/regex/regex_email.txt"); + + executor + .execute(exe.clone(), StdIn::from_bytes(data.as_bytes())) + .unwrap(); +} diff --git a/benchmarks/programs/regex/src/main.rs b/benchmarks/programs/regex/src/main.rs index b4a1b0aad2..63dceb0cff 100644 --- a/benchmarks/programs/regex/src/main.rs +++ b/benchmarks/programs/regex/src/main.rs @@ -7,14 +7,14 @@ use regex::Regex; openvm::entry!(main); -const pattern: &str = r"(?m)(\r\n|^)From:([^\r\n]+<)?(?P[^<>]+)>?"; +const PATTERN: &str = r"(?m)(\r\n|^)From:([^\r\n]+<)?(?P[^<>]+)>?"; pub fn main() { let data = openvm::io::read_vec(); let data = core::str::from_utf8(&data).expect("Invalid UTF-8"); // Compile the regex - let re = Regex::new(pattern).expect("Invalid regex"); + let re = Regex::new(PATTERN).expect("Invalid regex"); let caps = re.captures(data).expect("No match found."); let email = caps.name("email").expect("No email found."); From 74fac04032b985e58d784da5224d9fdc46fca69c Mon Sep 17 00:00:00 2001 From: alv-around <8678242+alv-around@users.noreply.github.com> Date: Tue, 24 Dec 2024 17:48:45 +0100 Subject: [PATCH 8/8] book: build sdk example (#1123) * build sdk example and link in doc * fix clippy error --- book/src/advanced-usage/sdk.md | 116 ++++--------------- crates/sdk/examples/sdk.rs | 130 ++++++++++++++++++++++ crates/sdk/{example => guest}/Cargo.toml | 0 crates/sdk/{example => guest}/src/main.rs | 0 crates/sdk/tests/integration_test.rs | 2 +- 5 files changed, 150 insertions(+), 98 deletions(-) create mode 100644 crates/sdk/examples/sdk.rs rename crates/sdk/{example => guest}/Cargo.toml (100%) rename crates/sdk/{example => guest}/src/main.rs (100%) diff --git a/book/src/advanced-usage/sdk.md b/book/src/advanced-usage/sdk.md index 2f0fb973d0..5e9ffe32a7 100644 --- a/book/src/advanced-usage/sdk.md +++ b/book/src/advanced-usage/sdk.md @@ -8,75 +8,35 @@ For more information on the basic CLI flow, see [Overview of Basic Usage](../wri If you have a guest program and would like to try running the **host program** specified below, you can do so by adding the following imports and setup at the top of the file. You may need to modify the imports and/or the `SomeStruct` struct to match your program. -```rust -use std::{fs, sync::Arc}; -use eyre::Result; -use openvm::platform::memory::MEM_SIZE; -use openvm_build::{GuestOptions, TargetFilter}; -use openvm_native_recursion::halo2::utils::CacheHalo2ParamsReader; -use openvm_sdk::{ - config::{AggConfig, AppConfig, SdkVmConfig}, - prover::AppProver, - Sdk, StdIn, -}; -use openvm_stark_sdk::config::FriParameters; -use openvm_transpiler::elf::Elf; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize)] -pub struct SomeStruct { - pub a: u64, - pub b: u64, -} +```rust,no_run,noplayground +{{ #include ../../../crates/sdk/examples/sdk.rs:dependencies }} ``` ## Building and Transpiling a Program The SDK provides lower-level control over the building and transpiling process. -```rust -// 1. Build the VmConfig with the extensions needed. -let sdk = Sdk; -let vm_config = SdkVmConfig::builder() - .system(Default::default()) - .rv32i(Default::default()) - .rv32m(Default::default()) - .io(Default::default()) - .build(); - -// 2a. Build the ELF with guest options and a target filter. -let target_path = "your_path_project_root"; -let guest_opts = GuestOptions::default(); -let target_filter = TargetFilter { - name: target_path.to_string(), - kind: "bin".to_string(), -}; -let elf = sdk.build(guest_opts, target_path, &Some(target_filter))?; -// 2b. Load the ELF from a file -let elf_bytes = fs::read("your_path_to_elf")?; -let elf = Elf::decode(&elf_bytes, MEM_SIZE as u32)?; - -// 3. Transpile the ELF into a VmExe -let exe = sdk.transpile(elf, vm_config.transpiler())?; +```rust,no_run,noplayground +{{ #include ../../../crates/sdk/examples/sdk.rs:build }} +{{ #include ../../../crates/sdk/examples/sdk.rs:read_elf}} + +{{ #include ../../../crates/sdk/examples/sdk.rs:transpilation }} ``` ### Using `SdkVmConfig` The `SdkVmConfig` struct allows you to specify the extensions and system configuration your VM will use. To customize your own configuration, you can use the `SdkVmConfig::builder()` method and set the extensions and system configuration you want. +```rust,no_run,noplayground +{{ #include ../../../crates/sdk/examples/sdk.rs:vm_config }} +``` + ## Running a Program To run your program and see the public value output, you can do the following: -```rust -// 4. Format your input into StdIn -let my_input = SomeStruct { a: 1, b: 2 }; // anything that can be serialized -let mut stdin = StdIn::default(); -stdin.write(&my_input); - -// 5. Run the program -let output = sdk.execute(exe.clone(), vm_config.clone(), stdin.clone())?; -println!("public values output: {:?}", output); +```rust,no_run,noplayground +{{ #include ../../../crates/sdk/examples/sdk.rs:execution }} ``` ### Using `StdIn` @@ -90,62 +50,24 @@ The `StdIn` struct allows you to format any serializable type into a VM-readable After building and transpiling a program, you can then generate a proof. To do so, you need to commit your `VmExe`, generate an `AppProvingKey`, format your input into `StdIn`, and then generate a proof. -```rust -// 6. Set app configuration -let app_log_blowup = 2; -let app_fri_params = FriParameters::standard_with_100_bits_conjectured_security(app_log_blowup); -let app_config = AppConfig::new(app_fri_params, vm_config); - -// 7. Commit the exe -let app_committed_exe = sdk.commit_app_exe(app_fri_params, exe)?; - -// 8. Generate an AppProvingKey -let app_pk = Arc::new(sdk.app_keygen(app_config)?); - -// 9a. Generate a proof -let proof = sdk.generate_app_proof(app_pk.clone(), app_committed_exe.clone(), stdin.clone())?; -// 9b. Generate a proof with an AppProver with custom fields -let app_prover = AppProver::new(app_pk.app_vm_pk.clone(), app_committed_exe.clone()) - .with_program_name("test_program"); -let proof = app_prover.generate_app_proof(stdin.clone()); +```rust,no_run,noplayground +{{ #include ../../../crates/sdk/examples/sdk.rs:proof_generation }} ``` ## Verifying Proofs After generating a proof, you can verify it. To do so, you need your verifying key (which you can get from your `AppProvingKey`) and the output of your `generate_app_proof` call. -```rust -// 10. Verify your program -let app_vk = app_pk.get_vk(); -sdk.verify_app_proof(&app_vk, &proof)?; +```rust,no_run,noplayground +{{ #include ../../../crates/sdk/examples/sdk.rs:verification }} ``` ## End-to-end EVM Proof Generation and Verification Generating and verifying an EVM proof is an extension of the above process. -```rust -// 11. Generate the aggregation proving key -const DEFAULT_PARAMS_DIR: &str = concat!(env!("HOME"), "/.openvm/params/"); -let halo2_params_reader = CacheHalo2ParamsReader::new(DEFAULT_PARAMS_DIR); -let agg_config = AggConfig::default(); -let agg_pk = sdk.agg_keygen(agg_config, &halo2_params_reader)?; - -// 12. Generate the SNARK verifier contract -let verifier = sdk.generate_snark_verifier_contract(&halo2_params_reader, &agg_pk)?; - -// 13. Generate an EVM proof -let proof = sdk.generate_evm_proof( - &halo2_params_reader, - app_pk, - app_committed_exe, - agg_pk, - stdin, -)?; - -// 14. Verify the EVM proof -let success = sdk.verify_evm_proof(&verifier, &proof); -assert!(success); +```rust,no_run,noplayground +{{ #include ../../../crates/sdk/examples/sdk.rs:evm_verification }} ``` > ⚠️ **WARNING** diff --git a/crates/sdk/examples/sdk.rs b/crates/sdk/examples/sdk.rs new file mode 100644 index 0000000000..588e8c8368 --- /dev/null +++ b/crates/sdk/examples/sdk.rs @@ -0,0 +1,130 @@ +// ANCHOR: dependencies +use std::{fs, sync::Arc}; + +use eyre::Result; +use openvm::platform::memory::MEM_SIZE; +use openvm_build::GuestOptions; +use openvm_native_recursion::halo2::utils::CacheHalo2ParamsReader; +use openvm_sdk::{ + config::{AggConfig, AppConfig, SdkVmConfig}, + prover::AppProver, + Sdk, StdIn, +}; +use openvm_stark_sdk::config::FriParameters; +use openvm_transpiler::elf::Elf; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct SomeStruct { + pub a: u64, + pub b: u64, +} +// ANCHOR_END: dependencies + +#[allow(dead_code, unused_variables)] +fn read_elf() -> Result<(), Box> { + // ANCHOR: read_elf + // 2b. Load the ELF from a file + let elf_bytes = fs::read("your_path_to_elf")?; + let elf = Elf::decode(&elf_bytes, MEM_SIZE as u32)?; + // ANCHOR_END: read_elf + Ok(()) +} + +#[allow(unused_variables, unused_doc_comments)] +fn main() -> Result<(), Box> { + // ANCHOR: vm_config + let vm_config = SdkVmConfig::builder() + .system(Default::default()) + .rv32i(Default::default()) + .rv32m(Default::default()) + .io(Default::default()) + .build(); + // ANCHOR_END: vm_config + + /// to import example guest code in crate replace `target_path` for: + /// ``` + /// use std::path::PathBuf; + /// + /// let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).to_path_buf(); + /// path.push("guest"); + /// let target_path = path.to_str().unwrap(); + /// ``` + // ANCHOR: build + // 1. Build the VmConfig with the extensions needed. + let sdk = Sdk; + + // 2a. Build the ELF with guest options and a target filter. + let guest_opts = GuestOptions::default(); + let target_path = "your_path_project_root"; + let elf = sdk.build(guest_opts, target_path, &Default::default())?; + // ANCHOR_END: build + + // ANCHOR: transpilation + // 3. Transpile the ELF into a VmExe + let exe = sdk.transpile(elf, vm_config.transpiler())?; + // ANCHOR_END: transpilation + + // ANCHOR: execution + // 4. Format your input into StdIn + let my_input = SomeStruct { a: 1, b: 2 }; // anything that can be serialized + let mut stdin = StdIn::default(); + stdin.write(&my_input); + + // 5. Run the program + let output = sdk.execute(exe.clone(), vm_config.clone(), stdin.clone())?; + println!("public values output: {:?}", output); + // ANCHOR_END: execution + + // ANCHOR: proof_generation + // 6. Set app configuration + let app_log_blowup = 2; + let app_fri_params = FriParameters::standard_with_100_bits_conjectured_security(app_log_blowup); + let app_config = AppConfig::new(app_fri_params, vm_config); + + // 7. Commit the exe + let app_committed_exe = sdk.commit_app_exe(app_fri_params, exe)?; + + // 8. Generate an AppProvingKey + let app_pk = Arc::new(sdk.app_keygen(app_config)?); + + // 9a. Generate a proof + let proof = sdk.generate_app_proof(app_pk.clone(), app_committed_exe.clone(), stdin.clone())?; + // 9b. Generate a proof with an AppProver with custom fields + let app_prover = AppProver::new(app_pk.app_vm_pk.clone(), app_committed_exe.clone()) + .with_program_name("test_program"); + let proof = app_prover.generate_app_proof(stdin.clone()); + // ANCHOR_END: proof_generation + + // ANCHOR: verification + // 10. Verify your program + let app_vk = app_pk.get_vk(); + sdk.verify_app_proof(&app_vk, &proof)?; + // ANCHOR_END: verification + + // ANCHOR: evm_verification + // 11. Generate the aggregation proving key + const DEFAULT_PARAMS_DIR: &str = concat!(env!("HOME"), "/.openvm/params/"); + let halo2_params_reader = CacheHalo2ParamsReader::new(DEFAULT_PARAMS_DIR); + let agg_config = AggConfig::default(); + let agg_pk = sdk.agg_keygen(agg_config, &halo2_params_reader)?; + + // 12. Generate the SNARK verifier contract + let verifier = sdk.generate_snark_verifier_contract(&halo2_params_reader, &agg_pk)?; + + // 13. Generate an EVM proof + let proof = sdk.generate_evm_proof( + &halo2_params_reader, + app_pk, + app_committed_exe, + agg_pk, + stdin, + )?; + + // 14. Verify the EVM proof + let success = sdk.verify_evm_proof(&verifier, &proof); + assert!(success); + // ANCHOR_END: evm_verification + + Ok(()) +} diff --git a/crates/sdk/example/Cargo.toml b/crates/sdk/guest/Cargo.toml similarity index 100% rename from crates/sdk/example/Cargo.toml rename to crates/sdk/guest/Cargo.toml diff --git a/crates/sdk/example/src/main.rs b/crates/sdk/guest/src/main.rs similarity index 100% rename from crates/sdk/example/src/main.rs rename to crates/sdk/guest/src/main.rs diff --git a/crates/sdk/tests/integration_test.rs b/crates/sdk/tests/integration_test.rs index 7f174552e8..c3727abf30 100644 --- a/crates/sdk/tests/integration_test.rs +++ b/crates/sdk/tests/integration_test.rs @@ -285,7 +285,7 @@ fn test_sdk_guest_build_and_transpile() { // .with_options(vec!["--release"]); ; let mut pkg_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).to_path_buf(); - pkg_dir.push("example"); + pkg_dir.push("guest"); let one = sdk .build(guest_opts.clone(), &pkg_dir, &Default::default()) .unwrap();