Fix a nonce allocator that breaks after restart

A service encrypts payment instructions with AES-GCM. Its local counter resets after a restart and exposes another message. Repair the allocator without changing the encryption algorithm.

Requires Node.js 22 or newer. No package installation or server needed. To work in a container instead, open the folder with VS Code Dev Containers or GitHub Codespaces: .devcontainer/devcontainer.json uses the Dev Containers Node.js 24 image, pinned by digest.
1. Unzip all files into one folder.
2. In that folder, run: node --test exercise.test.mjs
3. Edit allocator.mjs, then run the same command again.

The starter deliberately fails some tests. A successful repair passes every case, including cases that catch common wrong fixes. The browser runs this same cases.mjs with the same fixtures; locally Node runs your code directly. If you completed the optional first stage in the browser, Download my work adds your input as stage1-input.json and the tests include it. Only run code you wrote or reviewed.

1. See the failure
Run the starter. The restart test creates the allocator twice under the same key. Both return zero. Real AES-GCM encryption then lets the test recover pay=9000 from the two ciphertexts and known pay=1000.

2. Replace process-local state
Inside the returned async function, ask the supplied store for a reservation using key.materialId, await that counter, and pass it to encodeNonce. Remove the local next counter.

3. Handle more than one process
Run the worker and key-alias tests. A counter per process can collide; a counter keyed by the display label can reset when someone renames the same key.

4. Fail safely
Let reservation errors propagate. When the store runs out, returning any fallback nonce allows encryption to continue unsafely. The exhaustion test must reject its third allocation. Keep the reserved counter as a BigInt; do not convert it to Number.

SUPPLIED CONTRACT
createAllocator receives key.materialId (stable for the actual key bytes), a key.label for display, a shared store, and encodeNonce. await store.reserve(key.materialId) atomically reserves a counter and rejects on exhaustion. encodeNonce converts a BigInt to a 12-byte hexadecimal nonce.

REFERENCE
reference.mjs contains an explained solution: The shared store reserves counter 0 for the first material-A allocation and counter 1 after allocator recreation, producing different 12-byte encodings. Two writers and two display labels still use that same material scope. The large-counter case must preserve 9007199254740993 and 9007199254740994 as BigInts. When a two-reservation store rejects its third call, the allocator propagates the error and encryption stops. A new material-B key may start its own counter, because uniqueness is required per actual key, not across unrelated keys.

SCOPE
The restart test recreates the allocator; it does not restart the browser or reload the module. The pre-existing reservation test also checks that a module-local counter cannot replace the shared store. The supplied store is a deterministic model of an atomic durable reservation service. Building that service, surviving rollback, key retirement, and production GCM usage limits are outside this exercise. The fixed public keys and messages are test fixtures. The XOR demonstration exposes plaintext at known positions, not the AES key; GCM reuse can also break authentication.

NEXT TASK
List the writers sharing a real encryption key in your system. What durable component coordinates them, and what happens after a backup restore or reservation outage?

SOURCE
https://www.rfc-editor.org/rfc/rfc5116.html#section-5.1.1
