From: Chris Duncan Date: Wed, 25 Feb 2026 22:31:34 +0000 (-0800) Subject: Overhaul module to use static buffers to prevent memory leaks, 32-bit limbs to accele... X-Git-Tag: v1.0.0~20 X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=b24eb24ae8fe6dab14a56f6e4c42dfa81c0e5797;p=nano25519.git Overhaul module to use static buffers to prevent memory leaks, 32-bit limbs to accelerate multiplication with SIMD vectors, copy I/O directly to and from buffers, simplify entry point functions in host code, and fix calculation bugs. --- diff --git a/assembly/nano-nacl.ts b/assembly/nano-nacl.ts index 6ab26f3..5bcc896 100644 --- a/assembly/nano-nacl.ts +++ b/assembly/nano-nacl.ts @@ -47,32 +47,34 @@ class Blake2b { // m: message buffer, represents current state of `b` during compression // t: total byte counter, incremented by `c` (BLAKE2b supports 2¹²⁸-1) // v: state vector, set to bytes of `h` and `iv` then modified by `t` during compression - b: Uint8Array - c: u8 - h: Uint64Array - m: Uint64Array - t: v128 - v: Uint64Array + b: Uint8Array = new Uint8Array(128) + c: u8 = 0 + h: Uint64Array = new Uint64Array(8) + m: StaticArray = new StaticArray(16) + t: v128 = v128.splat(0) + v: StaticArray = new StaticArray(16) // param_block: set according to BLAKE2 layout from constructor arguments param_block: Uint8Array = new Uint8Array(64) param_view: DataView = new DataView(this.param_block.buffer) - // length: bytes of hash output (1-64) - constructor (length: u8) { + // input: byte-level view of refillable input buffer + input: DataView = new DataView(this.b.buffer) + // output: byte-level view of 64-bit chain buffer + output: Uint8Array = Uint8Array.wrap(this.h.buffer) + + init (): Blake2b { // initialize buffers and counters - this.b = new Uint8Array(128) + this.b.fill(0) this.c = 0 - this.h = new Uint64Array(8) - this.m = new Uint64Array(16) + this.m.fill(0) this.t = v128.splat(0) - this.v = new Uint64Array(16) + this.v.fill(0) // initialize parameter block - this.param_block = new Uint8Array(64) - this.param_view = new DataView(this.param_block.buffer) - this.param_block[0] = length + this.param_block.fill(0) + this.param_block[0] = 64 // always 64 bytes for this implementation this.param_block[1] = 0 // no key this.param_block[2] = 1 // fanout this.param_block[3] = 1 // depth @@ -81,17 +83,18 @@ class Blake2b { for (let i = 0; i < 8; i++) { this.h[i] = blake2b_iv[i] ^ this.param_view.getUint64(i << 3, true) } + return this } // input: variable-length message data passed by user to be hashed - update (input: Uint8Array): Blake2b { - for (let i = 0; i < input.byteLength; i++) { + update (input: StaticArray): Blake2b { + for (let i = 0; i < input.length; i++) { // is buffer full? if (this.c === this.b.byteLength) { // increment total byte counter - this.t = unchecked(v128.add(this.t, i64x2(this.b.byteLength, 0))) + this.t = v128.add(this.t, i64x2(this.b.byteLength, 0)) // reset buffer counter to zero this.c = 0 @@ -106,10 +109,10 @@ class Blake2b { return this } - digest (): Uint8Array { + digest (output: StaticArray): void { // add final message block size to total bytes - this.t = unchecked(v128.add(this.t, i64x2(this.c, 0))) + this.t = v128.add(this.t, i64x2(this.c, 0)) // pad final block with zeros this.b.fill(0, this.c) @@ -117,11 +120,10 @@ class Blake2b { // set final block flag and compress this.COMPRESS(true) - // copy bytes - const bytes = Uint8Array.wrap(this.h.buffer) - const hash = new Uint8Array(this.param_block[0]) - hash.set(bytes.subarray(0, this.param_block[0])) - return hash + // return byte array of 64-bit chain buffer + for (let i = 0; i < 64; i++) { + output[i] = this.output[i] + } } // Defined in BLAKE2 section 2.4 @@ -143,9 +145,8 @@ class Blake2b { isFinal ? this.v[14] = ~this.v[14] : this.v[14] = this.v[14] // copy input buffer to message block - const buf = new DataView(this.b.buffer) for (let i = 0; i < 16; i++) { - this.m[i] = buf.getUint64(i << 3, true) + this.m[i] = this.input.getUint64(i << 3, true) } // twelve rounds of mixing @@ -166,94 +167,123 @@ class Blake2b { const c = blake2b_state[i][2] const d = blake2b_state[i][3] const s = blake2b_sigma[r] - this.G(a, b, c, d, s[i + i], s[i + i + 1]) + this.G(a, b, c, d, s[i << 1], s[(i << 1) + 1]) } } G (a: u8, b: u8, c: u8, d: u8, x: u8, y: u8): void { - this.v[a] = unchecked(unchecked(this.v[a] + this.v[b]) + this.m[x]) + this.v[a] = this.v[a] + this.v[b] + this.m[x] this.v[d] = rotr(this.v[d] ^ this.v[a], 32) - this.v[c] = unchecked(this.v[c] + this.v[d]) + this.v[c] = this.v[c] + this.v[d] this.v[b] = rotr(this.v[b] ^ this.v[c], 24) - this.v[a] = unchecked(unchecked(this.v[a] + this.v[b]) + this.m[y]) + this.v[a] = this.v[a] + this.v[b] + this.m[y] this.v[d] = rotr(this.v[d] ^ this.v[a], 16) - this.v[c] = unchecked(this.v[c] + this.v[d]) + this.v[c] = this.v[c] + this.v[d] this.v[b] = rotr(this.v[b] ^ this.v[c], 63) } } -const BLOCKHASH_BYTES: u8 = 32 -const PRIVATEKEY_BYTES: u8 = 32 -const PUBLICKEY_BYTES: u8 = 32 -const SIGNATURE_BYTES: u8 = 64 -const D: Int64Array = new Int64Array(16); D.set([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203]) -const D2: Int64Array = new Int64Array(16); D2.set([0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406]) -const X: Int64Array = new Int64Array(16); X.set([0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169]) -const Y: Int64Array = new Int64Array(16); Y.set([0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666]) -const I: Int64Array = new Int64Array(16); I.set([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83]) -const XY: Int64Array = new Int64Array(16); XY.set([0xdd90, 0xa5b7, 0x8ab3, 0x6dde, 0x52f5, 0x7751, 0x9f80, 0x20f0, 0xe37d, 0x64ab, 0x4e8e, 0x66ea, 0x7665, 0xd78b, 0x5f0f, 0xe787]) - -function vn (x: Uint8Array, xi: u8, y: Uint8Array, yi: u8, n: i32): i64 { - let d = 0 - for (let i = 0; i < n; i++) { - d |= x[xi + i] ^ y[yi + i] +const BLOCKHASH_BYTES: i32 = 32 +const PRIVATEKEY_BYTES: i32 = 32 +const PUBLICKEY_BYTES: i32 = 32 +const SECRETKEY_BYTES: i32 = PRIVATEKEY_BYTES + PUBLICKEY_BYTES +const SIGNATURE_BYTES: i32 = 64 +const SIGNEDBLOCKHASH_BYTES: i32 = SIGNATURE_BYTES + BLOCKHASH_BYTES +const D: StaticArray = StaticArray.fromArray([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203]) +const D2: StaticArray = StaticArray.fromArray([0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406]) +const X: StaticArray = StaticArray.fromArray([0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169]) +const Y: StaticArray = StaticArray.fromArray([0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666]) +const I: StaticArray = StaticArray.fromArray([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83]) +const XY: StaticArray = StaticArray.fromArray([0xdd90, 0xa5b7, 0x8ab3, 0x6dde, 0x52f5, 0x7751, 0x9f80, 0x20f0, 0xe37d, 0x64ab, 0x4e8e, 0x66ea, 0x7665, 0xd78b, 0x5f0f, 0xe787]) + +// Static I/O buffers +const INPUT_BUFFER = memory.data(128) +const OUTPUT_BUFFER = memory.data(64) + +function vn (x: StaticArray, y: StaticArray): bool { + let d: u8 = 0 + for (let i = 0; i < 32; i++) { + d |= x[i] ^ y[i] } - return (1 & ((d - 1) >>> 8)) - 1 + return d !== 0 } -function pow2523 (o: Int64Array, i: Int64Array): void { - const c: Int64Array = new Int64Array(16) - c.set(i.subarray(0, 16), 0) - for (let a = 0; a < 249; a++) { +const pow_c: StaticArray = new StaticArray(16) +function pow2523 (o: StaticArray, a: StaticArray): void { + const c = pow_c + for (let i = 0; i < 16; i++) { + c[i] = a[i] + } + for (let i = 0; i < 249; i++) { Square(c, c) - Multiply(c, c, i) + Multiply(c, c, a) } Square(c, c) Square(c, c) - Multiply(c, c, i) - o.set(c, 0) + Multiply(c, c, a) + for (let i = 0; i < 16; i++) { + o[i] = c[i] + } } -function car25519 (o: Int64Array): void { - let v: i64 = 0 - let c: i64 = 0 - const s: i64 = 1 << 16 +function car25519 (o: StaticArray): void { + let c: i32 = 0 for (let i = 0; i < 16; i++) { - v = o[i] + c + s - o[i] = v % s - c = v / s - 1 + c += o[i] + o[i] = c & 0xFFFF + c >>= 16 } o[0] += 38 * c } -function inv25519 (o: Int64Array, i: Int64Array): void { - const c: Int64Array = new Int64Array(16) - c.set(i.subarray(0, 16), 0) - for (let a = 253; a >= 0; a--) { +const inv_c: StaticArray = new StaticArray(16) +function inv25519 (o: StaticArray, a: StaticArray): void { + const c = inv_c + for (let i = 0; i < 16; i++) { + c[i] = a[i] + } + for (let i = 0; i < 249; i++) { Square(c, c) - if (a !== 2 && a !== 4) { - Multiply(c, c, i) - } + Multiply(c, c, a) + } + Square(c, c) + Square(c, c) + Multiply(c, c, a) + Square(c, c) + Square(c, c) + Multiply(c, c, a) + Square(c, c) + Multiply(c, c, a) + for (let i = 0; i < 16; i++) { + o[i] = c[i] } - o.set(c, 0) } -function neq25519 (a: Int64Array, b: Int64Array): i64 { - const c = new Uint8Array(32) - const d = new Uint8Array(32) +const neq_c = new StaticArray(32) +const neq_d = new StaticArray(32) +function neq25519 (a: StaticArray, b: StaticArray): bool { + const c = neq_c + const d = neq_d pack25519(c, a) pack25519(d, b) - return vn(c, 0, d, 0, 32) + return vn(c, d) } -function pack25519 (o: Uint8Array, n: Int64Array): void { - let b: i64 = 0 - const m: Int64Array = new Int64Array(16) - const t: Int64Array = new Int64Array(16) - t.set(n.subarray(0, 16), 0) +const pack_m: StaticArray = new StaticArray(16) +const pack_t: StaticArray = new StaticArray(16) +function pack25519 (o: StaticArray, n: StaticArray): void { + const m = pack_m + const t = pack_t + let b: i32 = 0 + + for (let i = 0; i < 16; i++) { + t[i] = n[i] + } + car25519(t) car25519(t) car25519(t) + for (let j = 0; j < 2; j++) { m[0] = t[0] - 0xffed for (let i = 1; i < 15; i++) { @@ -265,21 +295,23 @@ function pack25519 (o: Uint8Array, n: Int64Array): void { m[14] &= 0xffff sel25519(t, m, 1 - b) } + for (let i = 0; i < 16; i++) { - o[i + i] = u8(t[i] & 0xff) - o[i + i + 1] = u8(t[i] >> 8) + o[i << 1] = u8(t[i] & 0xff) + o[(i << 1) + 1] = u8(t[i] >> 8) } } -function par25519 (a: Int64Array): u8 { - const d = new Uint8Array(32) +const par_d = new StaticArray(32) +function par25519 (a: StaticArray): u8 { + const d = par_d pack25519(d, a) return d[0] & 1 } -function sel25519 (p: Int64Array, q: Int64Array, b: i64): void { - let t: i64 = 0 - const c: i64 = ~(b - 1) +function sel25519 (p: StaticArray, q: StaticArray, b: i32): void { + let t: i32 = 0 + const c: i32 = ~(b - 1) for (let i = 0; i < 16; i++) { t = c & (p[i] ^ q[i]) p[i] ^= t @@ -287,119 +319,335 @@ function sel25519 (p: Int64Array, q: Int64Array, b: i64): void { } } -function unpack25519 (o: Int64Array, n: Uint8Array): void { +function unpack25519 (o: StaticArray, n: StaticArray): void { for (let i = 0; i < 16; i++) { - o[i] = n[i + i] + (n[i + i + 1] << 8) + o[i] = i32(n[i << 1]) + (i32(n[(i << 1) + 1]) << 8) } o[15] &= (1 << 15) - 1 } -function Add (o: Int64Array, a: Int64Array, b: Int64Array): void { - for (let i = 0; i < 16; i++) { - o[i] = a[i] + b[i] - } +@inline +function Add (o: StaticArray, a: StaticArray, b: StaticArray): void { + o[0] = a[0] + b[0] + o[1] = a[1] + b[1] + o[2] = a[2] + b[2] + o[3] = a[3] + b[3] + o[4] = a[4] + b[4] + o[5] = a[5] + b[5] + o[6] = a[6] + b[6] + o[7] = a[7] + b[7] + o[8] = a[8] + b[8] + o[9] = a[9] + b[9] + o[10] = a[10] + b[10] + o[11] = a[11] + b[11] + o[12] = a[12] + b[12] + o[13] = a[13] + b[13] + o[14] = a[14] + b[14] + o[15] = a[15] + b[15] } -function Subtract (o: Int64Array, a: Int64Array, b: Int64Array): void { - for (let i = 0; i < 16; i++) { - o[i] = a[i] - b[i] - } +@inline +function Subtract (o: StaticArray, a: StaticArray, b: StaticArray): void { + o[0] = a[0] - b[0] + o[1] = a[1] - b[1] + o[2] = a[2] - b[2] + o[3] = a[3] - b[3] + o[4] = a[4] - b[4] + o[5] = a[5] - b[5] + o[6] = a[6] - b[6] + o[7] = a[7] - b[7] + o[8] = a[8] - b[8] + o[9] = a[9] - b[9] + o[10] = a[10] - b[10] + o[11] = a[11] - b[11] + o[12] = a[12] - b[12] + o[13] = a[13] - b[13] + o[14] = a[14] - b[14] + o[15] = a[15] - b[15] } -function Multiply (o: Int64Array, a: Int64Array, b: Int64Array): void { - let v: i64 = 0 - let c: i64 = 0 - const s: i64 = 1 << 16 - const t = new Int64Array(31) - t.fill(0) +const multiply_t = new StaticArray(32) +function Multiply (oo: StaticArray, aa: StaticArray, bb: StaticArray): void { + const a = changetype(aa) + const b = changetype(bb) + const o = changetype(oo) + const t = changetype(multiply_t.fill(0)) + + const b0 = v128.load(b) + const b4 = v128.load(b + 16) + const b8 = v128.load(b + 32) + const b12 = v128.load(b + 48) - // init t values + // init values in accumulator `t` for (let i = 0; i < 16; i++) { - for (let j = 0; j < 16; j++) { - t[i + j] += a[i] * b[j] - } + const ai = v128.splat(load(a + (i << 2))) + + let ptr = t + (usize(i) << 3) + let pLo = i64x2.extmul_low_i32x4_s(ai, b0) + let pHi = i64x2.extmul_high_i32x4_s(ai, b0) + let tLo = v128.load(ptr) + let tHi = v128.load(ptr + 16) + tLo = i64x2.add(tLo, pLo) + tHi = i64x2.add(tHi, pHi) + v128.store(ptr, tLo) + v128.store(ptr + 16, tHi) + + ptr += 32 + pLo = i64x2.extmul_low_i32x4_s(ai, b4) + pHi = i64x2.extmul_high_i32x4_s(ai, b4) + tLo = v128.load(ptr) + tHi = v128.load(ptr + 16) + tLo = i64x2.add(tLo, pLo) + tHi = i64x2.add(tHi, pHi) + v128.store(ptr, tLo) + v128.store(ptr + 16, tHi) + + ptr += 32 + pLo = i64x2.extmul_low_i32x4_s(ai, b8) + pHi = i64x2.extmul_high_i32x4_s(ai, b8) + tLo = v128.load(ptr) + tHi = v128.load(ptr + 16) + tLo = i64x2.add(tLo, pLo) + tHi = i64x2.add(tHi, pHi) + v128.store(ptr, tLo) + v128.store(ptr + 16, tHi) + + ptr += 32 + pLo = i64x2.extmul_low_i32x4_s(ai, b12) + pHi = i64x2.extmul_high_i32x4_s(ai, b12) + tLo = v128.load(ptr) + tHi = v128.load(ptr + 16) + tLo = i64x2.add(tLo, pLo) + tHi = i64x2.add(tHi, pHi) + v128.store(ptr, tLo) + v128.store(ptr + 16, tHi) } + Normalize(o, t) +} - for (let i = 0; i < 15; i++) { - t[i] += 38 * t[i + 16] - } - // t15 left as is +function Square (o: StaticArray, a: StaticArray): void { + Multiply(o, a, a) +} + +// Multiply and Square normalization across limbs +// for (let i = 0; i < 15; i++) { +// t[i] += 38 * t[i + 16] +// } +// for (let i = 0; i < 2; i++) { +// for (let i = 0; i < 16; i++) { +// c += t[i] +// t[i] = c & 0xFFFF +// c >>= 16 +// } +// t[0] += 38 * c +// } +function Normalize (o: usize, t: usize): void { + // reduce + let x = load(t) + let y = load(t + 128) + store(t, x + (38 * y)) + + x = load(t + 8) + y = load(t + 136) + store(t + 8, x + (38 * y)) + + x = load(t + 16) + y = load(t + 144) + store(t + 16, x + (38 * y)) + + x = load(t + 24) + y = load(t + 152) + store(t + 24, x + (38 * y)) + + x = load(t + 32) + y = load(t + 160) + store(t + 32, x + (38 * y)) + + x = load(t + 40) + y = load(t + 168) + store(t + 40, x + (38 * y)) + + x = load(t + 48) + y = load(t + 176) + store(t + 48, x + (38 * y)) + + x = load(t + 56) + y = load(t + 184) + store(t + 56, x + (38 * y)) + + x = load(t + 64) + y = load(t + 192) + store(t + 64, x + (38 * y)) + + x = load(t + 72) + y = load(t + 200) + store(t + 72, x + (38 * y)) + + x = load(t + 80) + y = load(t + 208) + store(t + 80, x + (38 * y)) + + x = load(t + 88) + y = load(t + 216) + store(t + 88, x + (38 * y)) + + x = load(t + 96) + y = load(t + 224) + store(t + 96, x + (38 * y)) + + x = load(t + 104) + y = load(t + 232) + store(t + 104, x + (38 * y)) + + x = load(t + 112) + y = load(t + 240) + store(t + 112, x + (38 * y)) // first carry - c = 0 - for (let i = 0; i < 16; i++) { - v = t[i] + c - t[i] = v % s - c = v / s - } - t[0] += 38 * c + let c: i64 = load(t) + store(t, c & 0xFFFF) + c >>= 16 - // second carry - c = 0 - for (let i = 0; i < 16; i++) { - v = t[i] + c - t[i] = v % s - c = v / s - } - t[0] += 38 * c + c += load(t + 8) + store(t + 8, c & 0xFFFF) + c >>= 16 - // assign result to output - o.set(t.slice(0, 16), 0) -} + c += load(t + 16) + store(t + 16, c & 0xFFFF) + c >>= 16 -function Square (o: Int64Array, a: Int64Array): void { - let v: i64 = 0 - let c: i64 = 0 - const s = 1 << 16 - const t = new Int64Array(31) - t.fill(0) + c += load(t + 24) + store(t + 24, c & 0xFFFF) + c >>= 16 - // init t values, same as Multiply except we can skip some iterations of - // the inner loop since a[x]*a[y] + a[y]*a[x] = 2*a[x]*a[y] - for (let i = 0; i < 16; i++) { - for (let j = i; j < 16; j++) { - t[i + j] += a[i] * a[j] * (u8(i < j) + 1) - } - } + c += load(t + 32) + store(t + 32, c & 0xFFFF) + c >>= 16 - for (let i = 0; i < 15; i++) { - t[i] += 38 * t[i + 16] - } - // t15 left as is + c += load(t + 40) + store(t + 40, c & 0xFFFF) + c >>= 16 - // first carry - c = 0 - for (let i = 0; i < 16; i++) { - v = t[i] + c - t[i] = v % s - c = v / s - } - t[0] += 38 * c + c += load(t + 48) + store(t + 48, c & 0xFFFF) + c >>= 16 - // second carry - c = 0 - for (let i = 0; i < 16; i++) { - v = t[i] + c - t[i] = v % s - c = v / s - } - t[0] += 38 * c + c += load(t + 56) + store(t + 56, c & 0xFFFF) + c >>= 16 - // assign result to output - o.set(t.slice(0, 16), 0) -} + c += load(t + 64) + store(t + 64, c & 0xFFFF) + c >>= 16 + + c += load(t + 72) + store(t + 72, c & 0xFFFF) + c >>= 16 + + c += load(t + 80) + store(t + 80, c & 0xFFFF) + c >>= 16 + + c += load(t + 88) + store(t + 88, c & 0xFFFF) + c >>= 16 + + c += load(t + 96) + store(t + 96, c & 0xFFFF) + c >>= 16 + + c += load(t + 104) + store(t + 104, c & 0xFFFF) + c >>= 16 + + c += load(t + 112) + store(t + 112, c & 0xFFFF) + c >>= 16 + + c += load(t + 120) + store(t + 120, c & 0xFFFF) + c >>= 16 + + store(t, load(t) + (38 * c)) + + // second carry and assign result to output + c = load(t) + store(o, c & 0xFFFF) + c >>= 16 + + c += load(t + 8) + store(o + 4, c & 0xFFFF) + c >>= 16 + + c += load(t + 16) + store(o + 8, c & 0xFFFF) + c >>= 16 + + c += load(t + 24) + store(o + 12, c & 0xFFFF) + c >>= 16 + + c += load(t + 32) + store(o + 16, c & 0xFFFF) + c >>= 16 -function add (p: Int64Array[], q: Int64Array[]): void { - const a: Int64Array = new Int64Array(16) - const b: Int64Array = new Int64Array(16) - const c: Int64Array = new Int64Array(16) - const d: Int64Array = new Int64Array(16) - const e: Int64Array = new Int64Array(16) - const f: Int64Array = new Int64Array(16) - const g: Int64Array = new Int64Array(16) - const h: Int64Array = new Int64Array(16) - const t: Int64Array = new Int64Array(16) + c += load(t + 40) + store(o + 20, c & 0xFFFF) + c >>= 16 + c += load(t + 48) + store(o + 24, c & 0xFFFF) + c >>= 16 + + c += load(t + 56) + store(o + 28, c & 0xFFFF) + c >>= 16 + + c += load(t + 64) + store(o + 32, c & 0xFFFF) + c >>= 16 + + c += load(t + 72) + store(o + 36, c & 0xFFFF) + c >>= 16 + + c += load(t + 80) + store(o + 40, c & 0xFFFF) + c >>= 16 + + c += load(t + 88) + store(o + 44, c & 0xFFFF) + c >>= 16 + + c += load(t + 96) + store(o + 48, c & 0xFFFF) + c >>= 16 + + c += load(t + 104) + store(o + 52, c & 0xFFFF) + c >>= 16 + + c += load(t + 112) + store(o + 56, c & 0xFFFF) + c >>= 16 + + c += load(t + 120) + store(o + 60, c & 0xFFFF) + c >>= 16 + + store(o, load(o) + (38 * c)) +} + +const a: StaticArray = new StaticArray(16) +const b: StaticArray = new StaticArray(16) +const c: StaticArray = new StaticArray(16) +const d: StaticArray = new StaticArray(16) +const e: StaticArray = new StaticArray(16) +const f: StaticArray = new StaticArray(16) +const g: StaticArray = new StaticArray(16) +const h: StaticArray = new StaticArray(16) +const t: StaticArray = new StaticArray(16) +function add (p: StaticArray[], q: StaticArray[]): void { Subtract(a, p[1], p[0]) Subtract(t, q[1], q[0]) Multiply(a, a, t) @@ -421,16 +669,16 @@ function add (p: Int64Array[], q: Int64Array[]): void { Multiply(p[3], e, h) } -function cswap (p: Int64Array[], q: Int64Array[], b: i64): void { +function cswap (p: StaticArray[], q: StaticArray[], b: i32): void { for (let i = 0; i < 4; i++) { sel25519(p[i], q[i], b) } } -function pack (r: Uint8Array, p: Int64Array[]): void { - const tx: Int64Array = new Int64Array(16) - const ty: Int64Array = new Int64Array(16) - const zi: Int64Array = new Int64Array(16) +const tx: StaticArray = new StaticArray(16) +const ty: StaticArray = new StaticArray(16) +const zi: StaticArray = new StaticArray(16) +function pack (r: StaticArray, p: StaticArray[]): void { inv25519(zi, p[2]) Multiply(tx, p[0], zi) Multiply(ty, p[1], zi) @@ -438,13 +686,9 @@ function pack (r: Uint8Array, p: Int64Array[]): void { r[31] ^= par25519(tx) << 7 } -function scalarmult (p: Int64Array[], q: Int64Array[], s: Uint8Array): void { - p[0].fill(0) - p[1].fill(0).set([1], 0) - p[2].fill(0).set([1], 0) - p[3].fill(0) - for (let i: i32 = 255; i >= 0; i--) { - const b = (s[(i / 8) | 0] >> u8(i & 7)) & 1 +function scalarmult (p: StaticArray[], q: StaticArray[], s: StaticArray): void { + for (let i = 255; i >= 0; i--) { + const b: i32 = (s[i >> 3] >> u8(i & 7)) & 1 cswap(p, q, b) add(q, p) add(p, p) @@ -452,69 +696,78 @@ function scalarmult (p: Int64Array[], q: Int64Array[], s: Uint8Array): void { } } -function scalarbase (p: Int64Array[], s: Uint8Array): void { - const q: Int64Array[] = [new Int64Array(16), new Int64Array(16), new Int64Array(16), new Int64Array(16)] - q[0].set(X, 0) - q[1].set(Y, 0) - q[2].set([1], 0) - q[3].set(XY, 0) +const scalarbase_q: StaticArray[] = [new StaticArray(16), new StaticArray(16), new StaticArray(16), new StaticArray(16)] +function scalarbase (p: StaticArray[], s: StaticArray): void { + const q = scalarbase_q + for (let i = 0; i < 16; i++) { + q[0][i] = X[i] + q[1][i] = Y[i] + q[2][i] = 0 + q[3][i] = XY[i] + } + q[2][0] = 1 scalarmult(p, q, s) } -const L: StaticArray = StaticArray.fromArray([ +const L: StaticArray = StaticArray.fromArray([ 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 ]) -function modL (r: Uint8Array, x: Int64Array): void { - let carry: i64 - let i: i32 - let j: i32 - let k: i32 - for (i = 63; i >= 32; --i) { - carry = 0 - for (j = i - 32, k = i - 12; j < k; ++j) { - x[j] += carry - 16 * x[i] * L[j - (i - 32)] - carry = (x[j] + 128) >> 8 - x[j] -= carry << 8 +function modL (r: StaticArray, x: StaticArray): void { + let c: i32 + let t: i32 + let v: i32 + for (let i = 63; i >= 32; --i) { + c = 0 + const xi = x[i] + for (let j = i - 32, k = i - 12; j < k; j++) { + t = (xi * L[j - (i - 32)]) << 4 + v = x[j] + c - t + c = (v + 128) >> 8 + x[j] = v - (c << 8) } - x[j] += carry + x[i - 12] += c x[i] = 0 } - carry = 0 - for (j = 0; j < 32; j++) { - x[j] += carry - (x[31] >> 4) * L[j] - carry = x[j] >> 8 + c = 0 + const x31 = x[31] + for (let j = 0; j < 32; j++) { + x[j] += c - (x31 >> 4) * L[j] + c = x[j] >> 8 x[j] &= 255 } - for (j = 0; j < 32; j++) { - x[j] -= carry * L[j] + for (let j = 0; j < 32; j++) { + x[j] -= c * L[j] } - for (i = 0; i < 32; i++) { + for (let i = 0; i < 32; i++) { x[i + 1] += x[i] >> 8 r[i] = u8(x[i] & 255) } } -function reduce (r: Uint8Array): void { - let x = new Int64Array(64) - x.set(r.subarray(0, 64), 0) - r.fill(0, 0, 64) +const x = new StaticArray(64) +function reduce (r: StaticArray): void { + for (let i = 0; i < 64; i++) { + x[i] = i32(r[i]) + r[i] = 0 + } modL(r, x) } -function unpackneg (r: Array, p: Uint8Array): i8 { - const t: Int64Array = new Int64Array(16) - const chk: Int64Array = new Int64Array(16) - const num: Int64Array = new Int64Array(16) - const den: Int64Array = new Int64Array(16) - const den2: Int64Array = new Int64Array(16) - const den4: Int64Array = new Int64Array(16) - const den6: Int64Array = new Int64Array(16) +const unpack_t: StaticArray = new StaticArray(16) +const chk: StaticArray = new StaticArray(16) +const num: StaticArray = new StaticArray(16) +const den: StaticArray = new StaticArray(16) +const den2: StaticArray = new StaticArray(16) +const den4: StaticArray = new StaticArray(16) +const den6: StaticArray = new StaticArray(16) +const z: StaticArray = new StaticArray(16) +function unpackneg (r: Array>, p: StaticArray): i8 { + const t = unpack_t - r[2].fill(0).set([1], 0) unpack25519(r[1], p) Square(num, r[1]) Multiply(den, num, D) @@ -547,170 +800,252 @@ function unpackneg (r: Array, p: Uint8Array): i8 { } if (par25519(r[0]) === (p[31] >> 7)) { - Subtract(r[0], new Int64Array(16), r[0]) + Subtract(r[0], z, r[0]) } Multiply(r[3], r[0], r[1]) return 0 } -function crypto_sign (sm: Uint8Array, m: Uint8Array, n: i32, sk: Uint8Array, pk: Uint8Array): void { - const p: Int64Array[] = [new Int64Array(16), new Int64Array(16), new Int64Array(16), new Int64Array(16)] +const blake2b = new Blake2b() +function crypto_hash (o: StaticArray, i: StaticArray): void { + blake2b.init().update(i).digest(o) +} - const d: Uint8Array = new Blake2b(64).update(sk).digest() +const crypto_convert_h = new StaticArray(64) +const crypto_convert_p = [new StaticArray(16), new StaticArray(16), new StaticArray(16), new StaticArray(16)] +function crypto_convert (pk: StaticArray, sk: StaticArray): void { + const h = crypto_convert_h + const p = crypto_convert_p + p[0].fill(0) + p[1].fill(0); p[1][0] = 1 + p[2].fill(0); p[2][0] = 1 + p[3].fill(0) + crypto_hash(h, sk) + h[0] &= 248 + h[31] &= 127 + h[31] |= 64 + scalarbase(p, h) + pack(pk, p) +} + +const crypto_sign_d = new StaticArray(64) +const crypto_sign_h = new StaticArray(64) +const crypto_sign_p = [new StaticArray(16), new StaticArray(16), new StaticArray(16), new StaticArray(16)] +const crypto_sign_r = new StaticArray(64) +const crypto_sign_x = new StaticArray(64) +const crypto_sign_s = new StaticArray(SIGNATURE_BYTES) +const crypto_sign_prv = new StaticArray(PRIVATEKEY_BYTES) +function crypto_sign (sm: StaticArray, m: StaticArray, sk: StaticArray): void { + const d = crypto_sign_d + const h = crypto_sign_h + const p = crypto_sign_p + const r = crypto_sign_r + const x = crypto_sign_x + const s = crypto_sign_s + const prv = crypto_sign_prv + p[0].fill(0) + p[1].fill(0); p[1][0] = 1 + p[2].fill(0); p[2][0] = 1 + p[3].fill(0) + + for (let i = 0; i < PRIVATEKEY_BYTES; i++) { + prv[i] = sk[i] + } + crypto_hash(d, prv) d[0] &= 248 d[31] &= 127 d[31] |= 64 - sm.set(m.subarray(0, n), 64) - sm.set(d.subarray(32, 64), 32) + for (let i = 0; i < 32; i++) { + s[i] = sm[i + 32] = d[i + 32] + s[i + 32] = sm[i + 64] = m[i] + } - const r: Uint8Array = new Blake2b(64).update(sm.subarray(32)).digest() + crypto_hash(r, s) reduce(r) scalarbase(p, r) pack(sm, p) - sm.set(pk, 32) - const h: Uint8Array = new Blake2b(64).update(sm).digest() + for (let i = 32; i < 64; i++) { + sm[i] = sk[i] + } + crypto_hash(h, sm) reduce(h) - const x = new Int64Array(64) - x.set(r.subarray(0, 32)) + x.fill(0) + for (let i = 0; i < 32; i++) { + x[i] = r[i] + } for (let i = 0; i < 32; i++) { for (let j = 0; j < 32; j++) { - x[i + j] += i64(h[i]) * i64(d[j]) + x[i + j] += i32(h[i]) * i32(d[j]) } } - modL(sm.subarray(32), x) + for (let i = 0; i < SIGNATURE_BYTES; i++) { + s[i] = sm[i + 32] + } + modL(s, x) + for (let i = 0; i < SIGNATURE_BYTES; i++) { + sm[i + 32] = s[i] + } } +const crypto_verify_open_d = new StaticArray(64) +const crypto_verify_open_p = [new StaticArray(16), new StaticArray(16), new StaticArray(16), new StaticArray(16)] +const crypto_verify_open_q = [new StaticArray(16), new StaticArray(16), new StaticArray(16), new StaticArray(16)] +const crypto_verify_open_t = new StaticArray(32) +const crypto_verify_open_sm = new StaticArray(SIGNEDBLOCKHASH_BYTES) +const crypto_verify_open_S = new StaticArray(32) /** -* Verifies that the signed message `sm` was signed using the public key `pk`. -* If so, the original message is copied into `m` and the original message -* bytelength is returned. Otherwise, `m` is zeroed and -1 is returned. +* Verifies block hash `h` was signed with signature `s` against public key `k`. */ -function crypto_sign_open (m: Uint8Array, sm: Uint8Array, n: i32, pk: Uint8Array): i32 { - const t = new Uint8Array(32) - const p: Int64Array[] = [new Int64Array(16), new Int64Array(16), new Int64Array(16), new Int64Array(16)] - const q: Int64Array[] = [new Int64Array(16), new Int64Array(16), new Int64Array(16), new Int64Array(16)] +function crypto_verify (h: StaticArray, s: StaticArray, k: StaticArray): bool { + const d = crypto_verify_open_d + const p = crypto_verify_open_p + const q = crypto_verify_open_q + const t = crypto_verify_open_t + const sm = crypto_verify_open_sm + const S = crypto_verify_open_S + p[0].fill(0) + p[1].fill(0); p[1][0] = 1 + p[2].fill(0); p[2][0] = 1 + p[3].fill(0) + q[0].fill(0) + q[1].fill(0) + q[2].fill(0); q[2][0] = 1 + q[3].fill(0) // fail - if (unpackneg(q, pk)) { - return -1 + if (unpackneg(q, k)) { + return false } - m.set(sm.subarray(0, n), 0) - m.set(pk.subarray(0, 32), 32) - const h: Uint8Array = new Blake2b(64).update(m).digest() - reduce(h) - scalarmult(p, q, h) - scalarbase(q, sm.subarray(32)) + // signature is nonce point R and scalar S (R || S) + // data to hash is nonce point R, public key A, and message M (R || A || M) + // R, S, A, and M are all 32-byte values in this implementation + for (let i = 0; i < 32; i++) { + S[i] = s[i + 32] + sm[i] = s[i] + sm[i + 32] = k[i] + sm[i + 64] = h[i] + } + crypto_hash(d, sm) + reduce(d) + scalarmult(p, q, d) + + q[0].fill(0) + q[1].fill(0); q[1][0] = 1 + q[2].fill(0); q[2][0] = 1 + q[3].fill(0) + scalarbase(q, S) add(p, q) pack(t, p) - // check failure - if (vn(sm, 0, t, 0, 32)) { - m.fill(0) - return -1 - } + return !vn(s, t) +} + +// Returns the pointer to the static input buffer (128 bytes). +export function getInputPointer (): usize { + return INPUT_BUFFER +} - // success - n -= 64 - m.set(sm.subarray(64, n + 64), 0) - return n +// Returns the pointer to the static output buffer (64 bytes). +export function getOutputPointer (): usize { + return OUTPUT_BUFFER } +const derive_sk = new StaticArray(PRIVATEKEY_BYTES) +const derive_pk = new StaticArray(PUBLICKEY_BYTES) /** -* Derives a Nano public key from a private key. This mirrors the functionality -* of `nacl.sign.keyPair.fromSeed()`, returning just the `publicKey` property. -* -* @param {Uint8Array} privateKey - 32-byte private key -* @returns {Uint8Array} 32-byte public key +* Derives a Nano public key from a private key and writes it to the static +* output buffer. This mirrors the functionality of +* `nacl.sign.keyPair.fromSeed()`, returning just the `publicKey` property. */ -export function derive (k0: u64, k1: u64, k2: u64, k3: u64): Uint8Array { - const k = new Uint64Array(4); k.set([k0, k1, k2, k3]) - const privateKey = Uint8Array.wrap(k.buffer) - if (privateKey.byteLength !== PRIVATEKEY_BYTES) { - const err = `Invalid private key byte length to convert to public key, expected ${PRIVATEKEY_BYTES}, actual ${privateKey.byteLength}` - trace(err, 1, privateKey.byteLength) - throw new Error(err) - } - const publicKey = new Uint8Array(PUBLICKEY_BYTES) - const p: Int64Array[] = [new Int64Array(16), new Int64Array(16), new Int64Array(16), new Int64Array(16)] - - const h: Uint8Array = new Blake2b(64).update(privateKey).digest() - h[0] &= 248 - h[31] &= 127 - h[31] |= 64 +export function derive (): void { + const sk = derive_sk + const pk = derive_pk + for (let i = 0; i < PRIVATEKEY_BYTES; i++) { + sk[i] = load(INPUT_BUFFER + i) + } - scalarbase(p, h) - pack(publicKey, p) + const start = performance.now() + crypto_convert(pk, sk) + const end = performance.now() + trace('derive time', 1, end - start) - return publicKey + for (let i = 0; i < PUBLICKEY_BYTES; i++) { + store(OUTPUT_BUFFER + i, pk[i]) + } } +const sign_h = new StaticArray(BLOCKHASH_BYTES) +const sign_sk = new StaticArray(SECRETKEY_BYTES) +const sign_s = new StaticArray(SIGNEDBLOCKHASH_BYTES) /** -* Signs the message using the private key and returns a signature. This mirrors -* the functionality of `nacl.sign.detached()`. +* Signs the message using the private key and writes the signature to the static +* output buffer. This mirrors the functionality of `nacl.sign.detached()`. * -* @param {Uint8Array} message - Message to sign -* @param {Uint8Array} privateKey - 32-byte key to use for signing -* @returns {Uint8Array} 64-byte signature +* @param {u64} h0-h3 - Message to sign (32 bytes as 4 × u64) +* @param {u64} k0-k7 - Secret key (32 bytes private key + 32 bytes public key as 8 × u64) */ -export function sign (h0: u64, h1: u64, h2: u64, h3: u64, k0: u64, k1: u64, k2: u64, k3: u64): Uint8Array { - const h = new Uint64Array(4); h.set([h0, h1, h2, h3]) - const k = new Uint64Array(4); k.set([k0, k1, k2, k3]) - const message = Uint8Array.wrap(h.buffer) - const privateKey = Uint8Array.wrap(k.buffer) - if (privateKey.byteLength !== PRIVATEKEY_BYTES) { - const err = `Invalid key byte length to sign message, expected ${PRIVATEKEY_BYTES}, actual ${privateKey.byteLength}` - trace(err, 1, privateKey.byteLength) - throw new Error(err) - } - const signed = new Uint8Array(SIGNATURE_BYTES + message.length) - const publicKey = derive(k0, k1, k2, k3) - crypto_sign(signed, message, message.length, privateKey, publicKey) - const sig = new Uint8Array(SIGNATURE_BYTES) - for (let i = 0; i < sig.length; i++) { - sig[i] = signed[i] - } - return sig +export function sign (): void { + const h = sign_h + const sk = sign_sk + const s = sign_s + let ptr = INPUT_BUFFER + for (let i = 0; i < BLOCKHASH_BYTES; i++) { + h[i] = load(usize(i) + ptr) + } + ptr += BLOCKHASH_BYTES + for (let i = 0; i < SECRETKEY_BYTES; i++) { + sk[i] = load(usize(i) + ptr) + } + + const start = performance.now() + crypto_sign(s, h, sk) + const end = performance.now() + trace('sign time', 1, end - start) + + for (let i = 0; i < SIGNATURE_BYTES; i++) { + store(OUTPUT_BUFFER + i, s[i]) + } } +const verify_h = new StaticArray(BLOCKHASH_BYTES) +const verify_s = new StaticArray(SIGNATURE_BYTES) +const verify_k = new StaticArray(PUBLICKEY_BYTES) /** -* Verifies a signature on a blockhash against a public key. This mirrors the +* Verifies a signature on a block hash against a public key. This mirrors the * functionality of `nacl.sign.detached.verify()`. * -* @param {Uint8Array} blockhash - 32-byte hash of signed Nano block -* @param {Uint8Array} signature - 64-byte signature -* @param {Uint8Array} publicKey - 32-byte key used for signing -* @returns {boolean} - True if `publicKey` was used to sign `blockhash` and generate `signature`, else false +* @param {u64} h0-h3 - Blockhash (32 bytes as 4 × u64) +* @param {u64} s0-s7 - Signature (64 bytes as 8 × u64) +* @param {u64} k0-k3 - Public key (32 bytes as 4 × u64) +* @returns {i32} - 1 if valid, 0 if invalid */ -export function verify (h0: u64, h1: u64, h2: u64, h3: u64, s0: u64, s1: u64, s2: u64, s3: u64, s4: u64, s5: u64, s6: u64, s7: u64, k0: u64, k1: u64, k2: u64, k3: u64): i32 { - const h = new Uint64Array(4); h.set([h0, h1, h2, h3]) - const s = new Uint64Array(8); s.set([s0, s1, s2, s3, s4, s5, s6, s7]) - const k = new Uint64Array(4); k.set([k0, k1, k2, k3]) - const blockhash = Uint8Array.wrap(h.buffer) - const signature = Uint8Array.wrap(s.buffer) - const publicKey = Uint8Array.wrap(k.buffer) - if (blockhash.byteLength !== BLOCKHASH_BYTES) { - const err = `Invalid blockhash size to verify signature, expected ${BLOCKHASH_BYTES}, actual ${blockhash.byteLength}` - trace(err, 1, blockhash.byteLength) - throw new Error(err) - } - if (signature.byteLength !== SIGNATURE_BYTES) { - const err = `Invalid signature size to verify signature, expected ${SIGNATURE_BYTES}, actual ${signature.byteLength}` - trace(err, 1, signature.byteLength) - throw new Error(err) - } - if (publicKey.byteLength !== PUBLICKEY_BYTES) { - const err = `Invalid public key size to verify signature, expected ${PUBLICKEY_BYTES}, actual ${publicKey.byteLength}` - trace(err, 1, signature.byteLength) - throw new Error(err) - } - const sm = new Uint8Array(SIGNATURE_BYTES + blockhash.length) - const m = new Uint8Array(SIGNATURE_BYTES + blockhash.length) - sm.set(signature, 0) - sm.set(blockhash, SIGNATURE_BYTES) - return (crypto_sign_open(m, sm, sm.length, publicKey) >= 0) ? 1 : 0 +export function verify (): i32 { + const h = verify_h + const s = verify_s + const k = verify_k + + let ptr = INPUT_BUFFER + for (let i = 0; i < BLOCKHASH_BYTES; i++) { + h[i] = load(usize(i) + ptr) + } + ptr += BLOCKHASH_BYTES + for (let i = 0; i < SIGNATURE_BYTES; i++) { + s[i] = load(usize(i) + ptr) + } + ptr += SIGNATURE_BYTES + for (let i = 0; i < PUBLICKEY_BYTES; i++) { + k[i] = load(usize(i) + ptr) + } + + const start = performance.now() + const result = crypto_verify(h, s, k) + const end = performance.now() + trace('verify time', 1, end - start) + + return result ? 1 : 0 } diff --git a/index.html b/index.html index 64bce25..06670ff 100644 --- a/index.html +++ b/index.html @@ -6,169 +6,222 @@ SPDX-License-Identifier: GPL-3.0-or-later + + + @@ -321,34 +332,27 @@ try {

Speed test for NanoNaCl tool.

Times below are in milliseconds and are summarized by various averaging methods.


- -
- -
+ +
+ +
+ +

-

Options

- -
- - - - +

Benchmarking

-
-

Benchmarking

- - - - + + + + @@ -359,11 +363,6 @@ try {
-

Scoring

- - - -

WAITING



diff --git a/index.ts b/index.ts
index d58f854..736abf8 100644
--- a/index.ts
+++ b/index.ts
@@ -2,14 +2,17 @@
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
 import nacl from './build/nano-nacl.wasm'
-type Derive = (k0: bigint, k1: bigint, k2: bigint, k3: bigint) => number
-type Sign = (h0: bigint, h1: bigint, h2: bigint, h3: bigint, k0: bigint, k1: bigint, k2: bigint, k3: bigint) => number
-type Verify = (h0: bigint, h1: bigint, h2: bigint, h3: bigint, s0: bigint, s1: bigint, s2: bigint, s3: bigint, s4: bigint, s5: bigint, s6: bigint, s7: bigint, k0: bigint, k1: bigint, k2: bigint, k3: bigint) => number
+type Derive = () => void
+type Sign = () => void
+type Verify = () => number
+type GetInputPointer = () => number
+type GetOutputPointer = () => number
 type Data = {
 	action: string
-	hash?: string
+	blockHash?: string
 	privateKey?: string
 	publicKey?: string
+	secretKey?: string
 	signature?: string
 }
 
@@ -18,10 +21,10 @@ const NanoNaCl = async (bytes: number[]): Promise => {
 	let wasm
 	let module
 	let instance
-	let derive: (k: BigUint64Array) => Uint8Array
-	let sign: (h: BigUint64Array, k: BigUint64Array) => Uint8Array
-	let verify: (h: BigUint64Array, s: BigUint64Array, k: BigUint64Array) => boolean
 	let memory: WebAssembly.Memory
+	let derive: (k: Uint8Array) => Uint8Array
+	let sign: (h: Uint8Array, k: Uint8Array) => Uint8Array
+	let verify: (h: Uint8Array, s: Uint8Array, k: Uint8Array) => boolean
 
 	async function setup (): Promise {
 		try {
@@ -33,6 +36,10 @@ const NanoNaCl = async (bytes: number[]): Promise => {
 						console.error('Wasm abort:', `msg ${msg}`, `file ${file}`, `row ${row}`, `col ${col}`)
 						throw new Error(`Wasm abort: ${msg}`)
 					},
+					"performance.now" () {
+						// ~lib/bindings/dom/performance.now() => f64
+						return performance.now()
+					},
 					trace: (message: any, n?: number, a0?: number, a1?: number, a2?: number, a3?: number, a4?: number) => {
 						// ~lib/builtins/trace(~lib/string/String, i32?, f64?, f64?, f64?, f64?, f64?) => void
 						message = __liftString(message >>> 0);
@@ -41,11 +48,12 @@ const NanoNaCl = async (bytes: number[]): Promise => {
 							console.log(message, ...[a0, a1, a2, a3, a4].slice(0, n))
 						})()
 					},
-					memory: new WebAssembly.Memory({ initial: 256, maximum: 1024 })
+					memory: new WebAssembly.Memory({ initial: 1, maximum: 8 })
 				}
 			})
-			const { exports } = instance as { exports: { derive: Derive, sign: Sign, verify: Verify, memory: WebAssembly.Memory } }
+			const { exports } = instance as { exports: { derive: Derive, sign: Sign, verify: Verify, getInputPointer: GetInputPointer, getOutputPointer: GetOutputPointer, memory: WebAssembly.Memory } }
 			memory = exports.memory
+
 			function __liftString (pointer: number) {
 				if (!pointer) return null
 				const
@@ -60,56 +68,62 @@ const NanoNaCl = async (bytes: number[]): Promise => {
 				return string + String.fromCharCode(...memoryU16.subarray(start, end))
 			}
 
-			function __liftTypedArray (pointer: number) {
-				if (!pointer) throw new Error('bad pointer')
-				const membuf = new DataView(memory.buffer)
-				const offset = membuf.getUint32(pointer + 4, true)
-				const length = membuf.getUint32(pointer + 8, true)
-				return new Uint8Array(memory.buffer, offset, length).slice()
-			}
-
-			derive = function (k: BigUint64Array): Uint8Array {
-				// assembly/nano-nacl/derive(u64, u64, u64, u64) => ~lib/typedarray/Uint8Array
-				const k0 = k[0] ?? 0n
-				const k1 = k[1] ?? 0n
-				const k2 = k[2] ?? 0n
-				const k3 = k[3] ?? 0n
-				return __liftTypedArray(exports.derive(k0, k1, k2, k3) >>> 0)
+			derive = function (k: Uint8Array): Uint8Array {
+				// assembly/nano-nacl/derive() => void
+				let buffer: DataView | undefined = new DataView(memory.buffer)
+				let inPtr = exports.getInputPointer()
+				for (let i = 0; i < 32; i++) {
+					buffer.setUint8(inPtr + i, k[i])
+				}
+				exports.derive()
+				const outPtr = exports.getOutputPointer()
+				const pk = new Uint8Array(32)
+				for (let i = 0; i < 32; i++) {
+					pk[i] = buffer.getUint8(outPtr + i)
+				}
+				buffer = undefined
+				return pk
 			}
 
-			sign = function (h: BigUint64Array, k: BigUint64Array): Uint8Array {
-				// assembly/nano-nacl/sign(u64, u64, u64, u64, u64, u64, u64, u64) => ~lib/typedarray/Uint8Array
-				const h0 = h[0] ?? 0n
-				const h1 = h[1] ?? 0n
-				const h2 = h[2] ?? 0n
-				const h3 = h[3] ?? 0n
-				const k0 = k[0] ?? 0n
-				const k1 = k[1] ?? 0n
-				const k2 = k[2] ?? 0n
-				const k3 = k[3] ?? 0n
-				return __liftTypedArray(exports.sign(h0, h1, h2, h3, k0, k1, k2, k3) >>> 0)
+			sign = function (h: Uint8Array, k: Uint8Array): Uint8Array {
+				// assembly/nano-nacl/sign() => void
+				let buffer: DataView | undefined = new DataView(memory.buffer)
+				let inPtr = exports.getInputPointer()
+				for (let i = 0; i < 32; i++) {
+					buffer.setUint8(inPtr + i, h[i])
+				}
+				inPtr += 32
+				for (let i = 0; i < 64; i++) {
+					buffer.setUint8(inPtr + i, k[i])
+				}
+				exports.sign()
+				const outPtr = exports.getOutputPointer()
+				const s = new Uint8Array(64)
+				for (let i = 0; i < 64; i++) {
+					s[i] = buffer.getUint8(outPtr + i)
+				}
+				buffer = undefined
+				return s
 			}
 
-			verify = function (h: BigUint64Array, s: BigUint64Array, k: BigUint64Array): boolean {
-				// assembly/nano-nacl/sign(u64, u64, u64, u64, u64, u64, u64, u64) => ~lib/typedarray/Uint8Array
-				const h0 = h[0] || 0n
-				const h1 = h[1] || 0n
-				const h2 = h[2] || 0n
-				const h3 = h[3] || 0n
-				const s0 = s[0] || 0n
-				const s1 = s[1] || 0n
-				const s2 = s[2] || 0n
-				const s3 = s[3] || 0n
-				const s4 = s[4] || 0n
-				const s5 = s[5] || 0n
-				const s6 = s[6] || 0n
-				const s7 = s[7] || 0n
-				const k0 = k[0] || 0n
-				const k1 = k[1] || 0n
-				const k2 = k[2] || 0n
-				const k3 = k[3] || 0n
-				const result = exports.verify(h0, h1, h2, h3, s0, s1, s2, s3, s4, s5, s6, s7, k0, k1, k2, k3)
-				return result === 1
+			verify = function (h: Uint8Array, s: Uint8Array, k: Uint8Array): boolean {
+				// assembly/nano-nacl/verify() => bool
+				let buffer: DataView | undefined = new DataView(memory.buffer)
+				let inPtr = exports.getInputPointer()
+				for (let i = 0; i < 32; i++) {
+					buffer.setUint8(inPtr + i, h[i])
+				}
+				inPtr += 32
+				for (let i = 0; i < 64; i++) {
+					buffer.setUint8(inPtr + i, s[i])
+				}
+				inPtr += 64
+				for (let i = 0; i < 32; i++) {
+					buffer.setUint8(inPtr + i, k[i])
+				}
+				const v = exports.verify()
+				buffer = undefined
+				return v === 1
 			}
 
 			isReady = true
@@ -118,18 +132,28 @@ const NanoNaCl = async (bytes: number[]): Promise => {
 		}
 	}
 
+	function hex2bytes (type: string, byteLength: number, hex?: unknown): Uint8Array {
+		if (typeof type !== 'string') {
+			throw new TypeError(`hex2bytes(): Invalid type ${type}`)
+		}
+		if (typeof byteLength !== 'number' && (byteLength & 1) !== 0) {
+			throw new TypeError(`hex2bytes(): Invalid byte length ${byteLength}`)
+		}
+		const regex = RegExp(`[a-z0-9]{${byteLength << 1}}`, 'i')
+		if (typeof hex !== 'string' || !regex.test(hex)) {
+			throw new TypeError(`hex2bytes(): Invalid ${type} ${hex}`)
+		}
+		const bytes = new Uint8Array(hex.match(/.{2}/g)?.map(b => parseInt(b, 16)) || [])
+		if (bytes.byteLength !== byteLength) {
+			throw new TypeError(`hex2bytes(): Error parsing ${type} ${bytes}`)
+		}
+		return bytes
+	}
+
 	async function handleMessage (msg: any): Promise {
 		let result: any = null
 		try {
 			if (!isReady) await setup()
-			const hashArray = new BigUint64Array(4)
-			const hashView = new DataView(hashArray.buffer)
-			const privateKeyArray = new BigUint64Array(4)
-			const privateKeyView = new DataView(privateKeyArray.buffer)
-			const publicKeyArray = new BigUint64Array(4)
-			const publicKeyView = new DataView(publicKeyArray.buffer)
-			const signatureArray = new BigUint64Array(8)
-			const signatureView = new DataView(signatureArray.buffer)
 
 			if (msg.data === 'start') {
 				result = 'started'
@@ -141,68 +165,31 @@ const NanoNaCl = async (bytes: number[]): Promise => {
 				const { action } = data
 				if (action === 'derive') {
 					const { privateKey } = data
-					if (privateKey == null) {
-						throw new TypeError('Private key required to derive public key')
-					}
-					for (let i = 0; i < privateKey.length; i += 16) {
-						const u64 = privateKey.slice(i, i + 16)
-						privateKeyView.setBigUint64(i / 2, BigInt(`0x${u64}`))
+					const privateKeyBytes = hex2bytes('private key', 32, privateKey)
+					const publicKeyBytes = derive(privateKeyBytes)
+					if (publicKeyBytes == null || !(publicKeyBytes instanceof Uint8Array) || publicKeyBytes.byteLength != 32) {
+						throw new TypeError('Invalid public key from WASM derive()')
 					}
-					const publicKey = derive(privateKeyArray)
-					if (publicKey == null) {
-						throw new TypeError('Invalid verification response from WASM')
-					}
-					result = publicKey
+					result = publicKeyBytes
 				} else if (action === 'sign') {
-					const { hash, privateKey } = data
-					if (hash == null) {
-						throw new TypeError('Block hash required for signing')
-					}
-					if (privateKey == null) {
-						throw new TypeError('Private key required to sign block hash')
-					}
-					for (let i = 0; i < hash.length; i += 16) {
-						const u64 = hash.slice(i, i + 16)
-						hashView.setBigUint64(i / 2, BigInt(`0x${u64}`))
-					}
-					for (let i = 0; i < privateKey.length; i += 16) {
-						const u64 = privateKey.slice(i, i + 16)
-						privateKeyView.setBigUint64(i / 2, BigInt(`0x${u64}`))
-					}
-					const signature = sign(hashArray, privateKeyArray)
+					const { blockHash, secretKey } = data
+					const blockHashBytes = hex2bytes('block hash', 32, blockHash)
+					const secretKeyBytes = hex2bytes('secret key', 64, secretKey)
+					const signature = sign(blockHashBytes, secretKeyBytes)
 					if (signature == null) {
-						throw new TypeError('Invalid signature from WASM')
+						throw new TypeError('Invalid signature from WASM sign()')
 					}
-					console.log('signature', signature)
 					result = signature
 				} else if (action === 'verify') {
-					const { hash, publicKey, signature } = data
-					if (hash == null) {
-						throw new TypeError('Hash required to verify signature')
-					}
-					if (publicKey == null) {
-						throw new TypeError('Public key required to verify signature')
-					}
-					if (signature == null) {
-						throw new TypeError('Signature required to verify against public key')
-					}
-					for (let i = 0; i < hash.length; i += 16) {
-						const u64 = hash.slice(i, i + 16)
-						hashView.setBigUint64(i / 2, BigInt(`0x${u64}`))
-					}
-					for (let i = 0; i < signature.length; i += 16) {
-						const u64 = signature.slice(i, i + 16)
-						signatureView.setBigUint64(i / 2, BigInt(`0x${u64}`))
-					}
-					for (let i = 0; i < publicKey.length; i += 16) {
-						const u64 = publicKey.slice(i, i + 16)
-						publicKeyView.setBigUint64(i / 2, BigInt(`0x${u64}`))
-					}
-					const isVerified = verify(hashArray, signatureArray, publicKeyArray)
+					const { blockHash, publicKey, signature } = data
+					const blockHashBytes = hex2bytes('block hash', 32, blockHash)
+					const signatureBytes = hex2bytes('signature', 64, signature)
+					const publicKeyBytes = hex2bytes('public key', 32, publicKey)
+					const isVerified = verify(blockHashBytes, signatureBytes, publicKeyBytes)
 					if (isVerified == null) {
-						throw new TypeError('Invalid verification response from WASM')
+						throw new TypeError('Invalid verification from WASM verify()')
 					}
-					result = new Uint8Array([isVerified ? 0 : 1])
+					result = new Uint8Array([isVerified ? 1 : 0])
 				}
 			}
 		} catch (err: unknown) {
@@ -253,6 +240,7 @@ function reset (): void {
 }
 
 async function init (): Promise {
+	if (!isReady) setup()
 	return new Promise(async (ready, fail): Promise => {
 		worker.onmessage = msg => msg.data === 'started' ? ready(msg.data) : fail(msg.data)
 		worker.onerror = err => fail(err.message)
@@ -265,10 +253,10 @@ async function dispatch (data: { [key: string]: string }): Promise {
 			const result = msg.data
-			console.log(`received result from worker`, result)
+			LOG: console.log(`received result from worker`)
 			resolve(result)
 		}
-		console.log(`sending data to worker`)
+		LOG: console.log(`sending data to worker`)
 		worker.postMessage(JSON.stringify(data))
 	})
 }
@@ -279,7 +267,7 @@ async function stop (): Promise {
 		worker.onmessage = (msg): void => {
 			const result = msg.data
 			if (result === 'stopped') {
-				console.log('workers stopped successfully')
+				console.log('worker stopped successfully')
 				resolve()
 			} else {
 				reject(result)
@@ -289,92 +277,51 @@ async function stop (): Promise {
 	})
 }
 
-/**
-* Nano public key derivation using WebAssembly.
-*/
-export async function derive (privateKey: string): Promise {
-	if (isReady === false) setup()
+async function run (data: Record): Promise {
 	try {
 		await init()
-		console.log('Workers ready')
 	} catch (err) {
 		console.error(err)
-		throw new Error('Error initializing workers')
+		throw new Error('Error initializing worker')
 	}
 
-	let publicKey = new Uint8Array(0)
 	try {
-		publicKey = await dispatch({ action: 'derive', privateKey })
-	} catch (err) {
-		console.error(err)
-	} finally {
+		const result = await dispatch(data)
+		if (!(result instanceof Uint8Array)) {
+			throw new Error(result)
+		}
+		return [...result].map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase()
+	} catch (err: any) {
 		try {
+			console.error(err)
 			await stop()
-		} catch (err: any) {
-			console.error('failed to stop workers', err?.message ?? err ?? 'unknown reason')
+		} catch (e: any) {
+			console.error('failed to stop worker', err?.message ?? err ?? 'unknown reason')
 			reset()
+		} finally {
+			return ''
 		}
 	}
+}
 
-	return [...publicKey].map(b => b.toString(16).padStart(2, '0')).join('')
+/**
+* Nano public key derivation using WebAssembly.
+*/
+export async function derive (privateKey: string): Promise {
+	return await run({ action: 'derive', privateKey })
 }
 
 /**
 * Nano block signature using WebAssembly.
 */
-export async function sign (hash: string, privateKey: string): Promise {
-	if (isReady === false) setup()
-	try {
-		await init()
-		console.log('Workers ready')
-	} catch (err) {
-		console.error(err)
-		throw new Error('Error initializing workers')
-	}
-
-	let signature = new Uint8Array(0)
-	try {
-		signature = await dispatch({ action: 'sign', hash, privateKey })
-	} catch (err) {
-		console.error(err)
-	} finally {
-		try {
-			await stop()
-		} catch (err: any) {
-			console.error('failed to stop workers', err?.message ?? err ?? 'unknown reason')
-			reset()
-		}
-	}
-
-	return [...signature].map(b => b.toString(16).padStart(2, '0')).join('')
+export async function sign (blockHash: string, secretKey: string): Promise {
+	return await run({ action: 'sign', blockHash, secretKey })
 }
 
 /**
 * Nano block signature verification using WebAssembly.
 */
-export async function verify (hash: string, signature: string, publicKey: string): Promise {
-	if (isReady === false) setup()
-	try {
-		await init()
-		console.log('Workers ready')
-	} catch (err) {
-		console.error(err)
-		throw new Error('Error initializing workers')
-	}
-
-	let isValid = new Uint8Array(0)
-	try {
-		isValid = await dispatch({ action: 'verify', hash, signature, publicKey })
-	} catch (err) {
-		console.error(err)
-	} finally {
-		try {
-			await stop()
-		} catch (err: any) {
-			console.error('failed to stop workers', err?.message ?? err ?? 'unknown reason')
-			reset()
-		}
-	}
-
-	return isValid[0] === 1
+export async function verify (blockHash: string, signature: string, publicKey: string): Promise {
+	const result = await run({ action: 'verify', blockHash, signature, publicKey })
+	return result === '01'
 }