From b1f55c7eb504c8c5950af13daaa41fecbdd3b5bb Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Thu, 19 Oct 2023 16:04:21 +0000 Subject: [PATCH] Refactor salsa and chacha more --- benchmark/aead.js | 19 +++++++++++++++++-- src/_arx.ts | 41 ++++++++++++++++++----------------------- src/_micro.ts | 12 +++--------- src/chacha.ts | 6 +----- src/salsa.ts | 7 ++----- test/arx.test.js | 10 ++++++---- 6 files changed, 47 insertions(+), 48 deletions(-) diff --git a/benchmark/aead.js b/benchmark/aead.js index ddc462d..66c75d3 100644 --- a/benchmark/aead.js +++ b/benchmark/aead.js @@ -4,6 +4,7 @@ import { createCipheriv, createDecipheriv } from 'node:crypto'; import { concatBytes } from '@noble/ciphers/utils'; import { xchacha20poly1305, chacha20poly1305 } from '@noble/ciphers/chacha'; import { xsalsa20poly1305 } from '@noble/ciphers/salsa'; +import { gcm, siv } from '@noble/ciphers/aes'; import * as micro from '@noble/ciphers/_micro'; import { ChaCha20Poly1305 as StableChachaPoly } from '@stablelib/chacha20poly1305'; @@ -31,7 +32,7 @@ const buffers = [ let chainsafe_chacha_poly; export const ciphers = { - xsalsa20_poly1305: { + xsalsa20poly1305: { opts: { key: buf(32), nonce: buf(24) }, tweetnacl: { encrypt: (buf, opts) => tweetnacl.secretbox(buf, opts.nonce, opts.key), @@ -46,7 +47,7 @@ export const ciphers = { decrypt: (buf, opts) => micro.xsalsa20poly1305(opts.key, opts.nonce).decrypt(buf), }, }, - chacha20_poly1305: { + chacha20poly1305: { opts: { key: buf(32), nonce: buf(12) }, node: { encrypt: (buf, opts) => { @@ -103,6 +104,20 @@ export const ciphers = { decrypt: (buf, opts) => micro.xchacha20poly1305(opts.key, opts.nonce).decrypt(buf), }, }, + 'aes-256-gcm': { + opts: { key: buf(32), nonce: buf(12) }, + noble: { + encrypt: (buf, opts) => gcm(opts.key, opts.nonce).encrypt(buf), + decrypt: (buf, opts) => gcm(opts.key, opts.nonce).decrypt(buf), + }, + }, + 'aes-256-gcm-siv': { + opts: { key: buf(32), nonce: buf(12) }, + noble: { + encrypt: (buf, opts) => siv(opts.key, opts.nonce).encrypt(buf), + decrypt: (buf, opts) => siv(opts.key, opts.nonce).decrypt(buf), + }, + } }; export async function main() { diff --git a/src/_arx.ts b/src/_arx.ts index a9f31b5..e00e04c 100644 --- a/src/_arx.ts +++ b/src/_arx.ts @@ -57,9 +57,9 @@ export type CipherCoreFn = ( export type ExtendNonceFn = ( sigma: Uint32Array, - key: Uint8Array, - input: Uint8Array, - output: Uint8Array + key: Uint32Array, + input: Uint32Array, + output: Uint32Array ) => void; export type CipherOpts = { @@ -87,15 +87,13 @@ const U32_EMPTY = new Uint32Array(); function runCipher( core: CipherCoreFn, sigma: Uint32Array, - key: Uint8Array, - nonce: Uint8Array, + key: Uint32Array, + nonce: Uint32Array, data: Uint8Array, output: Uint8Array, counter: number, rounds: number ): void { - const key32 = u32(key); - const nonce32 = u32(nonce); const len = data.length; const block = new Uint8Array(BLOCK_LEN); const b32 = u32(block); @@ -104,7 +102,7 @@ function runCipher( const d32 = isAligned ? u32(data) : U32_EMPTY; const o32 = isAligned ? u32(output) : U32_EMPTY; for (let pos = 0; pos < len; counter++) { - core(sigma, key32, nonce32, b32, counter, rounds); + core(sigma, key, nonce, b32, counter, rounds); if (counter >= MAX_COUNTER) throw new Error('arx: counter overflow'); const take = Math.min(BLOCK_LEN, len - pos); // aligned to 4 bytes @@ -158,23 +156,21 @@ export function createCipher(core: CipherCoreFn, opts: CipherOpts): XorStream { // 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; - else { - // Align key to 4 bytes - k = key.slice(); - toClean.push(k); - } + let l = key.length, + k: Uint8Array, + sigma: Uint32Array; + if (l === 32) { + k = key.slice(); + toClean.push(k); sigma = sigma32_32; - } else if (key.length === 16 && allowShortKeys) { + } else if (l === 16 && allowShortKeys) { k = new Uint8Array(32); k.set(key); k.set(key, 16); sigma = sigma16_32; toClean.push(k); } else { - throw new Error(`arx: invalid 32-byte key, got length=${key.length}`); + throw new Error(`arx: invalid 32-byte key, got length=${l}`); } // Nonce @@ -189,13 +185,11 @@ export function createCipher(core: CipherCoreFn, opts: CipherOpts): XorStream { toClean.push(nonce); } + const k32 = u32(k); // hsalsa & hchacha: handle extended nonce if (extendNonceFn) { if (nonce.length !== 24) throw new Error(`arx: extended nonce must be 24 bytes`); - let _k = new Uint8Array(32); - extendNonceFn(sigma, k, nonce.subarray(0, 16), _k); - // toClean.push(k); - k = _k; + extendNonceFn(sigma, k32, u32(nonce.subarray(0, 16)), k32); nonce = nonce.subarray(16); } @@ -211,7 +205,8 @@ export function createCipher(core: CipherCoreFn, opts: CipherOpts): XorStream { nonce = nc; toClean.push(nonce); } - runCipher(core, sigma, k, nonce, data, output, counter, rounds); + const n32 = u32(nonce); + runCipher(core, sigma, k32, n32, data, output, counter, rounds); while (toClean.length > 0) toClean.pop()!.fill(0); return output; }; diff --git a/src/_micro.ts b/src/_micro.ts index 2b7b307..02cc9b1 100644 --- a/src/_micro.ts +++ b/src/_micro.ts @@ -6,7 +6,7 @@ // prettier-ignore import { - Cipher, XorStream, createView, setBigUint64, wrapCipher, u32, + Cipher, XorStream, createView, setBigUint64, wrapCipher, bytesToHex, concatBytes, ensureBytes, equalBytes, hexToNumber, numberToBytesBE, } from './utils.js'; import { createCipher, rotl } from './_arx.js'; @@ -80,10 +80,7 @@ function salsaCore( } // prettier-ignore -export function hsalsa(s: Uint32Array, key: Uint8Array, input: Uint8Array, output: Uint8Array) { - const k = u32(key); - const i = u32(input); - const o32 = u32(output); +export function hsalsa(s: Uint32Array, k: Uint32Array, i: Uint32Array, o32: Uint32Array) { const x = new Uint32Array([ s[0], k[0], k[1], k[2], k[3], s[1], i[0], i[1], @@ -119,10 +116,7 @@ function chachaCore( } // prettier-ignore -export function hchacha(s: Uint32Array, key: Uint8Array, input: Uint8Array, output: Uint8Array) { - const k = u32(key); - const i = u32(input); - const o32 = u32(output); +export function hchacha(s: Uint32Array, k: Uint32Array, i: Uint32Array, o32: Uint32Array) { const x = new Uint32Array([ s[0], s[1], s[2], s[3], k[0], k[1], k[2], k[3], diff --git a/src/chacha.ts b/src/chacha.ts index aaa587d..3db1005 100644 --- a/src/chacha.ts +++ b/src/chacha.ts @@ -6,7 +6,6 @@ import { ensureBytes, equalBytes, setBigUint64, - u32, } from './utils.js'; import { poly1305 } from './_poly1305.js'; import { createCipher, rotl } from './_arx.js'; @@ -91,11 +90,8 @@ function chachaCore( */ // prettier-ignore export function hchacha( - s: Uint32Array, key: Uint8Array, input: Uint8Array, out: Uint8Array + s: Uint32Array, k: Uint32Array, i: Uint32Array, o32: Uint32Array ) { - 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], x04 = k[0], x05 = k[1], x06 = k[2], x07 = k[3], x08 = k[4], x09 = k[5], x10 = k[6], x11 = k[7], diff --git a/src/salsa.ts b/src/salsa.ts index 60afbf5..dae8df5 100644 --- a/src/salsa.ts +++ b/src/salsa.ts @@ -1,4 +1,4 @@ -import { wrapCipher, Cipher, ensureBytes, equalBytes, u32 } from './utils.js'; +import { wrapCipher, Cipher, ensureBytes, equalBytes } from './utils.js'; import { poly1305 } from './_poly1305.js'; import { createCipher, rotl } from './_arx.js'; @@ -62,11 +62,8 @@ function salsaCore( */ // prettier-ignore export function hsalsa( - s: Uint32Array, key: Uint8Array, input: Uint8Array, out: Uint8Array + s: Uint32Array, k: Uint32Array, i: Uint32Array, o32: Uint32Array ) { - const k = u32(key); - const i = u32(input); - const o32 = u32(out); 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], diff --git a/test/arx.test.js b/test/arx.test.js index 9e8192e..d925f4f 100644 --- a/test/arx.test.js +++ b/test/arx.test.js @@ -28,6 +28,7 @@ const sigma16 = utils.utf8ToBytes('expand 16-byte k'); const sigma32 = utils.utf8ToBytes('expand 32-byte k'); const sigma16_32 = utils.u32(sigma16); const sigma32_32 = utils.u32(sigma32); +const { u32 } = utils; const getKey = (key) => { if (key.length === 32) return { key, sigma: sigma32_32 }; @@ -60,12 +61,13 @@ describe('Salsa20', () => { const src = hex.decode('fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0'); const good = 'c6cb53882782b5b86df1ab2ed9b810ec8a88c0a7f29211e693f0019fe0728858'; const dst = new Uint8Array(32); + const { key, sigma } = getKey( hex.decode('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f') ); - hsalsa(sigma, key, src, dst); + hsalsa(sigma, u32(key), u32(src), u32(dst)); deepStrictEqual(hex.encode(dst), good); - slow.hsalsa(sigma, key, src, dst); + slow.hsalsa(sigma, u32(key), u32(src), u32(dst)); deepStrictEqual(hex.encode(dst), good); }); should('xsalsa20', () => { @@ -112,10 +114,10 @@ describe('chacha', () => { const nonce = hex.decode('000000090000004a0000000031415927'); const good = '82413b4227b27bfed30e42508a877d73a0f9e4d58a74a853c12ec41326d3ecdc'; const subkey = new Uint8Array(32); - hchacha(sigma, key, nonce.subarray(0, 16), subkey); + hchacha(sigma, u32(key), u32(nonce.subarray(0, 16)), u32(subkey)); deepStrictEqual(hex.encode(subkey), good); const subkeySlow = new Uint8Array(32); - slow.hchacha(sigma, key, nonce.subarray(0, 16), subkeySlow); + slow.hchacha(sigma, u32(key), u32(nonce.subarray(0, 16)), u32(subkeySlow)); deepStrictEqual(hex.encode(subkeySlow), good); });