---
name: co-fita-hashmath-analysis
type: analysis
title: "HashMath Verifier: Linear Chain, Not Merkle DAG"
description: "Technical analysis of the HashMath Verifier: what it actually does (SHA-256 linear chain), the gap between documentation and implementation, the persistence problem, and recommendations."
tags: [hashmath, cryptography, sha256, hash-chain, merkle-dag, verification, audit-trail, integrity, co-fita]
timestamp: 2026-07-22
---

# HashMath Verifier: Linear Chain, Not Merkle DAG

> **Source:** `RESEARCH_REPORT.md` (2026-07-22), `harness_core/hash_math.py` (90 lines)
> **Cross-references:** [[co-fita-harness]], [[co-fita-orchestrator]], [[co-fita-governance]], [[co-fita-hyper-workspace]]

---

## What It Actually Does

The HashMath Verifier is a **linear hash chain** inspired by blockchain and git commit objects. It seals research discoveries with cryptographic proofs that link each discovery to its predecessor, creating a tamper-evident history of the orchestrator's work.

### The Seal Operation

```python
def seal_discovery(prior_hash, diff_content, metrics):
    metrics_str = json.dumps(metrics, sort_keys=True)
    payload = f"{prior_hash}::{diff_content}::{metrics_str}"
    return SHA256(payload)
```

Each "block" in the chain contains:
- **Prior hash** -- link to the previous state
- **Diff content** -- the action taken (approach description or code diff)
- **Metrics** -- the outcome (empirical gain, audit confidence, iteration number)

The `::` separator is a simple delimiter. No length-prefixing or canonical encoding beyond `json.dumps(sort_keys=True)`.

### Chain Verification

```python
def verify_chain(state_history):
    current = "GENESIS_HASH"
    for entry in state_history:
        expected = seal_discovery(current, entry["diff"], entry["metrics"])
        if expected != entry["hash"]:
            return False
        current = entry["hash"]
    return True
```

Verification iterates through the history, recomputing each expected hash. If any recomputed hash does not match the recorded hash, the chain is broken.

---

## What It Detects

| Tampering Type | Detected? | Mechanism |
|:---|:---|:---|
| Modified metrics | Yes | Hash depends on serialized metrics |
| Modified diff content | Yes | Hash depends on diff content |
| Reordered entries | Yes | Each hash depends on prior hash |
| Inserted entries | Yes | Chain of dependencies would break |
| Appended fake entries at end | **No** | New entries with valid prior hash are accepted |
| Full history replacement | **No** | No external anchor (blockchain, timestamp authority) |

### Tamper Detection Diagram

```mermaid
graph LR
    G["GENESIS_HASH"] --> H1["Hash 1<br/>(seals diff1 + metrics1)"]
    H1 --> H2["Hash 2<br/>(seals diff2 + metrics2)"]
    H2 --> H3["Hash 3<br/>(seals diff3 + metrics3)"]

    style H2 fill:#ff6b6b,color:#fff

    H2 -.->|"Tampered metrics?<br/>Recompute: hash(expected) != hash(recorded)"| ALERT["CHAIN BROKEN"]
```

---

## What It Is NOT

### Not a Merkle DAG

The Co-Fita README mentions "Merkle DAG construction" but the actual implementation is a **simple linear chain**, not a DAG:

| Property | Merkle DAG (git/IPFS) | HashMath (actual) |
|:---|:---|:---|
| Topology | Directed Acyclic Graph (multiple parents) | Linear chain (single parent per block) |
| Content addressing | Hash of content determines address | Hash of prior + content + metrics |
| Tree hashing | Intermediate hashes in tree structure | No tree structure |
| Selective verification | Can verify sub-trees independently | Must verify entire chain from genesis |
| Parallel branches | Multiple branches can coexist | Single chain only |

The difference matters: a Merkle DAG would allow the failure tree to be cryptographically anchored with each branch independently verifiable. The current linear chain can only verify the main path.

### Not Blockchain

Despite the blockchain inspiration, the HashMath chain lacks several critical blockchain properties:

| Property | Blockchain | HashMath |
|:---|:---|:---|
| Consensus mechanism | Proof-of-Work/Stake | None (single writer) |
| Distributed storage | Peer-to-peer replication | Single machine |
| External anchoring | Timestamp authority / global state | None |
| Append-only guarantee | Cryptoeconomic incentives | Trust the orchestrator |
| Fork resolution | Longest chain / finality | Not applicable (linear) |

### Not Persistent

The most critical gap: **the `HashMathVerifier` itself has no persistence.** The `history_chain` list lives in memory only. The orchestrator's `history_chain` is a Python list that is not saved to `state/progress.json` or any file.

This means:

1. **Restart loses the chain.** If the orchestrator restarts, the cryptographic audit trail is gone.
2. **The Failure Tree is persisted** (to `failure_tree/failure_tree.json`, 658.7 KB) but the hash chain is not.
3. **Verification is impossible after restart.** The `verify_chain()` function can only verify what exists in memory.

```mermaid
graph TB
    subgraph "In Memory (LOST on restart)"
        HC["history_chain: list[dict]<br/>GENESIS_HASH -> Hash1 -> Hash2 -> ..."]
        HM["HashMathVerifier<br/>seal_discovery(), verify_chain()"]
    end

    subgraph "On Disk (PERSISTED)"
        FT["failure_tree/failure_tree.json<br/>658.7 KB, full tree"]
        PS["state/progress.json<br/>iteration, findings, status"]
        LOG["logs/orchestrator.jsonl<br/>append-only events"]
    end

    HC -.->|"NOT SAVED"| FT
    HC -.->|"NOT SAVED"| PS
    HC -.->|"NOT SAVED"| LOG
```

---

## The Documentation-Implementation Gap

### What the README Claims

The README describes the HashMath system as providing "Merkle DAG construction" and "cryptographic anchoring" for research discoveries. This implies:
- A directed acyclic graph structure
- Content-addressed storage
- Independent verification of sub-trees
- Integration with the failure tree for branch-level verification

### What the Code Implements

The actual implementation in `hash_math.py` (90 lines) provides:
- A linear chain (not a DAG)
- SHA-256 hashing (not content-addressing)
- Full-chain verification only (not branch-level)
- No integration with the failure tree

### The Gap

| Claim | Reality | Impact |
|:---|:---|:---|
| Merkle DAG | Linear chain | Cannot verify branches independently |
| Cryptographic anchoring | In-memory hash chain | Lost on restart |
| Tamper evidence | Limited (no external anchor) | Cannot detect full history replacement |
| Content-addressed storage | Not implemented | Cannot look up discoveries by hash |

---

## The Persistence Problem

### State Manager Does Not Save the Chain

The [[co-fita-hyper-workspace]] `StateManager` persists:
- `task_spec.md` -- goal specification
- `progress.json` -- iteration counter, findings count, status
- `directions_tried.json` -- attempted directions
- `logs/*.jsonl` -- per-source event logs

None of these include the hash chain. The `history_chain` is maintained in the orchestrator's local variable and passed to `seal_discovery()` and `verify_chain()` but never serialized.

### The Fix Is Simple

The hash chain should be persisted to a dedicated file:

```python
# After each seal_discovery():
with open("state/hash_chain.json", "w") as f:
    json.dump(history_chain, f, indent=2)

# On startup, before first seal_discovery():
if os.path.exists("state/hash_chain.json"):
    with open("state/hash_chain.json") as f:
        history_chain = json.load(f)
```

This would:
1. Survive orchestrator restarts
2. Allow post-hoc verification of the entire chain
3. Be human-readable and git-friendly (like other state files)
4. Maintain the append-only audit trail philosophy

### Concurrency Consideration

Currently there is no file locking on state files. The same applies to a hypothetical `hash_chain.json`. If multiple orchestrator instances run concurrently, they could corrupt the chain. This is a known limitation documented in [[co-fita-hyper-workspace]].

---

## Security Analysis

### Strengths

1. **Tamper evidence for modifications.** Any change to a prior entry's diff or metrics breaks all subsequent hashes.
2. **Order enforcement.** Reordering entries is detected because each hash depends on the prior.
3. **Deterministic verification.** `verify_chain()` is a pure function -- given the same history, it always produces the same result.
4. **Zero dependencies.** Uses Python stdlib `hashlib` and `json` -- no external cryptography libraries.
5. **Simple.** 90 lines of code. Auditable by a human in minutes.

### Weaknesses

1. **No persistence.** The chain is lost on restart. This is the most critical weakness.
2. **No external anchor.** A malicious actor who replaces the entire history (with a self-consistent chain from GENESIS_HASH) would not be detected.
3. **No content-addressing.** Cannot look up a discovery by its hash. The hash is a verification token, not an addressing mechanism.
4. **Weak separator.** The `::` delimiter could theoretically collide with content that contains `::`. No escaping or length-prefixing is applied.
5. **Metrics can be gamed.** The metrics dict is self-reported by the orchestrator. A buggy or adversarial orchestrator could report fabricated metrics and the chain would accept them as valid.
6. **No timestamp.** The chain does not include timestamps. An entry from iteration 1 and an entry from iteration 100 look the same from a temporal perspective.

### The Metrics Gaming Problem

The `seal_discovery()` function seals whatever metrics it receives. If the orchestrator reports `{"loss": 0.01}` when the actual loss was `{"loss": 0.45}`, the chain would accept this as valid. The chain only guarantees that the metrics were not modified AFTER sealing -- it does not guarantee that the metrics were correct WHEN sealed.

This is a fundamental limitation of self-attestation. The Red Team auditor's verification (Phase III) is the external check on the orchestrator's claims, but its output is not incorporated into the hash chain.

---

## Comparison with Git

Git's commit objects use a similar linear hash chain structure:

| Property | Git Commit | HashMath Block |
|:---|:---|:---|
| Hash input | Tree + parent + author + message + timestamp | Prior hash + diff + metrics |
| Hash algorithm | SHA-1 (migrating to SHA-256) | SHA-256 |
| Content-addressing | Yes (hash = address) | No (hash = verification token) |
| Persistence | `.git/objects/` | In-memory only |
| Merkle structure | Yes (tree -> blob -> commit) | No (linear chain) |
| Tamper evidence | Full (including content) | Partial (no external anchor) |

The most significant difference: git is a Merkle DAG with content-addressed storage and persistent objects. HashMath is a linear chain without content addressing and without persistence.

---

## Recommendations

### Priority 1: Persist the Chain

Save `history_chain` to `state/hash_chain.json` after every `seal_discovery()` call. Load it on startup. This is a one-line fix in the orchestrator and a few lines in the verifier.

### Priority 2: Add Timestamps

Include `time.time()` in the sealed payload:

```python
payload = f"{prior_hash}::{diff_content}::{metrics_str}::{timestamp}"
```

This enables temporal analysis of the chain and prevents backdating.

### Priority 3: Incorporate Red Team Results

Seal the Red Team audit result alongside the metrics. This creates a chain where both the orchestrator's claims AND the auditor's verification are cryptographically linked:

```python
payload = f"{prior_hash}::{diff_content}::{metrics_str}::{audit_result_str}"
```

### Priority 4: External Anchoring

Periodically publish the chain hash to an external, immutable store:
- **Git commit:** Include the latest hash in a commit message
- **Blockchain:** Publish to Polygon (the same chain used for [[clickfix-attack-chain]] BDDR analysis)
- **Timestamp authority:** RFC 3161 timestamping

This prevents full history replacement attacks.

### Priority 5: Merkle DAG Upgrade

If the failure tree is to be cryptographically anchored, upgrade the linear chain to a Merkle DAG:
- Each failure tree node gets a hash that depends on its children
- Branches can be verified independently
- The root hash represents the entire tree state

This is a significant implementation effort but would align the code with the README's "Merkle DAG construction" claim.

---

## Integration with Co-Fita Components

### Orchestrator (Phase IV: HashMath Fixation)

The hash chain is created in Phase IV of the [[co-fita-orchestrator]] 5-phase loop. Only successful audits reach Phase IV -- failures skip it and return to Phase I. This means the chain only records successes, not failures. The failure tree records both, but the hash chain is a success-only ledger.

### Failure Tree

The [[co-fita-harness]] Failure Tree is persisted to `failure_tree/failure_tree.json` (658.7 KB). The hash chain is not. This asymmetry means the failure tree survives restarts but the hash chain does not. Both should have the same persistence guarantees.

### Governance

The [[co-fita-governance]] system persists all governance data to JSONL files (append-only). The hash chain should follow the same pattern. The governance system's append-only design is philosophically aligned with the hash chain's tamper-evidence goal.

### Wiki Export

The orchestrator exports wiki state (failure tree mermaid, reflections, Elo arena, governance state) to `unified-harness-wiki/`. The hash chain is not included in this export. Adding it would make the wiki a more complete audit trail.

---

## Summary

The HashMath Verifier is a well-conceived but incompletely realized component. Its core design -- a linear SHA-256 chain sealing discoveries -- is sound and provides meaningful tamper evidence for modification, reordering, and insertion attacks. The 90-line implementation is simple, auditable, and dependency-free.

The critical gap is persistence: the chain exists only in memory and is lost on restart. This undermines the entire purpose of cryptographic anchoring. The documentation claims Merkle DAG construction, but the implementation is a linear chain without content addressing or tree hashing.

The recommended path is incremental: persist the chain first (simple), add timestamps and audit results (moderate), then consider Merkle DAG upgrade (significant). Each step increases the system's integrity guarantees without requiring architectural changes to the orchestrator.

---

*This document is alive. It evolves as the research evolves. Challenge it. Improve it. That is the protocol.*
