mirror of
https://github.com/vacp2p/nescience-zkvm-testing.git
synced 2026-01-09 13:58:11 -05:00
Merge pull request #6 from vacp2p/schouhy/selective-privacy-poc
Add Selective Privacy POC with Risc0
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -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
|
||||
19
risc0-selective-privacy-poc/Cargo.toml
Normal file
19
risc0-selective-privacy-poc/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "nssa"
|
||||
version = "0.12.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
risc0-zkvm = "2.2"
|
||||
core = { path = "core" }
|
||||
program-methods = { path = "program_methods" }
|
||||
serde = "1.0"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
rand = "0.8"
|
||||
sparse-merkle-tree = { path = "./sparse_merkle_tree/" }
|
||||
|
||||
[features]
|
||||
cuda = ["risc0-zkvm/cuda"]
|
||||
default = []
|
||||
prove = ["risc0-zkvm/prove"]
|
||||
|
||||
9
risc0-selective-privacy-poc/core/Cargo.toml
Normal file
9
risc0-selective-privacy-poc/core/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "core"
|
||||
version = "0.12.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
risc0-zkvm = "2.0.2"
|
||||
serde = { version = "1.0", default-features = false }
|
||||
|
||||
42
risc0-selective-privacy-poc/core/src/account.rs
Normal file
42
risc0-selective-privacy-poc/core/src/account.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use crate::{
|
||||
hash,
|
||||
types::{Address, Commitment, Key, Nonce, ProgramId},
|
||||
};
|
||||
use risc0_zkvm::serde::to_vec;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Account to be used both in public and private contexts
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Account {
|
||||
pub program_owner: Option<ProgramId>,
|
||||
pub address: Address,
|
||||
pub balance: u128,
|
||||
pub nonce: Nonce,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub fn new(address: Address, balance: u128) -> Self {
|
||||
Self {
|
||||
program_owner: None,
|
||||
address,
|
||||
balance,
|
||||
nonce: [0; 8],
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new account with address = hash(private_key) and balance = 0
|
||||
pub fn new_from_private_key(private_key: Key) -> Self {
|
||||
let address = Self::address_for_key(&private_key);
|
||||
Self::new(address, 0)
|
||||
}
|
||||
|
||||
/// Computes the address corresponding to the given private key
|
||||
pub fn address_for_key(private_key: &Key) -> Address {
|
||||
hash(private_key)
|
||||
}
|
||||
|
||||
/// Returns (first 8 bytes of) SHA256(Account)
|
||||
pub fn commitment(&self) -> Commitment {
|
||||
hash(&to_vec(&self).unwrap())[0]
|
||||
}
|
||||
}
|
||||
126
risc0-selective-privacy-poc/core/src/lib.rs
Normal file
126
risc0-selective-privacy-poc/core/src/lib.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
pub mod account;
|
||||
pub mod types;
|
||||
pub mod visibility;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::{
|
||||
account::Account,
|
||||
types::{AuthenticationPath, Commitment, Key, Nullifier, ProgramId},
|
||||
};
|
||||
use risc0_zkvm::sha::{Impl, Sha256};
|
||||
|
||||
pub fn hash(bytes: &[u32]) -> [u32; 8] {
|
||||
Impl::hash_words(bytes).as_words().try_into().unwrap()
|
||||
}
|
||||
|
||||
pub fn is_in_tree(commitment: Commitment, path: &AuthenticationPath, root: [u32; 8]) -> bool {
|
||||
const HASH_ONE: [u32; 8] = [
|
||||
789771595, 3310634292, 3140410939, 3820475020, 3591004369, 2777006897, 1021496535, 2588247415,
|
||||
];
|
||||
|
||||
let mut hash = HASH_ONE;
|
||||
let mut current_index = commitment;
|
||||
for path_value in path.iter() {
|
||||
if current_index & 1 == 0 {
|
||||
hash = hash_two(&hash, path_value);
|
||||
} else {
|
||||
hash = hash_two(path_value, &hash);
|
||||
}
|
||||
current_index >>= 1;
|
||||
}
|
||||
root == hash
|
||||
}
|
||||
|
||||
/// Returns Hash(Commitment || private_key)
|
||||
pub fn compute_nullifier(commitment: &Commitment, private_key: &Key) -> Nullifier {
|
||||
let mut bytes_to_hash = [0; 9]; // <- 1 word for the commitment, 8 words for the private key
|
||||
bytes_to_hash[..1].copy_from_slice(&[*commitment]);
|
||||
bytes_to_hash[1..].copy_from_slice(private_key);
|
||||
hash(&bytes_to_hash)
|
||||
}
|
||||
|
||||
fn hash_two(left: &[u32; 8], right: &[u32; 8]) -> [u32; 8] {
|
||||
let mut bytes_to_hash = [0; 16];
|
||||
bytes_to_hash[..8].copy_from_slice(left);
|
||||
bytes_to_hash[8..].copy_from_slice(right);
|
||||
hash(&bytes_to_hash)
|
||||
}
|
||||
|
||||
pub fn bytes_to_words(bytes: &[u8; 32]) -> [u32; 8] {
|
||||
let mut words = [0; 8];
|
||||
for (i, chunk) in bytes.chunks(4).enumerate() {
|
||||
words[i] = u32::from_le_bytes(chunk.try_into().unwrap());
|
||||
}
|
||||
words
|
||||
}
|
||||
|
||||
/// Ensures that account transitions follow the rules of a well-behaved program.
|
||||
///
|
||||
/// A well-behaved program is one that:
|
||||
/// - does not change account addresses
|
||||
/// - does not change account nonces
|
||||
/// - does not change the `program_owner` field
|
||||
/// - only reduces the balance of accounts it owns and for which authentication was provided
|
||||
/// (**NOTE**: this authentication check is **not** included in this proof of concept)
|
||||
/// - preserves the total token supply across all accounts
|
||||
///
|
||||
/// This function does **not** check that the output accounts are the result of correctly
|
||||
/// executing the program. That must be checked separately, either by re-executing
|
||||
/// the program with the inputs or by verifying a proof of correct execution.
|
||||
pub fn check_well_behaved_account_transition(
|
||||
input_accounts: &[Account],
|
||||
output_accounts: &[Account],
|
||||
program_id: ProgramId,
|
||||
) -> bool {
|
||||
// Fail if the number of input and output accounts differ
|
||||
if input_accounts.len() != output_accounts.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (account_pre, account_post) in input_accounts.iter().zip(output_accounts) {
|
||||
// Fail if the program modified the addresses of the input accounts
|
||||
if account_pre.address != account_post.address {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fail if the program modified the nonces of the input accounts
|
||||
if account_pre.nonce != account_post.nonce {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fail if the program modified the program owner
|
||||
if account_pre.program_owner != account_post.program_owner {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fail if the program subtracted balance from an account it doesn't own.
|
||||
// (This check always passes if `program_owner` is `None`)
|
||||
if account_pre.balance > account_post.balance && account_pre.program_owner.unwrap_or(program_id) != program_id {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Fail if the execution didn't preserve the total supply.
|
||||
let total_balance_pre: u128 = input_accounts.iter().map(|account| account.balance).sum();
|
||||
let total_balance_post: u128 = output_accounts.iter().map(|account| account.balance).sum();
|
||||
if total_balance_pre != total_balance_post {
|
||||
return false;
|
||||
}
|
||||
// The previous check is only meaningful if the accounts involved in the sum are all different
|
||||
// otherwise the same balance is counted more than once in the total balance check. Therefore, we need to check
|
||||
// that the set of input accounts doesn't have repeated pairs of (address, nonce).
|
||||
// Note: Checking only that addresses are unique would be too restrictive, since different private accounts can have
|
||||
// the same address (but different nonces).
|
||||
if input_accounts
|
||||
.iter()
|
||||
.map(|account| (account.address, account.nonce))
|
||||
.collect::<HashSet<_>>()
|
||||
.len()
|
||||
!= input_accounts.len()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
27
risc0-selective-privacy-poc/core/src/types.rs
Normal file
27
risc0-selective-privacy-poc/core/src/types.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::account::Account;
|
||||
|
||||
/// For this POC we consider 32-bit commitments
|
||||
pub type Commitment = u32;
|
||||
pub type Nullifier = [u32; 8];
|
||||
pub type Address = [u32; 8];
|
||||
pub type Nonce = [u32; 8];
|
||||
pub type Key = [u32; 8];
|
||||
pub type AuthenticationPath = [[u32; 8]; 32];
|
||||
pub type ProgramId = [u32; 8];
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ProgramOutput {
|
||||
pub accounts_pre: Vec<Account>,
|
||||
pub accounts_post: Vec<Account>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct PrivacyExecutionOutput {
|
||||
pub public_accounts_pre: Vec<Account>,
|
||||
pub public_accounts_post: Vec<Account>,
|
||||
pub private_output_commitments: Vec<Commitment>,
|
||||
pub nullifiers: Vec<Nullifier>,
|
||||
pub commitment_tree_root: [u32; 8],
|
||||
}
|
||||
10
risc0-selective-privacy-poc/core/src/visibility.rs
Normal file
10
risc0-selective-privacy-poc/core/src/visibility.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use crate::types::{AuthenticationPath, Key};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum AccountVisibility {
|
||||
// A public account
|
||||
Public,
|
||||
// A private account
|
||||
Private(Option<(Key, AuthenticationPath)>),
|
||||
}
|
||||
88
risc0-selective-privacy-poc/examples/happy_path.rs
Normal file
88
risc0-selective-privacy-poc/examples/happy_path.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use core::{bytes_to_words, types::Address, visibility::AccountVisibility};
|
||||
|
||||
use nssa::program::PinataProgram;
|
||||
|
||||
use crate::mocked_components::sequencer::{print_accounts, MockedSequencer, PINATA_ADDRESS};
|
||||
use crate::mocked_components::{client::MockedClient, USER_CLIENTS};
|
||||
|
||||
mod mocked_components;
|
||||
|
||||
fn main() {
|
||||
let mut sequencer = MockedSequencer::new();
|
||||
let addresses: [Address; 3] = USER_CLIENTS.map(|client| client.user_address());
|
||||
println!("📝 Initial balances");
|
||||
print_accounts(&sequencer, &[]);
|
||||
|
||||
// A public execution of the Transfer Program
|
||||
// User1 sends 51 tokens to the piñata account
|
||||
USER_CLIENTS[1]
|
||||
.transfer_public(&PINATA_ADDRESS, 51, &mut sequencer)
|
||||
.unwrap();
|
||||
println!("📝 Balances after transfer");
|
||||
print_accounts(&sequencer, &[]);
|
||||
|
||||
// A shielded execution of the Transfer Program
|
||||
// User0 shields 15 tokens to a new private account of User1
|
||||
let private_account_user_1 = USER_CLIENTS[0]
|
||||
.transfer_shielded(&addresses[1], 15, &mut sequencer)
|
||||
.unwrap();
|
||||
println!("📝 Balances after shielded execution");
|
||||
print_accounts(&sequencer, &[&private_account_user_1]);
|
||||
|
||||
// A private execution of the Transfer Program
|
||||
// User1 uses it's private account to send 8 tokens to a new private account of User2
|
||||
let [private_account_user_1, private_account_user_2] = USER_CLIENTS[1]
|
||||
.transfer_private(private_account_user_1, &addresses[2], 8, &mut sequencer)
|
||||
.unwrap();
|
||||
println!("📝 Balances after private execution");
|
||||
print_accounts(&sequencer, &[&private_account_user_1, &private_account_user_2]);
|
||||
|
||||
// A deshielded execution of the Transfer Program
|
||||
// User2 deshields 1 token to the public account of User0
|
||||
let private_account_user_2 = USER_CLIENTS[2]
|
||||
.transfer_deshielded(private_account_user_2, &addresses[0], 1, &mut sequencer)
|
||||
.unwrap();
|
||||
println!("📝 Balances after deshielded execution");
|
||||
print_accounts(&sequencer, &[&private_account_user_1, &private_account_user_2]);
|
||||
|
||||
// A public execution of the Piñata program
|
||||
// User2 claims the prize of the Piñata program to its public account
|
||||
let preimage = bytes_to_words(b"NSSA Selective privacy is great!").to_vec();
|
||||
sequencer
|
||||
.process_public_execution::<PinataProgram>(&[PINATA_ADDRESS, addresses[2]], preimage)
|
||||
.unwrap();
|
||||
println!("📝 Balances after public piñata execution");
|
||||
print_accounts(&sequencer, &[&private_account_user_1, &private_account_user_2]);
|
||||
|
||||
// A shielded execution of the Piñata program
|
||||
// User1 claims the prize of the Piñata program to a new self-owned private account
|
||||
let another_private_account_user_1 = {
|
||||
// All of this is executed locally by the User1
|
||||
let pinata_account = sequencer.get_account(&PINATA_ADDRESS).unwrap();
|
||||
let receiver_account = MockedClient::fresh_account_for_mint(USER_CLIENTS[1].user_address());
|
||||
let visibilities = [AccountVisibility::Public, AccountVisibility::Private(None)];
|
||||
let preimage = bytes_to_words(b"NSSA Selective privacy is great!").to_vec();
|
||||
|
||||
let private_outputs = MockedClient::prove_and_send_to_sequencer::<PinataProgram>(
|
||||
&[pinata_account, receiver_account],
|
||||
preimage,
|
||||
&visibilities,
|
||||
sequencer.get_commitment_tree_root(),
|
||||
&mut sequencer,
|
||||
)
|
||||
.unwrap();
|
||||
let [private_account_user_1] = private_outputs.try_into().unwrap();
|
||||
private_account_user_1
|
||||
};
|
||||
println!("📝 Balances after private piñata execution");
|
||||
print_accounts(
|
||||
&sequencer,
|
||||
&[
|
||||
&private_account_user_1,
|
||||
&private_account_user_2,
|
||||
&another_private_account_user_1,
|
||||
],
|
||||
);
|
||||
|
||||
println!("Ok!");
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
use crate::mocked_components::sequencer::MockedSequencer;
|
||||
use core::{
|
||||
account::Account,
|
||||
types::{Address, Key},
|
||||
visibility::AccountVisibility,
|
||||
};
|
||||
|
||||
use super::sequencer::error::Error;
|
||||
|
||||
pub mod transfer_deshielded;
|
||||
pub mod transfer_private;
|
||||
pub mod transfer_public;
|
||||
pub mod transfer_shielded;
|
||||
|
||||
/// A client that creates and submits transfer transactions
|
||||
pub struct MockedClient {
|
||||
user_private_key: Key,
|
||||
}
|
||||
|
||||
impl MockedClient {
|
||||
pub const fn new(user_private_key: Key) -> Self {
|
||||
Self { user_private_key }
|
||||
}
|
||||
|
||||
pub fn user_address(&self) -> Address {
|
||||
Account::address_for_key(&self.user_private_key)
|
||||
}
|
||||
|
||||
/// Runs the outer program and submits the proof to the sequencer.
|
||||
/// Returns the output private accounts of the execution.
|
||||
pub fn prove_and_send_to_sequencer<P: nssa::Program>(
|
||||
input_accounts: &[Account],
|
||||
instruction_data: P::InstructionData,
|
||||
visibilities: &[AccountVisibility],
|
||||
commitment_tree_root: [u32; 8],
|
||||
sequencer: &mut MockedSequencer,
|
||||
) -> Result<Vec<Account>, Error> {
|
||||
// Execute and generate proof of the outer program
|
||||
let (receipt, private_outputs) =
|
||||
nssa::execute_offchain::<P>(input_accounts, instruction_data, visibilities, commitment_tree_root)
|
||||
.map_err(|_| Error::BadInput)?;
|
||||
|
||||
// Send proof to the sequencer
|
||||
sequencer.process_privacy_execution(receipt)?;
|
||||
|
||||
// Return private outputs
|
||||
Ok(private_outputs)
|
||||
}
|
||||
|
||||
/// Returns a new account used in privacy executions that mint private accounts
|
||||
pub fn fresh_account_for_mint(address: Address) -> Account {
|
||||
Account::new(address, 0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
use super::{MockedClient, MockedSequencer};
|
||||
use crate::mocked_components::sequencer::error::Error;
|
||||
use core::account::Account;
|
||||
use core::types::Address;
|
||||
use core::visibility::AccountVisibility;
|
||||
use nssa::program::TransferProgram;
|
||||
|
||||
impl MockedClient {
|
||||
/// A deshielded transaction of the Transfer program.
|
||||
/// All of this is executed locally by the sender
|
||||
pub fn transfer_deshielded(
|
||||
&self,
|
||||
from_account: Account,
|
||||
to_address: &Address,
|
||||
balance_to_move: u128,
|
||||
sequencer: &mut MockedSequencer,
|
||||
) -> Result<Account, Error> {
|
||||
// Fetch commitment tree root from the sequencer
|
||||
let commitment_tree_root = sequencer.get_commitment_tree_root();
|
||||
// Compute authenticaton path for the input private account
|
||||
let sender_commitment_auth_path = sequencer.get_authentication_path_for(&from_account.commitment());
|
||||
|
||||
// Fetch public account to deshield to
|
||||
let to_account = sequencer.get_account(to_address).unwrap();
|
||||
|
||||
// Set account visibilities
|
||||
// First entry is the private sender. Second entry is the public receiver
|
||||
let visibilities = vec![
|
||||
AccountVisibility::Private(Some((self.user_private_key, sender_commitment_auth_path))),
|
||||
AccountVisibility::Public,
|
||||
];
|
||||
|
||||
// Execute privately (off-chain) and submit it to the sequencer
|
||||
let private_outputs = Self::prove_and_send_to_sequencer::<TransferProgram>(
|
||||
&[from_account, to_account],
|
||||
balance_to_move,
|
||||
&visibilities,
|
||||
commitment_tree_root,
|
||||
sequencer,
|
||||
)?;
|
||||
|
||||
// There's only one private output account corresponding to the new private account of
|
||||
// the sender, with the remaining balance.
|
||||
let [sender_private_account] = private_outputs.try_into().unwrap();
|
||||
Ok(sender_private_account)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
use super::{MockedClient, MockedSequencer};
|
||||
use crate::mocked_components::sequencer::error::Error;
|
||||
use core::account::Account;
|
||||
use core::types::Address;
|
||||
use core::visibility::AccountVisibility;
|
||||
use nssa::program::TransferProgram;
|
||||
|
||||
impl MockedClient {
|
||||
/// A private execution of the Transfer program
|
||||
// All of this is executed locally by the sender
|
||||
pub fn transfer_private(
|
||||
&self,
|
||||
owned_private_account: Account,
|
||||
to_address: &Address,
|
||||
balance_to_move: u128,
|
||||
sequencer: &mut MockedSequencer,
|
||||
) -> Result<[Account; 2], Error> {
|
||||
// Fetch commitment tree root from the sequencer
|
||||
let commitment_tree_root = sequencer.get_commitment_tree_root();
|
||||
// Compute authenticaton path for the input private account
|
||||
let sender_commitment_auth_path = sequencer.get_authentication_path_for(&owned_private_account.commitment());
|
||||
|
||||
// Create a new default private account for the recipient
|
||||
let receiver_account = Self::fresh_account_for_mint(*to_address);
|
||||
|
||||
// Set visibilities. Both private accounts.
|
||||
let visibilities = vec![
|
||||
AccountVisibility::Private(Some((self.user_private_key, sender_commitment_auth_path))),
|
||||
AccountVisibility::Private(None),
|
||||
];
|
||||
|
||||
// Execute privately (off-chain) and submit it to the sequencer
|
||||
let private_outputs = Self::prove_and_send_to_sequencer::<TransferProgram>(
|
||||
&[owned_private_account, receiver_account],
|
||||
balance_to_move,
|
||||
&visibilities,
|
||||
commitment_tree_root,
|
||||
sequencer,
|
||||
)?;
|
||||
|
||||
// There are two output private accounts of this execution.
|
||||
// The first corresponds to the sender, with the remaining balance.
|
||||
// The second corresponds to the newly minted private account for the recipient.
|
||||
let [sender_private_account, receiver_private_account] = private_outputs.try_into().unwrap();
|
||||
Ok([sender_private_account, receiver_private_account])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
use core::types::Address;
|
||||
|
||||
use nssa::program::TransferProgram;
|
||||
|
||||
use crate::mocked_components::{
|
||||
client::MockedClient,
|
||||
sequencer::{error::Error, MockedSequencer},
|
||||
};
|
||||
|
||||
impl MockedClient {
|
||||
pub fn transfer_public(
|
||||
&self,
|
||||
to_address: &Address,
|
||||
amount_to_transfer: u128,
|
||||
sequencer: &mut MockedSequencer,
|
||||
) -> Result<(), Error> {
|
||||
// Submit a public (on-chain) execution of the Transfer program to the sequencer
|
||||
sequencer
|
||||
.process_public_execution::<TransferProgram>(&[self.user_address(), *to_address], amount_to_transfer)
|
||||
.map_err(|_| Error::BadInput)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use super::{MockedClient, MockedSequencer};
|
||||
use crate::mocked_components::sequencer::error::Error;
|
||||
use core::account::Account;
|
||||
use core::types::Address;
|
||||
use core::visibility::AccountVisibility;
|
||||
use nssa::program::TransferProgram;
|
||||
|
||||
impl MockedClient {
|
||||
/// A shielded execution of the Transfer program
|
||||
// All of this is executed locally by the sender
|
||||
pub fn transfer_shielded(
|
||||
&self,
|
||||
to_address: &Address,
|
||||
balance_to_move: u128,
|
||||
sequencer: &mut MockedSequencer,
|
||||
) -> Result<Account, Error> {
|
||||
// Fetch commitment tree root from the sequencer
|
||||
let commitment_tree_root = sequencer.get_commitment_tree_root();
|
||||
|
||||
// Fetch sender account from the sequencer
|
||||
let from_account = sequencer.get_account(&self.user_address()).ok_or(Error::NotFound)?;
|
||||
|
||||
// Create a new default private account for the receiver
|
||||
let to_account = Self::fresh_account_for_mint(*to_address);
|
||||
|
||||
// Set account visibilities
|
||||
// First is the public account of the sender. Second is the private account minted in this
|
||||
// execution
|
||||
let visibilities = [AccountVisibility::Public, AccountVisibility::Private(None)];
|
||||
|
||||
// Execute privately (off-chain) and submit it to the sequencer
|
||||
let private_outputs = Self::prove_and_send_to_sequencer::<TransferProgram>(
|
||||
&[from_account, to_account],
|
||||
balance_to_move,
|
||||
&visibilities,
|
||||
commitment_tree_root,
|
||||
sequencer,
|
||||
)?;
|
||||
|
||||
// There is only one private account as the output of this execution. It corresponds to the
|
||||
// receiver.
|
||||
let [receiver_private_account] = private_outputs.try_into().unwrap();
|
||||
Ok(receiver_private_account)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
use crate::mocked_components::client::MockedClient;
|
||||
|
||||
pub mod client;
|
||||
pub mod sequencer;
|
||||
|
||||
/// Default users for examples
|
||||
pub const USER_CLIENTS: [MockedClient; 3] = [
|
||||
MockedClient::new([1; 8]),
|
||||
MockedClient::new([2; 8]),
|
||||
MockedClient::new([3; 8]),
|
||||
];
|
||||
@@ -0,0 +1,16 @@
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
NotFound,
|
||||
BadInput,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Error::NotFound => write!(f, "Not found"),
|
||||
Error::BadInput => write!(f, "Bad input"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
@@ -0,0 +1,130 @@
|
||||
use core::{
|
||||
account::Account,
|
||||
bytes_to_words,
|
||||
types::{Address, AuthenticationPath, Commitment, Nullifier},
|
||||
};
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
|
||||
use nssa::{program::PinataProgram, Program};
|
||||
use sparse_merkle_tree::SparseMerkleTree;
|
||||
|
||||
use crate::mocked_components::USER_CLIENTS;
|
||||
|
||||
pub mod error;
|
||||
pub mod process_privacy_execution;
|
||||
pub mod process_public_execution;
|
||||
|
||||
pub struct MockedSequencer {
|
||||
accounts: BTreeMap<Address, Account>,
|
||||
commitment_tree: SparseMerkleTree,
|
||||
nullifier_set: HashSet<Nullifier>,
|
||||
}
|
||||
|
||||
/// The initial balance of the genesis accounts
|
||||
const INITIAL_BALANCE: u128 = 150;
|
||||
/// The address of the piñata program account
|
||||
pub const PINATA_ADDRESS: Address = [0xcafe; 8];
|
||||
|
||||
impl MockedSequencer {
|
||||
pub fn new() -> Self {
|
||||
let mut accounts: BTreeMap<Address, Account> = USER_CLIENTS
|
||||
.iter()
|
||||
.map(|client| client.user_address())
|
||||
.map(|address| Account::new(address, INITIAL_BALANCE))
|
||||
.map(|account| (account.address, account))
|
||||
.collect();
|
||||
|
||||
let pinata_account = {
|
||||
let mut this = Account::new(PINATA_ADDRESS, INITIAL_BALANCE);
|
||||
// Set the owner of the Pinata account so that only the Pinata program
|
||||
// can reduce its balance.
|
||||
this.program_owner = Some(PinataProgram::PROGRAM_ID);
|
||||
this
|
||||
};
|
||||
|
||||
accounts.insert(pinata_account.address, pinata_account);
|
||||
|
||||
let commitment_tree = SparseMerkleTree::new_empty();
|
||||
let nullifier_set = HashSet::new();
|
||||
Self {
|
||||
accounts,
|
||||
commitment_tree,
|
||||
nullifier_set,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current state of the account for the given address
|
||||
pub fn get_account(&self, address: &Address) -> Option<Account> {
|
||||
self.accounts.get(address).cloned()
|
||||
}
|
||||
|
||||
/// Returns the root of the commitment tree
|
||||
pub fn get_commitment_tree_root(&self) -> [u32; 8] {
|
||||
bytes_to_words(&self.commitment_tree.root())
|
||||
}
|
||||
|
||||
/// Computes the authentication path for the given commitment
|
||||
pub fn get_authentication_path_for(&self, commitment: &Commitment) -> AuthenticationPath {
|
||||
self.commitment_tree
|
||||
.get_authentication_path_for_value(*commitment)
|
||||
.iter()
|
||||
.map(bytes_to_words)
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Pretty prints the chain's state
|
||||
pub fn print_accounts(sequencer: &MockedSequencer, private_accounts: &[&Account]) {
|
||||
println!("\n====================== ACCOUNT SNAPSHOT ======================\n");
|
||||
|
||||
println!(">> Public Accounts:");
|
||||
println!("{:<20} | {:>10} |", "Address (first u32)", "Balance");
|
||||
println!("{:-<20}-+-{:-<10}", "", "");
|
||||
|
||||
for account in sequencer.accounts.values() {
|
||||
println!("0x{:<20x} | {:>10} |", account.address[0], account.balance);
|
||||
}
|
||||
|
||||
println!("{:-<20}-+-{:-<10}\n", "", "");
|
||||
|
||||
println!(">> Commitments:");
|
||||
println!("{:-<20}", "");
|
||||
|
||||
for commitment in sequencer.commitment_tree.values().iter() {
|
||||
println!("{commitment:<20x}");
|
||||
}
|
||||
|
||||
println!("{:-<20}\n", "");
|
||||
|
||||
let formatted: Vec<String> = sequencer
|
||||
.nullifier_set
|
||||
.iter()
|
||||
.map(|nullifier| format!("0x{:x}", nullifier[0]))
|
||||
.collect();
|
||||
|
||||
println!(">> Nullifiers (first u32):");
|
||||
println!("{:-<20}", "");
|
||||
|
||||
for entry in formatted {
|
||||
println!("{entry:<20}");
|
||||
}
|
||||
|
||||
println!("{:-<20}\n", "");
|
||||
|
||||
println!(">> Private Accounts:");
|
||||
println!("{:<20} | {:>10} | {:>10}", "Address (first u32)", "Nonce", "Balance");
|
||||
println!("{:-<20}-+-{:-<10}-+-{:-<10}", "", "", "");
|
||||
|
||||
for account in private_accounts.iter() {
|
||||
println!(
|
||||
"{:<20x} | {:>10x}| {:>10} | ",
|
||||
account.address[0], account.nonce[0], account.balance,
|
||||
);
|
||||
}
|
||||
|
||||
println!("{:-<20}-+-{:-<10}-+-{:-<10}", "", "", "");
|
||||
|
||||
println!("\n=============================================================\n");
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
use core::types::PrivacyExecutionOutput;
|
||||
|
||||
use risc0_zkvm::Receipt;
|
||||
|
||||
use super::error::Error;
|
||||
use super::MockedSequencer;
|
||||
|
||||
impl MockedSequencer {
|
||||
/// Processes a privacy execution request.
|
||||
/// Verifies the proof of the privacy execution and updates the state of the chain.
|
||||
pub fn process_privacy_execution(&mut self, receipt: Receipt) -> Result<(), Error> {
|
||||
// Parse the output of the "outer" program
|
||||
let output: PrivacyExecutionOutput = receipt.journal.decode().unwrap();
|
||||
|
||||
// Reject if the states of the public input accounts used in the inner execution do not
|
||||
// coincide with the on-chain state.
|
||||
for account in output.public_accounts_pre.iter() {
|
||||
let current_account = self.get_account(&account.address).ok_or(Error::NotFound)?;
|
||||
if ¤t_account != account {
|
||||
return Err(Error::BadInput);
|
||||
}
|
||||
}
|
||||
|
||||
// Reject in case the root used in the privacy execution is not the current root.
|
||||
if output.commitment_tree_root != self.get_commitment_tree_root() {
|
||||
return Err(Error::BadInput);
|
||||
}
|
||||
|
||||
// Reject if the nullifiers of this privacy execution have already been published.
|
||||
if output
|
||||
.nullifiers
|
||||
.iter()
|
||||
.any(|nullifier| self.nullifier_set.contains(nullifier))
|
||||
{
|
||||
return Err(Error::BadInput);
|
||||
}
|
||||
|
||||
// Reject if the commitments have already been seen.
|
||||
if output
|
||||
.private_output_commitments
|
||||
.iter()
|
||||
.any(|commitment| self.commitment_tree.values().contains(commitment))
|
||||
{
|
||||
return Err(Error::BadInput);
|
||||
}
|
||||
|
||||
// Verify the proof of the privacy execution.
|
||||
// This includes a proof of the following statements
|
||||
// - Public inputs, public outputs, commitments and nullifiers are consistent with the
|
||||
// execution of some program.
|
||||
// - The given nullifiers correctly correspond to commitments that currently belong to
|
||||
// the commitment tree.
|
||||
// - The given commitments are correctly computed from valid accounts.
|
||||
// - The chain invariants are preserved
|
||||
nssa::verify_privacy_execution(receipt).map_err(|_| Error::BadInput)?;
|
||||
|
||||
// At this point the privacy execution is considered valid.
|
||||
//
|
||||
// Update the state of the public accounts with the post-state of this privacy execution
|
||||
output.public_accounts_post.into_iter().for_each(|account_post_state| {
|
||||
self.accounts.insert(account_post_state.address, account_post_state);
|
||||
});
|
||||
|
||||
// Add all nullifiers to the nullifier set.
|
||||
self.nullifier_set.extend(output.nullifiers);
|
||||
|
||||
// Add commitments to the commitment tree.
|
||||
for commitment in output.private_output_commitments.iter() {
|
||||
self.commitment_tree.add_value(*commitment);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
use core::{account::Account, check_well_behaved_account_transition, types::Address};
|
||||
|
||||
use crate::mocked_components::sequencer::error::Error;
|
||||
|
||||
use super::MockedSequencer;
|
||||
|
||||
impl MockedSequencer {
|
||||
/// Processes a public execution request of the program `P`.
|
||||
pub fn process_public_execution<P: nssa::Program>(
|
||||
&mut self,
|
||||
input_account_addresses: &[Address],
|
||||
instruction_data: P::InstructionData,
|
||||
) -> Result<(), Error> {
|
||||
// Fetch the current state of the input accounts.
|
||||
let input_accounts: Vec<Account> = input_account_addresses
|
||||
.iter()
|
||||
.map(|address| self.get_account(address).ok_or(Error::NotFound))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
// Execute the program
|
||||
let program_output =
|
||||
nssa::execute_onchain::<P>(&input_accounts, instruction_data).map_err(|_| Error::BadInput)?;
|
||||
|
||||
// Assert accounts pre- and post-states preserve chains invariants
|
||||
if !check_well_behaved_account_transition(&input_accounts, &program_output.accounts_post, P::PROGRAM_ID) {
|
||||
return Err(Error::BadInput);
|
||||
}
|
||||
|
||||
// Update the accounts states
|
||||
program_output.accounts_post.into_iter().for_each(|account_post_state| {
|
||||
self.accounts.insert(account_post_state.address, account_post_state);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
70
risc0-selective-privacy-poc/examples/private_execution.rs
Normal file
70
risc0-selective-privacy-poc/examples/private_execution.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use core::{
|
||||
account::Account,
|
||||
bytes_to_words,
|
||||
types::{Address, AuthenticationPath},
|
||||
visibility::AccountVisibility,
|
||||
};
|
||||
use nssa::program::TransferMultipleProgram;
|
||||
use sparse_merkle_tree::SparseMerkleTree;
|
||||
|
||||
/// A private execution of the TransferMultiple function.
|
||||
/// This actually "burns" a sender private account and "mints" two new private accounts:
|
||||
/// one for the recipient with the transferred balance, and another owned by the sender with the remaining balance.
|
||||
fn main() {
|
||||
// Setup commitment tree, simulating a current chain state that has a private account already
|
||||
// committed to the commitment tree.
|
||||
let sender_private_key = [1, 2, 3, 4, 4, 3, 2, 1];
|
||||
let sender = {
|
||||
// Creating this here but it is supposed to be already in the private possesion of the user
|
||||
let mut account = Account::new_from_private_key(sender_private_key);
|
||||
account.balance = 150;
|
||||
account
|
||||
};
|
||||
let commitment_tree = SparseMerkleTree::new([sender.commitment()].into_iter().collect());
|
||||
|
||||
// Get the root of the commitment tree and the authentication path of the commitment of the private account.
|
||||
let root = bytes_to_words(&commitment_tree.root());
|
||||
let auth_path: Vec<[u32; 8]> = commitment_tree
|
||||
.get_authentication_path_for_value(sender.commitment())
|
||||
.iter()
|
||||
.map(bytes_to_words)
|
||||
.collect();
|
||||
let auth_path: AuthenticationPath = auth_path.try_into().unwrap();
|
||||
|
||||
// These are the new private account being minted by this private execution.
|
||||
// (the `receiver_address` would be <Npk> in UTXO's terminology)
|
||||
let receiver_address_1 = [99; 8];
|
||||
let receiver_1 = new_default_account(receiver_address_1);
|
||||
let receiver_address_2 = [100; 8];
|
||||
let receiver_2 = new_default_account(receiver_address_2);
|
||||
|
||||
// Setup input account visibilites. All accounts are private for this execution.
|
||||
let visibilities = vec![
|
||||
AccountVisibility::Private(Some((sender_private_key, auth_path))),
|
||||
AccountVisibility::Private(None),
|
||||
AccountVisibility::Private(None),
|
||||
];
|
||||
|
||||
// Set the balances to be sent to the two receiver addresses.
|
||||
// This means, the execution will remove 70 tokens from the sender
|
||||
// and send 30 to the first receiver and 40 to the second.
|
||||
let balance_to_move = vec![30, 40];
|
||||
|
||||
// Execute and prove the outer program for the TransferMultipleProgram.
|
||||
// This is executed off-chain by the sender.
|
||||
let (receipt, _) = nssa::execute_offchain::<TransferMultipleProgram>(
|
||||
&[sender, receiver_1, receiver_2],
|
||||
balance_to_move,
|
||||
&visibilities,
|
||||
root,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Verify the proof
|
||||
assert!(nssa::verify_privacy_execution(receipt).is_ok());
|
||||
println!("OK!");
|
||||
}
|
||||
|
||||
fn new_default_account(address: Address) -> Account {
|
||||
Account::new(address, 0)
|
||||
}
|
||||
24
risc0-selective-privacy-poc/examples/public_execution.rs
Normal file
24
risc0-selective-privacy-poc/examples/public_execution.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use core::account::Account;
|
||||
|
||||
use nssa::program::TransferMultipleProgram;
|
||||
|
||||
/// A public execution of the TransferMultipleProgram.
|
||||
/// This would be executed by the runtime after checking that
|
||||
/// the initiating transaction includes the sender's signature.
|
||||
pub fn main() {
|
||||
// Account fetched from the chain state with 150 in its balance.
|
||||
let sender = Account::new([5; 8], 150);
|
||||
|
||||
// Account fetched from the chain state with 900 in its balance.
|
||||
let receiver_1 = Account::new([6; 8], 900);
|
||||
|
||||
// Account fetched from the chain state with 500 in its balance.
|
||||
let receiver_2 = Account::new([6; 8], 500);
|
||||
|
||||
let balance_to_move = vec![10, 20];
|
||||
|
||||
let _inputs_outputs =
|
||||
nssa::execute_onchain::<TransferMultipleProgram>(&[sender, receiver_1, receiver_2], balance_to_move).unwrap();
|
||||
|
||||
println!("OK!");
|
||||
}
|
||||
10
risc0-selective-privacy-poc/program_methods/Cargo.toml
Normal file
10
risc0-selective-privacy-poc/program_methods/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "program-methods"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[build-dependencies]
|
||||
risc0-build = { version = "2.2" }
|
||||
|
||||
[package.metadata.risc0]
|
||||
methods = ["guest"]
|
||||
3
risc0-selective-privacy-poc/program_methods/build.rs
Normal file
3
risc0-selective-privacy-poc/program_methods/build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
risc0_build::embed_methods();
|
||||
}
|
||||
10
risc0-selective-privacy-poc/program_methods/guest/Cargo.toml
Normal file
10
risc0-selective-privacy-poc/program_methods/guest/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "programs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
risc0-zkvm = { version = "2.2.0", default-features = false, features = ['std'] }
|
||||
core = { path = "../../core" }
|
||||
@@ -0,0 +1,157 @@
|
||||
use core::{
|
||||
check_well_behaved_account_transition, compute_nullifier, hash, is_in_tree,
|
||||
types::{Nonce, PrivacyExecutionOutput, ProgramId, ProgramOutput},
|
||||
visibility::AccountVisibility,
|
||||
};
|
||||
use risc0_zkvm::{guest::env, serde::to_vec};
|
||||
|
||||
/// Privacy execution logic.
|
||||
/// This is the circuit for proving correct off-chain executions of programs.
|
||||
/// It also verifies that the chain's invariants are not violated and the program is well-behaved.
|
||||
///
|
||||
/// Inputs:
|
||||
/// - ProgramOuptut: The output of the inner program. This is includes the accounts pre and post-states of the execution of the inner program.
|
||||
/// - Vec<AccountVisibility>: A vector indicating which accounts are private and which are public.
|
||||
/// - Vec<Nonce>: The vector of nonces to be used for the output accounts. This is assumed to be sampled at random by the host program.
|
||||
/// - [u32; 8]: The root of the commitment tree. Commitments of input private accounts will be checked against this to prove that they belong to the tree.
|
||||
/// - ProgamId: The ID of the inner program.
|
||||
///
|
||||
/// Public outputs:
|
||||
/// - The vector of accounts' pre and post states for the public accounts.
|
||||
/// - The nullifiers of the used private accounts.
|
||||
/// - The commitments for the ouput private accounts.
|
||||
/// - The commitment tree root used for the authentication path verifications.
|
||||
fn main() {
|
||||
// Read inner program output
|
||||
let inner_program_output: ProgramOutput = env::read();
|
||||
let num_inputs = inner_program_output.accounts_pre.len();
|
||||
|
||||
// Read visibilities
|
||||
let visibilities: Vec<AccountVisibility> = env::read();
|
||||
assert_eq!(visibilities.len(), num_inputs);
|
||||
|
||||
// Read nonces for outputs
|
||||
let output_nonces: Vec<Nonce> = env::read();
|
||||
assert_eq!(output_nonces.len(), num_inputs);
|
||||
|
||||
// Read root and program id.
|
||||
let commitment_tree_root: [u32; 8] = env::read();
|
||||
let program_id: ProgramId = env::read();
|
||||
|
||||
// Authentication step:
|
||||
let nullifiers = verify_and_nullify_private_inputs(&inner_program_output, &visibilities, commitment_tree_root);
|
||||
|
||||
// Verify pre states and post states of accounts are consistent
|
||||
// with the execution of the `program_id` program
|
||||
env::verify(program_id, &to_vec(&inner_program_output).unwrap()).unwrap();
|
||||
|
||||
// Assert accounts pre- and post-states preserve chains invariants
|
||||
assert!(check_well_behaved_account_transition(
|
||||
&inner_program_output.accounts_pre,
|
||||
&inner_program_output.accounts_post,
|
||||
program_id
|
||||
));
|
||||
|
||||
// From this point on the execution is considered valid
|
||||
|
||||
let output = assemble_privacy_execution_output(
|
||||
inner_program_output,
|
||||
visibilities,
|
||||
output_nonces,
|
||||
commitment_tree_root,
|
||||
nullifiers,
|
||||
);
|
||||
|
||||
env::commit(&output);
|
||||
}
|
||||
|
||||
fn assemble_privacy_execution_output(
|
||||
inner_program_output: ProgramOutput,
|
||||
visibilities: Vec<AccountVisibility>,
|
||||
output_nonces: Vec<[u32; 8]>,
|
||||
commitment_tree_root: [u32; 8],
|
||||
nullifiers: Vec<[u32; 8]>,
|
||||
) -> PrivacyExecutionOutput {
|
||||
// Insert new nonces in private outputs
|
||||
let accounts_pre = inner_program_output.accounts_pre;
|
||||
let mut accounts_post = inner_program_output.accounts_post;
|
||||
accounts_post
|
||||
.iter_mut()
|
||||
.zip(output_nonces)
|
||||
.zip(visibilities.iter())
|
||||
.for_each(|((account, new_nonce), visibility)| {
|
||||
if matches!(visibility, AccountVisibility::Private(_)) {
|
||||
account.nonce = new_nonce;
|
||||
}
|
||||
});
|
||||
|
||||
// Compute commitments for every private output
|
||||
let mut private_outputs = Vec::new();
|
||||
for (output, visibility) in accounts_post.iter().zip(visibilities.iter()) {
|
||||
match visibility {
|
||||
AccountVisibility::Public => continue,
|
||||
AccountVisibility::Private(_) => private_outputs.push(output),
|
||||
}
|
||||
}
|
||||
let private_output_commitments: Vec<_> = private_outputs.iter().map(|account| account.commitment()).collect();
|
||||
|
||||
// Get the list of public accounts pre and post states
|
||||
let mut public_accounts_pre = Vec::new();
|
||||
let mut public_accounts_post = Vec::new();
|
||||
for ((account_pre, account_post), visibility) in accounts_pre
|
||||
.into_iter()
|
||||
.zip(accounts_post.into_iter())
|
||||
.zip(visibilities)
|
||||
{
|
||||
match visibility {
|
||||
AccountVisibility::Public => {
|
||||
public_accounts_pre.push(account_pre);
|
||||
public_accounts_post.push(account_post);
|
||||
}
|
||||
AccountVisibility::Private(_) => continue,
|
||||
}
|
||||
}
|
||||
|
||||
PrivacyExecutionOutput {
|
||||
public_accounts_pre,
|
||||
public_accounts_post,
|
||||
private_output_commitments,
|
||||
nullifiers,
|
||||
commitment_tree_root,
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute nullifiers of private accounts pre states and check that their commitments belong to the commitments tree
|
||||
fn verify_and_nullify_private_inputs(
|
||||
inner_program_output: &ProgramOutput,
|
||||
account_visibilities: &[AccountVisibility],
|
||||
commitment_tree_root: [u32; 8],
|
||||
) -> Vec<[u32; 8]> {
|
||||
let mut nullifiers = Vec::new();
|
||||
for (visibility, input_account) in account_visibilities
|
||||
.iter()
|
||||
.zip(inner_program_output.accounts_pre.iter())
|
||||
{
|
||||
match visibility {
|
||||
AccountVisibility::Private(Some((private_key, auth_path))) => {
|
||||
// Prove ownership of input accounts by proving knowledge of the pre-image of their addresses.
|
||||
assert_eq!(hash(private_key), input_account.address);
|
||||
// Check the input account was created by a previous transaction by checking it belongs to the commitments tree.
|
||||
let commitment = input_account.commitment();
|
||||
assert!(is_in_tree(commitment, auth_path, commitment_tree_root));
|
||||
// Compute the nullifier to nullify this private input account.
|
||||
let nullifier = compute_nullifier(&commitment, private_key);
|
||||
nullifiers.push(nullifier);
|
||||
}
|
||||
AccountVisibility::Private(None) => {
|
||||
// Private accounts without a companion private key are enforced to have default values
|
||||
// Used for executions that need to create a new private account.
|
||||
assert_eq!(input_account.balance, 0);
|
||||
assert_eq!(input_account.nonce, [0; 8]);
|
||||
}
|
||||
// No checks on public accounts
|
||||
AccountVisibility::Public => continue,
|
||||
}
|
||||
}
|
||||
nullifiers
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use core::{account::Account, hash, types::ProgramOutput};
|
||||
use risc0_zkvm::guest::env;
|
||||
|
||||
const TARGET_HASH: [u32; 8] = [
|
||||
1363824975, 720119575, 717909014, 2043925380, 717793160, 1495780600, 1253022833, 116132328,
|
||||
];
|
||||
const PINATA_ACCOUNT_ADDR: [u32; 8] = [0xcafe; 8];
|
||||
const PINATA_PRIZE: u128 = 100;
|
||||
|
||||
/// A Piñata program
|
||||
/// To be used both in public and privacy contexts.
|
||||
fn main() {
|
||||
// Read input accounts.
|
||||
// It is expected to receive only two accounts: [pinata_account, winner_account]
|
||||
let input_accounts: Vec<Account> = env::read();
|
||||
|
||||
// Read claimed preimage
|
||||
let preimage: Vec<u32> = env::read();
|
||||
|
||||
// Unpack accounts.
|
||||
assert_eq!(input_accounts.len(), 2);
|
||||
let [pinata_account, winner_account] = input_accounts.try_into().unwrap();
|
||||
|
||||
// Check that the given `pinata_account` is correct
|
||||
assert_eq!(pinata_account.address, PINATA_ACCOUNT_ADDR);
|
||||
|
||||
// Check that the piñata account has enough balance to pay the prize
|
||||
assert!(pinata_account.balance >= PINATA_PRIZE);
|
||||
|
||||
// Check that the preimage is correct.
|
||||
assert_eq!(hash(&preimage), TARGET_HASH);
|
||||
|
||||
// Pay the prize
|
||||
let mut winner_account_post = winner_account.clone();
|
||||
let mut pinata_account_post = pinata_account.clone();
|
||||
pinata_account_post.balance -= PINATA_PRIZE;
|
||||
winner_account_post.balance += PINATA_PRIZE;
|
||||
|
||||
let output = ProgramOutput {
|
||||
accounts_pre: vec![pinata_account, winner_account],
|
||||
accounts_post: vec![pinata_account_post, winner_account_post],
|
||||
};
|
||||
|
||||
env::commit(&output);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
use core::{account::Account, types::ProgramOutput};
|
||||
use risc0_zkvm::guest::env;
|
||||
|
||||
/// A transfer of balance program.
|
||||
/// To be used both in public and private contexts.
|
||||
fn main() {
|
||||
// Read input accounts.
|
||||
// It is expected to receive only two accounts: [sender_account, receiver_account]
|
||||
let input_accounts: Vec<Account> = env::read();
|
||||
let balance_to_move: u128 = env::read();
|
||||
|
||||
// Unpack sender and receiver
|
||||
assert_eq!(input_accounts.len(), 2);
|
||||
let [sender, receiver] = input_accounts.try_into().unwrap();
|
||||
|
||||
// Check sender has enough balance
|
||||
assert!(sender.balance >= balance_to_move);
|
||||
|
||||
// Create accounts post states, with updated balances
|
||||
let mut sender_post = sender.clone();
|
||||
let mut receiver_post = receiver.clone();
|
||||
sender_post.balance -= balance_to_move;
|
||||
receiver_post.balance += balance_to_move;
|
||||
|
||||
let output = ProgramOutput {
|
||||
accounts_pre: vec![sender, receiver],
|
||||
accounts_post: vec![sender_post, receiver_post],
|
||||
};
|
||||
|
||||
env::commit(&output);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
use core::{account::Account, types::ProgramOutput};
|
||||
use risc0_zkvm::guest::env;
|
||||
|
||||
/// A transfer of balance program with one sender and multiple recipients.
|
||||
/// To be used both in public and private contexts.
|
||||
fn main() {
|
||||
// Read input accounts.
|
||||
// First account is the sender.
|
||||
let mut input_accounts: Vec<Account> = env::read();
|
||||
|
||||
// Read the balances to be send to each recipient
|
||||
let target_balances: Vec<u128> = env::read();
|
||||
|
||||
// Check that there is at least one recipient
|
||||
assert!(input_accounts.len() > 1);
|
||||
|
||||
// Check that there's one target balance for each recipient.
|
||||
assert_eq!(target_balances.len() + 1, input_accounts.len());
|
||||
|
||||
// Unpack sender and recipients
|
||||
let recipients = input_accounts.split_off(1);
|
||||
let sender = input_accounts.pop().unwrap();
|
||||
|
||||
// Check that the sender has enough balance to pay to all recipients
|
||||
let total_balance_to_move = target_balances.iter().sum();
|
||||
assert!(sender.balance >= total_balance_to_move);
|
||||
|
||||
// Create accounts post states, with updated balances
|
||||
let mut sender_post = sender.clone();
|
||||
let mut recipients_post = recipients.clone();
|
||||
|
||||
// Transfer balances
|
||||
sender_post.balance -= total_balance_to_move;
|
||||
for (receiver, balance_for_receiver) in recipients_post.iter_mut().zip(target_balances) {
|
||||
receiver.balance += balance_for_receiver;
|
||||
}
|
||||
|
||||
let output = ProgramOutput {
|
||||
accounts_pre: vec![sender].into_iter().chain(recipients).collect(),
|
||||
accounts_post: vec![sender_post].into_iter().chain(recipients_post).collect(),
|
||||
};
|
||||
|
||||
env::commit(&output);
|
||||
}
|
||||
1
risc0-selective-privacy-poc/program_methods/src/lib.rs
Normal file
1
risc0-selective-privacy-poc/program_methods/src/lib.rs
Normal file
@@ -0,0 +1 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/methods.rs"));
|
||||
4
risc0-selective-privacy-poc/rust-toolchain.toml
Normal file
4
risc0-selective-privacy-poc/rust-toolchain.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "stable"
|
||||
components = ["rustfmt", "rust-src"]
|
||||
profile = "minimal"
|
||||
1
risc0-selective-privacy-poc/rustfmt.toml
Normal file
1
risc0-selective-privacy-poc/rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
||||
max_width = 120
|
||||
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "sparse-merkle-tree"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
sha2 = "0.10.9"
|
||||
@@ -0,0 +1,142 @@
|
||||
// Values computed as follows
|
||||
//
|
||||
// fn default_hashes() -> Vec<[u8; 32]> {
|
||||
// let mut defaults = vec![ZERO_HASH];
|
||||
// for i in 1..TREE_DEPTH {
|
||||
// let h = hash_node(&defaults[i - 1], &defaults[i - 1]);
|
||||
// defaults.push(h);
|
||||
// }
|
||||
// defaults.into_iter().rev().collect()
|
||||
// }
|
||||
//
|
||||
//
|
||||
pub(crate) const DEFAULT_HASHES: [[u8; 32]; 32] = [
|
||||
[
|
||||
157, 148, 193, 146, 141, 23, 128, 25, 196, 90, 21, 193, 179, 235, 209, 157, 146, 64, 171,
|
||||
100, 192, 44, 121, 46, 78, 53, 190, 198, 191, 82, 85, 16,
|
||||
],
|
||||
[
|
||||
244, 159, 70, 55, 214, 43, 252, 179, 167, 206, 218, 26, 170, 158, 22, 127, 12, 155, 130,
|
||||
224, 1, 98, 105, 28, 59, 57, 233, 189, 41, 50, 63, 115,
|
||||
],
|
||||
[
|
||||
204, 67, 6, 158, 244, 197, 238, 10, 26, 69, 178, 150, 78, 185, 192, 92, 5, 224, 36, 51,
|
||||
115, 21, 130, 142, 229, 223, 102, 195, 57, 198, 17, 27,
|
||||
],
|
||||
[
|
||||
49, 193, 198, 248, 157, 86, 23, 193, 111, 194, 178, 234, 70, 158, 128, 133, 165, 168, 43,
|
||||
60, 43, 15, 129, 209, 237, 51, 21, 134, 74, 209, 147, 88,
|
||||
],
|
||||
[
|
||||
219, 156, 83, 145, 70, 138, 195, 224, 161, 165, 121, 148, 215, 154, 104, 234, 219, 0, 91,
|
||||
255, 134, 28, 229, 108, 126, 225, 184, 6, 104, 156, 105, 140,
|
||||
],
|
||||
[
|
||||
196, 49, 181, 107, 231, 60, 253, 69, 245, 236, 7, 145, 32, 86, 58, 140, 47, 241, 0, 236,
|
||||
104, 61, 176, 23, 170, 95, 128, 66, 179, 134, 192, 209,
|
||||
],
|
||||
[
|
||||
155, 28, 109, 240, 82, 104, 71, 197, 186, 104, 178, 17, 138, 195, 57, 194, 194, 216, 96,
|
||||
246, 131, 233, 26, 84, 7, 124, 175, 159, 223, 60, 187, 161,
|
||||
],
|
||||
[
|
||||
183, 22, 254, 169, 215, 123, 104, 4, 156, 9, 23, 45, 110, 238, 115, 162, 108, 188, 142,
|
||||
141, 151, 185, 20, 199, 63, 150, 94, 146, 124, 30, 53, 145,
|
||||
],
|
||||
[
|
||||
122, 148, 43, 150, 236, 64, 36, 158, 18, 108, 140, 219, 34, 52, 143, 194, 69, 12, 185, 195,
|
||||
88, 206, 30, 249, 126, 255, 18, 221, 99, 72, 18, 91,
|
||||
],
|
||||
[
|
||||
51, 0, 157, 127, 41, 170, 190, 201, 194, 188, 222, 202, 115, 37, 229, 84, 111, 185, 104,
|
||||
69, 151, 66, 69, 34, 201, 161, 159, 139, 200, 11, 135, 67,
|
||||
],
|
||||
[
|
||||
186, 234, 98, 18, 205, 31, 46, 119, 118, 209, 66, 20, 180, 72, 129, 169, 242, 250, 48, 128,
|
||||
81, 175, 108, 228, 250, 226, 170, 123, 227, 21, 242, 221,
|
||||
],
|
||||
[
|
||||
27, 207, 232, 194, 77, 200, 137, 234, 233, 209, 180, 73, 180, 248, 193, 243, 50, 118, 191,
|
||||
199, 245, 30, 142, 242, 28, 234, 249, 134, 195, 154, 138, 162,
|
||||
],
|
||||
[
|
||||
199, 222, 136, 204, 114, 129, 19, 245, 177, 223, 179, 178, 201, 1, 202, 99, 26, 55, 146,
|
||||
90, 166, 193, 206, 36, 34, 171, 170, 245, 236, 35, 142, 161,
|
||||
],
|
||||
[
|
||||
121, 214, 101, 193, 197, 86, 227, 248, 59, 227, 3, 15, 20, 191, 124, 129, 209, 226, 93,
|
||||
128, 155, 137, 229, 66, 156, 221, 29, 179, 227, 120, 78, 59,
|
||||
],
|
||||
[
|
||||
118, 250, 222, 147, 174, 99, 105, 0, 241, 223, 160, 108, 11, 209, 143, 124, 59, 56, 11,
|
||||
164, 127, 2, 3, 18, 236, 149, 4, 176, 167, 196, 138, 245,
|
||||
],
|
||||
[
|
||||
204, 148, 248, 102, 164, 48, 65, 245, 219, 189, 191, 120, 157, 122, 63, 66, 228, 30, 143,
|
||||
166, 50, 157, 68, 187, 191, 110, 195, 83, 158, 2, 133, 52,
|
||||
],
|
||||
[
|
||||
179, 199, 88, 222, 194, 63, 148, 195, 88, 33, 190, 181, 102, 109, 100, 199, 212, 19, 198,
|
||||
123, 91, 167, 50, 157, 151, 242, 194, 103, 171, 143, 88, 198,
|
||||
],
|
||||
[
|
||||
209, 77, 39, 86, 1, 182, 123, 170, 109, 89, 182, 199, 89, 116, 244, 69, 49, 192, 149, 31,
|
||||
156, 226, 106, 73, 2, 112, 161, 78, 75, 153, 68, 189,
|
||||
],
|
||||
[
|
||||
158, 75, 216, 188, 35, 5, 86, 141, 82, 160, 215, 125, 16, 116, 45, 129, 224, 201, 105, 239,
|
||||
127, 37, 135, 136, 159, 255, 91, 222, 78, 64, 60, 246,
|
||||
],
|
||||
[
|
||||
121, 98, 24, 197, 183, 169, 52, 200, 156, 241, 142, 73, 241, 171, 113, 215, 133, 250, 13,
|
||||
105, 112, 253, 80, 197, 118, 105, 228, 77, 237, 254, 195, 66,
|
||||
],
|
||||
[
|
||||
188, 30, 229, 197, 205, 48, 162, 67, 206, 188, 130, 44, 72, 150, 168, 221, 170, 202, 59,
|
||||
110, 83, 205, 9, 10, 130, 11, 129, 79, 5, 218, 164, 97,
|
||||
],
|
||||
[
|
||||
182, 25, 214, 145, 150, 6, 219, 238, 38, 49, 166, 24, 255, 75, 56, 6, 31, 46, 163, 172,
|
||||
120, 213, 141, 74, 137, 21, 191, 169, 116, 50, 172, 71,
|
||||
],
|
||||
[
|
||||
52, 69, 229, 255, 230, 237, 127, 41, 223, 116, 249, 52, 228, 220, 231, 233, 38, 66, 188,
|
||||
188, 141, 176, 216, 204, 129, 209, 214, 199, 116, 203, 218, 0,
|
||||
],
|
||||
[
|
||||
34, 210, 93, 96, 203, 255, 15, 139, 0, 56, 109, 64, 224, 255, 168, 143, 235, 238, 144, 247,
|
||||
57, 237, 244, 210, 215, 160, 98, 250, 108, 101, 127, 130,
|
||||
],
|
||||
[
|
||||
57, 218, 36, 154, 181, 246, 243, 88, 152, 87, 31, 19, 81, 50, 15, 16, 66, 65, 78, 191, 194,
|
||||
47, 162, 102, 108, 254, 215, 38, 131, 209, 233, 88,
|
||||
],
|
||||
[
|
||||
155, 30, 22, 245, 84, 30, 111, 118, 197, 124, 53, 108, 138, 34, 183, 149, 93, 161, 54, 20,
|
||||
81, 52, 135, 241, 96, 199, 21, 156, 123, 208, 105, 244,
|
||||
],
|
||||
[
|
||||
173, 212, 84, 212, 76, 106, 120, 123, 235, 152, 249, 21, 121, 57, 137, 70, 8, 109, 9, 102,
|
||||
153, 66, 109, 61, 116, 176, 20, 123, 52, 240, 173, 143,
|
||||
],
|
||||
[
|
||||
148, 174, 121, 229, 202, 140, 51, 7, 46, 210, 185, 87, 169, 223, 189, 164, 252, 59, 133,
|
||||
226, 4, 99, 142, 243, 43, 14, 151, 8, 159, 60, 235, 60,
|
||||
],
|
||||
[
|
||||
175, 132, 242, 248, 185, 9, 188, 62, 34, 213, 240, 199, 176, 177, 75, 99, 187, 215, 70,
|
||||
226, 72, 67, 45, 66, 103, 218, 50, 31, 1, 52, 216, 168,
|
||||
],
|
||||
[
|
||||
248, 211, 204, 204, 180, 196, 230, 213, 226, 254, 251, 255, 140, 104, 170, 245, 141, 86,
|
||||
82, 142, 59, 109, 142, 191, 7, 180, 33, 12, 239, 230, 161, 241,
|
||||
],
|
||||
[
|
||||
178, 137, 222, 169, 44, 165, 171, 165, 242, 225, 137, 26, 26, 241, 27, 226, 121, 20, 196,
|
||||
136, 84, 219, 15, 229, 180, 187, 149, 193, 55, 224, 242, 214,
|
||||
],
|
||||
[
|
||||
110, 52, 11, 156, 255, 179, 122, 152, 156, 165, 68, 230, 187, 120, 10, 44, 120, 144, 29,
|
||||
63, 179, 55, 56, 118, 133, 17, 163, 6, 23, 175, 160, 29,
|
||||
],
|
||||
];
|
||||
211
risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs
Normal file
211
risc0-selective-privacy-poc/sparse_merkle_tree/src/lib.rs
Normal file
@@ -0,0 +1,211 @@
|
||||
mod default_hashes;
|
||||
|
||||
use default_hashes::DEFAULT_HASHES;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
const TREE_DEPTH: usize = 32;
|
||||
const ONE_HASH: [u8; 32] = [
|
||||
75, 245, 18, 47, 52, 69, 84, 197, 59, 222, 46, 187, 140, 210, 183, 227, 209, 96, 10, 214, 49, 195, 133, 165, 215,
|
||||
204, 226, 60, 119, 133, 69, 154,
|
||||
];
|
||||
|
||||
/// Compute parent as the hash of two child nodes
|
||||
fn hash_node(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(left);
|
||||
hasher.update(right);
|
||||
hasher.finalize().into()
|
||||
}
|
||||
|
||||
/// Sparse Merkle Tree with 2^32 leaves
|
||||
pub struct SparseMerkleTree {
|
||||
values: HashSet<u32>,
|
||||
node_map: HashMap<(usize, u32), [u8; 32]>,
|
||||
}
|
||||
|
||||
impl SparseMerkleTree {
|
||||
pub fn new(values: HashSet<u32>) -> Self {
|
||||
let node_map = Self::node_map(&values);
|
||||
Self { values, node_map }
|
||||
}
|
||||
|
||||
pub fn new_empty() -> Self {
|
||||
Self::new(HashSet::new())
|
||||
}
|
||||
|
||||
pub fn add_value(&mut self, new_value: u32) {
|
||||
if self.values.insert(new_value) {
|
||||
self.node_map = Self::node_map(&self.values);
|
||||
}
|
||||
}
|
||||
|
||||
fn node_map(values: &HashSet<u32>) -> HashMap<(usize, u32), [u8; 32]> {
|
||||
let mut nodes: HashMap<(usize, u32), [u8; 32]> = HashMap::new();
|
||||
|
||||
// Start from occupied leaves
|
||||
for &leaf_index in values {
|
||||
nodes.insert((TREE_DEPTH, leaf_index), ONE_HASH);
|
||||
}
|
||||
|
||||
// Build tree bottom-up
|
||||
for depth in (0..TREE_DEPTH).rev() {
|
||||
let mut next_level = HashMap::new();
|
||||
let indices: Vec<u32> = nodes
|
||||
.keys()
|
||||
.filter(|(d, _)| *d == depth + 1)
|
||||
.map(|(_, i)| i >> 1) // parent index
|
||||
.collect();
|
||||
|
||||
for &parent_index in indices.iter() {
|
||||
let left_index = parent_index << 1;
|
||||
let right_index = left_index | 1;
|
||||
|
||||
let left = nodes.get(&(depth + 1, left_index)).unwrap_or(&DEFAULT_HASHES[depth]);
|
||||
let right = nodes.get(&(depth + 1, right_index)).unwrap_or(&DEFAULT_HASHES[depth]);
|
||||
|
||||
if left != &DEFAULT_HASHES[depth] || right != &DEFAULT_HASHES[depth] {
|
||||
let h = hash_node(left, right);
|
||||
next_level.insert((depth, parent_index), h);
|
||||
}
|
||||
}
|
||||
|
||||
nodes.extend(next_level);
|
||||
}
|
||||
nodes
|
||||
}
|
||||
|
||||
pub fn root(&self) -> [u8; 32] {
|
||||
self.node_map.get(&(0, 0)).cloned().unwrap_or(DEFAULT_HASHES[0])
|
||||
}
|
||||
|
||||
pub fn get_authentication_path_for_value(&self, value: u32) -> [[u8; 32]; 32] {
|
||||
let mut path = [[0u8; 32]; 32];
|
||||
|
||||
let mut current_index = value;
|
||||
|
||||
for depth in (0..32).rev() {
|
||||
let sibling_index = current_index ^ 1;
|
||||
|
||||
let sibling_hash = self
|
||||
.node_map
|
||||
.get(&(depth + 1, sibling_index))
|
||||
.cloned()
|
||||
.unwrap_or(DEFAULT_HASHES[depth]);
|
||||
|
||||
path[31 - depth] = sibling_hash;
|
||||
current_index >>= 1;
|
||||
}
|
||||
|
||||
path
|
||||
}
|
||||
|
||||
pub fn values(&self) -> HashSet<u32> {
|
||||
self.values.clone()
|
||||
}
|
||||
|
||||
pub fn verify_value_is_in_set(value: u32, path: [[u8; 32]; 32], root: [u8; 32]) -> bool {
|
||||
let mut hash = ONE_HASH;
|
||||
let mut current_index = value;
|
||||
for path_value in path.iter() {
|
||||
if current_index & 1 == 0 {
|
||||
hash = hash_node(&hash, path_value);
|
||||
} else {
|
||||
hash = hash_node(path_value, &hash);
|
||||
}
|
||||
current_index >>= 1;
|
||||
}
|
||||
root == hash
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const ZERO_HASH: [u8; 32] = [
|
||||
110, 52, 11, 156, 255, 179, 122, 152, 156, 165, 68, 230, 187, 120, 10, 44, 120, 144, 29, 63, 179, 55, 56, 118,
|
||||
133, 17, 163, 6, 23, 175, 160, 29,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_default_hashes() {
|
||||
assert_eq!(DEFAULT_HASHES[TREE_DEPTH - 1], ZERO_HASH);
|
||||
assert_eq!(
|
||||
DEFAULT_HASHES[0],
|
||||
[
|
||||
157, 148, 193, 146, 141, 23, 128, 25, 196, 90, 21, 193, 179, 235, 209, 157, 146, 64, 171, 100, 192, 44,
|
||||
121, 46, 78, 53, 190, 198, 191, 82, 85, 16
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_tree() {
|
||||
let empty_tree = SparseMerkleTree::new_empty();
|
||||
assert_eq!(empty_tree.root(), DEFAULT_HASHES[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tree_1() {
|
||||
let values: HashSet<u32> = vec![0, 1, 2, 3].into_iter().collect();
|
||||
let tree = SparseMerkleTree::new(values);
|
||||
assert_eq!(
|
||||
tree.root(),
|
||||
[
|
||||
109, 94, 224, 93, 195, 77, 137, 36, 108, 105, 177, 22, 212, 17, 160, 255, 224, 61, 191, 17, 129, 10,
|
||||
26, 76, 197, 42, 230, 160, 80, 44, 101, 184
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tree_2() {
|
||||
let values: HashSet<u32> = vec![2147483648, 2147483649, 2147483650, 2147483651]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let tree = SparseMerkleTree::new(values);
|
||||
|
||||
assert_eq!(
|
||||
tree.root(),
|
||||
[
|
||||
36, 178, 159, 245, 165, 76, 242, 85, 25, 218, 149, 135, 194, 127, 130, 201, 219, 187, 167, 216, 1, 222,
|
||||
234, 197, 152, 156, 243, 174, 68, 27, 114, 8
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tree_3() {
|
||||
let values: HashSet<u32> = vec![2147483648, 0, 1, 2147483649].into_iter().collect();
|
||||
let tree = SparseMerkleTree::new(values);
|
||||
|
||||
assert_eq!(
|
||||
tree.root(),
|
||||
[
|
||||
148, 76, 190, 191, 248, 243, 89, 40, 197, 157, 206, 23, 58, 197, 86, 169, 225, 217, 110, 166, 54, 10,
|
||||
245, 175, 168, 4, 145, 220, 30, 210, 67, 113
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_auth_path() {
|
||||
let values: HashSet<u32> = vec![0, 1, 2, 3, 1337].into_iter().collect();
|
||||
let mut tree = SparseMerkleTree::new(values);
|
||||
let root = tree.root();
|
||||
let path = tree.get_authentication_path_for_value(0);
|
||||
assert!(SparseMerkleTree::verify_value_is_in_set(0, path, root));
|
||||
let path = tree.get_authentication_path_for_value(1);
|
||||
assert!(SparseMerkleTree::verify_value_is_in_set(1, path, root));
|
||||
let path = tree.get_authentication_path_for_value(1337);
|
||||
assert!(SparseMerkleTree::verify_value_is_in_set(1337, path, root));
|
||||
let path = tree.get_authentication_path_for_value(1338);
|
||||
assert!(!SparseMerkleTree::verify_value_is_in_set(1338, path, root));
|
||||
|
||||
tree.add_value(1338);
|
||||
let path = tree.get_authentication_path_for_value(1338);
|
||||
let root = tree.root();
|
||||
assert!(SparseMerkleTree::verify_value_is_in_set(1338, path, root));
|
||||
}
|
||||
}
|
||||
16
risc0-selective-privacy-poc/src/error.rs
Normal file
16
risc0-selective-privacy-poc/src/error.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
BadInput,
|
||||
Risc0(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Error::BadInput => write!(f, "Bad input"),
|
||||
Error::Risc0(_) => write!(f, "Risc0 error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
134
risc0-selective-privacy-poc/src/lib.rs
Normal file
134
risc0-selective-privacy-poc/src/lib.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
use core::{
|
||||
account::Account,
|
||||
types::{Nonce, ProgramOutput},
|
||||
visibility::AccountVisibility,
|
||||
};
|
||||
use program_methods::{OUTER_ELF, OUTER_ID};
|
||||
use rand::{rngs::OsRng, Rng};
|
||||
use risc0_zkvm::{default_executor, default_prover, ExecutorEnv, ExecutorEnvBuilder, Receipt};
|
||||
|
||||
pub mod error;
|
||||
pub mod program;
|
||||
pub use error::Error;
|
||||
|
||||
pub use program::Program;
|
||||
|
||||
pub fn new_random_nonce() -> Nonce {
|
||||
let mut rng = OsRng;
|
||||
std::array::from_fn(|_| rng.gen())
|
||||
}
|
||||
|
||||
/// Writes inputs to `env_builder` in the order expected by the programs
|
||||
fn write_inputs<P: Program>(
|
||||
input_accounts: &[Account],
|
||||
instruction_data: P::InstructionData,
|
||||
env_builder: &mut ExecutorEnvBuilder,
|
||||
) -> Result<(), Error> {
|
||||
let input_accounts = input_accounts.to_vec();
|
||||
env_builder.write(&input_accounts).map_err(|_| Error::BadInput)?;
|
||||
env_builder.write(&instruction_data).map_err(|_| Error::BadInput)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Executes and proves the program `P`.
|
||||
/// Returns the proof and the list of accounts pre and post states
|
||||
fn execute_and_prove_inner<P: Program>(
|
||||
input_accounts: &[Account],
|
||||
instruction_data: P::InstructionData,
|
||||
) -> Result<Receipt, Error> {
|
||||
// Write inputs to the program
|
||||
let mut env_builder = ExecutorEnv::builder();
|
||||
write_inputs::<P>(input_accounts, instruction_data, &mut env_builder)?;
|
||||
let env = env_builder.build().unwrap();
|
||||
|
||||
// Prove the program
|
||||
let prover = default_prover();
|
||||
let prove_info = prover
|
||||
.prove(env, P::PROGRAM_ELF)
|
||||
.map_err(|e| Error::Risc0(e.to_string()))?;
|
||||
Ok(prove_info.receipt)
|
||||
}
|
||||
|
||||
/// Builds the private outputs from the results of the execution of an inner program.
|
||||
/// Filters private outputs and populates the nonces with the ones provided.
|
||||
fn build_private_outputs_from_inner_results(
|
||||
inner_program_output: &ProgramOutput,
|
||||
visibilities: &[AccountVisibility],
|
||||
nonces: &[Nonce],
|
||||
) -> Vec<Account> {
|
||||
inner_program_output
|
||||
.accounts_post
|
||||
.iter()
|
||||
.zip(visibilities)
|
||||
.zip(nonces)
|
||||
.filter(|((_, visibility), _)| matches!(visibility, AccountVisibility::Private(_)))
|
||||
.map(|((account, _), nonce)| {
|
||||
let mut this = account.clone();
|
||||
this.nonce = *nonce;
|
||||
this
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Executes the program `P` without generating a proof.
|
||||
/// Returns the list of accounts pre and post states.
|
||||
pub fn execute_onchain<P: Program>(
|
||||
input_accounts: &[Account],
|
||||
instruction_data: P::InstructionData,
|
||||
) -> Result<ProgramOutput, Error> {
|
||||
// Write inputs to the program
|
||||
let mut env_builder = ExecutorEnv::builder();
|
||||
write_inputs::<P>(input_accounts, instruction_data, &mut env_builder)?;
|
||||
let env = env_builder.build().unwrap();
|
||||
|
||||
// Execute the program (without proving)
|
||||
let executor = default_executor();
|
||||
let session_info = executor
|
||||
.execute(env, P::PROGRAM_ELF)
|
||||
.map_err(|e| Error::Risc0(e.to_string()))?;
|
||||
|
||||
// Get (inputs and) outputs
|
||||
session_info.journal.decode().map_err(|e| Error::Risc0(e.to_string()))
|
||||
}
|
||||
|
||||
/// Executes and proves the inner program `P` and executes and proves the outer program on top of it.
|
||||
/// Returns the proof of execution of the outer program and the list of new private accounts
|
||||
/// resulted from this execution.
|
||||
pub fn execute_offchain<P: Program>(
|
||||
inputs: &[Account],
|
||||
instruction_data: P::InstructionData,
|
||||
visibilities: &[AccountVisibility],
|
||||
commitment_tree_root: [u32; 8],
|
||||
) -> Result<(Receipt, Vec<Account>), Error> {
|
||||
// Prove inner program and get post state of the accounts
|
||||
let inner_receipt = execute_and_prove_inner::<P>(inputs, instruction_data)?;
|
||||
let inner_program_output: ProgramOutput = inner_receipt
|
||||
.journal
|
||||
.decode()
|
||||
.map_err(|e| Error::Risc0(e.to_string()))?;
|
||||
|
||||
// Sample fresh random nonces for the outputs of this execution
|
||||
let output_nonces: Vec<_> = (0..inputs.len()).map(|_| new_random_nonce()).collect();
|
||||
|
||||
// Prove outer program.
|
||||
let mut env_builder = ExecutorEnv::builder();
|
||||
env_builder.add_assumption(inner_receipt);
|
||||
env_builder.write(&inner_program_output).unwrap();
|
||||
env_builder.write(&visibilities).unwrap();
|
||||
env_builder.write(&output_nonces).unwrap();
|
||||
env_builder.write(&commitment_tree_root).unwrap();
|
||||
env_builder.write(&P::PROGRAM_ID).unwrap();
|
||||
let env = env_builder.build().unwrap();
|
||||
let prover = default_prover();
|
||||
let prove_info = prover.prove(env, OUTER_ELF).unwrap();
|
||||
|
||||
// Build private accounts.
|
||||
let private_outputs = build_private_outputs_from_inner_results(&inner_program_output, visibilities, &output_nonces);
|
||||
|
||||
Ok((prove_info.receipt, private_outputs))
|
||||
}
|
||||
|
||||
/// Verifies a proof of the outer program for the given parameters.
|
||||
pub fn verify_privacy_execution(receipt: Receipt) -> Result<(), Error> {
|
||||
receipt.verify(OUTER_ID).map_err(|e| Error::Risc0(e.to_string()))
|
||||
}
|
||||
35
risc0-selective-privacy-poc/src/program/mod.rs
Normal file
35
risc0-selective-privacy-poc/src/program/mod.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use core::types::ProgramId;
|
||||
|
||||
use program_methods::{PINATA_ELF, PINATA_ID, TRANSFER_ELF, TRANSFER_ID, TRANSFER_MULTIPLE_ELF, TRANSFER_MULTIPLE_ID};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A trait to be implemented by inner programs.
|
||||
pub trait Program {
|
||||
const PROGRAM_ID: ProgramId;
|
||||
const PROGRAM_ELF: &[u8];
|
||||
type InstructionData: Serialize + for<'de> Deserialize<'de>;
|
||||
}
|
||||
|
||||
pub struct TransferProgram;
|
||||
impl Program for TransferProgram {
|
||||
const PROGRAM_ID: ProgramId = TRANSFER_ID;
|
||||
const PROGRAM_ELF: &[u8] = TRANSFER_ELF;
|
||||
/// Amount to transfer
|
||||
type InstructionData = u128;
|
||||
}
|
||||
|
||||
pub struct TransferMultipleProgram;
|
||||
impl Program for TransferMultipleProgram {
|
||||
const PROGRAM_ID: ProgramId = TRANSFER_MULTIPLE_ID;
|
||||
const PROGRAM_ELF: &[u8] = TRANSFER_MULTIPLE_ELF;
|
||||
/// Amounts to transfer
|
||||
type InstructionData = Vec<u128>;
|
||||
}
|
||||
|
||||
pub struct PinataProgram;
|
||||
impl Program for PinataProgram {
|
||||
const PROGRAM_ID: ProgramId = PINATA_ID;
|
||||
const PROGRAM_ELF: &[u8] = PINATA_ELF;
|
||||
/// Preimage of target hash to win prize
|
||||
type InstructionData = Vec<u32>;
|
||||
}
|
||||
Reference in New Issue
Block a user