feat(ffi): wire real BLAKE3 combiner — build+link libochrance.so, drop dead stubs#58
Merged
Merged
Conversation
…p 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
hyperpolymath
marked this pull request as ready for review
June 18, 2026 13:35
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
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 — not stubbed, not aspirational.The root cause was a real, silent bug:
build.zignamed the shared libraryochrance-shared→libochrance-shared.so, but the Idris bindings declare%foreign "C:blake3_hash,libochrance", so the runtime loader looks forlibochrance.so. That file was never produced — the FFI could never have loaded at runtime.Changes
Build/link
ffi/zig/build.zig: name the shared libraryochranceso it emitslibochrance.so(the soname the%foreignloader resolves). Staticlibochrance.aand sharedlibochrance.socoexist (distinct extensions).Runtime tests — the contract Idris2's FFI loader actually depends on
ffi/zig/test/link_test.c+run_link_test.sh:dlopenlibochrance.soand call each exported symbol against published KAT vectors (BLAKE3 / SHA-256 / SHA3-256), plus discriminators proving the Merkle combiner is order-sensitive with no XOR self-cancellation — i.e. real BLAKE3, not the XOR stub. Wired intozig-ffi.ymlCI (usescc, already present on the runner).tests/ffi/CryptoFFITest.idr: an Idris executable that drivesblake3/sha256/sha3_256/rootHashBytesIOacross%foreignand checks the production Merkle root equalsBLAKE3(leafA ++ leafB)and differs from the XOR spec root. (Local/maintainer recipejust test-ffi-idris; not CI-gated, since CI has no Idris2.)Idris cleanup
FFI/Crypto.idr: remove the dead zero/false stubsblake3Stub,sha256Stub,sha3_256Stub,ed25519VerifyStub(superseded by the real FFI functions; were referenced only in docs).hashPairStub→xorCombineracrossCrypto/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, isolatedCollisionResistantassumption (pigeonhole-false;MerkleAssumption).Docs/state:
CLAUDE.md,EXPLAINME.adoc,VALENCE_SHELL_BRIDGE.adoc,STATE.a2ml,debt.a2mlupdated to reflect the closed gap honestly (Merkle roots are cryptographic commitments given a collision-resistant BLAKE3 — the standard, clearly-stated trust root).Verification (run locally — idris2 0.8.0 built from source + zig 0.11.0)
idris2 --build ochrance.ipkg(--total)zig build test(Zig KAT + Ed25519 round-trip)dlopenlink testlibochrance.so)e2e_test.sh(grep guards incl. soname regression)What this does not claim
Collision resistance remains the single, irreducible, explicit assumption (
CollisionResistant, pigeonhole-false, isolated inMerkleAssumption) — supplied at the verification boundary, never proved. Full Ed25519 attestation still needs a key-management / trust-root layer (the signature primitive is real and linked; the purevalidateManifestpath skips signatures by construction).🤖 Generated with Claude Code
Generated by Claude Code