Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
# `integrity/integrity` — BLAKE3 per-entry digest and bundle-level Merkle root
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`@suaveplan/packer/integrity` provides the content-addressable integrity layer
the rest of the package uses to detect any tampering or accidental corruption
of packed bundles. It computes a per-entry BLAKE3-256 digest and a
bundle-level Merkle root over the sorted entry digests; both are recorded in
the produced envelope (whole-file `PackedDataMetadata.integrity`, chunked
`Manifest.merkleRoot` plus per-entry `ManifestChunkEntry.entryDigest`) and
verified on read. Verification coexists with the existing ed25519 signing
pipeline — neither replaces the other — because BLAKE3 is fast enough to run
on every chunk without measurable overhead while ed25519 still anchors the
publisher's identity.
## Features
- BLAKE3-256 one-shot digest of arbitrary bytes via `@noble/hashes/blake3`.
- `merkleRootBlake3(digests)` over already-sorted entry digests.
- `computeWholeFileIntegrity(payload)` convenience producing the `{ entryDigest, merkleRoot }`
pair stored on `PackedDataMetadata.integrity`.
- Constant-time `bytesEqual` for use in verify paths.
- Strict input validation: every digest must be exactly 32 bytes; mismatches
raise `TypeError` (programmer error — not a packer error code).
## Usage: Basic Example
```ts
import {
entryDigestBlake3,
merkleRootBlake3,
computeWholeFileIntegrity,
} from "@suaveplan/packer/integrity";
const payload = new TextEncoder().encode("hello world");
const digest = entryDigestBlake3(payload);
console.log(digest.byteLength); // 32
const integrity = computeWholeFileIntegrity(payload);
console.log(integrity.entryDigest.byteLength); // 32
console.log(integrity.merkleRoot.byteLength); // 32
```
## Usage: Advanced Example
Build a Merkle root over a chunked bundle's entries, sorted by `chunkId`:
```ts
import {
entryDigestBlake3,
merkleRootBlake3,
bytesEqual,
} from "@suaveplan/packer/integrity";
const chunks = [/* wire bytes from packChunks(...) */];
const digests = chunks
.map((c) => ({ id: c.chunkId, d: entryDigestBlake3(c.bytes) }))
.sort((a, b) => a.id - b.id)
.map((x) => x.d);
const root = merkleRootBlake3(digests);
// Later, on read:
const recomputed = merkleRootBlake3(digests);
if (!bytesEqual(root, recomputed)) {
throw new Error("Merkle root drifted between pack and verify");
}
```
## API Reference
- `entryDigestBlake3(data: Uint8Array): Uint8Array` — BLAKE3-256 of `data`.
Returns a fresh 32-byte array.
- `merkleRootBlake3(digests: readonly Uint8Array[]): Uint8Array` — BLAKE3-256
of the byte concatenation of `digests` (caller supplies sort order).
Empty input maps to `blake3("")`. Each digest must be 32 bytes;
mismatches throw `TypeError`.
- `computeWholeFileIntegrity(payload): PackedDataIntegrity` — single-entry
convenience returning `{ entryDigest: blake3(payload), merkleRoot: blake3(blake3(payload)) }`.
- `bytesEqual(a, b): boolean` — constant-time byte-equality test on two
`Uint8Array`s.
- `BLAKE3_DIGEST_LEN: 32` — exported constant for callers that need the
digest length without recomputing.
- `PackedDataIntegrity` — `{ entryDigest, merkleRoot }` shape stored on
`PackedDataMetadata.integrity`.
## Implementation Notes
- BLAKE3 is sourced from `@noble/hashes/blake3`. The package adds
`@noble/hashes` as a direct dependency (catalog `crypto`) rather than going
through `@suaveplan/crypto-hash`, which does not yet surface BLAKE3 —
exposing it there is tracked separately and is out of scope for this change.
- The Merkle root scheme is intentionally flat (single-level hash of sorted
concatenated leaves) rather than a binary tree. The bundle-level root's job
is to detect any leaf substitution, which a flat root accomplishes; binary
Merkle proofs are not in the public surface and adding them later does not
invalidate the flat root.
- Domain separation: the whole-file Merkle root wraps the lone entry digest
in a second BLAKE3 call so that a verifier never confuses a leaf digest
with a root. The same applies to chunked manifests — `merkleRoot` is
always `blake3(concat(...))`, never the bare concatenation.
- `bytesEqual` is intentionally constant-time. Integrity verification is not
a cryptographic primitive (the signature does that), but the verify path
sits on the request hot path and constant-time comparisons keep the
semantics consistent with the rest of the package.