Skip to content

Commit

Permalink
feat: modify private key handler to match the abstract class
Browse files Browse the repository at this point in the history
  • Loading branch information
Polybius93 committed Dec 5, 2024
1 parent cab9569 commit 926327d
Showing 1 changed file with 56 additions and 278 deletions.
334 changes: 56 additions & 278 deletions src/dlc-handlers/private-key-dlc-handler.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<Transaction> {
const { fundingPayment, multisigPayment } = this.createPayments(
vault.uuid,
attestorGroupPublicKey
);
private getPrivateKey(paymentType: PaymentType): Signer {
const keyPairMap: Record<PaymentType, BIP32Interface> = {
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<Transaction> {
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<Transaction> {
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;
}
}

0 comments on commit 926327d

Please sign in to comment.