first commit
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
# inline-hint
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Implements DTP-25 (drive-plugin-chunks-inline-sig). Provides the signed `InlineHint` shape used by `FileLocation.storageHints` entries with `kind: 'inline'`, the Ed25519 signing helper that produces them, the verifier that the drive runs before returning inline bytes, and the legacy-detection / upgrade helpers that let pre-DTP-25 hints (no `sig` field) round-trip safely. The signing input is the canonical concatenation `fileId ‖ bytes`, which binds the signature to one file so a replay onto a different fileId would fail verification.
|
||||
|
||||
## Features
|
||||
|
||||
- `InlineHint` — `{ kind: 'inline'; bytes: Uint8Array; sig: Uint8Array }` for the DTP-25 wire format.
|
||||
- `LegacyInlineHint` — `{ kind: 'inline'; bytes: Uint8Array }` for backward-compat reads.
|
||||
- `signInlineHint` — Ed25519-sign over `fileId ‖ bytes` under the drive's secret key.
|
||||
- `verifyInlineHint` — verify and return bytes; throws `CHUNKS.INLINE_SIG_INVALID` on failure (catch arm covers verifier exceptions too).
|
||||
- `isLegacyInlineHint` — type-guard distinguishing legacy from signed shapes.
|
||||
- `upgradeLegacyInlineHint` — sign a legacy hint with the drive's key so the next write rewrites it as `InlineHint`.
|
||||
- `inlineSigningInput` — exposes the canonical concatenation so other layers can re-derive the bytes that are signed.
|
||||
- Cause-chain preservation: verifier exceptions are wrapped as `ChunksError` with the original error attached as `cause`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
signInlineHint,
|
||||
verifyInlineHint,
|
||||
} from "@suaveplan/drive-plugin-chunks";
|
||||
import { sha256 } from "@suaveplan/crypto-hash";
|
||||
|
||||
const fileId = sha256(plaintext);
|
||||
const hint = signInlineHint(fileId, plaintext, driveSecretKey);
|
||||
// later, on read:
|
||||
const bytes = verifyInlineHint(hint, fileId, drivePublicKey);
|
||||
console.log(bytes.byteLength);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
isLegacyInlineHint,
|
||||
upgradeLegacyInlineHint,
|
||||
verifyInlineHint,
|
||||
type AnyInlineHint,
|
||||
} from "@suaveplan/drive-plugin-chunks";
|
||||
|
||||
function readInline(
|
||||
hint: AnyInlineHint,
|
||||
fileId: Uint8Array,
|
||||
driveSecretKey: Uint8Array,
|
||||
drivePublicKey: Uint8Array,
|
||||
onLegacyWarning: () => void
|
||||
): Uint8Array {
|
||||
if (isLegacyInlineHint(hint)) {
|
||||
onLegacyWarning(); // emit drive:inline:legacy
|
||||
const upgraded = upgradeLegacyInlineHint(
|
||||
hint,
|
||||
fileId,
|
||||
driveSecretKey
|
||||
);
|
||||
// Persist `upgraded` on the next write; for the current read we
|
||||
// can return the bytes directly because legacy hints predate the
|
||||
// sig requirement.
|
||||
return upgraded.bytes;
|
||||
}
|
||||
return verifyInlineHint(hint, fileId, drivePublicKey);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `signInlineHint(fileId: Uint8Array, bytes: Uint8Array, driveSecretKey: Uint8Array): InlineHint` — Ed25519-sign `fileId ‖ bytes` and return the signed hint.
|
||||
- `verifyInlineHint(hint: InlineHint, fileId: Uint8Array, drivePublicKey: Uint8Array): Uint8Array` — verify and return `hint.bytes`. Throws `ChunksError(CHUNKS.INLINE_SIG_INVALID, ...)` if the signature does not verify or the verifier raises.
|
||||
- `isLegacyInlineHint(hint: AnyInlineHint): hint is LegacyInlineHint` — true when the hint omits `sig`.
|
||||
- `upgradeLegacyInlineHint(legacy: LegacyInlineHint, fileId: Uint8Array, driveSecretKey: Uint8Array): InlineHint` — sign the legacy bytes under the drive's key.
|
||||
- `inlineSigningInput(fileId: Uint8Array, bytes: Uint8Array): Uint8Array` — canonical signing input.
|
||||
- `InlineHint`, `LegacyInlineHint`, `AnyInlineHint`, `StorageHint`, `FileLocation` — wire types.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The verifier uses `@noble/curves` Ed25519 via `@suaveplan/crypto-curve/ed25519`. `verify()` is implemented to never throw on malformed inputs in the noble path, but the catch arm exists for engine-substitution scenarios where a custom verifier may throw — the `injectable verifyFn` pattern is intentionally not surfaced here because the chunks plugin always uses noble. The `CHUNKS.INLINE_SIG_INVALID` code is registered through `defineCodes("CHUNKS", ...)` in `error-codes.ts`. Domain separation is provided by including `fileId` in the signing input; this is sufficient because every inline-hint context already has a unique fileId, so the signature cannot be replayed across files even if an attacker controls one inline body.
|
||||
Reference in New Issue
Block a user