UNPKG

@ledgerhq/hw-app-btc

Version:
204 lines • 8.56 kB
"use strict"; 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