]> git.codecow.com Git - nano25519.git/commitdiff
Overhaul module to use static buffers to prevent memory leaks, 32-bit limbs to accele...
authorChris Duncan <chris@codecow.com>
Wed, 25 Feb 2026 22:31:34 +0000 (14:31 -0800)
committerChris Duncan <chris@codecow.com>
Wed, 25 Feb 2026 22:31:34 +0000 (14:31 -0800)
assembly/nano-nacl.ts
index.html
index.ts

index 6ab26f3075abb598d2e174ec6c4a5c3bd3b4c290..5bcc896f78604fc85176de02b7d7dcf291a832fd 100644 (file)
@@ -47,32 +47,34 @@ class Blake2b {
        // m: message buffer, represents current state of `b` during compression\r
        // t: total byte counter, incremented by `c` (BLAKE2b supports 2¹²⁸-1)\r
        // v: state vector, set to bytes of `h` and `iv` then modified by `t` during compression\r
-       b: Uint8Array\r
-       c: u8\r
-       h: Uint64Array\r
-       m: Uint64Array\r
-       t: v128\r
-       v: Uint64Array\r
+       b: Uint8Array = new Uint8Array(128)\r
+       c: u8 = 0\r
+       h: Uint64Array = new Uint64Array(8)\r
+       m: StaticArray<u64> = new StaticArray<u64>(16)\r
+       t: v128 = v128.splat<u64>(0)\r
+       v: StaticArray<u64> = new StaticArray<u64>(16)\r
 \r
        // param_block: set according to BLAKE2 layout from constructor arguments\r
        param_block: Uint8Array = new Uint8Array(64)\r
        param_view: DataView = new DataView(this.param_block.buffer)\r
 \r
-       // length: bytes of hash output (1-64)\r
-       constructor (length: u8) {\r
+       // input: byte-level view of refillable input buffer\r
+       input: DataView = new DataView(this.b.buffer)\r
 \r
+       // output: byte-level view of 64-bit chain buffer\r
+       output: Uint8Array = Uint8Array.wrap(this.h.buffer)\r
+\r
+       init (): Blake2b {\r
                // initialize buffers and counters\r
-               this.b = new Uint8Array(128)\r
+               this.b.fill(0)\r
                this.c = 0\r
-               this.h = new Uint64Array(8)\r
-               this.m = new Uint64Array(16)\r
+               this.m.fill(0)\r
                this.t = v128.splat<u64>(0)\r
-               this.v = new Uint64Array(16)\r
+               this.v.fill(0)\r
 \r
                // initialize parameter block\r
-               this.param_block = new Uint8Array(64)\r
-               this.param_view = new DataView(this.param_block.buffer)\r
-               this.param_block[0] = length\r
+               this.param_block.fill(0)\r
+               this.param_block[0] = 64 // always 64 bytes for this implementation\r
                this.param_block[1] = 0 // no key\r
                this.param_block[2] = 1 // fanout\r
                this.param_block[3] = 1 // depth\r
@@ -81,17 +83,18 @@ class Blake2b {
                for (let i = 0; i < 8; i++) {\r
                        this.h[i] = blake2b_iv[i] ^ this.param_view.getUint64(i << 3, true)\r
                }\r
+               return this\r
        }\r
 \r
        // input: variable-length message data passed by user to be hashed\r
-       update (input: Uint8Array): Blake2b {\r
-               for (let i = 0; i < input.byteLength; i++) {\r
+       update (input: StaticArray<u8>): Blake2b {\r
+               for (let i = 0; i < input.length; i++) {\r
 \r
                        // is buffer full?\r
                        if (this.c === this.b.byteLength) {\r
 \r
                                // increment total byte counter\r
-                               this.t = unchecked(v128.add<u64>(this.t, i64x2(this.b.byteLength, 0)))\r
+                               this.t = v128.add<u64>(this.t, i64x2(this.b.byteLength, 0))\r
 \r
                                // reset buffer counter to zero\r
                                this.c = 0\r
@@ -106,10 +109,10 @@ class Blake2b {
                return this\r
        }\r
 \r
-       digest (): Uint8Array {\r
+       digest (output: StaticArray<u8>): void {\r
 \r
                // add final message block size to total bytes\r
-               this.t = unchecked(v128.add<u64>(this.t, i64x2(this.c, 0)))\r
+               this.t = v128.add<u64>(this.t, i64x2(this.c, 0))\r
 \r
                // pad final block with zeros\r
                this.b.fill(0, this.c)\r
@@ -117,11 +120,10 @@ class Blake2b {
                // set final block flag and compress\r
                this.COMPRESS(true)\r
 \r
-               // copy bytes\r
-               const bytes = Uint8Array.wrap(this.h.buffer)\r
-               const hash = new Uint8Array(this.param_block[0])\r
-               hash.set(bytes.subarray(0, this.param_block[0]))\r
-               return hash\r
+               // return byte array of 64-bit chain buffer\r
+               for (let i = 0; i < 64; i++) {\r
+                       output[i] = this.output[i]\r
+               }\r
        }\r
 \r
        // Defined in BLAKE2 section 2.4\r
@@ -143,9 +145,8 @@ class Blake2b {
                isFinal ? this.v[14] = ~this.v[14] : this.v[14] = this.v[14]\r
 \r
                // copy input buffer to message block\r
-               const buf = new DataView(this.b.buffer)\r
                for (let i = 0; i < 16; i++) {\r
-                       this.m[i] = buf.getUint64(i << 3, true)\r
+                       this.m[i] = this.input.getUint64(i << 3, true)\r
                }\r
 \r
                // twelve rounds of mixing\r
@@ -166,94 +167,123 @@ class Blake2b {
                        const c = blake2b_state[i][2]\r
                        const d = blake2b_state[i][3]\r
                        const s = blake2b_sigma[r]\r
-                       this.G(a, b, c, d, s[i + i], s[i + i + 1])\r
+                       this.G(a, b, c, d, s[i << 1], s[(i << 1) + 1])\r
                }\r
        }\r
 \r
        G (a: u8, b: u8, c: u8, d: u8, x: u8, y: u8): void {\r
-               this.v[a] = unchecked(unchecked(this.v[a] + this.v[b]) + this.m[x])\r
+               this.v[a] = this.v[a] + this.v[b] + this.m[x]\r
                this.v[d] = rotr<u64>(this.v[d] ^ this.v[a], 32)\r
-               this.v[c] = unchecked(this.v[c] + this.v[d])\r
+               this.v[c] = this.v[c] + this.v[d]\r
                this.v[b] = rotr<u64>(this.v[b] ^ this.v[c], 24)\r
-               this.v[a] = unchecked(unchecked(this.v[a] + this.v[b]) + this.m[y])\r
+               this.v[a] = this.v[a] + this.v[b] + this.m[y]\r
                this.v[d] = rotr<u64>(this.v[d] ^ this.v[a], 16)\r
-               this.v[c] = unchecked(this.v[c] + this.v[d])\r
+               this.v[c] = this.v[c] + this.v[d]\r
                this.v[b] = rotr<u64>(this.v[b] ^ this.v[c], 63)\r
        }\r
 }\r
 \r
-const BLOCKHASH_BYTES: u8 = 32\r
-const PRIVATEKEY_BYTES: u8 = 32\r
-const PUBLICKEY_BYTES: u8 = 32\r
-const SIGNATURE_BYTES: u8 = 64\r
-const D: Int64Array = new Int64Array(16); D.set([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203])\r
-const D2: Int64Array = new Int64Array(16); D2.set([0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406])\r
-const X: Int64Array = new Int64Array(16); X.set([0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169])\r
-const Y: Int64Array = new Int64Array(16); Y.set([0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666])\r
-const I: Int64Array = new Int64Array(16); I.set([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83])\r
-const XY: Int64Array = new Int64Array(16); XY.set([0xdd90, 0xa5b7, 0x8ab3, 0x6dde, 0x52f5, 0x7751, 0x9f80, 0x20f0, 0xe37d, 0x64ab, 0x4e8e, 0x66ea, 0x7665, 0xd78b, 0x5f0f, 0xe787])\r
-\r
-function vn (x: Uint8Array, xi: u8, y: Uint8Array, yi: u8, n: i32): i64 {\r
-       let d = 0\r
-       for (let i = 0; i < n; i++) {\r
-               d |= x[xi + i] ^ y[yi + i]\r
+const BLOCKHASH_BYTES: i32 = 32\r
+const PRIVATEKEY_BYTES: i32 = 32\r
+const PUBLICKEY_BYTES: i32 = 32\r
+const SECRETKEY_BYTES: i32 = PRIVATEKEY_BYTES + PUBLICKEY_BYTES\r
+const SIGNATURE_BYTES: i32 = 64\r
+const SIGNEDBLOCKHASH_BYTES: i32 = SIGNATURE_BYTES + BLOCKHASH_BYTES\r
+const D: StaticArray<i32> = StaticArray.fromArray<i32>([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203])\r
+const D2: StaticArray<i32> = StaticArray.fromArray<i32>([0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406])\r
+const X: StaticArray<i32> = StaticArray.fromArray<i32>([0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169])\r
+const Y: StaticArray<i32> = StaticArray.fromArray<i32>([0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666])\r
+const I: StaticArray<i32> = StaticArray.fromArray<i32>([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83])\r
+const XY: StaticArray<i32> = StaticArray.fromArray<i32>([0xdd90, 0xa5b7, 0x8ab3, 0x6dde, 0x52f5, 0x7751, 0x9f80, 0x20f0, 0xe37d, 0x64ab, 0x4e8e, 0x66ea, 0x7665, 0xd78b, 0x5f0f, 0xe787])\r
+\r
+// Static I/O buffers\r
+const INPUT_BUFFER = memory.data(128)\r
+const OUTPUT_BUFFER = memory.data(64)\r
+\r
+function vn (x: StaticArray<u8>, y: StaticArray<u8>): bool {\r
+       let d: u8 = 0\r
+       for (let i = 0; i < 32; i++) {\r
+               d |= x[i] ^ y[i]\r
        }\r
-       return (1 & ((d - 1) >>> 8)) - 1\r
+       return d !== 0\r
 }\r
 \r
-function pow2523 (o: Int64Array, i: Int64Array): void {\r
-       const c: Int64Array = new Int64Array(16)\r
-       c.set(i.subarray(0, 16), 0)\r
-       for (let a = 0; a < 249; a++) {\r
+const pow_c: StaticArray<i32> = new StaticArray<i32>(16)\r
+function pow2523 (o: StaticArray<i32>, a: StaticArray<i32>): void {\r
+       const c = pow_c\r
+       for (let i = 0; i < 16; i++) {\r
+               c[i] = a[i]\r
+       }\r
+       for (let i = 0; i < 249; i++) {\r
                Square(c, c)\r
-               Multiply(c, c, i)\r
+               Multiply(c, c, a)\r
        }\r
        Square(c, c)\r
        Square(c, c)\r
-       Multiply(c, c, i)\r
-       o.set(c, 0)\r
+       Multiply(c, c, a)\r
+       for (let i = 0; i < 16; i++) {\r
+               o[i] = c[i]\r
+       }\r
 }\r
 \r
-function car25519 (o: Int64Array): void {\r
-       let v: i64 = 0\r
-       let c: i64 = 0\r
-       const s: i64 = 1 << 16\r
+function car25519 (o: StaticArray<i32>): void {\r
+       let c: i32 = 0\r
        for (let i = 0; i < 16; i++) {\r
-               v = o[i] + c + s\r
-               o[i] = v % s\r
-               c = v / s - 1\r
+               c += o[i]\r
+               o[i] = c & 0xFFFF\r
+               c >>= 16\r
        }\r
        o[0] += 38 * c\r
 }\r
 \r
-function inv25519 (o: Int64Array, i: Int64Array): void {\r
-       const c: Int64Array = new Int64Array(16)\r
-       c.set(i.subarray(0, 16), 0)\r
-       for (let a = 253; a >= 0; a--) {\r
+const inv_c: StaticArray<i32> = new StaticArray<i32>(16)\r
+function inv25519 (o: StaticArray<i32>, a: StaticArray<i32>): void {\r
+       const c = inv_c\r
+       for (let i = 0; i < 16; i++) {\r
+               c[i] = a[i]\r
+       }\r
+       for (let i = 0; i < 249; i++) {\r
                Square(c, c)\r
-               if (a !== 2 && a !== 4) {\r
-                       Multiply(c, c, i)\r
-               }\r
+               Multiply(c, c, a)\r
+       }\r
+       Square(c, c)\r
+       Square(c, c)\r
+       Multiply(c, c, a)\r
+       Square(c, c)\r
+       Square(c, c)\r
+       Multiply(c, c, a)\r
+       Square(c, c)\r
+       Multiply(c, c, a)\r
+       for (let i = 0; i < 16; i++) {\r
+               o[i] = c[i]\r
        }\r
-       o.set(c, 0)\r
 }\r
 \r
-function neq25519 (a: Int64Array, b: Int64Array): i64 {\r
-       const c = new Uint8Array(32)\r
-       const d = new Uint8Array(32)\r
+const neq_c = new StaticArray<u8>(32)\r
+const neq_d = new StaticArray<u8>(32)\r
+function neq25519 (a: StaticArray<i32>, b: StaticArray<i32>): bool {\r
+       const c = neq_c\r
+       const d = neq_d\r
        pack25519(c, a)\r
        pack25519(d, b)\r
-       return vn(c, 0, d, 0, 32)\r
+       return vn(c, d)\r
 }\r
 \r
-function pack25519 (o: Uint8Array, n: Int64Array): void {\r
-       let b: i64 = 0\r
-       const m: Int64Array = new Int64Array(16)\r
-       const t: Int64Array = new Int64Array(16)\r
-       t.set(n.subarray(0, 16), 0)\r
+const pack_m: StaticArray<i32> = new StaticArray<i32>(16)\r
+const pack_t: StaticArray<i32> = new StaticArray<i32>(16)\r
+function pack25519 (o: StaticArray<u8>, n: StaticArray<i32>): void {\r
+       const m = pack_m\r
+       const t = pack_t\r
+       let b: i32 = 0\r
+\r
+       for (let i = 0; i < 16; i++) {\r
+               t[i] = n[i]\r
+       }\r
+\r
        car25519(t)\r
        car25519(t)\r
        car25519(t)\r
+\r
        for (let j = 0; j < 2; j++) {\r
                m[0] = t[0] - 0xffed\r
                for (let i = 1; i < 15; i++) {\r
@@ -265,21 +295,23 @@ function pack25519 (o: Uint8Array, n: Int64Array): void {
                m[14] &= 0xffff\r
                sel25519(t, m, 1 - b)\r
        }\r
+\r
        for (let i = 0; i < 16; i++) {\r
-               o[i + i] = u8(t[i] & 0xff)\r
-               o[i + i + 1] = u8(t[i] >> 8)\r
+               o[i << 1] = u8(t[i] & 0xff)\r
+               o[(i << 1) + 1] = u8(t[i] >> 8)\r
        }\r
 }\r
 \r
-function par25519 (a: Int64Array): u8 {\r
-       const d = new Uint8Array(32)\r
+const par_d = new StaticArray<u8>(32)\r
+function par25519 (a: StaticArray<i32>): u8 {\r
+       const d = par_d\r
        pack25519(d, a)\r
        return d[0] & 1\r
 }\r
 \r
-function sel25519 (p: Int64Array, q: Int64Array, b: i64): void {\r
-       let t: i64 = 0\r
-       const c: i64 = ~(b - 1)\r
+function sel25519 (p: StaticArray<i32>, q: StaticArray<i32>, b: i32): void {\r
+       let t: i32 = 0\r
+       const c: i32 = ~(b - 1)\r
        for (let i = 0; i < 16; i++) {\r
                t = c & (p[i] ^ q[i])\r
                p[i] ^= t\r
@@ -287,119 +319,335 @@ function sel25519 (p: Int64Array, q: Int64Array, b: i64): void {
        }\r
 }\r
 \r
-function unpack25519 (o: Int64Array, n: Uint8Array): void {\r
+function unpack25519 (o: StaticArray<i32>, n: StaticArray<u8>): void {\r
        for (let i = 0; i < 16; i++) {\r
-               o[i] = n[i + i] + (n[i + i + 1] << 8)\r
+               o[i] = i32(n[i << 1]) + (i32(n[(i << 1) + 1]) << 8)\r
        }\r
        o[15] &= (1 << 15) - 1\r
 }\r
 \r
-function Add (o: Int64Array, a: Int64Array, b: Int64Array): void {\r
-       for (let i = 0; i < 16; i++) {\r
-               o[i] = a[i] + b[i]\r
-       }\r
+@inline\r
+function Add (o: StaticArray<i32>, a: StaticArray<i32>, b: StaticArray<i32>): void {\r
+       o[0] = a[0] + b[0]\r
+       o[1] = a[1] + b[1]\r
+       o[2] = a[2] + b[2]\r
+       o[3] = a[3] + b[3]\r
+       o[4] = a[4] + b[4]\r
+       o[5] = a[5] + b[5]\r
+       o[6] = a[6] + b[6]\r
+       o[7] = a[7] + b[7]\r
+       o[8] = a[8] + b[8]\r
+       o[9] = a[9] + b[9]\r
+       o[10] = a[10] + b[10]\r
+       o[11] = a[11] + b[11]\r
+       o[12] = a[12] + b[12]\r
+       o[13] = a[13] + b[13]\r
+       o[14] = a[14] + b[14]\r
+       o[15] = a[15] + b[15]\r
 }\r
 \r
-function Subtract (o: Int64Array, a: Int64Array, b: Int64Array): void {\r
-       for (let i = 0; i < 16; i++) {\r
-               o[i] = a[i] - b[i]\r
-       }\r
+@inline\r
+function Subtract (o: StaticArray<i32>, a: StaticArray<i32>, b: StaticArray<i32>): void {\r
+       o[0] = a[0] - b[0]\r
+       o[1] = a[1] - b[1]\r
+       o[2] = a[2] - b[2]\r
+       o[3] = a[3] - b[3]\r
+       o[4] = a[4] - b[4]\r
+       o[5] = a[5] - b[5]\r
+       o[6] = a[6] - b[6]\r
+       o[7] = a[7] - b[7]\r
+       o[8] = a[8] - b[8]\r
+       o[9] = a[9] - b[9]\r
+       o[10] = a[10] - b[10]\r
+       o[11] = a[11] - b[11]\r
+       o[12] = a[12] - b[12]\r
+       o[13] = a[13] - b[13]\r
+       o[14] = a[14] - b[14]\r
+       o[15] = a[15] - b[15]\r
 }\r
 \r
-function Multiply (o: Int64Array, a: Int64Array, b: Int64Array): void {\r
-       let v: i64 = 0\r
-       let c: i64 = 0\r
-       const s: i64 = 1 << 16\r
-       const t = new Int64Array(31)\r
-       t.fill(0)\r
+const multiply_t = new StaticArray<i64>(32)\r
+function Multiply (oo: StaticArray<i32>, aa: StaticArray<i32>, bb: StaticArray<i32>): void {\r
+       const a = changetype<usize>(aa)\r
+       const b = changetype<usize>(bb)\r
+       const o = changetype<usize>(oo)\r
+       const t = changetype<usize>(multiply_t.fill(0))\r
+\r
+       const b0 = v128.load(b)\r
+       const b4 = v128.load(b + 16)\r
+       const b8 = v128.load(b + 32)\r
+       const b12 = v128.load(b + 48)\r
 \r
-       // init t values\r
+       // init values in accumulator `t`\r
        for (let i = 0; i < 16; i++) {\r
-               for (let j = 0; j < 16; j++) {\r
-                       t[i + j] += a[i] * b[j]\r
-               }\r
+               const ai = v128.splat<i32>(load<i32>(a + (i << 2)))\r
+\r
+               let ptr = t + (usize(i) << 3)\r
+               let pLo = i64x2.extmul_low_i32x4_s(ai, b0)\r
+               let pHi = i64x2.extmul_high_i32x4_s(ai, b0)\r
+               let tLo = v128.load(ptr)\r
+               let tHi = v128.load(ptr + 16)\r
+               tLo = i64x2.add(tLo, pLo)\r
+               tHi = i64x2.add(tHi, pHi)\r
+               v128.store(ptr, tLo)\r
+               v128.store(ptr + 16, tHi)\r
+\r
+               ptr += 32\r
+               pLo = i64x2.extmul_low_i32x4_s(ai, b4)\r
+               pHi = i64x2.extmul_high_i32x4_s(ai, b4)\r
+               tLo = v128.load(ptr)\r
+               tHi = v128.load(ptr + 16)\r
+               tLo = i64x2.add(tLo, pLo)\r
+               tHi = i64x2.add(tHi, pHi)\r
+               v128.store(ptr, tLo)\r
+               v128.store(ptr + 16, tHi)\r
+\r
+               ptr += 32\r
+               pLo = i64x2.extmul_low_i32x4_s(ai, b8)\r
+               pHi = i64x2.extmul_high_i32x4_s(ai, b8)\r
+               tLo = v128.load(ptr)\r
+               tHi = v128.load(ptr + 16)\r
+               tLo = i64x2.add(tLo, pLo)\r
+               tHi = i64x2.add(tHi, pHi)\r
+               v128.store(ptr, tLo)\r
+               v128.store(ptr + 16, tHi)\r
+\r
+               ptr += 32\r
+               pLo = i64x2.extmul_low_i32x4_s(ai, b12)\r
+               pHi = i64x2.extmul_high_i32x4_s(ai, b12)\r
+               tLo = v128.load(ptr)\r
+               tHi = v128.load(ptr + 16)\r
+               tLo = i64x2.add(tLo, pLo)\r
+               tHi = i64x2.add(tHi, pHi)\r
+               v128.store(ptr, tLo)\r
+               v128.store(ptr + 16, tHi)\r
        }\r
+       Normalize(o, t)\r
+}\r
 \r
-       for (let i = 0; i < 15; i++) {\r
-               t[i] += 38 * t[i + 16]\r
-       }\r
-       // t15 left as is\r
+function Square (o: StaticArray<i32>, a: StaticArray<i32>): void {\r
+       Multiply(o, a, a)\r
+}\r
+\r
+// Multiply and Square normalization across limbs\r
+//     for (let i = 0; i < 15; i++) {\r
+//             t[i] += 38 * t[i + 16]\r
+//     }\r
+//     for (let i = 0; i < 2; i++) {\r
+//             for (let i = 0; i < 16; i++) {\r
+//                     c += t[i]\r
+//                     t[i] = c & 0xFFFF\r
+//                     c >>= 16\r
+//             }\r
+//             t[0] += 38 * c\r
+// }\r
+function Normalize (o: usize, t: usize): void {\r
+       // reduce\r
+       let x = load<i64>(t)\r
+       let y = load<i64>(t + 128)\r
+       store<i64>(t, x + (38 * y))\r
+\r
+       x = load<i64>(t + 8)\r
+       y = load<i64>(t + 136)\r
+       store<i64>(t + 8, x + (38 * y))\r
+\r
+       x = load<i64>(t + 16)\r
+       y = load<i64>(t + 144)\r
+       store<i64>(t + 16, x + (38 * y))\r
+\r
+       x = load<i64>(t + 24)\r
+       y = load<i64>(t + 152)\r
+       store<i64>(t + 24, x + (38 * y))\r
+\r
+       x = load<i64>(t + 32)\r
+       y = load<i64>(t + 160)\r
+       store<i64>(t + 32, x + (38 * y))\r
+\r
+       x = load<i64>(t + 40)\r
+       y = load<i64>(t + 168)\r
+       store<i64>(t + 40, x + (38 * y))\r
+\r
+       x = load<i64>(t + 48)\r
+       y = load<i64>(t + 176)\r
+       store<i64>(t + 48, x + (38 * y))\r
+\r
+       x = load<i64>(t + 56)\r
+       y = load<i64>(t + 184)\r
+       store<i64>(t + 56, x + (38 * y))\r
+\r
+       x = load<i64>(t + 64)\r
+       y = load<i64>(t + 192)\r
+       store<i64>(t + 64, x + (38 * y))\r
+\r
+       x = load<i64>(t + 72)\r
+       y = load<i64>(t + 200)\r
+       store<i64>(t + 72, x + (38 * y))\r
+\r
+       x = load<i64>(t + 80)\r
+       y = load<i64>(t + 208)\r
+       store<i64>(t + 80, x + (38 * y))\r
+\r
+       x = load<i64>(t + 88)\r
+       y = load<i64>(t + 216)\r
+       store<i64>(t + 88, x + (38 * y))\r
+\r
+       x = load<i64>(t + 96)\r
+       y = load<i64>(t + 224)\r
+       store<i64>(t + 96, x + (38 * y))\r
+\r
+       x = load<i64>(t + 104)\r
+       y = load<i64>(t + 232)\r
+       store<i64>(t + 104, x + (38 * y))\r
+\r
+       x = load<i64>(t + 112)\r
+       y = load<i64>(t + 240)\r
+       store<i64>(t + 112, x + (38 * y))\r
 \r
        // first carry\r
-       c = 0\r
-       for (let i = 0; i < 16; i++) {\r
-               v = t[i] + c\r
-               t[i] = v % s\r
-               c = v / s\r
-       }\r
-       t[0] += 38 * c\r
+       let c: i64 = load<i64>(t)\r
+       store<i64>(t, c & 0xFFFF)\r
+       c >>= 16\r
 \r
-       // second carry\r
-       c = 0\r
-       for (let i = 0; i < 16; i++) {\r
-               v = t[i] + c\r
-               t[i] = v % s\r
-               c = v / s\r
-       }\r
-       t[0] += 38 * c\r
+       c += load<i64>(t + 8)\r
+       store<i64>(t + 8, c & 0xFFFF)\r
+       c >>= 16\r
 \r
-       // assign result to output\r
-       o.set(t.slice(0, 16), 0)\r
-}\r
+       c += load<i64>(t + 16)\r
+       store<i64>(t + 16, c & 0xFFFF)\r
+       c >>= 16\r
 \r
-function Square (o: Int64Array, a: Int64Array): void {\r
-       let v: i64 = 0\r
-       let c: i64 = 0\r
-       const s = 1 << 16\r
-       const t = new Int64Array(31)\r
-       t.fill(0)\r
+       c += load<i64>(t + 24)\r
+       store<i64>(t + 24, c & 0xFFFF)\r
+       c >>= 16\r
 \r
-       // init t values, same as Multiply except we can skip some iterations of\r
-       // the inner loop since a[x]*a[y] + a[y]*a[x] = 2*a[x]*a[y]\r
-       for (let i = 0; i < 16; i++) {\r
-               for (let j = i; j < 16; j++) {\r
-                       t[i + j] += a[i] * a[j] * (u8(i < j) + 1)\r
-               }\r
-       }\r
+       c += load<i64>(t + 32)\r
+       store<i64>(t + 32, c & 0xFFFF)\r
+       c >>= 16\r
 \r
-       for (let i = 0; i < 15; i++) {\r
-               t[i] += 38 * t[i + 16]\r
-       }\r
-       // t15 left as is\r
+       c += load<i64>(t + 40)\r
+       store<i64>(t + 40, c & 0xFFFF)\r
+       c >>= 16\r
 \r
-       // first carry\r
-       c = 0\r
-       for (let i = 0; i < 16; i++) {\r
-               v = t[i] + c\r
-               t[i] = v % s\r
-               c = v / s\r
-       }\r
-       t[0] += 38 * c\r
+       c += load<i64>(t + 48)\r
+       store<i64>(t + 48, c & 0xFFFF)\r
+       c >>= 16\r
 \r
-       // second carry\r
-       c = 0\r
-       for (let i = 0; i < 16; i++) {\r
-               v = t[i] + c\r
-               t[i] = v % s\r
-               c = v / s\r
-       }\r
-       t[0] += 38 * c\r
+       c += load<i64>(t + 56)\r
+       store<i64>(t + 56, c & 0xFFFF)\r
+       c >>= 16\r
 \r
-       // assign result to output\r
-       o.set(t.slice(0, 16), 0)\r
-}\r
+       c += load<i64>(t + 64)\r
+       store<i64>(t + 64, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 72)\r
+       store<i64>(t + 72, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 80)\r
+       store<i64>(t + 80, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 88)\r
+       store<i64>(t + 88, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 96)\r
+       store<i64>(t + 96, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 104)\r
+       store<i64>(t + 104, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 112)\r
+       store<i64>(t + 112, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 120)\r
+       store<i64>(t + 120, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       store<i64>(t, load<i64>(t) + (38 * c))\r
+\r
+       // second carry and assign result to output\r
+       c = load<i64>(t)\r
+       store<i32>(o, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 8)\r
+       store<i32>(o + 4, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 16)\r
+       store<i32>(o + 8, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 24)\r
+       store<i32>(o + 12, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 32)\r
+       store<i32>(o + 16, c & 0xFFFF)\r
+       c >>= 16\r
 \r
-function add (p: Int64Array[], q: Int64Array[]): void {\r
-       const a: Int64Array = new Int64Array(16)\r
-       const b: Int64Array = new Int64Array(16)\r
-       const c: Int64Array = new Int64Array(16)\r
-       const d: Int64Array = new Int64Array(16)\r
-       const e: Int64Array = new Int64Array(16)\r
-       const f: Int64Array = new Int64Array(16)\r
-       const g: Int64Array = new Int64Array(16)\r
-       const h: Int64Array = new Int64Array(16)\r
-       const t: Int64Array = new Int64Array(16)\r
+       c += load<i64>(t + 40)\r
+       store<i32>(o + 20, c & 0xFFFF)\r
+       c >>= 16\r
 \r
+       c += load<i64>(t + 48)\r
+       store<i32>(o + 24, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 56)\r
+       store<i32>(o + 28, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 64)\r
+       store<i32>(o + 32, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 72)\r
+       store<i32>(o + 36, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 80)\r
+       store<i32>(o + 40, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 88)\r
+       store<i32>(o + 44, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 96)\r
+       store<i32>(o + 48, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 104)\r
+       store<i32>(o + 52, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 112)\r
+       store<i32>(o + 56, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       c += load<i64>(t + 120)\r
+       store<i32>(o + 60, c & 0xFFFF)\r
+       c >>= 16\r
+\r
+       store<i64>(o, load<i64>(o) + (38 * c))\r
+}\r
+\r
+const a: StaticArray<i32> = new StaticArray<i32>(16)\r
+const b: StaticArray<i32> = new StaticArray<i32>(16)\r
+const c: StaticArray<i32> = new StaticArray<i32>(16)\r
+const d: StaticArray<i32> = new StaticArray<i32>(16)\r
+const e: StaticArray<i32> = new StaticArray<i32>(16)\r
+const f: StaticArray<i32> = new StaticArray<i32>(16)\r
+const g: StaticArray<i32> = new StaticArray<i32>(16)\r
+const h: StaticArray<i32> = new StaticArray<i32>(16)\r
+const t: StaticArray<i32> = new StaticArray<i32>(16)\r
+function add (p: StaticArray<i32>[], q: StaticArray<i32>[]): void {\r
        Subtract(a, p[1], p[0])\r
        Subtract(t, q[1], q[0])\r
        Multiply(a, a, t)\r
@@ -421,16 +669,16 @@ function add (p: Int64Array[], q: Int64Array[]): void {
        Multiply(p[3], e, h)\r
 }\r
 \r
-function cswap (p: Int64Array[], q: Int64Array[], b: i64): void {\r
+function cswap (p: StaticArray<i32>[], q: StaticArray<i32>[], b: i32): void {\r
        for (let i = 0; i < 4; i++) {\r
                sel25519(p[i], q[i], b)\r
        }\r
 }\r
 \r
-function pack (r: Uint8Array, p: Int64Array[]): void {\r
-       const tx: Int64Array = new Int64Array(16)\r
-       const ty: Int64Array = new Int64Array(16)\r
-       const zi: Int64Array = new Int64Array(16)\r
+const tx: StaticArray<i32> = new StaticArray<i32>(16)\r
+const ty: StaticArray<i32> = new StaticArray<i32>(16)\r
+const zi: StaticArray<i32> = new StaticArray<i32>(16)\r
+function pack (r: StaticArray<u8>, p: StaticArray<i32>[]): void {\r
        inv25519(zi, p[2])\r
        Multiply(tx, p[0], zi)\r
        Multiply(ty, p[1], zi)\r
@@ -438,13 +686,9 @@ function pack (r: Uint8Array, p: Int64Array[]): void {
        r[31] ^= par25519(tx) << 7\r
 }\r
 \r
-function scalarmult (p: Int64Array[], q: Int64Array[], s: Uint8Array): void {\r
-       p[0].fill(0)\r
-       p[1].fill(0).set([1], 0)\r
-       p[2].fill(0).set([1], 0)\r
-       p[3].fill(0)\r
-       for (let i: i32 = 255; i >= 0; i--) {\r
-               const b = (s[(i / 8) | 0] >> u8(i & 7)) & 1\r
+function scalarmult (p: StaticArray<i32>[], q: StaticArray<i32>[], s: StaticArray<u8>): void {\r
+       for (let i = 255; i >= 0; i--) {\r
+               const b: i32 = (s[i >> 3] >> u8(i & 7)) & 1\r
                cswap(p, q, b)\r
                add(q, p)\r
                add(p, p)\r
@@ -452,69 +696,78 @@ function scalarmult (p: Int64Array[], q: Int64Array[], s: Uint8Array): void {
        }\r
 }\r
 \r
-function scalarbase (p: Int64Array[], s: Uint8Array): void {\r
-       const q: Int64Array[] = [new Int64Array(16), new Int64Array(16), new Int64Array(16), new Int64Array(16)]\r
-       q[0].set(X, 0)\r
-       q[1].set(Y, 0)\r
-       q[2].set([1], 0)\r
-       q[3].set(XY, 0)\r
+const scalarbase_q: StaticArray<i32>[] = [new StaticArray<i32>(16), new StaticArray<i32>(16), new StaticArray<i32>(16), new StaticArray<i32>(16)]\r
+function scalarbase (p: StaticArray<i32>[], s: StaticArray<u8>): void {\r
+       const q = scalarbase_q\r
+       for (let i = 0; i < 16; i++) {\r
+               q[0][i] = X[i]\r
+               q[1][i] = Y[i]\r
+               q[2][i] = 0\r
+               q[3][i] = XY[i]\r
+       }\r
+       q[2][0] = 1\r
        scalarmult(p, q, s)\r
 }\r
 \r
-const L: StaticArray<i64> = StaticArray.fromArray<i64>([\r
+const L: StaticArray<i32> = StaticArray.fromArray<i32>([\r
        0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58,\r
        0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14,\r
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\r
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10\r
 ])\r
 \r
-function modL (r: Uint8Array, x: Int64Array): void {\r
-       let carry: i64\r
-       let i: i32\r
-       let j: i32\r
-       let k: i32\r
-       for (i = 63; i >= 32; --i) {\r
-               carry = 0\r
-               for (j = i - 32, k = i - 12; j < k; ++j) {\r
-                       x[j] += carry - 16 * x[i] * L[j - (i - 32)]\r
-                       carry = (x[j] + 128) >> 8\r
-                       x[j] -= carry << 8\r
+function modL (r: StaticArray<u8>, x: StaticArray<i32>): void {\r
+       let c: i32\r
+       let t: i32\r
+       let v: i32\r
+       for (let i = 63; i >= 32; --i) {\r
+               c = 0\r
+               const xi = x[i]\r
+               for (let j = i - 32, k = i - 12; j < k; j++) {\r
+                       t = (xi * L[j - (i - 32)]) << 4\r
+                       v = x[j] + c - t\r
+                       c = (v + 128) >> 8\r
+                       x[j] = v - (c << 8)\r
                }\r
-               x[j] += carry\r
+               x[i - 12] += c\r
                x[i] = 0\r
        }\r
-       carry = 0\r
-       for (j = 0; j < 32; j++) {\r
-               x[j] += carry - (x[31] >> 4) * L[j]\r
-               carry = x[j] >> 8\r
+       c = 0\r
+       const x31 = x[31]\r
+       for (let j = 0; j < 32; j++) {\r
+               x[j] += c - (x31 >> 4) * L[j]\r
+               c = x[j] >> 8\r
                x[j] &= 255\r
        }\r
-       for (j = 0; j < 32; j++) {\r
-               x[j] -= carry * L[j]\r
+       for (let j = 0; j < 32; j++) {\r
+               x[j] -= c * L[j]\r
        }\r
-       for (i = 0; i < 32; i++) {\r
+       for (let i = 0; i < 32; i++) {\r
                x[i + 1] += x[i] >> 8\r
                r[i] = u8(x[i] & 255)\r
        }\r
 }\r
 \r
-function reduce (r: Uint8Array): void {\r
-       let x = new Int64Array(64)\r
-       x.set(r.subarray(0, 64), 0)\r
-       r.fill(0, 0, 64)\r
+const x = new StaticArray<i32>(64)\r
+function reduce (r: StaticArray<u8>): void {\r
+       for (let i = 0; i < 64; i++) {\r
+               x[i] = i32(r[i])\r
+               r[i] = 0\r
+       }\r
        modL(r, x)\r
 }\r
 \r
-function unpackneg (r: Array<Int64Array>, p: Uint8Array): i8 {\r
-       const t: Int64Array = new Int64Array(16)\r
-       const chk: Int64Array = new Int64Array(16)\r
-       const num: Int64Array = new Int64Array(16)\r
-       const den: Int64Array = new Int64Array(16)\r
-       const den2: Int64Array = new Int64Array(16)\r
-       const den4: Int64Array = new Int64Array(16)\r
-       const den6: Int64Array = new Int64Array(16)\r
+const unpack_t: StaticArray<i32> = new StaticArray<i32>(16)\r
+const chk: StaticArray<i32> = new StaticArray<i32>(16)\r
+const num: StaticArray<i32> = new StaticArray<i32>(16)\r
+const den: StaticArray<i32> = new StaticArray<i32>(16)\r
+const den2: StaticArray<i32> = new StaticArray<i32>(16)\r
+const den4: StaticArray<i32> = new StaticArray<i32>(16)\r
+const den6: StaticArray<i32> = new StaticArray<i32>(16)\r
+const z: StaticArray<i32> = new StaticArray<i32>(16)\r
+function unpackneg (r: Array<StaticArray<i32>>, p: StaticArray<u8>): i8 {\r
+       const t = unpack_t\r
 \r
-       r[2].fill(0).set([1], 0)\r
        unpack25519(r[1], p)\r
        Square(num, r[1])\r
        Multiply(den, num, D)\r
@@ -547,170 +800,252 @@ function unpackneg (r: Array<Int64Array>, p: Uint8Array): i8 {
        }\r
 \r
        if (par25519(r[0]) === (p[31] >> 7)) {\r
-               Subtract(r[0], new Int64Array(16), r[0])\r
+               Subtract(r[0], z, r[0])\r
        }\r
        Multiply(r[3], r[0], r[1])\r
        return 0\r
 }\r
 \r
-function crypto_sign (sm: Uint8Array, m: Uint8Array, n: i32, sk: Uint8Array, pk: Uint8Array): void {\r
-       const p: Int64Array[] = [new Int64Array(16), new Int64Array(16), new Int64Array(16), new Int64Array(16)]\r
+const blake2b = new Blake2b()\r
+function crypto_hash (o: StaticArray<u8>, i: StaticArray<u8>): void {\r
+       blake2b.init().update(i).digest(o)\r
+}\r
 \r
-       const d: Uint8Array = new Blake2b(64).update(sk).digest()\r
+const crypto_convert_h = new StaticArray<u8>(64)\r
+const crypto_convert_p = [new StaticArray<i32>(16), new StaticArray<i32>(16), new StaticArray<i32>(16), new StaticArray<i32>(16)]\r
+function crypto_convert (pk: StaticArray<u8>, sk: StaticArray<u8>): void {\r
+       const h = crypto_convert_h\r
+       const p = crypto_convert_p\r
+       p[0].fill(0)\r
+       p[1].fill(0); p[1][0] = 1\r
+       p[2].fill(0); p[2][0] = 1\r
+       p[3].fill(0)\r
+       crypto_hash(h, sk)\r
+       h[0] &= 248\r
+       h[31] &= 127\r
+       h[31] |= 64\r
+       scalarbase(p, h)\r
+       pack(pk, p)\r
+}\r
+\r
+const crypto_sign_d = new StaticArray<u8>(64)\r
+const crypto_sign_h = new StaticArray<u8>(64)\r
+const crypto_sign_p = [new StaticArray<i32>(16), new StaticArray<i32>(16), new StaticArray<i32>(16), new StaticArray<i32>(16)]\r
+const crypto_sign_r = new StaticArray<u8>(64)\r
+const crypto_sign_x = new StaticArray<i32>(64)\r
+const crypto_sign_s = new StaticArray<u8>(SIGNATURE_BYTES)\r
+const crypto_sign_prv = new StaticArray<u8>(PRIVATEKEY_BYTES)\r
+function crypto_sign (sm: StaticArray<u8>, m: StaticArray<u8>, sk: StaticArray<u8>): void {\r
+       const d = crypto_sign_d\r
+       const h = crypto_sign_h\r
+       const p = crypto_sign_p\r
+       const r = crypto_sign_r\r
+       const x = crypto_sign_x\r
+       const s = crypto_sign_s\r
+       const prv = crypto_sign_prv\r
+       p[0].fill(0)\r
+       p[1].fill(0); p[1][0] = 1\r
+       p[2].fill(0); p[2][0] = 1\r
+       p[3].fill(0)\r
+\r
+       for (let i = 0; i < PRIVATEKEY_BYTES; i++) {\r
+               prv[i] = sk[i]\r
+       }\r
+       crypto_hash(d, prv)\r
        d[0] &= 248\r
        d[31] &= 127\r
        d[31] |= 64\r
 \r
-       sm.set(m.subarray(0, n), 64)\r
-       sm.set(d.subarray(32, 64), 32)\r
+       for (let i = 0; i < 32; i++) {\r
+               s[i] = sm[i + 32] = d[i + 32]\r
+               s[i + 32] = sm[i + 64] = m[i]\r
+       }\r
 \r
-       const r: Uint8Array = new Blake2b(64).update(sm.subarray(32)).digest()\r
+       crypto_hash(r, s)\r
        reduce(r)\r
        scalarbase(p, r)\r
        pack(sm, p)\r
 \r
-       sm.set(pk, 32)\r
-       const h: Uint8Array = new Blake2b(64).update(sm).digest()\r
+       for (let i = 32; i < 64; i++) {\r
+               sm[i] = sk[i]\r
+       }\r
+       crypto_hash(h, sm)\r
        reduce(h)\r
 \r
-       const x = new Int64Array(64)\r
-       x.set(r.subarray(0, 32))\r
+       x.fill(0)\r
+       for (let i = 0; i < 32; i++) {\r
+               x[i] = r[i]\r
+       }\r
        for (let i = 0; i < 32; i++) {\r
                for (let j = 0; j < 32; j++) {\r
-                       x[i + j] += i64(h[i]) * i64(d[j])\r
+                       x[i + j] += i32(h[i]) * i32(d[j])\r
                }\r
        }\r
 \r
-       modL(sm.subarray(32), x)\r
+       for (let i = 0; i < SIGNATURE_BYTES; i++) {\r
+               s[i] = sm[i + 32]\r
+       }\r
+       modL(s, x)\r
+       for (let i = 0; i < SIGNATURE_BYTES; i++) {\r
+               sm[i + 32] = s[i]\r
+       }\r
 }\r
 \r
+const crypto_verify_open_d = new StaticArray<u8>(64)\r
+const crypto_verify_open_p = [new StaticArray<i32>(16), new StaticArray<i32>(16), new StaticArray<i32>(16), new StaticArray<i32>(16)]\r
+const crypto_verify_open_q = [new StaticArray<i32>(16), new StaticArray<i32>(16), new StaticArray<i32>(16), new StaticArray<i32>(16)]\r
+const crypto_verify_open_t = new StaticArray<u8>(32)\r
+const crypto_verify_open_sm = new StaticArray<u8>(SIGNEDBLOCKHASH_BYTES)\r
+const crypto_verify_open_S = new StaticArray<u8>(32)\r
 /**\r
-* Verifies that the signed message `sm` was signed using the public key `pk`.\r
-* If so, the original message is copied into `m` and the original message\r
-* bytelength is returned. Otherwise, `m` is zeroed and -1 is returned.\r
+* Verifies block hash `h` was signed with signature `s` against public key `k`.\r
 */\r
-function crypto_sign_open (m: Uint8Array, sm: Uint8Array, n: i32, pk: Uint8Array): i32 {\r
-       const t = new Uint8Array(32)\r
-       const p: Int64Array[] = [new Int64Array(16), new Int64Array(16), new Int64Array(16), new Int64Array(16)]\r
-       const q: Int64Array[] = [new Int64Array(16), new Int64Array(16), new Int64Array(16), new Int64Array(16)]\r
+function crypto_verify (h: StaticArray<u8>, s: StaticArray<u8>, k: StaticArray<u8>): bool {\r
+       const d = crypto_verify_open_d\r
+       const p = crypto_verify_open_p\r
+       const q = crypto_verify_open_q\r
+       const t = crypto_verify_open_t\r
+       const sm = crypto_verify_open_sm\r
+       const S = crypto_verify_open_S\r
+       p[0].fill(0)\r
+       p[1].fill(0); p[1][0] = 1\r
+       p[2].fill(0); p[2][0] = 1\r
+       p[3].fill(0)\r
+       q[0].fill(0)\r
+       q[1].fill(0)\r
+       q[2].fill(0); q[2][0] = 1\r
+       q[3].fill(0)\r
 \r
        // fail\r
-       if (unpackneg(q, pk)) {\r
-               return -1\r
+       if (unpackneg(q, k)) {\r
+               return false\r
        }\r
 \r
-       m.set(sm.subarray(0, n), 0)\r
-       m.set(pk.subarray(0, 32), 32)\r
-       const h: Uint8Array = new Blake2b(64).update(m).digest()\r
-       reduce(h)\r
-       scalarmult(p, q, h)\r
-       scalarbase(q, sm.subarray(32))\r
+       // signature is nonce point R and scalar S (R || S)\r
+       // data to hash is nonce point R, public key A, and message M (R || A || M)\r
+       // R, S, A, and M are all 32-byte values in this implementation\r
+       for (let i = 0; i < 32; i++) {\r
+               S[i] = s[i + 32]\r
+               sm[i] = s[i]\r
+               sm[i + 32] = k[i]\r
+               sm[i + 64] = h[i]\r
+       }\r
+       crypto_hash(d, sm)\r
+       reduce(d)\r
+       scalarmult(p, q, d)\r
+\r
+       q[0].fill(0)\r
+       q[1].fill(0); q[1][0] = 1\r
+       q[2].fill(0); q[2][0] = 1\r
+       q[3].fill(0)\r
+       scalarbase(q, S)\r
        add(p, q)\r
        pack(t, p)\r
 \r
-       // check failure\r
-       if (vn(sm, 0, t, 0, 32)) {\r
-               m.fill(0)\r
-               return -1\r
-       }\r
+       return !vn(s, t)\r
+}\r
+\r
+// Returns the pointer to the static input buffer (128 bytes).\r
+export function getInputPointer (): usize {\r
+       return INPUT_BUFFER\r
+}\r
 \r
-       // success\r
-       n -= 64\r
-       m.set(sm.subarray(64, n + 64), 0)\r
-       return n\r
+// Returns the pointer to the static output buffer (64 bytes).\r
+export function getOutputPointer (): usize {\r
+       return OUTPUT_BUFFER\r
 }\r
 \r
+const derive_sk = new StaticArray<u8>(PRIVATEKEY_BYTES)\r
+const derive_pk = new StaticArray<u8>(PUBLICKEY_BYTES)\r
 /**\r
-* Derives a Nano public key from a private key. This mirrors the functionality\r
-* of `nacl.sign.keyPair.fromSeed()`, returning just the `publicKey` property.\r
-*\r
-* @param {Uint8Array} privateKey - 32-byte private key\r
-* @returns {Uint8Array} 32-byte public key\r
+* Derives a Nano public key from a private key and writes it to the static\r
+* output buffer. This mirrors the functionality of\r
+* `nacl.sign.keyPair.fromSeed()`, returning just the `publicKey` property.\r
 */\r
-export function derive (k0: u64, k1: u64, k2: u64, k3: u64): Uint8Array {\r
-       const k = new Uint64Array(4); k.set([k0, k1, k2, k3])\r
-       const privateKey = Uint8Array.wrap(k.buffer)\r
-       if (privateKey.byteLength !== PRIVATEKEY_BYTES) {\r
-               const err = `Invalid private key byte length to convert to public key, expected ${PRIVATEKEY_BYTES}, actual ${privateKey.byteLength}`\r
-               trace(err, 1, privateKey.byteLength)\r
-               throw new Error(err)\r
-       }\r
-       const publicKey = new Uint8Array(PUBLICKEY_BYTES)\r
-       const p: Int64Array[] = [new Int64Array(16), new Int64Array(16), new Int64Array(16), new Int64Array(16)]\r
-\r
-       const h: Uint8Array = new Blake2b(64).update(privateKey).digest()\r
-       h[0] &= 248\r
-       h[31] &= 127\r
-       h[31] |= 64\r
+export function derive (): void {\r
+       const sk = derive_sk\r
+       const pk = derive_pk\r
+       for (let i = 0; i < PRIVATEKEY_BYTES; i++) {\r
+               sk[i] = load<u8>(INPUT_BUFFER + i)\r
+       }\r
 \r
-       scalarbase(p, h)\r
-       pack(publicKey, p)\r
+       const start = performance.now()\r
+       crypto_convert(pk, sk)\r
+       const end = performance.now()\r
+       trace('derive time', 1, end - start)\r
 \r
-       return publicKey\r
+       for (let i = 0; i < PUBLICKEY_BYTES; i++) {\r
+               store<u8>(OUTPUT_BUFFER + i, pk[i])\r
+       }\r
 }\r
 \r
+const sign_h = new StaticArray<u8>(BLOCKHASH_BYTES)\r
+const sign_sk = new StaticArray<u8>(SECRETKEY_BYTES)\r
+const sign_s = new StaticArray<u8>(SIGNEDBLOCKHASH_BYTES)\r
 /**\r
-* Signs the message using the private key and returns a signature. This mirrors\r
-* the functionality of `nacl.sign.detached()`.\r
+* Signs the message using the private key and writes the signature to the static\r
+* output buffer. This mirrors the functionality of `nacl.sign.detached()`.\r
 *\r
-* @param {Uint8Array} message - Message to sign\r
-* @param {Uint8Array} privateKey - 32-byte key to use for signing\r
-* @returns {Uint8Array} 64-byte signature\r
+* @param {u64} h0-h3 - Message to sign (32 bytes as 4 × u64)\r
+* @param {u64} k0-k7 - Secret key (32 bytes private key + 32 bytes public key as 8 × u64)\r
 */\r
-export function sign (h0: u64, h1: u64, h2: u64, h3: u64, k0: u64, k1: u64, k2: u64, k3: u64): Uint8Array {\r
-       const h = new Uint64Array(4); h.set([h0, h1, h2, h3])\r
-       const k = new Uint64Array(4); k.set([k0, k1, k2, k3])\r
-       const message = Uint8Array.wrap(h.buffer)\r
-       const privateKey = Uint8Array.wrap(k.buffer)\r
-       if (privateKey.byteLength !== PRIVATEKEY_BYTES) {\r
-               const err = `Invalid key byte length to sign message, expected ${PRIVATEKEY_BYTES}, actual ${privateKey.byteLength}`\r
-               trace(err, 1, privateKey.byteLength)\r
-               throw new Error(err)\r
-       }\r
-       const signed = new Uint8Array(SIGNATURE_BYTES + message.length)\r
-       const publicKey = derive(k0, k1, k2, k3)\r
-       crypto_sign(signed, message, message.length, privateKey, publicKey)\r
-       const sig = new Uint8Array(SIGNATURE_BYTES)\r
-       for (let i = 0; i < sig.length; i++) {\r
-               sig[i] = signed[i]\r
-       }\r
-       return sig\r
+export function sign (): void {\r
+       const h = sign_h\r
+       const sk = sign_sk\r
+       const s = sign_s\r
+       let ptr = INPUT_BUFFER\r
+       for (let i = 0; i < BLOCKHASH_BYTES; i++) {\r
+               h[i] = load<u8>(usize(i) + ptr)\r
+       }\r
+       ptr += BLOCKHASH_BYTES\r
+       for (let i = 0; i < SECRETKEY_BYTES; i++) {\r
+               sk[i] = load<u8>(usize(i) + ptr)\r
+       }\r
+\r
+       const start = performance.now()\r
+       crypto_sign(s, h, sk)\r
+       const end = performance.now()\r
+       trace('sign time', 1, end - start)\r
+\r
+       for (let i = 0; i < SIGNATURE_BYTES; i++) {\r
+               store<u8>(OUTPUT_BUFFER + i, s[i])\r
+       }\r
 }\r
 \r
+const verify_h = new StaticArray<u8>(BLOCKHASH_BYTES)\r
+const verify_s = new StaticArray<u8>(SIGNATURE_BYTES)\r
+const verify_k = new StaticArray<u8>(PUBLICKEY_BYTES)\r
 /**\r
-* Verifies a signature on a blockhash against a public key. This mirrors the\r
+* Verifies a signature on a block hash against a public key. This mirrors the\r
 * functionality of `nacl.sign.detached.verify()`.\r
 *\r
-* @param {Uint8Array} blockhash - 32-byte hash of signed Nano block\r
-* @param {Uint8Array} signature - 64-byte signature\r
-* @param {Uint8Array} publicKey - 32-byte key used for signing\r
-* @returns {boolean} - True if `publicKey` was used to sign `blockhash` and generate `signature`, else false\r
+* @param {u64} h0-h3 - Blockhash (32 bytes as 4 × u64)\r
+* @param {u64} s0-s7 - Signature (64 bytes as 8 × u64)\r
+* @param {u64} k0-k3 - Public key (32 bytes as 4 × u64)\r
+* @returns {i32} - 1 if valid, 0 if invalid\r
 */\r
-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 {\r
-       const h = new Uint64Array(4); h.set([h0, h1, h2, h3])\r
-       const s = new Uint64Array(8); s.set([s0, s1, s2, s3, s4, s5, s6, s7])\r
-       const k = new Uint64Array(4); k.set([k0, k1, k2, k3])\r
-       const blockhash = Uint8Array.wrap(h.buffer)\r
-       const signature = Uint8Array.wrap(s.buffer)\r
-       const publicKey = Uint8Array.wrap(k.buffer)\r
-       if (blockhash.byteLength !== BLOCKHASH_BYTES) {\r
-               const err = `Invalid blockhash size to verify signature, expected ${BLOCKHASH_BYTES}, actual ${blockhash.byteLength}`\r
-               trace(err, 1, blockhash.byteLength)\r
-               throw new Error(err)\r
-       }\r
-       if (signature.byteLength !== SIGNATURE_BYTES) {\r
-               const err = `Invalid signature size to verify signature, expected ${SIGNATURE_BYTES}, actual ${signature.byteLength}`\r
-               trace(err, 1, signature.byteLength)\r
-               throw new Error(err)\r
-       }\r
-       if (publicKey.byteLength !== PUBLICKEY_BYTES) {\r
-               const err = `Invalid public key size to verify signature, expected ${PUBLICKEY_BYTES}, actual ${publicKey.byteLength}`\r
-               trace(err, 1, signature.byteLength)\r
-               throw new Error(err)\r
-       }\r
-       const sm = new Uint8Array(SIGNATURE_BYTES + blockhash.length)\r
-       const m = new Uint8Array(SIGNATURE_BYTES + blockhash.length)\r
-       sm.set(signature, 0)\r
-       sm.set(blockhash, SIGNATURE_BYTES)\r
-       return (crypto_sign_open(m, sm, sm.length, publicKey) >= 0) ? 1 : 0\r
+export function verify (): i32 {\r
+       const h = verify_h\r
+       const s = verify_s\r
+       const k = verify_k\r
+\r
+       let ptr = INPUT_BUFFER\r
+       for (let i = 0; i < BLOCKHASH_BYTES; i++) {\r
+               h[i] = load<u8>(usize(i) + ptr)\r
+       }\r
+       ptr += BLOCKHASH_BYTES\r
+       for (let i = 0; i < SIGNATURE_BYTES; i++) {\r
+               s[i] = load<u8>(usize(i) + ptr)\r
+       }\r
+       ptr += SIGNATURE_BYTES\r
+       for (let i = 0; i < PUBLICKEY_BYTES; i++) {\r
+               k[i] = load<u8>(usize(i) + ptr)\r
+       }\r
+\r
+       const start = performance.now()\r
+       const result = crypto_verify(h, s, k)\r
+       const end = performance.now()\r
+       trace('verify time', 1, end - start)\r
+\r
+       return result ? 1 : 0\r
 }\r
index 64bce25a22a9abd925d0a0b43ef1fc912b2d7a7f..06670ff4587d0bb6403dd51242eb2732b7278002 100644 (file)
@@ -6,169 +6,222 @@ SPDX-License-Identifier: GPL-3.0-or-later
 <!DOCTYPE html>
 
 <head>
+       <meta charset="utf-8">
        <link rel="icon" href="data:,">
+       <script src="https://unpkg.com/nanocurrency-web@1.4.3/dist/index.min.js"></script>
+       <script src="https://unpkg.com/tweetnacl@1.0.3/nacl-fast.js"></script>
        <script type="module">
-
-import * as NanoNaCl from './dist/browser.js'
-
-const blockHash = 'BB569136FA05F8CBF65CEF2EDE368475B289C4477342976556BA4C0DDF216E45'
-const privateKey = '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3'
-
-try {
-       const { derive, sign, verify } = NanoNaCl
-
-       const publicKey = await derive(privateKey)
-       console.log('publicKey', publicKey)
-       console.log(publicKey.toUpperCase() === '3068BB1CA04525BB0E416C485FE6A67FD52540227D267CC8B6E8DA958A7FA039')
-
-       const signature = await sign(blockHash, privateKey)
-       console.log('signature', signature)
-       console.log(signature.toUpperCase() === '74BCC59DBA39A1E34A5F75F96D6DE9154E3477AAD7DE30EA563DFCFE501A804228008F98DDF4A15FD35705102785C50EF76732C3A74B0FEC5B0DD67B574A5900')
-
-       const isValid = await verify(blockHash, signature, publicKey)
-       console.log('isValid', isValid)
-       console.log(isValid === true)
-} catch (err) {
-       console.error(err)
-}
-/*
-               let NanoNaCl, stats
+               let NanoNaCl, TEST_VECTORS
                try {
-                       ({ NanoNaCl, stats } = await import('./index.js'))
+                       NanoNaCl = await import('./dist/browser.js')
                } catch (err) {
                        console.warn(err)
                        try {
-                               ({ NanoNaCl, stats } = await import('https://unpkg.com/nano-nacl@5.1/dist/main.min.js'))
+                               NanoNaCl = await import('https://unpkg.com/nano-nacl')
                        } catch (err) {
                                console.warn(err)
                                try {
-                                       ({ NanoNaCl, stats } = await import('https://cdn.jsdelivr.net/npm/nano-nacl@5.1/dist/main.min.js'))
+                                       NanoNaCl = await import('https://cdn.jsdelivr.net/npm/nano-nacl@latest')
                                } catch (err) {
                                        throw new Error(`Failed to load NanoNaCl ${err}`)
                                }
                        }
                }
+               try {
+                       ({ TEST_VECTORS } = await import('./env.mjs'))
+               } catch (err) {
+                       console.error(err)
+               }
 
-               const glSize = (canvas => {
-                       const gl = canvas.getContext('webgl2')
-                       canvas.height = gl.getParameter(gl.MAX_VIEWPORT_DIMS)[0]
-                       canvas.width = gl.getParameter(gl.MAX_VIEWPORT_DIMS)[1]
-                       return Math.min(gl.drawingBufferHeight, gl.drawingBufferWidth)
-               })(new OffscreenCanvas(0, 0))
-
-               function random (size = 64) {
-                       let hex = ''
-                       while (hex.length < size) {
-                               hex += crypto.randomUUID().replace(/-.*-/g, '')
+               /**
+               * Computes various types of averages for a set of numbers.
+               *
+               * @param {number[]} times - List of numbers, often timing durations.
+               * @returns Object with averaged values for the specified list.
+               */
+               function stats (times) {
+                       if (times == null || times.length === 0) return null
+
+                       const count = times.length
+                       const truncatedStart = Math.floor(count * 0.1)
+                       const truncatedEnd = count - truncatedStart
+                       const truncatedCount = truncatedEnd - truncatedStart
+                       let min = Number.MAX_SAFE_INTEGER
+                       let logarithms, max, median, reciprocals, total
+                       logarithms = max = median = reciprocals = total = 0
+
+                       let truncatedMin = Number.MAX_SAFE_INTEGER
+                       let truncatedLogarithms, truncatedMax, truncatedReciprocals, truncatedTotal
+                       truncatedLogarithms = truncatedMax = truncatedReciprocals = truncatedTotal = 0
+
+                       times.sort((a, b) => a - b)
+                       for (let i = 0; i < count; i++) {
+                               const time = times[i]
+                               total += time
+                               logarithms += Math.log(time)
+                               reciprocals += 1 / time
+                               min = Math.min(min, time)
+                               max = Math.max(max, time)
+                               if (i === Math.floor((count - 1) / 2)) median = time
+                               if (i === Math.floor(count / 2) && count % 2 === 0) median = (median + time) / 2
+                       }
+                       for (let i = truncatedStart; i < truncatedEnd; i++) {
+                               const time = times[i]
+                               truncatedTotal += time
+                               truncatedLogarithms += Math.log(time)
+                               truncatedReciprocals += 1 / time
+                               truncatedMin = Math.min(truncatedMin, time)
+                               truncatedMax = Math.max(truncatedMax, time)
+                       }
+                       return {
+                               count,
+                               total,
+                               rate: 1000 * count / total,
+                               min,
+                               max,
+                               median,
+                               arithmetic: total / count,
+                               geometric: Math.exp(logarithms / count),
+                               harmonic: count / reciprocals,
+                               truncatedCount,
+                               truncatedTotal,
+                               truncatedRate: 1000 * truncatedCount / truncatedTotal,
+                               truncatedMin,
+                               truncatedMax,
+                               truncatedArithmetic: truncatedTotal / truncatedCount,
+                               truncatedGeometric: Math.exp(truncatedLogarithms / truncatedCount),
+                               truncatedHarmonic: truncatedCount / truncatedReciprocals,
                        }
-                       return hex.slice(0, size)
                }
 
-               function average (times, type, effort) {
+               function average (times, type) {
                        const averages = stats(times)
-                       const title = `NanoNaCl (${type})`
+                       const title = `${type} derive-sign-verify`
                        return {
                                [title]: averages
                        }
                }
 
-               export async function run (size, difficulty, effort, api, isOutputShown, isDebug, isSelfCheck) {
-                       // Generate once on load to compile shaders and initialize buffers
-                       await NanoNaCl.sign(random(), { api, difficulty: 0 })
-                       const type = api
-                       api = type.toLowerCase()
+               function random (length = 32) {
+                       const bytes = crypto.getRandomValues(new Uint8Array(length))
+                       return [...bytes].map(b => b.toString(16).padStart(2, '0')).join('')
+               }
+
+               export async function test (api, size, runs, isDebug, isSelfCheck) {
+                       // Execute once on load to initialize worker and WASM
+                       const h = random(), k = random(64)
+                       await NanoNaCl.sign(h, k)
+
+                       const derive = sk => {
+                               switch (api) {
+                                       case 'NanoNaCl': {
+                                               return NanoNaCl.derive(sk)
+                                       }
+                                       case 'NanocurrencyWeb': {
+                                               return NanocurrencyWeb.wallet.legacyAccounts(sk)[0].publicKey
+                                       }
+                                       case 'TweetNaCl.js': {
+                                               const sk8 = sk.match(/.{2}/g).map(b => parseInt(b, 16))
+                                               const pk8 = nacl.sign.keyPair.fromSeed(new Uint8Array(sk8)).publicKey
+                                               return [...pk8].map(b => b.toString(16).padStart(2, '0')).join('')
+                                       }
+                                       default: {
+                                               return null
+                                       }
+                               }
+                       }
+                       const sign = (h, sk, pk) => {
+                               switch (api) {
+                                       case 'NanoNaCl': {
+                                               return NanoNaCl.sign(h, sk + pk)
+                                       }
+                                       case 'NanocurrencyWeb': {
+                                               const { privateKey } = NanocurrencyWeb.wallet.legacyAccounts(sk)[0]
+                                               return NanocurrencyWeb.tools.sign(privateKey, h)
+                                       }
+                                       case 'TweetNaCl.js': {
+                                               sk = sk + pk
+                                               const h8 = h.match(/.{2}/g).map(b => parseInt(b, 16))
+                                               const sk8 = sk.match(/.{2}/g).map(b => parseInt(b, 16))
+                                               const s8 = nacl.sign.detached(new Uint8Array(h8), new Uint8Array(sk8))
+                                               return [...s8].map(b => b.toString(16).padStart(2, '0')).join('')
+                                       }
+                                       default: {
+                                               return null
+                                       }
+                               }
+                       }
+                       const verify = (h, s, pk) => {
+                               switch (api) {
+                                       case 'NanoNaCl': {
+                                               return NanoNaCl.verify(h, s, pk)
+                                       }
+                                       case 'NanocurrencyWeb': {
+                                               return NanocurrencyWeb.tools.verify(pk, s, h)
+                                       }
+                                       case 'TweetNaCl.js': {
+                                               const h8 = h.match(/.{2}/g).map(b => parseInt(b, 16))
+                                               const s8 = s.match(/.{2}/g).map(b => parseInt(b, 16))
+                                               const pk8 = pk.match(/.{2}/g).map(b => parseInt(b, 16))
+                                               return nacl.sign.detached.verify(new Uint8Array(h8), new Uint8Array(s8), new Uint8Array(pk8))
+                                       }
+                                       default: {
+                                               return null
+                                       }
+                               }
+                       }
+
                        if (isSelfCheck) {
                                document.getElementById('status').innerHTML = `RUNNING SELF-CHECK`
                                console.log(`%cNanoNaCl`, 'color:green', 'Checking validation against known values')
-
+                               TEST_VECTORS ??= {
+                                       blockHash: 'BB569136FA05F8CBF65CEF2EDE368475B289C4477342976556BA4C0DDF216E45',
+                                       privateKey: '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3',
+                                       publicKey: '3068BB1CA04525BB0E416C485FE6A67FD52540227D267CC8B6E8DA958A7FA039',
+                                       signature: '74BCC59DBA39A1E34A5F75F96D6DE9154E3477AAD7DE30EA563DFCFE501A804228008F98DDF4A15FD35705102785C50EF76732C3A74B0FEC5B0DD67B574A5900'
+                               }
+                               const zeroes = '0000000000000000000000000000000000000000000000000000000000000000'
                                const expect = []
                                let result
 
                                // PASS
-                               result = await NanoNaCl.verify('47c83266398728cf', '92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D', { api, debug: isDebug })
-                               console.log(result)
-                               result = result.valid_all === '1'
-                               console.log(`verify() output for good nonce 1 is ${result === true ? 'correct' : 'incorrect'}`)
-                               expect.push(result)
-
-                               result = await NanoNaCl.verify('4a8fb104eebbd336', '8797585D56B8AEA3A62899C31FC088F9BE849BA8298A88E94F6E3112D4E55D01', { api, debug: isDebug })
+                               result = await NanoNaCl.derive(TEST_VECTORS.privateKey)
                                console.log(result)
-                               result = result.valid_all === '1'
-                               console.log(`verify() output for good nonce 2 is ${result === true ? 'correct' : 'incorrect'}`)
+                               result = result.toUpperCase() === TEST_VECTORS.publicKey.toUpperCase()
+                               console.log(`derive() output for good private key is ${result === true ? 'correct' : 'incorrect'}`)
                                expect.push(result)
 
-                               result = await NanoNaCl.verify('c5d5d6f7c5d6ccd1', '281E89AC73B1082B464B9C3C1168384F846D39F6DF25105F8B4A22915E999117', { api, debug: isDebug })
+                               result = await NanoNaCl.sign(TEST_VECTORS.blockHash, TEST_VECTORS.privateKey + TEST_VECTORS.publicKey)
                                console.log(result)
-                               result = result.valid_all === '1'
-                               console.log(`verify() output for colliding nonce is ${result === true ? 'correct' : 'incorrect'}`)
+                               result = result.toUpperCase() === TEST_VECTORS.signature.toUpperCase()
+                               console.log(`sign() output for good block hash and secret key is ${result === true ? 'correct' : 'incorrect'}`)
                                expect.push(result)
 
-                               result = await NanoNaCl.verify('6866c1ac3831a891', '7069D9CD1E85D6204301D254B0927F06ACC794C9EA5DF70EA5578458FB597090', { api, difficulty: 0xfffffe0000000000n, debug: isDebug })
+                               result = await NanoNaCl.verify(TEST_VECTORS.blockHash, TEST_VECTORS.signature, TEST_VECTORS.publicKey)
                                console.log(result)
-                               result = result.valid === '1' && result.valid_all === '0' && result.valid_receive === '1'
-                               console.log(`verify() output for good receive difficulty nonce is ${result === true ? 'correct' : 'incorrect'}`)
+                               result = result === true
+                               console.log(`verify() output for good block hash, signature, and private key is ${result === true ? 'correct' : 'incorrect'}`)
                                expect.push(result)
 
                                // XFAIL
-                               result = await NanoNaCl.verify('0000000000000000', '0000000000000000000000000000000000000000000000000000000000000000', { api, debug: isDebug })
-                               console.log(result)
-                               result = result.valid_all === '0'
-                               console.log(`verify() output for bad nonce 1 is ${result === true ? 'correct' : 'incorrect'}`)
-                               expect.push(result)
-
-                               result = await NanoNaCl.verify('c5d5d6f7c5d6ccd1', 'BA1E946BA3D778C2F30A83D44D2132CC6EEF010D8D06FF10A8ABD0100D8FB47E', { api, debug: isDebug })
-                               console.log(result)
-                               result = result.valid_all === '0'
-                               console.log(`verify() output for bad nonce 2 is ${result === true ? 'correct' : 'incorrect'}`)
-                               expect.push(result)
-
-                               result = await NanoNaCl.verify('ae238556213c3624', 'BF41D87DA3057FDC6050D2B00C06531F89F4AA6195D7C6C2EAAF15B6E703F8F6', { api, difficulty: 0xfffffff800000001n, debug: isDebug })
+                               result = await NanoNaCl.verify(zeroes, TEST_VECTORS.signature, TEST_VECTORS.publicKey)
                                console.log(result)
-                               result = result.error === 'Invalid difficulty fffffff800000001'
-                               console.log(`verify() output for bad difficulty beyond max is ${result === true ? 'correct' : 'incorrect'}`)
+                               result = result === false
+                               console.log(`verify() output for bad block hash is ${result === true ? 'correct' : 'incorrect'}`)
                                expect.push(result)
 
-                               result = await NanoNaCl.verify('29a9ae0236990e2e', '32721F4BD2AFB6F6A08D41CD0DF3C0D9C0B5294F68D0D12422F52B28F0800B5F', { api, debug: isDebug })
+                               result = await NanoNaCl.verify(TEST_VECTORS.blockHash, `${zeroes}${zeroes}`, TEST_VECTORS.publicKey)
                                console.log(result)
-                               result = result.valid_all === '0'
-                               console.log(`verify() output for slightly wrong nonce is ${result === true ? 'correct' : 'incorrect'}`)
+                               result = result === false
+                               console.log(`verify() output for bad signature is ${result === true ? 'correct' : 'incorrect'}`)
                                expect.push(result)
 
-                               result = await NanoNaCl.verify('7d903b18d03f9820', '39C57C28F904DFE4012288FFF64CE80C0F42601023A9C82108E8F7B2D186C150', { api, difficulty: 0xfffffe0000000000n, debug: isDebug })
+                               result = await NanoNaCl.verify(TEST_VECTORS.blockHash, TEST_VECTORS.signature, zeroes)
                                console.log(result)
-                               result = result.valid === '0' && result.valid_all === '0' && result.valid_receive === '0'
-                               console.log(`verify() output for bad receive difficulty nonce is ${result === true ? 'correct' : 'incorrect'}`)
+                               result = result === false
+                               console.log(`verify() output for bad public key is ${result === true ? 'correct' : 'incorrect'}`)
                                expect.push(result)
 
-                               const prefixes = [
-                                       '0B1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111',
-                                       '0b1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111',
-                                       '0O17777777777777777777777777777777777777777777777777777777777777777777777777777777777777',
-                                       '0o17777777777777777777777777777777777777777777777777777777777777777777777777777777777777',
-                                       '0Xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
-                                       '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
-                                       ' 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn ',
-                                       ' ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn ',
-                               ]
-                               for (let i = 0; i < prefixes.length; i++) {
-                                       const hash = prefixes[i]
-                                       let result = null
-                                       const start = performance.now()
-                                       try {
-                                               result = await NanoNaCl.sign(hash, { api, difficulty, effort, debug: isDebug })
-                                               console.log(result)
-                                       } catch (err) {
-                                               document.getElementById('output').innerHTML += `Error: ${err.message}<br/>`
-                                               console.error(err)
-                                               return
-                                       }
-                                       const end = performance.now()
-                                       const check = await NanoNaCl.verify(result.work, result.hash, { difficulty, debug: isDebug })
-                                       const isValid = (check.valid === '1' && BigInt(`0x${result.hash}`) === BigInt(hash.replace('n', '').replace(' f', '0xf')))
-                                       console.log(`sign() output for max value block hash is ${isValid === true ? 'correct' : 'incorrect'}`)
-                                       expect.push(isValid)
-                               }
-
                                try {
                                        if (!expect.every(result => result)) throw new Error(`Validation is not working`)
                                } catch (err) {
@@ -179,85 +232,60 @@ try {
                                }
                        }
 
-                       document.getElementById('status').innerHTML = `TESTING IN PROGRESS 0/${size}`
-                       console.log(`%cNanoNaCl (${type})`, 'color:green', `Sign ${size} unique send block hashes`)
-                       const times = []
-                       for (let i = 0; i < size; i++) {
-                               document.getElementById('status').innerHTML = `TESTING IN PROGRESS ${i}/${size}<br/>`
-                               const hash = random()
-                               let result = null
-                               const start = performance.now()
-                               try {
-                                       result = await NanoNaCl.sign(hash, { api, difficulty, effort, debug: isDebug })
-                               } catch (err) {
-                                       document.getElementById('output').innerHTML += `Error: ${err.message}<br/>`
-                                       console.error(err)
-                                       return
-                               }
-                               const end = performance.now()
-                               const check = await NanoNaCl.verify(result.work, result.hash, { difficulty, debug: isDebug })
-                               const isValid = (result.hash === hash && check.valid === '1') ? 'VALID' : 'INVALID'
-                               times.push(end - start)
-                               const msg = `${isValid} (${end - start} ms)\n${JSON.stringify(result, ' ', 2)}`
-                               if (isOutputShown) document.getElementById('output').innerHTML += `${msg}<br/>`
-                       }
-                       document.getElementById('output').innerHTML += `<hr/>`
-                       document.getElementById('summary').innerHTML += `${JSON.stringify(average(times, type, effort), null, '\t')}<br/>`
-                       document.getElementById('status').innerHTML = `TESTING COMPLETE<br/>`
-                       console.log('%cTESTING COMPLETE', 'color:orange;font-weight:bold')
-               }
-
-               export async function score (runs, size, difficulty, effort, api) {
-                       console.log(`%cNanoNaCl`, 'color:green', `Calculate truncated harmonic mean of the truncated arithmetic rate across ${runs} runs of ${size} samples.`)
+                       console.log(`%c${api}`, 'color:green', `Calculate truncated harmonic mean of the truncated arithmetic rate of signing random block hashes across ${runs} runs of ${size} samples.`)
                        const rates = []
+                       let start = 0, end = 0, publicKey = null, signature = null, isValid = false
                        for (let i = 0; i < runs; i++) {
                                const times = []
                                for (let j = 0; j < size; j++) {
-                                       document.getElementById('status').innerHTML = `SCORING IN PROGRESS. THIS WILL TAKE A LONG TIME. ${i}/${size} ${j}/${runs}<br/>`
-                                       const hash = random()
-                                       let result = null
-                                       const start = performance.now()
+                                       document.getElementById('status').innerHTML = `TESTING IN PROGRESS. THIS MAY TAKE A LONG TIME. ${i}/${runs} ${j}/${size}<br/>`
+                                       const blockHash = random()
+                                       const privateKey = blockHash
                                        try {
-                                               result = await NanoNaCl.sign(hash, { difficulty, effort, api })
+                                               start = performance.now()
+                                               publicKey = await derive(privateKey)
+                                               signature = await sign(blockHash, privateKey, publicKey)
+                                               isValid = await verify(blockHash, signature, publicKey)
+                                               end = performance.now()
                                        } catch (err) {
                                                document.getElementById('output').innerHTML += `Error: ${err.message}<br/>`
                                                console.error(err)
                                                return
                                        }
-                                       times.push(performance.now() - start)
+                                       if (!isValid) {
+                                               document.getElementById('status').innerHTML = `ERROR: invalid result\nblock hash: ${blockHash}\nsignature: ${signature}\npublic key: ${publicKey}`
+                                               return
+                                       }
+                                       if (!start || !end || start > end) {
+                                               document.getElementById('status').innerHTML = `ERROR: invalid timing\nstart: ${start}\nend: ${end}`
+                                               return
+                                       }
+                                       times.push(end - start)
                                }
-                               const results = Object.values(average(times))[0]
-                               const { truncatedRate } = results
+                               const avg = average(times, api)
+                               const result = Object.values(avg)[0]
+                               const { truncatedRate } = result
                                rates.push(truncatedRate)
-                               document.getElementById('output').innerHTML += `Benchmark ${i + 1} score: ${truncatedRate} wps<br/>`
+                               if (isDebug) document.getElementById('output').innerHTML += `Benchmark ${i + 1} score: ${truncatedRate} ops<br/>`
+                               if (isDebug) document.getElementById('summary').innerHTML += `${JSON.stringify(average(times, api), null, '\t')}<br/>`
                        }
-                       const results = Object.values(average(rates))[0]
-                       console.log(rates)
-                       console.log(results)
+                       const results = Object.values(average(rates, api))[0]
                        const { truncatedHarmonic } = results
                        document.getElementById('output').innerHTML += `<hr/>`
-                       document.getElementById('summary').innerHTML += `work-per-second: ${truncatedHarmonic}<br/>`
-                       document.getElementById('status').innerHTML = `SCORING COMPLETE<br/>`
-                       console.log('%cSCORING COMPLETE', 'color:orange;font-weight:bold')
+                       document.getElementById('summary').innerHTML += `${api} derive-sign-verify: ${truncatedHarmonic} per second<br/>`
+                       document.getElementById('status').innerHTML = `TESTING COMPLETE<br/>`
+                       console.log('%cTESTING COMPLETE', 'color:orange;font-weight:bold')
                }
 
                function startValidation (event) {
-                       const difficulty = document.getElementById('difficulty')?.value
-                       const work = document.getElementById('work')?.value
-                       const hash = document.getElementById('hash')?.value
-                       const validation = document.getElementById('validation')
                        const api = document.getElementById('api')?.value
-                       const apiContainer = document.getElementById('api')?.parentElement
-                       if (api === 'CPU') {
-                               apiContainer.classList.add('warning')
-                               apiContainer.title = 'Use only for testing with very low difficulty.'
-                       } else {
-                               apiContainer.classList.remove('warning')
-                               apiContainer.title = ''
-                       }
+                       const signature = document.getElementById('signature')?.value
+                       const blockHash = document.getElementById('blockHash')?.value
+                       const publicKey = document.getElementById('publicKey')?.value
+                       const validation = document.getElementById('validation')
                        validation.innerText = '⏳'
-                       if (work.length === 16 && hash.length === 64) {
-                               NanoNaCl.verify(work, hash, { difficulty })
+                       if (signature.length === 128 && blockHash.length === 64 && publicKey.length === 64) {
+                               NanoNaCl.verify(blockHash, signature, publicKey)
                                        .then(result => {
                                                validation.innerText = result
                                                        ? '✔️'
@@ -268,49 +296,32 @@ try {
                                        })
                        }
                }
-               document.getElementById('difficulty').addEventListener('input', startValidation)
-               document.getElementById('work').addEventListener('input', startValidation)
-               document.getElementById('hash').addEventListener('input', startValidation)
                document.getElementById('api').addEventListener('input', startValidation)
+               document.getElementById('blockHash').addEventListener('input', startValidation)
+               document.getElementById('signature').addEventListener('input', startValidation)
+               document.getElementById('publicKey').addEventListener('input', startValidation)
 
                function startTest (event) {
                        event.target.disabled = true
-                       const size = document.getElementById('size')
-                       const difficulty = document.getElementById('difficulty')
-                       const effort = document.getElementById('effort')
                        const api = document.getElementById('api')
-                       const isOutputShown = document.getElementById('isOutputShown')
+                       const size = document.getElementById('size')
+                       const runs = document.getElementById('runs')
                        const isDebug = document.getElementById('isDebug')
                        const isSelfCheck = document.getElementById('isSelfCheck')
-                       run(+size.value, difficulty.value, +effort.value, api.value, isOutputShown.checked, isDebug.checked, isSelfCheck.checked)
+                       test(api.value, +size.value, +runs.value, isDebug.checked, isSelfCheck.checked)
                                .then(() => {
                                        event.target.disabled = false
                                        isSelfCheck.checked = false
                                })
                }
 
-               function startScore (event) {
-                       event.target.disabled = true
-                       const runs = document.getElementById('runs')
-                       const size = document.getElementById('size')
-                       const difficulty = document.getElementById('difficulty')
-                       const effort = document.getElementById('effort')
-                       const api = document.getElementById('api')
-                       const isOutputShown = document.getElementById('isOutputShown')
-                       score(+runs.value, +size.value, difficulty.value, +effort.value, api.value)
-                               .then(() => event.target.disabled = false)
-               }
-
                document.getElementById('btnStartTest').addEventListener('click', startTest)
-               document.getElementById('btnStartScore').addEventListener('click', startScore)
-               document.getElementById('effort').value = Math.max(1, Math.floor(navigator.hardwareConcurrency) / 2)
-               */
        </script>
        <style>
                body{background:black;color:white;}a{color:darkcyan;}input[type=number]{width:5em;}input[type=checkbox]{margin-right:2em;}
-               div.hex{display:inline-block;}
-               div.hex::before{color:grey;content:'0x';display:inline-block;font-size:90%;left:0.5em;position:relative;width:0;}
-               div.hex>input{padding-left:1.5em;}
+               div.hex{display:inline-block;font-family:monospace;}
+               div.hex::before{color:grey;content:'0x';display:inline-block;font-family:monospace;font-size:90%;left:0.5em;position:relative;width:0;}
+               div.hex>input{font-family:monospace;padding-left:1.5em;}
                .warning::before{content:'⚠️';}
        </style>
 </head>
@@ -321,34 +332,27 @@ try {
        <h2>Speed test for NanoNaCl tool.</h2>
        <p>Times below are in milliseconds and are summarized by various averaging methods.</p>
        <hr />
-       <label for="work">Validate Work</label>
-       <div class="hex"><input id="work" type="text" /></div>
-       <label for="hash" class="hex">Hash</label>
-       <div class="hex"><input id="hash" type="text" /></div>
+       <label for="signature">Verify Signature</label>
+       <div class="hex"><input id="signature" type="text" /></div>
+       <label for="blockHash" class="hex">Block Hash</label>
+       <div class="hex"><input id="blockHash" type="text" /></div>
+       <label for="publicKey" class="hex">Public Key</label>
+       <div class="hex"><input id="publicKey" type="text" /></div>
        <span id="validation"></span>
        <hr />
-       <h3>Options</h3>
-       <label for="difficulty" class="hex">Difficulty</label>
-       <div class="hex"><input id="difficulty" type="text" value="FFFFFFF800000000" /></div>
-       <label for="size">Test Size</label>
-       <input id="size" type="number" value="1" autofocus />
-       <label for="effort">Effort (1-32)</label>
-       <input id="effort" type="number" min="1" max="32" />
+       <h3>Benchmarking</h3>
        <span>
                <label for="api">API</label>
                <select id="api">
-                       <option>WebGPU</option>
-                       <option>WebGL</option>
-                       <option>WASM</option>
-                       <option>CPU</option>
+                       <option>NanoNaCl</option>
+                       <option>NanocurrencyWeb</option>
+                       <option>TweetNaCl.js</option>
                </select>
        </span>
-       <hr />
-       <h3>Benchmarking</h3>
-       <span>
-               <label for="isOutputShown">Show output?</label>
-               <input id="isOutputShown" type="checkbox" checked />
-       </span>
+       <label for="size">Test Size</label>
+       <input id="size" type="number" value="100" autofocus />
+       <label for="runs">Test Runs</label>
+       <input id="runs" type="number" value="10" autofocus />
        <span>
                <label for="isDebug">Debug?</label>
                <input id="isDebug" type="checkbox" />
@@ -359,11 +363,6 @@ try {
        </span>
        <button id="btnStartTest">Go</button>
        <hr />
-       <h3>Scoring</h3>
-       <label for="runs">Score Runs</label>
-       <input id="runs" type="number" value="1" autofocus />
-       <button id="btnStartScore">Score ❗</button>
-       <hr />
        <h3 id="status">WAITING</h3>
        <hr />
        <pre id="summary"></pre>
index d58f8547834cb7cede08a1a7bb94795d89a54136..736abf832daff3373b7667743ff4bfbeb3a65b23 100644 (file)
--- 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<void> => {
        let wasm
        let module
        let instance
-       let derive: (k: BigUint64Array) => Uint8Array<ArrayBuffer>
-       let sign: (h: BigUint64Array, k: BigUint64Array) => Uint8Array<ArrayBuffer>
-       let verify: (h: BigUint64Array, s: BigUint64Array, k: BigUint64Array) => boolean
        let memory: WebAssembly.Memory
+       let derive: (k: Uint8Array) => Uint8Array<ArrayBuffer>
+       let sign: (h: Uint8Array, k: Uint8Array) => Uint8Array<ArrayBuffer>
+       let verify: (h: Uint8Array, s: Uint8Array, k: Uint8Array) => boolean
 
        async function setup (): Promise<void> {
                try {
@@ -33,6 +36,10 @@ const NanoNaCl = async (bytes: number[]): Promise<void> => {
                                                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<void> => {
                                                        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<void> => {
                                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<ArrayBuffer> {
-                               // 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<ArrayBuffer> {
+                               // 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<ArrayBuffer> {
-                               // 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<ArrayBuffer> {
+                               // 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<void> => {
                }
        }
 
+       function hex2bytes (type: string, byteLength: number, hex?: unknown): Uint8Array<ArrayBuffer> {
+               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<void> {
                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<void> => {
                                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<void> {
+       if (!isReady) setup()
        return new Promise(async (ready, fail): Promise<void> => {
                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<Uint8Array<Ar
                worker.onerror = reject
                worker.onmessage = (msg): void => {
                        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<void> {
                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<void> {
        })
 }
 
-/**
-* Nano public key derivation using WebAssembly.
-*/
-export async function derive (privateKey: string): Promise<string> {
-       if (isReady === false) setup()
+async function run (data: Record<string, string>): Promise<string> {
        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<string> {
+       return await run({ action: 'derive', privateKey })
 }
 
 /**
 * Nano block signature using WebAssembly.
 */
-export async function sign (hash: string, privateKey: string): Promise<string> {
-       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<string> {
+       return await run({ action: 'sign', blockHash, secretKey })
 }
 
 /**
 * Nano block signature verification using WebAssembly.
 */
-export async function verify (hash: string, signature: string, publicKey: string): Promise<boolean> {
-       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<boolean> {
+       const result = await run({ action: 'verify', blockHash, signature, publicKey })
+       return result === '01'
 }