Decision
BLAKE3 is the sole authoritative hash algorithm for SHCL-v2 chain integrity.
SHA-256 is retained as a parallel content fingerprint in the SHCL codec (src/shcl/shcl_codec.py) and is supported as a backward-compatible fallback in memory seal verification for seals generated before 2026-04-10. It must not be used for chain-hash computation in any new code.
runtime/ledger/verify_shcl_chain.py (chain-integrity verification) and orchestration/scripts/governance/verify_memory_seal.py (artifact-hash verification inside memory seals). Everything else is unaffected.
Background — the latent defect
The SHCL-v2 codec at src/shcl/shcl_codec.py has always used BLAKE3 as the primary chain hash:
# line 89 chain_hash = blake3.blake3(compressed_bytes).hexdigest() # line 188 blake3.blake3(preimage).hexdigest() # node_id
Meanwhile, runtime/ledger/verify_shcl_chain.py computed chain hashes with hashlib.sha256, then passed "algorithm": "blake3" into enforce(). The HALT-G211 chain-integrity predicate therefore compared a SHA-256-computed hash against a stored BLAKE3 hash, guaranteeing a mismatch on every valid chain entry.
verify_shcl_chain.py was not updated at that time. This change closes that gap.
Changes · verify_shcl_chain.py
_canonical_hash(entry, hash_key) now uses blake3.blake3 instead of hashlib.sha256. The hashlib import was removed. The file now correctly computes the BLAKE3 hash it claims to compute when calling enforce_or_raise() with "algorithm": "blake3".
| Aspect | Before | After |
|---|---|---|
Hash function in _canonical_hash | hashlib.sha256 | blake3.blake3 |
Algorithm passed to enforce() | "blake3" | "blake3" |
| Match against stored chain hash | guaranteed mismatch | matches when chain is intact |
hashlib import | present | removed |
Changes · verify_memory_seal.py
Added _blake3_hash(path) alongside the retained _sha256(path). Artifact-hash comparison now follows a strict precedence:
| # | Condition | Action | algo_used |
|---|---|---|---|
| 1 | seal entry has blake3 key | compute BLAKE3, compare | "blake3" |
| 2 | seal entry has sha256 key (no blake3) | compute SHA-256, compare · legacy fallback for seals before 2026-04-10 | "sha256" |
| 3 | neither key present | no hash check performed | — |
The artifact_hash_mismatch finding now includes an algorithm field so downstream tooling can tell which algorithm produced the mismatch.
Seal generation · new seals
Any seal-generating code — scripts/envelope/mint_ack_envelope.py, scripts/crypto_fabric/seal/ — that writes artifact hashes into a seal must write them under the blake3 key:
{
"file": "runtime/governance/enforcement_engine.py",
"blake3": "<64-char blake3 hex digest>"
}
sha256 field is legacy-only. New seals carry blake3 exclusively. The verifier's precedence ladder (§ IV) means a seal with both keys present will be checked under blake3 only, but emitting both is needless surface — write blake3 alone.
SHCL codec · the surviving SHA-256 field
src/shcl/shcl_codec.py computes:
content_fingerprint— SHA-256, retained as a parallel interoperability fieldchain_hash— BLAKE3, the authoritative chain-integrity hash
The SHA-256 content_fingerprint is not consumed by any enforcement predicate and has no authority role. It may be deprecated in a future Tier 3 change.
Test coverage
tests/governance/test_hash_unification.py verifies:
_canonical_hash()produces BLAKE3, not SHA-256- chain validation passes on a BLAKE3-hashed ledger
- chain validation detects tampered hashes and broken linkage
- memory-seal verification uses the
blake3key when present - memory-seal verification falls back to the
sha256key for legacy seals - the
blake3key takes precedence when both keys are present