diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f416a33..871ea018 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: os: - ubuntu-latest channel: - - stable + # TODO: add custom stable checks for those parts of the workspace that can run on it - nightly runs-on: ${{ matrix.os }} steps: @@ -74,7 +74,7 @@ jobs: - ubuntu-latest - windows-latest channel: - - stable + # TODO: add custom stable checks for those parts of the workspace that can run on it - nightly runs-on: ${{ matrix.os }} steps: @@ -93,7 +93,7 @@ jobs: - ubuntu-latest - windows-latest channel: - - stable + # TODO: add custom stable checks for those parts of the workspace that can run on it - nightly runs-on: ${{ matrix.os }} steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a05497e..c759cf8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - [\#47](https://github.com/openzklib/openzl/pull/47) Migrate docs to main OpenZL repo - [\#36](https://github.com/openzklib/openzl/pull/36) Finish OpenZL migration feature requirements - [\#34](https://github.com/openzklib/openzl/pull/34) Migrate Poseidon from the Manta-Network codebase +- [\#14](https://github.com/openzklib/openzl/pull/14) Build initial Plonky2 plugin implementation with Poseidon integration - [\#4](https://github.com/openzklib/openzl/pull/4) Add CI and Relevant Contribution Files - [\#3](https://github.com/openzklib/openzl/pull/3) Migrate some of OpenZL from the Manta-Network codebase -- [\#4](https://github.com/openzklib/openzl/pull/4) Add CI and Relevant Contribution Files -- [\#34](https://github.com/openzklib/openzl/pull/34) Migrate Poseidon from the Manta-Network codebase ### Changed diff --git a/Cargo.toml b/Cargo.toml index b5400fda..9f2f2837 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,2 @@ [workspace] -members = ["eclair", "openzl", "openzl-*", "plugins/*"] +members = ["eclair", "examples/*", "openzl", "openzl-*", "plugins/*"] diff --git a/examples/ecdsa-wasm/Cargo.toml b/examples/ecdsa-wasm/Cargo.toml new file mode 100644 index 00000000..d9d28de8 --- /dev/null +++ b/examples/ecdsa-wasm/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "ecdsa-wasm" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] + +[dependencies] +getrandom = { version = "0.2.8", features = ["js"] } +openzl-plugin-plonky2 = { path = "../../plugins/plonky2", default-features = false, features = ["ecdsa"] } +wasm-bindgen = { version = "0.2.83" } + +[dev-dependencies] +instant = { version = "0.1.12", default-features = false, features = ["wasm-bindgen"] } +wasm-bindgen-test = { version = "0.3.33", default-features = false } +web-sys = { version = "0.3.59", default-features = false, features = ["console"] } diff --git a/examples/ecdsa-wasm/src/lib.rs b/examples/ecdsa-wasm/src/lib.rs new file mode 100644 index 00000000..51e3f354 --- /dev/null +++ b/examples/ecdsa-wasm/src/lib.rs @@ -0,0 +1,103 @@ +//! ECDSA WASM + +use openzl_plugin_plonky2::base::{ + ecdsa::{ + curve::{ + curve_types::{Curve, CurveScalar}, + ecdsa::{sign_message, ECDSAPublicKey, ECDSASecretKey}, + secp256k1::Secp256K1, + }, + gadgets::{ + curve::CircuitBuilderCurve, + ecdsa::{verify_message_circuit, ECDSAPublicKeyTarget, ECDSASignatureTarget}, + nonnative::CircuitBuilderNonNative, + }, + }, + field::{secp256k1_scalar::Secp256K1Scalar, types::Sample}, + iop::witness::PartialWitness, + plonk::{circuit_builder::CircuitBuilder, circuit_data::CircuitConfig, config::GenericConfig}, +}; + +/// Tests the proving and verification of the ECDSA signature circuit. +#[inline] +pub fn test_ecdsa_circuit(config: CircuitConfig) +where + C: GenericConfig, +{ + let pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + + let msg = Secp256K1Scalar::rand(); + let msg_target = builder.constant_nonnative(msg); + + let sk = ECDSASecretKey::(Secp256K1Scalar::rand()); + + let pk = ECDSAPublicKey((CurveScalar(sk.0) * Secp256K1::GENERATOR_PROJECTIVE).to_affine()); + let pk_target = ECDSAPublicKeyTarget(builder.constant_affine_point(pk.0)); + + let sig = sign_message(msg, sk); + let sig_target = ECDSASignatureTarget { + r: builder.constant_nonnative(sig.r), + s: builder.constant_nonnative(sig.s), + }; + + verify_message_circuit(&mut builder, msg_target, sig_target, pk_target); + + let data = builder.build::(); + let proof = data.prove(pw).expect("Proving failed."); + data.verify(proof).expect("Verification failed."); +} + +/// Testing Module +#[cfg(test)] +pub mod test { + use super::*; + use openzl_plugin_plonky2::base::plonk::config::PoseidonGoldilocksConfig; + use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; + use web_sys::console; + + wasm_bindgen_test_configure!(run_in_browser); + + /// Defines a benchmark over `f` with `REPEAT` repetitions. + #[inline] + fn bench(mut f: F, label: &str) + where + F: FnMut(), + { + let start_time = instant::Instant::now(); + for _ in 0..REPEAT { + f(); + } + let end_time = instant::Instant::now(); + console::log_1( + &format!( + "{:?} Performance: {:?}", + label, + ((end_time - start_time) / REPEAT as u32) + ) + .into(), + ); + } + + /// Runs the ECDSA benchmark for a narrow circuit configuration. + #[wasm_bindgen_test] + pub fn bench_ecdsa_circuit_narrow() { + bench::<_, 3>( + || { + test_ecdsa_circuit::( + CircuitConfig::standard_ecc_config(), + ) + }, + "Bench ECDSA Narrow", + ) + } + + /// Runs the ECDSA benchmark for a narrow circuit configuration. + #[wasm_bindgen_test] + pub fn bench_ecdsa_circuit_wide() { + bench::<_, 3>( + || test_ecdsa_circuit::(CircuitConfig::wide_ecc_config()), + "Bench ECDSA Wide", + ) + } +} diff --git a/openzl-crypto/src/accumulator.rs b/openzl-crypto/src/accumulator.rs index 1ddda1dd..acd6294d 100644 --- a/openzl-crypto/src/accumulator.rs +++ b/openzl-crypto/src/accumulator.rs @@ -358,7 +358,7 @@ pub mod test { .collect::>(); for (i, x) in outputs.iter().enumerate() { for (j, y) in outputs.iter().enumerate().skip(i + 1) { - assert_ne!(x, y, "Found matching outputs at {i:?} and {j:?}.") + assert_ne!(x, y, "Found matching outputs at {i:?} and {j:?}."); } } } diff --git a/openzl-crypto/src/poseidon/hash.rs b/openzl-crypto/src/poseidon/hash.rs index 13f4a2e9..f4eefe73 100644 --- a/openzl-crypto/src/poseidon/hash.rs +++ b/openzl-crypto/src/poseidon/hash.rs @@ -86,8 +86,8 @@ where /// Builds a new [`Hasher`] over `permutation` using `T` to generate the domain tag. #[inline] - pub fn from_permutation(permutation: Permutation) -> Self { - Self::new(permutation, S::from_parameter(T::domain_tag())) + pub fn from_permutation(permutation: Permutation, compiler: &mut COM) -> Self { + Self::new(permutation, S::from_parameter(T::domain_tag(), compiler)) } /// Computes the hash over `input` in the given `compiler` and returns the untruncated state. @@ -116,7 +116,7 @@ where #[inline] fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { - Self::from_permutation(this.permutation.as_constant(compiler)) + Self::from_permutation(this.permutation.as_constant(compiler), compiler) } } @@ -173,10 +173,10 @@ where } } -impl Sample for Hasher +impl Sample for Hasher where - S: Specification, - Permutation: Sample, + S: Specification, + Permutation: Sample, S::ParameterField: NativeField + FieldGeneration, T: DomainTag, { @@ -185,6 +185,6 @@ where where R: RngCore + ?Sized, { - Self::from_permutation(rng.sample(distribution)) + Self::from_permutation(rng.sample(distribution), &mut ()) } } diff --git a/openzl-crypto/src/poseidon/mds.rs b/openzl-crypto/src/poseidon/mds.rs index 3bdaabf4..8af6d6f3 100644 --- a/openzl-crypto/src/poseidon/mds.rs +++ b/openzl-crypto/src/poseidon/mds.rs @@ -85,13 +85,13 @@ where where F: FieldGeneration, { - let ys: Vec = (t as u64..2 * t as u64).map(F::from_u64).collect(); + let ys: Vec = (t as u32..2 * t as u32).map(F::from_u32).collect(); // TODO: Change u64 to integer mod order(F) SquareMatrix::new_unchecked(Matrix::new_unchecked( - (0..t as u64) + (0..t as u32) .map(|x| { ys.iter() .map(|y| { - F::add(&F::from_u64(x), y) + F::add(&F::from_u32(x), y) .inverse() .expect("`x+y` is invertible.") }) diff --git a/openzl-crypto/src/poseidon/mod.rs b/openzl-crypto/src/poseidon/mod.rs index 6c63a28f..8c5861e4 100644 --- a/openzl-crypto/src/poseidon/mod.rs +++ b/openzl-crypto/src/poseidon/mod.rs @@ -66,8 +66,8 @@ pub trait FieldGeneration { /// Number of bits of modulus of the field. const MODULUS_BITS: usize; - /// Converts a `u64` value to a field element. - fn from_u64(elem: u64) -> Self; + /// Converts a `u32` value to a field element. + fn from_u32(elem: u32) -> Self; /// Converts from `bits` into a field element in big endian order, returning `None` if `bits` /// are out of range. @@ -112,6 +112,16 @@ pub trait Constants { const ADDITIVE_ROUND_KEYS_COUNT: usize = Self::ROUNDS * Self::WIDTH; } +/// Poseidon S-Box Exponent +/// +/// For Poseidon implementations that use an `x^n` s-box with `n > 0`, this helper `trait` can be +/// used to mark the exponent `n` for convenience. For `n = -1` the s-box "power" needs to be +/// specified in the [`Specification::apply_sbox`] method. +pub trait SBoxExponent { + /// S-Box Exponent Value + const SBOX_EXPONENT: u64; +} + /// Parameter Field Type #[component] pub type ParameterField; @@ -140,7 +150,7 @@ pub trait Field: ParameterFieldType { fn add_const_assign(lhs: &mut Self::Field, rhs: &Self::ParameterField, compiler: &mut COM); /// Converts a constant parameter `point` for permutation state. - fn from_parameter(point: Self::ParameterField) -> Self::Field; + fn from_parameter(point: Self::ParameterField, compiler: &mut COM) -> Self::Field; } /// Poseidon Permutation Specification diff --git a/openzl-tutorials/src/poseidon.rs b/openzl-tutorials/src/poseidon.rs index 6032b619..3312855f 100644 --- a/openzl-tutorials/src/poseidon.rs +++ b/openzl-tutorials/src/poseidon.rs @@ -311,7 +311,7 @@ pub mod bls12_381 { impl FieldGeneration for BlsScalar { const MODULUS_BITS: usize = 255; - fn from_u64(elem: u64) -> Self { + fn from_u32(elem: u32) -> Self { Self(Fr::from(elem)) } diff --git a/plugins/arkworks/src/poseidon/mod.rs b/plugins/arkworks/src/poseidon/mod.rs index 908e95ec..c72a2583 100644 --- a/plugins/arkworks/src/poseidon/mod.rs +++ b/plugins/arkworks/src/poseidon/mod.rs @@ -9,8 +9,9 @@ use core::marker::PhantomData; use eclair::alloc::Constant; use openzl_crypto::poseidon::{ self, encryption::BlockElement, hash::DomainTag, Constants, FieldGeneration, NativeField, - ParameterFieldType, + ParameterFieldType, SBoxExponent, }; +use openzl_util::derivative; #[cfg(test)] pub mod test; @@ -19,12 +20,9 @@ pub mod test; type Compiler = R1CS<::Field>; /// Poseidon Permutation Specification. -pub trait Specification: Constants { +pub trait Specification: Constants + SBoxExponent { /// Field Type type Field: PrimeField; - - /// S-BOX Exponenet - const SBOX_EXPONENT: u64; } impl NativeField for Fp @@ -84,7 +82,7 @@ where } #[inline] - fn from_u64(elem: u64) -> Self { + fn from_u32(elem: u32) -> Self { Self(F::from(elem)) } } @@ -143,19 +141,16 @@ where } /// Poseidon Specification Configuration -#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Spec(PhantomData) -where - F: PrimeField; +#[derive(derivative::Derivative)] +#[derivative(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Spec(PhantomData); impl Specification for Spec where F: PrimeField, - Self: poseidon::Constants, + Self: poseidon::Constants + SBoxExponent, { type Field = F; - - const SBOX_EXPONENT: u64 = 5; } impl Constant for Spec @@ -217,7 +212,7 @@ where } #[inline] - fn from_parameter(point: Self::ParameterField) -> Self::Field { + fn from_parameter(point: Self::ParameterField, _: &mut ()) -> Self::Field { point } } @@ -268,7 +263,7 @@ where } #[inline] - fn from_parameter(point: Self::ParameterField) -> Self::Field { + fn from_parameter(point: Self::ParameterField, _: &mut Compiler) -> Self::Field { FpVar::Constant(point.0) } } @@ -297,6 +292,10 @@ where } } +impl SBoxExponent for Spec { + const SBOX_EXPONENT: u64 = 5; +} + impl poseidon::Constants for Spec { const WIDTH: usize = 3; const FULL_ROUNDS: usize = 8; diff --git a/plugins/plonky2/Cargo.toml b/plugins/plonky2/Cargo.toml new file mode 100644 index 00000000..639f6089 --- /dev/null +++ b/plugins/plonky2/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "openzl-plugin-plonky2" +version = "0.0.0" +edition = "2021" +readme = "README.md" +license = "MIT OR Apache-2.0" +repository = "https://github.com/openzklib/openzl" +homepage = "https://openzl.org" +documentation = "https://docs.rs/openzl-plugin-plonky2" +categories = [""] +keywords = [""] +description = "Open ZL Plonky2 Plugin" + +[package.metadata.docs.rs] +# To build locally: +# RUSTDOCFLAGS="--cfg doc_cfg" cargo +nightly doc --all-features --open +all-features = true +rustdoc-args = ["--cfg", "doc_cfg"] + +[badges] +is-it-maintained-issue-resolution = { repository = "openzklib/openzl" } +is-it-maintained-open-issues = { repository = "openzklib/openzl" } +maintenance = { status = "actively-developed" } + +[features] +# ECDSA Library +ecdsa = ["plonky2_ecdsa"] + +# Standard Library +std = [ + "anyhow/std", + "num-bigint/std", + "openzl-crypto/std", + "openzl-util/std", + "plonky2/std", + "starky/std" +] + +[dependencies] +anyhow = { version = "1.0.66", default-features = false } +eclair = { path = "../../eclair", default-features = false, features = ["alloc"] } +num-bigint = { version = "0.4.3", default-features = false } +openzl-crypto = { path = "../../openzl-crypto", default-features = false, features = ["alloc"] } +openzl-util = { path = "../../openzl-util", default-features = false, features = ["alloc"] } +plonky2 = { git = "https://github.com/openzklib/plonky2", branch = "feat/circuit-data-serde", default-features = false } +plonky2_ecdsa = { git = "https://github.com/openzklib/plonky2", branch = "feat/circuit-data-serde", optional = true, default-features = false } +starky = { git = "https://github.com/openzklib/plonky2", branch = "feat/circuit-data-serde", default-features = false } diff --git a/plugins/plonky2/src/base.rs b/plugins/plonky2/src/base.rs new file mode 100644 index 00000000..ee2aa5bd --- /dev/null +++ b/plugins/plonky2/src/base.rs @@ -0,0 +1,8 @@ +//! Plonky2 Base Library + +#[doc(inline)] +pub use plonky2::*; + +#[cfg(feature = "ecdsa")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "ecdsa")))] +pub use plonky2_ecdsa as ecdsa; diff --git a/plugins/plonky2/src/bool.rs b/plugins/plonky2/src/bool.rs new file mode 100644 index 00000000..a2a8f5f3 --- /dev/null +++ b/plugins/plonky2/src/bool.rs @@ -0,0 +1,136 @@ +//! Plonky2 Booleans + +use crate::compiler::Compiler; +use core::marker::PhantomData; +use eclair::{ + alloc::{ + mode::{Public, Secret}, + Constant, Variable, + }, + bool::Assert, + cmp::PartialEq, + ops::{BitAnd, Not}, + Has, +}; +use plonky2::{field::extension::Extendable, hash::hash_types::RichField, iop::target::BoolTarget}; + +/// Boolean Type +pub struct Bool { + /// Target + pub target: BoolTarget, + + /// Type Parameter Marker + __: PhantomData, +} + +impl Bool { + /// Builds a new [`Bool`] variable from a `target`. + #[inline] + pub fn new(target: BoolTarget) -> Self { + Self { + target, + __: PhantomData, + } + } +} + +impl Has for Compiler +where + F: RichField + Extendable, +{ + type Type = Bool; +} + +impl Assert for Compiler +where + F: RichField + Extendable, +{ + #[inline] + fn assert(&mut self, b: &Bool) { + self.builder.assert_bool(b.target) + } +} + +impl Constant> for Bool +where + F: RichField + Extendable, +{ + type Type = bool; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut Compiler) -> Self { + Self::new(compiler.builder.constant_bool(*this)) + } +} + +impl Variable> for Bool +where + F: RichField + Extendable, +{ + type Type = bool; + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut Compiler) -> Self { + Self::new(compiler.add_bool_target(*this)) + } + + #[inline] + fn new_unknown(compiler: &mut Compiler) -> Self { + Self::new(compiler.add_virtual_bool_target()) + } +} + +impl Variable> for Bool +where + F: RichField + Extendable, +{ + type Type = bool; + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut Compiler) -> Self { + Self::new(compiler.add_public_bool_target(*this)) + } + + #[inline] + fn new_unknown(compiler: &mut Compiler) -> Self { + Self::new(compiler.add_virtual_public_bool_target()) + } +} + +impl BitAnd> for Bool +where + F: RichField + Extendable, +{ + type Output = Self; + + #[inline] + fn bitand(self, rhs: Self, compiler: &mut Compiler) -> Self::Output { + Self::new(compiler.builder.and(self.target, rhs.target)) + } +} + +impl Not> for Bool +where + F: RichField + Extendable, +{ + type Output = Self; + + #[inline] + fn not(self, compiler: &mut Compiler) -> Self::Output { + Self::new(compiler.builder.not(self.target)) + } +} + +impl PartialEq> for Bool +where + F: RichField + Extendable, +{ + #[inline] + fn eq(&self, rhs: &Self, compiler: &mut Compiler) -> Self { + Bool::new( + compiler + .builder + .is_equal(self.target.target, rhs.target.target), + ) + } +} diff --git a/plugins/plonky2/src/compiler.rs b/plugins/plonky2/src/compiler.rs new file mode 100644 index 00000000..506617af --- /dev/null +++ b/plugins/plonky2/src/compiler.rs @@ -0,0 +1,225 @@ +//! Plonky2 Compiler + +use alloc::vec::Vec; +use core::marker::PhantomData; +use openzl_crypto::constraint::ProofSystem; +use openzl_util::rand::{CryptoRng, RngCore}; +use plonky2::{ + field::extension::Extendable, + hash::hash_types::RichField, + iop::{ + target::{BoolTarget, Target}, + witness::{PartialWitness, WitnessWrite}, + }, + plonk::{ + circuit_builder::CircuitBuilder, + circuit_data::{CircuitData, ProverCircuitData, VerifierCircuitData}, + config::GenericConfig, + proof, + proof::ProofWithPublicInputs, + }, +}; + +/// Compiler +pub struct Compiler +where + F: RichField + Extendable, +{ + /// Circuit Builder + pub builder: CircuitBuilder, + + /// Partial Witness + pub partial_witness: PartialWitness, +} + +impl Compiler +where + F: RichField + Extendable, +{ + /// Builds a new [`Compiler`] using `builder` and `partial_witness`. + #[inline] + pub fn new(builder: CircuitBuilder, partial_witness: PartialWitness) -> Self { + Self { + builder, + partial_witness, + } + } + + /// Sets the `target` to the `value` in the partial witness. + #[inline] + fn set_target(&mut self, target: Target, value: F) { + self.partial_witness.set_target(target, value); + } + + /// Returns a new virtual target. + #[inline] + pub fn add_virtual_target(&mut self) -> Target { + self.builder.add_virtual_target() + } + + /// Returns a new virtual public target. + #[inline] + pub fn add_virtual_public_target(&mut self) -> Target { + let target = self.add_virtual_target(); + self.builder.register_public_input(target); + target + } + + /// Returns a new target assigned to `value`. + #[inline] + pub fn add_target(&mut self, value: F) -> Target { + let target = self.add_virtual_target(); + self.set_target(target, value); + target + } + + /// Returns a new public target assigned to `value`. + #[inline] + pub fn add_public_target(&mut self, value: F) -> Target { + let target = self.add_target(value); + self.builder.register_public_input(target); + target + } + + /// Sets the boolean `target` to the `value` in the partial witness. + #[inline] + fn set_bool_target(&mut self, target: BoolTarget, value: bool) { + self.partial_witness.set_bool_target(target, value); + } + + /// Returns a new virtual boolean target. + #[inline] + pub fn add_virtual_bool_target(&mut self) -> BoolTarget { + self.builder.add_virtual_bool_target_safe() + } + + /// Returns a new virtual public boolean target. + #[inline] + pub fn add_virtual_public_bool_target(&mut self) -> BoolTarget { + let target = self.add_virtual_bool_target(); + self.builder.register_public_input(target.target); + target + } + + /// Returns a new boolean target assigned to `value`. + #[inline] + pub fn add_bool_target(&mut self, value: bool) -> BoolTarget { + let target = self.add_virtual_bool_target(); + self.set_bool_target(target, value); + target + } + + /// Returns a new public boolean target assigned to `value`. + #[inline] + pub fn add_public_bool_target(&mut self, value: bool) -> BoolTarget { + let target = self.add_bool_target(value); + self.builder.register_public_input(target.target); + target + } +} + +impl Default for Compiler +where + F: RichField + Extendable, +{ + #[inline] + fn default() -> Self { + Self::new(CircuitBuilder::new(Default::default()), Default::default()) + } +} + +/// Proving Context +pub struct ProvingContext(ProverCircuitData) +where + C: GenericConfig; + +/// Verifying Context +pub struct VerifyingContext(VerifierCircuitData) +where + C: GenericConfig; + +/// Proof +pub struct Proof(proof::Proof) +where + C: GenericConfig; + +/// Plonky2 Proving System +pub struct Plonky2(PhantomData) +where + C: GenericConfig; + +impl ProofSystem for Plonky2 +where + C: GenericConfig, +{ + type Compiler = Compiler; + type PublicParameters = (); + type ProvingContext = ProvingContext; + type VerifyingContext = VerifyingContext; + type Input = Vec; + type Proof = Proof; + type Error = anyhow::Error; + + #[inline] + fn context_compiler() -> Self::Compiler { + Default::default() + } + + #[inline] + fn proof_compiler() -> Self::Compiler { + Default::default() + } + + #[inline] + fn compile( + public_parameters: &Self::PublicParameters, + compiler: Self::Compiler, + rng: &mut R, + ) -> Result<(Self::ProvingContext, Self::VerifyingContext), Self::Error> + where + R: CryptoRng + RngCore + ?Sized, + { + let _ = (public_parameters, rng); + let CircuitData { + prover_only, + verifier_only, + common, + } = compiler.builder.build::(); + Ok(( + ProvingContext(ProverCircuitData { + prover_only, + common: common.clone(), + }), + VerifyingContext(VerifierCircuitData { + verifier_only, + common, + }), + )) + } + + #[inline] + fn prove( + context: &Self::ProvingContext, + compiler: Self::Compiler, + rng: &mut R, + ) -> Result + where + R: CryptoRng + RngCore + ?Sized, + { + let _ = rng; + Ok(Proof(context.0.prove(compiler.partial_witness)?.proof)) + } + + #[inline] + fn verify( + context: &Self::VerifyingContext, + input: &Self::Input, + proof: &Self::Proof, + ) -> Result { + context.0.verify(ProofWithPublicInputs { + proof: proof.0.clone(), + public_inputs: input.clone(), + })?; + Ok(true) + } +} diff --git a/plugins/plonky2/src/field.rs b/plugins/plonky2/src/field.rs new file mode 100644 index 00000000..51e53c54 --- /dev/null +++ b/plugins/plonky2/src/field.rs @@ -0,0 +1,228 @@ +//! Plonky2 Field Elements + +use crate::{bool::Bool, compiler::Compiler}; +use core::marker::PhantomData; +use eclair::{ + alloc::{ + mode::{Public, Secret}, + Constant, Variable, + }, + bool::ConditionalSelect, + cmp::PartialEq, + num::{One, Zero}, + ops::{Add, Div, Mul, Neg, Sub}, +}; +use openzl_util::derivative; + +pub use plonky2::{field::extension::Extendable, hash::hash_types::RichField, iop::target::Target}; + +/// +pub struct Fp(pub F); + +/// Variable Field Type +#[derive(derivative::Derivative)] +#[derivative(Clone, Copy)] +pub struct Field { + /// Target + pub target: Target, + + /// Type Parameter Marker + __: PhantomData, +} + +impl Field { + /// Builds a new [`Field`] variable from a `target`. + #[inline] + pub fn new(target: Target) -> Self { + Self { + target, + __: PhantomData, + } + } +} + +impl Constant> for Field +where + F: RichField + Extendable, +{ + type Type = F; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut Compiler) -> Self { + Self::new(compiler.builder.constant(*this)) + } +} + +impl Variable> for Field +where + F: RichField + Extendable, +{ + type Type = F; + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut Compiler) -> Self { + Self::new(compiler.add_target(*this)) + } + + #[inline] + fn new_unknown(compiler: &mut Compiler) -> Self { + Self::new(compiler.add_virtual_target()) + } +} + +impl Variable> for Field +where + F: RichField + Extendable, +{ + type Type = F; + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut Compiler) -> Self { + Self::new(compiler.add_public_target(*this)) + } + + #[inline] + fn new_unknown(compiler: &mut Compiler) -> Self { + Self::new(compiler.add_virtual_public_target()) + } +} + +impl Add> for Field +where + F: RichField + Extendable, +{ + type Output = Self; + + #[inline] + fn add(self, rhs: Self, compiler: &mut Compiler) -> Self::Output { + Self::new(compiler.builder.add(self.target, rhs.target)) + } +} + +impl Add> for Field +where + F: RichField + Extendable, +{ + type Output = Self; + + #[inline] + fn add(self, rhs: F, compiler: &mut Compiler) -> Self::Output { + Self::new(compiler.builder.add_const(self.target, rhs)) + } +} + +impl Sub> for Field +where + F: RichField + Extendable, +{ + type Output = Self; + + #[inline] + fn sub(self, rhs: Self, compiler: &mut Compiler) -> Self::Output { + Self::new(compiler.builder.sub(self.target, rhs.target)) + } +} + +impl Mul> for Field +where + F: RichField + Extendable, +{ + type Output = Self; + + #[inline] + fn mul(self, rhs: Self, compiler: &mut Compiler) -> Self::Output { + Self::new(compiler.builder.mul(self.target, rhs.target)) + } +} + +impl Mul> for Field +where + F: RichField + Extendable, +{ + type Output = Self; + + #[inline] + fn mul(self, rhs: F, compiler: &mut Compiler) -> Self::Output { + Self::new(compiler.builder.mul_const(rhs, self.target)) + } +} + +impl Div> for Field +where + F: RichField + Extendable, +{ + type Output = Self; + + #[inline] + fn div(self, rhs: Self, compiler: &mut Compiler) -> Self::Output { + Self::new(compiler.builder.div(self.target, rhs.target)) + } +} + +impl Neg> for Field +where + F: RichField + Extendable, +{ + type Output = Self; + + #[inline] + fn neg(self, compiler: &mut Compiler) -> Self::Output { + Self::new(compiler.builder.neg(self.target)) + } +} + +impl PartialEq> for Field +where + F: RichField + Extendable, +{ + #[inline] + fn eq(&self, rhs: &Self, compiler: &mut Compiler) -> Bool { + Bool::new(compiler.builder.is_equal(self.target, rhs.target)) + } +} + +impl ConditionalSelect> for Field +where + F: RichField + Extendable, +{ + #[inline] + fn select(bit: &Bool, lhs: &Self, rhs: &Self, compiler: &mut Compiler) -> Self { + Self::new(compiler.builder.select(bit.target, lhs.target, rhs.target)) + } +} + +impl Zero> for Field +where + F: RichField + Extendable, +{ + type Verification = Bool; + + #[inline] + fn zero(compiler: &mut Compiler) -> Self { + Self::new(compiler.builder.zero()) + } + + #[inline] + fn is_zero(&self, compiler: &mut Compiler) -> Self::Verification { + // TODO: is there a better choice here? + self.eq(&Self::zero(compiler), compiler) + } +} + +impl One> for Field +where + F: RichField + Extendable, +{ + type Verification = Bool; + + #[inline] + fn one(compiler: &mut Compiler) -> Self { + Self::new(compiler.builder.one()) + } + + #[inline] + fn is_one(&self, compiler: &mut Compiler) -> Self::Verification { + // TODO: is there a better choice here? + self.eq(&Self::one(compiler), compiler) + } +} diff --git a/plugins/plonky2/src/lib.rs b/plugins/plonky2/src/lib.rs new file mode 100644 index 00000000..ccb8853a --- /dev/null +++ b/plugins/plonky2/src/lib.rs @@ -0,0 +1,20 @@ +//! Plonky2 OpenZL Plugin + +#![cfg_attr(not(any(feature = "std", test)), no_std)] +#![cfg_attr(doc_cfg, feature(doc_cfg))] +#![forbid(rustdoc::broken_intra_doc_links)] +#![forbid(missing_docs)] + +extern crate alloc; + +pub mod base; +pub mod bool; +pub mod compiler; +pub mod field; +pub mod poseidon; + +#[doc(inline)] +pub use num_bigint; + +#[doc(inline)] +pub use starky; diff --git a/plugins/plonky2/src/poseidon/mod.rs b/plugins/plonky2/src/poseidon/mod.rs new file mode 100644 index 00000000..4aded4fe --- /dev/null +++ b/plugins/plonky2/src/poseidon/mod.rs @@ -0,0 +1,346 @@ +//! Poseidon Plonky2 Backend + +use crate::{ + base::{field::goldilocks_field::GoldilocksField, util::bits_u64}, + compiler::Compiler as PlonkCompiler, + field::{Extendable, Field, Fp, RichField}, +}; +use alloc::vec::Vec; +use core::marker::PhantomData; +use eclair::{ + alloc::Constant, + ops::{Add, Mul, Sub}, +}; +use num_bigint::BigUint; +use openzl_crypto::poseidon::{ + self, encryption::BlockElement, hash::DomainTag, Constants, FieldGeneration, NativeField, + ParameterFieldType, SBoxExponent, +}; +use openzl_util::derivative; +use plonky2::field::types::Field as _; + +/// Compiler Type. +type Compiler = PlonkCompiler<::Field, D>; + +/// Poseidon Permutation Specification. +pub trait Specification: Constants + SBoxExponent { + /// Field Type + type Field: RichField; +} + +impl NativeField for Fp +where + F: RichField, +{ + #[inline] + fn zero() -> Self { + Self(F::ZERO) + } + + #[inline] + fn is_zero(&self) -> bool { + self.0 == F::ZERO + } + + #[inline] + fn one() -> Self { + Self(F::ONE) + } + + #[inline] + fn add(&self, rhs: &Self) -> Self { + Self(self.0 + rhs.0) + } + + #[inline] + fn add_assign(&mut self, rhs: &Self) { + self.0 += rhs.0; + } + + #[inline] + fn sub(&self, rhs: &Self) -> Self { + Self(self.0 - rhs.0) + } + + #[inline] + fn mul(&self, rhs: &Self) -> Self { + Self(self.0 * rhs.0) + } + + #[inline] + fn inverse(&self) -> Option { + self.0.try_inverse().map(Self) + } +} + +impl FieldGeneration for Fp +where + F: RichField, +{ + const MODULUS_BITS: usize = F::BITS; + + #[inline] + fn try_from_bits_be(bits: &[bool]) -> Option { + let mut bytes = Vec::new(); + let mut acc: u8 = 0; + let mut bits = bits.to_vec(); + bits.reverse(); + for bits8 in bits.chunks(8) { + for bit in bits8.iter().rev() { + acc <<= 1; + acc += *bit as u8; + } + bytes.push(acc); + acc = 0; + } + Some(Self(F::from_noncanonical_biguint(BigUint::from_bytes_be( + &bytes, + )))) + } + + #[inline] + fn from_u32(elem: u32) -> Self { + Self(F::from_canonical_u32(elem)) + } +} + +impl BlockElement for Fp +where + F: RichField, +{ + #[inline] + fn add(&self, rhs: &Self, _: &mut ()) -> Self { + Self(self.0 + rhs.0) + } + + #[inline] + fn sub(&self, rhs: &Self, _: &mut ()) -> Self { + Self(self.0 - rhs.0) + } +} + +impl BlockElement> for Field +where + F: RichField + Extendable, +{ + #[inline] + fn add(&self, rhs: &Self, compiler: &mut PlonkCompiler) -> Self { + Add::add(*self, *rhs, compiler) + } + + #[inline] + fn sub(&self, rhs: &Self, compiler: &mut PlonkCompiler) -> Self { + Sub::sub(*self, *rhs, compiler) + } +} + +/// Domain tag as 2^arity - 1 +pub struct TwoPowerMinusOneDomainTag; + +impl Constant for TwoPowerMinusOneDomainTag { + type Type = Self; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { + let _ = (this, compiler); + Self + } +} + +impl DomainTag for TwoPowerMinusOneDomainTag +where + S: Specification + ParameterFieldType>, + S::Field: From, +{ + #[inline] + fn domain_tag() -> Fp<::Field> { + Fp(S::Field::from(((1 << (S::WIDTH - 1)) - 1) as u32)) + } +} + +/// Poseidon Specification Configuration +#[derive(derivative::Derivative)] +#[derivative(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Spec(PhantomData); + +impl Specification for Spec +where + F: RichField + Extendable, + Self: poseidon::Constants + SBoxExponent, +{ + type Field = F; +} + +impl Constant for Spec +where + F: RichField + Extendable, +{ + type Type = Self; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut COM) -> Self { + let _ = (this, compiler); + Self(PhantomData) + } +} + +impl ParameterFieldType for Spec +where + Self: Specification, + F: RichField + Extendable, +{ + type ParameterField = Fp<::Field>; +} + +impl poseidon::Field for Spec +where + Self: Specification, + F: RichField + Extendable, +{ + type Field = Fp<::Field>; + + #[inline] + fn add(lhs: &Self::Field, rhs: &Self::Field, _: &mut ()) -> Self::Field { + Fp(lhs.0 + rhs.0) + } + + #[inline] + fn add_const(lhs: &Self::Field, rhs: &Self::ParameterField, _: &mut ()) -> Self::Field { + Fp(lhs.0 + rhs.0) + } + + #[inline] + fn mul(lhs: &Self::Field, rhs: &Self::Field, _: &mut ()) -> Self::Field { + Fp(lhs.0 * rhs.0) + } + + #[inline] + fn mul_const(lhs: &Self::Field, rhs: &Self::ParameterField, _: &mut ()) -> Self::Field { + Fp(lhs.0 * rhs.0) + } + + #[inline] + fn add_assign(lhs: &mut Self::Field, rhs: &Self::Field, _: &mut ()) { + lhs.0 += rhs.0; + } + + #[inline] + fn add_const_assign(lhs: &mut Self::Field, rhs: &Self::ParameterField, _: &mut ()) { + lhs.0 += rhs.0; + } + + #[inline] + fn from_parameter(point: Self::ParameterField, _: &mut ()) -> Self::Field { + point + } +} + +impl poseidon::Field> for Spec +where + Self: Specification, + F: RichField + Extendable, + ::Field: Extendable, +{ + type Field = Field<::Field, D>; + + #[inline] + fn add(lhs: &Self::Field, rhs: &Self::Field, compiler: &mut Compiler) -> Self::Field { + lhs.add(rhs, compiler) + } + + #[inline] + fn add_const( + lhs: &Self::Field, + rhs: &Self::ParameterField, + compiler: &mut Compiler, + ) -> Self::Field { + lhs.add(&Field::new_constant(&rhs.0, compiler), compiler) + } + + #[inline] + fn mul(lhs: &Self::Field, rhs: &Self::Field, compiler: &mut Compiler) -> Self::Field { + lhs.mul(*rhs, compiler) + } + + #[inline] + fn mul_const( + lhs: &Self::Field, + rhs: &Self::ParameterField, + compiler: &mut Compiler, + ) -> Self::Field { + lhs.mul(Field::new_constant(&rhs.0, compiler), compiler) + } + + #[inline] + fn add_assign(lhs: &mut Self::Field, rhs: &Self::Field, compiler: &mut Compiler) { + *lhs = lhs.add(*rhs, compiler); + } + + #[inline] + fn add_const_assign( + lhs: &mut Self::Field, + rhs: &Self::ParameterField, + compiler: &mut Compiler, + ) { + *lhs = lhs.add(Field::new_constant(&rhs.0, compiler), compiler); + } + + #[inline] + fn from_parameter( + point: Self::ParameterField, + compiler: &mut Compiler, + ) -> Self::Field { + Field::new_constant(&point.0, compiler) + } +} + +impl poseidon::Specification for Spec +where + Self: Specification, + F: RichField + Extendable, +{ + #[inline] + fn apply_sbox(point: &mut Self::Field, _: &mut ()) { + point.0 = point.0.exp_u64(Self::SBOX_EXPONENT); + } +} + +impl poseidon::Specification> + for Spec +where + Self: Specification, + F: RichField + Extendable, + ::Field: Extendable, +{ + #[inline] + fn apply_sbox(point: &mut Self::Field, compiler: &mut Compiler) { + let mut current = *point; + let mut product = Field::new(compiler.builder.one()); + + for j in 0..bits_u64(Self::SBOX_EXPONENT) { + if (Self::SBOX_EXPONENT >> j & 1) != 0 { + product = product.mul(current, compiler); + } + current = current.mul(current, compiler) + } + *point = product + } +} + +impl poseidon::SBoxExponent + for Spec +{ + const SBOX_EXPONENT: u64 = 7; +} + +impl poseidon::Constants for Spec { + const WIDTH: usize = 8; + const FULL_ROUNDS: usize = 8; + const PARTIAL_ROUNDS: usize = 22; +} + +impl poseidon::Constants for Spec { + const WIDTH: usize = 12; + const FULL_ROUNDS: usize = 8; + const PARTIAL_ROUNDS: usize = 22; +}