import { default as TransportHID } from '@ledgerhq/hw-transport-webhid'\r
import { Account, AccountList } from '../account'\r
import { Block } from '../block'\r
-import { BIP44_COIN_NANO, BIP44_PURPOSE, HARDENED_OFFSET, LEDGER_ADPU_CODES, LEDGER_STATUS_CODES } from '../constants'\r
+import { BIP44_COIN_NANO, BIP44_PURPOSE, HARDENED_OFFSET } from '../constants'\r
import { bytes, dec, hex } from '../convert'\r
import { Database } from '../database'\r
import { Rpc } from '../rpc'\r
* is responsible for using Ledger technology to back up these pieces of data.\r
*/\r
export class Ledger extends Wallet {\r
- static #derivationPath: Uint8Array = new Uint8Array([\r
- LEDGER_ADPU_CODES.bip32DerivationLevel,\r
+ static #isInternal: boolean = false\r
+\r
+ static #ADPU_CODES: { [key: string]: number } = Object.freeze({\r
+ class: 0xa1,\r
+ bip32DerivationLevel: 0x03,\r
+ version: 0x01,\r
+ account: 0x02,\r
+ cacheBlock: 0x03,\r
+ signBlock: 0x04,\r
+ signNonce: 0x05,\r
+ paramUnused: 0x00\r
+ })\r
+ static #DERIVATION_PATH: Uint8Array = new Uint8Array([\r
+ Ledger.#ADPU_CODES.bip32DerivationLevel,\r
...dec.toBytes(BIP44_PURPOSE + HARDENED_OFFSET, 4),\r
...dec.toBytes(BIP44_COIN_NANO + HARDENED_OFFSET, 4)\r
])\r
- static #isInternal: boolean = false\r
- static INTERNAL: Symbol = Symbol('Ledger')\r
+ static #STATUS_CODES: { [key: number]: string } = Object.freeze({\r
+ 0x6700: 'INCORRECT_LENGTH',\r
+ 0x670a: 'NO_APPLICATION_SPECIFIED',\r
+ 0x6807: 'APPLICATION_NOT_INSTALLED',\r
+ 0x6d00: 'APPLICATION_ALREADY_LAUNCHED',\r
+ 0x6982: 'SECURITY_STATUS_NOT_SATISFIED',\r
+ 0x6985: 'CONDITIONS_OF_USE_NOT_SATISFIED',\r
+ 0x6a81: 'INVALID_SIGNATURE',\r
+ 0x6a82: 'CACHE_MISS',\r
+ 0x6b00: 'INCORRECT_PARAMETER',\r
+ 0x6e01: 'TRANSPORT_STATUS_ERROR',\r
+ 0x9000: 'OK'\r
+ })\r
\r
static DynamicTransport: typeof TransportBLE | typeof TransportUSB | typeof TransportHID\r
+ static SYMBOL: Symbol = Symbol('Ledger')\r
+\r
static get listenTimeout (): 30000 { return 30000 }\r
static get openTimeout (): 3000 { return 3000 }\r
\r
* Check which transport protocols are supported by the browser and return the\r
* transport type according to the following priorities: USB, Bluetooth, HID.\r
*/\r
- static get isUnsupported (): boolean {\r
+ static get #isUnsupported (): boolean {\r
console.log('Checking browser Ledger support...')\r
if (typeof globalThis.navigator?.usb?.getDevices === 'function') {\r
this.DynamicTransport = TransportUSB\r
*/\r
static async create (): Promise<Ledger> {\r
try {\r
- if (this.isUnsupported) throw new Error('Browser is unsupported')\r
+ if (this.#isUnsupported) throw new Error('Browser is unsupported')\r
this.#isInternal = true\r
const self = new this()\r
await Database.add({ [self.id]: { id: self.id, type: 'Ledger' } }, Wallet.DB_NAME)\r
const name = new TextEncoder().encode('Nano')\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
+ .send(0xe0, 0xd8, Ledger.#ADPU_CODES.paramUnused, Ledger.#ADPU_CODES.paramUnused, name as Buffer)\r
.then(res => bytes.toDec(res))\r
.catch(err => err.statusCode) as number\r
- return new Promise(r => setTimeout(r, 1000, { status: LEDGER_STATUS_CODES[response] }))\r
+ return new Promise(r => setTimeout(r, 1000, { status: Ledger.#STATUS_CODES[response] }))\r
}\r
\r
/**\r
async #close (): Promise<LedgerResponse> {\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
+ .send(0xb0, 0xa7, Ledger.#ADPU_CODES.paramUnused, Ledger.#ADPU_CODES.paramUnused)\r
.then(res => bytes.toDec(res))\r
.catch(err => err.statusCode) as number\r
- return new Promise(r => setTimeout(r, 1000, { status: LEDGER_STATUS_CODES[response] }))\r
+ return new Promise(r => setTimeout(r, 1000, { status: Ledger.#STATUS_CODES[response] }))\r
}\r
\r
/**\r
throw new TypeError('Invalid account index')\r
}\r
const account = dec.toBytes(index + HARDENED_OFFSET, 4)\r
- const data = new Uint8Array([...Ledger.#derivationPath, ...account])\r
+ const data = new Uint8Array([...Ledger.#DERIVATION_PATH, ...account])\r
\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
+ .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
await transport.close()\r
\r
const statusCode = bytes.toDec(response.slice(-2)) as number\r
- const status = LEDGER_STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'\r
+ const status = Ledger.#STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'\r
if (status !== 'OK') {\r
return { status, publicKey: null, address: null }\r
}\r
const representative = hex.toBytes(block.representative.publicKey, 32)\r
const balance = hex.toBytes(block.balance.toString(16), 16)\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
+ const data = new Uint8Array([Ledger.#ADPU_CODES.bip32DerivationLevel, ...purpose, ...coin, ...account, ...previous, ...link, ...representative, ...balance, ...signature])\r
\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
+ .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
.catch(err => err.statusCode) as number\r
await transport.close()\r
\r
- return { status: LEDGER_STATUS_CODES[response] }\r
+ return { status: Ledger.#STATUS_CODES[response] }\r
}\r
\r
/**\r
const link = block.link\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([...Ledger.#derivationPath, ...account, ...previous, ...link, ...representative, ...balance])\r
+ const data = new Uint8Array([...Ledger.#DERIVATION_PATH, ...account, ...previous, ...link, ...representative, ...balance])\r
\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.signBlock, LEDGER_ADPU_CODES.paramUnused, LEDGER_ADPU_CODES.paramUnused, data as Buffer)\r
+ .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
const statusCode = bytes.toDec(response.slice(-2)) as number\r
- const status = LEDGER_STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'\r
+ const status = Ledger.#STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'\r
\r
if (response.byteLength === 2) {\r
return { status, signature: null }\r
}\r
\r
const derivationAccount = dec.toBytes(index + HARDENED_OFFSET, 4)\r
- const data = new Uint8Array([...Ledger.#derivationPath, ...derivationAccount, ...nonce])\r
+ const data = new Uint8Array([...Ledger.#DERIVATION_PATH, ...derivationAccount, ...nonce])\r
\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
+ .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
await transport.close()\r
\r
const statusCode = bytes.toDec(response.slice(-2)) as number\r
- const status = LEDGER_STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'\r
+ const status = Ledger.#STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'\r
\r
if (response.byteLength === 2) {\r
return { status, signature: null }\r
async #version (): Promise<LedgerVersionResponse> {\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
+ .send(0xb0, Ledger.#ADPU_CODES.version, Ledger.#ADPU_CODES.paramUnused, Ledger.#ADPU_CODES.paramUnused)\r
.catch(err => dec.toBytes(err.statusCode)) as Uint8Array\r
await transport.close()\r
\r
const statusCode = bytes.toDec(response.slice(-2)) as number\r
- const status = LEDGER_STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'\r
+ const status = Ledger.#STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'\r
if (status !== 'OK') {\r
return { status, name: null, version: null }\r
}\r