fee: insurance-recovery integration test (FIL-474) - #13
Conversation
8fb6f6e to
c28efa8
Compare
fbb1ee8 to
6b0b9c8
Compare
c28efa8 to
06155ac
Compare
19eb203 to
5435a62
Compare
06155ac to
3b7e86f
Compare
5435a62 to
1214a14
Compare
f2aad36 to
23c529a
Compare
ReviewChecked out the head and ran it: Mostly duplicates
|
| New | Existing on base |
|---|---|
TestInsuranceRecoveryRoundTrip |
TestRoundTripECDHES (same MinChunkSize, multi-chunk sizes) + TestEnvelopeWireConventions (already asserts the recipient kid) |
…WrongPrivateKeyFailsBeforeDecrypt |
TestDecryptWrongECDHKey — identical ErrIntegrity / NotErrorIs(ErrCorrupted) / Nil(r) assertions |
…CorruptedProtectedHeaderFailsDecode |
TestDecryptCorruptedProtectedHeader — same bytes.Index(blob, RawProtected) / ^= 0xFF technique |
Helpers are duplicated in-package too: samplePlaintext is byte-for-byte patternBytes (including its doc comment), encryptToEnvelope ≈ encrypt, streamChunkSize ≈ the local chunk consts. That's not inert — it's a second copy of the byte-flip mechanics to keep in lockstep if the envelope layout changes.
What is new and worth keeping: the fixed checked-in tenant keypair with the public key derived from the private scalar — the thing FIL-474 actually wants to show (recovery starts from the archived private key and nothing else).
Suggestion: keep the round trip as the recovery artifact, reusing patternBytes/encrypt/decryptAll rather than re-declaring near-clones; drop tests 2 and 3 (or reduce them to cases referencing the existing coverage). ~50 lines of signal instead of 186 that mostly restate #14.
One inaccurate comment
In TestInsuranceRecoveryRoundTrip:
With a single recipient the unwrap would still succeed if the kid were wrong or dropped…
Not true here: matchRecipient (fee/fee.go:508) requires an exact kid match with no single-recipient fallback and returns ErrNoMatchingRecipient on a wrong or absent kid — see TestDecryptKidMismatch. The next sentence contradicts it. The on-wire check is worth having as a direct wire-format assertion, not because Decrypt would let it slide.
Minor
bytes.Index(blob, RawProtected)could match a coincidental earlier occurrence; offsetting past the tag/array head would harden it (inherited from the existing test).- The file opens with a
// Package fee_test's …doc comment. No conflict today, but a package doc on an external test file invites a future duplicate — a plain// This file …reads better. - It's a
make testunit test, not anitest/one —recovery_test.gowould signal the tier better thanintegration_test.go.
Other notes
- Committed key material is clearly test-only and derived from ASCII bytes; stating the derived public key in the comment is a good touch.
fee.Decryptreturns a plainio.Reader, so not closingpris correct.10_000bytes at 4 KiB → 3 chunks with a partial final one, as claimed.- Confirm the retarget to
mainlands after fee: top-level package composing encrypt/decrypt over existing primitives (FIL-569) #14 merges.
Not blocking on correctness; I'd trim before merge.
Generated by Claude Code
There was a problem hiding this comment.
Pull request overview
Adds an integration test in fee_test to validate the “insurance recovery” workflow: recovering plaintext from a FEE envelope using only an archived tenant X25519 private key (no region KEK, no DB), while asserting the on-wire kid and key failure modes.
Changes:
- Add
TestInsuranceRecoveryRoundTripexercisingfee.Encrypt→ on-wire COSEkidassertion →fee.Decrypt. - Add negative coverage for wrong private key unwrap failure (must fail before STREAM decryption).
- Add negative coverage for corrupted protected header decoding (must fail as malformed, no plaintext reader).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Add fee/integration_test.go (package fee_test) proving the composed fee API recovers plaintext from a FEE envelope using only an archived tenant X25519 private key: fee.Encrypt seals a few-KB sample to an ECDH-ES tenant recipient, and fee.Decrypt recovers it with the tenant private key back to the exact original. Rides on the top-level fee package (FIL-569) rather than sequencing the primitives by hand; the one drop to a sub-package is the explicit on-wire kid assertion (cose.Decode), which the issue calls for before recovery. Covers the three acceptance criteria: the round trip; a wrong private key failing at unwrap (aeskw.ErrIntegrity) before any decryption is attempted, with no plaintext reader produced; and a corrupted protected header making fee.Decrypt return a wrapped cose.ErrMalformed rather than a reader over garbage. The tenant keypair is a fixed, non-secret test fixture checked in for determinism. Chunk size is aesstream.MinChunkSize (4 KiB), the smallest legal value, so the multi-chunk path runs without a large fixture. Stacked on FIL-569 (PR #14). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0199oDgYVzczErg9qbbqmL4B
23c529a to
9ff30b0
Compare
|
Cherry picked into #32 |
tl:dr: Just an integration test to confirm that we can in fact recover data using the tenant key if needed. This may be overkill? Curious what people think.
What
Adds
fee/integration_test.go(packagefee_test) — the v1 confidence artifact for the Hilt recovery path (FIL-474). It proves the composedfeeAPI (FIL-569) recovers plaintext from a FEE envelope using only an archived tenant X25519 private key — no region KEK, no Ingot DB.The round trip, in one test run:
fee.Encryptseals a few-KB sample to a single ECDH-ES tenant recipient (WithChunkSizeat the 4 KiB minimum, so the multi-chunk STREAM path runs).fee/coseand assert the recipientkidnames the tenant key — the explicit on-wire check the issue calls for before recovery.fee.Decryptrecovers with the archived tenant private key; assert the plaintext matches the original exactly.Acceptance criteria
TestInsuranceRecoveryRoundTrip.TestInsuranceRecoveryWrongPrivateKeyFailsBeforeDecrypt:fee.Decryptfails at the unwrap withaeskw.ErrIntegrity, returns no plaintext reader, and never reaches STREAM decryption (asserted via the absence of anyaesstreamerror).TestInsuranceRecoveryCorruptedProtectedHeaderFailsDecode: flipping a byte in the protected-header bytes makesfee.Decryptreturn a wrappedcose.ErrMalformed, rather than a reader over garbage.Stacked on FIL-569 (PR #14)
This targets
claude/fil-569-nqlw8i, notmain. Merge #14 first, then this retargets tomainautomatically. The earlier version of this PR inlined the envelope-assembly glue because no composed API existed; now that FIL-569 providesfee.Encrypt/fee.Decrypt, the test drives that public API instead (the only sub-package call left is the explicitcose.Decodekid assertion).Notes
fil-474-fee-tenant-recovery-test), checked in for determinism as the issue requires; the public key is derived from the private scalar.fee.NewECDHESRecipienttreats the kid.aesstream.MinChunkSize(4 KiB), not the 1 KiB the issue suggested —aesstreamrejects anything below 4 KiB. Same intent: small chunks so the multi-chunk path runs without a large fixture. The 10 KB sample spans 3 chunks with a partial final chunk.Testing
GOWORK=off go test -race ./fee/ -run TestInsuranceRecovery -count=1— all three green under the race detector (fee.Encryptis goroutine-backed). Fullfeetree,go build ./...,go vet ./fee/..., andgofmtall clean.Closes FIL-474.
🤖 Generated with Claude Code