Skip to main content

Try it Live

Run BLS12-381 examples in the interactive playground

Security Considerations

Security properties and best practices for BLS12-381 implementations.

Security Level

Target: 128-bit classical security, 64-bit post-quantum security
AttackComplexity
Discrete log (G1)~2^128
Discrete log (G2)~2^128
Pairing inversionComputationally infeasible
MOV attackPrevented (embedding degree 12)
Recommended usage: Until 2030+ per NIST guidelines.

Known Attack Vectors

Rogue Key Attack

Problem: Adversary can construct malicious public key that causes aggregated signature verification to pass for messages they didn’t sign. Attack:
  1. Honest user has public key pk₁
  2. Adversary computes pk₂ = G1 - pk₁ (where G1 is generator)
  3. Aggregated key: pk₁ + pk₂ = G1
  4. Adversary can forge signatures for the “aggregate”
Mitigation: Proof of Possession (PoP)
// Each participant must prove knowledge of secret key
fn generateProofOfPossession(secret_key: Fr, public_key: G1Point) G2Point {
    // Sign the public key itself
    const pop_message = Bls12381.G2.hashToCurve(public_key.serialize(), "BLS_POP_");
    return Bls12381.G2.mul(pop_message, secret_key);
}

fn verifyProofOfPossession(public_key: G1Point, pop: G2Point) bool {
    const pop_message = Bls12381.G2.hashToCurve(public_key.serialize(), "BLS_POP_");
    return Bls12381.Pairing.verify(public_key, pop_message, pop);
}

Subgroup Attack

Problem: Points not in the correct prime-order subgroup can break security. Mitigation: Always validate points are in the correct subgroup:
fn validateG1Point(point: G1Point) !void {
    // Check point is on curve
    if (!Bls12381.G1.isOnCurve(point)) {
        return error.PointNotOnCurve;
    }
    // Check point is in prime-order subgroup
    if (!Bls12381.G1.isInSubgroup(point)) {
        return error.PointNotInSubgroup;
    }
}

Invalid Curve Attack

Problem: Accepting points from different curves enables key recovery. Mitigation: Strict point validation before any operation.

Side-Channel Resistance

Constant-Time Operations

All operations must be constant-time to prevent timing attacks:
// ✅ Constant-time scalar multiplication
fn scalarMul(point: G1Point, scalar: Fr) G1Point {
    // Double-and-add with constant iterations
    var result = G1Point.identity();
    var temp = point;

    for (0..256) |i| {
        // Constant-time conditional add
        const bit = (scalar >> i) & 1;
        result = constantTimeSelect(bit,
            Bls12381.G1.add(result, temp),
            result
        );
        temp = Bls12381.G1.double(temp);
    }
    return result;
}

Memory Access Patterns

Avoid data-dependent memory access:
// ❌ BAD: Variable-time lookup
fn badLookup(table: []G1Point, index: usize) G1Point {
    return table[index]; // Timing leak!
}

// ✅ GOOD: Constant-time lookup
fn constantTimeLookup(table: []G1Point, index: usize) G1Point {
    var result = G1Point.identity();
    for (table, 0..) |point, i| {
        const mask = constantTimeEquals(i, index);
        result = constantTimeSelect(mask, point, result);
    }
    return result;
}

Implementation Checklist

Point Validation

  • Check point is on curve (satisfies curve equation)
  • Check point is in prime-order subgroup
  • Reject point at infinity where invalid
  • Validate encoding format (compressed/uncompressed)

Scalar Validation

  • Check scalar is in valid range [0, r-1]
  • Reduce scalars modulo curve order
  • Handle zero scalar correctly

Signature Validation

  • Verify signature is valid G2 point
  • Check signature is in correct subgroup
  • Validate against correct domain separator
  • Reject malformed or oversized inputs

Key Management

  • Use cryptographically secure random number generator
  • Implement proof of possession for aggregation
  • Secure key storage (HSM recommended for validators)
  • Key derivation follows EIP-2333

Domain Separation

Always use distinct domain separators to prevent cross-protocol attacks:
// Ethereum consensus domains
const DOMAIN_BEACON_PROPOSER = 0x00000000;
const DOMAIN_BEACON_ATTESTER = 0x01000000;
const DOMAIN_RANDAO = 0x02000000;
const DOMAIN_DEPOSIT = 0x03000000;
const DOMAIN_VOLUNTARY_EXIT = 0x04000000;
const DOMAIN_SYNC_COMMITTEE = 0x07000000;

fn computeDomain(domain_type: u32, fork_version: [4]u8, genesis_root: [32]u8) [32]u8 {
    const fork_data_root = hashTreeRoot(ForkData{
        .current_version = fork_version,
        .genesis_validators_root = genesis_root,
    });
    var domain: [32]u8 = undefined;
    @memcpy(domain[0..4], @bitCast([4]u8, domain_type));
    @memcpy(domain[4..32], fork_data_root[0..28]);
    return domain;
}

Aggregation Security

Safe Aggregation Rules

  1. Same message: Only aggregate signatures over identical messages
  2. Proof of possession: Require PoP before allowing key in aggregation
  3. Distinct signers: Ensure no duplicate public keys
  4. Domain binding: Include domain in signed message

Unsafe Patterns

// ❌ UNSAFE: Aggregating signatures over different messages
fn unsafeAggregate(sigs: []G2Point) G2Point {
    // This allows forgery attacks!
    return sumG2Points(sigs);
}

// ✅ SAFE: Only aggregate same-message signatures
fn safeAggregate(
    message: []const u8,
    public_keys: []G1Point,
    signatures: []G2Point,
) !AggregatedSignature {
    // Verify each signature first
    for (public_keys, signatures) |pk, sig| {
        if (!verify(pk, message, sig)) {
            return error.InvalidSignature;
        }
    }
    return AggregatedSignature{
        .public_keys = public_keys,
        .signature = sumG2Points(signatures),
    };
}

Library Recommendations

Production Use

BLST (Supranational) - Recommended for production:
  • Audited by Trail of Bits, NCC Group
  • Assembly-optimized
  • Constant-time implementation
  • Used by all major Ethereum consensus clients

Testing Only

Noble-BLS12-381 - Pure JavaScript for testing:
  • Not constant-time
  • Slower performance
  • Useful for test vector generation

Audit History

LibraryAuditorDateFindings
BLSTTrail of Bits2020No critical issues
BLSTNCC Group2021All issues resolved
py_eccLeast Authority2020Reference implementation