WikifitaGitHub live67e8de5
outro · co-fita/co-fita-hashmath-analysis

HashMath Verifier: Linear Chain, Not Merkle DAG

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.

Baixar raw

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

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

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 TypeDetected?Mechanism
Modified metricsYesHash depends on serialized metrics
Modified diff contentYesHash depends on diff content
Reordered entriesYesEach hash depends on prior hash
Inserted entriesYesChain of dependencies would break
Appended fake entries at endNoNew entries with valid prior hash are accepted
Full history replacementNoNo external anchor (blockchain, timestamp authority)

Tamper Detection Diagram

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:

PropertyMerkle DAG (git/IPFS)HashMath (actual)
TopologyDirected Acyclic Graph (multiple parents)Linear chain (single parent per block)
Content addressingHash of content determines addressHash of prior + content + metrics
Tree hashingIntermediate hashes in tree structureNo tree structure
Selective verificationCan verify sub-trees independentlyMust verify entire chain from genesis
Parallel branchesMultiple branches can coexistSingle 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:

PropertyBlockchainHashMath
Consensus mechanismProof-of-Work/StakeNone (single writer)
Distributed storagePeer-to-peer replicationSingle machine
External anchoringTimestamp authority / global stateNone
Append-only guaranteeCryptoeconomic incentivesTrust the orchestrator
Fork resolutionLongest chain / finalityNot 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.
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

ClaimRealityImpact
Merkle DAGLinear chainCannot verify branches independently
Cryptographic anchoringIn-memory hash chainLost on restart
Tamper evidenceLimited (no external anchor)Cannot detect full history replacement
Content-addressed storageNot implementedCannot 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:

# 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:

PropertyGit CommitHashMath Block
Hash inputTree + parent + author + message + timestampPrior hash + diff + metrics
Hash algorithmSHA-1 (migrating to SHA-256)SHA-256
Content-addressingYes (hash = address)No (hash = verification token)
Persistence.git/objects/In-memory only
Merkle structureYes (tree -> blob -> commit)No (linear chain)
Tamper evidenceFull (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:

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:

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.