Files
skills/docs/foundation/data/packer-browser/src/signature/signature.md
2026-05-29 08:20:56 +01:00

2.9 KiB

signature

(no submodules)

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

import { verifyArchiveSignature } from "@suaveplan/packer-browser";

const ok = verifyArchiveSignature(manifestBytes, signature, publicKey);
if (!ok) throw new Error("signature did not verify");

Usage: Advanced

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.