UNPKG

@iota/ledgerjs-hw-app-iota

Version:

Ledger Hardware Wallet IOTA Application API

258 lines (257 loc) 10.9 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __typeError = (msg) => { throw TypeError(msg); }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method); var Iota_exports = {}; __export(Iota_exports, { default: () => Iota }); module.exports = __toCommonJS(Iota_exports); var import_fast_sha256 = __toESM(require("fast-sha256")); var _verbose, _Iota_instances, internalGetVersion_fn, sendChunks_fn, handleBlocksProtocol_fn, log_fn; var LedgerToHost = /* @__PURE__ */ ((LedgerToHost2) => { LedgerToHost2[LedgerToHost2["RESULT_ACCUMULATING"] = 0] = "RESULT_ACCUMULATING"; LedgerToHost2[LedgerToHost2["RESULT_FINAL"] = 1] = "RESULT_FINAL"; LedgerToHost2[LedgerToHost2["GET_CHUNK"] = 2] = "GET_CHUNK"; LedgerToHost2[LedgerToHost2["PUT_CHUNK"] = 3] = "PUT_CHUNK"; return LedgerToHost2; })(LedgerToHost || {}); var HostToLedger = /* @__PURE__ */ ((HostToLedger2) => { HostToLedger2[HostToLedger2["START"] = 0] = "START"; HostToLedger2[HostToLedger2["GET_CHUNK_RESPONSE_SUCCESS"] = 1] = "GET_CHUNK_RESPONSE_SUCCESS"; HostToLedger2[HostToLedger2["GET_CHUNK_RESPONSE_FAILURE"] = 2] = "GET_CHUNK_RESPONSE_FAILURE"; HostToLedger2[HostToLedger2["PUT_CHUNK_RESPONSE"] = 3] = "PUT_CHUNK_RESPONSE"; HostToLedger2[HostToLedger2["RESULT_ACCUMULATING_RESPONSE"] = 4] = "RESULT_ACCUMULATING_RESPONSE"; return HostToLedger2; })(HostToLedger || {}); class Iota { constructor(transport, scrambleKey = "default_iota_scramble_key", verbose = false) { __privateAdd(this, _Iota_instances); __privateAdd(this, _verbose); __privateSet(this, _verbose, verbose); this.transport = transport; this.transport.decorateAppAPIMethods( this, ["getPublicKey", "signTransaction", "getVersion"], scrambleKey ); } /** * Retrieves the public key associated with a particular BIP32 path from the Ledger app. * * @param path - the path to retrieve. * @param displayOnDevice - whether or not the address should be displayed on the device. * */ async getPublicKey(path, displayOnDevice = false) { const cla = 0; const ins = displayOnDevice ? 1 : 2; const p1 = 0; const p2 = 0; const payload = buildBip32KeyPayload(path); const response = await __privateMethod(this, _Iota_instances, sendChunks_fn).call(this, cla, ins, p1, p2, payload); const keySize = response[0]; const publicKey = response.slice(1, keySize + 1); let address = null; if (response.length > keySize + 2) { const addressSize = response[keySize + 1]; address = response.slice(keySize + 2, keySize + 2 + addressSize); } const res = { publicKey, address }; return res; } /** * Sign a transaction with the key at a BIP32 path. * * @param txn - The transaction bytes to sign. * @param path - The path to use when signing the transaction. * @param options - Additional options used for clear signing purposes. */ async signTransaction(path, txn, options) { const cla = 0; const ins = 3; const p1 = 0; const p2 = 0; if (__privateGet(this, _verbose)) __privateMethod(this, _Iota_instances, log_fn).call(this, txn); const rawTxn = Buffer.from(txn); const hashSize = Buffer.alloc(4); hashSize.writeUInt32LE(rawTxn.length, 0); const payloadTxn = Buffer.concat([hashSize, rawTxn]); __privateMethod(this, _Iota_instances, log_fn).call(this, "Payload Txn", payloadTxn); const bip32KeyPayload = buildBip32KeyPayload(path); const payloads = [payloadTxn, bip32KeyPayload]; const { major } = await __privateMethod(this, _Iota_instances, internalGetVersion_fn).call(this); const bcsObjects = options?.bcsObjects ?? []; __privateMethod(this, _Iota_instances, log_fn).call(this, "Objects list length", bcsObjects.length); __privateMethod(this, _Iota_instances, log_fn).call(this, "App version", major); if (major > 0 && bcsObjects.length > 0) { const numItems = Buffer.alloc(4); numItems.writeUInt32LE(bcsObjects.length, 0); let listData = Buffer.from(numItems); for (const item of bcsObjects) { const rawItem = Buffer.from(item); const itemLen = Buffer.alloc(4); itemLen.writeUInt32LE(rawItem.length, 0); listData = Buffer.concat([listData, itemLen, rawItem]); } payloads.push(listData); } const signature = await __privateMethod(this, _Iota_instances, sendChunks_fn).call(this, cla, ins, p1, p2, payloads); return { signature }; } /** * Retrieve the app version on the attached Ledger device. */ async getVersion() { return await __privateMethod(this, _Iota_instances, internalGetVersion_fn).call(this); } } _verbose = new WeakMap(); _Iota_instances = new WeakSet(); internalGetVersion_fn = async function() { const [major, minor, patch] = await __privateMethod(this, _Iota_instances, sendChunks_fn).call(this, 0, 0, 0, 0, Buffer.alloc(1)); return { major, minor, patch }; }; sendChunks_fn = async function(cla, ins, p1, p2, payload, extraData = /* @__PURE__ */ new Map()) { const chunkSize = 180; if (!(payload instanceof Array)) { payload = [payload]; } const parameterList = []; let data = new Map(extraData); for (let j = 0; j < payload.length; j++) { const chunkList = []; for (let i = 0; i < payload[j].length; i += chunkSize) { const cur = payload[j].slice(i, i + chunkSize); chunkList.push(cur); } let lastHash = Buffer.alloc(32); __privateMethod(this, _Iota_instances, log_fn).call(this, lastHash); data = chunkList.reduceRight((blocks, chunk) => { const linkedChunk = Buffer.concat([lastHash, chunk]); __privateMethod(this, _Iota_instances, log_fn).call(this, "Chunk: ", chunk); __privateMethod(this, _Iota_instances, log_fn).call(this, "linkedChunk: ", linkedChunk); lastHash = Buffer.from((0, import_fast_sha256.default)(linkedChunk)); blocks.set(lastHash.toString("hex"), linkedChunk); return blocks; }, data); parameterList.push(lastHash); lastHash = Buffer.alloc(32); } __privateMethod(this, _Iota_instances, log_fn).call(this, data); return await __privateMethod(this, _Iota_instances, handleBlocksProtocol_fn).call(this, cla, ins, p1, p2, Buffer.concat( [Buffer.from([0 /* START */])].concat( parameterList ) ), data); }; handleBlocksProtocol_fn = async function(cla, ins, p1, p2, initialPayload, data) { let payload = initialPayload; let result = Buffer.alloc(0); let rv_instruction; do { __privateMethod(this, _Iota_instances, log_fn).call(this, "Sending payload to ledger: ", payload.toString("hex")); const rv = await this.transport.send(cla, ins, p1, p2, payload); __privateMethod(this, _Iota_instances, log_fn).call(this, "Received response: ", rv); rv_instruction = rv[0]; const rv_payload = rv.slice(1, rv.length - 2); if (!(rv_instruction in LedgerToHost)) { throw new TypeError("Unknown instruction returned from ledger"); } switch (rv_instruction) { case 0 /* RESULT_ACCUMULATING */: case 1 /* RESULT_FINAL */: result = Buffer.concat([result, rv_payload]); payload = Buffer.from([4 /* RESULT_ACCUMULATING_RESPONSE */]); break; case 2 /* GET_CHUNK */: const chunk = data.get(rv_payload.toString("hex")); __privateMethod(this, _Iota_instances, log_fn).call(this, "Getting block ", rv_payload); __privateMethod(this, _Iota_instances, log_fn).call(this, "Found block ", chunk); if (chunk) { payload = Buffer.concat([ Buffer.from([1 /* GET_CHUNK_RESPONSE_SUCCESS */]), chunk ]); } else { payload = Buffer.from([2 /* GET_CHUNK_RESPONSE_FAILURE */]); } break; case 3 /* PUT_CHUNK */: data.set( Buffer.from((0, import_fast_sha256.default)(rv_payload)).toString("hex"), rv_payload ); payload = Buffer.from([3 /* PUT_CHUNK_RESPONSE */]); break; } } while (rv_instruction !== 1 /* RESULT_FINAL */); return result; }; log_fn = function(...args) { if (__privateGet(this, _verbose)) console.log(args); }; function buildBip32KeyPayload(path) { const paths = splitPath(path); const payload = Buffer.alloc(1 + paths.length * 4); payload[0] = paths.length; paths.forEach((element, index) => { payload.writeUInt32LE(element, 1 + 4 * index); }); return payload; } function splitPath(path) { const result = []; const components = path.split("/"); components.forEach((element) => { let number = parseInt(element, 10); if (isNaN(number)) { return; } if (element.length > 1 && element[element.length - 1] === "'") { number += 2147483648; } result.push(number); }); return result; } //# sourceMappingURL=Iota.js.map