pub(crate) mod execute;
pub(crate) mod execute_only;
pub(crate) mod prove;
pub(crate) mod recurser;
pub(crate) mod setup;
pub(crate) mod upload;
pub(crate) mod verify_constraints;
pub(crate) mod wrap;
pub use execute_only::{EmbeddedExecuteOnlyBuilder, EmbeddedExecuteOnlyClient};
use std::marker::PhantomData;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use crate::setup::SetupResult;
use crate::{Result, SdkError};
use zisk_common::ProofKind;
use zisk_common::{ProgramVK, Proof, PublicValues, ZiskPaths};
use zisk_prover_backend::{Asm, AsmOptions, AsmProver, Emu, EmuProver, GuestProgram, ZiskProver};
use crate::aggregate_proofs::{AggregateProofsRequest, AggregationInput};
use crate::lifecycle::{SetupTarget, UploadTarget};
use crate::recurser::Recurser;
use crate::upload::UploadResult;
use crate::{
execute::{ExecuteRequest, ExecuteResult},
hints::HintsSource,
input_source::InputSource,
job_handle::{JobHandle, SubscriberList},
opts::EmbeddedOpts,
prove::ProveRequest,
setup::SetupRequest,
upload::UploadRequest,
wrap::WrapRequest,
Client, ClientSync, ExecutorKind,
};
const ERR_ASSEMBLY_NOT_ENABLED: &str =
"Assembly executor not enabled — call .assembly() on the builder";
const ERR_HINTS_REQUIRE_ASSEMBLY: &str = "Hints require Assembly executor";
const ERR_STREAM_STDIN_ON_EMULATOR: &str =
"Stream stdin (quic://, unix://) is not supported with the Emulator executor — use Assembly executor";
const ERR_GRPC_ON_EMBEDDED: &str =
"gRPC streams are not supported with the embedded executor — use a remote client";
const ERR_SETUP_WITHOUT_HINTS: &str =
"Program was set up without hints — call setup().with_hints() first";
const ERR_SETUP_WITH_HINTS: &str = "Program was set up with hints — call .hints() on the request";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum HintsKind {
Inline,
LocalStream,
GrpcStream,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum StdinKind {
Buffered,
LocalStream,
GrpcStream,
}
impl HintsKind {
fn of(hints: &HintsSource) -> Self {
match hints {
HintsSource::Hints(_) => HintsKind::Inline,
HintsSource::Stream(s) if s.is_grpc() => HintsKind::GrpcStream,
HintsSource::Stream(_) => HintsKind::LocalStream,
}
}
}
impl StdinKind {
fn of(stdin: &InputSource) -> Self {
match stdin {
InputSource::Stdin(_) => StdinKind::Buffered,
InputSource::Stream(s) if s.is_grpc() => StdinKind::GrpcStream,
InputSource::Stream(_) => StdinKind::LocalStream,
}
}
}
pub(crate) fn validate_embedded_request(
prover_is_asm: bool,
executor: ExecutorKind,
hints: Option<HintsKind>,
stdin: StdinKind,
was_setup_with_hints: bool,
) -> Result<()> {
match executor {
ExecutorKind::Emulator => {
if hints.is_some() {
return Err(SdkError::UnsupportedExecutor(ERR_HINTS_REQUIRE_ASSEMBLY.to_string()));
}
if stdin != StdinKind::Buffered {
return Err(SdkError::UnsupportedExecutor(
ERR_STREAM_STDIN_ON_EMULATOR.to_string(),
));
}
Ok(())
}
ExecutorKind::Assembly => {
if !prover_is_asm {
return Err(SdkError::UnsupportedExecutor(ERR_ASSEMBLY_NOT_ENABLED.to_string()));
}
match hints {
Some(hints) => {
if !was_setup_with_hints {
return Err(SdkError::InvalidConfig(ERR_SETUP_WITHOUT_HINTS.to_string()));
}
if hints == HintsKind::GrpcStream {
return Err(SdkError::UnsupportedExecutor(
ERR_GRPC_ON_EMBEDDED.to_string(),
));
}
Ok(())
}
None => {
if was_setup_with_hints {
return Err(SdkError::InvalidConfig(ERR_SETUP_WITH_HINTS.to_string()));
}
if stdin == StdinKind::GrpcStream {
return Err(SdkError::UnsupportedExecutor(
ERR_GRPC_ON_EMBEDDED.to_string(),
));
}
Ok(())
}
}
}
}
}
pub struct EmbeddedClientBuilder<Out = EmbeddedClient> {
executor: ExecutorKind,
proof_kind: ProofKind,
embedded_opts: EmbeddedOpts,
gpu: bool,
cpu_mops: bool,
asm_options: Option<AsmOptions>,
proving_key: Option<PathBuf>,
proving_key_snark: Option<PathBuf>,
verbose: u8,
no_aggregation: bool,
verify_constraints: bool,
_out: PhantomData<fn() -> Out>,
}
impl<Out> EmbeddedClientBuilder<Out> {
pub(crate) fn for_output() -> Self {
Self {
executor: ExecutorKind::Emulator,
proof_kind: ProofKind::VadcopFinalMinimal,
embedded_opts: EmbeddedOpts::default(),
gpu: false,
cpu_mops: false,
asm_options: None,
proving_key: None,
proving_key_snark: None,
verbose: 0,
no_aggregation: false,
verify_constraints: false,
_out: PhantomData,
}
}
}
impl Default for EmbeddedClientBuilder<EmbeddedClient> {
fn default() -> Self {
Self::for_output()
}
}
pub trait WitnessBuilderExt: Sized {
#[must_use]
fn no_aggregation(self) -> Self;
#[must_use]
fn verify_constraints(self) -> Self;
}
impl<Out> WitnessBuilderExt for EmbeddedClientBuilder<Out> {
fn no_aggregation(mut self) -> Self {
self.no_aggregation = true;
self
}
fn verify_constraints(mut self) -> Self {
self.verify_constraints = true;
self
}
}
impl<Out> EmbeddedClientBuilder<Out> {
#[must_use]
pub fn executor(mut self, executor: ExecutorKind) -> Self {
self.executor = executor;
self
}
#[must_use]
pub fn emulator(mut self) -> Self {
self.executor = ExecutorKind::Emulator;
self
}
#[must_use]
pub fn assembly(mut self) -> Self {
self.executor = ExecutorKind::Assembly;
self
}
#[must_use]
pub fn with_embedded_opts(mut self, opts: EmbeddedOpts) -> Self {
self.embedded_opts = opts;
self
}
#[must_use]
pub fn gpu(mut self) -> Self {
self.gpu = true;
self
}
#[must_use]
pub fn cpu_mops(mut self) -> Self {
self.cpu_mops = true;
self
}
#[must_use]
pub fn plonk(mut self) -> Self {
self.proof_kind = ProofKind::Plonk;
self
}
#[must_use]
pub fn asm_options(mut self, opts: AsmOptions) -> Self {
self.asm_options = Some(opts);
self
}
#[must_use]
pub fn proving_key(mut self, path: impl Into<PathBuf>) -> Self {
self.proving_key = Some(path.into());
self
}
#[must_use]
pub fn proving_key_plonk(mut self, path: impl Into<PathBuf>) -> Self {
self.proving_key_snark = Some(path.into());
self
}
#[must_use]
pub fn verbose(mut self, level: u8) -> Self {
self.verbose = level;
self
}
#[must_use]
pub fn execute_only(self) -> EmbeddedExecuteOnlyBuilder {
EmbeddedExecuteOnlyBuilder::from_parts(self.executor, self.asm_options)
}
fn build_emu(
pk: PathBuf,
pk_snark: PathBuf,
backend_opts: zisk_prover_backend::BackendProverOpts,
proof_kind: ProofKind,
) -> Result<EmbeddedProver> {
let emu = EmuProver::new(
proof_kind == ProofKind::Plonk, backend_opts.preload_plonk(), pk, pk_snark, true, backend_opts.build_proofman_options(), None, )
.map_err(SdkError::backend)?;
Ok(EmbeddedProver::Emu(ZiskProver::<Emu>::new(emu, backend_opts)))
}
fn build_asm(
pk: PathBuf,
pk_snark: PathBuf,
backend_opts: zisk_prover_backend::BackendProverOpts,
proof_kind: ProofKind,
) -> Result<EmbeddedProver> {
let asm_opts = backend_opts.asm_options();
let asm = AsmProver::new(
proof_kind == ProofKind::Plonk, backend_opts.preload_plonk(), pk, pk_snark, true, asm_opts.unlock_mapped_memory, asm_opts.asm_out_file, asm_opts.no_auto_setup, backend_opts.build_proofman_options(), false, None, backend_opts.cpu_mops_enabled(), )
.map_err(SdkError::backend)?;
Ok(EmbeddedProver::Asm(ZiskProver::<Asm>::new(asm, backend_opts)))
}
}
impl<Out: From<EmbeddedClient>> EmbeddedClientBuilder<Out> {
pub fn build(self) -> Result<Out> {
crate::client::ensure_single_instance();
if self.asm_options.is_some() && self.executor != ExecutorKind::Assembly {
panic!(
"asm_options were set but the executor is not Assembly. \
Call .assembly() on the builder before setting asm_options."
);
}
let mut embedded_opts = self.embedded_opts;
if let Some(pk) = self.proving_key {
embedded_opts.proving_key = Some(pk);
}
if let Some(pk) = self.proving_key_snark {
embedded_opts.proving_key_snark = Some(pk);
}
let mut backend_opts = embedded_opts.into_backend_opts(self.gpu);
if self.cpu_mops {
backend_opts = backend_opts.cpu_mops();
}
if self.verbose > 0 {
backend_opts = backend_opts.verbose(self.verbose);
}
if self.no_aggregation {
backend_opts = backend_opts.no_aggregation();
}
if self.verify_constraints {
backend_opts = backend_opts.verify_constraints();
}
if let Some(asm_opts) = self.asm_options {
*backend_opts.asm_options_mut() = asm_opts;
}
let pk = ZiskPaths::get_proving_key(backend_opts.get_proving_key());
let pk_snark = ZiskPaths::get_proving_key_snark(backend_opts.get_proving_key_snark());
let proving_key = pk.clone();
let prover = match self.executor {
ExecutorKind::Emulator => EmbeddedClientBuilder::<Out>::build_emu(
pk,
pk_snark,
backend_opts,
self.proof_kind,
)?,
ExecutorKind::Assembly => EmbeddedClientBuilder::<Out>::build_asm(
pk,
pk_snark,
backend_opts,
self.proof_kind,
)?,
};
Ok(EmbeddedClient { prover: Arc::new(prover), executor: self.executor, proving_key }.into())
}
}
enum EmbeddedProver {
Emu(ZiskProver<Emu>),
Asm(ZiskProver<Asm>),
}
impl EmbeddedProver {
fn register_recurser(&self, output_dir: &str, recurser_id: &str) -> Result<()> {
match self {
EmbeddedProver::Emu(p) => p.register_recurser(output_dir, recurser_id),
EmbeddedProver::Asm(p) => p.register_recurser(output_dir, recurser_id),
}
.map_err(SdkError::backend)
}
fn prove_recurser(
&self,
recurser_id: &str,
proof_a: &proofman_verifier::VadcopFinalProof,
proof_b: &proofman_verifier::VadcopFinalProof,
free_a: &[u64],
free_b: &[u64],
root_c_recurser_agg: Option<[u64; 4]>,
) -> Result<proofman_verifier::VadcopFinalProof> {
match self {
EmbeddedProver::Emu(p) => {
p.prove_recurser(recurser_id, proof_a, proof_b, free_a, free_b, root_c_recurser_agg)
}
EmbeddedProver::Asm(p) => {
p.prove_recurser(recurser_id, proof_a, proof_b, free_a, free_b, root_c_recurser_agg)
}
}
.map_err(SdkError::backend)
}
}
pub struct EmbeddedClient {
prover: Arc<EmbeddedProver>,
executor: ExecutorKind,
pub(crate) proving_key: PathBuf,
}
impl Clone for EmbeddedClient {
fn clone(&self) -> Self {
Self {
prover: Arc::clone(&self.prover),
executor: self.executor,
proving_key: self.proving_key.clone(),
}
}
}
impl Client for EmbeddedClient {
fn run_upload(&self, program: &GuestProgram) -> Result<crate::upload::UploadResult> {
self.do_upload(program)
}
fn run_setup(
&self,
program: &GuestProgram,
with_hints: bool,
emulator_only: bool,
timeout: Option<Duration>,
subs: SubscriberList,
) -> Result<JobHandle<SetupResult>> {
self.do_setup(program, with_hints, emulator_only, timeout, subs)
}
fn run_prove(
&self,
program: &GuestProgram,
stdin: InputSource,
hints: Option<HintsSource>,
executor: ExecutorKind,
proof_kind: ProofKind,
timeout: Option<Duration>,
subs: SubscriberList,
) -> Result<JobHandle<crate::prove::ProveResult>> {
self.do_prove(program, stdin, hints, executor, proof_kind, timeout, subs)
}
fn run_execute(
&self,
program: &GuestProgram,
stdin: InputSource,
hints: Option<HintsSource>,
executor: ExecutorKind,
timeout: Option<Duration>,
subs: SubscriberList,
) -> Result<JobHandle<ExecuteResult>> {
self.do_execute(program, stdin, hints, executor, timeout, subs)
}
fn run_wrap(
&self,
proof: &Proof,
proof_kind: ProofKind,
override_publics: Option<PublicValues>,
override_program_vk: Option<ProgramVK>,
timeout: Option<Duration>,
subs: SubscriberList,
) -> Result<JobHandle<crate::prove::ProveResult>> {
self.do_wrap(proof, proof_kind, override_publics, override_program_vk, timeout, subs)
}
fn run_upload_aggregation_program(&self, agg: &Recurser) -> Result<UploadResult> {
self.do_upload_aggregation_program(agg)
}
fn run_setup_aggregation_program(
&self,
agg: &Recurser,
timeout: Option<Duration>,
subs: SubscriberList,
) -> Result<JobHandle<SetupResult>> {
self.do_setup_aggregation_program(agg, timeout, subs)
}
fn run_aggregate_proofs(
&self,
agg: &Recurser,
proof_a: &Proof,
proof_b: &Proof,
free_a: &[u64],
free_b: &[u64],
root_c_recurser_agg: Option<[u64; 4]>,
timeout: Option<Duration>,
subs: SubscriberList,
) -> Result<JobHandle<crate::prove::ProveResult>> {
self.do_aggregate_proofs(
agg,
proof_a,
proof_b,
free_a,
free_b,
root_c_recurser_agg,
timeout,
subs,
)
}
}
impl ClientSync for EmbeddedClient {
fn run_setup_sync(
&self,
program: &GuestProgram,
with_hints: bool,
emulator_only: bool,
subs: SubscriberList,
) -> Result<SetupResult> {
self.do_setup_sync(program, with_hints, emulator_only, subs)
}
fn run_setup_aggregation_program_sync(
&self,
agg: &Recurser,
subs: SubscriberList,
) -> Result<SetupResult> {
self.do_setup_aggregation_program_sync(agg, subs)
}
fn run_prove_sync(
&self,
program: &GuestProgram,
stdin: InputSource,
hints: Option<HintsSource>,
executor: ExecutorKind,
proof_kind: ProofKind,
subs: SubscriberList,
) -> Result<crate::prove::ProveResult> {
self.do_prove_sync(program, stdin, hints, executor, proof_kind, subs)
}
fn run_execute_sync(
&self,
program: &GuestProgram,
stdin: InputSource,
hints: Option<HintsSource>,
executor: ExecutorKind,
subs: SubscriberList,
) -> Result<ExecuteResult> {
self.do_execute_sync(program, stdin, hints, executor, subs)
}
fn run_wrap_sync(
&self,
proof: &Proof,
proof_kind: ProofKind,
override_publics: Option<PublicValues>,
override_program_vk: Option<ProgramVK>,
subs: SubscriberList,
) -> Result<crate::prove::ProveResult> {
self.do_wrap_sync(proof, proof_kind, override_publics, override_program_vk, subs)
}
}
impl EmbeddedClient {
#[must_use]
pub(crate) fn executor(&self) -> ExecutorKind {
self.executor
}
#[must_use]
pub fn prove<'a>(
&'a self,
program: &'a GuestProgram,
stdin: impl Into<InputSource>,
) -> ProveRequest<'a, Self> {
ProveRequest::new(self, program, stdin, self.executor)
}
#[must_use]
pub fn execute<'a>(
&'a self,
program: &'a GuestProgram,
stdin: impl Into<InputSource>,
) -> ExecuteRequest<'a, Self> {
ExecuteRequest::new(self, program, stdin, self.executor)
}
#[must_use]
pub fn setup<'a, T: Into<SetupTarget<'a>>>(&'a self, target: T) -> SetupRequest<'a, Self> {
SetupRequest::new(self, target.into())
}
#[must_use]
pub fn upload<'a, T: Into<UploadTarget<'a>>>(&'a self, target: T) -> UploadRequest<'a, Self> {
UploadRequest::new(self, target.into())
}
#[must_use]
pub fn wrap_proof<'a>(
&'a self,
proof: &'a Proof,
proof_kind: ProofKind,
) -> WrapRequest<'a, Self> {
WrapRequest::new(self, proof, proof_kind)
}
#[must_use]
pub fn aggregate_proofs<'a>(
&'a self,
agg: &'a Recurser,
input_a: impl Into<AggregationInput<'a>>,
input_b: impl Into<AggregationInput<'a>>,
) -> AggregateProofsRequest<'a, Self> {
AggregateProofsRequest::new(self, agg, input_a.into(), input_b.into())
}
}
#[cfg(test)]
mod validate_tests {
use super::*;
use crate::ExecutorKind::{Assembly, Emulator};
fn assert_unsupported(res: Result<()>, msg: &str) {
match res {
Err(SdkError::UnsupportedExecutor(m)) => assert_eq!(m, msg),
other => panic!("expected UnsupportedExecutor({msg:?}), got {other:?}"),
}
}
fn assert_invalid(res: Result<()>, msg: &str) {
match res {
Err(SdkError::InvalidConfig(m)) => assert_eq!(m, msg),
other => panic!("expected InvalidConfig({msg:?}), got {other:?}"),
}
}
#[test]
fn emulator_buffered_no_hints_ok() {
for is_asm in [false, true] {
assert!(validate_embedded_request(is_asm, Emulator, None, StdinKind::Buffered, false)
.is_ok());
}
}
#[test]
fn emulator_rejects_hints_before_stream_stdin() {
for is_asm in [false, true] {
assert_unsupported(
validate_embedded_request(
is_asm,
Emulator,
Some(HintsKind::Inline),
StdinKind::LocalStream,
false,
),
ERR_HINTS_REQUIRE_ASSEMBLY,
);
}
}
#[test]
fn emulator_rejects_stream_stdin() {
for stdin in [StdinKind::LocalStream, StdinKind::GrpcStream] {
assert_unsupported(
validate_embedded_request(true, Emulator, None, stdin, false),
ERR_STREAM_STDIN_ON_EMULATOR,
);
}
}
#[test]
fn assembly_on_emu_prover_rejected_regardless_of_inputs() {
for hints in [None, Some(HintsKind::Inline), Some(HintsKind::GrpcStream)] {
for stdin in [StdinKind::Buffered, StdinKind::LocalStream, StdinKind::GrpcStream] {
assert_unsupported(
validate_embedded_request(false, Assembly, hints, stdin, true),
ERR_ASSEMBLY_NOT_ENABLED,
);
}
}
}
#[test]
fn assembly_hints_setup_with_hints_ok() {
for h in [HintsKind::Inline, HintsKind::LocalStream] {
assert!(validate_embedded_request(true, Assembly, Some(h), StdinKind::Buffered, true)
.is_ok());
}
}
#[test]
fn assembly_hints_but_setup_without_hints_is_invalid_config() {
assert_invalid(
validate_embedded_request(
true,
Assembly,
Some(HintsKind::Inline),
StdinKind::Buffered,
false,
),
ERR_SETUP_WITHOUT_HINTS,
);
}
#[test]
fn assembly_grpc_hints_rejected() {
assert_unsupported(
validate_embedded_request(
true,
Assembly,
Some(HintsKind::GrpcStream),
StdinKind::Buffered,
true,
),
ERR_GRPC_ON_EMBEDDED,
);
}
#[test]
fn assembly_setup_error_takes_precedence_over_grpc() {
assert_invalid(
validate_embedded_request(
true,
Assembly,
Some(HintsKind::GrpcStream),
StdinKind::Buffered,
false,
),
ERR_SETUP_WITHOUT_HINTS,
);
}
#[test]
fn assembly_no_hints_ok_for_buffered_and_local_stream() {
for stdin in [StdinKind::Buffered, StdinKind::LocalStream] {
assert!(validate_embedded_request(true, Assembly, None, stdin, false).is_ok());
}
}
#[test]
fn assembly_no_hints_but_setup_with_hints_is_invalid_config() {
assert_invalid(
validate_embedded_request(true, Assembly, None, StdinKind::Buffered, true),
ERR_SETUP_WITH_HINTS,
);
}
#[test]
fn assembly_no_hints_grpc_stdin_rejected() {
assert_unsupported(
validate_embedded_request(true, Assembly, None, StdinKind::GrpcStream, false),
ERR_GRPC_ON_EMBEDDED,
);
}
#[test]
fn assembly_no_hints_setup_error_takes_precedence_over_grpc_stdin() {
assert_invalid(
validate_embedded_request(true, Assembly, None, StdinKind::GrpcStream, true),
ERR_SETUP_WITH_HINTS,
);
}
}