UNPKG

@citadeldao/hw-app-alamgu

Version:

Common ledgerJS routines for Alamgu apps

362 lines 17.5 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; import sha256 from "fast-sha256"; /** * Common API for ledger apps * * @example * import Kadena from "hw-app-kda"; * const kda = new Kadena(transport) */ var Common = /** @class */ (function () { function Common(transport, scrambleKey, appName, verbosity) { if (appName === void 0) { appName = null; } if (verbosity === void 0) { verbosity = null; } this.transport = transport; this.appName = appName; this.verbose = verbosity === true; transport.decorateAppAPIMethods(this, ["menu", "getPublicKey", "signTransaction", "getVersion"], scrambleKey); } /** * Retrieves the public key associated with a particular BIP32 path from the ledger app. * * @param path - the path to retrieve. */ Common.prototype.getPublicKey = function (path) { return __awaiter(this, void 0, void 0, function () { var cla, ins, p1, p2, payload, response, keySize, publicKey, address, addressSize, res; return __generator(this, function (_a) { switch (_a.label) { case 0: cla = 0x00; ins = 0x02; p1 = 0; p2 = 0; payload = buildBip32KeyPayload(path); return [4 /*yield*/, this.sendChunks(cla, ins, p1, p2, payload)]; case 1: response = _a.sent(); keySize = response[0]; publicKey = response.slice(1, keySize + 1); address = null; if (response.length > keySize + 2) { addressSize = response[keySize + 1]; address = response.slice(keySize + 2, keySize + 2 + addressSize); } res = { publicKey: publicKey, address: address }; return [2 /*return*/, res]; } }); }); }; /** * Sign a transaction with the key at a BIP32 path. * * @param txn - The transaction; this can be any of a node Buffer, Uint8Array, or a hexadecimal string, encoding the form of the transaction appropriate for hashing and signing. * @param path - the path to use when signing the transaction. */ Common.prototype.signTransaction = function (path, txn) { return __awaiter(this, void 0, void 0, function () { var paths, cla, ins, p1, p2, rawTxn, hashSize, bip32KeyPayload, payload_txn, signature; return __generator(this, function (_a) { switch (_a.label) { case 0: paths = splitPath(path); cla = 0x00; ins = 0x03; p1 = 0; p2 = 0; // Transaction payload is the byte length as uint32le followed by the bytes // Type guard not actually required but TypeScript can't tell that. if (this.verbose) this.log(txn); rawTxn = typeof txn == "string" ? Buffer.from(txn, "hex") : Buffer.from(txn); hashSize = Buffer.alloc(4); hashSize.writeUInt32LE(rawTxn.length, 0); bip32KeyPayload = buildBip32KeyPayload(path); payload_txn = Buffer.concat([hashSize, rawTxn]); this.log("Payload Txn", payload_txn); return [4 /*yield*/, this.sendChunks(cla, ins, p1, p2, [payload_txn, bip32KeyPayload])]; case 1: signature = _a.sent(); return [2 /*return*/, { signature: signature }]; } }); }); }; /** * Retrieve the app version on the attached ledger device. * @alpha TODO this doesn't exist yet */ Common.prototype.getVersion = function () { return __awaiter(this, void 0, void 0, function () { var _a, major, minor, patch, appName; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, this.sendChunks(0x00, 0x00, 0x00, 0x00, Buffer.alloc(1))]; case 1: _a = __read.apply(void 0, [_b.sent()]), major = _a[0], minor = _a[1], patch = _a[2], appName = _a.slice(3); return [2 /*return*/, { major: major, minor: minor, patch: patch }]; } }); }); }; /** * Send a raw payload as chunks to a particular APDU instruction. * * @remarks * * This is intended to be used to implement a more useful API in this class and subclasses of it, not for end use. */ Common.prototype.sendChunks = function (cla, ins, p1, p2, payload) { return __awaiter(this, void 0, void 0, function () { var rv, chunkSize, i; return __generator(this, function (_a) { switch (_a.label) { case 0: rv = Buffer.alloc(0); chunkSize = 230; if (payload instanceof Array) { payload = Buffer.concat(payload); } i = 0; _a.label = 1; case 1: if (!(i < payload.length)) return [3 /*break*/, 4]; return [4 /*yield*/, this.transport.send(cla, ins, p1, p2, payload.slice(i, i + chunkSize))]; case 2: rv = _a.sent(); _a.label = 3; case 3: i += chunkSize; return [3 /*break*/, 1]; case 4: // Remove the status code here instead of in signTransaction, because sendWithBlocks _has_ to handle it. return [2 /*return*/, rv.slice(0, -2)]; } }); }); }; /** * Convert a raw payload into what is essentially a singly-linked list of chunks, which allows the ledger to re-seek the data in a secure fashion. */ Common.prototype.sendWithBlocks = function (cla, ins, p1, p2, payload, // Constant (protocol dependent) data that the ledger may want to refer to // besides the payload. extraData) { if (extraData === void 0) { extraData = new Map(); } return __awaiter(this, void 0, void 0, function () { var rv, chunkSize, parameterList, data, _loop_1, this_1, j; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: chunkSize = 180; if (!(payload instanceof Array)) { payload = [payload]; } parameterList = []; data = new Map(extraData); _loop_1 = function (j) { var chunkList = []; for (var i = 0; i < payload[j].length; i += chunkSize) { var cur = payload[j].slice(i, i + chunkSize); chunkList.push(cur); } // Store the hash that points to the "rest of the list of chunks" var lastHash = Buffer.alloc(32); this_1.log(lastHash); // Since we are doing a foldr, we process the last chunk first // We have to do it this way, because a block knows the hash of // the next block. data = chunkList.reduceRight(function (blocks, chunk) { var linkedChunk = Buffer.concat([lastHash, chunk]); _this.log("Chunk: ", chunk); _this.log("linkedChunk: ", linkedChunk); lastHash = Buffer.from(sha256(linkedChunk)); blocks.set(lastHash.toString('hex'), linkedChunk); return blocks; }, data); parameterList.push(lastHash); lastHash = Buffer.alloc(32); }; this_1 = this; for (j = 0; j < payload.length; j++) { _loop_1(j); } this.log(data); return [4 /*yield*/, this.handleBlocksProtocol(cla, ins, p1, p2, Buffer.concat([Buffer.from([HostToLedger.START])].concat(parameterList)), data)]; case 1: return [2 /*return*/, _a.sent()]; } }); }); }; Common.prototype.handleBlocksProtocol = function (cla, ins, p1, p2, initialPayload, data) { return __awaiter(this, void 0, void 0, function () { var payload, result, rv, rv_instruction, rv_payload, chunk; return __generator(this, function (_a) { switch (_a.label) { case 0: payload = initialPayload; result = Buffer.alloc(0); _a.label = 1; case 1: this.log("Sending payload to ledger: ", payload.toString('hex')); return [4 /*yield*/, this.transport.send(cla, ins, p1, p2, payload)]; case 2: rv = _a.sent(); this.log("Received response: ", rv); rv_instruction = rv[0]; 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 LedgerToHost.RESULT_ACCUMULATING: case LedgerToHost.RESULT_FINAL: result = Buffer.concat([result, rv_payload]); // Won't actually send this if we drop out of the loop for RESULT_FINAL payload = Buffer.from([HostToLedger.RESULT_ACCUMULATING_RESPONSE]); break; case LedgerToHost.GET_CHUNK: chunk = data.get(rv_payload.toString('hex')); this.log("Getting block ", rv_payload); this.log("Found block ", chunk); if (chunk) { payload = Buffer.concat([Buffer.from([HostToLedger.GET_CHUNK_RESPONSE_SUCCESS]), chunk]); } else { payload = Buffer.from([HostToLedger.GET_CHUNK_RESPONSE_FAILURE]); } break; case LedgerToHost.PUT_CHUNK: data.set(Buffer.from(sha256(rv_payload)).toString('hex'), rv_payload); payload = Buffer.from([HostToLedger.PUT_CHUNK_RESPONSE]); break; } _a.label = 3; case 3: if (rv_instruction != 1) return [3 /*break*/, 1]; _a.label = 4; case 4: return [2 /*return*/, result]; } }); }); }; Common.prototype.log = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } if (this.verbose) console.log(args); }; return Common; }()); export { Common }; var LedgerToHost; (function (LedgerToHost) { LedgerToHost[LedgerToHost["RESULT_ACCUMULATING"] = 0] = "RESULT_ACCUMULATING"; LedgerToHost[LedgerToHost["RESULT_FINAL"] = 1] = "RESULT_FINAL"; LedgerToHost[LedgerToHost["GET_CHUNK"] = 2] = "GET_CHUNK"; LedgerToHost[LedgerToHost["PUT_CHUNK"] = 3] = "PUT_CHUNK"; })(LedgerToHost || (LedgerToHost = {})); ; var HostToLedger; (function (HostToLedger) { HostToLedger[HostToLedger["START"] = 0] = "START"; HostToLedger[HostToLedger["GET_CHUNK_RESPONSE_SUCCESS"] = 1] = "GET_CHUNK_RESPONSE_SUCCESS"; HostToLedger[HostToLedger["GET_CHUNK_RESPONSE_FAILURE"] = 2] = "GET_CHUNK_RESPONSE_FAILURE"; HostToLedger[HostToLedger["PUT_CHUNK_RESPONSE"] = 3] = "PUT_CHUNK_RESPONSE"; HostToLedger[HostToLedger["RESULT_ACCUMULATING_RESPONSE"] = 4] = "RESULT_ACCUMULATING_RESPONSE"; })(HostToLedger || (HostToLedger = {})); ; export function buildBip32KeyPayload(path) { var paths = splitPath(path); // Bip32Key payload is: // 1 byte with number of elements in u32 array path // Followed by the u32 array itself var payload = Buffer.alloc(1 + paths.length * 4); payload[0] = paths.length; paths.forEach(function (element, index) { payload.writeUInt32LE(element, 1 + 4 * index); }); return payload; } // TODO use bip32-path library export function splitPath(path) { var result = []; var components = path.split("/"); components.forEach(function (element) { var number = parseInt(element, 10); if (isNaN(number)) { return; // FIXME shouldn't it throws instead? } if (element.length > 1 && element[element.length - 1] === "'") { number += 0x80000000; } result.push(number); }); return result; } //# sourceMappingURL=Common.js.map