From dac630d9d4c456a3a1c1028c19c25e0fecf18f28 Mon Sep 17 00:00:00 2001 From: lightsing Date: Tue, 30 Sep 2025 14:04:25 +0800 Subject: [PATCH 01/15] add secp256k1 ecrecover precompile --- Cargo.lock | 5 + ceno_host/tests/test_elf.rs | 12 ++ ceno_rt/Cargo.toml | 1 + ceno_rt/src/lib.rs | 17 +++ ceno_rt/src/syscalls.rs | 7 +- examples-builder/build.rs | 1 + examples/.cargo/config.toml | 2 + examples/Cargo.toml | 2 + examples/examples/secp256k1_add_syscall.rs | 4 +- examples/examples/secp256k1_ecrecover.rs | 56 +++++++ examples/examples/syscalls.rs | 4 +- guest_libs/crypto/Cargo.toml | 2 + guest_libs/crypto/src/lib.rs | 4 +- guest_libs/crypto/src/secp256k1.rs | 169 ++++++++++++++++++++- 14 files changed, 274 insertions(+), 12 deletions(-) create mode 100644 examples/examples/secp256k1_ecrecover.rs diff --git a/Cargo.lock b/Cargo.lock index c0b55ae35..a02963e26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -906,7 +906,9 @@ version = "0.1.0" dependencies = [ "alloy-consensus", "alloy-primitives", + "ceno_keccak", "ceno_rt", + "k256", "revm-precompile", "thiserror 2.0.12", ] @@ -959,6 +961,7 @@ name = "ceno_rt" version = "0.1.0" dependencies = [ "getrandom 0.2.16", + "getrandom 0.3.2", "rkyv", ] @@ -1776,8 +1779,10 @@ name = "examples" version = "0.1.0" dependencies = [ "alloy-primitives", + "ceno_crypto", "ceno_keccak", "ceno_rt", + "getrandom 0.3.2", "rand 0.8.5", "rkyv", "substrate-bn", diff --git a/ceno_host/tests/test_elf.rs b/ceno_host/tests/test_elf.rs index e7a6c3bda..35dadf6a6 100644 --- a/ceno_host/tests/test_elf.rs +++ b/ceno_host/tests/test_elf.rs @@ -438,6 +438,18 @@ fn test_secp256k1_decompress() -> Result<()> { Ok(()) } +#[test] +fn test_secp256k1_ecrecover() -> Result<()> { + let _ = ceno_host::run( + CENO_PLATFORM, + ceno_examples::secp256k1_ecrecover, + &CenoStdin::default(), + None, + ); + + Ok(()) +} + #[test] fn test_sha256_extend() -> Result<()> { let program_elf = ceno_examples::sha_extend_syscall; diff --git a/ceno_rt/Cargo.toml b/ceno_rt/Cargo.toml index fd47edc50..7e2a4e39a 100644 --- a/ceno_rt/Cargo.toml +++ b/ceno_rt/Cargo.toml @@ -11,4 +11,5 @@ version = "0.1.0" [dependencies] getrandom = { version = "0.2.15", features = ["custom"], default-features = false } +getrandom_v3 = { package = "getrandom", version = "0.3", default-features = false } rkyv.workspace = true diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index 1ced41d48..cc0deb217 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -86,6 +86,23 @@ pub fn my_get_random(buf: &mut [u8]) -> Result<(), Error> { } register_custom_getrandom!(my_get_random); +/// Custom getrandom implementation for getrandom v0.3 +/// +/// see also: +/// +/// # Safety +/// - `dest` must be valid for writes of `len` bytes. +#[unsafe(no_mangle)] +pub unsafe extern "Rust" fn __getrandom_v03_custom( + dest: *mut u8, + len: usize, +) -> Result<(), getrandom_v3::Error> { + unsafe { + sys_rand(dest, len); + } + Ok(()) +} + pub fn halt(exit_code: u32) -> ! { #[cfg(target_arch = "riscv32")] unsafe { diff --git a/ceno_rt/src/syscalls.rs b/ceno_rt/src/syscalls.rs index 743b8a77d..914fab8b7 100644 --- a/ceno_rt/src/syscalls.rs +++ b/ceno_rt/src/syscalls.rs @@ -54,9 +54,11 @@ pub fn syscall_keccak_permute(state: &mut [u64; KECCAK_STATE_WORDS]) { /// - The caller must ensure that `p` and `q` are valid points on the `secp256k1` curve, and that `p` and `q` are not equal to each other. /// - The result is stored in the first point. #[allow(unused_variables)] -pub fn syscall_secp256k1_add(p: *mut [u32; 16], q: *mut [u32; 16]) { +pub fn syscall_secp256k1_add(p: &mut [u32; 16], q: &[u32; 16]) { #[cfg(target_os = "zkvm")] unsafe { + let p = p.as_mut_ptr(); + let q = q.as_ptr(); asm!( "ecall", in("t0") SECP256K1_ADD, @@ -79,9 +81,10 @@ pub fn syscall_secp256k1_add(p: *mut [u32; 16], q: *mut [u32; 16]) { /// For example, the word `p[0]` contains the least significant `4` bytes of `X` and their significance is maintained w.r.t `p[0]` /// - The result is stored in p #[allow(unused_variables)] -pub fn syscall_secp256k1_double(p: *mut [u32; 16]) { +pub fn syscall_secp256k1_double(p: &mut [u32; 16]) { #[cfg(target_os = "zkvm")] unsafe { + let p = p.as_mut_ptr(); asm!( "ecall", in("t0") SECP256K1_DOUBLE, diff --git a/examples-builder/build.rs b/examples-builder/build.rs index 5d566ae65..754efd0f2 100644 --- a/examples-builder/build.rs +++ b/examples-builder/build.rs @@ -57,6 +57,7 @@ fn build_elfs() { } rerun_all_but_target(Path::new("../examples")); rerun_all_but_target(Path::new("../ceno_rt")); + rerun_all_but_target(Path::new("../guest_libs")); } fn main() { diff --git a/examples/.cargo/config.toml b/examples/.cargo/config.toml index 0b79e4d7a..f831dd7d5 100644 --- a/examples/.cargo/config.toml +++ b/examples/.cargo/config.toml @@ -25,5 +25,7 @@ rustflags = [ "-Zlocation-detail=none", "-C", "passes=lower-atomic", + '--cfg', + 'getrandom_backend="custom"', # getrandom v3.3+ requires this cfg to use a custom getrandom implementation ] target = "../ceno_rt/riscv32im-ceno-zkvm-elf.json" diff --git a/examples/Cargo.toml b/examples/Cargo.toml index d9947337b..6a6bfbec0 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -11,8 +11,10 @@ version = "0.1.0" [dependencies] alloy-primitives = { version = "1.3", features = ["native-keccak"] } +ceno_crypto = { path = "../guest_libs/crypto" } ceno_keccak = { path = "../guest_libs/keccak" } ceno_rt = { path = "../ceno_rt" } +getrandom = { version = "0.3" } rand.workspace = true tiny-keccak.workspace = true diff --git a/examples/examples/secp256k1_add_syscall.rs b/examples/examples/secp256k1_add_syscall.rs index 07bb054a1..5c66cd3fa 100644 --- a/examples/examples/secp256k1_add_syscall.rs +++ b/examples/examples/secp256k1_add_syscall.rs @@ -41,9 +41,9 @@ fn bytes_to_words(bytes: [u8; 65]) -> [u32; 16] { } fn main() { let mut p: DecompressedPoint = bytes_to_words(P); - let mut q: DecompressedPoint = bytes_to_words(Q); + let q: DecompressedPoint = bytes_to_words(Q); let p_plus_q: DecompressedPoint = bytes_to_words(P_PLUS_Q); - syscall_secp256k1_add(&mut p, &mut q); + syscall_secp256k1_add(&mut p, &q); assert_eq!(p, p_plus_q); } diff --git a/examples/examples/secp256k1_ecrecover.rs b/examples/examples/secp256k1_ecrecover.rs new file mode 100644 index 000000000..5812a88f8 --- /dev/null +++ b/examples/examples/secp256k1_ecrecover.rs @@ -0,0 +1,56 @@ +// Test ecrecover of real world signatures from scroll mainnet. Assert result inside the guest. +extern crate ceno_rt; + +use alloy_primitives::{Address, B256, address, b256, hex}; +use ceno_crypto::secp256k1::secp256k1_ecrecover; + +const TEST_CASES: [(&[u8], u8, B256, Address); 5] = [ + // (sig, recid, tx_hash, signer) + ( + &hex!( + "15a7bb615483f66a697431cd414294b6bd1e1b9b9d6d163cfd97290ea77b53061810c4d228e424087ad77ee75bb25e77c832ad9038b89f7e573a34b574648348" + ), + 0, + b256!("b329f831352e37f4426583986465b065d9c867901b42f576f00ef36dfac1cfdf"), + address!("ca585e09df67e83106c9bcd839c989ace537bf95"), + ), + ( + &hex!( + "870077f742ca34760810033caf13c99e90e207db6f820124b827907e9658d7d04f302d6675c8625c02fc95c131a3ce77e7f90dba10dbda368efeaaba9be60916" + ), + 0, + b256!("4e13990772a9454712c7560ad8a64b845fd472b913b90d680867ab3dad56a18d"), + address!("a79c12bcf11133af01b6b20f16f8aafaecdebc93"), + ), + ( + &hex!( + "455a6249244154e8f5d516a3036e26576449bef05171657dbf3a5d7b9c02fe96629f7eb0aa2a006ff4ac6fc0523a6f5a365cf375240f5a560b1972eb21cec087" + ), + 1, + b256!("4dedbd995fc79db979c6484132568fe30fdf6bfa8b64ac74ba844cc30e764b0c"), + address!("c623f214c8eefc771147c5806be250db39555555"), + ), + ( + &hex!( + "854c4656c421158b4e5d8c29ccc3adcaee329587cee630398f3ce2e32745e45b67b1fc40e3206c70a75bcdf3c877c26874c75c2fabd5566c85b58c7c7d872e00" + ), + 0, + b256!("e4559e37c72fb3df0349df42b3aa0e94607287ecb3e6530b7c50ed984e0428a2"), + address!("b82def35c814584d3d929cfb3a1fb1b886b6e57b"), + ), + ( + &hex!( + "004a0ac1306d096c06fb77f82b76f43fb2459638826f4846444686b3036b9a4b3d6bf124bf22f23b851adfa2c4bdc670b4ecb5129186a4e89032916a77a56b90" + ), + 0, + b256!("83e5e11daa2d14736ab1d578c41250c6f6445782c215684a18f67b44686ccb90"), + address!("0a6f0ed4896be1caa9e37047578e7519481f22ea"), + ), +]; + +fn main() { + for (sig, recid, tx_hash, signer) in TEST_CASES { + let recovered = secp256k1_ecrecover(sig.try_into().unwrap(), recid, &tx_hash.0).unwrap(); + assert_eq!(&recovered[12..], &signer.0); + } +} diff --git a/examples/examples/syscalls.rs b/examples/examples/syscalls.rs index d5e6a486c..9318a863b 100644 --- a/examples/examples/syscalls.rs +++ b/examples/examples/syscalls.rs @@ -51,9 +51,9 @@ pub fn test_syscalls() { ]; { let mut p = bytes_to_words(P); - let mut q = bytes_to_words(Q); + let q = bytes_to_words(Q); let p_plus_q = bytes_to_words(P_PLUS_Q); - syscall_secp256k1_add(&mut p, &mut q); + syscall_secp256k1_add(&mut p, &q); assert!(p == p_plus_q); } diff --git a/guest_libs/crypto/Cargo.toml b/guest_libs/crypto/Cargo.toml index 54ef0295d..1376ab07b 100644 --- a/guest_libs/crypto/Cargo.toml +++ b/guest_libs/crypto/Cargo.toml @@ -10,7 +10,9 @@ repository = "https://github.com/scroll-tech/ceno" version = "0.1.0" [dependencies] +ceno_keccak = { path = "../keccak" } ceno_rt = { path = "../../ceno_rt" } +k256 = { version = "0.13", default-features = false, features = ["std", "ecdsa"] } thiserror.workspace = true [dev-dependencies] diff --git a/guest_libs/crypto/src/lib.rs b/guest_libs/crypto/src/lib.rs index 0d52933aa..b160d5882 100644 --- a/guest_libs/crypto/src/lib.rs +++ b/guest_libs/crypto/src/lib.rs @@ -33,8 +33,8 @@ pub enum CenoCryptoError { #[error("Bn254 pair length error")] Bn254PairLength, /// Sepk256k1 ecrecover error - #[error("Secp256k1 ecrecover error")] - Secp256k1EcRecover, + #[error("Secp256k1 signature error")] + Secp256k1Signature(#[from] k256::ecdsa::Error), } #[cfg(test)] diff --git a/guest_libs/crypto/src/secp256k1.rs b/guest_libs/crypto/src/secp256k1.rs index a4ca088c6..86301fad3 100644 --- a/guest_libs/crypto/src/secp256k1.rs +++ b/guest_libs/crypto/src/secp256k1.rs @@ -1,11 +1,172 @@ use crate::CenoCryptoError; +use ceno_keccak::{Hasher, Keccak}; +use ceno_rt::syscalls::{syscall_secp256k1_add, syscall_secp256k1_double}; +use k256::{ + AffinePoint, EncodedPoint, Scalar, Secp256k1, U256, + ecdsa::{Error, RecoveryId, Signature, hazmat::bits2field}, + elliptic_curve::{ + Curve, Field, FieldBytesEncoding, PrimeField, + bigint::CheckedAdd, + ops::{Invert, Reduce}, + sec1::{FromEncodedPoint, ToEncodedPoint}, + }, +}; + +type UntaggedUncompressedPoint = [u8; 64]; + +#[repr(align(4))] +struct Aligned64([u8; 64]); /// secp256k1 ECDSA signature recovery. #[inline] pub fn secp256k1_ecrecover( - _sig: &[u8; 64], - _recid: u8, - _msg: &[u8; 32], + sig: &[u8; 64], + recid: u8, + msg: &[u8; 32], ) -> Result<[u8; 32], CenoCryptoError> { - unimplemented!() + // Copied from + let mut signature = Signature::from_slice(sig)?; + let mut recid = recid; + + // normalize signature and flip recovery id if needed. + if let Some(sig_normalized) = signature.normalize_s() { + signature = sig_normalized; + recid ^= 1; + } + let recid = RecoveryId::from_byte(recid).expect("recovery ID is valid"); + + // recover key + let recovered_key = recover_from_prehash_unchecked(&msg[..], &signature, recid)?; + + let mut hasher = Keccak::v256(); + hasher.update(&recovered_key); + let mut hash = [0u8; 32]; + hasher.finalize(&mut hash); + // truncate to 20 bytes + hash[..12].fill(0); + Ok(hash) +} + +/// Copied from +/// Modified to use ceno syscalls +fn recover_from_prehash_unchecked( + prehash: &[u8], + signature: &Signature, + recovery_id: RecoveryId, +) -> k256::ecdsa::signature::Result { + let (r, s) = signature.split_scalars(); + let z = >::reduce_bytes(&bits2field::(prehash)?); + + let mut r_bytes = r.to_repr(); + if recovery_id.is_x_reduced() { + let decoded: U256 = FieldBytesEncoding::::decode_field_bytes(&r_bytes); + match decoded.checked_add(&Secp256k1::ORDER).into_option() { + Some(restored) => { + r_bytes = >::encode_field_bytes(&restored) + } + // No reduction should happen here if r was reduced + None => return Err(Error::new()), + }; + } + + // Modified part: use ceno syscall to decompress point + // Original: + // let R = AffinePoint::decompress(&r_bytes, u8::from(recovery_id.is_y_odd()).into()); + let r_point = { + let mut buf = Aligned64([0u8; 64]); + buf.0[..32].copy_from_slice(&r_bytes); + + // SAFETY: + // [x] The input array should be 64 bytes long, with the first 32 bytes containing the X coordinate in + // big-endian format. + // [x] The caller must ensure that `point` is valid pointer to data that is aligned along a four byte + // boundary. + ceno_rt::syscalls::syscall_secp256k1_decompress(&mut buf.0, recovery_id.is_y_odd()); + let point = EncodedPoint::from_untagged_bytes((&buf.0).into()); + AffinePoint::from_encoded_point(&point) + }; + + let Some(r_point) = r_point.into_option() else { + return Err(Error::new()); + }; + + // TODO: scalar syscalls + let r_inv = *r.invert(); + let u1 = -(r_inv * z); + let u2 = r_inv * *s; + + // Original: + // ProjectivePoint::lincomb(&ProjectivePoint::GENERATOR, &u1, &r_point, &u2) + // Equivalent to: G * u1 + R * u2 + let pk_words = match (u1 == Scalar::ZERO, u2 == Scalar::ZERO) { + (true, true) => return Err(Error::new()), + (true, false) => secp256k1_mul(&r_point, u2), + (false, true) => secp256k1_mul(&AffinePoint::GENERATOR, u1), + (false, false) => { + let mut p1 = secp256k1_mul(&AffinePoint::GENERATOR, u1); + let p2 = secp256k1_mul(&r_point, u2); + syscall_secp256k1_add(&mut p1, &p2); + p1 + } + }; + + // FIXME: do we really need to verify the signature again here? + // Original: + // let vk = VerifyingKey::from_affine(pk.to_affine())?; + // // Ensure signature verifies with the recovered key + // vk.verify_prehash(prehash, signature)?; + + Ok(words_to_untagged_bytes(pk_words)) +} + +fn secp256k1_mul(point: &AffinePoint, scalar: Scalar) -> [u32; 16] { + let mut base = point_to_words(point.to_encoded_point(false)); + let mut acc: [u32; 16] = [0; 16]; + let mut acc_init = false; + + let mut k = scalar; + while !k.is_zero_vartime() { + if bool::from(k.is_odd()) { + if !acc_init { + acc = base; + acc_init = true; + } else { + // SAFETY: syscall requires point not to be infinity + let tmp = base; + syscall_secp256k1_add(&mut acc, &tmp) + } + } + syscall_secp256k1_double(&mut base); + k = k.shr_vartime(1); + } + + acc +} + +/// `bytes` is expected to contain the uncompressed representation of +/// a curve point, as described in https://docs.rs/secp/latest/secp/struct.Point.html +/// +/// The return value is an array of words compatible with the sp1 syscall for `add` and `double` +/// Notably, these words should encode the X and Y coordinates of the point +/// in "little endian" and not "big endian" as is the case of secp +fn point_to_words(point: EncodedPoint) -> [u32; 16] { + debug_assert!(!point.is_compressed()); + // ignore the tag byte (specific to the secp repr.) + let mut bytes: [u8; 64] = point.as_bytes()[1..].try_into().unwrap(); + + // Reverse the order of bytes for each coordinate + bytes[0..32].reverse(); + bytes[32..].reverse(); + std::array::from_fn(|i| u32::from_le_bytes(bytes[4 * i..4 * (i + 1)].try_into().unwrap())) +} + +fn words_to_untagged_bytes(words: [u32; 16]) -> UntaggedUncompressedPoint { + let mut bytes = [0u8; 64]; + for i in 0..16 { + bytes[4 * i..4 * (i + 1)].copy_from_slice(&words[i].to_le_bytes()); + } + // Reverse the order of bytes for each coordinate + bytes[..32].reverse(); + bytes[32..].reverse(); + bytes } From 02c0219b902fc6ca06a3b5471b5c5729405b3220 Mon Sep 17 00:00:00 2001 From: lightsing Date: Tue, 30 Sep 2025 14:06:13 +0800 Subject: [PATCH 02/15] rename --- guest_libs/crypto/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guest_libs/crypto/src/lib.rs b/guest_libs/crypto/src/lib.rs index b160d5882..004e2542c 100644 --- a/guest_libs/crypto/src/lib.rs +++ b/guest_libs/crypto/src/lib.rs @@ -33,8 +33,8 @@ pub enum CenoCryptoError { #[error("Bn254 pair length error")] Bn254PairLength, /// Sepk256k1 ecrecover error - #[error("Secp256k1 signature error")] - Secp256k1Signature(#[from] k256::ecdsa::Error), + #[error("Secp256k1 ecrecover error")] + Secp256k1Ecrecover(#[from] k256::ecdsa::Error), } #[cfg(test)] From 20b947b6e3ee746c5ba88b7718dbd0a680f420a4 Mon Sep 17 00:00:00 2001 From: lightsing Date: Tue, 30 Sep 2025 16:15:01 +0800 Subject: [PATCH 03/15] add verify --- guest_libs/crypto/src/secp256k1.rs | 75 +++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/guest_libs/crypto/src/secp256k1.rs b/guest_libs/crypto/src/secp256k1.rs index 86301fad3..ce1961dd1 100644 --- a/guest_libs/crypto/src/secp256k1.rs +++ b/guest_libs/crypto/src/secp256k1.rs @@ -2,7 +2,7 @@ use crate::CenoCryptoError; use ceno_keccak::{Hasher, Keccak}; use ceno_rt::syscalls::{syscall_secp256k1_add, syscall_secp256k1_double}; use k256::{ - AffinePoint, EncodedPoint, Scalar, Secp256k1, U256, + AffinePoint, EncodedPoint, FieldBytes, NonZeroScalar, Scalar, Secp256k1, U256, ecdsa::{Error, RecoveryId, Signature, hazmat::bits2field}, elliptic_curve::{ Curve, Field, FieldBytesEncoding, PrimeField, @@ -36,7 +36,7 @@ pub fn secp256k1_ecrecover( let recid = RecoveryId::from_byte(recid).expect("recovery ID is valid"); // recover key - let recovered_key = recover_from_prehash_unchecked(&msg[..], &signature, recid)?; + let recovered_key = recover_from_prehash(&msg[..], &signature, recid)?; let mut hasher = Keccak::v256(); hasher.update(&recovered_key); @@ -49,13 +49,14 @@ pub fn secp256k1_ecrecover( /// Copied from /// Modified to use ceno syscalls -fn recover_from_prehash_unchecked( +fn recover_from_prehash( prehash: &[u8], signature: &Signature, recovery_id: RecoveryId, -) -> k256::ecdsa::signature::Result { +) -> Result { let (r, s) = signature.split_scalars(); - let z = >::reduce_bytes(&bits2field::(prehash)?); + let prehash: FieldBytes = bits2field::(prehash)?; + let z = >::reduce_bytes(&prehash); let mut r_bytes = r.to_repr(); if recovery_id.is_x_reduced() { @@ -98,25 +99,63 @@ fn recover_from_prehash_unchecked( // Original: // ProjectivePoint::lincomb(&ProjectivePoint::GENERATOR, &u1, &r_point, &u2) // Equivalent to: G * u1 + R * u2 - let pk_words = match (u1 == Scalar::ZERO, u2 == Scalar::ZERO) { - (true, true) => return Err(Error::new()), - (true, false) => secp256k1_mul(&r_point, u2), - (false, true) => secp256k1_mul(&AffinePoint::GENERATOR, u1), - (false, false) => { - let mut p1 = secp256k1_mul(&AffinePoint::GENERATOR, u1); - let p2 = secp256k1_mul(&r_point, u2); - syscall_secp256k1_add(&mut p1, &p2); - p1 - } - }; + let pk_words = lincomb(u1, &r_point, u2)?; + + let bytes = words_to_untagged_bytes(pk_words); - // FIXME: do we really need to verify the signature again here? // Original: // let vk = VerifyingKey::from_affine(pk.to_affine())?; // // Ensure signature verifies with the recovered key // vk.verify_prehash(prehash, signature)?; + verify_prehash(&z, (&r, &s), &bytes)?; + + Ok(bytes) +} + +/// Copied from +fn verify_prehash( + z: &Scalar, + (r, s): (&NonZeroScalar, &NonZeroScalar), + bytes: &UntaggedUncompressedPoint, +) -> Result<(), Error> { + let q = EncodedPoint::from_untagged_bytes(bytes.into()); + let q = AffinePoint::from_encoded_point(&q) + .into_option() + .ok_or(Error::new())?; + + let s_inv = *s.invert_vartime(); + let u1 = z * &s_inv; + let u2 = **r * s_inv; - Ok(words_to_untagged_bytes(pk_words)) + // Original: + // let x = ProjectivePoint::lincomb(&ProjectivePoint::GENERATOR, &u1, &q, &u2) + // .to_affine() + // .x(); + // Equivalent to: G * u1 + q * u2 + let p = lincomb(u1, &q, u2)?; + let p_bytes = words_to_untagged_bytes(p); + let x = FieldBytes::from_slice(&p_bytes[..32]); + + if **r == >::reduce_bytes(&x) { + Ok(()) + } else { + Err(Error::new()) + } +} + +#[inline] +fn lincomb(u1: Scalar, p: &AffinePoint, u2: Scalar) -> Result<[u32; 16], Error> { + Ok(match (u1 == Scalar::ZERO, u2 == Scalar::ZERO) { + (false, false) => { + let mut p1 = secp256k1_mul(&AffinePoint::GENERATOR, u1); + let p2 = secp256k1_mul(&p, u2); + syscall_secp256k1_add(&mut p1, &p2); + p1 + } + (true, false) => secp256k1_mul(&p, u2), + (false, true) => secp256k1_mul(&AffinePoint::GENERATOR, u1), + (true, true) => return Err(Error::new()), + }) } fn secp256k1_mul(point: &AffinePoint, scalar: Scalar) -> [u32; 16] { From a23d4be3a24f412b260e8dd109d68ae727cbedd7 Mon Sep 17 00:00:00 2001 From: lightsing Date: Thu, 9 Oct 2025 10:29:09 +0800 Subject: [PATCH 04/15] lint & fmt --- Cargo.lock | 22 +++++++++++----------- guest_libs/crypto/src/secp256k1.rs | 6 +++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a02963e26..7bd0916ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1830,7 +1830,7 @@ dependencies = [ [[package]] name = "ff_ext" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?branch=updates-for-precompiles#0f8ab8141aadd78c69a0a67ab6bd49399563e6b9" +source = "git+https://github.com/scroll-tech/gkr-backend.git?rev=v1.0.0-alpha.9#44e4aa4456b084481a9aef1b7ee5f829221d5a0d" dependencies = [ "once_cell", "p3", @@ -2619,7 +2619,7 @@ dependencies = [ [[package]] name = "mpcs" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?branch=updates-for-precompiles#0f8ab8141aadd78c69a0a67ab6bd49399563e6b9" +source = "git+https://github.com/scroll-tech/gkr-backend.git?rev=v1.0.0-alpha.9#44e4aa4456b084481a9aef1b7ee5f829221d5a0d" dependencies = [ "bincode", "clap", @@ -2643,7 +2643,7 @@ dependencies = [ [[package]] name = "multilinear_extensions" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?branch=updates-for-precompiles#0f8ab8141aadd78c69a0a67ab6bd49399563e6b9" +source = "git+https://github.com/scroll-tech/gkr-backend.git?rev=v1.0.0-alpha.9#44e4aa4456b084481a9aef1b7ee5f829221d5a0d" dependencies = [ "either", "ff_ext", @@ -2964,7 +2964,7 @@ dependencies = [ [[package]] name = "p3" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?branch=updates-for-precompiles#0f8ab8141aadd78c69a0a67ab6bd49399563e6b9" +source = "git+https://github.com/scroll-tech/gkr-backend.git?rev=v1.0.0-alpha.9#44e4aa4456b084481a9aef1b7ee5f829221d5a0d" dependencies = [ "p3-baby-bear", "p3-challenger", @@ -3373,7 +3373,7 @@ dependencies = [ [[package]] name = "poseidon" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?branch=updates-for-precompiles#0f8ab8141aadd78c69a0a67ab6bd49399563e6b9" +source = "git+https://github.com/scroll-tech/gkr-backend.git?rev=v1.0.0-alpha.9#44e4aa4456b084481a9aef1b7ee5f829221d5a0d" dependencies = [ "ff_ext", "p3", @@ -4313,7 +4313,7 @@ dependencies = [ [[package]] name = "sp1-curves" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?branch=updates-for-precompiles#0f8ab8141aadd78c69a0a67ab6bd49399563e6b9" +source = "git+https://github.com/scroll-tech/gkr-backend.git?rev=v1.0.0-alpha.9#44e4aa4456b084481a9aef1b7ee5f829221d5a0d" dependencies = [ "cfg-if", "dashu", @@ -4419,7 +4419,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sumcheck" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?branch=updates-for-precompiles#0f8ab8141aadd78c69a0a67ab6bd49399563e6b9" +source = "git+https://github.com/scroll-tech/gkr-backend.git?rev=v1.0.0-alpha.9#44e4aa4456b084481a9aef1b7ee5f829221d5a0d" dependencies = [ "either", "ff_ext", @@ -4437,7 +4437,7 @@ dependencies = [ [[package]] name = "sumcheck_macro" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?branch=updates-for-precompiles#0f8ab8141aadd78c69a0a67ab6bd49399563e6b9" +source = "git+https://github.com/scroll-tech/gkr-backend.git?rev=v1.0.0-alpha.9#44e4aa4456b084481a9aef1b7ee5f829221d5a0d" dependencies = [ "itertools 0.13.0", "p3", @@ -4832,7 +4832,7 @@ dependencies = [ [[package]] name = "transcript" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?branch=updates-for-precompiles#0f8ab8141aadd78c69a0a67ab6bd49399563e6b9" +source = "git+https://github.com/scroll-tech/gkr-backend.git?rev=v1.0.0-alpha.9#44e4aa4456b084481a9aef1b7ee5f829221d5a0d" dependencies = [ "ff_ext", "itertools 0.13.0", @@ -5104,7 +5104,7 @@ dependencies = [ [[package]] name = "whir" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?branch=updates-for-precompiles#0f8ab8141aadd78c69a0a67ab6bd49399563e6b9" +source = "git+https://github.com/scroll-tech/gkr-backend.git?rev=v1.0.0-alpha.9#44e4aa4456b084481a9aef1b7ee5f829221d5a0d" dependencies = [ "bincode", "clap", @@ -5391,7 +5391,7 @@ dependencies = [ [[package]] name = "witness" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?branch=updates-for-precompiles#0f8ab8141aadd78c69a0a67ab6bd49399563e6b9" +source = "git+https://github.com/scroll-tech/gkr-backend.git?rev=v1.0.0-alpha.9#44e4aa4456b084481a9aef1b7ee5f829221d5a0d" dependencies = [ "ff_ext", "multilinear_extensions", diff --git a/guest_libs/crypto/src/secp256k1.rs b/guest_libs/crypto/src/secp256k1.rs index ce1961dd1..93bdf63f3 100644 --- a/guest_libs/crypto/src/secp256k1.rs +++ b/guest_libs/crypto/src/secp256k1.rs @@ -136,7 +136,7 @@ fn verify_prehash( let p_bytes = words_to_untagged_bytes(p); let x = FieldBytes::from_slice(&p_bytes[..32]); - if **r == >::reduce_bytes(&x) { + if **r == >::reduce_bytes(x) { Ok(()) } else { Err(Error::new()) @@ -148,11 +148,11 @@ fn lincomb(u1: Scalar, p: &AffinePoint, u2: Scalar) -> Result<[u32; 16], Error> Ok(match (u1 == Scalar::ZERO, u2 == Scalar::ZERO) { (false, false) => { let mut p1 = secp256k1_mul(&AffinePoint::GENERATOR, u1); - let p2 = secp256k1_mul(&p, u2); + let p2 = secp256k1_mul(p, u2); syscall_secp256k1_add(&mut p1, &p2); p1 } - (true, false) => secp256k1_mul(&p, u2), + (true, false) => secp256k1_mul(p, u2), (false, true) => secp256k1_mul(&AffinePoint::GENERATOR, u1), (true, true) => return Err(Error::new()), }) From 5990329889a38737c505bf03ecdae840aa911f76 Mon Sep 17 00:00:00 2001 From: lightsing Date: Mon, 13 Oct 2025 10:50:17 +0800 Subject: [PATCH 05/15] add crypto to ceno_rt --- ceno_rt/Cargo.toml | 1 + ceno_rt/src/crypto.rs | 4 + ceno_rt/src/crypto/ecdsa.rs | 113 +++++++ ceno_rt/src/crypto/ecdsa/affine.rs | 243 ++++++++++++++ ceno_rt/src/crypto/ecdsa/projective.rs | 424 +++++++++++++++++++++++++ ceno_rt/src/crypto/secp256k1.rs | 76 +++++ ceno_rt/src/crypto/utils.rs | 193 +++++++++++ ceno_rt/src/lib.rs | 2 + 8 files changed, 1056 insertions(+) create mode 100644 ceno_rt/src/crypto.rs create mode 100644 ceno_rt/src/crypto/ecdsa.rs create mode 100644 ceno_rt/src/crypto/ecdsa/affine.rs create mode 100644 ceno_rt/src/crypto/ecdsa/projective.rs create mode 100644 ceno_rt/src/crypto/secp256k1.rs create mode 100644 ceno_rt/src/crypto/utils.rs diff --git a/ceno_rt/Cargo.toml b/ceno_rt/Cargo.toml index 7e2a4e39a..7846163e6 100644 --- a/ceno_rt/Cargo.toml +++ b/ceno_rt/Cargo.toml @@ -13,3 +13,4 @@ version = "0.1.0" getrandom = { version = "0.2.15", features = ["custom"], default-features = false } getrandom_v3 = { package = "getrandom", version = "0.3", default-features = false } rkyv.workspace = true +elliptic-curve = { version = "0.13.8", features = ["hazmat", "sec1", "ecdh"] } diff --git a/ceno_rt/src/crypto.rs b/ceno_rt/src/crypto.rs new file mode 100644 index 000000000..58df49493 --- /dev/null +++ b/ceno_rt/src/crypto.rs @@ -0,0 +1,4 @@ + +pub mod utils; +pub mod ecdsa; +pub mod secp256k1; diff --git a/ceno_rt/src/crypto/ecdsa.rs b/ceno_rt/src/crypto/ecdsa.rs new file mode 100644 index 000000000..6a6d236cd --- /dev/null +++ b/ceno_rt/src/crypto/ecdsa.rs @@ -0,0 +1,113 @@ +//! Copied from +//! +//! An implementation of the types needed for [`CurveArithmetic`]. +//! +//! [`CurveArithmetic`] is a trait that is used in [RustCryptos ECDSA](https://github.com/RustCrypto/signatures). +//! +//! [`CurveArithmetic`] contains all the types needed to implement the ECDSA algorithm over some +//! curve. +//! +//! This implementation is specifically for use inside of SP1, and internally uses SP1's Weierstrass +//! precompiles. Weierstrass precompiles. +//! +//! In summary, SP1 overrides curve arithmetic entirely, and patches upstream field operations +//! to be more efficient in the VM, such as `sqrt` or `inverse`. + +use super::utils::AffinePoint as AffinePointTrait; + +use elliptic_curve::{ + ff, generic_array::typenum::consts::U32, subtle::CtOption, CurveArithmetic, FieldBytes, +}; +use std::{fmt::Debug, ops::Neg}; + +/// The affine point type for SP1. +pub mod affine; +pub use affine::AffinePoint; + +/// The projective point type for SP1. +pub mod projective; +pub use projective::ProjectivePoint; + +/// NOTE: The only supported ECDSA curves are secp256k1 and secp256r1, which both +/// have 8 limbs in their field elements. +const POINT_LIMBS: usize = 8 * 2; + +/// The number of bytes in a field element as an [`usize`]. +const FIELD_BYTES_SIZE_USIZE: usize = 32; + +/// The number of bytes in a field element as an [`elliptic_curve::generic_array::U32`]. +#[allow(non_camel_case_types)] +type FIELD_BYTES_SIZE = U32; + +/// A [`CurveArithmetic`] extension for SP1 acceleration. +/// +/// Patched crates implement this trait to take advantage of SP1-specific acceleration in the zkVM +/// context. +/// +/// Note: This trait only supports 32 byte base field curves. +pub trait ECDSACurve +where + Self: CurveArithmetic< + FieldBytesSize = FIELD_BYTES_SIZE, + AffinePoint = AffinePoint, + ProjectivePoint = ProjectivePoint, + >, +{ + type FieldElement: Field + Neg; + + /// The underlying [`AffinePointTrait`] implementation. + type SP1AffinePoint: ECDSAPoint; + + /// The `a` coefficient in the curve equation. + const EQUATION_A: Self::FieldElement; + + /// The `b` coefficient in the curve equation. + const EQUATION_B: Self::FieldElement; +} + +/// Alias trait for the [`ff::PrimeField`] with 32 byte field elements. +/// +/// Note: All bytes should be considered to be in big-endian format. +pub trait Field: ff::PrimeField { + /// Create an instance of self from a FieldBytes. + fn from_bytes(bytes: &FieldBytes) -> CtOption; + + /// Convert self to a FieldBytes. + /// + /// Note: Implementers should ensure these methods normalize first. + fn to_bytes(self) -> FieldBytes; + + /// Ensure the field element is normalized. + fn normalize(self) -> Self; +} + +pub type FieldElement = ::FieldElement; + +/// Alias trait for the [`AffinePointTrait`] with 32 byte field elements. +pub trait ECDSAPoint: +AffinePointTrait + Clone + Copy + Debug + Send + Sync +{ + #[inline] + fn from(x: &[u8], y: &[u8]) -> Self { + >::from(x, y) + } +} + +impl

ECDSAPoint for P where + P: AffinePointTrait + Clone + Copy + Debug + Send + Sync +{ +} + +pub mod ecdh { + pub use elliptic_curve::ecdh::{diffie_hellman, EphemeralSecret, SharedSecret}; + + use super::{AffinePoint, ECDSACurve, Field}; + + impl From<&AffinePoint> for SharedSecret { + fn from(affine: &AffinePoint) -> SharedSecret { + let (x, _) = affine.field_elements(); + + x.to_bytes().into() + } + } +} diff --git a/ceno_rt/src/crypto/ecdsa/affine.rs b/ceno_rt/src/crypto/ecdsa/affine.rs new file mode 100644 index 000000000..5438ff2e0 --- /dev/null +++ b/ceno_rt/src/crypto/ecdsa/affine.rs @@ -0,0 +1,243 @@ +//! Copied from +//! +//! Implementation of an affine point, with acceleration for operations in the context of SP1. +//! +//! The [`crate::ecdsa::ProjectivePoint`] type is mainly used in the `ecdsa-core` algorithms, +//! however, in some cases, the affine point is required. +//! +//! Note: When performing curve operations, accelerated crates for SP1 use affine arithmetic instead +//! of projective arithmetic for performance. + +use super::{ + ECDSACurve, ECDSAPoint, Field, FieldElement, AffinePointTrait, FIELD_BYTES_SIZE_USIZE, +}; + +use elliptic_curve::{ + ff::Field as _, + group::GroupEncoding, + point::{AffineCoordinates, DecompactPoint, DecompressPoint}, + sec1::{self, CompressedPoint, EncodedPoint, FromEncodedPoint, ToEncodedPoint}, + subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}, + zeroize::DefaultIsZeroes, + FieldBytes, PrimeField, +}; +use std::ops::Neg; + +#[derive(Clone, Copy, Debug)] +pub struct AffinePoint { + pub inner: C::SP1AffinePoint, +} + +impl AffinePoint { + /// Create an affine point from the given field elements, without checking if the point is on + /// the curve. + pub fn from_field_elements_unchecked(x: FieldElement, y: FieldElement) -> Self { + let mut x_slice = x.to_bytes(); + let x_slice = x_slice.as_mut_slice(); + x_slice.reverse(); + + let mut y_slice = y.to_bytes(); + let y_slice = y_slice.as_mut_slice(); + y_slice.reverse(); + + AffinePoint { inner: ::from(x_slice, y_slice) } + } + + /// Get the x and y field elements of the point. + /// + /// The returned elements are always normalized. + pub fn field_elements(&self) -> (FieldElement, FieldElement) { + if self.is_identity().into() { + return (FieldElement::::ZERO, FieldElement::::ZERO); + } + + let bytes = self.inner.to_le_bytes(); + + let mut x_bytes: [u8; FIELD_BYTES_SIZE_USIZE] = + bytes[..FIELD_BYTES_SIZE_USIZE].try_into().unwrap(); + + x_bytes.reverse(); + + let mut y_bytes: [u8; FIELD_BYTES_SIZE_USIZE] = + bytes[FIELD_BYTES_SIZE_USIZE..].try_into().unwrap(); + + y_bytes.reverse(); + + let x = FieldElement::::from_bytes(&x_bytes.into()).unwrap(); + let y = FieldElement::::from_bytes(&y_bytes.into()).unwrap(); + (x, y) + } + + /// Get the generator point. + pub fn generator() -> Self { + AffinePoint { inner: C::SP1AffinePoint::GENERATOR } + } + + /// Get the identity point. + pub fn identity() -> Self { + AffinePoint { inner: C::SP1AffinePoint::identity() } + } + + /// Check if the point is the identity point. + pub fn is_identity(&self) -> Choice { + Choice::from(self.inner.is_identity() as u8) + } +} + +impl FromEncodedPoint for AffinePoint { + fn from_encoded_point(point: &EncodedPoint) -> CtOption { + match point.coordinates() { + sec1::Coordinates::Identity => CtOption::new(Self::identity(), 1.into()), + sec1::Coordinates::Compact { x } => Self::decompact(x), + sec1::Coordinates::Compressed { x, y_is_odd } => { + AffinePoint::::decompress(x, Choice::from(y_is_odd as u8)) + } + sec1::Coordinates::Uncompressed { x, y } => { + let x = FieldElement::::from_bytes(x); + let y = FieldElement::::from_bytes(y); + + x.and_then(|x| { + y.and_then(|y| { + // Ensure the point is on the curve. + let lhs = (y * y).normalize(); + let rhs = (x * x * x) + (C::EQUATION_A * x) + C::EQUATION_B; + + let point = Self::from_field_elements_unchecked(x, y); + + CtOption::new(point, lhs.ct_eq(&rhs.normalize())) + }) + }) + } + } + } +} + +impl ToEncodedPoint for AffinePoint { + fn to_encoded_point(&self, compress: bool) -> EncodedPoint { + // If the point is the identity point, just return the identity point. + if self.is_identity().into() { + return EncodedPoint::::identity(); + } + + let (x, y) = self.field_elements(); + + // The field elements are already normalized by virtue of being created via `FromBytes`. + EncodedPoint::::from_affine_coordinates(&x.to_bytes(), &y.to_bytes(), compress) + } +} + +impl DecompressPoint for AffinePoint { + fn decompress(x_bytes: &FieldBytes, y_is_odd: Choice) -> CtOption { + FieldElement::::from_bytes(x_bytes).and_then(|x| { + let alpha = (x * x * x) + (C::EQUATION_A * x) + C::EQUATION_B; + let beta = alpha.sqrt(); + + beta.map(|beta| { + // Ensure the element is normalized for consistency. + let beta = beta.normalize(); + + let y = FieldElement::::conditional_select( + &beta.neg(), + &beta, + beta.is_odd().ct_eq(&y_is_odd), + ); + + // X is normalized by virtue of being created via `FromBytes`. + AffinePoint::from_field_elements_unchecked(x, y.normalize()) + }) + }) + } +} + +impl DecompactPoint for AffinePoint { + fn decompact(x_bytes: &FieldBytes) -> CtOption { + Self::decompress(x_bytes, Choice::from(0)) + } +} + +impl AffineCoordinates for AffinePoint { + type FieldRepr = FieldBytes; + + fn x(&self) -> FieldBytes { + let (x, _) = self.field_elements(); + + x.to_bytes() + } + + fn y_is_odd(&self) -> Choice { + let (_, y) = self.field_elements(); + + // As field elements are created via [`Field::from_bytes`], they are already normalized. + y.is_odd() + } +} + +impl ConditionallySelectable for AffinePoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + // Conditional select is a constant time if-else operation. + // + // In the SP1 vm, there are no attempts made to prevent side channel attacks. + if choice.into() { + *b + } else { + *a + } + } +} + +impl ConstantTimeEq for AffinePoint { + fn ct_eq(&self, other: &Self) -> Choice { + let (x1, y1) = self.field_elements(); + let (x1, y1) = (x1, y1); + + let (x2, y2) = other.field_elements(); + let (x2, y2) = (x2, y2); + + // These are already normalized by virtue of being created via `FromBytes`. + x1.ct_eq(&x2) & y1.ct_eq(&y2) + } +} + +impl PartialEq for AffinePoint { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +impl Eq for AffinePoint {} + +impl Default for AffinePoint { + fn default() -> Self { + AffinePoint::identity() + } +} + +impl DefaultIsZeroes for AffinePoint {} + +impl GroupEncoding for AffinePoint { + type Repr = CompressedPoint; + + fn from_bytes(bytes: &Self::Repr) -> CtOption { + EncodedPoint::::from_bytes(bytes) + .map(|point| CtOption::new(point, Choice::from(1))) + .unwrap_or_else(|_| { + // SEC1 identity encoding is technically 1-byte 0x00, but the + // `GroupEncoding` API requires a fixed-width `Repr`. + let is_identity = bytes.ct_eq(&Self::Repr::default()); + CtOption::new(EncodedPoint::::identity(), is_identity) + }) + .and_then(|point| Self::from_encoded_point(&point)) + } + + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + // There is no unchecked conversion for compressed points. + Self::from_bytes(bytes) + } + + fn to_bytes(&self) -> Self::Repr { + let encoded = self.to_encoded_point(true); + let mut result = CompressedPoint::::default(); + result[..encoded.len()].copy_from_slice(encoded.as_bytes()); + result + } +} diff --git a/ceno_rt/src/crypto/ecdsa/projective.rs b/ceno_rt/src/crypto/ecdsa/projective.rs new file mode 100644 index 000000000..d20c93aaf --- /dev/null +++ b/ceno_rt/src/crypto/ecdsa/projective.rs @@ -0,0 +1,424 @@ +//! Copied from +//! +//! Implementation of the SP1 accelerated projective point. The projective point wraps the affine +//! point. +//! +//! This type is mainly used in the `ecdsa-core` algorithms. +//! +//! Note: When performing curve operations, accelerated crates for SP1 use affine arithmetic instead +//! of projective arithmetic for performance. + +use super::{AffinePoint, ECDSACurve, AffinePointTrait}; + +use elliptic_curve::{ + group::{cofactor::CofactorGroup, prime::PrimeGroup}, + ops::MulByGenerator, + sec1::{CompressedPoint, ModulusSize}, + CurveArithmetic, FieldBytes, +}; + +use elliptic_curve::{ + ff::{Field, PrimeField}, + group::{Curve, Group, GroupEncoding}, + ops::LinearCombination, + rand_core::RngCore, + subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}, + zeroize::DefaultIsZeroes, +}; + +use std::{ + iter::Sum, + ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, +}; + +use std::borrow::Borrow; + +/// The SP1 accelerated projective point. +#[derive(Clone, Copy, Debug)] +pub struct ProjectivePoint { + /// The inner affine point. + /// + /// SP1 uses affine arithmetic for all operations. + pub inner: AffinePoint, +} + +impl ProjectivePoint { + pub fn identity() -> Self { + ProjectivePoint { inner: AffinePoint::::identity() } + } + + /// Convert the projective point to an affine point. + pub fn to_affine(self) -> AffinePoint { + self.inner + } + + fn to_zkvm_point(self) -> C::SP1AffinePoint { + self.inner.inner + } + + fn as_zkvm_point(&self) -> &C::SP1AffinePoint { + &self.inner.inner + } + + fn as_mut_zkvm_point(&mut self) -> &mut C::SP1AffinePoint { + &mut self.inner.inner + } + + /// Check if the point is the identity point. + pub fn is_identity(&self) -> Choice { + self.inner.is_identity() + } + + fn from_zkvm_point(p: C::SP1AffinePoint) -> Self { + Self { inner: AffinePoint { inner: p } } + } +} + +impl From> for ProjectivePoint { + fn from(p: AffinePoint) -> Self { + ProjectivePoint { inner: p } + } +} + +impl From<&AffinePoint> for ProjectivePoint { + fn from(p: &AffinePoint) -> Self { + ProjectivePoint { inner: *p } + } +} + +impl From> for AffinePoint { + fn from(p: ProjectivePoint) -> Self { + p.inner + } +} + +impl From<&ProjectivePoint> for AffinePoint { + fn from(p: &ProjectivePoint) -> Self { + p.inner + } +} + +impl Group for ProjectivePoint { + type Scalar = ::Scalar; + + fn identity() -> Self { + Self::identity() + } + + fn random(rng: impl RngCore) -> Self { + ProjectivePoint::::generator() * Self::Scalar::random(rng) + } + + fn double(&self) -> Self { + *self + self + } + + fn generator() -> Self { + Self { inner: AffinePoint::::generator() } + } + + fn is_identity(&self) -> Choice { + self.inner.is_identity() + } +} + +impl Curve for ProjectivePoint { + type AffineRepr = AffinePoint; + + fn to_affine(&self) -> Self::AffineRepr { + self.inner + } +} + +impl MulByGenerator for ProjectivePoint {} + +impl LinearCombination for ProjectivePoint { + fn lincomb(x: &Self, k: &Self::Scalar, y: &Self, l: &Self::Scalar) -> Self { + let x = x.to_zkvm_point(); + let y = y.to_zkvm_point(); + + let a_bits_le = be_bytes_to_le_bits(k.to_repr().as_ref()); + let b_bits_le = be_bytes_to_le_bits(l.to_repr().as_ref()); + + let sp1_point = + C::SP1AffinePoint::multi_scalar_multiplication(&a_bits_le, x, &b_bits_le, y); + + Self::from_zkvm_point(sp1_point) + } +} + +// Implementation of scalar multiplication for the projective point. + +impl> Mul for ProjectivePoint { + type Output = ProjectivePoint; + + fn mul(mut self, rhs: T) -> Self::Output { + let sp1_point = self.as_mut_zkvm_point(); + sp1_point.mul_assign(&be_bytes_to_le_words(rhs.borrow().to_repr())); + + self + } +} + +impl> MulAssign for ProjectivePoint { + fn mul_assign(&mut self, rhs: T) { + self.as_mut_zkvm_point().mul_assign(&be_bytes_to_le_words(rhs.borrow().to_repr())); + } +} + +// Implementation of projective arithmetic. + +impl Neg for ProjectivePoint { + type Output = ProjectivePoint; + + fn neg(self) -> Self::Output { + if self.is_identity().into() { + return self; + } + + let point = self.to_affine(); + let (x, y) = point.field_elements(); + + AffinePoint::::from_field_elements_unchecked(x, y.neg()).into() + } +} + +impl Add> for ProjectivePoint { + type Output = ProjectivePoint; + + fn add(mut self, rhs: ProjectivePoint) -> Self::Output { + self.as_mut_zkvm_point().add_assign(rhs.as_zkvm_point()); + + self + } +} + +impl Add<&ProjectivePoint> for ProjectivePoint { + type Output = ProjectivePoint; + + fn add(mut self, rhs: &ProjectivePoint) -> Self::Output { + self.as_mut_zkvm_point().add_assign(rhs.as_zkvm_point()); + + self + } +} + +impl Sub> for ProjectivePoint { + type Output = ProjectivePoint; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn sub(self, rhs: ProjectivePoint) -> Self::Output { + self + rhs.neg() + } +} + +impl Sub<&ProjectivePoint> for ProjectivePoint { + type Output = ProjectivePoint; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn sub(self, rhs: &ProjectivePoint) -> Self::Output { + self + (*rhs).neg() + } +} + +impl Sum> for ProjectivePoint { + fn sum>(iter: I) -> Self { + iter.fold(Self::identity(), |a, b| a + b) + } +} + +impl<'a, C: ECDSACurve> Sum<&'a ProjectivePoint> for ProjectivePoint { + fn sum>>(iter: I) -> Self { + iter.cloned().sum() + } +} + +impl AddAssign> for ProjectivePoint { + fn add_assign(&mut self, rhs: ProjectivePoint) { + self.as_mut_zkvm_point().add_assign(rhs.as_zkvm_point()); + } +} + +impl AddAssign<&ProjectivePoint> for ProjectivePoint { + fn add_assign(&mut self, rhs: &ProjectivePoint) { + self.as_mut_zkvm_point().add_assign(rhs.as_zkvm_point()); + } +} + +impl SubAssign> for ProjectivePoint { + fn sub_assign(&mut self, rhs: ProjectivePoint) { + self.as_mut_zkvm_point().add_assign(rhs.neg().as_zkvm_point()); + } +} + +impl SubAssign<&ProjectivePoint> for ProjectivePoint { + fn sub_assign(&mut self, rhs: &ProjectivePoint) { + self.as_mut_zkvm_point().add_assign(rhs.neg().as_zkvm_point()); + } +} + +impl Default for ProjectivePoint { + fn default() -> Self { + Self::identity() + } +} + +// Implementation of mixed arithmetic. + +impl Add> for ProjectivePoint { + type Output = ProjectivePoint; + + fn add(self, rhs: AffinePoint) -> Self::Output { + self + ProjectivePoint { inner: rhs } + } +} + +impl Add<&AffinePoint> for ProjectivePoint { + type Output = ProjectivePoint; + + fn add(self, rhs: &AffinePoint) -> Self::Output { + self + ProjectivePoint { inner: *rhs } + } +} + +impl AddAssign> for ProjectivePoint { + fn add_assign(&mut self, rhs: AffinePoint) { + self.as_mut_zkvm_point().add_assign(&rhs.inner); + } +} + +impl AddAssign<&AffinePoint> for ProjectivePoint { + fn add_assign(&mut self, rhs: &AffinePoint) { + self.as_mut_zkvm_point().add_assign(&rhs.inner); + } +} + +impl Sub> for ProjectivePoint { + type Output = ProjectivePoint; + + fn sub(self, rhs: AffinePoint) -> Self::Output { + self - ProjectivePoint { inner: rhs } + } +} + +impl Sub<&AffinePoint> for ProjectivePoint { + type Output = ProjectivePoint; + + fn sub(self, rhs: &AffinePoint) -> Self::Output { + self - ProjectivePoint { inner: *rhs } + } +} + +impl SubAssign> for ProjectivePoint { + fn sub_assign(&mut self, rhs: AffinePoint) { + let projective = ProjectivePoint { inner: rhs }.neg(); + + self.as_mut_zkvm_point().add_assign(projective.as_zkvm_point()); + } +} + +impl SubAssign<&AffinePoint> for ProjectivePoint { + fn sub_assign(&mut self, rhs: &AffinePoint) { + let projective = ProjectivePoint { inner: *rhs }.neg(); + + self.as_mut_zkvm_point().add_assign(projective.as_zkvm_point()); + } +} + +impl DefaultIsZeroes for ProjectivePoint {} + +impl ConditionallySelectable for ProjectivePoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self { inner: AffinePoint::conditional_select(&a.inner, &b.inner, choice) } + } +} + +impl ConstantTimeEq for ProjectivePoint { + fn ct_eq(&self, other: &Self) -> Choice { + self.inner.ct_eq(&other.inner) + } +} + +impl PartialEq for ProjectivePoint { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +impl Eq for ProjectivePoint {} + +impl GroupEncoding for ProjectivePoint +where + FieldBytes: Copy, + C::FieldBytesSize: ModulusSize, + CompressedPoint: Copy, +{ + type Repr = CompressedPoint; + + fn from_bytes(bytes: &Self::Repr) -> CtOption { + as GroupEncoding>::from_bytes(bytes).map(Into::into) + } + + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + // No unchecked conversion possible for compressed points. + Self::from_bytes(bytes) + } + + fn to_bytes(&self) -> Self::Repr { + self.inner.to_bytes() + } +} + +impl PrimeGroup for ProjectivePoint +where + FieldBytes: Copy, + C::FieldBytesSize: ModulusSize, + CompressedPoint: Copy, +{ +} + +/// The scalar field has prime order, so the cofactor is 1. +impl CofactorGroup for ProjectivePoint +where + FieldBytes: Copy, + C::FieldBytesSize: ModulusSize, + CompressedPoint: Copy, +{ + type Subgroup = Self; + + fn clear_cofactor(&self) -> Self { + *self + } + + fn into_subgroup(self) -> CtOption { + CtOption::new(self, Choice::from(1)) + } + + fn is_torsion_free(&self) -> Choice { + Choice::from(1) + } +} + +#[inline] +fn be_bytes_to_le_words>(mut bytes: T) -> [u32; 8] { + let bytes = bytes.as_mut(); + bytes.reverse(); + + let mut iter = bytes.chunks(4).map(|b| u32::from_le_bytes(b.try_into().unwrap())); + core::array::from_fn(|_| iter.next().unwrap()) +} + +/// Convert big-endian bytes with the most significant bit first to little-endian bytes with the +/// least significant bit first. Panics: If the bytes have len > 32. +#[inline] +fn be_bytes_to_le_bits(be_bytes: &[u8]) -> [bool; 256] { + let mut bits = [false; 256]; + // Reverse the byte order to little-endian. + for (i, &byte) in be_bytes.iter().rev().enumerate() { + for j in 0..8 { + // Flip the bit order so the least significant bit is now the first bit of the chunk. + bits[i * 8 + j] = ((byte >> j) & 1) == 1; + } + } + bits +} diff --git a/ceno_rt/src/crypto/secp256k1.rs b/ceno_rt/src/crypto/secp256k1.rs new file mode 100644 index 000000000..c3e65e39e --- /dev/null +++ b/ceno_rt/src/crypto/secp256k1.rs @@ -0,0 +1,76 @@ +//! Copied from + +use crate::{ + syscalls::{syscall_secp256k1_add, syscall_secp256k1_double}, + crypto::utils::{AffinePoint, WeierstrassAffinePoint, WeierstrassPoint}, +}; + +/// The number of limbs in [Secp256k1Point]. +pub const N: usize = 16; + +/// An affine point on the Secp256k1 curve. +#[derive(Copy, Clone, Debug)] +#[repr(align(4))] +pub struct Secp256k1Point(pub WeierstrassPoint); + +impl WeierstrassAffinePoint for Secp256k1Point { + fn infinity() -> Self { + Self(WeierstrassPoint::Infinity) + } + + fn is_infinity(&self) -> bool { + matches!(self.0, WeierstrassPoint::Infinity) + } +} + +impl AffinePoint for Secp256k1Point { + /// The values are taken from https://en.bitcoin.it/wiki/Secp256k1. + const GENERATOR: Self = Self(WeierstrassPoint::Affine([ + 385357720, 1509065051, 768485593, 43777243, 3464956679, 1436574357, 4191992748, 2042521214, + 4212184248, 2621952143, 2793755673, 4246189128, 235997352, 1571093500, 648266853, + 1211816567, + ])); + + fn new(limbs: [u32; N]) -> Self { + Self(WeierstrassPoint::Affine(limbs)) + } + + fn identity() -> Self { + Self::infinity() + } + + fn limbs_ref(&self) -> &[u32; N] { + match &self.0 { + WeierstrassPoint::Infinity => panic!("Infinity point has no limbs"), + WeierstrassPoint::Affine(limbs) => limbs, + } + } + + fn limbs_mut(&mut self) -> &mut [u32; N] { + match &mut self.0 { + WeierstrassPoint::Infinity => panic!("Infinity point has no limbs"), + WeierstrassPoint::Affine(limbs) => limbs, + } + } + + fn is_identity(&self) -> bool { + self.is_infinity() + } + + fn add_assign(&mut self, other: &Self) { + let a = self.limbs_mut(); + let b = other.limbs_ref(); + syscall_secp256k1_add(a, b); + } + + fn complete_add_assign(&mut self, other: &Self) { + self.weierstrass_add_assign(other); + } + + fn double(&mut self) { + match &mut self.0 { + WeierstrassPoint::Infinity => (), + WeierstrassPoint::Affine(limbs) => syscall_secp256k1_double(limbs), + } + } +} diff --git a/ceno_rt/src/crypto/utils.rs b/ceno_rt/src/crypto/utils.rs new file mode 100644 index 000000000..d07380686 --- /dev/null +++ b/ceno_rt/src/crypto/utils.rs @@ -0,0 +1,193 @@ +//! Copied from +pub trait AffinePoint: Clone + Sized { + /// The generator. + const GENERATOR: Self; + + /// Creates a new [`AffinePoint`] from the given limbs. + fn new(limbs: [u32; N]) -> Self; + + /// Creates a new [`AffinePoint`] that corresponds to the identity point. + fn identity() -> Self; + + /// Returns a reference to the limbs. + fn limbs_ref(&self) -> &[u32; N]; + + /// Returns a mutable reference to the limbs. If the point is the infinity point, this will + /// panic. + fn limbs_mut(&mut self) -> &mut [u32; N]; + + fn is_identity(&self) -> bool; + + /// Creates a new [`AffinePoint`] from the given x and y coordinates. + /// + /// The bytes are the concatenated little endian representations of the coordinates. + fn from(x: &[u8], y: &[u8]) -> Self { + debug_assert!(x.len() == N * 2); + debug_assert!(y.len() == N * 2); + + let mut limbs = [0u32; N]; + let x = bytes_to_words_le(x); + let y = bytes_to_words_le(y); + + debug_assert!(x.len() == N / 2); + debug_assert!(y.len() == N / 2); + + limbs[..(N / 2)].copy_from_slice(&x); + limbs[(N / 2)..].copy_from_slice(&y); + Self::new(limbs) + } + + /// Creates a new [`AffinePoint`] from the given bytes in little endian. + fn from_le_bytes(bytes: &[u8]) -> Self { + let limbs = bytes_to_words_le(bytes); + debug_assert!(limbs.len() == N); + Self::new(limbs.try_into().unwrap()) + } + + /// Creates a new [`AffinePoint`] from the given bytes in big endian. + fn to_le_bytes(&self) -> Vec { + let le_bytes = words_to_bytes_le(self.limbs_ref()); + debug_assert!(le_bytes.len() == N * 4); + le_bytes + } + + /// Adds the given [`AffinePoint`] to `self`. + fn add_assign(&mut self, other: &Self); + + /// Adds the given [`AffinePoint`] to `self`. Can be optionally overridden to use a different + /// implementation of addition in multi-scalar multiplication, which is used in secp256k1 + /// recovery. + fn complete_add_assign(&mut self, other: &Self) { + self.add_assign(other); + } + + /// Doubles `self`. + fn double(&mut self); + + /// Multiplies `self` by the given scalar. + fn mul_assign(&mut self, scalar: &[u32]) { + debug_assert!(scalar.len() == N / 2); + + let mut res: Self = Self::identity(); + let mut temp = self.clone(); + + for &words in scalar.iter() { + for i in 0..32 { + if (words >> i) & 1 == 1 { + res.complete_add_assign(&temp); + } + temp.double(); + } + } + + *self = res; + } + + /// Performs multi-scalar multiplication (MSM) on slices of bit vectors and points. Note: + /// a_bits_le and b_bits_le should be in little endian order. + fn multi_scalar_multiplication( + a_bits_le: &[bool], + a: Self, + b_bits_le: &[bool], + b: Self, + ) -> Self { + // The length of the bit vectors must be the same. + debug_assert!(a_bits_le.len() == b_bits_le.len()); + + let mut res: Self = Self::identity(); + let mut temp_a = a.clone(); + let mut temp_b = b.clone(); + for (a_bit, b_bit) in a_bits_le.iter().zip(b_bits_le.iter()) { + if *a_bit { + res.complete_add_assign(&temp_a); + } + if *b_bit { + res.complete_add_assign(&temp_b); + } + temp_a.double(); + temp_b.double(); + } + res + } +} + +/// Errors that can occur during scalar multiplication of an [`AffinePoint`]. +#[derive(Debug)] +pub enum MulAssignError { + ScalarIsZero, +} + +/// Converts a slice of words to a byte array in little endian. +pub fn words_to_bytes_le(words: &[u32]) -> Vec { + words.iter().flat_map(|word| word.to_le_bytes().to_vec()).collect::>() +} + +/// Converts a byte array in little endian to a slice of words. +pub fn bytes_to_words_le(bytes: &[u8]) -> Vec { + bytes + .chunks_exact(4) + .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap())) + .collect::>() +} + +#[derive(Copy, Clone, Debug)] +/// A representation of a point on a Weierstrass curve. +pub enum WeierstrassPoint { + Infinity, + Affine([u32; N]), +} + +/// A trait for affine points on Weierstrass curves. +pub trait WeierstrassAffinePoint: AffinePoint { + /// The infinity point representation of the Weierstrass curve. Typically an enum variant. + fn infinity() -> Self; + + /// Returns true if the point is the infinity point. + fn is_infinity(&self) -> bool; + + /// Performs the complete addition of two [`AffinePoint`]'s on a Weierstrass curve. + /// For an addition of two points P1 and P2, the cases are: + /// 1. P1 is infinity + /// 2. P2 is infinity + /// 3. P1 equals P2 + /// 4. P1 is the negation of P2 + /// 5. Default addition. + /// + /// Implements the complete addition cases according to the + /// [Zcash complete addition spec](https://zcash.github.io/halo2/design/gadgets/ecc/addition.html#complete-addition). + fn weierstrass_add_assign(&mut self, other: &Self) { + // Case 1: p1 is infinity. + if self.is_infinity() { + *self = other.clone(); + return; + } + + // Case 2: p2 is infinity. + if other.is_infinity() { + return; + } + + // Once it's known the points are not infinity, their limbs can be safely used. + let p1 = self.limbs_mut(); + let p2 = other.limbs_ref(); + + // Case 3: p1 equals p2. + if p1 == p2 { + self.double(); + return; + } + + // Case 4: p1 is the negation of p2. + // Note: If p1 and p2 are valid elliptic curve points, and p1.x == p2.x, that means that + // either p1.y == p2.y or p1.y + p2.y == p. Because we are past Case 4, we know that p1.y != + // p2.y, so we can just check if p1.x == p2.x. Therefore, this implicitly checks that + // p1.x == p2.x AND p1.y + p2.y == p without modular negation. + if p1[..N / 2] == p2[..N / 2] { + *self = Self::infinity(); + return; + } + + // Case 5: Default addition. + self.add_assign(other); + } +} \ No newline at end of file diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index cc0deb217..779a330a8 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -24,6 +24,8 @@ pub use params::*; pub mod syscalls; +pub mod crypto; + #[unsafe(no_mangle)] #[linkage = "weak"] pub extern "C" fn sys_write(_fd: i32, _buf: *const u8, _count: usize) -> isize { From 318275c5f052c906ea7a2f7a9e48af7f4bb7cf49 Mon Sep 17 00:00:00 2001 From: lightsing Date: Mon, 13 Oct 2025 10:54:15 +0800 Subject: [PATCH 06/15] make allocator as feature --- ceno_rt/Cargo.toml | 4 ++++ ceno_rt/src/lib.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ceno_rt/Cargo.toml b/ceno_rt/Cargo.toml index 7846163e6..7d81d52ba 100644 --- a/ceno_rt/Cargo.toml +++ b/ceno_rt/Cargo.toml @@ -14,3 +14,7 @@ getrandom = { version = "0.2.15", features = ["custom"], default-features = fals getrandom_v3 = { package = "getrandom", version = "0.3", default-features = false } rkyv.workspace = true elliptic-curve = { version = "0.13.8", features = ["hazmat", "sec1", "ecdh"] } + +[features] +default = ["allocator"] +allocator = [] diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index 779a330a8..70b7c187d 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -9,7 +9,7 @@ use std::{ ptr::null, }; -#[cfg(target_arch = "riscv32")] +#[cfg(all(feature = "allocator", target_arch = "riscv32"))] mod allocator; mod mmio; From f9a5dea4e66818037dd9f8498c6ee88393d8a6f6 Mon Sep 17 00:00:00 2001 From: lightsing Date: Mon, 13 Oct 2025 11:05:42 +0800 Subject: [PATCH 07/15] move out ceno_rt --- Cargo.lock | 92 ++++++++++++++++--- Cargo.toml | 1 + ceno_rt/Cargo.toml | 5 - ceno_rt/src/lib.rs | 8 +- ceno_syscall/Cargo.toml | 11 +++ .../syscalls.rs => ceno_syscall/src/lib.rs | 86 ++++++++--------- guest_libs/crypto-primitives/Cargo.toml | 13 +++ .../crypto-primitives/src}/ecdsa.rs | 0 .../crypto-primitives/src}/ecdsa/affine.rs | 0 .../src}/ecdsa/projective.rs | 0 .../crypto-primitives/src/lib.rs | 1 - .../crypto-primitives/src}/secp256k1.rs | 5 +- .../crypto-primitives/src}/utils.rs | 0 13 files changed, 153 insertions(+), 69 deletions(-) create mode 100644 ceno_syscall/Cargo.toml rename ceno_rt/src/syscalls.rs => ceno_syscall/src/lib.rs (86%) create mode 100644 guest_libs/crypto-primitives/Cargo.toml rename {ceno_rt/src/crypto => guest_libs/crypto-primitives/src}/ecdsa.rs (100%) rename {ceno_rt/src/crypto => guest_libs/crypto-primitives/src}/ecdsa/affine.rs (100%) rename {ceno_rt/src/crypto => guest_libs/crypto-primitives/src}/ecdsa/projective.rs (100%) rename ceno_rt/src/crypto.rs => guest_libs/crypto-primitives/src/lib.rs (98%) rename {ceno_rt/src/crypto => guest_libs/crypto-primitives/src}/secp256k1.rs (93%) rename {ceno_rt/src/crypto => guest_libs/crypto-primitives/src}/utils.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 7bd0916ce..e02ab974f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,7 +61,7 @@ dependencies = [ "c-kzg", "derive_more 2.0.1", "either", - "k256", + "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell", "secp256k1 0.30.0", "serde", @@ -143,7 +143,7 @@ dependencies = [ "hashbrown 0.16.0", "indexmap 2.9.0", "itoa", - "k256", + "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", "keccak-asm", "paste", "proptest", @@ -893,6 +893,14 @@ dependencies = [ "shlex", ] +[[package]] +name = "ceno-crypto-primitives" +version = "0.1.0" +dependencies = [ + "ceno_syscall", + "elliptic-curve", +] + [[package]] name = "ceno-examples" version = "0.1.0" @@ -907,8 +915,7 @@ dependencies = [ "alloy-consensus", "alloy-primitives", "ceno_keccak", - "ceno_rt", - "k256", + "k256 0.13.4 (git+https://github.com/scroll-tech/elliptic-curves?branch=ceno%2Fk256-13.4)", "revm-precompile", "thiserror 2.0.12", ] @@ -918,7 +925,7 @@ name = "ceno_emul" version = "0.1.0" dependencies = [ "anyhow", - "ceno_rt", + "ceno_rt 0.1.0", "elf", "ff_ext", "itertools 0.13.0", @@ -952,7 +959,7 @@ dependencies = [ name = "ceno_keccak" version = "0.1.0" dependencies = [ - "ceno_rt", + "ceno_rt 0.1.0", "tiny-keccak", ] @@ -965,6 +972,21 @@ dependencies = [ "rkyv", ] +[[package]] +name = "ceno_rt" +version = "0.1.0" +source = "git+https://github.com/scroll-tech/ceno?branch=feat%2Fsecp256k1-guest#318275c5f052c906ea7a2f7a9e48af7f4bb7cf49" +dependencies = [ + "elliptic-curve", + "getrandom 0.2.16", + "getrandom 0.3.2", + "rkyv", +] + +[[package]] +name = "ceno_syscall" +version = "0.1.0" + [[package]] name = "ceno_zkvm" version = "0.1.0" @@ -1680,7 +1702,20 @@ dependencies = [ "der", "digest 0.10.7", "elliptic-curve", - "rfc6979", + "rfc6979 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "signature", + "spki", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "git+https://github.com/sp1-patches/signatures.git?tag=patch-16.9-sp1-4.1.0#1880299a48fe7ef249edaa616fd411239fb5daf1" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979 0.4.0 (git+https://github.com/sp1-patches/signatures.git?tag=patch-16.9-sp1-4.1.0)", "signature", "spki", ] @@ -1724,6 +1759,7 @@ dependencies = [ "ff", "generic-array 0.14.7", "group", + "hkdf", "pem-rfc7468", "pkcs8", "rand_core 0.6.4", @@ -1781,7 +1817,7 @@ dependencies = [ "alloy-primitives", "ceno_crypto", "ceno_keccak", - "ceno_rt", + "ceno_rt 0.1.0", "getrandom 0.3.2", "rand 0.8.5", "rkyv", @@ -2094,6 +2130,15 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -2408,13 +2453,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", - "ecdsa", + "ecdsa 0.16.9 (registry+https://github.com/rust-lang/crates.io-index)", "elliptic-curve", "once_cell", "sha2 0.10.9", "signature", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "git+https://github.com/scroll-tech/elliptic-curves?branch=ceno%2Fk256-13.4#18cc237e254e2dccd94437eb99c3c8e6ac7373cd" +dependencies = [ + "ceno_rt 0.1.0 (git+https://github.com/scroll-tech/ceno?branch=feat%2Fsecp256k1-guest)", + "cfg-if", + "ecdsa 0.16.9 (git+https://github.com/sp1-patches/signatures.git?tag=patch-16.9-sp1-4.1.0)", + "elliptic-curve", + "hex", + "once_cell", + "sha2 0.10.9", +] + [[package]] name = "keccak" version = "0.1.5" @@ -2955,7 +3014,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" dependencies = [ - "ecdsa", + "ecdsa 0.16.9 (registry+https://github.com/rust-lang/crates.io-index)", "elliptic-curve", "primeorder", "sha2 0.10.9", @@ -3770,7 +3829,7 @@ dependencies = [ "blst", "c-kzg", "cfg-if", - "k256", + "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", "libsecp256k1", "p256", "revm-primitives", @@ -3802,6 +3861,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "git+https://github.com/sp1-patches/signatures.git?tag=patch-16.9-sp1-4.1.0#1880299a48fe7ef249edaa616fd411239fb5daf1" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rgb" version = "0.8.50" @@ -4321,7 +4389,7 @@ dependencies = [ "ff_ext", "generic-array 1.2.0", "itertools 0.13.0", - "k256", + "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", "multilinear_extensions", "num", "p256", diff --git a/Cargo.toml b/Cargo.toml index 42c6b41c6..af94968f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "ceno_emul", "ceno_host", "ceno_rt", + "ceno_syscall", "ceno_zkvm", "derive", "examples-builder", diff --git a/ceno_rt/Cargo.toml b/ceno_rt/Cargo.toml index 7d81d52ba..7e2a4e39a 100644 --- a/ceno_rt/Cargo.toml +++ b/ceno_rt/Cargo.toml @@ -13,8 +13,3 @@ version = "0.1.0" getrandom = { version = "0.2.15", features = ["custom"], default-features = false } getrandom_v3 = { package = "getrandom", version = "0.3", default-features = false } rkyv.workspace = true -elliptic-curve = { version = "0.13.8", features = ["hazmat", "sec1", "ecdh"] } - -[features] -default = ["allocator"] -allocator = [] diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index 70b7c187d..dacb134ee 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -9,7 +9,7 @@ use std::{ ptr::null, }; -#[cfg(all(feature = "allocator", target_arch = "riscv32"))] +#[cfg(target_arch = "riscv32")] mod allocator; mod mmio; @@ -22,10 +22,6 @@ pub use io::info_out; mod params; pub use params::*; -pub mod syscalls; - -pub mod crypto; - #[unsafe(no_mangle)] #[linkage = "weak"] pub extern "C" fn sys_write(_fd: i32, _buf: *const u8, _count: usize) -> isize { @@ -122,7 +118,7 @@ pub fn halt(exit_code: u32) -> ! { ); } -#[cfg(target_arch = "riscv32")] +#[cfg(all(feature = "guest", target_arch = "riscv32"))] global_asm!( " // The entry point for the program. diff --git a/ceno_syscall/Cargo.toml b/ceno_syscall/Cargo.toml new file mode 100644 index 000000000..5fded0a10 --- /dev/null +++ b/ceno_syscall/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ceno_syscall" +categories.workspace = true +edition.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true + +[dependencies] diff --git a/ceno_rt/src/syscalls.rs b/ceno_syscall/src/lib.rs similarity index 86% rename from ceno_rt/src/syscalls.rs rename to ceno_syscall/src/lib.rs index 914fab8b7..b00f8c037 100644 --- a/ceno_rt/src/syscalls.rs +++ b/ceno_syscall/src/lib.rs @@ -33,9 +33,9 @@ pub fn syscall_keccak_permute(state: &mut [u64; KECCAK_STATE_WORDS]) { #[cfg(target_os = "zkvm")] unsafe { asm!( - "ecall", - in("t0") KECCAK_PERMUTE, - in("a0") state as *mut [u64; 25], + "ecall", + in("t0") KECCAK_PERMUTE, + in("a0") state as *mut [u64; 25], ); } #[cfg(not(target_os = "zkvm"))] @@ -60,10 +60,10 @@ pub fn syscall_secp256k1_add(p: &mut [u32; 16], q: &[u32; 16]) { let p = p.as_mut_ptr(); let q = q.as_ptr(); asm!( - "ecall", - in("t0") SECP256K1_ADD, - in("a0") p, - in("a1") q + "ecall", + in("t0") SECP256K1_ADD, + in("a0") p, + in("a1") q ); } @@ -86,10 +86,10 @@ pub fn syscall_secp256k1_double(p: &mut [u32; 16]) { unsafe { let p = p.as_mut_ptr(); asm!( - "ecall", - in("t0") SECP256K1_DOUBLE, - in("a0") p, - in("a1") 0 + "ecall", + in("t0") SECP256K1_DOUBLE, + in("a0") p, + in("a1") 0 ); } @@ -114,10 +114,10 @@ pub fn syscall_secp256k1_decompress(point: &mut [u8; 64], is_odd: bool) { let p = point.as_mut_ptr(); unsafe { asm!( - "ecall", - in("t0") SECP256K1_DECOMPRESS, - in("a0") p, - in("a1") is_odd as u8 + "ecall", + in("t0") SECP256K1_DECOMPRESS, + in("a0") p, + in("a1") is_odd as u8 ); } } @@ -138,10 +138,10 @@ pub fn syscall_sha256_extend(w: *mut [u32; 64]) { #[cfg(target_os = "zkvm")] unsafe { asm!( - "ecall", - in("t0") SHA_EXTEND, - in("a0") w, - in("a1") 0 + "ecall", + in("t0") SHA_EXTEND, + in("a0") w, + in("a1") 0 ); } @@ -163,10 +163,10 @@ pub extern "C" fn syscall_bn254_add(p: *mut [u32; 16], q: *const [u32; 16]) { #[cfg(target_os = "zkvm")] unsafe { asm!( - "ecall", - in("t0") BN254_ADD, - in("a0") p, - in("a1") q, + "ecall", + in("t0") BN254_ADD, + in("a0") p, + in("a1") q, ); } @@ -188,10 +188,10 @@ pub extern "C" fn syscall_bn254_double(p: *mut [u32; 16]) { #[cfg(target_os = "zkvm")] unsafe { asm!( - "ecall", - in("t0") BN254_DOUBLE, - in("a0") p, - in("a1") 0, + "ecall", + in("t0") BN254_DOUBLE, + in("a0") p, + in("a1") 0, ); } @@ -208,10 +208,10 @@ pub extern "C" fn syscall_bn254_fp_addmod(x: *mut u32, y: *const u32) { #[cfg(target_os = "zkvm")] unsafe { asm!( - "ecall", - in("t0") BN254_FP_ADD, - in("a0") x, - in("a1") y, + "ecall", + in("t0") BN254_FP_ADD, + in("a0") x, + in("a1") y, ); } @@ -228,10 +228,10 @@ pub extern "C" fn syscall_bn254_fp_mulmod(x: *mut u32, y: *const u32) { #[cfg(target_os = "zkvm")] unsafe { asm!( - "ecall", - in("t0") BN254_FP_MUL, - in("a0") x, - in("a1") y, + "ecall", + in("t0") BN254_FP_MUL, + in("a0") x, + in("a1") y, ); } @@ -248,10 +248,10 @@ pub extern "C" fn syscall_bn254_fp2_addmod(x: *mut u32, y: *const u32) { #[cfg(target_os = "zkvm")] unsafe { asm!( - "ecall", - in("t0") BN254_FP2_ADD, - in("a0") x, - in("a1") y, + "ecall", + in("t0") BN254_FP2_ADD, + in("a0") x, + in("a1") y, ); } @@ -268,10 +268,10 @@ pub extern "C" fn syscall_bn254_fp2_mulmod(x: *mut u32, y: *const u32) { #[cfg(target_os = "zkvm")] unsafe { asm!( - "ecall", - in("t0") BN254_FP2_MUL, - in("a0") x, - in("a1") y, + "ecall", + in("t0") BN254_FP2_MUL, + in("a0") x, + in("a1") y, ); } diff --git a/guest_libs/crypto-primitives/Cargo.toml b/guest_libs/crypto-primitives/Cargo.toml new file mode 100644 index 000000000..fcb56b546 --- /dev/null +++ b/guest_libs/crypto-primitives/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ceno-crypto-primitives" +categories.workspace = true +edition.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true + +[dependencies] +ceno_syscall = { path = "../../ceno_syscall" } +elliptic-curve = { version = "0.13.8", features = ["hazmat", "sec1", "ecdh"] } diff --git a/ceno_rt/src/crypto/ecdsa.rs b/guest_libs/crypto-primitives/src/ecdsa.rs similarity index 100% rename from ceno_rt/src/crypto/ecdsa.rs rename to guest_libs/crypto-primitives/src/ecdsa.rs diff --git a/ceno_rt/src/crypto/ecdsa/affine.rs b/guest_libs/crypto-primitives/src/ecdsa/affine.rs similarity index 100% rename from ceno_rt/src/crypto/ecdsa/affine.rs rename to guest_libs/crypto-primitives/src/ecdsa/affine.rs diff --git a/ceno_rt/src/crypto/ecdsa/projective.rs b/guest_libs/crypto-primitives/src/ecdsa/projective.rs similarity index 100% rename from ceno_rt/src/crypto/ecdsa/projective.rs rename to guest_libs/crypto-primitives/src/ecdsa/projective.rs diff --git a/ceno_rt/src/crypto.rs b/guest_libs/crypto-primitives/src/lib.rs similarity index 98% rename from ceno_rt/src/crypto.rs rename to guest_libs/crypto-primitives/src/lib.rs index 58df49493..57f3a8c17 100644 --- a/ceno_rt/src/crypto.rs +++ b/guest_libs/crypto-primitives/src/lib.rs @@ -1,4 +1,3 @@ - pub mod utils; pub mod ecdsa; pub mod secp256k1; diff --git a/ceno_rt/src/crypto/secp256k1.rs b/guest_libs/crypto-primitives/src/secp256k1.rs similarity index 93% rename from ceno_rt/src/crypto/secp256k1.rs rename to guest_libs/crypto-primitives/src/secp256k1.rs index c3e65e39e..07d301f59 100644 --- a/ceno_rt/src/crypto/secp256k1.rs +++ b/guest_libs/crypto-primitives/src/secp256k1.rs @@ -1,9 +1,10 @@ //! Copied from use crate::{ - syscalls::{syscall_secp256k1_add, syscall_secp256k1_double}, - crypto::utils::{AffinePoint, WeierstrassAffinePoint, WeierstrassPoint}, + + utils::{AffinePoint, WeierstrassAffinePoint, WeierstrassPoint}, }; +use ceno_syscall::{syscall_secp256k1_add, syscall_secp256k1_double}; /// The number of limbs in [Secp256k1Point]. pub const N: usize = 16; diff --git a/ceno_rt/src/crypto/utils.rs b/guest_libs/crypto-primitives/src/utils.rs similarity index 100% rename from ceno_rt/src/crypto/utils.rs rename to guest_libs/crypto-primitives/src/utils.rs From 98f45d56f5c44455026c0a7049e1418044feb9dd Mon Sep 17 00:00:00 2001 From: lightsing Date: Mon, 13 Oct 2025 11:07:05 +0800 Subject: [PATCH 08/15] naming --- Cargo.lock | 16 ++++++++-------- guest_libs/crypto-primitives/Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e02ab974f..5b6b4ece0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -893,14 +893,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "ceno-crypto-primitives" -version = "0.1.0" -dependencies = [ - "ceno_syscall", - "elliptic-curve", -] - [[package]] name = "ceno-examples" version = "0.1.0" @@ -920,6 +912,14 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "ceno_crypto_primitives" +version = "0.1.0" +dependencies = [ + "ceno_syscall", + "elliptic-curve", +] + [[package]] name = "ceno_emul" version = "0.1.0" diff --git a/guest_libs/crypto-primitives/Cargo.toml b/guest_libs/crypto-primitives/Cargo.toml index fcb56b546..c5954610f 100644 --- a/guest_libs/crypto-primitives/Cargo.toml +++ b/guest_libs/crypto-primitives/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ceno-crypto-primitives" +name = "ceno_crypto_primitives" categories.workspace = true edition.workspace = true keywords.workspace = true From 7ffce5bb3674c978be8eb36a40199a3f63cac96f Mon Sep 17 00:00:00 2001 From: lightsing Date: Mon, 13 Oct 2025 11:10:30 +0800 Subject: [PATCH 09/15] ok --- Cargo.lock | 33 +-- ceno_emul/Cargo.toml | 1 + ceno_emul/src/syscalls.rs | 2 +- ceno_emul/src/syscalls/bn254/bn254_curve.rs | 4 +- ceno_emul/src/syscalls/bn254/bn254_fptower.rs | 8 +- ceno_emul/src/syscalls/keccak_permute.rs | 2 +- ceno_emul/src/syscalls/secp256k1.rs | 6 +- ceno_emul/src/syscalls/sha256.rs | 2 +- ceno_rt/src/lib.rs | 2 +- examples/Cargo.toml | 1 + examples/examples/bn254_curve_syscalls.rs | 2 +- examples/examples/bn254_fptower_syscalls.rs | 2 +- examples/examples/keccak_syscall.rs | 2 +- examples/examples/secp256k1_add_syscall.rs | 2 +- .../examples/secp256k1_decompress_syscall.rs | 2 +- examples/examples/secp256k1_double_syscall.rs | 2 +- examples/examples/sha256.rs | 2 +- examples/examples/sha_extend_syscall.rs | 2 +- examples/examples/syscalls.rs | 2 +- guest_libs/crypto-primitives/src/ecdsa.rs | 21 +- .../crypto-primitives/src/ecdsa/affine.rs | 22 +- .../crypto-primitives/src/ecdsa/projective.rs | 39 ++-- guest_libs/crypto-primitives/src/lib.rs | 2 +- guest_libs/crypto-primitives/src/secp256k1.rs | 5 +- guest_libs/crypto-primitives/src/utils.rs | 7 +- guest_libs/crypto/Cargo.toml | 3 +- guest_libs/crypto/src/secp256k1.rs | 201 ++---------------- guest_libs/keccak/Cargo.toml | 2 +- guest_libs/keccak/src/lib.rs | 2 +- guest_libs/keccak/src/vendor.rs | 2 +- 30 files changed, 115 insertions(+), 270 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b6b4ece0..1a0f98564 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -916,7 +916,16 @@ dependencies = [ name = "ceno_crypto_primitives" version = "0.1.0" dependencies = [ - "ceno_syscall", + "ceno_syscall 0.1.0", + "elliptic-curve", +] + +[[package]] +name = "ceno_crypto_primitives" +version = "0.1.0" +source = "git+https://github.com/scroll-tech/ceno?branch=feat%2Fsecp256k1-guest#98f45d56f5c44455026c0a7049e1418044feb9dd" +dependencies = [ + "ceno_syscall 0.1.0 (git+https://github.com/scroll-tech/ceno?branch=feat%2Fsecp256k1-guest)", "elliptic-curve", ] @@ -925,7 +934,8 @@ name = "ceno_emul" version = "0.1.0" dependencies = [ "anyhow", - "ceno_rt 0.1.0", + "ceno_rt", + "ceno_syscall 0.1.0", "elf", "ff_ext", "itertools 0.13.0", @@ -959,7 +969,7 @@ dependencies = [ name = "ceno_keccak" version = "0.1.0" dependencies = [ - "ceno_rt 0.1.0", + "ceno_syscall 0.1.0", "tiny-keccak", ] @@ -973,19 +983,13 @@ dependencies = [ ] [[package]] -name = "ceno_rt" +name = "ceno_syscall" version = "0.1.0" -source = "git+https://github.com/scroll-tech/ceno?branch=feat%2Fsecp256k1-guest#318275c5f052c906ea7a2f7a9e48af7f4bb7cf49" -dependencies = [ - "elliptic-curve", - "getrandom 0.2.16", - "getrandom 0.3.2", - "rkyv", -] [[package]] name = "ceno_syscall" version = "0.1.0" +source = "git+https://github.com/scroll-tech/ceno?branch=feat%2Fsecp256k1-guest#98f45d56f5c44455026c0a7049e1418044feb9dd" [[package]] name = "ceno_zkvm" @@ -1817,7 +1821,8 @@ dependencies = [ "alloy-primitives", "ceno_crypto", "ceno_keccak", - "ceno_rt 0.1.0", + "ceno_rt", + "ceno_syscall 0.1.0", "getrandom 0.3.2", "rand 0.8.5", "rkyv", @@ -2463,9 +2468,9 @@ dependencies = [ [[package]] name = "k256" version = "0.13.4" -source = "git+https://github.com/scroll-tech/elliptic-curves?branch=ceno%2Fk256-13.4#18cc237e254e2dccd94437eb99c3c8e6ac7373cd" +source = "git+https://github.com/scroll-tech/elliptic-curves?branch=ceno%2Fk256-13.4#2881a464bc20636189bd61ee79de4638e5c050fa" dependencies = [ - "ceno_rt 0.1.0 (git+https://github.com/scroll-tech/ceno?branch=feat%2Fsecp256k1-guest)", + "ceno_crypto_primitives 0.1.0 (git+https://github.com/scroll-tech/ceno?branch=feat%2Fsecp256k1-guest)", "cfg-if", "ecdsa 0.16.9 (git+https://github.com/sp1-patches/signatures.git?tag=patch-16.9-sp1-4.1.0)", "elliptic-curve", diff --git a/ceno_emul/Cargo.toml b/ceno_emul/Cargo.toml index 2fe057102..b0af43fe3 100644 --- a/ceno_emul/Cargo.toml +++ b/ceno_emul/Cargo.toml @@ -12,6 +12,7 @@ version.workspace = true [dependencies] anyhow.workspace = true ceno_rt = { path = "../ceno_rt" } +ceno_syscall = { path = "../ceno_syscall" } elf = "0.7" ff_ext.workspace = true itertools.workspace = true diff --git a/ceno_emul/src/syscalls.rs b/ceno_emul/src/syscalls.rs index 11dfaabd5..ee6e928dd 100644 --- a/ceno_emul/src/syscalls.rs +++ b/ceno_emul/src/syscalls.rs @@ -9,7 +9,7 @@ pub mod sha256; // Using the same function codes as sp1: // https://github.com/succinctlabs/sp1/blob/013c24ea2fa15a0e7ed94f7d11a7ada4baa39ab9/crates/core/executor/src/syscalls/code.rs -pub use ceno_rt::syscalls::{ +pub use ceno_syscall::{ BLS12381_ADD, BLS12381_DECOMPRESS, BLS12381_DOUBLE, BN254_ADD, BN254_DOUBLE, BN254_FP_ADD, BN254_FP_MUL, BN254_FP2_ADD, BN254_FP2_MUL, KECCAK_PERMUTE, SECP256K1_ADD, SECP256K1_DECOMPRESS, SECP256K1_DOUBLE, SECP256R1_ADD, SECP256R1_DECOMPRESS, SECP256R1_DOUBLE, diff --git a/ceno_emul/src/syscalls/bn254/bn254_curve.rs b/ceno_emul/src/syscalls/bn254/bn254_curve.rs index 41a826083..5a1aed686 100644 --- a/ceno_emul/src/syscalls/bn254/bn254_curve.rs +++ b/ceno_emul/src/syscalls/bn254/bn254_curve.rs @@ -13,7 +13,7 @@ impl SyscallSpec for Bn254AddSpec { const REG_OPS_COUNT: usize = 2; const MEM_OPS_COUNT: usize = 2 * BN254_POINT_WORDS; - const CODE: u32 = ceno_rt::syscalls::BN254_ADD; + const CODE: u32 = ceno_syscall::BN254_ADD; } pub struct Bn254DoubleSpec; @@ -22,7 +22,7 @@ impl SyscallSpec for Bn254DoubleSpec { const REG_OPS_COUNT: usize = 2; const MEM_OPS_COUNT: usize = BN254_POINT_WORDS; - const CODE: u32 = ceno_rt::syscalls::BN254_DOUBLE; + const CODE: u32 = ceno_syscall::BN254_DOUBLE; } pub fn bn254_add(vm: &VMState) -> SyscallEffects { diff --git a/ceno_emul/src/syscalls/bn254/bn254_fptower.rs b/ceno_emul/src/syscalls/bn254/bn254_fptower.rs index 0e7c21db6..75c70a055 100644 --- a/ceno_emul/src/syscalls/bn254/bn254_fptower.rs +++ b/ceno_emul/src/syscalls/bn254/bn254_fptower.rs @@ -17,7 +17,7 @@ impl SyscallSpec for Bn254FpAddSpec { const REG_OPS_COUNT: usize = 2; const MEM_OPS_COUNT: usize = 2 * BN254_FP_WORDS; - const CODE: u32 = ceno_rt::syscalls::BN254_FP_ADD; + const CODE: u32 = ceno_syscall::BN254_FP_ADD; } pub struct Bn254Fp2AddSpec; @@ -26,7 +26,7 @@ impl SyscallSpec for Bn254Fp2AddSpec { const REG_OPS_COUNT: usize = 2; const MEM_OPS_COUNT: usize = 2 * BN254_FP2_WORDS; - const CODE: u32 = ceno_rt::syscalls::BN254_FP2_ADD; + const CODE: u32 = ceno_syscall::BN254_FP2_ADD; } pub struct Bn254FpMulSpec; @@ -35,7 +35,7 @@ impl SyscallSpec for Bn254FpMulSpec { const REG_OPS_COUNT: usize = 2; const MEM_OPS_COUNT: usize = 2 * BN254_FP_WORDS; - const CODE: u32 = ceno_rt::syscalls::BN254_FP_MUL; + const CODE: u32 = ceno_syscall::BN254_FP_MUL; } pub struct Bn254Fp2MulSpec; @@ -44,7 +44,7 @@ impl SyscallSpec for Bn254Fp2MulSpec { const REG_OPS_COUNT: usize = 2; const MEM_OPS_COUNT: usize = 2 * BN254_FP2_WORDS; - const CODE: u32 = ceno_rt::syscalls::BN254_FP2_MUL; + const CODE: u32 = ceno_syscall::BN254_FP2_MUL; } fn bn254_fptower_binary_op< diff --git a/ceno_emul/src/syscalls/keccak_permute.rs b/ceno_emul/src/syscalls/keccak_permute.rs index 31757ea38..c9fc2cac7 100644 --- a/ceno_emul/src/syscalls/keccak_permute.rs +++ b/ceno_emul/src/syscalls/keccak_permute.rs @@ -15,7 +15,7 @@ impl SyscallSpec for KeccakSpec { const REG_OPS_COUNT: usize = 1; const MEM_OPS_COUNT: usize = KECCAK_WORDS; - const CODE: u32 = ceno_rt::syscalls::KECCAK_PERMUTE; + const CODE: u32 = ceno_syscall::KECCAK_PERMUTE; const HAS_LOOKUPS: bool = true; } diff --git a/ceno_emul/src/syscalls/secp256k1.rs b/ceno_emul/src/syscalls/secp256k1.rs index 2e0e89506..2facffba4 100644 --- a/ceno_emul/src/syscalls/secp256k1.rs +++ b/ceno_emul/src/syscalls/secp256k1.rs @@ -14,7 +14,7 @@ impl SyscallSpec for Secp256k1AddSpec { const REG_OPS_COUNT: usize = 2; const MEM_OPS_COUNT: usize = 2 * SECP256K1_ARG_WORDS; - const CODE: u32 = ceno_rt::syscalls::SECP256K1_ADD; + const CODE: u32 = ceno_syscall::SECP256K1_ADD; } impl SyscallSpec for Secp256k1DoubleSpec { @@ -22,7 +22,7 @@ impl SyscallSpec for Secp256k1DoubleSpec { const REG_OPS_COUNT: usize = 2; const MEM_OPS_COUNT: usize = SECP256K1_ARG_WORDS; - const CODE: u32 = ceno_rt::syscalls::SECP256K1_DOUBLE; + const CODE: u32 = ceno_syscall::SECP256K1_DOUBLE; } impl SyscallSpec for Secp256k1DecompressSpec { @@ -30,7 +30,7 @@ impl SyscallSpec for Secp256k1DecompressSpec { const REG_OPS_COUNT: usize = 2; const MEM_OPS_COUNT: usize = 2 * COORDINATE_WORDS; - const CODE: u32 = ceno_rt::syscalls::SECP256K1_DECOMPRESS; + const CODE: u32 = ceno_syscall::SECP256K1_DECOMPRESS; } // A secp256k1 point in uncompressed form takes 64 bytes diff --git a/ceno_emul/src/syscalls/sha256.rs b/ceno_emul/src/syscalls/sha256.rs index 08bdf0fb5..a6b1e404c 100644 --- a/ceno_emul/src/syscalls/sha256.rs +++ b/ceno_emul/src/syscalls/sha256.rs @@ -11,7 +11,7 @@ impl SyscallSpec for Sha256ExtendSpec { const REG_OPS_COUNT: usize = 2; const MEM_OPS_COUNT: usize = SHA_EXTEND_WORDS; - const CODE: u32 = ceno_rt::syscalls::SHA_EXTEND; + const CODE: u32 = ceno_syscall::SHA_EXTEND; } /// Wrapper type for the sha_extend argument that implements conversions diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index dacb134ee..006281709 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -118,7 +118,7 @@ pub fn halt(exit_code: u32) -> ! { ); } -#[cfg(all(feature = "guest", target_arch = "riscv32"))] +#[cfg(target_arch = "riscv32")] global_asm!( " // The entry point for the program. diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 6a6bfbec0..34a3b3c97 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -14,6 +14,7 @@ alloy-primitives = { version = "1.3", features = ["native-keccak"] } ceno_crypto = { path = "../guest_libs/crypto" } ceno_keccak = { path = "../guest_libs/keccak" } ceno_rt = { path = "../ceno_rt" } +ceno_syscall = { path = "../ceno_syscall" } getrandom = { version = "0.3" } rand.workspace = true tiny-keccak.workspace = true diff --git a/examples/examples/bn254_curve_syscalls.rs b/examples/examples/bn254_curve_syscalls.rs index cf760338c..3b7a75bd3 100644 --- a/examples/examples/bn254_curve_syscalls.rs +++ b/examples/examples/bn254_curve_syscalls.rs @@ -1,6 +1,6 @@ // Test addition of two curve points. Assert result inside the guest extern crate ceno_rt; -use ceno_rt::syscalls::{syscall_bn254_add, syscall_bn254_double}; +use ceno_syscall::{syscall_bn254_add, syscall_bn254_double}; use substrate_bn::{AffineG1, Fr, G1, Group}; fn bytes_to_words(bytes: [u8; 64]) -> [u32; 16] { diff --git a/examples/examples/bn254_fptower_syscalls.rs b/examples/examples/bn254_fptower_syscalls.rs index deb9836bb..53c499ac2 100644 --- a/examples/examples/bn254_fptower_syscalls.rs +++ b/examples/examples/bn254_fptower_syscalls.rs @@ -1,5 +1,5 @@ extern crate ceno_rt; -use ceno_rt::syscalls::{ +use ceno_syscall::{ syscall_bn254_fp_addmod, syscall_bn254_fp_mulmod, syscall_bn254_fp2_addmod, syscall_bn254_fp2_mulmod, }; diff --git a/examples/examples/keccak_syscall.rs b/examples/examples/keccak_syscall.rs index 56ac44705..3f44a4f1f 100644 --- a/examples/examples/keccak_syscall.rs +++ b/examples/examples/keccak_syscall.rs @@ -3,7 +3,7 @@ //! Iterate multiple times and log the state after each iteration. extern crate ceno_rt; -use ceno_rt::syscalls::syscall_keccak_permute; +use ceno_syscall::syscall_keccak_permute; const ITERATIONS: usize = 100; diff --git a/examples/examples/secp256k1_add_syscall.rs b/examples/examples/secp256k1_add_syscall.rs index 5c66cd3fa..a05a7761f 100644 --- a/examples/examples/secp256k1_add_syscall.rs +++ b/examples/examples/secp256k1_add_syscall.rs @@ -1,6 +1,6 @@ // Test addition of two curve points. Assert result inside the guest extern crate ceno_rt; -use ceno_rt::syscalls::syscall_secp256k1_add; +use ceno_syscall::syscall_secp256k1_add; // Byte repr. of points from https://docs.rs/secp/latest/secp/#arithmetic-1 const P: [u8; 65] = [ diff --git a/examples/examples/secp256k1_decompress_syscall.rs b/examples/examples/secp256k1_decompress_syscall.rs index 6666148ac..6276c31a4 100644 --- a/examples/examples/secp256k1_decompress_syscall.rs +++ b/examples/examples/secp256k1_decompress_syscall.rs @@ -1,6 +1,6 @@ // Test decompression of curve point. Assert result inside the guest extern crate ceno_rt; -use ceno_rt::syscalls::syscall_secp256k1_decompress; +use ceno_syscall::syscall_secp256k1_decompress; // Byte repr. of point P1 from https://docs.rs/secp/latest/secp/#arithmetic-1 const COMPRESSED: [u8; 33] = [ diff --git a/examples/examples/secp256k1_double_syscall.rs b/examples/examples/secp256k1_double_syscall.rs index 8585a8e19..287f03700 100644 --- a/examples/examples/secp256k1_double_syscall.rs +++ b/examples/examples/secp256k1_double_syscall.rs @@ -1,6 +1,6 @@ // Test addition of two curve points. Assert result inside the guest extern crate ceno_rt; -use ceno_rt::syscalls::syscall_secp256k1_double; +use ceno_syscall::syscall_secp256k1_double; // Byte repr. of points from https://docs.rs/secp/latest/secp/#arithmetic-1 const P: [u8; 65] = [ diff --git a/examples/examples/sha256.rs b/examples/examples/sha256.rs index e8661b86b..cdc0cc5d3 100644 --- a/examples/examples/sha256.rs +++ b/examples/examples/sha256.rs @@ -1,6 +1,6 @@ extern crate ceno_rt; -use ceno_rt::syscalls::syscall_sha256_extend; +use ceno_syscall::syscall_sha256_extend; use rkyv::vec::ArchivedVec; // SHA-256 constants diff --git a/examples/examples/sha_extend_syscall.rs b/examples/examples/sha_extend_syscall.rs index 9f9021ce9..0ccd586eb 100644 --- a/examples/examples/sha_extend_syscall.rs +++ b/examples/examples/sha_extend_syscall.rs @@ -2,7 +2,7 @@ extern crate ceno_rt; use std::array; -use ceno_rt::syscalls::syscall_sha256_extend; +use ceno_syscall::syscall_sha256_extend; fn main() { let mut words: [u32; 64] = array::from_fn(|i| i as u32); diff --git a/examples/examples/syscalls.rs b/examples/examples/syscalls.rs index 9318a863b..7c081afe8 100644 --- a/examples/examples/syscalls.rs +++ b/examples/examples/syscalls.rs @@ -1,6 +1,6 @@ use std::array; -use ceno_rt::syscalls::{ +use ceno_syscall::{ syscall_keccak_permute, syscall_secp256k1_add, syscall_secp256k1_decompress, syscall_secp256k1_double, syscall_sha256_extend, }; diff --git a/guest_libs/crypto-primitives/src/ecdsa.rs b/guest_libs/crypto-primitives/src/ecdsa.rs index 6a6d236cd..aeb9a1e75 100644 --- a/guest_libs/crypto-primitives/src/ecdsa.rs +++ b/guest_libs/crypto-primitives/src/ecdsa.rs @@ -16,7 +16,7 @@ use super::utils::AffinePoint as AffinePointTrait; use elliptic_curve::{ - ff, generic_array::typenum::consts::U32, subtle::CtOption, CurveArithmetic, FieldBytes, + CurveArithmetic, FieldBytes, ff, generic_array::typenum::consts::U32, subtle::CtOption, }; use std::{fmt::Debug, ops::Neg}; @@ -48,10 +48,10 @@ type FIELD_BYTES_SIZE = U32; pub trait ECDSACurve where Self: CurveArithmetic< - FieldBytesSize = FIELD_BYTES_SIZE, - AffinePoint = AffinePoint, - ProjectivePoint = ProjectivePoint, - >, + FieldBytesSize = FIELD_BYTES_SIZE, + AffinePoint = AffinePoint, + ProjectivePoint = ProjectivePoint, + >, { type FieldElement: Field + Neg; @@ -84,22 +84,17 @@ pub trait Field: ff::PrimeField { pub type FieldElement = ::FieldElement; /// Alias trait for the [`AffinePointTrait`] with 32 byte field elements. -pub trait ECDSAPoint: -AffinePointTrait + Clone + Copy + Debug + Send + Sync -{ +pub trait ECDSAPoint: AffinePointTrait + Clone + Copy + Debug + Send + Sync { #[inline] fn from(x: &[u8], y: &[u8]) -> Self { >::from(x, y) } } -impl

ECDSAPoint for P where - P: AffinePointTrait + Clone + Copy + Debug + Send + Sync -{ -} +impl

ECDSAPoint for P where P: AffinePointTrait + Clone + Copy + Debug + Send + Sync {} pub mod ecdh { - pub use elliptic_curve::ecdh::{diffie_hellman, EphemeralSecret, SharedSecret}; + pub use elliptic_curve::ecdh::{EphemeralSecret, SharedSecret, diffie_hellman}; use super::{AffinePoint, ECDSACurve, Field}; diff --git a/guest_libs/crypto-primitives/src/ecdsa/affine.rs b/guest_libs/crypto-primitives/src/ecdsa/affine.rs index 5438ff2e0..0a5dbda30 100644 --- a/guest_libs/crypto-primitives/src/ecdsa/affine.rs +++ b/guest_libs/crypto-primitives/src/ecdsa/affine.rs @@ -9,17 +9,17 @@ //! of projective arithmetic for performance. use super::{ - ECDSACurve, ECDSAPoint, Field, FieldElement, AffinePointTrait, FIELD_BYTES_SIZE_USIZE, + AffinePointTrait, ECDSACurve, ECDSAPoint, FIELD_BYTES_SIZE_USIZE, Field, FieldElement, }; use elliptic_curve::{ + FieldBytes, PrimeField, ff::Field as _, group::GroupEncoding, point::{AffineCoordinates, DecompactPoint, DecompressPoint}, sec1::{self, CompressedPoint, EncodedPoint, FromEncodedPoint, ToEncodedPoint}, subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}, zeroize::DefaultIsZeroes, - FieldBytes, PrimeField, }; use std::ops::Neg; @@ -40,7 +40,9 @@ impl AffinePoint { let y_slice = y_slice.as_mut_slice(); y_slice.reverse(); - AffinePoint { inner: ::from(x_slice, y_slice) } + AffinePoint { + inner: ::from(x_slice, y_slice), + } } /// Get the x and y field elements of the point. @@ -70,12 +72,16 @@ impl AffinePoint { /// Get the generator point. pub fn generator() -> Self { - AffinePoint { inner: C::SP1AffinePoint::GENERATOR } + AffinePoint { + inner: C::SP1AffinePoint::GENERATOR, + } } /// Get the identity point. pub fn identity() -> Self { - AffinePoint { inner: C::SP1AffinePoint::identity() } + AffinePoint { + inner: C::SP1AffinePoint::identity(), + } } /// Check if the point is the identity point. @@ -177,11 +183,7 @@ impl ConditionallySelectable for AffinePoint { // Conditional select is a constant time if-else operation. // // In the SP1 vm, there are no attempts made to prevent side channel attacks. - if choice.into() { - *b - } else { - *a - } + if choice.into() { *b } else { *a } } } diff --git a/guest_libs/crypto-primitives/src/ecdsa/projective.rs b/guest_libs/crypto-primitives/src/ecdsa/projective.rs index d20c93aaf..0b46dbf49 100644 --- a/guest_libs/crypto-primitives/src/ecdsa/projective.rs +++ b/guest_libs/crypto-primitives/src/ecdsa/projective.rs @@ -8,13 +8,13 @@ //! Note: When performing curve operations, accelerated crates for SP1 use affine arithmetic instead //! of projective arithmetic for performance. -use super::{AffinePoint, ECDSACurve, AffinePointTrait}; +use super::{AffinePoint, AffinePointTrait, ECDSACurve}; use elliptic_curve::{ + CurveArithmetic, FieldBytes, group::{cofactor::CofactorGroup, prime::PrimeGroup}, ops::MulByGenerator, sec1::{CompressedPoint, ModulusSize}, - CurveArithmetic, FieldBytes, }; use elliptic_curve::{ @@ -44,7 +44,9 @@ pub struct ProjectivePoint { impl ProjectivePoint { pub fn identity() -> Self { - ProjectivePoint { inner: AffinePoint::::identity() } + ProjectivePoint { + inner: AffinePoint::::identity(), + } } /// Convert the projective point to an affine point. @@ -70,7 +72,9 @@ impl ProjectivePoint { } fn from_zkvm_point(p: C::SP1AffinePoint) -> Self { - Self { inner: AffinePoint { inner: p } } + Self { + inner: AffinePoint { inner: p }, + } } } @@ -114,7 +118,9 @@ impl Group for ProjectivePoint { } fn generator() -> Self { - Self { inner: AffinePoint::::generator() } + Self { + inner: AffinePoint::::generator(), + } } fn is_identity(&self) -> Choice { @@ -162,7 +168,8 @@ impl> Mul for ProjectivePoint { impl> MulAssign for ProjectivePoint { fn mul_assign(&mut self, rhs: T) { - self.as_mut_zkvm_point().mul_assign(&be_bytes_to_le_words(rhs.borrow().to_repr())); + self.as_mut_zkvm_point() + .mul_assign(&be_bytes_to_le_words(rhs.borrow().to_repr())); } } @@ -247,13 +254,15 @@ impl AddAssign<&ProjectivePoint> for ProjectivePoint { impl SubAssign> for ProjectivePoint { fn sub_assign(&mut self, rhs: ProjectivePoint) { - self.as_mut_zkvm_point().add_assign(rhs.neg().as_zkvm_point()); + self.as_mut_zkvm_point() + .add_assign(rhs.neg().as_zkvm_point()); } } impl SubAssign<&ProjectivePoint> for ProjectivePoint { fn sub_assign(&mut self, rhs: &ProjectivePoint) { - self.as_mut_zkvm_point().add_assign(rhs.neg().as_zkvm_point()); + self.as_mut_zkvm_point() + .add_assign(rhs.neg().as_zkvm_point()); } } @@ -313,7 +322,8 @@ impl SubAssign> for ProjectivePoint { fn sub_assign(&mut self, rhs: AffinePoint) { let projective = ProjectivePoint { inner: rhs }.neg(); - self.as_mut_zkvm_point().add_assign(projective.as_zkvm_point()); + self.as_mut_zkvm_point() + .add_assign(projective.as_zkvm_point()); } } @@ -321,7 +331,8 @@ impl SubAssign<&AffinePoint> for ProjectivePoint { fn sub_assign(&mut self, rhs: &AffinePoint) { let projective = ProjectivePoint { inner: *rhs }.neg(); - self.as_mut_zkvm_point().add_assign(projective.as_zkvm_point()); + self.as_mut_zkvm_point() + .add_assign(projective.as_zkvm_point()); } } @@ -329,7 +340,9 @@ impl DefaultIsZeroes for ProjectivePoint {} impl ConditionallySelectable for ProjectivePoint { fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { - Self { inner: AffinePoint::conditional_select(&a.inner, &b.inner, choice) } + Self { + inner: AffinePoint::conditional_select(&a.inner, &b.inner, choice), + } } } @@ -404,7 +417,9 @@ fn be_bytes_to_le_words>(mut bytes: T) -> [u32; 8] { let bytes = bytes.as_mut(); bytes.reverse(); - let mut iter = bytes.chunks(4).map(|b| u32::from_le_bytes(b.try_into().unwrap())); + let mut iter = bytes + .chunks(4) + .map(|b| u32::from_le_bytes(b.try_into().unwrap())); core::array::from_fn(|_| iter.next().unwrap()) } diff --git a/guest_libs/crypto-primitives/src/lib.rs b/guest_libs/crypto-primitives/src/lib.rs index 57f3a8c17..8cd3c7f45 100644 --- a/guest_libs/crypto-primitives/src/lib.rs +++ b/guest_libs/crypto-primitives/src/lib.rs @@ -1,3 +1,3 @@ -pub mod utils; pub mod ecdsa; pub mod secp256k1; +pub mod utils; diff --git a/guest_libs/crypto-primitives/src/secp256k1.rs b/guest_libs/crypto-primitives/src/secp256k1.rs index 07d301f59..b00af7744 100644 --- a/guest_libs/crypto-primitives/src/secp256k1.rs +++ b/guest_libs/crypto-primitives/src/secp256k1.rs @@ -1,9 +1,6 @@ //! Copied from -use crate::{ - - utils::{AffinePoint, WeierstrassAffinePoint, WeierstrassPoint}, -}; +use crate::utils::{AffinePoint, WeierstrassAffinePoint, WeierstrassPoint}; use ceno_syscall::{syscall_secp256k1_add, syscall_secp256k1_double}; /// The number of limbs in [Secp256k1Point]. diff --git a/guest_libs/crypto-primitives/src/utils.rs b/guest_libs/crypto-primitives/src/utils.rs index d07380686..e6f95a6fc 100644 --- a/guest_libs/crypto-primitives/src/utils.rs +++ b/guest_libs/crypto-primitives/src/utils.rs @@ -119,7 +119,10 @@ pub enum MulAssignError { /// Converts a slice of words to a byte array in little endian. pub fn words_to_bytes_le(words: &[u32]) -> Vec { - words.iter().flat_map(|word| word.to_le_bytes().to_vec()).collect::>() + words + .iter() + .flat_map(|word| word.to_le_bytes().to_vec()) + .collect::>() } /// Converts a byte array in little endian to a slice of words. @@ -190,4 +193,4 @@ pub trait WeierstrassAffinePoint: AffinePoint { // Case 5: Default addition. self.add_assign(other); } -} \ No newline at end of file +} diff --git a/guest_libs/crypto/Cargo.toml b/guest_libs/crypto/Cargo.toml index 1376ab07b..5648636ba 100644 --- a/guest_libs/crypto/Cargo.toml +++ b/guest_libs/crypto/Cargo.toml @@ -11,8 +11,7 @@ version = "0.1.0" [dependencies] ceno_keccak = { path = "../keccak" } -ceno_rt = { path = "../../ceno_rt" } -k256 = { version = "0.13", default-features = false, features = ["std", "ecdsa"] } +k256 = { git = "https://github.com/scroll-tech/elliptic-curves", branch = "ceno/k256-13.4", default-features = false, features = ["std", "ecdsa"] } thiserror.workspace = true [dev-dependencies] diff --git a/guest_libs/crypto/src/secp256k1.rs b/guest_libs/crypto/src/secp256k1.rs index 93bdf63f3..0f21eea4b 100644 --- a/guest_libs/crypto/src/secp256k1.rs +++ b/guest_libs/crypto/src/secp256k1.rs @@ -1,211 +1,38 @@ use crate::CenoCryptoError; use ceno_keccak::{Hasher, Keccak}; -use ceno_rt::syscalls::{syscall_secp256k1_add, syscall_secp256k1_double}; -use k256::{ - AffinePoint, EncodedPoint, FieldBytes, NonZeroScalar, Scalar, Secp256k1, U256, - ecdsa::{Error, RecoveryId, Signature, hazmat::bits2field}, - elliptic_curve::{ - Curve, Field, FieldBytesEncoding, PrimeField, - bigint::CheckedAdd, - ops::{Invert, Reduce}, - sec1::{FromEncodedPoint, ToEncodedPoint}, - }, -}; - -type UntaggedUncompressedPoint = [u8; 64]; - -#[repr(align(4))] -struct Aligned64([u8; 64]); +use k256::ecdsa::{RecoveryId, Signature, VerifyingKey}; /// secp256k1 ECDSA signature recovery. #[inline] pub fn secp256k1_ecrecover( sig: &[u8; 64], - recid: u8, + mut recid: u8, msg: &[u8; 32], ) -> Result<[u8; 32], CenoCryptoError> { // Copied from - let mut signature = Signature::from_slice(sig)?; - let mut recid = recid; + // parse signature + let mut sig = Signature::from_slice(sig.as_slice())?; // normalize signature and flip recovery id if needed. - if let Some(sig_normalized) = signature.normalize_s() { - signature = sig_normalized; + if let Some(sig_normalized) = sig.normalize_s() { + sig = sig_normalized; recid ^= 1; } let recid = RecoveryId::from_byte(recid).expect("recovery ID is valid"); // recover key - let recovered_key = recover_from_prehash(&msg[..], &signature, recid)?; - + let recovered_key = VerifyingKey::recover_from_prehash(&msg[..], &sig, recid)?; + // hash it let mut hasher = Keccak::v256(); - hasher.update(&recovered_key); let mut hash = [0u8; 32]; + hasher.update( + &recovered_key + .to_encoded_point(/* compress = */ false) + .as_bytes()[1..], + ); hasher.finalize(&mut hash); + // truncate to 20 bytes hash[..12].fill(0); Ok(hash) } - -/// Copied from -/// Modified to use ceno syscalls -fn recover_from_prehash( - prehash: &[u8], - signature: &Signature, - recovery_id: RecoveryId, -) -> Result { - let (r, s) = signature.split_scalars(); - let prehash: FieldBytes = bits2field::(prehash)?; - let z = >::reduce_bytes(&prehash); - - let mut r_bytes = r.to_repr(); - if recovery_id.is_x_reduced() { - let decoded: U256 = FieldBytesEncoding::::decode_field_bytes(&r_bytes); - match decoded.checked_add(&Secp256k1::ORDER).into_option() { - Some(restored) => { - r_bytes = >::encode_field_bytes(&restored) - } - // No reduction should happen here if r was reduced - None => return Err(Error::new()), - }; - } - - // Modified part: use ceno syscall to decompress point - // Original: - // let R = AffinePoint::decompress(&r_bytes, u8::from(recovery_id.is_y_odd()).into()); - let r_point = { - let mut buf = Aligned64([0u8; 64]); - buf.0[..32].copy_from_slice(&r_bytes); - - // SAFETY: - // [x] The input array should be 64 bytes long, with the first 32 bytes containing the X coordinate in - // big-endian format. - // [x] The caller must ensure that `point` is valid pointer to data that is aligned along a four byte - // boundary. - ceno_rt::syscalls::syscall_secp256k1_decompress(&mut buf.0, recovery_id.is_y_odd()); - let point = EncodedPoint::from_untagged_bytes((&buf.0).into()); - AffinePoint::from_encoded_point(&point) - }; - - let Some(r_point) = r_point.into_option() else { - return Err(Error::new()); - }; - - // TODO: scalar syscalls - let r_inv = *r.invert(); - let u1 = -(r_inv * z); - let u2 = r_inv * *s; - - // Original: - // ProjectivePoint::lincomb(&ProjectivePoint::GENERATOR, &u1, &r_point, &u2) - // Equivalent to: G * u1 + R * u2 - let pk_words = lincomb(u1, &r_point, u2)?; - - let bytes = words_to_untagged_bytes(pk_words); - - // Original: - // let vk = VerifyingKey::from_affine(pk.to_affine())?; - // // Ensure signature verifies with the recovered key - // vk.verify_prehash(prehash, signature)?; - verify_prehash(&z, (&r, &s), &bytes)?; - - Ok(bytes) -} - -/// Copied from -fn verify_prehash( - z: &Scalar, - (r, s): (&NonZeroScalar, &NonZeroScalar), - bytes: &UntaggedUncompressedPoint, -) -> Result<(), Error> { - let q = EncodedPoint::from_untagged_bytes(bytes.into()); - let q = AffinePoint::from_encoded_point(&q) - .into_option() - .ok_or(Error::new())?; - - let s_inv = *s.invert_vartime(); - let u1 = z * &s_inv; - let u2 = **r * s_inv; - - // Original: - // let x = ProjectivePoint::lincomb(&ProjectivePoint::GENERATOR, &u1, &q, &u2) - // .to_affine() - // .x(); - // Equivalent to: G * u1 + q * u2 - let p = lincomb(u1, &q, u2)?; - let p_bytes = words_to_untagged_bytes(p); - let x = FieldBytes::from_slice(&p_bytes[..32]); - - if **r == >::reduce_bytes(x) { - Ok(()) - } else { - Err(Error::new()) - } -} - -#[inline] -fn lincomb(u1: Scalar, p: &AffinePoint, u2: Scalar) -> Result<[u32; 16], Error> { - Ok(match (u1 == Scalar::ZERO, u2 == Scalar::ZERO) { - (false, false) => { - let mut p1 = secp256k1_mul(&AffinePoint::GENERATOR, u1); - let p2 = secp256k1_mul(p, u2); - syscall_secp256k1_add(&mut p1, &p2); - p1 - } - (true, false) => secp256k1_mul(p, u2), - (false, true) => secp256k1_mul(&AffinePoint::GENERATOR, u1), - (true, true) => return Err(Error::new()), - }) -} - -fn secp256k1_mul(point: &AffinePoint, scalar: Scalar) -> [u32; 16] { - let mut base = point_to_words(point.to_encoded_point(false)); - let mut acc: [u32; 16] = [0; 16]; - let mut acc_init = false; - - let mut k = scalar; - while !k.is_zero_vartime() { - if bool::from(k.is_odd()) { - if !acc_init { - acc = base; - acc_init = true; - } else { - // SAFETY: syscall requires point not to be infinity - let tmp = base; - syscall_secp256k1_add(&mut acc, &tmp) - } - } - syscall_secp256k1_double(&mut base); - k = k.shr_vartime(1); - } - - acc -} - -/// `bytes` is expected to contain the uncompressed representation of -/// a curve point, as described in https://docs.rs/secp/latest/secp/struct.Point.html -/// -/// The return value is an array of words compatible with the sp1 syscall for `add` and `double` -/// Notably, these words should encode the X and Y coordinates of the point -/// in "little endian" and not "big endian" as is the case of secp -fn point_to_words(point: EncodedPoint) -> [u32; 16] { - debug_assert!(!point.is_compressed()); - // ignore the tag byte (specific to the secp repr.) - let mut bytes: [u8; 64] = point.as_bytes()[1..].try_into().unwrap(); - - // Reverse the order of bytes for each coordinate - bytes[0..32].reverse(); - bytes[32..].reverse(); - std::array::from_fn(|i| u32::from_le_bytes(bytes[4 * i..4 * (i + 1)].try_into().unwrap())) -} - -fn words_to_untagged_bytes(words: [u32; 16]) -> UntaggedUncompressedPoint { - let mut bytes = [0u8; 64]; - for i in 0..16 { - bytes[4 * i..4 * (i + 1)].copy_from_slice(&words[i].to_le_bytes()); - } - // Reverse the order of bytes for each coordinate - bytes[..32].reverse(); - bytes[32..].reverse(); - bytes -} diff --git a/guest_libs/keccak/Cargo.toml b/guest_libs/keccak/Cargo.toml index 879d748eb..59e53a37b 100644 --- a/guest_libs/keccak/Cargo.toml +++ b/guest_libs/keccak/Cargo.toml @@ -10,5 +10,5 @@ repository = "https://github.com/scroll-tech/ceno" version = "0.1.0" [dependencies] -ceno_rt = { path = "../../ceno_rt" } +ceno_syscall = { path = "../../ceno_syscall" } tiny-keccak = { workspace = true } diff --git a/guest_libs/keccak/src/lib.rs b/guest_libs/keccak/src/lib.rs index eb024d542..0a9b53e36 100644 --- a/guest_libs/keccak/src/lib.rs +++ b/guest_libs/keccak/src/lib.rs @@ -10,7 +10,7 @@ pub use tiny_keccak::{self, Hasher}; mod vendor; pub use vendor::keccak::Keccak; -pub use ceno_rt::syscalls::syscall_keccak_permute as keccakf; +pub use ceno_syscall::syscall_keccak_permute as keccakf; mod keccakf { use crate::{ diff --git a/guest_libs/keccak/src/vendor.rs b/guest_libs/keccak/src/vendor.rs index c13f727bf..b3c26bc0b 100644 --- a/guest_libs/keccak/src/vendor.rs +++ b/guest_libs/keccak/src/vendor.rs @@ -1,6 +1,6 @@ //! Private types and traits copied from the `tiny-keccak`. -use ceno_rt::syscalls::KECCAK_STATE_WORDS; +use ceno_syscall::KECCAK_STATE_WORDS; pub mod keccak; From 18dbd43469ee9bca2505da8ab21803939611ae7f Mon Sep 17 00:00:00 2001 From: lightsing Date: Mon, 13 Oct 2025 11:11:06 +0800 Subject: [PATCH 10/15] update ref --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 1a0f98564..52359cd3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2468,7 +2468,7 @@ dependencies = [ [[package]] name = "k256" version = "0.13.4" -source = "git+https://github.com/scroll-tech/elliptic-curves?branch=ceno%2Fk256-13.4#2881a464bc20636189bd61ee79de4638e5c050fa" +source = "git+https://github.com/scroll-tech/elliptic-curves?branch=ceno%2Fk256-13.4#8fde0eef0eeabbafa043262b0ea58adde6bd8d2b" dependencies = [ "ceno_crypto_primitives 0.1.0 (git+https://github.com/scroll-tech/ceno?branch=feat%2Fsecp256k1-guest)", "cfg-if", From 1a428338c16e37d89149ea6cdd7a365945ab14b9 Mon Sep 17 00:00:00 2001 From: lightsing Date: Mon, 13 Oct 2025 15:37:55 +0800 Subject: [PATCH 11/15] lint --- Cargo.lock | 4 ++-- ceno_syscall/Cargo.toml | 1 + guest_libs/crypto-primitives/Cargo.toml | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52359cd3b..8611703fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -923,7 +923,7 @@ dependencies = [ [[package]] name = "ceno_crypto_primitives" version = "0.1.0" -source = "git+https://github.com/scroll-tech/ceno?branch=feat%2Fsecp256k1-guest#98f45d56f5c44455026c0a7049e1418044feb9dd" +source = "git+https://github.com/scroll-tech/ceno?branch=feat%2Fsecp256k1-guest#f0876e88bf734f045780eafd36d7def8d7982c9a" dependencies = [ "ceno_syscall 0.1.0 (git+https://github.com/scroll-tech/ceno?branch=feat%2Fsecp256k1-guest)", "elliptic-curve", @@ -989,7 +989,7 @@ version = "0.1.0" [[package]] name = "ceno_syscall" version = "0.1.0" -source = "git+https://github.com/scroll-tech/ceno?branch=feat%2Fsecp256k1-guest#98f45d56f5c44455026c0a7049e1418044feb9dd" +source = "git+https://github.com/scroll-tech/ceno?branch=feat%2Fsecp256k1-guest#f0876e88bf734f045780eafd36d7def8d7982c9a" [[package]] name = "ceno_zkvm" diff --git a/ceno_syscall/Cargo.toml b/ceno_syscall/Cargo.toml index 5fded0a10..119849ed7 100644 --- a/ceno_syscall/Cargo.toml +++ b/ceno_syscall/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "ceno_syscall" +description = "Ceno zkVM Syscall Interface" categories.workspace = true edition.workspace = true keywords.workspace = true diff --git a/guest_libs/crypto-primitives/Cargo.toml b/guest_libs/crypto-primitives/Cargo.toml index c5954610f..207dde4b3 100644 --- a/guest_libs/crypto-primitives/Cargo.toml +++ b/guest_libs/crypto-primitives/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "ceno_crypto_primitives" +description = "Cryptographic primitives for Ceno zkVM guest" categories.workspace = true edition.workspace = true keywords.workspace = true From 666c52b2c62c8f344c30beb7b8127b95797cdcbd Mon Sep 17 00:00:00 2001 From: lightsing Date: Mon, 13 Oct 2025 15:44:25 +0800 Subject: [PATCH 12/15] taplo fmt --- ceno_syscall/Cargo.toml | 4 ++-- guest_libs/crypto-primitives/Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ceno_syscall/Cargo.toml b/ceno_syscall/Cargo.toml index 119849ed7..436a58904 100644 --- a/ceno_syscall/Cargo.toml +++ b/ceno_syscall/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "ceno_syscall" -description = "Ceno zkVM Syscall Interface" categories.workspace = true +description = "Ceno zkVM Syscall Interface" edition.workspace = true keywords.workspace = true license.workspace = true +name = "ceno_syscall" readme.workspace = true repository.workspace = true version.workspace = true diff --git a/guest_libs/crypto-primitives/Cargo.toml b/guest_libs/crypto-primitives/Cargo.toml index 207dde4b3..df9302ad9 100644 --- a/guest_libs/crypto-primitives/Cargo.toml +++ b/guest_libs/crypto-primitives/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "ceno_crypto_primitives" -description = "Cryptographic primitives for Ceno zkVM guest" categories.workspace = true +description = "Cryptographic primitives for Ceno zkVM guest" edition.workspace = true keywords.workspace = true license.workspace = true +name = "ceno_crypto_primitives" readme.workspace = true repository.workspace = true version.workspace = true From 152bdda282091bded4c05f97e207761606adffc4 Mon Sep 17 00:00:00 2001 From: lightsing Date: Mon, 13 Oct 2025 16:09:22 +0800 Subject: [PATCH 13/15] lock generic-array --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index af94968f7..16e66caaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ cfg-if = "1.0" clap = { version = "4.5", features = ["derive"] } criterion = { version = "0.5", features = ["html_reports"] } either = { version = "1.15.*", features = ["serde"] } -generic-array = { version = "*", features = ["alloc", "serde"] } +generic-array = { version = "1.2", features = ["alloc", "serde"] } itertools = "0.13" ndarray = "*" num-derive = "0.4" From a2ff48940bdb9166e4bdaeee30e7792fa3cd76c0 Mon Sep 17 00:00:00 2001 From: lightsing Date: Tue, 14 Oct 2025 10:16:45 +0800 Subject: [PATCH 14/15] add README --- guest_libs/crypto-primitives/README.md | 3 +++ guest_libs/crypto/README.md | 13 +++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 guest_libs/crypto-primitives/README.md diff --git a/guest_libs/crypto-primitives/README.md b/guest_libs/crypto-primitives/README.md new file mode 100644 index 000000000..dd9a2ff28 --- /dev/null +++ b/guest_libs/crypto-primitives/README.md @@ -0,0 +1,3 @@ +# Cryptographic primitives for Ceno zkVM guest + +Modified from [sp1-lib](https://github.com/succinctlabs/sp1/tree/dev/crates/zkvm/lib/src) diff --git a/guest_libs/crypto/README.md b/guest_libs/crypto/README.md index e0f8fe4b0..6e5237a7f 100644 --- a/guest_libs/crypto/README.md +++ b/guest_libs/crypto/README.md @@ -51,3 +51,16 @@ fn main() { // Your other code here } ``` + +## Development + +### k256 + +This crate use patched [k256](https://docs.rs/k256/latest/k256/) crate. + +Our patched [k256](https://github.com/scroll-tech/elliptic-curves) is modified from the sp1 fork +[k256](https://github.com/sp1-patches/elliptic-curves/tree/patch-k256-13.4-sp1-5.0.0/k256). + +The sp1 patches: https://github.com/RustCrypto/elliptic-curves/compare/k256/v0.13.4...sp1-patches:elliptic-curves:patch-k256-13.4-sp1-5.0.0 + +The scroll-tech patches: https://github.com/RustCrypto/elliptic-curves/compare/k256/v0.13.4...scroll-tech:elliptic-curves:ceno/k256-13.4 From b1719dfe0b6b2abe5020d4f9b84c5989b8ba189a Mon Sep 17 00:00:00 2001 From: lightsing Date: Tue, 14 Oct 2025 15:14:25 +0800 Subject: [PATCH 15/15] fix --- examples/examples/syscalls.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/examples/syscalls.rs b/examples/examples/syscalls.rs index 7c081afe8..691823c14 100644 --- a/examples/examples/syscalls.rs +++ b/examples/examples/syscalls.rs @@ -1,3 +1,5 @@ +extern crate ceno_rt; + use std::array; use ceno_syscall::{