Skip to content

Commit

Permalink
Some refactoring. Improve tree-shaking
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmillr committed Feb 8, 2024
1 parent 5dbad2a commit 2df3b11
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 88 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"url": "git+https://github.com/paulmillr/noble-ciphers.git"
},
"license": "MIT",
"sideEffects": false,
"devDependencies": {
"@scure/base": "1.1.3",
"fast-check": "3.0.0",
Expand Down
11 changes: 5 additions & 6 deletions src/_assert.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
function number(n: number) {
if (!Number.isSafeInteger(n) || n < 0) throw new Error(`wrong positive integer: ${n}`);
if (!Number.isSafeInteger(n) || n < 0) throw new Error(`positive integer expected, not ${n}`);
}

function bool(b: boolean) {
if (typeof b !== 'boolean') throw new Error(`boolean expected, not ${b}`);
}

// TODO: merge with utils
function isBytes(a: unknown): a is Uint8Array {
export function isBytes(a: unknown): a is Uint8Array {
return (
a != null &&
typeof a === 'object' &&
(a instanceof Uint8Array || a.constructor.name === 'Uint8Array')
a instanceof Uint8Array ||
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array')
);
}

Expand All @@ -38,6 +36,7 @@ function exists(instance: any, checkFinished = true) {
if (instance.destroyed) throw new Error('Hash instance has been destroyed');
if (checkFinished && instance.finished) throw new Error('Hash#digest() has already been called');
}

function output(out: any, instance: any) {
bytes(out);
const min = instance.outputLen;
Expand Down
34 changes: 19 additions & 15 deletions src/_micro.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */

// micro-noble-ciphers: more auditable, but slower version of salsa20, chacha & poly1305.
// Implements the same algorithms that are present in other files,
// but without unrolled loops (https://en.wikipedia.org/wiki/Loop_unrolling).

// prettier-ignore
import {
Cipher, XorStream, createView, setBigUint64, wrapCipher,
Expand All @@ -12,6 +7,12 @@ import {
import { createCipher, rotl } from './_arx.js';
import { bytes as abytes } from './_assert.js';

/*
noble-ciphers-micro: more auditable, but slower version of salsa20, chacha & poly1305.
Implements the same algorithms that are present in other files, but without
unrolled loops (https://en.wikipedia.org/wiki/Loop_unrolling).
*/

function bytesToNumberLE(bytes: Uint8Array): bigint {
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
}
Expand Down Expand Up @@ -135,20 +136,23 @@ export function hchacha(s: Uint32Array, k: Uint32Array, i: Uint32Array, o32: Uin
/**
* salsa20, 12-byte nonce.
*/
export const salsa20 = createCipher(salsaCore, { allowShortKeys: true, counterRight: true });
export const salsa20 = /* @__PURE__ */ createCipher(salsaCore, {
allowShortKeys: true,
counterRight: true,
});

/**
* xsalsa20, 24-byte nonce.
*/
export const xsalsa20 = createCipher(salsaCore, {
export const xsalsa20 = /* @__PURE__ */ createCipher(salsaCore, {
counterRight: true,
extendNonceFn: hsalsa,
});

/**
* chacha20 non-RFC, original version by djb. 8-byte nonce, 8-byte counter.
*/
export const chacha20orig = createCipher(chachaCore, {
export const chacha20orig = /* @__PURE__ */ createCipher(chachaCore, {
allowShortKeys: true,
counterRight: false,
counterLength: 8,
Expand All @@ -157,15 +161,15 @@ export const chacha20orig = createCipher(chachaCore, {
/**
* chacha20 RFC 8439 (IETF / TLS). 12-byte nonce, 4-byte counter.
*/
export const chacha20 = createCipher(chachaCore, {
export const chacha20 = /* @__PURE__ */ createCipher(chachaCore, {
counterRight: false,
counterLength: 4,
});

/**
* xchacha20 eXtended-nonce. https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
*/
export const xchacha20 = createCipher(chachaCore, {
export const xchacha20 = /* @__PURE__ */ createCipher(chachaCore, {
counterRight: false,
counterLength: 8,
extendNonceFn: hchacha,
Expand All @@ -174,7 +178,7 @@ export const xchacha20 = createCipher(chachaCore, {
/**
* 8-round chacha from the original paper.
*/
export const chacha8 = createCipher(chachaCore, {
export const chacha8 = /* @__PURE__ */ createCipher(chachaCore, {
counterRight: false,
counterLength: 4,
rounds: 8,
Expand All @@ -183,7 +187,7 @@ export const chacha8 = createCipher(chachaCore, {
/**
* 12-round chacha from the original paper.
*/
export const chacha12 = createCipher(chachaCore, {
export const chacha12 = /* @__PURE__ */ createCipher(chachaCore, {
counterRight: false,
counterLength: 4,
rounds: 12,
Expand Down Expand Up @@ -240,7 +244,7 @@ function computeTag(
/**
* xsalsa20-poly1305 eXtended-nonce (24 bytes) salsa.
*/
export const xsalsa20poly1305 = wrapCipher(
export const xsalsa20poly1305 = /* @__PURE__ */ wrapCipher(
{ blockSize: 64, nonceLength: 24, tagLength: 16 },
function xsalsa20poly1305(key: Uint8Array, nonce: Uint8Array) {
abytes(key);
Expand Down Expand Up @@ -306,7 +310,7 @@ export const _poly1305_aead =
/**
* chacha20-poly1305 12-byte-nonce chacha.
*/
export const chacha20poly1305 = wrapCipher(
export const chacha20poly1305 = /* @__PURE__ */ wrapCipher(
{ blockSize: 64, nonceLength: 12, tagLength: 16 },
_poly1305_aead(chacha20)
);
Expand All @@ -315,7 +319,7 @@ export const chacha20poly1305 = wrapCipher(
* xchacha20-poly1305 eXtended-nonce (24 bytes) chacha.
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
*/
export const xchacha20poly1305 = wrapCipher(
export const xchacha20poly1305 = /* @__PURE__ */ wrapCipher(
{ blockSize: 64, nonceLength: 24, tagLength: 16 },
_poly1305_aead(xchacha20)
);
66 changes: 36 additions & 30 deletions src/aes.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { wrapCipher, Cipher, CipherWithOutput, equalBytes, u32, u8 } from './utils.js';
import { createView, setBigUint64 } from './utils.js';
// prettier-ignore
import {
wrapCipher, Cipher, CipherWithOutput,
createView, setBigUint64, equalBytes, u32, u8,
} from './utils.js';
import { ghash, polyval } from './_polyval.js';
import { bytes as abytes } from './_assert.js';

// AES (Advanced Encryption Standard) aka Rijndael block cipher.
//
// Data is split into 128-bit blocks. Encrypted in 10/12/14 rounds (128/192/256bit). Every round:
// 1. **S-box**, table substitution
// 2. **Shift rows**, cyclic shift left of all rows of data array
// 3. **Mix columns**, multiplying every column by fixed polynomial
// 4. **Add round key**, round_key xor i-th column of array
//
// Resources:
// - FIPS-197 https://csrc.nist.gov/files/pubs/fips/197/final/docs/fips-197.pdf
// - Original proposal: https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/aes-development/rijndael-ammended.pdf
/*
AES (Advanced Encryption Standard) aka Rijndael block cipher.
Data is split into 128-bit blocks. Encrypted in 10/12/14 rounds (128/192/256 bits). In every round:
1. **S-box**, table substitution
2. **Shift rows**, cyclic shift left of all rows of data array
3. **Mix columns**, multiplying every column by fixed polynomial
4. **Add round key**, round_key xor i-th column of array
Resources:
- FIPS-197 https://csrc.nist.gov/files/pubs/fips/197/final/docs/fips-197.pdf
- Original proposal: https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/aes-development/rijndael-ammended.pdf
*/

const BLOCK_SIZE = 16;
const BLOCK_SIZE32 = 4;
Expand All @@ -24,6 +29,7 @@ const POLY = 0x11b; // 1 + x + x**3 + x**4 + x**8
function mul2(n: number) {
return (n << 1) ^ (POLY & -(n >> 7));
}

function mul(a: number, b: number) {
let res = 0;
for (; b > 0; b >>= 1) {
Expand All @@ -36,21 +42,21 @@ function mul(a: number, b: number) {

// AES S-box is generated using finite field inversion,
// an affine transform, and xor of a constant 0x63.
const _sbox = /* @__PURE__ */ (() => {
const sbox = /* @__PURE__ */ (() => {
let t = new Uint8Array(256);
for (let i = 0, x = 1; i < 256; i++, x ^= mul2(x)) t[i] = x;
const sbox = new Uint8Array(256);
sbox[0] = 0x63; // first elm
const box = new Uint8Array(256);
box[0] = 0x63; // first elm
for (let i = 0; i < 255; i++) {
let x = t[255 - i];
x |= x << 8;
sbox[t[i]] = (x ^ (x >> 4) ^ (x >> 5) ^ (x >> 6) ^ (x >> 7) ^ 0x63) & 0xff;
box[t[i]] = (x ^ (x >> 4) ^ (x >> 5) ^ (x >> 6) ^ (x >> 7) ^ 0x63) & 0xff;
}
return sbox;
return box;
})();

// Inverted S-box
const _inv_sbox = /* @__PURE__ */ _sbox.map((_, j) => _sbox.indexOf(j));
const invSbox = /* @__PURE__ */ sbox.map((_, j) => sbox.indexOf(j));

// Rotate u32 by 8
const rotr32_8 = (n: number) => (n << 24) | (n >>> 8);
Expand Down Expand Up @@ -80,16 +86,16 @@ function genTtable(sbox: Uint8Array, fn: (n: number) => number) {
return { sbox, sbox2, T0, T1, T2, T3, T01, T23 };
}

const TABLE_ENC = /* @__PURE__ */ genTtable(
_sbox,
const tableEncoding = /* @__PURE__ */ genTtable(
sbox,
(s: number) => (mul(s, 3) << 24) | (s << 16) | (s << 8) | mul(s, 2)
);
const TABLE_DEC = /* @__PURE__ */ genTtable(
_inv_sbox,
const tableDecoding = /* @__PURE__ */ genTtable(
invSbox,
(s) => (mul(s, 11) << 24) | (mul(s, 13) << 16) | (mul(s, 9) << 8) | mul(s, 14)
);

const POWX = /* @__PURE__ */ (() => {
const xPowers = /* @__PURE__ */ (() => {
const p = new Uint8Array(16);
for (let i = 0, x = 1; i < 16; i++, x = mul2(x)) p[i] = x;
return p;
Expand All @@ -100,7 +106,7 @@ export function expandKeyLE(key: Uint8Array): Uint32Array {
const len = key.length;
if (![16, 24, 32].includes(len))
throw new Error(`aes: wrong key size: should be 16, 24 or 32, got: ${len}`);
const { sbox2 } = TABLE_ENC;
const { sbox2 } = tableEncoding;
const k32 = u32(key);
const Nk = k32.length;
const subByte = (n: number) => applySbox(sbox2, n, n, n, n);
Expand All @@ -109,7 +115,7 @@ export function expandKeyLE(key: Uint8Array): Uint32Array {
// 4.3.1 Key expansion
for (let i = Nk; i < xk.length; i++) {
let t = xk[i - 1];
if (i % Nk === 0) t = subByte(rotr32_8(t)) ^ POWX[i / Nk - 1];
if (i % Nk === 0) t = subByte(rotr32_8(t)) ^ xPowers[i / Nk - 1];
else if (Nk > 6 && i % Nk === 4) t = subByte(t);
xk[i] = xk[i - Nk] ^ t;
}
Expand All @@ -120,8 +126,8 @@ export function expandKeyDecLE(key: Uint8Array): Uint32Array {
const encKey = expandKeyLE(key);
const xk = encKey.slice();
const Nk = encKey.length;
const { sbox2 } = TABLE_ENC;
const { T0, T1, T2, T3 } = TABLE_DEC;
const { sbox2 } = tableEncoding;
const { T0, T1, T2, T3 } = tableDecoding;
// Inverse key by chunks of 4 (rounds)
for (let i = 0; i < Nk; i += 4) {
for (let j = 0; j < 4; j++) xk[i + j] = encKey[Nk - i - 4 + j];
Expand Down Expand Up @@ -159,7 +165,7 @@ function applySbox(sbox2: Uint16Array, s0: number, s1: number, s2: number, s3: n
}

function encrypt(xk: Uint32Array, s0: number, s1: number, s2: number, s3: number) {
const { sbox2, T01, T23 } = TABLE_ENC;
const { sbox2, T01, T23 } = tableEncoding;
let k = 0;
(s0 ^= xk[k++]), (s1 ^= xk[k++]), (s2 ^= xk[k++]), (s3 ^= xk[k++]);
const rounds = xk.length / 4 - 2;
Expand All @@ -179,7 +185,7 @@ function encrypt(xk: Uint32Array, s0: number, s1: number, s2: number, s3: number
}

function decrypt(xk: Uint32Array, s0: number, s1: number, s2: number, s3: number) {
const { sbox2, T01, T23 } = TABLE_DEC;
const { sbox2, T01, T23 } = tableDecoding;
let k = 0;
(s0 ^= xk[k++]), (s1 ^= xk[k++]), (s2 ^= xk[k++]), (s3 ^= xk[k++]);
const rounds = xk.length / 4 - 2;
Expand Down
8 changes: 2 additions & 6 deletions src/chacha.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
// prettier-ignore
import {
wrapCipher,
CipherWithOutput,
XorStream,
createView,
equalBytes,
setBigUint64,
wrapCipher, CipherWithOutput, XorStream, createView, equalBytes, setBigUint64,
} from './utils.js';
import { poly1305 } from './_poly1305.js';
import { createCipher, rotl } from './_arx.js';
Expand Down
10 changes: 4 additions & 6 deletions src/crypto.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
// We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
// See utils.ts for details.
declare const globalThis: Record<string, any> | undefined;
const crypto =
typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined;
const cr = typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined;

export function randomBytes(bytesLength = 32): Uint8Array {
if (crypto && typeof crypto.getRandomValues === 'function') {
return crypto.getRandomValues(new Uint8Array(bytesLength));
}
if (cr && typeof cr.getRandomValues === 'function')
return cr.getRandomValues(new Uint8Array(bytesLength));
throw new Error('crypto.getRandomValues must be defined');
}

export function getWebcryptoSubtle() {
if (crypto && typeof crypto.subtle === 'object' && crypto.subtle != null) return crypto.subtle;
if (cr && typeof cr.subtle === 'object' && cr.subtle != null) return cr.subtle;
throw new Error('crypto.subtle must be defined');
}
10 changes: 4 additions & 6 deletions src/cryptoNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,15 @@
// The file will throw on node.js 14 and earlier.
// @ts-ignore
import * as nc from 'node:crypto';
const crypto =
nc && typeof nc === 'object' && 'webcrypto' in nc ? (nc.webcrypto as any) : undefined;
const cr = nc && typeof nc === 'object' && 'webcrypto' in nc ? (nc.webcrypto as any) : undefined;

export function randomBytes(bytesLength = 32): Uint8Array {
if (crypto && typeof crypto.getRandomValues === 'function') {
return crypto.getRandomValues(new Uint8Array(bytesLength));
}
if (cr && typeof cr.getRandomValues === 'function')
return cr.getRandomValues(new Uint8Array(bytesLength));
throw new Error('crypto.getRandomValues must be defined');
}

export function getWebcryptoSubtle() {
if (crypto && typeof crypto.subtle === 'object' && crypto.subtle != null) return crypto.subtle;
if (cr && typeof cr.subtle === 'object' && cr.subtle != null) return cr.subtle;
throw new Error('crypto.subtle must be defined');
}
2 changes: 1 addition & 1 deletion src/ff1.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Cipher, bytesToNumberBE, numberToBytesBE } from './utils.js';
import { unsafe } from './aes.js';
// NOTE: no point in inlining encrypt instead encryptBlock, since BigInt stuff will be slow
// NOTE: no point in inlining encrypt instead of encryptBlock, since BigInt stuff will be slow
const { expandKeyLE, encryptBlock } = unsafe;

// Format-preserving encryption algorithm (FPE-FF1) specified in NIST Special Publication 800-38G.
Expand Down
6 changes: 3 additions & 3 deletions src/salsa.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { wrapCipher, Cipher, equalBytes } from './utils.js';
import { poly1305 } from './_poly1305.js';
import { createCipher, rotl } from './_arx.js';
import { bytes as abytes } from './_assert.js';
import { createCipher, rotl } from './_arx.js';
import { poly1305 } from './_poly1305.js';
import { wrapCipher, Cipher, equalBytes } from './utils.js';

// Salsa20 stream cipher was released in 2005.
// Salsa's goal was to implement AES replacement that does not rely on S-Boxes,
Expand Down
Loading

0 comments on commit 2df3b11

Please sign in to comment.