async sign (input?: number | string, frontier?: ChangeBlock | ReceiveBlock | SendBlock): Promise<string> {
if (typeof input === 'number') {
const index = input
- const { LedgerWallet } = await import('./ledger')
- const ledger = await LedgerWallet.create()
+ const { Ledger } = await import('./ledger')
+ const ledger = await Ledger.create()
await ledger.connect()
if (frontier) {
try {
* private keys are held in the secure chip of the device. As such, the user\r
* is responsible for using Ledger technology to back up these pieces of data.\r
*/\r
-export class LedgerWallet extends Wallet {\r
+export class Ledger extends Wallet {\r
static #isInternal: boolean = false\r
static #derivationPath: Uint8Array = new Uint8Array([\r
LEDGER_ADPU_CODES.bip32DerivationLevel,\r
* Creates a new Ledger hardware wallet communication layer by dynamically\r
* importing the ledger.js service.\r
*\r
- * @returns {LedgerWallet} A wallet containing accounts and a Ledger device communication object\r
+ * @returns {Ledger} A wallet containing accounts and a Ledger device communication object\r
*/\r
- static async create (): Promise<LedgerWallet> {\r
+ static async create (): Promise<Ledger> {\r
try {\r
if (this.isUnsupported) throw new Error('Browser is unsupported')\r
- LedgerWallet.#isInternal = true\r
+ Ledger.#isInternal = true\r
const self = new this()\r
await Database.add({ [self.id]: { id: self.id, type: 'Ledger' } }, Wallet.DB_NAME)\r
return self\r
* Overrides `import()` from the base Wallet class since Ledger secrets cannot\r
* be extracted from the device.\r
*/\r
- static import (): Promise<LedgerWallet> {\r
- return LedgerWallet.create()\r
+ static import (): Promise<Ledger> {\r
+ return Ledger.create()\r
}\r
\r
#status: DeviceStatus = 'DISCONNECTED'\r
get status (): DeviceStatus { return this.#status }\r
\r
private constructor () {\r
- if (!LedgerWallet.#isInternal) {\r
+ if (!Ledger.#isInternal) {\r
throw new Error(`LedgerWallet cannot be instantiated directly. Use 'await LedgerWallet.create()' instead.`)\r
}\r
- LedgerWallet.#isInternal = false\r
+ Ledger.#isInternal = false\r
super('Ledger')\r
}\r
\r
if (version.status !== 'OK') {\r
this.#status = 'DISCONNECTED'\r
} else if (version.name === 'Nano') {\r
- const { status } = await LedgerWallet.#account()\r
+ const { status } = await Ledger.#account()\r
if (status === 'OK') {\r
this.#status = 'CONNECTED'\r
} else if (status === 'SECURITY_STATUS_NOT_SATISFIED') {\r
*/\r
async #open (): Promise<LedgerResponse> {\r
const name = new TextEncoder().encode('Nano')\r
- const transport = await LedgerWallet.DynamicTransport.create(LedgerWallet.openTimeout, LedgerWallet.listenTimeout)\r
+ const transport = await Ledger.DynamicTransport.create(Ledger.openTimeout, Ledger.listenTimeout)\r
const response = await transport\r
.send(0xe0, 0xd8, LEDGER_ADPU_CODES.paramUnused, LEDGER_ADPU_CODES.paramUnused, name as Buffer)\r
.then(res => bytes.toDec(res))\r
* @returns Status of command\r
*/\r
async #close (): Promise<LedgerResponse> {\r
- const transport = await LedgerWallet.DynamicTransport.create(LedgerWallet.openTimeout, LedgerWallet.listenTimeout)\r
+ const transport = await Ledger.DynamicTransport.create(Ledger.openTimeout, Ledger.listenTimeout)\r
const response = await transport\r
.send(0xb0, 0xa7, LEDGER_ADPU_CODES.paramUnused, LEDGER_ADPU_CODES.paramUnused)\r
.then(res => bytes.toDec(res))\r
throw new TypeError('Invalid account index')\r
}\r
const account = dec.toBytes(index + HARDENED_OFFSET, 4)\r
- const data = new Uint8Array([...LedgerWallet.#derivationPath, ...account])\r
+ const data = new Uint8Array([...Ledger.#derivationPath, ...account])\r
\r
- const transport = await LedgerWallet.DynamicTransport.create(LedgerWallet.openTimeout, LedgerWallet.listenTimeout)\r
+ const transport = await Ledger.DynamicTransport.create(Ledger.openTimeout, Ledger.listenTimeout)\r
const response = await transport\r
.send(LEDGER_ADPU_CODES.class, LEDGER_ADPU_CODES.account, show ? 1 : 0, LEDGER_ADPU_CODES.paramUnused, data as Buffer)\r
.catch(err => dec.toBytes(err.statusCode)) as Uint8Array\r
const signature = hex.toBytes(block.signature, 64)\r
const data = new Uint8Array([LEDGER_ADPU_CODES.bip32DerivationLevel, ...purpose, ...coin, ...account, ...previous, ...link, ...representative, ...balance, ...signature])\r
\r
- const transport = await LedgerWallet.DynamicTransport.create(LedgerWallet.openTimeout, LedgerWallet.listenTimeout)\r
+ const transport = await Ledger.DynamicTransport.create(Ledger.openTimeout, Ledger.listenTimeout)\r
const response = await transport\r
.send(LEDGER_ADPU_CODES.class, LEDGER_ADPU_CODES.cacheBlock, LEDGER_ADPU_CODES.paramUnused, LEDGER_ADPU_CODES.paramUnused, data as Buffer)\r
.then(res => bytes.toDec(res))\r
const link = hex.toBytes(block.link, 32)\r
const representative = hex.toBytes(block.representative.publicKey, 32)\r
const balance = hex.toBytes(BigInt(block.balance).toString(16), 16)\r
- const data = new Uint8Array([...LedgerWallet.#derivationPath, ...account, ...previous, ...link, ...representative, ...balance])\r
+ const data = new Uint8Array([...Ledger.#derivationPath, ...account, ...previous, ...link, ...representative, ...balance])\r
\r
- const transport = await LedgerWallet.DynamicTransport.create(LedgerWallet.openTimeout, LedgerWallet.listenTimeout)\r
+ const transport = await Ledger.DynamicTransport.create(Ledger.openTimeout, Ledger.listenTimeout)\r
const response = await transport.send(LEDGER_ADPU_CODES.class, LEDGER_ADPU_CODES.signBlock, LEDGER_ADPU_CODES.paramUnused, LEDGER_ADPU_CODES.paramUnused, data as Buffer)\r
.catch(err => dec.toBytes(err.statusCode)) as Uint8Array\r
await transport.close()\r
}\r
\r
const derivationAccount = dec.toBytes(index + HARDENED_OFFSET, 4)\r
- const data = new Uint8Array([...LedgerWallet.#derivationPath, ...derivationAccount, ...nonce])\r
+ const data = new Uint8Array([...Ledger.#derivationPath, ...derivationAccount, ...nonce])\r
\r
- const transport = await LedgerWallet.DynamicTransport.create(LedgerWallet.openTimeout, LedgerWallet.listenTimeout)\r
+ const transport = await Ledger.DynamicTransport.create(Ledger.openTimeout, Ledger.listenTimeout)\r
const response = await transport\r
.send(LEDGER_ADPU_CODES.class, LEDGER_ADPU_CODES.signNonce, LEDGER_ADPU_CODES.paramUnused, LEDGER_ADPU_CODES.paramUnused, data as Buffer)\r
.catch(err => dec.toBytes(err.statusCode)) as Uint8Array\r
* @returns Status, process name, and version\r
*/\r
async #version (): Promise<LedgerVersionResponse> {\r
- const transport = await LedgerWallet.DynamicTransport.create(LedgerWallet.openTimeout, LedgerWallet.listenTimeout)\r
+ const transport = await Ledger.DynamicTransport.create(Ledger.openTimeout, Ledger.listenTimeout)\r
const response = await transport\r
.send(0xb0, LEDGER_ADPU_CODES.version, LEDGER_ADPU_CODES.paramUnused, LEDGER_ADPU_CODES.paramUnused)\r
.catch(err => dec.toBytes(err.statusCode)) as Uint8Array\r
async ckd (indexes: number[]): Promise<KeyPair[]> {\r
const results: KeyPair[] = []\r
for (const index of indexes) {\r
- const { status, publicKey } = await LedgerWallet.#account(index)\r
+ const { status, publicKey } = await Ledger.#account(index)\r
if (status === 'OK' && publicKey != null) {\r
results.push({ publicKey, index })\r
} else {\r
import { SendBlock } from './block'
import { UNITS } from './constants'
import { bytes, hex } from './convert'
-import { LedgerWallet } from './ledger'
+import { Ledger } from './ledger'
import { NanoNaCl } from './nano-nacl'
import { Rpc } from './rpc'
import { Wallet } from './wallet'
* them all to a single recipient address. Hardware wallets are unsupported.
*
* @param {Rpc|string|URL} rpc - RPC node information required to refresh accounts, calculate PoW, and process blocks
-* @param {Blake2bWallet|Bip44Wallet|LedgerWallet} wallet - Wallet from which to sweep funds
+* @param {Blake2bWallet|Bip44Wallet|Ledger} wallet - Wallet from which to sweep funds
* @param {string} recipient - Destination address for all swept funds
* @param {number} from - Starting account index to sweep
* @param {number} to - Ending account index to sweep
*/
export async function sweep (
rpc: Rpc | string | URL,
- wallet: Wallet | LedgerWallet,
+ wallet: Wallet | Ledger,
recipient: string,
from: number = 0,
to: number = from
import { Account } from './lib/account'
import { Blake2b } from './lib/blake2b'
import { ChangeBlock, ReceiveBlock, SendBlock } from './lib/block'
+import { Ledger } from './lib/ledger'
import { Rolodex } from './lib/rolodex'
import { Rpc } from './lib/rpc'
import { Tools } from './lib/tools'
Account,
Blake2b,
ChangeBlock, ReceiveBlock, SendBlock,
+ Ledger,
Rolodex,
Rpc,
Tools,
* them all to a single recipient address. Hardware wallets are unsupported.
*
* @param {Rpc|string|URL} rpc - RPC node information required to refresh accounts, calculate PoW, and process blocks
-* @param {Wallet|LedgerWallet} wallet - Wallet from which to sweep funds
+* @param {Wallet|Ledger} wallet - Wallet from which to sweep funds
* @param {string} recipient - Destination address for all swept funds
* @param {number} from - Starting account index to sweep
* @param {number} to - Ending account index to sweep
* @returns An array of results including both successes and failures
*/
-export declare function sweep (rpc: Rpc | string | URL, wallet: Wallet | LedgerWallet, recipient: string, from?: number, to?: number): Promise<SweepResult[]>
+export declare function sweep (rpc: Rpc | string | URL, wallet: Wallet | Ledger, recipient: string, from?: number, to?: number): Promise<SweepResult[]>
/**
* Verifies the signature of arbitrary strings using a public key.
*
* private keys are held in the secure chip of the device. As such, the user
* is responsible for using Ledger technology to back up these pieces of data.
*/
-export declare class LedgerWallet extends Wallet {
+export declare class Ledger extends Wallet {
#private
static DynamicTransport: typeof TransportBLE | typeof TransportUSB | typeof TransportHID
static get listenTimeout (): 30000
* Creates a new Ledger hardware wallet communication layer by dynamically
* importing the ledger.js service.
*
- * @returns {LedgerWallet} A wallet containing accounts and a Ledger device communication object
+ * @returns {Ledger} A wallet containing accounts and a Ledger device communication object
*/
- static create (): Promise<LedgerWallet>
+ static create (): Promise<Ledger>
+ /**
+ * Overrides `import()` from the base Wallet class since Ledger secrets cannot
+ * be extracted from the device.
+ */
+ static import (): Promise<Ledger>
get status (): DeviceStatus
private constructor ()
/**
onConnectUsb: (e: USBConnectionEvent) => Promise<void>
onDisconnectUsb: (e: USBConnectionEvent) => Promise<void>
/**
- * Retrieves an existing Ledger wallet from storage using its ID.
- *
- * @param {string} id - Generated when the wallet was initially created
- * @returns {LedgerWallet} Restored LedgerWallet
- */
- static restore (id: string): Promise<LedgerWallet>
- /**
* Sign a block with the Ledger device.
*
* @param {number} index - Account number
*/
updateCache (index: number, hash: string, rpc: Rpc): Promise<LedgerResponse>
/**
+ * Checks whether a given seed matches the wallet seed. The wallet must be
+ * unlocked prior to verification.
+ *
+ * @param {string} seed - Hexadecimal seed to be matched against the wallet data
+ * @returns True if input matches wallet seed
+ */
+ verify (seed: string): Promise<boolean>
+ /**
+ * Checks whether a given mnemonic phrase matches the wallet mnemonic. If a
+ * personal salt was used when generating the mnemonic, it cannot be verified.
+ * The wallet must be unlocked prior to verification.
+ *
+ * @param {string} mnemonic - Phrase to be matched against the wallet data
+ * @returns True if input matches wallet mnemonic
+ */
+ verify (mnemonic: string): Promise<boolean>
+ /**
* Get the version of the current process. If a specific app is running, get
* the app version. Otherwise, get the Ledger BOLOS version instead.
*
suite('Block signing using official test vectors', async () => {\r
\r
await test('sign open block with wallet', async () => {\r
- const wallet = await Wallet.import('Test', 'BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
+ const wallet = await Wallet.import('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
await assert.resolves(wallet.unlock(NANO_TEST_VECTORS.PASSWORD))\r
\r
const block = new ReceiveBlock(\r
import { NANO_TEST_VECTORS } from './VECTORS.mjs'\r
\r
/**\r
-* @type {typeof import('../dist/types.d.ts').Bip44Wallet}\r
+* @type {typeof import('../dist/types.d.ts').Wallet}\r
*/\r
-let Bip44Wallet\r
-/**\r
-* @type {typeof import('../dist/types.d.ts').Blake2bWallet}\r
-*/\r
-let Blake2bWallet\r
+let Wallet\r
if (isNode) {\r
- ({ Bip44Wallet, Blake2bWallet } = await import('../dist/nodejs.min.js'))\r
+ ({ Wallet } = await import('../dist/nodejs.min.js'))\r
} else {\r
- ({ Bip44Wallet, Blake2bWallet } = await import('../dist/browser.min.js'))\r
+ ({ Wallet } = await import('../dist/browser.min.js'))\r
}\r
\r
await Promise.all([\r
suite('Derive accounts from BIP-44 wallet', async () => {\r
\r
await test('derive the first account from the given BIP-44 seed', async () => {\r
- const wallet = await Bip44Wallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
+ const wallet = await Wallet.import('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
const account = await wallet.account()\r
\r
})\r
\r
await test('derive low indexed accounts from the given BIP-44 seed', async () => {\r
- const wallet = await Bip44Wallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
+ const wallet = await Wallet.import('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
const accounts = await wallet.accounts(1, 2)\r
\r
})\r
\r
await test('derive high indexed accounts from the given seed', async () => {\r
- const wallet = await Bip44Wallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
+ const wallet = await Wallet.import('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
const accounts = await wallet.accounts(0x70000000, 0x7000000f)\r
\r
suite('Derive accounts from BLAKE2b wallet', async () => {\r
\r
await test('derive the second account from the given BLAKE2b seed', async () => {\r
- const wallet = await Blake2bWallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BLAKE2B_SEED)\r
+ const wallet = await Wallet.import('BLAKE2b', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BLAKE2B_SEED)\r
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
const account = await wallet.account(1)\r
\r
})\r
\r
await test('derive low indexed accounts from the given BLAKE2B seed', async () => {\r
- const wallet = await Blake2bWallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BLAKE2B_SEED)\r
+ const wallet = await Wallet.import('BLAKE2b', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BLAKE2B_SEED)\r
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
const accounts = await wallet.accounts(2, 3)\r
\r
})\r
\r
await test('derive high indexed accounts from the given seed', async () => {\r
- const wallet = await Blake2bWallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BLAKE2B_SEED)\r
+ const wallet = await Wallet.import('BLAKE2b', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BLAKE2B_SEED)\r
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
const accounts = await wallet.accounts(0x70000000, 0x7000000f)\r
\r
*/
let Account
/**
-* @type {typeof import('../dist/types.d.ts').LedgerWallet}
+* @type {typeof import('../dist/types.d.ts').Ledger}
*/
-let LedgerWallet
+let Ledger
/**
* @type {typeof import('../dist/types.d.ts').ReceiveBlock}
*/
let SendBlock
if (isNode) {
- ({ Account, LedgerWallet, ReceiveBlock, Rpc, SendBlock } = await import('../dist/nodejs.min.js'))
+ ({ Account, Ledger, ReceiveBlock, Rpc, SendBlock } = await import('../dist/nodejs.min.js'))
} else {
- ({ Account, LedgerWallet, ReceiveBlock, Rpc, SendBlock } = await import('../dist/browser.min.js'))
+ ({ Account, Ledger, ReceiveBlock, Rpc, SendBlock } = await import('../dist/browser.min.js'))
}
const rpc = new Rpc(env.NODE_URL ?? '', env.API_KEY_NAME)
*/
await Promise.all([
/* node:coverage disable */
- suite('Ledger hardware wallet', { skip: false || isNode || LedgerWallet.isUnsupported }, async () => {
+ suite('Ledger hardware wallet', { skip: false || isNode || Ledger.isUnsupported }, async () => {
let wallet, account, openBlock, sendBlock, receiveBlock
await test('request permissions', async () => {
- wallet = await LedgerWallet.create()
+ wallet = await Ledger.create()
let status = wallet.status
assert.equal(status, 'DISCONNECTED')
assert.equal(status, wallet.status)
await test('fail when using new', async () => {
//@ts-expect-error
- assert.throws(() => new LedgerWallet())
+ assert.throws(() => new Ledger())
})
await test('fail to sign a block without caching frontier', async () => {