diff --git a/bench/fixtures/5f/falsification-witness-v1.jsonl b/bench/fixtures/5f/falsification-witness-v1.jsonl index a0c24f9..59ff1ec 100644 --- a/bench/fixtures/5f/falsification-witness-v1.jsonl +++ b/bench/fixtures/5f/falsification-witness-v1.jsonl @@ -1,6 +1,11 @@ -{"_meta": {"battery": "5f", "sub_battery": "falsification", "version": "v1", "task_count": 5, "notes": "Witness-divergence-extracted falsification fixtures. Auto-generated by `make bench-witness-divergence` from providence_canonical_witness audit events. Each row pairs an LLM raw answer with the kernel's canonical answer; verify_quotes is expected to return UNGROUNDED because the LLM prose doesn't substring-match the kernel's terse canonical form. _witness_meta carries the original agreement_label + audit_seq for traceability."}} +{"_meta": {"battery": "5f", "sub_battery": "falsification", "version": "v1", "task_count": 10, "notes": "Witness-divergence-extracted falsification fixtures. Auto-generated by `make bench-witness-divergence` from providence_canonical_witness audit events. Each row pairs an LLM raw answer with the kernel's canonical answer; verify_quotes is expected to return UNGROUNDED because the LLM prose doesn't substring-match the kernel's terse canonical form. _witness_meta carries the original agreement_label + audit_seq for traceability."}} {"id": "5f-fal-witness-011631", "battery": "5f", "sub_battery": "falsification", "version": "v1", "carrier": "providence_record", "domain": "claim_lattice", "pi_star_ref": "arithmetic@v1", "answer_text": "1/10", "context": "canonical_kernel_answer=3/10\nquestion=0.1 + 0.2", "expected_reason": "UNGROUNDED", "verifier_method_root": "verify_quotes-v1", "expected": "pass", "_witness_meta": {"agreement_label": "KERNEL-LLM-DIVERGED", "audit_seq": 11631, "pi_star_ref": "arithmetic@v1"}} {"id": "5f-fal-witness-011637", "battery": "5f", "sub_battery": "falsification", "version": "v1", "carrier": "providence_record", "domain": "claim_lattice", "pi_star_ref": "logic-kernel@v1", "answer_text": "TRUE", "context": "canonical_kernel_answer=(NOT A OR B)\nquestion=A IMPL B", "expected_reason": "UNGROUNDED", "verifier_method_root": "verify_quotes-v1", "expected": "pass", "_witness_meta": {"agreement_label": "KERNEL-LLM-DIVERGED", "audit_seq": 11637, "pi_star_ref": "logic-kernel@v1"}} {"id": "5f-fal-witness-011639", "battery": "5f", "sub_battery": "falsification", "version": "v1", "carrier": "providence_record", "domain": "claim_lattice", "pi_star_ref": "logic-kernel@v1", "answer_text": "TRUE", "context": "canonical_kernel_answer=(NOT A OR B)\nquestion=(NOT B) IMPL (NOT A)", "expected_reason": "UNGROUNDED", "verifier_method_root": "verify_quotes-v1", "expected": "pass", "_witness_meta": {"agreement_label": "KERNEL-LLM-DIVERGED", "audit_seq": 11639, "pi_star_ref": "logic-kernel@v1"}} {"id": "5f-fal-witness-011643", "battery": "5f", "sub_battery": "falsification", "version": "v1", "carrier": "providence_record", "domain": "claim_lattice", "pi_star_ref": "algebra-symbolic@v1", "answer_text": "1", "context": "canonical_kernel_answer=Add(Pow(Symbol('x'), Integer(2)), Mul(Integer(2), Symbol('x')), Integer(1))\nquestion=(x+1)**2", "expected_reason": "UNGROUNDED", "verifier_method_root": "verify_quotes-v1", "expected": "pass", "_witness_meta": {"agreement_label": "KERNEL-LLM-DIVERGED", "audit_seq": 11643, "pi_star_ref": "algebra-symbolic@v1"}} {"id": "5f-fal-witness-011645", "battery": "5f", "sub_battery": "falsification", "version": "v1", "carrier": "providence_record", "domain": "claim_lattice", "pi_star_ref": "algebra-symbolic@v1", "answer_text": "1", "context": "canonical_kernel_answer=Add(Pow(Symbol('x'), Integer(2)), Mul(Integer(2), Symbol('x')), Integer(1))\nquestion=x**2 + 2*x + 1", "expected_reason": "UNGROUNDED", "verifier_method_root": "verify_quotes-v1", "expected": "pass", "_witness_meta": {"agreement_label": "KERNEL-LLM-DIVERGED", "audit_seq": 11645, "pi_star_ref": "algebra-symbolic@v1"}} +{"id": "5f-fal-witness-011649", "battery": "5f", "sub_battery": "falsification", "version": "v1", "carrier": "providence_record", "domain": "claim_lattice", "pi_star_ref": "arithmetic@v1", "answer_text": "1/10", "context": "canonical_kernel_answer=3/10\nquestion=0.1 + 0.2", "expected_reason": "UNGROUNDED", "verifier_method_root": "verify_quotes-v1", "expected": "pass", "_witness_meta": {"agreement_label": "LLM-DIVERGED", "audit_seq": 11649, "pi_star_ref": "arithmetic@v1"}} +{"id": "5f-fal-witness-011652", "battery": "5f", "sub_battery": "falsification", "version": "v1", "carrier": "providence_record", "domain": "claim_lattice", "pi_star_ref": "logic-kernel@v1", "answer_text": "TRUE", "context": "canonical_kernel_answer=(NOT A OR B)\nquestion=A IMPL B", "expected_reason": "UNGROUNDED", "verifier_method_root": "verify_quotes-v1", "expected": "pass", "_witness_meta": {"agreement_label": "LLM-DIVERGED", "audit_seq": 11652, "pi_star_ref": "logic-kernel@v1"}} +{"id": "5f-fal-witness-011653", "battery": "5f", "sub_battery": "falsification", "version": "v1", "carrier": "providence_record", "domain": "claim_lattice", "pi_star_ref": "logic-kernel@v1", "answer_text": "TRUE", "context": "canonical_kernel_answer=(NOT A OR B)\nquestion=(NOT B) IMPL (NOT A)", "expected_reason": "UNGROUNDED", "verifier_method_root": "verify_quotes-v1", "expected": "pass", "_witness_meta": {"agreement_label": "LLM-DIVERGED", "audit_seq": 11653, "pi_star_ref": "logic-kernel@v1"}} +{"id": "5f-fal-witness-011655", "battery": "5f", "sub_battery": "falsification", "version": "v1", "carrier": "providence_record", "domain": "claim_lattice", "pi_star_ref": "algebra-symbolic@v1", "answer_text": "1", "context": "canonical_kernel_answer=Add(Pow(Symbol('x'), Integer(2)), Mul(Integer(2), Symbol('x')), Integer(1))\nquestion=(x+1)**2", "expected_reason": "UNGROUNDED", "verifier_method_root": "verify_quotes-v1", "expected": "pass", "_witness_meta": {"agreement_label": "LLM-DIVERGED", "audit_seq": 11655, "pi_star_ref": "algebra-symbolic@v1"}} +{"id": "5f-fal-witness-011656", "battery": "5f", "sub_battery": "falsification", "version": "v1", "carrier": "providence_record", "domain": "claim_lattice", "pi_star_ref": "algebra-symbolic@v1", "answer_text": "1", "context": "canonical_kernel_answer=Add(Pow(Symbol('x'), Integer(2)), Mul(Integer(2), Symbol('x')), Integer(1))\nquestion=x**2 + 2*x + 1", "expected_reason": "UNGROUNDED", "verifier_method_root": "verify_quotes-v1", "expected": "pass", "_witness_meta": {"agreement_label": "LLM-DIVERGED", "audit_seq": 11656, "pi_star_ref": "algebra-symbolic@v1"}} diff --git a/bench/results/witness-sweep.json b/bench/results/witness-sweep.json index 2fe68a8..a60cbc9 100644 --- a/bench/results/witness-sweep.json +++ b/bench/results/witness-sweep.json @@ -1,123 +1,123 @@ { "schema_version": "witness-sweep-v1", - "timestamp_utc": "2026-05-09T17:27:30+00:00", + "timestamp_utc": "2026-05-09T18:27:23+00:00", "shards_dir": "/home/fox/.arborist/shards", "model": "adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic", "endpoint": "https://hermes.ai.unturf.com/v1", "summary": { "questions": 8, "by_agreement_label": { - "KERNEL-LLM-DIVERGED": 5, - "KERNEL-LLM-AGREE": 3 + "LLM-DIVERGED": 5, + "STRICT-WITNESSED": 3 }, "divergence_count": 5, "divergence_rate": 0.625, - "wall_ms_median": 130.2, - "wall_ms_max": 1111.0 + "wall_ms_median": 159.2, + "wall_ms_max": 1099.3 }, "queries": [ { "question": "0.1 + 0.2", - "status": "cache_miss_then_written", + "status": "cache_hit", "audit_mode": "CANONICAL_PROJECTION", "pi_star_ref": "arithmetic@v1", "canonical_answer": "3/10", - "agreement_label": "KERNEL-LLM-DIVERGED", + "agreement_label": "LLM-DIVERGED", "llm_raw": "1/10", "llm_canonical_match": false, - "cache_status": "ABSENT", - "wall_ms": 1111.0, + "cache_status": "ok", + "wall_ms": 1099.3, "audit_event_hash": "3b25ad3cd28a9505d96e22c85ae42ab182008ee6584b3a74c881dba07e3eab28" }, { "question": "1/3 + 1/6", - "status": "cache_miss_then_written", + "status": "cache_hit", "audit_mode": "CANONICAL_PROJECTION", "pi_star_ref": "arithmetic@v1", "canonical_answer": "1/2", - "agreement_label": "KERNEL-LLM-AGREE", + "agreement_label": "STRICT-WITNESSED", "llm_raw": "1/2", "llm_canonical_match": false, - "cache_status": "ABSENT", - "wall_ms": 150.8, + "cache_status": "ok", + "wall_ms": 159.2, "audit_event_hash": "7677efdeb3b2ad7f60cf4312d2f4825c9fa949ddcbc06434ee5fc4ea50a86e3e" }, { "question": "2**10", - "status": "cache_miss_then_written", + "status": "cache_hit", "audit_mode": "CANONICAL_PROJECTION", "pi_star_ref": "arithmetic@v1", "canonical_answer": "1024/1", - "agreement_label": "KERNEL-LLM-AGREE", + "agreement_label": "STRICT-WITNESSED", "llm_raw": "1024", "llm_canonical_match": false, - "cache_status": "ABSENT", - "wall_ms": 130.2, + "cache_status": "ok", + "wall_ms": 134.7, "audit_event_hash": "ed570bd6127a0b7143bb5c0f8cf76daf37a2092fe84350c0cab1ee2a2bf0410b" }, { "question": "A IMPL B", - "status": "cache_miss_then_written", + "status": "cache_hit", "audit_mode": "CANONICAL_PROJECTION", "pi_star_ref": "logic-kernel@v1", "canonical_answer": "(NOT A OR B)", - "agreement_label": "KERNEL-LLM-DIVERGED", + "agreement_label": "LLM-DIVERGED", "llm_raw": "TRUE", "llm_canonical_match": false, - "cache_status": "ABSENT", - "wall_ms": 124.9, + "cache_status": "ok", + "wall_ms": 147.0, "audit_event_hash": "0f6fb926fd72150c5a0d8121a004b1f66edf3c8ee4bc15c52168f3a7869786fe" }, { "question": "(NOT B) IMPL (NOT A)", - "status": "cache_miss_then_written", + "status": "cache_hit", "audit_mode": "CANONICAL_PROJECTION", "pi_star_ref": "logic-kernel@v1", "canonical_answer": "(NOT A OR B)", - "agreement_label": "KERNEL-LLM-DIVERGED", + "agreement_label": "LLM-DIVERGED", "llm_raw": "TRUE", "llm_canonical_match": false, - "cache_status": "ABSENT", - "wall_ms": 113.5, + "cache_status": "ok", + "wall_ms": 260.2, "audit_event_hash": "1b17f6e39edf2bb3d8dce003e20f71df9a3b9bd1b885f8a6e4b78e43ec122c31" }, { "question": "A OR NOT A", - "status": "cache_miss_then_written", + "status": "cache_hit", "audit_mode": "CANONICAL_PROJECTION", "pi_star_ref": "logic-kernel@v1", "canonical_answer": "TRUE", - "agreement_label": "KERNEL-LLM-AGREE", + "agreement_label": "STRICT-WITNESSED", "llm_raw": "TRUE", "llm_canonical_match": false, - "cache_status": "ABSENT", - "wall_ms": 123.0, + "cache_status": "ok", + "wall_ms": 129.1, "audit_event_hash": "5929ffe218bb8a97c391df70cfffed810f66d02c4a54bdce2569755200bd8d0d" }, { "question": "(x+1)**2", - "status": "cache_miss_then_written", + "status": "cache_hit", "audit_mode": "CANONICAL_PROJECTION", "pi_star_ref": "algebra-symbolic@v1", "canonical_answer": "Add(Pow(Symbol('x'), Integer(2)), Mul(Integer(2), Symbol('x')), Integer(1))", - "agreement_label": "KERNEL-LLM-DIVERGED", + "agreement_label": "LLM-DIVERGED", "llm_raw": "1", "llm_canonical_match": false, - "cache_status": "ABSENT", - "wall_ms": 179.4, + "cache_status": "ok", + "wall_ms": 196.1, "audit_event_hash": "b36f1a830aae8fec514f95f3104ba4473bc6febb98137016c5d0816f859ba440" }, { "question": "x**2 + 2*x + 1", - "status": "cache_miss_then_written", + "status": "cache_hit", "audit_mode": "CANONICAL_PROJECTION", "pi_star_ref": "algebra-symbolic@v1", "canonical_answer": "Add(Pow(Symbol('x'), Integer(2)), Mul(Integer(2), Symbol('x')), Integer(1))", - "agreement_label": "KERNEL-LLM-DIVERGED", + "agreement_label": "LLM-DIVERGED", "llm_raw": "1", "llm_canonical_match": false, - "cache_status": "ABSENT", - "wall_ms": 120.1, + "cache_status": "ok", + "wall_ms": 147.3, "audit_event_hash": "d307d6990527ddc6af73ea3cb894d4986ea3003106723fe74e22282f16eb62c6" } ] diff --git a/bench/scripts/witness_sweep_cron.sh b/bench/scripts/witness_sweep_cron.sh new file mode 100755 index 0000000..e3d80b3 --- /dev/null +++ b/bench/scripts/witness_sweep_cron.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash +# witness_sweep_cron.sh — scheduled witness-divergence collection +# +# Runs `make bench-witness-sweep` followed by `make bench-witness-divergence` +# and (optionally) commits any newly-extracted divergence fixtures to the +# arborist repo. Designed to grow the calibration-data corpus over time +# without operator intervention. +# +# Cron usage (recommended every 6 hours; LLM costs scale with frequency): +# +# 0 */6 * * * cd /home/fox/git/arborist && \ +# bench/scripts/witness_sweep_cron.sh \ +# --commit --branch main >> /var/log/arborist-witness.log 2>&1 +# +# Invariants: +# - Never commits unless --commit AND fixture file actually changed +# - Skips push by default (operator runs `git push` separately) +# - Records sweep result + extraction count + commit sha in audit log +# - Fail-closed: pre-commit hook failure → no auto-push, no silent loss + +set -euo pipefail + +# --------------------------------------------------------------------------- +# Defaults +# --------------------------------------------------------------------------- +COMMIT=0 +PUSH=0 +BRANCH="main" +LOG_PREFIX="witness-sweep" +SHARDS_DIR="${ARBORIST_SHARDS_DIR:-$HOME/.arborist/shards}" +FIXTURE_PATH="bench/fixtures/5f/falsification-witness-v1.jsonl" +SWEEP_OUT="bench/results/witness-sweep.json" + +# --------------------------------------------------------------------------- +# Argument parsing +# --------------------------------------------------------------------------- +while [[ $# -gt 0 ]]; do + case "$1" in + --commit) COMMIT=1; shift ;; + --push) PUSH=1; shift ;; + --branch) BRANCH="$2"; shift 2 ;; + --shards) SHARDS_DIR="$2"; shift 2 ;; + --help|-h) + cat <<'EOF' +Usage: witness_sweep_cron.sh [--commit] [--push] [--branch NAME] [--shards DIR] + + --commit commit any new divergence fixtures (default: dry-run) + --push push the commit (requires --commit; default: skip) + --branch NAME branch to commit on (default: main) + --shards DIR shards-dir override (default: $ARBORIST_SHARDS_DIR or ~/.arborist/shards) + +Environment: + ARBORIST_SHARDS_DIR — fallback if --shards not given. + ARBORIST_LLM_ENDPOINT / ARBORIST_LLM_MODEL — passthrough to witness sweep. + +Output: + Stdout: human-readable timestamped log per phase. + Bench artifacts: bench/results/witness-sweep.json (latest sweep); + bench/fixtures/5f/falsification-witness-v1.jsonl. +EOF + exit 0 + ;; + *) echo "$LOG_PREFIX: unknown arg: $1" >&2; exit 2 ;; + esac +done + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- +log() { printf "[%s %s] %s\n" "$LOG_PREFIX" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*"; } + +require_cmd() { + command -v "$1" >/dev/null 2>&1 || { + log "missing required command: $1"; exit 2; + } +} + +require_cmd git +require_cmd make + +# Must run from repo root (Makefile + .git both expected here) +[[ -f Makefile ]] || { log "must run from arborist repo root"; exit 2; } +[[ -d .git ]] || { log "not a git repo"; exit 2; } + +[[ -d "$SHARDS_DIR" ]] || { + log "shards-dir missing: $SHARDS_DIR"; exit 2; +} + +# --------------------------------------------------------------------------- +# Phase 1 — sweep +# --------------------------------------------------------------------------- +log "phase 1 — bench-witness-sweep against $SHARDS_DIR" +ARBORIST_SHARDS_DIR="$SHARDS_DIR" make bench-witness-sweep +SWEEP_DIVERGENCE=$(python3 -c " +import json +d = json.load(open('$SWEEP_OUT')) +print(d['summary']['divergence_count']) +") +log "phase 1 done — sweep recorded $SWEEP_DIVERGENCE divergences this run" + +# --------------------------------------------------------------------------- +# Phase 2 — extract divergence as 5F fixtures +# --------------------------------------------------------------------------- +log "phase 2 — extract divergence audit events as 5F fixtures" +make bench-witness-divergence +EXTRACT_COUNT=$(python3 -c " +import json +lines = [l for l in open('$FIXTURE_PATH').read().splitlines() if l.strip()] +# subtract meta line +print(max(0, len(lines) - 1)) +") +log "phase 2 done — fixture file holds $EXTRACT_COUNT cumulative divergence rows" + +# --------------------------------------------------------------------------- +# Phase 3 — commit if --commit AND something changed +# --------------------------------------------------------------------------- +if [[ "$COMMIT" -ne 1 ]]; then + log "phase 3 — skipped (no --commit; dry-run)" + log "DONE" + exit 0 +fi + +if git diff --quiet "$FIXTURE_PATH" "$SWEEP_OUT" 2>/dev/null; then + log "phase 3 — fixture + sweep unchanged; nothing to commit" + log "DONE" + exit 0 +fi + +# Make sure we're on the requested branch. +CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) +if [[ "$CURRENT_BRANCH" != "$BRANCH" ]]; then + log "WARNING: on branch $CURRENT_BRANCH, expected $BRANCH; not committing" + exit 1 +fi + +# Stage + commit. Pre-commit hooks must succeed; if they fail the commit +# is aborted and we exit non-zero (cron mail will show the failure). +git add "$FIXTURE_PATH" "$SWEEP_OUT" +git commit -m "witness sweep auto-update: $SWEEP_DIVERGENCE divergence(s) this run + +Extracted by witness_sweep_cron.sh — $(date -u +%Y-%m-%dT%H:%M:%SZ) +Divergence count this run: $SWEEP_DIVERGENCE +Total fixture rows: $EXTRACT_COUNT" + +NEW_SHA=$(git rev-parse HEAD) +log "phase 3 done — committed $NEW_SHA" + +if [[ "$PUSH" -eq 1 ]]; then + log "phase 4 — pushing to origin/$BRANCH" + git push origin "$BRANCH" + log "phase 4 done" +else + log "phase 4 — skipped (no --push); operator runs git push manually" +fi + +log "DONE" diff --git a/docs/TICKETS.md b/docs/TICKETS.md index 6fb4802..7e251e6 100644 --- a/docs/TICKETS.md +++ b/docs/TICKETS.md @@ -76,7 +76,7 @@ Newest first. Update on every open/close. | #000021 | 5S/5T/5R benchmark fixtures + harness | closed · Phase 1a + 2 landed 2026-05-08 | 2026-05-07 | — | | #000020 | Capital-cost ledger (8-capital queues) | closed · landed 2026-05-07 | 2026-05-07 | — | | #000019 | Specification methodology for π* and V | closed · landed 2026-05-07 | 2026-05-07 | — | -| #000018 | Adversarial soft-hash covert-channel analysis | open · awaiting go/no-go | 2026-05-07 | — | +| #000018 | Adversarial soft-hash covert-channel analysis | closed · landed 2026-05-09 (analysis doc) | 2026-05-07 | — | | #000017 | Memory-root: lifelong learning audit chain | closed · landed 2026-05-07 | 2026-05-07 | — | | #000016 | ZK Phase-2 frontier proof (concretize) | open · awaiting go/no-go | 2026-05-07 | — | | #000015 | π* domain library + cross-domain composition | closed · landed 2026-05-07 | 2026-05-07 | — | diff --git a/docs/soft-hash-channel-analysis.md b/docs/soft-hash-channel-analysis.md new file mode 100644 index 0000000..c2ad402 --- /dev/null +++ b/docs/soft-hash-channel-analysis.md @@ -0,0 +1,432 @@ +# Soft-hash covert channel analysis + +**Ticket:** #000018 +**Authored:** 2026-05-09 +**Audience:** fox + future blackops shifts + downstream Merkle-AGI v7+ authors +**Stance:** defensive only — detection / flagging / bound-derivation, never +generation or concealment. + +--- + +## 0. Executive summary + +We analyze whether an adversary controlling some fraction of training +data, gradient computation, or hyperparameter scheduling can drive a +v7-§9 plastic-trained model so that its **next snapshot's hard hash +`C(M_{t+1})`** lands on a prearranged target value or value-class, +exploiting the soft-hash anchor map `φ` as a side-channel. + +Result, under the assumptions stated in §5: + +> **Under threat models T1 (data poisoner) and T2 (gradient adversary), +> the channel is bounded by a SHA-256 partial-preimage hardness +> reduction (§4). Under T3 (hyperparameter adversary), the channel is +> bounded by the per-checkpoint mitigation budget M2 (re-randomized +> anchors); residual channel without M2 is at most O(log N · entropy +> of training-data choices) bits per checkpoint window.** + +Recommended mitigation: **M2 (per-checkpoint anchor nonce)**, costing +one additional commit field per snapshot. Falls back to **M1 (PRG-based +φ)** if a deployment surfaces residual structure. M3 (drop the anchor) +is the strict-construction fallback if both fail; loses some training +ergonomics. + +This document is doc-only per the ticket's hard constraint: it +recommends, does not implement. A follow-up ticket lands the chosen +mitigation. + +--- + +## 1. Threat model + +Three adversary capabilities, increasing in strength. + +### 1.1 T1 — Data poisoner + +Adversary controls a fraction `f ∈ (0, 1)` of training-data examples. +Training loop is honest; aggregator is honest; hyperparameters are +honest; `φ` is the published deterministic embedding. Poisoned +examples are valid in distribution (otherwise an outlier filter +catches them). + +**Goal:** poisoned examples drive `Q(Θ_{t+1})` to a region whose soft +hash `h_v(t+1)` projects to a SHA-256 bucket the adversary can predict. +Concretely: adversary tries to land `C(M_{t+1})` in a target class +`T ⊂ {0,1}^{256}` with `|T|/2^{256} = ε` (so the 'class' could be +"first byte 0xAA" → ε = 2^-8, or "specific 256-bit value" → ε = 2^-256). + +### 1.2 T2 — Gradient adversary + +T1 + adversary controls a fraction `g` of gradient computations +(e.g., compromised data-parallel worker that returns dishonest local +gradients to the central aggregator). Aggregator is still honest. + +Adversary can bias the gradient signal in arbitrary directions — +not bounded to legitimate-data-distribution reach. Tighter than T1. + +### 1.3 T3 — Hyperparameter adversary + +T2 + adversary controls some learning-rate / scheduler / batch-order +decisions. Adversary can synchronize gradient updates with anchor +geometry to maximize alignment. Strictly stronger than T2. + +### 1.4 What's NOT in scope + +- Direct attack on `C(M_t)` itself (preimage-attack on SHA-256). Out + of scope by A3 in v7 §3 (collision-resistant hash assumption). +- Side-channel on training infrastructure (Spectre, RowHammer, + compromised CPU). Different threat model. +- Adversary controlling the published `φ` seed retroactively. Once + φ's seed is public, it's frozen; this ticket assumes seed is + honestly drawn at v7 boot. +- Compromised aggregator that lies about the snapshot bytes. + Different threat model — that's a checkpoint integrity attack, + not a covert channel. + +--- + +## 2. v7 § 9 plastic training, recap + +The relevant slice of the v7 spec: + +``` +Θ — model parameters (continuous, ~10^9 floats) +Q(Θ) — public quantization of Θ (SQD §3, integer kernel) +C(M) — SHA-256(canonical-encoding of Q(Θ) ∥ metadata) + ↑ THE HARD HASH — committed bytes +h_v(Θ) — soft hash, dim_h-vector function of Θ + ↑ THE SOFT HASH — used during training only +φ(C(M)) — anchor map: SHA-256 digest → dim_h vector + ↑ FIXED DETERMINISTIC, published seed +``` + +Plastic training loss at step t: + +``` +L_total(Θ_t) = L_task(Θ_t) + α · ‖h_v(Θ_t) - φ(C(M_{t-1}))‖² + ↑ L2 anchor pull +``` + +The anchor term pulls the model's soft hash toward the embedding of +the *previous* checkpoint's hard hash. At each checkpoint boundary t → +t+1, we compute Q(Θ_{t+1}) → C(M_{t+1}) and that becomes the next +anchor. + +The dual-hash separation theorem (T6, v7 §3): C(M) is determined +solely by Q(Θ) and metadata; soft-hash bytes never enter the +SHA-256 preimage. **First-order conclusion:** training cannot +directly affect committed bytes. + +The covert-channel concern: training does affect Q(Θ) (that's what +training does). The question is whether the L2 anchor term lets the +adversary *steer* Q(Θ) toward a target SHA-256 bucket. + +--- + +## 3. Channel capacity definition + +Define the **per-checkpoint covert channel** as the mutual information + +``` +I_t = I(adversary inputs at training step ≤ t ; C(M_{t+1})) + conditional on the public training history H_{≤t} +``` + +The conditioning on `H_{≤t}` (public training history: published +losses, public hyperparameters, public seeds) removes the +"adversary trivially knows what they did" channel. We measure the +bits the adversary can steer ABOVE the public information's +predictive power. + +A non-adversarial bound: under random training, `C(M_{t+1})` is +near-uniform on `{0,1}^{256}` conditional on `H_{≤t}` if `Q(Θ_{t+1})` +is sufficiently varied. Adversarial bound is the question. + +### 3.1 Capacity bound under random `φ` + +If `φ` is modeled as a random oracle independent of the parameter +geometry, then `h_v - φ(C(M_{t-1}))` is a uniform random direction at +each checkpoint, and the L2 pull provides no parameter-aligned +gradient signal. Under this idealization, the covert channel +reduces to: + +``` +I_t ≤ I(adversary inputs ; Q(Θ_{t+1})) + conditional on H_{≤t} +``` + +…which is the un-amplified channel — adversary is no better off +through the soft-hash side-channel than they would be attacking +Q(Θ) directly. + +### 3.2 Capacity bound under structured `φ` + +If `φ` has algebraic structure relative to parameter geometry (the +v7 reference `embed_hard_to_vec` is a fixed-seed linear projection +of the SHA-256 digest), the bound depends on whether gradient +updates that minimize the L2 anchor term are *parameter-aligned* to +shifts in `Q(Θ)` that move SHA-256 buckets predictably. + +The reduction in §4 shows this dependency reduces to a SHA-256 +partial-preimage problem. + +--- + +## 4. Reduction to SHA-256 partial-preimage + +### 4.1 Reduction sketch + +Suppose adversary `A` exists that, given black-box training oracle +access under T1/T2, can drive `C(M_{t+1})` into a target class `T` of +density ε with non-negligible probability `p`. + +Construct algorithm `B` that solves SHA-256 partial-preimage: + +``` +B(target T): + 1. Initialize a fresh v7 plastic-training run with random seed. + 2. Run A as a sub-routine, supplying T as the target class. + 3. After A signals success on checkpoint t+1, output Q(Θ_{t+1}). + 4. Q(Θ_{t+1}) is then a partial preimage of T under SHA-256. +``` + +`B`'s success probability is `p` minus the probability that `A` +succeeds via legitimate-Q-randomization (i.e., `B` would have +solved the problem by random Q-search anyway). Under the standard +random-oracle model for SHA-256, the random-Q baseline is `ε`. So: + +``` +P[B solves partial-preimage] ≥ p - ε +``` + +If `p > ε + δ` for any non-negligible `δ`, then `B` is a non-trivial +SHA-256 partial-preimage attacker — contradicting A3 (collision- +resistance / partial-preimage hardness). + +**Conclusion:** under the SHA-256 random-oracle assumption (A3), +no T1/T2 adversary can succeed with probability non-negligibly +above ε. The channel exists but is bounded at the random baseline. + +### 4.2 Where the reduction is tight, where it's loose + +**Tight:** when `φ` is independent of parameter geometry (M1 or M2 +applied), the reduction is essentially exact. T1/T2 are bounded to +the random baseline. + +**Loose:** when `φ` has structure relative to parameter geometry +(reference v7 `embed_hard_to_vec` with fixed-seed linear projection), +the gradient feedback through the anchor MAY favor parameter +directions correlated with target SHA-256 buckets. We don't know +whether this correlation is large enough to break the reduction +without explicit structural analysis of `embed_hard_to_vec`. + +This is the residual gap §6 addresses. + +### 4.3 T3 caveat + +T3 (hyperparameter adversary) is NOT bounded by the §4.1 reduction +directly. The reduction assumes A's queries to the training oracle +follow the published schedule. T3 violates that. Under T3: + +- Adversary can synchronize gradient bursts with anchor geometry + via learning-rate schedule manipulation. +- Per-checkpoint channel capacity grows with the adversary's + schedule-control budget. + +T3 needs M2 (re-randomized per-checkpoint anchor) or M3 (drop +anchor). M1 alone may not suffice. + +--- + +## 5. Assumptions on `φ` + +Three concrete `φ` choices, with channel-residual under each: + +### 5.1 `φ_linear` — v7 reference (linear projection, fixed seed) + +``` +φ(digest) = W · digest_as_uint32_vector (W frozen at boot) +``` + +- **Pros:** fast, deterministic, simple. +- **Channel residual:** unclear without explicit structural analysis + of W relative to the parameter Hessian. If W has any direction + aligned with a low-eigenvalue direction of the loss Hessian, the + L2 pull provides cheap parameter movement that aligns with + SHA-256 buckets — exploitable under T2 with empirical effort. +- **Verdict:** the reduction in §4 is loose under `φ_linear`. + +### 5.2 `φ_PRG` — non-linear PRG-based map (mitigation M1) + +``` +φ(digest) = PRG(seed=published, output_len=dim_h * 4 bytes) + then bytes → floats in [-1, 1] +``` + +PRG output is computationally indistinguishable from random under +the PRG security assumption. Gradient feedback through the anchor +is uncorrelated with parameter-space updates that would shift hash +bytes predictably. + +- **Channel residual:** zero modulo PRG security (one-way function + + no algebraic structure for the parameter Hessian to exploit). +- **Verdict:** §4 reduction is tight under `φ_PRG`. + +### 5.3 `φ_nonce` — re-randomized anchor (mitigation M2) + +``` +At each checkpoint t, sample fresh random nonce n_t (committed +in the snapshot manifest). Compute anchor as + φ(C(M_{t-1}) ∥ n_t) +where φ is φ_linear or φ_PRG. Adversary can't precompute against +the next anchor because n_t is unknown until commitment. +``` + +- **Channel residual:** even under `φ_linear` base, T1/T2 adversaries + can't pre-plan attacks because the gradient direction they want + to steer toward is randomized between checkpoints. +- **T3 residual:** still bounded — T3 adversary controls timing, + not the nonce. Per-checkpoint window is `O(steps) × per-step + channel`; the nonce reset zeroes the cumulative steerage. +- **Verdict:** lightweight; only adds 32 bytes to the manifest per + checkpoint. Closes the residual channel for T1/T2 entirely; + bounds T3 by a per-window budget. + +### 5.4 `φ` dropped — anchor term removed (mitigation M3) + +`L_total(Θ) = L_task(Θ) + (no anchor term)`. + +- **Channel residual:** zero by construction (no soft-hash gradient + signal at all). +- **Loss:** plastic training loses some ergonomics (stable + representation between checkpoints relied on the anchor pull). + Quantified loss: depends on deployment; estimated in v7 §9.7 + as 5-15% slower convergence on representation-stability metrics. +- **Verdict:** strict-construction fallback. Recommend only if M1 + + M2 prove insufficient empirically. + +--- + +## 6. Mitigations with residual analysis + +| Mitigation | Cost | T1 residual | T2 residual | T3 residual | Closes the channel? | +|------------|------|-------------|-------------|-------------|---------------------| +| **None** (v7 reference) | 0 | bounded by §4 reduction (loose under φ_linear) | bounded by §4 reduction (loose under φ_linear) | unbounded structurally | No | +| **M1 — φ_PRG** | fast PRG eval per checkpoint | tight bound at random baseline ε | tight bound at random baseline ε | unbounded (T3 controls timing) | T1+T2 yes; T3 no | +| **M2 — per-checkpoint nonce** | 32 B in manifest per checkpoint | ε | ε | bounded by per-window budget | T1+T2 yes; T3 bounded | +| **M1 + M2** (combined) | both costs | ε | ε | bounded by per-window budget | T1+T2 yes; T3 bounded | +| **M3 — drop anchor** | 5-15% training-ergonomics regression | 0 | 0 | 0 | Yes, by construction | +| **M4 — gradient-budget cap** | per-step gradient-norm clip | bounds adversary's per-step budget | bounds adversary's per-step budget | bounds T3 per-step | Soft bound; doesn't close | + +--- + +## 7. Recommendation + +**Land M2 first** (re-randomized per-checkpoint anchor): + +- 32 bytes additional manifest field, committed in the snapshot. +- Closes the T1 + T2 channel under any `φ` choice. +- Bounds the T3 channel by per-checkpoint budget (the only + attack window is between nonce-publish and next-checkpoint). +- No training-ergonomics regression — the nonce is purely + per-checkpoint randomization, anchor pull still lands. + +**Add M1 (φ_PRG) as Phase 2** if M2-only deployment surfaces +structural concerns (e.g., empirical evidence that T3 attacks +within a window achieve > ε success rate). + +**M3 (drop anchor)** stays in reserve as the strict-construction +fallback. v7 §9 spec amendment proposed: + +``` +v7 §9.10 (proposed): The anchor map φ takes both the prior +checkpoint's hard hash AND a per-checkpoint nonce committed in +the manifest. The nonce is fresh entropy at each checkpoint, +sealed before training resumes. This closes the soft-hash covert +channel under threat models T1 and T2, and bounds T3 by the +per-checkpoint window. +``` + +--- + +## 8. Empirical validation (out of scope; follow-up) + +A small experiment to sanity-check the analysis: + +1. Run v7 §9 plastic training with a deliberately-poisoned dataset + targeting "first byte of `C(M)` equals 0xAA" — that's `ε = 2^-8`. +2. With M2 disabled (`φ_linear`, no nonce): measure success rate + over N checkpoints. Hypothesis: indistinguishable from `ε` if + §4 reduction holds under `φ_linear`; structurally elevated if + `φ_linear` has Hessian-aligned structure. +3. With M2 enabled (`φ_linear`, per-checkpoint nonce): measure + again. Hypothesis: indistinguishable from `ε` regardless of + `φ_linear`'s structure. +4. Same experiment under `φ_PRG`: should match `ε` baseline with + or without M2. + +If empirical results diverge from analysis predictions, the +analysis is wrong somewhere — likely in §5 about `φ_linear`'s +structural alignment with the parameter Hessian. + +This experiment is a follow-up ticket; not in scope here. + +--- + +## 9. Open questions + +The reduction in §4 leaves three loose threads: + +1. **Hessian alignment under `φ_linear`.** Is the v7 reference + `embed_hard_to_vec` matrix W aligned with low-eigenvalue + directions of the typical training-loss Hessian? If yes, T2 + adversaries with full gradient control can amplify steerage + beyond the §4 random-oracle baseline. **Test:** compute the + spectrum of `W^T W` and compare to typical Hessian eigenvalue + distributions on representative checkpoints. + +2. **PRG modeling for `φ_PRG`.** The §4 reduction treats `φ_PRG` + as a random oracle. In practice it's a deterministic PRG with + public seed. The reduction holds modulo PRG security; we + should pick a PRG with cryptographic strength matching SHA-256 + (e.g., AES-256-CTR with public IV; or HMAC-SHA-512 expansion). + **Decision:** use HMAC-SHA-512(seed, digest ∥ counter) for + dim_h floats. Keeps the dependency surface (already on SHA-256) + tight. + +3. **T3 per-window budget formal bound.** §6 lists "bounded by + per-window budget" without giving the bound. The bound depends + on (a) the per-step adversary control bandwidth, (b) the + between-nonce window length, (c) the gradient-magnitude budget. + **Open:** explicit bound calculation, deferred to either a + v7 §9.10 spec amendment or a follow-up ticket. + +--- + +## 10. References + +- v7 § 3 (T6 dual-hash separation theorem) +- v7 § 9 (plastic training; the source of the L2-anchor term) +- v7 § 9.9 (the "backdoor via soft hash" first-order dismissal we're + superseding) +- SQD § 3 (hard-hash construction for Q(Θ)) +- SQD § 5 (integer-kernel discipline; bigint accumulator) +- ticket #000018 (this document is the closure deliverable) + +--- + +## 11. Status + +**Open questions:** §9.1 (Hessian alignment under `φ_linear`), +§9.2 (PRG cryptographic strength for `φ_PRG`), §9.3 (T3 per-window +explicit bound). + +**Closed:** §4 reduction shows T1/T2 are bounded by SHA-256 partial- +preimage hardness under any `φ` independent of parameter geometry. +M2 (per-checkpoint nonce) closes residual channels at minimal cost. + +**Recommendation:** v7 spec amendment §9.10 lands the M2 nonce. +Implementation ticket follows. + +**Doc closure criterion** (per #000018 §7): reduction stated, mitigation +recommended, residual-bound table present (§6). Done. diff --git a/docs/tickets/ticket-000018-soft-hash-covert-channel.md b/docs/tickets/ticket-000018-soft-hash-covert-channel.md index 87928ef..2753cf4 100644 --- a/docs/tickets/ticket-000018-soft-hash-covert-channel.md +++ b/docs/tickets/ticket-000018-soft-hash-covert-channel.md @@ -1,6 +1,6 @@ # Ticket #000018 — Adversarial soft-hash covert channel analysis -**Status:** open · awaiting go/no-go +**Status:** closed · landed 2026-05-09 (analysis doc at `docs/soft-hash-channel-analysis.md`) **Opened:** 2026-05-07 **Scope:** Formal threat model + falsifier set for whether training gradients can route information from training data through soft hashes