UNPKG

@tomo-inc/ledger-bitcoin-babylon

Version:

Ledger Hardware Wallet Babylon Application Client

375 lines 33 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AppClient = exports.PartialSignature = void 0; const descriptors = __importStar(require("@bitcoinerlab/descriptors")); const secp256k1 = __importStar(require("@bitcoinerlab/secp256k1")); const { Descriptor } = descriptors.DescriptorsFactory(secp256k1); const bitcoinjs_lib_1 = require("bitcoinjs-lib"); 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.`); } } /** * 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) { 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}`); } const walletId = response.subarray(0, 32); const walletHMAC = response.subarray(32); // sanity check: derive and validate the first address with a 3rd party const firstAddrDevice = await this.getWalletAddress(walletPolicy, walletHMAC, 0, 0, false); await this.validateAddress(firstAddrDevice, walletPolicy, 0, 0); return [walletId, walletHMAC]; } /** * 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'); } 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); const address = response.toString('ascii'); await this.validateAddress(address, walletPolicy, change, addressIndex); return address; } /** * 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) { 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 check on the generated address before returning it.*/ async validateAddress(address, walletPolicy, change, addressIndex) { if (change !== 0 && change !== 1) throw new Error('Change can only be 0 or 1'); const isChange = change === 1; if (addressIndex < 0 || !Number.isInteger(addressIndex)) throw new Error('Invalid address index'); const appAndVer = await this.getAppAndVersion(); let network; if (appAndVer.name === 'Babylon BTC Test') { network = bitcoinjs_lib_1.networks.testnet; } else if (appAndVer.name === 'Babylon BTC Staking') { network = bitcoinjs_lib_1.networks.bitcoin; } else { throw new Error(`Invalid app: ${appAndVer.name}. Expected 'Babylon BTC Test' or 'Babylon BTC Staking'.`); } let expression = walletPolicy.descriptorTemplate; // Replace change: expression = expression.replace(/\/\*\*/g, `/<0;1>/*`); const regExpMN = new RegExp(`/<(\\d+);(\\d+)>`, 'g'); let matchMN; while ((matchMN = regExpMN.exec(expression)) !== null) { const [M, N] = [parseInt(matchMN[1], 10), parseInt(matchMN[2], 10)]; expression = expression.replace(`/<${M};${N}>`, `/${isChange ? N : M}`); } // Replace index: expression = expression.replace(/\/\*/g, `/${addressIndex}`); // Replace origin in reverse order to prevent // misreplacements, e.g., @10 being mistaken for @1 and leaving a 0. for (let i = walletPolicy.keys.length - 1; i >= 0; i--) expression = expression.replace(new RegExp(`@${i}`, 'g'), walletPolicy.keys[i]); let thirdPartyValidationApplicable = true; let thirdPartyGeneratedAddress; try { thirdPartyGeneratedAddress = new Descriptor({ expression, network }).getAddress(); } catch (err) { // Note: @bitcoinerlab/descriptors@1.0.x does not support Tapscript yet. // These are the supported descriptors: // - pkh(KEY) // - wpkh(KEY) // - sh(wpkh(KEY)) // - sh(SCRIPT) // - wsh(SCRIPT) // - sh(wsh(SCRIPT)), where // SCRIPT is any of the (non-tapscript) fragments in: https://bitcoin.sipa.be/miniscript/ // // Other expressions are not supported and third party validation would not be applicable: thirdPartyValidationApplicable = false; } if (thirdPartyValidationApplicable && address !== thirdPartyGeneratedAddress) throw new Error(`Third party address validation mismatch: ${address} != ${thirdPartyGeneratedAddress}`); } } exports.AppClient = AppClient; exports.default = AppClient; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwQ2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2xpYi9hcHBDbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSx1RUFBeUQ7QUFDekQsbUVBQXFEO0FBQ3JELE1BQU0sRUFBRSxVQUFVLEVBQUUsR0FBRyxXQUFXLENBQUMsa0JBQWtCLENBQUMsU0FBUyxDQUFDLENBQUM7QUFFakUsaURBQXlDO0FBRXpDLG1DQUFrRTtBQUNsRSxxREFBNEQ7QUFDNUQscURBQWtEO0FBQ2xELHFDQUE0QztBQUU1QyxxQ0FBa0M7QUFDbEMscUNBQXFEO0FBRXJELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQztBQUNyQixNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUM7QUFFM0IsTUFBTSx3QkFBd0IsR0FBRyxDQUFDLENBQUMsQ0FBQywwQ0FBMEM7QUFFOUUsSUFBSyxVQU9KO0FBUEQsV0FBSyxVQUFVO0lBQ2IsdURBQWlCLENBQUE7SUFDakIsaUVBQXNCLENBQUE7SUFDdEIsdUVBQXlCLENBQUE7SUFDekIscURBQWdCLENBQUE7SUFDaEIsK0VBQTZCLENBQUE7SUFDN0IsNERBQW1CLENBQUE7QUFDckIsQ0FBQyxFQVBJLFVBQVUsS0FBVixVQUFVLFFBT2Q7QUFFRCxJQUFLLFlBRUo7QUFGRCxXQUFLLFlBQVk7SUFDZiwrRUFBMkIsQ0FBQTtBQUM3QixDQUFDLEVBRkksWUFBWSxLQUFaLFlBQVksUUFFaEI7QUFFRDs7Ozs7R0FLRztBQUNILE1BQWEsZ0JBQWdCO0lBSzNCLFlBQVksTUFBYyxFQUFFLFNBQWlCLEVBQUUsV0FBb0I7UUFDakUsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFDckIsSUFBSSxDQUFDLFNBQVMsR0FBRyxTQUFTLENBQUM7UUFDM0IsSUFBSSxDQUFDLFdBQVcsR0FBRyxXQUFXLENBQUM7SUFDakMsQ0FBQztDQUNGO0FBVkQsNENBVUM7QUFF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