"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",
"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": {
"@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",
}\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
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.
*/
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)
}
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)
} catch (err) {
throw new Error(ERR_MSG)
}
- return (storage.getItem(name) != null)
+ return (this.#storage.getItem(name) != null)
}
/**
}
const item = await new Promise<string | null>(resolve => {
- resolve(storage.getItem(name))
+ resolve(this.#storage.getItem(name))
})
if (item == null) {
return null
const decoded = buffer.toUtf8(decrypted)
const data = JSON.parse(decoded)
passkey = ''
- storage.removeItem(name)
+ this.#storage.removeItem(name)
return data
} catch (err) {
return null
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.
*
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)
} finally {
resolve(null)
}
- }))
+ })
+ blockQueue.push(blockRequest)
}
}
await Promise.allSettled(blockQueue)
hex.toBytes(signature))
}
-export default { blake2b, convert, hash, littleEndian, sign, sweep, verify }
+export default { blake2b, convert, hash, sign, sweep, verify }
// 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
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
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
*/\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
* @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
* @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
* @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
* @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
--- /dev/null
+// 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
+ })
+}
// 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'
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',
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({
\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
\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
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
\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
})\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
\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
'use strict'
+import './GLOBALS.mjs'
import { describe, it } from 'node:test'
import { strict as assert } from 'assert'
import { Rolodex, Tools } from '../dist/main.js'
'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
\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
\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
{ 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