diff --git a/sidebar-zkllvm.js b/sidebar-zkllvm.js index 7718ccda..c99747f4 100644 --- a/sidebar-zkllvm.js +++ b/sidebar-zkllvm.js @@ -33,7 +33,7 @@ export default { type: 'category', label: 'Getting started', collapsible: true, - collapsed: false, + collapsed: true, items: [ { type: 'doc', @@ -121,20 +121,12 @@ export default { label: 'Unrolling loops', id: 'best-practices-limitations/unrolling-loops' }, - ] - }, - { - type: 'category', - label: 'Circuit development', - collapsible: true, - collapsed: true, - items: [ { type: 'doc', - label: 'Standard library', - id: 'circuit-development/standalone-clang', - }, - ], + label: 'Structs and enums in Rust', + id: 'best-practices-limitations/rust-derive' + } + ] }, { type: 'category', @@ -144,24 +136,38 @@ export default { items: [ { type: 'doc', - label: 'First circuit with hashes', - id: 'tutorials/hashes' - }, - { - type: 'doc', - label: 'EsDSA signature verifications', - id: 'tutorials/eddsa' - }, - { - type: 'doc', - label: 'Merkle tree commitment schemes', - id: 'tutorials/merkle-tree' + label: 'Primer', + id: 'use-cases/primer' }, { - type: 'doc', - label: 'Constructing a zkBridge', - id: 'tutorials/zkbridge' + type: 'category', + label: 'Construct a zkBridge', + collapsible: true, + collapsed: true, + items: [ + { + type: 'doc', + label: 'Write a circuit with hashes', + id: 'use-cases/zk-bridge/hashes' + }, + { + type: 'doc', + label: 'Verify EdDSA signatures', + id: 'use-cases/zk-bridge/eddsa' + }, + { + type: 'doc', + label: 'Create a Merkle tree commitment scheme', + id: 'use-cases/zk-bridge/merkle-tree' + }, + { + type: 'doc', + label: 'Write an algorithm for state-proof verification', + id: 'use-cases/zk-bridge/zkbridge' + }, + ] }, + ] }, { @@ -170,21 +176,11 @@ export default { collapsible: true, collapsed: true, items: [ - { - type: 'doc', - label: 'Contributing', - id: 'misc/contributing' - }, { type: 'doc', label: 'Code of conduct', id: 'misc/code-of-conduct' }, - { - type: 'doc', - label: 'Contact', - id: 'misc/contact' - }, ] }, ], diff --git a/zkllvm/best-practices-limitations/rust-derive.mdx b/zkllvm/best-practices-limitations/rust-derive.mdx new file mode 100644 index 00000000..af7b5f3e --- /dev/null +++ b/zkllvm/best-practices-limitations/rust-derive.mdx @@ -0,0 +1,60 @@ +# Structs and enums in Rust + +Whenever a Rust circuit is compiled, `rustc` applies various optimizations to reduce its memory usage. + +Among these memory optimizations is [**reordering fields in structs and enums to avoid unnecessary 'paddings' in circuit IRs**](https://doc.rust-lang.org/nomicon/repr-rust.html). Consider the following example: + +```rust +use ark_pallas::Fq; + +type BlockType = [Fq; 2]; + +pub struct BlockDataType { + prev_block_hash: BlockType, + data: BlockType, + validators_signature: i32, + validators_key: u64, +} +``` + +The public input representation of the `BlockDataType` struct would look as follows: + +```json +"struct": [ + { + "array": [{"field": 1}, {"field": 1}] + }, + { + "array": [{"field": 3}, {"field": 1}] + }, + { + "int": 1 + }, + { + "int": 1 + } +] +``` + +When compiling the `BlockDataType` struct, `rustc` may reorder its fields. + +When `assigner` is called on a circuit with this struct, the circuit IR would conflict with the public input as the field order in the IR and the public input file would no longer match. + +To avoid this problem, use the `#[repr(C)]` directive: + +```rust + +use ark_pallas::Fq; + +type BlockType = [Fq; 2]; + +#[repr(C)] +pub struct BlockDataType { + prev_block_hash: BlockType, + data: BlockType, + validators_signature: i32, + validators_key: u64, +} +``` + +If this directive is included, Rust will treat structs and enums as C-like types, meaning that `rustc` will never reorder fields in them. diff --git a/zkllvm/circuit-development/standalone-clang.md b/zkllvm/circuit-development/standalone-clang.md deleted file mode 100644 index 8d0f42d9..00000000 --- a/zkllvm/circuit-development/standalone-clang.md +++ /dev/null @@ -1,25 +0,0 @@ -# Standalone Clang usage - -Now `zkLLVM clang` requires several essential arguments: - -``` --target assigner -Xclang -no-opaque-pointers -emit-llvm -S -``` - -Moreover, you need to use our custom [standard library](https://github.com/NilFoundation/zkllvm-stdlib). Library headers could be added with options: - -``` --Izkllvm/libs/stdlib/libc/include -Izkllvm/libs/stdlib/libcpp -``` - -With these arguments, clang will generate `.ll` assembler as an output. -These assembly files could be linked with `llvm-link` binary (it’s also an artifact from zkLLVM project): - -``` -llvm-link -opaque-pointers=0 -o output.ll input1.ll input2.ll … inputX.ll -``` - -`output.ll` could be used further as an input for llvm-link. So you can link some intermediate targets first, and then use them for creating the final circuit. - -The expected result that could be passed to `assigner` is a single `.ll` file that must contain exactly one function with `[[circuit]]` attribute, which is considered as an entry point of the circuit. -In case if you are going to use cmake as a build system, you could reuse our module [CircuitCompile.cmake](https://github.com/NilFoundation/zkllvm/blob/master/cmake/CircuitCompile.cmake). diff --git a/zkllvm/misc/contact.md b/zkllvm/misc/contact.md deleted file mode 100644 index 50e1be7e..00000000 --- a/zkllvm/misc/contact.md +++ /dev/null @@ -1,5 +0,0 @@ -# Contact - -* [Discord ](https://discord.gg/xJmBxGYqjk) -* [Twitter](https://twitter.com/nil\_foundation) -* [Website](https://nil.foundation/) diff --git a/zkllvm/misc/contributing.md b/zkllvm/misc/contributing.md deleted file mode 100644 index ce31cb6a..00000000 --- a/zkllvm/misc/contributing.md +++ /dev/null @@ -1,3 +0,0 @@ -# Contributing - -Coming Soon diff --git a/zkllvm/tutorials/01-hashes.md b/zkllvm/tutorials/01-hashes.md deleted file mode 100644 index f4b468ae..00000000 --- a/zkllvm/tutorials/01-hashes.md +++ /dev/null @@ -1,183 +0,0 @@ -# First circuit with hashes - -Circuit development slightly differs from the usual software development. The main difference is that you only can use pure functions in your circuits. We will provide other recommendations for efficient circuit development in this tutorial and the following ones. - -The good news is that many circuit-friendly algorithm implementations already became part of the SDK. -You can use them in your circuits, speeding up the development process. -In this tutorial we will show you how to use hashes in your circuits — in particular, the `sha2-256` hash function. - -:::info - -zkLLVM SDK has `sha2-256`, `sha2-512` and `Poseidon` hash functions. -Later we are going to add more hash functions, such as `Pedersen` hash and `keccak` (`sha3`). - -::: - -To use hashes in your C++ code, include the following header: - -```cpp -#include -#include -``` - -Then you can use the `hash` function to calculate hashes of the given data. - -The function can work with different forms of input. - -It takes one template parameter — the hash algorithm that you want to use. - -In this example, we will use the `sha2-256` hash function with two `sha2-256` blocks input. - -```cpp -#include -#include - -using namespace nil::crypto3::hashes; - -[[circuit]] typename sha2<256>::block_type sha256_example( - typename sha2<256>::block_type first_input_block, - typename sha2<256>::block_type second_input_block) { - - typename sha2<256>::block_type hash_result = - hash>(first_input_block, second_input_block); - return hash_result; -} -``` - -We use namespace `nil::crypto3::hashes` to avoid writing `nil::crypto3::hashes::sha2<256>` every time we want to use `sha2-256` hash function. - -Instead, we can write `sha2<256>`. - -It's recommended to use fixed-size containers, since they reduce the number of constraints in the resulting circuit. -In this example we use `std::array` with 64 elements. - -We are constantly working on optimizing `std` algorithms implementations for circuit form, so other containers will be available soon. - -Let's hash elements of the array one by one and return the result: - -```cpp -#include -#include - -using namespace nil::crypto3::hashes; - -[[circuit]] typename sha2<256>::block_type sha256_example( - std::array input_blocks) { - - typename sha2<256>::block_type result = input_blocks[0]; - for (int i = 1; i < input_blocks.size(); i++) { - result = hash>(result, input_blocks[i]); - } - - return result; -} -``` - -Now a real-life example. - -Let's assume that we have a blockchain with blocks having the following simplified structure: - -```cpp -struct block_data_type { - typename hashes::sha2<256>::block_type prev_block_hash; - typename hashes::sha2<256>::block_type data; -}; -``` - -Let's imagine that we want to verify that a particular block was created correctly. - -We have the hash of a previous block, which is already confirmed, and all the blocks from that verified block to the one we want to verify. - -For each unverified block, we will calculate the hash of its parent and check that it matches the hash stored in the block. - -We will start with a simple example where - -Then we need to check that the hash of the previous block in the chain is equal to the hash of the previous block in the chain in the block we want to verify. - -```cpp -#include -#include - -using namespace nil::crypto3::hashes; - -struct block_data_type { - typename sha2<256>::block_type prev_block_hash; - typename sha2<256>::block_type data; -}; - -bool is_same( - typename hashes::sha2<256>::block_type block0, - typename hashes::sha2<256>::block_type block1) { - - return block0[0] == block1[0] && block0[1] == block1[1]; -} - -[[circuit]] bool verify_protocol_state_proof ( - typename sha2<256>::block_type last_confirmed_block_hash, - block_data_type unconfirmed_block) { - - return is_same( - unconfirmed_block.prev_block_hash, - last_confirmed_block_hash); -} -``` - -Here we use a simple function `is_same` to compare two hashes. - -You can call any other functions in your circuits without overhead, but they should be pure functions. - -Now let's do the same process but with a list of blocks instead of a single block: - -```cpp -#include -#include - -using namespace nil::crypto3::hashes; - -struct block_data_type { - typename sha2<256>::block_type prev_block_hash; - typename sha2<256>::block_type data; -}; - -bool is_same( - typename hashes::sha2<256>::block_type block0, - typename hashes::sha2<256>::block_type block1) { - - return block0[0] == block1[0] && block0[1] == block1[1]; -} - -[[circuit]] bool verify_protocol_state_proof ( - typename sha2<256>::block_type last_confirmed_block_hash, - std::array unconfirmed_blocks) { - - for (int i = 0; i < unconfirmed_blocks.size(); i++) { - - // Check hashes correctness - if (i == 0) { - if (!is_same( - unconfirmed_blocks[i].prev_block_hash, - last_confirmed_block_hash)) { - return false; - } - } else { - typename sha2<256>::block_type evaluated_block_hash = - hash>( - unconfirmed_blocks[i-1].prev_block_hash, - unconfirmed_blocks[i-1].data); - - if (!is_same( - unconfirmed_blocks[i].prev_block_hash, - evaluated_block_hash)) { - return false; - } - } - } - return true; -} -``` - -Congratulations! - -You have just created your first circuit using hashes. - -We will use it later to verify blocks in the protocol state. diff --git a/zkllvm/tutorials/02-eddsa.md b/zkllvm/tutorials/02-eddsa.md deleted file mode 100644 index e8fcc812..00000000 --- a/zkllvm/tutorials/02-eddsa.md +++ /dev/null @@ -1,126 +0,0 @@ -# EdDSA signature verification - -EdDSA signature plays a significant role in the blockchain world. - -In the zkBridge tutorial serie, we will use it to verify validators' signatures. - -In this tutorial, we will show you how to implement the EdDSA signature verification algorithm in the circuit form. - -EdDSA signature is based on ED25519 elliptic curve and its Galois field. So, the following SDK headers need to be included to support circuit-friendly implementations of curves and fields arithmetic: - -```cpp -#include -#include -#include -``` - -Now we have types for the curve and field elements: - -- `typename ed25519::template g1_type<>::value_type` — g1 group element, -- `typename ed25519::base_field_type::value_type` — ed25519 curve base field type, -- `typename ed25519::scalar_field_type::value_type` — ed25519 curve scalar field type. - -All these three types are available from `nil::crypto3::algebra::curves` namespace, so we are going to use it instead of writing `nil::crypto3::algebra::curves::ed25519` every time: - -```cpp -#include -#include -#include - -using namespace nil::crypto3::algebra::curves; -``` - -The EdDSA signature result consists of two elements: `R` and `s`. - -`R` is an element of the g1 group, and `s` is an element of the scalar field. - -We will use a `struct` to represent the signature result: - -```cpp -#include -#include -#include - -using namespace nil::crypto3::algebra::curves; - -struct eddsa_signature_type { - typename ed25519::template g1_type<>::value_type R; - typename ed25519::scalar_field_type::value_type s; -}; -``` - -We also need a type for the message, which is going to be signed. At the moment of writing there are some limitations on the message size, so we will use a fixed size array of the Pallas field elements: - -```cpp -#include -#include -#include -#include - -using namespace nil::crypto3::algebra::curves; - -typedef __attribute__((ext_vector_type(4))) - typename pallas::base_field_type::value_type eddsa_message_block_type; - -struct eddsa_signature_type { - typename ed25519::template g1_type<>::value_type R; - typename ed25519::scalar_field_type::value_type s; -}; -``` - -Now we can define the signature verification function: - -```cpp -#include -#include -#include -#include - -using namespace nil::crypto3::algebra::curves; - -typedef __attribute__((ext_vector_type(4))) - typename pallas::base_field_type::value_type eddsa_message_block_type; - -struct eddsa_signature_type { - typename ed25519::template g1_type<>::value_type R; - typename ed25519::scalar_field_type::value_type s; -}; - -[[circuit]] bool verify_eddsa_signature (eddsa_signature_type input, - typename ed25519::template g1_type<>::value_type pk, - eddsa_message_block_type M) { - - typename ed25519::template g1_type<>::value_type B = - ed25519::template g1_type<>::value_type::one(); - __zkllvm_field_curve25519_scalar k = - __builtin_assigner_sha2_512_curve25519(input.R.data, pk.data, M); - - return (B*input.s - (input.R + (pk*k))).is_zero(); - } -``` - -:::info - -You've learned how to implement the EdDSA signature verification algorithm in the circuit form. Now you can directly take it from Crypto3 library and use it in your circuits. - -::: - -# Using the implementation of EdDSA signature verification from Crypto3 library - -```cpp -#include - -typedef __attribute__((ext_vector_type(4))) - typename pallas::base_field_type::value_type eddsa_message_block_type; - -[[circuit]] int main (){ - - eddsa_message_block_type msg = {0, 1, 2, 3}; - - public_key pk; - typename eddsa::signature_type sig; - - verify(msg, sig, pubkey); - return 0; -} -``` diff --git a/zkllvm/tutorials/03-merkle-tree.md b/zkllvm/tutorials/03-merkle-tree.md deleted file mode 100644 index fe60f2ea..00000000 --- a/zkllvm/tutorials/03-merkle-tree.md +++ /dev/null @@ -1,125 +0,0 @@ -# Merkle tree commitment scheme - -Merkle tree is often used in blockchain to commit to a set of data. - -This tutorial will show constructing a Merkle tree verification circuit with zkLLVM. - -First of all, let's define the structure of a Merkle tree. - -It is a binary tree where each leaf node contains a hash of a data block, and each non-leaf node contains a hash of its children. - -The tree's root element is called a Merkle root. It contains a hash of all the data in the tree. - -Like in the [first tutorial](./hashes) of this series, we will use the `sha2-256` hash function and `std::array` as a container for data blocks. - -We include the same headers as in the first tutorial and use the same namespace. - -:::info - -zkLLVM supports `sha2-256`, `sha2-512`, and `Poseidon` hash functions. - -::: - -:::info - -In this tutorial we use `std::array` as a container for data blocks, but we understand the necessity of supporting other containers. - -We are working on it. - -Extended support of `std` algorithms is one of our priorities. - -::: - -```cpp -#include -#include - -using namespace nil::crypto3::hashes; -``` - -Now let's add the first layer hashing function. - -It takes pairs of leaves and hashes them together. - -The resulting array of hashes will be two times smaller than the original array of leaves. - -```cpp -#include -#include - -using namespace nil::crypto3::hashes; - -[[circuit]] typename sha2<256>::block_type - hash_layer_1(std::array::block_type, 0x10> layer_0_leaves) { - - std::array::block_type, 0x8> layer_1_leaves; - - for (std::size_t leaf_index = 0; leaf_index < layer_1_leaves.size(); leaf_index++) { - layer_1_leaves[leaf_index] = - hash>(layer_0_leaves[2 * leaf_index], layer_0_leaves[2 * leaf_index + 1]); - } - - return layer_1_leaves; -} -``` - -Each subsequent layer is constructed in the same way. - -The only difference is that the array of leaves is two times smaller than the previous one. - -The last layer will contain only one element: the Merkle root. - -The circuit function that we want to build should return this element. - -So, let's add the remaining logic and finish the circuit: - -```cpp -#include -#include - -using namespace nil::crypto3::hashes; - -[[circuit]] typename sha2<256>::block_type - balance(std::array::block_type, 0x10> layer_0_leaves) { - - std::array::block_type, 0x8> layer_1_leaves; - std::size_t layer_1_size = 0x8; - std::array::block_type, 0x4> layer_2_leaves; - std::size_t layer_2_size = 0x4; - std::array::block_type, 0x2> layer_3_leaves; - std::size_t layer_3_size = 0x2; - typename sha2<256>::block_type root; - - for (std::size_t leaf_index = 0; leaf_index < layer_1_size; leaf_index++) { - layer_1_leaves[leaf_index] = - hash>(layer_0_leaves[2 * leaf_index], layer_0_leaves[2 * leaf_index + 1]); - } - - for (std::size_t leaf_index = 0; leaf_index < layer_2_size; leaf_index++) { - layer_2_leaves[leaf_index] = - hash>(layer_1_leaves[2 * leaf_index], layer_1_leaves[2 * leaf_index + 1]); - } - - for (std::size_t leaf_index = 0; leaf_index < layer_3_size; leaf_index++) { - layer_3_leaves[leaf_index] = - hash>(layer_2_leaves[2 * leaf_index], layer_2_leaves[2 * leaf_index + 1]); - } - - root = hash>(layer_3_leaves[0], layer_3_leaves[1]); - - return root; -} -``` - -:::info -Merkle tree is a widely-used data structure. - -In this tutorial, you've learned how to construct it from scratch. - -Though, you don't need to do it every time. - -Crypto3 library has an optimized circuit-friendly implementation of Merkle tree and other algorithms. - -::: - -We will use this Merkle Tree circuit to build a zkBridge in the next part of the tutorial. diff --git a/zkllvm/tutorials/04-zkbridge.md b/zkllvm/tutorials/04-zkbridge.md deleted file mode 100644 index c94493d2..00000000 --- a/zkllvm/tutorials/04-zkbridge.md +++ /dev/null @@ -1,153 +0,0 @@ -# Constructing a zkBridge - -Finally, we have all the parts to construct a zkBridge. - -Precisely speaking, we will write circuit of a state-proof verification algorithm. - -As always, we start with headers and namespaces: - -```cpp -#include -#include -#include -#include -#include -#include - -using namespace nil::crypto3::hashes; -using namespace nil::crypto3::algebra::curves; -``` - -Here we have included the following components: - -- Headers for hashes: we will use `sha2-256` hash algorithm in our zk-Bridge. -- The `curve25519` elliptic curve for operations with curve elements in EdDSA signature algorithm. -- The `pallas` field to store the Merkle tree nodes in it. - -We will now use the following custom types for our algorithm: - -```cpp -struct eddsa_signature_type { - typename ed25519::template g1_type<>::value_type R; - typename ed25519::scalar_field_type::value_type s; -}; - -typedef __attribute__((ext_vector_type(4))) - typename pallas::base_field_type::value_type eddsa_message_block_type; - -struct block_data_type { - typename sha2<256>::block_type prev_block_hash; - typename sha2<256>::block_type data; - std::array validators_signatures; - std::array::value_type, 16> validators_keys; - eddsa_signature_type validators_signature; - typename ed25519::template g1_type<>::value_type validators_key; -}; -``` - -Now let's add the EdDSA signature verification function from the [previous tutorial](./eddsa): - -```cpp -bool verify_eddsa_signature (eddsa_signature_type input, - typename ed25519::template g1_type<>::value_type pk, - eddsa_message_block_type M) { - - typename ed25519::template g1_type<>::value_type B = ed25519::template g1_type<>::value_type::one(); - __zkllvm_field_curve25519_scalar k = __builtin_assigner_sha2_512_curve25519(input.R.data, pk.data, M); - - return (B*input.s - (input.R + (pk*k))).is_zero(); -} -``` - -And after adding the final function for verification of the unconfirmed blocks, the code will look like this: - -```cpp -#include -#include -#include -#include -#include -#include - -using namespace nil::crypto3; -using namespace nil::crypto3::algebra::curves; - -typedef __attribute__((ext_vector_type(4))) - typename pallas::base_field_type::value_type eddsa_message_block_type; - -struct eddsa_signature_type { - typename ed25519::template g1_type<>::value_type R; - typename ed25519::scalar_field_type::value_type s; -}; - -bool verify_eddsa_signature (eddsa_signature_type input, - typename ed25519::template g1_type<>::value_type pk, - eddsa_message_block_type M) { - - typename ed25519::template g1_type<>::value_type B = ed25519::template g1_type<>::value_type::one(); - __zkllvm_field_curve25519_scalar k = __builtin_assigner_sha2_512_curve25519(input.R.data, pk.data, M); - - return (B*input.s - (input.R + (pk*k))).is_zero(); -} - -struct block_data_type { - typename sha2<256>::block_type prev_block_hash; - typename sha2<256>::block_type data; - std::array validators_signatures; - std::array::value_type, 16> validators_keys; - eddsa_signature_type validators_signature; - typename ed25519::template g1_type<>::value_type validators_key; -}; - -bool is_same(typename sha2<256>::block_type block0, - typename sha2<256>::block_type block1){ - - return block0[0] == block1[0] && block0[1] == block1[1]; -} - -// The circuit function itself starts with [[circuit]] -[[circuit]] bool verify_protocol_state_proof ( - typename sha2<256>::block_type last_confirmed_block_hash, - std::array unconfirmed_blocks) { - - for (int i = 0; i < unconfirmed_blocks.size(); i++) { - - // Check hashes correctness - if (i == 0) { - if (!is_same(unconfirmed_blocks[i].prev_block_hash, - last_confirmed_block_hash)) { - return false; - } - } else { - typename sha2<256>::block_type evaluated_block_hash = - hash>(unconfirmed_blocks[i-1].prev_block_hash, - unconfirmed_blocks[i-1].data); - - if (!is_same(unconfirmed_blocks[i].prev_block_hash, - evaluated_block_hash)) { - return false; - } - } - - // Verify signatures - for (int j = 0; j < 16; j++) { - eddsa_message_block_type message = {unconfirmed_blocks[i].prev_block_hash[0], - unconfirmed_blocks[i].prev_block_hash[1], - unconfirmed_blocks[i].data[0], - unconfirmed_blocks[i].data[1]}; - - if (!verify_eddsa_signature(unconfirmed_blocks[i].validators_signatures[j], - unconfirmed_blocks[i].validators_keys[j], - message)) { - return false; - } - } - } - - return true; -} -``` - -That's it! - -Now we can compile this code to a state-proof verification circuit and build a zkBridge upon it. diff --git a/zkllvm/use-cases/primer.mdx b/zkllvm/use-cases/primer.mdx new file mode 100644 index 00000000..b06440be --- /dev/null +++ b/zkllvm/use-cases/primer.mdx @@ -0,0 +1,197 @@ +# Primer + +This primer acts as an intro guide to writing complex circuits and compiling them. + +## Dependency management + +Most circuits that cover common use cases (e.g., circuits verifying state transitions) require the use of external dependencies. + +In such cases, [**using `clang` or `rustc` directly**](../getting-started/compiling-a-circuit) is discouraged. Instead, it is preferable to use a dedicated build management system: + +* CMake for C++ +* Cargo for Rust + +:::tip[`clang`] + +When calling `clang` directly, dependency management can also be handled via the following methods: + +* Via the CLI +* Via env variables +* Via updating the system path + +Using CMake is still preferable as it offers several valuable features (such as external library detection) that simplify working with complex circuits. + +::: + +:::info + +This primer uses `crypto3` (C++) and `arkworks` (Rust) to show how to use external libraries in a circuit. The primer can be reused for any other suitable library. + +::: + +### Using CMake + +Switch to the working directory: + +```bash +mkdir new_circuit && cd new_circuit +``` + +Create the circuit: + +```bash +cd .. && mkdir src +``` + +```bash +cd src && touch main.cpp +``` + +Add circuit code that references an external library: + +```cpp +#include +#include + +... + +[[circuit]] bool circuit_func() {...} +``` + +Create a configuration file for CMake: + +```bash +cd .. && touch CMakeLists.txt +``` + +Inside `CMakeLists.txt`, set the project config and include the `CircuitCompile.cmake` module: + +```cmake +cmake_minimum_required(VERSION 3.2) + +project(hashes_circuit + VERSION 1.0 + DESCRIPTION "Tutorial circuit with hashes" + LANGUAGES CXX) + +list(APPEND CMAKE_MODULE_PATH "/usr/lib/zkllvm/share/zkllvm") + +include(CircuitCompile) +``` + +Add the required library to the project configuration: + +```cmake +set(crypto3_DIR "path/to/crypto3/installation") + +find_package(crypto3 REQUIRED) +``` + +Set the circuit source and add a build target: + +``` +set(SOURCES src/main.cpp) + +add_circuit( + circuit SOURCES ${SOURCES} + LINK_LIBRARIES + crypto3::all +) + +``` + +, where `circuit` is the desired target name. + +Compile the circuit with: + +```bash +cmake -G "Unix Makefiles" -B ./build -DCMAKE_BUILD_TYPE=Release . +make -C ./build circuit -j$(nproc) +``` + +`add_circuit()` will generate the circuit IR under the name `circuit.ll`. + +:::warning + +The `CircuitCompile.cmake` module does not support using custom paths to `clang` built from sources (as well as custom paths to a linker). To use this module, it is necessary to [**install the `zkllvm` Deb package**](../getting-started/installation#install-zkllvm-binaries). + +::: + +:::tip + +Instead of specifying `crypto3_DIR` in `CMakeLists.txt`, it is possible to specify it when calling `cmake`: + +```bash +cmake -G "Unix Makefiles" -B ./build -DCMAKE_BUILD_TYPE=Release . -Dcrypto3_DIR=/path/to/crypto3/installation +``` + +This method is useful if `crypto3_DIR` is not a fixed path and cannot be 'hardcoded' in the CMake configuration file. + +::: + +### Using Cargo + +Create a new `cargo` project: + +```bash +cargo new new_circuit --bin +cd new_circuit +``` + +:::tip + +Optionally add the `--vcs none` option to prevent `cargo` from automatically creating a Git repository. + +::: + +Open the `Cargo.toml` file and add the following dependencies and feature flags: + +```toml +ark-pallas = { git = "https://github.com/NilFoundation/arkworks-curves.git" } +unroll = { git = "https://github.com/NilFoundation/zkllvm-unroll.git" } + +... + +[features] +zkllvm = ["ark-ff/zkllvm"] +``` + +:::tip + +Adding `unroll` [**is mandatory**](../best-practices-limitations/unrolling-loops) for circuits that use loops. + +::: + +:::tip + +The `"zkllvm"` feature flag is necessary to ensure compatibility with the original Rust compiler. + +When calling Cargo, use the `--features zkllvm` option to enable the feature for the specified package. If the feature is enabled, built-in zkLLVM types are used to represent curves, fields, and other elements. If this feature is disabled, type definitions from the `arkworks` project are used. + +The flag should only be used if `assigner-unknown-unknown` is the specified build target when calling `cargo`. If it is not used, calling `assigner` on the resulting circuit IR may fail. + +If `cargo` is called for any other build target, omit the `--features zkllvm` option when calling Cargo. + +::: + +Add circuit code that references an external library: + +```rust +#![no_main] + +use ark_pallas::Fq; +... + +type BlockType = [Fq; 2]; + +#[circuit] +pub fn sha256_example() -> BlockType {...} +``` + +Compile the circuit with: + +```bash +cargo +zkllvm build --target assigner-unknown-unknown --features zkllvm --release +``` + +To learn more about additional compilation options, [**click here**](../getting-started/compiling-rust-code). diff --git a/zkllvm/use-cases/zk-bridge/eddsa.mdx b/zkllvm/use-cases/zk-bridge/eddsa.mdx new file mode 100644 index 00000000..2463166c --- /dev/null +++ b/zkllvm/use-cases/zk-bridge/eddsa.mdx @@ -0,0 +1,190 @@ +import Tabs from '@theme/Tabs' +import TabItem from '@theme/TabItem' + +# Verify EdDSA signatures + +Handling EdDSA signatures is the next logical step in creating a simple zkBridge. + +## Prerequisites + +Read the following tutorials before proceeding further. + +* [**Primer**](../primer) +* [**Write a circuit with hashes**](./hashes) + +## Circuit code + +The circuit consists of the following components: + +### Headers and namespaces / crates and modules + + + + ```cpp + #include + #include + #include + #include + + using namespace nil::crypto3::algebra::curves; + ``` + + + ```rust + #![no_main] + + use ark_curve25519::{EdwardsAffine, Fr}; + use ark_pallas::Fq; + + use std::intrinsics::assigner_sha2_512; + ``` + + + +### Structs and types + + + + ```cpp + typedef __attribute__((ext_vector_type(4))) + typename pallas::base_field_type::value_type eddsa_message_block_type; + ``` + + + ```rust + type EdDSAMessageBlockType = [Fq; 4]; + ``` + + + +### Additional functions (Rust only) + +```rust +fn hash(r: EdwardsAffine, pk: EdwardsAffine, m: EdDSAMessageBlockType) -> Fr { + assigner_sha2_512(r.0, pk.0, [m[0].0, m[1].0, m[2].0, m[3].0]).into() +} +``` + +### Circuit function + + + + ```cpp + [[circuit]] bool verify_eddsa_signature ( + + typename ed25519::template g1_type<>::value_type input_R, + typename ed25519::scalar_field_type::value_type input_s, + typename ed25519::template g1_type<>::value_type pk, + eddsa_message_block_type M) { + + typename ed25519::template g1_type<>::value_type B = ed25519::template g1_type<>::one(); + typename ed25519::scalar_field_type::value_type k = __builtin_assigner_sha2_512_curve25519(input_R, pk, M); + + return B*input_s == (input_R + (pk*k)); + } + ``` + + + ```rust + #[circuit] + pub fn verify_eddsa_signature( + input_r: EdwardsAffine, + input_s: Fr, + pk: EdwardsAffine, + m: EdDSAMessageBlockType, + ) -> bool { + let b = EdwardsAffine::generator(); + let k = hash(input_r, pk, m); + b * input_s == input_r + (pk * k) + } + ``` + + + +### Full code + + + + ```cpp + #include + #include + #include + #include + + using namespace nil::crypto3::algebra::curves; + + typedef __attribute__((ext_vector_type(4))) + typename pallas::base_field_type::value_type eddsa_message_block_type; + + [[circuit]] bool verify_eddsa_signature ( + + typename ed25519::template g1_type<>::value_type input_R, + typename ed25519::scalar_field_type::value_type input_s, + typename ed25519::template g1_type<>::value_type pk, + eddsa_message_block_type M) { + + typename ed25519::template g1_type<>::value_type B = ed25519::template g1_type<>::one(); + typename ed25519::scalar_field_type::value_type k = __builtin_assigner_sha2_512_curve25519(input_R, pk, M); + + return B*input_s == (input_R + (pk*k)); + } + ``` + + + ```rust + #![no_main] + + use ark_curve25519::{EdwardsAffine, Fr}; + use ark_pallas::Fq; + + use std::intrinsics::assigner_sha2_512; + + type EdDSAMessageBlockType = [Fq; 4]; + + #[circuit] + pub fn verify_eddsa_signature( + input_r: EdwardsAffine, + input_s: Fr, + pk: EdwardsAffine, + m: EdDSAMessageBlockType, + ) -> bool { + let b = EdwardsAffine::generator(); + let k = hash(input_r, pk, m); + b * input_s == input_r + (pk * k) + } + + fn hash(r: EdwardsAffine, pk: EdwardsAffine, m: EdDSAMessageBlockType) -> Fr { + assigner_sha2_512(r.0, pk.0, [m[0].0, m[1].0, m[2].0, m[3].0]).into() + } + ``` + + + +## Public input + +The public input for the circuit could look as follows: + +```json +[ + {"curve": [ + "0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec", + "0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8" + ] + }, + {"field": "0x823978718be1d3a785af015d1472346213f76d2ffc57ac716effa76184d67d1" + }, + {"curve": [ + "0x44c7f6527c825acd6acdc008763cc37f866dd7afb3d9dd6d1f4deb397d75b61e", + "0x2e710a39d3a2cb049c86f6b8592286911b5d76de778e66d35f4aceedd2ad50f0" + ] + }, + {"vector": [ + {"field": "0x3992d30ed00000001"}, + {"field": "0x891a63f02533e46"}, + {"field": "0"}, + {"field": "0x100000000000000"} + ] + } +] +``` + diff --git a/zkllvm/use-cases/zk-bridge/hashes.mdx b/zkllvm/use-cases/zk-bridge/hashes.mdx new file mode 100644 index 00000000..e41555df --- /dev/null +++ b/zkllvm/use-cases/zk-bridge/hashes.mdx @@ -0,0 +1,338 @@ +import Tabs from '@theme/Tabs' +import TabItem from '@theme/TabItem' + +# Write a circuit with hashes + +This 'how-to' series constructs a 'mock' zero-knowledge bridge from scratch. The series starts with writing a simple circuit that uses hashes and ends with creating an algorithm for state-proof verification. + +## Prerequisites + +Read the following tutorials before proceeding further. + +* [**Writing a simple circuit**](../../getting-started/writing-a-simple-circuit) +* [**Compiling a circuit**](../../getting-started/compiling-a-circuit) +* [**Built-ins**](../../getting-started/built-ins) +* [**Primer**](../primer) + +## Circuit code + +The circuit consists of the following components: + +### Headers and namespaces / crates and modules + + + + ```cpp + #include + #include + #include + + using namespace nil::crypto3::algebra::curves; + ``` + + + ```rust + #![no_main] + + use std::intrinsics::assigner_sha2_256; + use ark_pallas::Fq; + + use unroll::unroll_for_loops; + ``` + + + +### Structs and types + + + + ```cpp + struct block_data_type { + typename sha2<256>::block_type prev_block_hash; + typename sha2<256>::block_type data; + }; + ``` + + + ```rust + type BlockType = [Fq; 2]; + + struct BlockDataType { + prev_block_hash: BlockType, + data: BlockType, + } + ``` + + + +### Additional functions + + + + ```cpp + bool is_same( + typename sha2<256>::block_type block0, + typename sha2<256>::block_type block1) { + + return block0[0] == block1[0] && block0[1] == block1[1]; + } + ``` + + + ```rust + fn is_same(x: BlockType, y: BlockType) -> bool { + x[0] == y[0] && x[1] == y[1] + } + + fn hash(block1: BlockType, block2: BlockType) -> BlockType { + let sha = assigner_sha2_256([block1[0].0, block1[1].0], [block2[0].0, block2[1].0]); + [sha[0].into(), sha[1].into()] + } + ``` + + + +### Circuit function + + + + ```cpp + [[circuit]] bool verify_protocol_state_proof ( + typename sha2<256>::block_type last_confirmed_block_hash, + std::array unconfirmed_blocks) { + bool res = true; + if (!is_same(unconfirmed_blocks[0].prev_block_hash, last_confirmed_block_hash)) { + return false; + } + for (int i = 1; i < 2; i++) { + typename sha2<256>::block_type evaluated_block_hash = + hash>( + unconfirmed_blocks[i-1].prev_block_hash, + unconfirmed_blocks[i-1].data); + res = res & is_same(unconfirmed_blocks[i].prev_block_hash, evaluated_block_hash); + } + return res; + } + ``` + + + ```rust + #[circuit] + #[unroll_for_loops] + fn verify_protocol_state_proof( + last_confirmed_block_hash: BlockType, + unconfirmed_blocks: [BlockDataType; 2], + ) -> bool { + let mut res: bool = true; + if !is_same( + unconfirmed_blocks[0].prev_block_hash, + last_confirmed_block_hash, + ) { + return false; + } + for i in 1..2 { + let evaluated_block_hash = hash( + unconfirmed_blocks[i - 1].prev_block_hash, + unconfirmed_blocks[i - 1].data, + ); + res = res & is_same(unconfirmed_blocks[i].prev_block_hash, evaluated_block_hash); + } + res + } + ``` + + + +### Full code + + + + ```cpp + #include + #include + + using namespace nil::crypto3; + using namespace nil::crypto3::hashes; + + struct block_data_type { + typename sha2<256>::block_type prev_block_hash; + typename sha2<256>::block_type data; + }; + + bool is_same( + typename sha2<256>::block_type block0, + typename sha2<256>::block_type block1) { + + return block0[0] == block1[0] && block0[1] == block1[1]; + } + + [[circuit]] bool verify_protocol_state_proof ( + typename sha2<256>::block_type last_confirmed_block_hash, + std::array unconfirmed_blocks) { + bool res = true; + if (!is_same(unconfirmed_blocks[0].prev_block_hash, last_confirmed_block_hash)) { + return false; + } + for (int i = 1; i < 2; i++) { + typename sha2<256>::block_type evaluated_block_hash = + hash>( + unconfirmed_blocks[i-1].prev_block_hash, + unconfirmed_blocks[i-1].data); + res = res & is_same(unconfirmed_blocks[i].prev_block_hash, evaluated_block_hash); + } + return res; + } + ``` + + + ```rust + #![no_main] + + use ark_pallas::Fq; + use std::intrinsics::assigner_sha2_256; + use unroll::unroll_for_loops; + + type BlockType = [Fq; 2]; + + struct BlockDataType { + prev_block_hash: BlockType, + data: BlockType, + } + + fn is_same(x: BlockType, y: BlockType) -> bool { + x[0] == y[0] && x[1] == y[1] + } + + fn hash(block1: BlockType, block2: BlockType) -> BlockType { + let sha = assigner_sha2_256([block1[0].0, block1[1].0], [block2[0].0, block2[1].0]); + [sha[0].into(), sha[1].into()] + } + + #[circuit] + #[unroll_for_loops] + fn verify_protocol_state_proof( + last_confirmed_block_hash: BlockType, + unconfirmed_blocks: [BlockDataType; 2], + ) -> bool { + let mut res: bool = true; + if !is_same( + unconfirmed_blocks[0].prev_block_hash, + last_confirmed_block_hash, + ) { + return false; + } + for i in 1..2 { + let evaluated_block_hash = hash( + unconfirmed_blocks[i - 1].prev_block_hash, + unconfirmed_blocks[i - 1].data, + ); + res = res && is_same(unconfirmed_blocks[i].prev_block_hash, evaluated_block_hash); + } + res + } + ``` + + + +## Public input + +The public input for the circuit could look as follows: + + + + ```json + [ + { + "vector": [ + {"field": "4209827349872394872394872394872398472398472398472398472398472398"}, + {"field": "9823472983472938472938472938472983472983472983472983472983472983"} + ] + }, + { + "array": [ + { + "struct": [ + { + "vector": [ + {"field": "129837498237498237498237498237498237498237498237498237498237"}, + {"field": "23984723984723984723984723984723984723984723984723984723984"} + ] + }, + { + "vector": [ + {"field": "3872498273498237498237498237498237498237498237498237498237498"}, + {"field": "1823048723048723048723048723048723048723048723048723048723048"} + ] + } + ] + }, + { + "struct": [ + { + "vector": [ + {"field": "978293748293748293748239874823984723984723984723984723984723"}, + {"field": "5092384750293847502938475029384750293847502938475029384750293"} + ] + }, + { + "vector": [ + {"field": "3948572039847502938475029384750293847502938475029384750293847"}, + {"field": "5029384750293847502938475029384750293847502938475029384750293"} + ] + } + ] + } + ] + } + ] + ``` + + + ```json + [ + { + "array": [ + {"field": "4209827349872394872394872394872398472398472398472398472398472398"}, + {"field": "9823472983472938472938472938472983472983472983472983472983472983"} + ] + }, + { + "array": [ + { + "struct": [ + { + "array": [ + {"field": "129837498237498237498237498237498237498237498237498237498237"}, + {"field": "23984723984723984723984723984723984723984723984723984723984"} + ] + }, + { + "array": [ + {"field": "3872498273498237498237498237498237498237498237498237498237498"}, + {"field": "1823048723048723048723048723048723048723048723048723048723048"} + ] + } + ] + }, + { + "struct": [ + { + "array": [ + {"field": "978293748293748293748239874823984723984723984723984723984723"}, + {"field": "5092384750293847502938475029384750293847502938475029384750293"} + ] + }, + { + "array": [ + {"field": "3948572039847502938475029384750293847502938475029384750293847"}, + {"field": "5029384750293847502938475029384750293847502938475029384750293"} + ] + } + ] + } + ] + } + ] + ``` + + diff --git a/zkllvm/use-cases/zk-bridge/merkle-tree.mdx b/zkllvm/use-cases/zk-bridge/merkle-tree.mdx new file mode 100644 index 00000000..560e3cad --- /dev/null +++ b/zkllvm/use-cases/zk-bridge/merkle-tree.mdx @@ -0,0 +1,268 @@ +import Tabs from '@theme/Tabs' +import TabItem from '@theme/TabItem' + +# Create a Merkle tree commitment scheme + +A Merkle tree is a widely used commitment scheme: + +* Each leaf node in a Merkle tree contains a hash of a data block +* Each non-leaf node contains a hash of its children + +Merkle trees are a good choice for a simple zkBridge given their simplicity. + +## Prerequisites + +Read the following tutorials before proceeding further. + +* [**Primer**](../primer) +* [**Write a circuit with hashes**](./hashes) +* [**Verify EdDSA signatures**](./eddsa) + +## Circuit code + +### Headers and namespaces / crates and modules + + + + ```cpp + #include + #include + + using namespace nil::crypto3; + ``` + + + ```rust + #![no_main] + + use std::intrinsics::assigner_sha2_256; + + use ark_ff::AdditiveGroup; + use ark_pallas::Fq; + use unroll::unroll_for_loops; + ``` + + + +### Additional types and functions (Rust only) + +```rust +type BlockType = [Fq; 2]; + +fn hash(block1: BlockType, block2: BlockType) -> BlockType { + let sha = assigner_sha2_256([block1[0].0, block1[1].0], [block2[0].0, block2[1].0]); + [sha[0].into(), sha[1].into()] +} +``` + +### Circuit function + + + + ```cpp + [[circuit]] typename hashes::sha2<256>::block_type + balance(std::array::block_type, 0x10> layer_0_leaves) { + + std::array::block_type, 0x8> layer_1_leaves; + constexpr std::size_t layer_1_size = layer_1_leaves.size(); + std::array::block_type, 0x4> layer_2_leaves; + constexpr std::size_t layer_2_size = layer_2_leaves.size(); + std::array::block_type, 0x2> layer_3_leaves; + constexpr std::size_t layer_3_size = layer_3_leaves.size(); + typename hashes::sha2<256>::block_type root; + + for (std::size_t leaf_index = 0; leaf_index < layer_1_size; leaf_index++) { + layer_1_leaves[leaf_index] = + hash>(layer_0_leaves[2 * leaf_index], layer_0_leaves[2 * leaf_index + 1]); + } + + for (std::size_t leaf_index = 0; leaf_index < layer_2_size; leaf_index++) { + layer_2_leaves[leaf_index] = + hash>(layer_1_leaves[2 * leaf_index], layer_1_leaves[2 * leaf_index + 1]); + } + + for (std::size_t leaf_index = 0; leaf_index < layer_3_size; leaf_index++) { + layer_3_leaves[leaf_index] = + hash>(layer_2_leaves[2 * leaf_index], layer_2_leaves[2 * leaf_index + 1]); + } + + root = hash>(layer_3_leaves[0], layer_3_leaves[1]); + + return root; + } + ``` + + + ```rust + #[circuit] + #[unroll_for_loops] + pub fn balance(layer_0_leaves: [BlockType; 0x10]) -> BlockType { + let mut layer_1_leaves: [BlockType; 0x8] = [[Fq::ZERO, Fq::ZERO]; 0x8]; + let mut layer_2_leaves: [BlockType; 0x4] = [[Fq::ZERO, Fq::ZERO]; 0x4]; + let mut layer_3_leaves: [BlockType; 0x2] = [[Fq::ZERO, Fq::ZERO]; 0x2]; + + for leaf_index in 0..8 { + layer_1_leaves[leaf_index] = hash(layer_0_leaves[2 * leaf_index], layer_0_leaves[2 * leaf_index + 1]); + } + + for leaf_index in 0..4 { + layer_2_leaves[leaf_index] = hash(layer_1_leaves[2 * leaf_index], layer_1_leaves[2 * leaf_index + 1]); + } + + for leaf_index in 0..2 { + layer_3_leaves[leaf_index] = hash(layer_2_leaves[2 * leaf_index], layer_2_leaves[2 * leaf_index + 1]); + } + + let root: BlockType = hash(layer_3_leaves[0], layer_3_leaves[1]); + + root + } + ``` + + + +### Full code + + + + ```cpp + #include + #include + + using namespace nil::crypto3; + + [[circuit]] typename hashes::sha2<256>::block_type + balance(std::array::block_type, 0x10> layer_0_leaves) { + + std::array::block_type, 0x8> layer_1_leaves; + constexpr std::size_t layer_1_size = layer_1_leaves.size(); + std::array::block_type, 0x4> layer_2_leaves; + constexpr std::size_t layer_2_size = layer_2_leaves.size(); + std::array::block_type, 0x2> layer_3_leaves; + constexpr std::size_t layer_3_size = layer_3_leaves.size(); + typename hashes::sha2<256>::block_type root; + + for (std::size_t leaf_index = 0; leaf_index < layer_1_size; leaf_index++) { + layer_1_leaves[leaf_index] = + hash>(layer_0_leaves[2 * leaf_index], layer_0_leaves[2 * leaf_index + 1]); + } + + for (std::size_t leaf_index = 0; leaf_index < layer_2_size; leaf_index++) { + layer_2_leaves[leaf_index] = + hash>(layer_1_leaves[2 * leaf_index], layer_1_leaves[2 * leaf_index + 1]); + } + + for (std::size_t leaf_index = 0; leaf_index < layer_3_size; leaf_index++) { + layer_3_leaves[leaf_index] = + hash>(layer_2_leaves[2 * leaf_index], layer_2_leaves[2 * leaf_index + 1]); + } + + root = hash>(layer_3_leaves[0], layer_3_leaves[1]); + + return root; + } + ``` + + + ```rust + #![no_main] + + use std::intrinsics::assigner_sha2_256; + + use ark_ff::AdditiveGroup; + use ark_pallas::Fq; + use unroll::unroll_for_loops; + + type BlockType = [Fq; 2]; + + #[circuit] + #[unroll_for_loops] + pub fn balance(layer_0_leaves: [BlockType; 0x10]) -> BlockType { + let mut layer_1_leaves: [BlockType; 0x8] = [[Fq::ZERO, Fq::ZERO]; 0x8]; + let mut layer_2_leaves: [BlockType; 0x4] = [[Fq::ZERO, Fq::ZERO]; 0x4]; + let mut layer_3_leaves: [BlockType; 0x2] = [[Fq::ZERO, Fq::ZERO]; 0x2]; + + for leaf_index in 0..8 { + layer_1_leaves[leaf_index] = hash(layer_0_leaves[2 * leaf_index], layer_0_leaves[2 * leaf_index + 1]); + } + + for leaf_index in 0..4 { + layer_2_leaves[leaf_index] = hash(layer_1_leaves[2 * leaf_index], layer_1_leaves[2 * leaf_index + 1]); + } + + for leaf_index in 0..2 { + layer_3_leaves[leaf_index] = hash(layer_2_leaves[2 * leaf_index], layer_2_leaves[2 * leaf_index + 1]); + } + + let root: BlockType = hash(layer_3_leaves[0], layer_3_leaves[1]); + + root + } + + fn hash(block1: BlockType, block2: BlockType) -> BlockType { + let sha = assigner_sha2_256([block1[0].0, block1[1].0], [block2[0].0, block2[1].0]); + [sha[0].into(), sha[1].into()] + } + ``` + + + +## Public input + +The public input for the circuit could look as follows: + + + + ```json + [ + { + "array": [ + {"vector": [{"field": 1},{"field": 2}]}, + {"vector": [{"field": 3},{"field": 4}]}, + {"vector": [{"field": 5},{"field": 6}]}, + {"vector": [{"field": 7},{"field": 8}]}, + {"vector": [{"field": 9},{"field": 10}]}, + {"vector": [{"field": 11},{"field": 12}]}, + {"vector": [{"field": 13},{"field": 14}]}, + {"vector": [{"field": 15},{"field": 16}]}, + {"vector": [{"field": 17},{"field": 18}]}, + {"vector": [{"field": 19},{"field": 20}]}, + {"vector": [{"field": 21},{"field": 22}]}, + {"vector": [{"field": 23},{"field": 24}]}, + {"vector": [{"field": 25},{"field": 26}]}, + {"vector": [{"field": 27},{"field": 28}]}, + {"vector": [{"field": 29},{"field": 30}]}, + {"vector": [{"field": 31},{"field": 32}]} + ] + } + ] + ``` + + + ```json + [ + { + "array": [ + {"array": [{"field": 1},{"field": 2}]}, + {"array": [{"field": 3},{"field": 4}]}, + {"array": [{"field": 5},{"field": 6}]}, + {"array": [{"field": 7},{"field": 8}]}, + {"array": [{"field": 9},{"field": 10}]}, + {"array": [{"field": 11},{"field": 12}]}, + {"array": [{"field": 13},{"field": 14}]}, + {"array": [{"field": 15},{"field": 16}]}, + {"array": [{"field": 17},{"field": 18}]}, + {"array": [{"field": 19},{"field": 20}]}, + {"array": [{"field": 21},{"field": 22}]}, + {"array": [{"field": 23},{"field": 24}]}, + {"array": [{"field": 25},{"field": 26}]}, + {"array": [{"field": 27},{"field": 28}]}, + {"array": [{"field": 29},{"field": 30}]}, + {"array": [{"field": 31},{"field": 32}]}, + ] + } + ] + ``` + + + diff --git a/zkllvm/use-cases/zk-bridge/zkbridge.mdx b/zkllvm/use-cases/zk-bridge/zkbridge.mdx new file mode 100644 index 00000000..13abfcf4 --- /dev/null +++ b/zkllvm/use-cases/zk-bridge/zkbridge.mdx @@ -0,0 +1,526 @@ +import Tabs from '@theme/Tabs' +import TabItem from '@theme/TabItem' + +# Write an algorithm for state-proof verification + +The final step in making a simple zkBridge is the creation of a state-proof verification algorithm. + +## Prerequisites + +Read the following tutorials before proceeding further. + +* [**Primer**](../primer) +* [**Write a circuit with hashes**](./hashes) +* [**Verify EdDSA signatures**](./eddsa) +* [**Create a Merkle tree commitment scheme**](./merkle-tree) + +## Circuit code + +### Headers and namespaces / crates and modules + + + + ```cpp + #include + #include + #include + #include + + using namespace nil::crypto3; + using namespace nil::crypto3::algebra::curves; + ``` + + + ```rust + #![no_main] + + use std::intrinsics::assigner_sha2_256; + use std::intrinsics::assigner_sha2_512; + + use ark_curve25519::{EdwardsAffine, Fr}; + use ark_pallas::Fq; + use unroll::unroll_for_loops; + ``` + + + +### Structs and types + + + + ```cpp + typedef __attribute__((ext_vector_type(4))) + typename pallas::base_field_type::value_type eddsa_message_block_type; + + typedef struct { + typename ed25519::template g1_type<>::value_type R; + typename ed25519::scalar_field_type::value_type s; + } eddsa_signature_type; + + typedef struct { + typename hashes::sha2<256>::block_type prev_block_hash; + typename hashes::sha2<256>::block_type data; + std::array validators_signatures; + std::array::value_type, 4> validators_keys; + } block_data_type; + ``` + + + ```rust + type BlockType = [Fq; 2]; + type EdDSAMessageBlockType = [Fq; 4]; + + #[repr(C)] + pub struct BlockDataType { + prev_block_hash: BlockType, + data: BlockType, + validators_signatures: [EdDSASignatureType; 4], + validators_keys: [EdwardsAffine; 4], + } + + #[repr(C)] + pub struct EdDSASignatureType { + r: EdwardsAffine, + s: Fr, + } + ``` + + + +:::info[Rust directives] + +To learn more about the `#[derive(C)]` directive, [**click here**](../../best-practices-limitations/rust-derive). + +::: + + +### Additional functions + + + + ```cpp + bool is_same(typename hashes::sha2<256>::block_type block0, + typename hashes::sha2<256>::block_type block1) { + return block0[0] == block1[0] && block0[1] == block1[1]; + } + + bool verify_eddsa_signature (eddsa_signature_type input, + typename ed25519::template g1_type<>::value_type pk, + eddsa_message_block_type M) { + typename ed25519::template g1_type<>::value_type B = ed25519::template g1_type<>::one(); + typename ed25519::scalar_field_type::value_type k = __builtin_assigner_sha2_512_curve25519(input.R, pk, M); + + return B*input.s == (input.R + (pk*k)); + } + + bool verify_signature(block_data_type unconfirmed_block) { + bool is_verified = true; + eddsa_message_block_type message = {unconfirmed_block.prev_block_hash[0], + unconfirmed_block.prev_block_hash[1], unconfirmed_block.data[0], + unconfirmed_block.data[1]}; + for (int j = 0; j < 4; j++) { + is_verified = is_verified && verify_eddsa_signature(unconfirmed_block.validators_signatures[j], + unconfirmed_block.validators_keys[j], + message); + } + return is_verified; + } + ``` + + + ```rust + pub fn hash_512(r: &EdwardsAffine, pk: &EdwardsAffine, m: &EdDSAMessageBlockType) -> Fr { + assigner_sha2_512(r.0, pk.0, [m[0].0, m[1].0, m[2].0, m[3].0]).into() + } + + pub fn hash_256(block1: &BlockType, block2: &BlockType) -> BlockType { + let sha = assigner_sha2_256([block1[0].0, block1[1].0], [block2[0].0, block2[1].0]); + [sha[0].into(), sha[1].into()] + } + + pub fn verify_eddsa_signature( + input: &EdDSASignatureType, + pk: &EdwardsAffine, + m: &EdDSAMessageBlockType, + ) -> bool { + let b = EdwardsAffine::one(); + let k = hash_512(&input.r, pk, m); + b * input.s == input.r + (pk * k) + } + + pub fn is_same(x: BlockType, y: BlockType) -> bool { + x[0] == y[0] && x[1] == y[1] + } + + #[unroll_for_loops] + pub fn verify_signature(unconfirmed_block: &BlockDataType) -> bool { + let mut is_verified: bool = true; + let message: EdDSAMessageBlockType = [ + unconfirmed_block.prev_block_hash[0], + unconfirmed_block.prev_block_hash[1], + unconfirmed_block.data[0], + unconfirmed_block.data[1], + ]; + + for i in 0..4 { + is_verified = is_verified + && verify_eddsa_signature( + &unconfirmed_block.validators_signatures[i], + &unconfirmed_block.validators_keys[i], + &message, + ); + } + + is_verified + } + ``` + + + +### Circuit function + + + + ```cpp + [[circuit]] bool verify_protocol_state_proof ( + typename sha2<256>::block_type last_confirmed_block_hash, + std::array unconfirmed_blocks) { + bool res = true; + if (!is_same(unconfirmed_blocks[0].prev_block_hash, last_confirmed_block_hash)) { + return false; + } + for (int i = 1; i < 2; i++) { + typename sha2<256>::block_type evaluated_block_hash = + hash>( + unconfirmed_blocks[i-1].prev_block_hash, + unconfirmed_blocks[i-1].data); + res = res & is_same(unconfirmed_blocks[i].prev_block_hash, evaluated_block_hash); + } + return res; + } + ``` + + + ```rust + #[circuit] + #[unroll_for_loops] + pub fn verify_protocol_state_proof( + last_confirmed_block_hash: BlockType, + unconfirmed_blocks: [BlockDataType; 2], + ) -> bool { + let mut is_correct = is_same( + unconfirmed_blocks[0].prev_block_hash, + last_confirmed_block_hash, + ); + is_correct = is_correct && verify_signature(&unconfirmed_blocks[0]); + + for i in 1..2 { + let evaluated_block_hash: BlockType = hash_256( + &unconfirmed_blocks[i - 1].prev_block_hash, + &unconfirmed_blocks[i - 1].data, + ); + + is_correct = + is_correct && is_same(unconfirmed_blocks[i].prev_block_hash, evaluated_block_hash); + is_correct = is_correct && verify_signature(&unconfirmed_blocks[i]); + } + + is_correct + } + ``` + + + +### Full code + + + + ```cpp + #include + #include + #include + #include + + using namespace nil::crypto3; + using namespace nil::crypto3::algebra::curves; + + typedef __attribute__((ext_vector_type(4))) + typename pallas::base_field_type::value_type eddsa_message_block_type; + + typedef struct { + typename ed25519::template g1_type<>::value_type R; + typename ed25519::scalar_field_type::value_type s; + } eddsa_signature_type; + + bool verify_eddsa_signature (eddsa_signature_type input, + typename ed25519::template g1_type<>::value_type pk, + eddsa_message_block_type M) { + + typename ed25519::template g1_type<>::value_type B = ed25519::template g1_type<>::one(); + typename ed25519::scalar_field_type::value_type k = __builtin_assigner_sha2_512_curve25519(input.R, pk, M); + + return B*input.s == (input.R + (pk*k)); + } + + typedef struct { + typename hashes::sha2<256>::block_type prev_block_hash; + typename hashes::sha2<256>::block_type data; + std::array validators_signatures; + std::array::value_type, 4> validators_keys; + } block_data_type; + + bool is_same(typename hashes::sha2<256>::block_type block0, + typename hashes::sha2<256>::block_type block1){ + + return block0[0] == block1[0] && block0[1] == block1[1]; + } + + bool verify_signature(block_data_type unconfirmed_block) { + bool is_verified = true; + eddsa_message_block_type message = {unconfirmed_block.prev_block_hash[0], + unconfirmed_block.prev_block_hash[1], unconfirmed_block.data[0], + unconfirmed_block.data[1]}; + for (int j = 0; j < 4; j++) { + is_verified = is_verified && verify_eddsa_signature(unconfirmed_block.validators_signatures[j], + unconfirmed_block.validators_keys[j], + message); + } + return is_verified; + } + + [[circuit]] bool verify_protocol_state_proof ( + typename hashes::sha2<256>::block_type last_confirmed_block_hash, + std::array unconfirmed_blocks) { + + bool is_correct = is_same(unconfirmed_blocks[0].prev_block_hash, last_confirmed_block_hash); + is_correct = is_correct && verify_signature(unconfirmed_blocks[0]); + + for (int i = 1; i < 2; i++) { + + typename hashes::sha2<256>::block_type evaluated_block_hash = + hash>(unconfirmed_blocks[i-1].prev_block_hash, + unconfirmed_blocks[i-1].data); + + is_correct = is_correct && is_same(unconfirmed_blocks[i].prev_block_hash, evaluated_block_hash); + + is_correct = is_correct && verify_signature(unconfirmed_blocks[i]); + } + + return is_correct; + } + ``` + + + ```rust + #![no_main] + + use std::intrinsics::assigner_sha2_256; + use std::intrinsics::assigner_sha2_512; + + use ark_curve25519::{EdwardsAffine, Fr}; + use ark_pallas::Fq; + use unroll::unroll_for_loops; + + type BlockType = [Fq; 2]; + type EdDSAMessageBlockType = [Fq; 4]; + + #[repr(C)] + pub struct BlockDataType { + prev_block_hash: BlockType, + data: BlockType, + validators_signatures: [EdDSASignatureType; 4], + validators_keys: [EdwardsAffine; 4], + } + + #[repr(C)] + pub struct EdDSASignatureType { + r: EdwardsAffine, + s: Fr, + } + + pub fn hash_512(r: &EdwardsAffine, pk: &EdwardsAffine, m: &EdDSAMessageBlockType) -> Fr { + assigner_sha2_512(r.0, pk.0, [m[0].0, m[1].0, m[2].0, m[3].0]).into() + } + + pub fn hash_256(block1: &BlockType, block2: &BlockType) -> BlockType { + let sha = assigner_sha2_256([block1[0].0, block1[1].0], [block2[0].0, block2[1].0]); + [sha[0].into(), sha[1].into()] + } + + pub fn verify_eddsa_signature( + input: &EdDSASignatureType, + pk: &EdwardsAffine, + m: &EdDSAMessageBlockType, + ) -> bool { + let b = EdwardsAffine::one(); + let k = hash_512(&input.r, pk, m); + b * input.s == input.r + (pk * k) + } + + pub fn is_same(x: BlockType, y: BlockType) -> bool { + x[0] == y[0] && x[1] == y[1] + } + + #[unroll_for_loops] + pub fn verify_signature(unconfirmed_block: &BlockDataType) -> bool { + let mut is_verified: bool = true; + let message: EdDSAMessageBlockType = [ + unconfirmed_block.prev_block_hash[0], + unconfirmed_block.prev_block_hash[1], + unconfirmed_block.data[0], + unconfirmed_block.data[1], + ]; + + for i in 0..4 { + is_verified = is_verified + && verify_eddsa_signature( + &unconfirmed_block.validators_signatures[i], + &unconfirmed_block.validators_keys[i], + &message, + ); + } + + is_verified + } + + #[circuit] + #[unroll_for_loops] + pub fn verify_protocol_state_proof( + last_confirmed_block_hash: BlockType, + unconfirmed_blocks: [BlockDataType; 2], + ) -> bool { + let mut is_correct = is_same( + unconfirmed_blocks[0].prev_block_hash, + last_confirmed_block_hash, + ); + is_correct = is_correct && verify_signature(&unconfirmed_blocks[0]); + + for i in 1..2 { + let evaluated_block_hash: BlockType = hash_256( + &unconfirmed_blocks[i - 1].prev_block_hash, + &unconfirmed_blocks[i - 1].data, + ); + + is_correct = + is_correct && is_same(unconfirmed_blocks[i].prev_block_hash, evaluated_block_hash); + is_correct = is_correct && verify_signature(&unconfirmed_blocks[i]); + } + + is_correct + } + ``` + + + + +## Public input + +The public input for the circuit could look as follows: + + + + ```cpp + [ + {"vector":[{"field": 1}, {"field":1}]}, + {"array": [ + {"struct": [{"vector": [{"field": 1}, {"field": 1}]}, + {"vector": [{"field": 1}, {"field": 1}]}, + {"array": [ + {"struct": [{"curve":[4,5]}, {"field": 8}]}, + {"struct": [{"curve":[4,5]}, {"field": 8}]}, + {"struct": [{"curve":[4,5]}, {"field": 8}]}, + {"struct": [{"curve":[4,5]}, {"field": 8}]} + ]}, + {"array":[ + {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec","0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]}, + {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec","0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]}, + {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec","0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]}, + {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec","0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]} + ]} + ]}, + {"struct": [{"vector": [{"field": 1}, {"field": 1}]}, + {"vector": [{"field": 1}, {"field": 1}]}, + {"array": [ + {"struct": [{"curve":[4,5]}, {"field": 8}]}, + {"struct": [{"curve":[4,5]}, {"field": 8}]}, + {"struct": [{"curve":[4,5]}, {"field": 8}]}, + {"struct": [{"curve":[4,5]}, {"field": 8}]} + ]}, + {"array":[ + {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec","0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]}, + {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec","0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]}, + {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec","0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]}, + {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec","0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]} + ]} + ]} + ]} + ] + ``` + + + ```rust + [ + { + "array": [{"field": "1"}, {"field": "1"}] + }, + { + "array": [ + { + "struct": [ + { + "array": [{"field": 1}, {"field": 1}] + }, + { + "array": [{"field": 3}, {"field": 1}] + }, + { + "array": [ + {"struct": [{"curve": [4, 5]}, {"field": 8}]}, + {"struct": [{"curve": [4, 5]}, {"field": 8}]}, + {"struct": [{"curve": [4, 5]}, {"field": 8}]}, + {"struct": [{"curve": [4, 5]}, {"field": 8}]} + ] + }, + { + "array": [ + {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec", "0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]}, + {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec", "0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]}, + {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec", "0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]}, + {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec", "0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]} + ] + } + ] + }, + { + "struct": [ + { + "array": [{"field": 1}, {"field": 1}] + }, + { + "array": [{"field": 1}, {"field": 1}] + }, + { + "array": [ + {"struct": [{"curve": [4, 5]}, {"field": 8}]}, + {"struct": [{"curve": [4, 5]}, {"field": 8}]}, + {"struct": [{"curve": [4, 5]}, {"field": 8}]}, + {"struct": [{"curve": [4, 5]}, {"field": 8}]} + ] + }, + { + "array": [ + {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec", "0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]}, + {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec", "0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]}, + {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec", "0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]}, + {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec", "0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]} + ] + } + ] + } + ] + } + ] + ``` + + +