UNPKG

@ledgerhq/hw-app-btc

Version:
159 lines • 7.01 kB
import { BufferReader, extractHashFromScriptPubKey } from "@ledgerhq/psbtv2"; import { pubkeyFromXpub } from "../bip32"; import { p2tr } from "../newops/accounttype"; import { getDerivationAccessors, checkBip32Derivation, checkOutputBip32Derivation, } from "./derivationAccessors"; import { createAccountTypeFromScriptType, determineAccountTypeFromPurpose, } from "./accountTypeResolver"; /** * Extracts the scriptPubKey from a non-witness UTXO by parsing the previous transaction. */ export function getScriptPubKeyFromNonWitnessUtxo(psbt, inputIndex) { const nonWitnessUtxo = psbt.getInputNonWitnessUtxo(inputIndex); if (!nonWitnessUtxo) { return undefined; } try { const outputIndex = psbt.getInputOutputIndex(inputIndex); const reader = new 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. */ export 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. */ export 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 = 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 = pubkeyFromXpub(xpub); const xonlyDerived = derivedPubkey.subarray(1); if (existingPubkey.equals(xonlyDerived)) { accessors.setTapBip32Derivation(elementIndex, existingPubkey, derivationInfo.hashes, masterFp, path); return true; } } return false; } export async function fixExistingDerivationFingerprints(client, psbt, elementIndex, masterFp, elementType) { const accessors = getDerivationAccessors(psbt, elementType); return fixDerivationFingerprints(client, accessors, elementIndex, masterFp); } /** * Generic method to set BIP32 derivation data on an input or output. */ export function setElementBip32DerivationData(accessors, elementIndex, pubkey, masterFp, path, accountType) { if (accountType instanceof 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 = getDerivationAccessors(psbt, elementType); for (const elementIndex of indices) { const scriptPubKey = getScriptPubKey(elementIndex); if (!scriptPubKey) continue; const extracted = extractHashFromScriptPubKey(scriptPubKey); if (!extracted) continue; const result = lookupMap.get(extracted.hashHex); if (result) { const accountType = 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). */ export async function populateMissingBip32Derivations(client, psbt, inputCount, masterFp, accountPath, knownAddressDerivations) { const accountType = determineAccountTypeFromPurpose(accountPath, psbt, masterFp); if (!accountType) return; const outputCount = psbt.getGlobalOutputCount(); const [inputsNeedingLocalScan, outputsNeedingLocalScan] = await Promise.all([ collectElementsNeedingLocalScan(client, psbt, inputCount, masterFp, "input", i => checkBip32Derivation(psbt, i, masterFp).belongsToSigner), collectElementsNeedingLocalScan(client, psbt, outputCount, masterFp, "output", i => 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