UNPKG

ledger-bitcoin

Version:

Ledger Hardware Wallet Bitcoin Application Client

303 lines 26.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AppClient = exports.PartialSignature = void 0; const bip32_1 = require("./bip32"); const clientCommands_1 = require("./clientCommands"); const merkelizedPsbt_1 = require("./merkelizedPsbt"); const merkle_1 = require("./merkle"); const psbtv2_1 = require("./psbtv2"); const varint_1 = require("./varint"); const CLA_BTC = 0xe1; const CLA_FRAMEWORK = 0xf8; const CURRENT_PROTOCOL_VERSION = 1; // supported from version 2.1.0 of the app var BitcoinIns; (function (BitcoinIns) { BitcoinIns[BitcoinIns["GET_PUBKEY"] = 0] = "GET_PUBKEY"; 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 represents a partial signature produced by the app during signing. * It always contains the `signature` and the corresponding `pubkey` whose private key * was used for signing; in the case of taproot script paths, it also contains the * tapleaf hash. */ class PartialSignature { constructor(pubkey, signature, tapleafHash) { this.pubkey = pubkey; this.signature = signature; this.tapleafHash = tapleafHash; } } exports.PartialSignature = PartialSignature; /** * Creates an instance of `PartialSignature` from the returned raw augmented pubkey and signature. * @param pubkeyAugm the public key, concatenated with the tapleaf hash in the case of taproot script path spend. * @param signature the signature * @returns an instance of `PartialSignature`. */ function makePartialSignature(pubkeyAugm, signature) { if (pubkeyAugm.length == 64) { // tapscript spend: concatenation of 32-bytes x-only pubkey and 32-bytes tapleaf_hash return new PartialSignature(pubkeyAugm.slice(0, 32), signature, pubkeyAugm.slice(32, 64)); } else if (pubkeyAugm.length == 32 || pubkeyAugm.length == 33) { // legacy, segwit or taproot keypath spend: pubkeyAugm is just the pubkey return new PartialSignature(pubkeyAugm, signature); } else { throw new Error(`Invalid length for pubkeyAugm: ${pubkeyAugm.length} bytes.`); } } /** * Checks whether a descriptor template contains an `a:` fragment. */ function containsA(descriptorTemplate) { const matches = descriptorTemplate.match(/[asctdvjnlu]+:/g) || []; return matches.some(match => match.includes('a')); } /** * This class encapsulates the APDU protocol documented at * https://github.com/LedgerHQ/app-bitcoin-new/blob/master/doc/bitcoin.md */ class AppClient { constructor(transport) { this.transport = transport; } async makeRequest(ins, data, cci) { let response = await this.transport.send(CLA_BTC, ins, 0, CURRENT_PROTOCOL_VERSION, 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) } /** * Returns an object containing the currently running app's name, version and the device status flags. * * @returns an object with app name, version and device status flags. */ async getAppAndVersion() { const r = await this.transport.send(0xb0, 0x01, 0x00, 0x00); let i = 0; const format = r[i++]; if (format !== 1) throw new Error("Unexpected response"); const nameLength = r[i++]; const name = r.slice(i, (i += nameLength)).toString("ascii"); const versionLength = r[i++]; const version = r.slice(i, (i += versionLength)).toString("ascii"); const flagLength = r[i++]; const flags = r.slice(i, (i += flagLength)); return { name, version, flags, }; } ; /** * Requests the BIP-32 extended pubkey to the hardware wallet. * If `display` is `false`, only standard paths will be accepted; an error is returned if an unusual path is * requested. * If `display` is `true`, the requested path is shown on screen for user verification; unusual paths can be * requested, and a warning is shown to the user in that case. * * @param path the requested BIP-32 path as a string * @param display `false` to silently retrieve a pubkey for a standard path, `true` to display the path on screen * @returns the base58-encoded serialized extended pubkey (xpub) */ async getExtendedPubkey(path, display = false) { const pathElements = (0, bip32_1.pathStringToArray)(path); 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'); } /** * Registers a `WalletPolicy`, after interactive verification from the user. * On success, after user's approval, this function returns the id (which is the same that can be computed with * `walletPolicy.getid()`), followed by the 32-byte hmac. The client should store the hmac to use it for future * requests to `getWalletAddress` or `signPsbt` using this `WalletPolicy`. * * @param walletPolicy the `WalletPolicy` to register * @returns a pair of two 32-byte arrays: the id of the Wallet Policy, followed by the policy hmac */ async registerWallet(walletPolicy) { await this.validatePolicy(walletPolicy); const clientInterpreter = new clientCommands_1.ClientCommandInterpreter(); clientInterpreter.addKnownWalletPolicy(walletPolicy); const serializedWalletPolicy = walletPolicy.serialize(); const response = await this.makeRequest(BitcoinIns.REGISTER_WALLET, Buffer.concat([ (0, varint_1.createVarint)(serializedWalletPolicy.length), serializedWalletPolicy, ]), clientInterpreter); if (response.length != 64) { throw Error(`Invalid response length. Expected 64 bytes, got ${response.length}`); } return [response.subarray(0, 32), response.subarray(32)]; } /** * Returns the address of `walletPolicy` for the given `change` and `addressIndex`. * * @param walletPolicy the `WalletPolicy` to use * @param walletHMAC the 32-byte hmac returned during wallet registration for a registered policy; otherwise * `null` for a standard policy * @param change `0` for a normal receive address, `1` for a change address * @param addressIndex the address index to retrieve * @param display `True` to show the address on screen, `False` to retrieve it silently * @returns the address, as an ascii string. */ 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'); } await this.validatePolicy(walletPolicy); const clientInterpreter = new clientCommands_1.ClientCommandInterpreter(); clientInterpreter.addKnownWalletPolicy(walletPolicy); 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.getId(), walletHMAC || Buffer.alloc(32, 0), Buffer.from([change]), addressIndexBuffer, ]), clientInterpreter); return response.toString('ascii'); } /** * Signs a psbt using a (standard or registered) `WalletPolicy`. This is an interactive command, as user validation * is necessary using the device's secure screen. * On success, a map of input indexes and signatures is returned. * @param psbt a base64-encoded string, or a psbt in a binary Buffer. Using the `PsbtV2` type is deprecated. * @param walletPolicy the `WalletPolicy` to use for signing * @param walletHMAC the 32-byte hmac obtained during wallet policy registration, or `null` for a standard policy * @param progressCallback optionally, a callback that will be called every time a signature is produced during * the signing process. The callback does not receive any argument, but can be used to track progress. * @returns an array of of tuples with 2 elements containing: * - the index of the input being signed; * - an instance of PartialSignature */ async signPsbt(psbt, walletPolicy, walletHMAC, progressCallback) { await this.validatePolicy(walletPolicy); if (typeof psbt === 'string') { psbt = Buffer.from(psbt, "base64"); } if (Buffer.isBuffer(psbt)) { const psbtObj = new psbtv2_1.PsbtV2(); psbtObj.deserialize(psbt); psbt = psbtObj; } 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.addKnownWalletPolicy(walletPolicy); 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.getId(), walletHMAC || Buffer.alloc(32, 0), ]), clientInterpreter); const yielded = clientInterpreter.getYielded(); const ret = []; for (const inputAndSig of yielded) { // inputAndSig contains: // <inputIndex : varint> <pubkeyLen : 1 byte> <pubkey : pubkeyLen bytes (32 or 33)> <signature : variable length> const [inputIndex, inputIndexLen] = (0, varint_1.parseVarint)(inputAndSig, 0); const pubkeyAugmLen = inputAndSig[inputIndexLen]; const pubkeyAugm = inputAndSig.subarray(inputIndexLen + 1, inputIndexLen + 1 + pubkeyAugmLen); const signature = inputAndSig.subarray(inputIndexLen + 1 + pubkeyAugmLen); const partialSig = makePartialSignature(pubkeyAugm, signature); ret.push([Number(inputIndex), partialSig]); } return ret; } /** * Returns the fingerprint of the master public key, as per BIP-32 standard. * @returns the master key fingerprint as a string of 8 hexadecimal digits. */ async getMasterFingerprint() { const fpr = await this.makeRequest(BitcoinIns.GET_MASTER_FINGERPRINT, Buffer.from([])); return fpr.toString("hex"); } /** * Signs a message using the legacy Bitcoin Message Signing standard. The signed message is * the double-sha256 hash of the concatenation of: * - "\x18Bitcoin Signed Message:\n"; * - the length of `message`, encoded as a Bitcoin-style variable length integer; * - `message`. * * @param message the serialized message to sign * @param path the BIP-32 path of the key used to sign the message * @returns base64-encoded signature of the message. */ async signMessage(message, path) { const pathElements = (0, bip32_1.pathStringToArray)(path); 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 result = await this.makeRequest(BitcoinIns.SIGN_MESSAGE, Buffer.concat([ (0, bip32_1.pathElementsToBuffer)(pathElements), (0, varint_1.createVarint)(message.length), chunksRoot, ]), clientInterpreter); return result.toString('base64'); } /* Performs any additional checks on the policy before using it.*/ async validatePolicy(walletPolicy) { if (containsA(walletPolicy.descriptorTemplate)) { const appAndVer = await this.getAppAndVersion(); if (["2.1.0", "2.1.1"].includes(appAndVer.version)) { // Versions 2.1.0 and 2.1.1 produced incorrect scripts for policies containing // the `a:` fragment. throw new Error("Please update your Ledger Bitcoin app."); } } } } exports.AppClient = AppClient; exports.default = AppClient; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwQ2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2xpYi9hcHBDbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBRUEsbUNBQWtFO0FBQ2xFLHFEQUE0RDtBQUM1RCxxREFBa0Q7QUFDbEQscUNBQTRDO0FBRTVDLHFDQUFrQztBQUNsQyxxQ0FBcUQ7QUFFckQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDO0FBQ3JCLE1BQU0sYUFBYSxHQUFHLElBQUksQ0FBQztBQUUzQixNQUFNLHdCQUF3QixHQUFHLENBQUMsQ0FBQyxDQUFDLDBDQUEwQztBQUU5RSxJQUFLLFVBT0o7QUFQRCxXQUFLLFVBQVU7SUFDYix1REFBaUIsQ0FBQTtJQUNqQixpRUFBc0IsQ0FBQTtJQUN0Qix1RUFBeUIsQ0FBQTtJQUN6QixxREFBZ0IsQ0FBQTtJQUNoQiwrRUFBNkIsQ0FBQTtJQUM3Qiw0REFBbUIsQ0FBQTtBQUNyQixDQUFDLEVBUEksVUFBVSxLQUFWLFVBQVUsUUFPZDtBQUVELElBQUssWUFFSjtBQUZELFdBQUssWUFBWTtJQUNmLCtFQUEyQixDQUFBO0FBQzdCLENBQUMsRUFGSSxZQUFZLEtBQVosWUFBWSxRQUVoQjtBQUVEOzs7OztHQUtHO0FBQ0gsTUFBYSxnQkFBZ0I7SUFLM0IsWUFBWSxNQUFjLEVBQUUsU0FBaUIsRUFBRSxXQUFvQjtRQUNqRSxJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztRQUNyQixJQUFJLENBQUMsU0FBUyxHQUFHLFNBQVMsQ0FBQztRQUMzQixJQUFJLENBQUMsV0FBVyxHQUFHLFdBQVcsQ0FBQztJQUNqQyxDQUFDO0NBQ0Y7QUFWRCw0Q0FVQztBQUVEOzs7OztHQUtHO0FBQ0gsU0FBUyxvQkFBb0IsQ0FBQyxVQUFrQixFQUFFLFNBQWlCO0lBQ2pFLElBQUksVUFBVSxDQUFDLE1BQU0sSUFBSSxFQUFFLEVBQUU7UUFDM0IscUZBQXFGO1FBQ3JGLE9BQU8sSUFBSSxnQkFBZ0IsQ0FB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