Skip to content

Commit

Permalink
Initial SP1 support (WIP)
Browse files Browse the repository at this point in the history
The proofs work, but it's not yet integrated with the main server.
  • Loading branch information
Avi-D-coder committed Sep 24, 2024
1 parent 6305c25 commit cd1c804
Show file tree
Hide file tree
Showing 9 changed files with 7,057 additions and 0 deletions.
6,717 changes: 6,717 additions & 0 deletions kairos-sp1/Cargo.lock

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions kairos-sp1/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[workspace]
members = [
"program",
"server"
]
resolver = "2"
Binary file added kairos-sp1/elf/riscv32im-succinct-zkvm-elf
Binary file not shown.
9 changes: 9 additions & 0 deletions kairos-sp1/program/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
version = "0.1.0"
name = "kairos-batch-logic-sp1"
edition = "2021"

[dependencies]
kairos-circuit-logic = { path = "../../kairos-prover/kairos-circuit-logic", features = ["serde", "borsh"], default-features = false }

sp1-zkvm = "2.0.0"
18 changes: 18 additions & 0 deletions kairos-sp1/program/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#![cfg_attr(target_os = "zkvm", no_main)]

#[cfg(target_os = "zkvm")]
sp1_zkvm::entrypoint!(main);

use kairos_circuit_logic::ProofInputs;

pub fn main() {
let proof_inputs: ProofInputs = sp1_zkvm::io::read();

let output = proof_inputs
.run_batch_proof_logic()
.unwrap()
.borsh_serialize()
.unwrap();

sp1_zkvm::io::commit_slice(&output);
}
3 changes: 3 additions & 0 deletions kairos-sp1/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[toolchain]
channel = "1.79.0"
components = ["llvm-tools", "rustc-dev"]
37 changes: 37 additions & 0 deletions kairos-sp1/server/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[package]
name = "kairos-sp1-server"
version = "0.1.0"
edition = "2021"

[features]
cuda = ["sp1-sdk/cuda"]

[dependencies]
sp1-sdk = "2.0.0"

kairos-circuit-logic = { path = "../../kairos-prover/kairos-circuit-logic", features = ["serde", "borsh"], default-features = false }

serde_json = { version = "1" }
serde = { version = "1", default-features = false, features = ["derive"] }

tokio = { version = "1", features = ["rt-multi-thread", "tracing", "macros"] }
axum = { version = "0.7", features = ["tracing"] }
axum-extra = { version = "0.9", features = [
"typed-routing",
"typed-header",
"json-deserializer",
] }

dotenvy = "0.15"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

[build-dependencies]
sp1-helper = "2.0.0"

[dev-dependencies]
kairos-trie = { git = "https://github.com/cspr-rad/kairos-trie", features = ["serde"] }
kairos-circuit-logic = { path = "../../kairos-prover/kairos-circuit-logic", features = ["serde", "test-logic", "arbitrary" ], default-features = false }
test-strategy = { version = "0.3" }
proptest = { version = "1" }
casper-types = "4.0"
5 changes: 5 additions & 0 deletions kairos-sp1/server/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use sp1_helper::build_program_with_args;

fn main() {
build_program_with_args("../program", Default::default())
}
262 changes: 262 additions & 0 deletions kairos-sp1/server/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
use std::time::Instant;

use axum::{http::StatusCode, Json};
use axum_extra::routing::{RouterExt, TypedPath};
use kairos_circuit_logic::{ProofInputs, ProofOutputs};

use sp1_sdk::{SP1ProofWithPublicValues, SP1Stdin};
// These constants represent the RISC-V ELF and the image ID generated by risc0-build.
// The ELF is used for proving and the ID is used for verification.
use tracing::level_filters::LevelFilter;
use tracing_subscriber::{prelude::*, EnvFilter};

pub const PROVE_BATCH_ELF: &[u8] = include_bytes!("../../elf/riscv32im-succinct-zkvm-elf");

#[tokio::main]
async fn main() {
let _ = dotenvy::dotenv();
// Initialize tracing. In order to view logs, run `RUST_LOG=info cargo run`
tracing_subscriber::registry()
.with(
EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "info,kairos_sp1_server=info".into()),
)
.with(tracing_subscriber::fmt::layer())
.init();

let socket_addr = std::env::var("KAIROS_SP1_PROVER_SERVER_SOCKET_ADDR")
.expect("Failed to fetch environment variable KAIROS_SP1_PROVER_SERVER_SOCKET_ADDR");
let socket_addr = socket_addr
.parse::<std::net::SocketAddr>()
.expect("Failed to parse KAIROS_SP1_PROVER_SERVER_SOCKET_ADDR");

let app = axum::Router::new()
.typed_post(prove_batch_route)
.with_state(());

tracing::info!("starting http server on `{}`", socket_addr);
let listener = tokio::net::TcpListener::bind(socket_addr).await.unwrap();
tracing::info!("listening on `{}`", socket_addr);
axum::serve(listener, app).await.unwrap()
}

#[derive(TypedPath, Debug, Clone, Copy)]
#[typed_path("/api/v1/prove/batch")]
pub struct ProveBatch;

pub async fn prove_batch_route(
_: ProveBatch,
proof_inputs: Json<ProofInputs>,
) -> Result<Json<(ProofOutputs, SP1ProofWithPublicValues)>, (StatusCode, String)> {
let proof = tokio::task::spawn_blocking(move || prove_execution(proof_inputs.0))
.await
.map_err(|e| {
let e = format!("Error while joining proving task: {e}");
tracing::error!(e);
(StatusCode::INTERNAL_SERVER_ERROR, e)
})?;

let proof = proof.map_err(|e| {
let e = format!("Error while proving batch: {e}");
tracing::error!(e);
(StatusCode::INTERNAL_SERVER_ERROR, e)
})?;

Ok(Json(proof))
}

fn prove_execution(
proof_inputs: kairos_circuit_logic::ProofInputs,
) -> Result<(ProofOutputs, SP1ProofWithPublicValues), String> {
let timestamp = Instant::now();

let client = sp1_sdk::ProverClient::local();

// Setup the inputs.
let mut stdin = SP1Stdin::new();
stdin.write(&proof_inputs);

let (pk, vk) = client.setup(PROVE_BATCH_ELF);

let proof = client
.prove(&pk, stdin)
.run()
.map_err(|e| format!("Failed to prove execution: {e}"))?;

tracing::info!("Proved batch: {}s", timestamp.elapsed().as_secs_f64());

let timestamp = Instant::now();

tracing::info!("Verified batch: {}s", timestamp.elapsed().as_secs_f64());

client
.verify(&proof, &vk)
.map_err(|e| format!("Failed to verify proof: {e}"))?;

// this panics if deserialization fails
let proof_outputs: &[u8] = proof.public_values.as_slice();
let proof_outputs = ProofOutputs::borsh_deserialize(proof_outputs)?;

Ok((proof_outputs, proof))
}

pub fn test_setup() {
let _ = tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy(),
)
.try_init();

if cfg!(feature = "disable-dev-mode") {
std::env::set_var("RISC0_DEV_MODE", "0");
}
}

#[cfg(test)]
mod tests {
use std::rc::Rc;

use casper_types::{bytesrepr::ToBytes, AsymmetricType};
use kairos_trie::{stored::memory_db::MemoryDb, TrieRoot};
use proptest::prelude::*;

use kairos_circuit_logic::{
account_trie::{test_logic::test_prove_batch, Account},
transactions::{
arbitrary::RandomBatches, KairosTransaction, L1Deposit, Signed, Transfer, Withdraw,
},
};

use crate::test_setup;

#[test]
fn test_prove_simple_batches() {
test_setup();

let alice_public_key = include_bytes!("../../../testdata/users/user-2/public_key_hex");
let alice_public_key = casper_types::PublicKey::from_hex(alice_public_key)
.expect("Failed to parse public key")
.to_bytes()
.expect("Failed to convert public key to bytes");

let bob_public_key = include_bytes!("../../../testdata/users/user-3/public_key_hex");
let bob_public_key = casper_types::PublicKey::from_hex(bob_public_key)
.expect("Failed to parse public key")
.to_bytes()
.expect("Failed to convert public key to bytes");

let batches = vec![
vec![
KairosTransaction::Deposit(L1Deposit {
recipient: alice_public_key.clone(),
amount: 10,
}),
KairosTransaction::Transfer(Signed {
public_key: alice_public_key.clone(),
transaction: Transfer {
recipient: bob_public_key.clone(),
amount: 5,
},
nonce: 0,
}),
KairosTransaction::Withdraw(Signed {
public_key: alice_public_key.clone(),
transaction: Withdraw { amount: 5 },
nonce: 1,
}),
],
vec![
KairosTransaction::Transfer(Signed {
public_key: bob_public_key.clone(),
transaction: Transfer {
recipient: alice_public_key.clone(),
amount: 2,
},
nonce: 0,
}),
KairosTransaction::Withdraw(Signed {
public_key: bob_public_key.clone(),
transaction: Withdraw { amount: 3 },
nonce: 1,
}),
KairosTransaction::Withdraw(Signed {
public_key: alice_public_key.clone(),
transaction: Withdraw { amount: 2 },
nonce: 2,
}),
],
];

test_prove_batch(
TrieRoot::Empty,
Rc::new(MemoryDb::<Account>::empty()),
batches,
|(batch_num, proof_inputs)| {
let (proof_outputs, receipt) =
crate::prove_execution(proof_inputs).expect("Failed to prove execution");

if cfg!(feature = "write-test-proofs") {
let proof_file = std::fs::File::create(format!(
"test_prove_simple_batches_{batch_num}.json"
))
.expect("Failed to create proof file");

serde_json::to_writer(proof_file, &receipt)
.expect("Failed to write proof file");
};

Ok(proof_outputs)
},
);
}

#[test_strategy::proptest(ProptestConfig::default(), cases = if cfg!(feature = "disable-dev-mode") { 2 } else { 40 })]
fn proptest_prove_batches(
#[any(batch_size = 5..=10, batch_count = 2..=4, initial_l2_accounts = 10_000..=100_000)]
args: RandomBatches,
) {
test_setup();
let batches = args.filter_success();

proptest::prop_assume!(batches.len() >= 2);

test_prove_batch(
args.initial_trie,
args.trie_db,
batches,
|(_, proof_inputs)| {
tracing::info!(
"Proving batch of size: {}, over trie of {} accounts.",
proof_inputs.transactions.len(),
args.initial_state.l2.len()
);

let (proof_outputs, receipt) =
crate::prove_execution(proof_inputs).expect("Failed to prove execution");

// if cfg!(feature = "write-test-proofs") {
// use std::hash::{DefaultHasher, Hasher};

// let mut hasher = DefaultHasher::new();
// hasher.write(&receipt.journal.bytes);
// let journal_hash = hasher.finish();

// let proof_path = format!(
// "proptest_prove_batches-proof-journal-{:x}.json",
// journal_hash
// );

// let proof_file =
// std::fs::File::create(proof_path).expect("Failed to create proof file");

// serde_json::to_writer(proof_file, &receipt)
// .expect("Failed to write proof file");
// }

Ok(proof_outputs)
},
)
}
}

0 comments on commit cd1c804

Please sign in to comment.