From 56f2c5209a0d3d4f64aad63c5db04cb1eb2d9fe5 Mon Sep 17 00:00:00 2001 From: Yusuf Idi Maina <120538505+yusufidimaina9989@users.noreply.github.com> Date: Fri, 18 Jul 2025 09:59:18 +0100 Subject: [PATCH 1/3] Add oracle.md --- btc-docs/tutorials/oracle.md | 183 +++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 btc-docs/tutorials/oracle.md diff --git a/btc-docs/tutorials/oracle.md b/btc-docs/tutorials/oracle.md new file mode 100644 index 000000000..e69132a0a --- /dev/null +++ b/btc-docs/tutorials/oracle.md @@ -0,0 +1,183 @@ +--- +sidebar_position: 3 +--- + +# Tutorial 3: Oracle + +## Overview + +In this tutorial, we will go over how to build a smart contract that consumes off-chain data from an oracle. Specifically, we will implement a smart contract that lets two players bet on the price of BTC at some point in the future. It retrieves prices from an oracle. + +### What is an Oracle? +A blockchain oracle is a third-party service or agent that provides external data to a blockchain network. It is a bridge between the blockchain and the external world, enabling smart contracts to access, verify, and incorporate data from outside the blockchain. This allows smart contracts to execute based on real-world events and conditions, enhancing their utility and functionality. + +![](../../static/img/oracle.jpeg) + +[Credit: bitnovo](https://blog.bitnovo.com/en/what-is-a-blockchain-oracle/) + +The data supplied by oracles can include various types of information, such as stock prices, weather data, election results, and sports scores. + +### Rabin Signatures +A digital signature is required to verify the authenticity and integrity of arbitrary data provided by known oracles in a smart contract. Instead of ECDSA used in Bitcoin, we use an alternative digital signature algorithm called [Rabin signatures](https://en.wikipedia.org/wiki/Rabin_signature_algorithm). This is because Rabin signature verification is orders of magnitude cheaper than ECDSA. +We have implemented [Rabin signature](https://github.com/sCrypt-Inc/scrypt-ts-lib/blob/master/src/rabinSignature.ts) as part of the standard libraries [`scrypt-ts-lib-btc`](https://www.npmjs.com/package/scrypt-ts-lib-btc), which can be imported and used directly. + +## Contract Properties + +Our contract will take signed pricing data from the [WitnessOnChain oracle](https://witnessonchain.com). Depending if the price target is reached or not, it will pay out a reward to one of the two players. + +There are quite a few properties which our price betting smart contract will require: + +```ts +// Price target that needs to be reached. +@prop() +targetPrice: bigint + +// Symbol of the pair, e.g. "BTC_USDC" +@prop() +symbol: ByteString + +// Timestamp window in which the price target needs to be reached. +@prop() +timestampFrom: bigint +@prop() +timestampTo: bigint + +// Oracles Rabin public key. +@prop() +oraclePubKey: RabinPubKey + +// Addresses of both players. +@prop() +aliceAddr: Addr +@prop() +bobAddr: Addr +``` + +Notice that the type `RabinPubKey`, which represents a Rabin public key, is not a standard type. You can import it the following way: + +```ts +import { RabinPubKey } from 'scrypt-ts-lib-btc' +``` + +## Public Method - `unlock` + +The contract will have only a single public method, namely `unlock`. As parameters, it will take the oracles signature, the signed message from the oracle, and a signature of the winner, who can unlock the funds: + +```ts +@method() +public unlock(msg: ByteString, sig: RabinSig, winnerSig: Sig) { + // Verify oracle signature. + assert( + RabinVerifierWOC.verifySig(msg, sig, this.oraclePubKey), + 'Oracle sig verify failed.' + ) + + // Decode data. + const exchangeRate = PriceBet.parseExchangeRate(msg) + + // Validate data. + assert( + exchangeRate.timestamp >= this.timestampFrom, + 'Timestamp too early.' + ) + assert( + exchangeRate.timestamp <= this.timestampTo, + 'Timestamp too late.' + ) + assert(exchangeRate.symbol == this.symbol, 'Wrong symbol.') + + // Decide winner and check their signature. + const winner = + exchangeRate.price >= this.targetPrice + ? this.alicePubKey + : this.bobPubKey + assert(this.checkSig(winnerSig, winner)) +} +``` + +Let's walk through each part. + +First, we verify that the passed signature is correct. For that we use the `RabinVerifierWOC` library from the [`scrypt-ts-lib-btc`](https://www.npmjs.com/package/scrypt-ts-lib-btc) package + +```ts +import { RabinPubKey, RabinSig, RabinVerifierWoc } from 'scrypt-ts-lib-btc' +``` + +Now, we can call the `verifySig` method of the verification library: +```ts +// Verify oracle signature. +assert( + RabinVerifierWOC.verifySig(msg, sig, this.oraclePubKey), + 'Oracle sig verify failed.' +) +``` +The verification method requires the message signed by the oracle, the oracles signature for the message, and the oracle's public key, which we already set via the constructor. + +Next, we need to parse information from the chunk of data that is the signed message and assert on it. For a granular description of the message format check out the `"Exchange Rate"` section in the [WitnessOnChain API docs](https://witnessonchain.com). + +We need to implement the static method `parseExchangeRate` as follows: + +```ts +// Parses signed message from the oracle. +@method() +static parseExchangeRate(msg: ByteString): ExchangeRate { + // 4 bytes timestamp (LE) + 8 bytes rate (LE) + 1 byte decimal + 16 bytes symbol + return { + timestamp: Utils.fromLEUnsigned(slice(msg, 0n, 4n)), + price: Utils.fromLEUnsigned(slice(msg, 4n, 12n)), + symbol: slice(msg, 13n, 29n), + } +} +``` + +We parse out the following data: +- `timestamp` - The time at which this exchange rate is present. +- `price` - The exchange rate encoded as an integer -> (priceFloat * (10^decimal)). +- `symbol` - The symbol of the token pair, e.g. `BTC_USDC`. + +Finally, we wrap the parsed values in a custom type, named `ExchangeRate` and return it. Here's the definition of the type: + +```ts +type ExchangeRate = { + timestamp: bigint + price: bigint + symbol: ByteString +} +``` + +Now we can validate the data. First, we check if the timestamp of the exchange rate is within our specified range that we bet on: + +```ts +assert( + exchangeRate.timestamp >= this.timestampFrom, + 'Timestamp too early.' +) +assert( + exchangeRate.timestamp <= this.timestampTo, + 'Timestamp too late.' +) +``` + +Additionally, we check if the exchange rate is actually for the correct token pair: + +```ts +assert(exchangeRate.symbol == this.symbol, 'Wrong symbol.') +``` + +Lastly, upon having all the necessary information, we can choose the winner and check their signature: + +```ts +const winner = + exchangeRate.price >= this.targetPrice + ? this.alicePubKey + : this.bobPubKey +assert(this.checkSig(winnerSig, winner)) +``` + +As we can see, if the target price is reached, only Alice is able to unlock the funds, and if not, then only Bob is able to do so. + + +## Conclusion + +Congratulations! You have completed the oracle tutorial! + From bb23ac1d8879c4a5a0dec29ba8c66bafa0b6d08e Mon Sep 17 00:00:00 2001 From: Yusuf Idi Maina <120538505+yusufidimaina9989@users.noreply.github.com> Date: Fri, 18 Jul 2025 10:30:09 +0100 Subject: [PATCH 2/3] Add tic-tac-toe.md --- btc-docs/tutorials/tic-tac-toe.md | 348 ++++++++++++++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 btc-docs/tutorials/tic-tac-toe.md diff --git a/btc-docs/tutorials/tic-tac-toe.md b/btc-docs/tutorials/tic-tac-toe.md new file mode 100644 index 000000000..57202c60e --- /dev/null +++ b/btc-docs/tutorials/tic-tac-toe.md @@ -0,0 +1,348 @@ +--- +sidebar_position: 4 +--- + +# Tutorial 4: Tic Tac Toe + +## Overview + +In this tutorial, we will go over how to use sCrypt to build a Tic-Tac-Toe Contract on Bitcoin. + +It is initialized with the Bitcoin public key of two players (Alice and Bob respectively). They each bet the same amount and lock it into the contract. The winner takes all bitcoins locked in the contract. If no one wins and there is a draw, the two players can each withdraw half of the money. + +## Contract Properties + +Use `@prop` decorator to mark any property that intends to be stored on chain. This decorator accepts a boolean parameter. By default, it is set to `false`, meaning the property cannot be changed after the contract is deployed. If it is `true`, the property is a so-called [stateful](../how-to-write-a-contract/stateful-contract.md) property and its value can be updated in subsequent contract calls. + +The tic-tac-toe contract supports two players and their public keys need to be saved. It contains the following contract properties: + +- Two stateless properties `alice` and `bob`, both of which are `PubKey` type. +- Two stateful properties: + - `is_alice_turn`: a `boolean`. It represents whether it is `alice`'s turn to play. + - `board`: a fixed-size array `FixedArray` with a size of `9`. It represents the state of every square in the board. +- Three constants: + - `EMPTY`, type `bigint`, value `0n`. It means that a square in the board is empty + - `ALICE`, type `bigint`, value `1n`. Alice places symbol `X` in a square. + - `BOB`, type `bigint`, value `2n`. Bob places symbol `O` in a square. + +```ts +@prop() +alice: PubKey; // alice's public Key +@prop() +bob: PubKey; // bob's public Key + +@prop(true) +is_alice_turn: boolean; // stateful property, it represents whether it is `alice`'s turn to play. + +@prop(true) +board: FixedArray; // stateful property, a fixed-size array, it represents the state of every square in the board. + +@prop() +static readonly EMPTY: bigint = 0n; // static property, it means that the a square in the board is empty +@prop() +static readonly ALICE: bigint = 1n; // static property, it means that alice places symbol `X` in a square. +@prop() +static readonly BOB: bigint = 2n; // static property, it means that bob places symbol `O` in a square. +``` + +## Constructor + +Initialize all non-static properties in the constructor. Specifically, the entire board is empty at first. + +```ts +constructor(alice: PubKey, bob: PubKey) { + super(...arguments); + this.alice = alice; + this.bob = bob; + this.is_alice_turn = true; + this.board = fill(TicTacToe.EMPTY, 9); +} +``` + +## Public Methods + +A public `@method` can be called from an external transaction. The call succeeds if it runs to completion without violating any conditions in `assert()`. + +The `TicTacToe` contract have a public `@method` called `move()` with `2` parameters: + +```ts +/** + * play the game by calling move() + * @param n which square to place the symbol + * @param sig a player's signature + */ +@method() +public move(n: bigint, sig: Sig) { + assert(n >= 0n && n < 9n); +} +``` + +Alice and Bob each locks X bitcoins in a UTXO containing contract `TicTacToe`. Next, they alternately play the game by calling `move()`. + +### Signature Verification + +Once the game contract is deployed, anyone can view and potentially interact with it. We need a authentication mechanism to ensure only the desired player can update the contract if it's their turn. This is achieved using ditigal signatures. + +`this.checkSig()` is used to verify a signature against a public key. Use it to verify the `sig` parameter against the desired player in `move()`, identified by their public key stored in the contract's properties. + +```ts +// check signature `sig` +let player: PubKey = this.is_alice_turn ? this.alice : this.bob; +assert(this.checkSig(sig, player), `checkSig failed, pubkey: ${player}`); +``` + +## Non-Public Methods + +Without a `public` modifier, a `@method` is internal and cannot be directly called from an external transaction. + +The `TicTacToe` contract have two **Non-Public** methods: + +- `won()` : iterate over the `lines` array to check if a player has won the game. returns `boolean` type. +- `full()` : traverse all the squares of the board to check if all squares of the board have symbols. returns `boolean` type. + +```ts +@method() +won(play: bigint) : boolean { + let lines: FixedArray, 8> = [ + [0n, 1n, 2n], + [3n, 4n, 5n], + [6n, 7n, 8n], + [0n, 3n, 6n], + [1n, 4n, 7n], + [2n, 5n, 8n], + [0n, 4n, 8n], + [2n, 4n, 6n] + ]; + + let anyLine = false; + + for (let i = 0; i < 8; i++) { + let line = true; + for (let j = 0; j < 3; j++) { + line = line && this.board[Number(lines[i][j])] === play; + } + + anyLine = anyLine || line; + } + + return anyLine; +} + +@method() +full() : boolean { + let full = true; + for (let i = 0; i < 9; i++) { + full = full && this.board[i] !== TicTacToe.EMPTY; + } + return full; +} +``` + +## Maintain Game State + +We can directly access the [ScriptContext](../how-to-write-a-contract/scriptcontext.md) through `this.ctx` in the public `@method` `move()` to maintain game state. It can be considered additional information a public method gets when called, besides its function parameters. + +Contract maintenance state consists of the following three steps: + +### Step 1 + +Update the stateful properties in public `@method`. + +A player call `move()` to places the symbol in the board. We should update the stateful properties `board` and `is_alice_turn` in the `move()` `@method`: + +```ts +assert( + this.board[Number(n)] === TicTacToe.EMPTY, + `board at position ${n} is not empty: ${this.board[Number(n)]}` +); +let play = this.is_alice_turn ? TicTacToe.ALICE : TicTacToe.BOB; +// update stateful properties to make the move +this.board[Number(n)] = play; // Number() converts a bigint to a number +this.is_alice_turn = !this.is_alice_turn; +``` + +### Step 2 + +When you are ready to pass the new state onto the output[s] in the current spending transaction, simply call a built-in function `this.buildStateOutput()` to create an output containing the new state. It takes an input: the number of satoshis in the output. We keep the satoshis unchanged in the example. + +```ts +let output = this.buildStateOutput(); +``` + +#### Build outputs in public `@method` + +`TicTacToe` can contain the following three types of output during execution: + +1. The game is not over: a output containing the new state and a change output +2. A player wins the game: a `P2PKH` output that pays the winner, and a change output. +3. A draw: two `P2PKH` outputs that split the contract-locked bets equally between the players and a change output. + +The `P2PKH` output can be built using `Utils.buildPublicKeyHashOutput(pkh: PubKeyHash, amount: bigint)`. The [change output](https://wiki.bitcoinsv.io/index.php/Change) can be built using `this.buildChangeOutput()`. + +```ts +// build the transation outputs +let outputs = toByteString(""); +if (this.won(play)) { + outputs = Utils.buildPublicKeyHashOutput( + pubKey2Addr(player), + this.ctx.utxo.value + ); + + const outputScript: ByteString = TxUtils.buildP2PKHScript( + pubKey2Addr(player) + ); + outputs: ByteString = TxUtils.buildOutput(outputScript, this.ctx.spentAmount); +} else if (this.full()) { + const halfAmount = this.ctx.spentAmount / 2n; + const aliceOutput: ByteString = TxUtils.buildP2PKHOutput( + this.alice, + TxUtils.toSatoshis(halfAmount) + ); + const bobOutput: ByteString = TxUtils.buildP2PKHOutput( + this.bob, + TxUtils.toSatoshis(halfAmount) + ); + outputs = aliceOutput + bobOutput; +} else { + // build a output that contains latest contract state. + outputs = this.buildStateOutput(); +} + +outputs += this.buildChangeOutput(); +``` + +### Step 3 + +Make sure that the output of the current transaction must contain this incremented new state. If all outputs (only a single output here) we create in the contract hashes to `hashOutputs` in `ScriptContext`, we can be sure they are the outputs of the current transaction. Therefore, the updated state is propagated. + +```ts +// verify current tx has this single output +assert(this.ctx.shaOutputs == sha256(outputs), "shaOutputs mismatch"); +``` + +# Conclusion + +Congratulations, you have completed the `TicTacToe` contract! + +The [final complete code](https://github.com/sCrypt-Inc/tic-tac-toe/blob/main/src/contracts/tictactoe.ts) is as follows: + +```ts +export class TicTacToe extends SmartContract { + @prop() + alice: PubKey; + @prop() + bob: PubKey; + + @prop(true) + is_alice_turn: boolean; + + @prop(true) + board: FixedArray; + + @prop() + static readonly EMPTY: bigint = 0n; + @prop() + static readonly ALICE: bigint = 1n; + @prop() + static readonly BOB: bigint = 2n; + + constructor(alice: PubKey, bob: PubKey) { + super(...arguments); + this.alice = alice; + this.bob = bob; + this.is_alice_turn = true; + this.board = fill(TicTacToe.EMPTY, 9); + } + + @method() + public move(n: bigint, sig: Sig) { + // check position `n` + assert(n >= 0n && n < 9n); + // check signature `sig` + let player: PubKey = this.is_alice_turn ? this.alice : this.bob; + assert(this.checkSig(sig, player), `checkSig failed, pubkey: ${player}`); + // update stateful properties to make the move + assert( + this.board[Number(n)] === TicTacToe.EMPTY, + `board at position ${n} is not empty: ${this.board[Number(n)]}` + ); + let play = this.is_alice_turn ? TicTacToe.ALICE : TicTacToe.BOB; + this.board[Number(n)] = play; + this.is_alice_turn = !this.is_alice_turn; + + // build the transation outputs + let outputs = toByteString(""); + if (this.won(play)) { + outputs = Utils.buildPublicKeyHashOutput( + pubKey2Addr(player), + this.ctx.utxo.value + ); + + const outputScript: ByteString = TxUtils.buildP2PKHScript( + pubKey2Addr(player) + ); + outputs: ByteString = TxUtils.buildOutput( + outputScript, + this.ctx.spentAmount + ); + } else if (this.full()) { + const halfAmount = this.ctx.spentAmount / 2n; + const aliceOutput: ByteString = TxUtils.buildP2PKHOutput( + this.alice, + TxUtils.toSatoshis(halfAmount) + ); + const bobOutput: ByteString = TxUtils.buildP2PKHOutput( + this.bob, + TxUtils.toSatoshis(halfAmount) + ); + outputs = aliceOutput + bobOutput; + } else { + // build a output that contains latest contract state. + outputs = this.buildStateOutput(); + } + + outputs += this.buildChangeOutput(); + + // make sure the transaction contains the expected outputs built above + assert(this.ctx.shaOutputs == sha256(outputs), "shaOutputs mismatch"); + } + + @method() + won(play: bigint): boolean { + let lines: FixedArray, 8> = [ + [0n, 1n, 2n], + [3n, 4n, 5n], + [6n, 7n, 8n], + [0n, 3n, 6n], + [1n, 4n, 7n], + [2n, 5n, 8n], + [0n, 4n, 8n], + [2n, 4n, 6n], + ]; + + let anyLine = false; + + for (let i = 0; i < 8; i++) { + let line = true; + for (let j = 0; j < 3; j++) { + line = line && this.board[Number(lines[i][j])] === play; + } + + anyLine = anyLine || line; + } + + return anyLine; + } + + @method() + full(): boolean { + let full = true; + for (let i = 0; i < 9; i++) { + full = full && this.board[i] !== TicTacToe.EMPTY; + } + return full; + } +} +``` + +But no dApp is complete if users cannot interact with it. Go [here](../how-to-integrate-a-frontend/how-to-integrate-a-frontend.md) to see how to add a front-end to it. From bd697332a9cdb0ed2c0c76299dd4d375d7c20409 Mon Sep 17 00:00:00 2001 From: Yusuf Idi Maina <120538505+yusufidimaina9989@users.noreply.github.com> Date: Thu, 24 Jul 2025 13:28:14 +0100 Subject: [PATCH 3/3] Add zkp.md --- btc-docs/tutorials/zkp.md | 304 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 btc-docs/tutorials/zkp.md diff --git a/btc-docs/tutorials/zkp.md b/btc-docs/tutorials/zkp.md new file mode 100644 index 000000000..fbde5b89d --- /dev/null +++ b/btc-docs/tutorials/zkp.md @@ -0,0 +1,304 @@ +--- +sidebar_position: 5 +--- + +# Tutorial 5: Zero Knowledge Proofs + +## Overview + +In this tutorial we will go over how to create a zero-knowledge proof (ZKP) and verify it on Bitcoin using sCrypt. + +### What are zk-SNARKS? + +SNARK (zero-knowledge Succinct Non-interactive ARguments of Knowledge) is a type of ZKP that is amenable for blockchains. The generated proof is “succinct” and “non-interactive”: a proof is only a few hundred bytes and can be verified in constant time and within a few milliseconds, without needing to ask additional questions of the prover. Together, these properties make zk-SNARK especially suitable for blockchains, where on-chain storage and computation can be expensive and senders often go offline after sending a transaction. + +A proof is constructed off-chain by a prover who generates the proof using a secret input (often referred to as the "witness") and a public input. The prover can then use this proof as an input for an sCrypt smart contract, which can verify the validity of the proof using a verification key and the public input. + +![](../../static/img/zksnark-verifier.png) + +[Credit: altoros](https://www.altoros.com/blog/securing-a-blockchain-with-a-noninteractive-zero-knowledge-proof/) + + +There are many tools for creating such proofs, [ZoKrates](https://github.com/sCrypt-Inc/zokrates) and [SnarkJS](https://github.com/sCrypt-Inc/snarkjs) are among the most popular. + +In this example we will use ZoKrates. It provides a python-like higher-level language for developers to code the computational problem they want to prove. + +For a more comprehensive explanation of zk-SNARKS and how they work, we recommend reading [this blog post](https://xiaohuiliu.medium.com/zk-snarks-on-bitcoin-239d96d182bd). + +## Install ZoKrates + +Run the following command to install [released binaries](https://github.com/sCrypt-Inc/zokrates/releases): + +```sh +curl -Ls https://scrypt.io/scripts/setup-zokrates.sh | sh -s - +``` + +or build from source: + +```sh +git clone https://github.com/sCrypt-Inc/zokrates +cd ZoKrates +cargo +nightly build -p zokrates_cli --release +cd target/release +``` + +## ZoKrates Workflow + +### 1. Design a circuit + +Create a new ZoKrates file named `factor.zok` with the following content: + +```python +// p, q are the factors of n +def main(private field p, private field q, field n) { + assert(p * q == n); + assert(p > 1); + assert(q > 1); + return; +} +``` + +This simple circuit/program proves one knows a factorization of an integer `n` into two integers, without revealing the factors. The circuit has two private inputs named `p` and `q` and one public input named `n`. + + +### 2. Compile the circuit + +Compile the circuit with the following command: + +```sh +zokrates compile -i factor.zok +``` + +This generates two files that encode the circuit in binary and human-readable format. + +### 3. Setup + +This generates a proving key and a verification key for this circuit. + +```sh +zokrates setup +``` + +### 4. Calculate a witness + +A proof attests that a prover knows some secret/private information that satisfies the original program. This secret information is called witness. In the following example, `7` and `13` are the witnesses, as they are factors of `91`. + +```sh +zokrates compute-witness -a 7 13 91 +``` + +A file named `witness` is generated. + +### 5. Creating a proof + +The following command produces a proof, using both the proving key and the witness: + +```sh +zokrates generate-proof +``` + +The resulting file `proof.json` looks like the following: + +```json +{ + "scheme": "g16", + "curve": "bn128", + "proof": { + "a": [ + "0x0a7ea3ca37865347396645d017c7623431d13103e9107c937d722e5da15f352b", + "0x040c202ba8fa153f84af8dabc2ca40ff534f54efeb3271acc04a70c41afd079b" + ], + "b": [ + [ + "0x0ec1e4faea792762de35dcfd0da0e6859ce491cafad455c334d2c72cb8b24550", + "0x0985ef1d036b41d44376c1d42ff803b7cab9f9d4cf5bd75298e0fab2d109f096" + ], + [ + "0x265151afd8626b4c72dfefb86bac2b63489423d6cf895ed9fa186548b0b9e3f3", + "0x301f2b356621408e037649d0f5b4ad5f4b2333f58453791cc24f07d5673349bf" + ] + ], + "c": [ + "0x2b75a257d68763100ca11afb3beae511732c1cd1d3f1ce1804cbc0c26043cb6b", + "0x2f80c706b58482eec9e759fce805585595a76c27e37b67af3463414246fbabbd" + ] + }, + "inputs": [ + "0x000000000000000000000000000000000000000000000000000000000000005b" + ] +} +``` + +### 6. Export an sCrypt verifier + +Using our version of ZoKrates, we can export a project template, which will contain a verifier for our circuit. Simply run the following command: + +```sh +zokrates export-verifier-scrypt +``` + +This will create a directory named `verifier`, containing the project. Let's set it up. Run the following: + +```sh +cd verifier && git init && npm i +``` + +Now the verifier is ready to be used. In the following section we will go over the code and show how to use it. + + +### 7. Run the sCrypt Verifier + +In the generated project, let's open the file `src/contracts/verifier.ts`. This file contains an sCrypt smart contract, named `Verifier`, which can be unlocked by providing a valid ZK proof. + +Under the hood it uses the `SNARK` library from `src/contracts/snark.ts`. This file includes an elliptic curve implementation along with a library that implements pairings over that elliptic curve and lastly the implementation of the proof verification algorithm. In our example the [`BN-256` elliptic curve](https://hackmd.io/@jpw/bn254) is being used along with the [`Groth-16` proof system](https://eprint.iacr.org/2016/260.pdf).. + +Let's take a look at the implementation of `Verifier`: + +```ts +export class Verifier extends SmartContract { + + @prop() + vk: VerifyingKey + + @prop() + publicInputs: FixedArray, + + constructor( + vk: VerifyingKey, + publicInputs: FixedArray, + ) { + super(...arguments) + this.vk = vk + this.publicInputs = publicInputs + } + + @method() + public verifyProof( + proof: Proof + ) { + assert(SNARK.verify(this.vk, this.publicInputs, proof)) + } + +} +``` + +As we can see, the contract has two properties, namely the verification key and the value(s) of the public inputs to our ZK program. + +The contract also has a public method named `verifyProof`. As the name implies it verifies a ZK proof and can be unlocked by a valid one. The proof is passed as a parameter. The method calls the proof verification function: + +```ts +SNARK.verify(this.vk, this.publicInputs, proof) +``` + +The function takes as parameters the verification key, the public inputs and the proof. It's important to note that the proof is cryptographically tied to the verification key and thus must be a proof about the correct ZoKrates program (`factor.zok`). + +The generated project will also contain a deployment script `deploy.ts`. Let's take a look at the code: + +```ts +async function main() { + await Verifier.loadArtifact() + + // TODO: Adjust the amount of satoshis locked in the smart contract: + const amount = 100 + + // TODO: Insert public input values here: + const publicInputs: FixedArray = [ 0n ] + + let verifier = new Verifier( + prepareVerifyingKey(VERIFYING_KEY_DATA), + publicInputs + ) + + // Connect to a signer. + await verifier.connect(getDefaultSigner()) + + // Deploy: + const deployTx = await verifier.deploy(amount) + console.log('Verifier contract deployed: ', deployTx.id) +} + +main() +``` + +We can observe that we need to adjust two things. First, we need to set the amount of satoshis we will lock into the deployed smart contract. The second thing is the public input value, i.e. the product of the secret factors. Let's set it to the value `91`: + +```ts +const publicInputs: FixedArray = [ 91n ] +``` + +Note also, that ZoKrates already provided us with the values of the verification key, that we created during the setup phase. + +Now, we can build and deploy the contract. Simply run: + +```sh +npm run deploy +``` + +The first time you run the command, it will ask you to fund a testnet address. You can fund it using [mempool faucet](https://faucet.mempool.co/). + +After a successful run you should see something like the following: + +``` +Verifier contract deployed: 2396a4e52555cdc29795db281d17de423697bd5cbabbcb756cb14cea8e947235 +``` + +The smart contract is now deployed and can be unlocked using a valid proof, that proves the knowledge of the factors for the integer `91`. You can see [the transaction](https://test.whatsonchain.com/tx/2396a4e52555cdc29795db281d17de423697bd5cbabbcb756cb14cea8e947235) using a block explorer. + +Let's call the deployed contract. Let's create a file named `call.ts` with the following content: + +```ts +import { DefaultProvider } from 'scrypt-ts-btc' +import { parseProofFile } from './src/util' +import { Verifier } from './src/contracts/verifier' +import { Proof } from './src/contracts/snark' +import { getDefaultSigner } from './tests/utils/helper' +import { PathLike } from 'fs' + +export async function call(txId: string, proofPath: PathLike) { + await Verifier.loadArtifact() + + // Fetch TX via provider and reconstruct contract instance + const provider = new DefaultProvider() + const tx = await provider.getTransaction(txId) + const verifier = Verifier.fromTx(tx, 0) + + // Connect signer + await verifier.connect(getDefaultSigner()) + + // Parse proof.json + const proof: Proof = parseProofFile(proofPath) + + // Call verifyProof() + const { tx: callTx } = await verifier.methods.verifyProof( + proof + ) + console.log('Verifier contract unlocked: ', callTx.id) +} + +(async () => { + await call('2396a4e52555cdc29795db281d17de423697bd5cbabbcb756cb14cea8e947235', '../proof.json') +})() +``` + + +Let's unlock our contract by running the following command: +``` +npx ts-node call.ts +``` + +If everything goes as expected, we have now unlocked the verifier smart contract. You'll see an output similar to the following: + +``` +Verifier contract unlocked: e3e7030f99882b67235ad44f865eec8a4e2c4c5193d3e833ddf5aa999bb2f716 +``` + +Take a look at it using [a block explorer](https://mempool.space/tx/e3e7030f99882b67235ad44f865eec8a4e2c4c5193d3e833ddf5aa999bb2f716). + +## Conclusion + +Congratulations! You have successfully created a zk-SNARK and verified it on-chain! + +If you want to learn how you can integrate zk-SNARKS into a fully fledged Bitcoin web application, take a look at our free [course](https://academy.scrypt.io/en/courses/Build-a-zkSNARK-based-Battleship-Game-on-Bitcoin-64187ae0d1a6cb859d18d72a), which will teach you how to create a ZK Battleship game. +Additionally, it teaches you to use [snarkjs/circom](https://github.com/sCrypt-Inc/snarkjs). + +To know more about ZKP, you can refer to [this awesome list](https://github.com/sCrypt-Inc/awesome-zero-knowledge-proofs).