Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Plonky2 Plugin #14

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why these changes? Is there something wrong with the stable CI?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't use stable with plonky2 because it uses unstable features.

- nightly
runs-on: ${{ matrix.os }}
steps:
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[workspace]
members = ["eclair", "openzl", "openzl-*", "plugins/*"]
members = ["eclair", "examples/*", "openzl", "openzl-*", "plugins/*"]
17 changes: 17 additions & 0 deletions examples/ecdsa-wasm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"] }
103 changes: 103 additions & 0 deletions examples/ecdsa-wasm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<C, const D: usize>(config: CircuitConfig)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use the eclair interface at all for this circuit. If the eclair creator doesn't use it in a simple signature scheme example, who will?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This stuff was copied directly from plonky2, I haven't converted it over yet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we do change this let's allocate the pieces appropriately as public/private inputs, rather than all being constants as they are here.

where
C: GenericConfig<D>,
{
let pw = PartialWitness::new();
let mut builder = CircuitBuilder::<C::F, D>::new(config);
Comment on lines +27 to +28
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we'd interact with plonky2 through the plugin structs and not their native ones.


let msg = Secp256K1Scalar::rand();
let msg_target = builder.constant_nonnative(msg);

let sk = ECDSASecretKey::<Secp256K1>(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::<C>();
let proof = data.prove(pw).expect("Proving failed.");
data.verify(proof).expect("Verification failed.");
}

/// Testing Module
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Time for openzl-benchmark?

#[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<F, const REPEAT: usize>(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::<PoseidonGoldilocksConfig, 2>(
CircuitConfig::standard_ecc_config(),
)
},
"Bench ECDSA Narrow",
)
}

/// Runs the ECDSA benchmark for a narrow circuit configuration.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Runs the ECDSA benchmark for a narrow circuit configuration.
/// Runs the ECDSA benchmark for a wide circuit configuration.

#[wasm_bindgen_test]
pub fn bench_ecdsa_circuit_wide() {
bench::<_, 3>(
|| test_ecdsa_circuit::<PoseidonGoldilocksConfig, 2>(CircuitConfig::wide_ecc_config()),
"Bench ECDSA Wide",
)
}
}
2 changes: 1 addition & 1 deletion openzl-crypto/src/accumulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ pub mod test {
.collect::<Vec<_>>();
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:?}.");
}
}
}
Expand Down
14 changes: 7 additions & 7 deletions openzl-crypto/src/poseidon/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<S, COM>) -> Self {
Self::new(permutation, S::from_parameter(T::domain_tag()))
pub fn from_permutation(permutation: Permutation<S, COM>, 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.
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -173,10 +173,10 @@ where
}
}

impl<D, S, T, const ARITY: usize, COM> Sample<D> for Hasher<S, T, ARITY, COM>
impl<D, S, T, const ARITY: usize> Sample<D> for Hasher<S, T, ARITY>
where
S: Specification<COM>,
Permutation<S, COM>: Sample<D>,
S: Specification,
Permutation<S>: Sample<D>,
S::ParameterField: NativeField + FieldGeneration,
T: DomainTag<S>,
{
Expand All @@ -185,6 +185,6 @@ where
where
R: RngCore + ?Sized,
{
Self::from_permutation(rng.sample(distribution))
Self::from_permutation(rng.sample(distribution), &mut ())
}
}
6 changes: 3 additions & 3 deletions openzl-crypto/src/poseidon/mds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,13 @@ where
where
F: FieldGeneration,
{
let ys: Vec<F> = (t as u64..2 * t as u64).map(F::from_u64).collect();
let ys: Vec<F> = (t as u32..2 * t as u32).map(F::from_u32).collect(); // TODO: Change u64 to integer mod order(F)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this change because of the smaller size of the Goldilocks field?

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.")
})
Expand Down
16 changes: 13 additions & 3 deletions openzl-crypto/src/poseidon/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment on lines +69 to +70
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not an associated type NumberType and a function
fn from_number_type? This would cover both the arkworks and plonky2 plugin implementations of this trait without losing randomness from the arkworks side.


/// Converts from `bits` into a field element in big endian order, returning `None` if `bits`
/// are out of range.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -140,7 +150,7 @@ pub trait Field<COM = ()>: 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
Expand Down
2 changes: 1 addition & 1 deletion openzl-tutorials/src/poseidon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand Down
29 changes: 14 additions & 15 deletions plugins/arkworks/src/poseidon/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,12 +20,9 @@ pub mod test;
type Compiler<S> = R1CS<<S as Specification>::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<F> NativeField for Fp<F>
Expand Down Expand Up @@ -84,7 +82,7 @@ where
}

#[inline]
fn from_u64(elem: u64) -> Self {
fn from_u32(elem: u32) -> Self {
Self(F::from(elem))
}
}
Expand Down Expand Up @@ -143,19 +141,16 @@ where
}

/// Poseidon Specification Configuration
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Spec<F, const ARITY: usize>(PhantomData<F>)
where
F: PrimeField;
#[derive(derivative::Derivative)]
#[derivative(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Spec<F, const ARITY: usize>(PhantomData<F>);

impl<F, const ARITY: usize> Specification for Spec<F, ARITY>
where
F: PrimeField,
Self: poseidon::Constants,
Self: poseidon::Constants + SBoxExponent,
{
type Field = F;

const SBOX_EXPONENT: u64 = 5;
}

impl<F, const ARITY: usize, COM> Constant<COM> for Spec<F, ARITY>
Expand Down Expand Up @@ -217,7 +212,7 @@ where
}

#[inline]
fn from_parameter(point: Self::ParameterField) -> Self::Field {
fn from_parameter(point: Self::ParameterField, _: &mut ()) -> Self::Field {
point
}
}
Expand Down Expand Up @@ -268,7 +263,7 @@ where
}

#[inline]
fn from_parameter(point: Self::ParameterField) -> Self::Field {
fn from_parameter(point: Self::ParameterField, _: &mut Compiler<Self>) -> Self::Field {
FpVar::Constant(point.0)
}
}
Expand Down Expand Up @@ -297,6 +292,10 @@ where
}
}

impl<const ARITY: usize> SBoxExponent for Spec<bn254::Fr, ARITY> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why only bn254 implementation?

const SBOX_EXPONENT: u64 = 5;
}

impl poseidon::Constants for Spec<bn254::Fr, 2> {
const WIDTH: usize = 3;
const FULL_ROUNDS: usize = 8;
Expand Down
Loading