@logosnetwork/logos-webwallet-sdk
Version:
Create Logos wallets with or without a full Logos node
347 lines (304 loc) • 11.6 kB
text/typescript
import b2wasm from './blake2b'
interface Context {
b: Uint8Array;
h: Uint32Array;
t: number; // input count
c: number; // pointer within buffer
outlen: number; // output length in bytes
}
const wasm = b2wasm()
// Initialization Vector
const BLAKE2B_IV32 = new Uint32Array([
0xF3BCC908, 0x6A09E667, 0x84CAA73B, 0xBB67AE85,
0xFE94F82B, 0x3C6EF372, 0x5F1D36F1, 0xA54FF53A,
0xADE682D1, 0x510E527F, 0x2B3E6C1F, 0x9B05688C,
0xFB41BD6B, 0x1F83D9AB, 0x137E2179, 0x5BE0CD19
])
const SIGMA8 = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3,
11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4,
7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8,
9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13,
2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9,
12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11,
13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10,
6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5,
10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3
]
// These are offsets into a uint64 buffer.
// Multiply them all by 2 to make them offsets into a uint32 buffer,
// because this is Javascript and we don't have uint64s
const SIGMA82 = new Uint8Array(SIGMA8.map((x): number => x * 2))
const toHex = (n: number): string => {
if (n < 16) return '0' + n.toString(16)
return n.toString(16)
}
const hexSlice = (buf: Buffer|Uint8Array): string => {
let str = ''
for (let i = 0; i < buf.length; i++) str += toHex(buf[i])
return str
}
const wasmHexSlice = (buf: Buffer|Uint8Array, start: number, len: number): string => {
let str = ''
for (let i = 0; i < len; i++) str += toHex(buf[start + i])
return str
}
const WASM_LOADED = (): boolean => Boolean(wasm && wasm.exports)
const BYTES_MIN = 1
const BYTES_MAX = 64
const BYTES = 32
const KEYBYTES_MIN = 16
const KEYBYTES_MAX = 64
const KEYBYTES = 32
const SALTBYTES = 16
const PERSONALBYTES = 16
const SUPPORTED = typeof WebAssembly !== 'undefined'
let head = 64
const freeList: number[] = []
// Creates a BLAKE2b hashing context
// Requires an output length between 1 and 64 bytes
// Takes an optional Uint8Array key
export default class Blake2b {
private context: Context
private outlen: number
private finalized: boolean
private pointer: number
private mode: 'wasm' | 'js'
private v: Uint32Array
private m: Uint32Array
public constructor (outlen = 32, key: Uint8Array = null, salt: Uint8Array = null, personal: Uint8Array = null, forceUseJS = false) {
if (outlen < BYTES_MIN) throw new Error(`outlen must be at least ${BYTES_MIN}, was given ${outlen}`)
if (outlen > BYTES_MAX) throw new Error(`outlen must be at most ${BYTES_MAX}, was given ${outlen}`)
if (key !== null) {
if (key.length < KEYBYTES_MIN) throw new Error(`key must be at least ${KEYBYTES_MIN}, was given ${key.length}`)
if (key.length > KEYBYTES_MAX) throw new Error(`key must be at most ${KEYBYTES_MAX}, was given ${key.length}`)
}
if (salt !== null) {
if (salt.length !== SALTBYTES) throw new Error(`salt must be exactly ${SALTBYTES}, was given ${salt.length}`)
}
if (personal !== null) {
if (personal.length !== PERSONALBYTES) throw new Error(`personal must be exactly ${PERSONALBYTES}, was given ${personal.length}`)
}
this.v = new Uint32Array(32)
this.m = new Uint32Array(32)
this.mode = null
this.finalized = false
this.outlen = outlen
if (!forceUseJS && wasm && wasm.exports && (this.mode === null || this.mode === 'wasm')) {
this.mode = 'wasm'
if (!freeList.length) {
freeList.push(head)
head += 216
}
this.pointer = freeList.pop()
wasm.memory.fill(0, 0, 64)
wasm.memory[0] = this.outlen
wasm.memory[1] = key ? key.length : 0
wasm.memory[2] = 1 // fanout
wasm.memory[3] = 1 // depth
if (salt) wasm.memory.set(salt, 32)
if (personal) wasm.memory.set(personal, 48)
if (this.pointer + 216 > wasm.memory.length) wasm.realloc(this.pointer + 216) // we need 216 bytes for the state
wasm.exports.blake2b_init(this.pointer, this.outlen)
if (key) {
this.update(key)
wasm.memory.fill(0, head, head + key.length) // whiteout key
wasm.memory[this.pointer + 200] = 128
}
} else {
if (wasm && !wasm.exports && this.mode === null) console.log('Using JS Fallback since WASM is still loading')
this.mode = 'js'
// state, 'param block'
this.context = {
b: new Uint8Array(128),
h: new Uint32Array(16),
t: 0, // input count
c: 0, // pointer within buffer
outlen: outlen // output length in bytes
}
// initialize hash state
for (let i = 0; i < 16; i++) {
this.context.h[i] = BLAKE2B_IV32[i]
}
const keylen = key ? key.length : 0
this.context.h[0] ^= 0x01010000 ^ (keylen << 8) ^ this.context.outlen
// key the hash, if applicable
if (key) {
this.update(key)
// at the end
this.context.c = 128
}
}
}
public WASM = wasm && wasm.buffer
// 64-bit unsigned addition
// Sets v[a,a+1] += v[b,b+1]
private ADD64AA = (a: number, b: number): void => {
const o0 = this.v[a] + this.v[b]
let o1 = this.v[a + 1] + this.v[b + 1]
if (o0 >= 0x100000000) {
o1++
}
this.v[a] = o0
this.v[a + 1] = o1
}
// 64-bit unsigned addition
// Sets v[a,a+1] += b
// b0 is the low 32 bits of b, b1 represents the high 32 bits
private ADD64AC = (a: number, b0: number, b1: number): void => {
let o0 = this.v[a] + b0
if (b0 < 0) {
o0 += 0x100000000
}
let o1 = this.v[a + 1] + b1
if (o0 >= 0x100000000) {
o1++
}
this.v[a] = o0
this.v[a + 1] = o1
}
// Little-endian byte access
private B2B_GET32 = (arr: Uint8Array, i: number): number => {
return (arr[i] ^
(arr[i + 1] << 8) ^
(arr[i + 2] << 16) ^
(arr[i + 3] << 24))
}
// G Mixing function
// The ROTRs are inlined for speed
private B2B_G = (a: number, b: number, c: number, d: number, ix: number, iy: number): void => {
const x0 = this.m[ix]
const x1 = this.m[ix + 1]
const y0 = this.m[iy]
const y1 = this.m[iy + 1]
this.ADD64AA(a, b) // v[a,a+1] += v[b,b+1] ... in JS we must store a uint64 as two uint32s
this.ADD64AC(a, x0, x1) // v[a, a+1] += x ... x0 is the low 32 bits of x, x1 is the high 32 bits
// v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated to the right by 32 bits
let xor0 = this.v[d] ^ this.v[a]
let xor1 = this.v[d + 1] ^ this.v[a + 1]
this.v[d] = xor1
this.v[d + 1] = xor0
this.ADD64AA(c, d)
// v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 24 bits
xor0 = this.v[b] ^ this.v[c]
xor1 = this.v[b + 1] ^ this.v[c + 1]
this.v[b] = (xor0 >>> 24) ^ (xor1 << 8)
this.v[b + 1] = (xor1 >>> 24) ^ (xor0 << 8)
this.ADD64AA(a, b)
this.ADD64AC(a, y0, y1)
// v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated right by 16 bits
xor0 = this.v[d] ^ this.v[a]
xor1 = this.v[d + 1] ^ this.v[a + 1]
this.v[d] = (xor0 >>> 16) ^ (xor1 << 16)
this.v[d + 1] = (xor1 >>> 16) ^ (xor0 << 16)
this.ADD64AA(c, d)
// v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 63 bits
xor0 = this.v[b] ^ this.v[c]
xor1 = this.v[b + 1] ^ this.v[c + 1]
this.v[b] = (xor1 >>> 31) ^ (xor0 << 1)
this.v[b + 1] = (xor0 >>> 31) ^ (xor1 << 1)
}
// Compression function. 'last' flag indicates last block.
// Note we're representing 16 uint64s as 32 uint32s
private blake2bCompress = (last: boolean): void => {
// init work variables
for (let i = 0; i < 16; i++) {
this.v[i] = this.context.h[i]
this.v[i + 16] = BLAKE2B_IV32[i]
}
// low 64 bits of offset
this.v[24] = this.v[24] ^ this.context.t
this.v[25] = this.v[25] ^ (this.context.t / 0x100000000)
// high 64 bits not supported, offset may not be higher than 2**53-1
// last block flag set ?
if (last) {
this.v[28] = ~this.v[28]
this.v[29] = ~this.v[29]
}
// get little-endian words
for (let i = 0; i < 32; i++) {
this.m[i] = this.B2B_GET32(this.context.b, 4 * i)
}
// twelve rounds of mixing
for (let i = 0; i < 12; i++) {
this.B2B_G(0, 8, 16, 24, SIGMA82[i * 16 + 0], SIGMA82[i * 16 + 1])
this.B2B_G(2, 10, 18, 26, SIGMA82[i * 16 + 2], SIGMA82[i * 16 + 3])
this.B2B_G(4, 12, 20, 28, SIGMA82[i * 16 + 4], SIGMA82[i * 16 + 5])
this.B2B_G(6, 14, 22, 30, SIGMA82[i * 16 + 6], SIGMA82[i * 16 + 7])
this.B2B_G(0, 10, 20, 30, SIGMA82[i * 16 + 8], SIGMA82[i * 16 + 9])
this.B2B_G(2, 12, 22, 24, SIGMA82[i * 16 + 10], SIGMA82[i * 16 + 11])
this.B2B_G(4, 14, 16, 26, SIGMA82[i * 16 + 12], SIGMA82[i * 16 + 13])
this.B2B_G(6, 8, 18, 28, SIGMA82[i * 16 + 14], SIGMA82[i * 16 + 15])
}
for (let i = 0; i < 16; i++) {
this.context.h[i] = this.context.h[i] ^ this.v[i] ^ this.v[i + 16]
}
}
public update = (input: Uint8Array | Buffer): Blake2b => {
if (this.finalized) throw new Error('Hash instance finalized')
if (wasm && wasm.exports && this.mode === 'wasm') {
if (head + input.length > wasm.memory.length) wasm.realloc(head + input.length)
wasm.memory.set(input, head)
wasm.exports.blake2b_update(this.pointer, head, head + input.length)
} else {
for (let i = 0; i < input.length; i++) {
if (this.context.c === 128) { // buffer full ?
this.context.t += this.context.c // add counters
this.blake2bCompress(false) // compress (not last)
this.context.c = 0 // counter to zero
}
this.context.b[this.context.c++] = input[i]
}
}
return this
}
public digest = (out?: 'binary'|'hex'|Uint8Array|Buffer): Uint8Array|string => {
if (this.finalized) throw new Error('Hash instance finalized')
this.finalized = true
if (wasm && wasm.exports && this.mode === 'wasm') {
freeList.push(this.pointer)
wasm.exports.blake2b_final(this.pointer)
if (!out || out === 'binary') {
return wasm.memory.slice(this.pointer + 128, this.pointer + 128 + this.outlen)
}
if (out === 'hex') {
return wasmHexSlice(wasm.memory, this.pointer + 128, this.outlen).toUpperCase()
}
if (out.length >= this.outlen) throw new Error('input must be TypedArray or Buffer')
for (let i = 0; i < this.outlen; i++) {
out[i] = wasm.memory[this.pointer + 128 + i]
}
return out
} else {
const buf = (!out || out === 'binary' || out === 'hex') ? new Uint8Array(this.context.outlen) : out
if (buf.length < this.context.outlen) throw new Error(`out must have at least ${this.context.outlen} bytes of space`)
this.context.t += this.context.c // mark last block offset
while (this.context.c < 128) { // fill up with zeros
this.context.b[this.context.c++] = 0
}
this.blake2bCompress(true) // final block flag = 1
for (let i = 0; i < this.context.outlen; i++) {
buf[i] = this.context.h[i >> 2] >> (8 * (i & 3))
}
if (out === 'hex') return hexSlice(buf).toUpperCase()
return buf
}
}
public final = this.digest
}
export {
Blake2b,
WASM_LOADED,
SUPPORTED,
BYTES_MIN,
BYTES_MAX,
BYTES,
KEYBYTES_MIN,
KEYBYTES_MAX,
KEYBYTES,
SALTBYTES,
PERSONALBYTES
}