@tomo-inc/ledger-bitcoin-babylon
Version:
Ledger Hardware Wallet Babylon Application Client
375 lines • 33 kB
JavaScript
;
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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