# BLS-005: Cache Presence Does Not Affect Validation Correctness

> **Authoritative requirement:** [BLS-005](../NORMATIVE.md#BLS-005)
> **Verification:** [VERIFICATION.md](../VERIFICATION.md)
> **Tracking:** [TRACKING.yaml](../TRACKING.yaml)
> **Spec reference:** [SPEC.md](../../../../resources/SPEC.md) — Section 5.1

---

## Summary

The presence or absence of a `BlsCache` must not affect the correctness of validation results. For any given input, the `SpendResult` (additions, removals, fee) returned by `validate_spend_bundle()` and `validate_block()` must be identical regardless of whether the `bls_cache` parameter is `Some` (warm or cold) or `None`. A spend bundle that passes validation with a warm cache must also pass with a cold cache and with no cache, and vice versa. A spend bundle that fails validation must fail with the same error in all three scenarios.

This is the fundamental correctness invariant of the BLS cache subsystem. The cache is a pure performance optimization -- it affects how fast signature verification runs, not whether it succeeds or fails. Violating this invariant would mean that the validation outcome depends on the history of prior validation calls, which would be a consensus-breaking bug. Two DIG validators with different cache states could disagree on whether a block is valid, leading to a chain fork.

The invariant must hold across all possible cache states: empty cache (`BlsCache::default()`), partially populated cache (some but not all pairings cached), fully warm cache (all pairings cached from a prior call), and `None` (no cache at all). It must also hold across all possible inputs: valid bundles, invalid signatures, overspent costs, double spends, and malformed CLVM programs. This requirement is the capstone that ties together [BLS-001](BLS-001.md) (the interface), [BLS-002](BLS-002.md) (the `None` path), [BLS-003](BLS-003.md) (the reuse pattern), and [BLS-004](BLS-004.md) (the upstream type).

---

## Specification

### Formal Invariant

For all inputs `(bundle, context, config)`:

```
validate_spend_bundle(bundle, context, config, None)
  == validate_spend_bundle(bundle, context, config, Some(&mut BlsCache::default()))
  == validate_spend_bundle(bundle, context, config, Some(&mut warm_cache))
```

Where `warm_cache` is any `BlsCache` instance that has been used in prior validation calls (including calls with different bundles).

The same invariant applies to `validate_block()`:

```
validate_block(gen, refs, context, config, None)
  == validate_block(gen, refs, context, config, Some(&mut BlsCache::default()))
  == validate_block(gen, refs, context, config, Some(&mut warm_cache))
```

Equality here means:
- If both sides are `Ok`, the `SpendResult` fields are identical: same `additions` (same coins in same order), same `removals`, same `fee`.
- If both sides are `Err`, the `ValidationError` variant is the same (the error message may differ in non-semantic details like cache-related context, but the variant and key fields must match).

### Threat Model

Scenarios that could violate this invariant (and must be prevented):

1. **Stale cache entry**: A pairing was cached as valid, but the underlying conditions changed (e.g., different additional_data). This cannot happen because the cache key includes the full message, which includes the domain separation data.

2. **Cache poisoning**: A malicious actor inserts a fake "valid" entry into the cache. This cannot happen because `BlsCache` only adds entries after successful `aggregate_verify()` calls -- it is not externally mutable.

3. **Cache-dependent code paths**: The implementation takes a different logical path when a cache is present (e.g., skipping a validation step when the cache is warm). This must not happen -- the cache only affects BLS verification, not CLVM execution or condition checking.

4. **Partial cache coverage**: A block is validated with a cache that covers only some spends. The cached spends pass via cache lookup, the uncached spends pass via verification. The result must be identical to verifying all spends from scratch.

### Non-Affected Operations

The following operations must be completely independent of cache state:
- CLVM program execution (cost computation, condition extraction)
- Condition parsing and validation (opcode checks, argument validation)
- Coin addition/removal computation
- Fee calculation
- Cost limit enforcement
- Double-spend detection
- Timelock validation

Only BLS signature verification is affected by the cache, and its outcome (pass/fail) must not change.

---

## Acceptance Criteria

- [ ] For 10+ distinct valid spend bundles, `validate_spend_bundle()` produces identical `SpendResult` with `None`, cold cache, and warm cache
- [ ] For 5+ distinct invalid spend bundles (bad signatures), `validate_spend_bundle()` produces the same error variant with `None`, cold cache, and warm cache
- [ ] `validate_block()` produces identical `SpendResult` for the same block with `None`, cold cache, and warm cache
- [ ] A cache warm from unrelated bundles does not affect validation of a new bundle (no cross-contamination)
- [ ] The invariant holds for edge cases: zero-fee bundle, maximum-cost bundle, bundle with all 8 `AGG_SIG_*` variants

---

## Implementation Notes

- This requirement is satisfied by construction if the implementation correctly delegates to `chia-bls::BlsCache` (per [BLS-004](BLS-004.md)) and does not introduce cache-dependent branching in non-BLS code paths.
- The upstream `chia-bls::BlsCache` is designed to preserve this invariant. It only caches the result of successful `aggregate_verify()` calls and only returns cached results for exact key matches.
- Testing this requirement requires a systematic approach: validate the same input under multiple cache states and assert result equality. Property-based testing (e.g., with `proptest`) is recommended for thorough coverage.
- The invariant should be tested at both the unit level (individual function calls) and the integration level (full mempool-to-block flow).

---

## Verification

### Test Plan

1. **Parity test -- valid bundles**:
   - Generate 10 distinct valid `SpendBundle` instances using `Simulator`.
   - For each bundle, validate with:
     - `None`
     - `Some(&mut BlsCache::default())` (cold)
     - `Some(&mut warm_cache)` (warm from prior bundles)
   - Assert all three produce identical `SpendResult` (additions, removals, fee).

2. **Parity test -- invalid signatures**:
   - Generate 5 distinct `SpendBundle` instances with tampered signatures.
   - For each, validate with `None`, cold cache, and warm cache.
   - Assert all three produce the same `Err(ValidationError::InvalidSignature(..))`.

3. **Parity test -- block validation**:
   - Build a block from 5 valid bundles.
   - Validate with `None`, cold cache, and warm cache (warm from mempool validation of those 5 bundles).
   - Assert identical `SpendResult` in all cases.

4. **Cross-contamination test**:
   - Validate bundle A with `Some(&mut cache)`.
   - Validate unrelated bundle B with `Some(&mut cache)`.
   - Assert bundle B's result is identical to validating bundle B with a fresh cache.

5. **Edge case -- all AGG_SIG variants**:
   - Construct a spend that uses all 8 `AGG_SIG_*` condition types.
   - Validate with `None` and `Some(&mut cache)`.
   - Assert identical results.

6. **Property-based test** (recommended):
   - Use `proptest` to generate random valid and invalid bundles.
   - For each generated bundle, assert the correctness invariant holds across all cache states.

### Expected Test Files

- `tests/cache_tests.rs` -- BLS-005 correctness invariant tests
- `tests/parity_tests.rs` -- cross-validation of cache-independent behavior

---

## Source Citations

- dig-clvm correctness invariant: [SPEC.md Section 5.1](../../../../resources/SPEC.md) -- "bls_cache: When provided, verified signature pairings are cached and reused. Pass None to verify from scratch."
- `chia-bls::BlsCache` correctness: [`chia-bls` crate](https://crates.io/crates/chia-bls) -- cache only stores results of successful `aggregate_verify()` calls
- Chia L1 validation determinism: [`mempool_manager.py:445`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/full_node/mempool_manager.py#L445)

---

## References

- [BLS-001](BLS-001.md) -- Optional BlsCache parameter (defines the interface this invariant constrains)
- [BLS-002](BLS-002.md) -- None cache works correctly (one side of the equivalence)
- [BLS-003](BLS-003.md) -- Mempool-to-block reuse (the primary scenario where warm cache is used)
- [BLS-004](BLS-004.md) -- Uses `chia-bls::BlsCache` directly (upstream correctness guarantee)
