UNPKG

@x402-hpke/node

Version:

Provider-agnostic HPKE envelope library for x402 (Node) — cross-language interop with Python

63 lines (62 loc) 2.36 kB
import sodium from "libsodium-wrappers"; import { AeadLimitError, StreamNoncePrefixLenError } from "./errors.js"; function le64(n) { const b = new Uint8Array(8); let x = BigInt(n); for (let i = 0; i < 8; i++) { b[i] = Number(x & 0xffn); x >>= 8n; } return b; } export async function sealChunkXChaCha(key, noncePrefix16, seq, plaintext, aad) { await sodium.ready; if (noncePrefix16.length !== 16) throw new StreamNoncePrefixLenError("STREAM_NONCE_PREFIX_LEN"); const nonce = new Uint8Array(24); nonce.set(noncePrefix16, 0); nonce.set(le64(seq), 16); return sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, aad ?? null, null, nonce, key); } export async function openChunkXChaCha(key, noncePrefix16, seq, ciphertext, aad) { await sodium.ready; if (noncePrefix16.length !== 16) throw new StreamNoncePrefixLenError("STREAM_NONCE_PREFIX_LEN"); const nonce = new Uint8Array(24); nonce.set(noncePrefix16, 0); nonce.set(le64(seq), 16); return sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null, ciphertext, aad ?? null, nonce, key); } export class XChaChaStreamLimiter { key; prefix; maxChunks; maxBytes; chunksUsed = 0; bytesUsed = 0; constructor(key, noncePrefix16, opts) { if (noncePrefix16.length !== 16) throw new Error("STREAM_NONCE_PREFIX_LEN"); this.key = key; this.prefix = noncePrefix16; this.maxChunks = opts?.maxChunks ?? 1_000_000; // implementation-chosen default this.maxBytes = opts?.maxBytes ?? 1_000_000_000; // implementation-chosen default } enforceLimits(nextBytes) { if (this.chunksUsed + 1 > this.maxChunks) throw new AeadLimitError("AEAD_LIMIT"); if (this.bytesUsed + nextBytes > this.maxBytes) throw new AeadLimitError("AEAD_LIMIT"); } async seal(seq, chunk, aad) { this.enforceLimits(chunk.length); const ct = await sealChunkXChaCha(this.key, this.prefix, seq, chunk, aad); this.chunksUsed += 1; this.bytesUsed += chunk.length; return ct; } async open(seq, ct, aad) { // Limits are typically enforced on sealers; accepters may optionally enforce bytes return openChunkXChaCha(this.key, this.prefix, seq, ct, aad); } }