# VAL-006: Reject Puzzle Hash Mismatch

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

---

## Summary

In the Chia UTXO model, a coin commits to a specific puzzle via its `puzzle_hash` field. When spending a coin, the spender must reveal the actual puzzle program (`puzzle_reveal`) that, when hashed using the CLVM canonical tree hash algorithm, produces exactly the `puzzle_hash` committed in the coin. This binding is what gives the puzzle owner exclusive control over the coin: only someone who knows the correct puzzle can produce a reveal that matches the hash.

`validate_spend_bundle()` MUST verify this binding for every `CoinSpend` in the bundle by computing `tree_hash(puzzle_reveal)` and comparing it to `coin.puzzle_hash`. A mismatch means either the wrong puzzle was provided, the coin data is corrupted, or an attacker is attempting to execute an unauthorized puzzle against someone else's coin.

On Chia L1, the puzzle hash check is performed during CLVM execution in the Rust consensus layer. The `run_spendbundle()` function in `chia-consensus` computes the tree hash of each puzzle reveal and validates it against the coin's puzzle hash. Therefore, this check may be handled implicitly by the delegation to `run_spendbundle()` (VAL-002). However, `dig-clvm` SHOULD also perform this check explicitly as a structural pre-validation step to provide a clear, specific error message (`PuzzleHashMismatch`) rather than a generic CLVM error, and to fail fast before allocating CLVM execution resources.

The tree hash algorithm is defined in `clvm-utils` and follows the canonical specification: atoms hash as `sha256(0x01 || data)` and pairs hash as `sha256(0x02 || left_hash || right_hash)`. This is tested for parity in Chia's [`test_curry_and_treehash.py`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/_tests/clvm/test_curry_and_treehash.py).

---

## Specification

### Algorithm

For each `CoinSpend` in `bundle.coin_spends`:

1. Deserialize `coin_spend.puzzle_reveal` into the CLVM allocator to obtain a `NodePtr`.
2. Compute `computed_hash = tree_hash(&allocator, puzzle_node)`.
3. Convert `computed_hash` to `Bytes32`.
4. Compare `computed_hash` with `coin_spend.coin.puzzle_hash`.
5. If they differ, return `Err(ValidationError::PuzzleHashMismatch(coin_id))`.

```rust
fn check_puzzle_hash(
    allocator: &Allocator,
    coin_spend: &CoinSpend,
) -> Result<(), ValidationError> {
    let puzzle_node = node_from_bytes(allocator, coin_spend.puzzle_reveal.as_ref())?;
    let computed_hash = tree_hash(allocator, puzzle_node);
    let expected_hash = coin_spend.coin.puzzle_hash;

    if Bytes32::from(computed_hash) != expected_hash {
        let coin_id = coin_spend.coin.coin_id();
        return Err(ValidationError::PuzzleHashMismatch(coin_id));
    }
    Ok(())
}
```

### Tree Hash Algorithm

```
tree_hash(atom)     = sha256(0x01 || atom_bytes)
tree_hash(pair)     = sha256(0x02 || tree_hash(left) || tree_hash(right))
```

This is implemented by `clvm_utils::tree_hash()`.

### Error Details

```rust
#[error("Puzzle hash mismatch: {0}")]
PuzzleHashMismatch(Bytes32),
```

The `Bytes32` payload is the coin ID of the coin whose puzzle hash did not match, allowing the caller to identify the problematic spend.

### Ordering

This check occurs as part of the structural checks (step 1 of the validation flow). It can be performed either before CLVM execution as a pre-check, or it may be implicitly handled by `run_spendbundle()` which also verifies puzzle hashes internally. If performed as a pre-check, it provides a clearer error message and avoids wasting CLVM execution cost.

---

## Acceptance Criteria

- [ ] A bundle where `tree_hash(puzzle_reveal) != coin.puzzle_hash` for any spend is rejected with `ValidationError::PuzzleHashMismatch`.
- [ ] The `PuzzleHashMismatch` error contains the coin ID of the affected coin.
- [ ] A bundle where `tree_hash(puzzle_reveal) == coin.puzzle_hash` for all spends passes this check.
- [ ] The tree hash computation uses `clvm_utils::tree_hash()`, not a custom implementation.
- [ ] A puzzle reveal that is valid CLVM but hashes to the wrong value is still rejected.
- [ ] A puzzle reveal that cannot be deserialized as valid CLVM is rejected (either as `PuzzleHashMismatch` or `Clvm` error).

---

## Implementation Notes

- **Primary file:** `src/consensus/validate.rs`
- **Dependencies:** `clvm_utils::tree_hash`, `clvmr::serde::node_from_bytes`, `clvmr::Allocator`.
- **Implicit vs. explicit check:** `chia-consensus::run_spendbundle()` also validates puzzle hashes internally. The implementation may rely on this implicit check and map the resulting error, or perform an explicit pre-check for better error messages. Either approach satisfies this requirement as long as the `PuzzleHashMismatch` error variant is returned.
- **Allocator lifetime:** If performing the check before CLVM execution, a temporary allocator can be used and discarded. Alternatively, the same allocator used for `run_spendbundle()` can be reused.
- **Edge case:** An empty puzzle reveal (zero bytes) will fail deserialization before the hash can be computed.
- **Edge case:** A puzzle reveal that is a single atom (e.g., `0x80` for nil) is valid CLVM and will hash correctly, but will likely fail during CLVM execution when run as a program.
- **Ephemeral coins:** For coins found in `ephemeral_coins` but not `coin_records`, the `puzzle_hash` is taken from the `CoinSpend.coin` field, which the spender provides. The check still applies: the reveal must match the coin's committed puzzle hash.

---

## Verification

### Test Plan

| Test | Type | Description | Expected Result |
|------|------|-------------|-----------------|
| Wrong puzzle reveal | Simulator test | Build spend with a valid but incorrect puzzle (different from coin's puzzle_hash) | `Err(ValidationError::PuzzleHashMismatch(coin_id))` |
| Correct puzzle reveal | Simulator test | Build standard spend with matching puzzle | Passes this check |
| Corrupted puzzle bytes | Simulator test | Modify one byte of the serialized puzzle reveal | `Err(PuzzleHashMismatch)` or `Err(Clvm)` |
| Error payload correct | Simulator test | Trigger mismatch, inspect the `Bytes32` | Matches the coin ID |
| Standard puzzle hash | Unit test | Compute `tree_hash` of `P2_DELEGATED_PUZZLE_OR_HIDDEN_PUZZLE` and verify against known hash | Hashes match |
| Curried puzzle hash | Unit test | Curry a puzzle with arguments, verify `tree_hash` matches `curry_tree_hash` | Hashes match |

---

## Source Citations

- **SPEC.md Section 5.1** (line 543): `ValidationError::PuzzleHashMismatch(Bytes32)` definition.
- **SPEC.md Section 2.3** (lines 65-78): `clvm-utils` tree hash API: `tree_hash(allocator, node)` and hash algorithm specification.
- **SPEC.md Section 6.1** (line 639): Structural checks include puzzle hash verification (implied by "Verify all spent coins exist" and CLVM execution).
- **L1 reference — tree hash parity:** [`test_curry_and_treehash.py`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/_tests/clvm/test_curry_and_treehash.py)
- **L1 reference — CLVM execution verifies puzzle hash:** The Rust `run_spendbundle()` function verifies puzzle hashes as part of execution.

---

## References

- [VAL-001](VAL-001.md) — Entry point signature (orchestrates this check)
- [VAL-002](VAL-002.md) — Delegates to run_spendbundle (which also verifies puzzle hashes)
- [VAL-003](VAL-003.md) — Duplicate spend check (precedes this check)
- [VAL-004](VAL-004.md) — Missing coin check (precedes this check)
- [VAL-005](VAL-005.md) — Already-spent check (precedes this check)
