From a5802076771bb6a2b4e180425172869d581ad58e Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Thu, 19 Oct 2023 15:01:09 +0000 Subject: [PATCH] Refactor salsa and chacha --- README.md | 61 ++++++++++++++++++++++----------- src/_arx.ts | 22 +++++++----- src/_micro.ts | 88 +++++++++++++++++++++++++++--------------------- src/chacha.ts | 48 +++++++++++--------------- src/ff1.ts | 2 +- src/salsa.ts | 55 ++++++++++++------------------ src/utils.ts | 7 ++-- test/arx.test.js | 18 ++++++---- 8 files changed, 160 insertions(+), 141 deletions(-) diff --git a/README.md b/README.md index eb56267..0032329 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ import { xchacha20poly1305 } from '@noble/ciphers/chacha'; - [Examples](#examples) - [Encrypt and decrypt with ChaCha20-Poly1305](#encrypt-and-decrypt-with-chacha20-poly1305) - - [Encrypt and decrypt text with AES-GCM-256](#encrypt-and-decrypt-text-with-aes-gcm-256) + - [Encrypt and decrypt with AES-256-GCM](#encrypt-and-decrypt-with-aes-256-gcm) - [Securely generate random key and nonce](#securely-generate-random-key-and-nonce) - [Use managed nonce](#use-managed-nonce) - [Implementations](#implementations) @@ -68,38 +68,55 @@ import { xchacha20poly1305 } from '@noble/ciphers/chacha'; ## Examples -#### Encrypt and decrypt with ChaCha20-Poly1305 +#### Encrypt with XChaCha20-Poly1305 ```js import { xchacha20poly1305 } from '@noble/ciphers/chacha'; -import { bytesToHex, hexToBytes, bytesToUtf8, utf8ToBytes } from '@noble/ciphers/utils'; -const key = hexToBytes('4b7f89bac90a1086fef73f5da2cbe93b2fae9dfbf7678ae1f3e75fd118ddf999'); -const nonce = hexToBytes('9610467513de0bbd7c4cc2c3c64069f1802086fbd3232b13'); +import { hexToBytes, utf8ToBytes } from '@noble/ciphers/utils'; +import { randomBytes } from '@noble/ciphers/webcrypto/utils'; +const key = randomBytes(32); +const nonce = randomBytes(24); const chacha = xchacha20poly1305(key, nonce); const data = utf8ToBytes('hello, noble'); const ciphertext = chacha.encrypt(data); -const data_ = chacha.decrypt(ciphertext); // bytesToUtf8(data_) === data +const data_ = chacha.decrypt(ciphertext); // utils.bytesToUtf8(data_) === data ``` -#### Encrypt and decrypt text with AES-GCM-256 +#### Encrypt with AES-256-GCM ```js import { gcm } from '@noble/ciphers/aes'; -import { bytesToHex, hexToBytes, bytesToUtf8, utf8ToBytes } from '@noble/ciphers/utils'; +import { hexToBytes, utf8ToBytes } from '@noble/ciphers/utils'; +import { randomBytes } from '@noble/ciphers/webcrypto/utils'; const key = hexToBytes('5296fb2c5ceab0f59367994e5d81d9014027255f12336fabcd29596c2e9ecd87'); const nonce = hexToBytes('9610467513de0bbd7c4cc2c3c64069f1802086fbd3232b13'); const aes = gcm(key, nonce); const data = utf8ToBytes('hello, noble'); const ciphertext = aes.encrypt(data); -const data_ = aes.decrypt(ciphertext); // bytesToUtf8(data_) === data +const data_ = aes.decrypt(ciphertext); // utils.bytesToUtf8(data_) === data ``` -#### Use managed nonce +#### Securely generate random key and nonce + +```js +import { xchacha20poly1305 } from '@noble/ciphers/chacha'; +import { randomBytes } from '@noble/ciphers/webcrypto/utils'; +const key = hexToBytes('4b7f89bac90a1086fef73f5da2cbe93b2fae9dfbf7678ae1f3e75fd118ddf999'); +const nonce = hexToBytes('9610467513de0bbd7c4cc2c3c64069f1802086fbd3232b13'); +// const rkey = randomBytes(32); +// const rnonce = randomBytes(24); +const chacha = xchacha20poly1305(rkey, rnonce); +const data = utf8ToBytes('hello, noble'); +const ciphertext = chacha.encrypt(data); +const plaintext = chacha.decrypt(ciphertext); +``` + +#### Encrypt without nonce ```js import { xchacha20poly1305 } from '@noble/ciphers/chacha'; import { managedNonce } from '@noble/ciphers/webcrypto/utils' -import { bytesToHex, hexToBytes, utf8ToBytes } from '@noble/ciphers/utils'; +import { hexToBytes, utf8ToBytes } from '@noble/ciphers/utils'; const key = hexToBytes('fa686bfdffd3758f6377abbc23bf3d9bdc1a0dda4a6e7f8dbdd579fa1ff6d7e1'); const chacha = managedNonce(xchacha20poly1305)(key); // manages nonces for you const data = utf8ToBytes('hello, noble'); @@ -107,17 +124,21 @@ const ciphertext = chacha.encrypt(data); const data_ = chacha.decrypt(ciphertext); ``` -#### Securely generate random key and nonce +#### All imports ```js -import { xchacha20poly1305 } from '@noble/ciphers/chacha'; -import { randomBytes } from '@noble/ciphers/webcrypto/utils'; -const rkey = randomBytes(32); -const rnonce = randomBytes(24); -const chacha = xchacha20poly1305(rkey, rnonce); -const data = utf8ToBytes('hello, noble'); -const ciphertext = chacha.encrypt(data); -const plaintext = chacha.decrypt(ciphertext); +import { gcm, siv } from '@noble/ciphers/aes'; +import { xsalsa20poly1305 } from '@noble/ciphers/salsa'; +import { chacha20poly1305, xchacha20poly1305 } from '@noble/ciphers/chacha'; + +// Unauthenticated encryption: make sure to use HMAC or similar +import { ctr, cbc, ecb } from '@noble/ciphers/aes'; +import { salsa20, xsalsa20 } from '@noble/ciphers/salsa'; +import { chacha20, xchacha20, chacha8, chacha12 } from '@noble/ciphers/chacha'; + +// Utilities +import { bytesToHex, hexToBytes, bytesToUtf8, utf8ToBytes } from '@noble/ciphers/utils'; +import { managedNonce, randomBytes } from '@noble/ciphers/webcrypto/utils'; ``` ## Implementations diff --git a/src/_arx.ts b/src/_arx.ts index 2c7a101..a9f31b5 100644 --- a/src/_arx.ts +++ b/src/_arx.ts @@ -42,6 +42,10 @@ const sigma32 = utf8ToBytes('expand 32-byte k'); const sigma16_32 = u32(sigma16); const sigma32_32 = u32(sigma32); +export function rotl(a: number, b: number): number { + return (a << b) | (a >>> (32 - b)); +} + export type CipherCoreFn = ( sigma: Uint32Array, key: Uint32Array, @@ -55,8 +59,8 @@ export type ExtendNonceFn = ( sigma: Uint32Array, key: Uint8Array, input: Uint8Array, - out: Uint8Array -) => Uint8Array; + output: Uint8Array +) => void; export type CipherOpts = { allowShortKeys?: boolean; // Original salsa / chacha allow 16-byte keys @@ -152,10 +156,8 @@ export function createCipher(core: CipherCoreFn, opts: CipherOpts): XorStream { const toClean = []; // Key & sigma - // // key=16 -> sigma16, k=key|key // key=32 -> sigma32, k=key - let k: Uint8Array, sigma: Uint32Array; if (key.length === 32) { if (isAligned32(key)) k = key; @@ -171,16 +173,16 @@ export function createCipher(core: CipherCoreFn, opts: CipherOpts): XorStream { k.set(key, 16); sigma = sigma16_32; toClean.push(k); - } else throw new Error(`arx: invalid 32-byte key, got length=${key.length}`); + } else { + throw new Error(`arx: invalid 32-byte key, got length=${key.length}`); + } // 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) - // Align nonce to 4 bytes if (!isAligned32(nonce)) { nonce = nonce.slice(); @@ -190,8 +192,10 @@ export function createCipher(core: CipherCoreFn, opts: CipherOpts): XorStream { // hsalsa & hchacha: handle extended nonce if (extendNonceFn) { if (nonce.length !== 24) throw new Error(`arx: extended nonce must be 24 bytes`); - k = extendNonceFn(sigma, k, nonce.subarray(0, 16), new Uint8Array(32)); - toClean.push(k); + let _k = new Uint8Array(32); + extendNonceFn(sigma, k, nonce.subarray(0, 16), _k); + // toClean.push(k); + k = _k; nonce = nonce.subarray(16); } diff --git a/src/_micro.ts b/src/_micro.ts index 26c4f2d..2b7b307 100644 --- a/src/_micro.ts +++ b/src/_micro.ts @@ -6,23 +6,19 @@ // prettier-ignore import { - Cipher, XorStream, createView, setBigUint64, wrapCipher, - bytesToNumberLE, concatBytes, ensureBytes, equalBytes, numberToBytesBE, u32, u8, + Cipher, XorStream, createView, setBigUint64, wrapCipher, u32, + bytesToHex, concatBytes, ensureBytes, equalBytes, hexToNumber, numberToBytesBE, } from './utils.js'; -import { createCipher } from './_arx.js'; +import { createCipher, rotl } from './_arx.js'; -// Utils -// function bytesToNumberLE(bytes: Uint8Array): bigint { -// return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse())); -// } +function bytesToNumberLE(bytes: Uint8Array): bigint { + return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse())); +} function numberToBytesLE(n: number | bigint, len: number): Uint8Array { return numberToBytesBE(n, len).reverse(); } -const rotl = (a: number, b: number) => (a << b) | (a >>> (32 - b)); -// /Utils - function salsaQR(x: Uint32Array, a: number, b: number, c: number, d: number) { x[b] ^= rotl((x[a] + x[d]) | 0, 7); x[c] ^= rotl((x[b] + x[a]) | 0, 9); @@ -38,7 +34,7 @@ function chachaQR(x: Uint32Array, a: number, b: number, c: number, d: number) { } function salsaRound(x: Uint32Array, rounds = 20) { - for (let i = 0; i < rounds; i += 2) { + for (let r = 0; r < rounds; r += 2) { salsaQR(x, 0, 4, 8, 12); salsaQR(x, 5, 9, 13, 1); salsaQR(x, 10, 14, 2, 6); @@ -51,7 +47,7 @@ function salsaRound(x: Uint32Array, rounds = 20) { } function chachaRound(x: Uint32Array, rounds = 20) { - for (let i = 0; i < rounds; i += 2) { + for (let r = 0; r < rounds; r += 2) { chachaQR(x, 0, 4, 8, 12); chachaQR(x, 1, 5, 9, 13); chachaQR(x, 2, 6, 10, 14); @@ -64,7 +60,7 @@ function chachaRound(x: Uint32Array, rounds = 20) { } function salsaCore( - c: Uint32Array, + s: Uint32Array, k: Uint32Array, n: Uint32Array, out: Uint32Array, @@ -73,32 +69,37 @@ function salsaCore( ): void { // prettier-ignore const y = new Uint32Array([ - c[0], k[0], k[1], k[2], // "expa" Key Key Key - k[3], c[1], n[0], n[1], // Key "nd 3" Nonce Nonce - cnt, 0 , c[2], k[4], // Pos. Pos. "2-by" Key - k[5], k[6], k[7], c[3], // Key Key Key "te k" + s[0], k[0], k[1], k[2], // "expa" Key Key Key + k[3], s[1], n[0], n[1], // Key "nd 3" Nonce Nonce + cnt, 0 , s[2], k[4], // Pos. Pos. "2-by" Key + k[5], k[6], k[7], s[3], // Key Key Key "te k" ]); const x = y.slice(); salsaRound(x, rounds); for (let i = 0; i < 16; i++) out[i] = (y[i] + x[i]) | 0; } -export function hsalsa(c: Uint32Array, key: Uint8Array, nonce: Uint8Array): Uint8Array { +// prettier-ignore +export function hsalsa(s: Uint32Array, key: Uint8Array, input: Uint8Array, output: Uint8Array) { const k = u32(key); - const i = u32(nonce); - // prettier-ignore + const i = u32(input); + const o32 = u32(output); const x = new Uint32Array([ - c[0], k[0], k[1], k[2], - k[3], c[1], i[0], i[1], - i[2], i[3], c[2], k[4], - k[5], k[6], k[7], c[3] + s[0], k[0], k[1], k[2], + k[3], s[1], i[0], i[1], + i[2], i[3], s[2], k[4], + k[5], k[6], k[7], s[3] ]); - salsaRound(x); - return u8(new Uint32Array([x[0], x[5], x[10], x[15], x[6], x[7], x[8], x[9]])); + salsaRound(x, 20); + let oi = 0; + o32[oi++] = x[0]; o32[oi++] = x[5]; + o32[oi++] = x[10]; o32[oi++] = x[15]; + o32[oi++] = x[6]; o32[oi++] = x[7]; + o32[oi++] = x[8]; o32[oi++] = x[9]; } function chachaCore( - c: Uint32Array, + s: Uint32Array, k: Uint32Array, n: Uint32Array, out: Uint32Array, @@ -107,7 +108,7 @@ function chachaCore( ): void { // prettier-ignore const y = new Uint32Array([ - c[0], c[1], c[2], c[3], // "expa" "nd 3" "2-by" "te k" + s[0], s[1], s[2], s[3], // "expa" "nd 3" "2-by" "te k" k[0], k[1], k[2], k[3], // Key Key Key Key k[4], k[5], k[6], k[7], // Key Key Key Key cnt, n[0], n[1], n[2], // Counter Counter Nonce Nonce @@ -117,18 +118,23 @@ function chachaCore( for (let i = 0; i < 16; i++) out[i] = (y[i] + x[i]) | 0; } -export function hchacha(c: Uint32Array, key: Uint8Array, nonce: Uint8Array): Uint8Array { +// prettier-ignore +export function hchacha(s: Uint32Array, key: Uint8Array, input: Uint8Array, output: Uint8Array) { const k = u32(key); - const i = u32(nonce); - // prettier-ignore + const i = u32(input); + const o32 = u32(output); const x = new Uint32Array([ - c[0], c[1], c[2], c[3], + s[0], s[1], s[2], s[3], k[0], k[1], k[2], k[3], k[4], k[5], k[6], k[7], i[0], i[1], i[2], i[3], ]); - chachaRound(x); - return u8(new Uint32Array([x[0], x[1], x[2], x[3], x[12], x[13], x[14], x[15]])); + chachaRound(x, 20); + let oi = 0; + o32[oi++] = x[0]; o32[oi++] = x[1]; + o32[oi++] = x[2]; o32[oi++] = x[3]; + o32[oi++] = x[12]; o32[oi++] = x[13]; + o32[oi++] = x[14]; o32[oi++] = x[15]; } /** @@ -152,6 +158,7 @@ export const chacha20orig = createCipher(chachaCore, { counterRight: false, counterLength: 8, }); + /** * chacha20 RFC 8439 (IETF / TLS). 12-byte nonce, 4-byte counter. */ @@ -187,19 +194,22 @@ export const chacha12 = createCipher(chachaCore, { rounds: 12, }); -const POW_2_130_5 = 2n ** 130n - 5n; -const POW_2_128_1 = 2n ** (16n * 8n) - 1n; +const POW_2_130_5 = BigInt(2) ** BigInt(130) - BigInt(5); +const POW_2_128_1 = BigInt(2) ** BigInt(16 * 8) - BigInt(1); +const CLAMP_R = BigInt('0x0ffffffc0ffffffc0ffffffc0fffffff'); +const _0 = BigInt(0); +const _1 = BigInt(1); // Can be speed-up using BigUint64Array, but would be more complicated export function poly1305(msg: Uint8Array, key: Uint8Array): Uint8Array { ensureBytes(msg); ensureBytes(key); - let acc = 0n; - const r = bytesToNumberLE(key.subarray(0, 16)) & 0x0ffffffc0ffffffc0ffffffc0fffffffn; + let acc = _0; + const r = bytesToNumberLE(key.subarray(0, 16)) & CLAMP_R; const s = bytesToNumberLE(key.subarray(16)); // Process by 16 byte chunks for (let i = 0; i < msg.length; i += 16) { const m = msg.subarray(i, i + 16); - const n = bytesToNumberLE(m) | (1n << BigInt(8 * m.length)); + const n = bytesToNumberLE(m) | (_1 << BigInt(8 * m.length)); acc = ((acc + n) * r) % POW_2_130_5; } const res = (acc + s) & POW_2_128_1; diff --git a/src/chacha.ts b/src/chacha.ts index 5de02ba..aaa587d 100644 --- a/src/chacha.ts +++ b/src/chacha.ts @@ -9,15 +9,12 @@ import { u32, } from './utils.js'; import { poly1305 } from './_poly1305.js'; -import { createCipher } from './_arx.js'; +import { createCipher, rotl } from './_arx.js'; // ChaCha20 stream cipher was released in 2008. ChaCha aims to increase // the diffusion per round, but had slightly less cryptanalysis. // https://cr.yp.to/chacha.html, http://cr.yp.to/chacha/chacha-20080128.pdf -// Left rotate for uint32 -const rotl = (a: number, b: number) => (a << b) | (a >>> (32 - b)); - /** * ChaCha core function. */ @@ -25,17 +22,16 @@ const rotl = (a: number, b: number) => (a << b) | (a >>> (32 - b)); function chachaCore( s: Uint32Array, k: Uint32Array, n: Uint32Array, out: Uint32Array, cnt: number, rounds = 20 ): void { - let y00 = s[0], y01 = s[1], y02 = s[2], y03 = s[3]; // "expa" "nd 3" "2-by" "te k" - let y04 = k[0], y05 = k[1], y06 = k[2], y07 = k[3]; // Key Key Key Key - let y08 = k[4], y09 = k[5], y10 = k[6], y11 = k[7]; // Key Key Key Key - let y12 = cnt, y13 = n[0], y14 = n[1], y15 = n[2]; // Counter Counter Nonce Nonce + 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 Counter 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; - // Main loop - for (let i = 0; i < rounds; i += 2) { + 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); @@ -95,16 +91,16 @@ function chachaCore( */ // prettier-ignore export function hchacha( - s: Uint32Array, key: Uint8Array, src: Uint8Array, out: Uint8Array -): Uint8Array { - const k32 = u32(key); - const i32 = u32(src); + s: Uint32Array, key: Uint8Array, input: Uint8Array, out: Uint8Array +) { + const k = u32(key); + const i = u32(input); const o32 = u32(out); - let x00 = s[0], x01 = s[1], x02 = s[2], x03 = s[3]; - let x04 = k32[0], x05 = k32[1], x06 = k32[2], x07 = k32[3]; - let x08 = k32[4], x09 = k32[5], x10 = k32[6], x11 = k32[7] - let x12 = i32[0], x13 = i32[1], x14 = i32[2], x15 = i32[3]; - for (let i = 0; i < 20; i += 2) { + let x00 = s[0], x01 = s[1], x02 = s[2], x03 = s[3], + x04 = k[0], x05 = k[1], x06 = k[2], x07 = k[3], + x08 = k[4], x09 = k[5], x10 = k[6], x11 = k[7], + x12 = i[0], x13 = i[1], x14 = i[2], x15 = i[3]; + for (let r = 0; r < 20; 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); @@ -145,15 +141,11 @@ export function hchacha( x03 = (x03 + x04) | 0; x14 = rotl(x14 ^ x03, 8); x09 = (x09 + x14) | 0; x04 = rotl(x04 ^ x09, 7); } - o32[0] = x00; - o32[1] = x01; - o32[2] = x02; - o32[3] = x03; - o32[4] = x12; - o32[5] = x13; - o32[6] = x14; - o32[7] = x15; - return out; + let oi = 0; + o32[oi++] = x00; o32[oi++] = x01; + o32[oi++] = x02; o32[oi++] = x03; + o32[oi++] = x12; o32[oi++] = x13; + o32[oi++] = x14; o32[oi++] = x15; } /** * Original, non-RFC chacha20 from DJB. 8-byte nonce, 8-byte counter. diff --git a/src/ff1.ts b/src/ff1.ts index e502d8a..7e5e06e 100644 --- a/src/ff1.ts +++ b/src/ff1.ts @@ -17,7 +17,7 @@ function mod(a: any, b: any): number | bigint { } function NUMradix(radix: number, data: number[]): bigint { - let res = 0n; + let res = BigInt(0); for (let i of data) res = res * BigInt(radix) + BigInt(i); return res; } diff --git a/src/salsa.ts b/src/salsa.ts index 79cae3a..60afbf5 100644 --- a/src/salsa.ts +++ b/src/salsa.ts @@ -1,34 +1,30 @@ import { wrapCipher, Cipher, ensureBytes, equalBytes, u32 } from './utils.js'; import { poly1305 } from './_poly1305.js'; -import { createCipher } from './_arx.js'; +import { createCipher, rotl } from './_arx.js'; // Salsa20 stream cipher was released in 2005. // Salsa's goal was to implement AES replacement that does not rely on S-Boxes, // which are hard to implement in a constant-time manner. // https://cr.yp.to/snuffle.html, https://cr.yp.to/snuffle/salsafamily-20071225.pdf -// Left rotate for uint32 -const rotl = (a: number, b: number) => (a << b) | (a >>> (32 - b)); - /** * Salsa20 core function. */ // prettier-ignore function salsaCore( - s: Uint32Array, k: Uint32Array, i: Uint32Array, out: Uint32Array, cnt: number, rounds = 20 + s: Uint32Array, k: Uint32Array, n: Uint32Array, out: Uint32Array, cnt: number, rounds = 20 ): void { // Based on https://cr.yp.to/salsa20.html - let y00 = s[0], y01 = k[0], y02 = k[1], y03 = k[2]; // "expa" Key Key Key - let y04 = k[3], y05 = s[1], y06 = i[0], y07 = i[1]; // Key "nd 3" Nonce Nonce - let y08 = cnt, y09 = 0 , y10 = s[2], y11 = k[4]; // Pos. Pos. "2-by" Key - let y12 = k[5], y13 = k[6], y14 = k[7], y15 = s[3]; // Key Key Key "te k" + let y00 = s[0], y01 = k[0], y02 = k[1], y03 = k[2], // "expa" Key Key Key + y04 = k[3], y05 = s[1], y06 = n[0], y07 = n[1], // Key "nd 3" Nonce Nonce + y08 = cnt, y09 = 0 , y10 = s[2], y11 = k[4], // Pos. Pos. "2-by" Key + y12 = k[5], y13 = k[6], y14 = k[7], y15 = s[3]; // Key Key Key "te k" // 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; - // Main loop - for (let i = 0; i < rounds; i += 2) { + for (let r = 0; r < rounds; r += 2) { x04 ^= rotl(x00 + x12 | 0, 7); x08 ^= rotl(x04 + x00 | 0, 9); x12 ^= rotl(x08 + x04 | 0, 13); x00 ^= rotl(x12 + x08 | 0, 18); x09 ^= rotl(x05 + x01 | 0, 7); x13 ^= rotl(x09 + x05 | 0, 9); @@ -66,17 +62,16 @@ function salsaCore( */ // prettier-ignore export function hsalsa( - s: Uint32Array, key: Uint8Array, nonce: Uint8Array, out: Uint8Array -): Uint8Array { - const k32 = u32(key); - const i32 = u32(nonce); + s: Uint32Array, key: Uint8Array, input: Uint8Array, out: Uint8Array +) { + const k = u32(key); + const i = u32(input); const o32 = u32(out); - let x00 = s[0], x01 = k32[0], x02 = k32[1], x03 = k32[2], x04 = k32[3]; - let x05 = s[1], x06 = i32[0], x07 = i32[1], x08 = i32[2], x09 = i32[3]; - let x10 = s[2], x11 = k32[4], x12 = k32[5], x13 = k32[6], x14 = k32[7]; - let x15 = s[3]; - // Main loop - for (let i = 0; i < 20; i += 2) { + let x00 = s[0], x01 = k[0], x02 = k[1], x03 = k[2], + x04 = k[3], x05 = s[1], x06 = i[0], x07 = i[1], + x08 = i[2], x09 = i[3], x10 = s[2], x11 = k[4], + x12 = k[5], x13 = k[6], x14 = k[7], x15 = s[3]; + for (let r = 0; r < 20; r += 2) { x04 ^= rotl(x00 + x12 | 0, 7); x08 ^= rotl(x04 + x00 | 0, 9); x12 ^= rotl(x08 + x04 | 0, 13); x00 ^= rotl(x12 + x08 | 0, 18); x09 ^= rotl(x05 + x01 | 0, 7); x13 ^= rotl(x09 + x05 | 0, 9); @@ -94,15 +89,11 @@ export function hsalsa( x12 ^= rotl(x15 + x14 | 0, 7); x13 ^= rotl(x12 + x15 | 0, 9); x14 ^= rotl(x13 + x12 | 0, 13); x15 ^= rotl(x14 + x13 | 0, 18); } - o32[0] = x00; - o32[1] = x05; - o32[2] = x10; - o32[3] = x15; - o32[4] = x06; - o32[5] = x07; - o32[6] = x08; - o32[7] = x09; - return out; + let oi = 0; + o32[oi++] = x00; o32[oi++] = x05; + o32[oi++] = x10; o32[oi++] = x15; + o32[oi++] = x06; o32[oi++] = x07; + o32[oi++] = x08; o32[oi++] = x09; } /** @@ -128,7 +119,7 @@ export const xsalsa20 = /* @__PURE__ */ createCipher(salsaCore, { * With 24-byte nonce, it's safe to use fill it with random (CSPRNG). * Also known as secretbox from libsodium / nacl. */ -export const xsalsa20poly1305 = wrapCipher( +export const xsalsa20poly1305 = /* @__PURE__ */ wrapCipher( { blockSize: 64, nonceLength: 24, tagLength: 16 }, (key: Uint8Array, nonce: Uint8Array): Cipher => { const tagLength = 16; @@ -184,8 +175,6 @@ export const xsalsa20poly1305 = wrapCipher( * Alias to xsalsa20poly1305, for compatibility with libsodium / nacl */ export function secretbox(key: Uint8Array, nonce: Uint8Array) { - ensureBytes(key); - ensureBytes(nonce); const xs = xsalsa20poly1305(key, nonce); return { seal: xs.encrypt, open: xs.decrypt }; } diff --git a/src/utils.ts b/src/utils.ts index 911b7d4..718a7a5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -78,10 +78,6 @@ export function bytesToNumberBE(bytes: Uint8Array): bigint { return hexToNumber(bytesToHex(bytes)); } -export function bytesToNumberLE(bytes: Uint8Array): bigint { - return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse())); -} - export function numberToBytesBE(n: number | bigint, len: number): Uint8Array { return hexToBytes(n.toString(16).padStart(len * 2, '0')); } @@ -129,7 +125,8 @@ export type Input = Uint8Array | string; */ export function toBytes(data: Input): Uint8Array { if (typeof data === 'string') data = utf8ToBytes(data); - if (!u8a(data)) throw new Error(`expected Uint8Array, got ${typeof data}`); + else if (u8a(data)) data = data.slice(); + else throw new Error(`expected Uint8Array, got ${typeof data}`); return data; } diff --git a/test/arx.test.js b/test/arx.test.js index f1758bb..9e8192e 100644 --- a/test/arx.test.js +++ b/test/arx.test.js @@ -63,8 +63,10 @@ describe('Salsa20', () => { const { key, sigma } = getKey( hex.decode('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f') ); - deepStrictEqual(hex.encode(hsalsa(sigma, key, src, dst)), good); - deepStrictEqual(hex.encode(slow.hsalsa(sigma, key, src, dst)), good); + hsalsa(sigma, key, src, dst); + deepStrictEqual(hex.encode(dst), good); + slow.hsalsa(sigma, key, src, dst); + deepStrictEqual(hex.encode(dst), good); }); should('xsalsa20', () => { const key = hex.decode('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'); @@ -75,9 +77,11 @@ describe('Salsa20', () => { 'de32fc544f4f95576e2614377049c258664845a93d5ff5dd479cfeb55c7' + '579b60d419b8a8c03da3494993577b4597dcb658be52ab7'; const dst = new Uint8Array(good.length / 2); - deepStrictEqual(hex.encode(xsalsa20(key, nonce, dst)), good); + xsalsa20(key, nonce, dst, dst); + deepStrictEqual(hex.encode(dst), good); const dst2 = new Uint8Array(good.length / 2); - deepStrictEqual(hex.encode(slow.xsalsa20(key, nonce, dst2)), good); + slow.xsalsa20(key, nonce, dst2, dst2); + deepStrictEqual(hex.encode(dst2), good); }); }); @@ -107,9 +111,11 @@ describe('chacha', () => { ); const nonce = hex.decode('000000090000004a0000000031415927'); const good = '82413b4227b27bfed30e42508a877d73a0f9e4d58a74a853c12ec41326d3ecdc'; - const subkey = hchacha(sigma, key, nonce.subarray(0, 16), new Uint8Array(32)); + const subkey = new Uint8Array(32); + hchacha(sigma, key, nonce.subarray(0, 16), subkey); deepStrictEqual(hex.encode(subkey), good); - const subkeySlow = slow.hchacha(sigma, key, nonce.subarray(0, 16), new Uint8Array(32)); + const subkeySlow = new Uint8Array(32); + slow.hchacha(sigma, key, nonce.subarray(0, 16), subkeySlow); deepStrictEqual(hex.encode(subkeySlow), good); });