@x402-hpke/node
Version:
Provider-agnostic HPKE envelope library for x402 (Node) — cross-language interop with Python
63 lines (62 loc) • 2.36 kB
JavaScript
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);
}
}