From 926327d72d21441c21a0493e02c3752e1d441cab Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Thu, 5 Dec 2024 17:51:43 +0100 Subject: [PATCH] feat: modify private key handler to match the abstract class --- src/dlc-handlers/private-key-dlc-handler.ts | 334 ++++---------------- 1 file changed, 56 insertions(+), 278 deletions(-) diff --git a/src/dlc-handlers/private-key-dlc-handler.ts b/src/dlc-handlers/private-key-dlc-handler.ts index 26d16a1..19c85a8 100644 --- a/src/dlc-handlers/private-key-dlc-handler.ts +++ b/src/dlc-handlers/private-key-dlc-handler.ts @@ -1,78 +1,43 @@ import { bytesToHex } from '@noble/hashes/utils'; -import { Transaction, p2wpkh } from '@scure/btc-signer'; -import { P2Ret, P2TROut, p2tr } from '@scure/btc-signer/payment'; +import { Transaction } from '@scure/btc-signer'; import { Signer } from '@scure/btc-signer/transaction'; import { BIP32Interface } from 'bip32'; import { Network } from 'bitcoinjs-lib'; -import { bitcoin, regtest, testnet } from 'bitcoinjs-lib/src/networks.js'; import { - createTaprootMultisigPayment, deriveUnhardenedKeyPairFromRootPrivateKey, - deriveUnhardenedPublicKey, - ecdsaPublicKeyToSchnorr, - finalizeUserInputs, - getBalance, - getFeeRate, - getUnspendableKeyCommittedToUUID, + getInputIndicesByScript, } from '../functions/bitcoin/bitcoin-functions.js'; -import { - createDepositTransaction, - createFundingTransaction, - createWithdrawTransaction, -} from '../functions/bitcoin/psbt-functions.js'; import { PaymentInformation } from '../models/bitcoin-models.js'; -import { RawVault } from '../models/ethereum-models.js'; - -interface RequiredKeyPair { - fundingDerivedKeyPair: BIP32Interface; - taprootDerivedKeyPair: BIP32Interface; -} - -export class PrivateKeyDLCHandler { - private derivedKeyPair: RequiredKeyPair; - public payment: PaymentInformation | undefined; - private fundingPaymentType: 'wpkh' | 'tr'; - private bitcoinNetwork: Network; - private bitcoinBlockchainAPI: string; - private bitcoinBlockchainFeeRecommendationAPI: string; +import { + AbstractDLCHandler, + FundingPaymentType, + InvalidTransactionTypeError, + PaymentNotSetError, + PaymentType, + TransactionType, +} from './abstract-dlc-handler.js'; + +export class PrivateKeyDLCHandler extends AbstractDLCHandler { + readonly _dlcHandlerType = 'browser' as const; + protected _payment?: PaymentInformation; + private fundingDerivedKeyPair: BIP32Interface; + private taprootDerivedKeyPair: BIP32Interface; constructor( bitcoinWalletPrivateKey: string, walletAccountIndex: number, - fundingPaymentType: 'wpkh' | 'tr', + fundingPaymentType: FundingPaymentType, bitcoinNetwork: Network, - bitcoinBlockchainAPI?: string, - bitcoinBlockchainFeeRecommendationAPI?: string + bitcoinBlockchainAPI: string, + bitcoinBlockchainFeeRecommendationAPI: string ) { - switch (bitcoinNetwork) { - case bitcoin: - this.bitcoinBlockchainAPI = 'https://mempool.space/api'; - this.bitcoinBlockchainFeeRecommendationAPI = - 'https://mempool.space/api/v1/fees/recommended'; - break; - case testnet: - this.bitcoinBlockchainAPI = 'https://mempool.space/testnet/api'; - this.bitcoinBlockchainFeeRecommendationAPI = - 'https://mempool.space/testnet/api/v1/fees/recommended'; - break; - case regtest: - if ( - bitcoinBlockchainAPI === undefined || - bitcoinBlockchainFeeRecommendationAPI === undefined - ) { - throw new Error( - 'Regtest requires a Bitcoin Blockchain API and a Bitcoin Blockchain Fee Recommendation API' - ); - } - this.bitcoinBlockchainAPI = bitcoinBlockchainAPI; - this.bitcoinBlockchainFeeRecommendationAPI = bitcoinBlockchainFeeRecommendationAPI; - break; - default: - throw new Error('Invalid Bitcoin Network'); - } - this.fundingPaymentType = fundingPaymentType; - this.bitcoinNetwork = bitcoinNetwork; + super( + fundingPaymentType, + bitcoinNetwork, + bitcoinBlockchainAPI, + bitcoinBlockchainFeeRecommendationAPI + ); const fundingDerivedKeyPair = deriveUnhardenedKeyPairFromRootPrivateKey( bitcoinWalletPrivateKey, bitcoinNetwork, @@ -86,250 +51,63 @@ export class PrivateKeyDLCHandler { walletAccountIndex ); - this.derivedKeyPair = { - taprootDerivedKeyPair, - fundingDerivedKeyPair, - }; - } - - private setPayment(fundingPayment: P2Ret | P2TROut, multisigPayment: P2TROut): void { - this.payment = { - fundingPayment, - multisigPayment, - }; - } - - private getPayment(): PaymentInformation { - if (!this.payment) { - throw new Error('Payment Information not set'); - } - return this.payment; - } - - getTaprootDerivedPublicKey(): string { - return bytesToHex(this.derivedKeyPair.taprootDerivedKeyPair.publicKey); + this.fundingDerivedKeyPair = fundingDerivedKeyPair; + this.taprootDerivedKeyPair = taprootDerivedKeyPair; } - getVaultRelatedAddress(paymentType: 'funding' | 'multisig'): string { - const payment = this.payment; - - if (payment === undefined) { - throw new Error('Payment objects have not been set'); + getUserTaprootPublicKey(tweaked: boolean = false): string { + if (!tweaked) { + return bytesToHex(this.taprootDerivedKeyPair.publicKey); } - let address: string; - - switch (paymentType) { - case 'funding': - if (!payment.fundingPayment.address) { - throw new Error('Funding Address is undefined'); - } - address = payment.fundingPayment.address; - return address; - case 'multisig': - if (!payment.multisigPayment.address) { - throw new Error('Taproot Multisig Payment Address is undefined'); - } - address = payment.multisigPayment.address; - return address; - default: - throw new Error('Invalid Payment Type'); - } - } - - private getPrivateKey(paymentType: 'funding' | 'taproot'): Signer { - const privateKey = - paymentType === 'funding' - ? this.derivedKeyPair.fundingDerivedKeyPair.privateKey - : this.derivedKeyPair.taprootDerivedKeyPair.privateKey; - - if (!privateKey) { - throw new Error('Private Key is Undefined'); + if (!this.payment) { + throw new PaymentNotSetError(); } - return privateKey; + return bytesToHex(this.payment.multisigPayment.tweakedPubkey); } - private createPayments(vaultUUID: string, attestorGroupPublicKey: string): PaymentInformation { - try { - const unspendablePublicKey = getUnspendableKeyCommittedToUUID(vaultUUID, this.bitcoinNetwork); - const unspendableDerivedPublicKey = deriveUnhardenedPublicKey( - unspendablePublicKey, - this.bitcoinNetwork - ); - - const attestorDerivedPublicKey = deriveUnhardenedPublicKey( - attestorGroupPublicKey, - this.bitcoinNetwork - ); - - let fundingPayment: P2Ret | P2TROut; - - switch (this.fundingPaymentType) { - case 'wpkh': - fundingPayment = p2wpkh( - this.derivedKeyPair.fundingDerivedKeyPair.publicKey, - this.bitcoinNetwork - ); - break; - case 'tr': - fundingPayment = p2tr( - ecdsaPublicKeyToSchnorr(this.derivedKeyPair.taprootDerivedKeyPair.publicKey), - undefined, - this.bitcoinNetwork - ); - break; - default: - throw new Error('Invalid Funding Payment Type'); - } - - const multisigPayment = createTaprootMultisigPayment( - unspendableDerivedPublicKey, - attestorDerivedPublicKey, - this.derivedKeyPair.taprootDerivedKeyPair.publicKey, - this.bitcoinNetwork - ); - - this.setPayment(fundingPayment, multisigPayment); - - return { - fundingPayment, - multisigPayment, - }; - } catch (error: any) { - throw new Error(`Error creating required Payment objects: ${error}`); - } + getUserFundingPublicKey(): string { + return bytesToHex(this.fundingDerivedKeyPair.publicKey); } - async createFundingPSBT( - vault: RawVault, - bitcoinAmount: bigint, - attestorGroupPublicKey: string, - feeRateMultiplier?: number, - customFeeRate?: bigint - ): Promise { - const { fundingPayment, multisigPayment } = this.createPayments( - vault.uuid, - attestorGroupPublicKey - ); + private getPrivateKey(paymentType: PaymentType): Signer { + const keyPairMap: Record = { + funding: this.fundingDerivedKeyPair, + multisig: this.taprootDerivedKeyPair, + }; - const addressBalance = await getBalance(fundingPayment, this.bitcoinBlockchainAPI); + const keyPair = keyPairMap[paymentType]; - if (BigInt(addressBalance) < vault.valueLocked.toBigInt()) { - throw new Error('Insufficient Funds'); + if (!keyPair?.privateKey) { + throw new Error(`Private key not found for payment type: ${paymentType}`); } - const feeRate = - customFeeRate ?? - BigInt(await getFeeRate(this.bitcoinBlockchainFeeRecommendationAPI, feeRateMultiplier)); - - const fundingTransaction = await createFundingTransaction( - this.bitcoinBlockchainAPI, - this.bitcoinNetwork, - bitcoinAmount, - multisigPayment, - fundingPayment, - feeRate, - vault.btcFeeRecipient, - vault.btcMintFeeBasisPoints.toBigInt() - ); - - return fundingTransaction; + return keyPair.privateKey; } - async createWithdrawPSBT( - vault: RawVault, - withdrawAmount: bigint, - attestorGroupPublicKey: string, - fundingTransactionID: string, - feeRateMultiplier?: number, - customFeeRate?: bigint - ): Promise { - try { - const { fundingPayment, multisigPayment } = this.createPayments( - vault.uuid, - attestorGroupPublicKey - ); - - const feeRate = - customFeeRate ?? - BigInt(await getFeeRate(this.bitcoinBlockchainFeeRecommendationAPI, feeRateMultiplier)); - - const withdrawTransaction = await createWithdrawTransaction( - this.bitcoinBlockchainAPI, - this.bitcoinNetwork, - withdrawAmount, - fundingTransactionID, - multisigPayment, - fundingPayment, - feeRate, - vault.btcFeeRecipient, - vault.btcRedeemFeeBasisPoints.toBigInt() - ); - return withdrawTransaction; - } catch (error: any) { - throw new Error(`Error creating Withdraw PSBT: ${error}`); - } - } - - signPSBT(psbt: Transaction, transactionType: 'funding' | 'deposit' | 'withdraw'): Transaction { + async signPSBT(transaction: Transaction, transactionType: TransactionType): Promise { switch (transactionType) { case 'funding': - psbt.sign(this.getPrivateKey('funding')); - psbt.finalize(); + transaction.sign(this.getPrivateKey('funding')); break; case 'deposit': - try { - psbt.sign(this.getPrivateKey('funding')); - } catch (error: any) { - // this can happen if there are no tr inputs to sign - } - try { - psbt.sign(this.getPrivateKey('taproot')); - } catch (error: any) { - // this can happen if there are no p2wpkh inputs to sign - } - finalizeUserInputs(psbt, this.getPayment().fundingPayment); + getInputIndicesByScript(this.payment.fundingPayment.script, transaction).forEach(index => { + transaction.signIdx(this.getPrivateKey('funding'), index); + }); + getInputIndicesByScript(this.payment.multisigPayment.script, transaction).forEach(index => { + transaction.signIdx(this.getPrivateKey('multisig'), index); + }); break; case 'withdraw': - psbt.sign(this.getPrivateKey('taproot')); + transaction.sign(this.getPrivateKey('multisig')); break; default: - throw new Error('Invalid Transaction Type'); + throw new InvalidTransactionTypeError(transactionType); } - return psbt; - } - - async createDepositPSBT( - depositAmount: bigint, - vault: RawVault, - attestorGroupPublicKey: string, - fundingTransactionID: string, - feeRateMultiplier?: number, - customFeeRate?: bigint - ) { - const { fundingPayment, multisigPayment } = this.createPayments( - vault.uuid, - attestorGroupPublicKey - ); - - const feeRate = - customFeeRate ?? - BigInt(await getFeeRate(this.bitcoinBlockchainFeeRecommendationAPI, feeRateMultiplier)); - - const depositTransaction = await createDepositTransaction( - this.bitcoinBlockchainAPI, - this.bitcoinNetwork, - depositAmount, - fundingTransactionID, - multisigPayment, - fundingPayment, - feeRate, - vault.btcFeeRecipient, - vault.btcMintFeeBasisPoints.toBigInt() - ); + this.finalizeTransaction(transaction, transactionType, this.payment.fundingPayment); - return depositTransaction; + return transaction; } }