goas: user intent constraint

This commit is contained in:
David Rusu 2024-08-09 16:11:00 +04:00
parent da437db677
commit 847e253f10
7 changed files with 175 additions and 16 deletions

View File

@ -44,21 +44,17 @@ pub struct StateWitness {
} }
impl StateWitness { impl StateWitness {
/// Merkle tree over:
/// root
/// / \
/// io state
/// / \ / \
/// nonce txs zoneid balances
pub fn commit(&self) -> StateCommitment { pub fn commit(&self) -> StateCommitment {
let root = cl::merkle::root([ self.state_roots().commit()
self.nonce, }
self.included_txs_root(),
self.zone_metadata.id(),
self.balances_root(),
]);
StateCommitment(root) pub fn state_roots(&self) -> StateRoots {
StateRoots {
nonce: self.nonce,
tx_root: self.included_txs_root(),
zone_id: self.zone_metadata.id(),
balance_root: self.balances_root(),
}
} }
pub fn withdraw(mut self, w: Withdraw) -> Self { pub fn withdraw(mut self, w: Withdraw) -> Self {
@ -207,3 +203,35 @@ pub struct IncludedTxWitness {
pub tx: Tx, pub tx: Tx,
pub path: Vec<merkle::PathNode>, pub path: Vec<merkle::PathNode>,
} }
impl IncludedTxWitness {
pub fn tx_root(&self) -> [u8; 32] {
let leaf = merkle::leaf(&self.tx.to_bytes());
merkle::path_root(leaf, &self.path)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct StateRoots {
pub nonce: [u8; 32],
pub tx_root: [u8; 32],
pub zone_id: [u8; 32],
pub balance_root: [u8; 32],
}
impl StateRoots {
/// Merkle tree over:
/// root
/// / \
/// io state
/// / \ / \
/// nonce txs zoneid balances
pub fn commit(&self) -> StateCommitment {
StateCommitment(cl::merkle::root([
self.nonce,
self.tx_root,
self.zone_id,
self.balance_root,
]))
}
}

View File

@ -6,4 +6,6 @@ edition = "2021"
[dependencies] [dependencies]
common = { path = "../common" } common = { path = "../common" }
cl = { path = "../../cl/cl" } cl = { path = "../../cl/cl" }
ledger_proof_statements = { path = "../../cl/ledger_proof_statements" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
sha2 = "0.10"

View File

@ -1,2 +1,14 @@
pub mod user_note;
pub mod zone_funds; pub mod zone_funds;
pub mod zone_state; pub mod zone_state;
pub fn assert_is_zone_note(
zone_meta: &common::ZoneMetadata,
note: &cl::NoteWitness,
state_roots: &common::StateRoots,
) {
assert_eq!(state_roots.commit().0, note.state);
assert_eq!(zone_meta.id(), state_roots.zone_id);
assert_eq!(zone_meta.zone_vk, note.death_constraint);
assert_eq!(zone_meta.unit, note.unit);
}

View File

@ -14,7 +14,7 @@
/// ///
/// The User Notes death constraint requires the following statements to be satisfied /// The User Notes death constraint requires the following statements to be satisfied
/// in order for the fee to be captured. /// in order for the fee to be captured.
/// ///
/// 1. w_tx = withdraw(amt=100 NMO, from=Alice) tx was included in Zone A. /// 1. w_tx = withdraw(amt=100 NMO, from=Alice) tx was included in Zone A.
/// 2. d_tx = deposit(amt=100 NMO, to=Alice) tx was included in Zone B. /// 2. d_tx = deposit(amt=100 NMO, to=Alice) tx was included in Zone B.
/// 3. w_tx is included in Zone A iff d_tx is included in Zone B /// 3. w_tx is included in Zone A iff d_tx is included in Zone B
@ -22,3 +22,87 @@
/// Details: /// Details:
/// - the withdrawal in zone A must not be a general withdrawal tx, it must be bound to the user note. /// - the withdrawal in zone A must not be a general withdrawal tx, it must be bound to the user note.
/// i.e. the user_note must be present in the ptx for the withdrawal to be valid in Zone A. /// i.e. the user_note must be present in the ptx for the withdrawal to be valid in Zone A.
use ledger_proof_statements::death_constraint::DeathConstraintPublic;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UserIntent {
pub zone_a_meta: common::ZoneMetadata,
pub zone_b_meta: common::ZoneMetadata,
pub withdraw: common::Withdraw,
pub deposit: common::Deposit,
}
impl UserIntent {
pub fn commit(&self) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(b"USER_INTENT_STATE");
hasher.update(self.zone_a_meta.id());
hasher.update(self.zone_b_meta.id());
hasher.update(self.withdraw.to_bytes());
hasher.update(self.deposit.to_bytes());
hasher.finalize().into()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UserAtomicTransfer {
// user's note
pub user_note: cl::PartialTxInputWitness,
pub user_intent: UserIntent,
// the output state notes which should have included both tx's
pub zone_a: cl::PartialTxOutputWitness,
pub zone_b: cl::PartialTxOutputWitness,
// proofs of identies of the above notes
pub zone_a_roots: common::StateRoots,
pub zone_b_roots: common::StateRoots,
// proof that zone_a has included this withdrawal
pub withdraw_tx: common::IncludedTxWitness,
// proof that zone_b has included this deposit
pub deposit_tx: common::IncludedTxWitness,
}
impl UserAtomicTransfer {
pub fn assert_constraints(&self) -> DeathConstraintPublic {
// user committed to these actions in the user note
assert_eq!(self.user_intent.commit(), self.user_note.input.note.state);
// ensure we are interacting with the correct zone notes
crate::assert_is_zone_note(
&self.user_intent.zone_a_meta,
&self.zone_a.output.note,
&self.zone_a_roots,
);
crate::assert_is_zone_note(
&self.user_intent.zone_b_meta,
&self.zone_b.output.note,
&self.zone_b_roots,
);
// ensure txs were included in the respective zones
assert_eq!(self.withdraw_tx.tx_root(), self.zone_a_roots.tx_root);
assert_eq!(self.deposit_tx.tx_root(), self.zone_b_roots.tx_root);
// ensure the txs are the same ones the user requested
assert_eq!(
common::Tx::Withdraw(self.user_intent.withdraw),
self.withdraw_tx.tx
);
assert_eq!(
common::Tx::Deposit(self.user_intent.deposit),
self.deposit_tx.tx
);
let input_root = self.user_note.input_root();
let output_root = self.zone_a.output_root();
assert_eq!(output_root, self.zone_b.output_root());
let ptx_root = cl::PtxRoot(cl::merkle::node(input_root, output_root));
let nf = self.user_note.input.nullifier();
DeathConstraintPublic { ptx_root, nf }
}
}

View File

@ -7,5 +7,4 @@ edition = "2021"
risc0-build = { version = "1.0" } risc0-build = { version = "1.0" }
[package.metadata.risc0] [package.metadata.risc0]
methods = ["spend_zone_funds", "zone_state"] methods = ["spend_zone_funds", "zone_state", "user_atomic_transfer"]

View File

@ -0,0 +1,25 @@
[package]
name = "user_atomic_transfer"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
blake2 = "0.10"
serde = { version = "1.0", features = ["derive"] }
bincode = "1"
common = { path = "../../common" }
cl = { path = "../../../cl/cl" }
goas_proof_statements = { path = "../../proof_statements" }
ledger_proof_statements = { path = "../../../cl/ledger_proof_statements" }
sha2 = "0.10"
[patch.crates-io]
# Placing these patch statement in the workspace Cargo.toml will add RISC Zero SHA-256 and bigint
# multiplication 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" }
# k256 = { git = "https://github.com/risc0/RustCrypto-elliptic-curves", tag = "k256/v0.13.3-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" }

View File

@ -0,0 +1,9 @@
use goas_proof_statements::user_note::UserAtomicTransfer;
use ledger_proof_statements::death_constraint::DeathConstraintPublic;
use risc0_zkvm::guest::env;
fn main() {
let transfer: UserAtomicTransfer = env::read();
let public: DeathConstraintPublic = transfer.assert_constraints();
env::commit(&public);
}