paint-brush
Creating Your First ZK Project Using Risc0: A Guide for Beginnersby@luffysama
733 reads
733 reads

Creating Your First ZK Project Using Risc0: A Guide for Beginners

by Luffy SamaOctober 5th, 2023
Read on Terminal Reader
Read this story w/o Javascript

Too Long; Didn't Read

By the end, you'll have practical experience with zero-knowledge proofs in Risc0. No advanced math or cryptography background required. We'll focus on the coding essentials to start building real-world solutions. This hands-on introduction aims to make powerful privacy tech understandable for any developer.
featured image - Creating Your First ZK Project Using Risc0: A Guide for Beginners
Luffy Sama HackerNoon profile picture

Introduction:

Zero-knowledge proofs enable private, secure transactions. With zkSNARKs and zkSTARKs, a prover can prove possession of some information to a verifier without revealing the actual data.


This has huge potential for anonymity and confidentiality. But zkSTARKs and zkSNARKs are complex. Risc0 makes them more accessible. Let’s see it’s very basic implementation:


This tutorial will cover:

  • Installation of Risc0

  • Writing your first zero-knowledge proof program using rust


Prerequisites:

  • Copy pasting shortcuts

  • Installations required:

    • Rust language
    • Cargo crate
  • Some basic coding knowledge in rust

  • Code Editor (VSCODE if possible)


By the end, you'll have practical experience with zero-knowledge proofs in Risc0. No advanced math or cryptography background required.


We'll focus on the coding essentials to start building real-world solutions. This hands-on introduction aims to make powerful privacy tech understandable for any developer.

Installation:

  • (For MacOS) To install rust and cargo, you can run the below command in the terminal:

    curl [https://sh.rustup.rs](https://sh.rustup.rs/) -sSf | sh


  • To install Risc0, you run the below command after installation of rust and restart the terminal:

    cargo install cargo-risczero


  • For the above commands to build successfully, you will need to have installed the required dependencies. brew install openssl brew install pkgconf


  • Next, we'll need to install the risc0 toolchain with: cargo risczero install


That’s all we need. So, let’s head to the code editor.

Writing Some Magical Lines:

Now that we are done with the installation you can read and follow along these abra kadabra 🪄 steps:


  1. Open the code editor and head to the location where you want to create your project in the editor’s terminal.


  2. Create a new instance of the project using the below command in the terminal: cargo risczero new multiply.


    The command creates a basic template for us called multiply

  3. You should be seeing a folder created called multiply. cd into it. cd multiply


  4. The folder structure is very simple.


    1. We have a host folder and a methods folder.


    2. The host folder contains the host program which we call the guest program. It also has the ability to verify if you want.


    3. The methods folder contains the guest program which contains the portion of the zkvm application that gets proven. It receives the input params from the host, then based on the logic, it generates results, commits them to the journal, and sends them to the host as a receipt.


    4. The rest of the files will be explained on the go as required.

  5. Let's start with the guest program.


    1. Let’s change the name of the files main.rs —> multiply.rs.

    2. Create a folder named bin in src folder and move mutiply.rs to it. Your folder structure should look something like this:

    3. Open Cargo.toml and change the update to the name = "method_name” —> name = "multiply”.

    4. Add the below code in Cargo.toml.

      [[bin]]
      name = "multiply"
      path = "src/bin/multiply.rs"
      
    5. So your final Cargo.toml will look like this:

    6. Now, open multiply.rs . Here we will edit the main function. This is the function that will be executed in zkvm.


      1. In the below code, we are taking the input from the host program. Then we are making sure that the input is not trivial factors that is 1. Then we calculate the product and finally commit it back to the host of the program.

            // We will get the values for these variables from host program
            let a:u64 = env::read();
            let b:u64 = env::read();
        
            // To avoid trivial factors like multiplication by 1
            if a == 1 || b == 1 {
                panic!("Trivial factors !!")  // The panic! macro in Rust is used to intentionally crash a program when an unrecoverable error occurs
            }
        
            // Caculate the product of the two numbers
            let product = a.checked_mul(b).expect("Integer Overflow");
        
            // Commit back the output to the host to save it as receipt
            env::commit(&product);
        
      2. After the above changes, your multiply.rs should look like this.

    7. There is one more final change in the Cargo.toml of methods folder.

    8. Open and update the value of name = "multiply-methods”.


    9. Your final Cargo.toml will look like below.

    10. Our work here is done.

    11. Now, let’s go to the host program.

      1. Your host folder must be looking like this now.

      2. We want to split the main.rs into two files that are prover.rs and verify.rs.


      3. Create a new folder under src and name it as bin.


      4. Remove main.rs. Create files and name them as verify.rs & prove.rs.


      5. Your folder structure should look something like this now.

      6. Open prove.rs, and let’s start coding:


      7. Add the below code. These are the imports that we will require.

        use multiply_methods::MULTIPLY_ELF; // It is a binary file of multiply_method
        use risc0_zkvm::{
            default_prover,
            serde::{from_slice, to_vec},
            ExecutorEnv,
          };
        


      8. Let’s make changes to the main function.

        fn main() {
        
        		// Declaring our secret input params
            let a: u64 = 17;
            let b: u64 = 23;
        
            // First, we construct an executor environment
            let env = ExecutorEnv::builder()
                .add_input(&to_vec(&a).unwrap())  // Passing the input params to environment so it can be used by gues proggram
                .add_input(&to_vec(&b).unwrap())
                .build()
                .unwrap();
        
            // Obtain the default prover.
            let prover = default_prover();
        
            // Produce a receipt by proving the specified ELF binary.
            let receipt = prover.prove_elf(env, MULTIPLY_ELF).unwrap();
        
            // Extract journal of receipt (i.e. output c, where c = a * b)
            let c: u64 = from_slice(&receipt.journal).unwrap();
        
            // Print an assertion
            println!("Hello, world! I know the factors of {}, and I can prove it!", c);
        
        		// Let's serialize the receipt so we can save it to an file for verifier program to verify.
            let serialized = bincode::serialize(&receipt).unwrap();
        
        		// Writing the serialized contect to receipt.bin file
            let _saved_file = match std::fs::write("./receipt.bin", serialized){
                 Ok(()) => println!("Receipt saved and serialized as receipt.bin"),
                 Err(_) => println!("Something went wrong !!"),
            };
        
        }
        


      9. Your final prove.rs should look like this.

      10. Let’s open and add code to our verify.rs.


      11. Here, we will import the guest program image ID and some basic imports.

        use multiply_methods::MULTIPLY_ID;
        use risc0_zkvm::Receipt;
        


      12. Let’s make changes to the main function.

        fn main(){
            // Let's impor the receipt that was generated by prove
            let receipt_path ="./receipt.bin".to_string();
            let receipt_file = std::fs::read(receipt_path).unwrap();
           
            // As we has serialized the receipt we need to desrialize it
            let receipt = bincode::deserialize::<Receipt>(&receipt_file).unwrap();
            
        	// Let's verify if the receipt that was generated was not created tampered with
            let _verification = match receipt.verify(MULTIPLY_ID){
                Ok(()) => println!("Proof is Valid"),
                Err(_) => println!("Something went wrong !!"),
            };
        }
        


      13. Your final verify.rs should look something like this.

      14. I promise these are the final changes. Now, we are almost done.


      15. Open the Cargo.toml in host folder, and make the below changes under dependencies.

        multiply-methods = { path = "../methods" }
        
      16. Your Cargo.toml will look like this.

    12. It’s finally time to see if our code works.


    13. Fire up your console in the root directory of the project, and run the below command in console cargo run --release --bin prove


    14. This command will generate the proof and receipt for the verifier.


      When running first time it will take a lot of time. So don’t worry grab a coffee until it completes.


    15. Once this is done, if you want, you can verify the receipt that you have generated. For that, run this command cargo run --release --bin verify


      When running first time it will take a lot of time. So don’t worry grab a coffee until it completes.


    Congratulations !!! on writing your first ZK app using Risc0. If you need any help, please drop me a comment, and I will get back to you.