Skip to content

Commit f641a56

Browse files
committed
add secp256k1 ecrecover precompile
1 parent 9130e7e commit f641a56

File tree

14 files changed

+274
-12
lines changed

14 files changed

+274
-12
lines changed

Cargo.lock

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ceno_host/tests/test_elf.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,18 @@ fn test_secp256k1_decompress() -> Result<()> {
433433
Ok(())
434434
}
435435

436+
#[test]
437+
fn test_secp256k1_ecrecover() -> Result<()> {
438+
let _ = ceno_host::run(
439+
CENO_PLATFORM,
440+
ceno_examples::secp256k1_ecrecover,
441+
&CenoStdin::default(),
442+
None,
443+
);
444+
445+
Ok(())
446+
}
447+
436448
#[test]
437449
fn test_sha256_extend() -> Result<()> {
438450
let program_elf = ceno_examples::sha_extend_syscall;

ceno_rt/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ version = "0.1.0"
1111

1212
[dependencies]
1313
getrandom = { version = "0.2.15", features = ["custom"], default-features = false }
14+
getrandom_v3 = { package = "getrandom", version = "0.3", default-features = false }
1415
rkyv.workspace = true

ceno_rt/src/lib.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,23 @@ pub fn my_get_random(buf: &mut [u8]) -> Result<(), Error> {
8686
}
8787
register_custom_getrandom!(my_get_random);
8888

89+
/// Custom getrandom implementation for getrandom v0.3
90+
///
91+
/// see also: <https://docs.rs/getrandom/0.3.3/getrandom/#custom-backend>
92+
///
93+
/// # Safety
94+
/// - `dest` must be valid for writes of `len` bytes.
95+
#[unsafe(no_mangle)]
96+
pub unsafe extern "Rust" fn __getrandom_v03_custom(
97+
dest: *mut u8,
98+
len: usize,
99+
) -> Result<(), getrandom_v3::Error> {
100+
unsafe {
101+
sys_rand(dest, len);
102+
}
103+
Ok(())
104+
}
105+
89106
pub fn halt(exit_code: u32) -> ! {
90107
#[cfg(target_arch = "riscv32")]
91108
unsafe {

ceno_rt/src/syscalls.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@ pub fn syscall_keccak_permute(state: &mut [u64; KECCAK_STATE_WORDS]) {
4848
/// - 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.
4949
/// - The result is stored in the first point.
5050
#[allow(unused_variables)]
51-
pub fn syscall_secp256k1_add(p: *mut [u32; 16], q: *mut [u32; 16]) {
51+
pub fn syscall_secp256k1_add(p: &mut [u32; 16], q: &[u32; 16]) {
5252
#[cfg(target_os = "zkvm")]
5353
unsafe {
54+
let p = p.as_mut_ptr();
55+
let q = q.as_ptr();
5456
asm!(
5557
"ecall",
5658
in("t0") SECP256K1_ADD,
@@ -73,9 +75,10 @@ pub fn syscall_secp256k1_add(p: *mut [u32; 16], q: *mut [u32; 16]) {
7375
/// For example, the word `p[0]` contains the least significant `4` bytes of `X` and their significance is maintained w.r.t `p[0]`
7476
/// - The result is stored in p
7577
#[allow(unused_variables)]
76-
pub fn syscall_secp256k1_double(p: *mut [u32; 16]) {
78+
pub fn syscall_secp256k1_double(p: &mut [u32; 16]) {
7779
#[cfg(target_os = "zkvm")]
7880
unsafe {
81+
let p = p.as_mut_ptr();
7982
asm!(
8083
"ecall",
8184
in("t0") SECP256K1_DOUBLE,

examples-builder/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ fn build_elfs() {
5757
}
5858
rerun_all_but_target(Path::new("../examples"));
5959
rerun_all_but_target(Path::new("../ceno_rt"));
60+
rerun_all_but_target(Path::new("../guest_libs"));
6061
}
6162

6263
fn main() {

examples/.cargo/config.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,7 @@ rustflags = [
2525
"-Zlocation-detail=none",
2626
"-C",
2727
"passes=lower-atomic",
28+
'--cfg',
29+
'getrandom_backend="custom"', # getrandom v3.3+ requires this cfg to use a custom getrandom implementation
2830
]
2931
target = "../ceno_rt/riscv32im-ceno-zkvm-elf.json"

examples/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ version = "0.1.0"
1111

1212
[dependencies]
1313
alloy-primitives = { version = "1.3", features = ["native-keccak"] }
14+
ceno_crypto = { path = "../guest_libs/crypto" }
1415
ceno_keccak = { path = "../guest_libs/keccak" }
1516
ceno_rt = { path = "../ceno_rt" }
17+
getrandom = { version = "0.3" }
1618
rand.workspace = true
1719
tiny-keccak.workspace = true
1820

examples/examples/secp256k1_add_syscall.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ fn bytes_to_words(bytes: [u8; 65]) -> [u32; 16] {
4141
}
4242
fn main() {
4343
let mut p: DecompressedPoint = bytes_to_words(P);
44-
let mut q: DecompressedPoint = bytes_to_words(Q);
44+
let q: DecompressedPoint = bytes_to_words(Q);
4545
let p_plus_q: DecompressedPoint = bytes_to_words(P_PLUS_Q);
4646

47-
syscall_secp256k1_add(&mut p, &mut q);
47+
syscall_secp256k1_add(&mut p, &q);
4848
assert_eq!(p, p_plus_q);
4949
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Test ecrecover of real world signatures from scroll mainnet. Assert result inside the guest.
2+
extern crate ceno_rt;
3+
4+
use alloy_primitives::{Address, B256, address, b256, hex};
5+
use ceno_crypto::secp256k1::secp256k1_ecrecover;
6+
7+
const TEST_CASES: [(&[u8], u8, B256, Address); 5] = [
8+
// (sig, recid, tx_hash, signer)
9+
(
10+
&hex!(
11+
"15a7bb615483f66a697431cd414294b6bd1e1b9b9d6d163cfd97290ea77b53061810c4d228e424087ad77ee75bb25e77c832ad9038b89f7e573a34b574648348"
12+
),
13+
0,
14+
b256!("b329f831352e37f4426583986465b065d9c867901b42f576f00ef36dfac1cfdf"),
15+
address!("ca585e09df67e83106c9bcd839c989ace537bf95"),
16+
),
17+
(
18+
&hex!(
19+
"870077f742ca34760810033caf13c99e90e207db6f820124b827907e9658d7d04f302d6675c8625c02fc95c131a3ce77e7f90dba10dbda368efeaaba9be60916"
20+
),
21+
0,
22+
b256!("4e13990772a9454712c7560ad8a64b845fd472b913b90d680867ab3dad56a18d"),
23+
address!("a79c12bcf11133af01b6b20f16f8aafaecdebc93"),
24+
),
25+
(
26+
&hex!(
27+
"455a6249244154e8f5d516a3036e26576449bef05171657dbf3a5d7b9c02fe96629f7eb0aa2a006ff4ac6fc0523a6f5a365cf375240f5a560b1972eb21cec087"
28+
),
29+
1,
30+
b256!("4dedbd995fc79db979c6484132568fe30fdf6bfa8b64ac74ba844cc30e764b0c"),
31+
address!("c623f214c8eefc771147c5806be250db39555555"),
32+
),
33+
(
34+
&hex!(
35+
"854c4656c421158b4e5d8c29ccc3adcaee329587cee630398f3ce2e32745e45b67b1fc40e3206c70a75bcdf3c877c26874c75c2fabd5566c85b58c7c7d872e00"
36+
),
37+
0,
38+
b256!("e4559e37c72fb3df0349df42b3aa0e94607287ecb3e6530b7c50ed984e0428a2"),
39+
address!("b82def35c814584d3d929cfb3a1fb1b886b6e57b"),
40+
),
41+
(
42+
&hex!(
43+
"004a0ac1306d096c06fb77f82b76f43fb2459638826f4846444686b3036b9a4b3d6bf124bf22f23b851adfa2c4bdc670b4ecb5129186a4e89032916a77a56b90"
44+
),
45+
0,
46+
b256!("83e5e11daa2d14736ab1d578c41250c6f6445782c215684a18f67b44686ccb90"),
47+
address!("0a6f0ed4896be1caa9e37047578e7519481f22ea"),
48+
),
49+
];
50+
51+
fn main() {
52+
for (sig, recid, tx_hash, signer) in TEST_CASES {
53+
let recovered = secp256k1_ecrecover(sig.try_into().unwrap(), recid, &tx_hash.0).unwrap();
54+
assert_eq!(&recovered[12..], &signer.0);
55+
}
56+
}

0 commit comments

Comments
 (0)