Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ strata-test-utils-btcio = { path = "crates/test-utils/btcio" }
strata-test-utils-checkpoint = { path = "crates/test-utils/checkpoint" }

# Dependencies from alpenlabs/moho (28ef75c = strata-common rc14)
moho-recursive-proof = { git = "https://github.com/alpenlabs/moho", rev = "28ef75cf39758c4214cb383a4de2b2dcf0a3dd67" }
moho-runtime-impl = { git = "https://github.com/alpenlabs/moho", rev = "28ef75cf39758c4214cb383a4de2b2dcf0a3dd67" }
moho-runtime-interface = { git = "https://github.com/alpenlabs/moho", rev = "28ef75cf39758c4214cb383a4de2b2dcf0a3dd67" }
moho-types = { git = "https://github.com/alpenlabs/moho", rev = "28ef75cf39758c4214cb383a4de2b2dcf0a3dd67" }
moho-recursive-proof = { git = "https://github.com/alpenlabs/moho", rev = "18d515b3ff60b1f48e4070d0b4b9a5e7af82f99b" }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical: guest-moho still pins the old moho revision — causes SP1 CI failure

The workspace moho deps were bumped here to 18d515b, but guest-builder/sp1/guest-moho/Cargo.toml line 9 still references the old revision 28ef75c.

The host-side code now serializes inputs using the new moho types (StepMohoAttestation, StepMohoProof, RecursiveMohoProof), but the guest program deserializes them with the old types — causing an OffsetOutOfBounds SSZ deserialization panic inside the SP1 guest. This is the direct cause of the failing prover-perf (SP1) CI check.

# guest-builder/sp1/guest-moho/Cargo.toml line 9 — needs:
moho-recursive-proof = { git = "https://github.com/alpenlabs/moho", rev = "18d515b3ff60b1f48e4070d0b4b9a5e7af82f99b" }

The corresponding guest-builder/sp1/guest-moho/Cargo.lock will also need to be regenerated.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. Thanks! Fixed in b35148e.

moho-runtime-impl = { git = "https://github.com/alpenlabs/moho", rev = "18d515b3ff60b1f48e4070d0b4b9a5e7af82f99b" }
moho-runtime-interface = { git = "https://github.com/alpenlabs/moho", rev = "18d515b3ff60b1f48e4070d0b4b9a5e7af82f99b" }
moho-types = { git = "https://github.com/alpenlabs/moho", rev = "18d515b3ff60b1f48e4070d0b4b9a5e7af82f99b" }

# Dependencies from alpenlabs/strata-common
strata-btc-types = { git = "https://github.com/alpenlabs/strata-common", tag = "v0.1.0-alpha-rc14" }
Expand Down Expand Up @@ -128,6 +128,7 @@ zkaleido = { git = "https://github.com/alpenlabs/zkaleido", tag = "v0.1.0-alpha-
zkaleido-native-adapter = { git = "https://github.com/alpenlabs/zkaleido", tag = "v0.1.0-alpha-rc23", features = [
"remote-prover",
] }
zkaleido-sp1-groth16-verifier = { git = "https://github.com/alpenlabs/zkaleido", tag = "v0.1.0-alpha-rc23" }
zkaleido-sp1-host = { git = "https://github.com/alpenlabs/zkaleido", tag = "v0.1.0-alpha-rc23" }

# external dependencies
Expand Down Expand Up @@ -156,6 +157,8 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.94"
sha2 = "0.10"
sled = "0.34.7"
sp1-sdk = "5.2.1"
sp1-verifier = "5.2.1"
tempfile = "3.10.1"
thiserror = "2.0.11"
tokio = { version = "1.37", features = ["full"] }
Expand Down
15 changes: 14 additions & 1 deletion bin/asm-runner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ tree_hash.workspace = true

anyhow.workspace = true
async-trait.workspace = true
bincode = { workspace = true, optional = true }
bitcoin.workspace = true
bitcoincore-zmq = { version = "1.5.2", features = ["async"] }
bitcoind-async-client.workspace = true
Expand All @@ -44,16 +45,28 @@ jsonrpsee = { workspace = true, features = ["server", "macros"] }
serde.workspace = true
serde_json.workspace = true
sled.workspace = true
sp1-sdk = { workspace = true, optional = true }
sp1-verifier = { workspace = true, optional = true }
ssz.workspace = true
strata-asm-sp1-guest-builder = { path = "../../guest-builder/sp1", optional = true }
tokio.workspace = true
toml.workspace = true
tracing.workspace = true
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
zkaleido.workspace = true
zkaleido-native-adapter.workspace = true
zkaleido-sp1-groth16-verifier = { workspace = true, optional = true }
zkaleido-sp1-host = { workspace = true, optional = true, features = [
"remote-prover",
] }

[features]
sp1 = ["dep:zkaleido-sp1-host", "dep:strata-asm-sp1-guest-builder"]
default = []
sp1 = [
"dep:zkaleido-sp1-host",
"dep:zkaleido-sp1-groth16-verifier",
"dep:strata-asm-sp1-guest-builder",
"dep:sp1-verifier",
"dep:sp1-sdk",
"dep:bincode",
]
42 changes: 17 additions & 25 deletions bin/asm-runner/src/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use tokio::{
use crate::{
block_watcher::drive_asm_from_bitcoin,
config::{AsmRpcConfig, BitcoinConfig},
prover::{InputBuilder, ProofOrchestrator},
prover::{InputBuilder, ProofBackend, ProofOrchestrator},
rpc_server::run_rpc_server,
storage::create_storage,
worker_context::AsmWorkerContext,
Expand Down Expand Up @@ -62,37 +62,29 @@ pub(crate) async fn bootstrap(
let proof_db = SledProofDb::open(&orch_config.proof_db_path)?;
let proof_db_clone = proof_db.clone();

#[cfg(feature = "sp1")]
let (asm, moho) = {
use std::fs;

use strata_asm_sp1_guest_builder::{ASM_ELF_PATH, MOHO_ELF_PATH};
use zkaleido_sp1_host::SP1Host;
let asm_elf = fs::read(ASM_ELF_PATH)
.unwrap_or_else(|err| panic!("failed to read guest elf at {ASM_ELF_PATH}: {err}"));
let moho_elf = fs::read(MOHO_ELF_PATH)
.unwrap_or_else(|err| panic!("failed to read guest elf at {MOHO_ELF_PATH}: {err}"));
(SP1Host::init(&asm_elf), SP1Host::init(&moho_elf))
};

#[cfg(not(feature = "sp1"))]
let (asm, moho) = {
use moho_recursive_proof::MohoRecursiveProgram;
use strata_asm_proof_impl::program::AsmStfProofProgram;
(
AsmStfProofProgram::native_host(),
MohoRecursiveProgram::native_host(),
)
};
let ProofBackend {
asm_host,
moho_host,
asm_predicate,
moho_predicate,
} = ProofBackend::new()?;

let input_builder = InputBuilder::new(
state_db.clone(),
bitcoin_client.clone(),
proof_db.clone(),
params.anchor.block,
asm_predicate,
moho_predicate,
);
let mut orchestrator = ProofOrchestrator::new(
proof_db,
asm_host,
moho_host,
orch_config,
input_builder,
rx,
);
let mut orchestrator =
ProofOrchestrator::new(proof_db, asm, moho, orch_config, input_builder, rx);

// ZkVmRemoteProver is !Send (#[async_trait(?Send)]), so the orchestrator
// future cannot be spawned on a multi-threaded runtime directly. We run it
Expand Down
1 change: 1 addition & 0 deletions bin/asm-runner/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use strata_tasks::TaskManager;
use tokio::runtime::Builder;
use tracing::info;
use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
use zkaleido_native_adapter as _;

use crate::{bootstrap::bootstrap, config::AsmRpcConfig};

Expand Down
158 changes: 158 additions & 0 deletions bin/asm-runner/src/prover/backend.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//! ZK proof backend setup for the runner.
//!
//! Bundles the feature-gated selection of the ZK proof backend in one place:
//! host construction (SP1 or native), and derivation of the [`PredicateKey`]
//! that authorizes proofs from each host. The result is exposed as a single
//! [`ProofBackend`] value that the runner builds once at startup and threads
//! into the proof orchestrator and the input builder.

use anyhow::{Result, bail};
use strata_predicate::{PredicateKey, PredicateTypeId};
use zkaleido::{ZkVm, ZkVmHost};
#[cfg(feature = "sp1")]
use {
anyhow::Context,
sp1_sdk::{HashableKey, SP1VerifyingKey},
sp1_verifier::GROTH16_VK_BYTES,
zkaleido_sp1_groth16_verifier::SP1Groth16Verifier,
zkaleido_sp1_host::SP1Host,
};

/// Concrete host type used by the proof orchestrator.
///
/// Resolves to [`SP1Host`] when the `sp1` feature is enabled, otherwise to
/// the in-process [`zkaleido_native_adapter::NativeHost`].
#[cfg(feature = "sp1")]
pub(crate) type ProofHost = SP1Host;

#[cfg(not(feature = "sp1"))]
pub(crate) type ProofHost = zkaleido_native_adapter::NativeHost;

/// ZK proof backend used by the runner.
///
/// Bundles the `(asm, moho)` host pair together with the [`PredicateKey`] that
/// each one's proofs verify against. Constructed once at startup via
/// [`ProofBackend::new`] and consumed by the proof orchestrator (hosts) and
/// the input builder (predicates).
#[derive(Debug)]
pub(crate) struct ProofBackend {
pub(crate) asm_host: ProofHost,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider adding #[derive(Debug)] here (if ProofHost implements Debug). The workspace lints warn on missing_debug_implementations, and while pub(crate) structs are likely exempt, the derive would be consistent with the project's conventions and useful for logging/diagnostics during startup.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check 2c5fd26.

pub(crate) moho_host: ProofHost,
pub(crate) asm_predicate: PredicateKey,
pub(crate) moho_predicate: PredicateKey,
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider consuming ProofBackend via pattern destructuring at the call site instead of field-by-field access. This makes the intent clearer and prevents accidental partial moves:

let ProofBackend { asm_host, moho_host, asm_predicate, moho_predicate } = ProofBackend::new()?;

Minor — the current pub(crate) field approach works fine.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check 2c5fd26.

impl ProofBackend {
/// Builds the ZK proof backend.
///
/// Constructs both proof hosts and resolves the [`PredicateKey`] each
/// host's proofs verify against.
///
/// # Errors
///
/// Returns an error if either host cannot be constructed (e.g. a guest
/// ELF cannot be read in `sp1` builds) or if either host's verifying key
/// cannot be turned into a [`PredicateKey`].
pub(crate) fn new() -> Result<Self> {
let (asm_host, moho_host) = build_proof_hosts()?;
let asm_predicate = resolve_predicate(&asm_host)?;
let moho_predicate = resolve_predicate(&moho_host)?;
Ok(Self {
asm_host,
moho_host,
asm_predicate,
moho_predicate,
})
}
}

/// Builds the `(asm, moho)` host pair used by the proof orchestrator.
///
/// With the `sp1` feature, both hosts are SP1 hosts initialized from the
/// embedded guest ELFs and capable of dispatching proofs to a remote SP1
/// prover. Without the `sp1` feature, both hosts are native (in-process)
/// hosts that simply execute the proof programs and do not produce real
/// cryptographic proofs.
///
/// # Errors
///
/// With the `sp1` feature, returns an error if either guest ELF cannot be
/// read from the path baked into the guest builder.
#[cfg(feature = "sp1")]
fn build_proof_hosts() -> Result<(ProofHost, ProofHost)> {
use std::fs;

use strata_asm_sp1_guest_builder::{ASM_ELF_PATH, MOHO_ELF_PATH};

let asm_elf = fs::read(ASM_ELF_PATH)
.with_context(|| format!("failed to read ASM guest ELF at {ASM_ELF_PATH}"))?;
let moho_elf = fs::read(MOHO_ELF_PATH)
.with_context(|| format!("failed to read Moho guest ELF at {MOHO_ELF_PATH}"))?;

Ok((SP1Host::init(&asm_elf), SP1Host::init(&moho_elf)))
}

#[cfg(not(feature = "sp1"))]
fn build_proof_hosts() -> Result<(ProofHost, ProofHost)> {
use moho_recursive_proof::MohoRecursiveProgram;
use strata_asm_proof_impl::program::AsmStfProofProgram;

Ok((
AsmStfProofProgram::native_host(),
MohoRecursiveProgram::native_host(),
))
}

/// Resolves the [`PredicateKey`] for proofs produced by `host`.
///
/// The returned key carries both the predicate type (matching the host's
/// [`ZkVm`] backend) and the encoded verifying-key material required to
/// validate proofs from that host.
///
/// # Errors
///
/// - For SP1 hosts, returns an error if the host's verifying key cannot be deserialized into an
/// `SP1VerifyingKey` or if the SP1 Groth16 verifier cannot be loaded for the resulting program
/// hash.
/// - For Risc0 hosts, returns an error because predicate resolution is not yet implemented for that
/// backend.
/// - When built without the `sp1` feature, an SP1 host returns an error because the SP1
/// verifying-key handling is gated behind that feature.
fn resolve_predicate(host: &impl ZkVmHost) -> Result<PredicateKey> {
match host.zkvm() {
// Native execution does not produce a real cryptographic proof; the
// predicate simply carries the verifying-key bytes verbatim under the
// BIP-340 Schnorr type as a placeholder identifier.
ZkVm::Native => Ok(PredicateKey::new(
PredicateTypeId::Bip340Schnorr,
host.vk().as_bytes().to_vec(),
)),

// SP1 proofs are wrapped in a Groth16 proof, so the on-chain
// predicate must identify the SP1 Groth16 verifying key (not the SP1
// program vk itself). The conversion is:
// 1. Decode the SP1 verifying key from the host's raw bytes.
// 2. Hash it to obtain the program commitment expected by the Groth16 verifier.
// 3. Load the matching Groth16 verifier and serialize its vk into the predicate key.
#[cfg(feature = "sp1")]
ZkVm::SP1 => {
let vk = host.vk();
let sp1_vk: SP1VerifyingKey = bincode::deserialize(vk.as_bytes())
.context("failed to deserialize SP1 verifying key")?;

let verifier = SP1Groth16Verifier::load(&GROTH16_VK_BYTES, sp1_vk.hash_bytes())
.context("failed to load SP1 Groth16 verifier")?;

Ok(PredicateKey::new(
PredicateTypeId::Sp1Groth16,
verifier.vk.to_uncompressed_bytes(),
))
}
#[cfg(not(feature = "sp1"))]
ZkVm::SP1 => bail!("SP1 predicate key resolution requires the `sp1` feature"),

// Risc0 support is not yet wired up; surface a clear error rather
// than panicking so callers can fail gracefully.
ZkVm::Risc0 => bail!("predicate key resolution is not implemented for Risc0"),
}
}
Loading
Loading