@ledgerhq/hw-app-btc
Version:
Ledger Hardware Wallet Bitcoin Application API
204 lines • 8.56 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.p2wpkh = exports.p2wpkhWrapped = exports.p2tr = exports.p2pkh = void 0;
exports.computeTaprootOutputKey = computeTaprootOutputKey;
const bitcoinjs_lib_1 = require("bitcoinjs-lib");
const secp256k1_1 = require("@noble/curves/secp256k1");
const psbtv2_1 = require("@ledgerhq/psbtv2");
const constants_1 = require("../constants");
const hashPublicKey_1 = require("../hashPublicKey");
// Helper function to convert bytes to bigint for scalar operations
function bytesToBigInt(bytes) {
const hex = Array.from(bytes)
.map(b => b.toString(16).padStart(2, "0"))
.join("");
return BigInt("0x" + hex);
}
// Replacement for pointAddScalar from tiny-secp256k1
function pointAddScalar(point, scalar) {
try {
const p = secp256k1_1.secp256k1.ProjectivePoint.fromHex(point);
const s = bytesToBigInt(scalar);
const result = p.add(secp256k1_1.secp256k1.ProjectivePoint.BASE.multiply(s));
return result.toRawBytes(point.length === 33);
}
catch {
return null;
}
}
/**
* Computes the taproot output key (BIP341) from an internal x-only pubkey.
* Standalone so derivation population can build a hash-based lookup without AccountType.
*/
function computeTaprootOutputKey(internalXonlyPubkey) {
if (internalXonlyPubkey.length !== 32) {
throw new Error("Expected 32 byte pubkey. Got " + internalXonlyPubkey.length);
}
const h = bitcoinjs_lib_1.crypto.sha256(Buffer.from("TapTweak", "utf-8"));
const tweak = bitcoinjs_lib_1.crypto.sha256(Buffer.concat([h, h, internalXonlyPubkey]));
const evenEcdsaPubkey = Buffer.concat([Buffer.from([0x02]), internalXonlyPubkey]);
const tweakedKey = pointAddScalar(evenEcdsaPubkey, tweak);
if (!tweakedKey)
throw new Error("Point addition failed");
return Buffer.from(tweakedKey).subarray(1);
}
class BaseAccount {
psbt;
masterFp;
constructor(psbt, masterFp) {
this.psbt = psbt;
this.masterFp = masterFp;
}
}
/**
* Superclass for single signature accounts. This will make sure that the pubkey
* arrays and path arrays in the method arguments contains exactly one element
* and calls an abstract method to do the actual work.
*/
class SingleKeyAccount extends BaseAccount {
spendingCondition(pubkeys) {
if (pubkeys.length != 1) {
throw new Error("Expected single key, got " + pubkeys.length);
}
return this.singleKeyCondition(pubkeys[0]);
}
setInput(i, inputTx, spentOutput, pubkeys, pathElems) {
if (pubkeys.length != 1) {
throw new Error("Expected single key, got " + pubkeys.length);
}
if (pathElems.length != 1) {
throw new Error("Expected single path, got " + pathElems.length);
}
this.setSingleKeyInput(i, inputTx, spentOutput, pubkeys[0], pathElems[0]);
}
setOwnOutput(i, cond, pubkeys, paths) {
if (pubkeys.length != 1) {
throw new Error("Expected single key, got " + pubkeys.length);
}
if (paths.length != 1) {
throw new Error("Expected single path, got " + paths.length);
}
this.setSingleKeyOutput(i, cond, pubkeys[0], paths[0]);
}
}
class p2pkh extends SingleKeyAccount {
singleKeyCondition(pubkey) {
const buf = new psbtv2_1.BufferWriter();
const pubkeyHash = (0, hashPublicKey_1.hashPublicKey)(pubkey);
buf.writeSlice(Buffer.from([constants_1.OP_DUP, constants_1.OP_HASH160, constants_1.HASH_SIZE]));
buf.writeSlice(pubkeyHash);
buf.writeSlice(Buffer.from([constants_1.OP_EQUALVERIFY, constants_1.OP_CHECKSIG]));
return { scriptPubKey: buf.buffer() };
}
setSingleKeyInput(i, inputTx, _spentOutput, pubkey, path) {
if (!inputTx) {
throw new Error("Full input base transaction required");
}
this.psbt.setInputNonWitnessUtxo(i, inputTx);
this.psbt.setInputBip32Derivation(i, pubkey, this.masterFp, path);
}
setSingleKeyOutput(i, _cond, pubkey, path) {
this.psbt.setOutputBip32Derivation(i, pubkey, this.masterFp, path);
}
getDescriptorTemplate() {
return "pkh(@0/**)";
}
}
exports.p2pkh = p2pkh;
class p2tr extends SingleKeyAccount {
singleKeyCondition(pubkey) {
const xonlyPubkey = pubkey.subarray(1); // x-only pubkey
const buf = new psbtv2_1.BufferWriter();
const outputKey = this.getTaprootOutputKey(xonlyPubkey);
buf.writeSlice(Buffer.from([0x51, 32])); // push1, pubkeylen
buf.writeSlice(outputKey);
return { scriptPubKey: buf.buffer() };
}
setSingleKeyInput(i, _inputTx, spentOutput, pubkey, path) {
const xonly = pubkey.subarray(1);
this.psbt.setInputTapBip32Derivation(i, xonly, [], this.masterFp, path);
this.psbt.setInputWitnessUtxo(i, spentOutput.amount, spentOutput.cond.scriptPubKey);
}
setSingleKeyOutput(i, _cond, pubkey, path) {
const xonly = pubkey.subarray(1);
this.psbt.setOutputTapBip32Derivation(i, xonly, [], this.masterFp, path);
}
getDescriptorTemplate() {
return "tr(@0/**)";
}
/**
* Calculates a taproot output key from an internal key (BIP341).
*
* @param internalPubkey A 32 byte x-only taproot internal key
* @returns The output key
*/
getTaprootOutputKey(internalPubkey) {
return computeTaprootOutputKey(internalPubkey);
}
}
exports.p2tr = p2tr;
class p2wpkhWrapped extends SingleKeyAccount {
singleKeyCondition(pubkey) {
const buf = new psbtv2_1.BufferWriter();
const redeemScript = this.createRedeemScript(pubkey);
const scriptHash = (0, hashPublicKey_1.hashPublicKey)(redeemScript);
buf.writeSlice(Buffer.from([constants_1.OP_HASH160, constants_1.HASH_SIZE]));
buf.writeSlice(scriptHash);
buf.writeUInt8(constants_1.OP_EQUAL);
return { scriptPubKey: buf.buffer(), redeemScript: redeemScript };
}
setSingleKeyInput(i, inputTx, spentOutput, pubkey, path) {
if (!inputTx) {
throw new Error("Full input base transaction required");
}
this.psbt.setInputNonWitnessUtxo(i, inputTx);
this.psbt.setInputBip32Derivation(i, pubkey, this.masterFp, path);
const userSuppliedRedeemScript = spentOutput.cond.redeemScript;
const expectedRedeemScript = this.createRedeemScript(pubkey);
if (userSuppliedRedeemScript && !expectedRedeemScript.equals(userSuppliedRedeemScript)) {
// At what point might a user set the redeemScript on its own?
throw new Error(`User-supplied redeemScript ${userSuppliedRedeemScript.toString("hex")} doesn't
match expected ${expectedRedeemScript.toString("hex")} for input ${i}`);
}
this.psbt.setInputRedeemScript(i, expectedRedeemScript);
this.psbt.setInputWitnessUtxo(i, spentOutput.amount, spentOutput.cond.scriptPubKey);
}
setSingleKeyOutput(i, cond, pubkey, path) {
if (cond.redeemScript)
this.psbt.setOutputRedeemScript(i, cond.redeemScript);
this.psbt.setOutputBip32Derivation(i, pubkey, this.masterFp, path);
}
getDescriptorTemplate() {
return "sh(wpkh(@0/**))";
}
createRedeemScript(pubkey) {
const pubkeyHash = (0, hashPublicKey_1.hashPublicKey)(pubkey);
return Buffer.concat([Buffer.from("0014", "hex"), pubkeyHash]);
}
}
exports.p2wpkhWrapped = p2wpkhWrapped;
class p2wpkh extends SingleKeyAccount {
singleKeyCondition(pubkey) {
const buf = new psbtv2_1.BufferWriter();
const pubkeyHash = (0, hashPublicKey_1.hashPublicKey)(pubkey);
buf.writeSlice(Buffer.from([0, constants_1.HASH_SIZE]));
buf.writeSlice(pubkeyHash);
return { scriptPubKey: buf.buffer() };
}
setSingleKeyInput(i, inputTx, spentOutput, pubkey, path) {
if (!inputTx) {
throw new Error("Full input base transaction required");
}
this.psbt.setInputNonWitnessUtxo(i, inputTx);
this.psbt.setInputBip32Derivation(i, pubkey, this.masterFp, path);
this.psbt.setInputWitnessUtxo(i, spentOutput.amount, spentOutput.cond.scriptPubKey);
}
setSingleKeyOutput(i, cond, pubkey, path) {
this.psbt.setOutputBip32Derivation(i, pubkey, this.masterFp, path);
}
getDescriptorTemplate() {
return "wpkh(@0/**)";
}
}
exports.p2wpkh = p2wpkh;
//# sourceMappingURL=accounttype.js.map