diff --git a/src/functions/bitcoin/bitcoin-functions.ts b/src/functions/bitcoin/bitcoin-functions.ts index b9838fe..0810076 100644 --- a/src/functions/bitcoin/bitcoin-functions.ts +++ b/src/functions/bitcoin/bitcoin-functions.ts @@ -1,3 +1,4 @@ +import { hexToBytes } from '@noble/hashes/utils'; import { Address, OutScript, @@ -420,17 +421,20 @@ export function getInputByPaymentTypeArray( }); } -export function getValueMatchingOutputFromTransaction( +export function getScriptMatchingOutputFromTransaction( bitcoinTransaction: BitcoinTransaction, - bitcoinValue: number -): BitcoinTransactionVectorOutput { - const valueMatchingTransactionOutput = bitcoinTransaction.vout.find( - output => output.value === bitcoinValue + script: Uint8Array +): BitcoinTransactionVectorOutput | undefined { + return bitcoinTransaction.vout.find(output => + validateScript(script, hexToBytes(output.scriptpubkey)) + ); +} + +export function validateScript(script: Uint8Array, outputScript: Uint8Array): boolean { + return ( + outputScript.length === script.length && + outputScript.every((value, index) => value === script[index]) ); - if (!valueMatchingTransactionOutput) { - throw new Error('Could not find Value matching Input in Transaction'); - } - return valueMatchingTransactionOutput; } export function getInputIndicesByScript(script: Uint8Array, transaction: Transaction): number[] { diff --git a/src/functions/proof-of-reserve/proof-of-reserve-functions.ts b/src/functions/proof-of-reserve/proof-of-reserve-functions.ts new file mode 100644 index 0000000..74404bf --- /dev/null +++ b/src/functions/proof-of-reserve/proof-of-reserve-functions.ts @@ -0,0 +1,67 @@ +import { Network } from 'bitcoinjs-lib'; +import { RawVault } from 'src/models/ethereum-models.js'; + +import { + createTaprootMultisigPayment, + deriveUnhardenedPublicKey, + getScriptMatchingOutputFromTransaction, + getUnspendableKeyCommittedToUUID, +} from '../bitcoin/bitcoin-functions.js'; +import { + checkBitcoinTransactionConfirmations, + fetchBitcoinTransaction, +} from '../bitcoin/bitcoin-request-functions.js'; + +export async function verifyVaultDeposit( + vault: RawVault, + attestorGroupPublicKey: Buffer, + bitcoinBlockchainBlockHeight: number, + bitcoinBlockchainAPI: string, + bitcoinNetwork: Network +): Promise { + try { + const fundingTransaction = await fetchBitcoinTransaction( + vault.fundingTxId, + bitcoinBlockchainAPI + ); + + const isFundingTransactionConfirmed = await checkBitcoinTransactionConfirmations( + fundingTransaction, + bitcoinBlockchainBlockHeight + ); + + if (!isFundingTransactionConfirmed) { + return false; + } + + const unspendableKeyCommittedToUUID = deriveUnhardenedPublicKey( + getUnspendableKeyCommittedToUUID(vault.uuid, bitcoinNetwork), + bitcoinNetwork + ); + + const taprootMultisigPayment = createTaprootMultisigPayment( + unspendableKeyCommittedToUUID, + attestorGroupPublicKey, + Buffer.from(vault.taprootPubKey, 'hex'), + bitcoinNetwork + ); + + const vaultTransactionOutput = getScriptMatchingOutputFromTransaction( + fundingTransaction, + taprootMultisigPayment.script + ); + + if (!vaultTransactionOutput) { + return false; + } + + if (vaultTransactionOutput.value !== vault.valueLocked.toNumber()) { + return false; + } + + return true; + } catch (error) { + console.log(`Error verifying Vault Deposit: ${error}`); + return false; + } +} diff --git a/src/proof-of-reserve-handlers/proof-of-reserve-handler.ts b/src/proof-of-reserve-handlers/proof-of-reserve-handler.ts index 7641404..98b8e8c 100644 --- a/src/proof-of-reserve-handlers/proof-of-reserve-handler.ts +++ b/src/proof-of-reserve-handlers/proof-of-reserve-handler.ts @@ -1,19 +1,9 @@ -import { hex } from '@scure/base'; import { Network } from 'bitcoinjs-lib'; -import { - createTaprootMultisigPayment, - deriveUnhardenedPublicKey, - getUnspendableKeyCommittedToUUID, - getValueMatchingOutputFromTransaction, -} from '../functions/bitcoin/bitcoin-functions.js'; -import { - checkBitcoinTransactionConfirmations, - fetchBitcoinBlockchainBlockHeight, - fetchBitcoinTransaction, -} from '../functions/bitcoin/bitcoin-request-functions.js'; +import { deriveUnhardenedPublicKey } from '../functions/bitcoin/bitcoin-functions.js'; +import { fetchBitcoinBlockchainBlockHeight } from '../functions/bitcoin/bitcoin-request-functions.js'; +import { verifyVaultDeposit } from '../functions/proof-of-reserve/proof-of-reserve-functions.js'; import { RawVault } from '../models/ethereum-models.js'; -import { compareUint8Arrays } from '../utilities/index.js'; export class ProofOfReserveHandler { private bitcoinBlockchainAPI: string; @@ -30,51 +20,6 @@ export class ProofOfReserveHandler { this.attestorGroupPublicKey = attestorGroupPublicKey; } - async verifyVaultDeposit( - vault: RawVault, - attestorGroupPublicKey: Buffer, - bitcoinBlockchainBlockHeight: number - ): Promise { - try { - const fundingTransaction = await fetchBitcoinTransaction( - vault.fundingTxId, - this.bitcoinBlockchainAPI - ); - const isFundingTransactionConfirmed = await checkBitcoinTransactionConfirmations( - fundingTransaction, - bitcoinBlockchainBlockHeight - ); - - if (!isFundingTransactionConfirmed) { - return false; - } - - const vaultTransactionOutput = getValueMatchingOutputFromTransaction( - fundingTransaction, - vault.valueLocked.toNumber() - ); - - const unspendableKeyCommittedToUUID = deriveUnhardenedPublicKey( - getUnspendableKeyCommittedToUUID(vault.uuid, this.bitcoinNetwork), - this.bitcoinNetwork - ); - const taprootMultisigPayment = createTaprootMultisigPayment( - unspendableKeyCommittedToUUID, - attestorGroupPublicKey, - Buffer.from(vault.taprootPubKey, 'hex'), - this.bitcoinNetwork - ); - - return compareUint8Arrays( - taprootMultisigPayment.script, - hex.decode(vaultTransactionOutput.scriptpubkey) - ); - } catch (error) { - console.error(`Error verifying Vault Deposit: ${error}`); - return false; - } - } - async calculateProofOfReserve(vaults: RawVault[]): Promise { const bitcoinBlockchainBlockHeight = await fetchBitcoinBlockchainBlockHeight( this.bitcoinBlockchainAPI @@ -84,12 +29,15 @@ export class ProofOfReserveHandler { this.attestorGroupPublicKey, this.bitcoinNetwork ); + const verifiedDeposits = await Promise.all( vaults.map(async vault => { - return (await this.verifyVaultDeposit( + return (await verifyVaultDeposit( vault, derivedAttestorGroupPublicKey, - bitcoinBlockchainBlockHeight + bitcoinBlockchainBlockHeight, + this.bitcoinBlockchainAPI, + this.bitcoinNetwork )) === true ? vault.valueLocked.toNumber() : 0; diff --git a/tests/mocks/api.test.constants.ts b/tests/mocks/api.test.constants.ts new file mode 100644 index 0000000..c94000c --- /dev/null +++ b/tests/mocks/api.test.constants.ts @@ -0,0 +1,17 @@ +export const TEST_REGTEST_BITCOIN_BLOCKCHAIN_API = 'https://devnet.dlc.link/electrs'; +export const TEST_TESTNET_BITCOIN_BLOCKCHAIN_API = 'https://testnet.dlc.link/electrs'; + +export const TEST_BITCOIN_BLOCKCHAIN_FEE_RECOMMENDATION_API = + 'https://devnet.dlc.link/electrs/fee-estimates'; + +export const TEST_REGTEST_ATTESTOR_APIS = [ + 'https://devnet.dlc.link/attestor-1', + 'https://devnet.dlc.link/attestor-2', + 'https://devnet.dlc.link/attestor-3', +]; + +export const TEST_ETHEREUM_NODE_API = 'https://sepolia-rollup.arbitrum.io/rpc'; +export const TEST_ETHEREUM_READ_ONLY_NODE_API = 'https://sepolia-rollup.arbitrum.io/rpc'; + +export const TEST_ETHEREUM_GITHUB_DEPLOYMENT_PLAN_ROOT_URL = + 'https://raw.githubusercontent.com/DLC-link/dlc-solidity'; diff --git a/tests/mocks/attestor.test.constants.ts b/tests/mocks/attestor.test.constants.ts new file mode 100644 index 0000000..eadc1cb --- /dev/null +++ b/tests/mocks/attestor.test.constants.ts @@ -0,0 +1,8 @@ +export const TEST_REGTEST_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1 = + 'tpubDDqN2CmTDKaGeqXMayfCZEvjZqntifi4r1ztmRWsGuE1VE4bosR3mBKQwVaCxZcmg8R1nHDMDzDmzjoccBMgwZV1hhz51tAXVnhjABCQcwA'; + +export const TEST_TESTNET_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1 = + 'tpubDDRekL64eJJav32TLhNhG59qra7wAMaei8YMGXNiJE8ksdYrKgvaFM1XG6JrSt31W97XryScrX37RUEujjZT4qScNf8Zu1JxWj4VYkwz4rU'; + +export const TEST_TESTNET_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1 = + '027eda4d625f781dcc98bf68901360fdaaacce8ed466096c1dfe4865209b28c058'; diff --git a/tests/mocks/bitcoin-transaction.test.constants.ts b/tests/mocks/bitcoin-transaction.test.constants.ts index dbc6053..cbf7124 100644 --- a/tests/mocks/bitcoin-transaction.test.constants.ts +++ b/tests/mocks/bitcoin-transaction.test.constants.ts @@ -1,3 +1,5 @@ +import { BitcoinTransaction } from '../../src/models/bitcoin-models'; + export const TEST_DEPOSIT_PSBT_PARTIALLY_SIGNED_DEPOSIT_PSBT_1 = '70736274ff0100c5020000000269680a0f87525a598abaf823342a4cb6d632027252e51d242e51c61770f17c990200000000f0ffffff751757d16e395a1da0457531b8a9eed579bdd87f3d60034b179976be5fb5178a0100000000ffffffff0370170000000000001600145a81f36535980769bccc23c196337a78458abd1e80969800000000002251203789ed985ab9d1c94f5889973376a88d9940835206461151685a7a3fbb84de599ec6202901000000160014add70fb03578d3ac85aab80897395bb046223c92000000000001011f302b6d2901000000160014add70fb03578d3ac85aab80897395bb046223c92220202f4d8696f9b275f4e10af63f03bcb7fbba7b1ed44dd9b12a973c8d20212beb8d1483045022100d7788684a0d1f05f35a861f72457a8d1aa7efbfdf1227d3e6cdf72b7feab9f7e02203d57106adbaf190731c0447e990833835c6de86052161e39b06193ad306e80a1010001012b404b4c00000000002251203789ed985ab9d1c94f5889973376a88d9940835206461151685a7a3fbb84de594114bb7e175e63064479102ee0b69a719a9f54f8f1b29df17cfaa5437697393e7cfce24db130ab82d20d322c295aa18921a7fe87fb13df3d5b3b2ee0469e2a84511440d8fb920db7745d01f2762b6285e10a3d16ef056fd1dc26879eea5dc5410d4da53a458820088e5a22c11ef27527a8db546a0e8997d80b4b3c4f861a6b4410bd482215c1e52a5f154612da28faade658332157966492aa20a523162a8b584682a23dc72645205216652630455eb8546bbab45e6fdef6498eee683ed339f405d326414b05b192ad20bb7e175e63064479102ee0b69a719a9f54f8f1b29df17cfaa5437697393e7cfcacc0011720e52a5f154612da28faade658332157966492aa20a523162a8b584682a23dc726011820e24db130ab82d20d322c295aa18921a7fe87fb13df3d5b3b2ee0469e2a84511400000000'; // Readable format: @@ -918,3 +920,181 @@ export const TEST_WITHDRAW_PSBT_PARTIALLY_SIGNED_WITHDRAW_PSBT_2 = // "PSBTVersion": 0 // } // } + +// This is a testnet funding transaction with valid inputs and outputs +export const TEST_TESTNET_FUNDING_TRANSACTION_1: BitcoinTransaction = { + txid: '4cf5c2954c84bf5225d98ef014aa97bbfa0f05d56b5749782fcd8af8b9d505a5', + version: 2, + locktime: 0, + vin: [ + { + txid: 'cefbeafc3e50618a59646ba6e7b3bba8f15b3e2551570af98182f4234586d085', + vout: 2, + prevout: { + scriptpubkey: '5120192d65c33b86bc129d606c12f0183569d42732d59cad3bf208a9a9fd3b138248', + scriptpubkey_asm: + 'OP_PUSHNUM_1 OP_PUSHBYTES_32 192d65c33b86bc129d606c12f0183569d42732d59cad3bf208a9a9fd3b138248', + scriptpubkey_type: 'v1_p2tr', + scriptpubkey_address: 'tb1prykktsems67p98tqdsf0qxp4d82zwvk4njknhusg4x5l6wcnsfyqar32mq', + value: 71607616, + }, + scriptsig: '', + scriptsig_asm: '', + witness: [ + 'd4ad3523fdc9ec709e8bf2ecadd56c9266f9c57bccb5d165cd57dc815a88de34957764482a6fab3897ce7be2677168f69be93d799021b502899b556436c3f6bb', + ], + is_coinbase: false, + sequence: 4294967280, + }, + ], + vout: [ + { + scriptpubkey: '51206d7e5019c795d05fd3df81713069aa3a309e912a61555ab3ebd6e477f42c1f70', + scriptpubkey_asm: + 'OP_PUSHNUM_1 OP_PUSHBYTES_32 6d7e5019c795d05fd3df81713069aa3a309e912a61555ab3ebd6e477f42c1f70', + scriptpubkey_type: 'v1_p2tr', + scriptpubkey_address: 'tb1pd4l9qxw8jhg9l57ls9cnq6d28gcfayf2v9244vlt6mj80apvracqgdt090', + value: 10000000, + }, + { + scriptpubkey: '0014f28ec1a3e3df0240b98582ca7754e6948e9bf930', + scriptpubkey_asm: 'OP_0 OP_PUSHBYTES_20 f28ec1a3e3df0240b98582ca7754e6948e9bf930', + scriptpubkey_type: 'v0_p2wpkh', + scriptpubkey_address: 'tb1q728vrglrmupypwv9st98w48xjj8fh7fs8mrdre', + value: 100000, + }, + { + scriptpubkey: '5120192d65c33b86bc129d606c12f0183569d42732d59cad3bf208a9a9fd3b138248', + scriptpubkey_asm: + 'OP_PUSHNUM_1 OP_PUSHBYTES_32 192d65c33b86bc129d606c12f0183569d42732d59cad3bf208a9a9fd3b138248', + scriptpubkey_type: 'v1_p2tr', + scriptpubkey_address: 'tb1prykktsems67p98tqdsf0qxp4d82zwvk4njknhusg4x5l6wcnsfyqar32mq', + value: 61490226, + }, + ], + size: 236, + weight: 740, + fee: 17390, + status: { + confirmed: true, + block_height: 2867279, + block_hash: '000000000000001ee12e0297ff36e8c8041aefb65af0c1033a1af4fdb8146f0d', + block_time: 1720620175, + }, +}; + +// This transaction is missing the output with the multisig's script. +export const TEST_TESTNET_FUNDING_TRANSACTION_2: BitcoinTransaction = { + txid: '4cf5c2954c84bf5225d98ef014aa97bbfa0f05d56b5749782fcd8af8b9d505a5', + version: 2, + locktime: 0, + vin: [ + { + txid: 'cefbeafc3e50618a59646ba6e7b3bba8f15b3e2551570af98182f4234586d085', + vout: 2, + prevout: { + scriptpubkey: '5120192d65c33b86bc129d606c12f0183569d42732d59cad3bf208a9a9fd3b138248', + scriptpubkey_asm: + 'OP_PUSHNUM_1 OP_PUSHBYTES_32 192d65c33b86bc129d606c12f0183569d42732d59cad3bf208a9a9fd3b138248', + scriptpubkey_type: 'v1_p2tr', + scriptpubkey_address: 'tb1prykktsems67p98tqdsf0qxp4d82zwvk4njknhusg4x5l6wcnsfyqar32mq', + value: 71607616, + }, + scriptsig: '', + scriptsig_asm: '', + witness: [ + 'd4ad3523fdc9ec709e8bf2ecadd56c9266f9c57bccb5d165cd57dc815a88de34957764482a6fab3897ce7be2677168f69be93d799021b502899b556436c3f6bb', + ], + is_coinbase: false, + sequence: 4294967280, + }, + ], + vout: [ + { + scriptpubkey: '0014f28ec1a3e3df0240b98582ca7754e6948e9bf930', + scriptpubkey_asm: 'OP_0 OP_PUSHBYTES_20 f28ec1a3e3df0240b98582ca7754e6948e9bf930', + scriptpubkey_type: 'v0_p2wpkh', + scriptpubkey_address: 'tb1q728vrglrmupypwv9st98w48xjj8fh7fs8mrdre', + value: 100000, + }, + { + scriptpubkey: '5120192d65c33b86bc129d606c12f0183569d42732d59cad3bf208a9a9fd3b138248', + scriptpubkey_asm: + 'OP_PUSHNUM_1 OP_PUSHBYTES_32 192d65c33b86bc129d606c12f0183569d42732d59cad3bf208a9a9fd3b138248', + scriptpubkey_type: 'v1_p2tr', + scriptpubkey_address: 'tb1prykktsems67p98tqdsf0qxp4d82zwvk4njknhusg4x5l6wcnsfyqar32mq', + value: 61490226, + }, + ], + size: 236, + weight: 740, + fee: 17390, + status: { + confirmed: true, + block_height: 2867279, + block_hash: '000000000000001ee12e0297ff36e8c8041aefb65af0c1033a1af4fdb8146f0d', + block_time: 1720620175, + }, +}; + +// This transaction's multisig output value does not match the vault's valueLocked field. +export const TEST_TESTNET_FUNDING_TRANSACTION_3: BitcoinTransaction = { + txid: '4cf5c2954c84bf5225d98ef014aa97bbfa0f05d56b5749782fcd8af8b9d505a5', + version: 2, + locktime: 0, + vin: [ + { + txid: 'cefbeafc3e50618a59646ba6e7b3bba8f15b3e2551570af98182f4234586d085', + vout: 2, + prevout: { + scriptpubkey: '5120192d65c33b86bc129d606c12f0183569d42732d59cad3bf208a9a9fd3b138248', + scriptpubkey_asm: + 'OP_PUSHNUM_1 OP_PUSHBYTES_32 192d65c33b86bc129d606c12f0183569d42732d59cad3bf208a9a9fd3b138248', + scriptpubkey_type: 'v1_p2tr', + scriptpubkey_address: 'tb1prykktsems67p98tqdsf0qxp4d82zwvk4njknhusg4x5l6wcnsfyqar32mq', + value: 71607616, + }, + scriptsig: '', + scriptsig_asm: '', + witness: [ + 'd4ad3523fdc9ec709e8bf2ecadd56c9266f9c57bccb5d165cd57dc815a88de34957764482a6fab3897ce7be2677168f69be93d799021b502899b556436c3f6bb', + ], + is_coinbase: false, + sequence: 4294967280, + }, + ], + vout: [ + { + scriptpubkey: '51206d7e5019c795d05fd3df81713069aa3a309e912a61555ab3ebd6e477f42c1f70', + scriptpubkey_asm: + 'OP_PUSHNUM_1 OP_PUSHBYTES_32 6d7e5019c795d05fd3df81713069aa3a309e912a61555ab3ebd6e477f42c1f70', + scriptpubkey_type: 'v1_p2tr', + scriptpubkey_address: 'tb1pd4l9qxw8jhg9l57ls9cnq6d28gcfayf2v9244vlt6mj80apvracqgdt090', + value: 5000000, + }, + { + scriptpubkey: '0014f28ec1a3e3df0240b98582ca7754e6948e9bf930', + scriptpubkey_asm: 'OP_0 OP_PUSHBYTES_20 f28ec1a3e3df0240b98582ca7754e6948e9bf930', + scriptpubkey_type: 'v0_p2wpkh', + scriptpubkey_address: 'tb1q728vrglrmupypwv9st98w48xjj8fh7fs8mrdre', + value: 100000, + }, + { + scriptpubkey: '5120192d65c33b86bc129d606c12f0183569d42732d59cad3bf208a9a9fd3b138248', + scriptpubkey_asm: + 'OP_PUSHNUM_1 OP_PUSHBYTES_32 192d65c33b86bc129d606c12f0183569d42732d59cad3bf208a9a9fd3b138248', + scriptpubkey_type: 'v1_p2tr', + scriptpubkey_address: 'tb1prykktsems67p98tqdsf0qxp4d82zwvk4njknhusg4x5l6wcnsfyqar32mq', + value: 61490226, + }, + ], + size: 236, + weight: 740, + fee: 17390, + status: { + confirmed: true, + block_height: 2867279, + block_hash: '000000000000001ee12e0297ff36e8c8041aefb65af0c1033a1af4fdb8146f0d', + block_time: 1720620175, + }, +}; diff --git a/tests/mocks/bitcoin.test.constants.ts b/tests/mocks/bitcoin.test.constants.ts new file mode 100644 index 0000000..400b348 --- /dev/null +++ b/tests/mocks/bitcoin.test.constants.ts @@ -0,0 +1,43 @@ +export const TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_1 = 2867441; +export const TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_2 = 2867285; + +export const TEST_BITCOIN_AMOUNT = 0.01; +export const TEST_FUNDING_PAYMENT_TYPE = 'tr'; + +export const TEST_UNSPENDABLE_KEY_COMMITED_TO_UUID_1 = + 'tpubD6NzVbkrYhZ4Wmm9QfhLLpfzQRoJApR3Sf4AgkiyLMgxbhPtBLVmqA2ZG7zKTgYzzCCK7bAoS5UEXotNdAnhhQhUhB1Q1uqFF1BLVCkArmr'; + +export const TEST_UNHARDENED_DERIVED_UNSPENDABLE_KEY_COMMITED_TO_UUID_1 = + '02b733c776dd7776657c20a58f1f009567afc75db226965bce83d5d0afc29e46c9'; + +export const TEST_REGTEST_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY = + 'tpubDDqN2CmTDKaGeqXMayfCZEvjZqntifi4r1ztmRWsGuE1VE4bosR3mBKQwVaCxZcmg8R1nHDMDzDmzjoccBMgwZV1hhz51tAXVnhjABCQcwA'; + +export const TEST_ALICE_NATIVE_SEGWIT_PAYMENT_SCRIPT_1 = + '0014add70fb03578d3ac85aab80897395bb046223c92'; + +export const TEST_ALICE_NATIVE_SEGWIT_PUBLIC_KEY_1 = + '02f4d8696f9b275f4e10af63f03bcb7fbba7b1ed44dd9b12a973c8d20212beb8d1'; + +export const TEST_ALICE_NATIVE_SEGWIT_PUBLIC_KEY_2 = + '0385c8f8844b7c197b96d933b01b2b82b97b9f9d55c81947e1db5cfb3de375508b'; + +export const TEST_ALICE_TAPROOT_PUBLIC_KEY_1 = + '03bb7e175e63064479102ee0b69a719a9f54f8f1b29df17cfaa5437697393e7cfc'; + +export const TEST_ALICE_TAPROOT_PUBLIC_KEY_2 = + '03940f5559dc92a3253e8699e9c632badfe5c2b1a13a113b85022d30cdab9c0ed8'; + +export const TEST_TAPROOT_UNHARDENED_DERIVED_PUBLIC_KEY_1 = + 'dc544c17af0887dfc8ca9936755c9fdef0c79bbc8866cd69bf120c71509742d2'; + +export const TEST_TAPROOT_MULTISIG_PAYMENT_SCRIPT_1 = + '51206d7e5019c795d05fd3df81713069aa3a309e912a61555ab3ebd6e477f42c1f70'; + +export const TEST_ALICE_NATIVE_SEGWIT_BITCOIN_ADDRESS = + 'bcrt1q4htslvp40rf6epd2hqyfww2mkprzy0yjmagsy5'; + +export const TEST_BITCOIN_EXTENDED_PRIVATE_KEY = + 'tprv8ZgxMBicQKsPeJ7iQfVEb34R3JoSyJ1J9z6wv1yXJkd1NMTRbmQiLkcZXgqQ277LMtszhnp2L2VmmHhFzoLD12fjyXcAvfnvs6qTJMMcKFq'; + +export const TEST_BITCOIN_WALLET_ACCOUNT_INDEX = 0; diff --git a/tests/mocks/constants.ts b/tests/mocks/constants.ts deleted file mode 100644 index 44ac1a5..0000000 --- a/tests/mocks/constants.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { regtest } from 'bitcoinjs-lib/src/networks.js'; -import { BigNumber } from 'ethers'; - -import { RawVault } from '../../src/models/ethereum-models.js'; - -// Bitcoin -export const TEST_BITCOIN_NETWORK = regtest; -export const TEST_BITCOIN_ADDRESS = 'bcrt1q4htslvp40rf6epd2hqyfww2mkprzy0yjmagsy5'; -export const TEST_BITCOIN_EXTENDED_PRIVATE_KEY = - 'tprv8ZgxMBicQKsPeJ7iQfVEb34R3JoSyJ1J9z6wv1yXJkd1NMTRbmQiLkcZXgqQ277LMtszhnp2L2VmmHhFzoLD12fjyXcAvfnvs6qTJMMcKFq'; -export const TEST_BITCOIN_WALLET_ACCOUNT_INDEX = 0; -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 = ''; -export const TEST_ETHEREUM_NODE_API = 'https://sepolia-rollup.arbitrum.io/rpc'; -export const TEST_ETHEREUM_READ_ONLY_NODE_API = 'https://sepolia-rollup.arbitrum.io/rpc'; -export const TEST_ETHEREUM_GITHUB_DEPLOYMENT_PLAN_ROOT_URL = - 'https://raw.githubusercontent.com/DLC-link/dlc-solidity'; -export const TEST_ETHEREUM_DEVNET_GITHUB_DEPLOYMENT_PLAN_BRANCH = 'dev'; -export const TEST_ETHEREUM_ATTESTOR_CHAIN_ID = 'evm-arbsepolia'; -export const TEST_VAULT: RawVault = { - uuid: '0x400ca1a687f9c8241566d334fcb4b33efab8e540b943be1455143284c5afc962', - protocolContract: '0x6e692DB944162f8b4250aA25eCEe80608457D7a7', - timestamp: BigNumber.from('0x665da025'), - valueLocked: BigNumber.from('0x0f4240'), - valueMinted: BigNumber.from('0x0f4240'), - creator: '0x0DD4f29E21F10cb2E485cf9bDAb9F2dD1f240Bfa', - status: 0, - fundingTxId: '', - closingTxId: '', - wdTxId: '', - btcFeeRecipient: '031131cd88bcea8c1d84da8e034bb24c2f6e748c571922dc363e7e088f5df0436c', - btcMintFeeBasisPoints: BigNumber.from('0x64'), - btcRedeemFeeBasisPoints: BigNumber.from('0x64'), - taprootPubKey: '', -}; - -// Attestor -export const TEST_REGTEST_ATTESTOR_APIS = [ - 'https://devnet.dlc.link/attestor-1', - 'https://devnet.dlc.link/attestor-2', - 'https://devnet.dlc.link/attestor-3', -]; -export const TEST_REGTEST_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY = - 'tpubDDqN2CmTDKaGeqXMayfCZEvjZqntifi4r1ztmRWsGuE1VE4bosR3mBKQwVaCxZcmg8R1nHDMDzDmzjoccBMgwZV1hhz51tAXVnhjABCQcwA'; - -export const TEST_ALICE_NATIVE_SEGWIT_PAYMENT_SCRIPT_1 = - '0014add70fb03578d3ac85aab80897395bb046223c92'; - -export const TEST_ALICE_NATIVE_SEGWIT_PUBLIC_KEY_1 = - '02f4d8696f9b275f4e10af63f03bcb7fbba7b1ed44dd9b12a973c8d20212beb8d1'; - -export const TEST_ALICE_NATIVE_SEGWIT_PUBLIC_KEY_2 = - '0385c8f8844b7c197b96d933b01b2b82b97b9f9d55c81947e1db5cfb3de375508b'; - -export const TEST_ALICE_TAPROOT_PUBLIC_KEY_1 = - '03bb7e175e63064479102ee0b69a719a9f54f8f1b29df17cfaa5437697393e7cfc'; - -export const TEST_ALICE_TAPROOT_PUBLIC_KEY_2 = - '03940f5559dc92a3253e8699e9c632badfe5c2b1a13a113b85022d30cdab9c0ed8'; diff --git a/tests/mocks/ethereum-vault.test.constants.ts b/tests/mocks/ethereum-vault.test.constants.ts new file mode 100644 index 0000000..1a1cef1 --- /dev/null +++ b/tests/mocks/ethereum-vault.test.constants.ts @@ -0,0 +1,37 @@ +import { BigNumber } from 'ethers'; + +import { RawVault } from '../../src/models/ethereum-models'; + +export const TEST_VAULT_1: RawVault = { + uuid: '0x400ca1a687f9c8241566d334fcb4b33efab8e540b943be1455143284c5afc962', + protocolContract: '0x6e692DB944162f8b4250aA25eCEe80608457D7a7', + timestamp: BigNumber.from('0x665da025'), + valueLocked: BigNumber.from('0x0f4240'), + valueMinted: BigNumber.from('0x0f4240'), + creator: '0x0DD4f29E21F10cb2E485cf9bDAb9F2dD1f240Bfa', + status: 0, + fundingTxId: '', + closingTxId: '', + wdTxId: '', + btcFeeRecipient: '031131cd88bcea8c1d84da8e034bb24c2f6e748c571922dc363e7e088f5df0436c', + btcMintFeeBasisPoints: BigNumber.from('0x64'), + btcRedeemFeeBasisPoints: BigNumber.from('0x64'), + taprootPubKey: '', +}; + +export const TEST_VAULT_2: RawVault = { + uuid: '0x2b898d65df757575417a920aabe518586793bac4fa682f00ad2c33fad2471999', + protocolContract: '0x980feAeD0D5d3BaFFeb828a27e8b59c0FE78F1f9', + timestamp: BigNumber.from('0x668e9353'), + valueLocked: BigNumber.from('0x989680'), + valueMinted: BigNumber.from('0x989680'), + creator: '0x980feAeD0D5d3BaFFeb828a27e8b59c0FE78F1f9', + status: 1, + fundingTxId: '4cf5c2954c84bf5225d98ef014aa97bbfa0f05d56b5749782fcd8af8b9d505a5', + closingTxId: '', + wdTxId: '032392b61a5c3b0098774465ad61e429fd892615ff2890f849f8eb237a8a59f3ba', + btcFeeRecipient: '', + btcMintFeeBasisPoints: BigNumber.from('0x64'), + btcRedeemFeeBasisPoints: BigNumber.from('0x64'), + taprootPubKey: 'dc544c17af0887dfc8ca9936755c9fdef0c79bbc8866cd69bf120c71509742d2', +}; diff --git a/tests/mocks/ethereum.test.constants.ts b/tests/mocks/ethereum.test.constants.ts new file mode 100644 index 0000000..fdd43f8 --- /dev/null +++ b/tests/mocks/ethereum.test.constants.ts @@ -0,0 +1,5 @@ +export const TEST_VAULT_UUID_1 = + '0x2b898d65df757575417a920aabe518586793bac4fa682f00ad2c33fad2471999'; + +export const TEST_ETHEREUM_DEVNET_GITHUB_DEPLOYMENT_PLAN_BRANCH = 'dev'; +export const TEST_ETHEREUM_ATTESTOR_CHAIN_ID = 'evm-arbsepolia'; diff --git a/tests/unit/bitcoin-functions.test.ts b/tests/unit/bitcoin-functions.test.ts index 113520d..ae709ab 100644 --- a/tests/unit/bitcoin-functions.test.ts +++ b/tests/unit/bitcoin-functions.test.ts @@ -1,16 +1,26 @@ -import { hexToBytes } from '@noble/hashes/utils'; +import { bytesToHex, hexToBytes } from '@noble/hashes/utils'; import { Transaction, p2tr, p2wpkh } from '@scure/btc-signer'; -import { regtest } from 'bitcoinjs-lib/src/networks'; +import { regtest, testnet } from 'bitcoinjs-lib/src/networks'; import { + createTaprootMultisigPayment, + deriveUnhardenedPublicKey, ecdsaPublicKeyToSchnorr, finalizeUserInputs, getInputIndicesByScript, + getScriptMatchingOutputFromTransaction, + getUnspendableKeyCommittedToUUID, } from '../../src/functions/bitcoin/bitcoin-functions'; +import { + TEST_TESTNET_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1, + TEST_TESTNET_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1, +} from '../mocks/attestor.test.constants'; import { TEST_DEPOSIT_PSBT_PARTIALLY_SIGNED_DEPOSIT_PSBT_1, TEST_DEPOSIT_PSBT_PARTIALLY_SIGNED_DEPOSIT_PSBT_2, TEST_DEPOSIT_PSBT_PARTIALLY_SIGNED_DEPOSIT_PSBT_3, + TEST_TESTNET_FUNDING_TRANSACTION_1, + TEST_TESTNET_FUNDING_TRANSACTION_2, TEST_WITHDRAW_PSBT_PARTIALLY_SIGNED_WITHDRAW_PSBT_1, } from '../mocks/bitcoin-transaction.test.constants'; import { @@ -19,7 +29,12 @@ import { TEST_ALICE_NATIVE_SEGWIT_PUBLIC_KEY_2, TEST_ALICE_TAPROOT_PUBLIC_KEY_1, TEST_ALICE_TAPROOT_PUBLIC_KEY_2, -} from '../mocks/constants'; + TEST_TAPROOT_MULTISIG_PAYMENT_SCRIPT_1, + TEST_TAPROOT_UNHARDENED_DERIVED_PUBLIC_KEY_1, + TEST_UNHARDENED_DERIVED_UNSPENDABLE_KEY_COMMITED_TO_UUID_1, + TEST_UNSPENDABLE_KEY_COMMITED_TO_UUID_1, +} from '../mocks/bitcoin.test.constants'; +import { TEST_VAULT_UUID_1 } from '../mocks/ethereum.test.constants'; describe('Bitcoin Functions', () => { describe('getInputIndicesByScript', () => { @@ -79,37 +94,90 @@ describe('Bitcoin Functions', () => { expect(transaction.getInput(0).finalScriptWitness).toBeUndefined(); expect(transaction.getInput(1).finalScriptWitness).toBeUndefined(); }); + + it('correctly finalizes inputs given a transaction and a taproot payment script', () => { + const transaction = Transaction.fromPSBT( + hexToBytes(TEST_DEPOSIT_PSBT_PARTIALLY_SIGNED_DEPOSIT_PSBT_2) + ); + + const alicePublicKey = ecdsaPublicKeyToSchnorr( + Buffer.from(TEST_ALICE_TAPROOT_PUBLIC_KEY_1, 'hex') + ); + const alicePayment = p2tr(alicePublicKey, undefined, regtest); + + finalizeUserInputs(transaction, alicePayment); + + expect(transaction.getInput(0).finalScriptWitness).toBeDefined(); + expect(transaction.getInput(1).finalScriptWitness).toBeUndefined(); + }); + + it('does not finalize inputs given a transaction and a taproot payment script that is not included in this transaction', () => { + const transaction = Transaction.fromPSBT( + hexToBytes(TEST_DEPOSIT_PSBT_PARTIALLY_SIGNED_DEPOSIT_PSBT_2) + ); + + const alicePublicKey = ecdsaPublicKeyToSchnorr( + Buffer.from(TEST_ALICE_TAPROOT_PUBLIC_KEY_2, 'hex') + ); + const alicePayment = p2tr(alicePublicKey, undefined, regtest); + + finalizeUserInputs(transaction, alicePayment); + + expect(transaction.getInput(0).finalScriptWitness).toBeUndefined(); + expect(transaction.getInput(1).finalScriptWitness).toBeUndefined(); + }); + }); + + describe('getUnspendableKeyCommittedToUUID', () => { + it('should return an unspendable key committed to the given uuid', () => { + const result = getUnspendableKeyCommittedToUUID(TEST_VAULT_UUID_1, testnet); + + expect(result).toBe(TEST_UNSPENDABLE_KEY_COMMITED_TO_UUID_1); + }); }); - it('correctly finalizes inputs given a transaction and a taproot payment script', () => { - const transaction = Transaction.fromPSBT( - hexToBytes(TEST_DEPOSIT_PSBT_PARTIALLY_SIGNED_DEPOSIT_PSBT_2) - ); + describe('deriveUnhardenedPublicKey', () => { + it('should derive an unhardened public key from a given public key', () => { + const result = deriveUnhardenedPublicKey( + TEST_TESTNET_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1, + testnet + ); - const alicePublicKey = ecdsaPublicKeyToSchnorr( - Buffer.from(TEST_ALICE_TAPROOT_PUBLIC_KEY_1, 'hex') - ); - const alicePayment = p2tr(alicePublicKey, undefined, regtest); + expect(result.toString('hex')).toBe(TEST_TESTNET_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1); + }); + }); - finalizeUserInputs(transaction, alicePayment); + describe('createTaprootMultisigPayment', () => { + it('should create a taproot multisig payment', () => { + const result = createTaprootMultisigPayment( + Buffer.from(TEST_UNHARDENED_DERIVED_UNSPENDABLE_KEY_COMMITED_TO_UUID_1, 'hex'), + Buffer.from(TEST_TESTNET_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1, 'hex'), + Buffer.from(TEST_TAPROOT_UNHARDENED_DERIVED_PUBLIC_KEY_1, 'hex'), + testnet + ); - expect(transaction.getInput(0).finalScriptWitness).toBeDefined(); - expect(transaction.getInput(1).finalScriptWitness).toBeUndefined(); + expect(bytesToHex(result.script)).toBe(TEST_TAPROOT_MULTISIG_PAYMENT_SCRIPT_1); + }); }); - it('does not finalize inputs given a transaction and a taproot payment script that is not included in this transaction', () => { - const transaction = Transaction.fromPSBT( - hexToBytes(TEST_DEPOSIT_PSBT_PARTIALLY_SIGNED_DEPOSIT_PSBT_2) - ); + describe('getScriptMatchingOutputFromTransaction', () => { + it('should get the script matching output from a transaction', () => { + const result = getScriptMatchingOutputFromTransaction( + TEST_TESTNET_FUNDING_TRANSACTION_1, + hexToBytes(TEST_TAPROOT_MULTISIG_PAYMENT_SCRIPT_1) + ); - const alicePublicKey = ecdsaPublicKeyToSchnorr( - Buffer.from(TEST_ALICE_TAPROOT_PUBLIC_KEY_2, 'hex') - ); - const alicePayment = p2tr(alicePublicKey, undefined, regtest); + expect(result).toBeDefined(); + expect(result).toBe(TEST_TESTNET_FUNDING_TRANSACTION_1.vout[0]); + }); - finalizeUserInputs(transaction, alicePayment); + it('should return undefined for a transaction without any output linked to the multisig script', () => { + const result = getScriptMatchingOutputFromTransaction( + TEST_TESTNET_FUNDING_TRANSACTION_2, + hexToBytes(TEST_TAPROOT_MULTISIG_PAYMENT_SCRIPT_1) + ); - expect(transaction.getInput(0).finalScriptWitness).toBeUndefined(); - expect(transaction.getInput(1).finalScriptWitness).toBeUndefined(); + expect(result).toBeUndefined(); + }); }); }); diff --git a/tests/unit/proof-of-reserve.test.ts b/tests/unit/proof-of-reserve.test.ts new file mode 100644 index 0000000..e2d22c1 --- /dev/null +++ b/tests/unit/proof-of-reserve.test.ts @@ -0,0 +1,105 @@ +import { testnet } from 'bitcoinjs-lib/src/networks.js'; + +import * as bitcoinRequestFunctions from '../../src/functions/bitcoin/bitcoin-request-functions.js'; +import { verifyVaultDeposit } from '../../src/functions/proof-of-reserve/proof-of-reserve-functions.js'; +import { TEST_TESTNET_BITCOIN_BLOCKCHAIN_API } from '../mocks/api.test.constants.js'; +import { TEST_TESTNET_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1 } from '../mocks/attestor.test.constants.js'; +import { + TEST_TESTNET_FUNDING_TRANSACTION_1, + TEST_TESTNET_FUNDING_TRANSACTION_2, + TEST_TESTNET_FUNDING_TRANSACTION_3, +} from '../mocks/bitcoin-transaction.test.constants.js'; +import { + TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_1, + TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_2, +} from '../mocks/bitcoin.test.constants.js'; +import { TEST_VAULT_2 } from '../mocks/ethereum-vault.test.constants.js'; + +describe('Proof of Reserve Calculation', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('verifyVaultDeposit', () => { + it("should return true when the vault's funding transaction is confirmed, contains an output with the multisig's script, and the output's value matches the vault's valueLocked field", async () => { + jest + .spyOn(bitcoinRequestFunctions, 'fetchBitcoinTransaction') + .mockImplementationOnce(async () => TEST_TESTNET_FUNDING_TRANSACTION_1); + + const result = await verifyVaultDeposit( + TEST_VAULT_2, + Buffer.from(TEST_TESTNET_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1, 'hex'), + TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_1, + TEST_TESTNET_BITCOIN_BLOCKCHAIN_API, + testnet + ); + + expect(result).toBe(true); + }); + + it('should return false if the funding transaction is not found', async () => { + jest + .spyOn(bitcoinRequestFunctions, 'fetchBitcoinTransaction') + .mockImplementationOnce(async () => { + throw new Error('Transaction not found'); + }); + + const result = await verifyVaultDeposit( + TEST_VAULT_2, + Buffer.from(TEST_TESTNET_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1, 'hex'), + TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_1, + TEST_TESTNET_BITCOIN_BLOCKCHAIN_API, + testnet + ); + + expect(result).toBe(false); + }); + + it("should return false when the vault's funding transaction is not yet confirmed", async () => { + jest + .spyOn(bitcoinRequestFunctions, 'fetchBitcoinTransaction') + .mockImplementationOnce(async () => TEST_TESTNET_FUNDING_TRANSACTION_1); + + const result = await verifyVaultDeposit( + TEST_VAULT_2, + Buffer.from(TEST_TESTNET_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1, 'hex'), + TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_2, + TEST_TESTNET_BITCOIN_BLOCKCHAIN_API, + testnet + ); + + expect(result).toBe(false); + }); + + it("should return false if the vault's funding transaction lacks an output with the multisig's script", async () => { + jest + .spyOn(bitcoinRequestFunctions, 'fetchBitcoinTransaction') + .mockImplementationOnce(async () => TEST_TESTNET_FUNDING_TRANSACTION_2); + + const result = await verifyVaultDeposit( + TEST_VAULT_2, + Buffer.from(TEST_TESTNET_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1, 'hex'), + TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_1, + TEST_TESTNET_BITCOIN_BLOCKCHAIN_API, + testnet + ); + + expect(result).toBe(false); + }); + + it("should return false if the output value related to the multisig script differs from the vault's valueLocked field in the funding transaction", async () => { + jest + .spyOn(bitcoinRequestFunctions, 'fetchBitcoinTransaction') + .mockImplementationOnce(async () => TEST_TESTNET_FUNDING_TRANSACTION_3); + + const result = await verifyVaultDeposit( + TEST_VAULT_2, + Buffer.from(TEST_TESTNET_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1, 'hex'), + TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_1, + TEST_TESTNET_BITCOIN_BLOCKCHAIN_API, + testnet + ); + + expect(result).toBe(false); + }); + }); +}); diff --git a/tests/unit/sign-transactions.test.ts b/tests/unit/sign-transactions.test.ts index 4efcf41..860491e 100644 --- a/tests/unit/sign-transactions.test.ts +++ b/tests/unit/sign-transactions.test.ts @@ -1,18 +1,20 @@ import { Transaction, p2wpkh } from '@scure/btc-signer'; +import { regtest } from 'bitcoinjs-lib/src/networks.js'; import { PrivateKeyDLCHandler } from '../../src/index.js'; import { shiftValue } from '../../src/utilities/index.js'; import { - TEST_BITCOIN_AMOUNT, - TEST_BITCOIN_BLOCKCHAIN_API, TEST_BITCOIN_BLOCKCHAIN_FEE_RECOMMENDATION_API, + TEST_REGTEST_BITCOIN_BLOCKCHAIN_API, +} from '../mocks/api.test.constants.js'; +import { TEST_TESTNET_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1 } from '../mocks/attestor.test.constants.js'; +import { + TEST_BITCOIN_AMOUNT, 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'; +} from '../mocks/bitcoin.test.constants.js'; +import { TEST_VAULT_1 } from '../mocks/ethereum-vault.test.constants.js'; describe('Create and Sign Vault related Transactions', () => { let dlcHandler: PrivateKeyDLCHandler; @@ -24,26 +26,26 @@ describe('Create and Sign Vault related Transactions', () => { TEST_BITCOIN_EXTENDED_PRIVATE_KEY, TEST_BITCOIN_WALLET_ACCOUNT_INDEX, TEST_FUNDING_PAYMENT_TYPE, - TEST_BITCOIN_NETWORK, - TEST_BITCOIN_BLOCKCHAIN_API, + regtest, + TEST_REGTEST_BITCOIN_BLOCKCHAIN_API, TEST_BITCOIN_BLOCKCHAIN_FEE_RECOMMENDATION_API ); }); it('should create a funding transaction', async () => { fundingTransaction = await dlcHandler.createFundingPSBT( - TEST_VAULT, + TEST_VAULT_1, BigInt(shiftValue(TEST_BITCOIN_AMOUNT)), - TEST_REGTEST_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY, + TEST_TESTNET_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1, 2 ); - const vaultAmount = TEST_VAULT.valueLocked.toBigInt(); - const feeAmount = vaultAmount / TEST_VAULT.btcMintFeeBasisPoints.toBigInt(); + const vaultAmount = TEST_VAULT_1.valueLocked.toBigInt(); + const feeAmount = vaultAmount / TEST_VAULT_1.btcMintFeeBasisPoints.toBigInt(); const feeRecipientScript = p2wpkh( - Buffer.from(TEST_VAULT.btcFeeRecipient, 'hex'), - TEST_BITCOIN_NETWORK + Buffer.from(TEST_VAULT_1.btcFeeRecipient, 'hex'), + regtest ).script; const multisigScript = dlcHandler.payment?.multisigPayment.script;