diff --git a/.changeset/gold-oranges-rescue.md b/.changeset/gold-oranges-rescue.md new file mode 100644 index 0000000000..54985c8045 --- /dev/null +++ b/.changeset/gold-oranges-rescue.md @@ -0,0 +1,5 @@ +--- +'@chainlink/dlc-btc-por-adapter': minor +--- + +Fixed vault validation logic diff --git a/.pnp.cjs b/.pnp.cjs index 234a98a53c..d7e18337a9 100644 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -6800,6 +6800,7 @@ const RAW_RUNTIME_STATE = ["@chainlink/dlc-btc-por-adapter", "workspace:packages/sources/dlc-btc-por"],\ ["@bitcoinerlab/secp256k1", "npm:1.1.1"],\ ["@chainlink/external-adapter-framework", "npm:1.3.1"],\ + ["@noble/hashes", "npm:1.4.0"],\ ["@scure/base", "npm:1.1.6"],\ ["@scure/btc-signer", "npm:1.2.2"],\ ["@types/jest", "npm:27.5.2"],\ diff --git a/packages/sources/dlc-btc-por/package.json b/packages/sources/dlc-btc-por/package.json index 9380d12701..abcd103fa5 100644 --- a/packages/sources/dlc-btc-por/package.json +++ b/packages/sources/dlc-btc-por/package.json @@ -37,6 +37,7 @@ "dependencies": { "@bitcoinerlab/secp256k1": "1.1.1", "@chainlink/external-adapter-framework": "1.3.1", + "@noble/hashes": "1.4.0", "@scure/base": "1.1.6", "@scure/btc-signer": "1.2.2", "bip32": "4.0.0", diff --git a/packages/sources/dlc-btc-por/src/transport/proof-of-reserves.ts b/packages/sources/dlc-btc-por/src/transport/proof-of-reserves.ts index b1a4a1c68e..f5eefa5405 100644 --- a/packages/sources/dlc-btc-por/src/transport/proof-of-reserves.ts +++ b/packages/sources/dlc-btc-por/src/transport/proof-of-reserves.ts @@ -4,17 +4,15 @@ import { SubscriptionTransport } from '@chainlink/external-adapter-framework/tra import { EndpointContext } from '@chainlink/external-adapter-framework/adapter' import { Requester } from '@chainlink/external-adapter-framework/util/requester' import { ethers } from 'ethers' -import { hex } from '@scure/base' import { BaseEndpointTypes, inputParameters } from '../endpoint/proof-of-reserves' import abi from '../config/dlc-manager-abi.json' import { BitcoinTransaction, createTaprootMultisigPayment, getBitcoinNetwork, - getClosingTransactionInputFromFundingTransaction, getDerivedPublicKey, + getScriptMatchingOutputFromTransaction, getUnspendableKeyCommittedToUUID, - matchScripts, RawVault, } from './utils' import { AdapterError } from '@chainlink/external-adapter-framework/validation/error' @@ -110,26 +108,26 @@ export class DLCBTCPorTransport extends SubscriptionTransport { const deposits = await Promise.all( group.map(async (vault) => { try { - const isVerified = await this.verifyVaultDeposit(vault, attestorPublicKey) - if (isVerified) { - return vault.valueLocked.toNumber() - } + return await this.verifyVaultDeposit(vault, attestorPublicKey) } catch (e) { logger.error(e, `Error while verifying Deposit for Vault: ${vault.uuid}. ${e}`) + return 0 } - return 0 }), ) - // totalPoR represents total proof of reserves value in satoshis + // totalPoR represents total proof of reserves value in bitcoins totalPoR += deposits.reduce((sum, deposit) => sum + deposit, 0) } + // multiply by 10^8 to convert to satoshis + const result = totalPoR * 10 ** 8 + return { data: { - result: totalPoR, + result: result, }, statusCode: 200, - result: totalPoR, + result: result, timestamps: { providerDataRequestedUnixMs, providerDataReceivedUnixMs: Date.now(), @@ -155,23 +153,23 @@ export class DLCBTCPorTransport extends SubscriptionTransport { } async verifyVaultDeposit(vault: RawVault, attestorPublicKey: Buffer) { - if (!vault.fundingTxId || !vault.taprootPubKey || !vault.valueLocked || !vault.uuid) { - return false + if (!vault.taprootPubKey || !vault.valueLocked || !vault.uuid) { + return 0 + } + const txID = vault.wdTxId ? vault.wdTxId : vault.fundingTxId + + if (!txID) { + return 0 } + // Get the bitcoin transaction - const fundingTransaction = await this.fetchFundingTransaction(vault.fundingTxId) + const fundingTransaction = await this.fetchFundingTransaction(txID) // Check and filter transactions that have less than [settings.CONFIRMATIONS] confirmations if (fundingTransaction.confirmations < this.settings.CONFIRMATIONS) { - return false + return 0 } - // Get the Closing Transaction Input from the Funding Transaction by the locked Bitcoin value - const closingTransactionInput = getClosingTransactionInputFromFundingTransaction( - fundingTransaction, - vault.valueLocked.toNumber(), - ) - // Get the Bitcoin network object const bitcoinNetwork = getBitcoinNetwork(this.settings.BITCOIN_NETWORK) @@ -187,13 +185,16 @@ export class DLCBTCPorTransport extends SubscriptionTransport { bitcoinNetwork, ) - // Verify that the Funding Transaction's Output Script matches the expected MultiSig Script - const acceptedScript = matchScripts( - [multisigTransaction.script], - hex.decode(closingTransactionInput.scriptPubKey.hex), + const vaultTransactionOutput = getScriptMatchingOutputFromTransaction( + fundingTransaction, + multisigTransaction.script, ) - return acceptedScript + if (!vaultTransactionOutput) { + return 0 + } + + return vaultTransactionOutput.value } async fetchFundingTransaction(txId: string): Promise { diff --git a/packages/sources/dlc-btc-por/src/transport/utils.ts b/packages/sources/dlc-btc-por/src/transport/utils.ts index 7554bef6ac..95c4ad45fe 100644 --- a/packages/sources/dlc-btc-por/src/transport/utils.ts +++ b/packages/sources/dlc-btc-por/src/transport/utils.ts @@ -3,6 +3,7 @@ import { BIP32Factory } from 'bip32' import * as ellipticCurveCryptography from '@bitcoinerlab/secp256k1' import { Network } from 'bitcoinjs-lib' import { p2tr, p2tr_ns, P2TROut } from '@scure/btc-signer' +import { hexToBytes } from '@noble/hashes/utils' export interface RawVault { uuid: string @@ -12,6 +13,7 @@ export interface RawVault { creator: string status: number fundingTxId: string + wdTxId?: string closingTxId: string btcFeeRecipient: string btcMintFeeBasisPoints: BigNumber @@ -184,24 +186,18 @@ export const createTaprootMultisigPayment = ( return p2tr(unspendableDerivedPublicKeyFormatted, taprootMultiLeafWallet, bitcoinNetwork) } -export const getClosingTransactionInputFromFundingTransaction = ( - fundingTransaction: BitcoinTransaction, - bitcoinValue: number, -): BitcoinTransactionVectorOutput => { - const closingTransactionInput = fundingTransaction.vout.find( - // bitcoinValue in the vault is represented in satoshis, convert the transaction value to compare - (output) => output.value * 10 ** 8 === bitcoinValue, +export function validateScript(script: Uint8Array, outputScript: Uint8Array) { + return ( + outputScript.length === script.length && + outputScript.every((value, index) => value === script[index]) ) - if (!closingTransactionInput) { - throw new Error('Could not find Closing Transaction Input.') - } - return closingTransactionInput } -export const matchScripts = (multisigScripts: Uint8Array[], outputScript: Uint8Array): boolean => { - return multisigScripts.some( - (multisigScript) => - outputScript.length === multisigScript.length && - outputScript.every((value, index) => value === multisigScript[index]), +export function getScriptMatchingOutputFromTransaction( + bitcoinTransaction: BitcoinTransaction, + script: Uint8Array, +) { + return bitcoinTransaction.vout.find((output) => + validateScript(script, hexToBytes(output.scriptPubKey.hex)), ) } diff --git a/yarn.lock b/yarn.lock index f00e5588be..d4bc045f17 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3949,6 +3949,7 @@ __metadata: dependencies: "@bitcoinerlab/secp256k1": "npm:1.1.1" "@chainlink/external-adapter-framework": "npm:1.3.1" + "@noble/hashes": "npm:1.4.0" "@scure/base": "npm:1.1.6" "@scure/btc-signer": "npm:1.2.2" "@types/jest": "npm:27.5.2"