@ledgerhq/hw-app-btc
Version:
Ledger Hardware Wallet Bitcoin Application API
167 lines • 7.57 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getScriptPubKeyFromNonWitnessUtxo = getScriptPubKeyFromNonWitnessUtxo;
exports.getInputScriptPubKey = getInputScriptPubKey;
exports.fixDerivationFingerprints = fixDerivationFingerprints;
exports.fixExistingDerivationFingerprints = fixExistingDerivationFingerprints;
exports.setElementBip32DerivationData = setElementBip32DerivationData;
exports.populateMissingBip32Derivations = populateMissingBip32Derivations;
const psbtv2_1 = require("@ledgerhq/psbtv2");
const bip32_1 = require("../bip32");
const accounttype_1 = require("../newops/accounttype");
const derivationAccessors_1 = require("./derivationAccessors");
const accountTypeResolver_1 = require("./accountTypeResolver");
/**
* Extracts the scriptPubKey from a non-witness UTXO by parsing the previous transaction.
*/
function getScriptPubKeyFromNonWitnessUtxo(psbt, inputIndex) {
const nonWitnessUtxo = psbt.getInputNonWitnessUtxo(inputIndex);
if (!nonWitnessUtxo) {
return undefined;
}
try {
const outputIndex = psbt.getInputOutputIndex(inputIndex);
const reader = new psbtv2_1.BufferReader(nonWitnessUtxo);
reader.readSlice(4);
const marker = reader.readUInt8();
if (marker === 0) {
const flag = reader.readUInt8();
if (flag === 0) {
return undefined;
}
}
else {
reader.offset -= 1;
}
const inputCount = reader.readVarInt();
for (let i = 0; i < inputCount; i++) {
reader.readSlice(32);
reader.readSlice(4);
const scriptLen = reader.readVarInt();
reader.readSlice(scriptLen);
reader.readSlice(4);
}
const outputCount = reader.readVarInt();
for (let i = 0; i < outputCount; i++) {
reader.readSlice(8);
const scriptLen = reader.readVarInt();
const scriptPubKey = reader.readSlice(scriptLen);
if (i === outputIndex) {
return scriptPubKey;
}
}
return undefined;
}
catch {
return undefined;
}
}
/**
* Extracts the scriptPubKey from an input's UTXO data.
*/
function getInputScriptPubKey(psbt, inputIndex) {
const witnessUtxo = psbt.getInputWitnessUtxo(inputIndex);
if (witnessUtxo) {
return witnessUtxo.scriptPubKey;
}
return getScriptPubKeyFromNonWitnessUtxo(psbt, inputIndex);
}
/**
* Generic method to fix BIP32 derivations with wrong master fingerprint.
*/
async function fixDerivationFingerprints(client, accessors, elementIndex, masterFp) {
const keyDatas = accessors.getKeyDatas(elementIndex, accessors.bip32KeyType);
for (const existingPubkey of keyDatas) {
const derivationInfo = accessors.getBip32Derivation(elementIndex, existingPubkey);
if (!derivationInfo)
continue;
if (derivationInfo.masterFingerprint.equals(masterFp))
continue;
const path = derivationInfo.path;
const xpub = await client.getExtendedPubkey(false, path);
const derivedPubkey = (0, bip32_1.pubkeyFromXpub)(xpub);
if (existingPubkey.equals(derivedPubkey)) {
accessors.setBip32Derivation(elementIndex, existingPubkey, masterFp, path);
return true;
}
}
const tapKeyDatas = accessors.getKeyDatas(elementIndex, accessors.tapBip32KeyType);
for (const existingPubkey of tapKeyDatas) {
const derivationInfo = accessors.getTapBip32Derivation(elementIndex, existingPubkey);
if (!derivationInfo || derivationInfo.masterFingerprint.equals(masterFp))
continue;
const path = derivationInfo.path;
const xpub = await client.getExtendedPubkey(false, path);
const derivedPubkey = (0, bip32_1.pubkeyFromXpub)(xpub);
const xonlyDerived = derivedPubkey.subarray(1);
if (existingPubkey.equals(xonlyDerived)) {
accessors.setTapBip32Derivation(elementIndex, existingPubkey, derivationInfo.hashes, masterFp, path);
return true;
}
}
return false;
}
async function fixExistingDerivationFingerprints(client, psbt, elementIndex, masterFp, elementType) {
const accessors = (0, derivationAccessors_1.getDerivationAccessors)(psbt, elementType);
return fixDerivationFingerprints(client, accessors, elementIndex, masterFp);
}
/**
* Generic method to set BIP32 derivation data on an input or output.
*/
function setElementBip32DerivationData(accessors, elementIndex, pubkey, masterFp, path, accountType) {
if (accountType instanceof accounttype_1.p2tr) {
const xonlyPubkey = pubkey.subarray(1);
accessors.setTapBip32Derivation(elementIndex, xonlyPubkey, [], masterFp, path);
}
else {
accessors.setBip32Derivation(elementIndex, pubkey, masterFp, path);
}
}
async function collectElementsNeedingLocalScan(client, psbt, count, masterFp, elementType, hasDerivation) {
const indices = [];
for (let i = 0; i < count; i++) {
if (hasDerivation(i))
continue;
const fixed = await fixExistingDerivationFingerprints(client, psbt, i, masterFp, elementType);
if (!fixed)
indices.push(i);
}
return indices;
}
function applyLookupToElements(psbt, indices, lookupMap, masterFp, elementType, getScriptPubKey) {
const accessors = (0, derivationAccessors_1.getDerivationAccessors)(psbt, elementType);
for (const elementIndex of indices) {
const scriptPubKey = getScriptPubKey(elementIndex);
if (!scriptPubKey)
continue;
const extracted = (0, psbtv2_1.extractHashFromScriptPubKey)(scriptPubKey);
if (!extracted)
continue;
const result = lookupMap.get(extracted.hashHex);
if (result) {
const accountType = (0, accountTypeResolver_1.createAccountTypeFromScriptType)(extracted.scriptType, psbt, masterFp);
setElementBip32DerivationData(accessors, elementIndex, result.pubkey, masterFp, result.path, accountType);
}
}
}
/**
* Populates missing BIP32 derivations using the provided known-address map.
* The map must be built by the caller from the wallet's known addresses (e.g. receive/change).
*/
async function populateMissingBip32Derivations(client, psbt, inputCount, masterFp, accountPath, knownAddressDerivations) {
const accountType = (0, accountTypeResolver_1.determineAccountTypeFromPurpose)(accountPath, psbt, masterFp);
if (!accountType)
return;
const outputCount = psbt.getGlobalOutputCount();
const [inputsNeedingLocalScan, outputsNeedingLocalScan] = await Promise.all([
collectElementsNeedingLocalScan(client, psbt, inputCount, masterFp, "input", i => (0, derivationAccessors_1.checkBip32Derivation)(psbt, i, masterFp).belongsToSigner),
collectElementsNeedingLocalScan(client, psbt, outputCount, masterFp, "output", i => (0, derivationAccessors_1.checkOutputBip32Derivation)(psbt, i, masterFp)),
]);
if (inputsNeedingLocalScan.length === 0 && outputsNeedingLocalScan.length === 0)
return;
if (knownAddressDerivations.size === 0)
return;
applyLookupToElements(psbt, inputsNeedingLocalScan, knownAddressDerivations, masterFp, "input", i => getInputScriptPubKey(psbt, i));
applyLookupToElements(psbt, outputsNeedingLocalScan, knownAddressDerivations, masterFp, "output", i => psbt.getOutputScript(i));
}
//# sourceMappingURL=derivationPopulation.js.map