Initial Commit
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
# drive-plugin-chunks-inline-sig
|
||||
|
||||
## Why
|
||||
|
||||
DTP-25 (carved out of `drive-template-v1`) requires that
|
||||
`FileLocation.storageHints` entries with `kind: 'inline'` carry an
|
||||
Ed25519 signature over `fileId ‖ bytes`. Without the sig, an attacker
|
||||
who can write to drive storage (or who can MITM a peer-to-peer drive
|
||||
hand-off) could replace the inline body of a small file with arbitrary
|
||||
bytes; the read-side has no other mechanism to detect tamper because
|
||||
inline files do not flow through the chunk-by-chunk
|
||||
sign / verify pipeline.
|
||||
|
||||
The change ships the surface in `@suaveplan/drive-plugin-chunks` so
|
||||
the chunks plugin owns both flows for small files (chunked path and
|
||||
inline path). Backward compatibility ships in the same change: legacy
|
||||
inline hints (no `sig` field) read with a `drive:inline:legacy`
|
||||
warning and rewrite-with-sig on the next write.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add `INLINE_SIG_INVALID` to `CHUNKS_CODES` in
|
||||
`packages/drive/drive-plugin-chunks/src/error-codes.ts`.
|
||||
- Add `src/inline/` module: `inline-hint.ts` + `inline-hint.md` +
|
||||
`index.ts` exporting `InlineHint`, `LegacyInlineHint`,
|
||||
`AnyInlineHint`, `StorageHint`, `FileLocation`, `signInlineHint`,
|
||||
`verifyInlineHint`, `isLegacyInlineHint`,
|
||||
`upgradeLegacyInlineHint`, `inlineSigningInput`, `VerifyEd25519Fn`.
|
||||
- Wire the new surface into `src/index.ts`.
|
||||
- Add `tests/inline-file-sig.test.ts` covering the three required
|
||||
spec test names plus tamper, fileId-replay, key-mismatch,
|
||||
signing-input canonicalisation, and the catch-arm-via-injectable-
|
||||
verifier paths.
|
||||
- Update `error-codes.md` to mention the twelfth code.
|
||||
- The `verifyInlineHint` verifier is injectable (defaults to
|
||||
`@noble/curves` Ed25519) so the catch arm — wrapping a throwing
|
||||
native verifier — is testable without `istanbul ignore`
|
||||
annotations.
|
||||
|
||||
## Impact
|
||||
|
||||
- Public API addition — no existing consumers depend on inline-hint
|
||||
surface yet (drive-template / drive consumers will adopt this in a
|
||||
follow-up wiring change once they begin emitting `FileLocation`).
|
||||
- Wire format: legacy inline hints (`{ kind, bytes }`) remain readable
|
||||
and are rewritten with sig on next write, so on-disk drives are
|
||||
forward-compat.
|
||||
- Error registry grows from eleven to twelve codes.
|
||||
- 100% coverage gate maintained (74 tests, all passing); docs
|
||||
coverage 17/17.
|
||||
@@ -0,0 +1,57 @@
|
||||
# drive-plugin-chunks spec delta — DTP-25 (inline-sig)
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Inline hint signed over fileId ‖ bytes
|
||||
|
||||
`FileLocation.storageHints` entries with `kind: 'inline'` SHALL be
|
||||
`{ kind: 'inline'; bytes: Uint8Array; sig: Uint8Array }` where
|
||||
`sig = Ed25519-sign(driveSecretKey, fileId ‖ bytes)`. The drive
|
||||
SHALL verify the signature before returning `bytes`; failure SHALL
|
||||
throw `CHUNKS.INLINE_SIG_INVALID`.
|
||||
|
||||
The verifier SHALL be Ed25519 over the canonical concatenation
|
||||
exposed by `inlineSigningInput(fileId, bytes)`. A tampered signature,
|
||||
tampered bytes, replay onto a different `fileId`, or signature
|
||||
produced under a different key SHALL all reject with
|
||||
`CHUNKS.INLINE_SIG_INVALID`.
|
||||
|
||||
The `verifyInlineHint` function SHALL accept an optional
|
||||
`VerifyEd25519Fn` parameter so engine-substitution scenarios where a
|
||||
native verifier throws (rather than returning false) compose cleanly.
|
||||
When the injected verifier throws, the implementation SHALL wrap the
|
||||
exception as `CHUNKS.INLINE_SIG_INVALID` with the original error
|
||||
attached as `cause`.
|
||||
|
||||
**Verified by:** `tests/inline-file-sig.test.ts`::"inline file with
|
||||
valid sig reads successfully" AND "inline file with bad sig throws
|
||||
INLINE_SIG_INVALID" AND "sig bound to fileId — replay onto a
|
||||
different fileId rejects" AND "byte tamper rejects with
|
||||
INLINE_SIG_INVALID" AND "rejects under a different public key" AND
|
||||
"verify catch arm wraps a throwing verifier".
|
||||
|
||||
### Requirement: Legacy inline hints accepted with rewrite-on-write
|
||||
|
||||
Drives reading old-format inline entries (no `sig` field) SHALL
|
||||
accept them via the `LegacyInlineHint` shape and SHALL emit a
|
||||
`drive:inline:legacy` warning event. `isLegacyInlineHint(hint)` SHALL
|
||||
discriminate the two wire shapes (legacy vs signed). On the next
|
||||
write that touches such a hint, `upgradeLegacyInlineHint(legacy,
|
||||
fileId, driveSecretKey)` SHALL produce a signed `InlineHint` so the
|
||||
upgrade lands at-rest and subsequent reads no longer trigger the
|
||||
legacy warning.
|
||||
|
||||
**Verified by:** `tests/inline-file-sig.test.ts`::"old-format inline
|
||||
reads with warning AND rewrites with sig on next write" AND
|
||||
"isLegacyInlineHint distinguishes signed from legacy hints".
|
||||
|
||||
### Requirement: Canonical signing input exposed
|
||||
|
||||
`inlineSigningInput(fileId: Uint8Array, bytes: Uint8Array)` SHALL
|
||||
return `concat(fileId, bytes)` as a freshly-allocated `Uint8Array`.
|
||||
Any consumer that wants to re-derive the bytes that are signed (for
|
||||
auditing, alternate-keypath signing, or cross-checking against a
|
||||
hardware signer) SHALL use this function.
|
||||
|
||||
**Verified by:** `tests/inline-file-sig.test.ts`::"inlineSigningInput
|
||||
is the canonical fileId ‖ bytes concatenation".
|
||||
@@ -0,0 +1,38 @@
|
||||
# tasks — drive-plugin-chunks-inline-sig
|
||||
|
||||
## 1. Error registry
|
||||
|
||||
- [x] 1.1 Add `INLINE_SIG_INVALID` to `CHUNKS_CODES`.
|
||||
- [x] 1.2 Update `error-codes.md` (count + DTP-25 rationale).
|
||||
|
||||
## 2. Inline-hint module
|
||||
|
||||
- [x] 2.1 Create `src/inline/inline-hint.ts` with `InlineHint`,
|
||||
`LegacyInlineHint`, `AnyInlineHint`, `StorageHint`, `FileLocation`,
|
||||
`inlineSigningInput`, `signInlineHint`, `verifyInlineHint`
|
||||
(injectable verifier), `isLegacyInlineHint`,
|
||||
`upgradeLegacyInlineHint`.
|
||||
- [x] 2.2 Create `src/inline/inline-hint.md` (≥200-word doc with all
|
||||
six required sections).
|
||||
- [x] 2.3 Create `src/inline/index.ts` barrel.
|
||||
- [x] 2.4 Wire exports through `src/index.ts`.
|
||||
|
||||
## 3. Tests
|
||||
|
||||
- [x] 3.1 Required: `inline file with valid sig reads successfully`.
|
||||
- [x] 3.2 Required: `inline file with bad sig throws INLINE_SIG_INVALID`.
|
||||
- [x] 3.3 Required: `old-format inline reads with warning AND
|
||||
rewrites with sig on next write`.
|
||||
- [x] 3.4 fileId-replay rejection.
|
||||
- [x] 3.5 byte-tamper rejection.
|
||||
- [x] 3.6 wrong-public-key rejection.
|
||||
- [x] 3.7 signing-input canonicalisation property.
|
||||
- [x] 3.8 catch-arm via injectable throwing verifier.
|
||||
- [x] 3.9 `isLegacyInlineHint` discrimination including
|
||||
`sig: undefined` ambiguous case.
|
||||
|
||||
## 4. Gates
|
||||
|
||||
- [x] 4.1 `bunx turbo lint typecheck test --filter=@suaveplan/drive-plugin-chunks` green.
|
||||
- [x] 4.2 `bunx turbo test:coverage --filter=@suaveplan/drive-plugin-chunks` at 100/100/100/100.
|
||||
- [x] 4.3 `bun run docs:check` reports 17/17 files.
|
||||
Reference in New Issue
Block a user