Skip to content

Commit 1f1597e

Browse files
claudehyperpolymath
authored andcommitted
proof(verify): live-verifier Merkle redesign — root-comparison soundness at the Hash level
Part B of the auto-run (Part A = Stage 3.2). Proves the SOUNDNESS that a redesigned root-comparing verifier rests on: two block-hash vectors that decode and build trees with equal Merkle roots are equal — carrying rootVerifySound (HashBytes level) up to the live A2ML Hash level across the decoder bridge. In Ochrance.Filesystem.VerifyMerkle: - decAll — structural per-element decode (reduces definitionally, unlike Functor map). - DecodeInjective dec — named hypothesis: the decoder is injective (true for well-formed hashes; bottoms out in the hex/String wall, Stage 2.3). The live-verification counterpart of CollisionResistant. - mapDecodeInjective — lifts injectivity over a vector. - merkleRootVerifyHashSound — the main theorem; rootVerifySound ∘ mapDecodeInjective. Stated for an arbitrary decoder dec (as rootVerifySound is stated for an arbitrary Combiner h), instantiated at hashToBytes by a future verifier. Two named boundaries: CollisionResistant h (1.4) and DecodeInjective dec (2.3). Remaining is plumbing only, not a proof: pad an arbitrary-length block list to a power-of-two leaf Vect and switch the runtime verify path over. Verified: idris2 0.8.0, --build (--total), 27/27 modules, no warnings, axiom-free. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011z2t8zAxfcCNLJzU7YdpBQ
1 parent 7e47f66 commit 1f1597e

3 files changed

Lines changed: 90 additions & 14 deletions

File tree

.machine_readable/6a2/STATE.a2ml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ project = "ochrance"
77
version = "0.1.0"
88
last-updated = "2026-06-18"
99
status = "active"
10-
session = "machine-readable checkpoint — 2026-06-18 (Stage 1 complete; Stage 2 core + 2.4 verify↔Merkle wiring; Stage 3.1 pure repair + 3.2 repair⇒verify)"
10+
session = "machine-readable checkpoint — 2026-06-18 (Stage 1 complete; Stage 2 core + 2.4 verify↔Merkle wiring + live root-verify soundness; Stage 3.1 pure repair + 3.2 repair⇒verify)"
1111

1212
[project-context]
1313
name = "Ochránce"
@@ -33,6 +33,7 @@ theorems = [
3333
"repairBlock{Sets,Preserves,NumBlocks,Idempotent} (Stage 3.1) — pure repair primitive correctness over repairBlockPure: installs hash at index, preserves others + count, idempotent (wall-free Nat == via eqNatReflTrue/neqNatFalse)",
3434
"repairThenVerify (Stage 3.2) — whole-manifest repair ⇒ verify: verifyRefsHelper (repairRefsPure s refs) refs = Right (). Lemma A verifyRefsComplete (completeness, converse of 2.2) ∘ Lemma B repairRefsConsistent (install+no-clobber via repairBlockSets/repairRefsPurePreserves). Honest hyps: GoodRefs (distinct in-range names) + hashRefl (primitive Hash == reflexivity)",
3535
"rootFaithful / rootVerifySound / inclusionVerifySound / hashToBytes (Stage 2.4) — verify↔Merkle wiring, 3 modes generic→granular: root-equivalence (faithful fingerprint via merkleBindingTree), inclusion-proof (merkleCorrect per-leaf), live Hash↔HashBytes bridge for snapshot-root",
36+
"merkleRootVerifyHashSound (+ mapDecodeInjective) (Stage 2.4 live) — live root-verification soundness at the Hash level: block-hash vectors that decode (decAll dec) + build equal-rooted trees are equal; carries rootVerifySound up across the decoder bridge. The theorem a redesigned root-comparing verifier rests on. Named boundaries: CollisionResistant h (1.4) + DecodeInjective dec (hex wall 2.3); parametric in dec, used at hashToBytes",
3637
"roundtripManifest (+ algoRT/modeRT/refsRT/mpolRT codecs) — decodeManifest (encodeManifest m) = Just m",
3738
"SatisfiesMinimum / attestedSatisfiesLax — progressive-assurance threshold witness",
3839
"ABI Handle / createHandle — So (ptr /= 0) non-null invariant",
@@ -61,7 +62,8 @@ actions = [
6162
"Stage 1 COMPLETE — 1.1 buildGetLeaf, 1.2 rootFoldLaw, 1.3 IO↔pure bridge, 1.4 merkleBinding/CollisionResistant — all machine-checked (idris2 0.8.0, --total)",
6263
"Stage 2 provable core COMPLETE — 2.1 validateManifestSound, 2.2 verifyRefsSound, 2.3 parsePairsRoundtrip; all machine-checked. DEFERRED (architectural decision): wire verify to merkleCorrect — the verify path builds no Merkle tree today",
6364
"Stage 3.1 DONE — pure repair primitive correctness (repairBlockPure factored out; sets/preserves/numBlocks/idempotent machine-checked)",
64-
"Stage 3.2 DONE — whole-manifest repair ⇒ verify (repairThenVerify, module RepairVerify). repairRefsPure folds repairBlockPure; proof = verifyRefsComplete ∘ repairRefsConsistent; GoodRefs precondition + hashRefl hypothesis, both named. NEXT: architectural live-verifier Merkle redesign (verifyRefsHelper builds a tree via hashToBytes + rootVerifySound; bounded by hex walls + power-of-2 layout)",
65+
"Stage 3.2 DONE — whole-manifest repair ⇒ verify (repairThenVerify, module RepairVerify). repairRefsPure folds repairBlockPure; proof = verifyRefsComplete ∘ repairRefsConsistent; GoodRefs precondition + hashRefl hypothesis, both named",
66+
"Live-verifier Merkle redesign SOUNDNESS DONE (merkleRootVerifyHashSound) — root-comparing verifier is sound at the Hash level, modulo named CollisionResistant + DecodeInjective. REMAINING is plumbing only (not a proof): pad arbitrary-length block list to power-of-2 leaf Vect + switch the runtime verify path over. NEXT options: that plumbing, or Stage 4 (discharge CollisionResistant against the real combiner), or harvest the VerifiedSubsystem law",
6567
"Stage 2.4 DONE — verify↔Merkle proof-level wiring (rootFaithful/rootVerifySound): the root is a faithful fingerprint of the blocks (binding). REMAINING: connect to the live Hash-based verifyRefsHelper via hex Hash<->HashBytes conversion (2.3-bounded) + power-of-two layout (verify-path change, not a proof)",
6668
"Stage 3.2 (next provable): whole-manifest repairPure ⇒ verifyRefsHelper accepts (distinct-in-range ref-name precondition + isolated Hash-reflexivity hypothesis)",
6769
"Remove Idris-side crypto stubs and build + link libochrance.so into the flow (unblocks the crypto-integrity claim)",

docs/PROOFS.adoc

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,13 @@ The core is *clean*: all 19 `ochrance-core` modules carry `%default total` (the
121121
inclusion-proof verification (`merkleCorrect` as a per-leaf guarantee); and the live
122122
`Hash`↔`HashBytes` decode bridge for snapshot-root verification. Binding (1.4) and
123123
inclusion soundness (1.1) carried into the verify use-case.
124+
| `merkleRootVerifyHashSound` (+ `mapDecodeInjective`) (Stage 2.4 live)
125+
| live root-verification soundness at the A2ML `Hash` level: two block-hash vectors
126+
that decode (`decAll dec`) and build trees with equal roots are equal — carries
127+
`rootVerifySound` up across the decoder bridge. The theorem a redesigned
128+
root-comparing verifier rests on. Named boundaries: `CollisionResistant h` (1.4),
129+
`DecodeInjective dec` (hex wall, 2.3); stated for arbitrary `dec`, used at
130+
`hashToBytes`.
124131
| `repairThenVerify` (+ `verifyRefsComplete`, `repairRefsConsistent`) (Stage 3.2)
125132
| whole-manifest repair ⇒ verify: `verifyRefsHelper (repairRefsPure s refs) refs =
126133
Right ()`. Lemma A `verifyRefsComplete` (completeness — exact converse of 2.2) ∘
@@ -215,10 +222,15 @@ re-exposes `merkleCorrect` (1.1) as a per-leaf verification guarantee; (3) *live
215222
bridge* — `hashToBytes` decodes A2ML `Hash` → Merkle `HashBytes` for snapshot-root
216223
verification, composing with `rootVerifySound`.
217224
+
218-
STILL OPEN (architectural): fully *replacing* the live `Hash`-based
219-
`verifyRefsHelper` with a tree build needs `hashToBytes`' correctness (Stage 2.3,
220-
bounded by the `unpack`/`pack` + `Bits8` walls) and a power-of-two leaf layout - a
221-
verify-path change, not a proof.
225+
SOUNDNESS PROVEN (`merkleRootVerifyHashSound`): replacing the live `Hash`-based
226+
`verifyRefsHelper` with a root comparison is now *sound* at the `Hash` level - two
227+
block-hash vectors that decode and yield equal Merkle roots are equal - carrying
228+
`rootVerifySound` up across the decoder bridge (`mapDecodeInjective`). Two named
229+
boundaries: `CollisionResistant h` (1.4) and `DecodeInjective dec` (the hex wall,
230+
2.3); stated for an arbitrary `dec`, instantiated at `hashToBytes`.
231+
+
232+
STILL OPEN (plumbing, not a proof): padding an arbitrary-length block list to a
233+
power-of-two leaf `Vect` and switching the runtime verify path over - engineering.
222234

223235
. *[DONE — 2.3]* Hex codec structural round-trip. The full
224236
`hexStringToBytes (bytesToHex bs) = Just bs` crosses two primitive walls —

ochrance-core/Ochrance/Filesystem/VerifyMerkle.idr

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,23 @@
1111
||| trust a root, and it carries Stage 1.4's binding guarantee into the verification
1212
||| use-case.
1313
|||
14-
||| Three verification modes are surfaced, generic -> granular:
14+
||| Verification modes are surfaced, generic -> granular:
1515
||| * `rootFaithful` / `rootVerifySound` - root-equivalence (one root check stands
1616
||| in for every block hash; backed by `merkleBindingTree`, Stage 1.4);
1717
||| * `inclusionVerifySound` - inclusion-proof verification (per-leaf `(leaf, proof)`
1818
||| reconstructs the root; backed by `merkleCorrect`, Stage 1.1);
19-
||| * `hashToBytes` - the live `Hash` <-> `HashBytes` bridge for snapshot-root
20-
||| verification, composing with `rootVerifySound`.
19+
||| * `hashToBytes` - the live `Hash` <-> `HashBytes` bridge;
20+
||| * `merkleRootVerifyHashSound` - LIVE root-verification soundness at the A2ML
21+
||| `Hash` level: two block-hash vectors that decode and yield equal roots are
22+
||| equal. This is the theorem a redesigned root-comparing `verifyRefsHelper` rests
23+
||| on; it carries `rootVerifySound` up across the decoder bridge.
2124
|||
22-
||| NOTE (remaining): fully replacing the *live* A2ML-`Hash`-based verifier
23-
||| (`verifyRefsHelper`, Stage 2.2) with a tree build needs the hex conversion
24-
||| correctness (Stage 2.3, bounded by the `unpack`/`pack` + `Bits8` walls) and a
25-
||| power-of-two leaf layout - a verify-path change, documented in docs/PROOFS.adoc,
26-
||| not faked here.
25+
||| NOTE (remaining): the *soundness* of replacing the live `Hash`-based verifier with
26+
||| a tree build is now proven (`merkleRootVerifyHashSound`), modulo two named
27+
||| boundaries - `CollisionResistant h` (1.4) and `DecodeInjective dec` (the hex wall,
28+
||| 2.3). What is left is purely *plumbing*: padding an arbitrary-length block list to
29+
||| a power-of-two leaf `Vect` and swapping the verify path over - engineering, not a
30+
||| proof, and tracked in docs/PROOFS.adoc.
2731
module Ochrance.Filesystem.VerifyMerkle
2832

2933
import Data.Vect
@@ -96,3 +100,61 @@ inclusionVerifySound t i leaf prf gl gp = merkleCorrect t i leaf prf gl gp
96100
public export
97101
hashToBytes : Hash -> Maybe HashBytes
98102
hashToBytes h = hexStringToVect 32 h.value
103+
104+
--------------------------------------------------------------------------------
105+
-- Mode 2 (live): root-verification soundness at the A2ML `Hash` level
106+
--------------------------------------------------------------------------------
107+
--
108+
-- These are stated for an arbitrary decoder `dec : Hash -> Maybe HashBytes` (exactly
109+
-- as `rootVerifySound` is stated for an arbitrary `h : Combiner`); the intended
110+
-- instance is `dec := hashToBytes`, supplied at the call site by a redesigned
111+
-- verifier. Keeping `dec` an explicit parameter also avoids it being auto-bound as
112+
-- an unbound implicit in the signatures.
113+
114+
||| Decode every hash in a vector (structural, so it reduces definitionally - unlike
115+
||| the `Functor` `map`, which the per-element proofs below need to compute through).
116+
public export
117+
decAll : (Hash -> Maybe HashBytes) -> Vect k Hash -> Vect k (Maybe HashBytes)
118+
decAll dec [] = []
119+
decAll dec (x :: xs) = dec x :: decAll dec xs
120+
121+
||| A decoder is INJECTIVE - distinct hashes give distinct bytes. True for well-formed
122+
||| hashes, but bottoms out in the primitive hex/`String` wall (Stage 2.3), so it is
123+
||| taken as a named hypothesis: the live-verification counterpart of
124+
||| `CollisionResistant` (named, never faked).
125+
public export
126+
DecodeInjective : (Hash -> Maybe HashBytes) -> Type
127+
DecodeInjective dec = (a, b : Hash) -> dec a = dec b -> a = b
128+
129+
||| Lift decode-injectivity over a vector: two `Hash` vectors whose decodings agree
130+
||| are equal.
131+
export
132+
mapDecodeInjective : (dec : Hash -> Maybe HashBytes) -> DecodeInjective dec ->
133+
{k : Nat} -> (xs, ys : Vect k Hash) -> decAll dec xs = decAll dec ys -> xs = ys
134+
mapDecodeInjective dec inj [] [] _ = Refl
135+
mapDecodeInjective dec inj (x :: xs) (y :: ys) eq =
136+
rewrite inj x y (cong Data.Vect.head eq) in
137+
cong (\zs => y :: zs) (mapDecodeInjective dec inj xs ys (cong Data.Vect.tail eq))
138+
139+
||| LIVE root-verification soundness (Mode 2, full): two block-hash vectors that
140+
||| decode successfully and yield equal Merkle roots are EQUAL - carrying
141+
||| `rootVerifySound` (HashBytes level) up to the live A2ML `Hash` level across the
142+
||| decoder bridge (instantiate `dec := hashToBytes`). This is what a redesigned
143+
||| `verifyRefsHelper` (one root comparison instead of per-ref hash checks) would rest
144+
||| on. Honest hypotheses: `CollisionResistant h` (Stage 1.4) and `DecodeInjective dec`
145+
||| (the hex boundary, Stage 2.3). The `decAll dec xs = map Just xb` premises say "xs
146+
||| decodes to the leaf bytes xb"; the power-of-two layout is the leaf length index.
147+
export
148+
merkleRootVerifyHashSound :
149+
(h : Combiner) -> CollisionResistant h ->
150+
(dec : Hash -> Maybe HashBytes) -> DecodeInjective dec -> {n : Nat} ->
151+
(xs, ys : Vect (power 2 n) Hash) -> (xb, yb : Vect (power 2 n) HashBytes) ->
152+
decAll dec xs = map Just xb -> decAll dec ys = map Just yb ->
153+
rootHashWith h (buildMerkleTree {n} xb) = rootHashWith h (buildMerkleTree {n} yb) ->
154+
xs = ys
155+
merkleRootVerifyHashSound h cr dec inj xs ys xb yb dx dy roots =
156+
let bytesEq : (xb = yb)
157+
bytesEq = rootVerifySound h cr xb yb roots
158+
decsEq : (decAll dec xs = decAll dec ys)
159+
decsEq = trans dx (trans (cong (map Just) bytesEq) (sym dy))
160+
in mapDecodeInjective dec inj xs ys decsEq

0 commit comments

Comments
 (0)