ledger-bitcoin
Version:
Ledger Hardware Wallet Bitcoin Application Client
303 lines • 26.9 kB
JavaScript
;
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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