diff --git a/.codespellignore b/.codespellignore index c91d0f7707..4b1c229c68 100644 --- a/.codespellignore +++ b/.codespellignore @@ -2,4 +2,5 @@ InOut inout LoadE SelectE -ser \ No newline at end of file +ser +te \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 6b9bd7f8da..7913133053 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5344,12 +5344,14 @@ dependencies = [ "hex-literal 0.4.1", "lazy_static", "num-bigint 0.4.6", + "num-integer", "num-traits", "once_cell", "openvm-algebra-circuit", "openvm-circuit", "openvm-circuit-derive", "openvm-circuit-primitives", + "openvm-ecc-guest", "openvm-ecc-transpiler", "openvm-instructions", "openvm-mod-circuit-builder", @@ -5370,11 +5372,16 @@ dependencies = [ "elliptic-curve 0.13.8", "group 0.13.0", "halo2curves-axiom", + "hex-literal 0.4.1", + "lazy_static", + "num-bigint 0.4.6", "once_cell", "openvm", "openvm-algebra-guest", + "openvm-algebra-moduli-macros", "openvm-custom-insn", "openvm-ecc-sw-macros", + "openvm-ecc-te-macros", "openvm-rv32im-guest", "serde", "strum_macros", @@ -5411,6 +5418,15 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "openvm-ecc-te-macros" +version = "1.4.0-rc.2" +dependencies = [ + "openvm-macros-common", + "quote", + "syn 2.0.104", +] + [[package]] name = "openvm-ecc-transpiler" version = "1.4.0-rc.2" diff --git a/Cargo.toml b/Cargo.toml index 097a5f9f69..974cf25a74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ members = [ "extensions/ecc/transpiler", "extensions/ecc/guest", "extensions/ecc/sw-macros", + "extensions/ecc/te-macros", "extensions/ecc/tests", "extensions/pairing/circuit", "extensions/pairing/guest", @@ -164,6 +165,7 @@ openvm-ecc-circuit = { path = "extensions/ecc/circuit", default-features = false openvm-ecc-transpiler = { path = "extensions/ecc/transpiler", default-features = false } openvm-ecc-guest = { path = "extensions/ecc/guest", default-features = false } openvm-ecc-sw-macros = { path = "extensions/ecc/sw-macros", default-features = false } +openvm-ecc-te-macros = { path = "extensions/ecc/te-macros", default-features = false } openvm-pairing-circuit = { path = "extensions/pairing/circuit", default-features = false } openvm-pairing-transpiler = { path = "extensions/pairing/transpiler", default-features = false } openvm-pairing-guest = { path = "extensions/pairing/guest", default-features = false } diff --git a/benchmarks/execute/benches/execute.rs b/benchmarks/execute/benches/execute.rs index d9b708a33b..74963e593c 100644 --- a/benchmarks/execute/benches/execute.rs +++ b/benchmarks/execute/benches/execute.rs @@ -22,7 +22,7 @@ use openvm_continuations::{ verifier::{common::types::VmVerifierPvs, leaf::types::LeafVmVerifierInput}, SC, }; -use openvm_ecc_circuit::{EccCpuProverExt, WeierstrassExtension, WeierstrassExtensionExecutor}; +use openvm_ecc_circuit::{EccCpuProverExt, EccExtension, EccExtensionExecutor}; use openvm_ecc_transpiler::EccTranspilerExtension; use openvm_keccak256_circuit::{Keccak256, Keccak256CpuProverExt, Keccak256Executor}; use openvm_keccak256_transpiler::Keccak256TranspilerExtension; @@ -97,7 +97,7 @@ pub struct ExecuteConfig { #[extension] pub fp2: Fp2Extension, #[extension] - pub weierstrass: WeierstrassExtension, + pub ecc: EccExtension, #[extension(generics = true)] pub pairing: PairingExtension, } @@ -121,7 +121,7 @@ impl Default for ExecuteConfig { BN254_COMPLEX_STRUCT_NAME.to_string(), bn_config.modulus.clone(), )]), - weierstrass: WeierstrassExtension::new(vec![bn_config.clone()]), + ecc: EccExtension::new(vec![bn_config.clone()], vec![]), pairing: PairingExtension::new(vec![PairingCurve::Bn254]), } } @@ -179,11 +179,7 @@ where inventory, )?; VmProverExtension::::extend_prover(&AlgebraCpuProverExt, &config.fp2, inventory)?; - VmProverExtension::::extend_prover( - &EccCpuProverExt, - &config.weierstrass, - inventory, - )?; + VmProverExtension::::extend_prover(&EccCpuProverExt, &config.ecc, inventory)?; VmProverExtension::::extend_prover(&PairingProverExt, &config.pairing, inventory)?; Ok(chip_complex) } diff --git a/benchmarks/guest/ecrecover/openvm.toml b/benchmarks/guest/ecrecover/openvm.toml index c1261ee458..265d29d89a 100644 --- a/benchmarks/guest/ecrecover/openvm.toml +++ b/benchmarks/guest/ecrecover/openvm.toml @@ -9,9 +9,10 @@ supported_moduli = [ "115792089237316195423570985008687907852837564279074904382605163141518161494337", ] -[[app_vm_config.ecc.supported_curves]] +[[app_vm_config.ecc.supported_sw_curves]] struct_name = "Secp256k1Point" modulus = "115792089237316195423570985008687907853269984665640564039457584007908834671663" scalar = "115792089237316195423570985008687907852837564279074904382605163141518161494337" +[app_vm_config.ecc.supported_sw_curves.coeffs] a = "0" b = "7" diff --git a/benchmarks/guest/ecrecover/openvm_init.rs b/benchmarks/guest/ecrecover/openvm_init.rs index dc6d4917dd..d9cf1bbe09 100644 --- a/benchmarks/guest/ecrecover/openvm_init.rs +++ b/benchmarks/guest/ecrecover/openvm_init.rs @@ -1,3 +1,4 @@ // This file is automatically generated by cargo openvm. Do not rename or edit. openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } openvm_ecc_guest::sw_macros::sw_init! { "Secp256k1Point" } +openvm_ecc_guest::te_macros::te_init! { } diff --git a/benchmarks/guest/kitchen-sink/openvm.toml b/benchmarks/guest/kitchen-sink/openvm.toml index 2d1b307eef..a8a39a9fac 100644 --- a/benchmarks/guest/kitchen-sink/openvm.toml +++ b/benchmarks/guest/kitchen-sink/openvm.toml @@ -39,31 +39,35 @@ supported_moduli = [ ], ] -[[app_vm_config.ecc.supported_curves]] +[[app_vm_config.ecc.supported_sw_curves]] struct_name = "Secp256k1Point" modulus = "115792089237316195423570985008687907853269984665640564039457584007908834671663" scalar = "115792089237316195423570985008687907852837564279074904382605163141518161494337" +[app_vm_config.ecc.supported_sw_curves.coeffs] a = "0" b = "7" -[[app_vm_config.ecc.supported_curves]] +[[app_vm_config.ecc.supported_sw_curves]] struct_name = "P256Point" modulus = "115792089210356248762697446949407573530086143415290314195533631308867097853951" scalar = "115792089210356248762697446949407573529996955224135760342422259061068512044369" +[app_vm_config.ecc.supported_sw_curves.coeffs] a = "115792089210356248762697446949407573530086143415290314195533631308867097853948" b = "41058363725152142129326129780047268409114441015993725554835256314039467401291" -[[app_vm_config.ecc.supported_curves]] +[[app_vm_config.ecc.supported_sw_curves]] struct_name = "Bn254G1Affine" modulus = "21888242871839275222246405745257275088696311157297823662689037894645226208583" scalar = "21888242871839275222246405745257275088548364400416034343698204186575808495617" +[app_vm_config.ecc.supported_sw_curves.coeffs] a = "0" b = "3" -[[app_vm_config.ecc.supported_curves]] +[[app_vm_config.ecc.supported_sw_curves]] struct_name = "Bls12_381G1Affine" modulus = "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787" scalar = "52435875175126190479447740508185965837690552500527637822603658699938581184513" +[app_vm_config.ecc.supported_sw_curves.coeffs] a = "0" b = "4" diff --git a/benchmarks/guest/pairing/openvm.toml b/benchmarks/guest/pairing/openvm.toml index 321383b8eb..f34872b531 100644 --- a/benchmarks/guest/pairing/openvm.toml +++ b/benchmarks/guest/pairing/openvm.toml @@ -21,9 +21,10 @@ supported_moduli = [ supported_curves = ["Bn254"] # bn254 (alt bn128) -[[app_vm_config.ecc.supported_curves]] +[[app_vm_config.ecc.supported_sw_curves]] struct_name = "Bn254G1Affine" modulus = "21888242871839275222246405745257275088696311157297823662689037894645226208583" scalar = "21888242871839275222246405745257275088548364400416034343698204186575808495617" +[app_vm_config.ecc.supported_sw_curves.coeffs] a = "0" b = "3" diff --git a/book/src/custom-extensions/ecc.md b/book/src/custom-extensions/ecc.md index de8f31ab62..02ab2448c6 100644 --- a/book/src/custom-extensions/ecc.md +++ b/book/src/custom-extensions/ecc.md @@ -17,12 +17,20 @@ Developers can enable arbitrary Weierstrass curves by configuring this extension - `WeierstrassPoint` trait: It represents an affine point on a Weierstrass elliptic curve and it extends `Group`. - - `Coordinate` type is the type of the coordinates of the point, and it implements `IntMod`. - - `x()`, `y()` are used to get the affine coordinates + - `Coordinate` type is the type of the coordinates of the point, and it implements `Field`. + - `x()`, `y()` are used to get the affine coordinates. - `from_xy` is a constructor for the point, which checks if the point is either identity or on the affine curve. - The point supports elliptic curve operations through intrinsic functions `add_ne_nonidentity` and `double_nonidentity`. - `decompress`: Sometimes an elliptic curve point is compressed and represented by its `x` coordinate and the odd/even parity of the `y` coordinate. `decompress` is used to decompress the point back to `(x, y)`. +- `TwistedEdwardsPoint` trait: + It represents an affine point on a twisted Edwards elliptic curve and it extends `Group`. + + - `Coordinate` type is the type of the coordinates of the point, and it implements `Field`. + - `x()`, `y()` are used to get the affine coordinates. + - `from_xy` is a constructor for the point, which checks if the point is on the affine curve. + - The point supports elliptic curve addition through the `add_impl` method. + - `msm`: for multi-scalar multiplication. - `ecdsa`: for doing ECDSA signature verification and public key recovery from signature. @@ -31,17 +39,20 @@ Developers can enable arbitrary Weierstrass curves by configuring this extension For elliptic curve cryptography, the `openvm-ecc-guest` crate provides macros similar to those in [`openvm-algebra-guest`](./algebra.md): -1. **Declare**: Use `sw_declare!` to define elliptic curves over the previously declared moduli. For example: +1. **Declare**: Use `sw_declare!` or `te_declare!` to define short Weierstrass or twisted Edwards elliptic curves, respectively, over the previously declared moduli. For example: ```rust sw_declare! { Bls12_381G1Affine { mod_type = Bls12_381Fp, b = BLS12_381_B }, P256Affine { mod_type = P256Coord, a = P256_A, b = P256_B }, } +te_declare! { + Edwards25519 { mod_type = Edwards25519Coord, a = CURVE_A, d = CURVE_D }, +} ``` +This creates `Bls12_381G1Affine` and `P256Affine` structs which implement the `Group` and `WeierstrassPoint` traits, and the `Edwards25519` struct which implements the `Group` and `TwistedEdwardsPoint` traits. The underlying memory layout of the structs uses the memory layout of the `Bls12_381Fp`, `P256Coord`, and `Edwards25519Coord` structs, respectively. -Each declared curve must specify the `mod_type` (implementing `IntMod`) and a constant `b` for the Weierstrass curve equation \\(y^2 = x^3 + ax + b\\). `a` is optional and defaults to 0 for short Weierstrass curves. -This creates `Bls12_381G1Affine` and `P256Affine` structs which implement the `Group` and `WeierstrassPoint` traits. The underlying memory layout of the structs uses the memory layout of the `Bls12_381Fp` and `P256Coord` structs, respectively. +Each declared curve must specify the `mod_type` (implementing `Field`) and a constant `b` for the Weierstrass curve equation \\(y^2 = x^3 + ax + b\\) or `a` and `d` for the twisted Edwards curve equation \\(ax^2 + y^2 = 1 + dx^2y^2\\). For short Weierstrass curves, `a` is optional and defaults to 0. 2. **Init**: Called once, the [`openvm::init!` macro](./overview.md#automating-the-init-step) produces a call to `sw_init!` that enumerates these curves and allows the compiler to produce optimized instructions: @@ -51,17 +62,21 @@ openvm::init!(); sw_init! { "Bls12_381G1Affine", "P256Affine", } +te_init! { + Edwards25519, +} */ ``` **Summary**: -- `sw_declare!`: Declares elliptic curve structures. +- `sw_declare!`: Declares short Weierstrass elliptic curve structures. +- `te_declare!`: Declares twisted Edwards elliptic curve structures. - `init!`: Initializes them once, linking them to the underlying moduli. -To use elliptic curve operations on a struct defined with `sw_declare!`, it is expected that the struct for the curve's coordinate field was defined using `moduli_declare!`. In particular, the coordinate field needs to be initialized and set up as described in the [algebra extension](./algebra.md) chapter. +To use elliptic curve operations on a struct defined with `sw_declare!` or `te_declare!`, it is expected that the struct for the curve's coordinate field was defined using `moduli_declare!`. In particular, the coordinate field needs to be initialized and set up as described in the [algebra extension](./algebra.md) chapter. -For the basic operations provided by the `WeierstrassPoint` trait, the scalar field is not needed. For the ECDSA functions in the `ecdsa` module, the scalar field must also be declared, initialized, and set up. +For the basic operations provided by the `WeierstrassPoint` or `TwistedEdwardsPoint` traits, the scalar field is not needed. For the ECDSA functions in the `ecdsa` module, the scalar field must also be declared, initialized, and set up. ## ECDSA diff --git a/book/src/custom-extensions/overview.md b/book/src/custom-extensions/overview.md index 2b07a73ec4..9221a448ed 100644 --- a/book/src/custom-extensions/overview.md +++ b/book/src/custom-extensions/overview.md @@ -60,20 +60,39 @@ supported_moduli = ["", "", ...] [app_vm_config.pairing] supported_curves = ["Bls12_381", "Bn254"] -[[app_vm_config.ecc.supported_curves]] -struct_name = "" +[[app_vm_config.ecc.supported_sw_curves]] +struct_name = "" modulus = "" scalar = "" +[app_vm_config.ecc.supported_sw_curves.coeffs] a = "" b = "" -[[app_vm_config.ecc.supported_curves]] -struct_name = "" +[[app_vm_config.ecc.supported_sw_curves]] +struct_name = "" modulus = "" scalar = "" +[app_vm_config.ecc.supported_sw_curves.coeffs] a = "" b = "" + +[[app_vm_config.ecc.supported_te_curves]] +struct_name = "" +modulus = "" +scalar = "" +[app_vm_config.ecc.supported_te_curves.coeffs] +a = "" +d = "" + +[[app_vm_config.ecc.supported_te_curves]] +struct_name = "" +modulus = "" +scalar = "" +[app_vm_config.ecc.supported_te_curves.coeffs] +a = "" +d = "" +` ``` `rv32i`, `io`, and `rv32m` need to be always included if you make an `openvm.toml` file while the rest are optional and should be included if you want to use the corresponding extension. -All moduli and scalars must be provided in decimal format. Currently `pairing` supports only pre-defined `Bls12_381` and `Bn254` curves. To add more `ecc` curves you need to add more `[[app_vm_config.ecc.supported_curves]]` entries. +All moduli and scalars must be provided in decimal format. Currently `pairing` supports only pre-defined `Bls12_381` and `Bn254` curves. To add more `ecc` curves you need to add more `[[app_vm_config.ecc.supported_sw_curves]]` or `[[app_vm_config.ecc.supported_te_curves]]` entries. diff --git a/book/src/guest-libs/k256.md b/book/src/guest-libs/k256.md index 44aa4d2743..d17ebba97d 100644 --- a/book/src/guest-libs/k256.md +++ b/book/src/guest-libs/k256.md @@ -40,17 +40,18 @@ For the guest program to build successfully, all used moduli and curves must be [app_vm_config.modular] supported_moduli = ["115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337"] -[[app_vm_config.ecc.supported_curves]] +[[app_vm_config.ecc.supported_sw_curves]] struct_name = "Secp256k1Point" modulus = "115792089237316195423570985008687907853269984665640564039457584007908834671663" scalar = "115792089237316195423570985008687907852837564279074904382605163141518161494337" +[app_vm_config.ecc.supported_sw_curves.coeffs] a = "0" b = "7" ``` The `supported_moduli` parameter is a list of moduli that the guest program will use. As mentioned in the [algebra extension](../custom-extensions/algebra.md) chapter, the order of moduli in `[app_vm_config.modular]` must match the order in the `moduli_init!` macro. -The `ecc.supported_curves` parameter is a list of supported curves that the guest program will use. They must be provided in decimal format in the `.toml` file. For multiple curves create multiple `[[app_vm_config.ecc.supported_curves]]` sections. The order of curves in `[[app_vm_config.ecc.supported_curves]]` must match the order in the `sw_init!` macro. -Also, the `struct_name` field must be the name of the elliptic curve struct created by `sw_declare!`. +The `ecc.supported_curves` parameter is a list of supported curves that the guest program will use. They must be provided in decimal format in the `.toml` file. For multiple curves create multiple `[[app_vm_config.ecc.supported_sw_curves]]`/`[[app_vm_config.ecc.supported_te_curves]]` sections. The order of curves in `[[app_vm_config.ecc.supported_sw/te_curves]]` must match the order in the `sw_init!`/`te_init!` macros respectively. +Also, the `struct_name` field must be the name of the elliptic curve struct created by `sw_declare!`/`te_declare!`. In this example, the `Secp256k1Point` struct is created in `openvm_ecc_guest::k256`. diff --git a/book/src/guest-libs/p256.md b/book/src/guest-libs/p256.md index 2d39422cc0..e68f0652e6 100644 --- a/book/src/guest-libs/p256.md +++ b/book/src/guest-libs/p256.md @@ -11,15 +11,16 @@ For the guest program to build successfully, all used moduli and curves must be [app_vm_config.modular] supported_moduli = ["115792089210356248762697446949407573530086143415290314195533631308867097853951", "115792089210356248762697446949407573529996955224135760342422259061068512044369"] -[[app_vm_config.ecc.supported_curves]] +[[app_vm_config.ecc.supported_sw_curves]] struct_name = "P256Point" modulus = "115792089210356248762697446949407573530086143415290314195533631308867097853951" scalar = "115792089210356248762697446949407573529996955224135760342422259061068512044369" +[app_vm_config.ecc.supported_sw_curves.coeffs] a = "115792089210356248762697446949407573530086143415290314195533631308867097853948" b = "41058363725152142129326129780047268409114441015993725554835256314039467401291" ``` The `supported_moduli` parameter is a list of moduli that the guest program will use. As mentioned in the [algebra extension](../custom-extensions/algebra.md) chapter, the order of moduli in `[app_vm_config.modular]` must match the order in the `moduli_init!` macro. -The `ecc.supported_curves` parameter is a list of supported curves that the guest program will use. They must be provided in decimal format in the `.toml` file. For multiple curves create multiple `[[app_vm_config.ecc.supported_curves]]` sections. The order of curves in `[[app_vm_config.ecc.supported_curves]]` must match the order in the `sw_init!` macro. -Also, the `struct_name` field must be the name of the elliptic curve struct created by `sw_declare!`. +The `ecc.supported_curves` parameter is a list of supported curves that the guest program will use. They must be provided in decimal format in the `.toml` file. For multiple curves create multiple `[[app_vm_config.ecc.supported_sw_curves]]`/`[[app_vm_config.ecc.supported_te_curves]]` sections. The order of curves in `[[app_vm_config.ecc.supported_sw_curves]]`/`[[app_vm_config.ecc.supported_te_curves]]` must match the order in the `sw_init!`/`te_init!` macros respectively. +Also, the `struct_name` field must be the name of the elliptic curve struct created by `sw_declare!`/`te_declare!`. diff --git a/crates/circuits/mod-builder/src/builder.rs b/crates/circuits/mod-builder/src/builder.rs index 5d337130bb..a1a2a43c8d 100644 --- a/crates/circuits/mod-builder/src/builder.rs +++ b/crates/circuits/mod-builder/src/builder.rs @@ -418,6 +418,7 @@ impl SubAir for FieldExpr { for i in 0..self.constraints.len() { let expr = self.constraints[i] .evaluate_overflow_expr::(&inputs, &vars, &constants, &flags); + self.check_carry_mod_to_zero.eval( builder, ( diff --git a/crates/circuits/poseidon2-air/src/babybear.rs b/crates/circuits/poseidon2-air/src/babybear.rs index e12b60bfb4..6989f992c7 100644 --- a/crates/circuits/poseidon2-air/src/babybear.rs +++ b/crates/circuits/poseidon2-air/src/babybear.rs @@ -18,7 +18,7 @@ pub(crate) fn horizen_to_p3_babybear(horizen_babybear: HorizenBabyBear) -> BabyB } pub(crate) fn horizen_round_consts() -> Poseidon2Constants { - let p3_rc16: Vec> = RC16 + let p3_rc16: Vec> = RC16 .iter() .map(|round| { round @@ -29,18 +29,10 @@ pub(crate) fn horizen_round_consts() -> Poseidon2Constants { .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, + beginning_full_round_constants: from_fn(|i| p3_rc16[i].clone().try_into().unwrap()), + partial_round_constants: from_fn(|i| p3_rc16[i + BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS][0]), + ending_full_round_constants: from_fn(|i| p3_rc16[i + p_end].clone().try_into().unwrap()), } } diff --git a/crates/sdk/src/config/global.rs b/crates/sdk/src/config/global.rs index a1ac32942a..d7c1c196c7 100644 --- a/crates/sdk/src/config/global.rs +++ b/crates/sdk/src/config/global.rs @@ -16,7 +16,7 @@ use openvm_circuit::{ derive::VmConfig, system::{SystemChipInventory, SystemCpuBuilder, SystemExecutor}, }; -use openvm_ecc_circuit::{EccCpuProverExt, WeierstrassExtension, WeierstrassExtensionExecutor}; +use openvm_ecc_circuit::{EccCpuProverExt, EccExtension, EccExtensionExecutor}; use openvm_ecc_transpiler::EccTranspilerExtension; use openvm_keccak256_circuit::{Keccak256, Keccak256CpuProverExt, Keccak256Executor}; use openvm_keccak256_transpiler::Keccak256TranspilerExtension; @@ -61,7 +61,7 @@ pub struct SdkVmConfig { pub modular: Option, pub fp2: Option, pub pairing: Option, - pub ecc: Option, + pub ecc: Option, } #[derive(Copy, Clone)] @@ -96,8 +96,8 @@ pub struct SdkVmConfigInner { pub fp2: Option, #[extension(executor = "PairingExtensionExecutor")] pub pairing: Option, - #[extension(executor = "WeierstrassExtensionExecutor")] - pub ecc: Option, + #[extension(executor = "EccExtensionExecutor")] + pub ecc: Option, } // Generated by macro @@ -317,7 +317,7 @@ impl InitFileGenerator for SdkVmConfigInner { } if let Some(ecc_config) = &self.ecc { - contents.push_str(&ecc_config.generate_sw_init()); + contents.push_str(&ecc_config.generate_ecc_init()); contents.push('\n'); } @@ -408,7 +408,7 @@ struct SdkVmConfigWithDefaultDeser { pub modular: Option, pub fp2: Option, pub pairing: Option, - pub ecc: Option, + pub ecc: Option, } impl From for SdkVmConfig { diff --git a/crates/toolchain/tests/tests/transpiler_tests.rs b/crates/toolchain/tests/tests/transpiler_tests.rs index 4604656d6c..727ed0032f 100644 --- a/crates/toolchain/tests/tests/transpiler_tests.rs +++ b/crates/toolchain/tests/tests/transpiler_tests.rs @@ -14,7 +14,7 @@ use openvm_circuit::{ system::SystemExecutor, utils::air_test, }; -use openvm_ecc_circuit::{SECP256K1_MODULUS, SECP256K1_ORDER}; +use openvm_ecc_circuit::SECP256K1_CONFIG; use openvm_instructions::exe::VmExe; use openvm_platform::memory::MEM_SIZE; use openvm_rv32im_circuit::*; @@ -125,8 +125,14 @@ impl InitFileGenerator for Rv32ModularFp2Int256Config { #[test_case("tests/data/rv32im-intrin-from-as")] fn test_intrinsic_runtime(elf_path: &str) -> Result<()> { let config = Rv32ModularFp2Int256Config::new( - vec![SECP256K1_MODULUS.clone(), SECP256K1_ORDER.clone()], - vec![("Secp256k1Coord".to_string(), SECP256K1_MODULUS.clone())], + vec![ + SECP256K1_CONFIG.modulus.clone(), + SECP256K1_CONFIG.scalar.clone(), + ], + vec![( + SECP256K1_CONFIG.struct_name.clone(), + SECP256K1_CONFIG.modulus.clone(), + )], ); let elf = get_elf(elf_path)?; let openvm_exe = VmExe::from_elf( diff --git a/docs/specs/ISA.md b/docs/specs/ISA.md index eb4eb5004c..fc6b242e39 100644 --- a/docs/specs/ISA.md +++ b/docs/specs/ISA.md @@ -683,12 +683,13 @@ r32_fp2(a) -> Fp2 { ### Elliptic Curve Extension -The elliptic curve extension supports arithmetic over elliptic curves `C` in Weierstrass form given by -equation `C: y^2 = x^3 + C::A * x + C::B` where `C::A` and `C::B` are constants in the coordinate field. We note that -the definitions of the -curve arithmetic operations do not depend on `C::B`. The VM configuration will specify a list of supported curves. For -each Weierstrass curve `C` there will be associated configuration parameters `C::COORD_SIZE` and `C::BLOCK_SIZE` ( -defined below). The extension operates on address spaces `1` and `2`, meaning all memory cells are constrained to be +The elliptic curve extension supports arithmetic over elliptic curves `C` in the following forms: +- in short Weierstrass form given by equation `C: y^2 = x^3 + C::A * x + C::B` where `C::A` and `C::B` are constants in the coordinate field +- in twisted Edwards form given by equation `C: C::A * x^2 + y^2 = 1 + C::D * x^2 * y^2` where `C::A` and `C::D` are constants in the coordinate field + +We note that +the definitions of the curve arithmetic operations for short Weierstrass curves do not depend on `C::B`. The VM configuration will specify a list of supported curves. For +each curve `C` (of either form) there will be associated configuration parameters `C::COORD_SIZE` and `C::BLOCK_SIZE` (defined below). The extension operates on address spaces `1` and `2`, meaning all memory cells are constrained to be bytes. An affine curve point `EcPoint(x, y)` is a pair of `x,y` where each element is an array of `C::COORD_SIZE` elements each @@ -706,12 +707,16 @@ r32_ec_point(a) -> EcPoint { } ``` +The instructions that have prefix `SW_` perform short Weierstrass curve operations, and those with prefix `TE_` perform twisted Edwards curve operations. + | Name | Operands | Description | | -------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| EC_ADD_NE\ | `a,b,c,1,2` | Set `r32_ec_point(a) = r32_ec_point(b) + r32_ec_point(c)` (curve addition). Assumes that `r32_ec_point(b), r32_ec_point(c)` both lie on the curve and are not the identity point. Further assumes that `r32_ec_point(b).x, r32_ec_point(c).x` are not equal in the coordinate field. | -| SETUP_EC_ADD_NE\ | `a,b,c,1,2` | `assert(r32_ec_point(b).x == C::MODULUS)` in the chip for EC ADD. For the sake of implementation convenience it also writes something (can be anything) into `[r32{0}(a): 2*C::COORD_SIZE]_2`. It is required for proper functionality that `assert(r32_ec_point(b).x != r32_ec_point(c).x)` | -| EC_DOUBLE\ | `a,b,_,1,2` | Set `r32_ec_point(a) = 2 * r32_ec_point(b)`. This doubles the input point. Assumes that `r32_ec_point(b)` lies on the curve and is not the identity point. | -| SETUP_EC_DOUBLE\ | `a,b,_,1,2` | `assert(r32_ec_point(b).x == C::MODULUS && r32_ec_point(b).y == C::A)` in the chip for EC DOUBLE. For the sake of implementation convenience it also writes something (can be anything) into `[r32{0}(a): 2*C::COORD_SIZE]_2`. It is required for proper functionality that `assert(r32_ec_point(b).y != 0 mod C::MODULUS)` | +| SW_ADD_NE\ | `a,b,c,1,2` | Set `r32_ec_point(a) = r32_ec_point(b) + r32_ec_point(c)` (curve addition). Assumes that `r32_ec_point(b), r32_ec_point(c)` both lie on the curve and are not the identity point. Further assumes that `r32_ec_point(b).x, r32_ec_point(c).x` are not equal in the coordinate field. | +| SETUP_SW_ADD_NE\ | `a,b,c,1,2` | `assert(r32_ec_point(b).x == C::MODULUS)` in the chip for SW ADD. For the sake of implementation convenience it also writes something (can be anything) into `[r32{0}(a): 2*C::COORD_SIZE]_2`. It is required for proper functionality that `assert(r32_ec_point(b).x != r32_ec_point(c).x)` | +| SW_DOUBLE\ | `a,b,_,1,2` | Set `r32_ec_point(a) = 2 * r32_ec_point(b)`. This doubles the input point. Assumes that `r32_ec_point(b)` lies on the curve and is not the identity point. | +| SETUP_SW_DOUBLE\ | `a,b,_,1,2` | `assert(r32_ec_point(b).x == C::MODULUS && r32_ec_point(b).y == C::A)` in the chip for SW DOUBLE. For the sake of implementation convenience it also writes something (can be anything) into `[r32{0}(a): 2*C::COORD_SIZE]_2`. It is required for proper functionality that `assert(r32_ec_point(b).y != 0 mod C::MODULUS)` | +| TE_ADD\ | `a,b,c,1,2` | Set `r32_ec_point(a) = r32_ec_point(b) + r32_ec_point(c)` (curve addition). Assumes that `r32_ec_point(b), r32_ec_point(c)` both lie on the curve. | +| SETUP_TE_ADD\ | `a,b,c,1,2` | `assert(r32_ec_point(b).x == C::MODULUS && r32_ec_point(b).y == C::A && r32_ec_point(c).x == C::D)` in the chip for TE ADD. For the sake of implementation convenience it also writes something (can be anything) into `[r32{0}(a): 2*C::COORD_SIZE]_2`. | ### Pairing Extension diff --git a/docs/specs/RISCV.md b/docs/specs/RISCV.md index 82ad9c1445..31f716e3c0 100644 --- a/docs/specs/RISCV.md +++ b/docs/specs/RISCV.md @@ -176,13 +176,16 @@ Complex extension field arithmetic over `Fp2` depends on `Fp` where `-1` is not ## Elliptic Curve Extension -The elliptic curve extension supports arithmetic over short Weierstrass curves, which requires specification of the elliptic curve `C`. The extension must be configured to support a fixed ordered list of supported curves. We use `config.curve_idx(C)` to denote the index of `C` in this list. In the list below, `idx` denotes `config.curve_idx(C)`. +The elliptic curve extension supports arithmetic over short Weierstrass curves and twisted Edwards curves, which requires specification of the elliptic curve `C`. The extension must be configured to support two fixed ordered lists of supported curves: one list of short Weierstrass curves and one list of twisted Edwards curves. Instructions prefixed with `sw_` are for short Weierstrass curves and instructions prefixed with `te_` are for twisted Edwards curves. We use `config.curve_idx(C)` to denote the index of `C` in the appropriate list. In the list below, `idx` denotes `config.curve_idx(C)`. | RISC-V Inst | FMT | opcode[6:0] | funct3 | funct7 | RISC-V description and notes | | --------------- | --- | ----------- | ------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | sw_add_ne\ | R | 0101011 | 001 | `idx*8` | `EcPoint([rd:2*C::COORD_SIZE]_2) = EcPoint([rs1:2*C::COORD_SIZE]_2) + EcPoint([rs2:2*C::COORD_SIZE]_2)`. Assumes that input affine points are not identity and do not have same x-coordinate. | | sw_double\ | R | 0101011 | 001 | `idx*8+1` | `EcPoint([rd:2*C::COORD_SIZE]_2) = 2 * EcPoint([rs1:2*C::COORD_SIZE]_2)`. Assumes that input affine point is not identity. `rs2` is unused and must be set to `x0`. | -| setup\ | R | 0101011 | 001 | `idx*8+2` | If `ind(rs2) != 0`, then this instruction is setup for `sw_add_ne`. Otherwise it is setup for `sw_double`. If setup for `sw_add_ne`, it checks `assert([rs1: C::COORD_SIZE]_2 == C::MODULUS)`, and if setup for `sw_double`, checks `assert([rs1: 2*C::COORD_SIZE]_2 == [C::MODULUS, CURVE_A])`. For the sake of implementation convenience it also writes an unconstrained value into `[rd: 2*C::COORD_SIZE]_2`. When `ind(rs2) != 0` (add_ne), it is required for proper functionality that `[rs2: C::COORD_SIZE]_2 != [rs1: C::COORD_SIZE]_2`; otherwise (double), it is required that `[rs1 + C::COORD_SIZE: C::COORD_SIZE]_2 != C::Fp::ZERO` | +| sw_setup\ | R | 0101011 | 001 | `idx*8+2` | If `ind(rs2) != 0`, then this instruction is setup for `sw_add_ne`. Otherwise it is setup for `sw_double`. If setup for `sw_add_ne`, it checks `assert([rs1: C::COORD_SIZE]_2 == C::MODULUS)`, and if setup for `sw_double`, checks `assert([rs1: 2*C::COORD_SIZE]_2 == [C::MODULUS, CURVE_A])`. For the sake of implementation convenience it also writes an unconstrained value into `[rd: 2*C::COORD_SIZE]_2`. When `ind(rs2) != 0` (add_ne), it is required for proper functionality that `[rs2: C::COORD_SIZE]_2 != [rs1: C::COORD_SIZE]_2`; otherwise (double), it is required that `[rs1 + C::COORD_SIZE: C::COORD_SIZE]_2 != C::Fp::ZERO` | +| te_add\ | R | 0101011 | 100 | `idx*8` | `EcPoint([rd:2*C::COORD_SIZE]_2) = EcPoint([rs1:2*C::COORD_SIZE]_2) + EcPoint([rs2:2*C::COORD_SIZE]_2)`. | +| te_setup\ | R | 0101011 | 100 | `idx*8+1` | `assert([rs1: 2*C::COORD_SIZE]_2 == [C::MODULUS, C::CURVE_A] && [rs2: C::COORD_SIZE]_2 == C::CURVE_D])`. For the sake of implementation convenience it also writes an unconstrained value into `[rd: 2*C::COORD_SIZE]_2`. | + Since `funct7` is 7-bits, up to 16 curves can be supported simultaneously. We use `idx*8` to leave some room for future expansion. diff --git a/docs/specs/transpiler.md b/docs/specs/transpiler.md index fded65b6d8..b2e585a1e8 100644 --- a/docs/specs/transpiler.md +++ b/docs/specs/transpiler.md @@ -205,7 +205,9 @@ Each VM extension's behavior is specified below. | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | sw_add_ne\ | EC_ADD_NE_RV32\ `ind(rd), ind(rs1), ind(rs2), 1, 2` | | sw_double\ | EC_DOUBLE_RV32\ `ind(rd), ind(rs1), 0, 1, 2` | -| setup\ | SETUP_EC_ADD_NE_RV32\ `ind(rd), ind(rs1), ind(rs2), 1, 2` if `ind(rs2) != 0`, SETUP_EC_DOUBLE_RV32\ `ind(rd), ind(rs1), ind(rs2), 1, 2` if `ind(rs2) = 0` | +| sw_setup\ | SETUP_EC_ADD_NE_RV32\ `ind(rd), ind(rs1), ind(rs2), 1, 2` if `ind(rs2) != 0`, SETUP_EC_DOUBLE_RV32\ `ind(rd), ind(rs1), ind(rs2), 1, 2` if `ind(rs2) = 0` | +| te_add\ | TE_ADD_RV32\ `ind(rd), ind(rs1), ind(rs2), 1, 2` | +| te_setup\ | SETUP_TE_ADD_RV32\ `ind(rd), ind(rs1), ind(rs2), 1, 2` | ### Pairing Extension diff --git a/examples/algebra/openvm/app.vmexe b/examples/algebra/openvm/app.vmexe new file mode 100644 index 0000000000..801ce82638 Binary files /dev/null and b/examples/algebra/openvm/app.vmexe differ diff --git a/examples/ecc/Cargo.toml b/examples/ecc/Cargo.toml index 3e0dcdbcfc..0206cba6a0 100644 --- a/examples/ecc/Cargo.toml +++ b/examples/ecc/Cargo.toml @@ -11,9 +11,11 @@ openvm = { git = "https://github.com/openvm-org/openvm.git", features = [ "std", ] } openvm-algebra-guest = { git = "https://github.com/openvm-org/openvm.git" } -openvm-ecc-guest = { git = "https://github.com/openvm-org/openvm.git" } +openvm-ecc-guest = { git = "https://github.com/openvm-org/openvm.git", features = ["ed25519"]} openvm-k256 = { git = "https://github.com/openvm-org/openvm.git", package = "k256" } hex-literal = { version = "0.4.1", default-features = false } +serde = { version = "1.0", default-features = false, features = [ "derive" ] } +num-bigint = { version = "0.4.6", default-features = false } [features] default = [] diff --git a/examples/ecc/openvm.toml b/examples/ecc/openvm.toml index 1dc6cf25f2..db8e420efc 100644 --- a/examples/ecc/openvm.toml +++ b/examples/ecc/openvm.toml @@ -2,11 +2,22 @@ [app_vm_config.rv32m] [app_vm_config.io] [app_vm_config.modular] -supported_moduli = ["115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337"] +supported_moduli = ["115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337", "57896044618658097711785492504343953926634992332820282019728792003956564819949"] -[[app_vm_config.ecc.supported_curves]] +[[app_vm_config.ecc.supported_sw_curves]] struct_name = "Secp256k1Point" modulus = "115792089237316195423570985008687907853269984665640564039457584007908834671663" scalar = "115792089237316195423570985008687907852837564279074904382605163141518161494337" + +[app_vm_config.ecc.supported_sw_curves.coeffs] a = "0" -b = "7" \ No newline at end of file +b = "7" + +[[app_vm_config.ecc.supported_te_curves]] +struct_name = "Ed25519Point" +modulus = "57896044618658097711785492504343953926634992332820282019728792003956564819949" +scalar = "7237005577332262213973186563042994240857116359379907606001950938285454250989" + +[app_vm_config.ecc.supported_te_curves.coeffs] +a = "57896044618658097711785492504343953926634992332820282019728792003956564819948" +d = "37095705934669439343138083508754565189542113879843219016388785533085940283555" diff --git a/examples/ecc/openvm/app.vmexe b/examples/ecc/openvm/app.vmexe new file mode 100644 index 0000000000..910f3a4efd Binary files /dev/null and b/examples/ecc/openvm/app.vmexe differ diff --git a/examples/ecc/openvm_init.rs b/examples/ecc/openvm_init.rs index dc6d4917dd..a2ffd7cabd 100644 --- a/examples/ecc/openvm_init.rs +++ b/examples/ecc/openvm_init.rs @@ -1,3 +1,4 @@ // This file is automatically generated by cargo openvm. Do not rename or edit. -openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337", "57896044618658097711785492504343953926634992332820282019728792003956564819949" } openvm_ecc_guest::sw_macros::sw_init! { "Secp256k1Point" } +openvm_ecc_guest::te_macros::te_init! { "Ed25519Point" } diff --git a/examples/ecc/src/main.rs b/examples/ecc/src/main.rs index 5114b9968f..7e0f0817e0 100644 --- a/examples/ecc/src/main.rs +++ b/examples/ecc/src/main.rs @@ -1,7 +1,11 @@ // ANCHOR: imports use hex_literal::hex; -use openvm_algebra_guest::IntMod; -use openvm_ecc_guest::weierstrass::WeierstrassPoint; +use openvm_ecc_guest::{ + algebra::IntMod, + ed25519::{Ed25519Coord, Ed25519Point}, + edwards::TwistedEdwardsPoint, + weierstrass::WeierstrassPoint, +}; use openvm_k256::{Secp256k1Coord, Secp256k1Point}; // ANCHOR_END: imports @@ -9,11 +13,11 @@ use openvm_k256::{Secp256k1Coord, Secp256k1Point}; openvm::init!(); /* The init! macro will expand to the following openvm_algebra_guest::moduli_macros::moduli_init! { - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F", - "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141" +"115792089237316195423570985008687907853269984665640564039457584007908834671663", +"115792089237316195423570985008687907852837564279074904382605163141518161494337" } - openvm_ecc_guest::sw_macros::sw_init! { "Secp256k1Point" } +openvm_ecc_guest::te_macros::te_init! { "Ed25519Point" } */ // ANCHOR_END: init @@ -33,5 +37,22 @@ pub fn main() { #[allow(clippy::op_ref)] let _p3 = &p1 + &p2; + + let x1 = Ed25519Coord::from_be_bytes_unchecked(&hex!( + "216936D3CD6E53FEC0A4E231FDD6DC5C692CC7609525A7B2C9562D608F25D51A" + )); + let y1 = Ed25519Coord::from_be_bytes_unchecked(&hex!( + "6666666666666666666666666666666666666666666666666666666666666658" + )); + let p1 = Ed25519Point::from_xy(x1, y1).unwrap(); + + let x2 = Ed25519Coord::from_u32(2); + let y2 = Ed25519Coord::from_be_bytes_unchecked(&hex!( + "1A43BF127BDDC4D71FF910403C11DDB5BA2BCDD2815393924657EF111E712631" + )); + let p2 = Ed25519Point::from_xy(x2, y2).unwrap(); + + #[allow(clippy::op_ref)] + let _p3 = &p1 + &p2; } // ANCHOR_END: main diff --git a/examples/i256/openvm/app.vmexe b/examples/i256/openvm/app.vmexe new file mode 100644 index 0000000000..e45a699ef3 Binary files /dev/null and b/examples/i256/openvm/app.vmexe differ diff --git a/examples/keccak/Cargo.toml b/examples/keccak/Cargo.toml index 74f15e6234..3c5cd8a26e 100644 --- a/examples/keccak/Cargo.toml +++ b/examples/keccak/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" members = [] [dependencies] -openvm = { git = "https://github.com/openvm-org/openvm.git", features = [ +openvm = { git = "https://github.com/openvm-org/openvm.git", branch = "develop", features = [ "std", ] } openvm-keccak256 = { git = "https://github.com/openvm-org/openvm.git" } diff --git a/extensions/ecc/circuit/Cargo.toml b/extensions/ecc/circuit/Cargo.toml index 8aa0704af5..8a083bc922 100644 --- a/extensions/ecc/circuit/Cargo.toml +++ b/extensions/ecc/circuit/Cargo.toml @@ -17,8 +17,10 @@ openvm-stark-backend = { workspace = true } openvm-algebra-circuit = { workspace = true } openvm-rv32-adapters = { workspace = true } openvm-ecc-transpiler = { workspace = true } +openvm-ecc-guest = { workspace = true, features = ["ed25519"] } num-bigint = { workspace = true } +num-integer = { workspace = true } num-traits = { workspace = true } strum = { workspace = true } derive_more = { workspace = true, features = ["deref", "deref_mut"] } diff --git a/extensions/ecc/circuit/src/config.rs b/extensions/ecc/circuit/src/config.rs index cb555bdba9..f3d6be47c9 100644 --- a/extensions/ecc/circuit/src/config.rs +++ b/extensions/ecc/circuit/src/config.rs @@ -20,46 +20,54 @@ use serde::{Deserialize, Serialize}; use super::*; #[derive(Clone, Debug, VmConfig, Serialize, Deserialize)] -pub struct Rv32WeierstrassConfig { +pub struct Rv32EccConfig { #[config(generics = true)] pub modular: Rv32ModularConfig, #[extension] - pub weierstrass: WeierstrassExtension, + pub ecc: EccExtension, } -impl Rv32WeierstrassConfig { - pub fn new(curves: Vec) -> Self { - let primes: Vec<_> = curves +impl Rv32EccConfig { + pub fn new( + sw_curves: Vec>, + te_curves: Vec>, + ) -> Self { + let sw_primes: Vec<_> = sw_curves .iter() .flat_map(|c| [c.modulus.clone(), c.scalar.clone()]) .collect(); + let te_primes: Vec<_> = te_curves + .iter() + .flat_map(|c| [c.modulus.clone(), c.scalar.clone()]) + .collect(); + let primes = sw_primes.into_iter().chain(te_primes).collect(); Self { modular: Rv32ModularConfig::new(primes), - weierstrass: WeierstrassExtension::new(curves), + ecc: EccExtension::new(sw_curves, te_curves), } } } -impl InitFileGenerator for Rv32WeierstrassConfig { +impl InitFileGenerator for Rv32EccConfig { fn generate_init_file_contents(&self) -> Option { Some(format!( "// This file is automatically generated by cargo openvm. Do not rename or edit.\n{}\n{}\n", self.modular.modular.generate_moduli_init(), - self.weierstrass.generate_sw_init() + self.ecc.generate_ecc_init() )) } } #[derive(Clone)] -pub struct Rv32WeierstrassCpuBuilder; +pub struct Rv32EccCpuBuilder; -impl VmBuilder for Rv32WeierstrassCpuBuilder +impl VmBuilder for Rv32EccCpuBuilder where SC: StarkGenericConfig, E: StarkEngine, PD = CpuDevice>, Val: PrimeField32, { - type VmConfig = Rv32WeierstrassConfig; + type VmConfig = Rv32EccConfig; type SystemChipInventory = SystemChipInventory; type RecordArena = MatrixRecordArena>; @@ -74,11 +82,7 @@ where let mut chip_complex = VmBuilder::::create_chip_complex(&Rv32ModularCpuBuilder, &config.modular, circuit)?; let inventory = &mut chip_complex.inventory; - VmProverExtension::::extend_prover( - &EccCpuProverExt, - &config.weierstrass, - inventory, - )?; + VmProverExtension::::extend_prover(&EccCpuProverExt, &config.ecc, inventory)?; Ok(chip_complex) } } diff --git a/extensions/ecc/circuit/src/weierstrass_extension.rs b/extensions/ecc/circuit/src/ecc_extension.rs similarity index 55% rename from extensions/ecc/circuit/src/weierstrass_extension.rs rename to extensions/ecc/circuit/src/ecc_extension.rs index 5048584183..09c5afe43b 100644 --- a/extensions/ecc/circuit/src/weierstrass_extension.rs +++ b/extensions/ecc/circuit/src/ecc_extension.rs @@ -21,7 +21,11 @@ use openvm_circuit_primitives::{ }, var_range::VariableRangeCheckerBus, }; -use openvm_ecc_transpiler::Rv32WeierstrassOpcode; +use openvm_ecc_guest::{ + algebra::IntMod, + ed25519::{CURVE_A as ED25519_A, CURVE_D as ED25519_D, ED25519_MODULUS, ED25519_ORDER}, +}; +use openvm_ecc_transpiler::{Rv32EdwardsOpcode, Rv32WeierstrassOpcode}; use openvm_instructions::{LocalOpcode, VmOpcode}; use openvm_mod_circuit_builder::ExprBuilderConfig; use openvm_stark_backend::{ @@ -35,13 +39,14 @@ use serde_with::{serde_as, DisplayFromStr}; use strum::EnumCount; use crate::{ - get_ec_addne_air, get_ec_addne_chip, get_ec_addne_step, get_ec_double_air, get_ec_double_chip, - get_ec_double_step, EcAddNeExecutor, EcDoubleExecutor, EccCpuProverExt, WeierstrassAir, + get_sw_addne_air, get_sw_addne_chip, get_sw_addne_step, get_sw_double_air, get_sw_double_chip, + get_sw_double_step, get_te_add_air, get_te_add_chip, get_te_add_step, EccCpuProverExt, + EdwardsAir, SwAddNeExecutor, SwDoubleExecutor, TeAddExecutor, WeierstrassAir, }; #[serde_as] #[derive(Clone, Debug, derive_new::new, Serialize, Deserialize)] -pub struct CurveConfig { +pub struct CurveConfig { /// The name of the curve struct as defined by moduli_declare. pub struct_name: String, /// The coordinate modulus of the curve. @@ -50,6 +55,13 @@ pub struct CurveConfig { /// The scalar field modulus of the curve. #[serde_as(as = "DisplayFromStr")] pub scalar: BigUint, + // curve-specific coefficients + pub coeffs: T, +} + +#[serde_as] +#[derive(Clone, Debug, derive_new::new, Serialize, Deserialize)] +pub struct SwCurveCoeffs { /// The coefficient a of y^2 = x^3 + ax + b. #[serde_as(as = "DisplayFromStr")] pub a: BigUint, @@ -58,61 +70,114 @@ pub struct CurveConfig { pub b: BigUint, } -pub static SECP256K1_CONFIG: Lazy = Lazy::new(|| CurveConfig { - struct_name: SECP256K1_ECC_STRUCT_NAME.to_string(), - modulus: SECP256K1_MODULUS.clone(), - scalar: SECP256K1_ORDER.clone(), - a: BigUint::zero(), - b: BigUint::from_u8(7u8).unwrap(), +#[serde_as] +#[derive(Clone, Debug, derive_new::new, Serialize, Deserialize)] +pub struct TeCurveCoeffs { + /// The coefficient a of ax^2 + y^2 = 1 + dx^2y^2 + #[serde_as(as = "DisplayFromStr")] + pub a: BigUint, + /// The coefficient d of ax^2 + y^2 = 1 + dx^2y^2 + #[serde_as(as = "DisplayFromStr")] + pub d: BigUint, +} + +pub static SECP256K1_CONFIG: Lazy> = Lazy::new(|| CurveConfig { + struct_name: "Secp256k1Point".to_string(), + modulus: BigUint::from_bytes_be(&hex!( + "FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F" + )), + scalar: BigUint::from_bytes_be(&hex!( + "FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141" + )), + coeffs: SwCurveCoeffs { + a: BigUint::zero(), + b: BigUint::from_u8(7u8).unwrap(), + }, +}); + +pub static P256_CONFIG: Lazy> = Lazy::new(|| CurveConfig { + struct_name: "P256Point".to_string(), + modulus: BigUint::from_bytes_be(&hex!( + "ffffffff00000001000000000000000000000000ffffffffffffffffffffffff" + )), + scalar: BigUint::from_bytes_be(&hex!( + "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551" + )), + coeffs: SwCurveCoeffs { + a: BigUint::from_bytes_le(&hex!( + "fcffffffffffffffffffffff00000000000000000000000001000000ffffffff" + )), + b: BigUint::from_bytes_le(&hex!( + "4b60d2273e3cce3bf6b053ccb0061d65bc86987655bdebb3e7933aaad835c65a" + )), + }, }); -pub static P256_CONFIG: Lazy = Lazy::new(|| CurveConfig { - struct_name: P256_ECC_STRUCT_NAME.to_string(), - modulus: P256_MODULUS.clone(), - scalar: P256_ORDER.clone(), - a: BigUint::from_bytes_le(&P256_A), - b: BigUint::from_bytes_le(&P256_B), +pub static ED25519_CONFIG: Lazy> = Lazy::new(|| CurveConfig { + struct_name: "Ed25519Point".to_string(), + modulus: ED25519_MODULUS.clone(), + scalar: ED25519_ORDER.clone(), + coeffs: TeCurveCoeffs { + a: BigUint::from_bytes_le(ED25519_A.as_le_bytes()), + d: BigUint::from_bytes_le(ED25519_D.as_le_bytes()), + }, }); #[derive(Clone, Debug, derive_new::new, Serialize, Deserialize)] -pub struct WeierstrassExtension { - pub supported_curves: Vec, +pub struct EccExtension { + #[serde(default)] + pub supported_sw_curves: Vec>, + #[serde(default)] + pub supported_te_curves: Vec>, } -impl WeierstrassExtension { - pub fn generate_sw_init(&self) -> String { - let supported_curves = self - .supported_curves +impl EccExtension { + pub fn generate_ecc_init(&self) -> String { + let supported_sw_curves = self + .supported_sw_curves .iter() .map(|curve_config| format!("\"{}\"", curve_config.struct_name)) .collect::>() .join(", "); - format!("openvm_ecc_guest::sw_macros::sw_init! {{ {supported_curves} }}") + let supported_te_curves = self + .supported_te_curves + .iter() + .map(|curve_config| format!("\"{}\"", curve_config.struct_name)) + .collect::>() + .join(", "); + + format!( + "openvm_ecc_guest::sw_macros::sw_init! {{ {supported_sw_curves} }}\nopenvm_ecc_guest::te_macros::te_init! {{ {supported_te_curves} }}" + ) } } #[derive(Clone, AnyEnum, Executor, MeteredExecutor, PreflightExecutor)] -pub enum WeierstrassExtensionExecutor { +pub enum EccExtensionExecutor { // 32 limbs prime - EcAddNeRv32_32(EcAddNeExecutor<2, 32>), - EcDoubleRv32_32(EcDoubleExecutor<2, 32>), + SwAddNeRv32_32(SwAddNeExecutor<2, 32>), + SwDoubleRv32_32(SwDoubleExecutor<2, 32>), // 48 limbs prime - EcAddNeRv32_48(EcAddNeExecutor<6, 16>), - EcDoubleRv32_48(EcDoubleExecutor<6, 16>), + SwAddNeRv32_48(SwAddNeExecutor<6, 16>), + SwDoubleRv32_48(SwDoubleExecutor<6, 16>), + // 32 limbs prime + TeEcAddRv32_32(TeAddExecutor<2, 32>), } -impl VmExecutionExtension for WeierstrassExtension { - type Executor = WeierstrassExtensionExecutor; +impl VmExecutionExtension for EccExtension { + type Executor = EccExtensionExecutor; fn extend_execution( &self, - inventory: &mut ExecutorInventoryBuilder, + inventory: &mut ExecutorInventoryBuilder, ) -> Result<(), ExecutorInventoryError> { let pointer_max_bits = inventory.pointer_max_bits(); // TODO: somehow get the range checker bus from `ExecutorInventory` let dummy_range_checker_bus = VariableRangeCheckerBus::new(u16::MAX, 16); - for (i, curve) in self.supported_curves.iter().enumerate() { + + // add the sw curves + for (i, curve) in self.supported_sw_curves.iter().enumerate() { let start_offset = Rv32WeierstrassOpcode::CLASS_OFFSET + i * Rv32WeierstrassOpcode::COUNT; let bytes = curve.modulus.bits().div_ceil(8); @@ -123,7 +188,7 @@ impl VmExecutionExtension for WeierstrassExtension { num_limbs: 32, limb_bits: 8, }; - let addne = get_ec_addne_step( + let addne = get_sw_addne_step( config.clone(), dummy_range_checker_bus, pointer_max_bits, @@ -131,24 +196,24 @@ impl VmExecutionExtension for WeierstrassExtension { ); inventory.add_executor( - WeierstrassExtensionExecutor::EcAddNeRv32_32(addne), - ((Rv32WeierstrassOpcode::EC_ADD_NE as usize) - ..=(Rv32WeierstrassOpcode::SETUP_EC_ADD_NE as usize)) + EccExtensionExecutor::SwAddNeRv32_32(addne), + ((Rv32WeierstrassOpcode::SW_ADD_NE as usize) + ..=(Rv32WeierstrassOpcode::SETUP_SW_ADD_NE as usize)) .map(|x| VmOpcode::from_usize(x + start_offset)), )?; - let double = get_ec_double_step( + let double = get_sw_double_step( config, dummy_range_checker_bus, pointer_max_bits, start_offset, - curve.a.clone(), + curve.coeffs.a.clone(), ); inventory.add_executor( - WeierstrassExtensionExecutor::EcDoubleRv32_32(double), - ((Rv32WeierstrassOpcode::EC_DOUBLE as usize) - ..=(Rv32WeierstrassOpcode::SETUP_EC_DOUBLE as usize)) + EccExtensionExecutor::SwDoubleRv32_32(double), + ((Rv32WeierstrassOpcode::SW_DOUBLE as usize) + ..=(Rv32WeierstrassOpcode::SETUP_SW_DOUBLE as usize)) .map(|x| VmOpcode::from_usize(x + start_offset)), )?; } else if bytes <= 48 { @@ -157,7 +222,7 @@ impl VmExecutionExtension for WeierstrassExtension { num_limbs: 48, limb_bits: 8, }; - let addne = get_ec_addne_step( + let addne = get_sw_addne_step( config.clone(), dummy_range_checker_bus, pointer_max_bits, @@ -165,24 +230,55 @@ impl VmExecutionExtension for WeierstrassExtension { ); inventory.add_executor( - WeierstrassExtensionExecutor::EcAddNeRv32_48(addne), - ((Rv32WeierstrassOpcode::EC_ADD_NE as usize) - ..=(Rv32WeierstrassOpcode::SETUP_EC_ADD_NE as usize)) + EccExtensionExecutor::SwAddNeRv32_48(addne), + ((Rv32WeierstrassOpcode::SW_ADD_NE as usize) + ..=(Rv32WeierstrassOpcode::SETUP_SW_ADD_NE as usize)) .map(|x| VmOpcode::from_usize(x + start_offset)), )?; - let double = get_ec_double_step( + let double = get_sw_double_step( config, dummy_range_checker_bus, pointer_max_bits, start_offset, - curve.a.clone(), + curve.coeffs.a.clone(), + ); + + inventory.add_executor( + EccExtensionExecutor::SwDoubleRv32_48(double), + ((Rv32WeierstrassOpcode::SW_DOUBLE as usize) + ..=(Rv32WeierstrassOpcode::SETUP_SW_DOUBLE as usize)) + .map(|x| VmOpcode::from_usize(x + start_offset)), + )?; + } else { + panic!("Modulus too large"); + } + } + + // add the te curves + for (i, curve) in self.supported_te_curves.iter().enumerate() { + let start_offset = Rv32EdwardsOpcode::CLASS_OFFSET + i * Rv32EdwardsOpcode::COUNT; + let bytes = curve.modulus.bits().div_ceil(8); + + if bytes <= 32 { + let config = ExprBuilderConfig { + modulus: curve.modulus.clone(), + num_limbs: 32, + limb_bits: 8, + }; + let add = get_te_add_step( + config.clone(), + dummy_range_checker_bus, + pointer_max_bits, + start_offset, + curve.coeffs.a.clone(), + curve.coeffs.d.clone(), ); inventory.add_executor( - WeierstrassExtensionExecutor::EcDoubleRv32_48(double), - ((Rv32WeierstrassOpcode::EC_DOUBLE as usize) - ..=(Rv32WeierstrassOpcode::SETUP_EC_DOUBLE as usize)) + EccExtensionExecutor::TeEcAddRv32_32(add), + ((Rv32EdwardsOpcode::TE_ADD as usize) + ..=(Rv32EdwardsOpcode::SETUP_TE_ADD as usize)) .map(|x| VmOpcode::from_usize(x + start_offset)), )?; } else { @@ -194,7 +290,7 @@ impl VmExecutionExtension for WeierstrassExtension { } } -impl VmCircuitExtension for WeierstrassExtension { +impl VmCircuitExtension for EccExtension { fn extend_circuit(&self, inventory: &mut AirInventory) -> Result<(), AirInventoryError> { let SystemPort { execution_bus, @@ -218,7 +314,8 @@ impl VmCircuitExtension for WeierstrassExtension { air.bus } }; - for (i, curve) in self.supported_curves.iter().enumerate() { + + for (i, curve) in self.supported_sw_curves.iter().enumerate() { let start_offset = Rv32WeierstrassOpcode::CLASS_OFFSET + i * Rv32WeierstrassOpcode::COUNT; let bytes = curve.modulus.bits().div_ceil(8); @@ -230,7 +327,7 @@ impl VmCircuitExtension for WeierstrassExtension { limb_bits: 8, }; - let addne = get_ec_addne_air::<2, 32>( + let addne = get_sw_addne_air::<2, 32>( exec_bridge, memory_bridge, config.clone(), @@ -241,7 +338,7 @@ impl VmCircuitExtension for WeierstrassExtension { ); inventory.add_air(addne); - let double = get_ec_double_air::<2, 32>( + let double = get_sw_double_air::<2, 32>( exec_bridge, memory_bridge, config, @@ -249,7 +346,7 @@ impl VmCircuitExtension for WeierstrassExtension { bitwise_lu, pointer_max_bits, start_offset, - curve.a.clone(), + curve.coeffs.a.clone(), ); inventory.add_air(double); } else if bytes <= 48 { @@ -259,7 +356,7 @@ impl VmCircuitExtension for WeierstrassExtension { limb_bits: 8, }; - let addne = get_ec_addne_air::<6, 16>( + let addne = get_sw_addne_air::<6, 16>( exec_bridge, memory_bridge, config.clone(), @@ -270,7 +367,7 @@ impl VmCircuitExtension for WeierstrassExtension { ); inventory.add_air(addne); - let double = get_ec_double_air::<6, 16>( + let double = get_sw_double_air::<6, 16>( exec_bridge, memory_bridge, config, @@ -278,7 +375,7 @@ impl VmCircuitExtension for WeierstrassExtension { bitwise_lu, pointer_max_bits, start_offset, - curve.a.clone(), + curve.coeffs.a.clone(), ); inventory.add_air(double); } else { @@ -286,13 +383,41 @@ impl VmCircuitExtension for WeierstrassExtension { } } + for (i, curve) in self.supported_te_curves.iter().enumerate() { + let start_offset = Rv32EdwardsOpcode::CLASS_OFFSET + i * Rv32EdwardsOpcode::COUNT; + let bytes = curve.modulus.bits().div_ceil(8); + + if bytes <= 32 { + let config = ExprBuilderConfig { + modulus: curve.modulus.clone(), + num_limbs: 32, + limb_bits: 8, + }; + + let add = get_te_add_air::<2, 32>( + exec_bridge, + memory_bridge, + config.clone(), + range_checker_bus, + bitwise_lu, + pointer_max_bits, + start_offset, + curve.coeffs.a.clone(), + curve.coeffs.d.clone(), + ); + inventory.add_air(add); + } else { + panic!("Modulus too large"); + } + } + Ok(()) } } // This implementation is specific to CpuBackend because the lookup chips (VariableRangeChecker, // BitwiseOperationLookupChip) are specific to CpuBackend. -impl VmProverExtension for EccCpuProverExt +impl VmProverExtension for EccCpuProverExt where SC: StarkGenericConfig, E: StarkEngine, PD = CpuDevice>, @@ -301,7 +426,7 @@ where { fn extend_prover( &self, - extension: &WeierstrassExtension, + extension: &EccExtension, inventory: &mut ChipInventory>, ) -> Result<(), ChipInventoryError> { let range_checker = inventory.range_checker()?.clone(); @@ -321,7 +446,8 @@ where chip } }; - for curve in extension.supported_curves.iter() { + + for curve in extension.supported_sw_curves.iter() { let bytes = curve.modulus.bits().div_ceil(8); if bytes <= 32 { @@ -332,7 +458,7 @@ where }; inventory.next_air::>()?; - let addne = get_ec_addne_chip::, 2, 32>( + let addne = get_sw_addne_chip::, 2, 32>( config.clone(), mem_helper.clone(), range_checker.clone(), @@ -342,13 +468,13 @@ where inventory.add_executor_chip(addne); inventory.next_air::>()?; - let double = get_ec_double_chip::, 2, 32>( + let double = get_sw_double_chip::, 2, 32>( config, mem_helper.clone(), range_checker.clone(), bitwise_lu.clone(), pointer_max_bits, - curve.a.clone(), + curve.coeffs.a.clone(), ); inventory.add_executor_chip(double); } else if bytes <= 48 { @@ -359,7 +485,7 @@ where }; inventory.next_air::>()?; - let addne = get_ec_addne_chip::, 6, 16>( + let addne = get_sw_addne_chip::, 6, 16>( config.clone(), mem_helper.clone(), range_checker.clone(), @@ -369,13 +495,13 @@ where inventory.add_executor_chip(addne); inventory.next_air::>()?; - let double = get_ec_double_chip::, 6, 16>( + let double = get_sw_double_chip::, 6, 16>( config, mem_helper.clone(), range_checker.clone(), bitwise_lu.clone(), pointer_max_bits, - curve.a.clone(), + curve.coeffs.a.clone(), ); inventory.add_executor_chip(double); } else { @@ -383,6 +509,32 @@ where } } + for curve in extension.supported_te_curves.iter() { + let bytes = curve.modulus.bits().div_ceil(8); + + if bytes <= 32 { + let config = ExprBuilderConfig { + modulus: curve.modulus.clone(), + num_limbs: 32, + limb_bits: 8, + }; + + inventory.next_air::>()?; + let add = get_te_add_chip::, 2, 32>( + config.clone(), + mem_helper.clone(), + range_checker.clone(), + bitwise_lu.clone(), + pointer_max_bits, + curve.coeffs.a.clone(), + curve.coeffs.d.clone(), + ); + inventory.add_executor_chip(add); + } else { + panic!("Modulus too large"); + } + } + Ok(()) } } @@ -408,9 +560,11 @@ lazy_static! { )); } // little-endian -const P256_A: [u8; 32] = hex!("fcffffffffffffffffffffff00000000000000000000000001000000ffffffff"); +pub const P256_A: [u8; 32] = + hex!("fcffffffffffffffffffffff00000000000000000000000001000000ffffffff"); // little-endian -const P256_B: [u8; 32] = hex!("4b60d2273e3cce3bf6b053ccb0061d65bc86987655bdebb3e7933aaad835c65a"); +pub const P256_B: [u8; 32] = + hex!("4b60d2273e3cce3bf6b053ccb0061d65bc86987655bdebb3e7933aaad835c65a"); pub const SECP256K1_ECC_STRUCT_NAME: &str = "Secp256k1Point"; pub const P256_ECC_STRUCT_NAME: &str = "P256Point"; diff --git a/extensions/ecc/circuit/src/edwards_chip/README.md b/extensions/ecc/circuit/src/edwards_chip/README.md new file mode 100644 index 0000000000..24167e062a --- /dev/null +++ b/extensions/ecc/circuit/src/edwards_chip/README.md @@ -0,0 +1,17 @@ +# Twisted Edwards (TE) curve operations + +The `te_add` instruction is implemented in the `edwards_chip` module. + +### 1. `te_add` + +**Assumptions:** + +- Both points `(x1, y1)` and `(x2, y2)` lie on the curve. + +**Circuit statements:** + +- The chip takes two inputs: `(x1, y1)` and `(x2, y2)`, and returns `(x3, y3)` where: + - `x3 = (x1 * y2 + x2 * y1) / (1 + d * x1 * x2 * y1 * y2)` + - `y3 = (y1 * y2 - a * x1 * x2) / (1 - d * x1 * x2 * y1 * y2)` + +- The `TeAddChip` constrains that these field expressions are computed correctly over the field `C::Fp`. The coefficients `a` and `d` are taken from the `CurveConfig`. diff --git a/extensions/ecc/circuit/src/edwards_chip/add.rs b/extensions/ecc/circuit/src/edwards_chip/add.rs new file mode 100644 index 0000000000..6fa1512589 --- /dev/null +++ b/extensions/ecc/circuit/src/edwards_chip/add.rs @@ -0,0 +1,470 @@ +use std::{ + array::from_fn, + borrow::{Borrow, BorrowMut}, + cell::RefCell, + rc::Rc, +}; + +use derive_more::derive::{Deref, DerefMut}; +use num_bigint::BigUint; +use num_traits::One; +use openvm_circuit::{ + arch::{ExecutionBridge, *}, + system::memory::{ + offline_checker::MemoryBridge, online::GuestMemory, SharedMemoryHelper, POINTER_MAX_BITS, + }, +}; +use openvm_circuit_derive::PreflightExecutor; +use openvm_circuit_primitives::{ + bitwise_op_lookup::{BitwiseOperationLookupBus, SharedBitwiseOperationLookupChip}, + var_range::{SharedVariableRangeCheckerChip, VariableRangeCheckerBus}, + AlignedBytesBorrow, +}; +use openvm_ecc_transpiler::Rv32EdwardsOpcode; +use openvm_instructions::{ + instruction::Instruction, + program::DEFAULT_PC_STEP, + riscv::{RV32_CELL_BITS, RV32_MEMORY_AS, RV32_REGISTER_AS}, +}; +use openvm_mod_circuit_builder::{ + run_field_expression_precomputed, ExprBuilder, ExprBuilderConfig, FieldExpr, + FieldExpressionCoreAir, FieldExpressionExecutor, FieldExpressionFiller, +}; +use openvm_rv32_adapters::{ + Rv32VecHeapAdapterAir, Rv32VecHeapAdapterExecutor, Rv32VecHeapAdapterFiller, +}; +use openvm_stark_backend::p3_field::PrimeField32; + +use super::{utils::jacobi, EdwardsAir, EdwardsChip}; +use crate::edwards_chip::curves::{get_te_curve_type, te_add, TeCurveType}; + +pub fn te_add_expr( + config: ExprBuilderConfig, // The coordinate field. + range_bus: VariableRangeCheckerBus, + a_biguint: BigUint, + d_biguint: BigUint, +) -> FieldExpr { + config.check_valid(); + let builder = ExprBuilder::new(config, range_bus.range_max_bits); + let builder = Rc::new(RefCell::new(builder)); + + let x1 = ExprBuilder::new_input(builder.clone()); + let y1 = ExprBuilder::new_input(builder.clone()); + let x2 = ExprBuilder::new_input(builder.clone()); + let y2 = ExprBuilder::new_input(builder.clone()); + let a = ExprBuilder::new_const(builder.clone(), a_biguint.clone()); + let d = ExprBuilder::new_const(builder.clone(), d_biguint.clone()); + let one = ExprBuilder::new_const(builder.clone(), BigUint::one()); + + let x1y2 = x1.clone() * y2.clone(); + let x2y1 = x2.clone() * y1.clone(); + let y1y2 = y1 * y2; + let x1x2 = x1 * x2; + let dx1x2y1y2 = d * x1x2.clone() * y1y2.clone(); + + let mut x3 = (x1y2 + x2y1) / (one.clone() + dx1x2y1y2.clone()); + let mut y3 = (y1y2 - a * x1x2) / (one - dx1x2y1y2); + + x3.save_output(); + y3.save_output(); + + let builder = (*builder).borrow().clone(); + + FieldExpr::new_with_setup_values(builder, range_bus, true, vec![a_biguint, d_biguint]) +} + +#[derive(Clone, PreflightExecutor, Deref, DerefMut)] +pub struct TeAddExecutor( + pub(crate) FieldExpressionExecutor< + Rv32VecHeapAdapterExecutor<2, BLOCKS, BLOCKS, BLOCK_SIZE, BLOCK_SIZE>, + >, +); + +fn gen_base_expr( + config: ExprBuilderConfig, + range_checker_bus: VariableRangeCheckerBus, + a_biguint: BigUint, + d_biguint: BigUint, +) -> (FieldExpr, Vec) { + let expr = te_add_expr(config, range_checker_bus, a_biguint, d_biguint); + + let local_opcode_idx = vec![ + Rv32EdwardsOpcode::TE_ADD as usize, + Rv32EdwardsOpcode::SETUP_TE_ADD as usize, + ]; + + (expr, local_opcode_idx) +} + +#[allow(clippy::too_many_arguments)] +pub fn get_te_add_air( + exec_bridge: ExecutionBridge, + mem_bridge: MemoryBridge, + config: ExprBuilderConfig, + range_checker_bus: VariableRangeCheckerBus, + bitwise_lookup_bus: BitwiseOperationLookupBus, + pointer_max_bits: usize, + offset: usize, + a_biguint: BigUint, + d_biguint: BigUint, +) -> EdwardsAir<2, BLOCKS, BLOCK_SIZE> { + // Ensure that the addition operation is complete + assert!(jacobi(&a_biguint.clone().into(), &config.modulus.clone().into()) == 1); + assert!(jacobi(&d_biguint.clone().into(), &config.modulus.clone().into()) == -1); + + let (expr, local_opcode_idx) = gen_base_expr(config, range_checker_bus, a_biguint, d_biguint); + EdwardsAir::new( + Rv32VecHeapAdapterAir::new( + exec_bridge, + mem_bridge, + bitwise_lookup_bus, + pointer_max_bits, + ), + FieldExpressionCoreAir::new(expr.clone(), offset, local_opcode_idx.clone(), vec![]), + ) +} + +pub fn get_te_add_step( + config: ExprBuilderConfig, + range_checker_bus: VariableRangeCheckerBus, + pointer_max_bits: usize, + offset: usize, + a_biguint: BigUint, + d_biguint: BigUint, +) -> TeAddExecutor { + // Ensure that the addition operation is complete + assert!(jacobi(&a_biguint.clone().into(), &config.modulus.clone().into()) == 1); + assert!(jacobi(&d_biguint.clone().into(), &config.modulus.clone().into()) == -1); + + let (expr, local_opcode_idx) = gen_base_expr(config, range_checker_bus, a_biguint, d_biguint); + TeAddExecutor(FieldExpressionExecutor::new( + Rv32VecHeapAdapterExecutor::new(pointer_max_bits), + expr, + offset, + local_opcode_idx, + vec![], + "TeAdd", + )) +} + +pub fn get_te_add_chip( + config: ExprBuilderConfig, + mem_helper: SharedMemoryHelper, + range_checker: SharedVariableRangeCheckerChip, + bitwise_lookup_chip: SharedBitwiseOperationLookupChip, + pointer_max_bits: usize, + a_biguint: BigUint, + d_biguint: BigUint, +) -> EdwardsChip { + // Ensure that the addition operation is complete + assert!(jacobi(&a_biguint.clone().into(), &config.modulus.clone().into()) == 1); + assert!(jacobi(&d_biguint.clone().into(), &config.modulus.clone().into()) == -1); + + let (expr, local_opcode_idx) = gen_base_expr(config, range_checker.bus(), a_biguint, d_biguint); + EdwardsChip::new( + FieldExpressionFiller::new( + Rv32VecHeapAdapterFiller::new(pointer_max_bits, bitwise_lookup_chip), + expr, + local_opcode_idx, + vec![], + range_checker, + true, + ), + mem_helper, + ) +} + +#[derive(AlignedBytesBorrow, Clone)] +#[repr(C)] +struct TeAddPreCompute<'a> { + expr: &'a FieldExpr, + rs_addrs: [u8; 2], + a: u8, + flag_idx: u8, +} + +impl<'a, const BLOCKS: usize, const BLOCK_SIZE: usize> TeAddExecutor { + fn pre_compute_impl( + &'a self, + pc: u32, + inst: &Instruction, + data: &mut TeAddPreCompute<'a>, + ) -> Result { + let Instruction { + opcode, + a, + b, + c, + d, + e, + .. + } = inst; + + // Validate instruction format + let a = a.as_canonical_u32(); + let b = b.as_canonical_u32(); + let c = c.as_canonical_u32(); + let d = d.as_canonical_u32(); + let e = e.as_canonical_u32(); + if d != RV32_REGISTER_AS || e != RV32_MEMORY_AS { + return Err(StaticProgramError::InvalidInstruction(pc)); + } + + let local_opcode = opcode.local_opcode_idx(self.offset); + + // Pre-compute flag_idx + let needs_setup = self.expr.needs_setup(); + let mut flag_idx = self.expr.num_flags() as u8; + if needs_setup { + // Find which opcode this is in our local_opcode_idx list + if let Some(opcode_position) = self + .local_opcode_idx + .iter() + .position(|&idx| idx == local_opcode) + { + // If this is NOT the last opcode (setup), get the corresponding flag_idx + if opcode_position < self.opcode_flag_idx.len() { + flag_idx = self.opcode_flag_idx[opcode_position] as u8; + } + } + } + + let rs_addrs = from_fn(|i| if i == 0 { b } else { c } as u8); + *data = TeAddPreCompute { + expr: &self.expr, + rs_addrs, + a: a as u8, + flag_idx, + }; + + let local_opcode = opcode.local_opcode_idx(self.offset); + let is_setup = local_opcode == Rv32EdwardsOpcode::SETUP_TE_ADD as usize; + + Ok(is_setup) + } +} + +impl Executor + for TeAddExecutor +{ + #[inline(always)] + fn pre_compute_size(&self) -> usize { + std::mem::size_of::() + } + + fn pre_compute( + &self, + pc: u32, + inst: &Instruction, + data: &mut [u8], + ) -> Result, StaticProgramError> + where + Ctx: E1ExecutionCtx, + { + let pre_compute: &mut TeAddPreCompute = data.borrow_mut(); + + let is_setup = self.pre_compute_impl(pc, inst, pre_compute)?; + + if let Some(curve_type) = { + let modulus = &pre_compute.expr.builder.prime; + let a_coeff = &pre_compute.expr.setup_values[0]; + let d_coeff = &pre_compute.expr.setup_values[1]; + get_te_curve_type(modulus, a_coeff, d_coeff) + } { + match (is_setup, curve_type) { + (true, TeCurveType::ED25519) => Ok(execute_e12_impl::< + _, + _, + BLOCKS, + BLOCK_SIZE, + { TeCurveType::ED25519 as u8 }, + true, + >), + (false, TeCurveType::ED25519) => Ok(execute_e12_impl::< + _, + _, + BLOCKS, + BLOCK_SIZE, + { TeCurveType::ED25519 as u8 }, + false, + >), + } + } else if is_setup { + Ok(execute_e12_impl::<_, _, BLOCKS, BLOCK_SIZE, { u8::MAX }, true>) + } else { + Ok(execute_e12_impl::<_, _, BLOCKS, BLOCK_SIZE, { u8::MAX }, false>) + } + } +} + +impl MeteredExecutor + for TeAddExecutor +{ + #[inline(always)] + fn metered_pre_compute_size(&self) -> usize { + std::mem::size_of::>() + } + + fn metered_pre_compute( + &self, + chip_idx: usize, + pc: u32, + inst: &Instruction, + data: &mut [u8], + ) -> Result, StaticProgramError> + where + Ctx: E2ExecutionCtx, + { + let pre_compute: &mut E2PreCompute = data.borrow_mut(); + pre_compute.chip_idx = chip_idx as u32; + + let is_setup = self.pre_compute_impl(pc, inst, &mut pre_compute.data)?; + + if let Some(curve_type) = { + let modulus = &pre_compute.data.expr.builder.prime; + let a_coeff = &pre_compute.data.expr.setup_values[0]; + let d_coeff = &pre_compute.data.expr.setup_values[1]; + get_te_curve_type(modulus, a_coeff, d_coeff) + } { + match (is_setup, curve_type) { + (true, TeCurveType::ED25519) => Ok(execute_e2_setup_impl::< + _, + _, + BLOCKS, + BLOCK_SIZE, + { TeCurveType::ED25519 as u8 }, + >), + (false, TeCurveType::ED25519) => { + Ok(execute_e2_impl::<_, _, BLOCKS, BLOCK_SIZE, { TeCurveType::ED25519 as u8 }>) + } + } + } else if is_setup { + Ok(execute_e2_setup_impl::<_, _, BLOCKS, BLOCK_SIZE, { u8::MAX }>) + } else { + Ok(execute_e2_impl::<_, _, BLOCKS, BLOCK_SIZE, { u8::MAX }>) + } + } +} + +unsafe fn execute_e2_impl< + F: PrimeField32, + CTX: E2ExecutionCtx, + const BLOCKS: usize, + const BLOCK_SIZE: usize, + const CURVE_TYPE: u8, +>( + pre_compute: &[u8], + vm_state: &mut VmExecState, +) { + let e2_pre_compute: &E2PreCompute = pre_compute.borrow(); + vm_state + .ctx + .on_height_change(e2_pre_compute.chip_idx as usize, 1); + let pre_compute = unsafe { + std::slice::from_raw_parts( + &e2_pre_compute.data as *const _ as *const u8, + std::mem::size_of::(), + ) + }; + execute_e12_impl::<_, _, BLOCKS, BLOCK_SIZE, CURVE_TYPE, false>(pre_compute, vm_state); +} + +unsafe fn execute_e2_setup_impl< + F: PrimeField32, + CTX: E2ExecutionCtx, + const BLOCKS: usize, + const BLOCK_SIZE: usize, + const CURVE_TYPE: u8, +>( + pre_compute: &[u8], + vm_state: &mut VmExecState, +) { + let e2_pre_compute: &E2PreCompute = pre_compute.borrow(); + vm_state + .ctx + .on_height_change(e2_pre_compute.chip_idx as usize, 1); + let pre_compute = unsafe { + std::slice::from_raw_parts( + &e2_pre_compute.data as *const _ as *const u8, + std::mem::size_of::(), + ) + }; + execute_e12_impl::<_, _, BLOCKS, BLOCK_SIZE, CURVE_TYPE, true>(pre_compute, vm_state); +} + +unsafe fn execute_e12_impl< + F: PrimeField32, + CTX: E1ExecutionCtx, + const BLOCKS: usize, + const BLOCK_SIZE: usize, + const CURVE_TYPE: u8, + const IS_SETUP: bool, +>( + pre_compute: &[u8], + vm_state: &mut VmExecState, +) { + let pre_compute: &TeAddPreCompute = pre_compute.borrow(); + // Read register values + let rs_vals = pre_compute + .rs_addrs + .map(|addr| u32::from_le_bytes(vm_state.vm_read(RV32_REGISTER_AS, addr as u32))); + + // Read memory values for both points + let read_data: [[[u8; BLOCK_SIZE]; BLOCKS]; 2] = rs_vals.map(|address| { + debug_assert!(address as usize + BLOCK_SIZE * BLOCKS - 1 < (1 << POINTER_MAX_BITS)); + from_fn(|i| vm_state.vm_read(RV32_MEMORY_AS, address + (i * BLOCK_SIZE) as u32)) + }); + + if IS_SETUP { + let input_prime = BigUint::from_bytes_le(read_data[0][..BLOCKS / 2].as_flattened()); + let input_a = BigUint::from_bytes_le(read_data[0][BLOCKS / 2..].as_flattened()); + let input_d = BigUint::from_bytes_le(read_data[1][..BLOCKS / 2].as_flattened()); + + if input_prime != pre_compute.expr.prime { + vm_state.exit_code = Err(ExecutionError::Fail { + pc: vm_state.pc, + msg: "TeAdd: mismatched prime", + }); + return; + } + + if input_a != pre_compute.expr.setup_values[0] { + vm_state.exit_code = Err(ExecutionError::Fail { + pc: vm_state.pc, + msg: "TeAdd: mismatched a", + }); + return; + } + + if input_d != pre_compute.expr.setup_values[1] { + vm_state.exit_code = Err(ExecutionError::Fail { + pc: vm_state.pc, + msg: "TeAdd: mismatched d", + }); + return; + } + } + + let output_data = if CURVE_TYPE == u8::MAX { + let read_data: DynArray = read_data.into(); + run_field_expression_precomputed::( + pre_compute.expr, + pre_compute.flag_idx as usize, + &read_data.0, + ) + .into() + } else { + te_add::(read_data) + }; + + let rd_val = u32::from_le_bytes(vm_state.vm_read(RV32_REGISTER_AS, pre_compute.a as u32)); + debug_assert!(rd_val as usize + BLOCK_SIZE * BLOCKS - 1 < (1 << POINTER_MAX_BITS)); + + // Write output data to memory + for (i, block) in output_data.into_iter().enumerate() { + vm_state.vm_write(RV32_MEMORY_AS, rd_val + (i * BLOCK_SIZE) as u32, &block); + } + + vm_state.pc = vm_state.pc.wrapping_add(DEFAULT_PC_STEP); + vm_state.instret += 1; +} diff --git a/extensions/ecc/circuit/src/edwards_chip/curves.rs b/extensions/ecc/circuit/src/edwards_chip/curves.rs new file mode 100644 index 0000000000..db37b5c616 --- /dev/null +++ b/extensions/ecc/circuit/src/edwards_chip/curves.rs @@ -0,0 +1,89 @@ +use halo2curves_axiom::{ed25519::TwistedEdwardsCurveExt, ff::PrimeField}; +use lazy_static::lazy_static; +use num_bigint::BigUint; +use openvm_algebra_circuit::fields::{blocks_to_field_element, field_element_to_blocks}; + +use crate::weierstrass_chip::curves::get_modulus_as_bigint; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TeCurveType { + ED25519 = 0, +} + +pub(super) fn get_te_curve_type( + modulus: &BigUint, + a_coeff: &BigUint, + d_coeff: &BigUint, +) -> Option { + if modulus == &ED25519_CURVE_PARAMS.modulus + && a_coeff == &ED25519_CURVE_PARAMS.a + && d_coeff == &ED25519_CURVE_PARAMS.d + { + return Some(TeCurveType::ED25519); + } + + None +} + +struct CurveParams { + modulus: BigUint, + a: BigUint, + d: BigUint, +} + +lazy_static! { + static ref ED25519_CURVE_PARAMS: CurveParams = CurveParams { + modulus: get_modulus_as_bigint::(), + a: BigUint::from_bytes_le( + &::a().to_repr(), + ), + d: BigUint::from_bytes_le( + &::d().to_repr(), + ), + }; +} + +#[inline(always)] +pub fn te_add( + input_data: [[[u8; BLOCK_SIZE]; BLOCKS]; 2], +) -> [[u8; BLOCK_SIZE]; BLOCKS] { + match CURVE_TYPE { + x if x == TeCurveType::ED25519 as u8 => { + te_add_256bit::( + input_data, + halo2curves_axiom::ed25519::Ed25519::a(), + halo2curves_axiom::ed25519::Ed25519::d(), + ) + } + _ => panic!("Unsupported curve type: {}", CURVE_TYPE), + } +} + +#[inline(always)] +fn te_add_256bit, const BLOCKS: usize, const BLOCK_SIZE: usize>( + input_data: [[[u8; BLOCK_SIZE]; BLOCKS]; 2], + a: F, + d: F, +) -> [[u8; BLOCK_SIZE]; BLOCKS] { + let x1 = blocks_to_field_element::(input_data[0][..BLOCKS / 2].as_flattened()); + let y1 = blocks_to_field_element::(input_data[0][BLOCKS / 2..].as_flattened()); + let x2 = blocks_to_field_element::(input_data[1][..BLOCKS / 2].as_flattened()); + let y2 = blocks_to_field_element::(input_data[1][BLOCKS / 2..].as_flattened()); + + let (x3, y3) = te_add_impl::(x1, y1, x2, y2, a, d); + + let mut output = [[0u8; BLOCK_SIZE]; BLOCKS]; + field_element_to_blocks::(&x3, &mut output[..BLOCKS / 2]); + field_element_to_blocks::(&y3, &mut output[BLOCKS / 2..]); + output +} + +#[inline(always)] +pub fn te_add_impl(x1: F, y1: F, x2: F, y2: F, a: F, d: F) -> (F, F) { + println!("te_add_impl called, a: {:?}, d: {:?}", a, d); + let dx1x2y1y2 = d * x1 * x2 * y1 * y2; + let x3 = (x1 * y2 + x2 * y1) * (F::ONE + dx1x2y1y2).invert().unwrap(); + let y3 = (y1 * y2 - a * x1 * x2) * (F::ONE - dx1x2y1y2).invert().unwrap(); + + (x3, y3) +} diff --git a/extensions/ecc/circuit/src/edwards_chip/mod.rs b/extensions/ecc/circuit/src/edwards_chip/mod.rs new file mode 100644 index 0000000000..a396646f88 --- /dev/null +++ b/extensions/ecc/circuit/src/edwards_chip/mod.rs @@ -0,0 +1,30 @@ +mod add; +pub use add::*; + +mod curves; +mod utils; + +#[cfg(test)] +mod tests; + +use openvm_circuit::arch::{VmAirWrapper, VmChipWrapper}; +use openvm_mod_circuit_builder::{FieldExpressionCoreAir, FieldExpressionFiller}; +use openvm_rv32_adapters::{Rv32VecHeapAdapterAir, Rv32VecHeapAdapterFiller}; + +pub(crate) type EdwardsAir = + VmAirWrapper< + Rv32VecHeapAdapterAir, + FieldExpressionCoreAir, + >; + +pub(crate) type EdwardsChip< + F, + const NUM_READS: usize, + const BLOCKS: usize, + const BLOCK_SIZE: usize, +> = VmChipWrapper< + F, + FieldExpressionFiller< + Rv32VecHeapAdapterFiller, + >, +>; diff --git a/extensions/ecc/circuit/src/edwards_chip/tests.rs b/extensions/ecc/circuit/src/edwards_chip/tests.rs new file mode 100644 index 0000000000..9a24ff6d22 --- /dev/null +++ b/extensions/ecc/circuit/src/edwards_chip/tests.rs @@ -0,0 +1,242 @@ +use std::{str::FromStr, sync::Arc}; + +use num_bigint::BigUint; +use num_traits::FromPrimitive; +use openvm_circuit::arch::{ + testing::{TestChipHarness, VmChipTestBuilder, BITWISE_OP_LOOKUP_BUS}, + MatrixRecordArena, +}; +use openvm_circuit_primitives::{ + bigint::utils::big_uint_to_limbs, + bitwise_op_lookup::{ + BitwiseOperationLookupAir, BitwiseOperationLookupBus, BitwiseOperationLookupChip, + SharedBitwiseOperationLookupChip, + }, +}; +use openvm_ecc_transpiler::Rv32EdwardsOpcode; +use openvm_instructions::{riscv::RV32_CELL_BITS, LocalOpcode}; +use openvm_mod_circuit_builder::{test_utils::biguint_to_limbs, ExprBuilderConfig}; +use openvm_rv32_adapters::rv32_write_heap_default; +use openvm_stark_backend::p3_field::FieldAlgebra; +use openvm_stark_sdk::p3_baby_bear::BabyBear; + +use crate::{ + edwards_chip::{ + get_te_add_air, get_te_add_chip, get_te_add_step, EdwardsAir, EdwardsChip, TeAddExecutor, + }, + weierstrass_chip::prime_limbs, +}; + +const NUM_LIMBS: usize = 32; +const LIMB_BITS: usize = 8; +const BLOCK_SIZE: usize = 32; +const MAX_INS_CAPACITY: usize = 128; +type F = BabyBear; + +lazy_static::lazy_static! { + pub static ref SampleEcPoints: Vec<(BigUint, BigUint)> = { + // Base point of edwards25519 + let x1 = BigUint::from_str( + "15112221349535400772501151409588531511454012693041857206046113283949847762202", + ) + .unwrap(); + let y1 = BigUint::from_str( + "46316835694926478169428394003475163141307993866256225615783033603165251855960", + ) + .unwrap(); + + // random point on edwards25519 + let x2 = BigUint::from_u32(2).unwrap(); + let y2 = BigUint::from_str( + "11879831548380997166425477238087913000047176376829905612296558668626594440753", + ) + .unwrap(); + + // This is the sum of (x1, y1) and (x2, y2). + let x3 = BigUint::from_str( + "44969869612046584870714054830543834361257841801051546235130567688769346152934", + ) + .unwrap(); + let y3 = BigUint::from_str( + "50796027728050908782231253190819121962159170739537197094456293084373503699602", + ) + .unwrap(); + + // This is 2 * (x1, y1) + let x4 = BigUint::from_str( + "39226743113244985161159605482495583316761443760287217110659799046557361995496", + ) + .unwrap(); + let y4 = BigUint::from_str( + "12570354238812836652656274015246690354874018829607973815551555426027032771563", + ) + .unwrap(); + + vec![(x1, y1), (x2, y2), (x3, y3), (x4, y4)] + }; + + pub static ref Edwards25519_Prime: BigUint = BigUint::from_str( + "57896044618658097711785492504343953926634992332820282019728792003956564819949", + ) + .unwrap(); + + pub static ref Edwards25519_A: BigUint = BigUint::from_str( + "57896044618658097711785492504343953926634992332820282019728792003956564819948", + ) + .unwrap(); + + pub static ref Edwards25519_D: BigUint = BigUint::from_str( + "37095705934669439343138083508754565189542113879843219016388785533085940283555", + ) + .unwrap(); + + pub static ref Edwards25519_A_LIMBS: [BabyBear; NUM_LIMBS] = + big_uint_to_limbs(&Edwards25519_A, LIMB_BITS) + .into_iter() + .map(BabyBear::from_canonical_usize) + .collect::>() + .try_into() + .unwrap(); + pub static ref Edwards25519_D_LIMBS: [BabyBear; NUM_LIMBS] = + big_uint_to_limbs(&Edwards25519_D, LIMB_BITS) + .into_iter() + .map(BabyBear::from_canonical_usize) + .collect::>() + .try_into() + .unwrap(); +} + +type EdwardsHarness = TestChipHarness< + F, + TeAddExecutor<2, BLOCK_SIZE>, + EdwardsAir<2, 2, BLOCK_SIZE>, + EdwardsChip, + MatrixRecordArena, +>; + +fn create_test_chip( + tester: &VmChipTestBuilder, + config: ExprBuilderConfig, + offset: usize, + a_biguint: BigUint, + d_biguint: BigUint, +) -> ( + EdwardsHarness, + ( + BitwiseOperationLookupAir, + SharedBitwiseOperationLookupChip, + ), +) { + let bitwise_bus = BitwiseOperationLookupBus::new(BITWISE_OP_LOOKUP_BUS); + let bitwise_chip = Arc::new(BitwiseOperationLookupChip::::new( + bitwise_bus, + )); + let air = get_te_add_air( + tester.execution_bridge(), + tester.memory_bridge(), + config.clone(), + tester.range_checker().bus(), + bitwise_bus, + tester.address_bits(), + offset, + a_biguint.clone(), + d_biguint.clone(), + ); + let executor = get_te_add_step( + config.clone(), + tester.range_checker().bus(), + tester.address_bits(), + offset, + a_biguint.clone(), + d_biguint.clone(), + ); + let chip = get_te_add_chip( + config.clone(), + tester.memory_helper(), + tester.range_checker(), + bitwise_chip.clone(), + tester.address_bits(), + a_biguint, + d_biguint, + ); + let harness = EdwardsHarness::with_capacity(executor, air, chip, MAX_INS_CAPACITY); + + (harness, (bitwise_chip.air, bitwise_chip)) +} + +#[test] +fn test_add() { + let mut tester: VmChipTestBuilder = VmChipTestBuilder::default(); + let config = ExprBuilderConfig { + modulus: Edwards25519_Prime.clone(), + num_limbs: NUM_LIMBS, + limb_bits: LIMB_BITS, + }; + + let (mut harness, bitwise) = create_test_chip( + &tester, + config, + Rv32EdwardsOpcode::CLASS_OFFSET, + Edwards25519_A.clone(), + Edwards25519_D.clone(), + ); + + assert_eq!(harness.executor.expr.builder.num_variables, 12); + + let (p1_x, p1_y) = SampleEcPoints[0].clone(); + let (p2_x, p2_y) = SampleEcPoints[1].clone(); + + let p1_x_limbs = + biguint_to_limbs::(p1_x.clone(), LIMB_BITS).map(BabyBear::from_canonical_u32); + let p1_y_limbs = + biguint_to_limbs::(p1_y.clone(), LIMB_BITS).map(BabyBear::from_canonical_u32); + let p2_x_limbs = + biguint_to_limbs::(p2_x.clone(), LIMB_BITS).map(BabyBear::from_canonical_u32); + let p2_y_limbs = + biguint_to_limbs::(p2_y.clone(), LIMB_BITS).map(BabyBear::from_canonical_u32); + + let r = harness + .executor + .expr + .execute(vec![p1_x, p1_y, p2_x, p2_y], vec![true]); + assert_eq!(r.len(), 12); + + let outputs = harness + .executor + .expr + .output_indices() + .iter() + .map(|i| &r[*i]) + .collect::>(); + assert_eq!(outputs[0], &SampleEcPoints[2].0); + assert_eq!(outputs[1], &SampleEcPoints[2].1); + + let prime_limbs: [BabyBear; NUM_LIMBS] = + prime_limbs(&harness.executor.expr).try_into().unwrap(); + let mut one_limbs = [BabyBear::ZERO; NUM_LIMBS]; + one_limbs[0] = BabyBear::ONE; + let setup_instruction = rv32_write_heap_default( + &mut tester, + vec![prime_limbs, *Edwards25519_A_LIMBS], + vec![*Edwards25519_D_LIMBS], + harness.executor.offset + Rv32EdwardsOpcode::SETUP_TE_ADD as usize, + ); + tester.execute(&mut harness, &setup_instruction); + + let instruction = rv32_write_heap_default( + &mut tester, + vec![p1_x_limbs, p1_y_limbs], + vec![p2_x_limbs, p2_y_limbs], + harness.executor.offset + Rv32EdwardsOpcode::TE_ADD as usize, + ); + + tester.execute(&mut harness, &instruction); + + let tester = tester + .build() + .load(harness) + .load_periphery(bitwise) + .finalize(); + + tester.simple_test().expect("Verification failed"); +} diff --git a/extensions/ecc/circuit/src/edwards_chip/utils.rs b/extensions/ecc/circuit/src/edwards_chip/utils.rs new file mode 100644 index 0000000000..ce7711519f --- /dev/null +++ b/extensions/ecc/circuit/src/edwards_chip/utils.rs @@ -0,0 +1,101 @@ +use num_bigint::BigInt; +use num_integer::Integer; +use num_traits::{sign::Signed, One, Zero}; + +/// Jacobi returns the Jacobi symbol (x/y), either +1, -1, or 0. +/// The y argument must be an odd integer. +pub fn jacobi(x: &BigInt, y: &BigInt) -> isize { + if !y.is_odd() { + panic!( + "invalid arguments, y must be an odd integer,but got {:?}", + y + ); + } + + let mut a = x.clone(); + let mut b = y.clone(); + let mut j = 1; + + if b.is_negative() { + if a.is_negative() { + j = -1; + } + b = -b; + } + + loop { + if b.is_one() { + return j; + } + if a.is_zero() { + return 0; + } + + a = a.mod_floor(&b); + if a.is_zero() { + return 0; + } + + // a > 0 + + // handle factors of 2 in a + let s = a.trailing_zeros().unwrap(); + if s & 1 != 0 { + //let bmod8 = b.get_limb(0) & 7; + let bmod8 = mod_2_to_the_k(&b, 3); + if bmod8 == BigInt::from(3) || bmod8 == BigInt::from(5) { + j = -j; + } + } + + let c = &a >> s; // a = 2^s*c + + // swap numerator and denominator + if mod_2_to_the_k(&b, 2) == BigInt::from(3) && mod_2_to_the_k(&c, 2) == BigInt::from(3) { + j = -j + } + + a = b; + b = c; + } +} + +fn mod_2_to_the_k(x: &BigInt, k: u32) -> BigInt { + x & BigInt::from(2u32.pow(k) - 1) +} +#[cfg(test)] +mod tests { + use num_traits::FromPrimitive; + + use super::*; + + #[test] + fn test_jacobi() { + let cases = [ + [0, 1, 1], + [0, -1, 1], + [1, 1, 1], + [1, -1, 1], + [0, 5, 0], + [1, 5, 1], + [2, 5, -1], + [-2, 5, -1], + [2, -5, -1], + [-2, -5, 1], + [3, 5, -1], + [5, 5, 0], + [-5, 5, 0], + [6, 5, 1], + [6, -5, 1], + [-6, 5, 1], + [-6, -5, -1], + ]; + + for case in cases.iter() { + let x = BigInt::from_i64(case[0]).unwrap(); + let y = BigInt::from_i64(case[1]).unwrap(); + + assert_eq!(case[2] as isize, jacobi(&x, &y), "jacobi({}, {})", x, y); + } + } +} diff --git a/extensions/ecc/circuit/src/lib.rs b/extensions/ecc/circuit/src/lib.rs index 9986dca696..7f466952f9 100644 --- a/extensions/ecc/circuit/src/lib.rs +++ b/extensions/ecc/circuit/src/lib.rs @@ -1,8 +1,11 @@ mod weierstrass_chip; pub use weierstrass_chip::*; -mod weierstrass_extension; -pub use weierstrass_extension::*; +mod ecc_extension; +pub use ecc_extension::*; + +mod edwards_chip; +pub use edwards_chip::*; mod config; pub use config::*; diff --git a/extensions/ecc/circuit/src/weierstrass_chip/README.md b/extensions/ecc/circuit/src/weierstrass_chip/README.md index 94d8df6847..ba7119b0fc 100644 --- a/extensions/ecc/circuit/src/weierstrass_chip/README.md +++ b/extensions/ecc/circuit/src/weierstrass_chip/README.md @@ -1,8 +1,8 @@ # Short Weierstrass (SW) Curve Operations -The `ec_add_ne` and `ec_double` instructions are implemented in the `weierstrass_chip` module. +The `sw_add_ne` and `sw_double` instructions are implemented in the `weierstrass_chip` module. -### 1. `ec_add_ne` +### 1. `sw_add_ne` **Assumptions:** @@ -16,9 +16,9 @@ The `ec_add_ne` and `ec_double` instructions are implemented in the `weierstrass - `x3 = lambda^2 - x1 - x2` - `y3 = lambda * (x1 - x3) - y1` -- The `EcAddNeChip` constrains that these field expressions are computed correctly over the field `C::Fp`. +- The `SwAddNeChip` constrains that these field expressions are computed correctly over the field `C::Fp`. -### 2. `ec_double` +### 2. `sw_double` **Assumptions:** @@ -31,4 +31,4 @@ The `ec_add_ne` and `ec_double` instructions are implemented in the `weierstrass - `x3 = lambda^2 - 2 * x1` - `y3 = lambda * (x1 - x3) - y1` -- The `EcDoubleChip` constrains that these expressions are computed correctly over the field `C::Fp`. The coefficient `a` is taken from the `CurveConfig`. +- The `SwDoubleChip` constrains that these expressions are computed correctly over the field `C::Fp`. The coefficient `a` is taken from the `CurveConfig`. diff --git a/extensions/ecc/circuit/src/weierstrass_chip/add_ne.rs b/extensions/ecc/circuit/src/weierstrass_chip/add_ne.rs index 80df92e32e..5248bd8c7f 100644 --- a/extensions/ecc/circuit/src/weierstrass_chip/add_ne.rs +++ b/extensions/ecc/circuit/src/weierstrass_chip/add_ne.rs @@ -36,11 +36,11 @@ use openvm_rv32_adapters::{ use openvm_stark_backend::p3_field::PrimeField32; use super::{WeierstrassAir, WeierstrassChip}; -use crate::weierstrass_chip::curves::ec_add_ne; +use crate::weierstrass_chip::curves::sw_add_ne; // Assumes that (x1, y1), (x2, y2) both lie on the curve and are not the identity point. // Further assumes that x1, x2 are not equal in the coordinate field. -pub fn ec_add_ne_expr( +pub fn sw_add_ne_expr( config: ExprBuilderConfig, // The coordinate field. range_bus: VariableRangeCheckerBus, ) -> FieldExpr { @@ -67,7 +67,7 @@ pub fn ec_add_ne_expr( /// For example, for bls12_381, BLOCK_SIZE = 16, each element has 3 blocks and with two elements per /// input AffinePoint, BLOCKS = 6. For secp256k1, BLOCK_SIZE = 32, BLOCKS = 2. #[derive(Clone, PreflightExecutor, Deref, DerefMut)] -pub struct EcAddNeExecutor( +pub struct SwAddNeExecutor( FieldExpressionExecutor>, ); @@ -75,17 +75,17 @@ fn gen_base_expr( config: ExprBuilderConfig, range_checker_bus: VariableRangeCheckerBus, ) -> (FieldExpr, Vec) { - let expr = ec_add_ne_expr(config, range_checker_bus); + let expr = sw_add_ne_expr(config, range_checker_bus); let local_opcode_idx = vec![ - Rv32WeierstrassOpcode::EC_ADD_NE as usize, - Rv32WeierstrassOpcode::SETUP_EC_ADD_NE as usize, + Rv32WeierstrassOpcode::SW_ADD_NE as usize, + Rv32WeierstrassOpcode::SETUP_SW_ADD_NE as usize, ]; (expr, local_opcode_idx) } -pub fn get_ec_addne_air( +pub fn get_sw_addne_air( exec_bridge: ExecutionBridge, mem_bridge: MemoryBridge, config: ExprBuilderConfig, @@ -106,24 +106,24 @@ pub fn get_ec_addne_air( ) } -pub fn get_ec_addne_step( +pub fn get_sw_addne_step( config: ExprBuilderConfig, range_checker_bus: VariableRangeCheckerBus, pointer_max_bits: usize, offset: usize, -) -> EcAddNeExecutor { +) -> SwAddNeExecutor { let (expr, local_opcode_idx) = gen_base_expr(config, range_checker_bus); - EcAddNeExecutor(FieldExpressionExecutor::new( + SwAddNeExecutor(FieldExpressionExecutor::new( Rv32VecHeapAdapterExecutor::new(pointer_max_bits), expr, offset, local_opcode_idx, vec![], - "EcAddNe", + "SwAddNe", )) } -pub fn get_ec_addne_chip( +pub fn get_sw_addne_chip( config: ExprBuilderConfig, mem_helper: SharedMemoryHelper, range_checker: SharedVariableRangeCheckerChip, @@ -146,19 +146,19 @@ pub fn get_ec_addne_chip( #[derive(AlignedBytesBorrow, Clone)] #[repr(C)] -struct EcAddNePreCompute<'a> { +struct SwAddNePreCompute<'a> { expr: &'a FieldExpr, rs_addrs: [u8; 2], a: u8, flag_idx: u8, } -impl<'a, const BLOCKS: usize, const BLOCK_SIZE: usize> EcAddNeExecutor { +impl<'a, const BLOCKS: usize, const BLOCK_SIZE: usize> SwAddNeExecutor { fn pre_compute_impl( &'a self, pc: u32, inst: &Instruction, - data: &mut EcAddNePreCompute<'a>, + data: &mut SwAddNePreCompute<'a>, ) -> Result { let Instruction { opcode, @@ -200,7 +200,7 @@ impl<'a, const BLOCKS: usize, const BLOCK_SIZE: usize> EcAddNeExecutor EcAddNeExecutor Executor - for EcAddNeExecutor + for SwAddNeExecutor { #[inline(always)] fn pre_compute_size(&self) -> usize { - std::mem::size_of::() + std::mem::size_of::() } fn pre_compute( @@ -231,7 +231,7 @@ impl Executor where Ctx: E1ExecutionCtx, { - let pre_compute: &mut EcAddNePreCompute = data.borrow_mut(); + let pre_compute: &mut SwAddNePreCompute = data.borrow_mut(); let is_setup = self.pre_compute_impl(pc, inst, pre_compute)?; @@ -315,11 +315,11 @@ impl Executor } impl MeteredExecutor - for EcAddNeExecutor + for SwAddNeExecutor { #[inline(always)] fn metered_pre_compute_size(&self) -> usize { - std::mem::size_of::>() + std::mem::size_of::>() } fn metered_pre_compute( @@ -332,7 +332,7 @@ impl MeteredExecu where Ctx: E2ExecutionCtx, { - let pre_compute: &mut E2PreCompute = data.borrow_mut(); + let pre_compute: &mut E2PreCompute = data.borrow_mut(); pre_compute.chip_idx = chip_idx as u32; let is_setup = self.pre_compute_impl(pc, inst, &mut pre_compute.data)?; @@ -424,14 +424,14 @@ unsafe fn execute_e2_impl< pre_compute: &[u8], vm_state: &mut VmExecState, ) { - let e2_pre_compute: &E2PreCompute = pre_compute.borrow(); + let e2_pre_compute: &E2PreCompute = pre_compute.borrow(); vm_state .ctx .on_height_change(e2_pre_compute.chip_idx as usize, 1); let pre_compute = unsafe { std::slice::from_raw_parts( &e2_pre_compute.data as *const _ as *const u8, - std::mem::size_of::(), + std::mem::size_of::(), ) }; execute_e12_impl::<_, _, BLOCKS, BLOCK_SIZE, FIELD_TYPE, false>(pre_compute, vm_state); @@ -447,14 +447,14 @@ unsafe fn execute_e2_setup_impl< pre_compute: &[u8], vm_state: &mut VmExecState, ) { - let e2_pre_compute: &E2PreCompute = pre_compute.borrow(); + let e2_pre_compute: &E2PreCompute = pre_compute.borrow(); vm_state .ctx .on_height_change(e2_pre_compute.chip_idx as usize, 1); let pre_compute = unsafe { std::slice::from_raw_parts( &e2_pre_compute.data as *const _ as *const u8, - std::mem::size_of::(), + std::mem::size_of::(), ) }; execute_e12_impl::<_, _, BLOCKS, BLOCK_SIZE, FIELD_TYPE, true>(pre_compute, vm_state); @@ -471,7 +471,7 @@ unsafe fn execute_e12_impl< pre_compute: &[u8], vm_state: &mut VmExecState, ) { - let pre_compute: &EcAddNePreCompute = pre_compute.borrow(); + let pre_compute: &SwAddNePreCompute = pre_compute.borrow(); // Read register values let rs_vals = pre_compute .rs_addrs @@ -503,7 +503,7 @@ unsafe fn execute_e12_impl< ) .into() } else { - ec_add_ne::(read_data) + sw_add_ne::(read_data) }; let rd_val = u32::from_le_bytes(vm_state.vm_read(RV32_REGISTER_AS, pre_compute.a as u32)); diff --git a/extensions/ecc/circuit/src/weierstrass_chip/curves.rs b/extensions/ecc/circuit/src/weierstrass_chip/curves.rs index 085d40ff5f..2340a4cc26 100644 --- a/extensions/ecc/circuit/src/weierstrass_chip/curves.rs +++ b/extensions/ecc/circuit/src/weierstrass_chip/curves.rs @@ -7,7 +7,7 @@ use openvm_algebra_circuit::fields::{ }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum CurveType { +pub enum SwCurveType { K256 = 0, P256 = 1, BN254 = 2, @@ -16,55 +16,55 @@ pub enum CurveType { const P256_NEG_A: u64 = 3; -fn get_modulus_as_bigint() -> BigUint { +pub fn get_modulus_as_bigint() -> BigUint { BigUint::from_str_radix(F::MODULUS.trim_start_matches("0x"), 16).unwrap() } -pub(super) fn get_curve_type(modulus: &BigUint, a_coeff: &BigUint) -> Option { +pub(super) fn get_sw_curve_type(modulus: &BigUint, a_coeff: &BigUint) -> Option { if modulus == &get_modulus_as_bigint::() && a_coeff == &BigUint::ZERO { - return Some(CurveType::K256); + return Some(SwCurveType::K256); } let coeff_a = (-halo2curves_axiom::secp256r1::Fp::from(P256_NEG_A)).to_bytes(); if modulus == &get_modulus_as_bigint::() && a_coeff == &BigUint::from_bytes_le(&coeff_a) { - return Some(CurveType::P256); + return Some(SwCurveType::P256); } if modulus == &get_modulus_as_bigint::() && a_coeff == &BigUint::ZERO { - return Some(CurveType::BN254); + return Some(SwCurveType::BN254); } if modulus == &get_modulus_as_bigint::() && a_coeff == &BigUint::ZERO { - return Some(CurveType::BLS12_381); + return Some(SwCurveType::BLS12_381); } None } #[inline(always)] -pub fn ec_add_ne( +pub fn sw_add_ne( input_data: [[[u8; BLOCK_SIZE]; BLOCKS]; 2], ) -> [[u8; BLOCK_SIZE]; BLOCKS] { match FIELD_TYPE { x if x == FieldType::K256Coordinate as u8 => { - ec_add_ne_256bit::(input_data) + sw_add_ne_256bit::(input_data) } x if x == FieldType::P256Coordinate as u8 => { - ec_add_ne_256bit::(input_data) + sw_add_ne_256bit::(input_data) } x if x == FieldType::BN254Coordinate as u8 => { - ec_add_ne_256bit::(input_data) + sw_add_ne_256bit::(input_data) } x if x == FieldType::BLS12_381Coordinate as u8 => { - ec_add_ne_bls12_381::(input_data) + sw_add_ne_bls12_381::(input_data) } _ => panic!("Unsupported field type: {}", FIELD_TYPE), } @@ -72,30 +72,30 @@ pub fn ec_add_ne( +pub fn sw_double( input_data: [[u8; BLOCK_SIZE]; BLOCKS], ) -> [[u8; BLOCK_SIZE]; BLOCKS] { match CURVE_TYPE { - x if x == CurveType::K256 as u8 => { - ec_double_256bit::(input_data) + x if x == SwCurveType::K256 as u8 => { + sw_double_256bit::(input_data) } - x if x == CurveType::P256 as u8 => { - ec_double_256bit::( + x if x == SwCurveType::P256 as u8 => { + sw_double_256bit::( input_data, ) } - x if x == CurveType::BN254 as u8 => { - ec_double_256bit::(input_data) + x if x == SwCurveType::BN254 as u8 => { + sw_double_256bit::(input_data) } - x if x == CurveType::BLS12_381 as u8 => { - ec_double_bls12_381::(input_data) + x if x == SwCurveType::BLS12_381 as u8 => { + sw_double_bls12_381::(input_data) } _ => panic!("Unsupported curve type: {}", CURVE_TYPE), } } #[inline(always)] -fn ec_add_ne_256bit< +fn sw_add_ne_256bit< F: PrimeField, const BLOCKS: usize, const BLOCK_SIZE: usize, @@ -107,7 +107,7 @@ fn ec_add_ne_256bit< let x2 = blocks_to_field_element::(input_data[1][..BLOCKS / 2].as_flattened()); let y2 = blocks_to_field_element::(input_data[1][BLOCKS / 2..].as_flattened()); - let (x3, y3) = ec_add_ne_impl::(x1, y1, x2, y2); + let (x3, y3) = sw_add_ne_impl::(x1, y1, x2, y2); let mut output = [[0u8; BLOCK_SIZE]; BLOCKS]; field_element_to_blocks::(&x3, &mut output[..BLOCKS / 2]); @@ -116,7 +116,7 @@ fn ec_add_ne_256bit< } #[inline(always)] -fn ec_double_256bit< +fn sw_double_256bit< F: PrimeField, const NEG_A: u64, const BLOCKS: usize, @@ -127,7 +127,7 @@ fn ec_double_256bit< let x1 = blocks_to_field_element::(input_data[..BLOCKS / 2].as_flattened()); let y1 = blocks_to_field_element::(input_data[BLOCKS / 2..].as_flattened()); - let (x3, y3) = ec_double_impl::(x1, y1); + let (x3, y3) = sw_double_impl::(x1, y1); let mut output = [[0u8; BLOCK_SIZE]; BLOCKS]; field_element_to_blocks::(&x3, &mut output[..BLOCKS / 2]); @@ -136,7 +136,7 @@ fn ec_double_256bit< } #[inline(always)] -fn ec_add_ne_bls12_381( +fn sw_add_ne_bls12_381( input_data: [[[u8; BLOCK_SIZE]; BLOCKS]; 2], ) -> [[u8; BLOCK_SIZE]; BLOCKS] { // Extract coordinates @@ -149,7 +149,7 @@ fn ec_add_ne_bls12_381( let y2 = blocks_to_field_element_bls12_381_coordinate(input_data[1][BLOCKS / 2..].as_flattened()); - let (x3, y3) = ec_add_ne_impl::(x1, y1, x2, y2); + let (x3, y3) = sw_add_ne_impl::(x1, y1, x2, y2); // Final output let mut output = [[0u8; BLOCK_SIZE]; BLOCKS]; @@ -159,14 +159,14 @@ fn ec_add_ne_bls12_381( } #[inline(always)] -fn ec_double_bls12_381( +fn sw_double_bls12_381( input_data: [[u8; BLOCK_SIZE]; BLOCKS], ) -> [[u8; BLOCK_SIZE]; BLOCKS] { // Extract coordinates let x1 = blocks_to_field_element_bls12_381_coordinate(input_data[..BLOCKS / 2].as_flattened()); let y1 = blocks_to_field_element_bls12_381_coordinate(input_data[BLOCKS / 2..].as_flattened()); - let (x3, y3) = ec_double_impl::(x1, y1); + let (x3, y3) = sw_double_impl::(x1, y1); // Final output let mut output = [[0u8; BLOCK_SIZE]; BLOCKS]; @@ -176,7 +176,7 @@ fn ec_double_bls12_381( } #[inline(always)] -pub fn ec_add_ne_impl(x1: F, y1: F, x2: F, y2: F) -> (F, F) { +pub fn sw_add_ne_impl(x1: F, y1: F, x2: F, y2: F) -> (F, F) { // Calculate lambda = (y2 - y1) / (x2 - x1) let lambda = (y2 - y1) * (x2 - x1).invert().unwrap(); @@ -190,7 +190,7 @@ pub fn ec_add_ne_impl(x1: F, y1: F, x2: F, y2: F) -> (F, F) { } #[inline(always)] -pub fn ec_double_impl(x1: F, y1: F) -> (F, F) { +pub fn sw_double_impl(x1: F, y1: F) -> (F, F) { // Calculate lambda based on curve coefficient 'a' let x1_squared = x1.square(); let three_x1_squared = x1_squared + x1_squared.double(); diff --git a/extensions/ecc/circuit/src/weierstrass_chip/double.rs b/extensions/ecc/circuit/src/weierstrass_chip/double.rs index 6490c8e799..5578a40030 100644 --- a/extensions/ecc/circuit/src/weierstrass_chip/double.rs +++ b/extensions/ecc/circuit/src/weierstrass_chip/double.rs @@ -35,10 +35,10 @@ use openvm_rv32_adapters::{ }; use openvm_stark_backend::p3_field::PrimeField32; -use super::{curves::get_curve_type, WeierstrassAir, WeierstrassChip}; -use crate::weierstrass_chip::curves::{ec_double, CurveType}; +use super::{curves::get_sw_curve_type, WeierstrassAir, WeierstrassChip}; +use crate::weierstrass_chip::curves::{sw_double, SwCurveType}; -pub fn ec_double_ne_expr( +pub fn sw_double_ne_expr( config: ExprBuilderConfig, // The coordinate field. range_bus: VariableRangeCheckerBus, a_biguint: BigUint, @@ -73,7 +73,7 @@ pub fn ec_double_ne_expr( /// For example, for bls12_381, BLOCK_SIZE = 16, each element has 3 blocks and with two elements per /// input AffinePoint, BLOCKS = 6. For secp256k1, BLOCK_SIZE = 32, BLOCKS = 2. #[derive(Clone, PreflightExecutor, Deref, DerefMut)] -pub struct EcDoubleExecutor( +pub struct SwDoubleExecutor( FieldExpressionExecutor>, ); @@ -82,18 +82,18 @@ fn gen_base_expr( range_checker_bus: VariableRangeCheckerBus, a_biguint: BigUint, ) -> (FieldExpr, Vec) { - let expr = ec_double_ne_expr(config, range_checker_bus, a_biguint); + let expr = sw_double_ne_expr(config, range_checker_bus, a_biguint); let local_opcode_idx = vec![ - Rv32WeierstrassOpcode::EC_DOUBLE as usize, - Rv32WeierstrassOpcode::SETUP_EC_DOUBLE as usize, + Rv32WeierstrassOpcode::SW_DOUBLE as usize, + Rv32WeierstrassOpcode::SETUP_SW_DOUBLE as usize, ]; (expr, local_opcode_idx) } #[allow(clippy::too_many_arguments)] -pub fn get_ec_double_air( +pub fn get_sw_double_air( exec_bridge: ExecutionBridge, mem_bridge: MemoryBridge, config: ExprBuilderConfig, @@ -115,25 +115,25 @@ pub fn get_ec_double_air( ) } -pub fn get_ec_double_step( +pub fn get_sw_double_step( config: ExprBuilderConfig, range_checker_bus: VariableRangeCheckerBus, pointer_max_bits: usize, offset: usize, a_biguint: BigUint, -) -> EcDoubleExecutor { +) -> SwDoubleExecutor { let (expr, local_opcode_idx) = gen_base_expr(config, range_checker_bus, a_biguint); - EcDoubleExecutor(FieldExpressionExecutor::new( + SwDoubleExecutor(FieldExpressionExecutor::new( Rv32VecHeapAdapterExecutor::new(pointer_max_bits), expr, offset, local_opcode_idx, vec![], - "EcDouble", + "SwDouble", )) } -pub fn get_ec_double_chip( +pub fn get_sw_double_chip( config: ExprBuilderConfig, mem_helper: SharedMemoryHelper, range_checker: SharedVariableRangeCheckerChip, @@ -157,19 +157,19 @@ pub fn get_ec_double_chip( #[derive(AlignedBytesBorrow, Clone)] #[repr(C)] -struct EcDoublePreCompute<'a> { +struct SwDoublePreCompute<'a> { expr: &'a FieldExpr, rs_addrs: [u8; 1], a: u8, flag_idx: u8, } -impl<'a, const BLOCKS: usize, const BLOCK_SIZE: usize> EcDoubleExecutor { +impl<'a, const BLOCKS: usize, const BLOCK_SIZE: usize> SwDoubleExecutor { fn pre_compute_impl( &'a self, pc: u32, inst: &Instruction, - data: &mut EcDoublePreCompute<'a>, + data: &mut SwDoublePreCompute<'a>, ) -> Result { let Instruction { opcode, a, b, d, e, .. @@ -204,7 +204,7 @@ impl<'a, const BLOCKS: usize, const BLOCK_SIZE: usize> EcDoubleExecutor EcDoubleExecutor Executor - for EcDoubleExecutor + for SwDoubleExecutor { #[inline(always)] fn pre_compute_size(&self) -> usize { - std::mem::size_of::() + std::mem::size_of::() } fn pre_compute( @@ -235,72 +235,78 @@ impl Executor where Ctx: E1ExecutionCtx, { - let pre_compute: &mut EcDoublePreCompute = data.borrow_mut(); + let pre_compute: &mut SwDoublePreCompute = data.borrow_mut(); let is_setup = self.pre_compute_impl(pc, inst, pre_compute)?; if let Some(curve_type) = { let modulus = &pre_compute.expr.builder.prime; let a_coeff = &pre_compute.expr.setup_values[0]; - get_curve_type(modulus, a_coeff) + get_sw_curve_type(modulus, a_coeff) } { match (is_setup, curve_type) { - (true, CurveType::K256) => { - Ok( - execute_e12_impl::<_, _, BLOCKS, BLOCK_SIZE, { CurveType::K256 as u8 }, true>, - ) - } - (true, CurveType::P256) => { - Ok( - execute_e12_impl::<_, _, BLOCKS, BLOCK_SIZE, { CurveType::P256 as u8 }, true>, - ) - } - (true, CurveType::BN254) => Ok(execute_e12_impl::< + (true, SwCurveType::K256) => Ok(execute_e12_impl::< + _, + _, + BLOCKS, + BLOCK_SIZE, + { SwCurveType::K256 as u8 }, + true, + >), + (true, SwCurveType::P256) => Ok(execute_e12_impl::< + _, + _, + BLOCKS, + BLOCK_SIZE, + { SwCurveType::P256 as u8 }, + true, + >), + (true, SwCurveType::BN254) => Ok(execute_e12_impl::< _, _, BLOCKS, BLOCK_SIZE, - { CurveType::BN254 as u8 }, + { SwCurveType::BN254 as u8 }, true, >), - (true, CurveType::BLS12_381) => Ok(execute_e12_impl::< + (true, SwCurveType::BLS12_381) => Ok(execute_e12_impl::< _, _, BLOCKS, BLOCK_SIZE, - { CurveType::BLS12_381 as u8 }, + { SwCurveType::BLS12_381 as u8 }, true, >), - (false, CurveType::K256) => Ok(execute_e12_impl::< + (false, SwCurveType::K256) => Ok(execute_e12_impl::< _, _, BLOCKS, BLOCK_SIZE, - { CurveType::K256 as u8 }, + { SwCurveType::K256 as u8 }, false, >), - (false, CurveType::P256) => Ok(execute_e12_impl::< + (false, SwCurveType::P256) => Ok(execute_e12_impl::< _, _, BLOCKS, BLOCK_SIZE, - { CurveType::P256 as u8 }, + { SwCurveType::P256 as u8 }, false, >), - (false, CurveType::BN254) => Ok(execute_e12_impl::< + (false, SwCurveType::BN254) => Ok(execute_e12_impl::< _, _, BLOCKS, BLOCK_SIZE, - { CurveType::BN254 as u8 }, + { SwCurveType::BN254 as u8 }, false, >), - (false, CurveType::BLS12_381) => Ok(execute_e12_impl::< + (false, SwCurveType::BLS12_381) => Ok(execute_e12_impl::< _, _, BLOCKS, BLOCK_SIZE, - { CurveType::BLS12_381 as u8 }, + { SwCurveType::BLS12_381 as u8 }, false, >), } @@ -313,11 +319,11 @@ impl Executor } impl MeteredExecutor - for EcDoubleExecutor + for SwDoubleExecutor { #[inline(always)] fn metered_pre_compute_size(&self) -> usize { - std::mem::size_of::>() + std::mem::size_of::>() } fn metered_pre_compute( @@ -330,7 +336,7 @@ impl MeteredExecu where Ctx: E2ExecutionCtx, { - let pre_compute: &mut E2PreCompute = data.borrow_mut(); + let pre_compute: &mut E2PreCompute = data.borrow_mut(); pre_compute.chip_idx = chip_idx as u32; let is_setup = self.pre_compute_impl(pc, inst, &mut pre_compute.data)?; @@ -338,38 +344,50 @@ impl MeteredExecu if let Some(curve_type) = { let modulus = &pre_compute.data.expr.builder.prime; let a_coeff = &pre_compute.data.expr.setup_values[0]; - get_curve_type(modulus, a_coeff) + get_sw_curve_type(modulus, a_coeff) } { match (is_setup, curve_type) { - (true, CurveType::K256) => { - Ok(execute_e2_setup_impl::<_, _, BLOCKS, BLOCK_SIZE, { CurveType::K256 as u8 }>) - } - (true, CurveType::P256) => { - Ok(execute_e2_setup_impl::<_, _, BLOCKS, BLOCK_SIZE, { CurveType::P256 as u8 }>) - } - (true, CurveType::BN254) => { - Ok( - execute_e2_setup_impl::<_, _, BLOCKS, BLOCK_SIZE, { CurveType::BN254 as u8 }>, - ) - } - (true, CurveType::BLS12_381) => Ok(execute_e2_setup_impl::< + (true, SwCurveType::K256) => Ok(execute_e2_setup_impl::< + _, + _, + BLOCKS, + BLOCK_SIZE, + { SwCurveType::K256 as u8 }, + >), + (true, SwCurveType::P256) => Ok(execute_e2_setup_impl::< + _, + _, + BLOCKS, + BLOCK_SIZE, + { SwCurveType::P256 as u8 }, + >), + (true, SwCurveType::BN254) => Ok(execute_e2_setup_impl::< + _, + _, + BLOCKS, + BLOCK_SIZE, + { SwCurveType::BN254 as u8 }, + >), + (true, SwCurveType::BLS12_381) => Ok(execute_e2_setup_impl::< _, _, BLOCKS, BLOCK_SIZE, - { CurveType::BLS12_381 as u8 }, + { SwCurveType::BLS12_381 as u8 }, >), - (false, CurveType::K256) => { - Ok(execute_e2_impl::<_, _, BLOCKS, BLOCK_SIZE, { CurveType::K256 as u8 }>) + (false, SwCurveType::K256) => { + Ok(execute_e2_impl::<_, _, BLOCKS, BLOCK_SIZE, { SwCurveType::K256 as u8 }>) } - (false, CurveType::P256) => { - Ok(execute_e2_impl::<_, _, BLOCKS, BLOCK_SIZE, { CurveType::P256 as u8 }>) + (false, SwCurveType::P256) => { + Ok(execute_e2_impl::<_, _, BLOCKS, BLOCK_SIZE, { SwCurveType::P256 as u8 }>) } - (false, CurveType::BN254) => { - Ok(execute_e2_impl::<_, _, BLOCKS, BLOCK_SIZE, { CurveType::BN254 as u8 }>) + (false, SwCurveType::BN254) => { + Ok(execute_e2_impl::<_, _, BLOCKS, BLOCK_SIZE, { SwCurveType::BN254 as u8 }>) } - (false, CurveType::BLS12_381) => { - Ok(execute_e2_impl::<_, _, BLOCKS, BLOCK_SIZE, { CurveType::BLS12_381 as u8 }>) + (false, SwCurveType::BLS12_381) => { + Ok( + execute_e2_impl::<_, _, BLOCKS, BLOCK_SIZE, { SwCurveType::BLS12_381 as u8 }>, + ) } } } else if is_setup { @@ -390,14 +408,14 @@ unsafe fn execute_e2_impl< pre_compute: &[u8], vm_state: &mut VmExecState, ) { - let e2_pre_compute: &E2PreCompute = pre_compute.borrow(); + let e2_pre_compute: &E2PreCompute = pre_compute.borrow(); vm_state .ctx .on_height_change(e2_pre_compute.chip_idx as usize, 1); let pre_compute = unsafe { std::slice::from_raw_parts( &e2_pre_compute.data as *const _ as *const u8, - std::mem::size_of::(), + std::mem::size_of::(), ) }; execute_e12_impl::<_, _, BLOCKS, BLOCK_SIZE, CURVE_TYPE, false>(pre_compute, vm_state); @@ -413,14 +431,14 @@ unsafe fn execute_e2_setup_impl< pre_compute: &[u8], vm_state: &mut VmExecState, ) { - let e2_pre_compute: &E2PreCompute = pre_compute.borrow(); + let e2_pre_compute: &E2PreCompute = pre_compute.borrow(); vm_state .ctx .on_height_change(e2_pre_compute.chip_idx as usize, 1); let pre_compute = unsafe { std::slice::from_raw_parts( &e2_pre_compute.data as *const _ as *const u8, - std::mem::size_of::(), + std::mem::size_of::(), ) }; execute_e12_impl::<_, _, BLOCKS, BLOCK_SIZE, CURVE_TYPE, true>(pre_compute, vm_state); @@ -437,7 +455,7 @@ unsafe fn execute_e12_impl< pre_compute: &[u8], vm_state: &mut VmExecState, ) { - let pre_compute: &EcDoublePreCompute = pre_compute.borrow(); + let pre_compute: &SwDoublePreCompute = pre_compute.borrow(); // Read register values let rs_vals = pre_compute .rs_addrs @@ -456,7 +474,7 @@ unsafe fn execute_e12_impl< if input_prime != pre_compute.expr.builder.prime { vm_state.exit_code = Err(ExecutionError::Fail { pc: vm_state.pc, - msg: "EcDouble: mismatched prime", + msg: "SwDouble: mismatched prime", }); return; } @@ -467,7 +485,7 @@ unsafe fn execute_e12_impl< if input_a != *coeff_a { vm_state.exit_code = Err(ExecutionError::Fail { pc: vm_state.pc, - msg: "EcDouble: mismatched coeff_a", + msg: "SwDouble: mismatched coeff_a", }); return; } @@ -482,7 +500,7 @@ unsafe fn execute_e12_impl< ) .into() } else { - ec_double::(read_data) + sw_double::(read_data) }; let rd_val = u32::from_le_bytes(vm_state.vm_read(RV32_REGISTER_AS, pre_compute.a as u32)); diff --git a/extensions/ecc/circuit/src/weierstrass_chip/mod.rs b/extensions/ecc/circuit/src/weierstrass_chip/mod.rs index cc8e97841e..9d85397fc8 100644 --- a/extensions/ecc/circuit/src/weierstrass_chip/mod.rs +++ b/extensions/ecc/circuit/src/weierstrass_chip/mod.rs @@ -1,5 +1,5 @@ mod add_ne; -mod curves; +pub(crate) mod curves; mod double; pub use add_ne::*; @@ -7,10 +7,11 @@ pub use double::*; #[cfg(test)] mod tests; - use openvm_circuit::arch::{VmAirWrapper, VmChipWrapper}; use openvm_mod_circuit_builder::{FieldExpressionCoreAir, FieldExpressionFiller}; use openvm_rv32_adapters::{Rv32VecHeapAdapterAir, Rv32VecHeapAdapterFiller}; +#[cfg(test)] +pub use tests::*; pub type WeierstrassAir = VmAirWrapper< diff --git a/extensions/ecc/circuit/src/weierstrass_chip/tests.rs b/extensions/ecc/circuit/src/weierstrass_chip/tests.rs index 7248743a96..fd89c3d5bc 100644 --- a/extensions/ecc/circuit/src/weierstrass_chip/tests.rs +++ b/extensions/ecc/circuit/src/weierstrass_chip/tests.rs @@ -21,8 +21,8 @@ use openvm_stark_backend::p3_field::FieldAlgebra; use openvm_stark_sdk::p3_baby_bear::BabyBear; use crate::{ - get_ec_addne_air, get_ec_addne_chip, get_ec_addne_step, get_ec_double_air, get_ec_double_chip, - get_ec_double_step, EcDoubleExecutor, WeierstrassAir, WeierstrassChip, + get_sw_addne_air, get_sw_addne_chip, get_sw_addne_step, get_sw_double_air, get_sw_double_chip, + get_sw_double_step, SwDoubleExecutor, WeierstrassAir, WeierstrassChip, }; const NUM_LIMBS: usize = 32; @@ -80,7 +80,7 @@ lazy_static::lazy_static! { }; } -fn prime_limbs(expr: &FieldExpr) -> Vec { +pub fn prime_limbs(expr: &FieldExpr) -> Vec { expr.prime_limbs .iter() .map(|n| BabyBear::from_canonical_usize(*n)) @@ -89,7 +89,7 @@ fn prime_limbs(expr: &FieldExpr) -> Vec { type WeierstrassHarness = TestChipHarness< F, - EcDoubleExecutor<2, BLOCK_SIZE>, + SwDoubleExecutor<2, BLOCK_SIZE>, WeierstrassAir<1, 2, BLOCK_SIZE>, WeierstrassChip, MatrixRecordArena, @@ -111,7 +111,7 @@ fn create_test_double_chips( let bitwise_chip = Arc::new(BitwiseOperationLookupChip::::new( bitwise_bus, )); - let air = get_ec_double_air( + let air = get_sw_double_air( tester.execution_bridge(), tester.memory_bridge(), config.clone(), @@ -121,14 +121,14 @@ fn create_test_double_chips( offset, a_biguint.clone(), ); - let executor = get_ec_double_step( + let executor = get_sw_double_step( config.clone(), tester.range_checker().bus(), tester.address_bits(), offset, a_biguint.clone(), ); - let chip = get_ec_double_chip( + let chip = get_sw_double_chip( config.clone(), tester.memory_helper(), tester.range_checker(), @@ -154,7 +154,7 @@ fn test_add_ne() { bitwise_bus, )); - let air = get_ec_addne_air::<2, BLOCK_SIZE>( + let air = get_sw_addne_air::<2, BLOCK_SIZE>( tester.execution_bridge(), tester.memory_bridge(), config.clone(), @@ -163,13 +163,13 @@ fn test_add_ne() { tester.address_bits(), Rv32WeierstrassOpcode::CLASS_OFFSET, ); - let executor = get_ec_addne_step::<2, BLOCK_SIZE>( + let executor = get_sw_addne_step::<2, BLOCK_SIZE>( config.clone(), tester.range_checker().bus(), tester.address_bits(), Rv32WeierstrassOpcode::CLASS_OFFSET, ); - let chip = get_ec_addne_chip::( + let chip = get_sw_addne_chip::( config.clone(), tester.memory_helper(), tester.range_checker(), @@ -209,7 +209,7 @@ fn test_add_ne() { &mut tester, vec![prime_limbs, one_limbs], // inputs[0] = prime, others doesn't matter vec![one_limbs, one_limbs], - harness.executor.offset + Rv32WeierstrassOpcode::SETUP_EC_ADD_NE as usize, + harness.executor.offset + Rv32WeierstrassOpcode::SETUP_SW_ADD_NE as usize, ); tester.execute(&mut harness, &setup_instruction); @@ -217,7 +217,7 @@ fn test_add_ne() { &mut tester, vec![p1_x_limbs, p1_y_limbs], vec![p2_x_limbs, p2_y_limbs], - harness.executor.offset + Rv32WeierstrassOpcode::EC_ADD_NE as usize, + harness.executor.offset + Rv32WeierstrassOpcode::SW_ADD_NE as usize, ); tester.execute(&mut harness, &instruction); @@ -268,7 +268,7 @@ fn test_double() { vec![prime_limbs, a_limbs], /* inputs[0] = prime, inputs[1] = a coeff of weierstrass * equation */ vec![], - harness.executor.offset + Rv32WeierstrassOpcode::SETUP_EC_DOUBLE as usize, + harness.executor.offset + Rv32WeierstrassOpcode::SETUP_SW_DOUBLE as usize, ); tester.execute(&mut harness, &setup_instruction); @@ -276,7 +276,7 @@ fn test_double() { &mut tester, vec![p1_x_limbs, p1_y_limbs], vec![], - harness.executor.offset + Rv32WeierstrassOpcode::EC_DOUBLE as usize, + harness.executor.offset + Rv32WeierstrassOpcode::SW_DOUBLE as usize, ); tester.execute(&mut harness, &instruction); @@ -352,7 +352,7 @@ fn test_p256_double() { vec![prime_limbs, a_limbs], /* inputs[0] = prime, inputs[1] = a coeff of weierstrass * equation */ vec![], - harness.executor.offset + Rv32WeierstrassOpcode::SETUP_EC_DOUBLE as usize, + harness.executor.offset + Rv32WeierstrassOpcode::SETUP_SW_DOUBLE as usize, ); tester.execute(&mut harness, &setup_instruction); @@ -360,7 +360,7 @@ fn test_p256_double() { &mut tester, vec![p1_x_limbs, p1_y_limbs], vec![], - harness.executor.offset + Rv32WeierstrassOpcode::EC_DOUBLE as usize, + harness.executor.offset + Rv32WeierstrassOpcode::SW_DOUBLE as usize, ); tester.execute(&mut harness, &instruction); diff --git a/extensions/ecc/guest/Cargo.toml b/extensions/ecc/guest/Cargo.toml index e5251eb366..a85f8c4597 100644 --- a/extensions/ecc/guest/Cargo.toml +++ b/extensions/ecc/guest/Cargo.toml @@ -16,15 +16,23 @@ elliptic-curve = { workspace = true, features = ["arithmetic", "sec1"] } openvm-custom-insn = { workspace = true } openvm-rv32im-guest = { workspace = true } openvm-algebra-guest = { workspace = true } +openvm-algebra-moduli-macros = { workspace = true } openvm-ecc-sw-macros = { workspace = true } +openvm-ecc-te-macros = { workspace = true } once_cell = { workspace = true, features = ["race", "alloc"] } +num-bigint = { workspace = true } +hex-literal = { workspace = true } # Used for `halo2curves` feature halo2curves-axiom = { workspace = true, optional = true } group = "0.13.0" +[target.'cfg(not(target_os = "zkvm"))'.dependencies] +lazy_static = { workspace = true } + [features] default = [] +ed25519 = [] halo2curves = ["dep:halo2curves-axiom", "openvm-algebra-guest/halo2curves"] std = ["alloc"] alloc = [] diff --git a/extensions/ecc/guest/src/ecdsa.rs b/extensions/ecc/guest/src/ecdsa.rs index 07fc6d44fc..7c60e575d3 100644 --- a/extensions/ecc/guest/src/ecdsa.rs +++ b/extensions/ecc/guest/src/ecdsa.rs @@ -20,10 +20,7 @@ use elliptic_curve::{ }; use openvm_algebra_guest::{DivUnsafe, IntMod, Reduce}; -use crate::{ - weierstrass::{FromCompressed, IntrinsicCurve, WeierstrassPoint}, - CyclicGroup, Group, -}; +use crate::{weierstrass::WeierstrassPoint, CyclicGroup, FromCompressed, Group, IntrinsicCurve}; type Coordinate = <::Point as WeierstrassPoint>::Coordinate; type Scalar = ::Scalar; diff --git a/extensions/ecc/guest/src/ed25519.rs b/extensions/ecc/guest/src/ed25519.rs new file mode 100644 index 0000000000..32f48cba58 --- /dev/null +++ b/extensions/ecc/guest/src/ed25519.rs @@ -0,0 +1,85 @@ +use core::ops::Add; + +use hex_literal::hex; +#[cfg(not(target_os = "zkvm"))] +use lazy_static::lazy_static; +#[cfg(not(target_os = "zkvm"))] +use num_bigint::BigUint; +use openvm_algebra_guest::IntMod; + +use super::group::{CyclicGroup, Group}; +use crate::IntrinsicCurve; + +#[cfg(not(target_os = "zkvm"))] +lazy_static! { + pub static ref ED25519_MODULUS: BigUint = BigUint::from_bytes_be(&hex!( + "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED" + )); + pub static ref ED25519_ORDER: BigUint = BigUint::from_bytes_be(&hex!( + "1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED" + )); + pub static ref ED25519_A: BigUint = BigUint::from_bytes_be(&hex!( + "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC" + )); + pub static ref ED25519_D: BigUint = BigUint::from_bytes_be(&hex!( + "52036CEE2B6FFE738CC740797779E89800700A4D4141D8AB75EB4DCA135978A3" + )); +} + +openvm_algebra_moduli_macros::moduli_declare! { + Ed25519Coord { modulus = "0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED" }, + Ed25519Scalar { modulus = "0x1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED" }, +} + +pub const ED25519_NUM_LIMBS: usize = 32; +pub const ED25519_LIMB_BITS: usize = 8; +pub const ED25519_BLOCK_SIZE: usize = 32; +// from_const_bytes is little endian +pub const CURVE_A: Ed25519Coord = Ed25519Coord::from_const_bytes(hex!( + "ECFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F" +)); +pub const CURVE_D: Ed25519Coord = Ed25519Coord::from_const_bytes(hex!( + "A3785913CA4DEB75ABD841414D0A700098E879777940C78C73FE6F2BEE6C0352" +)); + +openvm_ecc_te_macros::te_declare! { + Ed25519Point { mod_type = Ed25519Coord, a = CURVE_A, d = CURVE_D }, +} + +impl CyclicGroup for Ed25519Point { + // from_const_bytes is little endian + const GENERATOR: Self = Ed25519Point { + x: Ed25519Coord::from_const_bytes(hex!( + "1AD5258F602D56C9B2A7259560C72C695CDCD6FD31E2A4C0FE536ECDD3366921" + )), + y: Ed25519Coord::from_const_bytes(hex!( + "5866666666666666666666666666666666666666666666666666666666666666" + )), + }; + const NEG_GENERATOR: Self = Ed25519Point { + x: Ed25519Coord::from_const_bytes([ + 211, 42, 218, 112, 159, 210, 169, 54, 77, 88, 218, 106, 159, 56, 211, 150, 163, 35, 41, + 2, 206, 29, 91, 63, 1, 172, 145, 50, 44, 201, 150, 94, + ]), + y: Ed25519Coord::from_const_bytes(hex!( + "5866666666666666666666666666666666666666666666666666666666666666" + )), + }; +} + +impl IntrinsicCurve for Ed25519Point { + type Scalar = Ed25519Scalar; + type Point = Ed25519Point; + + fn msm(coeffs: &[Self::Scalar], bases: &[Self::Point]) -> Self::Point + where + for<'a> &'a Self::Point: Add<&'a Self::Point, Output = Self::Point>, + { + if coeffs.len() < 25 { + let table = crate::edwards::CachedMulTable::::new(bases, 4); + table.windowed_mul(coeffs) + } else { + crate::msm(coeffs, bases) + } + } +} diff --git a/extensions/ecc/guest/src/edwards.rs b/extensions/ecc/guest/src/edwards.rs new file mode 100644 index 0000000000..e9089e49d8 --- /dev/null +++ b/extensions/ecc/guest/src/edwards.rs @@ -0,0 +1,396 @@ +use alloc::vec::Vec; +use core::ops::{AddAssign, Mul}; + +use openvm_algebra_guest::{Field, IntMod}; + +use crate::{Group, IntrinsicCurve}; + +pub trait TwistedEdwardsPoint: Sized { + /// The `a` coefficient in the twisted Edwards curve equation `ax^2 + y^2 = 1 + d x^2 y^2`. + const CURVE_A: Self::Coordinate; + /// The `d` coefficient in the twisted Edwards curve equation `ax^2 + y^2 = 1 + d x^2 y^2`. + const CURVE_D: Self::Coordinate; + const IDENTITY: Self; + + type Coordinate: Field; + + /// The concatenated `x, y` coordinates of the affine point, where + /// coordinates are in little endian. + /// + /// **Warning**: The memory layout of `Self` is expected to pack + /// `x` and `y` contigously with no unallocated space in between. + fn as_le_bytes(&self) -> &[u8]; + + /// Raw constructor without asserting point is on the curve. + fn from_xy_unchecked(x: Self::Coordinate, y: Self::Coordinate) -> Self; + fn into_coords(self) -> (Self::Coordinate, Self::Coordinate); + fn x(&self) -> &Self::Coordinate; + fn y(&self) -> &Self::Coordinate; + fn x_mut(&mut self) -> &mut Self::Coordinate; + fn y_mut(&mut self) -> &mut Self::Coordinate; + + fn add_impl(&self, p2: &Self) -> Self; + + #[inline(always)] + fn from_xy(x: Self::Coordinate, y: Self::Coordinate) -> Option + where + for<'a> &'a Self::Coordinate: Mul<&'a Self::Coordinate, Output = Self::Coordinate>, + { + let lhs = Self::CURVE_A * &x * &x + &y * &y; + let rhs = Self::CURVE_D * &x * &x * &y * &y + &Self::Coordinate::ONE; + if lhs != rhs { + return None; + } + Some(Self::from_xy_unchecked(x, y)) + } +} + +/// 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://en.wikipedia.org/wiki/Twisted_Edwards_curve) for twisted Edwards curves. +/// +/// The following imports are required: +/// ```rust +/// use core::ops::AddAssign; +/// +/// use openvm_algebra_guest::{DivUnsafe, Field}; +/// use openvm_ecc_guest::{edwards::TwistedEdwardsPoint, AffinePoint, Group}; +/// ``` +#[macro_export] +macro_rules! impl_te_affine { + ($struct_name:ident, $field:ty, $a:expr, $d:expr) => { + /// A newtype wrapper for [AffinePoint] that implements elliptic curve operations + /// by using the underlying field operations according to the [formulas](https://en.wikipedia.org/wiki/Twisted_Edwards_curve) for twisted Edwards curves. + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] + #[repr(transparent)] + pub struct $struct_name(AffinePoint<$field>); + + impl TwistedEdwardsPoint for $struct_name { + const CURVE_A: $field = $a; + const CURVE_D: $field = $d; + const IDENTITY: Self = Self(AffinePoint::new(<$field>::ZERO, <$field>::ONE)); + + 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 add_impl(&self, p2: &Self) -> Self { + use ::openvm_algebra_guest::DivUnsafe; + // For twisted Edwards curves: + // x3 = (x1*y2 + y1*x2)/(1 + d*x1*x2*y1*y2) + // y3 = (y1*y2 - a*x1*x2)/(1 - d*x1*x2*y1*y2) + let x1y2 = self.x() * p2.y(); + let y1x2 = self.y() * p2.x(); + let x1x2 = self.x() * p2.x(); + let y1y2 = self.y() * p2.y(); + let dx1x2y1y2 = Self::CURVE_D * x1x2 * y1y2; + + let x3 = (x1y2 + y1x2).div_unsafe(&(Self::Coordinate::ONE + dx1x2y1y2)); + let y3 = (y1y2 - Self::CURVE_A * x1x2).div_unsafe(&(Self::Coordinate::ONE - dx1x2y1y2)); + + Self(AffinePoint::new(x3, y3)) + } + + impl core::ops::Neg for $struct_name { + type Output = Self; + + fn neg(mut self) -> Self::Output { + self.0.x.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 +/// `TwistedEdwardsPoint`. Assumes that `Neg` is implemented for `&$struct_name`. +#[macro_export] +macro_rules! impl_te_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.add_impl(self) + } + } + + fn double_assign(&mut self) { + if !self.is_identity() { + *self = self.add_impl(self) + } + } + + // Note: It was found that implementing `is_identity` in group.rs as a default + // implementation increases the cycle count by 50% on the ecrecover benchmark. For + // this reason, we implement it here instead. We hypothesize that this is due to + // compiler optimizations that are not possible when the `is_identity` function is + // defined in a different source file. + #[inline(always)] + fn is_identity(&self) -> bool { + self == &::IDENTITY + } + } + + 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() == <$field as openvm_algebra_guest::Field>::ZERO + && self.y() == p2.y() + { + <$struct_name as TwistedEdwardsPoint>::IDENTITY + } else { + self.add_impl(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() == <$field as openvm_algebra_guest::Field>::ZERO + && self.y() == p2.y() + { + *self = <$struct_name as TwistedEdwardsPoint>::IDENTITY; + } else { + *self = self.add_impl(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 { + use core::ops::Add; + self.add(&-p2) + } + } + + impl core::ops::SubAssign<&$struct_name> for $struct_name { + fn sub_assign(&mut self, p2: &$struct_name) { + use core::ops::AddAssign; + self.add_assign(-p2); + } + } + + impl core::ops::SubAssign for $struct_name { + fn sub_assign(&mut self, rhs: Self) { + self.sub_assign(&rhs); + } + } + }; +} + +// This is the same as the Weierstrass version, but for Edwards curves we use +// TwistedEdwardsPoint::add_impl instead of WeierstrassPoint::add_ne_nonidentity, etc. +// Unlike the Weierstrass version, we do not require the bases to have prime order, since our +// addition formulas are complete. + +// MSM using preprocessed table (windowed method) +// Reference: modified from https://github.com/arkworks-rs/algebra/blob/master/ec/src/scalar_mul/mod.rs + +/// Cached precomputations of scalar multiples of several base points. +/// - `window_bits` is the window size used for the precomputation +/// - `max_scalar_bits` is the maximum size of the scalars that will be multiplied +/// - `table` is the precomputed table +pub struct CachedMulTable<'a, C: IntrinsicCurve> { + /// Window bits. Must be > 0. + /// For alignment, we currently require this to divide 8 (bits in a byte). + pub window_bits: usize, + pub bases: &'a [C::Point], + /// `table[i][j] = (j + 2) * bases[i]` for `j + 2 < 2 ** window_bits` + table: Vec>, + /// Needed to return reference to the identity point. + identity: C::Point, +} + +impl<'a, C: IntrinsicCurve> CachedMulTable<'a, C> +where + C::Point: TwistedEdwardsPoint + Group, + C::Scalar: IntMod, +{ + pub fn new(bases: &'a [C::Point], window_bits: usize) -> Self { + assert!(window_bits > 0); + let window_size = 1 << window_bits; + let table = bases + .iter() + .map(|base| { + if base.is_identity() { + vec![::IDENTITY; window_size - 2] + } else { + let mut multiples = Vec::with_capacity(window_size - 2); + for _ in 0..window_size - 2 { + let multiple = multiples + .last() + .map(|last| TwistedEdwardsPoint::add_impl(last, base)) + .unwrap_or_else(|| base.double()); + multiples.push(multiple); + } + multiples + } + }) + .collect(); + + Self { + window_bits, + bases, + table, + identity: ::IDENTITY, + } + } + + fn get_multiple(&self, base_idx: usize, scalar: usize) -> &C::Point { + if scalar == 0 { + &self.identity + } else if scalar == 1 { + unsafe { self.bases.get_unchecked(base_idx) } + } else { + unsafe { self.table.get_unchecked(base_idx).get_unchecked(scalar - 2) } + } + } + + /// Computes `sum scalars[i] * bases[i]`. + /// + /// For implementation simplicity, currently only implemented when + /// `window_bits` divides 8 (number of bits in a byte). + pub fn windowed_mul(&self, scalars: &[C::Scalar]) -> C::Point { + assert_eq!(8 % self.window_bits, 0); + assert_eq!(scalars.len(), self.bases.len()); + let windows_per_byte = 8 / self.window_bits; + + let num_windows = C::Scalar::NUM_LIMBS * windows_per_byte; + let mask = (1u8 << self.window_bits) - 1; + + // The current byte index (little endian) at the current step of the + // windowed method, across all scalars. + let mut limb_idx = C::Scalar::NUM_LIMBS; + // The current bit (little endian) within the current byte of the windowed + // method. The window will look at bits `bit_idx..bit_idx + window_bits`. + // bit_idx will always be in range [0, 8) + let mut bit_idx = 0; + + let mut res = ::IDENTITY; + for outer in 0..num_windows { + if bit_idx == 0 { + limb_idx -= 1; + bit_idx = 8 - self.window_bits; + } else { + bit_idx -= self.window_bits; + } + + if outer != 0 { + for _ in 0..self.window_bits { + res.double_assign(); + } + } + for (base_idx, scalar) in scalars.iter().enumerate() { + let scalar = (scalar.as_le_bytes()[limb_idx] >> bit_idx) & mask; + let summand = self.get_multiple(base_idx, scalar as usize); + // handles identity + res.add_assign(summand); + } + } + res + } +} diff --git a/extensions/ecc/guest/src/lib.rs b/extensions/ecc/guest/src/lib.rs index c7a9851cfd..aaaa1ee9fc 100644 --- a/extensions/ecc/guest/src/lib.rs +++ b/extensions/ecc/guest/src/lib.rs @@ -6,6 +6,7 @@ extern crate alloc; pub use once_cell; pub use openvm_algebra_guest as algebra; pub use openvm_ecc_sw_macros as sw_macros; +pub use openvm_ecc_te_macros as te_macros; use strum_macros::FromRepr; mod affine_point; @@ -17,11 +18,16 @@ pub use msm::*; /// Optimized ECDSA implementation with the same functional interface as the `ecdsa` crate pub mod ecdsa; +/// Edwards curve traits +pub mod edwards; /// Weierstrass curve traits pub mod weierstrass; +#[cfg(feature = "ed25519")] +pub mod ed25519; + /// This is custom-1 defined in RISC-V spec document -pub const OPCODE: u8 = 0x2b; +pub const SW_OPCODE: u8 = 0x2b; pub const SW_FUNCT3: u8 = 0b001; /// Short Weierstrass curves are configurable. @@ -37,3 +43,46 @@ pub enum SwBaseFunct7 { impl SwBaseFunct7 { pub const SHORT_WEIERSTRASS_MAX_KINDS: u8 = 8; } + +/// This is custom-1 defined in RISC-V spec document +pub const TE_OPCODE: u8 = 0x2b; +pub const TE_FUNCT3: u8 = 0b100; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, FromRepr)] +#[repr(u8)] +pub enum TeBaseFunct7 { + TeAdd = 0, + TeSetup, + TeHintDecompress, + TeHintNonQr, +} + +impl TeBaseFunct7 { + pub const TWISTED_EDWARDS_MAX_KINDS: u8 = 8; +} + +/// 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: Clone; + type Point: Clone; + + /// 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; +} + +pub trait FromCompressed { + /// Given `x`-coordinate, + /// + /// Decompresses a point from its x-coordinate and a recovery identifier which indicates + /// the parity of the y-coordinate. Given the x-coordinate, this function attempts to find the + /// corresponding y-coordinate that satisfies the elliptic curve equation. If successful, it + /// returns the point as an instance of Self. If the point cannot be decompressed, it returns + /// None. + fn decompress(x: Coordinate, rec_id: &u8) -> Option + where + Self: core::marker::Sized; +} diff --git a/extensions/ecc/guest/src/weierstrass.rs b/extensions/ecc/guest/src/weierstrass.rs index 82d5468b04..3c39cbcfe6 100644 --- a/extensions/ecc/guest/src/weierstrass.rs +++ b/extensions/ecc/guest/src/weierstrass.rs @@ -4,6 +4,7 @@ use core::ops::Mul; use openvm_algebra_guest::{Field, IntMod}; use super::group::Group; +use crate::IntrinsicCurve; /// Short Weierstrass curve affine point. pub trait WeierstrassPoint: Clone + Sized { @@ -113,32 +114,6 @@ pub trait WeierstrassPoint: Clone + Sized { } } -pub trait FromCompressed { - /// Given `x`-coordinate, - /// - /// Decompresses a point from its x-coordinate and a recovery identifier which indicates - /// the parity of the y-coordinate. Given the x-coordinate, this function attempts to find the - /// corresponding y-coordinate that satisfies the elliptic curve equation. If successful, it - /// returns the point as an instance of Self. If the point cannot be decompressed, it returns - /// None. - fn decompress(x: Coordinate, rec_id: &u8) -> Option - where - Self: core::marker::Sized; -} - -/// 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: Clone; - type Point: Clone; - - /// 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; -} - // MSM using preprocessed table (windowed method) // Reference: modified from https://github.com/arkworks-rs/algebra/blob/master/ec/src/scalar_mul/mod.rs // @@ -476,11 +451,11 @@ macro_rules! impl_sw_group_ops { self.double_assign_impl::(); } - // This implementation is the same as the default implementation in the `Group` trait, - // but it was found that overriding the default implementation reduced the cycle count - // by 50% on the ecrecover benchmark. - // We hypothesize that this is due to compiler optimizations that are not possible when - // the `is_identity` function is defined in a different source file. + // Note: It was found that implementing `is_identity` in group.rs as a default + // implementation increases the cycle count by 50% on the ecrecover benchmark. For + // this reason, we implement it here instead. We hypothesize that this is due to + // compiler optimizations that are not possible when the `is_identity` function is + // defined in a different source file. #[inline(always)] fn is_identity(&self) -> bool { self == &::IDENTITY diff --git a/extensions/ecc/sw-macros/README.md b/extensions/ecc/sw-macros/README.md index 091fcd20f5..994bd5a6d4 100644 --- a/extensions/ecc/sw-macros/README.md +++ b/extensions/ecc/sw-macros/README.md @@ -93,7 +93,7 @@ mod openvm_intrinsics_ffi_2 { 3. Again, if using the Rust bindings, then the `sw_setup_extern_func_*` function for every curve is automatically called on first use of any of the curve's intrinsics. -4. The order of the items in `sw_init!` **must match** the order of the moduli in the chip configuration -- more specifically, in the modular extension parameters (the order of `CurveConfig`s in `WeierstrassExtension::supported_curves`, which is usually defined with the whole `app_vm_config` in the `openvm.toml` file). +4. The order of the items in `sw_init!` **must match** the order of the moduli in the chip configuration -- more specifically, in the modular extension parameters (the order of `CurveConfig`s in `EccExtension::supported_sw_curves`, which is usually defined with the whole `app_vm_config` in the `openvm.toml` file). 5. Note that, due to the nature of function names, the name of the struct used in `sw_init!` must be the same as in `sw_declare!`. To illustrate, the following code will **fail** to compile: diff --git a/extensions/ecc/sw-macros/src/lib.rs b/extensions/ecc/sw-macros/src/lib.rs index 160301ea96..92f61634a1 100644 --- a/extensions/ecc/sw-macros/src/lib.rs +++ b/extensions/ecc/sw-macros/src/lib.rs @@ -217,7 +217,7 @@ pub fn sw_declare(input: TokenStream) -> TokenStream { use openvm_algebra_guest::IntMod; // Safety: Self::set_up_once() ensures IntMod::set_up_once() has been called. unsafe { - self.x.eq_impl::(&#intmod_type::ZERO) && self.y.eq_impl::(&#intmod_type::ZERO) + self.x.eq_impl::(&<#intmod_type as IntMod>::ZERO) && self.y.eq_impl::(&<#intmod_type as IntMod>::ZERO) } } } @@ -376,7 +376,7 @@ pub fn sw_declare(input: TokenStream) -> TokenStream { } mod #group_ops_mod_name { - use ::openvm_ecc_guest::{weierstrass::{WeierstrassPoint, FromCompressed}, impl_sw_group_ops, algebra::IntMod}; + use ::openvm_ecc_guest::{weierstrass::{WeierstrassPoint}, FromCompressed, impl_sw_group_ops, algebra::IntMod}; use super::*; impl_sw_group_ops!(#struct_name, #intmod_type); @@ -447,7 +447,7 @@ pub fn sw_init(input: TokenStream) -> TokenStream { #[no_mangle] extern "C" fn #add_ne_extern_func(rd: usize, rs1: usize, rs2: usize) { openvm::platform::custom_insn_r!( - opcode = OPCODE, + opcode = SW_OPCODE, funct3 = SW_FUNCT3 as usize, funct7 = SwBaseFunct7::SwAddNe as usize + #ec_idx * (SwBaseFunct7::SHORT_WEIERSTRASS_MAX_KINDS as usize), @@ -460,7 +460,7 @@ pub fn sw_init(input: TokenStream) -> TokenStream { #[no_mangle] extern "C" fn #double_extern_func(rd: usize, rs1: usize) { openvm::platform::custom_insn_r!( - opcode = OPCODE, + opcode = SW_OPCODE, funct3 = SW_FUNCT3 as usize, funct7 = SwBaseFunct7::SwDouble as usize + #ec_idx * (SwBaseFunct7::SHORT_WEIERSTRASS_MAX_KINDS as usize), @@ -475,7 +475,7 @@ pub fn sw_init(input: TokenStream) -> TokenStream { #[cfg(target_os = "zkvm")] { openvm::platform::custom_insn_r!( - opcode = ::openvm_ecc_guest::OPCODE, + opcode = ::openvm_ecc_guest::SW_OPCODE, funct3 = ::openvm_ecc_guest::SW_FUNCT3 as usize, funct7 = ::openvm_ecc_guest::SwBaseFunct7::SwSetup as usize + #ec_idx @@ -485,7 +485,7 @@ pub fn sw_init(input: TokenStream) -> TokenStream { rs2 = In p2 ); openvm::platform::custom_insn_r!( - opcode = ::openvm_ecc_guest::OPCODE, + opcode = ::openvm_ecc_guest::SW_OPCODE, funct3 = ::openvm_ecc_guest::SW_FUNCT3 as usize, funct7 = ::openvm_ecc_guest::SwBaseFunct7::SwSetup as usize + #ec_idx @@ -504,8 +504,8 @@ pub fn sw_init(input: TokenStream) -> TokenStream { TokenStream::from(quote::quote_spanned! { span.into() => #[allow(non_snake_case)] #[cfg(target_os = "zkvm")] - mod openvm_intrinsics_ffi_2 { - use ::openvm_ecc_guest::{OPCODE, SW_FUNCT3, SwBaseFunct7}; + mod openvm_intrinsics_ffi_2_sw { + use ::openvm_ecc_guest::{SW_OPCODE, SW_FUNCT3, SwBaseFunct7}; #(#externs)* } diff --git a/extensions/ecc/te-macros/Cargo.toml b/extensions/ecc/te-macros/Cargo.toml new file mode 100644 index 0000000000..de3544ff87 --- /dev/null +++ b/extensions/ecc/te-macros/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "openvm-ecc-te-macros" +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +syn = { version = "2.0", features = ["full"] } +quote = "1.0" +openvm-macros-common = { workspace = true, default-features = false } + +[lib] +proc-macro = true diff --git a/extensions/ecc/te-macros/README.md b/extensions/ecc/te-macros/README.md new file mode 100644 index 0000000000..6de5c50110 --- /dev/null +++ b/extensions/ecc/te-macros/README.md @@ -0,0 +1,125 @@ +# `openvm-ecc-te-macros` + +Procedural macros for use in guest program to generate short twisted Edwards elliptic curve struct with custom intrinsics for compile-time modulus. + +The workflow of this macro is very similar to the [`openvm-algebra-moduli-macros`](../moduli-macros/README.md) crate. We recommend reading it first. + +## Example + +```rust +// ... + +moduli_declare! { + Ed25519Coord { modulus = "0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED" }, + Ed25519Scalar { modulus = "0x1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED" }, +} + +// Note that from_const_bytes is little endian +pub const CURVE_A: Ed25519Coord = Ed25519Coord::from_const_bytes(hex!( + "ECFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F" +)); +pub const CURVE_D: Ed25519Coord = Ed25519Coord::from_const_bytes(hex!( + "A3785913CA4DEB75ABD841414D0A700098E879777940C78C73FE6F2BEE6C0352" +)); + +sw_declare! { + Ed25519Point { mod_type = Ed25519Coord, a = CURVE_A, d = CURVE_D }, +} + +openvm_algebra_guest::moduli_macros::moduli_init! { + "0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED", + "0x1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED", +} + +openvm_ecc_guest::te_macros::te_init! { + Ed25519Point, +} + +pub fn main() { + setup_all_moduli(); + setup_all_te_curves(); + // ... +} +``` + +## Full story + +Again, the principle is the same as in the [`openvm-algebra-moduli-macros`](../moduli-macros/README.md) crate. Here we emphasize the core differences. + +The crate provides two macros: `te_declare!` and `te_init!`. The signatures are: + +- `te_declare!` receives comma-separated list of moduli classes descriptions. Each description looks like `TeStruct { mod_type = ModulusName, a = a_expr, d = d_expr }`. Here `ModulusName` is the name of any struct that implements `trait IntMod` -- in particular, the ones created by `moduli_declare!` do. Parameters `a` and `d` correspond to the coefficients of the equation defining the curve. They **must be compile-time constants**. Both the parameters `a` and `d` are required. + +- `te_init!` receives comma-separated list of struct names. The struct name must exactly match the name in `te_declare!` -- type defs are not allowed (see point 5 below). + +What happens under the hood: + +1. `te_declare!` macro creates a struct with two field `x` and `y` of type `mod_type`. This struct denotes a point on the corresponding elliptic curve. In the example it would be + +```rust +struct Ed25519Point { + x: Ed25519Coord, + y: Ed25519Coord, +} +``` + +Similar to `moduli_declare!`, this macro also creates extern functions for arithmetic operations -- but in this case they are named after the te type, not after any hexadecimal (since the macro has no way to obtain it from the name of the modulus type anyway): + +```rust +extern "C" { + fn te_add_extern_func_Ed25519Point(rd: usize, rs1: usize, rs2: usize); + fn hint_decompress_extern_func_Ed25519Point(rs1: usize, rs2: usize); +} +``` + +2. Again, `te_init!` macro implements these extern functions and defines the setup functions for the te struct. + +```rust +#[cfg(target_os = "zkvm")] +mod openvm_intrinsics_ffi_2 { + use :openvm_ecc_guest::{OPCODE, TE_FUNCT3, TeBaseFunct7}; + + #[no_mangle] + extern "C" fn te_add_extern_func_Ed25519Point(rd: usize, rs1: usize, rs2: usize) { + // ... + } + // other externs +} +#[allow(non_snake_case)] +pub fn setup_te_Ed25519Point() { + #[cfg(target_os = "zkvm")] + { + // ... + } +} +pub fn setup_all_te_curves() { + setup_te_Ed25519Point(); + // other setups +} +``` + +3. Again, if using the Rust bindings, then the `te_setup_extern_func_*` function for every curve is automatically called on first use of any of the curve's intrinsics. + +4. The order of the items in `te_init!` **must match** the order of the moduli in the chip configuration -- more specifically, in the modular extension parameters (the order of `CurveConfig`s in `EccExtension::supported_te_curves`, which is usually defined with the whole `app_vm_config` in the `openvm.toml` file). + +5. Note that, due to the nature of function names, the name of the struct used in `te_init!` must be the same as in `te_declare!`. To illustrate, the following code will **fail** to compile: + +```rust +// ... + +te_declare! { + Ed25519Point { mod_type = Ed25519Coord, a = CURVE_A, d = CURVE_D }, +} + +pub type Te = Ed25519Point; + +te_init! { + Te, +} +``` + +The reason is that, for example, the function `sw_add_extern_func_Secp256k1Point` remains unimplemented, but we implement `sw_add_extern_func_Sw`. + +6. `cargo openvm build` will automatically generate a call to `te_init!` based on `openvm.toml`. +Note that `openvm.toml` must contain the name of each struct created by `te_declare!` as a string (in the example at the top of this document, its `"Ed25519Point"`). +The SDK also supports this feature. diff --git a/extensions/ecc/te-macros/src/lib.rs b/extensions/ecc/te-macros/src/lib.rs new file mode 100644 index 0000000000..6f1b3af29f --- /dev/null +++ b/extensions/ecc/te-macros/src/lib.rs @@ -0,0 +1,345 @@ +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, ExprPath, LitStr, Token, +}; + +/// This macro generates the code to setup a Twisted Edwards elliptic curve for a given modular +/// type. Also it places the curve parameters into a special static variable to be later extracted +/// from the ELF and used by the VM. Usage: +/// ``` +/// te_declare! { +/// [TODO] +/// } +/// ``` +/// +/// For this macro to work, you must import the `elliptic_curve` crate and the `openvm_ecc_guest` +/// crate.. +#[proc_macro] +pub fn te_declare(input: TokenStream) -> TokenStream { + let MacroArgs { items } = parse_macro_input!(input as MacroArgs); + + let mut output = Vec::new(); + + let span = proc_macro::Span::call_site(); + + for item in items.into_iter() { + let struct_name = item.name.to_string(); + let struct_name = syn::Ident::new(&struct_name, span.into()); + let struct_path: syn::Path = syn::parse_quote!(#struct_name); + let mut intmod_type: Option = None; + let mut const_a: Option = None; + let mut const_d: Option = None; + for param in item.params { + match param.name.to_string().as_str() { + "mod_type" => { + if let syn::Expr::Path(ExprPath { path, .. }) = param.value { + intmod_type = Some(path) + } else { + return syn::Error::new_spanned(param.value, "Expected a type") + .to_compile_error() + .into(); + } + } + "a" => { + const_a = Some(param.value); + } + "d" => { + const_d = Some(param.value); + } + _ => { + panic!("Unknown parameter {}", param.name); + } + } + } + + let intmod_type = intmod_type.expect("mod_type parameter is required"); + let const_a = const_a.expect("constant a coefficient is required"); + let const_d = const_d.expect("constant d coefficient is required"); + + macro_rules! create_extern_func { + ($name:ident) => { + let $name = syn::Ident::new( + &format!( + "{}_{}", + stringify!($name), + struct_path + .segments + .iter() + .map(|x| x.ident.to_string()) + .collect::>() + .join("_") + ), + span.into(), + ); + }; + } + create_extern_func!(te_add_extern_func); + create_extern_func!(te_setup_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 #te_add_extern_func(rd: usize, rs1: usize, rs2: usize); + fn #te_setup_extern_func(uninit: *mut core::ffi::c_void, p1: *const u8, p2: *const u8); + } + + #[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] + #[repr(C)] + pub struct #struct_name { + x: #intmod_type, + y: #intmod_type, + } + + impl #struct_name { + const fn identity() -> Self { + Self { + x: <#intmod_type as openvm_algebra_guest::IntMod>::ZERO, + y: <#intmod_type as openvm_algebra_guest::IntMod>::ONE, + } + } + // Below are wrapper functions for the intrinsic instructions. + // Should not be called directly. + #[inline(always)] + fn add_chip(p1: &#struct_name, p2: &#struct_name) -> #struct_name { + #[cfg(not(target_os = "zkvm"))] + { + use openvm_algebra_guest::DivUnsafe; + + let x1y2 = p1.x.clone() * p2.y.clone(); + let y1x2 = p1.y.clone() * p2.x.clone(); + let x1x2 = p1.x.clone() * p2.x.clone(); + let y1y2 = p1.y.clone() * p2.y.clone(); + let dx1x2y1y2 = ::CURVE_D * &x1x2 * &y1y2; + + let x3 = (x1y2 + y1x2).div_unsafe(&<#intmod_type as openvm_algebra_guest::IntMod>::ONE + &dx1x2y1y2); + let y3 = (y1y2 - ::CURVE_A * x1x2).div_unsafe(&<#intmod_type as openvm_algebra_guest::IntMod>::ONE - &dx1x2y1y2); + + #struct_name { x: x3, y: y3 } + } + #[cfg(target_os = "zkvm")] + { + Self::set_up_once(); + let mut uninit: core::mem::MaybeUninit<#struct_name> = core::mem::MaybeUninit::uninit(); + unsafe { + #te_add_extern_func( + uninit.as_mut_ptr() as usize, + p1 as *const #struct_name as usize, + p2 as *const #struct_name as usize + ) + }; + unsafe { uninit.assume_init() } + } + } + + // Helper function to call the setup instruction on first use + #[cfg(target_os = "zkvm")] + #[inline(always)] + fn set_up_once() { + static is_setup: ::openvm_ecc_guest::once_cell::race::OnceBool = ::openvm_ecc_guest::once_cell::race::OnceBool::new(); + is_setup.get_or_init(|| { + let modulus_bytes = <::Coordinate as openvm_algebra_guest::IntMod>::MODULUS; + let mut zero = [0u8; <::Coordinate as openvm_algebra_guest::IntMod>::NUM_LIMBS]; + let curve_a_bytes = openvm_algebra_guest::IntMod::as_le_bytes(&::CURVE_A); + let curve_d_bytes = openvm_algebra_guest::IntMod::as_le_bytes(&::CURVE_D); + let p1 = [modulus_bytes.as_ref(), curve_a_bytes.as_ref()].concat(); + let p2 = [curve_d_bytes.as_ref(), zero.as_ref()].concat(); + let mut uninit: core::mem::MaybeUninit<[Self; 2]> = core::mem::MaybeUninit::uninit(); + + unsafe { #te_setup_extern_func(uninit.as_mut_ptr() as *mut core::ffi::c_void, p1.as_ptr(), p2.as_ptr()); } + <#intmod_type as openvm_algebra_guest::IntMod>::set_up_once(); + true + }); + } + + #[cfg(not(target_os = "zkvm"))] + #[inline(always)] + fn set_up_once() { + // No-op for non-ZKVM targets + } + } + + impl ::openvm_ecc_guest::edwards::TwistedEdwardsPoint for #struct_name { + const CURVE_A: Self::Coordinate = #const_a; + const CURVE_D: Self::Coordinate = #const_d; + + const IDENTITY: Self = Self::identity(); + type Coordinate = #intmod_type; + + /// SAFETY: assumes that #intmod_type has a memory representation + /// such that with repr(C), two coordinates are packed contiguously. + #[inline(always)] + fn as_le_bytes(&self) -> &[u8] { + unsafe { &*core::ptr::slice_from_raw_parts(self as *const Self as *const u8, <#intmod_type as openvm_algebra_guest::IntMod>::NUM_LIMBS * 2) } + } + + #[inline(always)] + fn from_xy_unchecked(x: Self::Coordinate, y: Self::Coordinate) -> Self { + Self { x, y } + } + + #[inline(always)] + fn x(&self) -> &Self::Coordinate { + &self.x + } + + #[inline(always)] + fn y(&self) -> &Self::Coordinate { + &self.y + } + + #[inline(always)] + fn x_mut(&mut self) -> &mut Self::Coordinate { + &mut self.x + } + + #[inline(always)] + fn y_mut(&mut self) -> &mut Self::Coordinate { + &mut self.y + } + + #[inline(always)] + fn into_coords(self) -> (Self::Coordinate, Self::Coordinate) { + (self.x, self.y) + } + + #[inline(always)] + fn add_impl(&self, p2: &Self) -> Self { + Self::add_chip(self, p2) + } + } + + impl core::ops::Neg for #struct_name { + type Output = Self; + + fn neg(self) -> Self::Output { + #struct_name { + x: core::ops::Neg::neg(&self.x), + y: self.y, + } + } + } + + impl core::ops::Neg for &#struct_name { + type Output = #struct_name; + + fn neg(self) -> #struct_name { + #struct_name { + x: core::ops::Neg::neg(&self.x), + y: self.y.clone(), + } + } + } + + mod #group_ops_mod_name { + use ::openvm_ecc_guest::{edwards::TwistedEdwardsPoint, FromCompressed, impl_te_group_ops, algebra::{IntMod, DivUnsafe, DivAssignUnsafe, ExpBytes}}; + use super::*; + + impl_te_group_ops!(#struct_name, #intmod_type); + + impl FromCompressed<#intmod_type> for #struct_name { + fn decompress(y: #intmod_type, rec_id: &u8) -> Option { + use openvm_algebra_guest::{Sqrt, DivUnsafe}; + let x_squared = (<#intmod_type as openvm_algebra_guest::IntMod>::ONE - &y * &y).div_unsafe(<#struct_name as ::openvm_ecc_guest::edwards::TwistedEdwardsPoint>::CURVE_A - &<#struct_name as ::openvm_ecc_guest::edwards::TwistedEdwardsPoint>::CURVE_D * &y * &y); + let x = x_squared.sqrt(); + match x { + None => None, + Some(x) => { + let correct_x = if x.as_le_bytes()[0] & 1 == *rec_id & 1 { + x + } else { + -x + }; + // handle the case where x = 0 + if correct_x.as_le_bytes()[0] & 1 != *rec_id & 1 { + return None; + } + // In order for sqrt() to return Some, we are guaranteed that x * x == x_squared, which already proves (correct_x, y) is on the curve + Some(<#struct_name as ::openvm_ecc_guest::edwards::TwistedEdwardsPoint>::from_xy_unchecked(correct_x, y)) + } + } + } + } + } + }); + output.push(result); + } + + TokenStream::from_iter(output) +} + +struct TeDefine { + items: Vec, +} + +impl Parse for TeDefine { + fn parse(input: ParseStream) -> syn::Result { + let items = input.parse_terminated(::parse, Token![,])?; + Ok(Self { + items: items.into_iter().map(|e| e.value()).collect(), + }) + } +} + +#[proc_macro] +pub fn te_init(input: TokenStream) -> TokenStream { + let TeDefine { items } = parse_macro_input!(input as TeDefine); + + let mut externs = Vec::new(); + + let span = proc_macro::Span::call_site(); + + for (ec_idx, struct_id) in items.into_iter().enumerate() { + let add_extern_func = + syn::Ident::new(&format!("te_add_extern_func_{}", struct_id), span.into()); + let setup_extern_func = + syn::Ident::new(&format!("te_setup_extern_func_{}", struct_id), span.into()); + externs.push(quote::quote_spanned! { span.into() => + #[no_mangle] + extern "C" fn #add_extern_func(rd: usize, rs1: usize, rs2: usize) { + openvm::platform::custom_insn_r!( + opcode = TE_OPCODE, + funct3 = TE_FUNCT3 as usize, + funct7 = TeBaseFunct7::TeAdd as usize + #ec_idx + * (TeBaseFunct7::TWISTED_EDWARDS_MAX_KINDS as usize), + rd = In rd, + rs1 = In rs1, + rs2 = In rs2 + ); + } + + #[no_mangle] + extern "C" fn #setup_extern_func(uninit: *mut core::ffi::c_void, p1: *const u8, p2: *const u8) { + #[cfg(target_os = "zkvm")] + { + + openvm::platform::custom_insn_r!( + opcode = ::openvm_ecc_guest::TE_OPCODE, + funct3 = ::openvm_ecc_guest::TE_FUNCT3 as usize, + funct7 = ::openvm_ecc_guest::TeBaseFunct7::TeSetup as usize + + #ec_idx + * (::openvm_ecc_guest::TeBaseFunct7::TWISTED_EDWARDS_MAX_KINDS as usize), + rd = In uninit, + rs1 = In p1, + rs2 = In p2, + ); + } + } + }); + } + + TokenStream::from(quote::quote_spanned! { span.into() => + #[allow(non_snake_case)] + #[cfg(target_os = "zkvm")] + mod openvm_intrinsics_ffi_2_te { + use ::openvm_ecc_guest::{TE_OPCODE, TE_FUNCT3, TeBaseFunct7}; + + #(#externs)* + } + }) +} diff --git a/extensions/ecc/tests/Cargo.toml b/extensions/ecc/tests/Cargo.toml index 5f90e77fa4..0de9c0249e 100644 --- a/extensions/ecc/tests/Cargo.toml +++ b/extensions/ecc/tests/Cargo.toml @@ -21,8 +21,8 @@ serde.workspace = true serde_with.workspace = true toml.workspace = true eyre.workspace = true -hex-literal.workspace = true num-bigint.workspace = true +hex-literal.workspace = true halo2curves-axiom = { workspace = true } [features] diff --git a/extensions/ecc/tests/programs/Cargo.toml b/extensions/ecc/tests/programs/Cargo.toml index 55fcedd3ee..065dc404f2 100644 --- a/extensions/ecc/tests/programs/Cargo.toml +++ b/extensions/ecc/tests/programs/Cargo.toml @@ -11,6 +11,7 @@ openvm-custom-insn = { path = "../../../../crates/toolchain/custom_insn", defaul openvm-ecc-guest = { path = "../../guest", default-features = false } openvm-ecc-sw-macros = { path = "../../../../extensions/ecc/sw-macros", default-features = false } +openvm-ecc-te-macros = { path = "../../../../extensions/ecc/te-macros", default-features = false } openvm-algebra-guest = { path = "../../../algebra/guest", default-features = false } openvm-algebra-moduli-macros = { path = "../../../algebra/moduli-macros", default-features = false } openvm-rv32im-guest = { path = "../../../../extensions/rv32im/guest", default-features = false } @@ -43,6 +44,7 @@ default = [] std = ["serde/std", "openvm/std"] k256 = ["dep:openvm-k256"] p256 = ["dep:openvm-p256"] +ed25519 = ["openvm-ecc-guest/ed25519"] [profile.release] panic = "abort" @@ -63,7 +65,7 @@ required-features = ["k256", "p256"] [[example]] name = "decompress" -required-features = ["k256"] +required-features = ["k256", "ed25519"] [[example]] name = "ecdsa" @@ -81,6 +83,10 @@ required-features = ["k256"] name = "sec1_decode" required-features = ["k256"] +[[example]] +name = "edwards_ec" +required-features = ["ed25519"] + [[example]] name = "invalid_setup" required-features = ["k256", "p256"] diff --git a/extensions/ecc/tests/programs/examples/decompress.rs b/extensions/ecc/tests/programs/examples/decompress.rs index 0148d5d057..f6e9870a3e 100644 --- a/extensions/ecc/tests/programs/examples/decompress.rs +++ b/extensions/ecc/tests/programs/examples/decompress.rs @@ -7,9 +7,11 @@ extern crate alloc; use hex_literal::hex; use openvm::io::read_vec; use openvm_ecc_guest::{ - algebra::IntMod, - weierstrass::{FromCompressed, WeierstrassPoint}, - Group, + algebra::{Field, IntMod}, + ed25519::{Ed25519Coord, Ed25519Point}, + edwards::TwistedEdwardsPoint, + weierstrass::WeierstrassPoint, + FromCompressed, Group, }; use openvm_k256::{Secp256k1Coord, Secp256k1Point}; @@ -22,7 +24,6 @@ openvm_algebra_moduli_macros::moduli_declare! { Fp1mod4 { modulus = "0xffffffffffffffffffffffffffffffff000000000000000000000001" }, } -// const CURVE_B_5MOD8: Fp5mod8 = Fp5mod8::from_const_u8(3); const CURVE_B_5MOD8: Fp5mod8 = Fp5mod8::from_const_u8(6); const CURVE_A_1MOD4: Fp1mod4 = Fp1mod4::from_const_bytes(hex!( @@ -44,7 +45,7 @@ openvm_ecc_sw_macros::sw_declare! { }, } -openvm::init!("openvm_init_decompress_k256.rs"); +openvm::init!("openvm_init_decompress_k256_ed25519.rs"); // test decompression under an honest host pub fn main() { @@ -53,35 +54,43 @@ pub fn main() { let y = Secp256k1Coord::from_le_bytes_unchecked(&bytes[32..64]); let rec_id = y.as_le_bytes()[0] & 1; - test_possible_decompression::(&x, &y, rec_id); + test_possible_sw_decompression::(&x, &y, rec_id); // x = 5 is not on the x-coordinate of any point on the Secp256k1 curve - test_impossible_decompression::(&Secp256k1Coord::from_u8(5), rec_id); + test_impossible_sw_decompression::(&Secp256k1Coord::from_u8(5), rec_id); let x = Fp5mod8::from_le_bytes_unchecked(&bytes[64..96]); let y = Fp5mod8::from_le_bytes_unchecked(&bytes[96..128]); let rec_id = y.as_le_bytes()[0] & 1; - test_possible_decompression::(&x, &y, rec_id); + test_possible_sw_decompression::(&x, &y, rec_id); // x = 0 is not on the x-coordinate of any point on the CurvePoint5mod8 curve - test_impossible_decompression::(&Fp5mod8::ZERO, rec_id); + test_impossible_sw_decompression::(&::ZERO, rec_id); // this x is such that y^2 = x^3 + 6 = 0 // we want to test the case where y^2 = 0 and rec_id = 1 let x = Fp5mod8::from_le_bytes_unchecked(&hex!( "d634a701c3b9b8cbf7797988be3953b442863b74d2d5c4d5f1a9de3c0c256d90" )); - test_possible_decompression::(&x, &Fp5mod8::ZERO, 0); - test_impossible_decompression::(&x, 1); + test_possible_sw_decompression::(&x, &::ZERO, 0); + test_impossible_sw_decompression::(&x, 1); let x = Fp1mod4::from_le_bytes_unchecked(&bytes[128..160]); let y = Fp1mod4::from_le_bytes_unchecked(&bytes[160..192]); let rec_id = y.as_le_bytes()[0] & 1; - test_possible_decompression::(&x, &y, rec_id); + test_possible_sw_decompression::(&x, &y, rec_id); // x = 1 is not on the x-coordinate of any point on the CurvePoint1mod4 curve - test_impossible_decompression::(&Fp1mod4::from_u8(1), rec_id); + test_impossible_sw_decompression::(&Fp1mod4::from_u8(1), rec_id); + + // ed25519 + let x = Ed25519Coord::from_le_bytes_unchecked(&bytes[192..224]); + let y = Ed25519Coord::from_le_bytes_unchecked(&bytes[224..256]); + let rec_id = x.as_le_bytes()[0] & 1; + test_possible_te_decompression::(&x, &y, rec_id); + // y = 2 is not on the y-coordinate of any point on the Ed25519 curve + test_impossible_te_decompression::(&Ed25519Coord::from_u8(2), rec_id); } -fn test_possible_decompression>( +fn test_possible_sw_decompression>( x: &P::Coordinate, y: &P::Coordinate, rec_id: u8, @@ -91,7 +100,25 @@ fn test_possible_decompression>( +fn test_possible_te_decompression>( + x: &P::Coordinate, + y: &P::Coordinate, + rec_id: u8, +) { + let p = P::decompress(y.clone(), &rec_id).unwrap(); + assert_eq!(p.x(), x); + assert_eq!(p.y(), y); +} + +fn test_impossible_sw_decompression>( + x: &P::Coordinate, + rec_id: u8, +) { + let p = P::decompress(x.clone(), &rec_id); + assert!(p.is_none()); +} + +fn test_impossible_te_decompression>( x: &P::Coordinate, rec_id: u8, ) { diff --git a/extensions/ecc/tests/programs/examples/ec.rs b/extensions/ecc/tests/programs/examples/ec.rs index 1b63057c30..71c1194463 100644 --- a/extensions/ecc/tests/programs/examples/ec.rs +++ b/extensions/ecc/tests/programs/examples/ec.rs @@ -2,8 +2,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use hex_literal::hex; -use openvm_algebra_guest::IntMod; -use openvm_ecc_guest::{msm, weierstrass::WeierstrassPoint, Group}; +use openvm_ecc_guest::{algebra::IntMod, msm, weierstrass::WeierstrassPoint, Group}; use openvm_k256::{Secp256k1Coord, Secp256k1Point, Secp256k1Scalar}; openvm::init!("openvm_init_ec_k256.rs"); diff --git a/extensions/ecc/tests/programs/examples/ec_nonzero_a.rs b/extensions/ecc/tests/programs/examples/ec_nonzero_a.rs index 41db1ececc..854641c4bf 100644 --- a/extensions/ecc/tests/programs/examples/ec_nonzero_a.rs +++ b/extensions/ecc/tests/programs/examples/ec_nonzero_a.rs @@ -2,8 +2,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use hex_literal::hex; -use openvm_algebra_guest::IntMod; -use openvm_ecc_guest::{weierstrass::WeierstrassPoint, CyclicGroup, Group}; +use openvm_ecc_guest::{algebra::IntMod, weierstrass::WeierstrassPoint, CyclicGroup, Group}; use openvm_p256::{P256Coord, P256Point}; openvm::entry!(main); diff --git a/extensions/ecc/tests/programs/examples/ec_two_curves.rs b/extensions/ecc/tests/programs/examples/ec_two_curves.rs index 6412e3184f..681f1c9fe4 100644 --- a/extensions/ecc/tests/programs/examples/ec_two_curves.rs +++ b/extensions/ecc/tests/programs/examples/ec_two_curves.rs @@ -2,8 +2,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use hex_literal::hex; -use openvm_algebra_guest::IntMod; -use openvm_ecc_guest::{msm, weierstrass::WeierstrassPoint, Group}; +use openvm_ecc_guest::{algebra::IntMod, msm, weierstrass::WeierstrassPoint, Group}; use openvm_k256::{Secp256k1Coord, Secp256k1Point, Secp256k1Scalar}; use openvm_p256::{P256Coord, P256Point}; diff --git a/extensions/ecc/tests/programs/examples/edwards_ec.rs b/extensions/ecc/tests/programs/examples/edwards_ec.rs new file mode 100644 index 0000000000..c3236cd428 --- /dev/null +++ b/extensions/ecc/tests/programs/examples/edwards_ec.rs @@ -0,0 +1,64 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +use hex_literal::hex; +use openvm_algebra_guest::moduli_macros::moduli_init; +use openvm_ecc_guest::{ + algebra::IntMod, + ed25519::{Ed25519Coord, Ed25519Point}, + edwards::TwistedEdwardsPoint, + te_macros::te_init, + CyclicGroup, Group, +}; + +openvm::init!("openvm_init_edwards_ec_ed25519.rs"); + +openvm::entry!(main); + +pub fn main() { + // Base point of edwards25519 + let mut p1 = Ed25519Point::GENERATOR; + + // random point on edwards25519 + let x2 = Ed25519Coord::from_u32(2); + let y2 = Ed25519Coord::from_be_bytes_unchecked(&hex!( + "1A43BF127BDDC4D71FF910403C11DDB5BA2BCDD2815393924657EF111E712631" + )); + let mut p2 = Ed25519Point::from_xy(x2, y2).unwrap(); + + // This is the sum of (x1, y1) and (x2, y2). + let x3 = Ed25519Coord::from_be_bytes_unchecked(&hex!( + "636C0B519B2C5B1E0D3BFD213F45AFD5DAEE3CECC9B68CF88615101BC78329E6" + )); + let y3 = Ed25519Coord::from_be_bytes_unchecked(&hex!( + "704D8868CB335A7B609D04B9CD619511675691A78861F1DFF7A5EBC389C7EA92" + )); + + // This is 2 * (x1, y1) + let x4 = Ed25519Coord::from_be_bytes_unchecked(&hex!( + "56B98CC045559AD2BBC45CAB58D842ECEE264DB9395F6014B772501B62BB7EE8" + )); + let y4 = Ed25519Coord::from_be_bytes_unchecked(&hex!( + "1BCA918096D89C83A15105DF343DC9F7510494407750226DAC0A7620ACE77BEB" + )); + + // Generic add can handle equal or unequal points. + let p3 = &p1 + &p2; + if p3.x() != &x3 || p3.y() != &y3 { + panic!(); + } + let p4 = &p2 + &p2; + if p4.x() != &x4 || p4.y() != &y4 { + panic!(); + } + + // Add assign and double assign + p1 += &p2; + if p1.x() != &x3 || p1.y() != &y3 { + panic!(); + } + p2.double_assign(); + if p2.x() != &x4 || p2.y() != &y4 { + panic!(); + } +} diff --git a/extensions/ecc/tests/programs/openvm_ed25519.toml b/extensions/ecc/tests/programs/openvm_ed25519.toml new file mode 100644 index 0000000000..58701a8203 --- /dev/null +++ b/extensions/ecc/tests/programs/openvm_ed25519.toml @@ -0,0 +1,15 @@ +[app_vm_config.rv32i] +[app_vm_config.rv32m] +[app_vm_config.io] + +[app_vm_config.modular] +supported_moduli = ["57896044618658097711785492504343953926634992332820282019728792003956564819949", "7237005577332262213973186563042994240857116359379907606001950938285454250989"] + +[[app_vm_config.ecc.supported_te_curves]] +struct_name = "Ed25519Point" +modulus = "57896044618658097711785492504343953926634992332820282019728792003956564819949" +scalar = "7237005577332262213973186563042994240857116359379907606001950938285454250989" + +[app_vm_config.ecc.supported_te_curves.coeffs] +a = "57896044618658097711785492504343953926634992332820282019728792003956564819948" +d = "37095705934669439343138083508754565189542113879843219016388785533085940283555" diff --git a/extensions/ecc/tests/programs/openvm_init_decompress_k256.rs b/extensions/ecc/tests/programs/openvm_init_decompress_k256_ed25519.rs similarity index 73% rename from extensions/ecc/tests/programs/openvm_init_decompress_k256.rs rename to extensions/ecc/tests/programs/openvm_init_decompress_k256_ed25519.rs index a873a01d64..f1251f7039 100644 --- a/extensions/ecc/tests/programs/openvm_init_decompress_k256.rs +++ b/extensions/ecc/tests/programs/openvm_init_decompress_k256_ed25519.rs @@ -1,3 +1,4 @@ // This file is automatically generated by cargo openvm. Do not rename or edit. -openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337", "115792089237316195423570985008687907853269984665640564039457584007913129639501", "1000000007", "26959946667150639794667015087019630673557916260026308143510066298881", "26959946667150639794667015087019625940457807714424391721682722368061" } +openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337", "115792089237316195423570985008687907853269984665640564039457584007913129639501", "1000000007", "26959946667150639794667015087019630673557916260026308143510066298881", "26959946667150639794667015087019625940457807714424391721682722368061", "57896044618658097711785492504343953926634992332820282019728792003956564819949", "7237005577332262213973186563042994240857116359379907606001950938285454250989" } openvm_ecc_guest::sw_macros::sw_init! { "Secp256k1Point", "CurvePoint5mod8", "CurvePoint1mod4" } +openvm_ecc_guest::te_macros::te_init! { "Ed25519Point" } diff --git a/extensions/ecc/tests/programs/openvm_init_ec_k256.rs b/extensions/ecc/tests/programs/openvm_init_ec_k256.rs index dc6d4917dd..0905f21c53 100644 --- a/extensions/ecc/tests/programs/openvm_init_ec_k256.rs +++ b/extensions/ecc/tests/programs/openvm_init_ec_k256.rs @@ -1,3 +1,4 @@ // This file is automatically generated by cargo openvm. Do not rename or edit. openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } openvm_ecc_guest::sw_macros::sw_init! { "Secp256k1Point" } +openvm_ecc_guest::te_macros::te_init! {} diff --git a/extensions/ecc/tests/programs/openvm_init_ec_nonzero_a_p256.rs b/extensions/ecc/tests/programs/openvm_init_ec_nonzero_a_p256.rs index a2cd709ba0..fc26ca238d 100644 --- a/extensions/ecc/tests/programs/openvm_init_ec_nonzero_a_p256.rs +++ b/extensions/ecc/tests/programs/openvm_init_ec_nonzero_a_p256.rs @@ -1,3 +1,4 @@ // This file is automatically generated by cargo openvm. Do not rename or edit. openvm_algebra_guest::moduli_macros::moduli_init! { "115792089210356248762697446949407573530086143415290314195533631308867097853951", "115792089210356248762697446949407573529996955224135760342422259061068512044369" } openvm_ecc_guest::sw_macros::sw_init! { "P256Point" } +openvm_ecc_guest::te_macros::te_init! {} diff --git a/extensions/ecc/tests/programs/openvm_init_ec_two_curves_k256_p256.rs b/extensions/ecc/tests/programs/openvm_init_ec_two_curves_k256_p256.rs index d42d03051d..331836d7a1 100644 --- a/extensions/ecc/tests/programs/openvm_init_ec_two_curves_k256_p256.rs +++ b/extensions/ecc/tests/programs/openvm_init_ec_two_curves_k256_p256.rs @@ -1,3 +1,4 @@ // This file is automatically generated by cargo openvm. Do not rename or edit. openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337", "115792089210356248762697446949407573530086143415290314195533631308867097853951", "115792089210356248762697446949407573529996955224135760342422259061068512044369" } openvm_ecc_guest::sw_macros::sw_init! { "Secp256k1Point", "P256Point" } +openvm_ecc_guest::te_macros::te_init! {} diff --git a/extensions/ecc/tests/programs/openvm_init_ecdsa_k256.rs b/extensions/ecc/tests/programs/openvm_init_ecdsa_k256.rs index dc6d4917dd..0905f21c53 100644 --- a/extensions/ecc/tests/programs/openvm_init_ecdsa_k256.rs +++ b/extensions/ecc/tests/programs/openvm_init_ecdsa_k256.rs @@ -1,3 +1,4 @@ // This file is automatically generated by cargo openvm. Do not rename or edit. openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } openvm_ecc_guest::sw_macros::sw_init! { "Secp256k1Point" } +openvm_ecc_guest::te_macros::te_init! {} diff --git a/extensions/ecc/tests/programs/openvm_init_edwards_ec_ed25519.rs b/extensions/ecc/tests/programs/openvm_init_edwards_ec_ed25519.rs new file mode 100644 index 0000000000..1a21c687cc --- /dev/null +++ b/extensions/ecc/tests/programs/openvm_init_edwards_ec_ed25519.rs @@ -0,0 +1,4 @@ +// This file is automatically generated by cargo openvm. Do not rename or edit. +openvm_algebra_guest::moduli_macros::moduli_init! { "57896044618658097711785492504343953926634992332820282019728792003956564819949", "7237005577332262213973186563042994240857116359379907606001950938285454250989" } +openvm_ecc_guest::sw_macros::sw_init! { } +openvm_ecc_guest::te_macros::te_init! { "Ed25519Point" } diff --git a/extensions/ecc/tests/programs/openvm_k256.toml b/extensions/ecc/tests/programs/openvm_k256.toml index 571fdb895c..2fa80a5af3 100644 --- a/extensions/ecc/tests/programs/openvm_k256.toml +++ b/extensions/ecc/tests/programs/openvm_k256.toml @@ -8,9 +8,11 @@ supported_moduli = [ "115792089237316195423570985008687907852837564279074904382605163141518161494337", ] -[[app_vm_config.ecc.supported_curves]] +[[app_vm_config.ecc.supported_sw_curves]] struct_name = "Secp256k1Point" modulus = "115792089237316195423570985008687907853269984665640564039457584007908834671663" scalar = "115792089237316195423570985008687907852837564279074904382605163141518161494337" + +[app_vm_config.ecc.supported_sw_curves.coeffs] a = "0" b = "7" diff --git a/extensions/ecc/tests/programs/openvm_k256_keccak.toml b/extensions/ecc/tests/programs/openvm_k256_keccak.toml index c1261ee458..4dc77ccd80 100644 --- a/extensions/ecc/tests/programs/openvm_k256_keccak.toml +++ b/extensions/ecc/tests/programs/openvm_k256_keccak.toml @@ -9,9 +9,11 @@ supported_moduli = [ "115792089237316195423570985008687907852837564279074904382605163141518161494337", ] -[[app_vm_config.ecc.supported_curves]] +[[app_vm_config.ecc.supported_sw_curves]] struct_name = "Secp256k1Point" modulus = "115792089237316195423570985008687907853269984665640564039457584007908834671663" scalar = "115792089237316195423570985008687907852837564279074904382605163141518161494337" + +[app_vm_config.ecc.supported_sw_curves.coeffs] a = "0" b = "7" diff --git a/extensions/ecc/tests/programs/openvm_p256.toml b/extensions/ecc/tests/programs/openvm_p256.toml index 0035cd83da..2cc5bd92c3 100644 --- a/extensions/ecc/tests/programs/openvm_p256.toml +++ b/extensions/ecc/tests/programs/openvm_p256.toml @@ -7,9 +7,11 @@ supported_moduli = [ "115792089210356248762697446949407573529996955224135760342422259061068512044369", ] -[[app_vm_config.ecc.supported_curves]] +[[app_vm_config.ecc.supported_sw_curves]] struct_name = "P256Point" modulus = "115792089210356248762697446949407573530086143415290314195533631308867097853951" scalar = "115792089210356248762697446949407573529996955224135760342422259061068512044369" + +[app_vm_config.ecc.supported_sw_curves.coeffs] a = "115792089210356248762697446949407573530086143415290314195533631308867097853948" b = "41058363725152142129326129780047268409114441015993725554835256314039467401291" diff --git a/extensions/ecc/tests/src/lib.rs b/extensions/ecc/tests/src/lib.rs index 70e8c75fba..a29e4a049e 100644 --- a/extensions/ecc/tests/src/lib.rs +++ b/extensions/ecc/tests/src/lib.rs @@ -14,8 +14,8 @@ mod tests { utils::{air_test, air_test_with_min_segments, test_system_config_with_continuations}, }; use openvm_ecc_circuit::{ - CurveConfig, Rv32WeierstrassConfig, Rv32WeierstrassCpuBuilder, P256_CONFIG, - SECP256K1_CONFIG, + CurveConfig, Rv32EccConfig, Rv32EccCpuBuilder, SwCurveCoeffs, TeCurveCoeffs, + ED25519_CONFIG, P256_CONFIG, SECP256K1_CONFIG, }; use openvm_ecc_transpiler::EccTranspilerExtension; use openvm_rv32im_transpiler::{ @@ -39,15 +39,18 @@ mod tests { type F = BabyBear; #[cfg(test)] - fn test_rv32weierstrass_config(curves: Vec) -> Rv32WeierstrassConfig { - let mut config = Rv32WeierstrassConfig::new(curves); + fn test_rv32ecc_config( + sw_curves: Vec>, + te_curves: Vec>, + ) -> Rv32EccConfig { + let mut config = Rv32EccConfig::new(sw_curves, te_curves); *config.as_mut() = test_system_config_with_continuations(); config } #[test] fn test_ec() -> Result<()> { - let config = test_rv32weierstrass_config(vec![SECP256K1_CONFIG.clone()]); + let config = test_rv32ecc_config(vec![SECP256K1_CONFIG.clone()], vec![]); let elf = build_example_program_at_path_with_features( get_programs_dir!(), "ec", @@ -63,13 +66,13 @@ mod tests { .with_extension(EccTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - air_test(Rv32WeierstrassCpuBuilder, config, openvm_exe); + air_test(Rv32EccCpuBuilder, config, openvm_exe); Ok(()) } #[test] fn test_nonzero_a() -> Result<()> { - let config = test_rv32weierstrass_config(vec![P256_CONFIG.clone()]); + let config = test_rv32ecc_config(vec![P256_CONFIG.clone()], vec![]); let elf = build_example_program_at_path_with_features( get_programs_dir!(), "ec_nonzero_a", @@ -85,14 +88,14 @@ mod tests { .with_extension(EccTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - air_test(Rv32WeierstrassCpuBuilder, config, openvm_exe); + air_test(Rv32EccCpuBuilder, config, openvm_exe); Ok(()) } #[test] fn test_two_curves() -> Result<()> { let config = - test_rv32weierstrass_config(vec![SECP256K1_CONFIG.clone(), P256_CONFIG.clone()]); + test_rv32ecc_config(vec![SECP256K1_CONFIG.clone(), P256_CONFIG.clone()], vec![]); let elf = build_example_program_at_path_with_features( get_programs_dir!(), "ec_two_curves", @@ -108,15 +111,15 @@ mod tests { .with_extension(EccTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - air_test(Rv32WeierstrassCpuBuilder, config, openvm_exe); + air_test(Rv32EccCpuBuilder, config, openvm_exe); Ok(()) } #[test] fn test_decompress() -> Result<()> { - use halo2curves_axiom::{group::Curve, secp256k1::Secp256k1Affine}; + use halo2curves_axiom::{ed25519::Ed25519Affine, group::Curve, secp256k1::Secp256k1Affine}; - let config = test_rv32weierstrass_config(vec![SECP256K1_CONFIG.clone(), + let config = test_rv32ecc_config(vec![SECP256K1_CONFIG.clone(), CurveConfig { struct_name: "CurvePoint5mod8".to_string(), modulus: BigUint::from_str("115792089237316195423570985008687907853269984665640564039457584007913129639501") @@ -124,8 +127,10 @@ mod tests { // unused, set to 10e9 + 7 scalar: BigUint::from_str("1000000007") .unwrap(), - a: BigUint::ZERO, - b: BigUint::from_str("6").unwrap(), + coeffs: SwCurveCoeffs { + a: BigUint::ZERO, + b: BigUint::from_str("6").unwrap(), + }, }, CurveConfig { struct_name: "CurvePoint1mod4".to_string(), @@ -133,19 +138,24 @@ mod tests { .unwrap(), scalar: BigUint::from_radix_be(&hex!("ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d"), 256) .unwrap(), - a: BigUint::from_radix_be(&hex!("fffffffffffffffffffffffffffffffefffffffffffffffffffffffe"), 256) - .unwrap(), - b: BigUint::from_radix_be(&hex!("b4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4"), 256) - .unwrap(), + coeffs: SwCurveCoeffs { + a: BigUint::from_radix_be(&hex!("fffffffffffffffffffffffffffffffefffffffffffffffffffffffe"), 256) + .unwrap(), + b: BigUint::from_radix_be(&hex!("b4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4"), 256) + .unwrap(), + }, }, - ]); + ], + vec![ED25519_CONFIG.clone()], + ); let elf = build_example_program_at_path_with_features( get_programs_dir!(), "decompress", - ["k256"], + ["k256", "ed25519"], &config, )?; + let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -158,8 +168,7 @@ mod tests { let p = Secp256k1Affine::generator(); let p = (p + p + p).to_affine(); - println!("decompressed: {:?}", p); - + println!("secp256k1 decompressed: {:?}", p); let q_x: [u8; 32] = hex!("0100000000000000000000000000000000000000000000000000000000000000"); let q_y: [u8; 32] = @@ -168,19 +177,25 @@ mod tests { hex!("211D5C11D68032342211C256D3C1034AB99013327FBFB46BBD0C0EB700000000"); let r_y: [u8; 32] = hex!("347E00859981D5446447075AA07543CDE6DF224CFB23F7B5886337BD00000000"); + let s = Ed25519Affine::generator(); + let s = (s + s + s).to_affine(); - let coords = [p.x.to_bytes(), p.y.to_bytes(), q_x, q_y, r_x, r_y] - .concat() - .into_iter() - .map(FieldAlgebra::from_canonical_u8) - .collect(); - air_test_with_min_segments( - Rv32WeierstrassCpuBuilder, - config, - openvm_exe, - vec![coords], - 1, - ); + let coords = [ + p.x.to_bytes(), + p.y.to_bytes(), + q_x, + q_y, + r_x, + r_y, + s.x.to_bytes(), + s.y.to_bytes(), + ] + .concat() + .into_iter() + .map(FieldAlgebra::from_canonical_u8) + .collect(); + + air_test_with_min_segments(Rv32EccCpuBuilder, config, openvm_exe, vec![coords], 1); Ok(()) } @@ -255,6 +270,31 @@ mod tests { Ok(()) } + #[test] + fn test_edwards_ec() -> Result<()> { + let config = toml::from_str::>(include_str!( + "../programs/openvm_ed25519.toml" + ))? + .app_vm_config; + let elf = build_example_program_at_path_with_features::<&str>( + get_programs_dir!(), + "edwards_ec", + ["ed25519"], + &config, + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(EccTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + air_test(SdkVmCpuBuilder, config, openvm_exe); + Ok(()) + } + #[test] #[should_panic] fn test_invalid_setup() { @@ -276,7 +316,7 @@ mod tests { ) .unwrap(); let config = - test_rv32weierstrass_config(vec![SECP256K1_CONFIG.clone(), P256_CONFIG.clone()]); - air_test(Rv32WeierstrassCpuBuilder, config, openvm_exe); + test_rv32ecc_config(vec![SECP256K1_CONFIG.clone(), P256_CONFIG.clone()], vec![]); + air_test(Rv32EccCpuBuilder, config, openvm_exe); } } diff --git a/extensions/ecc/transpiler/src/lib.rs b/extensions/ecc/transpiler/src/lib.rs index 462e95dbdd..469868d3ae 100644 --- a/extensions/ecc/transpiler/src/lib.rs +++ b/extensions/ecc/transpiler/src/lib.rs @@ -1,4 +1,4 @@ -use openvm_ecc_guest::{SwBaseFunct7, OPCODE, SW_FUNCT3}; +use openvm_ecc_guest::{SwBaseFunct7, TeBaseFunct7, SW_FUNCT3, SW_OPCODE, TE_FUNCT3, TE_OPCODE}; use openvm_instructions::{ instruction::Instruction, riscv::RV32_REGISTER_NUM_LIMBS, LocalOpcode, VmOpcode, }; @@ -15,10 +15,21 @@ use strum::{EnumCount, EnumIter, FromRepr}; #[allow(non_camel_case_types)] #[repr(usize)] pub enum Rv32WeierstrassOpcode { - EC_ADD_NE, - SETUP_EC_ADD_NE, - EC_DOUBLE, - SETUP_EC_DOUBLE, + SW_ADD_NE, + SETUP_SW_ADD_NE, + SW_DOUBLE, + SETUP_SW_DOUBLE, +} + +#[derive( + Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, EnumCount, EnumIter, FromRepr, LocalOpcode, +)] +#[opcode_offset = 0x680] +#[allow(non_camel_case_types)] +#[repr(usize)] +pub enum Rv32EdwardsOpcode { + TE_ADD, + SETUP_TE_ADD, } #[derive(Default)] @@ -26,6 +37,67 @@ pub struct EccTranspilerExtension; impl TranspilerExtension for EccTranspilerExtension { fn process_custom(&self, instruction_stream: &[u32]) -> Option> { + self.process_weierstrass_instruction(instruction_stream) + .or(self.process_edwards_instruction(instruction_stream)) + } +} + +impl EccTranspilerExtension { + fn process_edwards_instruction( + &self, + instruction_stream: &[u32], + ) -> Option> { + if instruction_stream.is_empty() { + return None; + } + let instruction_u32 = instruction_stream[0]; + let opcode = (instruction_u32 & 0x7f) as u8; + let funct3 = ((instruction_u32 >> 12) & 0b111) as u8; + + if opcode != TE_OPCODE { + return None; + } + if funct3 != TE_FUNCT3 { + return None; + } + + let instruction = { + // twisted edwards ec + assert!(Rv32EdwardsOpcode::COUNT <= TeBaseFunct7::TWISTED_EDWARDS_MAX_KINDS as usize); + let dec_insn = RType::new(instruction_u32); + let base_funct7 = (dec_insn.funct7 as u8) % TeBaseFunct7::TWISTED_EDWARDS_MAX_KINDS; + let curve_idx = + ((dec_insn.funct7 as u8) / TeBaseFunct7::TWISTED_EDWARDS_MAX_KINDS) as usize; + let curve_idx_shift = curve_idx * Rv32EdwardsOpcode::COUNT; + + if base_funct7 == TeBaseFunct7::TeSetup as u8 { + let local_opcode = Rv32EdwardsOpcode::SETUP_TE_ADD; + Some(Instruction::new( + VmOpcode::from_usize(local_opcode.global_opcode().as_usize() + curve_idx_shift), + F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rd), + F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs1), + F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs2), + F::ONE, // d_as = 1 + F::TWO, // e_as = 2 + F::ZERO, + F::ZERO, + )) + } else { + let global_opcode = match TeBaseFunct7::from_repr(base_funct7) { + Some(TeBaseFunct7::TeAdd) => Rv32EdwardsOpcode::TE_ADD.global_opcode(), + _ => unimplemented!(), + }; + let global_opcode = global_opcode.as_usize() + curve_idx_shift; + Some(from_r_type(global_opcode, 2, &dec_insn, true)) + } + }; + instruction.map(TranspilerOutput::one_to_one) + } + + fn process_weierstrass_instruction( + &self, + instruction_stream: &[u32], + ) -> Option> { if instruction_stream.is_empty() { return None; } @@ -33,7 +105,7 @@ impl TranspilerExtension for EccTranspilerExtension { let opcode = (instruction_u32 & 0x7f) as u8; let funct3 = ((instruction_u32 >> 12) & 0b111) as u8; - if opcode != OPCODE { + if opcode != SW_OPCODE { return None; } if funct3 != SW_FUNCT3 { @@ -52,8 +124,8 @@ impl TranspilerExtension for EccTranspilerExtension { let curve_idx_shift = curve_idx * Rv32WeierstrassOpcode::COUNT; if base_funct7 == SwBaseFunct7::SwSetup as u8 { let local_opcode = match dec_insn.rs2 { - 0 => Rv32WeierstrassOpcode::SETUP_EC_DOUBLE, - _ => Rv32WeierstrassOpcode::SETUP_EC_ADD_NE, + 0 => Rv32WeierstrassOpcode::SETUP_SW_DOUBLE, + _ => Rv32WeierstrassOpcode::SETUP_SW_ADD_NE, }; Some(Instruction::new( VmOpcode::from_usize(local_opcode.global_opcode().as_usize() + curve_idx_shift), @@ -67,18 +139,14 @@ impl TranspilerExtension for EccTranspilerExtension { )) } else { let global_opcode = match SwBaseFunct7::from_repr(base_funct7) { - Some(SwBaseFunct7::SwAddNe) => { - Rv32WeierstrassOpcode::EC_ADD_NE as usize - + Rv32WeierstrassOpcode::CLASS_OFFSET - } + Some(SwBaseFunct7::SwAddNe) => Rv32WeierstrassOpcode::SW_ADD_NE.global_opcode(), Some(SwBaseFunct7::SwDouble) => { assert!(dec_insn.rs2 == 0); - Rv32WeierstrassOpcode::EC_DOUBLE as usize - + Rv32WeierstrassOpcode::CLASS_OFFSET + Rv32WeierstrassOpcode::SW_DOUBLE.global_opcode() } _ => unimplemented!(), }; - let global_opcode = global_opcode + curve_idx_shift; + let global_opcode = global_opcode.as_usize() + curve_idx_shift; Some(from_r_type(global_opcode, 2, &dec_insn, true)) } }; diff --git a/extensions/pairing/circuit/src/config.rs b/extensions/pairing/circuit/src/config.rs index 20ea07186a..3fe21a0437 100644 --- a/extensions/pairing/circuit/src/config.rs +++ b/extensions/pairing/circuit/src/config.rs @@ -12,7 +12,7 @@ use openvm_circuit::{ system::SystemChipInventory, }; use openvm_circuit_derive::VmConfig; -use openvm_ecc_circuit::{EccCpuProverExt, WeierstrassExtension, WeierstrassExtensionExecutor}; +use openvm_ecc_circuit::{EccCpuProverExt, EccExtension, EccExtensionExecutor}; use openvm_stark_backend::{ config::{StarkGenericConfig, Val}, engine::StarkEngine, @@ -30,7 +30,7 @@ pub struct Rv32PairingConfig { #[extension] pub fp2: Fp2Extension, #[extension] - pub weierstrass: WeierstrassExtension, + pub ecc: EccExtension, #[extension(generics = true)] pub pairing: PairingExtension, } @@ -51,9 +51,7 @@ impl Rv32PairingConfig { .zip(modulus_primes) .collect(), ), - weierstrass: WeierstrassExtension::new( - curves.iter().map(|c| c.curve_config()).collect(), - ), + ecc: EccExtension::new(curves.iter().map(|c| c.curve_config()).collect(), vec![]), pairing: PairingExtension::new(curves), } } @@ -65,7 +63,7 @@ impl InitFileGenerator for Rv32PairingConfig { "// This file is automatically generated by cargo openvm. Do not rename or edit.\n{}\n{}\n{}\n", self.modular.modular.generate_moduli_init(), self.fp2.generate_complex_init(&self.modular.modular), - self.weierstrass.generate_sw_init() + self.ecc.generate_ecc_init() )) } } @@ -95,11 +93,7 @@ where VmBuilder::::create_chip_complex(&Rv32ModularCpuBuilder, &config.modular, circuit)?; let inventory = &mut chip_complex.inventory; VmProverExtension::::extend_prover(&AlgebraCpuProverExt, &config.fp2, inventory)?; - VmProverExtension::::extend_prover( - &EccCpuProverExt, - &config.weierstrass, - inventory, - )?; + VmProverExtension::::extend_prover(&EccCpuProverExt, &config.ecc, inventory)?; VmProverExtension::::extend_prover(&PairingProverExt, &config.pairing, inventory)?; Ok(chip_complex) } diff --git a/extensions/pairing/circuit/src/pairing_extension.rs b/extensions/pairing/circuit/src/pairing_extension.rs index b81499948e..eacea93859 100644 --- a/extensions/pairing/circuit/src/pairing_extension.rs +++ b/extensions/pairing/circuit/src/pairing_extension.rs @@ -10,7 +10,7 @@ use openvm_circuit::{ system::phantom::PhantomExecutor, }; use openvm_circuit_derive::{AnyEnum, Executor, MeteredExecutor, PreflightExecutor}; -use openvm_ecc_circuit::CurveConfig; +use openvm_ecc_circuit::{CurveConfig, SwCurveCoeffs}; use openvm_instructions::PhantomDiscriminant; use openvm_pairing_guest::{ bls12_381::{ @@ -32,21 +32,25 @@ pub enum PairingCurve { } impl PairingCurve { - pub fn curve_config(&self) -> CurveConfig { + pub fn curve_config(&self) -> CurveConfig { match self { PairingCurve::Bn254 => CurveConfig::new( BN254_ECC_STRUCT_NAME.to_string(), BN254_MODULUS.clone(), BN254_ORDER.clone(), - BigUint::zero(), - BigUint::from_u8(3).unwrap(), + SwCurveCoeffs { + a: BigUint::zero(), + b: BigUint::from_u8(3).unwrap(), + }, ), PairingCurve::Bls12_381 => CurveConfig::new( BLS12_381_ECC_STRUCT_NAME.to_string(), BLS12_381_MODULUS.clone(), BLS12_381_ORDER.clone(), - BigUint::zero(), - BigUint::from_u8(4).unwrap(), + SwCurveCoeffs { + a: BigUint::zero(), + b: BigUint::from_u8(4).unwrap(), + }, ), } } diff --git a/guest-libs/k256/src/internal.rs b/guest-libs/k256/src/internal.rs index b8f8857dc9..868bce2cd5 100644 --- a/guest-libs/k256/src/internal.rs +++ b/guest-libs/k256/src/internal.rs @@ -4,8 +4,8 @@ use hex_literal::hex; use openvm_algebra_guest::IntMod; use openvm_algebra_moduli_macros::moduli_declare; use openvm_ecc_guest::{ - weierstrass::{CachedMulTable, IntrinsicCurve, WeierstrassPoint}, - CyclicGroup, Group, + weierstrass::{CachedMulTable, WeierstrassPoint}, + CyclicGroup, Group, IntrinsicCurve, }; use openvm_ecc_sw_macros::sw_declare; diff --git a/guest-libs/k256/src/point.rs b/guest-libs/k256/src/point.rs index b854ef582b..5e66303284 100644 --- a/guest-libs/k256/src/point.rs +++ b/guest-libs/k256/src/point.rs @@ -14,10 +14,7 @@ use elliptic_curve::{ FieldBytesEncoding, }; use openvm_algebra_guest::IntMod; -use openvm_ecc_guest::{ - weierstrass::{IntrinsicCurve, WeierstrassPoint}, - CyclicGroup, -}; +use openvm_ecc_guest::{weierstrass::WeierstrassPoint, CyclicGroup, IntrinsicCurve}; use crate::{ internal::{Secp256k1Coord, Secp256k1Point, Secp256k1Scalar}, @@ -181,7 +178,7 @@ impl MulByGenerator for Secp256k1Point {} impl DecompressPoint for Secp256k1Point { /// Note that this is not constant time fn decompress(x_bytes: &FieldBytes, y_is_odd: Choice) -> CtOption { - use openvm_ecc_guest::weierstrass::FromCompressed; + use openvm_ecc_guest::FromCompressed; let x = Secp256k1Coord::from_be_bytes_unchecked(x_bytes.as_slice()); let rec_id = y_is_odd.unwrap_u8(); diff --git a/guest-libs/k256/tests/lib.rs b/guest-libs/k256/tests/lib.rs index dfbbbc7e1b..b42ad6043b 100644 --- a/guest-libs/k256/tests/lib.rs +++ b/guest-libs/k256/tests/lib.rs @@ -6,9 +6,9 @@ mod guest_tests { arch::instructions::exe::VmExe, utils::{air_test, test_system_config_with_continuations}, }; - use openvm_ecc_circuit::{ - CurveConfig, Rv32WeierstrassConfig, Rv32WeierstrassCpuBuilder, SECP256K1_CONFIG, - }; + #[cfg(test)] + use openvm_ecc_circuit::SwCurveCoeffs; + use openvm_ecc_circuit::{CurveConfig, Rv32EccConfig, Rv32EccCpuBuilder, SECP256K1_CONFIG}; use openvm_ecc_transpiler::EccTranspilerExtension; use openvm_rv32im_transpiler::{ Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, @@ -23,15 +23,15 @@ mod guest_tests { type F = BabyBear; #[cfg(test)] - fn test_rv32weierstrass_config(curves: Vec) -> Rv32WeierstrassConfig { - let mut config = Rv32WeierstrassConfig::new(curves); + fn test_rv32ecc_config(sw_curves: Vec>) -> Rv32EccConfig { + let mut config = Rv32EccConfig::new(sw_curves, vec![]); *config.as_mut() = test_system_config_with_continuations(); config } #[test] fn test_add() -> Result<()> { - let config = test_rv32weierstrass_config(vec![SECP256K1_CONFIG.clone()]); + let config = test_rv32ecc_config(vec![SECP256K1_CONFIG.clone()]); let elf = build_example_program_at_path(get_programs_dir!("tests/programs"), "add", &config)?; let openvm_exe = VmExe::from_elf( @@ -43,13 +43,13 @@ mod guest_tests { .with_extension(EccTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - air_test(Rv32WeierstrassCpuBuilder, config, openvm_exe); + air_test(Rv32EccCpuBuilder, config, openvm_exe); Ok(()) } #[test] fn test_mul() -> Result<()> { - let config = test_rv32weierstrass_config(vec![SECP256K1_CONFIG.clone()]); + let config = test_rv32ecc_config(vec![SECP256K1_CONFIG.clone()]); let elf = build_example_program_at_path(get_programs_dir!("tests/programs"), "mul", &config)?; let openvm_exe = VmExe::from_elf( @@ -61,13 +61,13 @@ mod guest_tests { .with_extension(EccTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - air_test(Rv32WeierstrassCpuBuilder, config, openvm_exe); + air_test(Rv32EccCpuBuilder, config, openvm_exe); Ok(()) } #[test] fn test_linear_combination() -> Result<()> { - let config = test_rv32weierstrass_config(vec![SECP256K1_CONFIG.clone()]); + let config = test_rv32ecc_config(vec![SECP256K1_CONFIG.clone()]); let elf = build_example_program_at_path( get_programs_dir!("tests/programs"), "linear_combination", @@ -82,7 +82,7 @@ mod guest_tests { .with_extension(EccTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - air_test(Rv32WeierstrassCpuBuilder, config, openvm_exe); + air_test(Rv32EccCpuBuilder, config, openvm_exe); Ok(()) } @@ -97,8 +97,7 @@ mod guest_tests { system::SystemChipInventory, }; use openvm_ecc_circuit::{ - CurveConfig, Rv32WeierstrassConfig, Rv32WeierstrassConfigExecutor, - Rv32WeierstrassCpuBuilder, + CurveConfig, Rv32EccConfig, Rv32EccConfigExecutor, Rv32EccCpuBuilder, SwCurveCoeffs, }; use openvm_sha256_circuit::{Sha256, Sha256Executor, Sha2CpuProverExt}; use openvm_stark_backend::{ @@ -112,15 +111,15 @@ mod guest_tests { #[derive(Clone, Debug, VmConfig, Serialize, Deserialize)] pub struct EcdsaConfig { #[config(generics = true)] - pub weierstrass: Rv32WeierstrassConfig, + pub ecc: Rv32EccConfig, #[extension] pub sha256: Sha256, } impl EcdsaConfig { - pub fn new(curves: Vec) -> Self { + pub fn new(curves: Vec>) -> Self { Self { - weierstrass: Rv32WeierstrassConfig::new(curves), + ecc: Rv32EccConfig::new(curves, vec![]), sha256: Default::default(), } } @@ -130,8 +129,8 @@ mod guest_tests { fn generate_init_file_contents(&self) -> Option { Some(format!( "// This file is automatically generated by cargo openvm. Do not rename or edit.\n{}\n{}\n", - self.weierstrass.modular.modular.generate_moduli_init(), - self.weierstrass.weierstrass.generate_sw_init() + self.ecc.modular.modular.generate_moduli_init(), + self.ecc.ecc.generate_ecc_init() )) } } @@ -157,11 +156,8 @@ mod guest_tests { VmChipComplex, ChipInventoryError, > { - let mut chip_complex = VmBuilder::::create_chip_complex( - &Rv32WeierstrassCpuBuilder, - &config.weierstrass, - circuit, - )?; + let mut chip_complex = + VmBuilder::::create_chip_complex(&Rv32EccCpuBuilder, &config.ecc, circuit)?; let inventory = &mut chip_complex.inventory; VmProverExtension::::extend_prover( &Sha2CpuProverExt, @@ -195,7 +191,7 @@ mod guest_tests { #[test] fn test_scalar_sqrt() -> Result<()> { - let config = test_rv32weierstrass_config(vec![SECP256K1_CONFIG.clone()]); + let config = test_rv32ecc_config(vec![SECP256K1_CONFIG.clone()]); let elf = build_example_program_at_path( get_programs_dir!("tests/programs"), "scalar_sqrt", @@ -210,7 +206,7 @@ mod guest_tests { .with_extension(EccTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - air_test(Rv32WeierstrassCpuBuilder, config, openvm_exe); + air_test(Rv32EccCpuBuilder, config, openvm_exe); Ok(()) } } diff --git a/guest-libs/k256/tests/programs/openvm_init_add.rs b/guest-libs/k256/tests/programs/openvm_init_add.rs index dc6d4917dd..0905f21c53 100644 --- a/guest-libs/k256/tests/programs/openvm_init_add.rs +++ b/guest-libs/k256/tests/programs/openvm_init_add.rs @@ -1,3 +1,4 @@ // This file is automatically generated by cargo openvm. Do not rename or edit. openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } openvm_ecc_guest::sw_macros::sw_init! { "Secp256k1Point" } +openvm_ecc_guest::te_macros::te_init! {} diff --git a/guest-libs/k256/tests/programs/openvm_init_ecdsa.rs b/guest-libs/k256/tests/programs/openvm_init_ecdsa.rs index dc6d4917dd..0905f21c53 100644 --- a/guest-libs/k256/tests/programs/openvm_init_ecdsa.rs +++ b/guest-libs/k256/tests/programs/openvm_init_ecdsa.rs @@ -1,3 +1,4 @@ // This file is automatically generated by cargo openvm. Do not rename or edit. openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } openvm_ecc_guest::sw_macros::sw_init! { "Secp256k1Point" } +openvm_ecc_guest::te_macros::te_init! {} diff --git a/guest-libs/k256/tests/programs/openvm_init_linear_combination.rs b/guest-libs/k256/tests/programs/openvm_init_linear_combination.rs index dc6d4917dd..0905f21c53 100644 --- a/guest-libs/k256/tests/programs/openvm_init_linear_combination.rs +++ b/guest-libs/k256/tests/programs/openvm_init_linear_combination.rs @@ -1,3 +1,4 @@ // This file is automatically generated by cargo openvm. Do not rename or edit. openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } openvm_ecc_guest::sw_macros::sw_init! { "Secp256k1Point" } +openvm_ecc_guest::te_macros::te_init! {} diff --git a/guest-libs/k256/tests/programs/openvm_init_mul.rs b/guest-libs/k256/tests/programs/openvm_init_mul.rs index dc6d4917dd..0905f21c53 100644 --- a/guest-libs/k256/tests/programs/openvm_init_mul.rs +++ b/guest-libs/k256/tests/programs/openvm_init_mul.rs @@ -1,3 +1,4 @@ // This file is automatically generated by cargo openvm. Do not rename or edit. openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } openvm_ecc_guest::sw_macros::sw_init! { "Secp256k1Point" } +openvm_ecc_guest::te_macros::te_init! {} diff --git a/guest-libs/k256/tests/programs/openvm_init_scalar_sqrt.rs b/guest-libs/k256/tests/programs/openvm_init_scalar_sqrt.rs index dc6d4917dd..0905f21c53 100644 --- a/guest-libs/k256/tests/programs/openvm_init_scalar_sqrt.rs +++ b/guest-libs/k256/tests/programs/openvm_init_scalar_sqrt.rs @@ -1,3 +1,4 @@ // This file is automatically generated by cargo openvm. Do not rename or edit. openvm_algebra_guest::moduli_macros::moduli_init! { "115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337" } openvm_ecc_guest::sw_macros::sw_init! { "Secp256k1Point" } +openvm_ecc_guest::te_macros::te_init! {} diff --git a/guest-libs/p256/src/internal.rs b/guest-libs/p256/src/internal.rs index b98c401c8c..7db8f868c6 100644 --- a/guest-libs/p256/src/internal.rs +++ b/guest-libs/p256/src/internal.rs @@ -4,8 +4,8 @@ use hex_literal::hex; use openvm_algebra_guest::IntMod; use openvm_algebra_moduli_macros::moduli_declare; use openvm_ecc_guest::{ - weierstrass::{CachedMulTable, IntrinsicCurve, WeierstrassPoint}, - CyclicGroup, Group, + weierstrass::{CachedMulTable, WeierstrassPoint}, + CyclicGroup, Group, IntrinsicCurve, }; use openvm_ecc_sw_macros::sw_declare; diff --git a/guest-libs/p256/src/point.rs b/guest-libs/p256/src/point.rs index ee87396c74..3d4030d807 100644 --- a/guest-libs/p256/src/point.rs +++ b/guest-libs/p256/src/point.rs @@ -14,10 +14,7 @@ use elliptic_curve::{ FieldBytesEncoding, }; use openvm_algebra_guest::IntMod; -use openvm_ecc_guest::{ - weierstrass::{IntrinsicCurve, WeierstrassPoint}, - CyclicGroup, -}; +use openvm_ecc_guest::{weierstrass::WeierstrassPoint, CyclicGroup, IntrinsicCurve}; use crate::{ internal::{P256Coord, P256Point, P256Scalar}, @@ -177,7 +174,7 @@ impl MulByGenerator for P256Point {} impl DecompressPoint for P256Point { /// Note that this is not constant time fn decompress(x_bytes: &FieldBytes, y_is_odd: Choice) -> CtOption { - use openvm_ecc_guest::weierstrass::FromCompressed; + use openvm_ecc_guest::FromCompressed; let x = P256Coord::from_be_bytes_unchecked(x_bytes.as_slice()); let rec_id = y_is_odd.unwrap_u8(); diff --git a/guest-libs/p256/tests/lib.rs b/guest-libs/p256/tests/lib.rs index 39f2d0216b..b3583cca2f 100644 --- a/guest-libs/p256/tests/lib.rs +++ b/guest-libs/p256/tests/lib.rs @@ -6,9 +6,9 @@ mod guest_tests { arch::instructions::exe::VmExe, utils::{air_test, test_system_config_with_continuations}, }; - use openvm_ecc_circuit::{ - CurveConfig, Rv32WeierstrassConfig, Rv32WeierstrassCpuBuilder, P256_CONFIG, - }; + #[cfg(test)] + use openvm_ecc_circuit::SwCurveCoeffs; + use openvm_ecc_circuit::{CurveConfig, Rv32EccConfig, Rv32EccCpuBuilder, P256_CONFIG}; use openvm_ecc_transpiler::EccTranspilerExtension; use openvm_rv32im_transpiler::{ Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, @@ -23,15 +23,15 @@ mod guest_tests { type F = BabyBear; #[cfg(test)] - fn test_rv32weierstrass_config(curves: Vec) -> Rv32WeierstrassConfig { - let mut config = Rv32WeierstrassConfig::new(curves); + fn test_rv32ecc_config(sw_curves: Vec>) -> Rv32EccConfig { + let mut config = Rv32EccConfig::new(sw_curves, vec![]); *config.as_mut() = test_system_config_with_continuations(); config } #[test] fn test_add() -> Result<()> { - let config = test_rv32weierstrass_config(vec![P256_CONFIG.clone()]); + let config = test_rv32ecc_config(vec![P256_CONFIG.clone()]); let elf = build_example_program_at_path(get_programs_dir!("tests/programs"), "add", &config)?; let openvm_exe = VmExe::from_elf( @@ -43,13 +43,13 @@ mod guest_tests { .with_extension(EccTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - air_test(Rv32WeierstrassCpuBuilder, config, openvm_exe); + air_test(Rv32EccCpuBuilder, config, openvm_exe); Ok(()) } #[test] fn test_mul() -> Result<()> { - let config = test_rv32weierstrass_config(vec![P256_CONFIG.clone()]); + let config = test_rv32ecc_config(vec![P256_CONFIG.clone()]); let elf = build_example_program_at_path(get_programs_dir!("tests/programs"), "mul", &config)?; let openvm_exe = VmExe::from_elf( @@ -61,13 +61,13 @@ mod guest_tests { .with_extension(EccTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - air_test(Rv32WeierstrassCpuBuilder, config, openvm_exe); + air_test(Rv32EccCpuBuilder, config, openvm_exe); Ok(()) } #[test] fn test_linear_combination() -> Result<()> { - let config = test_rv32weierstrass_config(vec![P256_CONFIG.clone()]); + let config = test_rv32ecc_config(vec![P256_CONFIG.clone()]); let elf = build_example_program_at_path( get_programs_dir!("tests/programs"), "linear_combination", @@ -82,7 +82,7 @@ mod guest_tests { .with_extension(EccTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - air_test(Rv32WeierstrassCpuBuilder, config, openvm_exe); + air_test(Rv32EccCpuBuilder, config, openvm_exe); Ok(()) } @@ -97,8 +97,7 @@ mod guest_tests { system::SystemChipInventory, }; use openvm_ecc_circuit::{ - CurveConfig, Rv32WeierstrassConfig, Rv32WeierstrassConfigExecutor, - Rv32WeierstrassCpuBuilder, + CurveConfig, Rv32EccConfig, Rv32EccConfigExecutor, Rv32EccCpuBuilder, SwCurveCoeffs, }; use openvm_sha256_circuit::{Sha256, Sha256Executor, Sha2CpuProverExt}; use openvm_stark_backend::{ @@ -112,15 +111,15 @@ mod guest_tests { #[derive(Clone, Debug, VmConfig, Serialize, Deserialize)] pub struct EcdsaConfig { #[config(generics = true)] - pub weierstrass: Rv32WeierstrassConfig, + pub ecc: Rv32EccConfig, #[extension] pub sha256: Sha256, } impl EcdsaConfig { - pub fn new(curves: Vec) -> Self { + pub fn new(curves: Vec>) -> Self { Self { - weierstrass: Rv32WeierstrassConfig::new(curves), + ecc: Rv32EccConfig::new(curves, vec![]), sha256: Default::default(), } } @@ -130,8 +129,8 @@ mod guest_tests { fn generate_init_file_contents(&self) -> Option { Some(format!( "// This file is automatically generated by cargo openvm. Do not rename or edit.\n{}\n{}\n", - self.weierstrass.modular.modular.generate_moduli_init(), - self.weierstrass.weierstrass.generate_sw_init() + self.ecc.modular.modular.generate_moduli_init(), + self.ecc.ecc.generate_ecc_init() )) } } @@ -157,11 +156,8 @@ mod guest_tests { VmChipComplex, ChipInventoryError, > { - let mut chip_complex = VmBuilder::::create_chip_complex( - &Rv32WeierstrassCpuBuilder, - &config.weierstrass, - circuit, - )?; + let mut chip_complex = + VmBuilder::::create_chip_complex(&Rv32EccCpuBuilder, &config.ecc, circuit)?; let inventory = &mut chip_complex.inventory; VmProverExtension::::extend_prover( &Sha2CpuProverExt, @@ -195,7 +191,7 @@ mod guest_tests { #[test] fn test_scalar_sqrt() -> Result<()> { - let config = test_rv32weierstrass_config(vec![P256_CONFIG.clone()]); + let config = test_rv32ecc_config(vec![P256_CONFIG.clone()]); let elf = build_example_program_at_path( get_programs_dir!("tests/programs"), "scalar_sqrt", @@ -210,7 +206,7 @@ mod guest_tests { .with_extension(EccTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - air_test(Rv32WeierstrassCpuBuilder, config, openvm_exe); + air_test(Rv32EccCpuBuilder, config, openvm_exe); Ok(()) } } diff --git a/guest-libs/pairing/src/bls12_381/mod.rs b/guest-libs/pairing/src/bls12_381/mod.rs index 0a7c150e1c..d3557ba61a 100644 --- a/guest-libs/pairing/src/bls12_381/mod.rs +++ b/guest-libs/pairing/src/bls12_381/mod.rs @@ -4,7 +4,7 @@ use core::ops::Neg; use openvm_algebra_guest::IntMod; use openvm_algebra_moduli_macros::moduli_declare; -use openvm_ecc_guest::{weierstrass::IntrinsicCurve, CyclicGroup, Group}; +use openvm_ecc_guest::{CyclicGroup, Group, IntrinsicCurve}; mod fp12; mod fp2; diff --git a/guest-libs/pairing/src/bn254/mod.rs b/guest-libs/pairing/src/bn254/mod.rs index 8384b8b3e8..a8d3f99f68 100644 --- a/guest-libs/pairing/src/bn254/mod.rs +++ b/guest-libs/pairing/src/bn254/mod.rs @@ -5,10 +5,7 @@ use core::ops::{Add, Neg}; use hex_literal::hex; use openvm_algebra_guest::IntMod; use openvm_algebra_moduli_macros::moduli_declare; -use openvm_ecc_guest::{ - weierstrass::{CachedMulTable, IntrinsicCurve}, - CyclicGroup, Group, -}; +use openvm_ecc_guest::{weierstrass::CachedMulTable, CyclicGroup, Group, IntrinsicCurve}; use openvm_ecc_sw_macros::sw_declare; use openvm_pairing_guest::pairing::PairingIntrinsics; diff --git a/guest-libs/pairing/tests/lib.rs b/guest-libs/pairing/tests/lib.rs index 7e5c6d81ab..0981a282b6 100644 --- a/guest-libs/pairing/tests/lib.rs +++ b/guest-libs/pairing/tests/lib.rs @@ -15,7 +15,7 @@ mod bn254 { air_test, air_test_impl, air_test_with_min_segments, test_system_config_with_continuations, }; use openvm_ecc_circuit::{ - CurveConfig, Rv32WeierstrassConfig, Rv32WeierstrassCpuBuilder, WeierstrassExtension, + CurveConfig, EccExtension, Rv32EccConfig, Rv32EccCpuBuilder, SwCurveCoeffs, }; use openvm_ecc_guest::{ algebra::{field::FieldExtension, IntMod}, @@ -57,14 +57,16 @@ mod bn254 { Rv32PairingConfig { modular: Rv32ModularConfig::new(primes.to_vec()), fp2: Fp2Extension::new(primes_with_names), - weierstrass: WeierstrassExtension::new(vec![]), + ecc: EccExtension::new(vec![], vec![]), pairing: PairingExtension::new(vec![PairingCurve::Bn254]), } } #[cfg(test)] - fn test_rv32weierstrass_config(curves: Vec) -> Rv32WeierstrassConfig { - let mut config = Rv32WeierstrassConfig::new(curves); + fn test_rv32ecc_config(sw_curves: Vec>) -> Rv32EccConfig { + use openvm_ecc_circuit::Rv32EccConfig; + + let mut config = Rv32EccConfig::new(sw_curves, vec![]); *config.as_mut() = test_system_config_with_continuations(); config } @@ -72,7 +74,7 @@ mod bn254 { #[test] fn test_bn_ec() -> Result<()> { let curve = PairingCurve::Bn254.curve_config(); - let config = test_rv32weierstrass_config(vec![curve]); + let config = test_rv32ecc_config(vec![curve]); let elf = build_example_program_at_path_with_features( get_programs_dir!("tests/programs"), "bn_ec", @@ -88,7 +90,7 @@ mod bn254 { .with_extension(EccTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - air_test(Rv32WeierstrassCpuBuilder, config, openvm_exe); + air_test(Rv32EccCpuBuilder, config, openvm_exe); Ok(()) } @@ -483,9 +485,7 @@ mod bls12_381 { test_system_config_with_continuations, }, }; - use openvm_ecc_circuit::{ - CurveConfig, Rv32WeierstrassConfig, Rv32WeierstrassCpuBuilder, WeierstrassExtension, - }; + use openvm_ecc_circuit::{CurveConfig, Rv32EccConfig, Rv32EccCpuBuilder, SwCurveCoeffs}; use openvm_ecc_guest::{ algebra::{field::FieldExtension, IntMod}, AffinePoint, @@ -519,6 +519,8 @@ mod bls12_381 { #[cfg(test)] pub fn get_testing_config() -> Rv32PairingConfig { + use openvm_ecc_circuit::EccExtension; + let primes = [BLS12_381_MODULUS.clone()]; let complex_struct_names = [BLS12_381_COMPLEX_STRUCT_NAME.to_string()]; let primes_with_names = complex_struct_names @@ -528,14 +530,14 @@ mod bls12_381 { Rv32PairingConfig { modular: Rv32ModularConfig::new(primes.to_vec()), fp2: Fp2Extension::new(primes_with_names), - weierstrass: WeierstrassExtension::new(vec![]), + ecc: EccExtension::new(vec![], vec![]), pairing: PairingExtension::new(vec![PairingCurve::Bls12_381]), } } #[cfg(test)] - fn test_rv32weierstrass_config(curves: Vec) -> Rv32WeierstrassConfig { - let mut config = Rv32WeierstrassConfig::new(curves); + fn test_rv32ecc_config(sw_curves: Vec>) -> Rv32EccConfig { + let mut config = Rv32EccConfig::new(sw_curves, vec![]); *config.as_mut() = test_system_config_with_continuations(); config } @@ -546,10 +548,12 @@ mod bls12_381 { struct_name: BLS12_381_ECC_STRUCT_NAME.to_string(), modulus: BLS12_381_MODULUS.clone(), scalar: BLS12_381_ORDER.clone(), - a: BigUint::ZERO, - b: BigUint::from_u8(4).unwrap(), + coeffs: SwCurveCoeffs { + a: BigUint::ZERO, + b: BigUint::from_u8(4).unwrap(), + }, }; - let config = test_rv32weierstrass_config(vec![curve]); + let config = test_rv32ecc_config(vec![curve]); let elf = build_example_program_at_path_with_features( get_programs_dir!("tests/programs"), "bls_ec", @@ -565,7 +569,7 @@ mod bls12_381 { .with_extension(EccTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - air_test(Rv32WeierstrassCpuBuilder, config, openvm_exe); + air_test(Rv32EccCpuBuilder, config, openvm_exe); Ok(()) }