Skip to content

Commit

Permalink
Fixed DLC.BTC vault validation logic (#3418)
Browse files Browse the repository at this point in the history
* fix vault validation logic

* refactor to use BTC value, not evm one
  • Loading branch information
karen-stepanyan authored Sep 9, 2024
1 parent e1e4741 commit 88b49a2
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 42 deletions.
5 changes: 5 additions & 0 deletions .changeset/gold-oranges-rescue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/dlc-btc-por-adapter': minor
---

Fixed vault validation logic
1 change: 1 addition & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/sources/dlc-btc-por/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
53 changes: 27 additions & 26 deletions packages/sources/dlc-btc-por/src/transport/proof-of-reserves.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -110,26 +108,26 @@ export class DLCBTCPorTransport extends SubscriptionTransport<TransportTypes> {
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(),
Expand All @@ -155,23 +153,23 @@ export class DLCBTCPorTransport extends SubscriptionTransport<TransportTypes> {
}

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)

Expand All @@ -187,13 +185,16 @@ export class DLCBTCPorTransport extends SubscriptionTransport<TransportTypes> {
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<BitcoinTransaction> {
Expand Down
28 changes: 12 additions & 16 deletions packages/sources/dlc-btc-por/src/transport/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -12,6 +13,7 @@ export interface RawVault {
creator: string
status: number
fundingTxId: string
wdTxId?: string
closingTxId: string
btcFeeRecipient: string
btcMintFeeBasisPoints: BigNumber
Expand Down Expand Up @@ -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)),
)
}
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 88b49a2

Please sign in to comment.