Skip to content

Instantly share code, notes, and snippets.

@ColtonProvias
Last active March 11, 2023 04:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ColtonProvias/277fa0f161821a30a98af676c62c4a10 to your computer and use it in GitHub Desktop.
Save ColtonProvias/277fa0f161821a30a98af676c62c4a10 to your computer and use it in GitHub Desktop.
DO NOT USE. This is insecure and should not be used in production. This was developed as a learning exercise in cryptography years ago when WebCrypto wasn't consistent between browsers.
import { zipWith } from 'lodash-es'
import { AES } from './aes'
/**
* AES-CBC Implementation
*/
export class AESCBC {
/** Block cipher */
private cipher: AES
/**
* Initialize the AES-CBC cipher.
* @param key AES key
* @param iv 16-bit initialization vector
*/
constructor (key: Uint8Array, private iv: Uint8Array) {
if (this.iv.length !== 16) throw new Error('Initialization vector must be 128-bits long')
this.cipher = new AES(key)
}
/**
* Encrypt plaintext via AES-CBC.
* @param plaintext Plaintext to encrypt. Length must be a multiple of 16.
* @return Ciphertext
*/
encrypt (plaintext: Uint8Array): Uint8Array {
if (plaintext.length % 16 !== 0) throw new Error('Plaintext must have a length that is a multiple of 16')
let ret = new Uint8Array(plaintext.length)
let lastBlock = this.iv
for (let i = 0; i < plaintext.length; i += 16) {
let preCipher = new Uint8Array(zipWith(plaintext.slice(i, i + 16), lastBlock, (v1, v2) => v1 ^ v2))
lastBlock = this.cipher.encrypt(preCipher)
ret.set(lastBlock, i)
}
return ret
}
/**
* Decrypt ciphertext via AES-CBC.
* @param ciphertext Ciphertext to decrypt. Length must be a multiple of 16.
* @return Plaintext
*/
decrypt(ciphertext: Uint8Array): Uint8Array {
if (ciphertext.length % 16 !== 0) throw new Error('Ciphertext length must be a multiple of 16')
let ret = new Uint8Array(ciphertext.length)
let lastBlock = this.iv
for (let i = 0; i < ciphertext.length; i += 16) {
ret.set(new Uint8Array(zipWith(this.cipher.decrypt(ciphertext.slice(i, i + 16)), lastBlock, (x1, x2) => x1 ^ x2)), i)
lastBlock = ciphertext.slice(i, i + 16)
}
return ret
}
}
import { AES } from './aes'
import BN from 'bn.js'
/**
* AES-CTR Implementation
*/
export class AESCTR {
/** Block cipher */
cipher: AES
/** Nonce/IV */
nonce: BN
/** Current counter value */
private counter: BN
/** Cached modulo to help speed up the calculations. */
private mod = new BN('10000000000000000', 16)
/**
* Initialize AES-CTR for encryption
* @param key AES key
* @param nonce Nonce
* @param counter Counter starting value
*/
constructor (key: Uint8Array, nonce: Uint8Array = new Uint8Array(16), counter: number | BN = 0) {
if (nonce.length !== 16) throw new Error('Invalid nonce length')
this.cipher = new AES(key)
this.nonce = new BN(nonce)
this.counter = new BN(counter)
}
/**
* Encrypt plaintext via AES-CTR.
* @param plaintext Plaintext to encrypt.
* @param xorBlock Whether XORing against the cipher should be performed. This is used in AES-GCM.
* @return Ciphertext
*/
encrypt (plaintext: Uint8Array, xorBlock = true): Uint8Array {
let ret = new Uint8Array(plaintext.length)
for (let i = 0; i < plaintext.length; i += 16) {
let block = plaintext.slice(i, i + Math.min(16, plaintext.length - i))
let counterBlock = this.nonce.add(this.counter.mod(this.mod))
let ctx = this.cipher.encrypt(new Uint8Array(counterBlock.toArray('be', 16)))
this.counter = this.counter.iaddn(1)
if (xorBlock) {
ret.set(new Uint8Array(block.map((v, idx) => v ^ ctx[idx]).buffer), i)
} else {
ret.set(ctx, i)
}
}
return ret
}
/**
* Decrypt ciphertext via AES-CTR. This operation is exactly the same as encrypting.
* @param ciphertext Ciphertext to decrypt.
* @return Plaintext
*/
decrypt (ciphertext: Uint8Array): Uint8Array {
return this.encrypt(ciphertext)
}
}
/**
* AES-ECB Implementation. Insecure, do not use directly.
*/
export class AESECB {
/** Block cipher. */
private cipher: AES
/**
* Initialize the block cipher.
* @param key AES key
*/
constructor (key: Uint8Array) {
this.cipher = new AES(key)
}
/**
* Encrypt plaintext via AES-ECB
* @param plaintext Plaintext to encrypt. Length must be a multiple of 16.
* @return Ciphertext
*/
encrypt (plaintext: Uint8Array): Uint8Array {
if (plaintext.length % 16 !== 0) throw new Error('Plaintext must be a multiple of 16')
let ret = new Uint8Array(plaintext.length)
for (let i = 0; i < plaintext.length; i += 16) {
ret.set(this.cipher.encrypt(plaintext.slice(i, i + 16)), i)
}
return ret
}
/**
* Decrypt ciphertext via AES-ECB
* @param ciphertext Ciphertext to encrypt. Length must be a multiple of 16.
* @return Plaintext
*/
decrypt (ciphertext: Uint8Array): Uint8Array {
if (ciphertext.length % 16 !== 0) throw new Error('Ciphertext must be a multiple of 16')
let ret = new Uint8Array(ciphertext.length)
for (let i = 0; i < ciphertext.length; i += 16) {
ret.set(this.cipher.decrypt(ciphertext.slice(i, i + 16)), i)
}
return ret
}
}
import BN from 'bn.js'
import { bufferConstTimeEq } from '../../util'
import { AESCTR } from './aes-ctr'
/**
* AES-GCM Result. We keep them separate in case we want to alter processing
* later.
*/
export interface GCMResult {
ciphertext: Uint8Array
tag: Uint8Array
}
/**
* AES-GCM Implementation.
*/
export class AESGCM {
/** AES-CTR cipher that GCM extends upon */
private cipher: AESCTR
/** Hash key */
private h: Uint8Array
/** IV and counter */
private y0: Uint8Array
/** Hashed authentication data */
private s0: Uint8Array
/** Encrypted nonce for final tag generation */
private encY0: Uint8Array
/**
* Initialize the AES-GCM cipher.
* @param key AES key
* @param iv Initialization Vector. 12 bytes preferred.
* @param aad Optional additional authenticated data.
* @param minTagLength Optional minimum tag length for verification.
*/
constructor (key: Uint8Array, iv: Uint8Array, private aad = new Uint8Array(0), private minTagLength = 16) {
this.cipher = new AESCTR(key, iv)
this.h = this.cipher.cipher.encrypt(new Uint8Array(16))
if (iv.length === 12) {
this.y0 = new Uint8Array(16)
this.y0.set(iv)
this.y0[15] = 1
} else {
this.y0 = this.ghash(new Uint8Array(1), iv)
let len = new DataView(new ArrayBuffer(16))
len.setUint32(12, iv.length * 8)
this.y0 = this.ghash(this.y0, new Uint8Array(len.buffer))
}
this.cipher.nonce = new BN(this.y0)
this.s0 = this.ghash(new Uint8Array(16), aad)
this.encY0 = this.cipher.encrypt(this.y0, false)
}
/**
* Encrypt plaintext via AES-GCM.
* @param plaintext Plaintext to encrypt
* @return Ciphertext and tag
*/
encrypt (plaintext: Uint8Array): GCMResult {
let ciphertext = this.cipher.encrypt(plaintext)
let tag = this.ghash(this.s0, ciphertext)
let lastBlock = new Uint8Array(16)
lastBlock.set(new BN(this.aad.length).muln(8).toArray('be', 8))
lastBlock.set(new BN(ciphertext.length).muln(8).toArray('be', 8), 8)
let tagBN = new BN(this.ghash(tag, lastBlock))
tagBN = tagBN.uxor(new BN(this.encY0))
return {
ciphertext: ciphertext,
tag: new Uint8Array(tagBN.toArray('be', 16))
}
}
/**
* Decrypt ciphertext via AES-GCM
* @param ciphertext Ciphertext to decrypt.
* @param tag Authentication tag for verification.
* @return Plaintext.
*/
decrypt(ciphertext: Uint8Array, tag: Uint8Array): Uint8Array {
let checkTag = this.ghash(this.s0, ciphertext)
let lastBlock = new Uint8Array(16)
lastBlock.set(new BN(this.aad.length).muln(8).toArray('be', 8))
lastBlock.set(new BN(ciphertext.length).muln(8).toArray('be', 8), 8)
let checkTagBN = new BN(this.ghash(checkTag, lastBlock))
checkTagBN = checkTagBN.uxor(new BN(this.encY0))
let finalTag = new Uint8Array(checkTagBN.toArray('be', 16)).slice(0, this.minTagLength)
if (!bufferConstTimeEq(finalTag, tag.slice(0, this.minTagLength))) throw new Error('Could not authenticate')
return this.cipher.encrypt(ciphertext)
}
/**
* Perform the GHASH function as per the spec.
* @param a Usually the AAD
* @param c Usually the ciphertext
* @return 16-byte hash
*/
private ghash(a: Uint8Array, c: Uint8Array): Uint8Array {
if (c.length % 16 !== 0) {
let tmp = new Uint8Array(c.length + (16 - (c.length % 16)))
tmp.set(c)
c = tmp
}
let ret = new BN(a)
for (let i = 0; i < c.length; i += 16) {
ret = this.multiply(ret.uxor(new BN(c.slice(i, i + 16))), new BN(this.h)
)
}
return new Uint8Array(ret.toArray('be', 16))
}
/**
* Multiply two operands over a Galois field
* @param x First operand
* @param y Second operand
* @return Product in GF(2^128)
*/
private multiply(x: BN, y: BN): BN {
let z = new BN(0)
let r = new BN('e1', 16).ushln(120)
let one = new BN(1)
for (let i = 127; i > -1; i--) {
z = z.uxor(x.mul(y.ushrn(i).uand(one)))
x = x.ushrn(1).uxor(x.uand(one).mul(r))
}
return z
}
}
import BN from 'bn.js'
import { bufferConstTimeEq } from '../../util'
import { AES } from './aes'
/**
* AES-KW Implementation
*/
export class AESKW {
/** AES Block cipher */
private cipher: AES
/**
* Initialize the AES-KW cipher
* @param key AES key
*/
constructor (key: Uint8Array) {
this.cipher = new AES(key)
}
/**
* Wrap a key
* @param plaintext Key to wrap. Length must be a multiple of 8.
* @return Wrapped key.
*/
wrap (plaintext: Uint8Array): Uint8Array {
if (plaintext.length % 8 !== 0) throw new Error('Key to wrap must have a length a multiple of 8')
let n = plaintext.length / 8
let r = new Array(n).fill(0).map((_, i) => plaintext.slice(i * 8, i * 8 + 8))
let buffer = new Uint8Array(16)
let tBytes = new Uint8Array(8)
buffer.fill(0xa6, 0, 8)
for (let t = 0; t < 6 * n; t++) {
buffer.set(r[t % n], 8)
buffer = this.cipher.encrypt(buffer)
tBytes.set(new BN(t + 1).toArray('be', 8))
tBytes.forEach((x, i) => buffer[i] ^= x)
r[t % n] = buffer.slice(8)
}
let ret = new Uint8Array((n + 1) * 8)
ret.set(buffer)
r.forEach((v, i) => ret.set(v, (i + 1) * 8))
return ret
}
/**
* Unwrap a key
* @param ciphertext Key to unwrap. Length must be a multiple of 8.
* @return Unwrapped key.
*/
unwrap (ciphertext: Uint8Array): Uint8Array {
if (ciphertext.length % 8 !== 0) throw new Error('Key to unwrap must have a length a multiple of 8')
let n = ciphertext.length / 8 - 1
let r = new Array(n).fill(0).map((_, i) => ciphertext.slice((i + 1) * 8, (i + 2) * 8))
let buffer = new Uint8Array(16)
let tBytes = new Uint8Array(8)
buffer.set(ciphertext.slice(0, 8))
for (let t = 6 * n - 1; t >= 0; t--) {
tBytes.set(new BN(t + 1).toArray('be', 8))
tBytes.forEach((x, i) => buffer[i] ^= x)
buffer.set(r[t % n], 8)
buffer = this.cipher.decrypt(buffer)
r[t % n] = buffer.slice(8)
}
if (!bufferConstTimeEq(buffer.slice(0, 8), new Uint8Array(8).fill(0xa6))) throw new Error('Failed to unwrap key')
let ret = new Uint8Array((r.length) * 8)
r.forEach((v, i) => ret.set(v, i * 8))
return ret
}
}
/**
* Bit-wise right rotation (32-bit unsigned int simulated)
* @param x Value to rotate
* @param b Number of bits to rotate
* @return Rotated value
*/
let rotr = (x: number, b: number): number => ((x >>> b) | (x << (32 - b)) >>> 0) >>> 0
/**
* Double a number over the Rijndael finite field.
* @param x Number to double
* @returns 8-bit product mod 0xff
*/
let xtime = (x: number): number => (x << 1) ^ (0x11b & -(x >> 7))
/**
* Multiply two numbers over a Rijndael finite field.
* @param x First Multiplicand
* @param y Second Multiplicand
* @returns 8-bit product mod 0xff
*/
let mul = (x: number, y: number): number => (x && y) ? AES.pow[(AES.log[x] + AES.log[y]) % 255] : 0
/**
* AES Block Cipher
*/
export class AES {
/** Cached power table */
static pow = new Uint8Array(256)
/** Cached log table */
static log = new Uint8Array(256)
/** Cached round constants for key expansion */
static rcon = new Uint8Array(30)
/** Cached substitution array */
static sbox = new Uint8Array(256)
/** Cached inverted substitution array */
static sboxinv = new Uint8Array(256)
/** Pre-calculated encryption tables */
static e0 = new Uint32Array(256)
static e1 = new Uint32Array(256)
static e2 = new Uint32Array(256)
static e3 = new Uint32Array(256)
/** Pre-calculated decryption tables */
static d0 = new Uint32Array(256)
static d1 = new Uint32Array(256)
static d2 = new Uint32Array(256)
static d3 = new Uint32Array(256)
/** Pre-calculated tables for decryption key expansion */
static u0 = new Uint32Array(256)
static u1 = new Uint32Array(256)
static u2 = new Uint32Array(256)
static u3 = new Uint32Array(256)
/** Only initialize the tables once */
private static initialized = false
/** Expanded encryption key */
ke: Array<Uint32Array>
/** Expanded decryption key */
kd: Array<Uint32Array>
/**
* Initialize the block cipher
* @param key 128, 192, or 256 bit key
*/
constructor (key: Uint8Array) {
if (!AES.initialized) {
this.generateTables()
}
let rounds: number
switch (key.length) {
case 16:
rounds = 10
break
case 24:
rounds = 12
break
case 32:
rounds = 14
break
default:
throw new Error('Invalid key size')
}
this.ke = new Array(rounds + 1).fill(0).map(() => new Uint32Array(4))
this.kd = new Array(rounds + 1).fill(0).map(() => new Uint32Array(4))
let roundKeyCount = (rounds + 1) * 4
let KC = Math.floor(key.length / 4)
let view = new DataView(key.buffer)
let tk = new Uint32Array(KC).fill(0).map((_, i) => view.getUint32(i * 4))
for (let i = 0; i < KC; i++) {
this.ke[Math.floor(i / 4)][i % 4] = tk[i]
this.kd[rounds - Math.floor(i / 4)][i % 4] = tk[i]
}
let rconPtr = 0
let t = KC
while (t < roundKeyCount) {
let tt = tk[KC - 1]
tk[0] ^= ((AES.sbox[(tt >>> 16) & 0xff] << 24) ^
(AES.sbox[(tt >>> 8) & 0xff] << 16) ^
(AES.sbox[tt & 0xff] << 8) ^
AES.sbox[(tt >>> 24) & 0xff] ^
(AES.rcon[rconPtr] << 24))
rconPtr++
if (KC !== 8) {
for (let i = 1; i < KC; i++) {
tk[i] ^= tk[i - 1]
}
} else {
for (let i = 1; i < Math.floor(KC / 2); i++) {
tk[i] ^= tk[i - 1]
}
tt = tk[Math.floor(KC / 2) - 1]
tk[Math.floor(KC / 2)] ^= (AES.sbox[tt & 0xFF] ^
(AES.sbox[(tt >>> 8) & 0xFF] << 8) ^
(AES.sbox[(tt >>> 16) & 0xFF] << 16) ^
(AES.sbox[(tt >>> 24) & 0xFF] << 24))
for (let i = Math.floor(KC / 2) + 1; i < KC; i++) {
tk[i] ^= tk[i - 1]
}
}
let j = 0
while (j < KC && t < roundKeyCount) {
this.ke[Math.floor(t / 4)][t % 4] = tk[j]
this.kd[rounds - Math.floor(t / 4)][t % 4] = tk[j]
j++
t++
}
}
for (let r = 1; r < rounds; r++) {
for (let j = 0; j < 4; j++) {
let tt = this.kd[r][j]
this.kd[r][j] = (AES.u0[(tt >> 24) & 0xFF] ^
AES.u1[(tt >> 16) & 0xFF] ^
AES.u2[(tt >> 8) & 0xFF] ^
AES.u3[tt & 0xFF])
}
}
}
/**
* Encrypt a 128-byte block of plaintext.
* @param plaintext Plaintext to encrypt.
* @return Ciphertext.
*/
encrypt (plaintext: Uint8Array): Uint8Array {
if (plaintext.length !== 16) throw new Error('Invalid block length')
let rounds = this.ke.length - 1
let a = new Uint32Array(4).fill(0)
let view = new DataView(plaintext.buffer)
let t = new Uint32Array(4).fill(0).map((_, i) => view.getUint32(i * 4) ^ this.ke[0][i])
for (let r = 1; r < rounds; r++) {
for (let i = 0; i < 4; i++) {
a[i] = (AES.e0[(t[i] >> 24) & 0xff] ^
AES.e1[(t[(i + 1) % 4] >> 16) & 0xff] ^
AES.e2[(t[(i + 2) % 4] >> 8) & 0xff] ^
AES.e3[t[(i + 3) % 4] & 0xff] ^
this.ke[r][i])
}
t = a.slice(0)
}
let ret = new Uint8Array(16)
for (let i = 0; i < 4; i++) {
let tt = this.ke[rounds][i]
ret[i * 4] = (AES.sbox[(t[i] >> 24) & 0xff] ^ (tt >> 24)) & 0xff
ret[i * 4 + 1] = (AES.sbox[(t[(i + 1) % 4] >> 16) & 0xff] ^ (tt >> 16)) & 0xff
ret[i * 4 + 2] = (AES.sbox[(t[(i + 2) % 4] >> 8) & 0xff] ^ (tt >> 8)) & 0xff
ret[i * 4 + 3] = (AES.sbox[t[(i + 3) % 4] & 0xff] ^ tt) & 0xff
}
return ret
}
/**
* Decrypt a 128-bit block of ciphertext.
* @param ciphertext Ciphertext to decrypt.
* @return Plaintext
*/
decrypt (ciphertext: Uint8Array): Uint8Array {
if (ciphertext.length !== 16) throw new Error('Invalid block length')
let rounds = this.kd.length - 1
let a = new Uint32Array(4).fill(0)
let view = new DataView(ciphertext.buffer)
let t = new Uint32Array(4).fill(0).map((_, i) => view.getUint32(i * 4) ^ this.kd[0][i] >>> 0)
for (let r = 1; r < rounds; r++) {
for (let i = 0; i < 4; i++) {
a[i] = (AES.d0[(t[i] >>> 24) & 0xff] ^
AES.d1[(t[(i + 3) % 4] >>> 16) & 0xff] ^
AES.d2[(t[(i + 2) % 4] >>> 8) & 0xff] ^
AES.d3[t[(i + 1) % 4] & 0xff] ^
this.kd[r][i])
}
t = a.slice(0)
}
let ret = new Uint8Array(16)
for (let i = 0; i < 4; i++) {
let tt = this.kd[rounds][i]
ret[i * 4] = (AES.sboxinv[(t[i] >>> 24) & 0xff] ^ (tt >>> 24)) & 0xff
ret[i * 4 + 1] = (AES.sboxinv[(t[(i + 3) % 4] >>> 16) & 0xff] ^ (tt >>> 16)) & 0xff
ret[i * 4 + 2] = (AES.sboxinv[(t[(i + 2) % 4] >>> 8) & 0xff] ^ (tt >>> 8)) & 0xff
ret[i * 4 + 3] = (AES.sboxinv[t[(i + 1) % 4] & 0xff] ^ tt) & 0xff
}
return ret
}
/**
* Generate cached tables.
*/
private generateTables (): void {
let x = 1
for (let i = 0; i < 256; i++) {
AES.pow[i] = x
AES.log[x] = i
x = (x ^ xtime(x)) & 0xff
}
x = 1
for (let i = 0; i < 30; i++) {
AES.rcon[i] = x
x = xtime(x) & 0xff
}
AES.sbox[0x00] = 0x63
AES.sboxinv[0x63] = 0x00
for (let i = 1; i < 256; i++) {
x = AES.pow[255 - AES.log[i]]
let y = x
for (let i = 0; i < 3; i++) {
y = ((y << 1) | (y >> 7)) & 0xff
x ^= y
}
y = ((y << 1) | (y >> 7)) & 0xff
x ^= y ^ 0x63
AES.sbox[i] = x
AES.sboxinv[x] = i
}
for (let i = 0; i < 256; i++) {
let s = AES.sbox[i]
let si = AES.sboxinv[i]
AES.e0[i] = (mul(0x02, s) << 24) ^ (s << 16) ^ (s << 8) ^ mul(0x03, s)
AES.e1[i] = rotr(AES.e0[i], 8)
AES.e2[i] = rotr(AES.e1[i], 8)
AES.e3[i] = rotr(AES.e2[i], 8)
AES.d0[i] = (mul(0x0e, si) << 24) ^ (mul(0x09, si) << 16) ^ (mul(0x0d, si) << 8) ^ mul(0x0b, si)
AES.d1[i] = rotr(AES.d0[i], 8)
AES.d2[i] = rotr(AES.d1[i], 8)
AES.d3[i] = rotr(AES.d2[i], 8)
AES.u0[i] = (mul(0x0e, i) << 24) ^ (mul(0x09, i) << 16) ^ (mul(0x0d, i) << 8) ^ mul(0x0b, i)
AES.u1[i] = rotr(AES.u0[i], 8)
AES.u2[i] = rotr(AES.u1[i], 8)
AES.u3[i] = rotr(AES.u2[i], 8)
}
AES.initialized = true
}
}
import BN from 'bn.js'
import { generateRandomBytes } from '../../util'
import { SHA2 } from './sha2'
/**
* Reduction class. Defined here because bn.js is annoying.
*/
declare class Red { }
/**
* ECDSA Signature as separate r and s values.
*/
interface ECSignature {
/** x value from generated point during signing. */
r: BN
/** Value generated from an operation involing the calculated digest of the message. */
s: BN
}
/**
* Prime Elliptic Curve
*/
export class Curve {
/** Prime used as the modulo for the prime field. */
p: BN
/** The prime field reduction context. It handles the modulus operations for us. */
red: Red
/** a coefficient for the curve. */
a: BN
/** b coefficient for the curve. */
b: BN
/** Another prime used for point generation. */
n: BN
/** The generator point. */
g: Point
/**
* Initialize the curve and reduction context.
* @param name Common name for this curve.
* @param a a coefficient as a hexadecimal string.
* @param b b coefficient as a hexadecimal string.
* @param gx Generator point x value as a hexadecimal string.
* @param gy Generator point y value as a hexadecimal string.
* @param n Modulo for curve operations as a hexadecimal string.
* @param p Prime field definition as a hexadecimal string.
*/
constructor (public name: string, a: string, b: string, gx: string, gy: string, n: string, p: string) {
this.p = new BN(p, 16)
this.red = BN.red(this.p)
this.a = this.forceRed(new BN(a, 16))
this.b = this.forceRed(new BN(b, 16))
this.n = new BN(n, 16)
this.g = new Point(this, new BN(gx, 16), new BN(gy, 16))
}
/**
* Test if a curve contains a point.
*
* This is just the Weiserstrauss equation rewritten to equal 0:
*
* y² - x³ - ax - b = 0
*
* @param x x value of the point to check.
* @param y y value of the point to check.
* @return If the point exists on the curve.
*/
containsPoint (x: BN, y: BN): boolean {
return y
.redSqr()
.redSub(x.redPow(new BN(3)))
.redSub(this.a.redMul(x))
.redSub(this.b)
.isZero()
}
/**
* Ensures a big number is part of a reduction context, whether it is a conformist or a filthy independent.
* @param n Number to force a reduction context upon.
* @return A good little reduced conformist.
*/
forceRed (n: BN): BN {
if (n.red === null) {
n.forceRed(this.red)
}
return n
}
/**
* Returns the byte length of this curve as a property.
* @return Byte length.
*/
get byteLength (): number {
return this.n.byteLength()
}
/**
* Generate a new keypair with this curve.
*
* A keypair consists of a private scalar value and a public point generated from it.
* @return A promise to generate a keypair.
*/
async generate (): Promise<[BN, Point]> {
let d: BN
while (true) {
let raw: Uint8Array = await generateRandomBytes(this.byteLength)
d = new BN(raw)
if (d.lt(this.n) && d.gtn(1)) {
break
}
}
let q: Point = this.g.mul(d)
return [d, q]
}
/**
* Derive a key as per the Elliptic Curve Diffie-Hellman (ECDH) algorithm.
* @param d Private scalar.
* @param pub Public point.
* @return Shared secret.
*/
async derive (d: BN, pub: Point): Promise<BN|null> {
return pub.mul(d).x
}
/**
* Generate a signature for a message by the Elliptic Curve Digitial Signing Algorithm (ECDSA).
*
* Yes, this will always generate a different result each time. So if you try to test by matching against a
* pre-generated result, you are going to have a bad time. Luckily I didn't make that mistake myself thrice.
*
* @param hash Hashing algorithm instance.
* @param d Private scalar to sign with.
* @param message Message to sign.
* @return r-s signature pair of big numbers.
*/
async sign (hash: SHA2, d: BN, message: Uint8Array): Promise<ECSignature> {
hash.update(message)
let e = hash.finalize()
let z = e.slice(0, this.byteLength)
while (true) {
let k = new BN(await generateRandomBytes(this.byteLength))
if (k.ltn(1) || k.gte(this.n)) {
continue
}
let kp = this.g.mul(k)
let r = kp.x
if (r === null || r.isZero()) {
continue
}
let s = k.invm(this.n).mul(r.mul(d).add(new BN(z))).umod(this.n)
if (s.isZero()) {
continue
}
return { r: r, s: s }
}
}
}
/**
* Elliptic Curve Point Implementation.
*
* The operations here all happen within normal Euclidean-style Galois prime fields. There are also variations that
* are done using a third value, z, for representation in a 3 dimensional space. However, I have found them to be a tad
* temperamental in JavaScript when tested across browsers. Jacobi-based methods are used in OpenSSL, WebCrypto, and
* many other crypto libraries while this method is similar to that used in Go.
*/
export class Point {
/** x value of the point. */
public x: BN | null
/** y value of the point. */
public y: BN | null
/**
* Initialize the point.
* @param curve Curve that this point is on.
* @param x x value of the point.
* @param y y value of the point.
*/
constructor (public curve: Curve | null, x: BN | null, y: BN | null) {
this.x = (curve !== null && x !== null) ? curve.forceRed(x) : null
this.y = (curve !== null && y !== null) ? curve.forceRed(y) : null
if (this.curve !== null) {
if (this.curve === null || x === null || y === null || !this.curve.containsPoint(x, y)) throw new Error('Point not on curve')
if (this.curve === null || this.mul(this.curve.n) !== infinity) throw new Error('Must go to infinity and beyond when multiplied by n')
}
}
/**
* Perform the point addition group operation.
*
* For us to perform point addition, we need to quickly cover The Group Law. Elliptic Curves are symmetrical about the
* x-axis. Thus given a point P, we can assume -P to be the opposite point, namely (P_x, -P_y). A point at infinity O,
* however, will always be O as O = -O.
*
* If P and Q are two points on a curve, we can solve for the point -R. First, draw a line connecting points P and Q.
* The point at which the line then intersects the cubic is defined as R. Taking the opposite, we get our result which
* is -R.
*
* Some more edge cases and their solutions:
* 1. If a point O is at infinity, then P + O = P and O is the identity.
* 2. If points are opposite such as -P = Q, then P + Q = 0.
* 3. If P = Q, then the tangent is used as the line. This will be covered in point doubling.
* 4. If P is an inflection point, then R = P and P + P = -P
*
* If the curve is not in the Weiserstrauss normal form, we have to change our definition of group structure. One of
* the inflection points becomes O. Now -P is a third point passing through O and P. P + Q = -R assuming R to be the
* third point on the line. Luckily our curves are all Weiserstrauss and prime, so they are easy to work with.
*
* @param other Point to add to this point
* @return Summed point.
*/
add (other: Point): Point {
if (other === infinity || other.x === null || other.y === null) {
return this
}
if (this === infinity || this.x === null || this.y === null) {
return other
}
if (this.curve !== other.curve) throw new Error('Curves do not match')
if (this.x.eq(other.x)) {
if (this.y.redAdd(other.y).isZero()) {
return infinity
} else {
return this.dbl()
}
}
let lambda: BN = other.y
.redSub(this.y)
.redMul(other.x.redSub(this.x).redInvm())
let xR: BN = lambda
.redPow(new BN(2))
.redSub(this.x)
.redSub(other.x)
let yR: BN = lambda
.redMul(this.x.redSub(xR))
.redSub(this.y)
return new Point(this.curve, xR, yR)
}
/**
* Double a point.
*
* If yP = yQ ≠ 0, then Q = P and R = -(P + P) = -2P = -2Q. We then find R by utilizing a tangent line.
*
* @return Double of this point.
*/
dbl (): Point {
if (this === infinity || this.curve === null || this.x === null || this.y === null) {
return infinity
}
let lambda: BN = this.x
.redSqr()
.redMul(this.curve.forceRed(new BN(3)))
.redAdd(this.curve.a)
.redMul(this.y
.redMul(this.curve.forceRed(new BN(2)))
.redInvm())
let xR: BN = lambda
.redSqr()
.redSub(this.x
.redMul(this.curve.forceRed(new BN(2))))
let yR: BN = lambda
.redMul(this.x
.redSub(xR))
.redSub(this.y)
return new Point(this.curve, xR, yR)
}
/**
* Multiply point by a scalar.
*
* Scalar multiplication is easy. If you want to multiply a point by constant k, just add the point k times like
* normal. Of course that will take too long for 256-bit or greater numbers, so we do a different solution.
*
* The simplest solution is the double-and-add. Thus to compute Q = dP, we first need the binary representation of d:
* d = sum of series 2^i * d_i from i = 0 to m, which is the length of d, and that the final result [d_0, d_m] is
* either a 1 or a 0.
*
* 1. Clone P to N.
* 2. Set Q = 0
* 3. For i = 0 to m, repeat:
* 1. If d_i = 1, then Q = Q + N
* 2. N = 2N
* 4. Return Q
*
* While double-and-add may be the simplest method, it is vulnerable to timing attacks and thus should be avoided.
* Right now the most used method is the w-ary non-adjacent form (wNAF). OpenSSL and other libraries will use
* algorithms that target constant time or add randomness.
*
* In this, we use a variation of double-and-add that is the same algorithm used in both Google Chrome and in Go.
*
* @param other Scalar to multiply by.
* @return Resulting point.
*/
mul (other: BN): Point {
if (this === infinity || this.curve === null || this.x === null || this.y === null) {
return this
}
let e: BN = this.curve.forceRed(other.mod(this.curve.n))
if (e.isZero()) {
return infinity
}
if (!e.gtn(0)) throw new Error('Unsigned integer expected')
let negated: Point = new Point(this.curve, this.x, this.y.redNeg())
let e3: BN = e.muln(3)
let i: BN = new BN(2).pow(new BN(e3.bitLength()).subn(2))
let ret: Point = this
while (i.gtn(1)) {
ret = ret.dbl()
if (!e3.and(i).isZero() && e.and(i).isZero()) {
ret = ret.add(this)
}
if (e3.and(i).isZero() && !e.and(i).isZero()) {
ret = ret.add(negated)
}
i = i.divn(2)
}
return ret
}
/**
* Verify a signature against this point by ECDSA.
* @param hash Hash algorithm instance.
* @param message Message to verify signature against.
* @param signature r-s signature object.
* @return A promise which resolves in a boolean of if the signature is valid.
*/
async verify (hash: SHA2, message: Uint8Array, signature: ECSignature): Promise<boolean> {
if (this.curve === null) {
throw 'Invalid point being used for verification'
}
hash.update(message)
let e = hash.finalize()
let z = e.slice(0, this.curve.byteLength)
if (signature.r.ltn(1) || signature.r.gte(this.curve.n)) {
return false
}
if (signature.s.ltn(1) || signature.s.gte(this.curve.n)) {
return false
}
let w = signature.s.invm(this.curve.n)
let u1 = w.mul(new BN(z)).umod(this.curve.n)
let u2 = w.mul(new BN(signature.r)).umod(this.curve.n)
let p = this.curve.g.mul(u1).add(this.mul(u2))
if (p === infinity || p.x === null) {
return false
}
return p.x.eq(signature.r)
}
}
/** Apparently infinity equals null */
const infinity = new Point(null, null, null)
/** Set up the curve parameters. */
export let P256 = new Curve(
'P-256',
'ffffffff 00000001 00000000 00000000 00000000 ffffffff ffffffff fffffffc', // a
'5ac635d8 aa3a93e7 b3ebbd55 769886bc 651d06b0 cc53b0f6 3bce3c3e 27d2604b', // b
'6b17d1f2 e12c4247 f8bce6e5 63a440f2 77037d81 2deb33a0 f4a13945 d898c296', // G_x
'4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16 2bce3357 6b315ece cbb64068 37bf51f5', // G_y
'ffffffff 00000000 ffffffff ffffffff bce6faad a7179e84 f3b9cac2 fc632551', // n
'ffffffff 00000001 00000000 00000000 00000000 ffffffff ffffffff ffffffff') // p
export let P384 = new Curve(
'P-384',
'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe ffffffff 00000000 00000000 fffffffc',
'b3312fa7 e23ee7e4 988e056b e3f82d19 181d9c6e fe814112 0314088f 5013875a c656398d 8a2ed19d 2a85c8ed d3ec2aef',
'aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98 59f741e0 82542a38 5502f25d bf55296c 3a545e38 72760ab7',
'3617de4a 96262c6f 5d9e98bf 9292dc29 f8f41dbd 289a147c e9da3113 b5f0b8c0 0a60b1ce 1d7e819d 7a431d7c 90ea0e5f',
'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff c7634d81 f4372ddf 581a0db2 48b0a77a ecec196a ccc52973',
'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe ffffffff 00000000 00000000 ffffffff')
export let P521 = new Curve(
'P-521',
'000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff' +
'ffffffff ffffffff ffffffff ffffffff fffffffc',
'00000051 953eb961 8e1c9a1f 929a21a0 b68540ee a2da725b 99b315f3 b8b48991 8ef109e1 56193951 ec7e937b 1652c0bd' +
'3bb1bf07 3573df88 3d2c34f1 ef451fd4 6b503f00',
'000000c6 858e06b7 0404e9cd 9e3ecb66 2395b442 9c648139 053fb521 f828af60 6b4d3dba a14b5e77 efe75928 fe1dc127' +
'a2ffa8de 3348b3c1 856a429b f97e7e31 c2e5bd66',
'00000118 39296a78 9a3bc004 5c8a5fb4 2c7d1bd9 98f54449 579b4468 17afbd17 273e662c 97ee7299 5ef42640 c550b901' +
'3fad0761 353c7086 a272c240 88be9476 9fd16650',
'000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffa 51868783 bf2f966b 7fcc0148' +
'f709a5d0 3bb5c9b8 899c47ae bb6fb71e 91386409',
'000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff' +
'ffffffff ffffffff ffffffff ffffffff ffffffff')
import { SHA2 } from './sha2'
import { SHA256 } from './sha-256'
import { SHA384, SHA512 } from './sha-512'
/**
* HMAC Implementation
*/
export class HMAC {
/** Hash constructor */
alg: typeof SHA256 | typeof SHA384 | typeof SHA512
/** Inner hash instance */
hash: SHA2
/** Outer padding */
opad: Uint8Array
/** Digest length of the hashing algorithm */
size: number
/**
* Setup the HMAC instance
* @param h Hash constructor
* @param key Key to use for HMAC
*/
constructor (h: typeof SHA256 | typeof SHA384 | typeof SHA512, key: Uint8Array) {
this.alg = h
this.hash = new h()
this.size = this.hash.digestLength
if (key.length > this.hash.blockSize) {
let hash = new h()
hash.update(key)
key = hash.finalize()
} else if (key.length < this.hash.blockSize) {
let tmp = new Uint8Array(this.hash.blockSize)
tmp.set(key)
key = tmp
}
let ipad = new Uint8Array(this.hash.blockSize)
this.opad = new Uint8Array(this.hash.blockSize)
for (let i = 0; i < this.hash.blockSize; i++) {
ipad[i] = key[i] ^ 0x36
this.opad[i] = key[i] ^ 0x5c
}
this.hash.update(ipad)
}
/**
* Update the HMAC with new data.
* @param data Data to add to the HMAC instance
*/
update (data: Uint8Array): void {
this.hash.update(data)
}
/**
* Finalize and return the HMAC data.
* @return Final digest.
*/
finalize (): Uint8Array {
let h = this.hash.finalize()
let hash = new this.alg()
hash.update(this.opad)
hash.update(h)
return hash.finalize()
}
}
import { SHA256 } from './sha-256'
import { SHA384, SHA512 } from './sha-512'
import { HMAC } from './hmac'
/**
* PBKDF2 algorithm
* @param password Password to hash
* @param salt Salt for the password
* @param iterations Iterations to perform
* @param keyLen Output key length
* @param h Hashing Constructor
* @return PBKDF2 Hash
*/
export function pbkdf2 (password: Uint8Array, salt: Uint8Array, iterations: number, keyLen: number, h: typeof SHA256 | typeof SHA384 | typeof SHA512): Uint8Array {
let hasher = new HMAC(h, password)
let dk = new Uint8Array(keyLen)
let block1 = new Uint8Array(salt.length + 4)
block1.set(salt)
let destPos = 0
let hashLen = hasher.size
let l = Math.ceil(keyLen / hashLen)
for (let i = 1; i <= l; i++) {
let buf = new DataView(block1.buffer)
buf.setUint32(salt.length, i)
let hmac = new HMAC(h, password)
hmac.update(block1)
let T = hmac.finalize()
let U = new Uint8Array(T)
for (let j = 1; j < iterations; j++) {
hmac = new HMAC(h, password)
hmac.update(U)
U = hmac.finalize()
for (let k = 0; k < hashLen; k++) {
T[k] ^= U[k]
}
}
dk.set(T.slice(0, dk.length - destPos), destPos)
destPos += hashLen
}
return dk
}
import { SHA2 } from './sha2'
/**
* SHA-256 Implementation
*/
export class SHA256 extends SHA2 {
/** Hashing block size */
readonly blockSize = 64
/** Length of the final digest hash */
readonly digestLength = 32
/** HMAC strength */
readonly hmacStrength = 24
/** Length of padding */
protected readonly padLength = 8
/** Rounds of hashing to perform */
protected readonly rounds = 64
/** Hash constants */
protected readonly k = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
]
/** Initial hash state */
protected h = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]
/** Buffer for processing the initial message */
protected W: number[] = new Array(this.rounds)
/**
* Run SHA-256
* @param message Message to hash
*/
protected doUpdate (message: Uint8Array): void {
let view = new DataView(message.buffer)
for (let i = 0; i < 16; i++) {
this.W[i] = view.getUint32(i * 4)
}
for (let i = 16; i < this.rounds; i++) {
this.W[i] = this.sum(this.W[i - 16], this.sigma0(this.W[i - 15]), this.W[i - 7], this.sigma1(this.W[i - 2]))
}
let a = this.h[0]
let b = this.h[1]
let c = this.h[2]
let d = this.h[3]
let e = this.h[4]
let f = this.h[5]
let g = this.h[6]
let h = this.h[7]
for (let i = 0; i < this.rounds; i++) {
let temp1 = this.sum(h, this.Sigma1(e), this.ch(e, f, g), this.k[i], this.W[i])
let temp2 = this.sum(this.Sigma0(a), this.maj(a, b, c))
h = g
g = f
f = e
e = this.sum(d, temp1)
d = c
c = b
b = a
a = this.sum(temp1, temp2)
}
this.h[0] = this.sum(this.h[0], a)
this.h[1] = this.sum(this.h[1], b)
this.h[2] = this.sum(this.h[2], c)
this.h[3] = this.sum(this.h[3], d)
this.h[4] = this.sum(this.h[4], e)
this.h[5] = this.sum(this.h[5], f)
this.h[6] = this.sum(this.h[6], g)
this.h[7] = this.sum(this.h[7], h)
}
/**
* Generate the message digest
* @return Hash digest
*/
protected genDigest (): Uint8Array {
let ret = new DataView(new ArrayBuffer(this.digestLength))
this.h.forEach((v, i) => {
ret.setUint32(i * 4, v)
})
return new Uint8Array(ret.buffer)
}
/**
* Choose bitwise function.
* @param x Value to be tested.
* @param y Value to be returned if true.
* @param z Value to be returned if false.
* @return Integer comprising of a combination of y and z bits depending on x.
*/
private ch (x: number, y: number, z: number): number {
return (x & y) ^ ((~x) & z)
}
/**
* Majority bitwise function. Value becomes whatever the majority is (1 or 0) on a per-bit basis.
* @param x First value to test
* @param y Second value to test
* @param z Third value to test
* @return Integer consisting of the majority bits for each place.
*/
private maj (x: number, y: number, z: number): number {
return ((x & y) ^ (x & z) ^ (y & z))
}
/**
* Rotate an integer to right by b bits.
* @param w Integer to rotate
* @param b Rotation amount in bits
* @return Rotated integer
*/
private rotr (w: number, b: number): number {
return (w >>> b) | (w << (32 - b))
}
/**
* Add the arguments and return the value as an unsigned 32-bit integer modulo 2^32.
* @param ...values Arguments to reduce/add
* @return Sum mod 2^32.
*/
private sum (...values: number[]): number {
return values.reduce((a, b) => a + b >>> 0)
}
/**
* Big Sigma 0 function as specified in SHA-2 spec for SHA-256.
* @param x Value to process
* @return Resulting 32-bit integer.
*/
private Sigma0 (x: number): number {
return this.rotr(x, 2) ^ this.rotr(x, 13) ^ this.rotr(x, 22)
}
/**
* Big Sigma 1 function as specified in SHA-2 spec for SHA-256.
* @param x Value to process
* @return Resulting 32-bit integer.
*/
private Sigma1 (x: number): number {
return this.rotr(x, 6) ^ this.rotr(x, 11) ^ this.rotr(x, 25)
}
/**
* Small Sigma 0 function as specified in SHA-2 spec for SHA-256.
* @param x Value to process
* @return Resulting 32-bit integer.
*/
private sigma0 (x: number): number {
return this.rotr(x, 7) ^ this.rotr(x, 18) ^ (x >>> 3)
}
/**
* Small Sigma 1 function as specified in SHA-2 spec for SHA-256.
* @param x Value to process
* @return Resulting 32-bit integer.
*/
private sigma1 (x: number): number {
return this.rotr(x, 17) ^ this.rotr(x, 19) ^ (x >>> 10)
}
}
import BN from 'bn.js'
import { SHA2 } from './sha2'
/**
* SHA-512 implementation
*/
export class SHA512 extends SHA2 {
/** Block size */
readonly blockSize = 128
/** Length of the final hash digest */
digestLength = 64
/** HMAC Strength */
readonly hmacStrength = 24
/** Max padding length */
protected readonly padLength = 16
/** Rounds of hashing to perform */
protected readonly rounds = 80
/** Modulo */
protected readonly mod = new BN('10000000000000000', 16)
/** Hash constants */
protected readonly k = [
new BN('428a2f98d728ae22', 16), new BN('7137449123ef65cd', 16), new BN('b5c0fbcfec4d3b2f', 16),
new BN('e9b5dba58189dbbc', 16), new BN('3956c25bf348b538', 16), new BN('59f111f1b605d019', 16),
new BN('923f82a4af194f9b', 16), new BN('ab1c5ed5da6d8118', 16), new BN('d807aa98a3030242', 16),
new BN('12835b0145706fbe', 16), new BN('243185be4ee4b28c', 16), new BN('550c7dc3d5ffb4e2', 16),
new BN('72be5d74f27b896f', 16), new BN('80deb1fe3b1696b1', 16), new BN('9bdc06a725c71235', 16),
new BN('c19bf174cf692694', 16), new BN('e49b69c19ef14ad2', 16), new BN('efbe4786384f25e3', 16),
new BN('0fc19dc68b8cd5b5', 16), new BN('240ca1cc77ac9c65', 16), new BN('2de92c6f592b0275', 16),
new BN('4a7484aa6ea6e483', 16), new BN('5cb0a9dcbd41fbd4', 16), new BN('76f988da831153b5', 16),
new BN('983e5152ee66dfab', 16), new BN('a831c66d2db43210', 16), new BN('b00327c898fb213f', 16),
new BN('bf597fc7beef0ee4', 16), new BN('c6e00bf33da88fc2', 16), new BN('d5a79147930aa725', 16),
new BN('06ca6351e003826f', 16), new BN('142929670a0e6e70', 16), new BN('27b70a8546d22ffc', 16),
new BN('2e1b21385c26c926', 16), new BN('4d2c6dfc5ac42aed', 16), new BN('53380d139d95b3df', 16),
new BN('650a73548baf63de', 16), new BN('766a0abb3c77b2a8', 16), new BN('81c2c92e47edaee6', 16),
new BN('92722c851482353b', 16), new BN('a2bfe8a14cf10364', 16), new BN('a81a664bbc423001', 16),
new BN('c24b8b70d0f89791', 16), new BN('c76c51a30654be30', 16), new BN('d192e819d6ef5218', 16),
new BN('d69906245565a910', 16), new BN('f40e35855771202a', 16), new BN('106aa07032bbd1b8', 16),
new BN('19a4c116b8d2d0c8', 16), new BN('1e376c085141ab53', 16), new BN('2748774cdf8eeb99', 16),
new BN('34b0bcb5e19b48a8', 16), new BN('391c0cb3c5c95a63', 16), new BN('4ed8aa4ae3418acb', 16),
new BN('5b9cca4f7763e373', 16), new BN('682e6ff3d6b2b8a3', 16), new BN('748f82ee5defb2fc', 16),
new BN('78a5636f43172f60', 16), new BN('84c87814a1f0ab72', 16), new BN('8cc702081a6439ec', 16),
new BN('90befffa23631e28', 16), new BN('a4506cebde82bde9', 16), new BN('bef9a3f7b2c67915', 16),
new BN('c67178f2e372532b', 16), new BN('ca273eceea26619c', 16), new BN('d186b8c721c0c207', 16),
new BN('eada7dd6cde0eb1e', 16), new BN('f57d4f7fee6ed178', 16), new BN('06f067aa72176fba', 16),
new BN('0a637dc5a2c898a6', 16), new BN('113f9804bef90dae', 16), new BN('1b710b35131c471b', 16),
new BN('28db77f523047d84', 16), new BN('32caab7b40c72493', 16), new BN('3c9ebe0a15c9bebc', 16),
new BN('431d67c49c100d4c', 16), new BN('4cc5d4becb3e42b6', 16), new BN('597f299cfc657e2a', 16),
new BN('5fcb6fab3ad6faec', 16), new BN('6c44198c4a475817', 16)
]
/** Hash state */
protected h = [
new BN('6a09e667f3bcc908', 16), new BN('bb67ae8584caa73b', 16), new BN('3c6ef372fe94f82b', 16),
new BN('a54ff53a5f1d36f1', 16), new BN('510e527fade682d1', 16), new BN('9b05688c2b3e6c1f', 16),
new BN('1f83d9abfb41bd6b', 16), new BN('5be0cd19137e2179', 16)
]
/** Message state */
protected W: BN[] = new Array(this.rounds)
/**
* Hash a block of data.
* @param message Block of data.
*/
protected doUpdate (message: Uint8Array): void {
for (let i = 0; i < 16; i++) {
this.W[i] = new BN(message.slice(i * 8, i * 8 + 8))
}
for (let i = 16; i < this.rounds; i++) {
this.W[i] = this.sum(this.W[i - 16], this.sigma0(this.W[i - 15]), this.W[i - 7], this.sigma1(this.W[i - 2]))
}
let a = this.h[0]
let b = this.h[1]
let c = this.h[2]
let d = this.h[3]
let e = this.h[4]
let f = this.h[5]
let g = this.h[6]
let h = this.h[7]
for (let i = 0; i < this.rounds; i++) {
let temp1 = this.sum(h, this.Sigma1(e), this.ch(e, f, g), this.k[i], this.W[i])
let temp2 = this.sum(this.Sigma0(a), this.maj(a, b, c))
h = g
g = f
f = e
e = this.sum(d, temp1)
d = c
c = b
b = a
a = this.sum(temp1, temp2)
}
this.h[0] = this.sum(this.h[0], a)
this.h[1] = this.sum(this.h[1], b)
this.h[2] = this.sum(this.h[2], c)
this.h[3] = this.sum(this.h[3], d)
this.h[4] = this.sum(this.h[4], e)
this.h[5] = this.sum(this.h[5], f)
this.h[6] = this.sum(this.h[6], g)
this.h[7] = this.sum(this.h[7], h)
}
/**
* Generate the final digest.
* @return Hash digest
*/
protected genDigest (): Uint8Array {
let ret = new Uint8Array(this.digestLength)
this.h.forEach((h, i) => ret.set(h.toArray('be', 8), i * 8))
return ret
}
/**
* Bitwise Choose
* @param x Value to be tested.
* @param y Value to be returned if true.
* @param z Value to be returned if false.
* @return Integer comprising of a combination of y and z bits depending on x.
*/
private ch (x: BN, y: BN, z: BN): BN {
return x.and(y).xor(x.notn(64).and(z))
}
/**
* Majority bitwise function. Value becomes whatever the majority is (1 or 0) on a per-bit basis.
* @param x First value to test
* @param y Second value to test
* @param z Third value to test
* @return Integer consisting of the majority bits for each place.
*/
private maj (x: BN, y: BN, z: BN): BN {
return x.and(y).xor(x.and(z)).xor(y.and(z))
}
/**
* Rotate an integer to right by b bits.
* @param w Integer to rotate
* @param b Rotation amount in bits
* @return Rotated integer
*/
private rotr (num: BN, bits: number): BN {
return num.ushrn(bits).mod(this.mod).or(num.ushln(64 - bits).mod(this.mod))
}
/**
* Add the arguments and return the value as an unsigned 64-bit integer modulo 2^64.
* @param ...values Arguments to reduce/add
* @return Sum mod 2^64.
*/
private sum (...values: BN[]): BN {
return values.reduce((a, b) => a.add(b)).mod(this.mod)
}
/**
* Big Sigma 0 function as specified in SHA-2 spec for SHA-512.
* @param x Value to process
* @return Resulting 64-bit integer.
*/
private Sigma0 (n: BN): BN {
return this.rotr(n, 28).xor(this.rotr(n, 34)).xor(this.rotr(n, 39))
}
/**
* Big Sigma 1 function as specified in SHA-2 spec for SHA-512.
* @param x Value to process
* @return Resulting 64-bit integer.
*/
private Sigma1 (n: BN): BN {
return this.rotr(n, 14).xor(this.rotr(n, 18)).xor(this.rotr(n, 41))
}
/**
* Small Sigma 0 function as specified in SHA-2 spec for SHA-512.
* @param x Value to process
* @return Resulting 64-bit integer.
*/
private sigma0 (n: BN): BN {
return this.rotr(n, 1).xor(this.rotr(n, 8)).xor(n.ushrn(7))
}
/**
* Small Sigma 1 function as specified in SHA-2 spec for SHA-256.
* @param x Value to process
* @return Resulting 32-bit integer.
*/
private sigma1 (n: BN): BN {
return this.rotr(n, 19).xor(this.rotr(n, 61)).xor(n.ushrn(6))
}
}
/**
* SHA-384 Implementation
*/
export class SHA384 extends SHA512 {
/** Hash digest length */
readonly digestLength = 48
/** Hash state */
h = [
new BN('cbbb9d5dc1059ed8', 16), new BN('629a292a367cd507', 16), new BN('9159015a3070dd17', 16),
new BN('152fecd8f70e5939', 16), new BN('67332667ffc00b31', 16), new BN('8eb44a8768581511', 16),
new BN('db0c2e0d64f98fa7', 16), new BN('47b5481dbefa4fa4', 16)
]
/**
* Generate the hash digest
* @return Hash digest
*/
protected genDigest (): Uint8Array {
let ret = new Uint8Array(this.digestLength)
this.h.slice(0, 6).forEach((h, i) => ret.set(h.toArray('be', 8), i * 8))
return ret
}
}
import BN from 'bn.js'
/**
* SHA2 Implementation
*/
export abstract class SHA2 {
/** Hash block size */
abstract readonly blockSize: number
/** Length of the digest output */
abstract readonly digestLength: number
/** HMAC Strength */
abstract readonly hmacStrength: number
/** Data to hash but overflowing previous blocks */
protected pending: Uint8Array = new Uint8Array(0)
/** Message length so far */
protected messageLength = 0
/** Maximum amount of padding. */
protected abstract readonly padLength: number
/** Number of hashing rounds */
protected abstract readonly rounds: number
/** Hash constants */
protected abstract readonly k: number[] | BN[]
/** Hash state */
protected abstract h: number[] | BN[]
/** Block state */
protected abstract W: number[] | BN[]
/**
* Update a hash by adding new data to hash
* @param message Data to hash
*/
update (message: Uint8Array): void {
let pending = new Uint8Array(message.length + this.pending.length)
pending.set(this.pending)
pending.set(message, this.pending.length)
this.pending = pending
this.messageLength += message.length
while (this.pending.length >= this.blockSize) {
this.doUpdate(this.pending.slice(0, this.blockSize))
this.pending = this.pending.slice(this.blockSize)
}
}
/**
* Finalize a hash by padding it and generating the digest.
* @return Hash digest
*/
finalize (): Uint8Array {
this.update(this.pad())
return this.genDigest()
}
/**
* Perform the hashing operation.
* @param message Message block
*/
protected abstract doUpdate(message: Uint8Array): void
/**
* Generate the digest
* @return Hash digest
*/
protected abstract genDigest(): Uint8Array
/**
* Generate the necessary amount of padding.
* @return Padding
*/
protected pad (): Uint8Array {
let k = this.blockSize - ((this.messageLength + this.padLength) % this.blockSize)
let view = new DataView(new ArrayBuffer(k + this.padLength))
view.setUint8(0, 0x80)
view.setUint32(view.buffer.byteLength - 4, this.messageLength * 8)
return new Uint8Array(view.buffer)
}
}
import {zipWith} from 'lodash-es'
import * as base64 from 'base64-js'
/**
* Generate random bytes. This should be removed due to security reasons.
* @param length Number of bytes to generate
* @return Promise of generated bytes
*/
export async function generateRandomBytes (length: number): Promise<Uint8Array> {
let ret: Uint8Array = new Uint8Array(length)
try {
await window.crypto.getRandomValues(ret)
} catch (e) {
for (let offset: number = 0; offset < length; offset++) {
ret[offset] = Math.floor(Math.random() * 256)
}
}
return ret
}
/**
* Compare two buffers for equality in constant time.
* @param a First buffer
* @param b Second buffer
* @return Result of equality test
*/
export function bufferConstTimeEq (a: Uint8Array, b: Uint8Array): boolean {
if (a.length !== b.length) {
return false
}
let c = 0
zipWith(a, b, (x1, x2) => { c |= x1 ^ x2 })
return c === 0
}
/**
* Check equality of two buffers
* @param a Operand A
* @param b Operand B
* @return Result of equality test
*/
export function bytesEqual (a: Uint8Array, b: Uint8Array): boolean {
if (a.length !== b.length) {
return false
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false
}
}
return true
}
/**
* Generate a UUID
* @return 16 byte UUID
*/
export async function uuid4 (): Promise<Uint8Array> {
let uuid = await generateRandomBytes(16)
uuid[6] = (uuid[6] & 0x0f) | 0x40
uuid[8] = (uuid[8] & 0x3f) | 0x80
return uuid
}
/**
* Assert a conditional, raising an error if it fails
* @param assertion Conditional to test
* @param message Message of the error to raise
*/
export function assert (assertion: boolean, message: string) {
if (!assertion) {
throw new Error(`Aliro Assertion error: ${message}`)
}
}
/**
* Convert a byte array to URL-encoded Base64
* @param data Data to encode
* @return Encoded string
*/
export function toBase64URI (data: Uint8Array): string {
return base64.fromByteArray(data).replace(/\//g, '_').replace(/\+/g, '-').replace('=', '')
}
/**
* Convert URL-encoded Base64 to a byte array
* @param data String to decode
* @return Decoded data
*/
export function fromBase64URI (data: string): Uint8Array {
data = data.replace('-', '+').replace('_', '/')
if (data.length % 4 === 3) {
data += '='
} else if (data.length % 4 === 2) {
data += '=='
}
return base64.toByteArray(data)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment