Updates

SP1 Verifier on Sui

Feb 19, 2025

Yellow Flower
Yellow Flower

📢 Announcing SP1 Proofs on Sui!

In collaboration with Sui Foundation and Succinct Labs, Soundness Labs is bringing SP1 proofs to the Sui Ecosystem. This integration makes it easier than ever for developers to build zkApps without deep cryptography expertise. By leveraging SP1 zkVM and Sui’s built-in zk verification, developers can now seamlessly create and deploy scalable, privacy-preserving applications.

💡 TL;DR

Sui is a layer-1 blockchain designed to address the scalability and efficiency challenges that have long hindered decentralized applications. Key benefits of Sui are:

  • High Throughput & Low Latency: Sui is optimized for fast transaction processing, ensuring smooth dApp experiences.

  • Efficient zkProofs Integration: Native support for zk proofs, enabling privacy-preserving applications.

  • Strong Ecosystem & Adoption: Projects like zkLogin, zkSend, and Stashed demonstrate its real-world zkApp applications.

SP1 is a zero-knowledge virtual machine (zkVM) that makes creating zero-knowledge proofs way easier, the developers just write regular code, no need to get into the complex cryptography or special frameworks.

  • SP1 is the fastest zkVM (zero-knowledge virtual machine) to date, making zk proof development much easier.

  • Developers can simply write standard Rust code—no cryptography expertise or long, complicated audits required.

Soundness labs is a forward-looking company committed to the advancement of blockchain technology through innovative zkApps.

  • Soundness Labs has open-sourced an SP1 Groth16 SNARK verifier that makes zk proofs generated by SP1 compatible with the Sui blockchain.

  • Developers can now build zkApps more efficiently using SP1 zkVM and deploy them on Sui.

By combining Sui's powerful infrastructure with SP1's zkVM capabilities, developers can build more efficient and privacy-preserving blockchain applications, paving the way for scalable and user-friendly decentralized solutions.


🔍 Introduction

🖥 What Exactly is a zkVM?

A zkVM (zero-knowledge virtual machine) executes a program and generates a zero-knowledge proof to verify the correctness of execution—without revealing the underlying data. Unlike custom circuits, which are built for specific programs, zkVMs are universal, allowing developers to run any program within the VM using high level languages like Rust.

However, zkVMs come with challenges. Proving execution is significantly slower than custom circuit zk implementations, limiting adoption to blockchain use cases where latency is important. Despite these challenges, zkVMs are evolving rapidly, with precompiles, recursion, and hardware acceleration helping close the performance gap.

⚡ What is SP1?

SP1, developed by Succinct labs, is the most performant zkVM to date and simplifies zk proof development:

  • Easy to use: Developers write Rust code instead of designing circuits.

  • Fast: Optimized for blockchain tasks like rollups, digital signatures, and state proofs.

🔗 What is Sui?

Sui is a layer-1 blockchain designed for scalability and efficiency. Its high-performance architecture enables:

  • Low-latency, high-throughput transactions

  • Developer-friendly tools for building scalable dApps

  • Native zk support with zkLogin and custom zk verifiers

Popular zkApps on Sui include zkLogin, zkSend, and Stashed.

🔐 What is zkLogin?

  • zkLogin, a native Sui primitive, has become one of the most popular zkApps.

  • The Sui blockchain has already handled over 4 million zkLogin-signed transactions, with around 1.5 million unique zk proofs.

  • This makes it one of the most successful real-world applications of zk proofs.

🔥 Soundness Labs' Vision

Soundness Labs is dedicated to advancing zk technology. Our flagship product, Soundness Layer, redefines data verification across chains using Walrus and Sui.

🥞 What is Soundness layer?

  • Soundness Layer is a decentralized verification solution designed for low latency, high throughput, and cross-chain compatibility.

  • It features a censorship-resistant sequencer and multi-asset re-staking, making it ideal for zkApps and enhancing their scalability and security.


🛠 Using SP1 on Sui: JWT beyond zkLogin

To see how SP1 zkVM works in action, let's go through a simple example. Imagine a service that only allows access to employees of a specific company such that a user can gain access only by proving they have a valid email address with the domain company.xyz.

With zkLogin, we know that a user who has access to an existing Web2 account receives an ID token, known as a JSON Web Token (JWT), in the following format:

For example, the zkLogin address is derived from the following details in the JWT:

While zkLogin addresses don’t currently use email as an input, this example explores a scenario where users can reveal their email domain while keeping their full email address private. More specifically, this relation can be defined as follows:

such that the public and private values are highlighted with blue and red colors, respectively.

🏗️ How to Build it?

As said before, the idea is simple: we take a JWT that contains an email address, verify its validity using RSA signatures (RSA+Sha256), and then check if the email belongs to a specific domain, all while keeping sensitive details like the rest of JWT, full email address, etc private.

Here is how it works: first, we extract the JWT from the input and break it down into its components, (header, payload, signature). Then, we verify the signature using the provided RSA public key, ensuring the token is legit under the public key of the issuer, e.g. Google. This key is rotated once in a while, and to trust the new key, known as a JWK, we can use on-chain confirmation through a Move smart contract available in this link. JWKs are available on Sui and can be made accessible to developers in the future, but that is beyond the scope of this demonstration.

Next, we pull the email address from the token, split it into its local part (before the @) and domain (after the @), and check if the domain matches what we are expecting. Finally, we commit these verification results so they can be used as part of a proof. This way, users can prove they belong to a certain company (based on their email domain) without revealing their full email address. Here is the code to implement this:

// program/src/main.rs
#![no_main]
sp1_zkvm::entrypoint!(main);

use lib::{split_email, split_jwt, pem_to_der};
use rsa::{pkcs8::DecodePublicKey, Pkcs1v15Sign, RsaPublicKey};
use sha2_v0_10_8::{Digest, Sha256};

pub fn main() {
    // Read input values: JWT token, RSA public key, and the claimed domain
    let token = sp1_zkvm::io::read::<String>();
    let rsa_public_key = sp1_zkvm::io::read::<String>();
    let domain = sp1_zkvm::io::read::<String>();

    // Commit the domain to the zk proof (so it’s publicly known)
    sp1_zkvm::io::commit(&domain);

    // Split the JWT into its components: header, payload, and signature
    let (header, payload, signature) = split_jwt(&token)
        .expect("Failed to decode JWT");
    
    // Convert the PEM public key into DER format for RSA verification
    let pk_der = pem_to_der(&rsa_public_key);
    let public_key = RsaPublicKey::from_public_key_der(&pk_der).unwrap();

    // Reconstruct the signing input (header + payload) as a string
    let signing_input = format!(
        "{}.{}",
        token.split('.').collect::<Vec<&str>>()[0], // First part: header
        token.split('.').collect::<Vec<&str>>()[1]  // Second part: payload
    );

    // Hash the signing input using SHA256
    let mut hasher = Sha256::new();
    hasher.update(signing_input);
    let hashed_msg = hasher.finalize();

    // Verify the JWT signature using the provided RSA public key
    let verification_result = match public_key.verify(Pkcs1v15Sign::new::<Sha256>(), &hashed_msg, &signature) {
        Ok(_) => true,  // Signature is valid
        Err(_) => false, // Signature is invalid
    };

    // Commit the verification result (Confirming that the JWT is valid)
    sp1_zkvm::io::commit(&verification_result);

    // Fetch and split the email address from the JWT payload
    let email_parts = split_email(payload.get("email").unwrap().to_string()).unwrap();
    // Check if the email domain matches the expected domain
    let verified = email_parts.domain == domain;
    // Commit the verification result (proof that the email domain is correct)
    sp1_zkvm::io::commit(&verified);
}

To compile this code with specific inputs, we need to provide the token, rsa_public_key, and domain. To do this, we define these variables and call the program/src/main.rs file with the desired inputs from script/src/main.rs.

//! A simple script to generate and verify the proof of a given program.
use sp1_sdk::{include_elf, utils, ProverClient, SP1ProofWithPublicValues, SP1Stdin};
const JSON_ELF: &[u8] = include_elf!("json-program");

fn main() {
    // Set up logging for debugging and tracing.
    utils::setup_logger();

    // Initialize the proof input.
    let mut stdin = SP1Stdin::new();

    // Example JWT token (normally this would come from a real source).
    let token: &str = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJlbWFpbCI6InRlc3RAZ21haWwuY29tIiwiYXVkIjoiNTc1NTE5MjA0MjM3LW1zb3A5ZXA0NXUydW85OGhhcHFtbmd2OGQ4NHFkYzhrLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic2FsdCI6IjY1ODg3NDE0NjkwNTA1MDI0MjE1NTAxNDAxMDUzNDUwNTA4NTkifQ.CTd77H763dYAJqsktBOBQeLK0YHYq7VQUH8E0S8vCQt6amEkUxCjb8oCaoJG_iAiWTzWan0v8treLtiOCaFtHav8vfMbE-x_hVB74LYrBa192k_oWXvlmMoyfVaRuFj9iVtKakY8PXVfMQWq9Znlus9Hg5I0CRhJpAkUTmcZTUs1TSjR_td2pPRcag46QXicafT6AvGCkLDzeMKbF7o6o5zhIUa8hd7sBrW-Ru1Uo3BdIu2KCmaE-o9xnanCB_-CB-S_reUUh692UhM_urnr3XA_s76a2jihYMMT_sbb6j25sadGN1dLbOnh05fWg-ikWYTOn0xwtqPSflWkKeVt6g";

    // RSA public key (used for verifying the JWT signature).
    let rsa_public_key = r#"-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr2xKE7fuT/VV2Lk7gfCk
A4xOTcFXWboTJ6ZGx1zWCP8d1pY5mYPx/dTUgDtUjaYGIRJy6G8xYLZvj22aY3l/
DdfgLfk4Br9katexMSmKR0C9hVBWDbCk6ROK9dqEXuzGmpXhfcYs/9dL2N+Cptjs
S3PcBjxslcBJhUM60jLV+13No95DBw1f1PCEb3QNffxxVBEYLzv12xgafSjaCo+u
Y/BUgKbmU3OO6W1w+8z817t+n11mufobCHpyx5f9x7O66gEcT8YT6FtYEPSYVbxP
qXveBZaVAUe0uKlvd7yZE5ZAfyKHLNpT85ay/yfA6O4B9hwslM2El5ge3FKL53jV
FQIDAQAB
-----END PUBLIC KEY-----"#;

    // Domain we want to verify in the email.
    let domain: &str = "gmail.com";

    // Provide inputs to the proof system.
    stdin.write(&token);
    stdin.write(&rsa_public_key);
    stdin.write(&domain);

    // Initialize the prover.
    let client = ProverClient::from_env();

    // Set up the proving and verification keys.
    let (pk, vk) = client.setup(JSON_ELF);

    // Generate the proof.
    let proof = client.prove(&pk, &stdin).run().expect("proving failed");

    // Verify the proof.
    client.verify(&proof, &vk).expect("verification failed");

    // Save the proof to a file.
    proof
        .save("proof-with-io.bin")
        .expect("saving proof failed");

    // Load the proof from the file to test serialization/deserialization.
    let deserialized_proof =
        SP1ProofWithPublicValues::load("proof-with-io.bin").expect("loading proof failed");

    // Verify the deserialized proof to ensure consistency.
    client
        .verify(&deserialized_proof, &vk)
        .expect("verification failed");

    // Print success message.
    println!("Successfully generated and verified proof for the program!");
}

By running this code, we generate the proof and ELF files from SP1. Now, we are ready for the next step—deploying the proof on Sui. This allows us to use the verified proof within a Move smart contract, enabling seamless on-chain verification without exposing sensitive user data. The SP1 zkVM supports different proof types, each with tradeoffs in proof size, verification cost, and generation time. Options include Core (default STARK proofs), Compressed (constant-size STARK proofs), Groth16 (efficient SNARKs for onchain verification), and PLONK (larger but transparent SNARKs).

For more details, please refer to the repo.


🔄 Verifying SP1 Proofs on Sui

While Sui supports Groth16 verification, SP1 uses gnark Groth16 proofs, requiring conversion to Arkworks serialization.

The following script is designed to take an SP1 proof file, transform it into an Arkworks-compatible Groth16 proof format, and output the key verification components in a readable hex format. By doing this, we bridge the gap between SP1 proofs and the Arkworks ecosystem, making it easier to integrate these proofs into verification systems like Sui. This process is especially useful for ensuring compatibility with Sui.

// verifier/src/main.rs
use clap::Parser;
use sp1_sdk::SP1ProofWithPublicValues;
use sp1_sui::convert_sp1_gnark_to_ark;

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Args {
    /// Path to the SP1 proof file obtained from the previous step (default is a Fibonacci proof for testing)
    #[arg(short, long, default_value = "../proofs/fibonacci_proof.bin")]
    proof_path: String,
}

fn main() {
    let args = Args::parse();

    // Load the SP1 proof from the specified file
    let sp1_proof_with_public_values = SP1ProofWithPublicValues::load(&args.proof_path)
        .expect("Failed to load SP1 proof");

    // Convert the SP1 proof format into Arkworks-compatible Groth16 proof format
    let (ark_groth16_serialized, ark_public_inputs_serialized, ark_proof_serialized) =
        convert_sp1_gnark_to_ark(sp1_proof_with_public_values);

    // Convert the binary data into hex strings for easier readability
    let ark_groth16_hex: String = hex::encode(ark_groth16_serialized);
    let ark_public_inputs_hex: String = hex::encode(ark_public_inputs_serialized);
    let ark_proof_hex: String = hex::encode(ark_proof_serialized);
}

📜 Move Smart Contract for Proof Verification

After converting the generated proofs, they are ready to be checked on Sui. The following simple Move smart contract enables Groth16 proof verification by decoding the proof, public inputs, and verifying key from byte arrays over BN254 curves. The use of assertions guarantees that only valid proofs pass through, with failures causing the program to panic. This makes it easy to integrate zkSNARK verification into dApps on Sui.

// Move Smart contract
module groth16_verifier::groth16_verifier;

use sui::groth16::{
    prepare_verifying_key,       // Prepares the verifying key for proof checking
    proof_points_from_bytes,     // Converts raw proof bytes into proof points
    public_proof_inputs_from_bytes, // Converts raw public input bytes into a usable format
    bn254,                       // Specifies the BN254 elliptic curve
    verify_groth16_proof         // Function to actually verify the proof
};

// This function verifies a Groth16 proof using the BN254 curve
public fun verify_groth16_bn254_proof(
    groth16_vk: vector<u8>,  // The verifying key in bytes
    public_inputs: vector<u8>, // The public inputs in bytes
    proof: vector<u8>,       // The actual proof in bytes
) {
    // Convert the verifying key into a format we can use
    let pvk = prepare_verifying_key(&bn254(), &groth16_vk);
    
    // Decode the public inputs from bytes
    let public_inputs = public_proof_inputs_from_bytes(public_inputs);
    
    // Decode the proof itself from bytes
    let proof_points = proof_points_from_bytes(proof);
    
    // Verify the proof using the prepared verifying key and the provided inputs
    assert!(verify_groth16_proof(&bn254(), &pvk, &public_inputs, &proof_points));
    // If the proof is invalid, the program will panic. Otherwise, success!
}


📊 Performance Benchmarks

For the above mentioned example, you can find our benchmarks ran on an M4 Pro Mac with 24GB of memory in the following table with the average of 10 compiles:

🔹 Skipping JWT verification makes things much faster since it cuts down a lot of cryptographic overhead but this is necessary since the claimed token must be verified under the public key of the issuer.

Note that with a GPU prover and efficient memory handling, SP1 proof market can reduce proof generation costs. By integrating this proof market, developers can get near real-time verification while keeping security. The proof generated by SP1 market place took around 10 seconds. For more details please refer to this link.


🚀 Next Steps & Future Vision

🛠 Why This Matters for Developers

Developing zkApps has traditionally been complex and resource-intensive, requiring deep expertise in cryptography and circuit design. SP1 zkVM changes that by allowing developers to write regular Rust code instead of designing custom circuits. This means:

  • Faster zkApp development

  • Less cryptography expertise required

  • Simplified audits & security reviews

With SP1 now integrated into Sui, developers can build zkApps on a high-performance, scalable blockchain with native zk verification support.

🚀 How to Get Started

Developers looking to integrate SP1 proofs into their Sui applications can follow these steps:

1️⃣ Install SP1 SDK: Follow the setup guide in the SP1 repo

2️⃣ Write Your Proofs in Rust: Use SP1 to generate zk proofs with minimal effort

3️⃣ Convert Proofs for Sui: Use our proof converter to make proofs compatible with Sui

4️⃣ Deploy Your zkApp: Verify proofs on-chain using Sui’s native verifier

💡 Explore Examples: Check out the sample in our GitHub repo

🔮 Next Steps & Future Vision

This is just the beginning. Soundness Labs, Sui, and Succinct Labs are working on:

📌 Further optimizing SP1 verification on-chain

📌 Expanding zkLogin with more customizable privacy features

📌 Enhancing the developer experience with better tooling in fastcrypto

Our vision is to make zkApps as easy to build as smart contracts—giving developers the power of zk proofs without the complexity.

📣 Get Involved

At Soundness labs, we are excited to bring SP1 zkVM to Sui, and we would love to hear your feedback!

🛠 Try it out & contribute: Check out the GitHub repo

𝕏 Follow us on X (Twitter): Soundness labs official twitter account

💬 Join the conversation: Connect with us on Telegram

🚀 Build with us: Have a zkApp idea? Reach out for support & collaboration!

⚖️ Trade-offs

While zkVMs like SP1 make zk deployment much easier for developers, they do come with a few trade-offs.

  • zkVMs can introduce extra computational overhead compared to hand-optimized circuits, which can slow down proof generation times. This happens because zkVMs tend to be more memory and CPU-intensive, especially when you are dealing with complex computations like JWT.

  • On top of that, developers need to familiarize themselves with new tools and frameworks to work with zkVMs, which can slow down the adoption process. The zkVM ecosystem is still evolving, and there are fewer libraries, tools, and community resources available compared to traditional programming environments.

⚡ What are precompiles?

Another important thing to note is how precompiles can speed things up in the zkVM world. Precompiles are optimized code snippets built into the VM, which save a lot of time when running cryptographic operations. Without them, everything needs to be calculated from scratch, which just makes the process slower. So, integrating precompiles can seriously enhance performance. Here are the precompiles that used for this showcase.

[patch.crates-io]
sha2-v0-10-8 = { git = "<https://github.com/sp1-patches/RustCrypto-hashes>", package = "sha2", tag = "patch-sha2-0.10.8-sp1-4.0.0" }                         # Check for the latest version
rsa = { git = "<https://github.com/sp1-patches/RustCrypto-RSA/>", tag = "patch-0.9.6-sp1-4.0.0" }

🔧 Full Rust Implementation

Additionally, using the full Rust implementation for your dependencies is very important. A lot of dependencies rely on tools outside of Rust, which can lead to problems. These external tools can introduce inconsistencies or bugs, especially if they don't integrate perfectly with the Rust ecosystem. Keeping everything in Rust helps ensure better performance and fewer headaches.


🎯 Final Thought

The future of zkVMs will depend on overcoming performance challenges and expanding their use beyond just blockchain. As zk-proofs become increasingly crucial for privacy and scalability, zkVMs are positioned to play a key role in enabling secure, efficient, and trustless applications. Blockchains like Sui, with their high throughput and low-latency design, are well-suited platforms for zk proofs to be verified on-chain. With advancements in hardware acceleration, proof systems, and developer tooling, zkVMs are set to become a significant part of the next wave of cryptographic innovation, complementing custom circuits or potentially replacing them in some use cases. The zkVM race is just beginning.


Acknowledgement

Big thanks to the Sui and Succinct teams for their support and helpful feedback!

📚 Useful Resources

arxiv.org

zkLogin | Sui Documentation

Introducing SP1: Our first-generation zkVM.

©2025 Soundness Labs. All Rights Reserved