]> zoso.dev Git - libnemo.git/commitdiff
In BIP-32/44 keygen, add type guard against input to serialization functions and...
authorChris Duncan <chris@zoso.dev>
Mon, 18 Nov 2024 14:21:03 +0000 (06:21 -0800)
committerChris Duncan <chris@zoso.dev>
Mon, 18 Nov 2024 14:21:03 +0000 (06:21 -0800)
16 files changed:
package-lock.json
package.json
src/lib/bip32-key-derivation.ts
src/lib/safe.ts
src/lib/tools.ts
src/lib/wallet.ts
test/GLOBALS.mjs [new file with mode: 0644]
test/TEST_VECTORS.js
test/create-wallet.test.mjs
test/derive-accounts.test.mjs
test/import-wallet.test.mjs
test/lock-unlock-wallet.mjs
test/manage-rolodex.mjs
test/refresh-accounts.test.mjs
test/sign-blocks.test.mjs
test/tools.test.mjs

index 8fed5d2df8c4e25ee63c985f8a7e859e2cb7ed3a..1ec4b4b32bb906a59b5887fcda14568456dac135 100644 (file)
@@ -9,9 +9,11 @@
                        "version": "0.0.19",
                        "license": "(GPL-3.0-or-later AND MIT)",
                        "dependencies": {
+                               "blake2b-wasm": "^2.4.0",
                                "blakejs": "^1.2.1"
                        },
                        "devDependencies": {
+                               "@types/blake2b-wasm": "^2.4.3",
                                "@types/node": "^22.8.6",
                                "@types/w3c-web-hid": "^1.0.6",
                                "@types/w3c-web-usb": "^1.0.10",
                        "license": "Apache-2.0",
                        "optional": true
                },
+               "node_modules/@types/blake2b-wasm": {
+                       "version": "2.4.3",
+                       "resolved": "https://registry.npmjs.org/@types/blake2b-wasm/-/blake2b-wasm-2.4.3.tgz",
+                       "integrity": "sha512-emsOJOuF5shxg5zhN3CHOy4BO/a26O++yk0ncFW9fePquKSGs1g6PIps8u8zFmApJjIkMQr7neVUqvoic4BRFw==",
+                       "dev": true,
+                       "license": "MIT"
+               },
                "node_modules/@types/node": {
                        "version": "22.8.6",
                        "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.6.tgz",
                        "dev": true,
                        "license": "MIT"
                },
+               "node_modules/b4a": {
+                       "version": "1.6.7",
+                       "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz",
+                       "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==",
+                       "license": "Apache-2.0"
+               },
+               "node_modules/blake2b-wasm": {
+                       "version": "2.4.0",
+                       "resolved": "https://registry.npmjs.org/blake2b-wasm/-/blake2b-wasm-2.4.0.tgz",
+                       "integrity": "sha512-S1kwmW2ZhZFFFOghcx73+ZajEfKBqhP82JMssxtLVMxlaPea1p9uoLiUZ5WYyHn0KddwbLc+0vh4wR0KBNoT5w==",
+                       "license": "MIT",
+                       "dependencies": {
+                               "b4a": "^1.0.1",
+                               "nanoassert": "^2.0.0"
+                       }
+               },
                "node_modules/blakejs": {
                        "version": "1.2.1",
                        "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz",
                                "node": ">=0.8.x"
                        }
                },
+               "node_modules/nanoassert": {
+                       "version": "2.0.0",
+                       "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz",
+                       "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==",
+                       "license": "ISC"
+               },
                "node_modules/rxjs": {
                        "version": "7.8.1",
                        "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
index 7dee5a72231f659ce0155f8d152534618996d63c..93810e493f5f90cc03775d700b716882b093d707 100644 (file)
@@ -45,6 +45,7 @@
                "test:coverage:report": "npm run test:coverage -- --test-reporter=lcov --test-reporter-destination=coverage.info && genhtml coverage.info --output-directory test/coverage && rm coverage.info && xdg-open test/coverage/index.html"
        },
        "dependencies": {
+               "blake2b-wasm": "^2.4.0",
                "blakejs": "^1.2.1"
        },
        "optionalDependencies": {
@@ -53,6 +54,7 @@
                "@ledgerhq/hw-transport-webusb": "^6.29.4"
        },
        "devDependencies": {
+               "@types/blake2b-wasm": "^2.4.3",
                "@types/node": "^22.8.6",
                "@types/w3c-web-hid": "^1.0.6",
                "@types/w3c-web-usb": "^1.0.10",
index 8580ad0d2583adbec0dd3ac3f34cf010f7ddf242..3bff28a2a51ee84c98adf0eb656382fa9e22b732 100644 (file)
@@ -47,25 +47,25 @@ async function CKDpriv ({ privateKey, chainCode }: ExtendedKey, index: number):
 }\r
 \r
 function ser32 (integer: number): Uint8Array {\r
-       const bits = integer.toString(2)\r
+       if (typeof integer !== 'number') {\r
+               throw new TypeError(`Expected a number, received ${typeof integer}`)\r
+       }\r
+       const bits = dec.toBin(integer)\r
        if (bits.length > 32) {\r
-               throw new RangeError(`Expected 32-bit integer, received ${bits.length} bits: ${bits}`)\r
+               throw new RangeError(`Expected 32-bit integer, received ${bits.length}-bit value: ${integer}`)\r
        }\r
-       const bytes = dec.toBytes(integer)\r
-       const result = new Uint8Array(4)\r
-       result.set(bytes, 4 - bytes.length)\r
-       return result\r
+       return dec.toBytes(integer, 4)\r
 }\r
 \r
 function ser256 (integer: string): Uint8Array {\r
+       if (typeof integer !== 'string') {\r
+               throw new TypeError(`Expected string, received ${typeof integer}`)\r
+       }\r
        const bits = hex.toBin(integer)\r
        if (bits.length > 256) {\r
-               throw new RangeError(`Expected 256-bit integer, received ${bits.length} bits: ${bits}`)\r
+               throw new RangeError(`Expected 256-bit integer, received ${bits.length}-bit value: ${integer}`)\r
        }\r
-       const bytes = hex.toBytes(integer)\r
-       const result = new Uint8Array(32)\r
-       result.set(bytes, 32 - bytes.length)\r
-       return result\r
+       return hex.toBytes(integer, 32)\r
 }\r
 \r
 async function hmac (key: Uint8Array, data: Uint8Array): Promise<string> {\r
index cebab59d2e6870d189ba7314e45a133fdbe3417c..8654756877403ba8931cf492a198465dec1d3144 100644 (file)
@@ -4,10 +4,10 @@
 import { buffer, hex, utf8 } from './convert.js'
 import { Entropy } from './entropy.js'
 const { subtle } = globalThis.crypto
-const storage = globalThis.sessionStorage
 const ERR_MSG = 'Failed to store item in Safe'
 
 export class Safe {
+       #storage = globalThis.sessionStorage
 
        /**
        * Encrypts data with a password and stores it in the Safe.
@@ -18,7 +18,7 @@ export class Safe {
        */
        async put (name: string, key: CryptoKey, data: any): Promise<boolean>
        async put (name: string, passkey: string | CryptoKey, data: any): Promise<boolean> {
-               if (storage.getItem(name)) {
+               if (this.#storage.getItem(name)) {
                        throw new Error(ERR_MSG)
                }
                return this.overwrite(name, passkey as string, data)
@@ -60,7 +60,7 @@ export class Safe {
                        }
                        await new Promise<void>((resolve, reject) => {
                                try {
-                                       storage.setItem(name, JSON.stringify(record))
+                                       this.#storage.setItem(name, JSON.stringify(record))
                                        resolve()
                                } catch (err) {
                                        reject(err)
@@ -70,7 +70,7 @@ export class Safe {
                } catch (err) {
                        throw new Error(ERR_MSG)
                }
-               return (storage.getItem(name) != null)
+               return (this.#storage.getItem(name) != null)
        }
 
        /**
@@ -87,7 +87,7 @@ export class Safe {
                }
 
                const item = await new Promise<string | null>(resolve => {
-                       resolve(storage.getItem(name))
+                       resolve(this.#storage.getItem(name))
                })
                if (item == null) {
                        return null
@@ -110,7 +110,7 @@ export class Safe {
                        const decoded = buffer.toUtf8(decrypted)
                        const data = JSON.parse(decoded)
                        passkey = ''
-                       storage.removeItem(name)
+                       this.#storage.removeItem(name)
                        return data
                } catch (err) {
                        return null
index 20065ad219e009cc9bd9d6b5c0a9433c9022ca23..d5c24728c52137571f83143f864e049d118c6732 100644 (file)
@@ -88,18 +88,6 @@ export async function hash (data: string | string[]): Promise<string> {
        return bytes.toHex(hash)
 }
 
-
-/**
-* Checks the endianness of the current machine.
-*
-* @returns {Promise<boolean>} True if little-endian, else false
-*/
-export async function littleEndian () {
-       const buffer = new ArrayBuffer(2)
-       new DataView(buffer).setUint16(0, 256, true)
-       return new Uint16Array(buffer)[0] === 256
-}
-
 /**
 * Signs arbitrary strings with a private key using the Ed25519 signature scheme.
 *
@@ -152,7 +140,7 @@ export async function sweep (rpc: Rpc | string | URL, wallet: Blake2bWallet | Bi
                                account.representative.address,
                                account.frontier
                        )
-                       blockQueue.push(new Promise(async resolve => {
+                       const blockRequest = new Promise(async (resolve) => {
                                try {
                                        await block.pow(rpc)
                                        await block.sign(account.index)
@@ -163,7 +151,8 @@ export async function sweep (rpc: Rpc | string | URL, wallet: Blake2bWallet | Bi
                                } finally {
                                        resolve(null)
                                }
-                       }))
+                       })
+                       blockQueue.push(blockRequest)
                }
        }
        await Promise.allSettled(blockQueue)
@@ -187,4 +176,4 @@ export async function verify (key: string, signature: string, ...input: string[]
                hex.toBytes(signature))
 }
 
-export default { blake2b, convert, hash, littleEndian, sign, sweep, verify }
+export default { blake2b, convert, hash, sign, sweep, verify }
index 3608804b155f2b8df326918201d15dc9b01ccee8..b180edbba3e9929db4a07b385b788b54243bb0f2 100644 (file)
@@ -1,15 +1,15 @@
 // SPDX-FileCopyrightText: 2024 Chris Duncan <chris@zoso.dev>\r
 // SPDX-License-Identifier: GPL-3.0-or-later\r
 \r
+import blake2b from 'blake2b-wasm'\r
 import { Account } from './account.js'\r
-import { Bip39Mnemonic } from './bip39-mnemonic.js'\r
 import { nanoCKD } from './bip32-key-derivation.js'\r
+import { Bip39Mnemonic } from './bip39-mnemonic.js'\r
 import { ADDRESS_GAP, SEED_LENGTH_BIP44, SEED_LENGTH_BLAKE2B } from './constants.js'\r
-import { bytes, dec } from './convert.js'\r
+import { dec, hex } from './convert.js'\r
 import { Entropy } from './entropy.js'\r
 import { Rpc } from './rpc.js'\r
 import { Safe } from './safe.js'\r
-import Tools from './tools.js'\r
 import type { Ledger } from './ledger.js'\r
 \r
 /**\r
@@ -42,7 +42,7 @@ abstract class Wallet {
                return ''\r
        }\r
 \r
-       abstract ckd (index: number): Promise<Account | null>\r
+       abstract ckd (index: number): Promise<Account>\r
 \r
        constructor (seed?: string, mnemonic?: Bip39Mnemonic, id?: string) {\r
                if (this.constructor === Wallet) {\r
@@ -69,19 +69,12 @@ abstract class Wallet {
                        from = to\r
                        to = swap\r
                }\r
-               const accounts: Account[] = []\r
                for (let i = from; i <= to; i++) {\r
-                       if (this.#accounts[i]) {\r
-                               accounts.push(this.#accounts[i])\r
-                       } else {\r
-                               const account = await this.ckd(i)\r
-                               if (account != null) {\r
-                                       this.#accounts[i] = account\r
-                                       accounts.push(account)\r
-                               }\r
+                       if (this.#accounts[i] == null) {\r
+                               this.#accounts[i] = await this.ckd(i)\r
                        }\r
                }\r
-               return accounts\r
+               return this.#accounts.slice(from, to + 1)\r
        }\r
 \r
        /**\r
@@ -398,7 +391,10 @@ export class Bip44Wallet extends Wallet {
        */\r
        async ckd (index: number): Promise<Account> {\r
                const key = await nanoCKD(this.seed, index)\r
-               return await Account.fromPrivateKey(key, index)\r
+               if (typeof key !== 'string') {\r
+                       throw new TypeError('BIP-44 child key derivation returned invalid data')\r
+               }\r
+               return Account.fromPrivateKey(key, index)\r
        }\r
 }\r
 \r
@@ -539,13 +535,12 @@ export class Blake2bWallet extends Wallet {
        * @returns {Promise<Account>}\r
        */\r
        async ckd (index: number): Promise<Account> {\r
-               const indexBytes = dec.toBytes(index, 4)\r
-               if (await Tools.littleEndian()) {\r
-                       indexBytes.reverse()\r
+               const input = `${this.seed}${dec.toHex(index, 8)}`\r
+               const key = blake2b().update(hex.toBytes(input)).digest('hex')\r
+               if (typeof key !== 'string') {\r
+                       throw new TypeError('BLAKE2b child key derivation returned invalid data')\r
                }\r
-               const hash = await Tools.blake2b([this.seed, bytes.toHex(indexBytes)])\r
-               const key = bytes.toHex(hash)\r
-               return await Account.fromPrivateKey(key, index)\r
+               return Account.fromPrivateKey(key, index)\r
        }\r
 }\r
 \r
@@ -610,12 +605,12 @@ export class LedgerWallet extends Wallet {
        * @param {number} index - Index of the account\r
        * @returns {Promise<Account>}\r
        */\r
-       async ckd (index: number): Promise<Account | null> {\r
+       async ckd (index: number): Promise<Account> {\r
                const { status, publicKey } = await this.ledger.account(index)\r
                if (status === 'OK' && publicKey != null) {\r
                        return await Account.fromPublicKey(publicKey, index)\r
                }\r
-               return null\r
+               throw new Error(`Error getting Ledger account: ${status}`)\r
        }\r
 \r
        /**\r
@@ -627,10 +622,10 @@ export class LedgerWallet extends Wallet {
        * @returns True if successfully locked\r
        */\r
        async lock (): Promise<boolean> {\r
-               if (this.#ledger == null) {\r
+               if (this.ledger == null) {\r
                        return false\r
                }\r
-               const result = await this.#ledger.close()\r
+               const result = await this.ledger.close()\r
                return result === 'OK'\r
        }\r
 \r
@@ -643,10 +638,10 @@ export class LedgerWallet extends Wallet {
        * @returns True if successfully unlocked\r
        */\r
        async unlock (): Promise<boolean> {\r
-               if (this.#ledger == null) {\r
+               if (this.ledger == null) {\r
                        return false\r
                }\r
-               const result = await this.#ledger.connect()\r
+               const result = await this.ledger.connect()\r
                return result === 'OK'\r
        }\r
 }\r
diff --git a/test/GLOBALS.mjs b/test/GLOBALS.mjs
new file mode 100644 (file)
index 0000000..fa33be3
--- /dev/null
@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: 2024 Chris Duncan <chris@zoso.dev>
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+if (globalThis.sessionStorage == null) {
+       const _sessionStorage = {}
+       Object.defineProperty(globalThis, 'sessionStorage', {
+               value: {
+                       length: Object.entries(_sessionStorage).length,
+                       setItem: (key, value) => _sessionStorage[key] = value,
+                       getItem: (key) => _sessionStorage[key],
+                       removeItem: (key) => delete _sessionStorage[key],
+                       clear: () => _sessionStorage = {}
+               },
+               configurable: true,
+               enumerable: true
+       })
+}
index 5af1d33170b01c8e6a81dcbdda375dd9b5684bc2..32bfdf965508ab396c5a396f52154d4989dd7690 100644 (file)
@@ -1,23 +1,6 @@
 // SPDX-FileCopyrightText: 2024 Chris Duncan <chris@zoso.dev>
 // SPDX-License-Identifier: GPL-3.0-or-later
 
-export const STORAGE = (() => {
-       if (globalThis.sessionStorage == null) {
-               const _sessionStorage = {}
-               Object.defineProperty(globalThis, 'sessionStorage', {
-                       value: {
-                               length: Object.entries(_sessionStorage).length,
-                               setItem: (key, value) => _sessionStorage[key] = value,
-                               getItem: (key) => _sessionStorage[key],
-                               removeItem: (key) => delete _sessionStorage[key],
-                               clear: () => _sessionStorage = {}
-                       },
-                       configurable: true,
-                       enumerable: true
-               })
-       }
-})()
-
 export const GENESIS_ADDRESS = 'nano_3t6k35gi95xu6tergt6p69ck76ogmitsa8mnijtpxm9fkcm736xtoncuohr3'
 export const RAW_MAX = '340282366920938463463374607431768211455'
 export const SUPPLY_MAX = '133248297920938463463374607431768211455'
@@ -40,6 +23,10 @@ export const NANO_TEST_VECTORS = Object.freeze({
        ADDRESS_2: 'nano_3b5fnnerfrkt4me4wepqeqggwtfsxu8fai4n473iu6gxprfq4xd8pk9gh1dg'
 })
 
+/**
+* Source: https://github.com/trezor/python-mnemonic/blob/master/vectors.json
+* BLAKE2b keys calculated with Nano KeyTools: https://tools.nanos.cc/?tool=seed
+*/
 export const TREZOR_TEST_VECTORS = Object.freeze({
        PASSWORD: 'TREZOR',
 
@@ -47,33 +34,36 @@ export const TREZOR_TEST_VECTORS = Object.freeze({
        MNEMONIC_0: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
        SEED_0: "bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8",
        BIP32_KEY_0: "xprv9s21ZrQH143K32qBagUJAMU2LsHg3ka7jqMcV98Y7gVeVyNStwYS3U7yVVoDZ4btbRNf4h6ibWpY22iRmXq35qgLs79f312g2kj5539ebPM",
-       NANOS_CC_PRIVATE_0: "9F0E444C69F77A49BD0BE89DB92C38FE713E0963165CCA12FAF5712D7657120F",
-       NANOS_CC_PUBLIC_0: "C008B814A7D269A1FA3C6528B19201A24D797912DB9996FF02A1FF356E45552B",
-       NANOS_CC_ADDRESS_0: "nano_3i1aq1cchnmbn9x5rsbap8b15akfh7wj7pwskuzi7ahz8oq6cobd99d4r3b7",
+       BLAKE2B_PRIVATE_0: "9F0E444C69F77A49BD0BE89DB92C38FE713E0963165CCA12FAF5712D7657120F",
+       BLAKE2B_PUBLIC_0: "C008B814A7D269A1FA3C6528B19201A24D797912DB9996FF02A1FF356E45552B",
+       BLAKE2B_ADDRESS_0: "nano_3i1aq1cchnmbn9x5rsbap8b15akfh7wj7pwskuzi7ahz8oq6cobd99d4r3b7",
 
        ENTROPY_1: "7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F",
        MNEMONIC_1: "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title",
        SEED_1: "bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146ad717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87",
        BIP32_KEY_1: "xprv9s21ZrQH143K3Y1sd2XVu9wtqxJRvybCfAetjUrMMco6r3v9qZTBeXiBZkS8JxWbcGJZyio8TrZtm6pkbzG8SYt1sxwNLh3Wx7to5pgiVFU",
-       NANOS_CC_PRIVATE_1: "C54F9F69B088B554FF494D4CE7D23EB1B13E89D338F219F83BC91F415C3F7F2D",
-       NANOS_CC_PUBLIC_1: "1573BD1B96ECF80571BF544854026E6A967F065028FBC514B548471DC60B3229",
-       NANOS_CC_ADDRESS_1: "nano_17dmqnfsfu9r1oruyo4aci38wtnphw571c9urncdck495q51pejbp3c648yo",
+       BLAKE2B_1_PRIVATE_0: "C54F9F69B088B554FF494D4CE7D23EB1B13E89D338F219F83BC91F415C3F7F2D",
+       BLAKE2B_1_PUBLIC_0: "1573BD1B96ECF80571BF544854026E6A967F065028FBC514B548471DC60B3229",
+       BLAKE2B_1_ADDRESS_0: "nano_17dmqnfsfu9r1oruyo4aci38wtnphw571c9urncdck495q51pejbp3c648yo",
+       BLAKE2B_1_PRIVATE_1: "1B704560A0A04EAFD81E8D13481370DA458E2BB00C57F3AA00120D80F6A2BB6F",
+       BLAKE2B_1_PUBLIC_1: "353288BD57F98A2FC940B4D5A5CE9194EF1598611B00C629E96189320AC7409F",
+       BLAKE2B_1_ADDRESS_1: "nano_1fbkj4yohyec7z6n3f8onq9b579h4pe848r1rrnykreb8a7egi6z14nozo43",
 
        ENTROPY_2: "8080808080808080808080808080808080808080808080808080808080808080",
        MNEMONIC_2: "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless",
        SEED_2: "c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61af0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f",
        BIP32_KEY_2: "xprv9s21ZrQH143K3CSnQNYC3MqAAqHwxeTLhDbhF43A4ss4ciWNmCY9zQGvAKUSqVUf2vPHBTSE1rB2pg4avopqSiLVzXEU8KziNnVPauTqLRo",
-       NANOS_CC_PRIVATE_2: "554BE953D1E2DAAD0F8CBC2002967FC158E57032A6C4FD107FFEB2ACA518B613",
-       NANOS_CC_PUBLIC_2: "D85DECD78A303A18CC0D7B65FB384B9C49A7E2EF3666250CBD4F6EC4791513F8",
-       NANOS_CC_ADDRESS_2: "nano_3p4xxmdrne3t5581tyu7zew6q94bnzjgyfm86n8dtmugrjwjc6zrrci4g1rc",
+       BLAKE2B_2_PRIVATE_0: "554BE953D1E2DAAD0F8CBC2002967FC158E57032A6C4FD107FFEB2ACA518B613",
+       BLAKE2B_2_PUBLIC_0: "D85DECD78A303A18CC0D7B65FB384B9C49A7E2EF3666250CBD4F6EC4791513F8",
+       BLAKE2B_2_ADDRESS_0: "nano_3p4xxmdrne3t5581tyu7zew6q94bnzjgyfm86n8dtmugrjwjc6zrrci4g1rc",
 
        ENTROPY_3: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
        MNEMONIC_3: "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote",
        SEED_3: "dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad",
        BIP32_KEY_3: "xprv9s21ZrQH143K2WFF16X85T2QCpndrGwx6GueB72Zf3AHwHJaknRXNF37ZmDrtHrrLSHvbuRejXcnYxoZKvRquTPyp2JiNG3XcjQyzSEgqCB",
-       NANOS_CC_PRIVATE_3: "F1FD8CBD15A54FABDED17C65C4DD44E1F93AAD122FCC1840B1EDEFAAA5BA2B22",
-       NANOS_CC_PUBLIC_3: "6DDE6DDEDE04254B9BC75D04017D4F4406AC7A5F7374550C1EECC8594BFB1E70",
-       NANOS_CC_ADDRESS_3: "nano_1ugyfqhfw337bgfwgqa617ynyj18ojx7ywuncn83xu8ad77zp9mip188iakf"
+       BLAKE2B_3_PRIVATE_0: "F1FD8CBD15A54FABDED17C65C4DD44E1F93AAD122FCC1840B1EDEFAAA5BA2B22",
+       BLAKE2B_3_PUBLIC_0: "6DDE6DDEDE04254B9BC75D04017D4F4406AC7A5F7374550C1EECC8594BFB1E70",
+       BLAKE2B_3_ADDRESS_0: "nano_1ugyfqhfw337bgfwgqa617ynyj18ojx7ywuncn83xu8ad77zp9mip188iakf"
 })
 
 export const BIP32_TEST_VECTORS = Object.freeze({
index 583e5dfe161197645e973050d8a3134e64772368..4e63dc3efa91e3508777946c97aba18ef2c93958 100644 (file)
@@ -3,99 +3,53 @@
 \r
 'use strict'\r
 \r
+import './GLOBALS.mjs'\r
 import { describe, it } from 'node:test'\r
 import { strict as assert } from 'assert'\r
-import { NANO_TEST_VECTORS, STORAGE, TREZOR_TEST_VECTORS } from './TEST_VECTORS.js'\r
-import { Account, Bip44Wallet, Blake2bWallet, LedgerWallet } from '../dist/main.js'\r
+import { NANO_TEST_VECTORS } from './TEST_VECTORS.js'\r
+import { Bip44Wallet, Blake2bWallet, LedgerWallet } from '../dist/main.js'\r
 \r
 // WARNING: Do not send any funds to the test vectors below\r
 // Test vectors from https://docs.nano.org/integration-guides/key-management/ and elsewhere\r
-describe('generate wallet test', async () => {\r
-       it('should fail to create a wallet when using new', () => {\r
-               assert.throws(() => new Bip44Wallet())\r
-               assert.throws(() => new Blake2bWallet())\r
-               assert.throws(() => new LedgerWallet())\r
-       })\r
+describe('creating a new wallet', async () => {\r
 \r
-       it('should fail to create a software wallet without a password', async () => {\r
-               await assert.rejects(Bip44Wallet.create())\r
-               await assert.rejects(Blake2bWallet.create())\r
-       })\r
-\r
-       it('should replace invalid salt with empty string', async () => {\r
-               const invalidArgs = [null, true, false, 0, 1, 2, { "foo": "bar" }]\r
-               for (const arg of invalidArgs) {\r
-                       await assert.doesNotReject(Bip44Wallet.create(NANO_TEST_VECTORS.PASSWORD, arg), `Rejected ${arg}`)\r
-               }\r
-       })\r
-\r
-       it('should generate a BIP-44 wallet with random entropy', async () => {\r
+       it('BIP-44 wallet with random entropy', async () => {\r
                const wallet = await Bip44Wallet.create(NANO_TEST_VECTORS.PASSWORD)\r
                await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
-               const accounts = await wallet.accounts()\r
 \r
                assert.ok('id' in wallet)\r
                assert.ok('mnemonic' in wallet)\r
                assert.ok('seed' in wallet)\r
-               assert.ok(accounts[0] instanceof Account)\r
        })\r
 \r
-       it('should generate a BLAKE2b wallet with random entropy', async () => {\r
+       it('BLAKE2b wallet with random entropy', async () => {\r
                const wallet = await Blake2bWallet.create(NANO_TEST_VECTORS.PASSWORD)\r
                await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
-               const accounts = await wallet.accounts()\r
 \r
                assert.ok('id' in wallet)\r
                assert.ok('mnemonic' in wallet)\r
                assert.ok('seed' in wallet)\r
-               assert.ok(accounts[0] instanceof Account)\r
        })\r
 \r
-       it('should generate the correct wallet with the given test vector', async () => {\r
-               const wallet = await Bip44Wallet.fromEntropy(NANO_TEST_VECTORS.PASSWORD, '6CAF5A42BB8074314AAE20295975ECE663BE7AAD945A73613D193B0CC41C7970')\r
-               await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
-               const accounts = await wallet.accounts()\r
-\r
-               assert.ok('mnemonic' in wallet)\r
-               assert.ok('seed' in wallet)\r
-               assert.ok(accounts[0] instanceof Account)\r
-               assert.equal(wallet.mnemonic, 'hole kiss mouse jacket also board click series citizen slight kite smoke desk diary rent mercy inflict antique edge invite slush athlete total brain')\r
-               assert.equal(wallet.seed, '1ACCDD4C25E06E47310D0C62C290EC166071D024352E003E5366E8BA6BA523F2A0CB34116AC55A238A886778880A9B2A547112FD7CFFADE81D8D8D084CCB7D36')\r
-               assert.equal(accounts[0].privateKey, 'EB18B748BCC48F824CF8A1FE92F7FC93BFC6F2A1EB9C1D40FA26D335D8A0C30F')\r
-               assert.equal(accounts[0].publicKey, 'A9EF7BBC004813CF75C5FC5C582066182D5C9CFFD42EB7EB81CEFEA8E78C47C5')\r
-               assert.equal(accounts[0].address, 'nano_3chhhgy11k1msxtwdz4wd1i8e83fdkghzo3gpzor5mqyo5mrrjy79zpw1g34')\r
+       it('BIP-44 replace invalid salt with empty string', async () => {\r
+               const invalidArgs = [null, true, false, 0, 1, 2, { "foo": "bar" }]\r
+               for (const arg of invalidArgs) {\r
+                       await assert.doesNotReject(Bip44Wallet.create(NANO_TEST_VECTORS.PASSWORD, arg), `Rejected ${arg}`)\r
+               }\r
        })\r
 \r
-       it('should generate the correct wallet with the given test vector and a seed password', async () => {\r
-               const wallet = await Bip44Wallet.fromEntropy(NANO_TEST_VECTORS.PASSWORD, '6CAF5A42BB8074314AAE20295975ECE663BE7AAD945A73613D193B0CC41C7970', NANO_TEST_VECTORS.PASSWORD)\r
-               await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
-               const accounts = await wallet.accounts()\r
-\r
-               assert.ok('mnemonic' in wallet)\r
-               assert.ok('seed' in wallet)\r
-               assert.ok(accounts[0] instanceof Account)\r
-\r
-               assert.equal(wallet.mnemonic, 'hole kiss mouse jacket also board click series citizen slight kite smoke desk diary rent mercy inflict antique edge invite slush athlete total brain')\r
-               assert.notEqual(wallet.seed, '1ACCDD4C25E06E47310D0C62C290EC166071D024352E003E5366E8BA6BA523F2A0CB34116AC55A238A886778880A9B2A547112FD7CFFADE81D8D8D084CCB7D36')\r
-               assert.notEqual(accounts[0].privateKey, 'EB18B748BCC48F824CF8A1FE92F7FC93BFC6F2A1EB9C1D40FA26D335D8A0C30F')\r
-               assert.notEqual(accounts[0].publicKey, 'A9EF7BBC004813CF75C5FC5C582066182D5C9CFFD42EB7EB81CEFEA8E78C47C5')\r
-               assert.notEqual(accounts[0].address, 'nano_3chhhgy11k1msxtwdz4wd1i8e83fdkghzo3gpzor5mqyo5mrrjy79zpw1g34')\r
-\r
-               assert.equal(wallet.seed, '146E3E2A0530848C9174D45ECEC8C3F74A7BE3F1EE832F92EB6227284121EB2E48A6B8FC469403984CD5E8F0D1ED05777C78F458D0E98C911841590E5D645DC3')\r
-               assert.equal(accounts[0].privateKey, '2D5851BD5A89B8C943078BE6AD5BBEE8AEAB77D6A4744C20D1B87D78E3286B93')\r
-               assert.equal(accounts[0].publicKey, '923B6C7E281C1C5529FD2DC848117781216A1753CFD487FC34009F3591E636D7')\r
-               assert.equal(accounts[0].address, 'nano_36jufjz4i91wcnnztdgab1aqh1b3fado9mynizy5a16z8payefpqo81zsshc')\r
+       it('fail when using new', async () => {\r
+               assert.throws(() => new Bip44Wallet())\r
+               assert.throws(() => new Blake2bWallet())\r
+               assert.throws(() => new LedgerWallet())\r
        })\r
 \r
-       it('should throw when given invalid entropy with an invalid length', async () => {\r
-               assert.rejects(async () => await Bip44Wallet.fromEntropy(NANO_TEST_VECTORS.PASSWORD, '6CAF5A42BB8074314AAE20295975ECE663BE7AAD945A73613D193B0CC41C797'))\r
-               assert.rejects(async () => await Bip44Wallet.fromEntropy(NANO_TEST_VECTORS.PASSWORD, '6CAF5A42BB8074314AAE20295975ECE663BE7AAD945A73613D193B0CC41C79701'))\r
-               assert.rejects(async () => await Bip44Wallet.fromEntropy(NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.ENTROPY_0.replaceAll(/./g, 'x')))\r
+       it('fail without a password', async () => {\r
+               await assert.rejects(Bip44Wallet.create())\r
+               await assert.rejects(Blake2bWallet.create())\r
        })\r
-})\r
 \r
-describe('ledger wallet', { skip: true }, async () => {\r
-       it('should connect to ledger', async () => {\r
+       it('connect to ledger', { skip: true }, async () => {\r
                const wallet = await LedgerWallet.create()\r
                assert.ok(wallet)\r
        })\r
index 81153acf1ab344aa798e8868bfcc55868d0eaf85..d35f5461de42be0e4a85ffe7443b804248620ae3 100644 (file)
@@ -3,9 +3,10 @@
 \r
 'use strict'\r
 \r
+import './GLOBALS.mjs'\r
 import { describe, it } from 'node:test'\r
 import { strict as assert } from 'assert'\r
-import { NANO_TEST_VECTORS, STORAGE } from './TEST_VECTORS.js'\r
+import { NANO_TEST_VECTORS } from './TEST_VECTORS.js'\r
 import { Bip44Wallet, Blake2bWallet, LedgerWallet } from '../dist/main.js'\r
 \r
 // WARNING: Do not send any funds to the test vectors below\r
@@ -86,3 +87,23 @@ describe('Ledger device accounts', { skip: true }, async () => {
                assert.ok(accounts[0].address)\r
        })\r
 })\r
+\r
+describe('child key derivation performance', { skip: true }, async () => {\r
+       it('performance test of BIP-44 ckd', async function () {\r
+               const wallet = await Bip44Wallet.create(NANO_TEST_VECTORS.PASSWORD)\r
+               await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+\r
+               const accounts = await wallet.accounts(0, 0x7fff)\r
+\r
+               assert.equal(accounts.length, 0x8000)\r
+       })\r
+\r
+       it('performance test of BLAKE2b ckd', async () => {\r
+               const wallet = await Blake2bWallet.create(NANO_TEST_VECTORS.PASSWORD)\r
+               await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+\r
+               const accounts = await wallet.accounts(0, 0x7fff)\r
+\r
+               assert.equal(accounts.length, 0x8000)\r
+       })\r
+})\r
index 960c70f480cabbd98ed2b3d139d7aee61e229bf1..30bc54276c8a6fbcce8c5fee6b9ae4c9370a173e 100644 (file)
@@ -3,9 +3,10 @@
 \r
 'use strict'\r
 \r
+import './GLOBALS.mjs'\r
 import { describe, it } from 'node:test'\r
 import { strict as assert } from 'assert'\r
-import { BIP32_TEST_VECTORS, CUSTOM_TEST_VECTORS, NANO_TEST_VECTORS, STORAGE, TREZOR_TEST_VECTORS } from './TEST_VECTORS.js'\r
+import { BIP32_TEST_VECTORS, CUSTOM_TEST_VECTORS, NANO_TEST_VECTORS, TREZOR_TEST_VECTORS } from './TEST_VECTORS.js'\r
 import { Account, Bip44Wallet, Blake2bWallet } from '../dist/main.js'\r
 \r
 // WARNING: Do not send any funds to the test vectors below\r
@@ -138,42 +139,72 @@ describe('import wallet with test vectors test', () => {
        })\r
 \r
        it('should successfully import a BLAKE2b wallet with Trezor test vectors', async () => {\r
-               const wallet = await Blake2bWallet.fromMnemonic(TREZOR_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.MNEMONIC_1)\r
-               await wallet.unlock(TREZOR_TEST_VECTORS.PASSWORD)\r
-               const accounts = await wallet.accounts()\r
+               const wallet = await Blake2bWallet.fromMnemonic(NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.MNEMONIC_1)\r
+               await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+               const accounts = await wallet.accounts(0, 1)\r
 \r
                assert.ok('mnemonic' in wallet)\r
                assert.ok('seed' in wallet)\r
-               assert.ok(accounts[0] instanceof Account)\r
                assert.equal(wallet.mnemonic, TREZOR_TEST_VECTORS.MNEMONIC_1)\r
                assert.equal(wallet.seed, TREZOR_TEST_VECTORS.ENTROPY_1)\r
-               assert.equal(accounts[0].privateKey, TREZOR_TEST_VECTORS.NANOS_CC_PRIVATE_1)\r
-               assert.equal(accounts[0].publicKey, TREZOR_TEST_VECTORS.NANOS_CC_PUBLIC_1)\r
-               assert.equal(accounts[0].address, TREZOR_TEST_VECTORS.NANOS_CC_ADDRESS_1)\r
+               assert.ok(accounts[0] instanceof Account)\r
+               assert.equal(accounts[0].index, 0)\r
+               assert.equal(accounts[0].privateKey, TREZOR_TEST_VECTORS.BLAKE2B_1_PRIVATE_0)\r
+               assert.equal(accounts[0].publicKey, TREZOR_TEST_VECTORS.BLAKE2B_1_PUBLIC_0)\r
+               assert.equal(accounts[0].address, TREZOR_TEST_VECTORS.BLAKE2B_1_ADDRESS_0)\r
+               assert.ok(accounts[1] instanceof Account)\r
+               assert.equal(accounts[1].index, 1)\r
+               assert.equal(accounts[1].privateKey, TREZOR_TEST_VECTORS.BLAKE2B_1_PRIVATE_1)\r
+               assert.equal(accounts[1].publicKey, TREZOR_TEST_VECTORS.BLAKE2B_1_PUBLIC_1)\r
+               assert.equal(accounts[1].address, TREZOR_TEST_VECTORS.BLAKE2B_1_ADDRESS_1)\r
        })\r
 \r
        it('should get identical BLAKE2b wallets when created with a seed versus with its derived mnemonic', async () => {\r
-               const wallet = await Blake2bWallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.ENTROPY_0)\r
+               const wallet = await Blake2bWallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.ENTROPY_2)\r
                await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
                const walletAccounts = await wallet.accounts()\r
+               const walletAccount = walletAccounts[0]\r
 \r
                assert.ok('mnemonic' in wallet)\r
                assert.ok('seed' in wallet)\r
-               assert.ok(walletAccounts[0])\r
-               assert.equal(wallet.mnemonic, TREZOR_TEST_VECTORS.MNEMONIC_0)\r
+               assert.ok(walletAccount)\r
+               assert.equal(wallet.mnemonic, TREZOR_TEST_VECTORS.MNEMONIC_2)\r
 \r
-               const imported = await Blake2bWallet.fromMnemonic(NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.MNEMONIC_0)\r
-               await imported.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+               const imported = await Blake2bWallet.fromMnemonic(TREZOR_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.MNEMONIC_2)\r
+               await imported.unlock(TREZOR_TEST_VECTORS.PASSWORD)\r
                const importedAccounts = await imported.accounts()\r
+               const importedAccount = importedAccounts[0]\r
 \r
                assert.equal(imported.mnemonic, wallet.mnemonic)\r
                assert.equal(imported.seed, wallet.seed)\r
-               assert.equal(importedAccounts[0].privateKey, walletAccounts[0].privateKey)\r
-               assert.equal(importedAccounts[0].publicKey, walletAccounts[0].publicKey)\r
+               assert.equal(importedAccount.privateKey, walletAccount.privateKey)\r
+               assert.equal(importedAccount.publicKey, walletAccount.publicKey)\r
+       })\r
+\r
+       it('should get identical BLAKE2b wallets when created with max entropy value', async () => {\r
+               const wallet = await Blake2bWallet.fromMnemonic(NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.MNEMONIC_3)\r
+               await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+               const accounts = await wallet.accounts()\r
+\r
+               assert.ok('mnemonic' in wallet)\r
+               assert.ok('seed' in wallet)\r
+               assert.ok(accounts[0] instanceof Account)\r
+               assert.equal(wallet.mnemonic, TREZOR_TEST_VECTORS.MNEMONIC_3)\r
+               assert.equal(wallet.seed, TREZOR_TEST_VECTORS.ENTROPY_3)\r
+               assert.equal(accounts[0].index, 0)\r
+               assert.equal(accounts[0].privateKey, TREZOR_TEST_VECTORS.BLAKE2B_3_PRIVATE_0)\r
+               assert.equal(accounts[0].publicKey, TREZOR_TEST_VECTORS.BLAKE2B_3_PUBLIC_0)\r
+               assert.equal(accounts[0].address, TREZOR_TEST_VECTORS.BLAKE2B_3_ADDRESS_0)\r
        })\r
 })\r
 \r
 describe('invalid wallet', async () => {\r
+       it('throw when given invalid entropy', async () => {\r
+               assert.rejects(async () => await Bip44Wallet.fromEntropy(NANO_TEST_VECTORS.PASSWORD, '6CAF5A42BB8074314AAE20295975ECE663BE7AAD945A73613D193B0CC41C797'))\r
+               assert.rejects(async () => await Bip44Wallet.fromEntropy(NANO_TEST_VECTORS.PASSWORD, '6CAF5A42BB8074314AAE20295975ECE663BE7AAD945A73613D193B0CC41C79701'))\r
+               assert.rejects(async () => await Bip44Wallet.fromEntropy(NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.ENTROPY_0.replaceAll(/./g, 'x')))\r
+       })\r
+\r
        it('should throw when given a seed with an invalid length', async () => {\r
                await assert.rejects(Bip44Wallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED + 'f'),\r
                        { message: `Expected a ${NANO_TEST_VECTORS.BIP39_SEED.length}-character seed, but received ${NANO_TEST_VECTORS.BIP39_SEED.length + 1}-character string.` })\r
index d8c3dab24a53d31f680efea20b5c70e0bf6430a7..a67089fed3b6cbe090e0584e6587bcd84c4f3d48 100644 (file)
@@ -3,9 +3,10 @@
 \r
 'use strict'\r
 \r
+import './GLOBALS.mjs'\r
 import { describe, it } from 'node:test'\r
 import { strict as assert } from 'assert'\r
-import { NANO_TEST_VECTORS, STORAGE, TREZOR_TEST_VECTORS } from './TEST_VECTORS.js'\r
+import { NANO_TEST_VECTORS, TREZOR_TEST_VECTORS } from './TEST_VECTORS.js'\r
 import { Bip44Wallet, Blake2bWallet } from '../dist/main.js'\r
 \r
 const skip = false\r
index bcaff1a867168f485f7cfe0fb5e3f4d574d179a3..0d179d7b2b35bff94e16c5cdcb54889cfe5fc6e6 100644 (file)
@@ -3,6 +3,7 @@
 
 'use strict'
 
+import './GLOBALS.mjs'
 import { describe, it } from 'node:test'
 import { strict as assert } from 'assert'
 import { Rolodex, Tools } from '../dist/main.js'
index 3983e67516ad14d9a6c239239a26131d9de01b49..9dc71ccfc00d2dc5a84803e0f0c7e7fdac4a49b8 100644 (file)
@@ -3,9 +3,10 @@
 
 'use strict'
 
+import './GLOBALS.mjs'
 import { describe, it } from 'node:test'
 import { strict as assert } from 'assert'
-import { NANO_TEST_VECTORS, STORAGE } from './TEST_VECTORS.js'
+import { NANO_TEST_VECTORS } from './TEST_VECTORS.js'
 import { Account, Bip44Wallet, Rpc } from '../dist/main.js'
 
 // WARNING: Do not send any funds to the test vectors below
index a02a9c2c680b5f42aa6e591f3943e7a00bd8c915..06b179093d7c77047f2ebc586b754bdd53d1a2dd 100644 (file)
@@ -3,10 +3,11 @@
 \r
 'use strict'\r
 \r
+import './GLOBALS.mjs'\r
 import { describe, it } from 'node:test'\r
 import { strict as assert } from 'assert'\r
-import { SendBlock, ReceiveBlock, ChangeBlock } from '../dist/main.js'\r
 import { NANO_TEST_VECTORS } from './TEST_VECTORS.js'\r
+import { SendBlock, ReceiveBlock, ChangeBlock } from '../dist/main.js'\r
 \r
 // WARNING: Do not send any funds to the test vectors below\r
 // Test vectors from https://docs.nano.org/integration-guides/key-management/\r
index 9e7db5106728976037f2a5c1284a9b3477b6cf0a..4d98f9ce170efa6058995c698776bb707b0577b2 100644 (file)
@@ -3,11 +3,14 @@
 \r
 'use strict'\r
 \r
+import './GLOBALS.mjs'\r
 import { describe, it } from 'node:test'\r
 import { strict as assert } from 'assert'\r
-import { RAW_MAX, NANO_TEST_VECTORS, STORAGE } from './TEST_VECTORS.js'\r
+import { RAW_MAX, NANO_TEST_VECTORS } from './TEST_VECTORS.js'\r
 import { Bip44Wallet, Account, SendBlock, Rpc, Tools } from '../dist/main.js'\r
 \r
+const skip = true\r
+\r
 const wallet = await Bip44Wallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
 await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
 const rpc = new Rpc(process.env.NODE_URL, process.env.API_KEY_NAME, process.env.API_KEY_VALUE)\r
@@ -145,7 +148,7 @@ describe('sweeper', async () => {
                        { message: 'Missing required sweep arguments' })\r
        })\r
 \r
-       it('fails gracefully for ineligible accounts', async () => {\r
+       it('fails gracefully for ineligible accounts', { skip }, async () => {\r
                const results = await Tools.sweep(rpc, wallet, NANO_TEST_VECTORS.ADDRESS_1)\r
                assert.ok(results)\r
                assert.equal(results.length, 1)\r