Risc Zero

Risc Zero

изображение_2022-05-31_073801804.png

What is Risc0?

RISC Zero (Risc0) is the computing platform that enables any developer to build zero-knowledge proofs that can be executed and verified on any modern computer in the languages they are most familiar with.

In simple terms - Risc0 can help you make proofs about something without saying what it is, allowing you to maintain privacy

If you're interested to read more about RiscZero, check out their website at www.risczero.com

As an example, risc0 developers show a prototype of a battleship game written using Rust and risc0. The first important security property in a battleship is that the players can trust that their opponents have set up the game boards fairly and correctly, for example, they've placed all of their ships on the board. Therefore, we check the correct placement of the ships, but as a confirmation, we send just a receipt of the opposing board state which doesn't contain the board itself! This is important because this allows proving that players' boards are set up fairly without sharing the contents.

Once the game is set up fairly the next property to ensure is that turns are processed fairly. For processing fairly they use the same instruments.

There you can find a Battleship example parsing click there

Let's work with Risc0

Let's analyze the structure of the example of a simple risc0 project.

The GitHub repository with full example code:

Problem: Not every person can deliver packages - only deliveryman. Not every package can be delivered - only allowed.

Solution: Verification a person's profession and package without providing their information.

изображение_2022-06-01_141508270.png

So, now we will work with every package separately.

core

изображение_2022-05-31_140319510.png

core/Cargo.toml implementations:

serde = { version = "1.0", default-features = false }

This dependency will allow us to import crate serde which helps us make deserialization and serialization. Study more about serde crate click there.

Also, I have changed the package core name to checker-core in order not to get confused when declaring a new dependency, when we refer to core package.

изображение_2022-05-31_142448973.png

core/src/lib.rs code:

#![no_std]
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
pub struct Person {
    pub status: PersonStatus,
}

#[derive(Deserialize, Serialize, PartialEq)]
pub enum PersonStatus {
    Deliveryman,
    Unemployed,
    Seller,
    Programmer,
    Driver,
    Economist,
}

#[derive(Deserialize, Serialize)]
pub struct Package {
    pub package_type: PackageType,
}

#[derive(Deserialize, Serialize, PartialEq)]
pub enum PackageType {
    Alcohol,
    Forbidden,
    Gadget,
    Jewelery,
    SportEquipment,
    Food,
}

In this file, we created Package, Person structures and PersonStatus, PackageType enums. Person structure consists of only the status field which defines a person's occupation. There are some variants of occupations in PersonStatus enum. Our Person can deliver packages only if he works as a deliveryman. Package structure consists of only the package_type field which defines what is inside the package. There are some variants of filling in PackageType enum. Our package can be delivered only if it has not Forbidden PackageType.

methods

methods/guest/Cargo.toml:

изображение_2022-05-31_142654276.png

checker-core dependency - core package.

risc0_zkvm_guest dependency - for reading input data and committing results.

risc0-build dependency - contains helper functions to assist with building guest side code, and using it in host side code.

methods/guest/src/bin/checker.rs:

#![no_main]
#![no_std]
use risc0_zkvm_guest::env;
use checker_core::{Person, PersonStatus}; //import Person structure and PersonStatus enum
risc0_zkvm_guest::entry!(main);

pub fn main() {
    let a:Person = env::read();// reading input data;
    let c:bool = a.status == PersonStatus::Deliveryman; // if a person have another status he/she doesn't have the skills for this work, so he can't deliver packages
    env::commit(&c); // committing result
}

This file contains code that checks if a person is a delivery person. risc0_zkvm_guest::env provides functions for reading(env::read()) inputs we want to proof and commiting(env::commit()) them.

methods/guest/src/bin/package.rs:

#![no_main]
#![no_std]
use risc0_zkvm_guest::env;
use checker_core::{Package, PackageType}; //import Package structure and PackageType enum
risc0_zkvm_guest::entry!(main);

pub fn main() {
    let a:Package = env::read();// reading input data;
    let c:bool = a.package_type != PackageType::Forbidden; // allow to deliver only allowed packages
    env::commit(&c); // committing result
}

In this file, the code have the similar functionality as checker.rs file.

methods/guest/build.rs:

fn main() {
    risc0_build::link();
}

This file builds a crate for the RISC-V guest target.

methods/build.rs:

fn main() {
    risc0_build::embed_methods();
}

Embeds methods built for RISC-V for use by host-side dependencies.

methods/lib.rs:

fn main() {
    include!(concat!(env!("OUT_DIR"), "/methods.rs"));
}

Parses a file which locates in target/debug/build/methods-21104d46c0d6cfb5/out made by the risc0 build system. This file consists of public constants CHECKER_PATH and PACKAGE_PATH referring to given methods and public constants CHECKER_ID and PACKAGE_ID referring to identifiers of these methods.

risc0-build-methods

risc0-build-methods/Cargo.toml

изображение_2022-05-31_151750035.png

risc0-build-methods/src/main.rs

fn main() {
    risc0_build::build_all();
}

Builds all RISC-V ELF binaries specified by risc0 methods metadata.

starter

starter/Cargo.toml

[dependencies]
methods = { path = "../methods" } //methods package dependendency
checker-core = { path = "../core" } //core package dependendency
risc0-zkvm-host = "0.7" // for generating a Receipt by executing a given method in a ZKVM.
risc0-zkvm-serde = "0.7" //deserialization and serialization dependency.
serde = "1.0" // deserialization and serialization dependency.
tempfile = "3.3" //for creating a new temporary directory.

starter/src/main.rs

use std::fs;
use methods::{CHECKER_ID, CHECKER_PATH, PACKAGE_PATH, PACKAGE_ID};
use checker_core::{Person, PersonStatus, Package, PackageType};
use risc0_zkvm_host::Prover;
use risc0_zkvm_serde::{from_slice, to_vec};
use tempfile::tempdir;

fn main() {
    let a = Person { status: PersonStatus::Deliveryman };
    let temp_dir = tempdir().unwrap();
    let mut id_path = temp_dir
        .path()
        .join("checker.id")
        .to_str()
        .unwrap()
        .to_string();
    fs::write(&id_path, CHECKER_ID).unwrap();

    let mut prover = Prover::new(&CHECKER_PATH, &id_path).unwrap();
    prover.add_input(to_vec(&a).unwrap().as_slice()).unwrap();
    let receipt = prover.run().unwrap();
    println!("Checking a person's profession:");

    let c: bool = from_slice(&receipt.get_journal_vec().unwrap()).unwrap();
    if c {
        println!("This person can deliver packages.");
    } else {
        println!("This person can't deliver packages.");
    }
    receipt.verify(&id_path).unwrap();

    let b: Package = Package { package_type: PackageType::Forbidden };

    id_path = temp_dir
        .path()
        .join("package.id")
        .to_str()
        .unwrap()
        .to_string();
    fs::write(&id_path, PACKAGE_ID).unwrap();

    let mut prover = Prover::new(&PACKAGE_PATH, &id_path).unwrap();
    prover.add_input(to_vec(&b).unwrap().as_slice()).unwrap();
    let receipt = prover.run().unwrap();

    println!("Checking a package:");

    let c: bool = from_slice(&receipt.get_journal_vec().unwrap()).unwrap();
    if c {
        println!("The delivery service can deliver this package.");
    } else {
        println!("The delivery service can not deliver this package.");
    }
    receipt.verify(&id_path).unwrap();
}

We have created a new Person instance who is a deliveryman, so he/she can deliver the package. But if Person isn't a deliveryman - he/she can't.

Then we create a new Prover and add input for checking Person.

As a result, we receive a new Receipt and get from it a bool instance: true - Person can deliver the package, false - Person can't.

Then we have created a new Package instance, but inside we have something forbidden, so the delivery service can't deliver this package.

Then we create a new Prover and add input for checking Package.

As a result, we receive a new Receipt and get from it a bool instance: true - the delivery service can deliver this package., false - package is forbidden.

use methods::{CHECKER_ID, CHECKER_PATH, PACKAGE_PATH, PACKAGE_ID};

Constants CHECKER_ID, CHECKER_PATH, PACKAGE_PATH and PACKAGE_ID are generated by the risc0 build system automatically and locate in target/debug/build/methods-21104d46c0d6cfb5/out/methods.rs . CHECKER_PATH and PACKAGE_PATH refer to given methods and CHECKER_ID and PACKAGE_ID refer to identifiers of these methods.

изображение_2022-06-01_150216807.png

Let's run our code!

Make sure you are using the nightly version of rust:

rustup override set nightly

What is the nightly version and how to install it?

Run:

cargo run --bin risc0-build-methods && cargo run --release --bin starter

Result:

изображение_2022-06-01_144419926.png