Zero Knowledge Proof Verification On Bitcoin

Martin Jonas
·
08.11.2024
·

Introduction

BitVMX is a protocol that allows two parties to execute a program off-chain and the correct execution of this program to be challenged on-chain. This enables the possibility to create Bitcoin transactions where the spend condition can be resolved based on the result of such computation.

To use BitVMX, the program must be agreed upon by both parties before locking the funds. Later, one party can provide an input that satisfies the conditions of the program and thus claim the funds.

To accomplish this, BitVMX includes a BitVMX-CPU based on the RISC-V32IM architecture, which can run arbitrary programs. Additionally, we have implemented the necessary Bitcoin Script programs that allow for challenging any of the individual RISC-V operations.

Why do we need Zero Knowledge Proofs?

The short answer is costs. But let’s look at why not using ZKPs would be more expensive.

Transmitting Information Between Transactions

The BitVMX protocol requires that information sent in one transaction can be referenced in a later transaction without the risk of this information being tampered with by any of the parties involved. However, Bitcoin Script lacks opcodes that allows introspection or enables to inspect data inside other transactions, so more complex mechanisms are necessary.

One approach is to use One-Time Signatures, specifically Lamport and Winternitz, in combination with linked messages, as described in the BitVMX paper. While this method achieves the desired security, verifying these signatures in Bitcoin Script has significant overhead: approximately 400x for Lamport and 200x for Winternitz. For instance, if the input to verify is 10 KB, this would require a 2 MB Bitcoin Script, which becomes impractically large.

Zero-Knowledge Proofs (ZKPs)

A key advantage of ZKPs is that they allow the prover to convince the verifier they have the correct input for a program—without revealing that input. Beyond input privacy, a significant benefit is that some algorithms, particularly Groth16, produce proofs of constant size, around only 300 bytes.

With ZKPs, only these 300 bytes need to be encoded using One-Time Signatures (OTS), reducing the script size to approximately 60 KB while still ensuring that BitVMX and the involved parties have the same security guarantee that the correct input was used for verification.

To validate this approach, we developed a proof of concept (PoC), making it one of the first BitVM-derived designs to successfully verify a ZKP on the Bitcoin mainnet.

A note about ZK Algorithms

An interesting design decision about BitVMX is the implementation of a general-purpose CPU that operates independently of the specific ZK algorithm used. This means that if there is the need to use a different algorithm for some reason (security, cost, etc), the verification function can be easily changed by compiling a new one, written in C or RUST, into RISC-V

How We Did It

RISC-Zero

RISC-zero is a platform designed to simplify the process of writing ZKPs. The documentation and main repository are good sources of information to understand how ZK technology and their protocol works.

As described in their site:
“A zero knowledge proof allows one party (the prover) to convince another party (the verifier) that something is true without revealing all the details. In the case of RISC Zero, the prover can show they correctly executed some code (known to both parties), while only revealing to the verifier the output of the code, not any of its inputs or any state during execution.
The code runs in a special virtual machine, called a zkVM. The RISC Zero zkVM emulates a small RISC-V computer, allowing it to run arbitrary code in any language, so long as a compiler toolchain exists that targets RISC-V. Currently, SDK support exists for Rust, C, and C⁠+⁠+.”

(Note: the use of RISC-V as architecture for BitVMX is coincidental and operates independently of the RISC Zero zkVM. The verification programs are different between these two layers)

Citrea PoC

As part of our research, we found an amazing project by the Citrea team, who created a PoC allowing a RISC Zero program to be verified using a Groth16 verifier written in C. They made this work to be used with a BitVM1 implementation once it became available. However, the BitVM team eventually shifted its design from a general-purpose CPU to a pure Bitcoin Script implementation that verifies the proof on-chain. As a result, this project became outdated.

Although the work wasn't complete, it was actually very close to what we needed. In fact, we used the binary verifier they uploaded in their repository for our initial testing.

Phases of ZKP

Using a ZKP generally involves three distinct phases: setup, proving and verification.

  1. Setup: Both parties agree on the program that will be executed to verify certain input. While doing this some cryptographic hash or signature of the program will be taken to ensure that the program is not modified later.
  2. Proving: Once the prover has the input that satisfies the agreed program, the prover will privately execute the program, and the proving system will generate the proof.
  3. Verification: In the final phase, the verifier uses the proof generated on the previous step by the prover (and shared through some channel with the verifier) to confirm that the prover indeed knows an input that satisfies the agreed program, and the program was executed (Although not knowing the input).

Implementation Details

Simplified Version

Before examining the diagram of the ZKP implementation, let’s first consider a simplified BitVMX setup where the verification function is compiled directly for the BitVMX-CPU, and the prover’s input is provided as the complete input.

The components shown in green must be agreed upon by both parties before locking any funds. These components include:

  • The compiled verification program.
  • The taptree, which forms part of the Dispute Resolution Protocol (DRP), specifically, the execution challenge built from the compiled program.

When the prover is ready to claim the funds will post a transaction with the input that satisfies this verification program (note: in reality there are some happy paths that avoid posting any input if both parties agree off-chain).

Once the verifier observes this transaction will retrieve this input from the transaction and use it to execute the program off-chain in the BitVMX-CPU emulator. Based on the results of this execution, the verifier decides whether or not to challenge the prover on-chain.

As the DRP is well designed, if the prover claim is valid and the verifier initiates a challenge, the verifier will lose and the prover will take the funds. Conversely, if the prover's input does not satisfy the conditions, the verifier will block the prover from taking the funds.

Full Implementation

As we discussed, to reduce the size of the input data on-chain, we need to take advantage of the ZKPs.

In this updated diagram, two new boxes represent the RISC-Zero components of the process.

  1. The first layer represented in the box on the top is the STARK-based ZKP implemented in RISC-Zero. In this setup, the same function from the simplified version, previously compiled directly into BitVMX-CPU, is now treated as “guest” code and compiled to run inside the RISC-Zero ZK-VM. This compiled code is associated with a unique cryptographic ID (derived from the .elf binary) used in subsequent steps.
  2. ZKP Conversion Layer (Second Box): Here, the STARK proof is converted into a SNARK. This step reduces proof size—transforming the original STARK proof (thousands of KB) into a much smaller SNARK proof (approximately 300 bytes).
  3. Groth16 Verifier (Third Box): This box represents a Groth16 verifier written in C. It verifies that:
    • The proof corresponds to the specific cryptographic ID of the program compiled in the first step,
    • The program was executed with the correct input, and
    • The result of the execution was successful.
    If any of these conditions aren’t met, verification will fail.
  4. DRP (Fourth Box): Similar to the simplified version, this layer includes a taptree that permits to challenge the execution of the verification function on-chain.

Proving and Verification Phases:
During the proving phase, the prover will provide (off-chain) the full input to the RISC-Zero proving system, generating a STARK proof. This proof is then converted to a SNARK via the stark2snark process, resulting in a Groth16 proof. This last proof is then posted on-chain, signed with OTS.

For verification, the verifier runs the input in the emulator, as in the simplified version, and may initiate an on-chain challenge if necessary.

Verifier Execution

When we first ran the binary verifier from Citrea's PoC, we noticed it took significantly longer to execute compared to simpler tests we had previously run on our emulator. After measuring, we found it required around 4 billion execution steps to complete.

The initial BitVMX-CPU implementation was based on RISC-V32 “i”, meaning it lacked native support for multiplication, division, and modulo operations. When one of these operations was needed, the compiler resolved it through addition, which, while functional, was highly inefficient.

Since our n-ary search for the failure point requires steps logarithmic to the total execution steps, it was still manageable, but we anticipated that implementing these operations directly would lead to substantial improvements. Therefore, we added the “m” extension from the RISC-V specification. The results were impressive: the updated verifier required only ~400 million steps—a 10x improvement and one fewer round in the n-ary search.

Summary

BitVMX leverages zero-knowledge proofs (ZKPs) to enable two parties to verify the correctness of computations off-chain while allowing challenges to be resolved on-chain. By combining BitVMX with RISC-Zero, we achieve a solution that ensures security and efficiency for complex verification tasks on Bitcoin.

We invite you to explore the open-source BitVMX-CPU and the components used to implement this ZKP verification. Also if you are curious take a look at the BitVMX-explorer where you can see the example transactions of the Dispute Resolution Protocol on mainnet.

Join our community