Verifying Bitcoin Transactions in Clarity

Trustlessly validate Bitcoin data on-chain.

The current version of the clarity-bitcoin-lib is version 7. Click here for the deployed mainnet contract.

Intro

One of the unique features of the Stack chain and the Clarity language is that it allows for using read-only functions to trustlessly validate on-chain Bitcoin activity from Clarity smart contracts. This enables developers to build applications that react to real Bitcoin activity — such as deposits, transfers, and proofs of inclusion — without relying on off-chain indexers or oracles. In this guide, you’ll learn how to verify Bitcoin transactions in Clarity and safely bridge Bitcoin state into on-chain logic.

Knowledge of Bitcoin state: has knowledge of the full Bitcoin state; it can trustlessly read Bitcoin transactions and state changes and execute smart contracts triggered by Bitcoin transactions. The Bitcoin read functionality helps to keep the decentralized peg state consistent with BTC locked on Bitcoin L1, amongst other things. - Stacks Whitepaper

tl;dr

  • We'll be using a popular smart contract clarity-bitcoin-lib to verify bitcoin transactions on-chain in Clarity.

  • Allows contracts to enforce logic based on Bitcoin events—deposits, withdrawals, lockups, or multisig activity.

  • Powers designs like wrapped assets, and deposits that mint or unlock value only after provable Bitcoin transactions.

Steps

  • Fetch bitcoin transaction metadata: block height and header, transaction hex, and the transaction's merkle proof of inclusion.

  • Pass in transaction metadata as arguments to clarity-bitcoin-lib contract's was-tx-mined-compact read-only function.

  • Returns (ok <txid>) if the proof checks out and the transaction is indeed mined in the specified Bitcoin block.

Key Tools To Use

  • Clarity Bitcoin library: A Clarity library for parsing Bitcoin transactions and verifying Merkle proofs.

  • mempool.space APIs: Bitcoin explorer APIs used to fetch bitcoin transaction metadata.

  • Bitcoin Transaction Proof [optional]: A TypeScript library for generating Bitcoin transaction proofs, including witness data and merkle proofs.

  • Clarity Bitcoin Client [optional]: Clarity Bitcoin Client is an open-source TypeScript library for interacting with the clarity-bitcoin-lib contract on Stacks.


Complete Code

If you want to jump straight to the full implementation, the complete working code used in this guide is shown below.

The below consists of the main client side code of preparing the bitcoin transaction metadata and passing them into a contract call to the clarity-bitcoin-lib contract.


Walkthrough

1

Fetch bitcoin transaction metadata

Using mempool.space's APIs and with a bitcoin txid, fetch the required bitcoin transaction metadata needed for Clarity.

  • Fetches the transaction hex

  • Fetches the transaction merkle proof

  • Fetches the block header

  • Removes witness data from transaction hex

Are there libraries to help with fetching of the bitcoin transaction metadata?

There sure are. Here are two community-built libraries that can abstract away some of the complexities with gathering the bitcoin transaction metadata.

  • Bitcoin Transaction Proof - A TypeScript library for generating Bitcoin transaction proofs, including witness data and merkle proofs.

  • Clarity Bitcoin Client - A TypeScript library for interacting with the clarity-bitcoin-lib contract on Stacks.

Don't have a bitcoin transaction id?

Learn how to create and broadcast a bitcoin transaction on the frontend here.

2

Prepare metadata as Clarity arguments

Let's circle back to the was-tx-mined-compact function of the clarity-bitcoin-lib contract for a second and analyze the order/type of parameters.

You can see that it intakes the parameters with a certain typing and order:

  • (height uint) the block height you are looking to verify the transaction within

  • (tx (buff 1024)) the raw transaction hex of the transaction you are looking to verify

  • (header (buff 80)) the block header of the block

  • (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint}) a merkle proof formatted as a Clarity tuple

In short, the was-tx-mined-compact function takes the block height, the transaction hex, the block header, and a merkle proof, and determines that:

  • the block header corresponds to the block that was mined at the given Bitcoin height

  • the transaction's merkle proof links it to the block header's merkle root.

The current version of the clarity-bitcoin-lib is version 7. Click here for the deployed mainnet contract.

So after you've fetched the bitcoin tx metadata and have removed the witness data from the original transaction hex, let's go ahead and prepare the metadata into it's Clarity parameter values.

What is the purpose of the merkle proof?

A Merkle proof is a mathematically efficient and compact manner to prove that a transaction is included in a block in the Bitcoin blockchain.

How transactions are combined into the Merkle root

Transactions in a block are hashed and paired, then the hashes of the pairs are hashed and paired, and so on until a single hash remains — this is called the Merkle root.

Merkle root in the block header

The Merkle root is included in the block header. By providing the hashes that lead from a transaction's hash up to the Merkle root, along with the block header, one can prove that the transaction is included in that block.

Merkle proof (Merkle path)

The hashes that connect a transaction to the Merkle root are called the Merkle proof or Merkle path. By providing the Merkle proof along with the transaction hash and block header, anyone can verify that the transaction is part of that block.

Efficient decentralized verification

This allows for efficient decentralized verification of transactions without having to download the entire blockchain. One only needs the transaction hash, Merkle proof, and block header to verify.

Why are we removing the witness data from the original tx hex?

The clarity-bitcoin-lib contract's was-tx-mined-compact function only accepts a non-witness transaction hex. Usually only legacy bitcoin transactions are deemed as non-witness transactions. The contract also has a dedicated function for bitcoin transactions with witness data called was-segwit-tx-mined-compact .

But you could still verify any transaction type in was-tx-mined-compact by simply removing the witness data from the original tx hex.

3

Invoke `was-tx-mined-compact` function

Construct a read-only function call, pass in the contract call options, and await the results. If the bitcoin transaction in question is indeed mined in an existing Bitcoin block, the contract will return a response that looks like:

(ok 0xfb88309b4041f76bea0c196633c768dca82bb0dd424cbfe19be38569007f92d9)

You'll see your exact bitcoin txid returned wrapped in an ok response.

That's the end-to-end flow: fetch the bitcoin transaction metadata and its merkle proof, prepare and assemble the data for the contract's read-only function call, and finally call the Clarity contract function that verifies inclusion in a Bitcoin block.


Example Usage

Here are some example projects/contracts that would leverage the clarity-bitcoin-lib contract in their own code.

Square Runes

A Clarity implementation for parsing Bitcoin Runes protocol data. Check out the project repo here.

Bitcoin Transaction Enabled NFT

This contract example implements a basic NFT collection that is mintable only upon a user's bitcoin transaction. You can find this template available in the Hiro Platform.


Additional Resources

  • [Hiro YT] Bitcoin Enabled NFT - Stacks NFTs Minted By Bitcoin Transactions

  • [Hiro Blog] How to Use Bitcoin Data in Clarity Unit Tests

Last updated

Was this helpful?