Skip to main content
Demonstrates BLS12-381 elliptic curve pairing operations inside the zkVM, providing stronger security guarantees than BN254.

What You’ll Learn

  • BLS12-381 curve operations
  • Pairing verification in zkVM
  • Differences between BN254 and BLS12-381
  • Modern pairing-based cryptography

Overview

BLS12-381 is a pairing-friendly elliptic curve offering approximately 128-bit security, making it suitable for long-term cryptographic applications. It’s used by:
  • Ethereum 2.0: Validator signatures
  • Zcash: Sapling and Orchard protocols
  • Filecoin: Proof-of-Replication
  • Chia: BLS signatures
BLS12-381 offers significantly better security than BN254 and is recommended for new systems.

How It Works

1
Guest Program
2
The guest tests pairing operations against known values:
3
#![no_main]
risc0_zkvm::guest::entry!(main);

// Test pairing result against reference implementation
fn test_pairing_result_against_relic() {
    let a = bls12_381::G1Affine::generator();
    let b = bls12_381::G2Affine::generator();

    let res = bls12_381::pairing(&a, &b);

    // Verify against known constant
    assert_eq!(
        res.all_raw(),
        [
            [
                0xa843_05aa_ca17_89b6,
                0xb6d1_94f6_0839_c508,
                0x3dd8_e90c_e98d_b3e7,
                0x272d_441b_efa1_5c50,
                0xa7b2_d831_68d0_d727,
                0x1250_ebd8_71fc_0a92,
            ],
            // ... remaining 11 elements
        ]
    );
}

fn main() {
    test_pairing_result_against_relic();
}
4
This verifies that:
5
  • The pairing operation executes correctly in the zkVM
  • The result matches the expected value from a reference implementation
  • The computation is deterministic and verifiable
  • 6
    Host Program
    7
    The host executes the guest and verifies the proof:
    8
    use bls12_381_methods::{BLS12_381_VERIFY_ELF, BLS12_381_VERIFY_ID};
    use risc0_zkvm::{ExecutorEnv, default_prover};
    
    fn main() {
        let env = ExecutorEnv::builder()
            .build()
            .unwrap();
    
        let receipt = default_prover()
            .prove(env, BLS12_381_VERIFY_ELF)
            .unwrap()
            .receipt;
    
        receipt.verify(BLS12_381_VERIFY_ID).unwrap();
    
        println!("Successfully verified BLS12-381 pairing operation");
    }
    

    Running the Example

    cargo run --release
    
    Output:
    Successfully verified BLS12-381 pairing operation
    

    What Gets Proven?

    The receipt proves:
    1. Correct Implementation: BLS12-381 pairing computed correctly
    2. Deterministic Result: Matches known test vectors
    3. zkVM Compatibility: The curve works properly in the zkVM environment

    BLS12-381 Curve Details

    Parameters

    • Field characteristic: 381-bit prime
    • Embedding degree: 12
    • Subgroup size: 255-bit prime
    • Security level: ~128 bits

    Groups

    • G1: Points on E(Fq) where E: y² = x³ + 4
    • G2: Points on E'(Fq²) where E': y² = x³ + 4(1 + i)
    • GT: Elements in Fq¹²

    Compressed Point Sizes

    • G1: 48 bytes (compressed), 96 bytes (uncompressed)
    • G2: 96 bytes (compressed), 192 bytes (uncompressed)
    • GT: 576 bytes

    BLS Signatures

    BLS12-381 is designed for BLS (Boneh-Lynn-Shacham) signatures:

    Signature Scheme

    // Key generation
    let sk = Fr::random();  // Secret key
    let pk = G2::generator() * sk;  // Public key in G2
    
    // Signing
    let msg_hash = hash_to_curve(message);  // Hash to G1
    let signature = msg_hash * sk;  // Sign in G1
    
    // Verification
    let lhs = pairing(&signature, &G2::generator());
    let rhs = pairing(&msg_hash, &pk);
    assert_eq!(lhs, rhs);
    

    Signature Aggregation

    Multiple signatures can be aggregated:
    // Aggregate signatures
    let agg_sig = sig1 + sig2 + sig3;
    
    // Verify with batch pairing
    let valid = pairing_batch(&[
        (msg_hash1, pk1),
        (msg_hash2, pk2),
        (msg_hash3, pk3),
        (-agg_sig, G2::generator()),
    ]) == GT::one();
    
    Benefits:
    • Constant Size: Aggregated signature same size as single signature
    • Batch Verification: Verify multiple signatures efficiently
    • Non-Interactive: No coordination needed for aggregation

    Ethereum 2.0 Usage

    Ethereum 2.0 uses BLS12-381 for validator signatures:

    Why BLS12-381?

    1. Security: 128-bit security level
    2. Aggregation: Combine thousands of signatures
    3. Efficiency: Fast verification for aggregate signatures
    4. Standardization: Widely reviewed and adopted

    Validator Workflow

    Validator Signs Block
        └─ BLS signature (48 bytes in G1)
    
    Aggregate Signatures
        ├─ Combine 1000+ validator signatures
        └─ Result: Single 48-byte signature
    
    Verify Aggregate
        ├─ Batch pairing verification
        └─ Faster than 1000 individual checks
    

    Performance

    OperationCycles (approx)Time (local)
    G1 scalar mul~1M~1ms
    G2 scalar mul~3M~3ms
    Single pairing~20M~20ms
    Hash to G1~5M~5ms
    BLS12-381 operations are about 2x slower than BN254 but offer significantly better security.

    Use Cases in zkVM

    Signature Verification

    Verify BLS signatures inside the zkVM:
    let valid = verify_bls_signature(
        &message,
        &signature,
        &public_key,
    );
    
    env::commit(&valid);
    
    Produces a receipt proving signature validity without revealing the signature itself.

    Aggregate Signature Validation

    Verify aggregate signatures from multiple signers:
    let validators: Vec<PublicKey> = env::read();
    let messages: Vec<Message> = env::read();
    let aggregate_signature: G1Affine = env::read();
    
    let valid = verify_aggregate(
        &messages,
        &validators,
        &aggregate_signature,
    );
    
    env::commit(&valid);
    

    Threshold Signatures

    Implement threshold signature schemes:
    // 3-of-5 threshold
    let partial_sigs: Vec<G1Affine> = env::read();
    let threshold_sig = combine_partial_signatures(
        &partial_sigs,
        &lagrange_coefficients,
    );
    
    let valid = verify(&message, &threshold_sig, &group_pk);
    env::commit(&valid);
    

    Cross-Chain Bridges

    Verify signatures from other chains:
    // Verify Ethereum 2.0 validator signatures
    let eth2_block: BeaconBlock = env::read();
    let validator_set: Vec<PublicKey> = env::read();
    let aggregate_sig = eth2_block.signature;
    
    let valid = verify_eth2_block(
        &eth2_block,
        &validator_set,
        &aggregate_sig,
    );
    
    env::commit(&(valid, eth2_block.hash()));
    

    BLS12-381 Crate

    This example uses the bls12_381 crate:
    use bls12_381::{
        G1Affine, G2Affine, Gt,
        Scalar, pairing,
    };
    
    // Scalar operations
    let a = Scalar::from(123u64);
    let b = Scalar::from(456u64);
    let c = a * b;
    
    // Point operations
    let g1_gen = G1Affine::generator();
    let point = g1_gen * a;
    
    // Pairing
    let result = pairing(&g1_point, &g2_point);
    

    Comparison: BN254 vs BLS12-381

    FeatureBN254BLS12-381
    Security~100 bits~128 bits
    Field size254 bits381 bits
    G1 compressed32 bytes48 bytes
    G2 compressed64 bytes96 bytes
    Pairing speedFasterSlower (~2x)
    Hash-to-curveComplexStandardized
    EthereumEVM precompileETH2 standard
    AdoptionLegacyModern
    RecommendationLegacy onlyNew systems

    Security Considerations

    Why 128-bit Security?

    BLS12-381 provides:
    • Resistance to known attacks
    • Safety margin for future advances
    • Long-term security (20+ years)

    Subgroup Checks

    Always verify points are in correct subgroups:
    // Check G1 point
    assert!(g1_point.is_on_curve());
    assert!(g1_point.is_torsion_free());
    
    // Check G2 point
    assert!(g2_point.is_on_curve());
    assert!(g2_point.is_torsion_free());
    
    Skipping these checks can lead to vulnerabilities.

    Constant-Time Operations

    The bls12_381 crate provides constant-time operations for secret values:
    // Constant time for secret keys
    let signature = message_hash * secret_key;
    
    // Regular operations for public data
    let aggregate = pub_key1 + pub_key2;
    

    Standards and Specifications

    Next Steps

    Build docs developers (and LLMs) love