diff --git a/lightclient-circuits/src/sync_step_circuit.rs b/lightclient-circuits/src/sync_step_circuit.rs index 580bff09..d39e578d 100644 --- a/lightclient-circuits/src/sync_step_circuit.rs +++ b/lightclient-circuits/src/sync_step_circuit.rs @@ -70,7 +70,7 @@ use ssz_rs::{Merkleized, Node}; #[allow(type_alias_bounds)] #[derive(Clone, Debug, Default)] -pub struct SyncStepCircuit { +pub struct SyncStepCircuit { _f: PhantomData, _spec: PhantomData, } diff --git a/lightclient-circuits/src/util/circuit.rs b/lightclient-circuits/src/util/circuit.rs index c7b2e32f..fdb9262e 100644 --- a/lightclient-circuits/src/util/circuit.rs +++ b/lightclient-circuits/src/util/circuit.rs @@ -88,7 +88,7 @@ pub trait PinnableCircuit: CircuitExt { } } -pub trait AppCircuit: Sized { +pub trait AppCircuit { type Pinning: Halo2ConfigPinning; type Witness: Clone; @@ -191,7 +191,6 @@ pub trait AppCircuit: Sized { params: &ParamsKZG, pk: &ProvingKey, pinning_path: impl AsRef, - path: impl AsRef, deployment_code: Option>, witness: &Self::Witness, ) -> Result<(Vec, Vec>), Error> { diff --git a/prover/Cargo.toml b/prover/Cargo.toml index d91b45a7..75adec6d 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -10,7 +10,12 @@ strum = { version = "=0.25", features = ["derive"] } cli-batteries = "=0.4" eyre = "0.6" anstyle = "=1.0.0" +axum = "0.6" +tokio = { version = "1.32", features = ["macros"] } +jsonrpc-v2 = { version = "0.12", default-features = false, features = ["easy-errors", "macros", "bytes-v10"] } + +http = "0.2" # halo2 halo2curves = { git = "https://github.com/privacy-scaling-explorations/halo2curves", tag = "0.3.1" } @@ -39,6 +44,7 @@ url = "2" itertools = "0.11.0" serde = { version = "1.0.130", features = ["derive"] } serde_json = "1.0.78" +log = "0.4.14" # ethereum diff --git a/prover/src/args.rs b/prover/src/args.rs index 0d79e117..1a984ef0 100644 --- a/prover/src/args.rs +++ b/prover/src/args.rs @@ -1,9 +1,29 @@ use std::path::PathBuf; +use serde::{Deserialize, Serialize}; use strum::EnumString; #[derive(Clone, clap::Parser)] -pub struct Options { +#[command(name = "spectre-prover")] +#[command(about = "Spectre prover", long_about = None)] +pub struct Cli { + #[command(subcommand)] + pub subcommand: Subcommands, +} +#[derive(Clone, clap::Parser)] +#[allow(clippy::large_enum_variant)] +pub enum Subcommands { + Rpc(RpcOptions), + Circuit(CircuitOptions), +} +#[derive(Clone, clap::Parser)] +pub struct RpcOptions { + #[clap(long, short, default_value = "3000")] + pub port: String, +} + +#[derive(Clone, clap::Parser)] +pub struct CircuitOptions { #[command(subcommand)] pub proof: Proof, @@ -88,7 +108,7 @@ pub enum Out { Tx, } -#[derive(Clone, Debug, PartialEq, EnumString)] +#[derive(Clone, Debug, PartialEq, EnumString, Serialize, Deserialize)] pub enum Spec { #[strum(serialize = "minimal")] Minimal, diff --git a/prover/src/main.rs b/prover/src/main.rs index 5729c386..8cf65d10 100644 --- a/prover/src/main.rs +++ b/prover/src/main.rs @@ -1,25 +1,22 @@ #![allow(incomplete_features)] #![feature(associated_type_bounds)] -use std::{ - fs::{self, File}, - future::Future, - io::Write, - path::Path, - str::FromStr, - sync::Arc, -}; - -use args::{Args, Options, Out, Proof}; +mod rpc; +use args::{Args, Cli, Out, Proof}; +use axum::{response::IntoResponse, routing::post, Router}; use cli_batteries::version; use ethers::prelude::*; use halo2curves::bn256::{Bn256, Fr, G1Affine}; +use http::StatusCode; use itertools::Itertools; +use jsonrpc_v2::{MapRouter as JsonRpcMapRouter, Server as JsonRpcServer}; use lightclient_circuits::{ committee_update_circuit::CommitteeUpdateCircuit, sync_step_circuit::SyncStepCircuit, util::{gen_srs, AppCircuit}, }; use preprocessor::{fetch_rotation_args, fetch_step_args}; +use rpc::{gen_evm_proof_rotation_circuit_handler, gen_evm_proof_step_circuit_handler}; + use snark_verifier::{ loader::halo2::halo2_ecc::halo2_base::halo2_proofs::{ plonk::VerifyingKey, poly::kzg::commitment::ParamsKZG, @@ -27,8 +24,20 @@ use snark_verifier::{ system::halo2::Config, }; use snark_verifier_sdk::{halo2::aggregation::AggregationCircuit, read_instances, Snark}; - +use std::str::FromStr; +use std::{ + fs::{self, File}, + future::Future, + io::Write, + net::TcpListener, + path::Path, + sync::Arc, +}; mod args; +use jsonrpc_v2::RequestObject as JsonRpcRequestObject; + +use crate::args::RpcOptions; +pub type JsonRpcServerState = Arc>; ethers::contract::abigen!( SnarkVerifierSol, @@ -37,16 +46,43 @@ ethers::contract::abigen!( ]"#, ); -fn main() { - cli_batteries::run(version!(), app); -} +async fn app(options: Cli) -> eyre::Result<()> { + match options.subcommand { + args::Subcommands::Rpc(op) => { + let RpcOptions { port } = op; + + let tcp_listener = TcpListener::bind(format!("0.0.0.0:{}", port)).unwrap(); + let rpc_server = Arc::new( + JsonRpcServer::new() + .with_method( + "genEvmProofAndInstancesStepSyncCircuit", + gen_evm_proof_step_circuit_handler, + ) + .with_method( + "genEvmProofAndInstancesRotationCircuit", + gen_evm_proof_rotation_circuit_handler, + ) + .finish_unwrapped(), + ); + let router = Router::new() + .route("/rpc", post(handler)) + .with_state(rpc_server); -async fn app(options: Options) -> eyre::Result<()> { - match options.spec { - args::Spec::Minimal => spec_app::(&options.proof).await, - args::Spec::Testnet => spec_app::(&options.proof).await, - args::Spec::Mainnet => spec_app::(&options.proof).await, + log::info!("Ready for RPC connections"); + let server = axum::Server::from_tcp(tcp_listener) + .unwrap() + .serve(router.into_make_service()); + server.await.unwrap(); + + log::info!("Stopped accepting RPC connections"); + } + args::Subcommands::Circuit(op) => match op.spec { + args::Spec::Minimal => spec_app::(&op.proof).await.unwrap(), + args::Spec::Testnet => spec_app::(&op.proof).await.unwrap(), + args::Spec::Mainnet => spec_app::(&op.proof).await.unwrap(), + }, } + Ok(()) } async fn spec_app(proof: &Proof) -> eyre::Result<()> { @@ -209,7 +245,6 @@ async fn generic_circuit_cli< ¶ms, &pk, &args.config_path, - &args.path_out, None, &witness, ) @@ -241,6 +276,28 @@ async fn generic_circuit_cli< Ok(()) } +async fn handler( + axum::extract::State(rpc_server): axum::extract::State, + axum::Json(rpc_call): axum::Json, +) -> impl IntoResponse { + let response_headers = [("content-type", "application/json-rpc;charset=utf-8")]; + let response = rpc_server.handle(rpc_call).await; + + let response_str = serde_json::to_string(&response); + match response_str { + Ok(result) => (StatusCode::OK, response_headers, result), + Err(err) => ( + StatusCode::INTERNAL_SERVER_ERROR, + response_headers, + err.to_string(), + ), + } +} + +fn main() { + cli_batteries::run(version!(), app); +} + fn read_snark( params: &ParamsKZG, vk: &VerifyingKey, diff --git a/prover/src/rpc.rs b/prover/src/rpc.rs new file mode 100644 index 00000000..8e2ab2ef --- /dev/null +++ b/prover/src/rpc.rs @@ -0,0 +1,211 @@ +use super::{args, args::Spec}; + +use ethers::prelude::*; +use halo2curves::bn256::Fr; + +use jsonrpc_v2::{Error as JsonRpcError, Params}; +use lightclient_circuits::{ + committee_update_circuit::CommitteeUpdateCircuit, + sync_step_circuit::SyncStepCircuit, + util::{gen_srs, AppCircuit}, +}; +use preprocessor::{fetch_rotation_args, fetch_step_args}; +use serde::{Deserialize, Serialize}; + +use snark_verifier_sdk::{halo2::aggregation::AggregationCircuit, Snark}; + +use std::path::PathBuf; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GenProofRotationParams { + spec: args::Spec, + + k: Option, + #[serde(default = "default_beacon_api")] + beacon_api: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GenProofStepParams { + spec: args::Spec, + + k: Option, + #[serde(default = "default_beacon_api")] + beacon_api: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EvmProofResult { + proof: Vec, + public_inputs: Vec, +} + +fn default_beacon_api() -> String { + String::from("http://127.0.0.1:5052") +} + +fn gen_app_snark( + app_config_path: PathBuf, + app_pk_path: PathBuf, + witness: as AppCircuit>::Witness, +) -> eyre::Result { + let params = gen_srs(CommitteeUpdateCircuit::::get_degree( + &app_config_path, + )); + + let app_pk = CommitteeUpdateCircuit::::read_pk( + ¶ms, + app_pk_path, + & as AppCircuit>::Witness::default(), + ); + + Ok(CommitteeUpdateCircuit::::gen_snark_shplonk( + ¶ms, + &app_pk, + app_config_path, + None::, + &witness, + )?) +} + +fn gen_evm_proof( + k: Option, + build_dir: PathBuf, + pk_filename: String, + config_path: PathBuf, + witness: C::Witness, +) -> (Vec, Vec>) { + let k = k.unwrap_or_else(|| C::get_degree(&config_path)); + let params = gen_srs(k); + + let pk = C::read_pk( + ¶ms, + build_dir.join(pk_filename), + &witness + ); + + let (proof, instances) = + C::gen_evm_proof_shplonk(¶ms, &pk, &config_path, None, &witness) + .map_err(|e| eyre::eyre!("Failed to generate calldata: {}", e)) + .unwrap(); + + (proof, instances) +} + +pub(crate) async fn gen_evm_proof_rotation_circuit_handler( + Params(params): Params, +) -> Result { + let GenProofRotationParams { + spec, + k, + beacon_api, + } = params; + + // TODO: use config/build paths from CLI flags + let app_config_path = PathBuf::from("./lightclient-circuits/config/committee_update.json"); + let app_pk_path = PathBuf::from("./build/committee_update_circuit.pkey"); + + let config_path = + PathBuf::from("./lightclient-circuits/config/committee_update_aggregation.json"); + let build_dir = PathBuf::from("./build"); + + let (snark, pk_filename) = match spec { + Spec::Minimal => { + let witness = fetch_rotation_args(beacon_api).await?; + ( + gen_app_snark::(app_config_path, app_pk_path, witness)?, + "agg_rotation_circuit_minimal.pkey", + ) + } + Spec::Testnet => { + let witness = fetch_rotation_args(beacon_api).await?; + ( + gen_app_snark::(app_config_path, app_pk_path, witness)?, + "agg_rotation_circuit_testnet.pkey", + ) + } + Spec::Mainnet => { + let witness = fetch_rotation_args(beacon_api).await?; + ( + gen_app_snark::(app_config_path, app_pk_path, witness)?, + "agg_rotation_circuit_mainnet.pkey", + ) + } + }; + let (proof, instances) = gen_evm_proof::( + k, + build_dir, + pk_filename.to_string(), + config_path, + vec![snark], + ); + + let public_inputs = instances[0] + .iter() + .map(|pi| U256::from_little_endian(&pi.to_bytes())) + .collect(); + Ok(EvmProofResult { + proof, + public_inputs, + }) +} + +pub(crate) async fn gen_evm_proof_step_circuit_handler( + Params(params): Params, +) -> Result { + let GenProofStepParams { + spec, + k, + beacon_api, + } = params.clone(); + + let config_path = PathBuf::from("./lightclient-circuits/config/step_sync.json"); + let build_dir = PathBuf::from("./build"); + + let (proof, instances) = match spec { + Spec::Minimal => { + let pk_filename = format!("step_circuit_minimal.pkey"); + let witness = fetch_step_args(beacon_api).await.unwrap(); + gen_evm_proof::>( + k, + build_dir, + pk_filename, + config_path, + witness, + ) + } + Spec::Testnet => { + let pk_filename = format!("step_circuit_testnet.pkey"); + let witness = fetch_step_args(beacon_api).await.unwrap(); + + gen_evm_proof::>( + k, + build_dir, + pk_filename, + config_path, + witness, + ) + } + Spec::Mainnet => { + let pk_filename = format!("step_circuit_mainnet.pkey"); + let witness = fetch_step_args(beacon_api).await.unwrap(); + + gen_evm_proof::>( + k, + build_dir, + pk_filename, + config_path, + witness, + ) + } + }; + + let public_inputs = instances[0] + .iter() + .map(|pi| U256::from_little_endian(&pi.to_bytes())) + .collect(); + Ok(EvmProofResult { + proof, + public_inputs, + }) +}