diff --git a/src/dlc-handlers/ledger-dlc-handler.ts b/src/dlc-handlers/ledger-dlc-handler.ts index a2a15e3..977e779 100644 --- a/src/dlc-handlers/ledger-dlc-handler.ts +++ b/src/dlc-handlers/ledger-dlc-handler.ts @@ -1,6 +1,6 @@ import { bytesToHex } from '@noble/hashes/utils'; import { Transaction } from '@scure/btc-signer'; -import { P2Ret, P2TROut, p2wpkh } from '@scure/btc-signer/payment'; +import { P2Ret, P2TROut, p2tr, p2wpkh } from '@scure/btc-signer/payment'; import { Network, Psbt } from 'bitcoinjs-lib'; import { bitcoin, regtest, testnet } from 'bitcoinjs-lib/src/networks.js'; import { AppClient, DefaultWalletPolicy, WalletPolicy } from 'ledger-bitcoin'; @@ -9,6 +9,7 @@ import { createBitcoinInputSigningConfiguration, createTaprootMultisigPayment, deriveUnhardenedPublicKey, + ecdsaPublicKeyToSchnorr, getBalance, getFeeRate, getInputByPaymentTypeArray, @@ -30,18 +31,20 @@ import { RawVault } from '../models/ethereum-models.js'; import { truncateAddress } from '../utilities/index.js'; interface LedgerPolicyInformation { - nativeSegwitWalletPolicy: DefaultWalletPolicy; - taprootMultisigWalletPolicy: WalletPolicy; - taprootMultisigWalletPolicyHMac: Buffer; + fundingWalletPolicy: DefaultWalletPolicy; + multisigWalletPolicy: WalletPolicy; + multisigWalletPolicyHMac: Buffer; } export class LedgerDLCHandler { private ledgerApp: AppClient; private masterFingerprint: string; private walletAccountIndex: number; + private fundingPaymentType: 'wpkh' | 'tr'; private policyInformation: LedgerPolicyInformation | undefined; public payment: ExtendedPaymentInformation | undefined; private bitcoinNetwork: Network; + private bitcoinNetworkIndex: number; private bitcoinBlockchainAPI: string; private bitcoinBlockchainFeeRecommendationAPI: string; @@ -49,6 +52,7 @@ export class LedgerDLCHandler { ledgerApp: AppClient, masterFingerprint: string, walletAccountIndex: number, + fundingPaymentType: 'wpkh' | 'tr', bitcoinNetwork: Network, bitcoinBlockchainAPI?: string, bitcoinBlockchainFeeRecommendationAPI?: string @@ -58,11 +62,13 @@ export class LedgerDLCHandler { this.bitcoinBlockchainAPI = 'https://mempool.space/api'; this.bitcoinBlockchainFeeRecommendationAPI = 'https://mempool.space/api/v1/fees/recommended'; + this.bitcoinNetworkIndex = 0; break; case testnet: this.bitcoinBlockchainAPI = 'https://mempool.space/testnet/api'; this.bitcoinBlockchainFeeRecommendationAPI = 'https://mempool.space/testnet/api/v1/fees/recommended'; + this.bitcoinNetworkIndex = 1; break; case regtest: if ( @@ -75,6 +81,7 @@ export class LedgerDLCHandler { } this.bitcoinBlockchainAPI = bitcoinBlockchainAPI; this.bitcoinBlockchainFeeRecommendationAPI = bitcoinBlockchainFeeRecommendationAPI; + this.bitcoinNetworkIndex = 1; break; default: throw new Error('Invalid Bitcoin Network'); @@ -82,30 +89,31 @@ export class LedgerDLCHandler { this.ledgerApp = ledgerApp; this.masterFingerprint = masterFingerprint; this.walletAccountIndex = walletAccountIndex; + this.fundingPaymentType = fundingPaymentType; this.bitcoinNetwork = bitcoinNetwork; } private setPolicyInformation( - nativeSegwitWalletPolicy: DefaultWalletPolicy, - taprootMultisigWalletPolicy: WalletPolicy, - taprootMultisigWalletPolicyHMac: Buffer + fundingWalletPolicy: DefaultWalletPolicy, + multisigWalletPolicy: WalletPolicy, + multisigWalletPolicyHMac: Buffer ): void { this.policyInformation = { - nativeSegwitWalletPolicy, - taprootMultisigWalletPolicy, - taprootMultisigWalletPolicyHMac, + fundingWalletPolicy, + multisigWalletPolicy, + multisigWalletPolicyHMac, }; } private setPayment( - nativeSegwitPayment: P2Ret, - nativeSegwitDerivedPublicKey: Buffer, - taprootMultisigPayment: P2TROut, + fundingPayment: P2Ret | P2TROut, + fundingDerivedPublicKey: Buffer, + multisigPayment: P2TROut, taprootDerivedPublicKey: Buffer ): void { this.payment = { - nativeSegwitPayment, - nativeSegwitDerivedPublicKey, - taprootMultisigPayment, + fundingPayment, + fundingDerivedPublicKey, + multisigPayment, taprootDerivedPublicKey, }; } @@ -128,7 +136,7 @@ export class LedgerDLCHandler { return bytesToHex(this.getPayment().taprootDerivedPublicKey); } - getVaultRelatedAddress(paymentType: 'p2wpkh' | 'p2tr'): string { + getVaultRelatedAddress(paymentType: 'funding' | 'multisig'): string { const payment = this.getPayment(); if (payment === undefined) { @@ -138,17 +146,17 @@ export class LedgerDLCHandler { let address: string; switch (paymentType) { - case 'p2wpkh': - if (!payment.nativeSegwitPayment.address) { - throw new Error('Native Segwit Payment Address is undefined'); + case 'funding': + if (!payment.fundingPayment.address) { + throw new Error('Funding Payment Address is undefined'); } - address = payment.nativeSegwitPayment.address; + address = payment.fundingPayment.address; return address; - case 'p2tr': - if (!payment.taprootMultisigPayment.address) { + case 'multisig': + if (!payment.multisigPayment.address) { throw new Error('Taproot Multisig Payment Address is undefined'); } - address = payment.taprootMultisigPayment.address; + address = payment.multisigPayment.address; return address; default: throw new Error('Invalid Payment Type'); @@ -160,33 +168,40 @@ export class LedgerDLCHandler { attestorGroupPublicKey: string ): Promise { try { - const networkIndex = this.bitcoinNetwork === bitcoin ? 0 : 1; + const fundingPaymentTypeDerivationPath = this.fundingPaymentType === 'wpkh' ? '84' : '86'; - const nativeSegwitExtendedPublicKey = await this.ledgerApp.getExtendedPubkey( - `m/84'/${networkIndex}'/${this.walletAccountIndex}'` + const fundingExtendedPublicKey = await this.ledgerApp.getExtendedPubkey( + `m/${fundingPaymentTypeDerivationPath}'/${this.bitcoinNetworkIndex}'/${this.walletAccountIndex}'` ); - const nativeSegwitKeyinfo = `[${this.masterFingerprint}/84'/${networkIndex}'/${this.walletAccountIndex}']${nativeSegwitExtendedPublicKey}`; + const fundingKeyinfo = `[${this.masterFingerprint}/${fundingPaymentTypeDerivationPath}'/${this.bitcoinNetworkIndex}'/${this.walletAccountIndex}']${fundingExtendedPublicKey}`; - const nativeSegwitWalletPolicy = new DefaultWalletPolicy('wpkh(@0/**)', nativeSegwitKeyinfo); + const fundingWalletPolicy = new DefaultWalletPolicy( + `${this.fundingPaymentType}(@0/**)`, + fundingKeyinfo + ); - const nativeSegwitAddress = await this.ledgerApp.getWalletAddress( - nativeSegwitWalletPolicy, + const fundingAddress = await this.ledgerApp.getWalletAddress( + fundingWalletPolicy, null, 0, 0, false ); - const nativeSegwitDerivedPublicKey = deriveUnhardenedPublicKey( - nativeSegwitExtendedPublicKey, + const fundingDerivedPublicKey = deriveUnhardenedPublicKey( + fundingExtendedPublicKey, this.bitcoinNetwork ); - const nativeSegwitPayment = p2wpkh(nativeSegwitDerivedPublicKey, this.bitcoinNetwork); - if (nativeSegwitPayment.address !== nativeSegwitAddress) { + const fundingPayment = + this.fundingPaymentType === 'wpkh' + ? p2wpkh(fundingDerivedPublicKey, this.bitcoinNetwork) + : p2tr(ecdsaPublicKeyToSchnorr(fundingDerivedPublicKey), undefined, this.bitcoinNetwork); + + if (fundingPayment.address !== fundingAddress) { throw new Error( - `[Ledger] Recreated Native Segwit Address does not match the Ledger Native Segwit Address` + `[Ledger] Recreated Funding Address does not match the Ledger Funding Address` ); } @@ -202,10 +217,10 @@ export class LedgerDLCHandler { ); const taprootExtendedPublicKey = await this.ledgerApp.getExtendedPubkey( - `m/86'/${networkIndex}'/${this.walletAccountIndex}'` + `m/86'/${this.bitcoinNetworkIndex}'/${this.walletAccountIndex}'` ); - const ledgerTaprootKeyInfo = `[${this.masterFingerprint}/86'/${networkIndex}'/${this.walletAccountIndex}']${taprootExtendedPublicKey}`; + const ledgerTaprootKeyInfo = `[${this.masterFingerprint}/86'/${this.bitcoinNetworkIndex}'/${this.walletAccountIndex}']${taprootExtendedPublicKey}`; const taprootDerivedPublicKey = deriveUnhardenedPublicKey( taprootExtendedPublicKey, @@ -235,33 +250,33 @@ export class LedgerDLCHandler { false ); - const taprootMultisigPayment = createTaprootMultisigPayment( + const multisigPayment = createTaprootMultisigPayment( unspendableDerivedPublicKey, attestorDerivedPublicKey, taprootDerivedPublicKey, this.bitcoinNetwork ); - if (taprootMultisigAddress !== taprootMultisigPayment.address) { + if (taprootMultisigAddress !== multisigPayment.address) { throw new Error(`Recreated Multisig Address does not match the Ledger Multisig Address`); } this.setPolicyInformation( - nativeSegwitWalletPolicy, + fundingWalletPolicy, taprootMultisigAccountPolicy, taprootMultisigPolicyHMac ); this.setPayment( - nativeSegwitPayment, - nativeSegwitDerivedPublicKey, - taprootMultisigPayment, + fundingPayment, + fundingDerivedPublicKey, + multisigPayment, taprootDerivedPublicKey ); return { - nativeSegwitPayment, - nativeSegwitDerivedPublicKey, - taprootMultisigPayment, + fundingPayment, + fundingDerivedPublicKey, + multisigPayment, taprootDerivedPublicKey, }; } catch (error: any) { @@ -277,13 +292,12 @@ export class LedgerDLCHandler { customFeeRate?: bigint ): Promise { try { - const { nativeSegwitPayment, nativeSegwitDerivedPublicKey, taprootMultisigPayment } = - await this.createPayment(vault.uuid, attestorGroupPublicKey); + const { fundingPayment, fundingDerivedPublicKey, multisigPayment } = await this.createPayment( + vault.uuid, + attestorGroupPublicKey + ); - if ( - taprootMultisigPayment.address === undefined || - nativeSegwitPayment.address === undefined - ) { + if ([multisigPayment.address, fundingPayment.address].some(x => x === undefined)) { throw new Error('Payment Address is undefined'); } @@ -292,7 +306,7 @@ export class LedgerDLCHandler { BigInt(await getFeeRate(this.bitcoinBlockchainFeeRecommendationAPI, feeRateMultiplier)); const addressBalance = await getBalance( - nativeSegwitPayment.address, + fundingPayment.address as string, this.bitcoinBlockchainAPI ); @@ -303,8 +317,8 @@ export class LedgerDLCHandler { const fundingTransaction = await createFundingTransaction( bitcoinAmount, this.bitcoinNetwork, - taprootMultisigPayment.address, - nativeSegwitPayment, + multisigPayment.address as string, + fundingPayment, feeRate, vault.btcFeeRecipient, vault.btcMintFeeBasisPoints.toBigInt(), @@ -327,15 +341,26 @@ export class LedgerDLCHandler { this.bitcoinNetwork ); - const nativeSegwitInputsToSign = getNativeSegwitInputsToSign(inputByPaymentTypeArray); + if (this.fundingPaymentType === 'wpkh') { + const nativeSegwitInputsToSign = getNativeSegwitInputsToSign(inputByPaymentTypeArray); - await updateNativeSegwitInputs( - nativeSegwitInputsToSign, - nativeSegwitDerivedPublicKey, - this.masterFingerprint, - formattedFundingPSBT, - this.bitcoinBlockchainAPI - ); + await updateNativeSegwitInputs( + nativeSegwitInputsToSign, + fundingDerivedPublicKey, + this.masterFingerprint, + formattedFundingPSBT, + this.bitcoinBlockchainAPI + ); + } else { + const taprootInputsToSign = getTaprootInputsToSign(inputByPaymentTypeArray); + + await updateTaprootInputs( + taprootInputsToSign, + fundingDerivedPublicKey, + this.masterFingerprint, + formattedFundingPSBT + ); + } return formattedFundingPSBT; } catch (error: any) { @@ -352,13 +377,12 @@ export class LedgerDLCHandler { customFeeRate?: bigint ): Promise { try { - const { nativeSegwitPayment, taprootDerivedPublicKey, taprootMultisigPayment } = - await this.createPayment(vault.uuid, attestorGroupPublicKey); + const { fundingPayment, taprootDerivedPublicKey, multisigPayment } = await this.createPayment( + vault.uuid, + attestorGroupPublicKey + ); - if ( - taprootMultisigPayment.address === undefined || - nativeSegwitPayment.address === undefined - ) { + if (multisigPayment.address === undefined || fundingPayment.address === undefined) { throw new Error('Payment Address is undefined'); } @@ -371,8 +395,8 @@ export class LedgerDLCHandler { withdrawAmount, this.bitcoinNetwork, fundingTransactionID, - taprootMultisigPayment, - nativeSegwitPayment.address, + multisigPayment, + fundingPayment.address, feeRate, vault.btcFeeRecipient, vault.btcRedeemFeeBasisPoints.toBigInt() @@ -417,14 +441,10 @@ export class LedgerDLCHandler { feeRateMultiplier?: number, customFeeRate?: bigint ) { - const { - nativeSegwitPayment, - taprootDerivedPublicKey, - nativeSegwitDerivedPublicKey, - taprootMultisigPayment, - } = await this.createPayment(vault.uuid, attestorGroupPublicKey); + const { fundingPayment, taprootDerivedPublicKey, fundingDerivedPublicKey, multisigPayment } = + await this.createPayment(vault.uuid, attestorGroupPublicKey); - if (taprootMultisigPayment.address === undefined || nativeSegwitPayment.address === undefined) { + if (multisigPayment.address === undefined || fundingPayment.address === undefined) { throw new Error('Payment Address is undefined'); } @@ -437,8 +457,8 @@ export class LedgerDLCHandler { this.bitcoinNetwork, depositAmount, fundingTransactionID, - taprootMultisigPayment, - nativeSegwitPayment, + multisigPayment, + fundingPayment, feeRate, vault.btcFeeRecipient, vault.btcMintFeeBasisPoints.toBigInt() @@ -472,7 +492,7 @@ export class LedgerDLCHandler { await updateNativeSegwitInputs( nativeSegwitInputsToSign, - nativeSegwitDerivedPublicKey, + fundingDerivedPublicKey, this.masterFingerprint, formattedDepositPSBT, this.bitcoinBlockchainAPI @@ -483,54 +503,52 @@ export class LedgerDLCHandler { async signPSBT( psbt: Psbt, - transactionType: 'funding' | 'deposit' | 'closing' + transactionType: 'funding' | 'deposit' | 'withdraw' ): Promise { try { - const { - nativeSegwitWalletPolicy, - taprootMultisigWalletPolicy, - taprootMultisigWalletPolicyHMac, - } = this.getPolicyInformation(); + const { fundingWalletPolicy, multisigWalletPolicy, multisigWalletPolicyHMac } = + this.getPolicyInformation(); let signatures; let transaction: Transaction; switch (transactionType) { case 'funding': - signatures = await this.ledgerApp.signPsbt( - psbt.toBase64(), - nativeSegwitWalletPolicy, - null - ); - addNativeSegwitSignaturesToPSBT(psbt, signatures); + signatures = await this.ledgerApp.signPsbt(psbt.toBase64(), fundingWalletPolicy, null); + switch (this.fundingPaymentType) { + case 'wpkh': + addNativeSegwitSignaturesToPSBT(psbt, signatures); + break; + case 'tr': + addTaprootInputSignaturesToPSBT('funding', psbt, signatures); + break; + default: + throw new Error('Invalid Funding Payment Type'); + } transaction = Transaction.fromPSBT(psbt.toBuffer()); transaction.finalize(); return transaction; case 'deposit': signatures = await this.ledgerApp.signPsbt( psbt.toBase64(), - taprootMultisigWalletPolicy, - taprootMultisigWalletPolicyHMac + multisigWalletPolicy, + multisigWalletPolicyHMac ); - addTaprootInputSignaturesToPSBT(psbt, signatures); + addTaprootInputSignaturesToPSBT('funding', psbt, signatures); - signatures = await this.ledgerApp.signPsbt( - psbt.toBase64(), - nativeSegwitWalletPolicy, - null - ); + signatures = await this.ledgerApp.signPsbt(psbt.toBase64(), fundingWalletPolicy, null); addNativeSegwitSignaturesToPSBT(psbt, signatures); transaction = Transaction.fromPSBT(psbt.toBuffer()); return transaction; - case 'closing': + case 'withdraw': signatures = await this.ledgerApp.signPsbt( psbt.toBase64(), - taprootMultisigWalletPolicy, - taprootMultisigWalletPolicyHMac + multisigWalletPolicy, + multisigWalletPolicyHMac ); - addTaprootInputSignaturesToPSBT(psbt, signatures); + addTaprootInputSignaturesToPSBT('withdraw', psbt, signatures); transaction = Transaction.fromPSBT(psbt.toBuffer()); return transaction; default: diff --git a/src/dlc-handlers/private-key-dlc-handler.ts b/src/dlc-handlers/private-key-dlc-handler.ts index e922782..57e9081 100644 --- a/src/dlc-handlers/private-key-dlc-handler.ts +++ b/src/dlc-handlers/private-key-dlc-handler.ts @@ -1,6 +1,6 @@ import { bytesToHex } from '@noble/hashes/utils'; import { Transaction, p2wpkh } from '@scure/btc-signer'; -import { P2Ret, P2TROut } from '@scure/btc-signer/payment'; +import { P2Ret, P2TROut, p2tr } from '@scure/btc-signer/payment'; import { Signer } from '@scure/btc-signer/transaction'; import { BIP32Interface } from 'bip32'; import { Network } from 'bitcoinjs-lib'; @@ -10,6 +10,7 @@ import { createTaprootMultisigPayment, deriveUnhardenedKeyPairFromRootPrivateKey, deriveUnhardenedPublicKey, + ecdsaPublicKeyToSchnorr, finalizeUserInputs, getBalance, getFeeRate, @@ -24,13 +25,14 @@ import { PaymentInformation } from '../models/bitcoin-models.js'; import { RawVault } from '../models/ethereum-models.js'; interface RequiredKeyPair { - nativeSegwitDerivedKeyPair: BIP32Interface; + 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; @@ -38,6 +40,7 @@ export class PrivateKeyDLCHandler { constructor( bitcoinWalletPrivateKey: string, walletAccountIndex: number, + fundingPaymentType: 'wpkh' | 'tr', bitcoinNetwork: Network, bitcoinBlockchainAPI?: string, bitcoinBlockchainFeeRecommendationAPI?: string @@ -68,11 +71,12 @@ export class PrivateKeyDLCHandler { default: throw new Error('Invalid Bitcoin Network'); } + this.fundingPaymentType = fundingPaymentType; this.bitcoinNetwork = bitcoinNetwork; - const nativeSegwitDerivedKeyPair = deriveUnhardenedKeyPairFromRootPrivateKey( + const fundingDerivedKeyPair = deriveUnhardenedKeyPairFromRootPrivateKey( bitcoinWalletPrivateKey, bitcoinNetwork, - 'p2wpkh', + fundingPaymentType === 'wpkh' ? 'p2wpkh' : 'p2tr', walletAccountIndex ); const taprootDerivedKeyPair = deriveUnhardenedKeyPairFromRootPrivateKey( @@ -84,14 +88,14 @@ export class PrivateKeyDLCHandler { this.derivedKeyPair = { taprootDerivedKeyPair, - nativeSegwitDerivedKeyPair, + fundingDerivedKeyPair, }; } - private setPayment(nativeSegwitPayment: P2Ret, taprootMultisigPayment: P2TROut): void { + private setPayment(fundingPayment: P2Ret | P2TROut, multisigPayment: P2TROut): void { this.payment = { - nativeSegwitPayment, - taprootMultisigPayment, + fundingPayment, + multisigPayment, }; } @@ -106,7 +110,7 @@ export class PrivateKeyDLCHandler { return bytesToHex(this.derivedKeyPair.taprootDerivedKeyPair.publicKey); } - getVaultRelatedAddress(paymentType: 'p2wpkh' | 'p2tr'): string { + getVaultRelatedAddress(paymentType: 'funding' | 'multisig'): string { const payment = this.payment; if (payment === undefined) { @@ -116,27 +120,27 @@ export class PrivateKeyDLCHandler { let address: string; switch (paymentType) { - case 'p2wpkh': - if (!payment.nativeSegwitPayment.address) { - throw new Error('Native Segwit Payment Address is undefined'); + case 'funding': + if (!payment.fundingPayment.address) { + throw new Error('Funding Address is undefined'); } - address = payment.nativeSegwitPayment.address; + address = payment.fundingPayment.address; return address; - case 'p2tr': - if (!payment.taprootMultisigPayment.address) { + case 'multisig': + if (!payment.multisigPayment.address) { throw new Error('Taproot Multisig Payment Address is undefined'); } - address = payment.taprootMultisigPayment.address; + address = payment.multisigPayment.address; return address; default: throw new Error('Invalid Payment Type'); } } - private getPrivateKey(paymentType: 'p2wpkh' | 'p2tr'): Signer { + private getPrivateKey(paymentType: 'funding' | 'taproot'): Signer { const privateKey = - paymentType === 'p2wpkh' - ? this.derivedKeyPair.nativeSegwitDerivedKeyPair.privateKey + paymentType === 'funding' + ? this.derivedKeyPair.fundingDerivedKeyPair.privateKey : this.derivedKeyPair.taprootDerivedKeyPair.privateKey; if (!privateKey) { @@ -159,23 +163,38 @@ export class PrivateKeyDLCHandler { this.bitcoinNetwork ); - const nativeSegwitPayment = p2wpkh( - this.derivedKeyPair.nativeSegwitDerivedKeyPair.publicKey, - 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 taprootMultisigPayment = createTaprootMultisigPayment( + const multisigPayment = createTaprootMultisigPayment( unspendableDerivedPublicKey, attestorDerivedPublicKey, this.derivedKeyPair.taprootDerivedKeyPair.publicKey, this.bitcoinNetwork ); - this.setPayment(nativeSegwitPayment, taprootMultisigPayment); + this.setPayment(fundingPayment, multisigPayment); return { - nativeSegwitPayment, - taprootMultisigPayment, + fundingPayment, + multisigPayment, }; } catch (error: any) { throw new Error(`Error creating required Payment objects: ${error}`); @@ -189,16 +208,19 @@ export class PrivateKeyDLCHandler { feeRateMultiplier?: number, customFeeRate?: bigint ): Promise { - const { nativeSegwitPayment, taprootMultisigPayment } = this.createPayments( + const { fundingPayment, multisigPayment } = this.createPayments( vault.uuid, attestorGroupPublicKey ); - if (nativeSegwitPayment.address === undefined || taprootMultisigPayment.address === undefined) { - throw new Error('Could not get Addresses from Payments'); + if ([multisigPayment.address, fundingPayment.address].some(x => x === undefined)) { + throw new Error('Payment Address is undefined'); } - const addressBalance = await getBalance(nativeSegwitPayment.address, this.bitcoinBlockchainAPI); + const addressBalance = await getBalance( + fundingPayment.address as string, + this.bitcoinBlockchainAPI + ); if (BigInt(addressBalance) < vault.valueLocked.toBigInt()) { throw new Error('Insufficient Funds'); @@ -211,8 +233,8 @@ export class PrivateKeyDLCHandler { const fundingTransaction = await createFundingTransaction( bitcoinAmount, this.bitcoinNetwork, - taprootMultisigPayment.address, - nativeSegwitPayment, + multisigPayment.address as string, + fundingPayment, feeRate, vault.btcFeeRecipient, vault.btcMintFeeBasisPoints.toBigInt(), @@ -231,15 +253,12 @@ export class PrivateKeyDLCHandler { customFeeRate?: bigint ): Promise { try { - const { nativeSegwitPayment, taprootMultisigPayment } = this.createPayments( + const { fundingPayment, multisigPayment } = this.createPayments( vault.uuid, attestorGroupPublicKey ); - if ( - taprootMultisigPayment.address === undefined || - nativeSegwitPayment.address === undefined - ) { + if (multisigPayment.address === undefined || fundingPayment.address === undefined) { throw new Error('Payment Address is undefined'); } @@ -252,8 +271,8 @@ export class PrivateKeyDLCHandler { withdrawAmount, this.bitcoinNetwork, fundingTransactionID, - taprootMultisigPayment, - nativeSegwitPayment.address!, + multisigPayment, + fundingPayment.address!, feeRate, vault.btcFeeRecipient, vault.btcRedeemFeeBasisPoints.toBigInt() @@ -267,24 +286,24 @@ export class PrivateKeyDLCHandler { signPSBT(psbt: Transaction, transactionType: 'funding' | 'deposit' | 'withdraw'): Transaction { switch (transactionType) { case 'funding': - psbt.sign(this.getPrivateKey('p2wpkh')); + psbt.sign(this.getPrivateKey('funding')); psbt.finalize(); break; case 'deposit': try { - psbt.sign(this.getPrivateKey('p2tr')); + psbt.sign(this.getPrivateKey('funding')); } catch (error: any) { // this can happen if there are no tr inputs to sign } try { - psbt.sign(this.getPrivateKey('p2wpkh')); + psbt.sign(this.getPrivateKey('taproot')); } catch (error: any) { // this can happen if there are no p2wpkh inputs to sign } - finalizeUserInputs(psbt, this.getPayment().nativeSegwitPayment); + finalizeUserInputs(psbt, this.getPayment().fundingPayment); break; case 'withdraw': - psbt.sign(this.getPrivateKey('p2tr')); + psbt.sign(this.getPrivateKey('taproot')); break; default: throw new Error('Invalid Transaction Type'); @@ -301,12 +320,12 @@ export class PrivateKeyDLCHandler { feeRateMultiplier?: number, customFeeRate?: bigint ) { - const { nativeSegwitPayment, taprootMultisigPayment } = this.createPayments( + const { fundingPayment, multisigPayment } = this.createPayments( vault.uuid, attestorGroupPublicKey ); - if (taprootMultisigPayment.address === undefined || nativeSegwitPayment.address === undefined) { + if (multisigPayment.address === undefined || fundingPayment.address === undefined) { throw new Error('Payment Address is undefined'); } @@ -319,8 +338,8 @@ export class PrivateKeyDLCHandler { this.bitcoinNetwork, depositAmount, fundingTransactionID, - taprootMultisigPayment, - nativeSegwitPayment, + multisigPayment, + fundingPayment, feeRate, vault.btcFeeRecipient, vault.btcMintFeeBasisPoints.toBigInt() diff --git a/src/dlc-handlers/software-wallet-dlc-handler.ts b/src/dlc-handlers/software-wallet-dlc-handler.ts index deec837..becb107 100644 --- a/src/dlc-handlers/software-wallet-dlc-handler.ts +++ b/src/dlc-handlers/software-wallet-dlc-handler.ts @@ -19,7 +19,7 @@ import { PaymentInformation } from '../models/bitcoin-models.js'; import { RawVault } from '../models/ethereum-models.js'; export class SoftwareWalletDLCHandler { - private nativeSegwitDerivedPublicKey: string; + private fundingDerivedPublicKey: string; private taprootDerivedPublicKey: string; public payment: PaymentInformation | undefined; private bitcoinNetwork: Network; @@ -60,14 +60,14 @@ export class SoftwareWalletDLCHandler { throw new Error('Invalid Bitcoin Network'); } this.bitcoinNetwork = bitcoinNetwork; - this.nativeSegwitDerivedPublicKey = nativeSegwitDerivedPublicKey; + this.fundingDerivedPublicKey = nativeSegwitDerivedPublicKey; this.taprootDerivedPublicKey = taprootDerivedPublicKey; } - private setPayment(nativeSegwitPayment: P2Ret, taprootMultisigPayment: P2TROut): void { + private setPayment(fundingPayment: P2Ret, multisigPayment: P2TROut): void { this.payment = { - nativeSegwitPayment, - taprootMultisigPayment, + fundingPayment, + multisigPayment, }; } @@ -82,7 +82,7 @@ export class SoftwareWalletDLCHandler { return this.taprootDerivedPublicKey; } - getVaultRelatedAddress(paymentType: 'p2wpkh' | 'p2tr'): string { + getVaultRelatedAddress(paymentType: 'funding' | 'multisig'): string { const payment = this.getPayment(); if (payment === undefined) { @@ -92,17 +92,17 @@ export class SoftwareWalletDLCHandler { let address: string; switch (paymentType) { - case 'p2wpkh': - if (!payment.nativeSegwitPayment.address) { - throw new Error('Native Segwit Payment Address is undefined'); + case 'funding': + if (!payment.fundingPayment.address) { + throw new Error('Funding Payment Address is undefined'); } - address = payment.nativeSegwitPayment.address; + address = payment.fundingPayment.address; return address; - case 'p2tr': - if (!payment.taprootMultisigPayment.address) { + case 'multisig': + if (!payment.multisigPayment.address) { throw new Error('Taproot Multisig Payment Address is undefined'); } - address = payment.taprootMultisigPayment.address; + address = payment.multisigPayment.address; return address; default: throw new Error('Invalid Payment Type'); @@ -114,8 +114,8 @@ export class SoftwareWalletDLCHandler { attestorGroupPublicKey: string ): Promise { try { - const nativeSegwitPayment = p2wpkh( - Buffer.from(this.nativeSegwitDerivedPublicKey, 'hex'), + const fundingPayment = p2wpkh( + Buffer.from(this.fundingDerivedPublicKey, 'hex'), this.bitcoinNetwork ); @@ -130,18 +130,18 @@ export class SoftwareWalletDLCHandler { this.bitcoinNetwork ); - const taprootMultisigPayment = createTaprootMultisigPayment( + const multisigPayment = createTaprootMultisigPayment( unspendableDerivedPublicKey, attestorDerivedPublicKey, Buffer.from(this.taprootDerivedPublicKey, 'hex'), this.bitcoinNetwork ); - this.setPayment(nativeSegwitPayment, taprootMultisigPayment); + this.setPayment(fundingPayment, multisigPayment); return { - nativeSegwitPayment, - taprootMultisigPayment, + fundingPayment, + multisigPayment, }; } catch (error: any) { throw new Error(`Error creating required wallet information: ${error}`); @@ -156,15 +156,12 @@ export class SoftwareWalletDLCHandler { customFeeRate?: bigint ): Promise { try { - const { nativeSegwitPayment, taprootMultisigPayment } = await this.createPayments( + const { fundingPayment, multisigPayment } = await this.createPayments( vault.uuid, attestorGroupPublicKey ); - if ( - taprootMultisigPayment.address === undefined || - nativeSegwitPayment.address === undefined - ) { + if (fundingPayment.address === undefined || multisigPayment.address === undefined) { throw new Error('Payment Address is undefined'); } @@ -172,10 +169,7 @@ export class SoftwareWalletDLCHandler { customFeeRate ?? BigInt(await getFeeRate(this.bitcoinBlockchainFeeRecommendationAPI, feeRateMultiplier)); - const addressBalance = await getBalance( - nativeSegwitPayment.address, - this.bitcoinBlockchainAPI - ); + const addressBalance = await getBalance(fundingPayment.address, this.bitcoinBlockchainAPI); if (BigInt(addressBalance) < vault.valueLocked.toBigInt()) { throw new Error('Insufficient Funds'); @@ -184,8 +178,8 @@ export class SoftwareWalletDLCHandler { const fundingTransaction = await createFundingTransaction( bitcoinAmount, this.bitcoinNetwork, - taprootMultisigPayment.address, - nativeSegwitPayment, + multisigPayment.address, + fundingPayment, feeRate, vault.btcFeeRecipient, vault.btcMintFeeBasisPoints.toBigInt(), @@ -206,15 +200,12 @@ export class SoftwareWalletDLCHandler { customFeeRate?: bigint ): Promise { try { - const { nativeSegwitPayment, taprootMultisigPayment } = await this.createPayments( + const { fundingPayment, multisigPayment } = await this.createPayments( vault.uuid, attestorGroupPublicKey ); - if ( - taprootMultisigPayment.address === undefined || - nativeSegwitPayment.address === undefined - ) { + if (multisigPayment.address === undefined || fundingPayment.address === undefined) { throw new Error('Payment Address is undefined'); } @@ -227,8 +218,8 @@ export class SoftwareWalletDLCHandler { withdrawAmount, this.bitcoinNetwork, fundingTransactionID, - taprootMultisigPayment, - nativeSegwitPayment.address!, + multisigPayment, + fundingPayment.address!, feeRate, vault.btcFeeRecipient, vault.btcRedeemFeeBasisPoints.toBigInt() @@ -247,12 +238,12 @@ export class SoftwareWalletDLCHandler { feeRateMultiplier?: number, customFeeRate?: bigint ) { - const { nativeSegwitPayment, taprootMultisigPayment } = await this.createPayments( + const { fundingPayment, multisigPayment } = await this.createPayments( vault.uuid, attestorGroupPublicKey ); - if (taprootMultisigPayment.address === undefined || nativeSegwitPayment.address === undefined) { + if (multisigPayment.address === undefined || fundingPayment.address === undefined) { throw new Error('Payment Address is undefined'); } @@ -265,8 +256,8 @@ export class SoftwareWalletDLCHandler { this.bitcoinNetwork, depositAmount, fundingTransactionID, - taprootMultisigPayment, - nativeSegwitPayment, + multisigPayment, + fundingPayment, feeRate, vault.btcFeeRecipient, vault.btcMintFeeBasisPoints.toBigInt() diff --git a/src/functions/bitcoin/bitcoin-functions.ts b/src/functions/bitcoin/bitcoin-functions.ts index 68b3327..07cbe1e 100644 --- a/src/functions/bitcoin/bitcoin-functions.ts +++ b/src/functions/bitcoin/bitcoin-functions.ts @@ -169,14 +169,14 @@ export async function getFeeRate( /** * Gets the UTXOs of the User's Native Segwit Address. * - * @param bitcoinNativeSegwitTransaction - The User's Native Segwit Payment Transaction. + * @param fundingPayment - The User's Funding Payment Transaction. * @returns A Promise that resolves to the UTXOs of the User's Native Segwit Address. */ export async function getUTXOs( - bitcoinNativeSegwitTransaction: P2Ret | P2TROut, + fundingPayment: P2Ret | P2TROut, bitcoinBlockchainAPIURL: string ): Promise { - const utxoEndpoint = `${bitcoinBlockchainAPIURL}/address/${bitcoinNativeSegwitTransaction.address}/utxo`; + const utxoEndpoint = `${bitcoinBlockchainAPIURL}/address/${fundingPayment.address}/utxo`; const utxoResponse = await fetch(utxoEndpoint); if (!utxoResponse.ok) { @@ -188,15 +188,15 @@ export async function getUTXOs( const modifiedUTXOs = await Promise.all( userUTXOs.map(async (utxo: UTXO) => { return { - ...bitcoinNativeSegwitTransaction, + ...fundingPayment, txid: utxo.txid, index: utxo.vout, value: utxo.value, witnessUtxo: { - script: bitcoinNativeSegwitTransaction.script, + script: fundingPayment.script, amount: BigInt(utxo.value), }, - redeemScript: bitcoinNativeSegwitTransaction.redeemScript, + redeemScript: fundingPayment.redeemScript, }; }) ); diff --git a/src/functions/bitcoin/psbt-functions.ts b/src/functions/bitcoin/psbt-functions.ts index 3e1eb80..dde34ad 100644 --- a/src/functions/bitcoin/psbt-functions.ts +++ b/src/functions/bitcoin/psbt-functions.ts @@ -30,7 +30,7 @@ export async function createFundingTransaction( bitcoinAmount: bigint, bitcoinNetwork: Network, multisigAddress: string, - bitcoinNativeSegwitTransaction: P2Ret, + fundingPayment: P2Ret | P2TROut, feeRate: bigint, feePublicKey: string, feeBasisPoints: bigint, @@ -39,7 +39,7 @@ export async function createFundingTransaction( const feeAddress = getFeeRecipientAddressFromPublicKey(feePublicKey, bitcoinNetwork); const feeAmount = getFeeAmount(Number(bitcoinAmount), Number(feeBasisPoints)); - const userUTXOs = await getUTXOs(bitcoinNativeSegwitTransaction, bitcoinBlockchainAPIURL); + const userUTXOs = await getUTXOs(fundingPayment, bitcoinBlockchainAPIURL); const psbtOutputs = [ { address: multisigAddress, amount: bitcoinAmount }, @@ -50,7 +50,7 @@ export async function createFundingTransaction( ]; const selected = selectUTXO(userUTXOs, psbtOutputs, 'default', { - changeAddress: bitcoinNativeSegwitTransaction.address!, + changeAddress: fundingPayment.address!, feePerByte: feeRate, bip69: false, createTx: true, @@ -493,7 +493,7 @@ async function addNativeSegwitBip32Derivation( export function addNativeSegwitSignaturesToPSBT( psbt: Psbt, signatures: [number, PartialSignature][] -): void { +) { signatures.forEach(([index, signature]) => psbt.updateInput(index, { partialSig: [signature] })); } @@ -504,18 +504,25 @@ export function addNativeSegwitSignaturesToPSBT( * @returns The updated PSBT. */ export function addTaprootInputSignaturesToPSBT( + psbtType: 'funding' | 'withdraw', psbt: Psbt, signatures: [number, PartialSignature][] ): void { - signatures.forEach(([index, signature]) => - psbt.updateInput(index, { - tapScriptSig: [ - { - signature: signature.signature, - pubkey: signature.pubkey, - leafHash: signature.tapleafHash!, - }, - ], - }) - ); + if (psbtType === 'funding') { + signatures.forEach(([index, signature]) => + psbt.updateInput(index, { tapKeySig: signature.signature }) + ); + } else { + signatures.forEach(([index, signature]) => + psbt.updateInput(index, { + tapScriptSig: [ + { + signature: signature.signature, + pubkey: signature.pubkey, + leafHash: signature.tapleafHash!, + }, + ], + }) + ); + } } diff --git a/src/models/bitcoin-models.ts b/src/models/bitcoin-models.ts index 1f0aa0c..8d1a17e 100644 --- a/src/models/bitcoin-models.ts +++ b/src/models/bitcoin-models.ts @@ -21,12 +21,12 @@ export interface FeeRates { } export interface PaymentInformation { - nativeSegwitPayment: P2Ret; - taprootMultisigPayment: P2TROut; + fundingPayment: P2Ret | P2TROut; + multisigPayment: P2TROut; } export interface ExtendedPaymentInformation extends PaymentInformation { - nativeSegwitDerivedPublicKey: Buffer; + fundingDerivedPublicKey: Buffer; taprootDerivedPublicKey: Buffer; } diff --git a/tests/mocks/constants.ts b/tests/mocks/constants.ts index 8ba38ba..9c3da76 100644 --- a/tests/mocks/constants.ts +++ b/tests/mocks/constants.ts @@ -13,6 +13,7 @@ export const TEST_BITCOIN_BLOCKCHAIN_API = 'https://devnet.dlc.link/electrs'; export const TEST_BITCOIN_BLOCKCHAIN_FEE_RECOMMENDATION_API = 'https://devnet.dlc.link/electrs/fee-estimates'; export const TEST_BITCOIN_AMOUNT = 0.01; +export const TEST_FUNDING_PAYMENT_TYPE = 'tr'; // Ethereum export const TEST_ETHEREUM_PRIVATE_KEY = ''; diff --git a/tests/unit/sign-transactions.test.ts b/tests/unit/sign-transactions.test.ts index c02a17d..4efcf41 100644 --- a/tests/unit/sign-transactions.test.ts +++ b/tests/unit/sign-transactions.test.ts @@ -9,6 +9,7 @@ import { TEST_BITCOIN_EXTENDED_PRIVATE_KEY, TEST_BITCOIN_NETWORK, TEST_BITCOIN_WALLET_ACCOUNT_INDEX, + TEST_FUNDING_PAYMENT_TYPE, TEST_REGTEST_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY, TEST_VAULT, } from '../mocks/constants.js'; @@ -22,6 +23,7 @@ describe('Create and Sign Vault related Transactions', () => { dlcHandler = new PrivateKeyDLCHandler( TEST_BITCOIN_EXTENDED_PRIVATE_KEY, TEST_BITCOIN_WALLET_ACCOUNT_INDEX, + TEST_FUNDING_PAYMENT_TYPE, TEST_BITCOIN_NETWORK, TEST_BITCOIN_BLOCKCHAIN_API, TEST_BITCOIN_BLOCKCHAIN_FEE_RECOMMENDATION_API @@ -43,7 +45,7 @@ describe('Create and Sign Vault related Transactions', () => { Buffer.from(TEST_VAULT.btcFeeRecipient, 'hex'), TEST_BITCOIN_NETWORK ).script; - const multisigScript = dlcHandler.payment?.taprootMultisigPayment.script; + const multisigScript = dlcHandler.payment?.multisigPayment.script; const outputs = Array.from({ length: fundingTransaction.outputsLength }, (_, index) => fundingTransaction.getOutput(index)