Skip to content

Commit 23f956f

Browse files
committed
feat: refactor zkvm specific logic to proof backend and use appropriate predicate information
1 parent 31e8173 commit 23f956f

File tree

7 files changed

+205
-29
lines changed

7 files changed

+205
-29
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.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ zkaleido = { git = "https://github.com/alpenlabs/zkaleido", tag = "v0.1.0-alpha-
128128
zkaleido-native-adapter = { git = "https://github.com/alpenlabs/zkaleido", tag = "v0.1.0-alpha-rc23", features = [
129129
"remote-prover",
130130
] }
131+
zkaleido-sp1-groth16-verifier = { git = "https://github.com/alpenlabs/zkaleido", tag = "v0.1.0-alpha-rc23" }
131132
zkaleido-sp1-host = { git = "https://github.com/alpenlabs/zkaleido", tag = "v0.1.0-alpha-rc23" }
132133

133134
# external dependencies
@@ -156,6 +157,8 @@ serde = { version = "1.0", features = ["derive"] }
156157
serde_json = "1.0.94"
157158
sha2 = "0.10"
158159
sled = "0.34.7"
160+
sp1-sdk = "5.2.1"
161+
sp1-verifier = "5.2.1"
159162
tempfile = "3.10.1"
160163
thiserror = "2.0.11"
161164
tokio = { version = "1.37", features = ["full"] }

bin/asm-runner/Cargo.toml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ tree_hash.workspace = true
3535

3636
anyhow.workspace = true
3737
async-trait.workspace = true
38+
bincode = { workspace = true, optional = true }
3839
bitcoin.workspace = true
3940
bitcoincore-zmq = { version = "1.5.2", features = ["async"] }
4041
bitcoind-async-client.workspace = true
@@ -44,16 +45,28 @@ jsonrpsee = { workspace = true, features = ["server", "macros"] }
4445
serde.workspace = true
4546
serde_json.workspace = true
4647
sled.workspace = true
48+
sp1-sdk = { workspace = true, optional = true }
49+
sp1-verifier = { workspace = true, optional = true }
4750
ssz.workspace = true
4851
strata-asm-sp1-guest-builder = { path = "../../guest-builder/sp1", optional = true }
4952
tokio.workspace = true
5053
toml.workspace = true
5154
tracing.workspace = true
5255
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
5356
zkaleido.workspace = true
57+
zkaleido-native-adapter.workspace = true
58+
zkaleido-sp1-groth16-verifier = { workspace = true, optional = true }
5459
zkaleido-sp1-host = { workspace = true, optional = true, features = [
5560
"remote-prover",
5661
] }
5762

5863
[features]
59-
sp1 = ["dep:zkaleido-sp1-host", "dep:strata-asm-sp1-guest-builder"]
64+
default = ["sp1"]
65+
sp1 = [
66+
"dep:zkaleido-sp1-host",
67+
"dep:zkaleido-sp1-groth16-verifier",
68+
"dep:strata-asm-sp1-guest-builder",
69+
"dep:sp1-verifier",
70+
"dep:sp1-sdk",
71+
"dep:bincode",
72+
]

bin/asm-runner/src/bootstrap.rs

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use tokio::{
1616
use crate::{
1717
block_watcher::drive_asm_from_bitcoin,
1818
config::{AsmRpcConfig, BitcoinConfig},
19-
prover::{InputBuilder, ProofOrchestrator},
19+
prover::{InputBuilder, ProofBackend, ProofOrchestrator},
2020
rpc_server::run_rpc_server,
2121
storage::create_storage,
2222
worker_context::AsmWorkerContext,
@@ -62,37 +62,24 @@ pub(crate) async fn bootstrap(
6262
let proof_db = SledProofDb::open(&orch_config.proof_db_path)?;
6363
let proof_db_clone = proof_db.clone();
6464

65-
#[cfg(feature = "sp1")]
66-
let (asm, moho) = {
67-
use std::fs;
68-
69-
use strata_asm_sp1_guest_builder::{ASM_ELF_PATH, MOHO_ELF_PATH};
70-
use zkaleido_sp1_host::SP1Host;
71-
let asm_elf = fs::read(ASM_ELF_PATH)
72-
.unwrap_or_else(|err| panic!("failed to read guest elf at {ASM_ELF_PATH}: {err}"));
73-
let moho_elf = fs::read(MOHO_ELF_PATH)
74-
.unwrap_or_else(|err| panic!("failed to read guest elf at {MOHO_ELF_PATH}: {err}"));
75-
(SP1Host::init(&asm_elf), SP1Host::init(&moho_elf))
76-
};
77-
78-
#[cfg(not(feature = "sp1"))]
79-
let (asm, moho) = {
80-
use moho_recursive_proof::MohoRecursiveProgram;
81-
use strata_asm_proof_impl::program::AsmStfProofProgram;
82-
(
83-
AsmStfProofProgram::native_host(),
84-
MohoRecursiveProgram::native_host(),
85-
)
86-
};
65+
let backend = ProofBackend::new()?;
8766

8867
let input_builder = InputBuilder::new(
8968
state_db.clone(),
9069
bitcoin_client.clone(),
9170
proof_db.clone(),
9271
params.anchor.block,
72+
backend.asm_predicate.clone(),
73+
backend.moho_predicate.clone(),
74+
);
75+
let mut orchestrator = ProofOrchestrator::new(
76+
proof_db,
77+
backend.asm_host,
78+
backend.moho_host,
79+
orch_config,
80+
input_builder,
81+
rx,
9382
);
94-
let mut orchestrator =
95-
ProofOrchestrator::new(proof_db, asm, moho, orch_config, input_builder, rx);
9683

9784
// ZkVmRemoteProver is !Send (#[async_trait(?Send)]), so the orchestrator
9885
// future cannot be spawned on a multi-threaded runtime directly. We run it
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
//! ZK proof backend setup for the runner.
2+
//!
3+
//! Bundles the feature-gated selection of the ZK proof backend in one place:
4+
//! host construction (SP1 or native), and derivation of the [`PredicateKey`]
5+
//! that authorizes proofs from each host. The result is exposed as a single
6+
//! [`ProofBackend`] value that the runner builds once at startup and threads
7+
//! into the proof orchestrator and the input builder.
8+
9+
use anyhow::{Result, bail};
10+
use strata_predicate::{PredicateKey, PredicateTypeId};
11+
use zkaleido::{ZkVm, ZkVmHost};
12+
#[cfg(feature = "sp1")]
13+
use {
14+
anyhow::Context,
15+
sp1_sdk::{HashableKey, SP1VerifyingKey},
16+
sp1_verifier::GROTH16_VK_BYTES,
17+
zkaleido_sp1_groth16_verifier::SP1Groth16Verifier,
18+
zkaleido_sp1_host::SP1Host,
19+
};
20+
21+
/// Concrete host type used by the proof orchestrator.
22+
///
23+
/// Resolves to [`SP1Host`] when the `sp1` feature is enabled, otherwise to
24+
/// the in-process [`zkaleido_native_adapter::NativeHost`].
25+
#[cfg(feature = "sp1")]
26+
pub(crate) type ProofHost = SP1Host;
27+
28+
#[cfg(not(feature = "sp1"))]
29+
pub(crate) type ProofHost = zkaleido_native_adapter::NativeHost;
30+
31+
/// ZK proof backend used by the runner.
32+
///
33+
/// Bundles the `(asm, moho)` host pair together with the [`PredicateKey`] that
34+
/// each one's proofs verify against. Constructed once at startup via
35+
/// [`ProofBackend::new`] and consumed by the proof orchestrator (hosts) and
36+
/// the input builder (predicates).
37+
pub(crate) struct ProofBackend {
38+
pub(crate) asm_host: ProofHost,
39+
pub(crate) moho_host: ProofHost,
40+
pub(crate) asm_predicate: PredicateKey,
41+
pub(crate) moho_predicate: PredicateKey,
42+
}
43+
44+
impl ProofBackend {
45+
/// Builds the ZK proof backend.
46+
///
47+
/// Constructs both proof hosts and resolves the [`PredicateKey`] each
48+
/// host's proofs verify against.
49+
///
50+
/// # Errors
51+
///
52+
/// Returns an error if either host cannot be constructed (e.g. a guest
53+
/// ELF cannot be read in `sp1` builds) or if either host's verifying key
54+
/// cannot be turned into a [`PredicateKey`].
55+
pub(crate) fn new() -> Result<Self> {
56+
let (asm_host, moho_host) = build_proof_hosts()?;
57+
let asm_predicate = resolve_predicate(&asm_host)?;
58+
let moho_predicate = resolve_predicate(&moho_host)?;
59+
Ok(Self {
60+
asm_host,
61+
moho_host,
62+
asm_predicate,
63+
moho_predicate,
64+
})
65+
}
66+
}
67+
68+
/// Builds the `(asm, moho)` host pair used by the proof orchestrator.
69+
///
70+
/// With the `sp1` feature, both hosts are SP1 hosts initialized from the
71+
/// embedded guest ELFs and capable of dispatching proofs to a remote SP1
72+
/// prover. Without the `sp1` feature, both hosts are native (in-process)
73+
/// hosts that simply execute the proof programs and do not produce real
74+
/// cryptographic proofs.
75+
///
76+
/// # Errors
77+
///
78+
/// With the `sp1` feature, returns an error if either guest ELF cannot be
79+
/// read from the path baked into the guest builder.
80+
#[cfg(feature = "sp1")]
81+
fn build_proof_hosts() -> Result<(ProofHost, ProofHost)> {
82+
use std::fs;
83+
84+
use strata_asm_sp1_guest_builder::{ASM_ELF_PATH, MOHO_ELF_PATH};
85+
86+
let asm_elf = fs::read(ASM_ELF_PATH)
87+
.with_context(|| format!("failed to read ASM guest ELF at {ASM_ELF_PATH}"))?;
88+
let moho_elf = fs::read(MOHO_ELF_PATH)
89+
.with_context(|| format!("failed to read Moho guest ELF at {MOHO_ELF_PATH}"))?;
90+
91+
Ok((SP1Host::init(&asm_elf), SP1Host::init(&moho_elf)))
92+
}
93+
94+
#[cfg(not(feature = "sp1"))]
95+
fn build_proof_hosts() -> Result<(ProofHost, ProofHost)> {
96+
use moho_recursive_proof::MohoRecursiveProgram;
97+
use strata_asm_proof_impl::program::AsmStfProofProgram;
98+
99+
Ok((
100+
AsmStfProofProgram::native_host(),
101+
MohoRecursiveProgram::native_host(),
102+
))
103+
}
104+
105+
/// Resolves the [`PredicateKey`] for proofs produced by `host`.
106+
///
107+
/// The returned key carries both the predicate type (matching the host's
108+
/// [`ZkVm`] backend) and the encoded verifying-key material required to
109+
/// validate proofs from that host.
110+
///
111+
/// # Errors
112+
///
113+
/// - For SP1 hosts, returns an error if the host's verifying key cannot be deserialized into an
114+
/// [`SP1VerifyingKey`] or if the SP1 Groth16 verifier cannot be loaded for the resulting program
115+
/// hash.
116+
/// - For Risc0 hosts, returns an error because predicate resolution is not yet implemented for that
117+
/// backend.
118+
/// - When built without the `sp1` feature, an SP1 host returns an error because the SP1
119+
/// verifying-key handling is gated behind that feature.
120+
fn resolve_predicate(host: &impl ZkVmHost) -> Result<PredicateKey> {
121+
match host.zkvm() {
122+
// Native execution does not produce a real cryptographic proof; the
123+
// predicate simply carries the verifying-key bytes verbatim under the
124+
// BIP-340 Schnorr type as a placeholder identifier.
125+
ZkVm::Native => Ok(PredicateKey::new(
126+
PredicateTypeId::Bip340Schnorr,
127+
host.vk().as_bytes().to_vec(),
128+
)),
129+
130+
// SP1 proofs are wrapped in a Groth16 proof, so the on-chain
131+
// predicate must identify the SP1 Groth16 verifying key (not the SP1
132+
// program vk itself). The conversion is:
133+
// 1. Decode the SP1 verifying key from the host's raw bytes.
134+
// 2. Hash it to obtain the program commitment expected by the Groth16 verifier.
135+
// 3. Load the matching Groth16 verifier and serialize its vk into the predicate key.
136+
#[cfg(feature = "sp1")]
137+
ZkVm::SP1 => {
138+
let vk = host.vk();
139+
let sp1_vk: SP1VerifyingKey = bincode::deserialize(vk.as_bytes())
140+
.context("failed to deserialize SP1 verifying key")?;
141+
142+
let verifier = SP1Groth16Verifier::load(&GROTH16_VK_BYTES, sp1_vk.hash_bytes())
143+
.context("failed to load SP1 Groth16 verifier")?;
144+
145+
Ok(PredicateKey::new(
146+
PredicateTypeId::Sp1Groth16,
147+
verifier.vk.to_uncompressed_bytes(),
148+
))
149+
}
150+
#[cfg(not(feature = "sp1"))]
151+
ZkVm::SP1 => bail!("SP1 predicate key resolution requires the `sp1` feature"),
152+
153+
// Risc0 support is not yet wired up; surface a clear error rather
154+
// than panicking so callers can fail gracefully.
155+
ZkVm::Risc0 => bail!("predicate key resolution is not implemented for Risc0"),
156+
}
157+
}

bin/asm-runner/src/prover/input.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ pub(crate) struct InputBuilder {
3030
bitcoin_client: Arc<Client>,
3131
proof_db: SledProofDb,
3232
genesis: L1BlockCommitment,
33+
asm_predicate: PredicateKey,
34+
moho_predicate: PredicateKey,
3335
}
3436

3537
pub(crate) struct MohoPrerequisite {
@@ -43,12 +45,16 @@ impl InputBuilder {
4345
bitcoin_client: Arc<Client>,
4446
proof_db: SledProofDb,
4547
genesis: L1BlockCommitment,
48+
asm_predicate: PredicateKey,
49+
moho_predicate: PredicateKey,
4650
) -> Self {
4751
Self {
4852
state_db,
4953
bitcoin_client,
5054
proof_db,
5155
genesis,
56+
asm_predicate,
57+
moho_predicate,
5258
}
5359
}
5460

@@ -194,14 +200,16 @@ impl InputBuilder {
194200
prerequisite: MohoPrerequisite,
195201
l1_ref: L1BlockCommitment,
196202
) -> Result<MohoRecursiveInput> {
197-
let moho_predicate = PredicateKey::always_accept();
203+
let moho_predicate = self.moho_predicate.clone();
198204

199205
let MohoPrerequisite {
200206
prev_moho_proof,
201207
incremental_step_proof,
202208
} = prerequisite;
203209

204-
let step_predicate = PredicateKey::always_accept();
210+
// The inner step proof is the ASM STF proof, so the step predicate is
211+
// the ASM predicate.
212+
let step_predicate = self.asm_predicate.clone();
205213

206214
let parent = self.get_parent_commitment(l1_ref).await?;
207215
let parent_state = self.get_moho_state(parent).await?;

bin/asm-runner/src/prover/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
//! Manages the lifecycle of ASM step proofs and Moho recursive proofs by
44
//! scheduling jobs on a remote prover service and reconciling results.
55
6+
mod backend;
67
pub(crate) mod config;
78
mod input;
89
mod orchestrator;
910
mod proof_store;
1011
mod queue;
1112

12-
pub(crate) use self::{input::InputBuilder, orchestrator::ProofOrchestrator};
13+
pub(crate) use self::{
14+
backend::ProofBackend, input::InputBuilder, orchestrator::ProofOrchestrator,
15+
};

0 commit comments

Comments
 (0)