# VAL-012: BLS Signature Verification

> **Authoritative requirement:** [VAL-012](../NORMATIVE.md#VAL-012)
> **Verification:** [VERIFICATION.md](../VERIFICATION.md)
> **Tracking:** [TRACKING.yaml](../TRACKING.yaml)
> **Spec reference:** [SPEC.md](../../../../resources/SPEC.md) — Section 6.1 Step 5

---

## Summary

BLS (Boneh-Lynn-Shacham) signature verification is the cryptographic authentication step in spend bundle validation. Every spend bundle carries an aggregated BLS12-381 signature that must validate against all `AGG_SIG_*` conditions emitted by the CLVM puzzles in the bundle. The signature proves that the spender possesses the private keys corresponding to the public keys referenced in the conditions, authorizing the coin spends.

`dig-clvm` delegates BLS verification to `chia-consensus` internally, which handles the aggregation math, domain separation tags, and pairing operations. The crate collects all `AGG_SIG_PARENT`, `AGG_SIG_PUZZLE`, `AGG_SIG_AMOUNT`, `AGG_SIG_PUZZLE_AMOUNT`, `AGG_SIG_PARENT_AMOUNT`, `AGG_SIG_PARENT_PUZZLE`, `AGG_SIG_ME`, and `AGG_SIG_UNSAFE` conditions from the CLVM execution results, constructs the appropriate messages with domain-specific prefixes derived from the genesis challenge (CON-005), and verifies the aggregated signature against all (public_key, message) pairs.

On Chia L1, BLS signature verification is performed in [`condition_tools.py:74-100`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/condition_tools.py#L74), where `pkm_pairs_for_conditions_dict` extracts all public key / message pairs and `validate_clvm_and_signature` performs the pairing check. The `DONT_VALIDATE_SIGNATURE` flag allows skipping this step during mempool pre-validation or testing scenarios. The `BlsCache` (from the `bls_cache` domain) is used to avoid redundant pairing operations for previously verified pairs.

---

## Specification

### AGG_SIG Condition Opcodes

The following condition opcodes contribute to BLS signature verification:

| Opcode | Name | Domain Separation |
|--------|------|-------------------|
| 48 | AGG_SIG_UNSAFE | None (raw message) |
| 49 | AGG_SIG_ME | `agg_sig_me_additional_data` (= genesis_challenge) |
| 50 | AGG_SIG_PARENT | sha256(genesis_challenge \|\| 0x30) |
| 51 | AGG_SIG_PUZZLE | sha256(genesis_challenge \|\| 0x32) |
| 52 | AGG_SIG_AMOUNT | sha256(genesis_challenge \|\| 0x33) |
| 53 | AGG_SIG_PUZZLE_AMOUNT | sha256(genesis_challenge \|\| 0x34) |
| 54 | AGG_SIG_PARENT_AMOUNT | sha256(genesis_challenge \|\| 0x35) |
| 55 | AGG_SIG_PARENT_PUZZLE | sha256(genesis_challenge \|\| 0x36) |

### Signature Verification Flow

```rust
use chia_consensus::gen::validation_error::ValidationErr;

/// Verify the aggregated BLS signature on a spend bundle.
/// Skipped if DONT_VALIDATE_SIGNATURE flag is set.
fn verify_bls_signature(
    spend_bundle: &SpendBundle,
    conditions: &OwnedSpendBundleConditions,
    constants: &NetworkConstants,
    flags: u32,
    cache: Option<&mut BlsCache>,
) -> Result<(), ValidationError> {
    // Skip verification if flag is set
    if flags & DONT_VALIDATE_SIGNATURE != 0 {
        return Ok(());
    }

    // Collect all (public_key, message) pairs from AGG_SIG_* conditions.
    // Domain separation is applied per opcode using constants.
    // chia-consensus handles this internally.

    // Verify aggregated signature against all pairs
    let result = validate_clvm_and_signature(
        spend_bundle,
        conditions,
        constants.consensus(),
        cache,
    );

    match result {
        Ok(()) => Ok(()),
        Err(_) => Err(ValidationError::SignatureFailed),
    }
}
```

### DONT_VALIDATE_SIGNATURE Flag

```rust
/// When set, skip BLS signature verification.
/// Used for mempool pre-validation and testing.
pub const DONT_VALIDATE_SIGNATURE: u32 = 0x0004;
```

This flag is passed via `ValidationConfig.flags`. When set:
- No pairing operations are performed
- The `aggregated_signature` field of the `SpendBundle` is ignored
- All other validation steps proceed normally

### BlsCache Integration

```rust
/// Optional BLS cache to avoid redundant pairing operations.
/// Passed as `Option<&mut BlsCache>` to validation functions.
///
/// When a (public_key, message) pair has been previously verified,
/// the cache returns the result without performing the expensive
/// pairing computation.
let mut cache = BlsCache::default();

let result = validate_spend_bundle(
    &bundle,
    &context,
    &config,
    Some(&mut cache),  // Cache persists across calls
)?;
```

### Error Type

```rust
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ValidationError {
    // ... other variants ...

    /// The aggregated BLS signature does not verify against
    /// the collected AGG_SIG_* conditions.
    SignatureFailed,
}
```

### Message Construction per Opcode

For `AGG_SIG_ME`, the message is:
```
message = msg_body || coin_id || agg_sig_me_additional_data
```

For other domain-specific opcodes (PARENT, PUZZLE, AMOUNT, etc.), the message includes the relevant coin fields concatenated with the opcode-specific additional data derived from the genesis challenge (see CON-005).

For `AGG_SIG_UNSAFE`, the message is the raw `msg_body` with no additional data appended. This is "unsafe" because it lacks domain separation and could be replayed across networks.

---

## Acceptance Criteria

- [ ] A spend bundle with a valid aggregated BLS signature passes verification.
- [ ] A spend bundle with an invalid or tampered signature is rejected with `SignatureFailed`.
- [ ] When `DONT_VALIDATE_SIGNATURE` flag is set, signature verification is skipped entirely and validation proceeds.
- [ ] Domain separation is correctly applied for each `AGG_SIG_*` opcode using the genesis challenge.
- [ ] `BlsCache` integration avoids redundant pairing operations for previously verified pairs.
- [ ] All eight `AGG_SIG_*` opcodes (48-55) are supported.

---

## Implementation Notes

- **Primary file:** `src/consensus/validate.rs` (signature verification call site), delegating to `chia-consensus` for the actual BLS math.
- **No custom BLS code:** `dig-clvm` does NOT implement BLS pairing or aggregation. All cryptographic operations are performed by `chia-bls` (via `chia-consensus`). This ensures correctness and avoids divergence from the L1 implementation.
- **Cache lifetime:** The `BlsCache` should be created once and passed to multiple `validate_spend_bundle` calls. It is NOT thread-safe by default; callers must handle synchronization if using from multiple threads.
- **Performance:** BLS pairing is the most expensive single operation in validation. The cache can reduce this cost by 90%+ for repeated patterns (e.g., the same puzzle spending multiple coins with the same key).
- **Empty conditions:** If no `AGG_SIG_*` conditions are emitted, the signature must still be verified — it should be the identity element (point at infinity) for G2. A non-identity signature with no conditions to verify against is invalid.
- **AGG_SIG_UNSAFE caution:** Unlike other opcodes, `AGG_SIG_UNSAFE` has no domain separation. Signatures using this opcode could theoretically be replayed on other networks. Puzzle authors should prefer domain-specific opcodes.

---

## Verification

### Test Plan

| Test | Type | Description | Expected Result |
|------|------|-------------|-----------------|
| Valid signature | Simulator test | Bundle with correct aggregated sig | Validation passes |
| Invalid signature | Simulator test | Bundle with tampered sig bytes | Err(SignatureFailed) |
| Skip via flag | Unit test | DONT_VALIDATE_SIGNATURE set, invalid sig | Validation passes (sig not checked) |
| AGG_SIG_ME domain sep | Simulator test | Puzzle emits AGG_SIG_ME, verify correct message construction | Correct message with coin_id + additional_data |
| AGG_SIG_UNSAFE | Simulator test | Puzzle emits AGG_SIG_UNSAFE with raw message | Signature verified without domain separation |
| Cache hit | Unit test | Same bundle validated twice with shared cache | Second call faster (cache hit) |
| No conditions, identity sig | Unit test | No AGG_SIG_* conditions, sig = G2 identity | Passes |
| No conditions, non-identity sig | Unit test | No AGG_SIG_* conditions, sig != identity | Err(SignatureFailed) |

---

## Source Citations

- **SPEC.md Section 6.1 Step 5**: BLS signature verification — "Unless DONT_VALIDATE_SIGNATURE flag set, verify aggregated BLS sig against all AGG_SIG_* conditions."
- **SPEC.md Section 4.3**: `agg_sig_me_additional_data` and opcode-specific additional data derivation from genesis challenge.
- **L1 reference — condition_tools.py:74-100:** [`condition_tools.py`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/condition_tools.py#L74) — `pkm_pairs_for_conditions_dict` extracts public key / message pairs.
- **L1 reference — mempool.py:** BLS verification in mempool validation, with `DONT_VALIDATE_SIGNATURE` flag handling.

---

## References

- [VAL-009](VAL-009.md) — Condition validation (AGG_SIG_* conditions extracted here)
- [VAL-011](VAL-011.md) — Conservation check (follows BLS verification in pipeline)
- [VAL-013](VAL-013.md) — MEMPOOL_MODE stricter rules (flags interact with BLS flag)
- [CON-005](../../constants/specs/CON-005.md) — AGG_SIG additional data derivation from genesis challenge
- [PAR-011](../../parity/specs/PAR-011.md) — Mempool flag parity (DONT_VALIDATE_SIGNATURE)
