@pksuite/crypto
v0.2.0
Published
Browser and Node cryptographic primitives for the pk suite.
Readme
@pksuite/crypto
Browser and Node cryptographic primitives shared by the pk suite. Version
0.1.0 provides canonical age/X25519 identities and the streaming PKFILE1
file envelope. It has no workflow, storage-provider, or network concepts.
The ESM browser entry is intended for an application bundler. Its declared
@noble/* dependencies remain external in both ESM and CommonJS artifacts so
consumer lockfile updates change the code that actually executes.
import {
createRecipientIdentity,
createFileEncryption,
disposeEnvelope,
encryptChunk,
finalizeEnvelope,
openFileEnvelope,
decryptChunk,
} from "@pksuite/crypto";
const recipient = createRecipientIdentity();
const encryption = createFileEncryption({
recipient: recipient.recipient,
plaintextSize: file.size,
metadata: {
name: file.name,
mediaType: file.type || "application/octet-stream",
lastModified: file.lastModified,
},
});
for (let index = 0; index < encryption.chunkCount; index++) {
const start = index * encryption.chunkSize;
const plaintext = new Uint8Array(await file.slice(start, start + encryption.chunkSize).arrayBuffer());
const ciphertext = encryptChunk({ state: encryption, index, plaintext });
// Persist or upload this independently authenticated chunk before reading
// the next slice. Retrying an index produces a fresh XChaCha nonce.
await storeChunk(index, ciphertext);
}
finalizeEnvelope(encryption);
const decryption = openFileEnvelope({ identity: recipient.identity, header: encryption.header });
for (let index = 0; index < decryption.chunkCount; index++) {
const plaintext = decryptChunk({ state: decryption, index, ciphertext: await loadChunk(index) });
await consumePlaintext(plaintext);
}
finalizeEnvelope(decryption);When the identity scalar is held elsewhere (e.g. an agent process that
never releases it), pass unwrapFileKey instead of identity: the
callback receives a copy of the envelope's age header and returns the
16-byte file key; the package copies the result, so the callback may wipe
its own buffer.
finalizeEnvelope best-effort overwrites the file key retained by its state
and makes that state unusable. Call disposeEnvelope(state) on cancellation or
any abandoned upload/decryption. JavaScript cannot guarantee erasure of engine
or cryptographic-library internal copies.
Metadata names are encrypted portable filename components, not paths. A consumer that writes files must still join the name beneath an explicitly chosen directory and must not decode or reinterpret it as path syntax.
The encrypted header exposes file size, chunk geometry, recipient count, and
header length. Filename, media type, and modification time are encrypted. See
dist/SPEC-FILE.md in the package, or spec/SPEC-FILE.md
in the repository, for the implementation-independent wire format and threat
contract.
vectors/file-v1.json contains fixed identity, header, metadata, nonce, AAD,
and chunk outputs. An independent Node reference reader opens that frozen
vector in the test suite. Every structured case in
vectors/file-v1-tampering.json is executed by the conformance tests. The
frozen age-cli-v1.age artifact was written by the external age CLI.
The mobile release gate is recorded under test/mobile/reports/ for Android
Chrome and iOS Safari. Both devices completed foreground and background-resume
runs with matching byte counts. Safari does not expose performance.memory;
the harness records that measurement as unavailable.
