UNPKG

@ledgerhq/hw-app-cosmos

Version:
139 lines 5.25 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const bip32_path_1 = __importDefault(require("bip32-path")); const errors_1 = require("@ledgerhq/errors"); const CHUNK_SIZE = 250; const CLA = 0x55; const APP_KEY = "CSM"; const INS_GET_VERSION = 0x00; const INS_SIGN_SECP256K1 = 0x02; const INS_GET_ADDR_SECP256K1 = 0x04; const PAYLOAD_TYPE_INIT = 0x00; const PAYLOAD_TYPE_ADD = 0x01; const PAYLOAD_TYPE_LAST = 0x02; const SW_OK = 0x9000; const SW_CANCEL = 0x6986; /** * Cosmos API * * @example * import Cosmos from "@ledgerhq/hw-app-cosmos"; * const cosmos = new Cosmos(transport) */ class Cosmos { transport; constructor(transport, scrambleKey = APP_KEY) { this.transport = transport; transport.decorateAppAPIMethods(this, ["getAddress", "sign", "getAppConfiguration"], scrambleKey); } // FIXME: understand what is going on with the return type here getAppConfiguration() { return this.transport.send(CLA, INS_GET_VERSION, 0, 0).then(response => { return { test_mode: response[0] !== 0, version: "" + response[1] + "." + response[2] + "." + response[3], device_locked: response[4] === 1, major: response[1], }; }); } serializePath(path) { const buf = Buffer.alloc(20); // HACK : without the >>>, // the bitwise implicitly casts the result to be a signed int32, // which fails the internal type check of Buffer in case of overload. buf.writeUInt32LE((0x80000000 | path[0]) >>> 0, 0); buf.writeUInt32LE((0x80000000 | path[1]) >>> 0, 4); buf.writeUInt32LE((0x80000000 | path[2]) >>> 0, 8); buf.writeUInt32LE(path[3], 12); buf.writeUInt32LE(path[4], 16); return buf; } serializeHRP(hrp) { if (hrp == null || hrp.length === 0 || hrp.length > 83) { throw new Error("Invalid HRP"); } const buf = Buffer.alloc(1 + hrp.length); buf.writeUInt8(hrp.length, 0); buf.write(hrp, 1); return buf; } /** * get Cosmos address for a given BIP 32 path. * @param path a path in BIP 32 format * @param hrp usually cosmos * @option boolDisplay optionally enable or not the display * @return an object with a publicKey, address and (optionally) chainCode * @example * cosmos.getAddress("44'/60'/0'/0/0", "cosmos").then(o => o.address) */ getAddress(path, hrp, boolDisplay) { const bipPath = bip32_path_1.default.fromString(path).toPathArray(); const serializedPath = this.serializePath(bipPath); const data = Buffer.concat([this.serializeHRP(hrp), serializedPath]); return this.transport .send(CLA, INS_GET_ADDR_SECP256K1, boolDisplay ? 1 : 0, 0, data, [SW_OK]) .then(response => { const address = Buffer.from(response.slice(33, -2)).toString(); const publicKey = Buffer.from(response.slice(0, 33)).toString("hex"); return { address, publicKey, }; }); } foreach(arr, callback) { function iterate(index, array, result) { if (index >= array.length) { return result; } else return callback(array[index], index).then(function (res) { result.push(res); return iterate(index + 1, array, result); }); } return Promise.resolve().then(() => iterate(0, arr, [])); } async sign(path, message) { const bipPath = bip32_path_1.default.fromString(path).toPathArray(); const serializedPath = this.serializePath(bipPath); const chunks = []; chunks.push(serializedPath); const buffer = Buffer.from(message); for (let i = 0; i < buffer.length; i += CHUNK_SIZE) { let end = i + CHUNK_SIZE; if (i > buffer.length) { end = buffer.length; } chunks.push(buffer.slice(i, end)); } let response = {}; return this.foreach(chunks, (data, j) => this.transport .send(CLA, INS_SIGN_SECP256K1, j === 0 ? PAYLOAD_TYPE_INIT : j + 1 === chunks.length ? PAYLOAD_TYPE_LAST : PAYLOAD_TYPE_ADD, 0, data, [SW_OK, SW_CANCEL]) .then(apduResponse => (response = apduResponse))).then(() => { const errorCodeData = response.slice(-2); const returnCode = errorCodeData[0] * 256 + errorCodeData[1]; let signature = null; if (response.length > 2) { signature = response.slice(0, response.length - 2); } if (returnCode === 0x6986) { throw new errors_1.UserRefusedOnDevice(); } return { signature, return_code: returnCode, }; }); } } exports.default = Cosmos; //# sourceMappingURL=Cosmos.js.map