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.

A simple digital signature scheme that proves message authenticity and integrity using the RISC Zero zkVM.

What You’ll Learn

  • How to build digital signature schemes with zkVM
  • Using SHA-256 hashing in the guest
  • Command-line argument parsing
  • Verifying signatures with receipts

Overview

From Wikipedia:
A digital signature is a mathematical scheme for verifying the authenticity of digital messages or documents. A valid digital signature gives a recipient very high confidence that the message was created by a known sender (authenticity), and that the message was not altered in transit (integrity).
This example implements a simple signature scheme where:
  • The signer’s identity is the SHA-256 hash of their passphrase
  • The message is signed by proving knowledge of the passphrase
  • The receipt proves both authenticity and integrity

How It Works

1
Guest Program
2
The guest receives the passphrase and message, computes the identity hash, and commits both to the journal:
3
use digital_signature_core::{SignMessageCommit, SigningRequest};
use risc0_zkvm::guest::env;
use risc0_zkvm::sha::{Impl, Sha256};

fn main() {
    let request: SigningRequest = env::read();
    env::commit(&SignMessageCommit {
        identity: *Impl::hash_bytes(request.passphrase.as_bytes()),
        msg: request.msg,
    });
}
4
The passphrase is never revealed in the journal - only its hash (the identity) is public.
5
Host Program
6
The host collects the message and passphrase, generates the proof, and verifies integrity:
7
use clap::Parser;
use digital_signature::sign;
use sha2::{Digest, Sha256};

#[derive(Parser)]
struct Cli {
    #[arg()]
    message: String,

    #[arg(long)]
    passphrase: String,
}

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

    let signing_receipt = sign(&args.passphrase, &args.message).unwrap();

    println!("Inputs");
    println!("\tmessage: {:?}", args.message);
    println!("Commitment:");
    println!("\tmessage: {:?}", &signing_receipt.get_message().unwrap());
    println!("\tidentity: {:?}", &signing_receipt.get_identity().unwrap());
    
    // Verify the message hash matches
    let message_hash = &signing_receipt.get_message().unwrap();
    let expected_message_hash = Sha256::digest(args.message);
    if message_hash.as_bytes() != expected_message_hash.as_slice() {
        eprintln!("Message commitment does not match!");
        std::process::exit(1);
    }
    println!("\tmessage: valid");
    
    // Verify the receipt
    if signing_receipt.verify().is_err() {
        eprintln!("Receipt is invalid!");
        std::process::exit(1);
    }
    println!("\treceipt: valid");
}

Running the Example

1
Basic Usage
2
Sign a message with a passphrase:
3
cargo run --release -- "This is a signed message" --passphrase="passw0rd"
4
Output
5
Inputs
    message: "This is a signed message"
Commitment:
    message: "<message_hash>"
    identity: "<identity_hash>"
Integrity Checks:
    message: valid
    receipt: valid

What Gets Proven?

The receipt proves:
  1. Authenticity: The signer possesses the passphrase (without revealing it)
  2. Integrity: The message was signed by someone with that specific identity
  3. Non-repudiation: The signature cannot be forged without the passphrase
The receipt contains:
  • Message hash: SHA-256 of the signed message
  • Identity: SHA-256 of the passphrase (public key equivalent)
  • Seal: Cryptographic proof of correct execution

Signature Components

Identity (Public Key)

identity = SHA-256(passphrase)
The identity serves as a public key - it can be shared freely without compromising the passphrase.

Message Commitment

message_hash = SHA-256(message)
The message hash proves which message was signed.

Receipt (Signature)

The zkVM receipt serves as the digital signature. It proves:
  • The identity was computed from a known passphrase
  • The message hash corresponds to the signed message
  • Both were processed by the zkVM correctly

Verification Process

Anyone can verify the signature by:
  1. Checking the receipt verifies correctly
  2. Extracting the identity and message hash from the journal
  3. Confirming the message hash matches the message
  4. Trusting that the identity represents the claimed sender
// Verify receipt
receipt.verify(SIGN_ID).expect("Invalid signature");

// Extract commitments
let (identity, message_hash) = receipt.journal.decode().unwrap();

// Verify message integrity
assert_eq!(Sha256::digest(message), message_hash);

Security Considerations

This is an educational example. Production systems should use established cryptographic schemes like Ed25519 or ECDSA.
Limitations of this simple scheme:
  • Passphrase strength determines security
  • No key rotation mechanism
  • Identity binding requires external PKI
  • No forward secrecy

Use Cases

Document Signing

Prove you authored a document without exposing your signing key:
Document + Passphrase → Receipt

API Authentication

Authenticate API requests with zero-knowledge proofs:
API Request + Secret → Proof of Authorization

Blockchain Transactions

Sign transactions with privacy-preserving signatures:
Transaction + Private Key → zkVM Receipt

Comparison to Traditional Signatures

FeatureTraditional (ECDSA/Ed25519)zkVM Signature
Key GenerationSpecialized cryptoAny computation
Signature Size~64 bytes~128 KB (receipt)
Verification SpeedMicrosecondsMilliseconds
FlexibilityFixed algorithmArbitrary logic
PrivacyPublic key visibleCan hide details

Next Steps