Add zone funds spending logic (#2)
* Add zone funds spending logic First iteration of the zone funds death constraints * Add zone logic support for withdrawal txs * move zone proofs out of cl crate * Address review comments
This commit is contained in:
parent
221d7102a9
commit
e6402007f0
|
@ -51,8 +51,8 @@ impl InputWitness {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
pub fn to_bytes(&self) -> [u8; 64] {
|
pub fn to_bytes(&self) -> [u8; 96] {
|
||||||
let mut bytes = [0u8; 64];
|
let mut bytes = [0u8; 96];
|
||||||
bytes[..32].copy_from_slice(self.nullifier.as_bytes());
|
bytes[..32].copy_from_slice(self.nullifier.as_bytes());
|
||||||
bytes[32..64].copy_from_slice(&self.balance.to_bytes());
|
bytes[32..64].copy_from_slice(&self.balance.to_bytes());
|
||||||
bytes[64..96].copy_from_slice(&self.death_cm.0);
|
bytes[64..96].copy_from_slice(&self.death_cm.0);
|
||||||
|
|
|
@ -51,6 +51,10 @@ impl NullifierSecret {
|
||||||
let commit_bytes: [u8; 32] = hasher.finalize().into();
|
let commit_bytes: [u8; 32] = hasher.finalize().into();
|
||||||
NullifierCommitment(commit_bytes)
|
NullifierCommitment(commit_bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(bytes: [u8; 16]) -> Self {
|
||||||
|
Self(bytes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NullifierCommitment {
|
impl NullifierCommitment {
|
||||||
|
@ -73,6 +77,10 @@ impl NullifierNonce {
|
||||||
pub fn as_bytes(&self) -> &[u8; 16] {
|
pub fn as_bytes(&self) -> &[u8; 16] {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(bytes: [u8; 16]) -> Self {
|
||||||
|
Self(bytes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Nullifier {
|
impl Nullifier {
|
||||||
|
@ -86,7 +94,7 @@ impl Nullifier {
|
||||||
Self(nf_bytes)
|
Self(nf_bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn as_bytes(&self) -> &[u8; 32] {
|
pub fn as_bytes(&self) -> &[u8; 32] {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,15 @@ const MAX_OUTPUTS: usize = 8;
|
||||||
|
|
||||||
/// The partial transaction commitment couples an input to a partial transaction.
|
/// The partial transaction commitment couples an input to a partial transaction.
|
||||||
/// Prevents partial tx unbundling.
|
/// Prevents partial tx unbundling.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||||
pub struct PtxRoot(pub [u8; 32]);
|
pub struct PtxRoot(pub [u8; 32]);
|
||||||
|
|
||||||
|
impl From<[u8; 32]> for PtxRoot {
|
||||||
|
fn from(bytes: [u8; 32]) -> Self {
|
||||||
|
Self(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PtxRoot {
|
impl PtxRoot {
|
||||||
pub fn random(mut rng: impl RngCore) -> Self {
|
pub fn random(mut rng: impl RngCore) -> Self {
|
||||||
let mut sk = [0u8; 32];
|
let mut sk = [0u8; 32];
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
use cl::{Nullifier, PtxRoot};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct DeathConstraintPublic {
|
||||||
|
pub cm_root: [u8; 32],
|
||||||
|
pub nf: Nullifier,
|
||||||
|
pub ptx_root: PtxRoot,
|
||||||
|
}
|
|
@ -1 +1,3 @@
|
||||||
|
pub mod death_constraint;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
|
pub mod ptx;
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
use cl::{merkle, InputWitness, OutputWitness, PtxRoot};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
/// An input to a partial transaction
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct PartialTxInputPrivate {
|
||||||
|
pub input: InputWitness,
|
||||||
|
pub cm_path: Vec<merkle::PathNode>,
|
||||||
|
pub ptx_path: Vec<merkle::PathNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialTxInputPrivate {
|
||||||
|
pub fn ptx_root(&self) -> PtxRoot {
|
||||||
|
let leaf = merkle::leaf(&self.input.commit().to_bytes());
|
||||||
|
PtxRoot(merkle::path_root(leaf, &self.ptx_path))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cm_root(&self) -> [u8; 32] {
|
||||||
|
let leaf = merkle::leaf(self.input.to_output_witness().commit_note().as_bytes());
|
||||||
|
merkle::path_root(leaf, &self.cm_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An output to a partial transaction
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct PartialTxOutputPrivate {
|
||||||
|
pub output: OutputWitness,
|
||||||
|
pub ptx_path: Vec<merkle::PathNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialTxOutputPrivate {
|
||||||
|
pub fn ptx_root(&self) -> PtxRoot {
|
||||||
|
let leaf = merkle::leaf(&self.output.commit().to_bytes());
|
||||||
|
PtxRoot(merkle::path_root(leaf, &self.ptx_path))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [ "common","host", "methods"]
|
members = [ "common","host", "methods", "proof_statements", "risc0_proofs"]
|
||||||
|
|
||||||
# Always optimize; building and running the guest takes much longer without optimization.
|
# Always optimize; building and running the guest takes much longer without optimization.
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|
|
@ -5,3 +5,5 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
cl = { path = "../../cl/cl" }
|
||||||
|
proof_statements = { path = "../proof_statements", package = "goas_proof_statements" }
|
|
@ -1,28 +1,106 @@
|
||||||
use serde::{Serialize, Deserialize};
|
use cl::nullifier::{Nullifier, NullifierCommitment};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
// TODO: sparse merkle tree
|
||||||
|
pub const MAX_BALANCES: usize = 1 << 8;
|
||||||
|
pub const MAX_TXS: usize = 1 << 8;
|
||||||
|
pub const MAX_EVENTS: usize = 1 << 8;
|
||||||
|
|
||||||
// state of the zone
|
// state of the zone
|
||||||
pub type State = BTreeMap<u32, u32>;
|
|
||||||
// list of all inputs that were executed up to this point
|
|
||||||
pub type Journal = Vec<Input>;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub struct StateCommitment([u8; 32]);
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct StateWitness {
|
||||||
|
pub balances: BTreeMap<u32, u32>,
|
||||||
|
pub included_txs: Vec<Input>,
|
||||||
|
pub output_events: Vec<Event>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StateWitness {
|
||||||
|
pub fn commit(&self) -> StateCommitment {
|
||||||
|
let root = self.balances_root();
|
||||||
|
let root = cl::merkle::node(self.events_root(), root);
|
||||||
|
let root = cl::merkle::node(self.included_txs_root(), root);
|
||||||
|
StateCommitment(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn events_root(&self) -> [u8; 32] {
|
||||||
|
let event_bytes = Vec::from_iter(
|
||||||
|
self.output_events
|
||||||
|
.iter()
|
||||||
|
.map(Event::to_bytes)
|
||||||
|
.map(Vec::from_iter),
|
||||||
|
);
|
||||||
|
let event_merkle_leaves = cl::merkle::padded_leaves(&event_bytes);
|
||||||
|
cl::merkle::root::<MAX_EVENTS>(event_merkle_leaves)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn included_txs_root(&self) -> [u8; 32] {
|
||||||
|
let tx_bytes = Vec::from_iter(
|
||||||
|
self.included_txs
|
||||||
|
.iter()
|
||||||
|
.map(Input::to_bytes)
|
||||||
|
.map(Vec::from_iter),
|
||||||
|
);
|
||||||
|
let tx_merkle_leaves = cl::merkle::padded_leaves(&tx_bytes);
|
||||||
|
cl::merkle::root::<MAX_TXS>(tx_merkle_leaves)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn balances_root(&self) -> [u8; 32] {
|
||||||
|
let balance_bytes = Vec::from_iter(self.balances.iter().map(|(k, v)| {
|
||||||
|
let mut bytes = [0; 8];
|
||||||
|
bytes.copy_from_slice(&k.to_le_bytes());
|
||||||
|
bytes[8..].copy_from_slice(&v.to_le_bytes());
|
||||||
|
bytes.to_vec()
|
||||||
|
}));
|
||||||
|
let balance_merkle_leaves = cl::merkle::padded_leaves(&balance_bytes);
|
||||||
|
cl::merkle::root::<MAX_BALANCES>(balance_merkle_leaves)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub struct Withdraw {
|
||||||
|
pub from: u32,
|
||||||
|
pub amount: u32,
|
||||||
|
pub to: NullifierCommitment,
|
||||||
|
pub nf: Nullifier,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Withdraw {
|
||||||
|
pub fn to_bytes(&self) -> [u8; 72] {
|
||||||
|
let mut bytes = [0; 72];
|
||||||
|
bytes[0..4].copy_from_slice(&self.from.to_le_bytes());
|
||||||
|
bytes[4..8].copy_from_slice(&self.amount.to_le_bytes());
|
||||||
|
bytes[8..40].copy_from_slice(self.to.as_bytes());
|
||||||
|
bytes[40..72].copy_from_slice(self.nf.as_bytes());
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
pub enum Input {
|
pub enum Input {
|
||||||
Transfer { from: u32, to: u32, amount: u32 },
|
Withdraw(Withdraw),
|
||||||
None,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Input {
|
||||||
/// State transition function of the zone
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
pub fn stf(mut state: State, input: Input) -> State {
|
match self {
|
||||||
match input {
|
Input::Withdraw(withdraw) => withdraw.to_bytes().to_vec(),
|
||||||
Input::Transfer { from, to, amount } => {
|
}
|
||||||
// compute transfer
|
}
|
||||||
let from = state.entry(from).or_insert(0);
|
}
|
||||||
*from = from.checked_sub(amount).unwrap();
|
|
||||||
*state.entry(to).or_insert(0) += amount;
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub enum Event {
|
||||||
|
Spend(proof_statements::zone_funds::Spend),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Event {
|
||||||
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
match self {
|
||||||
|
Event::Spend(spend) => spend.to_bytes().to_vec(),
|
||||||
}
|
}
|
||||||
Input::None => {}
|
|
||||||
}
|
}
|
||||||
state
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,77 +1,28 @@
|
||||||
// These constants represent the RISC-V ELF and the image ID generated by risc0-build.
|
// 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.
|
// The ELF is used for proving and the ID is used for verification.
|
||||||
use blake2::{Blake2s256, Digest};
|
|
||||||
use common::*;
|
use common::*;
|
||||||
use risc0_zkvm::{default_prover, ExecutorEnv};
|
use risc0_zkvm::{default_prover, ExecutorEnv};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
enum Action {
|
enum Action {
|
||||||
Stf,
|
Stf {
|
||||||
|
// path to bincode-encoded state witness
|
||||||
|
state: PathBuf,
|
||||||
|
// path to bincode-encoded inputs
|
||||||
|
inputs: PathBuf,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stf_prove_stark() {
|
fn stf_prove_stark(state: StateWitness, inputs: Vec<Input>) {
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
|
|
||||||
let state: State = [(0, 1000)].into_iter().collect();
|
|
||||||
let journal = vec![];
|
|
||||||
let zone_input = Input::Transfer {
|
|
||||||
from: 0,
|
|
||||||
to: 1,
|
|
||||||
amount: 10,
|
|
||||||
};
|
|
||||||
|
|
||||||
let in_state_cm = calculate_state_hash(&state);
|
|
||||||
let in_journal_cm = calculate_journal_hash(&journal);
|
|
||||||
let in_state_root = cl::merkle::node(in_state_cm, in_journal_cm);
|
|
||||||
let in_note = cl::NoteWitness::new(1, "ZONE", in_state_root, &mut rng);
|
|
||||||
|
|
||||||
let mut out_journal = journal.clone();
|
|
||||||
out_journal.push(zone_input);
|
|
||||||
|
|
||||||
let out_state_cm = calculate_state_hash(&stf(state.clone(), zone_input));
|
|
||||||
let out_journal_cm = calculate_journal_hash(&out_journal);
|
|
||||||
let out_state_root = cl::merkle::node(out_state_cm, out_journal_cm);
|
|
||||||
let out_note = cl::NoteWitness::new(1, "ZONE", out_state_root, &mut rng);
|
|
||||||
|
|
||||||
let input = cl::InputWitness::random(in_note, &mut rng);
|
|
||||||
let output = cl::OutputWitness::random(
|
|
||||||
out_note,
|
|
||||||
cl::NullifierSecret::random(&mut rng).commit(),
|
|
||||||
&mut rng,
|
|
||||||
);
|
|
||||||
let ptx = cl::PartialTx::from_witness(cl::PartialTxWitness {
|
|
||||||
inputs: vec![input.clone()],
|
|
||||||
outputs: vec![output.clone()],
|
|
||||||
});
|
|
||||||
|
|
||||||
let ptx_root = ptx.root().0;
|
|
||||||
let in_ptx_path = ptx.input_merkle_path(0);
|
|
||||||
let out_ptx_path = ptx.output_merkle_path(0);
|
|
||||||
|
|
||||||
let env = ExecutorEnv::builder()
|
let env = ExecutorEnv::builder()
|
||||||
.write(&ptx_root)
|
.write(&inputs)
|
||||||
.unwrap()
|
|
||||||
.write(&ptx.input_root())
|
|
||||||
.unwrap()
|
|
||||||
.write(&ptx.output_root())
|
|
||||||
.unwrap()
|
|
||||||
.write(&in_ptx_path)
|
|
||||||
.unwrap()
|
|
||||||
.write(&out_ptx_path)
|
|
||||||
.unwrap()
|
|
||||||
.write(&input)
|
|
||||||
.unwrap()
|
|
||||||
.write(&output)
|
|
||||||
.unwrap()
|
|
||||||
.write(&zone_input)
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.write(&state)
|
.write(&state)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.write(&journal)
|
|
||||||
.unwrap()
|
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -109,16 +60,10 @@ fn main() {
|
||||||
let action = Action::parse();
|
let action = Action::parse();
|
||||||
|
|
||||||
match action {
|
match action {
|
||||||
Action::Stf => stf_prove_stark(),
|
Action::Stf { state, inputs } => {
|
||||||
|
let state = bincode::deserialize(&std::fs::read(state).unwrap()).unwrap();
|
||||||
|
let inputs = bincode::deserialize(&std::fs::read(inputs).unwrap()).unwrap();
|
||||||
|
stf_prove_stark(state, inputs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_state_hash(state: &State) -> [u8; 32] {
|
|
||||||
let bytes = bincode::serialize(state).unwrap();
|
|
||||||
Blake2s256::digest(&bytes).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_journal_hash(journal: &Journal) -> [u8; 32] {
|
|
||||||
let bytes = bincode::serialize(journal).unwrap();
|
|
||||||
Blake2s256::digest(&bytes).into()
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||||
bincode = "1"
|
bincode = "1"
|
||||||
common = { path = "../../common" }
|
common = { path = "../../common" }
|
||||||
cl = { path = "../../../cl/cl" }
|
cl = { path = "../../../cl/cl" }
|
||||||
|
proof_statements = { path = "../../proof_statements", package = "goas_proof_statements" }
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# Placing these patch statement in the workspace Cargo.toml will add RISC Zero SHA-256 and bigint
|
# Placing these patch statement in the workspace Cargo.toml will add RISC Zero SHA-256 and bigint
|
||||||
|
|
|
@ -1,113 +1,40 @@
|
||||||
use blake2::{Blake2s256, Digest};
|
|
||||||
use cl::input::InputWitness;
|
|
||||||
use cl::merkle;
|
|
||||||
use cl::output::OutputWitness;
|
|
||||||
use common::*;
|
use common::*;
|
||||||
|
use proof_statements::zone_funds::Spend;
|
||||||
use risc0_zkvm::guest::env;
|
use risc0_zkvm::guest::env;
|
||||||
|
|
||||||
/// Public Inputs:
|
fn withdraw(mut state: StateWitness, withdraw: Withdraw) -> StateWitness {
|
||||||
/// * ptx_root: the root of the partial tx merkle tree of inputs/outputs
|
state.included_txs.push(Input::Withdraw(withdraw));
|
||||||
/// Private inputs:
|
|
||||||
/// TODO
|
|
||||||
|
|
||||||
fn execute(
|
let Withdraw {
|
||||||
ptx_root: [u8; 32],
|
from,
|
||||||
input_root: [u8; 32],
|
amount,
|
||||||
output_root: [u8; 32],
|
to,
|
||||||
in_ptx_path: Vec<merkle::PathNode>,
|
nf,
|
||||||
out_ptx_path: Vec<merkle::PathNode>,
|
} = withdraw;
|
||||||
in_note: InputWitness,
|
|
||||||
out_note: OutputWitness,
|
|
||||||
input: Input,
|
|
||||||
state: State,
|
|
||||||
mut journal: Journal,
|
|
||||||
) {
|
|
||||||
// verify ptx/cl preconditions
|
|
||||||
eprintln!("start exec: {}", env::cycle_count());
|
|
||||||
assert_eq!(ptx_root, merkle::node(input_root, output_root));
|
|
||||||
eprintln!("ptx_root: {}", env::cycle_count());
|
|
||||||
|
|
||||||
// Glue the zone and the cl together, specifically, it verifies the note requesting
|
let from_balance = state.balances.entry(from).or_insert(0);
|
||||||
// a transfer is included as part of the same transaction in the cl
|
*from_balance = from.checked_sub(amount).expect("insufficient funds in account");
|
||||||
let in_comm = in_note.commit().to_bytes();
|
let spend_auth = Spend {
|
||||||
eprintln!("input comm: {}", env::cycle_count());
|
amount: amount.into(),
|
||||||
|
to,
|
||||||
|
nf,
|
||||||
|
};
|
||||||
|
|
||||||
assert_eq!(
|
state.output_events.push(Event::Spend(spend_auth));
|
||||||
merkle::path_root(merkle::leaf(&in_comm), &in_ptx_path),
|
state
|
||||||
input_root
|
|
||||||
);
|
|
||||||
eprintln!("input merkle path: {}", env::cycle_count());
|
|
||||||
|
|
||||||
// check the commitments match the actual data
|
|
||||||
let state_cm = calculate_state_hash(&state);
|
|
||||||
let journal_cm = calculate_journal_hash(&journal);
|
|
||||||
let state_root = merkle::node(state_cm, journal_cm);
|
|
||||||
assert_eq!(state_root, in_note.note.state);
|
|
||||||
eprintln!("input state root: {}", env::cycle_count());
|
|
||||||
|
|
||||||
// then run the state transition function
|
|
||||||
let state = stf(state, input);
|
|
||||||
journal.push(input);
|
|
||||||
eprintln!("stf: {}", env::cycle_count());
|
|
||||||
|
|
||||||
// verifying ptx/cl postconditions
|
|
||||||
|
|
||||||
let out_state_cm = calculate_state_hash(&state);
|
|
||||||
let out_journal_cm = calculate_journal_hash(&journal);
|
|
||||||
let out_state_root = merkle::node(out_state_cm, out_journal_cm);
|
|
||||||
// TODO: verify death constraints are propagated
|
|
||||||
assert_eq!(out_state_root, out_note.note.state);
|
|
||||||
eprintln!("out state root: {}", env::cycle_count());
|
|
||||||
|
|
||||||
// Glue the zone and the cl together, specifically, it verifies an output note
|
|
||||||
// containing the zone state is included as part of the same transaction in the cl
|
|
||||||
// (this is done in the death condition to disallow burning)
|
|
||||||
let out_comm = out_note.commit().to_bytes();
|
|
||||||
eprintln!("output comm: {}", env::cycle_count());
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
merkle::path_root(merkle::leaf(&out_comm), &out_ptx_path),
|
|
||||||
output_root
|
|
||||||
);
|
|
||||||
eprintln!("out merkle proof: {}", env::cycle_count());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// public input
|
let inputs: Vec<Input> = env::read();
|
||||||
let ptx_root: [u8; 32] = env::read();
|
let mut state: StateWitness = env::read();
|
||||||
|
|
||||||
// private input
|
for input in inputs {
|
||||||
let input_root: [u8; 32] = env::read();
|
match input {
|
||||||
let output_root: [u8; 32] = env::read();
|
Input::Withdraw(input) => {
|
||||||
let in_ptx_path: Vec<merkle::PathNode> = env::read();
|
state = withdraw(state, input);
|
||||||
let out_ptx_path: Vec<merkle::PathNode> = env::read();
|
}
|
||||||
let in_note: InputWitness = env::read();
|
}
|
||||||
let out_note: OutputWitness = env::read();
|
}
|
||||||
let input: Input = env::read();
|
|
||||||
let state: State = env::read();
|
|
||||||
let journal: Journal = env::read();
|
|
||||||
|
|
||||||
eprintln!("parse input: {}", env::cycle_count());
|
env::commit(&state.commit());
|
||||||
execute(
|
|
||||||
ptx_root,
|
|
||||||
input_root,
|
|
||||||
output_root,
|
|
||||||
in_ptx_path,
|
|
||||||
out_ptx_path,
|
|
||||||
in_note,
|
|
||||||
out_note,
|
|
||||||
input,
|
|
||||||
state,
|
|
||||||
journal,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_state_hash(state: &State) -> [u8; 32] {
|
|
||||||
let bytes = bincode::serialize(state).unwrap();
|
|
||||||
Blake2s256::digest(&bytes).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_journal_hash(journal: &Journal) -> [u8; 32] {
|
|
||||||
let bytes = bincode::serialize(journal).unwrap();
|
|
||||||
Blake2s256::digest(&bytes).into()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "goas_proof_statements"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cl = { path = "../../cl/cl" }
|
||||||
|
proof_statements = { path = "../../cl/proof_statements" }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod zone_funds;
|
|
@ -0,0 +1,40 @@
|
||||||
|
use proof_statements::{ptx::PartialTxInputPrivate, ptx::PartialTxOutputPrivate};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// An event that authorizes spending zone funds
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct Spend {
|
||||||
|
pub amount: u64,
|
||||||
|
/// The public key of the recipient
|
||||||
|
pub to: cl::NullifierCommitment,
|
||||||
|
/// The nullifier of note that is being spent, this is to avoid using the spend event to
|
||||||
|
/// for multiple notes
|
||||||
|
pub nf: cl::Nullifier,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spend {
|
||||||
|
pub fn to_bytes(&self) -> [u8; 72] {
|
||||||
|
let mut bytes = [0; 72];
|
||||||
|
bytes[0..8].copy_from_slice(&self.amount.to_le_bytes());
|
||||||
|
bytes[8..40].copy_from_slice(self.to.as_bytes());
|
||||||
|
bytes[40..72].copy_from_slice(self.nf.as_bytes());
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// There are two kind of paths
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct SpendFundsPrivate {
|
||||||
|
/// The note we're spending
|
||||||
|
pub in_zone_funds: PartialTxInputPrivate,
|
||||||
|
/// The zone note that is authorizing the spend
|
||||||
|
pub zone_note: PartialTxOutputPrivate,
|
||||||
|
/// The note that is being created to send the change back to the zone
|
||||||
|
pub out_zone_funds: PartialTxOutputPrivate,
|
||||||
|
/// The spent funds note
|
||||||
|
pub spent_note: PartialTxOutputPrivate,
|
||||||
|
/// The event emitted by the zone that authorizes the spend
|
||||||
|
pub spend_event: Spend,
|
||||||
|
/// Path to the zone output state
|
||||||
|
pub spend_event_state_path: Vec<cl::merkle::PathNode>,
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "goas_risc0_proofs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
risc0-build = { version = "1.0" }
|
||||||
|
|
||||||
|
[package.metadata.risc0]
|
||||||
|
methods = ["spend_zone_funds"]
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
risc0_build::embed_methods();
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "spend-zone-funds"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
cl = { path = "../../../cl/cl" }
|
||||||
|
goas_proof_statements = { path = "../../proof_statements" }
|
||||||
|
proof_statements = { path = "../../../cl/proof_statements" }
|
||||||
|
sha2 = "0.10"
|
||||||
|
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
# add RISC Zero accelerator support for all downstream usages of the following crates.
|
||||||
|
sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.8-risczero.0" }
|
||||||
|
crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.5-risczero.0" }
|
||||||
|
curve25519-dalek = { git = "https://github.com/risc0/curve25519-dalek", tag = "curve25519-4.1.2-risczero.0" }
|
|
@ -0,0 +1,89 @@
|
||||||
|
/// Zone Funds Spend Proof
|
||||||
|
///
|
||||||
|
/// Our goal: prove the zone authorized spending of funds
|
||||||
|
use cl::merkle;
|
||||||
|
use cl::nullifier::{Nullifier, NullifierNonce, NullifierSecret};
|
||||||
|
use goas_proof_statements::zone_funds::SpendFundsPrivate;
|
||||||
|
use proof_statements::death_constraint::DeathConstraintPublic;
|
||||||
|
use risc0_zkvm::guest::env;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let SpendFundsPrivate {
|
||||||
|
in_zone_funds,
|
||||||
|
out_zone_funds,
|
||||||
|
zone_note,
|
||||||
|
spent_note,
|
||||||
|
spend_event,
|
||||||
|
spend_event_state_path,
|
||||||
|
} = env::read();
|
||||||
|
|
||||||
|
let cm_root = in_zone_funds.cm_root();
|
||||||
|
let ptx_root = in_zone_funds.ptx_root();
|
||||||
|
let nf = Nullifier::new(in_zone_funds.input.nf_sk, in_zone_funds.input.nonce);
|
||||||
|
// check the zone funds note is the one in the spend event
|
||||||
|
assert_eq!(nf, spend_event.nf);
|
||||||
|
|
||||||
|
assert_eq!(ptx_root, zone_note.ptx_root());
|
||||||
|
// assert the spent event was an output of the zone stf
|
||||||
|
let spend_event_leaf = merkle::leaf(&spend_event.to_bytes());
|
||||||
|
// TODO: zones will have some more state
|
||||||
|
assert_eq!(
|
||||||
|
zone_note.output.note.state,
|
||||||
|
merkle::path_root(spend_event_leaf, &spend_event_state_path)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(ptx_root, out_zone_funds.ptx_root());
|
||||||
|
|
||||||
|
// Check we return the rest of the funds back to the zone
|
||||||
|
let change = in_zone_funds
|
||||||
|
.input
|
||||||
|
.note
|
||||||
|
.balance
|
||||||
|
.value
|
||||||
|
.checked_sub(spend_event.amount)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(out_zone_funds.output.note.balance.value, change);
|
||||||
|
// zone funds output should have the same death constraints as the zone funds input
|
||||||
|
assert_eq!(
|
||||||
|
out_zone_funds.output.note.death_constraint,
|
||||||
|
in_zone_funds.input.note.death_constraint
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
out_zone_funds.output.note.balance.unit,
|
||||||
|
in_zone_funds.input.note.balance.unit
|
||||||
|
);
|
||||||
|
// zone funds nullifier, nonce and value blinding should be public so that everybody can spend it
|
||||||
|
assert_eq!(
|
||||||
|
out_zone_funds.output.nf_pk,
|
||||||
|
NullifierSecret::from_bytes([0; 16]).commit()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
out_zone_funds.output.note.balance.blinding,
|
||||||
|
in_zone_funds.input.note.balance.blinding
|
||||||
|
);
|
||||||
|
let mut evolved_nonce = [0; 16];
|
||||||
|
evolved_nonce[..16]
|
||||||
|
.copy_from_slice(&Sha256::digest(&out_zone_funds.output.nonce.as_bytes())[..16]);
|
||||||
|
assert_eq!(
|
||||||
|
out_zone_funds.output.nonce,
|
||||||
|
NullifierNonce::from_bytes(evolved_nonce)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(ptx_root, spent_note.ptx_root());
|
||||||
|
|
||||||
|
// check the correct amount of funds is being spent
|
||||||
|
assert_eq!(spent_note.output.note.balance.value, spend_event.amount);
|
||||||
|
assert_eq!(
|
||||||
|
spent_note.output.note.balance.unit,
|
||||||
|
in_zone_funds.input.note.balance.unit
|
||||||
|
);
|
||||||
|
// check the correct recipient is being paid
|
||||||
|
assert_eq!(spent_note.output.nf_pk, spend_event.to);
|
||||||
|
|
||||||
|
env::commit(&DeathConstraintPublic {
|
||||||
|
cm_root,
|
||||||
|
ptx_root,
|
||||||
|
nf,
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/methods.rs"));
|
Loading…
Reference in New Issue