Skip to content

Commit

Permalink
feat: modify PrivateKeyDLCHandler to support taproot for the funding …
Browse files Browse the repository at this point in the history
…transaction
  • Loading branch information
Polybius93 committed Jun 24, 2024
1 parent a7bf54e commit 6427baa
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 29 deletions.
22 changes: 14 additions & 8 deletions src/dlc-handlers/ledger-dlc-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export class LedgerDLCHandler {
private policyInformation: LedgerPolicyInformation | undefined;
public payment: ExtendedPaymentInformation | undefined;
private bitcoinNetwork: Network;
private bitcoinNetworkIndex: number;
private bitcoinBlockchainAPI: string;
private bitcoinBlockchainFeeRecommendationAPI: string;

Expand All @@ -59,11 +60,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 (
Expand All @@ -76,6 +79,7 @@ export class LedgerDLCHandler {
}
this.bitcoinBlockchainAPI = bitcoinBlockchainAPI;
this.bitcoinBlockchainFeeRecommendationAPI = bitcoinBlockchainFeeRecommendationAPI;
this.bitcoinNetworkIndex = 1;
break;
default:
throw new Error('Invalid Bitcoin Network');
Expand Down Expand Up @@ -159,13 +163,12 @@ export class LedgerDLCHandler {
): Promise<ExtendedPaymentInformation> {
try {
const fundingPaymentTypeDerivationPath = this.fundingPaymentType === 'wpkh' ? '84' : '86';
const networkIndex = this.bitcoinNetwork === bitcoin ? 0 : 1;

const fundingExtendedPublicKey = await this.ledgerApp.getExtendedPubkey(
`m/${fundingPaymentTypeDerivationPath}'/${networkIndex}'/${this.walletAccountIndex}'`
`m/${fundingPaymentTypeDerivationPath}'/${this.bitcoinNetworkIndex}'/${this.walletAccountIndex}'`
);

const fundingKeyinfo = `[${this.masterFingerprint}/${fundingPaymentTypeDerivationPath}'/${networkIndex}'/${this.walletAccountIndex}']${fundingExtendedPublicKey}`;
const fundingKeyinfo = `[${this.masterFingerprint}/${fundingPaymentTypeDerivationPath}'/${this.bitcoinNetworkIndex}'/${this.walletAccountIndex}']${fundingExtendedPublicKey}`;

const fundingWalletPolicy = new DefaultWalletPolicy(
`${this.fundingPaymentType}(@0/**)`,
Expand Down Expand Up @@ -208,10 +211,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,
Expand Down Expand Up @@ -287,15 +290,18 @@ export class LedgerDLCHandler {
attestorGroupPublicKey
);

if (multisigPayment.address === undefined || fundingPayment.address === undefined) {
if ([multisigPayment.address, fundingPayment.address].some(x => x === undefined)) {
throw new Error('Payment Address is undefined');
}

const feeRate =
customFeeRate ??
BigInt(await getFeeRate(this.bitcoinBlockchainFeeRecommendationAPI, feeRateMultiplier));

const addressBalance = await getBalance(fundingPayment.address, this.bitcoinBlockchainAPI);
const addressBalance = await getBalance(
fundingPayment.address as string,
this.bitcoinBlockchainAPI
);

if (BigInt(addressBalance) < vault.valueLocked.toBigInt()) {
throw new Error('Insufficient Funds');
Expand All @@ -304,7 +310,7 @@ export class LedgerDLCHandler {
const fundingPSBT = await createFundingTransaction(
vault.valueLocked.toBigInt(),
this.bitcoinNetwork,
multisigPayment.address,
multisigPayment.address as string,
fundingPayment,
feeRate,
vault.btcFeeRecipient,
Expand Down
64 changes: 43 additions & 21 deletions src/dlc-handlers/private-key-dlc-handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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';
Expand All @@ -9,6 +9,7 @@ import {
createTaprootMultisigPayment,
deriveUnhardenedKeyPairFromRootPrivateKey,
deriveUnhardenedPublicKey,
ecdsaPublicKeyToSchnorr,
getBalance,
getFeeRate,
getUnspendableKeyCommittedToUUID,
Expand All @@ -21,20 +22,22 @@ 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;

constructor(
bitcoinWalletPrivateKey: string,
walletAccountIndex: number,
fundingPaymentType: 'wpkh' | 'tr',
bitcoinNetwork: Network,
bitcoinBlockchainAPI?: string,
bitcoinBlockchainFeeRecommendationAPI?: string
Expand Down Expand Up @@ -65,11 +68,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(
Expand All @@ -81,13 +85,13 @@ export class PrivateKeyDLCHandler {

this.derivedKeyPair = {
taprootDerivedKeyPair,
nativeSegwitDerivedKeyPair,
fundingDerivedKeyPair,
};
}

private setPayment(nativeSegwitPayment: P2Ret, multisigPayment: P2TROut): void {
private setPayment(fundingPayment: P2Ret | P2TROut, multisigPayment: P2TROut): void {
this.payment = {
fundingPayment: nativeSegwitPayment,
fundingPayment,
multisigPayment,
};
}
Expand Down Expand Up @@ -119,10 +123,10 @@ export class PrivateKeyDLCHandler {
}
}

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) {
Expand All @@ -145,10 +149,25 @@ 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 multisigPayment = createTaprootMultisigPayment(
unspendableDerivedPublicKey,
Expand All @@ -157,10 +176,10 @@ export class PrivateKeyDLCHandler {
this.bitcoinNetwork
);

this.setPayment(nativeSegwitPayment, multisigPayment);
this.setPayment(fundingPayment, multisigPayment);

return {
fundingPayment: nativeSegwitPayment,
fundingPayment,
multisigPayment,
};
} catch (error: any) {
Expand All @@ -179,11 +198,14 @@ export class PrivateKeyDLCHandler {
attestorGroupPublicKey
);

if (fundingPayment.address === undefined || multisigPayment.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(fundingPayment.address, this.bitcoinBlockchainAPI);
const addressBalance = await getBalance(
fundingPayment.address as string,
this.bitcoinBlockchainAPI
);

if (BigInt(addressBalance) < vault.valueLocked.toBigInt()) {
throw new Error('Insufficient Funds');
Expand All @@ -196,7 +218,7 @@ export class PrivateKeyDLCHandler {
const fundingPSBT = await createFundingTransaction(
vault.valueLocked.toBigInt(),
this.bitcoinNetwork,
multisigPayment.address,
multisigPayment.address as string,
fundingPayment,
feeRate,
vault.btcFeeRecipient,
Expand Down Expand Up @@ -242,7 +264,7 @@ export class PrivateKeyDLCHandler {
}

signPSBT(psbt: Transaction, transactionType: 'funding' | 'closing'): Transaction {
psbt.sign(this.getPrivateKey(transactionType === 'funding' ? 'p2wpkh' : 'p2tr'));
psbt.sign(this.getPrivateKey(transactionType === 'funding' ? 'funding' : 'taproot'));
if (transactionType === 'funding') psbt.finalize();
return psbt;
}
Expand Down
1 change: 1 addition & 0 deletions tests/mocks/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '';
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/sign-transactions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,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';
Expand All @@ -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
Expand Down

0 comments on commit 6427baa

Please sign in to comment.