ecrecover/
ecrecover.rs

1use clap::Parser;
2use eyre::Result;
3use k256::ecdsa::{SigningKey, VerifyingKey};
4use num_bigint::BigUint;
5use openvm_algebra_circuit::{
6    ModularExtension, ModularExtensionExecutor, ModularExtensionPeriphery,
7};
8use openvm_algebra_transpiler::ModularTranspilerExtension;
9use openvm_benchmarks_prove::util::BenchmarkCli;
10use openvm_circuit::{
11    arch::{instructions::exe::VmExe, SystemConfig},
12    derive::VmConfig,
13};
14use openvm_ecc_circuit::{
15    CurveConfig, WeierstrassExtension, WeierstrassExtensionExecutor, WeierstrassExtensionPeriphery,
16    SECP256K1_CONFIG,
17};
18use openvm_ecc_transpiler::EccTranspilerExtension;
19use openvm_keccak256_circuit::{Keccak256, Keccak256Executor, Keccak256Periphery};
20use openvm_keccak256_transpiler::Keccak256TranspilerExtension;
21use openvm_rv32im_circuit::{
22    Rv32I, Rv32IExecutor, Rv32IPeriphery, Rv32Io, Rv32IoExecutor, Rv32IoPeriphery, Rv32M,
23    Rv32MExecutor, Rv32MPeriphery,
24};
25use openvm_rv32im_transpiler::{
26    Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension,
27};
28use openvm_stark_backend::p3_field::{FieldAlgebra, PrimeField32};
29use openvm_stark_sdk::{bench::run_with_metric_collection, p3_baby_bear::BabyBear};
30use openvm_transpiler::{transpiler::Transpiler, FromElf};
31use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng};
32use serde::{Deserialize, Serialize};
33use tiny_keccak::{Hasher, Keccak};
34
35fn make_input(signing_key: &SigningKey, msg: &[u8]) -> Vec<BabyBear> {
36    let mut hasher = Keccak::v256();
37    hasher.update(msg);
38    let mut prehash = [0u8; 32];
39    hasher.finalize(&mut prehash);
40    let (signature, recid) = signing_key.sign_prehash_recoverable(&prehash).unwrap();
41    // Input format: https://www.evm.codes/precompiled?fork=cancun#0x01
42    let mut input = prehash.to_vec();
43    let v = recid.to_byte() + 27u8;
44    input.extend_from_slice(&[0; 31]);
45    input.push(v);
46    input.extend_from_slice(signature.to_bytes().as_ref());
47
48    input.into_iter().map(BabyBear::from_canonical_u8).collect()
49}
50
51#[derive(Clone, Debug, VmConfig, derive_new::new, Serialize, Deserialize)]
52pub struct Rv32ImEcRecoverConfig {
53    #[system]
54    pub system: SystemConfig,
55    #[extension]
56    pub base: Rv32I,
57    #[extension]
58    pub mul: Rv32M,
59    #[extension]
60    pub io: Rv32Io,
61    #[extension]
62    pub modular: ModularExtension,
63    #[extension]
64    pub keccak: Keccak256,
65    #[extension]
66    pub weierstrass: WeierstrassExtension,
67}
68
69impl Rv32ImEcRecoverConfig {
70    pub fn for_curves(curves: Vec<CurveConfig>) -> Self {
71        let primes: Vec<BigUint> = curves
72            .iter()
73            .flat_map(|c| [c.modulus.clone(), c.scalar.clone()])
74            .collect();
75        Self {
76            system: SystemConfig::default().with_continuations(),
77            base: Default::default(),
78            mul: Default::default(),
79            io: Default::default(),
80            modular: ModularExtension::new(primes),
81            keccak: Default::default(),
82            weierstrass: WeierstrassExtension::new(curves),
83        }
84    }
85}
86
87fn main() -> Result<()> {
88    let args = BenchmarkCli::parse();
89
90    let elf = args.build_bench_program("ecrecover")?;
91    let exe = VmExe::from_elf(
92        elf,
93        Transpiler::<BabyBear>::default()
94            .with_extension(Rv32ITranspilerExtension)
95            .with_extension(Rv32MTranspilerExtension)
96            .with_extension(Rv32IoTranspilerExtension)
97            .with_extension(Keccak256TranspilerExtension)
98            .with_extension(ModularTranspilerExtension)
99            .with_extension(EccTranspilerExtension),
100    )?;
101
102    run_with_metric_collection("OUTPUT_PATH", || -> Result<()> {
103        let mut rng = ChaCha8Rng::seed_from_u64(12345);
104        let signing_key: SigningKey = SigningKey::random(&mut rng);
105        let verifying_key = VerifyingKey::from(&signing_key);
106        let mut hasher = Keccak::v256();
107        let mut expected_address = [0u8; 32];
108        hasher.update(
109            &verifying_key
110                .to_encoded_point(/* compress = */ false)
111                .as_bytes()[1..],
112        );
113        hasher.finalize(&mut expected_address);
114        expected_address[..12].fill(0); // 20 bytes as the address.
115        let mut input_stream = vec![expected_address
116            .into_iter()
117            .map(BabyBear::from_canonical_u8)
118            .collect::<Vec<_>>()];
119
120        let msg = ["Elliptic", "Curve", "Digital", "Signature", "Algorithm"];
121        input_stream.extend(
122            msg.iter()
123                .map(|s| make_input(&signing_key, s.as_bytes()))
124                .collect::<Vec<_>>(),
125        );
126        args.bench_from_exe(
127            "ecrecover_program",
128            Rv32ImEcRecoverConfig::for_curves(vec![SECP256K1_CONFIG.clone()]),
129            exe,
130            input_stream.into(),
131        )
132    })
133}