@iota/ledgerjs-hw-app-iota
Version:
Ledger Hardware Wallet IOTA Application API
228 lines (227 loc) • 9.31 kB
JavaScript
var __typeError = (msg) => {
throw TypeError(msg);
};
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 _verbose, _Iota_instances, internalGetVersion_fn, sendChunks_fn, handleBlocksProtocol_fn, log_fn;
import sha256 from "fast-sha256";
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(sha256(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(sha256(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;
}
export {
Iota as default
};
//# sourceMappingURL=Iota.js.map