]> git.codecow.com Git - libnemo.git/commitdiff
Use pool for NanoNaCl conversion and verification.
authorChris Duncan <chris@zoso.dev>
Sun, 6 Jul 2025 20:47:23 +0000 (13:47 -0700)
committerChris Duncan <chris@zoso.dev>
Sun, 6 Jul 2025 20:47:23 +0000 (13:47 -0700)
src/lib/account.ts
src/lib/block.ts
src/lib/convert.ts
src/lib/wallets/wallet.ts
src/lib/workers/nano-nacl.ts
test/GLOBALS.mjs

index 2fc90d202bad34939d8b188ba81664dbc8759f87..7c6452b51fd9e7791f95988e49d304bd4adf40f4 100644 (file)
@@ -6,7 +6,7 @@ import { ACCOUNT_KEY_BYTE_LENGTH, ACCOUNT_KEY_HEX_LENGTH, ALPHABET, PREFIX, PREF
 import { base32, bytes, hex, obj, utf8 } from './convert'\r
 import { Pool } from './pool'\r
 import { Rpc } from './rpc'\r
-import { NanoNaCl, SafeWorker } from '#workers'\r
+import { NanoNaClWorker, SafeWorker } from '#workers'\r
 \r
 /**\r
 * Represents a single Nano address and the associated public key. To include the\r
@@ -16,7 +16,8 @@ import { NanoNaCl, SafeWorker } from '#workers'
 */\r
 export class Account {\r
        static #isInternal: boolean = false\r
-       static #poolSafe: Pool\r
+       static #poolSafe: Pool = new Pool(SafeWorker)\r
+       static #poolNanoNaCl: Pool = new Pool(NanoNaClWorker)\r
 \r
        #address: string\r
        #locked: boolean\r
@@ -71,8 +72,6 @@ export class Account {
                this.#locked = false\r
                this.#pub = publicKey\r
                this.#prv = privateKey\r
-               Account.#poolSafe ??= new Pool(SafeWorker)\r
-               Account.#isInternal = false\r
        }\r
 \r
        /**\r
@@ -101,9 +100,9 @@ export class Account {
        * @returns {Account} The instantiated Account object\r
        */\r
        static fromAddress (address: string, index?: number): Account {\r
-               Account.#isInternal = true\r
-               Account.validate(address)\r
-               const publicKey = Account.#addressToKey(address)\r
+               this.#isInternal = true\r
+               this.validate(address)\r
+               const publicKey = this.#addressToKey(address)\r
                const account = new this(address, publicKey, new Uint8Array(32), index)\r
                return account\r
        }\r
@@ -116,9 +115,9 @@ export class Account {
        * @returns {Account} The instantiated Account object\r
        */\r
        static fromPublicKey (publicKey: string, index?: number): Account {\r
-               Account.#isInternal = true\r
-               Account.#validateKey(publicKey)\r
-               const address = Account.#keyToAddress(publicKey)\r
+               this.#isInternal = true\r
+               this.#validateKey(publicKey)\r
+               const address = this.#keyToAddress(publicKey)\r
                const account = new this(address, publicKey, new Uint8Array(32), index)\r
                return account\r
        }\r
@@ -131,12 +130,21 @@ export class Account {
        * @param {number} [index] - Account number used when deriving the key\r
        * @returns {Account} A new Account object\r
        */\r
-       static fromPrivateKey (privateKey: string | Uint8Array<ArrayBuffer>, index?: number): Account {\r
+       static async fromPrivateKey (privateKey: string | Uint8Array<ArrayBuffer>, index?: number): Promise<Account> {\r
                if (typeof privateKey === 'string') privateKey = hex.toBytes(privateKey)\r
-               Account.#isInternal = true\r
-               Account.#validateKey(privateKey)\r
-               const publicKey = NanoNaCl.convert(privateKey)\r
-               const address = Account.#keyToAddress(publicKey)\r
+               this.#isInternal = true\r
+               this.#validateKey(privateKey)\r
+               let publicKey: string\r
+               try {\r
+                       const result = (await this.#poolNanoNaCl.assign({\r
+                               method: 'convert',\r
+                               privateKey: bytes.toArray(privateKey)\r
+                       }))[0]\r
+                       publicKey = result.publicKey\r
+               } catch (err) {\r
+                       throw new Error(`Failed to derive public key from private key`, { cause: err })\r
+               }\r
+               const address = this.#keyToAddress(publicKey)\r
                const account = new this(address, publicKey, privateKey, index)\r
                return account\r
        }\r
index 3686e3c211135810d4db5b6829f97f538be3bf86..63e4c4bde6effc9a9374cffff949f3bb371e207b 100644 (file)
@@ -8,7 +8,7 @@ import { BURN_ADDRESS, PREAMBLE, DIFFICULTY_RECEIVE, DIFFICULTY_SEND } from './c
 import { dec, hex } from './convert'
 import { Pool } from './pool'
 import { Rpc } from './rpc'
-import { NanoNaCl, NanoNaClWorker } from '#workers'
+import { NanoNaClWorker } from '#workers'
 
 /**
 * Represents a block as defined by the Nano cryptocurrency protocol. The Block
@@ -16,7 +16,8 @@ import { NanoNaCl, NanoNaClWorker } from '#workers'
 * of three derived classes: SendBlock, ReceiveBlock, ChangeBlock.
 */
 abstract class Block {
-       static #poolNanoNaCl: Pool
+       static #poolNanoNaCl: Pool = new Pool(NanoNaClWorker)
+
        account: Account
        type: string = 'state'
        abstract subtype: 'send' | 'receive' | 'change'
@@ -38,7 +39,6 @@ abstract class Block {
                } else {
                        throw new TypeError('Invalid account')
                }
-               Block.#poolNanoNaCl ??= new Pool(NanoNaClWorker)
        }
 
        /**
@@ -200,11 +200,17 @@ abstract class Block {
                if (!key) {
                        throw new Error('Provide a key for block signature verification.')
                }
-               return NanoNaCl.verify(
-                       hex.toBytes(this.hash),
-                       hex.toBytes(this.signature ?? ''),
-                       hex.toBytes(key)
-               )
+               try {
+                       const result = (await Block.#poolNanoNaCl.assign({
+                               method: 'verify',
+                               msg: hex.toArray(this.hash),
+                               signature: hex.toArray(this.signature ?? ''),
+                               publicKey: hex.toArray(key)
+                       }))[0]
+                       return result.isVerified
+               } catch (err) {
+                       throw new Error(`Failed to derive public key from private key`, { cause: err })
+               }
        }
 }
 
index f2a05907a4872d7fab2b5cef0e43777f40ed44aa..a1d5733f1aa79edf8b6dee33aa6e978b293d28a4 100644 (file)
@@ -76,6 +76,19 @@ export class bin {
 }\r
 \r
 export class bytes {\r
+       /**\r
+       * Convert a Uint8Array to an array of decimal byte values.\r
+       *\r
+       * @param {Uint8Array} bytes - Byte array to convert\r
+       * @param {number}[padding=0] - Minimum length of the resulting array padded as necessary with starting 0 values\r
+       * @returns {number[]} Decimal array representation of the input value\r
+       */\r
+       static toArray (bytes: Uint8Array, padding: number = 0): number[] {\r
+               if (!(bytes instanceof Uint8Array)) {\r
+                       throw new TypeError('bytes must be Uint8Array')\r
+               }\r
+               return [...bytes.values()]\r
+       }\r
        /**\r
        * Converts a Uint8Aarray of bytes to a base32 string.\r
        *\r
index b2efda7696592f4cc2ba85051f9713a36d8c5104..b3e87f1bdfafb7644656bca8bae6db7a00d89de0 100644 (file)
@@ -26,8 +26,7 @@ export type KeyPair = {
 export abstract class Wallet {\r
        abstract ckd (index: number[]): Promise<KeyPair[]>\r
 \r
-       static #poolNanoNacl: Pool\r
-       static #poolSafe: Pool\r
+       static #poolSafe: Pool = new Pool(SafeWorker)\r
 \r
        #accounts: AccountList\r
        #id: Entropy\r
@@ -49,8 +48,6 @@ export abstract class Wallet {
                this.#id = id\r
                this.#m = mnemonic ?? null\r
                this.#s = seed ?? null\r
-               Wallet.#poolNanoNacl ??= new Pool(NanoNaClWorker)\r
-               Wallet.#poolSafe ??= new Pool(SafeWorker)\r
        }\r
 \r
        /**\r
@@ -112,21 +109,13 @@ export abstract class Wallet {
                        }\r
                }\r
                if (indexes.length > 0) {\r
-                       let results = await this.ckd(indexes)\r
-                       const data: any = []\r
-                       results.forEach(r => data.push({\r
-                               method: 'convert',\r
-                               privateKey: r.privateKey,\r
-                               index: r.index\r
-                       }))\r
-                       const keypairs: KeyPair[] = await Wallet.#poolNanoNacl.assign(data)\r
+                       const keypairs = await this.ckd(indexes)\r
                        for (const keypair of keypairs) {\r
-                               if (keypair.privateKey == null) throw new RangeError('Account private key missing')\r
-                               if (keypair.publicKey == null) throw new RangeError('Account public key missing')\r
-                               if (keypair.index == null) throw new RangeError('Account keys derived but index missing')\r
                                const { privateKey, index } = keypair\r
-                               output[keypair.index] = Account.fromPrivateKey(privateKey, index)\r
-                               this.#accounts[keypair.index] = output[keypair.index]\r
+                               if (privateKey == null) throw new RangeError('Account private key missing')\r
+                               if (index == null) throw new RangeError('Account keys derived but index missing')\r
+                               output[index] = await Account.fromPrivateKey(privateKey, index)\r
+                               this.#accounts[index] = output[index]\r
                        }\r
                }\r
                return output\r
index 834d374698ef72f6a9f09c1e29e527fd1c86ff4f..0b2dabf5ba5b2dbd8d5651b7053c2f75702c8230 100644 (file)
@@ -28,12 +28,16 @@ export class NanoNaCl extends WorkerInterface {
                        for (let d of data) {\r
                                try {\r
                                        switch (d.method) {\r
+                                               case 'convert': {\r
+                                                       d.publicKey = await this.convert(Uint8Array.from(d.privateKey))\r
+                                                       break\r
+                                               }\r
                                                case 'detached': {\r
                                                        d.signature = await this.detached(Uint8Array.from(d.msg), Uint8Array.from(d.privateKey))\r
                                                        break\r
                                                }\r
-                                               case 'convert': {\r
-                                                       d.publicKey = await this.convert(d.privateKey)\r
+                                               case 'verify': {\r
+                                                       d.isVerified = await this.verify(Uint8Array.from(d.msg), Uint8Array.from(d.signature), Uint8Array.from(d.publicKey))\r
                                                        break\r
                                                }\r
                                                default: {\r
index 14ab050e9a706e05af98a1b033df092cb0179dab..7356b2886bf4b032e89044b4e491506c2d998436 100644 (file)
@@ -141,7 +141,7 @@ export function test (name, opts, fn) {
                try {
                        return fn
                                .then(() => pass(name))
-                               .catch((err) => { fail(`${name}: ${err}`) })
+                               .catch((err) => fail(`${name}: ${err}`))
                } catch (err) {
                        fail(`${name}: ${err.message}`)
                        fail(err)
@@ -150,7 +150,7 @@ export function test (name, opts, fn) {
                try {
                        return fn()
                                .then(() => pass(name))
-                               .catch((err) => fail(`${name}: ${err.message}`))
+                               .catch((err) => fail(`${name}: ${err}`))
                } catch (err) {
                        fail(`${name}: ${err.message}`)
                        fail(err)
@@ -160,7 +160,7 @@ export function test (name, opts, fn) {
                        fn()
                        pass(name)
                } catch (err) {
-                       fail(`${name}: ${err.message}`)
+                       fail(`${name}: ${err}`)
                        fail(err)
                }
        } else {