|
| 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 | +} |
0 commit comments