first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
# `signature`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`signature.ts` is the Ed25519 signature-verification gate run by the
loader before any packed bytes are handed to `Unpacker.unpack`. It
exists because the upstream audit (`enterprise/packer-browser.md` §3.5)
explicitly required archive-signature verification — without it, an
attacker who can substitute the bytes at the packed URL can ship
arbitrary content to the page. By verifying the manifest signature
under a caller-supplied public key first, the loader gives consumers a
hard cryptographic anchor before any decompression / decryption runs.
## Features
- Thin wrapper over `@suaveplan/crypto`'s `ed25519.verify` — single
authoritative implementation, no parallel curve code.
- Boundary defence: a signature whose length is not 64 bytes throws
`PackerBrowserError(ARCHIVE_SIGNATURE_INVALID)` rather than silently
returning `false`. This catches wire corruption / header mis-parse
before it pollutes downstream logs.
- Returns a boolean for cryptographic verify-or-not — never throws on
a non-verifying-but-well-formed signature (that's a domain decision
the caller makes; the loader throws on the bool side).
- Optional `messageResolver` for i18n on the boundary throw.
## Usage: Basic
```ts
import { verifyArchiveSignature } from "@suaveplan/packer-browser";
const ok = verifyArchiveSignature(manifestBytes, signature, publicKey);
if (!ok) throw new Error("signature did not verify");
```
## Usage: Advanced
```ts
import { ed25519 } from "@suaveplan/crypto";
import { verifyArchiveSignature } from "@suaveplan/packer-browser";
const { publicKey, secretKey } = ed25519.generateKeypair();
const message = new TextEncoder().encode("manifest:v1");
const sig = ed25519.sign(secretKey, message);
const valid = verifyArchiveSignature(message, sig, publicKey);
// valid === true
const tampered = new Uint8Array(sig);
tampered[0] ^= 1;
const invalid = verifyArchiveSignature(message, tampered, publicKey);
// invalid === false
```
## API Reference
- `verifyArchiveSignature(manifestBytes, signature, publicKey, opts?)`
— returns `boolean`.
- Throws `PackerBrowserError(ARCHIVE_SIGNATURE_INVALID)` when
`signature.length !== 64`.
- Forwards `publicKey` to `ed25519.verify`, which throws if the key
length is not 32 bytes.
- `VerifyArchiveSignatureOptions`:
- `messageResolver?: MessageResolver` — resolver for the boundary
error message.
## Implementation Notes
Signature material is conventionally hex-encoded in the
`X-Archive-Signature` response header; the loader decodes it before
calling this function. The public key is **not** sourced from the wire
— the loader requires the caller to pass `verifyKey` on
`PackedResourceLoadOptions`, ensuring an out-of-band trust anchor.
Cluster B's encrypted-archive keyring adds the per-key public-key
selection step on top of this primitive; Cluster A simply takes one
public key per `load(...)` call.