mirror of
https://github.com/logos-blockchain/logos-blockchain-pocs.git
synced 2026-05-27 19:59:32 +00:00
Merge pull request #26 from logos-co/goas/atomic-transfer-scenario
goas: atomic transfer scenario
This commit is contained in:
commit
9541c4b34d
@ -8,4 +8,4 @@ opt-level = 3
|
||||
|
||||
[profile.release]
|
||||
debug = 1
|
||||
lto = true
|
||||
lto = true
|
||||
|
||||
@ -9,3 +9,7 @@ cl = { path = "../../cl/cl" }
|
||||
ledger_proof_statements = { path = "../../cl/ledger_proof_statements" }
|
||||
once_cell = "1"
|
||||
sha2 = "0.10"
|
||||
curve25519-dalek = { version = "4.1", features = ["serde", "digest", "rand_core"] }
|
||||
ed25519-dalek = { version = "2.1.1", features = ["rand_core"] }
|
||||
rand_core = "0.6.0"
|
||||
serde_arrays = "0.1.0"
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
use cl::{balance::Unit, merkle, PartialTxInputWitness};
|
||||
use cl::{balance::Unit, merkle, NoteCommitment};
|
||||
use ed25519_dalek::{
|
||||
ed25519::{signature::SignerMut, SignatureBytes},
|
||||
Signature, SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use rand_core::CryptoRngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::collections::BTreeMap;
|
||||
@ -13,11 +18,15 @@ pub const MAX_EVENTS: usize = 1 << 8;
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct StateCommitment(pub [u8; 32]);
|
||||
|
||||
pub type AccountId = u32;
|
||||
pub type AccountId = [u8; PUBLIC_KEY_LENGTH];
|
||||
|
||||
// PLACEHOLDER: this is probably going to be NMO?
|
||||
pub static ZONE_CL_FUNDS_UNIT: Lazy<Unit> = Lazy::new(|| cl::note::unit_point("NMO"));
|
||||
|
||||
pub fn new_account(mut rng: impl CryptoRngCore) -> SigningKey {
|
||||
SigningKey::generate(&mut rng)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ZoneMetadata {
|
||||
pub zone_vk: [u8; 32],
|
||||
@ -28,8 +37,8 @@ pub struct ZoneMetadata {
|
||||
impl ZoneMetadata {
|
||||
pub fn id(&self) -> [u8; 32] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&self.zone_vk);
|
||||
hasher.update(&self.funds_vk);
|
||||
hasher.update(self.zone_vk);
|
||||
hasher.update(self.funds_vk);
|
||||
hasher.update(self.unit.compress().as_bytes());
|
||||
hasher.finalize().into()
|
||||
}
|
||||
@ -40,7 +49,6 @@ pub struct StateWitness {
|
||||
pub balances: BTreeMap<AccountId, u64>,
|
||||
pub included_txs: Vec<Tx>,
|
||||
pub zone_metadata: ZoneMetadata,
|
||||
pub nonce: [u8; 32],
|
||||
}
|
||||
|
||||
impl StateWitness {
|
||||
@ -50,14 +58,24 @@ impl StateWitness {
|
||||
|
||||
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 apply(self, tx: Tx) -> Self {
|
||||
let mut state = match tx {
|
||||
Tx::Withdraw(w) => self.withdraw(w),
|
||||
Tx::Deposit(d) => self.deposit(d),
|
||||
};
|
||||
|
||||
state.included_txs.push(tx);
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
fn withdraw(mut self, w: Withdraw) -> Self {
|
||||
let Withdraw { from, amount } = w;
|
||||
|
||||
let from_balance = self.balances.entry(from).or_insert(0);
|
||||
@ -65,12 +83,10 @@ impl StateWitness {
|
||||
.checked_sub(amount)
|
||||
.expect("insufficient funds in account");
|
||||
|
||||
self.included_txs.push(Tx::Withdraw(w));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn deposit(mut self, d: Deposit) -> Self {
|
||||
fn deposit(mut self, d: Deposit) -> Self {
|
||||
let Deposit { to, amount } = d;
|
||||
|
||||
let to_balance = self.balances.entry(to).or_insert(0);
|
||||
@ -78,7 +94,6 @@ impl StateWitness {
|
||||
.checked_add(amount)
|
||||
.expect("overflow in account balance");
|
||||
|
||||
self.included_txs.push(Tx::Deposit(d));
|
||||
self
|
||||
}
|
||||
|
||||
@ -87,7 +102,7 @@ impl StateWitness {
|
||||
}
|
||||
|
||||
pub fn included_tx_witness(&self, idx: usize) -> IncludedTxWitness {
|
||||
let tx = self.included_txs.get(idx).unwrap().clone();
|
||||
let tx = *self.included_txs.get(idx).unwrap();
|
||||
let path = merkle::path(self.included_tx_merkle_leaves(), idx);
|
||||
IncludedTxWitness { tx, path }
|
||||
}
|
||||
@ -95,7 +110,7 @@ impl StateWitness {
|
||||
pub fn balances_root(&self) -> [u8; 32] {
|
||||
let balance_bytes = Vec::from_iter(self.balances.iter().map(|(owner, balance)| {
|
||||
let mut bytes: Vec<u8> = vec![];
|
||||
bytes.extend(owner.to_le_bytes());
|
||||
bytes.extend(owner);
|
||||
bytes.extend(balance.to_le_bytes());
|
||||
bytes
|
||||
}));
|
||||
@ -107,19 +122,6 @@ impl StateWitness {
|
||||
self.balances.values().sum()
|
||||
}
|
||||
|
||||
pub fn evolve_nonce(self) -> Self {
|
||||
let updated_nonce = {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&self.nonce);
|
||||
hasher.update(b"NOMOS_ZONE_NONCE_EVOLVE");
|
||||
hasher.finalize().into()
|
||||
};
|
||||
Self {
|
||||
nonce: updated_nonce,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
fn included_tx_merkle_leaves(&self) -> [[u8; 32]; MAX_TXS] {
|
||||
let tx_bytes = self
|
||||
.included_txs
|
||||
@ -143,10 +145,10 @@ pub struct Withdraw {
|
||||
}
|
||||
|
||||
impl Withdraw {
|
||||
pub fn to_bytes(&self) -> [u8; 12] {
|
||||
let mut bytes = [0; 12];
|
||||
bytes[0..4].copy_from_slice(&self.from.to_le_bytes());
|
||||
bytes[4..12].copy_from_slice(&self.amount.to_le_bytes());
|
||||
pub fn to_bytes(&self) -> [u8; 40] {
|
||||
let mut bytes = [0; 40];
|
||||
bytes[0..PUBLIC_KEY_LENGTH].copy_from_slice(&self.from);
|
||||
bytes[PUBLIC_KEY_LENGTH..PUBLIC_KEY_LENGTH + 8].copy_from_slice(&self.amount.to_le_bytes());
|
||||
bytes
|
||||
}
|
||||
}
|
||||
@ -159,31 +161,58 @@ pub struct Deposit {
|
||||
}
|
||||
|
||||
impl Deposit {
|
||||
pub fn to_bytes(&self) -> [u8; 12] {
|
||||
let mut bytes = [0; 12];
|
||||
bytes[0..4].copy_from_slice(&self.to.to_le_bytes());
|
||||
bytes[4..12].copy_from_slice(&self.amount.to_le_bytes());
|
||||
pub fn to_bytes(&self) -> [u8; 40] {
|
||||
let mut bytes = [0; 40];
|
||||
bytes[0..PUBLIC_KEY_LENGTH].copy_from_slice(&self.to);
|
||||
bytes[PUBLIC_KEY_LENGTH..PUBLIC_KEY_LENGTH + 8].copy_from_slice(&self.amount.to_le_bytes());
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SignedBoundTx {
|
||||
pub bound_tx: BoundTx,
|
||||
#[serde(with = "serde_arrays")]
|
||||
pub sig: SignatureBytes,
|
||||
}
|
||||
|
||||
impl SignedBoundTx {
|
||||
pub fn sign(bound_tx: BoundTx, signing_key: &mut SigningKey) -> Self {
|
||||
let msg = bound_tx.to_bytes();
|
||||
let sig = signing_key.sign(&msg).to_bytes();
|
||||
|
||||
Self { bound_tx, sig }
|
||||
}
|
||||
|
||||
pub fn verify_and_unwrap(&self) -> BoundTx {
|
||||
let msg = self.bound_tx.to_bytes();
|
||||
|
||||
let sig = Signature::from_bytes(&self.sig);
|
||||
let vk = self.bound_tx.tx.verifying_key();
|
||||
vk.verify_strict(&msg, &sig).expect("Invalid tx signature");
|
||||
|
||||
self.bound_tx
|
||||
}
|
||||
}
|
||||
|
||||
/// A Tx that is executed in the zone if and only if the bind is
|
||||
/// present is the same partial transaction
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct BoundTx {
|
||||
pub tx: Tx,
|
||||
pub bind: PartialTxInputWitness,
|
||||
pub bind: NoteCommitment,
|
||||
}
|
||||
|
||||
impl BoundTx {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = self.tx.to_bytes();
|
||||
bytes.extend(self.bind.input.commit().to_bytes());
|
||||
let mut bytes = Vec::new();
|
||||
bytes.extend(self.tx.to_bytes());
|
||||
bytes.extend(self.bind.as_bytes());
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum Tx {
|
||||
Withdraw(Withdraw),
|
||||
Deposit(Deposit),
|
||||
@ -196,6 +225,13 @@ impl Tx {
|
||||
Tx::Deposit(deposit) => deposit.to_bytes().to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verifying_key(&self) -> VerifyingKey {
|
||||
match self {
|
||||
Tx::Withdraw(w) => VerifyingKey::from_bytes(&w.from).unwrap(),
|
||||
Tx::Deposit(d) => VerifyingKey::from_bytes(&d.to).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@ -213,25 +249,19 @@ impl IncludedTxWitness {
|
||||
|
||||
#[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
|
||||
/// Merkle tree over: [txs, zoneid, balances]
|
||||
pub fn commit(&self) -> StateCommitment {
|
||||
StateCommitment(cl::merkle::root([
|
||||
self.nonce,
|
||||
self.tx_root,
|
||||
self.zone_id,
|
||||
self.balance_root,
|
||||
]))
|
||||
let leaves = cl::merkle::padded_leaves::<4>(&[
|
||||
self.tx_root.to_vec(),
|
||||
self.zone_id.to_vec(),
|
||||
self.balance_root.to_vec(),
|
||||
]);
|
||||
StateCommitment(cl::merkle::root(leaves))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,105 @@
|
||||
use common::{BoundTx, StateWitness, ZoneMetadata};
|
||||
use goas_proof_statements::{zone_funds::SpendFundsPrivate, zone_state::ZoneStatePrivate};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use common::{AccountId, SignedBoundTx, StateWitness, Tx, ZoneMetadata};
|
||||
use goas_proof_statements::{
|
||||
user_note::UserAtomicTransfer, zone_funds::SpendFundsPrivate, zone_state::ZoneStatePrivate,
|
||||
};
|
||||
use rand_core::CryptoRngCore;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ZoneNotes {
|
||||
pub state: StateWitness,
|
||||
pub state_note: cl::OutputWitness,
|
||||
pub fund_note: cl::OutputWitness,
|
||||
}
|
||||
|
||||
impl ZoneNotes {
|
||||
pub fn new_with_balances(
|
||||
zone_name: &str,
|
||||
balances: BTreeMap<AccountId, u64>,
|
||||
mut rng: impl CryptoRngCore,
|
||||
) -> Self {
|
||||
let state = StateWitness {
|
||||
balances,
|
||||
included_txs: vec![],
|
||||
zone_metadata: zone_metadata(zone_name),
|
||||
};
|
||||
let state_note = zone_state_utxo(&state, &mut rng);
|
||||
let fund_note = zone_fund_utxo(state.total_balance(), state.zone_metadata, &mut rng);
|
||||
Self {
|
||||
state,
|
||||
state_note,
|
||||
fund_note,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state_input_witness(&self) -> cl::InputWitness {
|
||||
cl::InputWitness::public(self.state_note)
|
||||
}
|
||||
|
||||
pub fn fund_input_witness(&self) -> cl::InputWitness {
|
||||
cl::InputWitness::public(self.fund_note)
|
||||
}
|
||||
|
||||
pub fn run(mut self, txs: impl IntoIterator<Item = Tx>) -> Self {
|
||||
for tx in txs {
|
||||
self.state = self.state.apply(tx);
|
||||
}
|
||||
|
||||
let state_in = self.state_input_witness();
|
||||
self.state_note = cl::OutputWitness::public(
|
||||
cl::NoteWitness {
|
||||
state: self.state.commit().0,
|
||||
..state_in.note
|
||||
},
|
||||
state_in.evolved_nonce(b"STATE_NONCE"),
|
||||
);
|
||||
|
||||
let fund_in = self.fund_input_witness();
|
||||
self.fund_note = cl::OutputWitness::public(
|
||||
cl::NoteWitness {
|
||||
value: self.state.total_balance(),
|
||||
..fund_in.note
|
||||
},
|
||||
state_in.evolved_nonce(b"FUND_NONCE"),
|
||||
);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn zone_fund_utxo(
|
||||
value: u64,
|
||||
zone_meta: ZoneMetadata,
|
||||
mut rng: impl CryptoRngCore,
|
||||
) -> cl::OutputWitness {
|
||||
cl::OutputWitness::public(
|
||||
cl::NoteWitness {
|
||||
value,
|
||||
unit: *common::ZONE_CL_FUNDS_UNIT,
|
||||
death_constraint: zone_meta.funds_vk,
|
||||
state: zone_meta.id(),
|
||||
},
|
||||
cl::NullifierNonce::random(&mut rng),
|
||||
)
|
||||
}
|
||||
|
||||
fn zone_state_utxo(zone: &StateWitness, mut rng: impl CryptoRngCore) -> cl::OutputWitness {
|
||||
cl::OutputWitness::public(
|
||||
cl::NoteWitness {
|
||||
value: 1,
|
||||
unit: zone.zone_metadata.unit,
|
||||
death_constraint: zone.zone_metadata.zone_vk,
|
||||
state: zone.commit().0,
|
||||
},
|
||||
cl::NullifierNonce::random(&mut rng),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn user_atomic_transfer_death_constraint() -> [u8; 32] {
|
||||
ledger::death_constraint::risc0_id_to_cl_death_constraint(
|
||||
goas_risc0_proofs::USER_ATOMIC_TRANSFER_ID,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn zone_state_death_constraint() -> [u8; 32] {
|
||||
ledger::death_constraint::risc0_id_to_cl_death_constraint(goas_risc0_proofs::ZONE_STATE_ID)
|
||||
@ -21,7 +121,7 @@ pub fn zone_metadata(zone_mnemonic: &str) -> ZoneMetadata {
|
||||
|
||||
pub fn prove_zone_stf(
|
||||
state: StateWitness,
|
||||
inputs: Vec<BoundTx>,
|
||||
inputs: Vec<(SignedBoundTx, cl::PartialTxInputWitness)>,
|
||||
zone_in: cl::PartialTxInputWitness,
|
||||
zone_out: cl::PartialTxOutputWitness,
|
||||
funds_out: cl::PartialTxOutputWitness,
|
||||
@ -48,7 +148,11 @@ pub fn prove_zone_stf(
|
||||
let prove_info = prover
|
||||
.prove_with_opts(env, goas_risc0_proofs::ZONE_STATE_ELF, &opts)
|
||||
.unwrap();
|
||||
println!("STARK 'zone_stf' prover time: {:.2?}", start_t.elapsed());
|
||||
println!(
|
||||
"STARK 'zone_stf' prover time: {:.2?}, total_cycles: {}",
|
||||
start_t.elapsed(),
|
||||
prove_info.stats.total_cycles
|
||||
);
|
||||
let receipt = prove_info.receipt;
|
||||
ledger::DeathProof::from_risc0(goas_risc0_proofs::ZONE_STATE_ID, receipt)
|
||||
}
|
||||
@ -78,7 +182,35 @@ pub fn prove_zone_fund_withdraw(
|
||||
let prove_info = prover
|
||||
.prove_with_opts(env, goas_risc0_proofs::SPEND_ZONE_FUNDS_ELF, &opts)
|
||||
.unwrap();
|
||||
println!("STARK 'zone_fund' prover time: {:.2?}", start_t.elapsed());
|
||||
println!(
|
||||
"STARK 'zone_fund' prover time: {:.2?}, total_cycles: {}",
|
||||
start_t.elapsed(),
|
||||
prove_info.stats.total_cycles
|
||||
);
|
||||
let receipt = prove_info.receipt;
|
||||
ledger::DeathProof::from_risc0(goas_risc0_proofs::SPEND_ZONE_FUNDS_ID, receipt)
|
||||
}
|
||||
|
||||
pub fn prove_user_atomic_transfer(atomic_transfer: UserAtomicTransfer) -> ledger::DeathProof {
|
||||
let env = risc0_zkvm::ExecutorEnv::builder()
|
||||
.write(&atomic_transfer)
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let prover = risc0_zkvm::default_prover();
|
||||
|
||||
use std::time::Instant;
|
||||
let start_t = Instant::now();
|
||||
let opts = risc0_zkvm::ProverOpts::succinct();
|
||||
let prove_info = prover
|
||||
.prove_with_opts(env, goas_risc0_proofs::USER_ATOMIC_TRANSFER_ELF, &opts)
|
||||
.unwrap();
|
||||
println!(
|
||||
"STARK 'user atomic transfer' prover time: {:.2?}, total_cycles: {}",
|
||||
start_t.elapsed(),
|
||||
prove_info.stats.total_cycles
|
||||
);
|
||||
let receipt = prove_info.receipt;
|
||||
ledger::DeathProof::from_risc0(goas_risc0_proofs::USER_ATOMIC_TRANSFER_ID, receipt)
|
||||
}
|
||||
|
||||
176
goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs
Normal file
176
goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs
Normal file
@ -0,0 +1,176 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use cl::{BundleWitness, NoteWitness, NullifierNonce};
|
||||
use common::{new_account, BoundTx, Deposit, SignedBoundTx, Tx, Withdraw};
|
||||
use executor::ZoneNotes;
|
||||
use goas_proof_statements::user_note::{UserAtomicTransfer, UserIntent};
|
||||
|
||||
#[test]
|
||||
fn test_atomic_transfer() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let mut alice = new_account(&mut rng);
|
||||
let alice_vk = alice.verifying_key().to_bytes();
|
||||
|
||||
let zone_a_start =
|
||||
ZoneNotes::new_with_balances("ZONE_A", BTreeMap::from_iter([(alice_vk, 100)]), &mut rng);
|
||||
|
||||
let zone_b_start = ZoneNotes::new_with_balances("ZONE_B", BTreeMap::from_iter([]), &mut rng);
|
||||
|
||||
let alice_intent = UserIntent {
|
||||
zone_a_meta: zone_a_start.state.zone_metadata,
|
||||
zone_b_meta: zone_b_start.state.zone_metadata,
|
||||
withdraw: Withdraw {
|
||||
from: alice_vk,
|
||||
amount: 75,
|
||||
},
|
||||
deposit: Deposit {
|
||||
to: alice_vk,
|
||||
amount: 75,
|
||||
},
|
||||
};
|
||||
|
||||
let alice_intent_out = cl::OutputWitness::public(
|
||||
NoteWitness {
|
||||
value: 1,
|
||||
unit: cl::note::unit_point("INTENT"),
|
||||
death_constraint: executor::user_atomic_transfer_death_constraint(),
|
||||
state: alice_intent.commit(),
|
||||
},
|
||||
NullifierNonce::random(&mut rng),
|
||||
);
|
||||
|
||||
let user_ptx = cl::PartialTxWitness {
|
||||
inputs: vec![],
|
||||
outputs: vec![alice_intent_out],
|
||||
};
|
||||
|
||||
let zone_a_end = zone_a_start
|
||||
.clone()
|
||||
.run([Tx::Withdraw(alice_intent.withdraw)]);
|
||||
|
||||
let zone_b_end = zone_b_start
|
||||
.clone()
|
||||
.run([Tx::Deposit(alice_intent.deposit)]);
|
||||
|
||||
let alice_intent_in = cl::InputWitness::public(alice_intent_out);
|
||||
let atomic_transfer_ptx = cl::PartialTxWitness {
|
||||
inputs: vec![
|
||||
alice_intent_in,
|
||||
zone_a_start.state_input_witness(),
|
||||
zone_a_start.fund_input_witness(),
|
||||
zone_b_start.state_input_witness(),
|
||||
zone_b_start.fund_input_witness(),
|
||||
],
|
||||
outputs: vec![
|
||||
zone_a_end.state_note,
|
||||
zone_a_end.fund_note,
|
||||
zone_b_end.state_note,
|
||||
zone_b_end.fund_note,
|
||||
],
|
||||
};
|
||||
|
||||
let signed_withdraw = SignedBoundTx::sign(
|
||||
BoundTx {
|
||||
tx: Tx::Withdraw(alice_intent.withdraw),
|
||||
bind: alice_intent_in.note_commitment(),
|
||||
},
|
||||
&mut alice,
|
||||
);
|
||||
let signed_deposit = SignedBoundTx::sign(
|
||||
BoundTx {
|
||||
tx: Tx::Deposit(alice_intent.deposit),
|
||||
bind: alice_intent_in.note_commitment(),
|
||||
},
|
||||
&mut alice,
|
||||
);
|
||||
|
||||
let death_proofs = BTreeMap::from_iter([
|
||||
(
|
||||
alice_intent_in.nullifier(),
|
||||
executor::prove_user_atomic_transfer(UserAtomicTransfer {
|
||||
user_note: atomic_transfer_ptx.input_witness(0),
|
||||
user_intent: alice_intent,
|
||||
zone_a: atomic_transfer_ptx.output_witness(0),
|
||||
zone_b: atomic_transfer_ptx.output_witness(2),
|
||||
zone_a_roots: zone_a_end.state.state_roots(),
|
||||
zone_b_roots: zone_b_end.state.state_roots(),
|
||||
withdraw_tx: zone_a_end.state.included_tx_witness(0),
|
||||
deposit_tx: zone_b_end.state.included_tx_witness(0),
|
||||
}),
|
||||
),
|
||||
(
|
||||
zone_a_start.state_input_witness().nullifier(),
|
||||
executor::prove_zone_stf(
|
||||
zone_a_start.state.clone(),
|
||||
vec![(signed_withdraw, atomic_transfer_ptx.input_witness(0))], // withdraw bound to input intent note
|
||||
atomic_transfer_ptx.input_witness(1), // input state note
|
||||
atomic_transfer_ptx.output_witness(0), // output state note
|
||||
atomic_transfer_ptx.output_witness(1), // output funds note
|
||||
),
|
||||
),
|
||||
(
|
||||
zone_a_start.fund_input_witness().nullifier(),
|
||||
executor::prove_zone_fund_withdraw(
|
||||
atomic_transfer_ptx.input_witness(2), // input fund note
|
||||
atomic_transfer_ptx.output_witness(0), // output state note
|
||||
&zone_a_end.state,
|
||||
),
|
||||
),
|
||||
(
|
||||
zone_b_start.state_input_witness().nullifier(),
|
||||
executor::prove_zone_stf(
|
||||
zone_b_start.state.clone(),
|
||||
vec![(signed_deposit, atomic_transfer_ptx.input_witness(0))], // deposit bound to input intent note
|
||||
atomic_transfer_ptx.input_witness(3), // input state note
|
||||
atomic_transfer_ptx.output_witness(2), // output state note
|
||||
atomic_transfer_ptx.output_witness(3), // output funds note
|
||||
),
|
||||
),
|
||||
(
|
||||
zone_b_start.fund_input_witness().nullifier(),
|
||||
executor::prove_zone_fund_withdraw(
|
||||
atomic_transfer_ptx.input_witness(4), // input fund note (input #1)
|
||||
atomic_transfer_ptx.output_witness(2), // output state note (output #0)
|
||||
&zone_b_end.state,
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
let user_ptx_proof =
|
||||
ledger::partial_tx::ProvedPartialTx::prove(&user_ptx, BTreeMap::new(), &[])
|
||||
.expect("user ptx failed to prove");
|
||||
assert!(user_ptx_proof.verify());
|
||||
|
||||
let note_commitments = vec![
|
||||
alice_intent_out.commit_note(),
|
||||
zone_a_start.state_note.commit_note(),
|
||||
zone_a_start.fund_note.commit_note(),
|
||||
zone_b_start.state_note.commit_note(),
|
||||
zone_b_start.fund_note.commit_note(),
|
||||
];
|
||||
|
||||
let atomic_transfer_proof = ledger::partial_tx::ProvedPartialTx::prove(
|
||||
&atomic_transfer_ptx,
|
||||
death_proofs,
|
||||
¬e_commitments,
|
||||
)
|
||||
.expect("atomic transfer proof failed");
|
||||
|
||||
assert!(atomic_transfer_proof.verify());
|
||||
|
||||
let bundle = cl::Bundle {
|
||||
partials: vec![user_ptx.commit(), atomic_transfer_ptx.commit()],
|
||||
};
|
||||
|
||||
let bundle_witness = BundleWitness {
|
||||
balance_blinding: cl::BalanceWitness(
|
||||
user_ptx.balance_blinding().0 + atomic_transfer_ptx.balance_blinding().0,
|
||||
),
|
||||
};
|
||||
|
||||
let bundle_proof =
|
||||
ledger::bundle::ProvedBundle::prove(&bundle, &bundle_witness).expect("bundle proof failed");
|
||||
|
||||
assert!(bundle_proof.verify());
|
||||
}
|
||||
@ -1,65 +1,27 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use cl::{NoteWitness, NullifierNonce, NullifierSecret};
|
||||
use common::{BoundTx, StateWitness, Tx, ZoneMetadata, ZONE_CL_FUNDS_UNIT};
|
||||
use cl::{NoteWitness, NullifierSecret};
|
||||
use common::{new_account, BoundTx, SignedBoundTx, StateWitness, Tx, ZONE_CL_FUNDS_UNIT};
|
||||
use executor::ZoneNotes;
|
||||
use ledger::death_constraint::DeathProof;
|
||||
use rand_core::CryptoRngCore;
|
||||
|
||||
fn zone_fund_note(value: u64, zone_meta: ZoneMetadata) -> cl::NoteWitness {
|
||||
cl::NoteWitness {
|
||||
value,
|
||||
unit: *common::ZONE_CL_FUNDS_UNIT,
|
||||
death_constraint: zone_meta.funds_vk,
|
||||
state: zone_meta.id(),
|
||||
}
|
||||
}
|
||||
|
||||
fn zone_state_utxo(zone: &StateWitness, mut rng: impl CryptoRngCore) -> cl::OutputWitness {
|
||||
cl::OutputWitness::public(
|
||||
cl::NoteWitness {
|
||||
value: 1,
|
||||
unit: zone.zone_metadata.unit,
|
||||
death_constraint: zone.zone_metadata.zone_vk,
|
||||
state: zone.commit().0,
|
||||
},
|
||||
NullifierNonce::random(&mut rng),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deposit() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let alice = 42;
|
||||
let alice_sk = NullifierSecret::random(&mut rng);
|
||||
let mut alice = new_account(&mut rng);
|
||||
let alice_vk = alice.verifying_key().to_bytes();
|
||||
let alice_cl_sk = NullifierSecret::random(&mut rng);
|
||||
|
||||
let init_state = StateWitness {
|
||||
balances: BTreeMap::new(),
|
||||
included_txs: vec![],
|
||||
zone_metadata: executor::zone_metadata("ZONE"),
|
||||
nonce: [0; 32],
|
||||
};
|
||||
|
||||
let zone_state_in = cl::InputWitness::public(zone_state_utxo(&init_state, &mut rng));
|
||||
let zone_start = ZoneNotes::new_with_balances("ZONE", BTreeMap::new(), &mut rng);
|
||||
|
||||
let deposit = common::Deposit {
|
||||
to: alice,
|
||||
to: alice_vk,
|
||||
amount: 78,
|
||||
};
|
||||
|
||||
let end_state = init_state.clone().deposit(deposit).evolve_nonce();
|
||||
let zone_end = zone_start.clone().run([Tx::Deposit(deposit)]);
|
||||
|
||||
let zone_state_out = cl::OutputWitness::public(
|
||||
cl::NoteWitness {
|
||||
state: end_state.commit().0,
|
||||
..zone_state_in.note
|
||||
},
|
||||
zone_state_in.evolved_nonce(),
|
||||
);
|
||||
let zone_fund_out = cl::OutputWitness::public(
|
||||
zone_fund_note(78, init_state.zone_metadata),
|
||||
NullifierNonce::from_bytes(end_state.nonce),
|
||||
);
|
||||
let alice_deposit = cl::InputWitness::random(
|
||||
cl::OutputWitness::random(
|
||||
NoteWitness::stateless(
|
||||
@ -67,28 +29,33 @@ fn test_deposit() {
|
||||
*ZONE_CL_FUNDS_UNIT,
|
||||
DeathProof::nop_constraint(), // alice should demand a tx inclusion proof for the deposit
|
||||
),
|
||||
alice_sk.commit(),
|
||||
alice_cl_sk.commit(),
|
||||
&mut rng,
|
||||
),
|
||||
alice_sk,
|
||||
alice_cl_sk,
|
||||
&mut rng,
|
||||
);
|
||||
|
||||
let deposit_ptx = cl::PartialTxWitness {
|
||||
inputs: vec![zone_state_in, alice_deposit],
|
||||
outputs: vec![zone_state_out, zone_fund_out],
|
||||
inputs: vec![zone_start.state_input_witness(), alice_deposit],
|
||||
outputs: vec![zone_end.state_note, zone_end.fund_note],
|
||||
};
|
||||
|
||||
let signed_deposit = SignedBoundTx::sign(
|
||||
BoundTx {
|
||||
tx: Tx::Deposit(deposit),
|
||||
bind: alice_deposit.note_commitment(),
|
||||
},
|
||||
&mut alice,
|
||||
);
|
||||
|
||||
let death_proofs = BTreeMap::from_iter([
|
||||
(
|
||||
zone_state_in.nullifier(),
|
||||
zone_start.state_input_witness().nullifier(),
|
||||
executor::prove_zone_stf(
|
||||
init_state.clone(),
|
||||
vec![BoundTx {
|
||||
tx: Tx::Deposit(deposit),
|
||||
bind: deposit_ptx.input_witness(1), // bind it to the deposit note
|
||||
}],
|
||||
deposit_ptx.input_witness(0), // input state note (input #0)
|
||||
zone_start.state.clone(),
|
||||
vec![(signed_deposit, deposit_ptx.input_witness(1))], // bind it to the deposit note)],
|
||||
deposit_ptx.input_witness(0), // input state note (input #0)
|
||||
deposit_ptx.output_witness(0), // output state note (output #0)
|
||||
deposit_ptx.output_witness(1), // output funds note (output #1)
|
||||
),
|
||||
@ -100,7 +67,7 @@ fn test_deposit() {
|
||||
]);
|
||||
|
||||
let note_commitments = vec![
|
||||
zone_state_in.note_commitment(),
|
||||
zone_start.state_note.commit_note(),
|
||||
alice_deposit.note_commitment(),
|
||||
];
|
||||
|
||||
@ -110,14 +77,16 @@ fn test_deposit() {
|
||||
|
||||
assert!(deposit_proof.verify());
|
||||
|
||||
assert_eq!(deposit_proof.outputs[0].output, zone_state_out.commit());
|
||||
assert_eq!(
|
||||
zone_state_out.note.state,
|
||||
deposit_proof.outputs[0].output,
|
||||
zone_end.state_note.commit()
|
||||
);
|
||||
assert_eq!(
|
||||
zone_end.state_note.note.state,
|
||||
StateWitness {
|
||||
balances: BTreeMap::from_iter([(alice, 78)]),
|
||||
balances: BTreeMap::from_iter([(alice_vk, 78)]),
|
||||
included_txs: vec![Tx::Deposit(deposit)],
|
||||
zone_metadata: init_state.zone_metadata,
|
||||
nonce: init_state.evolve_nonce().nonce,
|
||||
zone_metadata: zone_start.state.zone_metadata,
|
||||
}
|
||||
.commit()
|
||||
.0
|
||||
|
||||
@ -1,87 +1,37 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use cl::{NoteWitness, NullifierNonce, NullifierSecret};
|
||||
use common::{BoundTx, StateWitness, Tx, ZoneMetadata, ZONE_CL_FUNDS_UNIT};
|
||||
use cl::{NoteWitness, NullifierSecret};
|
||||
use common::{new_account, BoundTx, SignedBoundTx, StateWitness, Tx, ZONE_CL_FUNDS_UNIT};
|
||||
use executor::ZoneNotes;
|
||||
use ledger::death_constraint::DeathProof;
|
||||
use rand_core::CryptoRngCore;
|
||||
|
||||
fn zone_fund_utxo(
|
||||
value: u64,
|
||||
zone_meta: ZoneMetadata,
|
||||
mut rng: impl CryptoRngCore,
|
||||
) -> cl::OutputWitness {
|
||||
cl::OutputWitness::public(
|
||||
cl::NoteWitness {
|
||||
value,
|
||||
unit: *common::ZONE_CL_FUNDS_UNIT,
|
||||
death_constraint: zone_meta.funds_vk,
|
||||
state: zone_meta.id(),
|
||||
},
|
||||
NullifierNonce::random(&mut rng),
|
||||
)
|
||||
}
|
||||
|
||||
fn zone_state_utxo(zone: &StateWitness, mut rng: impl CryptoRngCore) -> cl::OutputWitness {
|
||||
cl::OutputWitness::public(
|
||||
cl::NoteWitness {
|
||||
value: 1,
|
||||
unit: zone.zone_metadata.unit,
|
||||
death_constraint: zone.zone_metadata.zone_vk,
|
||||
state: zone.commit().0,
|
||||
},
|
||||
NullifierNonce::random(&mut rng),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_withdrawal() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let alice = 42;
|
||||
let alice_sk = NullifierSecret::random(&mut rng);
|
||||
let mut alice = new_account(&mut rng);
|
||||
let alice_vk = alice.verifying_key().to_bytes();
|
||||
let alice_cl_sk = NullifierSecret::random(&mut rng);
|
||||
|
||||
let init_state = StateWitness {
|
||||
balances: BTreeMap::from_iter([(alice, 100)]),
|
||||
included_txs: vec![],
|
||||
zone_metadata: executor::zone_metadata("ZONE"),
|
||||
nonce: [0; 32],
|
||||
};
|
||||
|
||||
let zone_fund_in =
|
||||
cl::InputWitness::public(zone_fund_utxo(100, init_state.zone_metadata, &mut rng));
|
||||
let zone_state_in = cl::InputWitness::public(zone_state_utxo(&init_state, &mut rng));
|
||||
let zone_start =
|
||||
ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([(alice_vk, 100)]), &mut rng);
|
||||
|
||||
let alice_intent = cl::InputWitness::random(
|
||||
cl::OutputWitness::random(
|
||||
NoteWitness::stateless(1, *ZONE_CL_FUNDS_UNIT, DeathProof::nop_constraint()), // TODO, intent should be in the death constraint
|
||||
alice_sk.commit(),
|
||||
alice_cl_sk.commit(),
|
||||
&mut rng,
|
||||
),
|
||||
alice_sk,
|
||||
alice_cl_sk,
|
||||
&mut rng,
|
||||
);
|
||||
|
||||
let withdraw = common::Withdraw {
|
||||
from: alice,
|
||||
from: alice_vk,
|
||||
amount: 78,
|
||||
};
|
||||
|
||||
let end_state = init_state.clone().withdraw(withdraw.clone()).evolve_nonce();
|
||||
|
||||
let zone_state_out = cl::OutputWitness::public(
|
||||
cl::NoteWitness {
|
||||
state: end_state.commit().0,
|
||||
..zone_state_in.note
|
||||
},
|
||||
zone_state_in.evolved_nonce(),
|
||||
);
|
||||
let zone_fund_out = cl::OutputWitness::public(
|
||||
cl::NoteWitness {
|
||||
value: zone_fund_in.note.value - withdraw.amount,
|
||||
..zone_fund_in.note
|
||||
},
|
||||
NullifierNonce::from_bytes(end_state.nonce),
|
||||
);
|
||||
let zone_end = zone_start.clone().run([Tx::Withdraw(withdraw)]);
|
||||
|
||||
let alice_withdrawal = cl::OutputWitness::random(
|
||||
NoteWitness::stateless(
|
||||
@ -89,35 +39,44 @@ fn test_withdrawal() {
|
||||
*ZONE_CL_FUNDS_UNIT,
|
||||
DeathProof::nop_constraint(),
|
||||
),
|
||||
alice_sk.commit(),
|
||||
alice_cl_sk.commit(),
|
||||
&mut rng,
|
||||
);
|
||||
|
||||
let withdraw_ptx = cl::PartialTxWitness {
|
||||
inputs: vec![zone_state_in, zone_fund_in, alice_intent],
|
||||
outputs: vec![zone_state_out, zone_fund_out, alice_withdrawal],
|
||||
inputs: vec![
|
||||
zone_start.state_input_witness(),
|
||||
zone_start.fund_input_witness(),
|
||||
alice_intent,
|
||||
],
|
||||
outputs: vec![zone_end.state_note, zone_end.fund_note, alice_withdrawal],
|
||||
};
|
||||
|
||||
let signed_withdraw = SignedBoundTx::sign(
|
||||
BoundTx {
|
||||
tx: Tx::Withdraw(withdraw),
|
||||
bind: alice_intent.note_commitment(),
|
||||
},
|
||||
&mut alice,
|
||||
);
|
||||
|
||||
let death_proofs = BTreeMap::from_iter([
|
||||
(
|
||||
zone_state_in.nullifier(),
|
||||
zone_start.state_input_witness().nullifier(),
|
||||
executor::prove_zone_stf(
|
||||
init_state.clone(),
|
||||
vec![BoundTx {
|
||||
tx: Tx::Withdraw(withdraw.clone()),
|
||||
bind: withdraw_ptx.input_witness(2),
|
||||
}],
|
||||
zone_start.state.clone(),
|
||||
vec![(signed_withdraw, withdraw_ptx.input_witness(2))],
|
||||
withdraw_ptx.input_witness(0), // input state note (input #0)
|
||||
withdraw_ptx.output_witness(0), // output state note (output #0)
|
||||
withdraw_ptx.output_witness(1), // output funds note (output #1)
|
||||
),
|
||||
),
|
||||
(
|
||||
zone_fund_in.nullifier(),
|
||||
zone_start.fund_input_witness().nullifier(),
|
||||
executor::prove_zone_fund_withdraw(
|
||||
withdraw_ptx.input_witness(1), // input fund note (input #1)
|
||||
withdraw_ptx.output_witness(0), // output state note (output #0)
|
||||
&end_state,
|
||||
&zone_end.state,
|
||||
),
|
||||
),
|
||||
(
|
||||
@ -127,8 +86,8 @@ fn test_withdrawal() {
|
||||
]);
|
||||
|
||||
let note_commitments = vec![
|
||||
zone_state_in.note_commitment(),
|
||||
zone_fund_in.note_commitment(),
|
||||
zone_start.state_note.commit_note(),
|
||||
zone_start.fund_note.commit_note(),
|
||||
alice_intent.note_commitment(),
|
||||
];
|
||||
|
||||
@ -138,14 +97,16 @@ fn test_withdrawal() {
|
||||
|
||||
assert!(withdraw_proof.verify());
|
||||
|
||||
assert_eq!(withdraw_proof.outputs[0].output, zone_state_out.commit());
|
||||
assert_eq!(
|
||||
zone_state_out.note.state,
|
||||
withdraw_proof.outputs[0].output,
|
||||
zone_end.state_note.commit()
|
||||
);
|
||||
assert_eq!(
|
||||
zone_end.state_note.note.state,
|
||||
StateWitness {
|
||||
balances: BTreeMap::from_iter([(alice, 22)]),
|
||||
balances: BTreeMap::from_iter([(alice_vk, 22)]),
|
||||
included_txs: vec![Tx::Withdraw(withdraw)],
|
||||
zone_metadata: init_state.zone_metadata,
|
||||
nonce: init_state.evolve_nonce().nonce,
|
||||
zone_metadata: zone_start.state.zone_metadata,
|
||||
}
|
||||
.commit()
|
||||
.0
|
||||
|
||||
@ -26,7 +26,7 @@ use ledger_proof_statements::death_constraint::DeathConstraintPublic;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct UserIntent {
|
||||
pub zone_a_meta: common::ZoneMetadata,
|
||||
pub zone_b_meta: common::ZoneMetadata,
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
use common::{BoundTx, StateWitness};
|
||||
use common::{SignedBoundTx, StateWitness};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ZoneStatePrivate {
|
||||
pub state: StateWitness,
|
||||
pub inputs: Vec<BoundTx>,
|
||||
pub inputs: Vec<(SignedBoundTx, cl::PartialTxInputWitness)>,
|
||||
pub zone_in: cl::PartialTxInputWitness,
|
||||
pub zone_out: cl::PartialTxOutputWitness,
|
||||
/// While the absence of birth constraints does not guarantee uniqueness of a note that can be used as
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use cl::{
|
||||
note::NoteWitness, nullifier::NullifierNonce, output::OutputWitness, PartialTxInputWitness,
|
||||
note::NoteWitness, output::OutputWitness,
|
||||
PtxRoot,
|
||||
};
|
||||
|
||||
@ -8,26 +8,6 @@ use goas_proof_statements::zone_state::ZoneStatePrivate;
|
||||
use ledger_proof_statements::death_constraint::DeathConstraintPublic;
|
||||
use risc0_zkvm::guest::env;
|
||||
|
||||
fn withdraw(
|
||||
state: StateWitness,
|
||||
input_root: [u8; 32],
|
||||
withdrawal: Withdraw,
|
||||
bind: PartialTxInputWitness,
|
||||
) -> StateWitness {
|
||||
assert_eq!(bind.input_root(), input_root);
|
||||
state.withdraw(withdrawal)
|
||||
}
|
||||
|
||||
fn deposit(
|
||||
state: StateWitness,
|
||||
input_root: [u8; 32],
|
||||
deposit: Deposit,
|
||||
bind: PartialTxInputWitness,
|
||||
) -> StateWitness {
|
||||
assert_eq!(bind.input_root(), input_root);
|
||||
state.deposit(deposit)
|
||||
}
|
||||
|
||||
fn validate_zone_transition(
|
||||
in_note: cl::PartialTxInputWitness,
|
||||
out_note: cl::PartialTxOutputWitness,
|
||||
@ -59,7 +39,7 @@ fn validate_zone_transition(
|
||||
);
|
||||
|
||||
// the nonce is correctly evolved
|
||||
assert_eq!(in_note.input.evolved_nonce(), out_note.output.nonce);
|
||||
assert_eq!(in_note.input.evolved_nonce(b"STATE_NONCE"), out_note.output.nonce);
|
||||
|
||||
// funds are still under control of the zone
|
||||
let expected_note_witness = NoteWitness::new(
|
||||
@ -72,7 +52,7 @@ fn validate_zone_transition(
|
||||
out_funds.output,
|
||||
OutputWitness::public(
|
||||
expected_note_witness,
|
||||
NullifierNonce::from_bytes(out_state.nonce)
|
||||
in_note.input.evolved_nonce(b"FUND_NONCE")
|
||||
)
|
||||
);
|
||||
// funds belong to the same partial tx
|
||||
@ -98,20 +78,18 @@ fn main() {
|
||||
|
||||
let in_state_cm = state.commit();
|
||||
|
||||
for input in inputs {
|
||||
state = match input {
|
||||
BoundTx {
|
||||
tx: Tx::Withdraw(w),
|
||||
bind,
|
||||
} => withdraw(state, input_root, w, bind),
|
||||
BoundTx {
|
||||
tx: Tx::Deposit(d),
|
||||
bind,
|
||||
} => deposit(state, input_root, d, bind),
|
||||
}
|
||||
for (signed_bound_tx, ptx_input_witness) in inputs {
|
||||
// verify the signature
|
||||
let bound_tx = signed_bound_tx.verify_and_unwrap();
|
||||
|
||||
// ensure the note this tx is bound to is present in the ptx
|
||||
assert_eq!(bound_tx.bind, ptx_input_witness.input.note_commitment());
|
||||
assert_eq!(ptx_input_witness.input_root(), input_root);
|
||||
|
||||
// apply the ptx
|
||||
state = state.apply(bound_tx.tx)
|
||||
}
|
||||
|
||||
let state = state.evolve_nonce();
|
||||
validate_zone_transition(zone_in, zone_out, funds_out, in_state_cm, state);
|
||||
|
||||
env::commit(&pub_inputs);
|
||||
|
||||
@ -52,21 +52,25 @@ impl InputWitness {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn evolved_nonce(&self) -> NullifierNonce {
|
||||
self.nonce.evolve(&self.nf_sk)
|
||||
pub fn evolved_nonce(&self, domain: &[u8]) -> NullifierNonce {
|
||||
self.nonce.evolve(domain, &self.nf_sk, &self.note)
|
||||
}
|
||||
|
||||
pub fn evolve_output(&self, balance_blinding: BalanceWitness) -> crate::OutputWitness {
|
||||
pub fn evolve_output(
|
||||
&self,
|
||||
domain: &[u8],
|
||||
balance_blinding: BalanceWitness,
|
||||
) -> crate::OutputWitness {
|
||||
crate::OutputWitness {
|
||||
note: self.note,
|
||||
balance_blinding,
|
||||
nf_pk: self.nf_sk.commit(),
|
||||
nonce: self.evolved_nonce(),
|
||||
nonce: self.evolved_nonce(domain),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nullifier(&self) -> Nullifier {
|
||||
Nullifier::new(self.nf_sk, self.nonce)
|
||||
Nullifier::new(self.nf_sk, self.note_commitment())
|
||||
}
|
||||
|
||||
pub fn commit(&self) -> Input {
|
||||
|
||||
@ -23,7 +23,7 @@ pub fn unit_point(unit: &str) -> Unit {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
pub struct NoteCommitment([u8; 32]);
|
||||
pub struct NoteCommitment(pub [u8; 32]);
|
||||
|
||||
impl NoteCommitment {
|
||||
pub fn as_bytes(&self) -> &[u8; 32] {
|
||||
@ -31,8 +31,6 @@ impl NoteCommitment {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Rename Note to NoteWitness and NoteCommitment to Note
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct NoteWitness {
|
||||
pub value: u64,
|
||||
|
||||
@ -9,6 +9,8 @@ use rand_core::RngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::{NoteCommitment, NoteWitness};
|
||||
|
||||
// TODO: create a nullifier witness and use it throughout.
|
||||
// struct NullifierWitness {
|
||||
// nf_sk: NullifierSecret,
|
||||
@ -90,11 +92,12 @@ impl NullifierNonce {
|
||||
Self(bytes)
|
||||
}
|
||||
|
||||
pub fn evolve(&self, nf_sk: &NullifierSecret) -> Self {
|
||||
pub fn evolve(&self, domain: &[u8], nf_sk: &NullifierSecret, note: &NoteWitness) -> Self {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"NOMOS_COIN_EVOLVE");
|
||||
hasher.update(&self.0);
|
||||
hasher.update(domain);
|
||||
hasher.update(nf_sk.0);
|
||||
hasher.update(note.commit(nf_sk.commit(), *self).0);
|
||||
|
||||
let nonce_bytes: [u8; 32] = hasher.finalize().into();
|
||||
Self(nonce_bytes)
|
||||
@ -102,11 +105,11 @@ impl NullifierNonce {
|
||||
}
|
||||
|
||||
impl Nullifier {
|
||||
pub fn new(sk: NullifierSecret, nonce: NullifierNonce) -> Self {
|
||||
pub fn new(sk: NullifierSecret, note_cm: NoteCommitment) -> Self {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"NOMOS_CL_NULLIFIER");
|
||||
hasher.update(sk.0);
|
||||
hasher.update(nonce.0);
|
||||
hasher.update(note_cm.0);
|
||||
|
||||
let nf_bytes: [u8; 32] = hasher.finalize().into();
|
||||
Self(nf_bytes)
|
||||
@ -119,6 +122,8 @@ impl Nullifier {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{note::unit_point, NoteWitness};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[ignore = "nullifier test vectors not stable yet"]
|
||||
@ -142,10 +147,31 @@ mod test {
|
||||
fn test_nullifier_same_sk_different_nonce() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let sk = NullifierSecret::random(&mut rng);
|
||||
let note = NoteWitness::basic(1, unit_point("NMO"));
|
||||
|
||||
let nonce_1 = NullifierNonce::random(&mut rng);
|
||||
let nonce_2 = NullifierNonce::random(&mut rng);
|
||||
let nf_1 = Nullifier::new(sk, nonce_1);
|
||||
let nf_2 = Nullifier::new(sk, nonce_2);
|
||||
let note_cm_1 = note.commit(sk.commit(), nonce_1);
|
||||
let note_cm_2 = note.commit(sk.commit(), nonce_2);
|
||||
|
||||
let nf_1 = Nullifier::new(sk, note_cm_1);
|
||||
let nf_2 = Nullifier::new(sk, note_cm_2);
|
||||
|
||||
assert_ne!(nf_1, nf_2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_same_sk_same_nonce_different_note() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let sk = NullifierSecret::random(&mut rng);
|
||||
let note_1 = NoteWitness::basic(1, unit_point("NMO"));
|
||||
let note_2 = NoteWitness::basic(1, unit_point("ETH"));
|
||||
let nonce = NullifierNonce::random(&mut rng);
|
||||
let note_cm_1 = note_1.commit(sk.commit(), nonce);
|
||||
let note_cm_2 = note_2.commit(sk.commit(), nonce);
|
||||
|
||||
let nf_1 = Nullifier::new(sk, note_cm_1);
|
||||
let nf_2 = Nullifier::new(sk, note_cm_2);
|
||||
|
||||
assert_ne!(nf_1, nf_2);
|
||||
}
|
||||
|
||||
@ -127,7 +127,7 @@ mod test {
|
||||
input: cl::Input {
|
||||
nullifier: cl::Nullifier::new(
|
||||
cl::NullifierSecret::random(&mut rng),
|
||||
cl::NullifierNonce::random(&mut rng),
|
||||
input.note_commitment(),
|
||||
),
|
||||
..expected_public_inputs.input
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user