feat(retrieval-receipt): Ed25519 signed anchoring for receipt roots - #949
Merged
Conversation
Closes the non-repudiation gap the 2026-08-13 nightly run's ADR-304 explicitly left open: unsigned receipts detect tamper but don't prove which issuer vouched for a root. Adds per-query and batched Ed25519 signing (BatchAnchor, a second Merkle tree over B receipt roots signed once) layered on top of the existing MerkleReceipt/PerResultReceipt without changing them, reusing the ed25519-dalek 2.1 + rand 0.8 pattern already used elsewhere in the workspace. Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01JTJKrXDPMiLzk1QqgUZLkB
ruvnet
marked this pull request as ready for review
August 31, 2026 16:04
Owner
Author
|
Security hardening is published in a913466. Local gates: 23 tests passed, strict release clippy passed, formatting passed, three warmed benchmark runs accepted, 1,500 of 1,500 tamper trials rejected, and RuFlo diff risk scored 5 of 100 with no high or critical findings. Public plain language overview: https://gist.github.com/ruvnet/c2b0cb1e533ae1c83c905ccf91ff026d |
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.
How RuVector proves which key signed a retrieval result
The short version
RuVector retrieval receipts could already prove that a result had not
changed after it was issued. They could not prove that a particular
signing key approved that receipt.
This change adds Ed25519 signatures to receipt roots. One signature can
cover one receipt or a batch of receipts. At a batch size of 128, the
measured signing cost per query fell to 5.8 to 7.7 percent of the single
receipt cost. Every one of 1,500 injected signing and proof tamper trials
was rejected.
The practical takeaway is simple: use one signature per query when the
proof must exist immediately. Use a batch when throughput matters and a
short wait for the batch to close is acceptable.
What was fixed before merge
Signing only a raw 32 byte root was too ambiguous. A valid signature
could be copied into a context the signer never intended. The first API
also let callers check Merkle inclusion without first checking the batch
signature.
The merged contract signs a complete statement containing:
Protocol version
Receipt or batch purpose
SHA256 identifier of the public key
Deployment or index scope
Issuance time
Receipt or batch root
Verification uses the strict Ed25519 path. A successful signature check
returns a trusted root token. Batch inclusion accepts only that token, so
the compiler makes the required authentication step difficult to skip.
Empty batches and invalid proof indexes now return typed errors instead
of terminating the process. The canonical statement encoder uses a
fixed 140 byte buffer, so the stronger contract adds no heap allocation
to the signing or verification path.
Measured result
Command:
Environment: 12 logical CPUs, Rust 1.94.1, release profile, 128 warmup
sign and verify operations, then three independent runs.
Mean results:
Batch 1: 15,598 ns signing per query, 36,411 ns uncached verify, 170
bytes of portable evidence
Batch 8: 2,940 ns signing per query, 34,948 ns uncached verify, 266
bytes of portable evidence
Batch 32: 1,337 ns signing per query, 35,394 ns uncached verify, 330
bytes of portable evidence
Batch 128: 1,090 ns signing per query, 41,219 ns uncached verify, 394
bytes of portable evidence
The batch 128 signing cost averaged about 14 times lower than batch 1.
Uncached verification did not improve with batch size, which confirms
that the benchmark measured real signature amortization instead of
hiding verification work.
What this proves
It proves that the signed statement came from the private key matching a
supplied public key and that the statement fields were not modified.
It does not prove that the result was correct. A compromised signer can
sign false data. It also does not prove that a key belongs to a named
company or engine. That requires an external key registry, rotation
policy, revocation history, and durable audit record.
This distinction matters. A cryptographic primitive can authenticate a
key. Organizational identity is a governance system built around that
primitive.
Deployment decision
This remains experimental and is not connected to the default query
path. Production promotion needs two additional measurements:
Batch fill latency under the target query arrival rate
Key custody and rotation using the target HSM or KMS
The largest uncertainty is batch fill time. A batch of 128 is useful only
if the traffic rate closes it inside the receipt availability service
level. The fix path is a time bounded batcher that signs when either the
size limit or the time limit is reached.
Acceptance test
Run the command above. Promotion requires both acceptance sections to
print
ACCEPT, all 23 tests to pass, and every tamper count to equal itstrial count.
Design record:
docs/adr/ADR-340-signed-retrieval-receipt-anchoring.mdEvidence:
docs/research/nightly/2026-08-31-signed-retrieval-receipts/README.md