This commit is contained in:
Sergio Chouhy 2025-07-11 18:47:03 -03:00
parent e204db5cd9
commit c74fa88380
16 changed files with 305 additions and 1 deletions

5
.gitignore vendored
View File

@ -1,2 +1,5 @@
target
Cargo.lock
Cargo.lock
risc0-selective-privacy-poc/target
risc0-selective-privacy-poc/Cargo.lock
risc0-selective-privacy-poc/methods/guest/Cargo.lock

View File

@ -0,0 +1,17 @@
[package]
name = "tuki"
version = "0.12.0"
edition = "2021"
[dependencies]
risc0-zkvm = "2.2"
toy-example-core = {path = "core"}
transfer-methods = {path = "transfer_methods"}
outer-methods = {path = "outer_methods"}
serde = "1.0"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
[features]
cuda = ["risc0-zkvm/cuda"]
default = []
prove = ["risc0-zkvm/prove"]

View File

@ -0,0 +1,8 @@
[package]
name = "toy-example-core"
version = "0.12.0"
edition = "2021"
[dependencies]
risc0-zkvm = "2.0.2"
serde = { version = "1.0", default-features = false }

View File

@ -0,0 +1,44 @@
#![cfg_attr(not(test), no_std)]
use serde::{Serialize, Deserialize};
use risc0_zkvm::{sha::{Impl, Sha256}, serde::to_vec};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Account {
pub address: [u32; 8],
pub balance: u128,
pub nonce: [u32; 8]
}
impl Account {
/// Creates a new account with address = hash(private_key) and balance = 0
pub fn new(private_key: [u32; 8], nonce: [u32; 8]) -> Self {
let address = hash(&private_key);
Self { address, balance: 0, nonce }
}
/// Returns Hash(Account)
pub fn commitment(&self) -> [u32; 8] {
hash(&to_vec(&self).unwrap())
}
}
pub fn hash(bytes: &[u32]) -> [u32; 8] {
Impl::hash_words(&bytes).as_words().try_into().unwrap()
}
pub fn is_in_commitment_tree(_commitment: [u32; 8], _tree_root: [u32; 8]) -> bool {
// Dummy implementation
true
}
/// Returns Hash(Commitment || private_key)
pub fn compute_nullifier(commitment: [u32; 8], private_key: [u32; 8]) -> [u32; 8] {
let mut bytes_to_hash = [0; 16];
bytes_to_hash[..8].copy_from_slice(&commitment);
bytes_to_hash[8..].copy_from_slice(&private_key);
hash(&bytes_to_hash)
}

View File

@ -0,0 +1,10 @@
[package]
name = "outer-methods"
version = "0.1.0"
edition = "2021"
[build-dependencies]
risc0-build = { version = "2.2" }
[package.metadata.risc0]
methods = ["guest"]

View File

@ -0,0 +1,3 @@
fn main() {
risc0_build::embed_methods();
}

View File

@ -0,0 +1,11 @@
[package]
name = "toy_example"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
risc0-zkvm = { version = "2.2.0", default-features = false, features = ['std'] }
toy-example-core = {path = "../../core" }
transfer-methods = {path = "../../transfer_methods"}

View File

@ -0,0 +1,42 @@
use risc0_zkvm::{guest::env, sha::{Impl, Sha256}, serde::to_vec};
use toy_example_core::{Account, hash, compute_nullifier, is_in_commitment_tree};
use transfer_methods::TRANSFER_ID;
fn main() {
// Read inputs
let sender_private_key: [u32; 8] = env::read();
let sender: Account = env::read();
let receiver: Account = env::read();
let balance_to_move: u128 = env::read();
let sender_post: Account = env::read();
let receiver_post: Account = env::read();
let commitment_tree_root: [u32; 8] = env::read();
// Assert receiver account is fresh
assert_eq!(receiver.balance, 0);
// Prove ownership of sender account by proving
// knowledge of the pre-image of its address
assert_eq!(hash(&sender_private_key), sender.address);
// Compute sender account commitment and prove it belongs to commitments tree
let sender_commitment = sender.commitment();
assert!(is_in_commitment_tree(sender_commitment, commitment_tree_root));
// Compute nullifier of sender account
let sender_nullifier = compute_nullifier(sender_commitment, sender_private_key);
// Compute receiver commitment
let receiver_commitment = receiver_post.commitment();
// Verify pre states and post states of accounts are consistent
// with the execution of the TRANSFER_ELF program
env::verify(TRANSFER_ID, &to_vec(&(sender.clone(), receiver.clone(), sender_post.clone(), receiver_post.clone())).unwrap()).unwrap();
// Assert TRANSFER_ELF program didn't modify address fields
assert_eq!(sender.address, sender_post.address);
assert_eq!(receiver.address, receiver_post.address);
// Output nullifier
env::commit(&(sender_nullifier, receiver_commitment));
}

View File

@ -0,0 +1 @@
include!(concat!(env!("OUT_DIR"), "/methods.rs"));

View File

@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["rustfmt", "rust-src"]
profile = "minimal"

View File

@ -0,0 +1,119 @@
use std::u128;
// These constants represent the RISC-V ELF and the image ID generated by risc0-build.
// The ELF is used for proving and the ID is used for verification.
use transfer_methods::{
TRANSFER_ELF, TRANSFER_ID
};
use outer_methods::{
OUTER_ELF, OUTER_ID
};
use risc0_zkvm::{default_executor, default_prover, ExecutorEnv, Receipt};
use toy_example_core::Account;
fn prove_inner(sender: Account, receiver: Account, balance_to_move: u128) -> (Receipt, Account, Account) {
let mut env_builder = ExecutorEnv::builder();
env_builder.write(&sender).unwrap();
env_builder.write(&receiver).unwrap();
env_builder.write(&balance_to_move).unwrap();
let env = env_builder.build().unwrap();
let prover = default_prover();
let prove_info = prover
.prove(env, TRANSFER_ELF)
.unwrap();
let receipt = prove_info.receipt;
let output: [Account; 4] = receipt.journal.decode().unwrap();
let [_, _, sender_post, receiver_post] = output;
println!("sender_before: {:?}, sender_after: {:?}", sender, sender_post);
println!("receiver_before: {:?}, receiver_after: {:?}", receiver, receiver_post);
// Sanity check
receipt
.verify(TRANSFER_ID)
.unwrap();
(receipt, sender_post, receiver_post)
}
pub fn run_private_execution_of_transfer_program() {
let commitment_tree_root = [0xdd, 0xee, 0xaa, 0xdd, 0xbb, 0xee, 0xee, 0xff];
let sender_private_key = [0; 8];
let mut sender = Account::new(sender_private_key, [1; 8]);
sender.balance = 150;
let receiver_private_key = [99; 8];
let receiver = Account::new(receiver_private_key, [1; 8]);
let balance_to_move: u128 = 3;
// Prove inner
let (inner_receipt, sender_post, receiver_post) = prove_inner(sender.clone(), receiver.clone(), balance_to_move);
// Prover outer
let mut env_builder = ExecutorEnv::builder();
env_builder.add_assumption(inner_receipt);
env_builder.write(&sender_private_key).unwrap();
env_builder.write(&sender).unwrap();
env_builder.write(&receiver) .unwrap();
env_builder.write(&balance_to_move).unwrap();
env_builder.write(&sender_post).unwrap();
env_builder.write(&receiver_post).unwrap();
env_builder.write(&commitment_tree_root).unwrap();
let env = env_builder.build().unwrap();
let prover = default_prover();
let prove_info = prover
.prove(env, OUTER_ELF)
.unwrap();
let receipt = prove_info.receipt;
receipt.verify(OUTER_ID).unwrap();
let (nullifier, commitment): ([u32; 8], [u32; 8]) = receipt.journal.decode().unwrap();
println!("nullifier: {:?}", nullifier);
println!("commitment: {:?}", commitment);
}
pub fn run_public_execution_of_transfer_program() {
let sender_private_key = [0; 8];
let mut sender = Account::new(sender_private_key, [1; 8]);
sender.balance = 150;
let receiver_private_key = [99; 8];
let mut receiver = Account::new(receiver_private_key, [1; 8]);
receiver.balance = 900;
let balance_to_move: u128 = 3;
let mut env_builder = ExecutorEnv::builder();
env_builder.write(&sender).unwrap();
env_builder.write(&receiver).unwrap();
env_builder.write(&balance_to_move).unwrap();
let env = env_builder.build().unwrap();
let executor = default_executor();
let result: [Account; 4] = executor.execute(env, TRANSFER_ELF).unwrap().journal.decode().unwrap();
let [_, _, sender_post, receiver_post] = result;
println!("sender_before: {:?}, sender_after: {:?}", sender, sender_post);
println!("receiver_before: {:?}, receiver_after: {:?}", receiver, receiver_post);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_private() {
run_private_execution_of_transfer_program();
}
#[test]
fn test_public() {
run_public_execution_of_transfer_program();
}
}

View File

@ -0,0 +1,10 @@
[package]
name = "transfer-methods"
version = "0.1.0"
edition = "2021"
[build-dependencies]
risc0-build = { version = "2.2" }
[package.metadata.risc0]
methods = ["guest"]

View File

@ -0,0 +1,3 @@
fn main() {
risc0_build::embed_methods();
}

View File

@ -0,0 +1,10 @@
[package]
name = "toy_example"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
risc0-zkvm = { version = "2.2.0", default-features = false, features = ['std'] }
toy-example-core = {path = "../../core" }

View File

@ -0,0 +1,18 @@
use risc0_zkvm::{guest::env, sha::{Impl, Sha256}, serde::to_vec};
use toy_example_core::Account;
fn main() {
let sender: Account = env::read();
let receiver: Account = env::read();
let balance_to_move: u128 = env::read();
assert!(sender.balance >= balance_to_move);
let mut sender_post = sender.clone();
let mut receiver_post = receiver.clone();
sender_post.balance -= balance_to_move;
receiver_post.balance += balance_to_move;
env::commit(&(sender, receiver, sender_post, receiver_post));
}

View File

@ -0,0 +1 @@
include!(concat!(env!("OUT_DIR"), "/methods.rs"));