From 2a118e0a32e76b92b98a8f1ed071c2cbee83a47a Mon Sep 17 00:00:00 2001 From: Shukhrat Khannanov Date: Wed, 15 May 2024 15:59:49 +0300 Subject: [PATCH 01/11] primer added, how-to guides cont --- sidebar-zkllvm.js | 69 +++---- .../circuit-development/standalone-clang.md | 25 --- zkllvm/misc/contact.md | 5 - zkllvm/misc/contributing.md | 3 - zkllvm/use-cases/primer.mdx | 188 ++++++++++++++++++ .../zk-bridge/eddsa.mdx} | 0 .../zk-bridge/hashes.mdx} | 74 ++++++- .../zk-bridge/merkle-tree.mdx} | 0 .../zk-bridge/zkbridge.mdx} | 0 9 files changed, 291 insertions(+), 73 deletions(-) delete mode 100644 zkllvm/circuit-development/standalone-clang.md delete mode 100644 zkllvm/misc/contact.md delete mode 100644 zkllvm/misc/contributing.md create mode 100644 zkllvm/use-cases/primer.mdx rename zkllvm/{tutorials/02-eddsa.md => use-cases/zk-bridge/eddsa.mdx} (100%) rename zkllvm/{tutorials/01-hashes.md => use-cases/zk-bridge/hashes.mdx} (76%) rename zkllvm/{tutorials/03-merkle-tree.md => use-cases/zk-bridge/merkle-tree.mdx} (100%) rename zkllvm/{tutorials/04-zkbridge.md => use-cases/zk-bridge/zkbridge.mdx} (100%) diff --git a/sidebar-zkllvm.js b/sidebar-zkllvm.js index 0960181c..58538a24 100644 --- a/sidebar-zkllvm.js +++ b/sidebar-zkllvm.js @@ -86,7 +86,7 @@ export default { collapsed: true, items: [ { - + type: 'doc', label: 'Refactoring if/else statements', id: 'best-practices-limitations/avoiding-if-else-statements' @@ -118,19 +118,6 @@ export default { }, ] }, - { - type: 'category', - label: 'Circuit development', - collapsible: true, - collapsed: true, - items: [ - { - type: 'doc', - label: 'Standard library', - id: 'circuit-development/standalone-clang', - }, - ], - }, { type: 'category', label: 'Use cases', @@ -139,24 +126,38 @@ export default { items: [ { type: 'doc', - label: 'First circuit with hashes', - id: 'tutorials/hashes' + label: 'Primer', + id: 'use-cases/primer' }, { - type: 'doc', - label: 'EsDSA signature verifications', - id: 'tutorials/eddsa' - }, - { - type: 'doc', - label: 'Merkle tree commitment schemes', - id: 'tutorials/merkle-tree' - }, - { - 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' + }, + ] }, + ] }, { @@ -165,21 +166,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/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/use-cases/primer.mdx b/zkllvm/use-cases/primer.mdx new file mode 100644 index 00000000..752ad5d5 --- /dev/null +++ b/zkllvm/use-cases/primer.mdx @@ -0,0 +1,188 @@ +# 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 would be necessary 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 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 a directory for CMake modules: + +```bash +mkdir cmake && cd cmake +``` + +Add the [**`CircuitCompile.cmake` module from the zkLLVM repository**](https://github.com/NilFoundation/zkLLVM/blob/master/cmake/CircuitCompile.cmake) to the `./new_circuit/cmake` directory: + +``` +new_circuit +-- cmake + -- CircuitCompile.cmake +``` + +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 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 "path/to/cmake") + +include(CircuitCompile) +``` + +Add the required library and Boost to the project configuration: + +```cmake +set(crypto3_DIR "path/to/crypto3_headers") + +find_package(crypto3 REQUIRED) + +find_package(Boost REQUIRED COMPONENTS system filesystem) +``` + +:::tip[Boost] + +Adding Boost is mandatory. + +::: + +Set the circuit source and add a build target: + +```cmake +add_circuit( + circuit SOURCES ${SOURCES} + LINK_LIBRARIES + crypto3::all + ${Boost_LIBRARIES} +) +``` + +, where `circuit` is the desired target name. + +Compile the circuit with: + +```bash +cmake -G "Unix Makefiles" -B ${ZKLLVM_BUILD:-build} -DCMAKE_BUILD_TYPE=Release . +make -C ${ZKLLVM_BUILD:-build} circuit -j$(nproc) +``` + +The circuit IR should appear in the `./build` folder. + +:::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, [**install the `zkllvm` Deb package**](../getting-started/installation#install-zkllvm-binaries). + +::: + +### 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: + +```toml +ark-pallas = { git = "https://github.com/NilFoundation/arkworks-curves.git", features = ["zkllvm"] } +unroll = { git = "https://github.com/NilFoundation/zkllvm-unroll.git" } +``` + +:::tip + +Adding `unroll` [**is mandatory**](../best-practices-limitations/unrolling-loops) for circuits that use loops. + +::: + +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 --release +``` + +To learn more about additional compilation options, [**click here**](../getting-started/compiling-rust-code). \ No newline at end of file diff --git a/zkllvm/tutorials/02-eddsa.md b/zkllvm/use-cases/zk-bridge/eddsa.mdx similarity index 100% rename from zkllvm/tutorials/02-eddsa.md rename to zkllvm/use-cases/zk-bridge/eddsa.mdx diff --git a/zkllvm/tutorials/01-hashes.md b/zkllvm/use-cases/zk-bridge/hashes.mdx similarity index 76% rename from zkllvm/tutorials/01-hashes.md rename to zkllvm/use-cases/zk-bridge/hashes.mdx index f4b468ae..3ec98e9f 100644 --- a/zkllvm/tutorials/01-hashes.md +++ b/zkllvm/use-cases/zk-bridge/hashes.mdx @@ -1,4 +1,76 @@ -# First circuit with hashes +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) + +## Key components + +The circuit consists of the following components: + +### Haders / modules + + + + ```cpp + #include + #include + ``` + + + ```rust + use std::intrinsics::assigner_sha2_256; + use ark_pallas::Fq; + ``` + + + +### Structs and types + + + + ```cpp + struct block_data_type { + typename hashes::sha2<256>::block_type prev_block_hash; + typename hashes::sha2<256>::block_type data; + }; + ``` + + + ```rust + type BlockType = [Fq; 2]; + ``` + + + +### Auxiliary functions + + + + ```cpp + struct block_data_type { + typename hashes::sha2<256>::block_type prev_block_hash; + typename hashes::sha2<256>::block_type data; + }; + ``` + + + ```rust + type BlockType = [Fq; 2]; + ``` + + + 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. diff --git a/zkllvm/tutorials/03-merkle-tree.md b/zkllvm/use-cases/zk-bridge/merkle-tree.mdx similarity index 100% rename from zkllvm/tutorials/03-merkle-tree.md rename to zkllvm/use-cases/zk-bridge/merkle-tree.mdx diff --git a/zkllvm/tutorials/04-zkbridge.md b/zkllvm/use-cases/zk-bridge/zkbridge.mdx similarity index 100% rename from zkllvm/tutorials/04-zkbridge.md rename to zkllvm/use-cases/zk-bridge/zkbridge.mdx From 067e97f3ce15c38a84b17823f24fc0d1aefab239 Mon Sep 17 00:00:00 2001 From: Shukhrat Khannanov Date: Thu, 16 May 2024 12:25:22 +0300 Subject: [PATCH 02/11] added rust info to primer, hashes done --- zkllvm/use-cases/primer.mdx | 23 +- zkllvm/use-cases/zk-bridge/hashes.mdx | 387 +++++++++++++++----------- 2 files changed, 238 insertions(+), 172 deletions(-) diff --git a/zkllvm/use-cases/primer.mdx b/zkllvm/use-cases/primer.mdx index 752ad5d5..2ab52d0e 100644 --- a/zkllvm/use-cases/primer.mdx +++ b/zkllvm/use-cases/primer.mdx @@ -152,11 +152,16 @@ Optionally add the `--vcs none` option to prevent `cargo` from automatically cre ::: -Open the `Cargo.toml` file and add the following dependencies: +Open the `Cargo.toml` file and add the following dependencies and feature flags: ```toml -ark-pallas = { git = "https://github.com/NilFoundation/arkworks-curves.git", features = ["zkllvm"] } +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 @@ -165,6 +170,16 @@ Adding `unroll` [**is mandatory**](../best-practices-limitations/unrolling-loops ::: +:::tip + +The `"zkllvm"` feature flag is necessary to ensure compatibility with the original Rust compiler. + +If this feature is enabled for a project, 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. + +::: + Add circuit code that references an external library: ```rust @@ -182,7 +197,7 @@ pub fn sha256_example() -> BlockType {...} Compile the circuit with: ```bash -cargo +zkllvm build --target assigner-unknown-unknown --release +cargo +zkllvm build --target assigner-unknown-unknown --features zkllvm --release ``` -To learn more about additional compilation options, [**click here**](../getting-started/compiling-rust-code). \ No newline at end of file +To learn more about additional compilation options, [**click here**](../getting-started/compiling-rust-code). diff --git a/zkllvm/use-cases/zk-bridge/hashes.mdx b/zkllvm/use-cases/zk-bridge/hashes.mdx index 3ec98e9f..a1f36306 100644 --- a/zkllvm/use-cases/zk-bridge/hashes.mdx +++ b/zkllvm/use-cases/zk-bridge/hashes.mdx @@ -14,7 +14,7 @@ Read the following tutorials before proceeding further. * [**Built-ins**](../../getting-started/built-ins) * [**Primer**](../primer) -## Key components +## Circuit code The circuit consists of the following components: @@ -25,12 +25,17 @@ The circuit consists of the following components: ```cpp #include #include + + using namespace nil::crypto3; + using namespace nil::crypto3::hashes; ``` ```rust use std::intrinsics::assigner_sha2_256; use ark_pallas::Fq; + + use unroll::unroll_for_loops; ``` @@ -41,215 +46,261 @@ The circuit consists of the following components: ```cpp struct block_data_type { - typename hashes::sha2<256>::block_type prev_block_hash; - typename hashes::sha2<256>::block_type data; + 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, + } ``` -### Auxiliary functions +### Additional functions ```cpp - struct block_data_type { - typename hashes::sha2<256>::block_type prev_block_hash; - typename hashes::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]; + } ``` ```rust - type BlockType = [Fq; 2]; + fn is_same(x: Block, y: Block) -> bool { + x[0] == y[0] && x[1] == y[1] + } + + fn hash(block1: Block, block2: Block) -> Block { + 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 -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]); + + + ```cpp + [[circuit]] bool verify_protocol_state_proof ( + typename sha2<256>::block_type last_confirmed_block_hash, + std::array unconfirmed_blocks) { + for (int i = 0; i < 64; i++) { + 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; } + ``` + + + ```rust + #[circuit] + #[unroll_for_loops] + fn verify_protocol_state_proof( + last_confirmed_block_hash: Fq, + unconfirmed_blocks: [BlockDataType; 64], + ) -> bool { + for i in 0..64 { + if i == 0 { + if is_same(unconfirmed_blocks[i][0], last_confirmed_block_hash) { + false + } + } else { + let evaluated_block_hash = + hash(unconfirmed_blocks[i - 1][0], unconfirmed_blocks[i - 1][1]); + if is_same(unconfirmed_blocks[i][0], evaluated_block_hash) { + false + } + } + true + } + } + ``` + + - return result; -} -``` +### Full code -Now a real-life example. + + + ```cpp + #include + #include -Let's assume that we have a blockchain with blocks having the following simplified structure: + using namespace nil::crypto3; + using namespace nil::crypto3::hashes; -```cpp -struct block_data_type { - typename hashes::sha2<256>::block_type prev_block_hash; - typename hashes::sha2<256>::block_type data; -}; -``` + struct block_data_type { + typename sha2<256>::block_type prev_block_hash; + typename sha2<256>::block_type data; + }; -Let's imagine that we want to verify that a particular block was created correctly. + bool is_same( + typename sha2<256>::block_type block0, + typename sha2<256>::block_type block1) { -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. + return block0[0] == block1[0] && block0[1] == block1[1]; + } -For each unverified block, we will calculate the hash of its parent and check that it matches the hash stored in the block. + [[circuit]] bool verify_protocol_state_proof ( + typename sha2<256>::block_type last_confirmed_block_hash, + std::array unconfirmed_blocks) { + for (int i = 0; i < 64; i++) { + 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; + } + ``` + + + ```rust + #![no_main] -We will start with a simple example where + use ark_pallas::Fq; + use std::intrinsics::assigner_sha2_256; -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. + type BlockType = [Fq; 2]; -```cpp -#include -#include + struct BlockDataType { + prev_block_hash: BlockType, + data: BlockType, + } -using namespace nil::crypto3::hashes; + fn is_same(x: Block, y: Block) -> bool { + x[0] == y[0] && x[1] == y[1] + } -struct block_data_type { - typename sha2<256>::block_type prev_block_hash; - typename sha2<256>::block_type data; -}; + fn hash(block1: Block, block2: Block) -> Block { + let sha = assigner_sha2_256([block1[0].0, block1[1].0], [block2[0].0, block2[1].0]); + [sha[0].into(), sha[1].into()] + } -bool is_same( - typename hashes::sha2<256>::block_type block0, - typename hashes::sha2<256>::block_type block1) { + #[circuit] + #[unroll_for_loops] + fn verify_protocol_state_proof( + last_confirmed_block_hash: Fq, + unconfirmed_blocks: [BlockDataType; 64], + ) -> bool { + for i in 0..64 { + if i == 0 { + if is_same(unconfirmed_blocks[i][0], last_confirmed_block_hash) { + false + } + } else { + let evaluated_block_hash = + hash(unconfirmed_blocks[i - 1][0], unconfirmed_blocks[i - 1][1]); + if is_same(unconfirmed_blocks[i][0], evaluated_block_hash) { + false + } + } + true + } + } + ``` + + - return block0[0] == block1[0] && block0[1] == block1[1]; -} +## Public input -[[circuit]] bool verify_protocol_state_proof ( - typename sha2<256>::block_type last_confirmed_block_hash, - block_data_type unconfirmed_block) { +The `block_data_type` / `BlockDataType` aggregate type can be expressed as follows: - return is_same( - unconfirmed_block.prev_block_hash, - last_confirmed_block_hash); +```json +{ + { + "struct": [ + {"vector": [{"field": 1}, {"field": 1}]}, + {"vector": [{"field": 1}, {"field": 1}]}, + ] + } } ``` -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; +The public input for the circuit would look as follows: + +```json +[ + { + "vector": [ + {"field": 4209827349872394872394872394872398472398472398472398472398472398}, + {"field": 9823472983472938472938472938472983472983472983472983472983472983} + ] + }, + { + "struct": [ + { + "vector": [ + {"field": 129837498237498237498237498237498237498237498237498237498237}, + {"field": 23984723984723984723984723984723984723984723984723984723984} + ] + }, + { + "vector": [ + {"field": 3872498273498237498237498237498237498237498237498237498237498}, + {"field": 1823048723048723048723048723048723048723048723048723048723048} + ] } - } 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; + ] + }, + { + "struct": [ + { + "vector": [ + {"field": 978293748293748293748239874823984723984723984723984723984723}, + {"field": 5092384750293847502938475029384750293847502938475029384750293} + ] + }, + { + "vector": [ + {"field": 3948572039847502938475029384750293847502938475029384750293847}, + {"field": 5029384750293847502938475029384750293847502938475029384750293} + ] } - } - } - return true; -} -``` - -Congratulations! - -You have just created your first circuit using hashes. - -We will use it later to verify blocks in the protocol state. + ] + }, +] +``` \ No newline at end of file From e2a5311e8990b1521b1689d43ec60cfc4d50772e Mon Sep 17 00:00:00 2001 From: Shukhrat Khannanov Date: Mon, 20 May 2024 15:35:00 +0300 Subject: [PATCH 03/11] cpp zkbridge added, need to add rust --- zkllvm/use-cases/primer.mdx | 27 +- zkllvm/use-cases/zk-bridge/eddsa.mdx | 275 +++++++++++------ zkllvm/use-cases/zk-bridge/hashes.mdx | 94 +++--- zkllvm/use-cases/zk-bridge/merkle-tree.mdx | 299 +++++++++++++----- zkllvm/use-cases/zk-bridge/zkbridge.mdx | 343 +++++++++++++-------- 5 files changed, 689 insertions(+), 349 deletions(-) diff --git a/zkllvm/use-cases/primer.mdx b/zkllvm/use-cases/primer.mdx index 2ab52d0e..0e9d54d9 100644 --- a/zkllvm/use-cases/primer.mdx +++ b/zkllvm/use-cases/primer.mdx @@ -93,19 +93,21 @@ list(APPEND CMAKE_MODULE_PATH "path/to/cmake") include(CircuitCompile) ``` -Add the required library and Boost to the project configuration: +Add the required library to the project configuration: ```cmake set(crypto3_DIR "path/to/crypto3_headers") find_package(crypto3 REQUIRED) - -find_package(Boost REQUIRED COMPONENTS system filesystem) ``` :::tip[Boost] -Adding Boost is mandatory. +`crypto3` already depends on Boost. If an alternative library (that does not depend on Boost) is used, add Boost to the CMake configuration: + +```cmake +find_package(Boost REQUIRED COMPONENTS system filesystem) +``` ::: @@ -125,18 +127,29 @@ add_circuit( Compile the circuit with: ```bash -cmake -G "Unix Makefiles" -B ${ZKLLVM_BUILD:-build} -DCMAKE_BUILD_TYPE=Release . + +cmake -G "Unix Makefiles" -B build -DCMAKE_BUILD_TYPE=Release . make -C ${ZKLLVM_BUILD:-build} circuit -j$(nproc) ``` -The circuit IR should appear in the `./build` folder. - :::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, [**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 a dependency is installed in a custom location rather than `/usr/local/` or any other location requiring root permissions. + +::: + ### Using Cargo Create a new `cargo` project: diff --git a/zkllvm/use-cases/zk-bridge/eddsa.mdx b/zkllvm/use-cases/zk-bridge/eddsa.mdx index e8fcc812..5746305e 100644 --- a/zkllvm/use-cases/zk-bridge/eddsa.mdx +++ b/zkllvm/use-cases/zk-bridge/eddsa.mdx @@ -1,126 +1,203 @@ -# EdDSA signature verification +import Tabs from '@theme/Tabs' +import TabItem from '@theme/TabItem' -EdDSA signature plays a significant role in the blockchain world. +# Verify EdDSA signatures -In the zkBridge tutorial serie, we will use it to verify validators' signatures. +Handling EdDSA signatures is the next logical step in creating a simple zkBridge. -In this tutorial, we will show you how to implement the EdDSA signature verification algorithm in the circuit form. +## Prerequisites -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: +Read the following tutorials before proceeding further. -```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. +* [**Primer**](../primer) +* [**Write a circuit with hashes**](./hashes) -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: +## Circuit code -```cpp -#include -#include -#include - -using namespace nil::crypto3::algebra::curves; -``` +The circuit consists of the following components: -The EdDSA signature result consists of two elements: `R` and `s`. +### Headers and namespaces / crates and modules -`R` is an element of the g1 group, and `s` is an element of the scalar field. + + + ```cpp + #include + #include + #include + #include -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; -}; -``` + using namespace nil::crypto3::algebra::curves; + ``` + + + ```rust + #![no_main] -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: + use ark_curve25519::{EdwardsAffine, Fr}; + use ark_pallas::Fq; -```cpp -#include -#include -#include -#include + use std::intrinsics::assigner_sha2_512; + ``` + + -using namespace nil::crypto3::algebra::curves; +### Structs and types -typedef __attribute__((ext_vector_type(4))) + + + ```cpp + 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; -}; + ``` + + + ```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() +} ``` -Now we can define the signature verification function: - -```cpp -#include -#include -#include -#include - -using namespace nil::crypto3::algebra::curves; +### Circuit function -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; -}; + + + ```cpp + [[circuit]] bool verify_eddsa_signature ( -[[circuit]] bool verify_eddsa_signature (eddsa_signature_type input, + 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<>::value_type::one(); - __zkllvm_field_curve25519_scalar k = - __builtin_assigner_sha2_512_curve25519(input.R.data, pk.data, 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))).is_zero(); + 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) } -``` - -:::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; + 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 `eddsa_message_block_type` / `EdDSAMessageBlockType` field is expressed as follows: + +```json +{ + "vector": [ + {"field": "0x3992d30ed00000001"}, + {"field": "0x891a63f02533e46"}, + {"field": "0"}, + {"field": "0x100000000000000"} + ] } ``` + +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 index a1f36306..10d27e56 100644 --- a/zkllvm/use-cases/zk-bridge/hashes.mdx +++ b/zkllvm/use-cases/zk-bridge/hashes.mdx @@ -18,20 +18,22 @@ Read the following tutorials before proceeding further. The circuit consists of the following components: -### Haders / modules +### Headers and namespaces / crates and modules ```cpp - #include - #include + #include + #include + #include - using namespace nil::crypto3; - using namespace nil::crypto3::hashes; + using namespace nil::crypto3::algebra::curves; ``` ```rust + #![no_main] + use std::intrinsics::assigner_sha2_256; use ark_pallas::Fq; @@ -78,11 +80,11 @@ The circuit consists of the following components: ```rust - fn is_same(x: Block, y: Block) -> bool { + fn is_same(x: BlockType, y: BlockType) -> bool { x[0] == y[0] && x[1] == y[1] } - fn hash(block1: Block, block2: Block) -> Block { + 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()] } @@ -97,8 +99,8 @@ The circuit consists of the following components: ```cpp [[circuit]] bool verify_protocol_state_proof ( typename sha2<256>::block_type last_confirmed_block_hash, - std::array unconfirmed_blocks) { - for (int i = 0; i < 64; i++) { + std::array unconfirmed_blocks) { + for (int i = 0; i < 2; i++) { if (i == 0) { if (!is_same( unconfirmed_blocks[i].prev_block_hash, @@ -127,9 +129,9 @@ The circuit consists of the following components: #[unroll_for_loops] fn verify_protocol_state_proof( last_confirmed_block_hash: Fq, - unconfirmed_blocks: [BlockDataType; 64], + unconfirmed_blocks: [BlockDataType; 2], ) -> bool { - for i in 0..64 { + for i in 0..2 { if i == 0 { if is_same(unconfirmed_blocks[i][0], last_confirmed_block_hash) { false @@ -173,8 +175,8 @@ The circuit consists of the following components: [[circuit]] bool verify_protocol_state_proof ( typename sha2<256>::block_type last_confirmed_block_hash, - std::array unconfirmed_blocks) { - for (int i = 0; i < 64; i++) { + std::array unconfirmed_blocks) { + for (int i = 0; i < 2; i++) { if (i == 0) { if (!is_same( unconfirmed_blocks[i].prev_block_hash, @@ -211,11 +213,11 @@ The circuit consists of the following components: data: BlockType, } - fn is_same(x: Block, y: Block) -> bool { + fn is_same(x: BlockType, y: BlockType) -> bool { x[0] == y[0] && x[1] == y[1] } - fn hash(block1: Block, block2: Block) -> Block { + 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()] } @@ -224,9 +226,9 @@ The circuit consists of the following components: #[unroll_for_loops] fn verify_protocol_state_proof( last_confirmed_block_hash: Fq, - unconfirmed_blocks: [BlockDataType; 64], + unconfirmed_blocks: [BlockDataType; 2], ) -> bool { - for i in 0..64 { + for i in 0..2 { if i == 0 { if is_same(unconfirmed_blocks[i][0], last_confirmed_block_hash) { false @@ -260,47 +262,51 @@ The `block_data_type` / `BlockDataType` aggregate type can be expressed as follo } ``` -The public input for the circuit would look as follows: +The public input for the circuit could look as follows: ```json [ { "vector": [ - {"field": 4209827349872394872394872394872398472398472398472398472398472398}, - {"field": 9823472983472938472938472938472983472983472983472983472983472983} + {"field": "4209827349872394872394872394872398472398472398472398472398472398"}, + {"field": "9823472983472938472938472938472983472983472983472983472983472983"} ] }, { - "struct": [ + "array": [ { - "vector": [ - {"field": 129837498237498237498237498237498237498237498237498237498237}, - {"field": 23984723984723984723984723984723984723984723984723984723984} + "struct": [ + { + "vector": [ + {"field": "129837498237498237498237498237498237498237498237498237498237"}, + {"field": "23984723984723984723984723984723984723984723984723984723984"} + ] + }, + { + "vector": [ + {"field": "3872498273498237498237498237498237498237498237498237498237498"}, + {"field": "1823048723048723048723048723048723048723048723048723048723048"} + ] + } ] }, { - "vector": [ - {"field": 3872498273498237498237498237498237498237498237498237498237498}, - {"field": 1823048723048723048723048723048723048723048723048723048723048} + "struct": [ + { + "vector": [ + {"field": "978293748293748293748239874823984723984723984723984723984723"}, + {"field": "5092384750293847502938475029384750293847502938475029384750293"} + ] + }, + { + "vector": [ + {"field": "3948572039847502938475029384750293847502938475029384750293847"}, + {"field": "5029384750293847502938475029384750293847502938475029384750293"} + ] + } ] } ] - }, - { - "struct": [ - { - "vector": [ - {"field": 978293748293748293748239874823984723984723984723984723984723}, - {"field": 5092384750293847502938475029384750293847502938475029384750293} - ] - }, - { - "vector": [ - {"field": 3948572039847502938475029384750293847502938475029384750293847}, - {"field": 5029384750293847502938475029384750293847502938475029384750293} - ] - } - ] - }, + } ] ``` \ No newline at end of file diff --git a/zkllvm/use-cases/zk-bridge/merkle-tree.mdx b/zkllvm/use-cases/zk-bridge/merkle-tree.mdx index fe60f2ea..62a3b92b 100644 --- a/zkllvm/use-cases/zk-bridge/merkle-tree.mdx +++ b/zkllvm/use-cases/zk-bridge/merkle-tree.mdx @@ -1,125 +1,268 @@ -# Merkle tree commitment scheme +import Tabs from '@theme/Tabs' +import TabItem from '@theme/TabItem' -Merkle tree is often used in blockchain to commit to a set of data. +# Create a Merkle tree commitment scheme -This tutorial will show constructing a Merkle tree verification circuit with zkLLVM. +A Merkle tree is a widely used commitment scheme: -First of all, let's define the structure of a Merkle tree. +* Each leaf node in a Merkle tree contains a hash of a data block +* Each non-leaf node contains a hash of its children -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. +Due to their simplicity, Merkle trees are recommended for creating a simple zkBridge. -The tree's root element is called a Merkle root. It contains a hash of all the data in the tree. +## Prerequisites -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. +Read the following tutorials before proceeding further. -We include the same headers as in the first tutorial and use the same namespace. +* [**Primer**](../primer) +* [**Write a circuit with hashes**](./hashes) +* [**Verify EdDSA signatures**](./eddsa) -:::info +## Circuit code -zkLLVM supports `sha2-256`, `sha2-512`, and `Poseidon` hash functions. +### Headers and namespaces / crates and modules -::: + + + ```cpp + #include + #include -:::info + using namespace nil::crypto3; + ``` + + + ```rust + #![no_main] -In this tutorial we use `std::array` as a container for data blocks, but we understand the necessity of supporting other containers. + use std::intrinsics::assigner_sha2_256; -We are working on it. + use ark_ff::AdditiveGroup; + use ark_pallas::Fq; + use unroll::unroll_for_loops; + ``` + + -Extended support of `std` algorithms is one of our priorities. +### Additional types and functions (Rust only) -::: +```rust +type BlockType = [Fq; 2]; -```cpp -#include -#include - -using namespace nil::crypto3::hashes; +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()] +} ``` -Now let's add the first layer hashing function. +### Circuit function -It takes pairs of leaves and hashes them together. + + + ```cpp + [[circuit]] typename hashes::sha2<256>::block_type + balance(std::array::block_type, 0x10> layer_0_leaves) { -The resulting array of hashes will be two times smaller than the original array of 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 hashes::sha2<256>::block_type root; -```cpp -#include -#include + 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]); + } -using namespace nil::crypto3::hashes; + 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]); + } -[[circuit]] typename sha2<256>::block_type - hash_layer_1(std::array::block_type, 0x10> layer_0_leaves) { + 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]); + } - std::array::block_type, 0x8> layer_1_leaves; + root = hash>(layer_3_leaves[0], layer_3_leaves[1]); - 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 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 + } + ``` + + - return layer_1_leaves; -} -``` - -Each subsequent layer is constructed in the same way. +### Full code -The only difference is that the array of leaves is two times smaller than the previous one. + + + ```cpp + #include + #include -The last layer will contain only one element: the Merkle root. + using namespace nil::crypto3; -The circuit function that we want to build should return this element. + [[circuit]] typename hashes::sha2<256>::block_type + balance(std::array::block_type, 0x10> layer_0_leaves) { -So, let's add the remaining logic and finish the circuit: + 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; -```cpp -#include -#include + 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]); + } -using namespace nil::crypto3::hashes; + 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]); + } -[[circuit]] typename sha2<256>::block_type - balance(std::array::block_type, 0x10> layer_0_leaves) { + 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]); + } - 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; + root = hash>(layer_3_leaves[0], layer_3_leaves[1]); - 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]); + return root; } + ``` + + + ```rust + #![no_main] - 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]); - } + use std::intrinsics::assigner_sha2_256; - 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]); - } + use ark_ff::AdditiveGroup; + use ark_pallas::Fq; + use unroll::unroll_for_loops; - root = hash>(layer_3_leaves[0], layer_3_leaves[1]); + type BlockType = [Fq; 2]; - return root; -} -``` + #[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]; -:::info -Merkle tree is a widely-used data structure. + 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]); + } -In this tutorial, you've learned how to construct it from scratch. + 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]); + } -Though, you don't need to do it every time. + 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]); + } -Crypto3 library has an optimized circuit-friendly implementation of Merkle tree and other algorithms. + 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}]}, + ] + } + ] + ``` + + -We will use this Merkle Tree circuit to build a zkBridge in the next part of the tutorial. diff --git a/zkllvm/use-cases/zk-bridge/zkbridge.mdx b/zkllvm/use-cases/zk-bridge/zkbridge.mdx index c94493d2..6f92add5 100644 --- a/zkllvm/use-cases/zk-bridge/zkbridge.mdx +++ b/zkllvm/use-cases/zk-bridge/zkbridge.mdx @@ -1,153 +1,254 @@ -# Constructing a zkBridge +import Tabs from '@theme/Tabs' +import TabItem from '@theme/TabItem' -Finally, we have all the parts to construct a zkBridge. +# Write an algorithm for state-proof verification -Precisely speaking, we will write circuit of a state-proof verification algorithm. +The final step in the creation of a simple zkBridge is the creation of a state-proof verification algorithm. -As always, we start with headers and namespaces: +## Prerequisites -```cpp -#include -#include -#include -#include -#include -#include +Read the following tutorials before proceeding further. -using namespace nil::crypto3::hashes; -using namespace nil::crypto3::algebra::curves; -``` +* [**Primer**](../primer) +* [**Write a circuit with hashes**](./hashes) +* [**Verify EdDSA signatures**](./eddsa) +* [**Create a Merkle tree commitment scheme**](./merkle-tree) -Here we have included the following components: +## Circuit code -- 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. +### Headers and namespaces / crates and modules -We will now use the following custom types for our algorithm: + + + ```cpp + #include + #include + #include + #include -```cpp -struct eddsa_signature_type { - typename ed25519::template g1_type<>::value_type R; - typename ed25519::scalar_field_type::value_type s; -}; + using namespace nil::crypto3; + using namespace nil::crypto3::algebra::curves; + ``` + + + ```rust + #![no_main] -typedef __attribute__((ext_vector_type(4))) - typename pallas::base_field_type::value_type eddsa_message_block_type; + use std::intrinsics::assigner_sha2_256; + + use ark_ff::AdditiveGroup; + use ark_pallas::Fq; + use unroll::unroll_for_loops; + ``` + + -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; -}; -``` +### Structs and types -Now let's add the EdDSA signature verification function from the [previous tutorial](./eddsa): + + + ```cpp + typedef __attribute__((ext_vector_type(4))) + typename pallas::base_field_type::value_type eddsa_message_block_type; -```cpp -bool verify_eddsa_signature (eddsa_signature_type input, + 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 + #![no_main] + + use std::intrinsics::assigner_sha2_256; + + use ark_ff::AdditiveGroup; + use ark_pallas::Fq; + use unroll::unroll_for_loops; + ``` + + + +### 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); - 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)); + } - return (B*input.s - (input.R + (pk*k))).is_zero(); -} -``` + 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 + #![no_main] -And after adding the final function for verification of the unconfirmed blocks, the code will look like this: + use std::intrinsics::assigner_sha2_256; -```cpp -#include -#include -#include -#include -#include -#include + use ark_ff::AdditiveGroup; + use ark_pallas::Fq; + use unroll::unroll_for_loops; + ``` + + -using namespace nil::crypto3; -using namespace nil::crypto3::algebra::curves; +### Circuit function -typedef __attribute__((ext_vector_type(4))) - typename pallas::base_field_type::value_type eddsa_message_block_type; + + + ```cpp + [[circuit]] bool verify_protocol_state_proof ( + typename hashes::sha2<256>::block_type last_confirmed_block_hash, + std::array unconfirmed_blocks) { -struct eddsa_signature_type { - typename ed25519::template g1_type<>::value_type R; - typename ed25519::scalar_field_type::value_type s; -}; + bool is_correct = is_same(unconfirmed_blocks[0].prev_block_hash, last_confirmed_block_hash); -bool verify_eddsa_signature (eddsa_signature_type input, - typename ed25519::template g1_type<>::value_type pk, - eddsa_message_block_type M) { + is_correct = is_correct && verify_signature(unconfirmed_blocks[0]); - 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, + 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); - - if (!is_same(unconfirmed_blocks[i].prev_block_hash, - evaluated_block_hash)) { - return false; - } + is_correct = is_correct && is_same(unconfirmed_blocks[i].prev_block_hash, evaluated_block_hash); + is_correct = is_correct && verify_signature(unconfirmed_blocks[i]); } - // 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 is_correct; + } + ``` + + + ```rust + #![no_main] + + use std::intrinsics::assigner_sha2_256; + + use ark_ff::AdditiveGroup; + use ark_pallas::Fq; + use unroll::unroll_for_loops; + ``` + + + +### 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; } - return true; -} -``` + [[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]); -That's it! + for (int i = 1; i < 2; i++) { -Now we can compile this code to a state-proof verification circuit and build a zkBridge upon it. + 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 ark_ff::AdditiveGroup; + use ark_pallas::Fq; + use unroll::unroll_for_loops; + ``` + + From 501458510e87a2afc1f9aad6e43dd4480bb60193 Mon Sep 17 00:00:00 2001 From: Shukhrat Khannanov Date: Tue, 21 May 2024 09:49:02 +0300 Subject: [PATCH 04/11] use cases proofread, rust directives added to limitations --- sidebar-zkllvm.js | 7 +- .../rust-derive.mdx | 81 +++++ zkllvm/use-cases/primer.mdx | 30 +- zkllvm/use-cases/zk-bridge/eddsa.mdx | 13 - zkllvm/use-cases/zk-bridge/hashes.mdx | 13 - zkllvm/use-cases/zk-bridge/merkle-tree.mdx | 8 +- zkllvm/use-cases/zk-bridge/zkbridge.mdx | 319 ++++++++++++++++-- 7 files changed, 402 insertions(+), 69 deletions(-) create mode 100644 zkllvm/best-practices-limitations/rust-derive.mdx diff --git a/sidebar-zkllvm.js b/sidebar-zkllvm.js index 58538a24..b6a3476f 100644 --- a/sidebar-zkllvm.js +++ b/sidebar-zkllvm.js @@ -28,7 +28,7 @@ export default { type: 'category', label: 'Getting started', collapsible: true, - collapsed: false, + collapsed: true, items: [ { type: 'doc', @@ -116,6 +116,11 @@ export default { label: 'Unrolling loops', id: 'best-practices-limitations/unrolling-loops' }, + { + type: 'doc', + label: 'Structs and enums in Rust', + id: 'best-practices-limitations/rust-derive' + } ] }, { diff --git a/zkllvm/best-practices-limitations/rust-derive.mdx b/zkllvm/best-practices-limitations/rust-derive.mdx new file mode 100644 index 00000000..48752257 --- /dev/null +++ b/zkllvm/best-practices-limitations/rust-derive.mdx @@ -0,0 +1,81 @@ +# 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://users.rust-lang.org/t/disable-struct-reordering-optimization-when-emitting-llvm-bitcode/74951). Consider the following example: + +```rust +use ark_curve25519::{EdwardsAffine, Fr}; +use ark_pallas::Fq; + +type BlockType = [Fq; 2]; +type EdDSAMessageBlockType = [Fq; 4]; + +#[derive(Copy, Clone)] +pub struct BlockDataType { + prev_block_hash: BlockType, + data: BlockType, + validators_signatures: [EdDSASignatureType; 4], + validators_keys: [EdwardsAffine; 4], +} + +#[derive(Copy, Clone)] +pub struct EdDSASignatureType { + r: EdwardsAffine, + s: Fr, +} +``` + +The public input representation of the `BlockDataType` struct would look as follows: + +```json +"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"]} + ] + } +] +``` + +When compiling the `BlockDataType` struct, `rustc` will reorder its fields. + +When `assigner` is called on a circuit with this struct, the circuit IR will conflict with the public input as the field order in the IR and the public input file will no longer match. + +To avoid this problem, use the `#[repr(C)]` directive: + +```rust +#[derive(Copy, Clone)] +pub struct BlockDataType { + prev_block_hash: BlockType, + data: BlockType, + validators_signatures: [EdDSASignatureType; 4], + validators_keys: [EdwardsAffine; 4], +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct EdDSASignatureType { + r: EdwardsAffine, + s: Fr, +} +``` + +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/use-cases/primer.mdx b/zkllvm/use-cases/primer.mdx index 0e9d54d9..6fc186a9 100644 --- a/zkllvm/use-cases/primer.mdx +++ b/zkllvm/use-cases/primer.mdx @@ -6,7 +6,7 @@ This primer acts as an intro guide to writing complex circuits and compiling the 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 would be necessary to use a dedicated build management system: +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 @@ -25,7 +25,7 @@ Using CMake is still preferable as it offers several valuable features (such as :::info -This primer uses `crypto3` (C++) and `arkworks` (Rust) to show how to use libraries in a circuit. The primer can be reused for any other suitable library. +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. ::: @@ -78,7 +78,7 @@ Create a configuration file for CMake: cd .. && touch CMakeLists.txt ``` -Inside `CMakeLists.txt`, set the project and include the `CircuitCompile.cmake` module: +Inside `CMakeLists.txt`, set the project config and include the `CircuitCompile.cmake` module: ```cmake cmake_minimum_required(VERSION 3.2) @@ -103,7 +103,7 @@ find_package(crypto3 REQUIRED) :::tip[Boost] -`crypto3` already depends on Boost. If an alternative library (that does not depend on Boost) is used, add Boost to the CMake configuration: +`crypto3` already depends on Boost. If an alternative library (that does not depend on Boost) is used, add Boost manually: ```cmake find_package(Boost REQUIRED COMPONENTS system filesystem) @@ -113,13 +113,17 @@ find_package(Boost REQUIRED COMPONENTS system filesystem) Set the circuit source and add a build target: -```cmake +``` +set(SOURCES src/main.cpp) + add_circuit( circuit SOURCES ${SOURCES} LINK_LIBRARIES crypto3::all ${Boost_LIBRARIES} ) + +set(binary_name circuit.ll) ``` , where `circuit` is the desired target name. @@ -127,14 +131,14 @@ add_circuit( Compile the circuit with: ```bash - -cmake -G "Unix Makefiles" -B build -DCMAKE_BUILD_TYPE=Release . -make -C ${ZKLLVM_BUILD:-build} circuit -j$(nproc) +cmake -G "Unix Makefiles" -B ./build -DCMAKE_BUILD_TYPE=Release . +cd build +make -C ./build circuit -j$(nproc) ``` :::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, [**install the `zkllvm` Deb package**](../getting-started/installation#install-zkllvm-binaries). +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). ::: @@ -143,7 +147,7 @@ The `CircuitCompile.cmake` module does not support using custom paths to `clang` 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 +cmake -G "Unix Makefiles" -B ./build -DCMAKE_BUILD_TYPE=Release . -Dcrypto3_DIR=/path/to/crypto3/installation ``` This method is useful if a dependency is installed in a custom location rather than `/usr/local/` or any other location requiring root permissions. @@ -185,12 +189,14 @@ Adding `unroll` [**is mandatory**](../best-practices-limitations/unrolling-loops :::tip -The `"zkllvm"` feature flag is necessary to ensure compatibility with the original Rust compiler. +The `"zkllvm"` feature flag is necessary to ensure compatibility with the original Rust compiler. -If this feature is enabled for a project, 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. +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: diff --git a/zkllvm/use-cases/zk-bridge/eddsa.mdx b/zkllvm/use-cases/zk-bridge/eddsa.mdx index 5746305e..2463166c 100644 --- a/zkllvm/use-cases/zk-bridge/eddsa.mdx +++ b/zkllvm/use-cases/zk-bridge/eddsa.mdx @@ -162,19 +162,6 @@ fn hash(r: EdwardsAffine, pk: EdwardsAffine, m: EdDSAMessageBlockType) -> Fr { ## Public input -The `eddsa_message_block_type` / `EdDSAMessageBlockType` field is expressed as follows: - -```json -{ - "vector": [ - {"field": "0x3992d30ed00000001"}, - {"field": "0x891a63f02533e46"}, - {"field": "0"}, - {"field": "0x100000000000000"} - ] -} -``` - The public input for the circuit could look as follows: ```json diff --git a/zkllvm/use-cases/zk-bridge/hashes.mdx b/zkllvm/use-cases/zk-bridge/hashes.mdx index 10d27e56..4c0d2054 100644 --- a/zkllvm/use-cases/zk-bridge/hashes.mdx +++ b/zkllvm/use-cases/zk-bridge/hashes.mdx @@ -249,19 +249,6 @@ The circuit consists of the following components: ## Public input -The `block_data_type` / `BlockDataType` aggregate type can be expressed as follows: - -```json -{ - { - "struct": [ - {"vector": [{"field": 1}, {"field": 1}]}, - {"vector": [{"field": 1}, {"field": 1}]}, - ] - } -} -``` - The public input for the circuit could look as follows: ```json diff --git a/zkllvm/use-cases/zk-bridge/merkle-tree.mdx b/zkllvm/use-cases/zk-bridge/merkle-tree.mdx index 62a3b92b..560e3cad 100644 --- a/zkllvm/use-cases/zk-bridge/merkle-tree.mdx +++ b/zkllvm/use-cases/zk-bridge/merkle-tree.mdx @@ -8,7 +8,7 @@ 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 -Due to their simplicity, Merkle trees are recommended for creating a simple zkBridge. +Merkle trees are a good choice for a simple zkBridge given their simplicity. ## Prerequisites @@ -64,11 +64,11 @@ fn hash(block1: BlockType, block2: BlockType) -> BlockType { balance(std::array::block_type, 0x10> layer_0_leaves) { std::array::block_type, 0x8> layer_1_leaves; - std::size_t layer_1_size = 0x8; + constexpr std::size_t layer_1_size = layer_1_leaves.size(); std::array::block_type, 0x4> layer_2_leaves; - std::size_t layer_2_size = 0x4; + constexpr std::size_t layer_2_size = layer_2_leaves.size(); std::array::block_type, 0x2> layer_3_leaves; - std::size_t layer_3_size = 0x2; + 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++) { diff --git a/zkllvm/use-cases/zk-bridge/zkbridge.mdx b/zkllvm/use-cases/zk-bridge/zkbridge.mdx index 6f92add5..36626c77 100644 --- a/zkllvm/use-cases/zk-bridge/zkbridge.mdx +++ b/zkllvm/use-cases/zk-bridge/zkbridge.mdx @@ -3,7 +3,7 @@ import TabItem from '@theme/TabItem' # Write an algorithm for state-proof verification -The final step in the creation of a simple zkBridge is the creation of a state-proof verification algorithm. +The final step in making a simple zkBridge is the creation of a state-proof verification algorithm. ## Prerequisites @@ -35,8 +35,9 @@ Read the following tutorials before proceeding further. #![no_main] use std::intrinsics::assigner_sha2_256; + use std::intrinsics::assigner_sha2_512; - use ark_ff::AdditiveGroup; + use ark_curve25519::{EdwardsAffine, Fr}; use ark_pallas::Fq; use unroll::unroll_for_loops; ``` @@ -66,17 +67,36 @@ Read the following tutorials before proceeding further. ```rust - #![no_main] - - use std::intrinsics::assigner_sha2_256; + type BlockType = [Fq; 2]; + type EdDSAMessageBlockType = [Fq; 4]; + + #[repr(C)] + #[derive(Copy, Clone)] + pub struct BlockDataType { + prev_block_hash: BlockType, + data: BlockType, + validators_signatures: [EdDSASignatureType; 4], + validators_keys: [EdwardsAffine; 4], + } - use ark_ff::AdditiveGroup; - use ark_pallas::Fq; - use unroll::unroll_for_loops; + #[repr(C)] + #[derive(Copy, Clone)] + pub struct EdDSASignatureType { + r: EdwardsAffine, + s: Fr, + } ``` +:::info[Rust directives] + +The `#[derive(Copy, Clone)]` makes it so that circuit code does not unintentionally take ownership of variables that are supposed to be changed/used later. + +To learn more about the `#[derive(C)]` directive, [**click here**](../../best-practices-limitations/rust-derive). + +::: + ### Additional functions @@ -112,13 +132,50 @@ Read the following tutorials before proceeding further. ```rust - #![no_main] + 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() + } - use std::intrinsics::assigner_sha2_256; + 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()] + } - use ark_ff::AdditiveGroup; - use ark_pallas::Fq; - use unroll::unroll_for_loops; + 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 + } ``` @@ -128,23 +185,30 @@ Read the following tutorials before proceeding further. ```cpp - [[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); - + #[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 (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); + 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]); } - return is_correct; + is_correct } ``` @@ -245,10 +309,213 @@ Read the following tutorials before proceeding further. #![no_main] use std::intrinsics::assigner_sha2_256; + use std::intrinsics::assigner_sha2_512; - use ark_ff::AdditiveGroup; + use ark_curve25519::{EdwardsAffine, Fr}; use ark_pallas::Fq; use unroll::unroll_for_loops; + + type BlockType = [Fq; 2]; + type EdDSAMessageBlockType = [Fq; 4]; + + #[repr(C)] + #[derive(Copy, Clone)] + pub struct BlockDataType { + prev_block_hash: BlockType, + data: BlockType, + validators_signatures: [EdDSASignatureType; 4], + validators_keys: [EdwardsAffine; 4], + } + + #[repr(C)] + #[derive(Copy, Clone)] + 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"]} + ] + } + ] + } + ] + } + ] + ``` + + + From 033eec0784d2b0f7edb4bdcd97407aebeb80ae0e Mon Sep 17 00:00:00 2001 From: Shukhrat Khannanov Date: Wed, 22 May 2024 08:50:57 +0300 Subject: [PATCH 05/11] addressed comments in primer --- zkllvm/use-cases/primer.mdx | 35 +++++------------------------------ 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/zkllvm/use-cases/primer.mdx b/zkllvm/use-cases/primer.mdx index 6fc186a9..b06440be 100644 --- a/zkllvm/use-cases/primer.mdx +++ b/zkllvm/use-cases/primer.mdx @@ -37,20 +37,6 @@ Switch to the working directory: mkdir new_circuit && cd new_circuit ``` -Create a directory for CMake modules: - -```bash -mkdir cmake && cd cmake -``` - -Add the [**`CircuitCompile.cmake` module from the zkLLVM repository**](https://github.com/NilFoundation/zkLLVM/blob/master/cmake/CircuitCompile.cmake) to the `./new_circuit/cmake` directory: - -``` -new_circuit --- cmake - -- CircuitCompile.cmake -``` - Create the circuit: ```bash @@ -88,7 +74,7 @@ project(hashes_circuit DESCRIPTION "Tutorial circuit with hashes" LANGUAGES CXX) -list(APPEND CMAKE_MODULE_PATH "path/to/cmake") +list(APPEND CMAKE_MODULE_PATH "/usr/lib/zkllvm/share/zkllvm") include(CircuitCompile) ``` @@ -96,21 +82,11 @@ include(CircuitCompile) Add the required library to the project configuration: ```cmake -set(crypto3_DIR "path/to/crypto3_headers") +set(crypto3_DIR "path/to/crypto3/installation") find_package(crypto3 REQUIRED) ``` -:::tip[Boost] - -`crypto3` already depends on Boost. If an alternative library (that does not depend on Boost) is used, add Boost manually: - -```cmake -find_package(Boost REQUIRED COMPONENTS system filesystem) -``` - -::: - Set the circuit source and add a build target: ``` @@ -120,10 +96,8 @@ add_circuit( circuit SOURCES ${SOURCES} LINK_LIBRARIES crypto3::all - ${Boost_LIBRARIES} ) -set(binary_name circuit.ll) ``` , where `circuit` is the desired target name. @@ -132,10 +106,11 @@ Compile the circuit with: ```bash cmake -G "Unix Makefiles" -B ./build -DCMAKE_BUILD_TYPE=Release . -cd build 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). @@ -150,7 +125,7 @@ Instead of specifying `crypto3_DIR` in `CMakeLists.txt`, it is possible to speci cmake -G "Unix Makefiles" -B ./build -DCMAKE_BUILD_TYPE=Release . -Dcrypto3_DIR=/path/to/crypto3/installation ``` -This method is useful if a dependency is installed in a custom location rather than `/usr/local/` or any other location requiring root permissions. +This method is useful if `crypto3_DIR` is not a fixed path and cannot be 'hardcoded' in the CMake configuration file. ::: From 176501104a1f79772b2cdd139e6f2e7b22bdc041 Mon Sep 17 00:00:00 2001 From: Shukhrat Khannanov Date: Wed, 22 May 2024 10:30:19 +0300 Subject: [PATCH 06/11] addressed comments in hashes --- zkllvm/use-cases/zk-bridge/hashes.mdx | 265 +++++++++++++++----------- 1 file changed, 152 insertions(+), 113 deletions(-) diff --git a/zkllvm/use-cases/zk-bridge/hashes.mdx b/zkllvm/use-cases/zk-bridge/hashes.mdx index 4c0d2054..0caa6316 100644 --- a/zkllvm/use-cases/zk-bridge/hashes.mdx +++ b/zkllvm/use-cases/zk-bridge/hashes.mdx @@ -3,7 +3,7 @@ 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. +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 @@ -100,26 +100,18 @@ The circuit consists of the following components: [[circuit]] bool verify_protocol_state_proof ( typename sha2<256>::block_type last_confirmed_block_hash, std::array unconfirmed_blocks) { - for (int i = 0; i < 2; i++) { - 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; - } - } + bool res = true; + if (!is_same(unconfirmed_blocks[0].prev_block_hash, last_confirmed_block_hash)) { + return false; } - return true; + 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; } ``` @@ -128,23 +120,24 @@ The circuit consists of the following components: #[circuit] #[unroll_for_loops] fn verify_protocol_state_proof( - last_confirmed_block_hash: Fq, + last_confirmed_block_hash: BlockType, unconfirmed_blocks: [BlockDataType; 2], ) -> bool { - for i in 0..2 { - if i == 0 { - if is_same(unconfirmed_blocks[i][0], last_confirmed_block_hash) { - false - } - } else { - let evaluated_block_hash = - hash(unconfirmed_blocks[i - 1][0], unconfirmed_blocks[i - 1][1]); - if is_same(unconfirmed_blocks[i][0], evaluated_block_hash) { - false - } - } - true + 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 } ``` @@ -176,26 +169,18 @@ The circuit consists of the following components: [[circuit]] bool verify_protocol_state_proof ( typename sha2<256>::block_type last_confirmed_block_hash, std::array unconfirmed_blocks) { - for (int i = 0; i < 2; i++) { - 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; - } - } + bool res = true; + if (!is_same(unconfirmed_blocks[0].prev_block_hash, last_confirmed_block_hash)) { + return false; } - return true; + 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; } ``` @@ -205,6 +190,7 @@ The circuit consists of the following components: use ark_pallas::Fq; use std::intrinsics::assigner_sha2_256; + use unroll::unroll_for_loops; type BlockType = [Fq; 2]; @@ -225,23 +211,24 @@ The circuit consists of the following components: #[circuit] #[unroll_for_loops] fn verify_protocol_state_proof( - last_confirmed_block_hash: Fq, + last_confirmed_block_hash: BlockType, unconfirmed_blocks: [BlockDataType; 2], ) -> bool { - for i in 0..2 { - if i == 0 { - if is_same(unconfirmed_blocks[i][0], last_confirmed_block_hash) { - false - } - } else { - let evaluated_block_hash = - hash(unconfirmed_blocks[i - 1][0], unconfirmed_blocks[i - 1][1]); - if is_same(unconfirmed_blocks[i][0], evaluated_block_hash) { - false - } - } - true + 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 } ``` @@ -251,49 +238,101 @@ The circuit consists of the following components: 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"} - ] - } - ] - } - ] - } -] -``` \ No newline at end of file + + + ```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"} + ] + } + ] + } + ] + } + ] + ``` + + From 46ac51e0e35c7ac6268976d1ba48e6c02341a3c8 Mon Sep 17 00:00:00 2001 From: Shukhrat Khannanov Date: Wed, 22 May 2024 10:54:18 +0300 Subject: [PATCH 07/11] addressed comments in zkbridge and added logical AND to hashes --- zkllvm/use-cases/zk-bridge/hashes.mdx | 2 +- zkllvm/use-cases/zk-bridge/zkbridge.mdx | 32 ++++++++++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/zkllvm/use-cases/zk-bridge/hashes.mdx b/zkllvm/use-cases/zk-bridge/hashes.mdx index 0caa6316..e41555df 100644 --- a/zkllvm/use-cases/zk-bridge/hashes.mdx +++ b/zkllvm/use-cases/zk-bridge/hashes.mdx @@ -226,7 +226,7 @@ The circuit consists of the following components: 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 = res && is_same(unconfirmed_blocks[i].prev_block_hash, evaluated_block_hash); } res } diff --git a/zkllvm/use-cases/zk-bridge/zkbridge.mdx b/zkllvm/use-cases/zk-bridge/zkbridge.mdx index 36626c77..cc36229a 100644 --- a/zkllvm/use-cases/zk-bridge/zkbridge.mdx +++ b/zkllvm/use-cases/zk-bridge/zkbridge.mdx @@ -91,12 +91,13 @@ Read the following tutorials before proceeding further. :::info[Rust directives] -The `#[derive(Copy, Clone)]` makes it so that circuit code does not unintentionally take ownership of variables that are supposed to be changed/used later. +The `#[derive(Copy, Clone)]` makes it so functions do not take ownership of the variables belonging to custom structs if these variables are passed by value. To learn more about the `#[derive(C)]` directive, [**click here**](../../best-practices-limitations/rust-derive). ::: + ### Additional functions @@ -214,13 +215,31 @@ To learn more about the `#[derive(C)]` directive, [**click here**](../../best-pr ```rust - #![no_main] + #[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]); - use std::intrinsics::assigner_sha2_256; + for i in 1..2 { + let evaluated_block_hash: BlockType = hash_256( + unconfirmed_blocks[i - 1].prev_block_hash, + unconfirmed_blocks[i - 1].data, + ); - use ark_ff::AdditiveGroup; - use ark_pallas::Fq; - use unroll::unroll_for_loops; + 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 + } ``` @@ -408,6 +427,7 @@ To learn more about the `#[derive(C)]` directive, [**click here**](../../best-pr + ## Public input The public input for the circuit could look as follows: From 0fc4c846da08079e40e40cc460c7a1dc584c0f20 Mon Sep 17 00:00:00 2001 From: Shukhrat Khannanov Date: Wed, 22 May 2024 11:08:05 +0300 Subject: [PATCH 08/11] addressed comments in Rust structs --- .../rust-derive.mdx | 33 +++++-------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/zkllvm/best-practices-limitations/rust-derive.mdx b/zkllvm/best-practices-limitations/rust-derive.mdx index 48752257..47ff6aec 100644 --- a/zkllvm/best-practices-limitations/rust-derive.mdx +++ b/zkllvm/best-practices-limitations/rust-derive.mdx @@ -2,27 +2,19 @@ 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://users.rust-lang.org/t/disable-struct-reordering-optimization-when-emitting-llvm-bitcode/74951). Consider the following example: +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_curve25519::{EdwardsAffine, Fr}; use ark_pallas::Fq; type BlockType = [Fq; 2]; -type EdDSAMessageBlockType = [Fq; 4]; #[derive(Copy, Clone)] pub struct BlockDataType { prev_block_hash: BlockType, data: BlockType, - validators_signatures: [EdDSASignatureType; 4], - validators_keys: [EdwardsAffine; 4], -} - -#[derive(Copy, Clone)] -pub struct EdDSASignatureType { - r: EdwardsAffine, - s: Fr, + validators_signature: i32, + validators_key: u64, } ``` @@ -37,31 +29,22 @@ The public input representation of the `BlockDataType` struct would look as foll "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}]} - ] + "int": 1 }, { - "array": [ - {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec", "0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]}, - {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec", "0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]}, - {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec", "0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]}, - {"curve": ["0x4f043d481c8f09de646b1aa05de7ebfab126fc8bbb74f42532378c4dec6e76ec", "0x58719b60b26bd8b8b76de1a886ed82aa11692b4dc5494fe96d5b31f1c63f36a8"]} - ] + "int": 1 } ] ``` -When compiling the `BlockDataType` struct, `rustc` will reorder its fields. +When compiling the `BlockDataType` struct, `rustc` may reorder its fields. -When `assigner` is called on a circuit with this struct, the circuit IR will conflict with the public input as the field order in the IR and the public input file will no longer match. +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 +#[repr(C)] #[derive(Copy, Clone)] pub struct BlockDataType { prev_block_hash: BlockType, From e86539cbd9e11ad6f6a59010e787664dada38360 Mon Sep 17 00:00:00 2001 From: Shukhrat Khannanov Date: Wed, 22 May 2024 11:12:27 +0300 Subject: [PATCH 09/11] example fix --- .../best-practices-limitations/rust-derive.mdx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/zkllvm/best-practices-limitations/rust-derive.mdx b/zkllvm/best-practices-limitations/rust-derive.mdx index 47ff6aec..f1c78e72 100644 --- a/zkllvm/best-practices-limitations/rust-derive.mdx +++ b/zkllvm/best-practices-limitations/rust-derive.mdx @@ -44,20 +44,18 @@ When `assigner` is called on a circuit with this struct, the circuit IR would co To avoid this problem, use the `#[repr(C)]` directive: ```rust + +use ark_pallas::Fq; + +type BlockType = [Fq; 2]; + #[repr(C)] #[derive(Copy, Clone)] pub struct BlockDataType { prev_block_hash: BlockType, data: BlockType, - validators_signatures: [EdDSASignatureType; 4], - validators_keys: [EdwardsAffine; 4], -} - -#[repr(C)] -#[derive(Copy, Clone)] -pub struct EdDSASignatureType { - r: EdwardsAffine, - s: Fr, + validators_signature: i32, + validators_key: u64, } ``` From e0cb199708dacb77bb37473fcf831eaf1c936375 Mon Sep 17 00:00:00 2001 From: Shukhrat Khannanov Date: Thu, 23 May 2024 08:50:52 +0300 Subject: [PATCH 10/11] changed final Rust circuit to use refs in most functions, removed some directives --- zkllvm/use-cases/zk-bridge/zkbridge.mdx | 99 +++++++++++-------------- 1 file changed, 42 insertions(+), 57 deletions(-) diff --git a/zkllvm/use-cases/zk-bridge/zkbridge.mdx b/zkllvm/use-cases/zk-bridge/zkbridge.mdx index cc36229a..13abfcf4 100644 --- a/zkllvm/use-cases/zk-bridge/zkbridge.mdx +++ b/zkllvm/use-cases/zk-bridge/zkbridge.mdx @@ -71,7 +71,6 @@ Read the following tutorials before proceeding further. type EdDSAMessageBlockType = [Fq; 4]; #[repr(C)] - #[derive(Copy, Clone)] pub struct BlockDataType { prev_block_hash: BlockType, data: BlockType, @@ -80,7 +79,6 @@ Read the following tutorials before proceeding further. } #[repr(C)] - #[derive(Copy, Clone)] pub struct EdDSASignatureType { r: EdwardsAffine, s: Fr, @@ -91,8 +89,6 @@ Read the following tutorials before proceeding further. :::info[Rust directives] -The `#[derive(Copy, Clone)]` makes it so functions do not take ownership of the variables belonging to custom structs if these variables are passed by value. - To learn more about the `#[derive(C)]` directive, [**click here**](../../best-practices-limitations/rust-derive). ::: @@ -133,22 +129,22 @@ To learn more about the `#[derive(C)]` directive, [**click here**](../../best-pr ```rust - pub fn hash_512(r: EdwardsAffine, pk: EdwardsAffine, m: EdDSAMessageBlockType) -> 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 { + 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, + input: &EdDSASignatureType, + pk: &EdwardsAffine, + m: &EdDSAMessageBlockType, ) -> bool { let b = EdwardsAffine::one(); - let k = hash_512(input.r, pk, m); + let k = hash_512(&input.r, pk, m); b * input.s == input.r + (pk * k) } @@ -157,7 +153,7 @@ To learn more about the `#[derive(C)]` directive, [**click here**](../../best-pr } #[unroll_for_loops] - pub fn verify_signature(unconfirmed_block: BlockDataType) -> bool { + pub fn verify_signature(unconfirmed_block: &BlockDataType) -> bool { let mut is_verified: bool = true; let message: EdDSAMessageBlockType = [ unconfirmed_block.prev_block_hash[0], @@ -169,9 +165,9 @@ To learn more about the `#[derive(C)]` directive, [**click here**](../../best-pr for i in 0..4 { is_verified = is_verified && verify_eddsa_signature( - unconfirmed_block.validators_signatures[i], - unconfirmed_block.validators_keys[i], - message, + &unconfirmed_block.validators_signatures[i], + &unconfirmed_block.validators_keys[i], + &message, ); } @@ -186,30 +182,21 @@ To learn more about the `#[derive(C)]` directive, [**click here**](../../best-pr ```cpp - #[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]); + [[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; } - - is_correct + 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; } ``` @@ -225,17 +212,17 @@ To learn more about the `#[derive(C)]` directive, [**click here**](../../best-pr unconfirmed_blocks[0].prev_block_hash, last_confirmed_block_hash, ); - is_correct = is_correct && verify_signature(unconfirmed_blocks[0]); + 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, + &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 = is_correct && verify_signature(&unconfirmed_blocks[i]); } is_correct @@ -338,7 +325,6 @@ To learn more about the `#[derive(C)]` directive, [**click here**](../../best-pr type EdDSAMessageBlockType = [Fq; 4]; #[repr(C)] - #[derive(Copy, Clone)] pub struct BlockDataType { prev_block_hash: BlockType, data: BlockType, @@ -347,28 +333,27 @@ To learn more about the `#[derive(C)]` directive, [**click here**](../../best-pr } #[repr(C)] - #[derive(Copy, Clone)] pub struct EdDSASignatureType { r: EdwardsAffine, s: Fr, } - pub fn hash_512(r: EdwardsAffine, pk: EdwardsAffine, m: EdDSAMessageBlockType) -> 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 { + 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, + input: &EdDSASignatureType, + pk: &EdwardsAffine, + m: &EdDSAMessageBlockType, ) -> bool { let b = EdwardsAffine::one(); - let k = hash_512(input.r, pk, m); + let k = hash_512(&input.r, pk, m); b * input.s == input.r + (pk * k) } @@ -377,7 +362,7 @@ To learn more about the `#[derive(C)]` directive, [**click here**](../../best-pr } #[unroll_for_loops] - pub fn verify_signature(unconfirmed_block: BlockDataType) -> bool { + pub fn verify_signature(unconfirmed_block: &BlockDataType) -> bool { let mut is_verified: bool = true; let message: EdDSAMessageBlockType = [ unconfirmed_block.prev_block_hash[0], @@ -389,9 +374,9 @@ To learn more about the `#[derive(C)]` directive, [**click here**](../../best-pr for i in 0..4 { is_verified = is_verified && verify_eddsa_signature( - unconfirmed_block.validators_signatures[i], - unconfirmed_block.validators_keys[i], - message, + &unconfirmed_block.validators_signatures[i], + &unconfirmed_block.validators_keys[i], + &message, ); } @@ -408,17 +393,17 @@ To learn more about the `#[derive(C)]` directive, [**click here**](../../best-pr unconfirmed_blocks[0].prev_block_hash, last_confirmed_block_hash, ); - is_correct = is_correct && verify_signature(unconfirmed_blocks[0]); + 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, + &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 = is_correct && verify_signature(&unconfirmed_blocks[i]); } is_correct From 5b81f1df6947d93b527d6ff1fb5ad1afc50dbf78 Mon Sep 17 00:00:00 2001 From: Shukhrat Khannanov Date: Thu, 23 May 2024 12:15:14 +0300 Subject: [PATCH 11/11] removed unnecessary directives in Rust structs and enums --- zkllvm/best-practices-limitations/rust-derive.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/zkllvm/best-practices-limitations/rust-derive.mdx b/zkllvm/best-practices-limitations/rust-derive.mdx index f1c78e72..af7b5f3e 100644 --- a/zkllvm/best-practices-limitations/rust-derive.mdx +++ b/zkllvm/best-practices-limitations/rust-derive.mdx @@ -9,7 +9,6 @@ use ark_pallas::Fq; type BlockType = [Fq; 2]; -#[derive(Copy, Clone)] pub struct BlockDataType { prev_block_hash: BlockType, data: BlockType, @@ -50,7 +49,6 @@ use ark_pallas::Fq; type BlockType = [Fq; 2]; #[repr(C)] -#[derive(Copy, Clone)] pub struct BlockDataType { prev_block_hash: BlockType, data: BlockType,