UNPKG

@ledgerhq/hw-app-btc

Version:
132 lines • 6.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AppClient = void 0; const bip32_1 = require("../bip32"); const merkelizedPsbt_1 = require("./merkelizedPsbt"); const clientCommands_1 = require("./clientCommands"); const varint_1 = require("../varint"); const merkle_1 = require("./merkle"); const CLA_BTC = 0xe1; const CLA_FRAMEWORK = 0xf8; var BitcoinIns; (function (BitcoinIns) { BitcoinIns[BitcoinIns["GET_PUBKEY"] = 0] = "GET_PUBKEY"; // GET_ADDRESS = 0x01, // Removed from app BitcoinIns[BitcoinIns["REGISTER_WALLET"] = 2] = "REGISTER_WALLET"; BitcoinIns[BitcoinIns["GET_WALLET_ADDRESS"] = 3] = "GET_WALLET_ADDRESS"; BitcoinIns[BitcoinIns["SIGN_PSBT"] = 4] = "SIGN_PSBT"; BitcoinIns[BitcoinIns["GET_MASTER_FINGERPRINT"] = 5] = "GET_MASTER_FINGERPRINT"; BitcoinIns[BitcoinIns["SIGN_MESSAGE"] = 16] = "SIGN_MESSAGE"; })(BitcoinIns || (BitcoinIns = {})); var FrameworkIns; (function (FrameworkIns) { FrameworkIns[FrameworkIns["CONTINUE_INTERRUPTED"] = 1] = "CONTINUE_INTERRUPTED"; })(FrameworkIns || (FrameworkIns = {})); /** * This class encapsulates the APDU protocol documented at * https://github.com/LedgerHQ/app-bitcoin-new/blob/master/doc/bitcoin.md */ class AppClient { transport; constructor(transport) { this.transport = transport; } async makeRequest(ins, data, cci) { let response = await this.transport.send(CLA_BTC, ins, 0, 0, data, [0x9000, 0xe000]); while (response.readUInt16BE(response.length - 2) === 0xe000) { if (!cci) { throw new Error("Unexpected SW_INTERRUPTED_EXECUTION"); } const hwRequest = response.slice(0, -2); const commandResponse = cci.execute(hwRequest); response = await this.transport.send(CLA_FRAMEWORK, FrameworkIns.CONTINUE_INTERRUPTED, 0, 0, commandResponse, [0x9000, 0xe000]); } return response.slice(0, -2); // drop the status word (can only be 0x9000 at this point) } async getExtendedPubkey(display, pathElements) { if (pathElements.length > 6) { throw new Error("Path too long. At most 6 levels allowed."); } const response = await this.makeRequest(BitcoinIns.GET_PUBKEY, Buffer.concat([Buffer.from(display ? [1] : [0]), (0, bip32_1.pathElementsToBuffer)(pathElements)])); return response.toString("ascii"); } async getWalletAddress(walletPolicy, walletHMAC, change, addressIndex, display) { if (change !== 0 && change !== 1) throw new Error("Change can only be 0 or 1"); if (addressIndex < 0 || !Number.isInteger(addressIndex)) throw new Error("Invalid address index"); if (walletHMAC != null && walletHMAC.length != 32) { throw new Error("Invalid HMAC length"); } const clientInterpreter = new clientCommands_1.ClientCommandInterpreter(() => { }); clientInterpreter.addKnownList(walletPolicy.keys.map(k => Buffer.from(k, "ascii"))); clientInterpreter.addKnownPreimage(walletPolicy.serialize()); const addressIndexBuffer = Buffer.alloc(4); addressIndexBuffer.writeUInt32BE(addressIndex, 0); const response = await this.makeRequest(BitcoinIns.GET_WALLET_ADDRESS, Buffer.concat([ Buffer.from(display ? [1] : [0]), walletPolicy.getWalletId(), walletHMAC || Buffer.alloc(32, 0), Buffer.from([change]), addressIndexBuffer, ]), clientInterpreter); return response.toString("ascii"); } async signPsbt(psbt, walletPolicy, walletHMAC, progressCallback) { const merkelizedPsbt = new merkelizedPsbt_1.MerkelizedPsbt(psbt); if (walletHMAC != null && walletHMAC.length != 32) { throw new Error("Invalid HMAC length"); } const clientInterpreter = new clientCommands_1.ClientCommandInterpreter(progressCallback); // prepare ClientCommandInterpreter clientInterpreter.addKnownList(walletPolicy.keys.map(k => Buffer.from(k, "ascii"))); clientInterpreter.addKnownPreimage(walletPolicy.serialize()); clientInterpreter.addKnownMapping(merkelizedPsbt.globalMerkleMap); for (const map of merkelizedPsbt.inputMerkleMaps) { clientInterpreter.addKnownMapping(map); } for (const map of merkelizedPsbt.outputMerkleMaps) { clientInterpreter.addKnownMapping(map); } clientInterpreter.addKnownList(merkelizedPsbt.inputMapCommitments); const inputMapsRoot = new merkle_1.Merkle(merkelizedPsbt.inputMapCommitments.map(m => (0, merkle_1.hashLeaf)(m))).getRoot(); clientInterpreter.addKnownList(merkelizedPsbt.outputMapCommitments); const outputMapsRoot = new merkle_1.Merkle(merkelizedPsbt.outputMapCommitments.map(m => (0, merkle_1.hashLeaf)(m))).getRoot(); await this.makeRequest(BitcoinIns.SIGN_PSBT, Buffer.concat([ merkelizedPsbt.getGlobalKeysValuesRoot(), (0, varint_1.createVarint)(merkelizedPsbt.getGlobalInputCount()), inputMapsRoot, (0, varint_1.createVarint)(merkelizedPsbt.getGlobalOutputCount()), outputMapsRoot, walletPolicy.getWalletId(), walletHMAC || Buffer.alloc(32, 0), ]), clientInterpreter); const yielded = clientInterpreter.getYielded(); const ret = new Map(); for (const inputAndSig of yielded) { ret.set(inputAndSig[0], inputAndSig.slice(1)); } return ret; } async getMasterFingerprint() { return this.makeRequest(BitcoinIns.GET_MASTER_FINGERPRINT, Buffer.from([])); } async signMessage(message, pathElements) { if (pathElements.length > 6) { throw new Error("Path too long. At most 6 levels allowed."); } const clientInterpreter = new clientCommands_1.ClientCommandInterpreter(() => { }); // prepare ClientCommandInterpreter const nChunks = Math.ceil(message.length / 64); const chunks = []; for (let i = 0; i < nChunks; i++) { chunks.push(message.subarray(64 * i, 64 * i + 64)); } clientInterpreter.addKnownList(chunks); const chunksRoot = new merkle_1.Merkle(chunks.map(m => (0, merkle_1.hashLeaf)(m))).getRoot(); const response = await this.makeRequest(BitcoinIns.SIGN_MESSAGE, Buffer.concat([(0, bip32_1.pathElementsToBuffer)(pathElements), (0, varint_1.createVarint)(message.length), chunksRoot]), clientInterpreter); return response.toString("base64"); } } exports.AppClient = AppClient; //# sourceMappingURL=appClient.js.map