@ledgerhq/hw-app-btc
Version:
Ledger Hardware Wallet Bitcoin Application API
94 lines (86 loc) • 3.51 kB
text/typescript
import { PsbtV2, psbtIn, psbtOut } from "@ledgerhq/psbtv2";
import type { DerivationAccessors, DerivationElementType } from "./types";
/**
* Returns accessors for BIP32 derivation operations based on element type.
*/
export function getDerivationAccessors(
psbt: PsbtV2,
type: DerivationElementType,
): DerivationAccessors {
if (type === "input") {
return {
getKeyDatas: (i, kt) => psbt.getInputKeyDatas(i, kt),
getBip32Derivation: (i, pk) => psbt.getInputBip32Derivation(i, pk),
getTapBip32Derivation: (i, pk) => psbt.getInputTapBip32Derivation(i, pk),
setBip32Derivation: (i, pk, fp, p) => psbt.setInputBip32Derivation(i, pk, fp, p),
setTapBip32Derivation: (i, pk, h, fp, p) => psbt.setInputTapBip32Derivation(i, pk, h, fp, p),
bip32KeyType: psbtIn.BIP32_DERIVATION,
tapBip32KeyType: psbtIn.TAP_BIP32_DERIVATION,
};
}
return {
getKeyDatas: (i, kt) => psbt.getOutputKeyDatas(i, kt),
getBip32Derivation: (i, pk) => psbt.getOutputBip32Derivation(i, pk),
getTapBip32Derivation: (i, pk) => psbt.getOutputTapBip32Derivation(i, pk),
setBip32Derivation: (i, pk, fp, p) => psbt.setOutputBip32Derivation(i, pk, fp, p),
setTapBip32Derivation: (i, pk, h, fp, p) => psbt.setOutputTapBip32Derivation(i, pk, h, fp, p),
bip32KeyType: psbtOut.BIP_32_DERIVATION,
tapBip32KeyType: psbtOut.TAP_BIP32_DERIVATION,
};
}
/**
* Generic method to check BIP32 derivation for either an input or output.
*/
export function checkElementBip32Derivation(
accessors: DerivationAccessors,
elementIndex: number,
masterFp: Buffer,
): { belongsToSigner: boolean; accountPath: number[] } {
const keyDatas = accessors.getKeyDatas(elementIndex, accessors.bip32KeyType);
for (const pubkey of keyDatas) {
const derivationInfo = accessors.getBip32Derivation(elementIndex, pubkey);
if (derivationInfo?.masterFingerprint.equals(masterFp)) {
return extractAccountPath(derivationInfo.path);
}
}
const tapKeyDatas = accessors.getKeyDatas(elementIndex, accessors.tapBip32KeyType);
for (const pubkey of tapKeyDatas) {
const derivationInfo = accessors.getTapBip32Derivation(elementIndex, pubkey);
if (derivationInfo?.masterFingerprint.equals(masterFp)) {
return extractAccountPath(derivationInfo.path);
}
}
return { belongsToSigner: false, accountPath: [] };
}
/**
* Returns belongsToSigner: true because this function is only called after
* a master fingerprint match against the connected signer (hardware wallet).
* This is unrelated to the BIP44 "internal chain" (change = 1) concept.
*/
export function extractAccountPath(fullPath: number[]): {
belongsToSigner: true;
accountPath: number[];
} {
const accountPath = fullPath.length >= 2 ? fullPath.slice(0, -2) : [];
return { belongsToSigner: true, accountPath };
}
export function checkBip32Derivation(
psbt: PsbtV2,
inputIndex: number,
masterFp: Buffer,
): { belongsToSigner: boolean; accountPath: number[] } {
const accessors = getDerivationAccessors(psbt, "input");
return checkElementBip32Derivation(accessors, inputIndex, masterFp);
}
/**
* Checks if an output has a valid BIP32 derivation with the correct master fingerprint.
*/
export function checkOutputBip32Derivation(
psbt: PsbtV2,
outputIndex: number,
masterFp: Buffer,
): boolean {
const accessors = getDerivationAccessors(psbt, "output");
const result = checkElementBip32Derivation(accessors, outputIndex, masterFp);
return result.belongsToSigner;
}