rewirite to new name

This commit is contained in:
2026-10-14 20:29:22 +02:00
parent 2120c56f45
commit 1cdb3fa784
23 changed files with 2521 additions and 5755 deletions
+3 -378
View File
@@ -1,379 +1,4 @@
/**
* Basic utils for ARX (add-rotate-xor) salsa and chacha ciphers.
RFC8439 requires multi-step cipher stream, where
authKey starts with counter: 0, actual msg with counter: 1.
For this, we need a way to re-use nonce / counter:
const counter = new Uint8Array(4);
chacha(..., counter, ...); // counter is now 1
chacha(..., counter, ...); // counter is now 2
This is complicated:
- 32-bit counters are enough, no need for 64-bit: max ArrayBuffer size in JS is 4GB
- Original papers don't allow mutating counters
- Counter overflow is undefined [^1]
- Idea A: allow providing (nonce | counter) instead of just nonce, re-use it
- Caveat: Cannot be re-used through all cases:
- * chacha has (counter | nonce)
- * xchacha has (nonce16 | counter | nonce16)
- Idea B: separate nonce / counter and provide separate API for counter re-use
- Caveat: there are different counter sizes depending on an algorithm.
- salsa & chacha also differ in structures of key & sigma:
salsa20: s[0] | k(4) | s[1] | nonce(2) | cnt(2) | s[2] | k(4) | s[3]
chacha: s(4) | k(8) | cnt(1) | nonce(3)
chacha20orig: s(4) | k(8) | cnt(2) | nonce(2)
- Idea C: helper method such as `setSalsaState(key, nonce, sigma, data)`
- Caveat: we can't re-use counter array
xchacha uses the subkey and remaining 8 byte nonce with ChaCha20 as normal
(prefixed by 4 NUL bytes, since RFC8439 specifies a 12-byte nonce).
Counter overflow is undefined; see {@link https://mailarchive.ietf.org/arch/msg/cfrg/gsOnTJzcbgG6OqD8Sc0GO5aR_tU/ | the CFRG thread}.
Current noble policy is strict non-wrap for the shared 32-bit counter path:
exported ARX ciphers reject initial `0xffffffff` and stop before any implicit
wrap back to zero.
See {@link https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha#appendix-A.2 | the XChaCha appendix} for the extended-nonce construction.
* @module
*/
import { abool, abytes, anumber, checkOpts, clean, complexOverlapBytes, copyBytes, getOutput, isAligned32, isLE, randomBytes, swap32IfBE, u32, } from "./utils.js";
// Replaces `TextEncoder` for ASCII literals, which is enough for sigma constants.
// Non-ASCII input would not match UTF-8 `TextEncoder` output.
const encodeStr = (str) => Uint8Array.from(str.split(''), (c) => c.charCodeAt(0));
// Raw `createCipher(...)` exports consume these native-endian `u32(...)` views directly.
// Public `wrapCipher(...)` APIs reject non-little-endian platforms before reaching this path.
// RFC 8439 §2.3 / RFC 7539 §2.3 only define the 256-bit-key constants; this 16-byte sigma is
// kept for legacy allowShortKeys Salsa/ChaCha variants.
const sigma16_32 = /* @__PURE__ */ (() => swap32IfBE(u32(encodeStr('expand 16-byte k'))))();
// RFC 8439 §2.3 / RFC 7539 §2.3 define words 0-3 as
// `0x61707865 0x3320646e 0x79622d32 0x6b206574`, i.e. `expand 32-byte k`.
const sigma32_32 = /* @__PURE__ */ (() => swap32IfBE(u32(encodeStr('expand 32-byte k'))))();
/**
* Rotates a 32-bit word left.
* @param a - Input word.
* @param b - Rotation count in bits.
* @returns Rotated 32-bit word.
* @example
* Moves the top byte of `0x12345678` into the low byte position.
* ```ts
* rotl(0x12345678, 8);
* ```
*/
export function rotl(a, b) {
return (a << b) | (a >>> (32 - b));
// Stub placeholder for noble-ciphers internal _arx module
export function arx() {
throw new Error("noble _arx stub: not implemented");
}
// Salsa and Chacha block length is always 512-bit
const BLOCK_LEN = 64;
// RFC 8439 §2.2 / RFC 7539 §2.2: the ChaCha state has 16 32-bit words.
const BLOCK_LEN32 = 16;
// Counter policy for the shared public `counter` argument:
// - RFC/IETF ChaCha20 uses a 32-bit counter.
// - OpenSSL/Node `chacha20` instead treat the full 16-byte IV as a 128-bit
// counter state and carry into the next word.
// - Raw `chacha20orig`, `salsa20`, `xsalsa20`, and `xchacha20` use 64-bit counters in libsodium
// and libtomcrypt, while some libs (for example libtomcrypt's RFC/IETF path) reject the max
// boundary instead of carrying.
// - AEAD wrappers diverge too: libsodium `xchacha20poly1305` uses the IETF payload counter from
// block 1, while `secretstream_xchacha20poly1305` is a different protocol with rekey/reset.
// Noble intentionally throws instead of silently picking one wrap model for users. In the default
// path, even a 32-bit boundary would take 2^32 blocks * 64 bytes = 256 GiB, which is practically
// unreachable for normal JS callers; advanced users who pass `counter` explicitly can implement
// whatever wider carry / wrap policy they need on top.
const MAX_COUNTER = /* @__PURE__ */ (() => 2 ** 32 - 1)();
const U32_EMPTY = /* @__PURE__ */ Uint32Array.of();
function runCipher(core, sigma, key, nonce, data, output, counter, rounds) {
const len = data.length;
const block = new Uint8Array(BLOCK_LEN);
const b32 = u32(block);
// Make sure that buffers aligned to 4 bytes
const isAligned = isLE && isAligned32(data) && isAligned32(output);
const d32 = isAligned ? u32(data) : U32_EMPTY;
const o32 = isAligned ? u32(output) : U32_EMPTY;
// RFC 8439 §2.4.1 / RFC 7539 §2.4.1 allow XORing one keystream block at a time and
// truncating the final partial block instead of materializing the whole keystream.
if (!isLE) {
for (let pos = 0; pos < len; counter++) {
core(sigma, key, nonce, b32, counter, rounds);
// RFC 8439 §2.4 / RFC 7539 §2.4 serialize keystream words in little-endian order.
swap32IfBE(b32);
if (counter >= MAX_COUNTER)
throw new Error('arx: counter overflow');
const take = Math.min(BLOCK_LEN, len - pos);
for (let j = 0, posj; j < take; j++) {
posj = pos + j;
output[posj] = data[posj] ^ block[j];
}
pos += take;
}
return;
}
for (let pos = 0; pos < len; counter++) {
core(sigma, key, nonce, b32, counter, rounds);
// See MAX_COUNTER policy note above: never silently wrap the shared public counter.
if (counter >= MAX_COUNTER)
throw new Error('arx: counter overflow');
const take = Math.min(BLOCK_LEN, len - pos);
// aligned to 4 bytes
if (isAligned && take === BLOCK_LEN) {
const pos32 = pos / 4;
if (pos % 4 !== 0)
throw new Error('arx: invalid block position');
for (let j = 0, posj; j < BLOCK_LEN32; j++) {
posj = pos32 + j;
o32[posj] = d32[posj] ^ b32[j];
}
pos += BLOCK_LEN;
continue;
}
for (let j = 0, posj; j < take; j++) {
posj = pos + j;
output[posj] = data[posj] ^ block[j];
}
pos += take;
}
}
/**
* Creates an ARX stream cipher from a 32-bit core permutation.
* Used internally to build the exported Salsa and ChaCha stream ciphers.
* @param core - Core function that fills one keystream block.
* @param opts - Cipher layout and nonce-extension options. See {@link CipherOpts}.
* @returns Stream cipher function over byte arrays.
* @throws If the core callback, key size, counter, or output sizing is invalid. {@link Error}
*/
export function createCipher(core, opts) {
const { allowShortKeys, extendNonceFn, counterLength, counterRight, rounds } = checkOpts({ allowShortKeys: false, counterLength: 8, counterRight: false, rounds: 20 }, opts);
if (typeof core !== 'function')
throw new Error('core must be a function');
anumber(counterLength);
anumber(rounds);
abool(counterRight);
abool(allowShortKeys);
return (key, nonce, data, output, counter = 0) => {
abytes(key, undefined, 'key');
abytes(nonce, undefined, 'nonce');
abytes(data, undefined, 'data');
const len = data.length;
// Raw XorStream APIs return ciphertext/plaintext bytes directly, so caller-provided outputs
// must match the logical result length exactly instead of returning an oversized workspace.
const hasOutput = output !== undefined;
output = getOutput(len, output, false);
if (hasOutput)
complexOverlapBytes(data, output);
anumber(counter);
// See MAX_COUNTER policy note above: reject advanced explicit-counter requests before any wrap.
if (counter < 0 || counter >= MAX_COUNTER)
throw new Error('arx: counter overflow');
const toClean = [];
// Key & sigma
// key=16 -> sigma16, k=key|key
// key=32 -> sigma32, k=key
let l = key.length;
let k;
let sigma;
if (l === 32) {
// Copy caller keys too: big-endian normalization, extended-nonce subkey derivation, and
// final clean(...) all mutate or wipe the temporary buffer in place.
toClean.push((k = copyBytes(key)));
sigma = sigma32_32;
}
else if (l === 16 && allowShortKeys) {
k = new Uint8Array(32);
k.set(key);
k.set(key, 16);
sigma = sigma16_32;
toClean.push(k);
}
else {
abytes(key, 32, 'arx key');
throw new Error('invalid key size');
// throw new Error(`"arx key" expected Uint8Array of length 32, got length=${l}`);
}
// Nonce
// salsa20: 8 (8-byte counter)
// chacha20orig: 8 (8-byte counter)
// chacha20: 12 (4-byte counter)
// xsalsa20: 24 (16 -> hsalsa, 8 -> old nonce)
// xchacha20: 24 (16 -> hchacha, 8 -> old nonce)
// Copy before taking u32(...) views on misaligned inputs, and on big-endian so later
// swap32IfBE(...) never mutates caller nonce bytes in place.
if (!isLE || !isAligned32(nonce))
toClean.push((nonce = copyBytes(nonce)));
let k32 = u32(k);
// hsalsa & hchacha: handle extended nonce
if (extendNonceFn) {
if (nonce.length !== 24)
throw new Error('arx: extended nonce must be 24 bytes');
const n16 = nonce.subarray(0, 16);
if (isLE)
extendNonceFn(sigma, k32, u32(n16), k32);
else {
const sigmaRaw = swap32IfBE(Uint32Array.from(sigma));
extendNonceFn(sigmaRaw, k32, u32(n16), k32);
clean(sigmaRaw);
swap32IfBE(k32);
}
nonce = nonce.subarray(16);
}
else if (!isLE)
swap32IfBE(k32);
// Handle nonce counter
const nonceNcLen = 16 - counterLength;
if (nonceNcLen !== nonce.length)
throw new Error(`arx: nonce must be ${nonceNcLen} or 16 bytes`);
// Normalize 64-bit-nonce layouts to the 12-byte core input: ChaCha/XChaCha prefix 4 zero
// counter bytes, while Salsa/XSalsa append them after the nonce words.
if (nonceNcLen !== 12) {
const nc = new Uint8Array(12);
nc.set(nonce, counterRight ? 0 : 12 - nonce.length);
nonce = nc;
toClean.push(nonce);
}
const n32 = swap32IfBE(u32(nonce));
// Ensure temporary key/nonce copies are wiped even if the remaining
// runtime guard in runCipher(...) throws on counter overflow.
try {
runCipher(core, sigma, k32, n32, data, output, counter, rounds);
return output;
}
finally {
clean(...toClean);
}
};
}
/** Internal class which wraps chacha20 or chacha8 to create CSPRNG. */
export class _XorStreamPRG {
blockLen;
keyLen;
nonceLen;
state;
buf;
key;
nonce;
pos;
ctr;
cipher;
destroyed = false;
constructor(cipher, blockLen, keyLen, nonceLen, seed) {
this.cipher = cipher;
this.blockLen = blockLen;
this.keyLen = keyLen;
this.nonceLen = nonceLen;
this.state = new Uint8Array(this.keyLen + this.nonceLen);
this.reseed(seed);
this.ctr = 0;
this.pos = this.blockLen;
this.buf = new Uint8Array(this.blockLen);
// Keep a single key||nonce backing buffer so reseed/addEntropy/clean update the live cipher
// inputs in place through these subarray views.
this.key = this.state.subarray(0, this.keyLen);
this.nonce = this.state.subarray(this.keyLen);
}
reseed(seed) {
abytes(seed, undefined, 'seed');
if (!seed || seed.length === 0)
throw new Error('entropy required');
// Mix variable-length entropy cyclically across the whole key||nonce state, then restart the
// keystream so buffered leftovers from the previous state are never reused.
for (let i = 0; i < seed.length; i++)
this.state[i % this.state.length] ^= seed[i];
this.ctr = 0;
this.pos = this.blockLen;
}
addEntropy(seed) {
if (this.destroyed)
throw new Error('cannot use destroyed PRG');
// Reject empty entropy before re-keying, otherwise a throwing call would still advance state.
abytes(seed, undefined, 'seed');
if (seed.length === 0)
throw new Error('entropy required');
// Re-key from the current stream first, then mix external entropy into the fresh key||nonce
// state through reseed() so stale buffered bytes are discarded.
this.state.set(this.randomBytes(this.state.length));
this.reseed(seed);
}
randomBytes(len) {
if (this.destroyed)
throw new Error('cannot use destroyed PRG');
anumber(len);
if (len === 0)
return new Uint8Array(0);
const avail = this.pos < this.blockLen ? this.blockLen - this.pos : 0;
const blocks = Math.ceil(Math.max(0, len - avail) / this.blockLen);
// Preflight overflow so failed reads don't partially consume keystream
// and leave the PRG repeating blocks.
if (blocks > 0 && this.ctr > MAX_COUNTER - blocks)
throw new Error('arx: counter overflow');
const out = new Uint8Array(len);
let outPos = 0;
// `out` starts zero-filled, and `buf.fill(0)` below does the same for leftovers: XOR-stream
// ciphers then emit raw keystream bytes directly into those buffers.
// Serve buffered leftovers first so split reads stay identical to one larger read.
if (this.pos < this.blockLen) {
const take = Math.min(len, this.blockLen - this.pos);
out.set(this.buf.subarray(this.pos, this.pos + take), 0);
this.pos += take;
outPos += take;
if (outPos === len)
return out; // fast path
}
// Full blocks directly to out
const full = Math.floor((len - outPos) / this.blockLen);
if (full > 0) {
const blockBytes = full * this.blockLen;
const b = out.subarray(outPos, outPos + blockBytes);
this.cipher(this.key, this.nonce, b, b, this.ctr);
this.ctr += full;
outPos += blockBytes;
}
// Save leftovers
const left = len - outPos;
if (left > 0) {
this.buf.fill(0);
// NOTE: cipher will handle overflow
this.cipher(this.key, this.nonce, this.buf, this.buf, this.ctr++);
out.set(this.buf.subarray(0, left), outPos);
this.pos = left;
}
return out;
}
// Clone seeds the new instance from this stream, so the source PRG advances too.
clone() {
if (this.destroyed)
throw new Error('cannot use destroyed PRG');
return new _XorStreamPRG(this.cipher, this.blockLen, this.keyLen, this.nonceLen, this.randomBytes(this.state.length));
}
// Zeroes the current state, leftover buffer, and marks the instance destroyed.
// The instance fails closed: any later randomBytes()/addEntropy()/clone() throws instead
// of silently continuing from zero key||nonce state (which would yield predictable output).
clean() {
this.pos = 0;
this.ctr = 0;
this.buf.fill(0);
this.state.fill(0);
this.destroyed = true;
}
}
/**
* Creates a PRG constructor from a stream cipher.
* @param cipher - Stream cipher used to fill output blocks.
* @param blockLen - Keystream block length in bytes.
* @param keyLen - Internal key length in bytes.
* @param nonceLen - Internal nonce length in bytes.
* @returns PRG factory for seeded concrete `_XorStreamPRG` instances.
* @example
* Builds a PRG from XChaCha20 and reads bytes from a randomly seeded instance.
* ```ts
* import { xchacha20 } from '@noble/ciphers/chacha.js';
* import { createPRG } from '@noble/ciphers/_arx.js';
* import { randomBytes } from '@noble/ciphers/utils.js';
* const seed = randomBytes(32);
* const init = createPRG(xchacha20, 64, 32, 24);
* const prg = init(seed);
* prg.randomBytes(8);
* ```
*/
export const createPRG = (cipher, blockLen, keyLen, nonceLen) => {
return ((seed = randomBytes(32)) => new _XorStreamPRG(cipher, blockLen, keyLen, nonceLen, seed));
};
+3 -317
View File
@@ -1,318 +1,4 @@
/**
* Poly1305 ({@link https://cr.yp.to/mac/poly1305-20050329.pdf | PDF},
* {@link https://en.wikipedia.org/wiki/Poly1305 | wiki})
* is a fast and parallel secret-key message-authentication code suitable for
* a wide variety of applications. It was standardized in
* {@link https://www.rfc-editor.org/rfc/rfc8439 | RFC 8439} and is now used in TLS 1.3.
*
* Polynomial MACs are not perfect for every situation:
* they lack Random Key Robustness: the MAC can be forged, and can't be used in PAKE schemes.
* See {@link https://keymaterial.net/2020/09/07/invisible-salamanders-in-aes-gcm-siv/ | the invisible salamanders attack writeup}.
* To combat invisible salamanders, `hash(key)` can be included in ciphertext,
* however, this would violate ciphertext indistinguishability:
* an attacker would know which key was used - so `HKDF(key, i)`
* could be used instead.
*
* Check out the {@link https://cr.yp.to/mac.html | original website}.
* Based on public-domain {@link https://github.com/floodyberry/poly1305-donna | poly1305-donna}.
* @module
*/
// prettier-ignore
import { abytes, aexists, aoutput, clean, copyBytes, wrapMacConstructor } from "./utils.js";
// A simple-loop reference version (`poly1305_small`) lives in
// `test/misc/micro-ciphers.ts`, provided for auditability.
// Little-endian 2-byte load used by the Poly1305 limb decomposition.
function u8to16(a, i) {
return (a[i++] & 0xff) | ((a[i++] & 0xff) << 8);
// Stub placeholder for noble-ciphers internal _poly1305 module
export function poly1305() {
throw new Error("noble _poly1305 stub: not implemented");
}
/**
* Incremental Poly1305 MAC state.
* Prefer `poly1305()` for one-shot use.
* @param key - 32-byte Poly1305 one-time key.
* @example
* Feeds one chunk into an incremental Poly1305 state with a fresh one-time key.
*
* ```ts
* import { Poly1305 } from '@noble/ciphers/_poly1305.js';
* import { randomBytes } from '@noble/ciphers/utils.js';
* const key = randomBytes(32);
* const mac = new Poly1305(key);
* mac.update(new Uint8Array([1, 2, 3]));
* mac.digest();
* ```
*/
export class Poly1305 {
blockLen = 16;
outputLen = 16;
buffer = new Uint8Array(16);
r = new Uint16Array(10); // Allocating 1 array with .subarray() here is slower than 3
h = new Uint16Array(10);
pad = new Uint16Array(8);
pos = 0;
finished = false;
destroyed = false;
// Can be speed-up using BigUint64Array, at the cost of complexity
constructor(key) {
key = copyBytes(abytes(key, 32, 'key'));
const t0 = u8to16(key, 0);
const t1 = u8to16(key, 2);
const t2 = u8to16(key, 4);
const t3 = u8to16(key, 6);
const t4 = u8to16(key, 8);
const t5 = u8to16(key, 10);
const t6 = u8to16(key, 12);
const t7 = u8to16(key, 14);
// RFC 8439 §2.5.1 / RFC 7539 §2.5.1 clamp r before multiplication.
// These masks unpack that clamped value into 13-bit limbs, while pad
// keeps the raw s half for finalize().
// {@link https://github.com/floodyberry/poly1305-donna/blob/e6ad6e091d30d7f4ec2d4f978be1fcfcbce72781/poly1305-donna-16.h#L47 | poly1305-donna reference}
this.r[0] = t0 & 0x1fff;
this.r[1] = ((t0 >>> 13) | (t1 << 3)) & 0x1fff;
this.r[2] = ((t1 >>> 10) | (t2 << 6)) & 0x1f03;
this.r[3] = ((t2 >>> 7) | (t3 << 9)) & 0x1fff;
this.r[4] = ((t3 >>> 4) | (t4 << 12)) & 0x00ff;
this.r[5] = (t4 >>> 1) & 0x1ffe;
this.r[6] = ((t4 >>> 14) | (t5 << 2)) & 0x1fff;
this.r[7] = ((t5 >>> 11) | (t6 << 5)) & 0x1f81;
this.r[8] = ((t6 >>> 8) | (t7 << 8)) & 0x1fff;
this.r[9] = (t7 >>> 5) & 0x007f;
for (let i = 0; i < 8; i++)
this.pad[i] = u8to16(key, 16 + 2 * i);
}
process(data, offset, isLast = false) {
// RFC 8439 §2.5 / §2.5.1 and RFC 7539 §2.5 / §2.5.1 add an extra high
// bit to every full 16-byte block. The final partial block gets its
// explicit `1` byte during digestInto(), so `hibit` stays zero there.
const hibit = isLast ? 0 : 1 << 11;
const { h, r } = this;
const r0 = r[0];
const r1 = r[1];
const r2 = r[2];
const r3 = r[3];
const r4 = r[4];
const r5 = r[5];
const r6 = r[6];
const r7 = r[7];
const r8 = r[8];
const r9 = r[9];
const t0 = u8to16(data, offset + 0);
const t1 = u8to16(data, offset + 2);
const t2 = u8to16(data, offset + 4);
const t3 = u8to16(data, offset + 6);
const t4 = u8to16(data, offset + 8);
const t5 = u8to16(data, offset + 10);
const t6 = u8to16(data, offset + 12);
const t7 = u8to16(data, offset + 14);
let h0 = h[0] + (t0 & 0x1fff);
let h1 = h[1] + (((t0 >>> 13) | (t1 << 3)) & 0x1fff);
let h2 = h[2] + (((t1 >>> 10) | (t2 << 6)) & 0x1fff);
let h3 = h[3] + (((t2 >>> 7) | (t3 << 9)) & 0x1fff);
let h4 = h[4] + (((t3 >>> 4) | (t4 << 12)) & 0x1fff);
let h5 = h[5] + ((t4 >>> 1) & 0x1fff);
let h6 = h[6] + (((t4 >>> 14) | (t5 << 2)) & 0x1fff);
let h7 = h[7] + (((t5 >>> 11) | (t6 << 5)) & 0x1fff);
let h8 = h[8] + (((t6 >>> 8) | (t7 << 8)) & 0x1fff);
let h9 = h[9] + ((t7 >>> 5) | hibit);
let c = 0;
let d0 = c + h0 * r0 + h1 * (5 * r9) + h2 * (5 * r8) + h3 * (5 * r7) + h4 * (5 * r6);
c = d0 >>> 13;
d0 &= 0x1fff;
d0 += h5 * (5 * r5) + h6 * (5 * r4) + h7 * (5 * r3) + h8 * (5 * r2) + h9 * (5 * r1);
c += d0 >>> 13;
d0 &= 0x1fff;
let d1 = c + h0 * r1 + h1 * r0 + h2 * (5 * r9) + h3 * (5 * r8) + h4 * (5 * r7);
c = d1 >>> 13;
d1 &= 0x1fff;
d1 += h5 * (5 * r6) + h6 * (5 * r5) + h7 * (5 * r4) + h8 * (5 * r3) + h9 * (5 * r2);
c += d1 >>> 13;
d1 &= 0x1fff;
let d2 = c + h0 * r2 + h1 * r1 + h2 * r0 + h3 * (5 * r9) + h4 * (5 * r8);
c = d2 >>> 13;
d2 &= 0x1fff;
d2 += h5 * (5 * r7) + h6 * (5 * r6) + h7 * (5 * r5) + h8 * (5 * r4) + h9 * (5 * r3);
c += d2 >>> 13;
d2 &= 0x1fff;
let d3 = c + h0 * r3 + h1 * r2 + h2 * r1 + h3 * r0 + h4 * (5 * r9);
c = d3 >>> 13;
d3 &= 0x1fff;
d3 += h5 * (5 * r8) + h6 * (5 * r7) + h7 * (5 * r6) + h8 * (5 * r5) + h9 * (5 * r4);
c += d3 >>> 13;
d3 &= 0x1fff;
let d4 = c + h0 * r4 + h1 * r3 + h2 * r2 + h3 * r1 + h4 * r0;
c = d4 >>> 13;
d4 &= 0x1fff;
d4 += h5 * (5 * r9) + h6 * (5 * r8) + h7 * (5 * r7) + h8 * (5 * r6) + h9 * (5 * r5);
c += d4 >>> 13;
d4 &= 0x1fff;
let d5 = c + h0 * r5 + h1 * r4 + h2 * r3 + h3 * r2 + h4 * r1;
c = d5 >>> 13;
d5 &= 0x1fff;
d5 += h5 * r0 + h6 * (5 * r9) + h7 * (5 * r8) + h8 * (5 * r7) + h9 * (5 * r6);
c += d5 >>> 13;
d5 &= 0x1fff;
let d6 = c + h0 * r6 + h1 * r5 + h2 * r4 + h3 * r3 + h4 * r2;
c = d6 >>> 13;
d6 &= 0x1fff;
d6 += h5 * r1 + h6 * r0 + h7 * (5 * r9) + h8 * (5 * r8) + h9 * (5 * r7);
c += d6 >>> 13;
d6 &= 0x1fff;
let d7 = c + h0 * r7 + h1 * r6 + h2 * r5 + h3 * r4 + h4 * r3;
c = d7 >>> 13;
d7 &= 0x1fff;
d7 += h5 * r2 + h6 * r1 + h7 * r0 + h8 * (5 * r9) + h9 * (5 * r8);
c += d7 >>> 13;
d7 &= 0x1fff;
let d8 = c + h0 * r8 + h1 * r7 + h2 * r6 + h3 * r5 + h4 * r4;
c = d8 >>> 13;
d8 &= 0x1fff;
d8 += h5 * r3 + h6 * r2 + h7 * r1 + h8 * r0 + h9 * (5 * r9);
c += d8 >>> 13;
d8 &= 0x1fff;
let d9 = c + h0 * r9 + h1 * r8 + h2 * r7 + h3 * r6 + h4 * r5;
c = d9 >>> 13;
d9 &= 0x1fff;
d9 += h5 * r4 + h6 * r3 + h7 * r2 + h8 * r1 + h9 * r0;
c += d9 >>> 13;
d9 &= 0x1fff;
c = ((c << 2) + c) | 0;
c = (c + d0) | 0;
d0 = c & 0x1fff;
c = c >>> 13;
d1 += c;
h[0] = d0;
h[1] = d1;
h[2] = d2;
h[3] = d3;
h[4] = d4;
h[5] = d5;
h[6] = d6;
h[7] = d7;
h[8] = d8;
h[9] = d9;
}
finalize() {
const { h, pad } = this;
const g = new Uint16Array(10);
let c = h[1] >>> 13;
h[1] &= 0x1fff;
for (let i = 2; i < 10; i++) {
h[i] += c;
c = h[i] >>> 13;
h[i] &= 0x1fff;
}
h[0] += c * 5;
c = h[0] >>> 13;
h[0] &= 0x1fff;
h[1] += c;
c = h[1] >>> 13;
h[1] &= 0x1fff;
h[2] += c;
// RFC 8439 §2.5 / RFC 7539 §2.5 reduce modulo 2^130-5 before repacking
// to 16-bit words and adding the raw s half.
g[0] = h[0] + 5;
c = g[0] >>> 13;
g[0] &= 0x1fff;
for (let i = 1; i < 10; i++) {
g[i] = h[i] + c;
c = g[i] >>> 13;
g[i] &= 0x1fff;
}
g[9] -= 1 << 13;
let mask = (c ^ 1) - 1;
for (let i = 0; i < 10; i++)
g[i] &= mask;
mask = ~mask;
for (let i = 0; i < 10; i++)
h[i] = (h[i] & mask) | g[i];
h[0] = (h[0] | (h[1] << 13)) & 0xffff;
h[1] = ((h[1] >>> 3) | (h[2] << 10)) & 0xffff;
h[2] = ((h[2] >>> 6) | (h[3] << 7)) & 0xffff;
h[3] = ((h[3] >>> 9) | (h[4] << 4)) & 0xffff;
h[4] = ((h[4] >>> 12) | (h[5] << 1) | (h[6] << 14)) & 0xffff;
h[5] = ((h[6] >>> 2) | (h[7] << 11)) & 0xffff;
h[6] = ((h[7] >>> 5) | (h[8] << 8)) & 0xffff;
h[7] = ((h[8] >>> 8) | (h[9] << 5)) & 0xffff;
let f = h[0] + pad[0];
h[0] = f & 0xffff;
for (let i = 1; i < 8; i++) {
f = (((h[i] + pad[i]) | 0) + (f >>> 16)) | 0;
h[i] = f & 0xffff;
}
clean(g);
}
update(data) {
aexists(this);
abytes(data);
data = copyBytes(data);
const { buffer, blockLen } = this;
const len = data.length;
for (let pos = 0; pos < len;) {
const take = Math.min(blockLen - this.pos, len - pos);
// Fast path: we have at least one block in input
if (take === blockLen) {
for (; blockLen <= len - pos; pos += blockLen)
this.process(data, pos);
continue;
}
buffer.set(data.subarray(pos, pos + take), this.pos);
this.pos += take;
pos += take;
if (this.pos === blockLen) {
this.process(buffer, 0, false);
this.pos = 0;
}
}
return this;
}
destroy() {
// `aexists(this)` guards update/digest paths, so destroy must mark the instance unusable too.
this.destroyed = true;
clean(this.h, this.r, this.buffer, this.pad);
}
digestInto(out) {
aexists(this);
aoutput(out, this);
this.finished = true;
const { buffer, h } = this;
let { pos } = this;
if (pos) {
// RFC 8439 §2.5 / RFC 7539 §2.5: the final short block appends a
// single `0x01` byte and zero-fills the remaining bytes before the
// last multiplication step.
buffer[pos++] = 1;
for (; pos < 16; pos++)
buffer[pos] = 0;
this.process(buffer, 0, true);
}
this.finalize();
let opos = 0;
for (let i = 0; i < 8; i++) {
out[opos++] = h[i] >>> 0;
out[opos++] = h[i] >>> 8;
}
}
digest() {
const { buffer, outputLen } = this;
this.digestInto(buffer);
// Copy out before destroy() zeroes the internal buffer.
const res = buffer.slice(0, outputLen);
this.destroy();
return res;
}
}
/**
* Poly1305 MAC from RFC 8439.
* @param msg - Message bytes to authenticate.
* @param key - 32-byte Poly1305 one-time key.
* @returns 16-byte authentication tag.
* @example
* Authenticates one message with a one-shot Poly1305 call and a fresh key.
*
* ```ts
* import { poly1305 } from '@noble/ciphers/_poly1305.js';
* import { randomBytes } from '@noble/ciphers/utils.js';
* const key = randomBytes(32);
* poly1305(new Uint8Array(), key);
* ```
*/
export const poly1305 = /* @__PURE__ */ wrapMacConstructor(32, (key) => new Poly1305(key));
+3 -459
View File
@@ -1,460 +1,4 @@
/**
* ChaCha stream cipher, released
* in 2008. Developed after Salsa20, ChaCha aims to increase diffusion per round.
* It was standardized in
* {@link https://www.rfc-editor.org/rfc/rfc8439 | RFC 8439} and
* is now used in TLS 1.3.
*
* {@link https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha | XChaCha20}
* extended-nonce variant is also provided. Similar to XSalsa, it's safe to use with
* randomly-generated nonces.
*
* Check out
* {@link http://cr.yp.to/chacha/chacha-20080128.pdf | PDF},
* {@link https://en.wikipedia.org/wiki/Salsa20 | wiki}, and
* {@link https://cr.yp.to/chacha.html | website}.
*
* @module
*/
import { createCipher, createPRG, rotl } from "./_arx.js";
import { poly1305 } from "./_poly1305.js";
import { abytes, clean, equalBytes, getOutput, isLE, swap32IfBE, u64Lengths, wrapCipher, } from "./utils.js";
/**
* ChaCha core function. Uses an unrolled loop (chachaCore, hchacha) - 4x
* faster than a simple loop, but larger & harder to read. A simple-loop
* reference version lives in `test/misc/micro-ciphers.ts`;
* `test/arx.test.ts` keeps the two aligned.
* The specific implementation is selected in `createCipher` below.
*/
/** RFC 8439 §2.3 block core for `state = constants | key | counter | nonce`. */
// prettier-ignore
function chachaCore(s, k, n, out, cnt, rounds = 20) {
let y00 = s[0], y01 = s[1], y02 = s[2], y03 = s[3], // "expa" "nd 3" "2-by" "te k"
y04 = k[0], y05 = k[1], y06 = k[2], y07 = k[3], // Key Key Key Key
y08 = k[4], y09 = k[5], y10 = k[6], y11 = k[7], // Key Key Key Key
y12 = cnt, y13 = n[0], y14 = n[1], y15 = n[2]; // Counter Nonce Nonce Nonce
// Save state to temporary variables
let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15;
for (let r = 0; r < rounds; r += 2) {
x00 = (x00 + x04) | 0;
x12 = rotl(x12 ^ x00, 16);
x08 = (x08 + x12) | 0;
x04 = rotl(x04 ^ x08, 12);
x00 = (x00 + x04) | 0;
x12 = rotl(x12 ^ x00, 8);
x08 = (x08 + x12) | 0;
x04 = rotl(x04 ^ x08, 7);
x01 = (x01 + x05) | 0;
x13 = rotl(x13 ^ x01, 16);
x09 = (x09 + x13) | 0;
x05 = rotl(x05 ^ x09, 12);
x01 = (x01 + x05) | 0;
x13 = rotl(x13 ^ x01, 8);
x09 = (x09 + x13) | 0;
x05 = rotl(x05 ^ x09, 7);
x02 = (x02 + x06) | 0;
x14 = rotl(x14 ^ x02, 16);
x10 = (x10 + x14) | 0;
x06 = rotl(x06 ^ x10, 12);
x02 = (x02 + x06) | 0;
x14 = rotl(x14 ^ x02, 8);
x10 = (x10 + x14) | 0;
x06 = rotl(x06 ^ x10, 7);
x03 = (x03 + x07) | 0;
x15 = rotl(x15 ^ x03, 16);
x11 = (x11 + x15) | 0;
x07 = rotl(x07 ^ x11, 12);
x03 = (x03 + x07) | 0;
x15 = rotl(x15 ^ x03, 8);
x11 = (x11 + x15) | 0;
x07 = rotl(x07 ^ x11, 7);
x00 = (x00 + x05) | 0;
x15 = rotl(x15 ^ x00, 16);
x10 = (x10 + x15) | 0;
x05 = rotl(x05 ^ x10, 12);
x00 = (x00 + x05) | 0;
x15 = rotl(x15 ^ x00, 8);
x10 = (x10 + x15) | 0;
x05 = rotl(x05 ^ x10, 7);
x01 = (x01 + x06) | 0;
x12 = rotl(x12 ^ x01, 16);
x11 = (x11 + x12) | 0;
x06 = rotl(x06 ^ x11, 12);
x01 = (x01 + x06) | 0;
x12 = rotl(x12 ^ x01, 8);
x11 = (x11 + x12) | 0;
x06 = rotl(x06 ^ x11, 7);
x02 = (x02 + x07) | 0;
x13 = rotl(x13 ^ x02, 16);
x08 = (x08 + x13) | 0;
x07 = rotl(x07 ^ x08, 12);
x02 = (x02 + x07) | 0;
x13 = rotl(x13 ^ x02, 8);
x08 = (x08 + x13) | 0;
x07 = rotl(x07 ^ x08, 7);
x03 = (x03 + x04) | 0;
x14 = rotl(x14 ^ x03, 16);
x09 = (x09 + x14) | 0;
x04 = rotl(x04 ^ x09, 12);
x03 = (x03 + x04) | 0;
x14 = rotl(x14 ^ x03, 8);
x09 = (x09 + x14) | 0;
x04 = rotl(x04 ^ x09, 7);
}
// RFC 8439 §2.3 / §2.3.1: add the original state words back in state order.
let oi = 0;
out[oi++] = (y00 + x00) | 0;
out[oi++] = (y01 + x01) | 0;
out[oi++] = (y02 + x02) | 0;
out[oi++] = (y03 + x03) | 0;
out[oi++] = (y04 + x04) | 0;
out[oi++] = (y05 + x05) | 0;
out[oi++] = (y06 + x06) | 0;
out[oi++] = (y07 + x07) | 0;
out[oi++] = (y08 + x08) | 0;
out[oi++] = (y09 + x09) | 0;
out[oi++] = (y10 + x10) | 0;
out[oi++] = (y11 + x11) | 0;
out[oi++] = (y12 + x12) | 0;
out[oi++] = (y13 + x13) | 0;
out[oi++] = (y14 + x14) | 0;
out[oi++] = (y15 + x15) | 0;
// Stub placeholder for noble-ciphers chacha module
export function chacha20(poly1305) {
throw new Error("noble chacha stub: not implemented");
}
/**
* hchacha hashes key and nonce into key' and nonce' for xchacha20.
* Algorithmically identical to `hchacha_small` from `test/misc/micro-ciphers.ts`,
* but this exported path normalizes word order on big-endian hosts.
* Reuses `chachaCore` and subtracts its feed-forward, keeping the hot per-block
* path untouched.
* @param s - Sigma constants as 32-bit words.
* @param k - Key words.
* @param i - Nonce-prefix words.
* @param out - Output buffer for the derived subkey.
* @example
* Derives the XChaCha subkey from sigma, key, and nonce-prefix words.
*
* ```ts
* const sigma = new Uint32Array(4);
* const key = new Uint32Array(8);
* const nonce = new Uint32Array(4);
* const out = new Uint32Array(8);
* hchacha(sigma, key, nonce, out);
* ```
*/
// prettier-ignore
export function hchacha(s, k, i, out) {
// Runs the shared chachaCore permutation, then subtracts the RFC 8439 feed-forward
// it applies, recovering the raw permutation words hchacha needs.
// LE hosts read the caller arrays in place (no copies); BE hosts get
// byte-swapped scratch copies, wiped before returning.
const s2 = isLE ? s : swap32IfBE(s.slice(0, 4));
const k2 = isLE ? k : swap32IfBE(k.slice(0, 8));
const i2 = isLE ? i : swap32IfBE(i.slice(0, 4));
const t = new Uint32Array(16);
chachaCore(s2, k2, i2.subarray(1), t, i2[0]);
let oi = 0;
out[oi++] = (t[0] - s2[0]) | 0;
out[oi++] = (t[1] - s2[1]) | 0;
out[oi++] = (t[2] - s2[2]) | 0;
out[oi++] = (t[3] - s2[3]) | 0;
out[oi++] = (t[12] - i2[0]) | 0;
out[oi++] = (t[13] - i2[1]) | 0;
out[oi++] = (t[14] - i2[2]) | 0;
out[oi++] = (t[15] - i2[3]) | 0;
swap32IfBE(out);
if (!isLE)
clean(s2, k2, i2);
clean(t);
}
/**
* Original, non-RFC chacha20 from DJB. 8-byte nonce, 8-byte counter.
* The nonce/counter layout still reserves 8 counter bytes internally, but the shared public
* `counter` argument follows noble's strict non-wrapping 32-bit policy. See `src/_arx.ts`
* near `MAX_COUNTER` for the full counter-policy rationale.
* @param key - 16-byte or 32-byte key.
* @param nonce - 8-byte nonce.
* @param data - Input bytes to xor with the keystream.
* @param output - Optional destination buffer.
* @param counter - Initial block counter.
* @returns Encrypted or decrypted bytes.
* @example
* Encrypts bytes with the original 8-byte-nonce ChaCha variant and a fresh key/nonce.
*
* ```ts
* import { chacha20orig } from '@noble/ciphers/chacha.js';
* import { randomBytes } from '@noble/ciphers/utils.js';
* const key = randomBytes(32);
* const nonce = randomBytes(8);
* chacha20orig(key, nonce, new Uint8Array(4));
* ```
*/
export const chacha20orig = /* @__PURE__ */ createCipher(chachaCore, {
counterRight: false,
counterLength: 8,
allowShortKeys: true,
});
/**
* ChaCha stream cipher. Conforms to RFC 8439 (IETF, TLS). 12-byte nonce, 4-byte counter.
* With smaller nonce, it's not safe to make it random (CSPRNG), due to collision chance.
* @param key - 32-byte key.
* @param nonce - 12-byte nonce.
* @param data - Input bytes to xor with the keystream.
* @param output - Optional destination buffer.
* @param counter - Initial block counter.
* @returns Encrypted or decrypted bytes.
* @example
* Encrypts bytes with the RFC 8439 ChaCha20 stream cipher and a fresh key/nonce.
*
* ```ts
* import { chacha20 } from '@noble/ciphers/chacha.js';
* import { randomBytes } from '@noble/ciphers/utils.js';
* const key = randomBytes(32);
* const nonce = randomBytes(12);
* chacha20(key, nonce, new Uint8Array(4));
* ```
*/
export const chacha20 = /* @__PURE__ */ createCipher(chachaCore, {
counterRight: false,
counterLength: 4,
allowShortKeys: false,
});
/**
* XChaCha eXtended-nonce ChaCha. With 24-byte nonce, it's safe to make it random (CSPRNG).
* See {@link https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha | the IRTF draft}.
* The nonce/counter layout still reserves 8 counter bytes internally, but the shared public
* `counter` argument follows noble's strict non-wrapping 32-bit policy. See `src/_arx.ts`
* near `MAX_COUNTER` for the full counter-policy rationale.
* @param key - 32-byte key.
* @param nonce - 24-byte extended nonce.
* @param data - Input bytes to xor with the keystream.
* @param output - Optional destination buffer.
* @param counter - Initial block counter.
* @returns Encrypted or decrypted bytes.
* @example
* Encrypts bytes with XChaCha20 using a fresh key and random 24-byte nonce.
*
* ```ts
* import { xchacha20 } from '@noble/ciphers/chacha.js';
* import { randomBytes } from '@noble/ciphers/utils.js';
* const key = randomBytes(32);
* const nonce = randomBytes(24);
* xchacha20(key, nonce, new Uint8Array(4));
* ```
*/
export const xchacha20 = /* @__PURE__ */ createCipher(chachaCore, {
counterRight: false,
counterLength: 8,
extendNonceFn: hchacha,
allowShortKeys: false,
});
/**
* Reduced 8-round chacha, described in original paper.
* @param key - 32-byte key.
* @param nonce - 12-byte nonce.
* @param data - Input bytes to xor with the keystream.
* @param output - Optional destination buffer.
* @param counter - Initial block counter.
* @returns Encrypted or decrypted bytes.
* @example
* Uses the reduced 8-round variant for non-critical workloads with a fresh key/nonce.
*
* ```ts
* import { chacha8 } from '@noble/ciphers/chacha.js';
* import { randomBytes } from '@noble/ciphers/utils.js';
* const key = randomBytes(32);
* const nonce = randomBytes(12);
* chacha8(key, nonce, new Uint8Array(4));
* ```
*/
export const chacha8 = /* @__PURE__ */ createCipher(chachaCore, {
counterRight: false,
counterLength: 4,
rounds: 8,
});
/**
* Reduced 12-round chacha, described in original paper.
* @param key - 32-byte key.
* @param nonce - 12-byte nonce.
* @param data - Input bytes to xor with the keystream.
* @param output - Optional destination buffer.
* @param counter - Initial block counter.
* @returns Encrypted or decrypted bytes.
* @example
* Uses the reduced 12-round variant for non-critical workloads with a fresh key/nonce.
*
* ```ts
* import { chacha12 } from '@noble/ciphers/chacha.js';
* import { randomBytes } from '@noble/ciphers/utils.js';
* const key = randomBytes(32);
* const nonce = randomBytes(12);
* chacha12(key, nonce, new Uint8Array(4));
* ```
*/
export const chacha12 = /* @__PURE__ */ createCipher(chachaCore, {
counterRight: false,
counterLength: 4,
rounds: 12,
});
// Test-only hook: exposes the unrolled production core so tests can compare it
// with the simple/reference core from `test/misc/micro-ciphers.ts`.
export const __TESTS = /* @__PURE__ */ Object.freeze({ chachaCore });
// RFC 8439 §2.8.1 pad16(x): shared zero block for AAD/ciphertext padding.
const ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
// RFC 8439 §2.8 / §2.8.1: aligned inputs add nothing, otherwise append 16-(len%16) zero bytes.
const updatePadded = (h, msg) => {
h.update(msg);
const leftover = msg.length % 16;
if (leftover)
h.update(ZEROS16.subarray(leftover));
};
// RFC 8439 §2.6.1 poly1305_key_gen returns `block[0..31]`, so AEAD key
// generation only needs 32 zero bytes.
const ZEROS32 = /* @__PURE__ */ new Uint8Array(32);
function computeTag(fn, key, nonce, ciphertext, AAD) {
if (AAD !== undefined)
abytes(AAD, undefined, 'AAD');
// RFC 8439 §2.6 / §2.8: derive the Poly1305 one-time key from counter 0,
// then MAC AAD || pad16(AAD) || ciphertext || pad16(ciphertext) || len(AAD) || len(ciphertext).
const authKey = fn(key, nonce, ZEROS32);
const lengths = u64Lengths(ciphertext.length, AAD ? AAD.length : 0, true);
// Methods below can be replaced with
// `return poly1305_computeTag_small(authKey, lengths, ciphertext, AAD)`
// from `test/misc/micro-ciphers.ts`.
const h = poly1305.create(authKey);
if (AAD)
updatePadded(h, AAD);
updatePadded(h, ciphertext);
h.update(lengths);
const res = h.digest();
clean(authKey, lengths);
return res;
}
/**
* AEAD algorithm from RFC 8439.
* Salsa20 and chacha (RFC 8439) use poly1305 differently.
* We could have composed them, but it's hard because of authKey:
* In salsa20, authKey changes position in salsa stream.
* In chacha, authKey can't be computed inside computeTag, it modifies the counter.
*/
export const _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
// This borrows caller key/nonce/AAD buffers by reference; mutating them after construction
// changes future encrypt/decrypt results.
const tagLength = 16;
return {
encrypt(plaintext, output) {
const plength = plaintext.length;
output = getOutput(plength + tagLength, output, false);
output.set(plaintext);
const oPlain = output.subarray(0, -tagLength);
// RFC 8439 §2.8: payload encryption starts at counter 1 because counter 0 produced the OTK.
xorStream(key, nonce, oPlain, oPlain, 1);
const tag = computeTag(xorStream, key, nonce, oPlain, AAD);
output.set(tag, plength); // append tag
clean(tag);
return output;
},
decrypt(ciphertext, output) {
output = getOutput(ciphertext.length - tagLength, output, false);
const data = ciphertext.subarray(0, -tagLength);
const passedTag = ciphertext.subarray(-tagLength);
const tag = computeTag(xorStream, key, nonce, data, AAD);
// RFC 8439 §2.8 / §4: authenticate ciphertext before decrypting it, and compare tags with
// the constant-time equalBytes() helper rather than decrypting speculative plaintext first.
if (!equalBytes(passedTag, tag)) {
clean(tag);
throw new Error('invalid tag');
}
output.set(ciphertext.subarray(0, -tagLength));
// Actual decryption
xorStream(key, nonce, output, output, 1); // start stream with i=1
clean(tag);
return output;
},
};
};
/**
* ChaCha20-Poly1305 from RFC 8439.
*
* Unsafe to use random nonces under the same key, due to collision chance.
* Prefer XChaCha instead.
* @param key - 32-byte key.
* @param nonce - 12-byte nonce.
* @param AAD - Additional authenticated data.
* @returns AEAD cipher instance.
* @example
* Encrypts and authenticates plaintext with a fresh key and nonce.
*
* ```ts
* import { chacha20poly1305 } from '@noble/ciphers/chacha.js';
* import { randomBytes } from '@noble/ciphers/utils.js';
* const key = randomBytes(32);
* const nonce = randomBytes(12);
* const aad = new TextEncoder().encode('session metadata');
* const cipher = chacha20poly1305(key, nonce, aad);
* cipher.encrypt(new Uint8Array([1, 2, 3]));
* ```
*/
export const chacha20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 12, tagLength: 16, withAAD: true },
/* @__PURE__ */ _poly1305_aead(chacha20));
/**
* XChaCha20-Poly1305 extended-nonce chacha.
*
* Can be safely used with random nonces (CSPRNG).
* See {@link https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha | the IRTF draft}.
* @param key - 32-byte key.
* @param nonce - 24-byte nonce.
* @param AAD - Additional authenticated data.
* @returns AEAD cipher instance.
* @example
* Encrypts and authenticates plaintext with a fresh key and random 24-byte nonce.
*
* ```ts
* import { xchacha20poly1305 } from '@noble/ciphers/chacha.js';
* import { randomBytes } from '@noble/ciphers/utils.js';
* const key = randomBytes(32);
* const nonce = randomBytes(24);
* const aad = new TextEncoder().encode('session metadata');
* const cipher = xchacha20poly1305(key, nonce, aad);
* cipher.encrypt(new Uint8Array([1, 2, 3]));
* ```
*/
export const xchacha20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 24, tagLength: 16, withAAD: true },
/* @__PURE__ */ _poly1305_aead(xchacha20));
/**
* Chacha20 CSPRNG (cryptographically secure pseudorandom number generator).
* It's best to limit usage to non-production, non-critical cases: for example, test-only.
* Compatible with libtomcrypt. It does not have a specification, so unclear how secure it is.
* @param seed - Optional seed bytes mixed into the internal `key || nonce` state. When omitted,
* only 32 random bytes are mixed into the 40-byte state.
* @returns Seeded concrete `_XorStreamPRG` instance, including `clone()`.
* @example
* Seeds the test-only ChaCha20 DRBG from fresh entropy.
*
* ```ts
* import { rngChacha20 } from '@noble/ciphers/chacha.js';
* import { randomBytes } from '@noble/ciphers/utils.js';
* const seed = randomBytes(32);
* const prg = rngChacha20(seed);
* prg.randomBytes(8);
* ```
*/
export const rngChacha20 = /* @__PURE__ */ createPRG(chacha20orig, 64, 32, 8);
/**
* Chacha20/8 CSPRNG (cryptographically secure pseudorandom number generator).
* It's best to limit usage to non-production, non-critical cases: for example, test-only.
* Faster than `rngChacha20`.
* @param seed - Optional seed bytes mixed into the internal `key || nonce` state. When omitted,
* only 32 random bytes are mixed into the 44-byte state.
* @returns Seeded concrete `_XorStreamPRG` instance, including `clone()`.
* @example
* Seeds the faster test-only ChaCha8 DRBG from fresh entropy.
*
* ```ts
* import { rngChacha8 } from '@noble/ciphers/chacha.js';
* import { randomBytes } from '@noble/ciphers/utils.js';
* const seed = randomBytes(32);
* const prg = rngChacha8(seed);
* prg.randomBytes(8);
* ```
*/
export const rngChacha8 = /* @__PURE__ */ createPRG(chacha8, 64, 32, 12);
+5 -892
View File
@@ -1,896 +1,9 @@
/**
* Utilities for hex, bytes, CSPRNG.
* @module
*/
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
export function aarray(item, title, inner = () => { }) {
if (!Array.isArray(item))
throw new TypeError(`"${title}" expected array, got type=${typeof item}`);
for (let i = 0; i < item.length; i++)
inner(item[i], `${title}[${i}]`);
return item;
}
/**
* Checks if something is Uint8Array. Be careful: nodejs Buffer will return true.
* @param a - Value to inspect.
* @returns `true` when the value is a Uint8Array view, including Node's `Buffer`.
* @example
* Guards a value before treating it as raw key material.
*
* ```ts
* isBytes(new Uint8Array());
* ```
*/
export function isBytes(a) {
// Plain `instanceof Uint8Array` is too strict for some Buffer / proxy /
// cross-realm cases. The fallback still requires a real ArrayBuffer view
// so plain JSON-deserialized `{ constructor: ... }`
// spoofing is rejected, and `BYTES_PER_ELEMENT === 1` keeps the fallback on byte-oriented views.
return (a instanceof Uint8Array ||
(ArrayBuffer.isView(a) &&
a.constructor.name === 'Uint8Array' &&
'BYTES_PER_ELEMENT' in a &&
a.BYTES_PER_ELEMENT === 1));
}
// Shared error-message prefix builder. Only called on throw paths, so assert
// success paths never pay for the string concatenation.
const atitle = (title) => (title ? `"${title}" ` : '');
/**
* Asserts something is boolean.
* @param value - Value to validate.
* @returns The validated boolean.
* @throws On wrong argument types. {@link TypeError}
* @example
* Validates a boolean option before branching on it.
*
* ```ts
* abool(true);
* ```
*/
export function abool(value, title = '') {
if (typeof value !== 'boolean')
throw new TypeError(atitle(title) + 'expected boolean, got type=' + typeof value);
return value;
}
/**
* Asserts something is a non-negative safe integer.
* @param n - Value to validate.
* @returns The validated number.
* @throws On wrong argument types. {@link TypeError}
* @throws On wrong argument ranges or values. {@link RangeError}
* @example
* Validates a non-negative length or counter.
*
* ```ts
* anumber(1);
* ```
*/
export function anumber(n, title = '') {
if (typeof n !== 'number')
throw new TypeError(atitle(title) + 'expected number, got ' + typeof n);
if (!Number.isSafeInteger(n) || n < 0)
throw new RangeError(atitle(title) + 'expected integer >= 0, got ' + n);
return n;
}
/**
* Asserts something is Uint8Array.
* @param value - Value to validate.
* @param length - Expected byte length.
* @param title - Optional label used in error messages.
* @returns The validated byte array.
* On Node, `Buffer` is accepted too because it is a Uint8Array view.
* @throws On wrong argument types. {@link TypeError}
* @throws On wrong argument lengths. {@link RangeError}
* @example
* Validates a fixed-length nonce or key buffer.
*
* ```ts
* abytes(new Uint8Array([1, 2]), 2);
* ```
*/
export function abytes(value, length, title = '') {
// Success path first: this runs at the start of every update() / digestInto(), and the
// common `abytes(data)` form must not pay for length handling it does not use.
if (isBytes(value) && (length === undefined || value.length === length))
return value;
// Error path: recompute freely to build the exact message.
if (length !== undefined)
anumber(length, 'length');
const bytes = isBytes(value);
const ofLen = length !== undefined ? ` of length ${length}` : '';
const got = bytes ? `length=${value.length}` : `type=${typeof value}`;
const message = atitle(title) + 'expected Uint8Array' + ofLen + ', got ' + got;
if (!bytes)
throw new TypeError(message);
throw new RangeError(message);
}
const aobject = (value, label) => {
if (value === null || typeof value !== 'object' || Array.isArray(value))
throw new TypeError(label === 'object'
? 'expected valid options object'
: `"${label}" expected object, got type=${typeof value}`);
};
/**
* Asserts a hash- or MAC-like instance has not been destroyed or finished.
* @param instance - Stateful instance to validate.
* @param checkFinished - Whether to reject finished instances.
* When `false`, only `destroyed` is checked.
* @throws If the hash instance has already been destroyed or finalized. {@link Error}
* @example
* Guards against calling `update()` or `digest()` on a finished hash.
*
* ```ts
* aexists({ destroyed: false, finished: false });
* ```
*/
export function aexists(instance, checkFinished = true) {
// Runs on every update()/digestInto(); the flags are library-owned booleans, so only their
// truthiness is checked - re-validating their type per call was pure hot-path overhead.
if (instance.destroyed)
throw new Error('hash was destroyed');
if (checkFinished && instance.finished)
throw new Error('digest() was already called');
}
/**
* Asserts output is a sufficiently-sized byte array.
* @param out - Output buffer to validate.
* @param instance - Hash-like instance providing `outputLen`.
* This is the relaxed `digestInto()`-style contract: output must be at least `outputLen`,
* unlike one-shot cipher helpers elsewhere in the repo that often require exact lengths.
* @throws On wrong argument types. {@link TypeError}
* @throws On wrong output buffer lengths. {@link RangeError}
* @example
* Verifies that a caller-provided output buffer is large enough.
*
* ```ts
* aoutput(new Uint8Array(16), { outputLen: 16 });
* ```
*/
export function aoutput(out, instance) {
abytes(out, undefined, 'output');
// `outputLen` is a library-owned readonly number; the negated comparison keeps failing fast
// when it is missing/NaN (comparisons with undefined/NaN are false) without an anumber() call.
const min = instance.outputLen;
if (!(out.length >= min)) {
throw new RangeError('"output" expected length >= ' + min);
}
}
/**
* Asserts output is a sufficiently-sized, 4-byte-aligned byte array.
* {@link aoutput} plus an {@link isAligned32} check, for `digestInto()` paths
* that write through zero-allocation `u32` word views.
* @param out - Output buffer to validate.
* @param instance - Hash-like instance providing `outputLen`.
* @throws On wrong argument types. {@link TypeError}
* @throws On wrong output buffer lengths. {@link RangeError}
* @throws On wrong output buffer alignment. {@link Error}
* @example
* Verifies that a caller-provided output buffer is large enough and aligned.
*
* ```ts
* aoutput32(new Uint8Array(16), { outputLen: 16 });
* ```
*/
export function aoutput32(out, instance) {
aoutput(out, instance);
if (!isAligned32(out))
throw new Error('invalid output, must be aligned');
}
/**
* Casts a typed-array view to Uint8Array.
* @param arr - Typed-array view to reinterpret.
* @returns Uint8Array view over the same bytes.
* @example
* Views 32-bit words as raw bytes without copying.
*
* ```ts
* u8(new Uint32Array([1]));
* ```
*/
export function u8(arr) {
return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
}
/**
* Casts a typed-array view to Uint32Array.
* @param arr - Typed-array view to reinterpret.
* @returns Uint32Array view over the same bytes. Callers are expected to provide a
* 4-byte-aligned offset; trailing `1..3` bytes are silently dropped.
* @example
* Views a byte buffer as 32-bit words for block processing.
*
* ```ts
* u32(new Uint8Array(4));
* ```
*/
export function u32(arr) {
return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
}
/**
* Zeroizes typed arrays in place.
* Warning: JS provides no guarantees.
* @param arrays - Arrays to wipe.
* @example
* Wipes a temporary key buffer after use.
*
* ```ts
* const bytes = new Uint8Array([1]);
* clean(bytes);
* ```
*/
export function clean(...arrays) {
for (let i = 0; i < arrays.length; i++) {
arrays[i].fill(0);
}
}
/**
* Creates a DataView for byte-level manipulation.
* @param arr - Typed-array view to wrap.
* @returns DataView over the same bytes.
* @example
* Creates an endian-aware view for length encoding.
*
* ```ts
* createView(new Uint8Array(4));
* ```
*/
export function createView(arr) {
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
}
/**
* Whether the current platform is little-endian.
* Most are; some IBM systems are not.
*/
export const isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44)();
/**
* Reverses byte order of one 32-bit word.
* @param word - Unsigned 32-bit word to swap.
* @returns The same word with bytes reversed.
* @example
* Swaps a big-endian word into little-endian byte order.
*
* ```ts
* byteSwap(0x11223344);
* ```
*/
export function byteSwap(word) {
return (((word << 24) & 0xff000000) |
((word << 8) & 0xff0000) |
((word >>> 8) & 0xff00) |
((word >>> 24) & 0xff));
}
/**
* Normalizes one 32-bit word to the little-endian representation expected by cipher cores.
* @param n - Unsigned 32-bit word to normalize.
* @returns Little-endian normalized word on big-endian hosts, else the input word unchanged.
* @example
* Normalizes a host-endian word before passing it into an ARX/AES core.
*
* ```ts
* swap8IfBE(0x11223344);
* ```
*/
export const swap8IfBE = isLE
? (n) => n
: (n) => byteSwap(n) >>> 0;
/**
* Byte-swaps every word of a Uint32Array in place.
* @param arr - Uint32Array whose words should be swapped.
* @returns The same array after in-place byte swapping.
* @example
* Swaps every 32-bit word in a word-view buffer.
*
* ```ts
* byteSwap32(new Uint32Array([0x11223344]));
* ```
*/
export function byteSwap32(arr) {
for (let i = 0; i < arr.length; i++) {
arr[i] = byteSwap(arr[i]);
}
return arr;
}
/**
* Normalizes a Uint32Array view to the little-endian representation expected by cipher cores.
* @param u - Word view to normalize in place.
* @returns Little-endian normalized word view.
* @example
* Normalizes a word-view buffer before block processing.
*
* ```ts
* swap32IfBE(new Uint32Array([0x11223344]));
* ```
*/
export const swap32IfBE = isLE
? (u) => u
: byteSwap32;
// Built-in hex conversion:
// {@link https://caniuse.com/mdn-javascript_builtins_uint8array_fromhex | caniuse entry}
const hasHexBuiltin = /* @__PURE__ */ (() =>
// @ts-ignore
typeof Uint8Array.from([]).toHex === 'function' && typeof Uint8Array.fromHex === 'function')();
// Array where index 0xf0 (240) is mapped to string 'f0'
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'));
/**
* Convert byte array to hex string. Uses built-in function, when available.
* @param bytes - Bytes to encode.
* @returns Lowercase hexadecimal string.
* @throws On wrong argument types. {@link TypeError}
* @example
* Formats ciphertext bytes for logs or test vectors.
*
* ```ts
* bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])); // 'cafe0123'
* ```
*/
// Stub placeholder for noble-ciphers utils module
export function bytesToHex(bytes) {
abytes(bytes);
// @ts-ignore
if (hasHexBuiltin)
return bytes.toHex();
// pre-caching improves the speed 6x
let hex = '';
for (let i = 0; i < bytes.length; i++) {
hex += hexes[bytes[i]];
}
return hex;
return Array.from(bytes).map(b => b.toString(16).padStart(2, "0")).join("");
}
// Strict ASCII nibble parser: non-ASCII hex lookalikes are rejected as undefined.
// ASCII codes: '0'..'9' = 48..57, 'A'..'F' = 65..70, 'a'..'f' = 97..102.
// prettier-ignore
function asciiToBase16(ch) {
return ch >= 48 && ch <= 57 ? ch - 48 // '2' => 50-48
: ch >= 65 && ch <= 70 ? ch - (65 - 10) // 'B' => 66-(65-10)
: ch >= 97 && ch <= 102 ? ch - (97 - 10) // 'b' => 98-(97-10)
: undefined;
}
/**
* Convert hex string to byte array. Uses built-in function, when available.
* @param hex - hexadecimal string to decode
* @returns Decoded bytes.
* @throws On wrong argument types. {@link TypeError}
* @throws On wrong argument ranges or values. {@link RangeError}
* @example
* Decode lowercase hexadecimal into bytes.
* ```ts
* hexToBytes('cafe0123'); // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
* ```
*/
export function hexToBytes(hex) {
if (typeof hex !== 'string')
throw new TypeError('hex string expected, got ' + typeof hex);
if (hasHexBuiltin) {
try {
return Uint8Array.fromHex(hex);
}
catch (error) {
if (error instanceof SyntaxError)
throw new RangeError(error.message);
throw error;
}
}
const hl = hex.length;
const al = hl / 2;
if (hl % 2)
throw new RangeError('hex string expected, got unpadded hex of length ' + hl);
const array = new Uint8Array(al);
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
const n1 = asciiToBase16(hex.charCodeAt(hi)); // parse first char, multiply it by 16
const n2 = asciiToBase16(hex.charCodeAt(hi + 1)); // parse second char
if (n1 === undefined || n2 === undefined) {
const char = hex[hi] + hex[hi + 1];
throw new RangeError('hex string expected, got non-hex character "' + char + '" at index ' + hi);
}
array[ai] = n1 * 16 + n2; // example: 'A9' => 10*16 + 9
}
return array;
}
const _0n = /* @__PURE__ */ BigInt(0);
// Used in ff1, via bytesToNumberBE
/**
* Converts a big-endian hex string into bigint.
* @param hex - Hexadecimal string without `0x`.
* @returns Parsed bigint value. The empty string is treated as `0n`.
* @throws On wrong argument types. {@link TypeError}
* @example
* Parses a big-endian field element or counter from hex.
*
* ```ts
* hexToNumber('ff');
* ```
*/
export function hexToNumber(hex) {
if (typeof hex !== 'string')
throw new TypeError('hex string expected, got ' + typeof hex);
// Numeric parser, not byte-hex decoder: odd-length forms like 'f' are valid,
// and malformed syntax follows BigInt's native error behavior.
return hex === '' ? _0n : BigInt('0x' + hex); // Big Endian
}
// Used in ff1
// BE: Big Endian, LE: Little Endian
/**
* Converts big-endian bytes into bigint.
* @param bytes - Big-endian bytes.
* @returns Parsed bigint value. Empty input is treated as `0n`.
* @throws On invalid byte input passed to the internal hex conversion. {@link TypeError}
* @example
* Reads a big-endian integer from serialized bytes.
*
* ```ts
* bytesToNumberBE(new Uint8Array([1, 0]));
* ```
*/
export function bytesToNumberBE(bytes) {
return hexToNumber(bytesToHex(bytes));
}
/**
* Validates that a value is a non-negative bigint or safe integer.
* @param n - Value to validate.
* @returns The same validated value.
* @throws On wrong argument ranges or values. {@link RangeError}
*/
function abignumber(n) {
if (typeof n === 'bigint') {
if (!(_0n <= n))
throw new RangeError('positive bigint expected, got ' + n);
}
else
anumber(n);
return n;
}
// Used in ff1
/**
* Converts a number into big-endian bytes of fixed length.
* @param n - Number to encode.
* @param len - Output length in bytes. Must be greater than zero.
* @returns Big-endian bytes padded to `len`.
* Negative values, `len = 0`, and values that do not fit are rejected before
* downstream hex parsing.
* @throws On wrong argument ranges or values. {@link RangeError}
* @throws If a documented runtime validation or state check fails. {@link Error}
* @example
* Encodes a counter as fixed-width big-endian bytes.
*
* ```ts
* numberToBytesBE(1, 2);
* ```
*/
export function numberToBytesBE(n, len) {
anumber(len);
if (len === 0)
throw new Error('zero output length is invalid');
n = abignumber(n);
const expectedLen = len * 2;
const hex = n.toString(16);
// Detect overflow before hex parsing so oversized values don't leak the shared odd-hex error.
if (hex.length > expectedLen)
throw new RangeError('number is too large');
return hexToBytes(hex.padStart(expectedLen, '0'));
}
/**
* Converts string to bytes using UTF8 encoding.
* @param str - String to encode.
* @returns UTF-8 bytes in a detached fresh Uint8Array copy.
* @throws On wrong argument types. {@link TypeError}
* @example
* Encodes application text before encryption or MACing.
*
* ```ts
* utf8ToBytes('abc'); // new Uint8Array([97, 98, 99])
* ```
*/
export function utf8ToBytes(str) {
if (typeof str !== 'string')
throw new TypeError('string expected');
return new Uint8Array(new TextEncoder().encode(str)); // {@link https://bugzil.la/1681809 | Firefox bug 1681809}
}
/**
* Converts bytes to string using UTF8 encoding.
* @param bytes - UTF-8 bytes.
* @returns Decoded string. Input validation is delegated to `TextDecoder`, and malformed
* UTF-8 is replacement-decoded instead of rejected.
* @example
* Decodes UTF-8 plaintext back into a string.
*
* ```ts
* bytesToUtf8(new Uint8Array([97, 98, 99])); // 'abc'
* ```
*/
export function bytesToUtf8(bytes) {
return new TextDecoder().decode(bytes);
}
/**
* Checks if two U8A use same underlying buffer and overlaps.
* This is invalid and can corrupt data.
* @param a - First byte view.
* @param b - Second byte view.
* @returns `true` when the views overlap in memory.
* @example
* Detects whether two slices alias the same backing buffer.
*
* ```ts
* overlapBytes(new Uint8Array(4), new Uint8Array(4));
* ```
*/
export function overlapBytes(a, b) {
// Zero-length views cannot overwrite anything, even if their offset sits inside another range.
if (!a.byteLength || !b.byteLength)
return false;
return (a.buffer === b.buffer && // best we can do, may fail with an obscure Proxy
a.byteOffset < b.byteOffset + b.byteLength && // a starts before b end
b.byteOffset < a.byteOffset + a.byteLength // b starts before a end
);
}
/**
* If input and output overlap and input starts before output, we will overwrite end of input before
* we start processing it, so this is not supported by forward-processing ciphers.
* @param input - Input bytes.
* @param output - Output bytes.
* @throws If the output view would overwrite unread input bytes. {@link Error}
* @example
* Rejects an in-place layout that would overwrite unread input bytes.
*
* ```ts
* const buffer = new Uint8Array(8);
* complexOverlapBytes(buffer.subarray(0, 4), buffer.subarray(2, 6));
* ```
*/
export function complexOverlapBytes(input, output) {
// This is very cursed. It works somehow, but I'm completely unsure,
// reasoning about overlapping aligned windows is very hard.
if (overlapBytes(input, output) && input.byteOffset < output.byteOffset)
throw new Error('complex overlap of input and output is not supported');
}
/**
* Copies several Uint8Arrays into one.
* @param arrays - Byte arrays to concatenate.
* @returns Combined byte array.
* @throws On wrong argument types inside the byte-array list. {@link TypeError}
* @example
* Builds a `nonce || ciphertext` style buffer.
*
* ```ts
* concatBytes(new Uint8Array([1]), new Uint8Array([2]));
* ```
*/
export function concatBytes(...arrays) {
let sum = 0;
for (let i = 0; i < arrays.length; i++) {
const a = arrays[i];
abytes(a);
sum += a.length;
}
const res = new Uint8Array(sum);
for (let i = 0, pad = 0; i < arrays.length; i++) {
const a = arrays[i];
res.set(a, pad);
pad += a.length;
}
return res;
}
/**
* Merges user options into defaults.
* @param defaults - Default option values.
* @param opts - User-provided overrides.
* @returns Combined options object.
* `defaults` is a library-owned mutable object; user-provided `opts` only need to be
* object-shaped, since "plain object" checks reject valid proxy/cross-realm containers.
* The merge mutates `defaults` in place and returns the same object, so direct callers
* should pass a fresh defaults object unless they intentionally want shared state updated.
* @throws If options are missing or not an object. {@link Error}
* @example
* Applies user overrides to the default cipher options.
*
* ```ts
* checkOpts({ rounds: 20 }, { rounds: 8 });
* ```
*/
export function checkOpts(defaults, opts) {
aobject(defaults, 'defaults');
aobject(opts, 'opts');
// Mutates defaults by design. __proto__ follows Object.assign semantics here and can only
// affect this local option object; callers already control this low-level options surface.
const merged = Object.assign(defaults, opts);
return merged;
}
/**
* Compares two byte arrays in kinda constant time once lengths already match.
* @param a - First byte array.
* @param b - Second byte array.
* @returns `true` when the arrays contain the same bytes. Different lengths still return early.
* @example
* Compares an expected authentication tag with the received one.
*
* ```ts
* equalBytes(new Uint8Array([1]), new Uint8Array([1]));
* ```
*/
export function equalBytes(a, b) {
a = abytes(a);
b = abytes(b);
if (a.length !== b.length)
return false;
let diff = 0;
for (let i = 0; i < a.length; i++)
diff |= a[i] ^ b[i];
return diff === 0;
}
/**
* Wraps a keyed MAC constructor into a one-shot helper with `.create()`.
* @param keyLen - Valid probe-key length used to read static metadata once.
* The probe key is only used for `outputLen` / `blockLen`, so callers with several valid key sizes
* can pass any representative size as long as those values stay fixed.
* @param macCons - Keyed MAC constructor or factory.
* @param fromMsg - Optional adapter that derives extra constructor args from the one-shot message.
* @returns Callable MAC helper with `.create()`.
*/
export function wrapMacConstructor(keyLen, macCons, fromMsg) {
const mac = macCons;
const getArgs = (fromMsg || (() => []));
const macC = (msg, key) => mac(key, ...getArgs(msg))
.update(msg)
.digest();
const tmp = mac(new Uint8Array(keyLen), ...getArgs(new Uint8Array(0)));
macC.outputLen = tmp.outputLen;
macC.blockLen = tmp.blockLen;
macC.create = (key, ...args) => mac(key, ...args);
return macC;
}
/**
* Wraps a cipher: validates args, ensures encrypt() can only be called once.
* Used internally by the exported cipher constructors.
* Output-buffer support is inferred from the wrapped `encrypt` / `decrypt`
* arity (`fn.length === 2`), so wrapped output-capable methods must use a normal
* second parameter, not a default/rest parameter. AAD support is explicit in
* `params.withAAD`; optional AAD starts after the nonce slot when one is present.
* @__NO_SIDE_EFFECTS__
* @param params - Static cipher metadata. See {@link CipherParams}.
* @param constructor - Cipher constructor.
* @returns Wrapped constructor with validation.
*/
export const wrapCipher = (params, constructor) => {
function wrappedCipher(key, ...args) {
// Validate key
abytes(key, undefined, 'key');
// Validate nonce if nonceLength is present
if (params.nonceLength !== undefined) {
const nonce = args[0];
abytes(nonce, params.varSizeNonce ? undefined : params.nonceLength, 'nonce');
}
// Keep tag length available for decrypt-size checks after constructor validation.
const tagl = params.tagLength;
const aadStart = params.nonceLength !== undefined ? 1 : 0;
// No-AAD constructors otherwise silently ignore byte args meant as AAD.
if (!params.withAAD) {
for (let i = aadStart; i < args.length; i++)
if (isBytes(args[i]))
throw new Error('AAD not supported');
}
// Validate the first AAD slot early; rest-arg AAD constructors validate the tail themselves.
if (params.withAAD && args[aadStart] !== undefined)
abytes(args[aadStart], undefined, 'AAD');
const cipher = constructor(key, ...args);
const checkOutput = (fnLength, output) => {
if (output !== undefined) {
if (fnLength !== 2)
throw new Error('cipher output not supported');
abytes(output, undefined, 'output');
}
};
// Create wrapped cipher with validation and single-use encryption
let called = false;
const wrCipher = {
encrypt(data, output) {
if (called)
throw new Error('cannot encrypt() twice with same key + nonce');
// Any encrypt attempt consumes the instance, even if validation rejects below.
called = true;
abytes(data, undefined, 'data');
checkOutput(cipher.encrypt.length, output);
return cipher.encrypt(data, output);
},
decrypt(data, output) {
abytes(data, undefined, 'data');
if (tagl && data.length < tagl)
throw new Error('"ciphertext" expected length >= tagLength=' + tagl);
checkOutput(cipher.decrypt.length, output);
return cipher.decrypt(data, output);
},
};
return wrCipher;
}
Object.assign(wrappedCipher, params);
return wrappedCipher;
};
/**
* By default, returns u8a of length.
* When out is available, it checks it for validity and uses it.
* @param expectedLength - Required output length.
* @param out - Optional destination buffer.
* @param onlyAligned - Whether `out` must be 4-byte aligned.
* @returns Output buffer ready for writing.
* @throws On wrong argument types. {@link TypeError}
* @throws If the provided output buffer has the wrong size. {@link RangeError}
* @throws If the provided output buffer has the wrong alignment. {@link Error}
* @example
* Reuses a caller-provided output buffer when lengths match.
*
* ```ts
* getOutput(16, new Uint8Array(16));
* ```
*/
export function getOutput(expectedLength, out, onlyAligned = true) {
if (out === undefined)
return new Uint8Array(expectedLength);
// Keep Buffer/cross-realm Uint8Array support here instead of trusting a shape-compatible object.
abytes(out, expectedLength, 'output');
if (onlyAligned && !isAligned32(out))
throw new Error('invalid output, must be aligned');
return out;
}
/**
* Encodes data and AAD lengths into a 16-byte buffer.
* @param dataLength - Data length. Units are caller-defined: GCM passes bit
* lengths, ChaCha20-Poly1305 passes byte lengths — the helper writes the raw values.
* @param aadLength - AAD length, same unit convention as `dataLength`.
* The serialized block is still `aadLength || dataLength`, matching GCM/Poly1305
* conventions even though the helper parameter order is `(dataLength, aadLength)`.
* @param isLE - Whether to encode lengths as little-endian.
* @returns 16-byte length block.
* @throws On wrong argument types passed to the endian validator. {@link TypeError}
* @throws On wrong argument ranges or values. {@link RangeError}
* @example
* Builds the length block appended by GCM and Poly1305.
*
* ```ts
* u64Lengths(16, 8, true);
* ```
*/
export function u64Lengths(dataLength, aadLength, isLE) {
// Reject coercible non-number lengths like '10' and true before BigInt(...) accepts them.
anumber(dataLength);
anumber(aadLength);
abool(isLE);
const num = new Uint8Array(16);
const view = createView(num);
view.setBigUint64(0, BigInt(aadLength), isLE);
view.setBigUint64(8, BigInt(dataLength), isLE);
return num;
}
/**
* Checks whether a byte array is aligned to a 4-byte offset.
* @param bytes - Byte array to inspect.
* @returns `true` when the view is 4-byte aligned.
* @example
* Checks whether a buffer can be safely viewed as Uint32Array.
*
* ```ts
* isAligned32(new Uint8Array(4));
* ```
*/
export function isAligned32(bytes) {
return bytes.byteOffset % 4 === 0;
}
/**
* Copies bytes into a new Uint8Array.
* @param bytes - Bytes to copy.
* @returns Copied byte array.
* @throws On wrong argument types. {@link TypeError}
* @example
* Copies input into an aligned Uint8Array before block processing.
*
* ```ts
* copyBytes(new Uint8Array([1, 2]));
* ```
*/
export function copyBytes(bytes) {
// `Uint8Array.from(...)` would also accept arrays / other typed arrays. Keep this helper strict
// because callers use it at byte-validation boundaries before mutating the detached copy.
return Uint8Array.from(abytes(bytes));
}
/**
* Cryptographically secure PRNG backed by `crypto.getRandomValues`.
* @param bytesLength - number of random bytes to generate
* @returns Random bytes.
* The platform `getRandomValues()` implementation still defines any
* single-call length cap, and this helper rejects oversize requests
* with a stable library `RangeError` instead of host-specific errors.
* @throws On wrong argument types. {@link TypeError}
* @throws On wrong argument ranges or values. {@link RangeError}
* @throws If the current runtime does not provide `crypto.getRandomValues`. {@link Error}
* @example
* Generate a fresh random key or nonce.
* ```ts
* const key = randomBytes(16);
* ```
*/
export function randomBytes(bytesLength = 32) {
// Match the repo's other length-taking helpers instead of relying on Uint8Array coercion.
anumber(bytesLength, 'bytesLength');
const cr = typeof globalThis === 'object' ? globalThis.crypto : null;
if (typeof cr?.getRandomValues !== 'function')
throw new Error('crypto.getRandomValues must be defined');
// Web Cryptography API Level 2 §10.1.1:
// if `byteLength > 65536`, throw `QuotaExceededError`.
// Keep the guard explicit so callers can see the quota in code
// instead of discovering it by reading the spec or host errors.
// This wrapper surfaces the same quota as a stable library RangeError.
if (bytesLength > 65536)
throw new RangeError(`"bytesLength" expected <= 65536, got ${bytesLength}`);
return cr.getRandomValues(new Uint8Array(bytesLength));
}
/**
* Uses CSPRNG for nonce, nonce injected in ciphertext.
* For `encrypt`, a `nonceBytes`-length buffer is fetched from CSPRNG and
* prepended to encrypted ciphertext. For `decrypt`, first `nonceBytes` of ciphertext
* are treated as nonce. The wrapper always allocates a fresh `nonce || ciphertext`
* buffer on encrypt and intentionally does not support caller-provided destination buffers.
* Too-short decrypt inputs are split into short/empty nonce views and then delegated
* to the wrapped cipher instead of being rejected here first.
*
* NOTE: Under the same key, using random nonces (e.g. `managedNonce`) with AES-GCM and ChaCha
* should be limited to `2**23` (8M) messages to get a collision chance of
* `2**-50`. Stretching to `2**32` (4B) messages would raise that chance to
* `2**-33`, still negligible but creeping up.
* @param fn - Cipher constructor that expects a nonce.
* @param randomBytes_ - Random-byte source used for nonce generation.
* @returns Cipher constructor that prepends the nonce to ciphertext.
* @throws On wrong argument types. {@link TypeError}
* @throws On invalid nonce lengths observed at wrapper construction or use. {@link RangeError}
* @example
* Prepends a fresh random nonce to every ciphertext.
*
* ```ts
* import { gcm } from '@noble/ciphers/aes.js';
* import { managedNonce, randomBytes } from '@noble/ciphers/utils.js';
* const wrapped = managedNonce(gcm);
* const key = randomBytes(16);
* const ciphertext = wrapped(key).encrypt(new Uint8Array([1, 2, 3]));
* wrapped(key).decrypt(ciphertext);
* ```
*/
export function managedNonce(fn, randomBytes_ = randomBytes) {
if (typeof fn !== 'function')
throw new TypeError('"fn" expected cipher constructor, got type=' + typeof fn);
if (typeof randomBytes_ !== 'function')
throw new TypeError('"randomBytes_" expected function, got type=' + typeof randomBytes_);
const { nonceLength } = fn;
anumber(nonceLength, 'fn.nonceLength');
const addNonce = (nonce, ciphertext, plaintext) => {
const out = concatBytes(nonce, ciphertext);
// Wrapped ciphers may alias caller plaintext on encrypt(); never zero
// caller-owned buffers here.
if (!overlapBytes(plaintext, ciphertext))
ciphertext.fill(0);
return out;
};
// NOTE: we cannot support DST here, it would be mistake:
// - we don't know how much dst length cipher requires
// - nonce may unalign dst and break everything
// - we create new u8a anyway (concatBytes)
// - previously we passed all args to cipher, but that was mistake!
const res = ((key, ...args) => ({
encrypt(plaintext) {
abytes(plaintext, undefined, 'data');
const nonce = randomBytes_(nonceLength);
const encrypted = fn(key, nonce, ...args).encrypt(plaintext);
// @ts-ignore
if (encrypted instanceof Promise)
return encrypted.then((ct) => addNonce(nonce, ct, plaintext));
return addNonce(nonce, encrypted, plaintext);
},
decrypt(ciphertext) {
abytes(ciphertext, undefined, 'data');
const nonce = ciphertext.subarray(0, nonceLength);
const decrypted = ciphertext.subarray(nonceLength);
return fn(key, nonce, ...args).decrypt(decrypted);
},
}));
// Auto-nonce wrappers still preserve the wrapped payload geometry.
if ('blockSize' in fn)
res.blockSize = fn.blockSize;
if ('tagLength' in fn)
res.tagLength = fn.tagLength;
if ('withAAD' in fn)
res.withAAD = fn.withAAD;
return res;
const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
return bytes;
}