UNPKG

xsalsa20-csprng

Version:
541 lines (470 loc) 15 kB
// TODO: Web assembly is more advantageous for large inputs. Creating a web // assembly version. // // benchmark with 4B input, 300000 iterations: // xsalsa20: 98.654ms // wasm: 139.835ms // // benchmark with 4096B input, 30000 iterations: // xsalsa20: 804.314ms // wasm: 325.942ms // // benchmark with 41943040B input, 3 iterations: // xsalsa20: 828.65ms // wasm: 332.185ms // IE11 support declare global { interface WindowOrWorkerGlobalScope { readonly msCrypto: Crypto } } export default class XSalsa20CSPRNG { private xsalsa: XSalsa20GeneratorInt32 constructor() { const buf = new Uint8Array(24 + 32) // IE11 does support web workers but Web Crypto API is not available inside // Web Workers. So we just use window.crypto instead of globalThis.crypto // // IE11 does not support `globalThis`. So `window.msCrypto` should come // first. const crypto = window.msCrypto || globalThis.crypto crypto.getRandomValues(buf) const nonce = buf.slice(0, 24) const key = buf.slice(24) this.xsalsa = xsalsa20GeneratorInt32(nonce, key) } static of(nonce: Uint8Array, key: Uint8Array): XSalsa20CSPRNG { const self = Object.create(XSalsa20CSPRNG.prototype) self.xsalsa = xsalsa20GeneratorInt32(nonce, key) return self } randomInt32(): number { return this.xsalsa.next().value } randomUint32(): number { return this.xsalsa.next().value + 2 ** 31 } uniformInt(exclusive_upper_bound: number): number { if (exclusive_upper_bound < 2) return 0 const min = 2 ** 32 % exclusive_upper_bound let r: number do { r = this.randomUint32() } while (r < min) return r % exclusive_upper_bound } } type XSalsa20Generator = Generator<Uint8Array, never, undefined> function* xsalsa20Generator( nonce: Uint8Array, key: Uint8Array, ): XSalsa20Generator { const s = new Uint8Array(32) const z = new Uint8Array(16) // biome-ignore format: binary const SIGMA = new Uint8Array([ 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x20, 0x33, 0x32, 0x2d, 0x62, 0x79, 0x74, 0x65, 0x20, 0x6b, ]) core_hsalsa20(s, nonce, key, SIGMA) for (let i = 0; i < 8; i++) z[i] = nonce[i + 16] while (true) { const output = new Uint8Array(64) core_salsa20(output, z, s, SIGMA) yield output let u = 1 for (let i = 8; i < 16; i++) { u += (z[i] & 0xff) | 0 z[i] = u & 0xff u >>>= 8 } } } type XSalsa20GeneratorInt32 = Generator<number, never, undefined> function* xsalsa20GeneratorInt32( nonce: Uint8Array, key: Uint8Array, ): XSalsa20GeneratorInt32 { const generator = xsalsa20Generator(nonce, key) while (true) { const b = generator.next().value // biome-ignore format: vertically aligned yield* [ b[ 0] | b[ 1] << 8 | b[ 2] << 16 | b[ 3] << 24, b[ 4] | b[ 5] << 8 | b[ 6] << 16 | b[ 7] << 24, b[ 8] | b[ 9] << 8 | b[10] << 16 | b[11] << 24, b[12] | b[13] << 8 | b[14] << 16 | b[15] << 24, b[16] | b[17] << 8 | b[18] << 16 | b[19] << 24, b[20] | b[21] << 8 | b[22] << 16 | b[23] << 24, b[24] | b[25] << 8 | b[26] << 16 | b[27] << 24, b[28] | b[29] << 8 | b[30] << 16 | b[31] << 24, b[32] | b[33] << 8 | b[34] << 16 | b[35] << 24, b[36] | b[37] << 8 | b[38] << 16 | b[39] << 24, b[40] | b[41] << 8 | b[42] << 16 | b[43] << 24, b[44] | b[45] << 8 | b[46] << 16 | b[47] << 24, b[48] | b[49] << 8 | b[50] << 16 | b[51] << 24, b[52] | b[53] << 8 | b[54] << 16 | b[55] << 24, b[56] | b[57] << 8 | b[58] << 16 | b[59] << 24, b[60] | b[61] << 8 | b[62] << 16 | b[63] << 24, ] } } export class XSalsa20 { private xsalsa: XSalsa20Generator private buffer: Uint8Array constructor(nonce: Uint8Array, key: Uint8Array) { // Check parameter if (nonce.length !== 24) throw new Error('nonce must be 24 bytes') if (key.length !== 32) throw new Error('key must be 32 bytes') // Initialize this.xsalsa = xsalsa20Generator(nonce, key) this.buffer = new Uint8Array(0) } stream(length: number): Uint8Array { let output: Uint8Array let counter: number const bufLength = this.buffer.length if (bufLength > 0) { if (length < bufLength) { output = this.buffer.slice(0, length) this.buffer = this.buffer.slice(length) return output } else if (length === bufLength) { output = this.buffer this.buffer = new Uint8Array(0) return output } else { output = new Uint8Array(length) output.set(this.buffer) counter = bufLength this.buffer = new Uint8Array(0) } } else { output = new Uint8Array(length) counter = 0 } while (length - counter >= 64) { output.set(this.xsalsa.next().value, counter) counter += 64 } const remain = length - counter if (remain > 0) { const buffer = this.xsalsa.next().value output.set(buffer.slice(0, remain), counter) this.buffer = buffer.slice(remain) } return output } update( input: Uint8Array, output: Uint8Array = new Uint8Array(input.length), ): Uint8Array { const stream = this.stream(input.length) for (let i = 0; i < input.length; ++i) output[i] = input[i] ^ stream[i] // Return return output } } // below methods are ported from tweet nacl function core_salsa20( o: Uint8Array, p: Uint8Array, k: Uint8Array, c: Uint8Array, ): void { // biome-ignore format: vertically aligned const j0 = c[ 0] | (c[ 1] << 8) | (c[ 2] << 16) | (c[ 3] << 24), j1 = k[ 0] | (k[ 1] << 8) | (k[ 2] << 16) | (k[ 3] << 24), j2 = k[ 4] | (k[ 5] << 8) | (k[ 6] << 16) | (k[ 7] << 24), j3 = k[ 8] | (k[ 9] << 8) | (k[10] << 16) | (k[11] << 24), j4 = k[12] | (k[13] << 8) | (k[14] << 16) | (k[15] << 24), j5 = c[ 4] | (c[ 5] << 8) | (c[ 6] << 16) | (c[ 7] << 24), j6 = p[ 0] | (p[ 1] << 8) | (p[ 2] << 16) | (p[ 3] << 24), j7 = p[ 4] | (p[ 5] << 8) | (p[ 6] << 16) | (p[ 7] << 24), j8 = p[ 8] | (p[ 9] << 8) | (p[10] << 16) | (p[11] << 24), j9 = p[12] | (p[13] << 8) | (p[14] << 16) | (p[15] << 24), j10 = c[ 8] | (c[ 9] << 8) | (c[10] << 16) | (c[11] << 24), j11 = k[16] | (k[17] << 8) | (k[18] << 16) | (k[19] << 24), j12 = k[20] | (k[21] << 8) | (k[22] << 16) | (k[23] << 24), j13 = k[24] | (k[25] << 8) | (k[26] << 16) | (k[27] << 24), j14 = k[28] | (k[29] << 8) | (k[30] << 16) | (k[31] << 24), j15 = c[12] | (c[13] << 8) | (c[14] << 16) | (c[15] << 24) // biome-ignore format: compact let x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7, x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14, x15 = j15, u: number for (let i = 0; i < 20; i += 2) { u = (x0 + x12) | 0 x4 ^= (u << 7) | (u >>> 25) u = (x4 + x0) | 0 x8 ^= (u << 9) | (u >>> 23) u = (x8 + x4) | 0 x12 ^= (u << 13) | (u >>> 19) u = (x12 + x8) | 0 x0 ^= (u << 18) | (u >>> 14) u = (x5 + x1) | 0 x9 ^= (u << 7) | (u >>> 25) u = (x9 + x5) | 0 x13 ^= (u << 9) | (u >>> 23) u = (x13 + x9) | 0 x1 ^= (u << 13) | (u >>> 19) u = (x1 + x13) | 0 x5 ^= (u << 18) | (u >>> 14) u = (x10 + x6) | 0 x14 ^= (u << 7) | (u >>> 25) u = (x14 + x10) | 0 x2 ^= (u << 9) | (u >>> 23) u = (x2 + x14) | 0 x6 ^= (u << 13) | (u >>> 19) u = (x6 + x2) | 0 x10 ^= (u << 18) | (u >>> 14) u = (x15 + x11) | 0 x3 ^= (u << 7) | (u >>> 25) u = (x3 + x15) | 0 x7 ^= (u << 9) | (u >>> 23) u = (x7 + x3) | 0 x11 ^= (u << 13) | (u >>> 19) u = (x11 + x7) | 0 x15 ^= (u << 18) | (u >>> 14) u = (x0 + x3) | 0 x1 ^= (u << 7) | (u >>> 25) u = (x1 + x0) | 0 x2 ^= (u << 9) | (u >>> 23) u = (x2 + x1) | 0 x3 ^= (u << 13) | (u >>> 19) u = (x3 + x2) | 0 x0 ^= (u << 18) | (u >>> 14) u = (x5 + x4) | 0 x6 ^= (u << 7) | (u >>> 25) u = (x6 + x5) | 0 x7 ^= (u << 9) | (u >>> 23) u = (x7 + x6) | 0 x4 ^= (u << 13) | (u >>> 19) u = (x4 + x7) | 0 x5 ^= (u << 18) | (u >>> 14) u = (x10 + x9) | 0 x11 ^= (u << 7) | (u >>> 25) u = (x11 + x10) | 0 x8 ^= (u << 9) | (u >>> 23) u = (x8 + x11) | 0 x9 ^= (u << 13) | (u >>> 19) u = (x9 + x8) | 0 x10 ^= (u << 18) | (u >>> 14) u = (x15 + x14) | 0 x12 ^= (u << 7) | (u >>> 25) u = (x12 + x15) | 0 x13 ^= (u << 9) | (u >>> 23) u = (x13 + x12) | 0 x14 ^= (u << 13) | (u >>> 19) u = (x14 + x13) | 0 x15 ^= (u << 18) | (u >>> 14) } x0 = (x0 + j0) | 0 x1 = (x1 + j1) | 0 x2 = (x2 + j2) | 0 x3 = (x3 + j3) | 0 x4 = (x4 + j4) | 0 x5 = (x5 + j5) | 0 x6 = (x6 + j6) | 0 x7 = (x7 + j7) | 0 x8 = (x8 + j8) | 0 x9 = (x9 + j9) | 0 x10 = (x10 + j10) | 0 x11 = (x11 + j11) | 0 x12 = (x12 + j12) | 0 x13 = (x13 + j13) | 0 x14 = (x14 + j14) | 0 x15 = (x15 + j15) | 0 o[0] = (x0 >>> 0) & 0xff o[1] = (x0 >>> 8) & 0xff o[2] = (x0 >>> 16) & 0xff o[3] = (x0 >>> 24) & 0xff o[4] = (x1 >>> 0) & 0xff o[5] = (x1 >>> 8) & 0xff o[6] = (x1 >>> 16) & 0xff o[7] = (x1 >>> 24) & 0xff o[8] = (x2 >>> 0) & 0xff o[9] = (x2 >>> 8) & 0xff o[10] = (x2 >>> 16) & 0xff o[11] = (x2 >>> 24) & 0xff o[12] = (x3 >>> 0) & 0xff o[13] = (x3 >>> 8) & 0xff o[14] = (x3 >>> 16) & 0xff o[15] = (x3 >>> 24) & 0xff o[16] = (x4 >>> 0) & 0xff o[17] = (x4 >>> 8) & 0xff o[18] = (x4 >>> 16) & 0xff o[19] = (x4 >>> 24) & 0xff o[20] = (x5 >>> 0) & 0xff o[21] = (x5 >>> 8) & 0xff o[22] = (x5 >>> 16) & 0xff o[23] = (x5 >>> 24) & 0xff o[24] = (x6 >>> 0) & 0xff o[25] = (x6 >>> 8) & 0xff o[26] = (x6 >>> 16) & 0xff o[27] = (x6 >>> 24) & 0xff o[28] = (x7 >>> 0) & 0xff o[29] = (x7 >>> 8) & 0xff o[30] = (x7 >>> 16) & 0xff o[31] = (x7 >>> 24) & 0xff o[32] = (x8 >>> 0) & 0xff o[33] = (x8 >>> 8) & 0xff o[34] = (x8 >>> 16) & 0xff o[35] = (x8 >>> 24) & 0xff o[36] = (x9 >>> 0) & 0xff o[37] = (x9 >>> 8) & 0xff o[38] = (x9 >>> 16) & 0xff o[39] = (x9 >>> 24) & 0xff o[40] = (x10 >>> 0) & 0xff o[41] = (x10 >>> 8) & 0xff o[42] = (x10 >>> 16) & 0xff o[43] = (x10 >>> 24) & 0xff o[44] = (x11 >>> 0) & 0xff o[45] = (x11 >>> 8) & 0xff o[46] = (x11 >>> 16) & 0xff o[47] = (x11 >>> 24) & 0xff o[48] = (x12 >>> 0) & 0xff o[49] = (x12 >>> 8) & 0xff o[50] = (x12 >>> 16) & 0xff o[51] = (x12 >>> 24) & 0xff o[52] = (x13 >>> 0) & 0xff o[53] = (x13 >>> 8) & 0xff o[54] = (x13 >>> 16) & 0xff o[55] = (x13 >>> 24) & 0xff o[56] = (x14 >>> 0) & 0xff o[57] = (x14 >>> 8) & 0xff o[58] = (x14 >>> 16) & 0xff o[59] = (x14 >>> 24) & 0xff o[60] = (x15 >>> 0) & 0xff o[61] = (x15 >>> 8) & 0xff o[62] = (x15 >>> 16) & 0xff o[63] = (x15 >>> 24) & 0xff } function core_hsalsa20( o: Uint8Array, p: Uint8Array, k: Uint8Array, c: Uint8Array, ): void { // biome-ignore format: vertically aligned const j0 = c[ 0] | (c[ 1] << 8) | (c[ 2] << 16) | (c[ 3] << 24), j1 = k[ 0] | (k[ 1] << 8) | (k[ 2] << 16) | (k[ 3] << 24), j2 = k[ 4] | (k[ 5] << 8) | (k[ 6] << 16) | (k[ 7] << 24), j3 = k[ 8] | (k[ 9] << 8) | (k[10] << 16) | (k[11] << 24), j4 = k[12] | (k[13] << 8) | (k[14] << 16) | (k[15] << 24), j5 = c[ 4] | (c[ 5] << 8) | (c[ 6] << 16) | (c[ 7] << 24), j6 = p[ 0] | (p[ 1] << 8) | (p[ 2] << 16) | (p[ 3] << 24), j7 = p[ 4] | (p[ 5] << 8) | (p[ 6] << 16) | (p[ 7] << 24), j8 = p[ 8] | (p[ 9] << 8) | (p[10] << 16) | (p[11] << 24), j9 = p[12] | (p[13] << 8) | (p[14] << 16) | (p[15] << 24), j10 = c[ 8] | (c[ 9] << 8) | (c[10] << 16) | (c[11] << 24), j11 = k[16] | (k[17] << 8) | (k[18] << 16) | (k[19] << 24), j12 = k[20] | (k[21] << 8) | (k[22] << 16) | (k[23] << 24), j13 = k[24] | (k[25] << 8) | (k[26] << 16) | (k[27] << 24), j14 = k[28] | (k[29] << 8) | (k[30] << 16) | (k[31] << 24), j15 = c[12] | (c[13] << 8) | (c[14] << 16) | (c[15] << 24) // biome-ignore format: compact let x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7, x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14, x15 = j15, u: number for (let i = 0; i < 20; i += 2) { u = (x0 + x12) | 0 x4 ^= (u << 7) | (u >>> 25) u = (x4 + x0) | 0 x8 ^= (u << 9) | (u >>> 23) u = (x8 + x4) | 0 x12 ^= (u << 13) | (u >>> 19) u = (x12 + x8) | 0 x0 ^= (u << 18) | (u >>> 14) u = (x5 + x1) | 0 x9 ^= (u << 7) | (u >>> 25) u = (x9 + x5) | 0 x13 ^= (u << 9) | (u >>> 23) u = (x13 + x9) | 0 x1 ^= (u << 13) | (u >>> 19) u = (x1 + x13) | 0 x5 ^= (u << 18) | (u >>> 14) u = (x10 + x6) | 0 x14 ^= (u << 7) | (u >>> 25) u = (x14 + x10) | 0 x2 ^= (u << 9) | (u >>> 23) u = (x2 + x14) | 0 x6 ^= (u << 13) | (u >>> 19) u = (x6 + x2) | 0 x10 ^= (u << 18) | (u >>> 14) u = (x15 + x11) | 0 x3 ^= (u << 7) | (u >>> 25) u = (x3 + x15) | 0 x7 ^= (u << 9) | (u >>> 23) u = (x7 + x3) | 0 x11 ^= (u << 13) | (u >>> 19) u = (x11 + x7) | 0 x15 ^= (u << 18) | (u >>> 14) u = (x0 + x3) | 0 x1 ^= (u << 7) | (u >>> 25) u = (x1 + x0) | 0 x2 ^= (u << 9) | (u >>> 23) u = (x2 + x1) | 0 x3 ^= (u << 13) | (u >>> 19) u = (x3 + x2) | 0 x0 ^= (u << 18) | (u >>> 14) u = (x5 + x4) | 0 x6 ^= (u << 7) | (u >>> 25) u = (x6 + x5) | 0 x7 ^= (u << 9) | (u >>> 23) u = (x7 + x6) | 0 x4 ^= (u << 13) | (u >>> 19) u = (x4 + x7) | 0 x5 ^= (u << 18) | (u >>> 14) u = (x10 + x9) | 0 x11 ^= (u << 7) | (u >>> 25) u = (x11 + x10) | 0 x8 ^= (u << 9) | (u >>> 23) u = (x8 + x11) | 0 x9 ^= (u << 13) | (u >>> 19) u = (x9 + x8) | 0 x10 ^= (u << 18) | (u >>> 14) u = (x15 + x14) | 0 x12 ^= (u << 7) | (u >>> 25) u = (x12 + x15) | 0 x13 ^= (u << 9) | (u >>> 23) u = (x13 + x12) | 0 x14 ^= (u << 13) | (u >>> 19) u = (x14 + x13) | 0 x15 ^= (u << 18) | (u >>> 14) } o[0] = (x0 >>> 0) & 0xff o[1] = (x0 >>> 8) & 0xff o[2] = (x0 >>> 16) & 0xff o[3] = (x0 >>> 24) & 0xff o[4] = (x5 >>> 0) & 0xff o[5] = (x5 >>> 8) & 0xff o[6] = (x5 >>> 16) & 0xff o[7] = (x5 >>> 24) & 0xff o[8] = (x10 >>> 0) & 0xff o[9] = (x10 >>> 8) & 0xff o[10] = (x10 >>> 16) & 0xff o[11] = (x10 >>> 24) & 0xff o[12] = (x15 >>> 0) & 0xff o[13] = (x15 >>> 8) & 0xff o[14] = (x15 >>> 16) & 0xff o[15] = (x15 >>> 24) & 0xff o[16] = (x6 >>> 0) & 0xff o[17] = (x6 >>> 8) & 0xff o[18] = (x6 >>> 16) & 0xff o[19] = (x6 >>> 24) & 0xff o[20] = (x7 >>> 0) & 0xff o[21] = (x7 >>> 8) & 0xff o[22] = (x7 >>> 16) & 0xff o[23] = (x7 >>> 24) & 0xff o[24] = (x8 >>> 0) & 0xff o[25] = (x8 >>> 8) & 0xff o[26] = (x8 >>> 16) & 0xff o[27] = (x8 >>> 24) & 0xff o[28] = (x9 >>> 0) & 0xff o[29] = (x9 >>> 8) & 0xff o[30] = (x9 >>> 16) & 0xff o[31] = (x9 >>> 24) & 0xff }