@iota/ledgerjs-hw-app-iota
Version:
Ledger Hardware Wallet IOTA Application API
258 lines (257 loc) • 10.9 kB
JavaScript
;
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