Skip to main content

Overview

This guide walks you through the basic operations of the ZK ElGamal Proof SDK:
  • Generating ElGamal keypairs
  • Encrypting and decrypting data
  • Creating zero-knowledge proofs
  • Verifying proofs
By the end, you’ll have created and verified your first public key validity proof.

Basic Usage

Generate a Keypair

First, let’s create an ElGamal keypair for encryption:
use solana_zk_sdk::encryption::elgamal::ElGamalKeypair;

fn main() {
    // Generate a random ElGamal keypair
    let keypair = ElGamalKeypair::new_rand();
    
    // Access the public and secret keys
    let public_key = keypair.pubkey();
    let secret_key = keypair.secret();
    
    println!("Keypair generated successfully!");
}

Encrypt and Decrypt Data

Now let’s encrypt a value and decrypt it:
use solana_zk_sdk::encryption::elgamal::ElGamalKeypair;

fn main() {
    let keypair = ElGamalKeypair::new_rand();
    let public_key = keypair.pubkey();
    
    // Encrypt a 64-bit value
    let amount = 42_u64;
    let ciphertext = public_key.encrypt(amount);
    println!("Encrypted value: {}", amount);
    
    // Decrypt using the secret key
    let decrypted = keypair.secret().decrypt_u32(&ciphertext);
    
    match decrypted {
        Some(value) => println!("Decrypted value: {}", value),
        None => println!("Decryption failed"),
    }
}
The decrypt_u32() method returns Option<u64> because decryption requires solving the discrete logarithm problem. It works efficiently for small values but may fail or take time for large values.

Create a Zero-Knowledge Proof

Let’s create a public key validity proof to prove we know the secret key:
use solana_zk_sdk::{
    encryption::elgamal::ElGamalKeypair,
    zk_elgamal_proof_program::proof_data::{
        pubkey_validity::PubkeyValidityProofData,
        ZkProofData,
    },
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Generate keypair
    let keypair = ElGamalKeypair::new_rand();
    
    // Create a public key validity proof
    let proof_data = PubkeyValidityProofData::new(&keypair)?;
    
    // Verify the proof
    proof_data.verify_proof()?;
    
    println!("✅ Public key validity proof created and verified!");
    Ok(())
}

Complete Example

Here’s a complete example demonstrating the full workflow:
use solana_zk_sdk::{
    encryption::elgamal::ElGamalKeypair,
    zk_elgamal_proof_program::proof_data::{
        pubkey_validity::PubkeyValidityProofData,
        ZkProofData,
    },
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("=== ZK ElGamal Proof SDK Quickstart ===");
    
    // Step 1: Generate keypair
    println!("\n1. Generating ElGamal keypair...");
    let keypair = ElGamalKeypair::new_rand();
    let public_key = keypair.pubkey();
    println!("   ✓ Keypair generated");
    
    // Step 2: Encrypt data
    println!("\n2. Encrypting value...");
    let amount = 100_u64;
    let ciphertext = public_key.encrypt(amount);
    println!("   ✓ Encrypted: {}", amount);
    
    // Step 3: Decrypt data
    println!("\n3. Decrypting value...");
    let decrypted = keypair.secret()
        .decrypt_u32(&ciphertext)
        .expect("Decryption failed");
    println!("   ✓ Decrypted: {}", decrypted);
    assert_eq!(amount, decrypted);
    
    // Step 4: Create zero-knowledge proof
    println!("\n4. Creating public key validity proof...");
    let proof_data = PubkeyValidityProofData::new(&keypair)?;
    println!("   ✓ Proof created");
    
    // Step 5: Verify proof
    println!("\n5. Verifying proof...");
    proof_data.verify_proof()?;
    println!("   ✓ Proof verified!");
    
    println!("\n✅ All steps completed successfully!");
    Ok(())
}

Working with Keys

Serialize and Deserialize Keys

use solana_zk_sdk::encryption::elgamal::{ElGamalKeypair, ElGamalSecretKey, ElGamalPubkey};

// Serialize public key to bytes
let keypair = ElGamalKeypair::new_rand();
let pubkey_bytes: [u8; 32] = keypair.pubkey().into();

// Deserialize public key from bytes
let recovered_pubkey = ElGamalPubkey::try_from(pubkey_bytes.as_ref())
    .expect("Invalid public key bytes");

// Serialize secret key
let secret_bytes = keypair.secret().as_bytes();

// Deserialize secret key
let recovered_secret = ElGamalSecretKey::try_from(secret_bytes.as_ref())
    .expect("Invalid secret key bytes");

Encrypt with Custom Opening

For advanced use cases, you can provide your own Pedersen opening:
use solana_zk_sdk::encryption::{
    elgamal::ElGamalKeypair,
    pedersen::PedersenOpening,
};

let keypair = ElGamalKeypair::new_rand();
let opening = PedersenOpening::new_rand();

// Encrypt with a specific opening
let amount = 50_u64;
let ciphertext = keypair.pubkey().encrypt_with(amount, &opening);

Understanding Proofs

Public Key Validity Proof

A public key validity proof proves that you know the secret key corresponding to a public key without revealing the secret key itself. What it proves: “I know the discrete logarithm (secret key) of this public key.” Use cases:
  • Proving ownership of an ElGamal public key
  • Authenticating without revealing credentials
  • Establishing trust in encrypted communications

Proof Context and Verification

use solana_zk_sdk::{
    encryption::elgamal::ElGamalKeypair,
    zk_elgamal_proof_program::proof_data::{
        pubkey_validity::PubkeyValidityProofData,
        ZkProofData,
    },
};

let keypair = ElGamalKeypair::new_rand();
let proof_data = PubkeyValidityProofData::new(&keypair)
    .expect("Failed to create proof");

// Access the proof context (contains the public key)
let context = &proof_data.context;
println!("Pubkey from context: {:?}", context.pubkey);

// Verify the proof
match proof_data.verify_proof() {
    Ok(()) => println!("✅ Proof is valid"),
    Err(e) => println!("❌ Proof is invalid: {}", e),
}

Next Steps

Core Concepts

Understand the cryptographic foundations of ElGamal encryption and zero-knowledge proofs.

Proof Types

Learn about range proofs, equality proofs, and batched proof systems.

Rust SDK

Explore the complete Rust API documentation for all cryptographic primitives.

JavaScript SDK

Browse JavaScript/TypeScript examples and integration patterns.

Common Patterns

When encrypting multiple values with the same public key:
let keypair = ElGamalKeypair::new_rand();
let pubkey = keypair.pubkey();

let values = vec![10_u64, 20, 30, 40, 50];
let ciphertexts: Vec<_> = values
    .iter()
    .map(|&v| pubkey.encrypt(v))
    .collect();

// Each ciphertext uses a different random opening
Proofs can be serialized and stored for later verification:Rust:
use bytemuck;

let proof_data = PubkeyValidityProofData::new(&keypair)?;
let proof_bytes = bytemuck::bytes_of(&proof_data);

// Later: deserialize
let recovered: &PubkeyValidityProofData = bytemuck::from_bytes(proof_bytes);
JavaScript:
const proof = new PubkeyValidityProofData(keypair);
const proofBytes = proof.toBytes();

// Later: deserialize
const recovered = PubkeyValidityProofData.fromBytes(new Uint8Array(proofBytes));
Always handle cryptographic errors gracefully:Rust:
match keypair.secret().decrypt_u32(&ciphertext) {
    Some(value) => println!("Decrypted: {}", value),
    None => {
        eprintln!("Decryption failed: value out of range or invalid ciphertext");
        // Handle error appropriately
    }
}
JavaScript:
try {
  const value = secretKey.decrypt(ciphertext);
  console.log('Decrypted:', value);
} catch (error) {
  console.error('Decryption failed:', error.message);
  // Handle error appropriately
}

Build docs developers (and LLMs) love