Prover

The zkm_sdk crate provides all the necessary tools for proof generation. Key features include the ProverClient, enabling you to:

  • Initialize proving/verifying keys via setup().
  • Execute your program via execute().
  • Generate proofs with prove().
  • Verify proofs through verify().

When generating Groth16 or PLONK proofs, the ProverClient automatically downloads the pre-generated proving key (pk) from a trusted setup by calling try_install_circuit_artifacts().

Example: Fibonacci

The following code is an example of using zkm_sdk in host.

use zkm_sdk::{include_elf, utils, ProverClient, ZKMProofWithPublicValues, ZKMStdin};

/// The ELF we want to execute inside the zkVM.
const ELF: &[u8] = include_elf!("fibonacci");

fn main() {
    // Create an input stream and write '1000' to it.
    let n = 1000u32;

    // The input stream that the guest will read from using `zkm_zkvm::io::read`. Note that the
    // types of the elements in the input stream must match the types being read in the program.
    let mut stdin = ZKMStdin::new();
    stdin.write(&n);

    // Create a `ProverClient` method.
    let client = ProverClient::new();

    // Execute the guest using the `ProverClient.execute` method, without generating a proof.
    let (_, report) = client.execute(ELF, stdin.clone()).run().unwrap();
    println!("executed program with {} cycles", report.total_instruction_count());

    // Generate the proof for the given program and input.
    let (pk, vk) = client.setup(ELF);
    let mut proof = client.prove(&pk, stdin).run().unwrap();

    // Read and verify the output.
    //
    // Note that this output is read from values committed to in the program using
    // `zkm_zkvm::io::commit`.
    let n = proof.public_values.read::<u32>();
    let a = proof.public_values.read::<u32>();
    let b = proof.public_values.read::<u32>();

    println!("n: {}", n);
    println!("a: {}", a);
    println!("b: {}", b);

    // Verify proof and public values
    client.verify(&proof, &vk).expect("verification failed");
}

Proof Types

zkMIPS provides customizable proof generation options:

#![allow(unused)]
fn main() {
/// A proof generated with zkMIPS of a particular proof mode.
#[derive(Debug, Clone, Serialize, Deserialize, EnumDiscriminants, EnumTryAs)]
#[strum_discriminants(derive(Default, Hash, PartialOrd, Ord))]
#[strum_discriminants(name(ZKMProofKind))]
pub enum ZKMProof {
    /// A proof generated by the core proof mode.
    ///
    /// The proof size scales linearly with the number of cycles.
    #[strum_discriminants(default)]
    Core(Vec<ShardProof<CoreSC>>),
    /// A proof generated by the compress proof mode.
    ///
    /// The proof size is constant, regardless of the number of cycles.
    Compressed(Box<ZKMReduceProof<InnerSC>>),
    /// A proof generated by the Plonk proof mode.
    Plonk(PlonkBn254Proof),
    /// A proof generated by the Groth16 proof mode.
    Groth16(Groth16Bn254Proof),
}
}

Core Proof (Default)

The default prover mode generates a sequence of STARK proofs whose cumulative proof size scales linearly with the execution trace length.

#![allow(unused)]
fn main() {
let client = ProverClient::new();
client.prove(&pk, stdin).run().unwrap();
}

Compressed Proof

The compressed proving mode generates constant-sized STARK proofs, but not suitable for on-chain verification.

#![allow(unused)]
fn main() {
let client = ProverClient::new();
client.prove(&pk, stdin).compressed().run().unwrap();
}

The Groth16 proving mode ​generates succinct SNARK proofs with a compact size of approximately 260 bytes, ​and features on-chain verification.

#![allow(unused)]
fn main() {
let client = ProverClient::new();
client.prove(&pk, stdin).groth16().run().unwrap();
}

PLONK Proof

The PLONK proving mode generates succinct SNARK proofs with a compact size of approximately 868 bytes, while maintaining on-chain verifiability. In contrast to Groth16, PLONK removes the dependency on trusted setup ceremonies.

#![allow(unused)]
fn main() {
let client = ProverClient::new();
client.prove(&pk, stdin).plonk().run().unwrap();
}

Hardware Acceleration

zkMIPS provides hardware acceleration support for AVX256/AVX512 on x86 CPUs due to support in Plonky3.

You can check your CPU's AVX compatibility by running:

grep avx /proc/cpuinfo

Check if you can see avx2 or avx512 in the results.

To activate AVX256 optimization, add these flags to your RUSTFLAGS environment variable:

RUSTFLAGS="-C target-cpu=native" cargo run --release

To activate AVX512 optimization, add these flags to your RUSTFLAGS environment variable:

RUSTFLAGS="-C target-cpu=native -C target-feature=+avx512f" cargo run --release

Network Prover

We support the use of a network prover via the ZKM proof network, accessible through our RESTful API. By default, it uses the Groth16 proving mode. >The proving process consists of several stages: queuing, splitting, proving, aggregating and finalizing. Each stage involves a varying duration.

Requirements

zkm-sdk = { git = "https://github.com/zkMIPS/zkm-project-template", branch = "main", features = ["snark"] }

Environment Variable Setup

Before running your application, make sure to export the required environment variable to enable the network prover. Here's an example:

export ZKM_PROVER=${ZKM_PROVER-"network"}
export RUST_LOG=${RUST_LOG-info}
export SEG_SIZE=${SEG_SIZE-65536}
export OUTPUT_DIR=${BASEDIR}/output
export EXECUTE_ONLY=false

##network proving
export CA_CERT_PATH=${BASEDIR}/tool/ca.pem
export CERT_PATH=${BASEDIR}/tool/cert.pem
export KEY_PATH=${BASEDIR}/tool/key.pem
##The private key corresponding to the public key when registering in the https://www.zkm.io/apply
export PROOF_NETWORK_PRVKEY=
export ENDPOINT=https://152.32.186.45:20002    ##the test entry of zkm proof network
export DOMAIN_NAME=stage

Example

The following is an example of using the network prover on the host:

const ELF: &[u8] = include_bytes!(env!("ZKM_ELF_bitvm2-covenant"));
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    env_logger::try_init().unwrap_or_default();

    //  Directory for output files
    let output_dir = env::var("OUTPUT_DIR").unwrap_or(String::from("./output"));
    let seg_size = env::var("SEG_SIZE").unwrap_or("262144".to_string());
    let seg_size = seg_size.parse::<_>().unwrap_or(262144);
    let execute_only = env::var("EXECUTE_ONLY").unwrap_or("false".to_string());
    let execute_only = execute_only.parse::<bool>().unwrap_or(false);

    // Network endpoint, should be: https://152.32.186.45:20002
    let endpoint = env::var("ENDPOINT").map_or(None, |endpoint| Some(endpoint.to_string()));
    let ca_cert_path = env::var("CA_CERT_PATH").map_or(None, |path| Some(path.to_string()));
    let cert_path = env::var("CERT_PATH").map_or(None, |x| Some(x.to_string()));
    // The private key path of the certificate
    let key_path = env::var("KEY_PATH").map_or(None, |x| Some(x.to_string()));
    let domain_name = Some(env::var("DOMAIN_NAME").unwrap_or("stage".to_string()));
    // The private key of the proof network, which is used to sign the proof.
    let proof_network_privkey =
        env::var("PROOF_NETWORK_PRVKEY").map_or(None, |x| Some(x.to_string()));

    // Create configuration for the prover client
    let prover_cfg = ClientCfg {
        zkm_prover_type: "network".to_string(),
        endpoint,
        ca_cert_path,
        cert_path,
        key_path,
        domain_name,
        proof_network_privkey,
    };
    let prover_client = ProverClient::new(&prover_cfg).await;

    let mut prover_input = ProverInput {
        elf: Vec::from(ELF),
        seg_size,
        execute_only,
        ..Default::default()
    };
    // If the guest program doesn't have inputs, it doesn't need the setting.
    set_guest_input(&mut prover_input);
    
    // Proving
    let proving_result = prover_client.prover.prove(&prover_input, None).await;
    
    // Write the proof to the output directory
    match proving_result {
        Ok(Some(prover_result)) => {
            if !execute_only {
                if prover_result.proof_with_public_inputs.is_empty() {
                    log::info!(
                        "Fail: snark_proof_with_public_inputs.len() is : {}.Please try setting SEG_SIZE={}",
                        prover_result.proof_with_public_inputs.len(), seg_size/2
                    );
                }
                let output_path = Path::new(&output_dir);
                let proof_result_path =
                    output_path.join("snark_proof_with_public_inputs.json");
                let mut f = file::new(&proof_result_path.to_string_lossy());
                match f.write(prover_result.proof_with_public_inputs.as_slice()) {
                    Ok(bytes_written) => {
                        log::info!("Proof: successfully written {} bytes.", bytes_written);
                    }
                    Err(e) => {
                        log::info!("Proof: failed to write to file: {}", e);
                    }
                }
                log::info!("Generating proof successfully.");
            } else {
                log::info!("Generating proof successfully .The proof is not saved.");
            }
        }
        Ok(None) => {
            log::info!("Failed to generate proof.The result is None.");
        }
        Err(e) => {
            log::info!("Failed to generate proof. error: {}", e);
        }
    }
    Ok(())
}

fn set_guest_input(prover_input: &mut ProverInput) {
    // Combine all the inputs into a two-dimensional array
    let mut private_input: Vec<Vec<u8>> = vec![];

    let goat_withdraw_txid: Vec<u8> =
        hex::decode(std::env::var("GOAT_WITHDRAW_TXID").unwrap_or("32bc8a6c5b3649f92812c461083bab5e8f3fe4516d792bb9a67054ba040b7988".to_string())).unwrap();
    write_to_guest_private_input(&mut private_input, &goat_withdraw_txid);
    
    // Encode private input into a one-dimensional array and pass it to the proof network.
    let mut pri_buf = Vec::new();
    bincode::serialize_into(&mut pri_buf, &private_input).expect("private_input serialization failed");
    prover_input.private_inputstream = pri_buf;
}

[!NOTE] The proof network uses stdin.write_vec() to write private input data to the guest program. If your guest program uses zkm_zkvm::io::read(); to read this input, you must serialize it before pushing to the private input:

#![allow(unused)]
fn main() {
fn write_to_guest_private_input(private_input: &mut Vec<Vec<u8>>, data: &[u8]) {
    let mut tmp = Vec::new();
    bincode::serialize_into(&mut tmp, data).expect("serialization failed");
    private_input.push(tmp);
}
}