The package's scope narrowed to WebCrypto-standard compatibility: the ChaCha interfaces (Modern Algorithms proposal-stage, not spec), the aead-internal-nonce kind, and bytes.constant-time-equal were cut — extensions to WebCrypto don't benefit from conforming to WebCrypto's shape, and belong in a sibling package if anywhere.
Re-add trigger: the Modern Algorithms proposal settles
When the W3C "Modern Algorithms in the Web Cryptography API" proposal merges into the spec, one-shot ChaCha20-Poly1305 becomes standard surface and re-enters under the ordinary additive path. Design facts already settled by the first implementation (recover from history, pre-cut):
- the alg-less
oct JWK moved once to follow the proposal's registered "C20P" — re-check the registration at re-add;
- the 12-byte-nonce rule renders
invalid-nonce;
- jco served it by feature detection (Node ≥ 24.18 experimental); browser coverage was declared per-engine in targets.toml;
- it was the in-guest provider's recommended AEAD (class A+B vs AES-GCM's C) — the provider README's classification narrative should regain that row.
XChaCha20-Poly1305 is NOT part of this trigger: it is absent from the proposal and from every platform WebCrypto, and does not return to this package.
Preserved design: the aead-internal-nonce WIT
Recorded here per the scope decision, in case a future spec development (or a sibling extension package) wants it. Motivations it carried: nonce-misuse resistance (the caller-nonce aead kind's standing hazard), and SP 800-38D approved-mode sealing (the FIPS-profile trajectory — which is correspondingly weakened in the direction notes by this cut). Key semantics: implementation-generated nonces, iv ‖ ciphertext ‖ tag wire format, a per-key nonce budget surfacing error.key-exhausted (an error case that leaves the variant with the cut and would need restoring).
The aead-internal-nonce kind (from wit/webcrypto.wit)
/// The `aead-internal-nonce` primitive kind: single-message authenticated
/// encryption with the nonce generated within the implementation (SP
/// 800-38D §8's "IV generated within the cryptographic module").
///
/// This is the misuse-resistant counterpart of `aead`: the nonce never
/// appears in the API, so nonce reuse — the most common catastrophic AEAD
/// failure — is unrepresentable. Each `seal` draws a fresh nonce that is
/// unique per key and carries it in the sealed output, so every sealed
/// message is self-contained.
///
/// Keys are a distinct resource from `aead.aead-key`, minted by separate
/// per-algorithm interfaces: the nonce discipline is part of the key
/// capability, so one key can never mix implementation-chosen and
/// caller-chosen nonces. Prefer this kind unless interop requires an
/// externally specified nonce layout; among its algorithms, prefer
/// XChaCha20-Poly1305 for high-volume keys (see the minting interfaces'
/// nonce-budget notes).
///
/// Like `aead`, this is a single-message primitive: `open` buffers and
/// verifies before it releases any plaintext. The streaming,
/// extractability, and getter contracts in `README.md` apply.
interface aead-internal-nonce {
use types.{error};
use wrapping.{wrap-input};
/// Mint-time policy for an `internal-nonce-key`. Grants nothing by
/// default; see `README.md`, "Key-options contract". The vocabulary is
/// seal/open only: this kind has no WebCrypto mapping, so no platform
/// usage vocabulary reaches beyond its own operations.
resource internal-nonce-key-options {
constructor();
/// Whether the minted key may `seal`. Disabled by default.
can-seal: func(allowed: bool);
/// Whether the minted key may `open`. Disabled by default.
can-open: func(allowed: bool);
/// Whether the minted key's material may be exported. Disabled by
/// default.
extractable: func(allowed: bool);
}
/// An internal-nonce AEAD key: an unforgeable capability, bound to one
/// algorithm at creation.
resource internal-nonce-key {
/// Encrypt and authenticate `plaintext` under a fresh
/// implementation-generated nonce with the associated data `aad`.
///
/// The returned stream is a self-contained sealed message:
/// everything `open` needs besides the key and `aad` travels
/// in-band. The key's minting interface documents the wire format.
/// Drain the returned stream concurrently with feeding `plaintext`
/// (see `README.md`, "Streaming contract").
///
/// Fails with `error.key-exhausted` once the implementation can no
/// longer guarantee nonce uniqueness for this key.
seal: async func(aad: list<u8>, plaintext: stream<u8>) -> result<stream<u8>, error>;
/// Decrypt and verify a sealed message, as produced by `seal` under
/// the same algorithm, with the associated data `aad`.
///
/// Security:
/// - `ok(stream)` resolves only after the sealed stream is fully
/// drained and the tag verified: it *is* the authentication
/// statement, and unverified plaintext is never observable.
/// - Any failure — a bad tag, wrong associated data, or input too
/// short to carry the wire format — reports
/// `error.authentication-failed` with no detail.
open: async func(aad: list<u8>, sealed: stream<u8>) -> result<stream<u8>, error>;
/// The registry name of the algorithm family this key is bound to,
/// e.g. `"AES-GCM"` (spelled as for `aead-key.algorithm-name`).
algorithm-name: func() -> string;
/// The key length in bits, e.g. `256`.
algorithm-length: func() -> u32;
/// The number of further `seal` invocations this key's nonce budget
/// (see `README.md`, "Terminology") permits, or `none` when the
/// implementation enforces no budget. A hint for key-rotation
/// scheduling: monotonically non-increasing, and `some(0)` means
/// the next `seal` fails `error.key-exhausted`. Implementations
/// MAY decrement faster than one per seal (a policy or accounting
/// choice), so it is not an exact invocation count.
///
/// There are deliberately no wire-layout getters here (nonce or tag
/// size): the sealed message's layout is the minting interface's
/// contract, and a consumer parsing it already knows that
/// interface's documented constants.
seals-remaining: func() -> option<u64>;
/// Whether the key material may be exported.
extractable: func() -> bool;
/// Whether this key permits `seal`. A refused operation fails
/// `error.not-permitted`.
can-seal: func() -> bool;
/// Whether this key permits `open`. See `can-seal`.
can-open: func() -> bool;
/// The raw key material. Fails with `error.not-extractable` unless
/// the key was created with `extractable` true. The nonce budget
/// does not travel with the material; see the minting interfaces'
/// import docs.
export-key-raw: async func() -> result<list<u8>, error>;
/// The key as an `oct` JWK, behind the same extractability gate
/// as `export-key-raw`; algorithms with no registered JWK form
/// fail `error.unsupported`, as on `aead-key.export-key-jwk`. The
/// nonce budget does not travel with the material.
export-key-jwk: async func() -> result<string, error>;
/// This key's raw material as a `wrap-input`, for wrapping
/// under another key (see the `wrapping` interface). Behind the
/// same extractability gate as `export-key-raw`; the material
/// itself never reaches the caller. As with the exports, the
/// nonce budget does not travel with the material.
to-wrap-input-raw: async func() -> result<wrap-input, error>;
/// The JWK serialization as a `wrap-input`, behind the same
/// gate; algorithms with no registered JWK form fail
/// `error.unsupported`, as on `export-key-jwk`.
to-wrap-input-jwk: async func() -> result<wrap-input, error>;
}
}
aes-gcm-internal-nonce (from wit/aes.wit)
/// AES-GCM internal-nonce key minting: `aead-internal-nonce` keys using the
/// SP 800-38D §8.2.2 RBG-based IV construction (96 random bits per seal).
///
/// Sealed messages are `iv ‖ ciphertext ‖ tag` (12-byte IV, 16-byte tag).
///
/// Security:
/// - A key's nonce budget (see `README.md`, "Terminology") is 2^32 `seal`
/// invocations (§8.2.2's repeat-probability bound). Implementations
/// SHOULD enforce it with `error.key-exhausted`; a FIPS 140-3 profile
/// MUST. For workloads that could approach the bound, prefer
/// `xchacha20-poly1305-internal-nonce`.
///
/// This interface is deliberately separate from `aes-gcm` (caller-nonce
/// minting) so a security profile can serve one nonce discipline without
/// the other at composition time — e.g. a FIPS 140-3 approved-mode
/// provider exports this interface but not `aes-gcm`.
interface aes-gcm-internal-nonce {
use types.{error};
use aead-internal-nonce.{internal-nonce-key, internal-nonce-key-options};
use aes.{aes-variant};
use wrapping.{unwrap-input};
/// Import raw key material as an internal-nonce AES-GCM key of the
/// declared variant (see `aes-gcm.import-key-raw` for the
/// variant/`raw`-length contract, which applies identically here).
///
/// Security:
/// - The nonce-uniqueness guarantee is per key *as managed by this
/// implementation*: importing the same material into multiple
/// implementations divides the nonce budget among them blindly.
/// Prefer `generate-key` where interop does not require import.
///
/// An implementation MAY reject imported material per its security
/// policy (for example, a profile that cannot establish uniqueness for
/// externally sourced material) with `error.invalid-key` or
/// `error.unsupported`.
import-key-raw: async func(%variant: aes-variant, raw: list<u8>, options: internal-nonce-key-options) -> result<internal-nonce-key, error>;
/// Import key material from an `oct` JWK, subject to
/// `import-key-raw`'s contract (including the policy-based-rejection
/// allowance) and the package-wide JWK contract (see
/// `mac-key.export-key-jwk`).
import-key-jwk: async func(%variant: aes-variant, jwk: string, options: internal-nonce-key-options) -> result<internal-nonce-key, error>;
/// Generate a fresh random internal-nonce AES-GCM key of the declared
/// variant. Fails with `error.unsupported` if this implementation does
/// not serve the variant.
generate-key: async func(%variant: aes-variant, options: internal-nonce-key-options) -> result<internal-nonce-key, error>;
/// Mint a key from unwrapped key material read as raw bytes, subject
/// to `import-key-raw`'s contract — including the shared-material
/// caveat and policy-rejection allowance. `input` is consumed; see
/// `aes-gcm.unwrap-key-raw` for the options model.
unwrap-key-raw: async func(%variant: aes-variant, input: unwrap-input, options: internal-nonce-key-options) -> result<internal-nonce-key, error>;
/// Mint a key from unwrapped key material read as an `oct` JWK,
/// subject to `import-key-jwk`'s contract. `input` is consumed.
unwrap-key-jwk: async func(%variant: aes-variant, input: unwrap-input, options: internal-nonce-key-options) -> result<internal-nonce-key, error>;
}
The package's scope narrowed to WebCrypto-standard compatibility: the ChaCha interfaces (Modern Algorithms proposal-stage, not spec), the
aead-internal-noncekind, andbytes.constant-time-equalwere cut — extensions to WebCrypto don't benefit from conforming to WebCrypto's shape, and belong in a sibling package if anywhere.Re-add trigger: the Modern Algorithms proposal settles
When the W3C "Modern Algorithms in the Web Cryptography API" proposal merges into the spec, one-shot ChaCha20-Poly1305 becomes standard surface and re-enters under the ordinary additive path. Design facts already settled by the first implementation (recover from history, pre-cut):
octJWK moved once to follow the proposal's registered"C20P"— re-check the registration at re-add;invalid-nonce;XChaCha20-Poly1305 is NOT part of this trigger: it is absent from the proposal and from every platform WebCrypto, and does not return to this package.
Preserved design: the
aead-internal-nonceWITRecorded here per the scope decision, in case a future spec development (or a sibling extension package) wants it. Motivations it carried: nonce-misuse resistance (the caller-nonce
aeadkind's standing hazard), and SP 800-38D approved-mode sealing (the FIPS-profile trajectory — which is correspondingly weakened in the direction notes by this cut). Key semantics: implementation-generated nonces,iv ‖ ciphertext ‖ tagwire format, a per-key nonce budget surfacingerror.key-exhausted(an error case that leaves the variant with the cut and would need restoring).The
aead-internal-noncekind (from wit/webcrypto.wit)aes-gcm-internal-nonce(from wit/aes.wit)