Build log ·

@shieldfive/crypto 1.0.0: a frozen format and a stable API, not an audit

The library reached 1.0.0 after a ten-week release candidate. No wire format or function changed from rc.6; the release makes the API stable under semantic versioning and keeps the v1 format frozen. Audit status is unchanged: no paid third-party audit, and 1.0.0 does not claim one.

Written by Cho García

@shieldfive/crypto reached 1.0.0 on 2026-09-21. The release changes no wire format and no function relative to 1.0.0-rc.6; what it changes is the promise. From 1.0.0 the public API follows semantic versioning, and the v1 wire format stays frozen: a file written today decrypts on every future v1 release.

This post records what the release covers, what it does not claim, and what happened during the ten weeks of release candidates.

What 1.0.0 means, and what it does not

Two separate axes, tracked separately:

  • API stability. Signatures in the public surface do not change in a minor or patch release. One exception is stated in the changelog: @shieldfive/crypto/vault is experimental and may change in a minor release until it is declared stable. It was three days old at release and backs a feature still being evaluated.
  • Audit status. Unchanged. The library has not had a paid third-party cryptographic audit, and 1.0.0 does not claim one. It has had an internal review with findings fixed, and it publishes positive and adversarial test vectors so an independent implementation can check it without trusting us.

A version number is not a security property. 1.0.0 says the interface will hold still; whether the construction is sound is a question the vectors, the specs and, eventually, an external audit answer.

The wire format

Four suites, one self-describing header:

IDNameStatus
0x01aes-256-gcm-v1Read path for files written before 0x04
0x02xchacha20-poly1305-v1libsodium, 192-bit nonces
0x03pq-hybrid-xchacha-mlkem1024-v1Default: XChaCha20-Poly1305 + ML-KEM-1024
0x04aes-256-gcm-v2Current AES-GCM write path, 8-byte nonce prefix

Suite 0x03 stays confidential against an adversary who breaks either the classical or the post-quantum primitive; only a break of both compromises the file. Chunk keys and nonce prefixes mix in a per-file file_id, so truncation, reordering and cross-file splicing fail at the AEAD rather than at an application-layer check.

The release-candidate period

rc.1 shipped on 2026-07-14 and froze the v1 format and the TypeScript API. The rule for the following ten weeks was that a release candidate could add API or fix behaviour but not remove or change an existing signature. Six candidates followed that rule:

  • rc.1: published adversarial test vectors (tests/vectors/adversarial-vectors.json), tampered v1 files that a conforming decoder must reject, across all four suites, each with a valid control. Bad magic, suite confusion, reserved flags, header and header-MAC tampering, chunk ciphertext and tag tampering, reordering, truncation and cross-file splice. The positive known-answer vectors show a decoder works; these show it fails closed.
  • rc.2 and rc.3: the inbound module behind document requests. An account-less sender encrypts to a recipient's published X25519 and ML-KEM-1024 keys, so only the recipient can decrypt. rc.3 adds an Ed25519 signature over the firm's key bundle so a sender can detect a swapped firm key. It does not defeat a server that also serves malicious verifying code; the changelog says so.
  • rc.4: two fixes from an external whitebox review, both low severity. The blob API now accepts and reports a signed file's trailing signature block instead of rejecting it. Suites 0x01, 0x02 and 0x04 now reject a non-zero reserved pad, as 0x03 already did; the encoder always wrote zeros, so no existing file is affected.
  • rc.5 and rc.6: @shieldfive/crypto/vault, one reviewed implementation of the formats the web app stores (key-chain wraps, name envelopes, the upload proof) plus the key wraps for AI-assistant access grants. Before this, a second client (the MCP server) would have needed its own hand-written copy of each format, and two hand-written copies are how clients silently diverge. Its vectors come from an independent implementation (@noble/hashes Argon2id plus raw Web Crypto), not from the library itself.

One change in the pre-release line was breaking, and it landed before the freeze. In beta.4 the optional sender-attribution signature was redefined to commit to every chunk's full ciphertext instead of only the AEAD tags, because a holder of the content key can forge a tag. The signing feature was unused in production, so no signed files existed under the old transcript.

Where to check it

The external audit remains the open item. When it happens, it will start from the frozen format, the specs and the two vector sets rather than from a moving target, which is the practical reason to freeze first.