Skip to content

Commit 703fa00

Browse files
claudehyperpolymath
authored andcommitted
feat(ffi): wire real BLAKE3 combiner — build+link libochrance.so, drop dead stubs
Closes the cryptographic-integrity gap (CLAUDE.md rule 4): the production verification path now computes real BLAKE3 through libochrance.so, verified end to end at runtime. Build/link - build.zig: name the shared library "ochrance" so it emits libochrance.so. It was "ochrance-shared" -> libochrance-shared.so, which the Idris %foreign loader ("C:blake3_hash,libochrance") could never resolve. This was the actual reason the FFI was never loadable at runtime. Runtime tests (the contract Idris2's FFI loader depends on) - ffi/zig/test/link_test.c + run_link_test.sh: dlopen libochrance.so and call each exported symbol against published KAT vectors; assert the Merkle combiner is order-sensitive with no XOR self-cancellation (real BLAKE3, not the stub). Gated in zig-ffi.yml CI. - tests/ffi/CryptoFFITest.idr: Idris executable driving blake3 / sha256 / sha3_256 / rootHashBytesIO across %foreign; checks the production Merkle root equals BLAKE3(leafA ++ leafB) and differs from the XOR spec root. Idris cleanup - FFI/Crypto.idr: remove dead zero/false stubs blake3Stub, sha256Stub, sha3_256Stub, ed25519VerifyStub (superseded by the real FFI functions). - Rename hashPairStub -> xorCombiner across Crypto/Merkle/PropertyTests: it is the totality-friendly pure spec instance of the combiner-generic theorems, not a crypto fallback (a pure BLAKE3 combiner is impossible — BLAKE3 only crosses the FFI boundary in IO). Security rests on the explicit, isolated CollisionResistant assumption (pigeonhole-false; MerkleAssumption). Docs/state: CLAUDE.md, EXPLAINME.adoc, VALENCE_SHELL_BRIDGE.adoc, STATE.a2ml, debt.a2ml updated to reflect the closed gap. Verified locally (idris2 0.8.0 + zig 0.11.0): core 29/29 (--total, axiom-free), property 47/47, A2ML + integration green, zig build test, C link test 10/10, Idris->FFI runtime test 6/6. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011z2t8zAxfcCNLJzU7YdpBQ
1 parent a9da088 commit 703fa00

17 files changed

Lines changed: 455 additions & 84 deletions

File tree

.github/workflows/zig-ffi.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,6 @@ jobs:
3636
- name: Test (hash known-answer vectors + Ed25519 round-trip)
3737
working-directory: ffi/zig
3838
run: zig build test
39+
- name: Runtime link test (C ABI via dlopen — the contract Idris2's FFI uses)
40+
working-directory: ffi/zig
41+
run: bash test/run_link_test.sh

.machine_readable/6a2/STATE.a2ml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ 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 + live root-verify soundness; 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; crypto FFI wired: libochrance.so built+linked, runtime KAT + Idris→FFI tests)"
1111

1212
[project-context]
1313
name = "Ochránce"
1414
purpose = """Neurosymbolic filesystem verification framework in Idris2 dependent types: a pluggable VerifiedSubsystem interface, A2ML attestation/audit markup, a verified Merkle tree with a combiner-generic soundness theorem, and ECHIDNA integration."""
15-
completion-percentage = 66
15+
completion-percentage = 68
1616

1717
[position]
1818
phase = "implementation" # design | implementation | testing | maintenance | archived
@@ -52,7 +52,6 @@ milestones = [
5252

5353
[blockers-and-issues]
5454
issues = [
55-
"No cryptographic-integrity claim yet: Idris stub fallbacks (hashPairStub / blake3Stub / ed25519VerifyStub in FFI/Crypto.idr) remain in the verification flow; libochrance.so not yet built + linked in",
5655
"Repair (L3) idempotence is property-tested only, not yet proven",
5756
"ECHIDNA FFI is stubbed (echidnaProve returns 'FFI not yet implemented')",
5857
"Production round-trip (parse∘lex∘serialize) is honestly bounded by primitive String ops — see docs/PROOFS.adoc",
@@ -66,10 +65,10 @@ actions = [
6665
"Stage 3.2 DONE — whole-manifest repair ⇒ verify (repairThenVerify, module RepairVerify). repairRefsPure folds repairBlockPure; proof = verifyRefsComplete ∘ repairRefsConsistent; GoodRefs precondition + hashRefl hypothesis, both named",
6766
"Live-verifier Merkle redesign SOUNDNESS DONE (merkleRootVerifyHashSound) — root-comparing verifier is sound at the Hash level, modulo named CollisionResistant + DecodeInjective",
6867
"Live-verifier PLUMBING DONE (module VerifyRoot) — padToLength/nextPow2Exp/layoutLeaves pad to power-of-2 leaf Vect; verifyByRoot/verifyByRootHash build tree + compare roots; fsBlockHashes/verifySnapshotRoot = runtime path vs FSSnapshot.rootHash. Executable, total; accept-on-match soundness = merkleRootVerifyHashSound",
69-
"Stage 4 DONE (honest isolation) — CollisionResistant cannot be discharged (pigeonhole-false for compressing combiner); isolated as the irreducible crypto assumption + proved it has teeth (constNotCollisionResistant, module MerkleAssumption). Optional follow-on: wire real Zig/FFI combiner + declare CR as its explicit assumption",
68+
"Stage 4 DONE (honest isolation) — CollisionResistant cannot be discharged (pigeonhole-false for compressing combiner); isolated as the irreducible crypto assumption + proved it has teeth (constNotCollisionResistant, module MerkleAssumption). Follow-on DONE: real Zig/BLAKE3 combiner wired via libochrance.so (production rootHashBytesIO/hashPairBlake3); CR remains its explicit, supplied-at-boundary assumption",
7069
"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)",
7170
"Stage 3.2 (next provable): whole-manifest repairPure ⇒ verifyRefsHelper accepts (distinct-in-range ref-name precondition + isolated Hash-reflexivity hypothesis)",
72-
"Remove Idris-side crypto stubs and build + link libochrance.so into the flow (unblocks the crypto-integrity claim)",
71+
"DONE — crypto FFI wired: build.zig emits libochrance.so (correct soname); production Idris path (blake3/sha256/sha3_256/hashPairBlake3/rootHashBytesIO/ed25519Verify) calls it via %foreign; dead stubs (blake3Stub/sha256Stub/sha3_256Stub/ed25519VerifyStub) removed; pure XOR combiner renamed xorCombiner (spec instance). Verified: zig build test, C dlopen link test (KAT, CI-gated in zig-ffi.yml), Idris→FFI runtime test tests/ffi/CryptoFFITest.idr (prod Merkle root = BLAKE3 fold ≠ XOR). Residual: CollisionResistant assumption (isolated)",
7372
"Sync .machine_readable scaffold to rsr-template (ai/, compliance/, configs/, policies/, scripts/, 0.1-AI-MANIFEST, ENSAID_CONFIG, root README, root-allow) — pending template access",
7473
]
7574

@@ -90,4 +89,4 @@ decisions = [
9089
[maintenance-status]
9190
last-run-utc = "2026-06-05T14:08:00Z"
9291
last-result = "pass" # unknown | pass | warn | fail
93-
notes = "CI green on main; core builds fully total. A2ML parser 16/16, property 47/47, e2e 22/22; integration suite and Zig FFI build+test green."
92+
notes = "CI green on main; core builds fully total (idris2 0.8.0, 29/29 modules, --total). A2ML parser 16/16, property 47/47, e2e 22/22; Zig FFI build+test green; C dlopen link test 10/10; Idris→FFI runtime test 6/6 (real BLAKE3 via libochrance.so)."

.machine_readable/agent_instructions/debt.a2ml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,6 @@ last-updated = "2026-06-05"
1515
# SHOULD — would fix next wave
1616
# ============================================================================
1717

18-
[[debt.should]]
19-
component = "ochrance-core/Ochrance/FFI/Crypto.idr"
20-
issue = "Idris stub fallbacks (hashPairStub / blake3Stub / ed25519VerifyStub) remain in the verification flow and libochrance.so is not yet built+linked — so no cryptographic-integrity claim can be made (CLAUDE.md rule 4)."
21-
effort = "hard"
22-
impact = "high"
23-
discovered = "2026-06-05"
24-
2518
[[debt.should]]
2619
component = "ochrance-core/Ochrance/Filesystem/Repair.idr"
2720
issue = "Repair idempotence is property-tested only, not proven (proof campaign Stage 3)."

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ idris2 --repl ochrance.ipkg
5151
1. **All functions must be total** - use `%default total` in every module
5252
2. **Structural recursion only** - no partial or assert_total
5353
3. **Idris2 0.8.0+** required
54-
4. **BLAKE3/SHA-256 via FFI** - real crypto is implemented in the Zig FFI (`ffi/zig/src/main.zig`: BLAKE3/SHA-256/SHA3-256/Ed25519 via `std.crypto`, with known-answer-vector tests). Still pending before any cryptographic-integrity claim: remove the Idris-side stub fallbacks (`hashPairStub`/`blake3Stub`/`ed25519VerifyStub` in `FFI/Crypto.idr`) and build+link `libochrance.so` into the verification flow.
54+
4. **BLAKE3/SHA-256 via FFI** - real crypto is implemented in the Zig FFI (`ffi/zig/src/main.zig`: BLAKE3/SHA-256/SHA3-256/Ed25519 via `std.crypto`, with known-answer-vector tests) and wired into the Idris production path (`blake3`/`sha256`/`sha3_256`/`hashPairBlake3`/`rootHashBytesIO`/`ed25519Verify`) via `%foreign "C:...,libochrance"`. `build.zig` emits `libochrance.so` with the correct soname; the runtime C-ABI contract is CI-gated by a dlopen link test (`ffi/zig/test/link_test.c`, KAT vectors), and `tests/ffi/CryptoFFITest.idr` confirms the production Merkle root is the real BLAKE3 fold (≠ the XOR root). The dead stub fallbacks (`blake3Stub`/`sha256Stub`/`sha3_256Stub`/`ed25519VerifyStub`) are removed; the pure XOR combiner is renamed `xorCombiner` — the totality-friendly *spec instance* of the combiner-generic theorems, not a fallback. The one irreducible crypto assumption is `CollisionResistant` (pigeonhole-false, isolated in `Filesystem.MerkleAssumption`).
5555
5. **Linear types for repair** - repair operations consume old state (Quantity 1)
5656

5757
## Error Taxonomy

EXPLAINME.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ Also integrates with ECHIDNA for neural proof synthesis — FFI calls to libechi
6868

6969
== Known Limitations & TODOs
7070

71-
* **Hashes**: BLAKE3/SHA-256/SHA3-256 are now implemented in the Zig FFI (`ffi/zig/src/main.zig`, tested via `zig build test`). Remaining: remove the Idris-side XOR/zero stub fallbacks (`hashPairStub`/`blake3Stub`) and build+link `libochrance.so` into the verification flow before claiming cryptographic integrity.
71+
* **Hashes**: BLAKE3/SHA-256/SHA3-256 are implemented in the Zig FFI (`ffi/zig/src/main.zig`, tested via `zig build test`) and wired into the Idris production path through `%foreign`. `build.zig` emits `libochrance.so`; a C `dlopen` link test (`ffi/zig/test/link_test.c`, KAT vectors, CI-gated in `zig-ffi.yml`) and an Idris→FFI runtime test (`tests/ffi/CryptoFFITest.idr`) confirm real BLAKE3 end to end — the production Merkle root is the BLAKE3 fold, not the XOR root. The dead zero-hash stubs are removed; the pure XOR combiner is renamed `xorCombiner` (the spec instance of the combiner-generic proofs). Residual assumption: `CollisionResistant` (isolated, pigeonhole-false).
7272
* **Attestation**: Ed25519 verification is implemented (Zig FFI + `validateManifestIO`). Remaining: a trust-root / key-management story and end-to-end integration before claiming full attestation.
7373
* **Linear repair**: Idris2 linear types framework incomplete for full use-after-repair prevention (ongoing research).
7474

Justfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ build-abi:
3232
build-ffi:
3333
cd ffi/zig && zig build
3434

35+
# Build libochrance.so + run the C-ABI runtime link test (KAT vectors via dlopen).
36+
# This is the runtime contract Idris2's %foreign loader depends on.
37+
test-ffi-link:
38+
bash ffi/zig/test/run_link_test.sh
39+
3540
# Build all components
3641
build: build-core build-abi build-ffi
3742

docs/VALENCE_SHELL_BRIDGE.adoc

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -159,24 +159,33 @@ Ochránce is the receipt substrate, but its integrity guarantees are still
159159
under construction. The following caveats are load-bearing and must be carried
160160
into any future receipt before it is trusted.
161161

162-
. *No cryptographic filesystem-integrity claim while placeholder hashes remain
163-
reachable.* The Zig FFI now implements real primitives — BLAKE3, SHA-256,
162+
. *Cryptographic filesystem-integrity now rests on real hashing, modulo one
163+
named assumption.* The Zig FFI implements real primitives — BLAKE3, SHA-256,
164164
SHA3-256 and Ed25519 verification via Zig's audited `std.crypto`
165-
(`ffi/zig/src/main.zig`) — and the Idris bindings in
166-
`ochrance-core/Ochrance/FFI/Crypto.idr` (plus `Merkle.idr`'s `hashPairBlake3`)
167-
call them. **But the integrity story is not yet closed:** XOR/zero-hash stub
168-
fallbacks (`hashPairStub`, `blake3Stub`, …) still live in the Idris tree, the
169-
cryptographic path still depends on `libochrance.so` being built and linked
170-
into the verification flow, and the roadmap still lists the Merkle BLAKE3
171-
wiring as incomplete. Until the stub paths are removed and the FFI path is the
172-
only one, Merkle roots are *structural* commitments, not cryptographic ones.
173-
174-
. *No full-attestation claim until signature verification is integrated.*
175-
`ed25519Verify` and `validateManifestIO` exist and can check an Ed25519
176-
attestation, but an `ed25519VerifyStub` remains, the pure `validateManifest`
177-
path skips signatures entirely, there is no key-management / trust-root
178-
story, and the whole path again depends on `libochrance.so`. "Attested"
179-
mode is defined; it is not yet an end-to-end trustworthy attestation.
165+
(`ffi/zig/src/main.zig`) — and the Idris production path
166+
(`ochrance-core/Ochrance/FFI/Crypto.idr` + `Merkle.idr`'s `hashPairBlake3` /
167+
`rootHashBytesIO`) calls them through `%foreign "C:...,libochrance"`.
168+
`build.zig` emits `libochrance.so` with the correct soname; a C `dlopen` link
169+
test (`ffi/zig/test/link_test.c`, KAT vectors, CI-gated) and an Idris→FFI
170+
runtime test (`tests/ffi/CryptoFFITest.idr`) confirm the production Merkle root
171+
is the real BLAKE3 fold, not the XOR root. The dead zero-hash stubs are gone;
172+
the remaining pure XOR combiner is renamed `xorCombiner` — the totality-friendly
173+
*spec instance* of the combiner-generic soundness/binding theorems, never a
174+
production fallback. The one irreducible assumption is collision resistance,
175+
modelled as the explicit, isolated `CollisionResistant` hypothesis
176+
(pigeonhole-false, so never dischargeable — see `Filesystem.MerkleAssumption`),
177+
supplied at the verification boundary. So Merkle roots are cryptographic
178+
commitments *given* a collision-resistant BLAKE3 — the standard, clearly-stated
179+
trust root.
180+
181+
. *No full-attestation claim until key management is integrated.*
182+
`ed25519Verify` (real Zig `std.crypto`, linked via `libochrance.so`) and
183+
`validateManifestIO` can check an Ed25519 attestation, and the
184+
`ed25519VerifyStub` has been removed. What remains for "Attested" mode is a
185+
trust-root / key-management story (which keys are authorised, and how), plus
186+
the fact that the *pure* `validateManifest` path skips signatures by
187+
construction. The signature primitive is real and linked; end-to-end
188+
trustworthy attestation still needs the key-management layer.
180189

181190
. *No complete-linear-repair claim until that work is actually complete.*
182191
`Repair.idr` correctly types repair with linear quantities

ffi/zig/build.zig

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,14 @@ pub fn build(b: *std.Build) void {
2323
b.installArtifact(lib);
2424

2525
// Also create a shared library version (libochrance.so) — this is what the
26-
// Idris2 FFI loads at runtime.
26+
// Idris2 FFI loads at runtime. The name MUST be "ochrance" so the artifact is
27+
// libochrance.so: the Idris bindings declare `%foreign "C:blake3_hash,
28+
// libochrance"` (ochrance-core/Ochrance/FFI/Crypto.idr), so the runtime loader
29+
// resolves the soname libochrance.so. Any other shared-library name would never
30+
// be found. A static libochrance.a and a shared libochrance.so share the base
31+
// name without clashing (distinct extensions).
2732
const shared_lib = b.addSharedLibrary(.{
28-
.name = "ochrance-shared",
33+
.name = "ochrance",
2934
.root_source_file = .{ .cwd_relative = "src/main.zig" },
3035
.target = target,
3136
.optimize = optimize,

0 commit comments

Comments
 (0)