Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/risc0/risc0/llms.txt

Use this file to discover all available pages before exploring further.

Validate signed JSON Web Tokens (JWTs) inside the zkVM to create zero-knowledge proofs of token authenticity and integrity.

What You’ll Learn

  • Verifying JWT signatures with RS256
  • Using the jwt-compact crate in the zkVM
  • Building Bonsai applications for offchain verification
  • Connecting offchain identities to onchain addresses

Overview

This example demonstrates JWT verification using:
  • RS256 algorithm: RSA signature with SHA-256
  • Public key verification: Validate tokens without the private key
  • Claim extraction: Commit specific JWT claims to the journal
  • Zero-knowledge: Prove token validity without revealing the full token

How It Works

1
Guest Program
2
The guest validates the JWT and commits the subject claim:
3
use jwt_core::Validator;
use risc0_zkvm::guest::env;

static PUBLIC_KEY: &str = r#"
    {
      "alg": "RS256",
      "e": "AQAB",
      "key_ops": ["verify"],
      "kty": "RSA",
      "n": "zcQwXx3EevOSkfH0VSWqtfmWTL4c2oIzW6u83qKO1W7X...",
      "use": "sig",
      "kid": "6ab0e8e4bc121fc287e35d3e5e0efb8a"
    }
    "#;

fn main() {
    // Read the token input
    let token: String = env::read();

    // Create validator from public key
    let validator = PUBLIC_KEY
        .parse::<Validator>()
        .expect("failed to create validator from key");

    // Validate token integrity
    let valid_token = validator
        .validate_token_integrity(token.as_str())
        .expect("token integrity check failed");

    // Commit the subject claim
    env::commit(&valid_token.claims().custom.subject);
}
4
The public key is embedded in the guest program, making it part of the Image ID. This ensures token verification uses the correct key.
5
Host Program
6
The host generates a JWT with a private key and proves its validity:
7
use jwt_core::{issue_token, Claims};
use jwt_validator_methods::{JWT_VALIDATOR_ELF, JWT_VALIDATOR_ID};
use risc0_zkvm::{ExecutorEnv, default_prover};

fn main() {
    // Issue a JWT with a private key
    let claims = Claims {
        subject: "user@example.com".to_string(),
        // ... other claims
    };
    
    let token = issue_token(&private_key, &claims).unwrap();

    // Prove the JWT is valid
    let env = ExecutorEnv::builder()
        .write(&token)
        .unwrap()
        .build()
        .unwrap();

    let receipt = default_prover()
        .prove(env, JWT_VALIDATOR_ELF)
        .unwrap()
        .receipt;

    // Verify the receipt
    receipt.verify(JWT_VALIDATOR_ID).unwrap();

    // Extract the subject from the journal
    let subject: String = receipt.journal.decode().unwrap();
    println!("Validated JWT for subject: {}", subject);
}

Running the Example

cargo run --release
Output:
Validated JWT for subject: user@example.com
You’ve successfully constructed a zero-knowledge proof attesting to JWT validity verified against a public key.

What Gets Proven?

The receipt proves:
  1. Signature Validity: The JWT signature is valid for the embedded public key
  2. Token Integrity: The token hasn’t been tampered with
  3. Claim Binding: The subject claim matches what’s committed to the journal
  4. Key Binding: Verification used the specific public key (via Image ID)

JWT Structure

A JWT consists of three parts:
header.payload.signature
Specifies the algorithm and key ID:
{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "6ab0e8e4bc121fc287e35d3e5e0efb8a"
}

Payload (Claims)

Contains the data being attested:
{
  "sub": "user@example.com",
  "iat": 1516239022,
  "exp": 1516242622,
  "aud": "your-app"
}

Signature

RSA signature over header and payload:
RSASHA256(base64(header) + "." + base64(payload), privateKey)

Use Cases

JWT validation in zkVM enables privacy-preserving identity verification across trust boundaries.

Onchain Identity Verification

Connect offchain OAuth accounts to onchain addresses:
Google Sign-In → JWT → zkVM Proof → Blockchain Verification
Workflow:
  1. User signs in with Google OAuth
  2. Identity provider issues JWT
  3. zkVM validates JWT and links to wallet address
  4. Smart contract verifies the receipt onchain
Example from Bonsai Pay:
function verifyIdentity(
    bytes memory journal,
    bytes memory seal
) public {
    // Verify the zkVM receipt
    verifier.verify(seal, imageId, journal);
    
    // Extract email from journal
    string memory email = abi.decode(journal, (string));
    
    // Link email to caller's address
    addressToEmail[msg.sender] = email;
}

API Authentication

Prove you have a valid API token without exposing it:
API Token (JWT) → zkVM → Receipt → API Server

Session Management

Validate session tokens with zero-knowledge:
Session JWT → Prove validity → Access granted

Credential Delegation

Prove you have credentials without revealing them:
Admin JWT → Prove admin claim → Execute privileged action

Bonsai Integration

Use as a Bonsai application for:
  • Offchain computation: Generate proofs off-chain
  • Onchain verification: Verify cheaply on blockchain
  • Scalability: Handle high-volume JWT validation
  • Privacy: Keep tokens offchain

Architecture

User Device
    ├─ Sign in with OAuth provider
    ├─ Receive JWT
    └─ Submit to Bonsai

Bonsai (Offchain)
    ├─ Validate JWT in zkVM
    ├─ Extract claims
    └─ Generate receipt

Blockchain (Onchain)
    ├─ Receive receipt + journal
    ├─ Verify receipt
    └─ Process claims

Security Considerations

Public Key Management

The public key is embedded in the guest:
static PUBLIC_KEY: &str = r#"{ ... }"#;
Implications:
  • Key is part of the Image ID
  • Changing the key requires recompiling
  • Ensures consistent verification

Token Expiration

This example doesn’t check expiration. Production systems should validate:
let claims = valid_token.claims();
let now = current_timestamp();

if claims.exp < now {
    panic!("Token expired");
}

if claims.iat > now {
    panic!("Token issued in future");
}

Algorithm Confusion

The validator enforces RS256:
let validator = PUBLIC_KEY.parse::<Validator>()?;
// Only accepts RS256 signatures
This prevents algorithm confusion attacks.

Using the jwt-compact Crate

The example uses jwt-compact for JWT operations:
use jwt_compact::{
    alg::{Rsa, Sha256},
    Algorithm, TimeOptions, Token,
};

// Create verifier
let algorithm = Rsa::rs256(public_key);
let token = Token::verify(&jwt_string, &algorithm)?;

// Extract claims
let subject = token.claims().custom.subject;
This crate works in the zkVM because it:
  • Uses pure Rust implementation
  • Avoids unsupported system calls
  • Supports no_std compilation
See Rust Resources for more on using crates in zkVM.

Performance

JWT validation with RS256:
  • Cycles: ~50-100M depending on key size
  • Proving time: 10-30 seconds locally
  • Verification time: Milliseconds onchain
  • Receipt size: ~128 KB
For production, use Bonsai for faster proving.

Next Steps