Integrate zone withdrawal with CL (#17)
* aat: integrate withdraw with CL * aat: withdrawal passes! * aat: cleanup withdrawals a bit * aat: move Ptx{Input|Output|Private to cl::partial_tx * aat: zone_state zone transition validation coded w.r.t. metadata * aat: rename meta to in_meta in zone transition validation
This commit is contained in:
parent
5547b739c5
commit
a320c20d25
|
@ -6,7 +6,6 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
cl = { path = "../../cl/cl" }
|
cl = { path = "../../cl/cl" }
|
||||||
goas_proof_statements = { path = "../proof_statements" }
|
ledger_proof_statements = { path = "../../cl/ledger_proof_statements" }
|
||||||
proof_statements = { path = "../../cl/proof_statements" }
|
|
||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
|
@ -0,0 +1,36 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum Event {
|
||||||
|
Spend(Spend),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Event {
|
||||||
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
// TODO: add variant tag to byte encoding
|
||||||
|
match self {
|
||||||
|
Event::Spend(spend) => spend.to_bytes().to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 fund_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.fund_nf.as_bytes());
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
|
pub mod events;
|
||||||
|
|
||||||
use cl::{
|
use cl::{
|
||||||
balance::Unit,
|
balance::Unit,
|
||||||
crypto,
|
|
||||||
input::InputWitness,
|
input::InputWitness,
|
||||||
nullifier::{Nullifier, NullifierCommitment},
|
nullifier::{Nullifier, NullifierCommitment},
|
||||||
output::OutputWitness,
|
output::OutputWitness,
|
||||||
|
@ -17,14 +18,14 @@ pub const MAX_EVENTS: usize = 1 << 8;
|
||||||
|
|
||||||
// state of the zone
|
// state of the zone
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
|
||||||
pub struct StateCommitment([u8; 32]);
|
pub struct StateCommitment(pub [u8; 32]);
|
||||||
|
|
||||||
pub type AccountId = u32;
|
pub type AccountId = u32;
|
||||||
|
|
||||||
// PLACEHOLDER: this is probably going to be NMO?
|
// PLACEHOLDER: this is probably going to be NMO?
|
||||||
pub static ZONE_CL_FUNDS_UNIT: Lazy<Unit> = Lazy::new(|| crypto::hash_to_curve(b"NMO"));
|
pub static ZONE_CL_FUNDS_UNIT: Lazy<Unit> = Lazy::new(|| cl::note::unit_point("NMO"));
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct ZoneMetadata {
|
pub struct ZoneMetadata {
|
||||||
pub zone_vk: [u8; 32],
|
pub zone_vk: [u8; 32],
|
||||||
pub funds_vk: [u8; 32],
|
pub funds_vk: [u8; 32],
|
||||||
|
@ -41,11 +42,11 @@ impl ZoneMetadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct StateWitness {
|
pub struct StateWitness {
|
||||||
pub balances: BTreeMap<u32, u32>,
|
pub balances: BTreeMap<AccountId, u64>,
|
||||||
pub included_txs: Vec<Input>,
|
pub included_txs: Vec<Input>,
|
||||||
pub output_events: Vec<Event>,
|
pub output_events: Vec<events::Event>,
|
||||||
pub zone_metadata: ZoneMetadata,
|
pub zone_metadata: ZoneMetadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,34 +68,61 @@ impl StateWitness {
|
||||||
StateCommitment(root)
|
StateCommitment(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn events_root(&self) -> [u8; 32] {
|
pub fn withdraw(mut self, w: Withdraw) -> Self {
|
||||||
let event_bytes = Vec::from_iter(
|
self.included_txs.push(Input::Withdraw(w));
|
||||||
self.output_events
|
|
||||||
.iter()
|
let Withdraw {
|
||||||
.map(Event::to_bytes)
|
from,
|
||||||
.map(Vec::from_iter),
|
amount,
|
||||||
);
|
to,
|
||||||
|
fund_nf,
|
||||||
|
} = w;
|
||||||
|
|
||||||
|
let from_balance = self.balances.entry(from).or_insert(0);
|
||||||
|
*from_balance = from_balance
|
||||||
|
.checked_sub(amount)
|
||||||
|
.expect("insufficient funds in account");
|
||||||
|
|
||||||
|
let spend_auth = events::Spend {
|
||||||
|
amount,
|
||||||
|
to,
|
||||||
|
fund_nf,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.output_events.push(events::Event::Spend(spend_auth));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn events_root(&self) -> [u8; 32] {
|
||||||
|
let event_bytes = Vec::from_iter(self.output_events.iter().map(events::Event::to_bytes));
|
||||||
let event_merkle_leaves = cl::merkle::padded_leaves(&event_bytes);
|
let event_merkle_leaves = cl::merkle::padded_leaves(&event_bytes);
|
||||||
cl::merkle::root::<MAX_EVENTS>(event_merkle_leaves)
|
cl::merkle::root::<MAX_EVENTS>(event_merkle_leaves)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn included_txs_root(&self) -> [u8; 32] {
|
pub fn included_txs_root(&self) -> [u8; 32] {
|
||||||
// this is a placeholder
|
// this is a placeholder
|
||||||
let tx_bytes = [vec![0u8; 32]];
|
let tx_bytes = [vec![0u8; 32]];
|
||||||
let tx_merkle_leaves = cl::merkle::padded_leaves(&tx_bytes);
|
let tx_merkle_leaves = cl::merkle::padded_leaves(&tx_bytes);
|
||||||
cl::merkle::root::<MAX_TXS>(tx_merkle_leaves)
|
cl::merkle::root::<MAX_TXS>(tx_merkle_leaves)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn balances_root(&self) -> [u8; 32] {
|
pub fn balances_root(&self) -> [u8; 32] {
|
||||||
let balance_bytes = Vec::from_iter(self.balances.iter().map(|(k, v)| {
|
let balance_bytes = Vec::from_iter(self.balances.iter().map(|(owner, balance)| {
|
||||||
let mut bytes = [0; 8];
|
let mut bytes: Vec<u8> = vec![];
|
||||||
bytes.copy_from_slice(&k.to_le_bytes());
|
bytes.extend(owner.to_le_bytes());
|
||||||
bytes[8..].copy_from_slice(&v.to_le_bytes());
|
bytes.extend(balance.to_le_bytes());
|
||||||
bytes.to_vec()
|
bytes
|
||||||
}));
|
}));
|
||||||
let balance_merkle_leaves = cl::merkle::padded_leaves(&balance_bytes);
|
let balance_merkle_leaves = cl::merkle::padded_leaves(&balance_bytes);
|
||||||
cl::merkle::root::<MAX_BALANCES>(balance_merkle_leaves)
|
cl::merkle::root::<MAX_BALANCES>(balance_merkle_leaves)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn event_merkle_path(&self, event: events::Event) -> Vec<cl::merkle::PathNode> {
|
||||||
|
let idx = self.output_events.iter().position(|e| e == &event).unwrap();
|
||||||
|
let event_bytes = Vec::from_iter(self.output_events.iter().map(events::Event::to_bytes));
|
||||||
|
let event_merkle_leaves = cl::merkle::padded_leaves(&event_bytes);
|
||||||
|
cl::merkle::path::<MAX_EVENTS>(event_merkle_leaves, idx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<StateCommitment> for [u8; 32] {
|
impl From<StateCommitment> for [u8; 32] {
|
||||||
|
@ -103,27 +131,35 @@ impl From<StateCommitment> for [u8; 32] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Withdraw {
|
pub struct Withdraw {
|
||||||
pub from: AccountId,
|
pub from: AccountId,
|
||||||
pub amount: AccountId,
|
pub amount: u64,
|
||||||
pub to: NullifierCommitment,
|
pub to: NullifierCommitment,
|
||||||
pub nf: Nullifier,
|
pub fund_nf: Nullifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Withdraw {
|
impl Withdraw {
|
||||||
|
pub fn to_event(&self) -> events::Spend {
|
||||||
|
events::Spend {
|
||||||
|
amount: self.amount,
|
||||||
|
to: self.to,
|
||||||
|
fund_nf: self.fund_nf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_bytes(&self) -> [u8; 72] {
|
pub fn to_bytes(&self) -> [u8; 72] {
|
||||||
let mut bytes = [0; 72];
|
let mut bytes = [0; 72];
|
||||||
bytes[0..4].copy_from_slice(&self.from.to_le_bytes());
|
bytes[0..4].copy_from_slice(&self.from.to_le_bytes());
|
||||||
bytes[4..8].copy_from_slice(&self.amount.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[8..40].copy_from_slice(self.to.as_bytes());
|
||||||
bytes[40..72].copy_from_slice(self.nf.as_bytes());
|
bytes[40..72].copy_from_slice(self.fund_nf.as_bytes());
|
||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A deposit of funds into the zone
|
/// A deposit of funds into the zone
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Deposit {
|
pub struct Deposit {
|
||||||
/// The note that is used to deposit funds into the zone
|
/// The note that is used to deposit funds into the zone
|
||||||
pub deposit: InputWitness,
|
pub deposit: InputWitness,
|
||||||
|
@ -137,21 +173,8 @@ pub struct Deposit {
|
||||||
pub zone_funds_out: OutputWitness,
|
pub zone_funds_out: OutputWitness,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum Input {
|
pub enum Input {
|
||||||
Withdraw(Withdraw),
|
Withdraw(Withdraw),
|
||||||
Deposit(Deposit),
|
Deposit(Deposit),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
|
||||||
pub enum Event {
|
|
||||||
Spend(goas_proof_statements::zone_funds::Spend),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Event {
|
|
||||||
pub fn to_bytes(&self) -> Vec<u8> {
|
|
||||||
match self {
|
|
||||||
Event::Spend(spend) => spend.to_bytes().to_vec(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -16,4 +16,8 @@ common = { path = "../common" }
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
rand_core = "0.6.0"
|
||||||
cl = { path = "../../cl/cl" }
|
cl = { path = "../../cl/cl" }
|
||||||
|
ledger = { path = "../../cl/ledger" }
|
||||||
|
ledger_proof_statements = { path = "../../cl/ledger_proof_statements" }
|
||||||
|
goas_proof_statements = { path = "../proof_statements" }
|
|
@ -4,10 +4,9 @@
|
||||||
/// This workaround manually calls into docker after creating a directory with the required permissions.
|
/// This workaround manually calls into docker after creating a directory with the required permissions.
|
||||||
/// In addition, splitting the process in different stages highlights better the different work that
|
/// In addition, splitting the process in different stages highlights better the different work that
|
||||||
/// needs to be done which could be split across different actors.
|
/// needs to be done which could be split across different actors.
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use risc0_zkvm::{get_prover_server, ProverOpts, Receipt};
|
use risc0_zkvm::{get_prover_server, ProverOpts, Receipt};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
const WORK_DIR_ENV: &str = "RISC0_WORK_DIR";
|
const WORK_DIR_ENV: &str = "RISC0_WORK_DIR";
|
||||||
|
|
||||||
|
@ -47,8 +46,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
risc0_groth16::docker::stark_to_snark(&converted.get_seal_bytes())?;
|
risc0_groth16::docker::stark_to_snark(&converted.get_seal_bytes())?;
|
||||||
|
|
||||||
std::fs::create_dir_all(&args.output_dir)?;
|
std::fs::create_dir_all(&args.output_dir)?;
|
||||||
std::fs::copy(work_dir_path.join("proof.json"), args.output_dir.join("proof.json"))?;
|
std::fs::copy(
|
||||||
std::fs::copy(work_dir_path.join("public.json"), args.output_dir.join("public.json"))?;
|
work_dir_path.join("proof.json"),
|
||||||
|
args.output_dir.join("proof.json"),
|
||||||
|
)?;
|
||||||
|
std::fs::copy(
|
||||||
|
work_dir_path.join("public.json"),
|
||||||
|
args.output_dir.join("public.json"),
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
use common::{events::Event, Input, StateWitness};
|
||||||
|
use goas_proof_statements::{zone_funds::SpendFundsPrivate, zone_state::ZoneStatePrivate};
|
||||||
|
|
||||||
|
pub fn prove_zone_stf(
|
||||||
|
state: StateWitness,
|
||||||
|
inputs: Vec<Input>,
|
||||||
|
zone_in: cl::PartialTxInputWitness,
|
||||||
|
zone_out: cl::PartialTxOutputWitness,
|
||||||
|
) -> ledger::DeathProof {
|
||||||
|
let private_inputs = ZoneStatePrivate {
|
||||||
|
state,
|
||||||
|
inputs,
|
||||||
|
zone_in,
|
||||||
|
zone_out,
|
||||||
|
};
|
||||||
|
|
||||||
|
let env = risc0_zkvm::ExecutorEnv::builder()
|
||||||
|
.write(&private_inputs)
|
||||||
|
.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::ZONE_STATE_ELF, &opts)
|
||||||
|
.unwrap();
|
||||||
|
println!("STARK 'zone_stf' prover time: {:.2?}", start_t.elapsed());
|
||||||
|
let receipt = prove_info.receipt;
|
||||||
|
ledger::DeathProof::from_risc0(goas_risc0_proofs::ZONE_STATE_ID, receipt)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prove_zone_fund_withdraw(
|
||||||
|
in_zone_funds: cl::PartialTxInputWitness,
|
||||||
|
zone_note: cl::PartialTxOutputWitness,
|
||||||
|
out_zone_funds: cl::PartialTxOutputWitness,
|
||||||
|
spent_note: cl::PartialTxOutputWitness,
|
||||||
|
out_zone_state: &StateWitness,
|
||||||
|
withdraw: common::Withdraw,
|
||||||
|
) -> ledger::DeathProof {
|
||||||
|
let spend_event = withdraw.to_event();
|
||||||
|
let private_inputs = SpendFundsPrivate {
|
||||||
|
in_zone_funds,
|
||||||
|
zone_note,
|
||||||
|
out_zone_funds,
|
||||||
|
spent_note,
|
||||||
|
spend_event,
|
||||||
|
spend_event_state_path: out_zone_state.event_merkle_path(Event::Spend(spend_event)),
|
||||||
|
balances_root: out_zone_state.balances_root(),
|
||||||
|
txs_root: out_zone_state.included_txs_root(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let env = risc0_zkvm::ExecutorEnv::builder()
|
||||||
|
.write(&private_inputs)
|
||||||
|
.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::SPEND_ZONE_FUNDS_ELF, &opts)
|
||||||
|
.unwrap();
|
||||||
|
println!("STARK 'zone_fund' prover time: {:.2?}", start_t.elapsed());
|
||||||
|
let receipt = prove_info.receipt;
|
||||||
|
ledger::DeathProof::from_risc0(goas_risc0_proofs::SPEND_ZONE_FUNDS_ID, receipt)
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use cl::{NoteWitness, NullifierNonce, NullifierSecret};
|
||||||
|
use common::{events::Event, Input, StateWitness, ZoneMetadata, ZONE_CL_FUNDS_UNIT};
|
||||||
|
use ledger::death_constraint::DeathProof;
|
||||||
|
use rand_core::CryptoRngCore;
|
||||||
|
|
||||||
|
fn zone_state_death_constraint() -> [u8; 32] {
|
||||||
|
ledger::death_constraint::risc0_id_to_cl_death_constraint(goas_risc0_proofs::ZONE_STATE_ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn zone_fund_death_constraint() -> [u8; 32] {
|
||||||
|
ledger::death_constraint::risc0_id_to_cl_death_constraint(
|
||||||
|
goas_risc0_proofs::SPEND_ZONE_FUNDS_ID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 init_state = StateWitness {
|
||||||
|
balances: BTreeMap::from_iter([(alice, 100)]),
|
||||||
|
included_txs: vec![],
|
||||||
|
output_events: vec![],
|
||||||
|
zone_metadata: ZoneMetadata {
|
||||||
|
zone_vk: zone_state_death_constraint(),
|
||||||
|
funds_vk: zone_fund_death_constraint(),
|
||||||
|
unit: cl::note::unit_point("ZONE_STATE"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let zone_fund_in =
|
||||||
|
cl::InputWitness::public(zone_fund_utxo(35240, init_state.zone_metadata, &mut rng));
|
||||||
|
let zone_state_in = cl::InputWitness::public(zone_state_utxo(&init_state, &mut rng));
|
||||||
|
|
||||||
|
let withdraw = common::Withdraw {
|
||||||
|
from: alice,
|
||||||
|
amount: 78,
|
||||||
|
to: alice_sk.commit(),
|
||||||
|
fund_nf: zone_fund_in.nullifier(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let end_state = init_state.clone().withdraw(withdraw);
|
||||||
|
|
||||||
|
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
|
||||||
|
},
|
||||||
|
zone_fund_in.evolved_nonce(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let alice_withdrawal = cl::OutputWitness::random(
|
||||||
|
NoteWitness::stateless(
|
||||||
|
withdraw.amount,
|
||||||
|
*ZONE_CL_FUNDS_UNIT,
|
||||||
|
DeathProof::nop_constraint(),
|
||||||
|
),
|
||||||
|
alice_sk.commit(),
|
||||||
|
&mut rng,
|
||||||
|
);
|
||||||
|
|
||||||
|
let withdraw_ptx = cl::PartialTxWitness {
|
||||||
|
inputs: vec![zone_state_in, zone_fund_in],
|
||||||
|
outputs: vec![zone_state_out, zone_fund_out, alice_withdrawal],
|
||||||
|
};
|
||||||
|
|
||||||
|
let death_proofs = BTreeMap::from_iter([
|
||||||
|
(
|
||||||
|
zone_state_in.nullifier(),
|
||||||
|
executor::prove_zone_stf(
|
||||||
|
init_state.clone(),
|
||||||
|
vec![Input::Withdraw(withdraw)],
|
||||||
|
withdraw_ptx.input_witness(0), // input state note (input #0)
|
||||||
|
withdraw_ptx.output_witness(0), // output state note (output #0)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
zone_fund_in.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)
|
||||||
|
withdraw_ptx.output_witness(1), // output state note (output #0)
|
||||||
|
withdraw_ptx.output_witness(2), // output state note (output #0)
|
||||||
|
&end_state,
|
||||||
|
withdraw,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let note_commitments = vec![
|
||||||
|
zone_state_in.note_commitment(),
|
||||||
|
zone_fund_in.note_commitment(),
|
||||||
|
];
|
||||||
|
|
||||||
|
let withdraw_proof =
|
||||||
|
ledger::partial_tx::ProvedPartialTx::prove(&withdraw_ptx, death_proofs, ¬e_commitments)
|
||||||
|
.expect("withdraw proof failed");
|
||||||
|
|
||||||
|
assert!(withdraw_proof.verify());
|
||||||
|
|
||||||
|
assert_eq!(withdraw_proof.outputs[0].output, zone_state_out.commit());
|
||||||
|
assert_eq!(
|
||||||
|
zone_state_out.note.state,
|
||||||
|
StateWitness {
|
||||||
|
balances: BTreeMap::from_iter([(alice, 22)]),
|
||||||
|
included_txs: vec![Input::Withdraw(withdraw)],
|
||||||
|
output_events: vec![Event::Spend(common::events::Spend {
|
||||||
|
amount: 78,
|
||||||
|
to: alice_sk.commit(),
|
||||||
|
fund_nf: zone_fund_in.nullifier()
|
||||||
|
})],
|
||||||
|
zone_metadata: init_state.zone_metadata
|
||||||
|
}
|
||||||
|
.commit()
|
||||||
|
.0
|
||||||
|
)
|
||||||
|
}
|
|
@ -4,6 +4,6 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
common = { path = "../common" }
|
||||||
cl = { path = "../../cl/cl" }
|
cl = { path = "../../cl/cl" }
|
||||||
proof_statements = { path = "../../cl/proof_statements" }
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
pub mod zone_funds;
|
pub mod zone_funds;
|
||||||
|
pub mod zone_state;
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
/// The User Note encodes the logic of the atomic asset transfer
|
||||||
|
///
|
||||||
|
/// The scenario is as follows:
|
||||||
|
/// The user, let's call her Alice has 100 NMO in Zone A and she wants to move it to
|
||||||
|
/// Zone B. She wants to arrange this transfer so that both the withdrawal from Zone
|
||||||
|
/// A and the deposit to Zone B occur atomically.
|
||||||
|
///
|
||||||
|
/// The Alice will create a partial tx that looks like this:
|
||||||
|
///
|
||||||
|
/// [fee note] -> [user note]
|
||||||
|
///
|
||||||
|
/// The User Note will encode the logic that orchestrates the withdrawal from zone A
|
||||||
|
/// and deposit to zone B.
|
||||||
|
///
|
||||||
|
/// The User Notes death constraint requires the following statements to be satisfied
|
||||||
|
/// in order for the fee to be captured.
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
/// 3. w_tx is included in Zone A iff d_tx is included in Zone B
|
||||||
|
///
|
||||||
|
/// Details:
|
||||||
|
/// - 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.
|
|
@ -1,39 +1,17 @@
|
||||||
use proof_statements::{ptx::PartialTxInputPrivate, ptx::PartialTxOutputPrivate};
|
|
||||||
use serde::{Deserialize, Serialize};
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct SpendFundsPrivate {
|
pub struct SpendFundsPrivate {
|
||||||
/// The note we're spending
|
/// The note we're spending
|
||||||
pub in_zone_funds: PartialTxInputPrivate,
|
pub in_zone_funds: cl::PartialTxInputWitness,
|
||||||
/// The zone note that is authorizing the spend
|
/// The zone note that is authorizing the spend
|
||||||
pub zone_note: PartialTxOutputPrivate,
|
pub zone_note: cl::PartialTxOutputWitness,
|
||||||
/// The note that is being created to send the change back to the zone
|
/// The note that is being created to send the change back to the zone
|
||||||
pub out_zone_funds: PartialTxOutputPrivate,
|
pub out_zone_funds: cl::PartialTxOutputWitness,
|
||||||
/// The spent funds note
|
/// The spent funds note
|
||||||
pub spent_note: PartialTxOutputPrivate,
|
pub spent_note: cl::PartialTxOutputWitness,
|
||||||
/// The event emitted by the zone that authorizes the spend
|
/// The event emitted by the zone that authorizes the spend
|
||||||
pub spend_event: Spend,
|
pub spend_event: common::events::Spend,
|
||||||
/// Path to the zone output events root
|
/// Path to the zone output events root
|
||||||
pub spend_event_state_path: Vec<cl::merkle::PathNode>,
|
pub spend_event_state_path: Vec<cl::merkle::PathNode>,
|
||||||
/// Merkle root of txs included in the zone
|
/// Merkle root of txs included in the zone
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
use common::{Input, StateWitness};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct ZoneStatePrivate {
|
||||||
|
pub state: StateWitness,
|
||||||
|
pub inputs: Vec<Input>,
|
||||||
|
pub zone_in: cl::PartialTxInputWitness,
|
||||||
|
pub zone_out: cl::PartialTxOutputWitness,
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
cl = { path = "../../../cl/cl" }
|
cl = { path = "../../../cl/cl" }
|
||||||
goas_proof_statements = { path = "../../proof_statements" }
|
goas_proof_statements = { path = "../../proof_statements" }
|
||||||
proof_statements = { path = "../../../cl/proof_statements" }
|
ledger_proof_statements = { path = "../../../cl/ledger_proof_statements" }
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
///
|
///
|
||||||
/// Our goal: prove the zone authorized spending of funds
|
/// Our goal: prove the zone authorized spending of funds
|
||||||
use cl::merkle;
|
use cl::merkle;
|
||||||
use cl::nullifier::{Nullifier, NullifierSecret};
|
use cl::partial_tx::PtxRoot;
|
||||||
use goas_proof_statements::zone_funds::SpendFundsPrivate;
|
use goas_proof_statements::zone_funds::SpendFundsPrivate;
|
||||||
use proof_statements::death_constraint::DeathConstraintPublic;
|
use ledger_proof_statements::death_constraint::DeathConstraintPublic;
|
||||||
use risc0_zkvm::guest::env;
|
use risc0_zkvm::guest::env;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -19,11 +19,12 @@ fn main() {
|
||||||
balances_root,
|
balances_root,
|
||||||
} = env::read();
|
} = env::read();
|
||||||
|
|
||||||
let ptx_root = in_zone_funds.ptx_root();
|
let input_root = in_zone_funds.input_root();
|
||||||
let nf = Nullifier::new(in_zone_funds.input.nf_sk, in_zone_funds.input.nonce);
|
let output_root = out_zone_funds.output_root();
|
||||||
// check the zone funds note is the one in the spend event
|
|
||||||
assert_eq!(nf, spend_event.nf);
|
assert_eq!(output_root, zone_note.output_root());
|
||||||
assert_eq!(ptx_root, zone_note.ptx_root());
|
assert_eq!(output_root, spent_note.output_root());
|
||||||
|
assert_eq!(output_root, out_zone_funds.output_root());
|
||||||
|
|
||||||
// ** Assert the spent event was an output of the correct zone stf **
|
// ** Assert the spent event was an output of the correct zone stf **
|
||||||
// The zone state field is a merkle tree over:
|
// The zone state field is a merkle tree over:
|
||||||
|
@ -45,8 +46,6 @@ fn main() {
|
||||||
zone_note.output.note.state
|
zone_note.output.note.state
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(ptx_root, out_zone_funds.ptx_root());
|
|
||||||
|
|
||||||
// Check we return the rest of the funds back to the zone
|
// Check we return the rest of the funds back to the zone
|
||||||
let change = in_zone_funds
|
let change = in_zone_funds
|
||||||
.input
|
.input
|
||||||
|
@ -57,39 +56,41 @@ fn main() {
|
||||||
assert_eq!(out_zone_funds.output.note.value, change);
|
assert_eq!(out_zone_funds.output.note.value, change);
|
||||||
// zone funds output should have the same death constraints as the zone funds input
|
// zone funds output should have the same death constraints as the zone funds input
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out_zone_funds.output.note.death_constraint,
|
in_zone_funds.input.note.death_constraint,
|
||||||
in_zone_funds.input.note.death_constraint
|
out_zone_funds.output.note.death_constraint
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out_zone_funds.output.note.unit,
|
in_zone_funds.input.note.unit,
|
||||||
in_zone_funds.input.note.unit
|
out_zone_funds.output.note.unit
|
||||||
);
|
);
|
||||||
// zone funds nullifier, nonce and value blinding should be public so that everybody can spend it
|
// ensure zone fund sk's, blindings and nonces are propagated correctly.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out_zone_funds.output.nf_pk,
|
in_zone_funds.input.nf_sk.commit(),
|
||||||
NullifierSecret::from_bytes([0; 16]).commit()
|
out_zone_funds.output.nf_pk
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out_zone_funds.output.balance_blinding,
|
in_zone_funds.input.balance_blinding,
|
||||||
in_zone_funds.input.balance_blinding
|
out_zone_funds.output.balance_blinding
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
in_zone_funds.input.evolved_nonce(),
|
||||||
out_zone_funds.output.nonce,
|
out_zone_funds.output.nonce,
|
||||||
in_zone_funds.input.evolved_nonce()
|
|
||||||
);
|
);
|
||||||
// the state is propagated
|
// the state is propagated
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
in_zone_funds.input.note.state,
|
||||||
out_zone_funds.output.note.state,
|
out_zone_funds.output.note.state,
|
||||||
in_zone_funds.input.note.state
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(ptx_root, spent_note.ptx_root());
|
|
||||||
|
|
||||||
// check the correct amount of funds is being spent
|
// check the correct amount of funds is being spent
|
||||||
assert_eq!(spent_note.output.note.value, spend_event.amount);
|
assert_eq!(spent_note.output.note.value, spend_event.amount);
|
||||||
assert_eq!(spent_note.output.note.unit, in_zone_funds.input.note.unit);
|
assert_eq!(spent_note.output.note.unit, in_zone_funds.input.note.unit);
|
||||||
// check the correct recipient is being paid
|
// check the correct recipient is being paid
|
||||||
assert_eq!(spent_note.output.nf_pk, spend_event.to);
|
assert_eq!(spent_note.output.nf_pk, spend_event.to);
|
||||||
|
|
||||||
|
let nf = in_zone_funds.input.nullifier();
|
||||||
|
assert_eq!(nf, spend_event.fund_nf); // ensure this event was meant for this note.
|
||||||
|
|
||||||
|
let ptx_root = PtxRoot(merkle::node(input_root, output_root));
|
||||||
env::commit(&DeathConstraintPublic { ptx_root, nf });
|
env::commit(&DeathConstraintPublic { ptx_root, nf });
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ bincode = "1"
|
||||||
common = { path = "../../common" }
|
common = { path = "../../common" }
|
||||||
cl = { path = "../../../cl/cl" }
|
cl = { path = "../../../cl/cl" }
|
||||||
goas_proof_statements = { path = "../../proof_statements" }
|
goas_proof_statements = { path = "../../proof_statements" }
|
||||||
proof_statements = { path = "../../../cl/proof_statements" }
|
ledger_proof_statements = { path = "../../../cl/ledger_proof_statements" }
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use cl::{
|
use cl::{
|
||||||
input::InputWitness,
|
|
||||||
merkle,
|
merkle,
|
||||||
nullifier::{Nullifier, NullifierSecret},
|
nullifier::{Nullifier, NullifierSecret},
|
||||||
partial_tx::{MAX_INPUTS, MAX_OUTPUTS},
|
partial_tx::{MAX_INPUTS, MAX_OUTPUTS},
|
||||||
|
@ -7,37 +6,10 @@ use cl::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use common::*;
|
use common::*;
|
||||||
use goas_proof_statements::zone_funds::Spend;
|
use goas_proof_statements::zone_state::ZoneStatePrivate;
|
||||||
use proof_statements::{
|
use ledger_proof_statements::death_constraint::DeathConstraintPublic;
|
||||||
death_constraint::DeathConstraintPublic,
|
|
||||||
ptx::{PartialTxInputPrivate, PartialTxOutputPrivate},
|
|
||||||
};
|
|
||||||
use risc0_zkvm::guest::env;
|
use risc0_zkvm::guest::env;
|
||||||
|
|
||||||
fn withdraw(mut state: StateWitness, withdraw: Withdraw) -> StateWitness {
|
|
||||||
state.included_txs.push(Input::Withdraw(withdraw));
|
|
||||||
|
|
||||||
let Withdraw {
|
|
||||||
from,
|
|
||||||
amount,
|
|
||||||
to,
|
|
||||||
nf,
|
|
||||||
} = withdraw;
|
|
||||||
|
|
||||||
let from_balance = state.balances.entry(from).or_insert(0);
|
|
||||||
*from_balance = from
|
|
||||||
.checked_sub(amount)
|
|
||||||
.expect("insufficient funds in account");
|
|
||||||
let spend_auth = Spend {
|
|
||||||
amount: amount.into(),
|
|
||||||
to,
|
|
||||||
nf,
|
|
||||||
};
|
|
||||||
|
|
||||||
state.output_events.push(Event::Spend(spend_auth));
|
|
||||||
state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deposit(
|
fn deposit(
|
||||||
mut state: StateWitness,
|
mut state: StateWitness,
|
||||||
deposit: Deposit,
|
deposit: Deposit,
|
||||||
|
@ -114,7 +86,7 @@ fn deposit(
|
||||||
assert_eq!(nullifier, pub_inputs.nf);
|
assert_eq!(nullifier, pub_inputs.nf);
|
||||||
|
|
||||||
// 6) We're now ready to do the deposit!
|
// 6) We're now ready to do the deposit!
|
||||||
let amount = deposit.note.value as u32;
|
let amount = deposit.note.value;
|
||||||
let to = AccountId::from_be_bytes(<[u8; 4]>::try_from(&deposit.note.state[0..4]).unwrap());
|
let to = AccountId::from_be_bytes(<[u8; 4]>::try_from(&deposit.note.state[0..4]).unwrap());
|
||||||
|
|
||||||
let to_balance = state.balances.entry(to).or_insert(0);
|
let to_balance = state.balances.entry(to).or_insert(0);
|
||||||
|
@ -125,59 +97,68 @@ fn deposit(
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_zone_input(
|
fn validate_zone_transition(
|
||||||
input: &PartialTxInputPrivate,
|
in_note: cl::PartialTxInputWitness,
|
||||||
state: &StateWitness,
|
out_note: cl::PartialTxOutputWitness,
|
||||||
) -> (PtxRoot, Nullifier) {
|
in_meta: ZoneMetadata,
|
||||||
let ptx_root = input.ptx_root();
|
in_state_cm: StateCommitment,
|
||||||
let nf = Nullifier::new(input.input.nf_sk, input.input.nonce);
|
out_state: StateWitness,
|
||||||
|
) {
|
||||||
|
// Ensure input/output notes are committing to the expected states.
|
||||||
|
assert_eq!(in_note.input.note.state, in_state_cm.0);
|
||||||
|
assert_eq!(out_note.output.note.state, out_state.commit().0);
|
||||||
|
|
||||||
assert_eq!(input.input.note.state, <[u8; 32]>::from(state.commit()));
|
// zone metadata is propagated
|
||||||
// should not be possible to create one but let's put this check here just in case
|
assert_eq!(out_state.zone_metadata.id(), in_meta.id());
|
||||||
debug_assert_eq!(
|
|
||||||
input.input.note.death_constraint,
|
// ensure units match metadata
|
||||||
state.zone_metadata.zone_vk
|
assert_eq!(in_note.input.note.unit, in_meta.unit);
|
||||||
|
assert_eq!(out_note.output.note.unit, in_meta.unit);
|
||||||
|
|
||||||
|
// ensure constraints match metadata
|
||||||
|
assert_eq!(in_note.input.note.death_constraint, in_meta.zone_vk);
|
||||||
|
assert_eq!(out_note.output.note.death_constraint, in_meta.zone_vk);
|
||||||
|
|
||||||
|
// nullifier secret is propagated
|
||||||
|
assert_eq!(in_note.input.nf_sk.commit(), out_note.output.nf_pk);
|
||||||
|
|
||||||
|
// balance blinding is propagated
|
||||||
|
assert_eq!(
|
||||||
|
in_note.input.balance_blinding,
|
||||||
|
out_note.output.balance_blinding
|
||||||
);
|
);
|
||||||
|
|
||||||
(ptx_root, nf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_zone_output(
|
|
||||||
ptx: PtxRoot,
|
|
||||||
input: InputWitness,
|
|
||||||
output: PartialTxOutputPrivate,
|
|
||||||
state: &StateWitness,
|
|
||||||
) {
|
|
||||||
assert_eq!(ptx, output.ptx_root()); // the ptx root is the same as in the input
|
|
||||||
let output = output.output;
|
|
||||||
assert_eq!(output.note.state, <[u8; 32]>::from(state.commit())); // the state in the output is as calculated by this function
|
|
||||||
assert_eq!(output.note.death_constraint, state.zone_metadata.zone_vk); // the death constraint is the correct one
|
|
||||||
assert_eq!(output.nf_pk, NullifierSecret::from_bytes([0; 16]).commit()); // the nullifier secret is public
|
|
||||||
assert_eq!(output.balance_blinding, input.balance_blinding); // the balance blinding is the same as in the input
|
|
||||||
assert_eq!(output.note.unit, state.zone_metadata.unit); // the balance unit is the same as in the input
|
|
||||||
|
|
||||||
// the nonce is correctly evolved
|
// the nonce is correctly evolved
|
||||||
assert_eq!(output.nonce, input.evolved_nonce());
|
assert_eq!(in_note.input.evolved_nonce(), out_note.output.nonce);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let zone_in: PartialTxInputPrivate = env::read();
|
let ZoneStatePrivate {
|
||||||
let mut state: StateWitness = env::read();
|
mut state,
|
||||||
let zone_out: PartialTxOutputPrivate = env::read();
|
inputs,
|
||||||
|
zone_in,
|
||||||
|
zone_out,
|
||||||
|
} = env::read();
|
||||||
|
|
||||||
let (ptx_root, nf) = validate_zone_input(&zone_in, &state);
|
let pub_inputs = DeathConstraintPublic {
|
||||||
|
ptx_root: PtxRoot(cl::merkle::node(
|
||||||
|
zone_in.input_root(),
|
||||||
|
zone_out.output_root(),
|
||||||
|
)),
|
||||||
|
nf: zone_in.input.nullifier(),
|
||||||
|
};
|
||||||
|
|
||||||
let pub_inputs = DeathConstraintPublic { ptx_root, nf };
|
let in_meta = state.zone_metadata;
|
||||||
|
let in_state_cm = state.commit();
|
||||||
let inputs: Vec<Input> = env::read();
|
|
||||||
|
|
||||||
for input in inputs {
|
for input in inputs {
|
||||||
match input {
|
state = match input {
|
||||||
Input::Withdraw(input) => state = withdraw(state, input),
|
Input::Withdraw(input) => state.withdraw(input),
|
||||||
Input::Deposit(input) => state = deposit(state, input, pub_inputs),
|
Input::Deposit(input) => deposit(state, input, pub_inputs),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validate_zone_output(ptx_root, zone_in.input, zone_out, &state);
|
validate_zone_transition(zone_in, zone_out, in_meta, in_state_cm, state);
|
||||||
|
|
||||||
env::commit(&pub_inputs);
|
env::commit(&pub_inputs);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [ "cl", "ledger", "proof_statements", "risc0_proofs"]
|
members = [ "cl", "ledger", "ledger_proof_statements", "risc0_proofs"]
|
||||||
|
|
||||||
# Always optimize; building and running the risc0_proofs takes much longer without optimization.
|
# Always optimize; building and running the risc0_proofs takes much longer without optimization.
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|
|
@ -40,6 +40,10 @@ impl BalanceWitness {
|
||||||
Self(blinding)
|
Self(blinding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn unblinded() -> Self {
|
||||||
|
Self::new(Scalar::ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn random(mut rng: impl CryptoRngCore) -> Self {
|
pub fn random(mut rng: impl CryptoRngCore) -> Self {
|
||||||
Self::new(Scalar::random(&mut rng))
|
Self::new(Scalar::random(&mut rng))
|
||||||
}
|
}
|
||||||
|
@ -60,8 +64,8 @@ pub fn balance(value: u64, unit: Unit, blinding: Scalar) -> Unit {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::note::unit_point;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pederson_blinding_point_pre_compute() {
|
fn test_pederson_blinding_point_pre_compute() {
|
||||||
|
@ -77,30 +81,34 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_balance_zero_unitless() {
|
fn test_balance_zero_unitless() {
|
||||||
// Zero is the same across all units
|
// Zero is the same across all units
|
||||||
|
let (nmo, eth) = (unit_point("NMO"), unit_point("ETH"));
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let b = BalanceWitness::random(&mut rng);
|
let b = BalanceWitness::random(&mut rng);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
b.commit(&NoteWitness::basic(0, "NMO")),
|
b.commit(&NoteWitness::basic(0, nmo)),
|
||||||
b.commit(&NoteWitness::basic(0, "ETH")),
|
b.commit(&NoteWitness::basic(0, eth)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_balance_blinding() {
|
fn test_balance_blinding() {
|
||||||
// balances are blinded
|
// balances are blinded
|
||||||
|
let nmo = unit_point("NMO");
|
||||||
|
|
||||||
let r_a = Scalar::from(12u32);
|
let r_a = Scalar::from(12u32);
|
||||||
let r_b = Scalar::from(8u32);
|
let r_b = Scalar::from(8u32);
|
||||||
let bal_a = BalanceWitness::new(r_a);
|
let bal_a = BalanceWitness::new(r_a);
|
||||||
let bal_b = BalanceWitness::new(r_b);
|
let bal_b = BalanceWitness::new(r_b);
|
||||||
|
|
||||||
let note = NoteWitness::basic(10, "NMO");
|
let note = NoteWitness::basic(10, nmo);
|
||||||
|
|
||||||
let a = bal_a.commit(¬e);
|
let a = bal_a.commit(¬e);
|
||||||
let b = bal_b.commit(¬e);
|
let b = bal_b.commit(¬e);
|
||||||
|
|
||||||
assert_ne!(a, b);
|
assert_ne!(a, b);
|
||||||
|
|
||||||
let diff_note = NoteWitness::basic(0, "NMO");
|
let diff_note = NoteWitness::basic(0, nmo);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
a.0 - b.0,
|
a.0 - b.0,
|
||||||
BalanceWitness::new(r_a - r_b).commit(&diff_note).0
|
BalanceWitness::new(r_a - r_b).commit(&diff_note).0
|
||||||
|
@ -110,24 +118,28 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_balance_units() {
|
fn test_balance_units() {
|
||||||
// Unit's differentiate between values.
|
// Unit's differentiate between values.
|
||||||
|
let (nmo, eth) = (unit_point("NMO"), unit_point("ETH"));
|
||||||
|
|
||||||
let b = BalanceWitness::new(Scalar::from(1337u32));
|
let b = BalanceWitness::new(Scalar::from(1337u32));
|
||||||
|
|
||||||
let nmo = NoteWitness::basic(10, "NMO");
|
let nmo = NoteWitness::basic(10, nmo);
|
||||||
let eth = NoteWitness::basic(10, "ETH");
|
let eth = NoteWitness::basic(10, eth);
|
||||||
assert_ne!(b.commit(&nmo), b.commit(ð));
|
assert_ne!(b.commit(&nmo), b.commit(ð));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_balance_homomorphism() {
|
fn test_balance_homomorphism() {
|
||||||
|
let nmo = unit_point("NMO");
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let b1 = BalanceWitness::random(&mut rng);
|
let b1 = BalanceWitness::random(&mut rng);
|
||||||
let b2 = BalanceWitness::random(&mut rng);
|
let b2 = BalanceWitness::random(&mut rng);
|
||||||
let b_zero = BalanceWitness::new(Scalar::ZERO);
|
let b_zero = BalanceWitness::new(Scalar::ZERO);
|
||||||
|
|
||||||
let ten = NoteWitness::basic(10, "NMO");
|
let ten = NoteWitness::basic(10, nmo);
|
||||||
let eight = NoteWitness::basic(8, "NMO");
|
let eight = NoteWitness::basic(8, nmo);
|
||||||
let two = NoteWitness::basic(2, "NMO");
|
let two = NoteWitness::basic(2, nmo);
|
||||||
let zero = NoteWitness::basic(0, "NMO");
|
let zero = NoteWitness::basic(0, nmo);
|
||||||
|
|
||||||
// Values of same unit are homomorphic
|
// Values of same unit are homomorphic
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -29,7 +29,10 @@ impl Bundle {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{
|
use crate::{
|
||||||
input::InputWitness, note::NoteWitness, nullifier::NullifierSecret, output::OutputWitness,
|
input::InputWitness,
|
||||||
|
note::{unit_point, NoteWitness},
|
||||||
|
nullifier::NullifierSecret,
|
||||||
|
output::OutputWitness,
|
||||||
partial_tx::PartialTxWitness,
|
partial_tx::PartialTxWitness,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -38,21 +41,22 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bundle_balance() {
|
fn test_bundle_balance() {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
let (nmo, eth, crv) = (unit_point("NMO"), unit_point("ETH"), unit_point("CRV"));
|
||||||
|
|
||||||
let nf_a = NullifierSecret::random(&mut rng);
|
let nf_a = NullifierSecret::random(&mut rng);
|
||||||
let nf_b = NullifierSecret::random(&mut rng);
|
let nf_b = NullifierSecret::random(&mut rng);
|
||||||
let nf_c = NullifierSecret::random(&mut rng);
|
let nf_c = NullifierSecret::random(&mut rng);
|
||||||
|
|
||||||
let nmo_10_utxo =
|
let nmo_10_utxo =
|
||||||
OutputWitness::random(NoteWitness::basic(10, "NMO"), nf_a.commit(), &mut rng);
|
OutputWitness::random(NoteWitness::basic(10, nmo), nf_a.commit(), &mut rng);
|
||||||
let nmo_10_in = InputWitness::random(nmo_10_utxo, nf_a, &mut rng);
|
let nmo_10_in = InputWitness::random(nmo_10_utxo, nf_a, &mut rng);
|
||||||
|
|
||||||
let eth_23_utxo =
|
let eth_23_utxo =
|
||||||
OutputWitness::random(NoteWitness::basic(23, "ETH"), nf_b.commit(), &mut rng);
|
OutputWitness::random(NoteWitness::basic(23, eth), nf_b.commit(), &mut rng);
|
||||||
let eth_23_in = InputWitness::random(eth_23_utxo, nf_b, &mut rng);
|
let eth_23_in = InputWitness::random(eth_23_utxo, nf_b, &mut rng);
|
||||||
|
|
||||||
let crv_4840_out =
|
let crv_4840_out =
|
||||||
OutputWitness::random(NoteWitness::basic(4840, "CRV"), nf_c.commit(), &mut rng);
|
OutputWitness::random(NoteWitness::basic(4840, crv), nf_c.commit(), &mut rng);
|
||||||
|
|
||||||
let ptx_unbalanced = PartialTxWitness {
|
let ptx_unbalanced = PartialTxWitness {
|
||||||
inputs: vec![nmo_10_in, eth_23_in],
|
inputs: vec![nmo_10_in, eth_23_in],
|
||||||
|
@ -80,12 +84,12 @@ mod test {
|
||||||
|
|
||||||
let crv_4840_in = InputWitness::random(crv_4840_out, nf_c, &mut rng);
|
let crv_4840_in = InputWitness::random(crv_4840_out, nf_c, &mut rng);
|
||||||
let nmo_10_out = OutputWitness::random(
|
let nmo_10_out = OutputWitness::random(
|
||||||
NoteWitness::basic(10, "NMO"),
|
NoteWitness::basic(10, nmo),
|
||||||
NullifierSecret::random(&mut rng).commit(), // transferring to a random owner
|
NullifierSecret::random(&mut rng).commit(), // transferring to a random owner
|
||||||
&mut rng,
|
&mut rng,
|
||||||
);
|
);
|
||||||
let eth_23_out = OutputWitness::random(
|
let eth_23_out = OutputWitness::random(
|
||||||
NoteWitness::basic(23, "ETH"),
|
NoteWitness::basic(23, eth),
|
||||||
NullifierSecret::random(&mut rng).commit(), // transferring to a random owner
|
NullifierSecret::random(&mut rng).commit(), // transferring to a random owner
|
||||||
&mut rng,
|
&mut rng,
|
||||||
);
|
);
|
||||||
|
|
|
@ -41,6 +41,17 @@ impl InputWitness {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn public(output: crate::OutputWitness) -> Self {
|
||||||
|
let nf_sk = NullifierSecret::zero();
|
||||||
|
assert_eq!(nf_sk.commit(), output.nf_pk); // ensure the output was a public UTXO
|
||||||
|
Self {
|
||||||
|
note: output.note,
|
||||||
|
balance_blinding: BalanceWitness::unblinded(),
|
||||||
|
nf_sk,
|
||||||
|
nonce: output.nonce,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn evolved_nonce(&self) -> NullifierNonce {
|
pub fn evolved_nonce(&self) -> NullifierNonce {
|
||||||
self.nonce.evolve(&self.nf_sk)
|
self.nonce.evolve(&self.nf_sk)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,4 +15,6 @@ pub use input::{Input, InputWitness};
|
||||||
pub use note::{DeathCommitment, NoteCommitment, NoteWitness};
|
pub use note::{DeathCommitment, NoteCommitment, NoteWitness};
|
||||||
pub use nullifier::{Nullifier, NullifierCommitment, NullifierNonce, NullifierSecret};
|
pub use nullifier::{Nullifier, NullifierCommitment, NullifierNonce, NullifierSecret};
|
||||||
pub use output::{Output, OutputWitness};
|
pub use output::{Output, OutputWitness};
|
||||||
pub use partial_tx::{PartialTx, PartialTxWitness, PtxRoot};
|
pub use partial_tx::{
|
||||||
|
PartialTx, PartialTxInputWitness, PartialTxOutputWitness, PartialTxWitness, PtxRoot,
|
||||||
|
};
|
||||||
|
|
|
@ -19,7 +19,7 @@ pub fn death_commitment(death_constraint: &[u8]) -> DeathCommitment {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unit_point(unit: &str) -> Unit {
|
pub fn unit_point(unit: &str) -> Unit {
|
||||||
crate::crypto::hash_to_curve(unit.as_bytes())
|
crate::crypto::hash_to_curve(format!("NOMOS_CL_UNIT{unit}").as_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
|
@ -42,25 +42,20 @@ pub struct NoteWitness {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NoteWitness {
|
impl NoteWitness {
|
||||||
pub fn new(
|
pub fn new(value: u64, unit: Unit, death_constraint: [u8; 32], state: [u8; 32]) -> Self {
|
||||||
value: u64,
|
|
||||||
unit: impl Into<String>,
|
|
||||||
death_constraint: [u8; 32],
|
|
||||||
state: [u8; 32],
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
value,
|
value,
|
||||||
unit: unit_point(&unit.into()),
|
unit,
|
||||||
death_constraint,
|
death_constraint,
|
||||||
state,
|
state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn basic(value: u64, unit: impl Into<String>) -> Self {
|
pub fn basic(value: u64, unit: Unit) -> Self {
|
||||||
Self::new(value, unit, [0u8; 32], [0u8; 32])
|
Self::new(value, unit, [0u8; 32], [0u8; 32])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stateless(value: u64, unit: impl Into<String>, death_constraint: [u8; 32]) -> Self {
|
pub fn stateless(value: u64, unit: Unit, death_constraint: [u8; 32]) -> Self {
|
||||||
Self::new(value, unit, death_constraint, [0u8; 32])
|
Self::new(value, unit, death_constraint, [0u8; 32])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,18 +89,19 @@ impl NoteWitness {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::nullifier::NullifierSecret;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::nullifier::NullifierSecret;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_note_commit_permutations() {
|
fn test_note_commit_permutations() {
|
||||||
|
let (nmo, eth) = (unit_point("NMO"), unit_point("ETH"));
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
let nf_pk = NullifierSecret::random(&mut rng).commit();
|
let nf_pk = NullifierSecret::random(&mut rng).commit();
|
||||||
let nf_nonce = NullifierNonce::random(&mut rng);
|
let nf_nonce = NullifierNonce::random(&mut rng);
|
||||||
|
|
||||||
let reference_note = NoteWitness::basic(32, "NMO");
|
let reference_note = NoteWitness::basic(32, nmo);
|
||||||
|
|
||||||
// different notes under same nullifier produce different commitments
|
// different notes under same nullifier produce different commitments
|
||||||
let mutation_tests = [
|
let mutation_tests = [
|
||||||
|
@ -114,7 +110,7 @@ mod test {
|
||||||
..reference_note
|
..reference_note
|
||||||
},
|
},
|
||||||
NoteWitness {
|
NoteWitness {
|
||||||
unit: unit_point("ETH"),
|
unit: eth,
|
||||||
..reference_note
|
..reference_note
|
||||||
},
|
},
|
||||||
NoteWitness {
|
NoteWitness {
|
||||||
|
|
|
@ -43,6 +43,10 @@ impl NullifierSecret {
|
||||||
Self(sk)
|
Self(sk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn zero() -> Self {
|
||||||
|
Self([0u8; 16])
|
||||||
|
}
|
||||||
|
|
||||||
pub fn commit(&self) -> NullifierCommitment {
|
pub fn commit(&self) -> NullifierCommitment {
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(b"NOMOS_CL_NULL_COMMIT");
|
hasher.update(b"NOMOS_CL_NULL_COMMIT");
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
error::Error,
|
error::Error,
|
||||||
note::{NoteCommitment, NoteWitness},
|
note::{NoteCommitment, NoteWitness},
|
||||||
nullifier::{NullifierCommitment, NullifierNonce},
|
nullifier::{NullifierCommitment, NullifierNonce},
|
||||||
BalanceWitness,
|
BalanceWitness, NullifierSecret,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
@ -37,6 +37,15 @@ impl OutputWitness {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn public(note: NoteWitness, nonce: NullifierNonce) -> Self {
|
||||||
|
Self {
|
||||||
|
note,
|
||||||
|
balance_blinding: BalanceWitness::unblinded(),
|
||||||
|
nf_pk: NullifierSecret::zero().commit(),
|
||||||
|
nonce,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn commit_note(&self) -> NoteCommitment {
|
pub fn commit_note(&self) -> NoteCommitment {
|
||||||
self.note.commit(self.nf_pk, self.nonce)
|
self.note.commit(self.nf_pk, self.nonce)
|
||||||
}
|
}
|
||||||
|
@ -86,14 +95,15 @@ impl Output {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::nullifier::NullifierSecret;
|
use crate::{note::unit_point, nullifier::NullifierSecret};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_output_proof() {
|
fn test_output_proof() {
|
||||||
|
let (nmo, eth) = (unit_point("NMO"), unit_point("ETH"));
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
let witness = OutputWitness {
|
let witness = OutputWitness {
|
||||||
note: NoteWitness::basic(10, "NMO"),
|
note: NoteWitness::basic(10, nmo),
|
||||||
balance_blinding: BalanceWitness::random(&mut rng),
|
balance_blinding: BalanceWitness::random(&mut rng),
|
||||||
nf_pk: NullifierSecret::random(&mut rng).commit(),
|
nf_pk: NullifierSecret::random(&mut rng).commit(),
|
||||||
nonce: NullifierNonce::random(&mut rng),
|
nonce: NullifierNonce::random(&mut rng),
|
||||||
|
@ -106,11 +116,11 @@ mod test {
|
||||||
|
|
||||||
let wrong_witnesses = [
|
let wrong_witnesses = [
|
||||||
OutputWitness {
|
OutputWitness {
|
||||||
note: NoteWitness::basic(11, "NMO"),
|
note: NoteWitness::basic(11, nmo),
|
||||||
..witness
|
..witness
|
||||||
},
|
},
|
||||||
OutputWitness {
|
OutputWitness {
|
||||||
note: NoteWitness::basic(10, "ETH"),
|
note: NoteWitness::basic(10, eth),
|
||||||
..witness
|
..witness
|
||||||
},
|
},
|
||||||
OutputWitness {
|
OutputWitness {
|
||||||
|
|
|
@ -60,6 +60,26 @@ impl PartialTxWitness {
|
||||||
|
|
||||||
BalanceWitness(out_sum - in_sum)
|
BalanceWitness(out_sum - in_sum)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn input_witness(&self, idx: usize) -> PartialTxInputWitness {
|
||||||
|
let input_bytes =
|
||||||
|
Vec::from_iter(self.inputs.iter().map(|i| i.commit().to_bytes().to_vec()));
|
||||||
|
let input_merkle_leaves = merkle::padded_leaves::<MAX_INPUTS>(&input_bytes);
|
||||||
|
|
||||||
|
let path = merkle::path(input_merkle_leaves, idx);
|
||||||
|
let input = self.inputs[idx];
|
||||||
|
PartialTxInputWitness { input, path }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn output_witness(&self, idx: usize) -> PartialTxOutputWitness {
|
||||||
|
let output_bytes =
|
||||||
|
Vec::from_iter(self.outputs.iter().map(|o| o.commit().to_bytes().to_vec()));
|
||||||
|
let output_merkle_leaves = merkle::padded_leaves::<MAX_OUTPUTS>(&output_bytes);
|
||||||
|
|
||||||
|
let path = merkle::path(output_merkle_leaves, idx);
|
||||||
|
let output = self.outputs[idx];
|
||||||
|
PartialTxOutputWitness { output, path }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialTx {
|
impl PartialTx {
|
||||||
|
@ -81,24 +101,6 @@ impl PartialTx {
|
||||||
merkle::root::<MAX_OUTPUTS>(output_merkle_leaves)
|
merkle::root::<MAX_OUTPUTS>(output_merkle_leaves)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn input_merkle_path(&self, idx: usize) -> Vec<merkle::PathNode> {
|
|
||||||
let input_bytes =
|
|
||||||
Vec::from_iter(self.inputs.iter().map(Input::to_bytes).map(Vec::from_iter));
|
|
||||||
let input_merkle_leaves = merkle::padded_leaves::<MAX_INPUTS>(&input_bytes);
|
|
||||||
merkle::path(input_merkle_leaves, idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn output_merkle_path(&self, idx: usize) -> Vec<merkle::PathNode> {
|
|
||||||
let output_bytes = Vec::from_iter(
|
|
||||||
self.outputs
|
|
||||||
.iter()
|
|
||||||
.map(Output::to_bytes)
|
|
||||||
.map(Vec::from_iter),
|
|
||||||
);
|
|
||||||
let output_merkle_leaves = merkle::padded_leaves::<MAX_OUTPUTS>(&output_bytes);
|
|
||||||
merkle::path(output_merkle_leaves, idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn root(&self) -> PtxRoot {
|
pub fn root(&self) -> PtxRoot {
|
||||||
let input_root = self.input_root();
|
let input_root = self.input_root();
|
||||||
let output_root = self.output_root();
|
let output_root = self.output_root();
|
||||||
|
@ -114,15 +116,47 @@ impl PartialTx {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An input to a partial transaction
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct PartialTxInputWitness {
|
||||||
|
pub input: InputWitness,
|
||||||
|
pub path: Vec<merkle::PathNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialTxInputWitness {
|
||||||
|
pub fn input_root(&self) -> [u8; 32] {
|
||||||
|
let leaf = merkle::leaf(&self.input.commit().to_bytes());
|
||||||
|
merkle::path_root(leaf, &self.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An output to a partial transaction
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct PartialTxOutputWitness {
|
||||||
|
pub output: OutputWitness,
|
||||||
|
pub path: Vec<merkle::PathNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialTxOutputWitness {
|
||||||
|
pub fn output_root(&self) -> [u8; 32] {
|
||||||
|
let leaf = merkle::leaf(&self.output.commit().to_bytes());
|
||||||
|
merkle::path_root(leaf, &self.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
use crate::{note::NoteWitness, nullifier::NullifierSecret};
|
use crate::{
|
||||||
|
note::{unit_point, NoteWitness},
|
||||||
|
nullifier::NullifierSecret,
|
||||||
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_partial_tx_balance() {
|
fn test_partial_tx_balance() {
|
||||||
|
let (nmo, eth, crv) = (unit_point("NMO"), unit_point("ETH"), unit_point("CRV"));
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
let nf_a = NullifierSecret::random(&mut rng);
|
let nf_a = NullifierSecret::random(&mut rng);
|
||||||
|
@ -130,15 +164,15 @@ mod test {
|
||||||
let nf_c = NullifierSecret::random(&mut rng);
|
let nf_c = NullifierSecret::random(&mut rng);
|
||||||
|
|
||||||
let nmo_10_utxo =
|
let nmo_10_utxo =
|
||||||
OutputWitness::random(NoteWitness::basic(10, "NMO"), nf_a.commit(), &mut rng);
|
OutputWitness::random(NoteWitness::basic(10, nmo), nf_a.commit(), &mut rng);
|
||||||
let nmo_10 = InputWitness::random(nmo_10_utxo, nf_a, &mut rng);
|
let nmo_10 = InputWitness::random(nmo_10_utxo, nf_a, &mut rng);
|
||||||
|
|
||||||
let eth_23_utxo =
|
let eth_23_utxo =
|
||||||
OutputWitness::random(NoteWitness::basic(23, "ETH"), nf_b.commit(), &mut rng);
|
OutputWitness::random(NoteWitness::basic(23, eth), nf_b.commit(), &mut rng);
|
||||||
let eth_23 = InputWitness::random(eth_23_utxo, nf_b, &mut rng);
|
let eth_23 = InputWitness::random(eth_23_utxo, nf_b, &mut rng);
|
||||||
|
|
||||||
let crv_4840 =
|
let crv_4840 =
|
||||||
OutputWitness::random(NoteWitness::basic(4840, "CRV"), nf_c.commit(), &mut rng);
|
OutputWitness::random(NoteWitness::basic(4840, crv), nf_c.commit(), &mut rng);
|
||||||
|
|
||||||
let ptx_witness = PartialTxWitness {
|
let ptx_witness = PartialTxWitness {
|
||||||
inputs: vec![nmo_10, eth_23],
|
inputs: vec![nmo_10, eth_23],
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use cl::note::unit_point;
|
||||||
use rand_core::CryptoRngCore;
|
use rand_core::CryptoRngCore;
|
||||||
|
|
||||||
fn receive_utxo(
|
fn receive_utxo(
|
||||||
|
@ -10,6 +11,7 @@ fn receive_utxo(
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simple_transfer() {
|
fn test_simple_transfer() {
|
||||||
|
let nmo = unit_point("NMO");
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
let sender_nf_sk = cl::NullifierSecret::random(&mut rng);
|
let sender_nf_sk = cl::NullifierSecret::random(&mut rng);
|
||||||
|
@ -18,13 +20,13 @@ fn test_simple_transfer() {
|
||||||
let recipient_nf_pk = cl::NullifierSecret::random(&mut rng).commit();
|
let recipient_nf_pk = cl::NullifierSecret::random(&mut rng).commit();
|
||||||
|
|
||||||
// Assume the sender has received an unspent output from somewhere
|
// Assume the sender has received an unspent output from somewhere
|
||||||
let utxo = receive_utxo(cl::NoteWitness::basic(10, "NMO"), sender_nf_pk, &mut rng);
|
let utxo = receive_utxo(cl::NoteWitness::basic(10, nmo), sender_nf_pk, &mut rng);
|
||||||
|
|
||||||
// and wants to send 8 NMO to some recipient and return 2 NMO to itself.
|
// and wants to send 8 NMO to some recipient and return 2 NMO to itself.
|
||||||
let recipient_output =
|
let recipient_output =
|
||||||
cl::OutputWitness::random(cl::NoteWitness::basic(8, "NMO"), recipient_nf_pk, &mut rng);
|
cl::OutputWitness::random(cl::NoteWitness::basic(8, nmo), recipient_nf_pk, &mut rng);
|
||||||
let change_output =
|
let change_output =
|
||||||
cl::OutputWitness::random(cl::NoteWitness::basic(2, "NMO"), sender_nf_pk, &mut rng);
|
cl::OutputWitness::random(cl::NoteWitness::basic(2, nmo), sender_nf_pk, &mut rng);
|
||||||
|
|
||||||
let ptx_witness = cl::PartialTxWitness {
|
let ptx_witness = cl::PartialTxWitness {
|
||||||
inputs: vec![cl::InputWitness::random(utxo, sender_nf_sk, &mut rng)],
|
inputs: vec![cl::InputWitness::random(utxo, sender_nf_sk, &mut rng)],
|
||||||
|
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cl = { path = "../cl" }
|
cl = { path = "../cl" }
|
||||||
proof_statements = { path = "../proof_statements" }
|
ledger_proof_statements = { path = "../ledger_proof_statements" }
|
||||||
nomos_cl_risc0_proofs = { path = "../risc0_proofs" }
|
nomos_cl_risc0_proofs = { path = "../risc0_proofs" }
|
||||||
risc0-zkvm = { version = "1.0", features = ["prove", "metal"] }
|
risc0-zkvm = { version = "1.0", features = ["prove", "metal"] }
|
||||||
risc0-groth16 = { version = "1.0" }
|
risc0-groth16 = { version = "1.0" }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use proof_statements::death_constraint::DeathConstraintPublic;
|
use ledger_proof_statements::death_constraint::DeathConstraintPublic;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
|
@ -7,11 +7,11 @@ pub type Risc0DeathConstraintId = [u32; 8];
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DeathProof {
|
pub struct DeathProof {
|
||||||
constraint: Risc0DeathConstraintId,
|
pub constraint: Risc0DeathConstraintId,
|
||||||
risc0_receipt: risc0_zkvm::Receipt,
|
pub risc0_receipt: risc0_zkvm::Receipt,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn risc0_id_to_cl_death_constraint(risc0_id: Risc0DeathConstraintId) -> [u8; 32] {
|
pub fn risc0_id_to_cl_death_constraint(risc0_id: Risc0DeathConstraintId) -> [u8; 32] {
|
||||||
// RISC0 proof ids have the format: [u32; 8], and cl death constraint ids have the format [u8; 32].
|
// RISC0 proof ids have the format: [u32; 8], and cl death constraint ids have the format [u8; 32].
|
||||||
// CL death constraints are meaningless beyond being binding, therefore we merely need a collision
|
// CL death constraints are meaningless beyond being binding, therefore we merely need a collision
|
||||||
// resisitant mapping of RISC0 ids to cl death constraints.
|
// resisitant mapping of RISC0 ids to cl death constraints.
|
||||||
|
@ -26,6 +26,16 @@ fn risc0_id_to_cl_death_constraint(risc0_id: Risc0DeathConstraintId) -> [u8; 32]
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeathProof {
|
impl DeathProof {
|
||||||
|
pub fn from_risc0(
|
||||||
|
risc0_id: Risc0DeathConstraintId,
|
||||||
|
risc0_receipt: risc0_zkvm::Receipt,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
constraint: risc0_id,
|
||||||
|
risc0_receipt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn death_commitment(&self) -> cl::DeathCommitment {
|
pub fn death_commitment(&self) -> cl::DeathCommitment {
|
||||||
cl::note::death_commitment(&risc0_id_to_cl_death_constraint(self.constraint))
|
cl::note::death_commitment(&risc0_id_to_cl_death_constraint(self.constraint))
|
||||||
}
|
}
|
||||||
|
@ -75,9 +85,6 @@ impl DeathProof {
|
||||||
// extract the receipt.
|
// extract the receipt.
|
||||||
let receipt = prove_info.receipt;
|
let receipt = prove_info.receipt;
|
||||||
|
|
||||||
Self {
|
Self::from_risc0(nomos_cl_risc0_proofs::DEATH_CONSTRAINT_NOP_ID, receipt)
|
||||||
constraint: nomos_cl_risc0_proofs::DEATH_CONSTRAINT_NOP_ID,
|
|
||||||
risc0_receipt: receipt,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use proof_statements::input::{InputPrivate, InputPublic};
|
use ledger_proof_statements::input::{InputPrivate, InputPublic};
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
|
@ -89,16 +89,18 @@ fn note_commitment_leaves(note_commitments: &[cl::NoteCommitment]) -> [[u8; 32];
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use rand::thread_rng;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use cl::note::unit_point;
|
||||||
|
use rand::thread_rng;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_input_prover() {
|
fn test_input_prover() {
|
||||||
|
let nmo = unit_point("NMO");
|
||||||
|
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
let input = cl::InputWitness {
|
let input = cl::InputWitness {
|
||||||
note: cl::NoteWitness::basic(32, "NMO"),
|
note: cl::NoteWitness::basic(32, nmo),
|
||||||
balance_blinding: cl::BalanceWitness::random(&mut rng),
|
balance_blinding: cl::BalanceWitness::random(&mut rng),
|
||||||
nf_sk: cl::NullifierSecret::random(&mut rng),
|
nf_sk: cl::NullifierSecret::random(&mut rng),
|
||||||
nonce: cl::NullifierNonce::random(&mut rng),
|
nonce: cl::NullifierNonce::random(&mut rng),
|
||||||
|
@ -141,7 +143,7 @@ mod test {
|
||||||
InputPublic {
|
InputPublic {
|
||||||
input: cl::Input {
|
input: cl::Input {
|
||||||
balance: cl::BalanceWitness::random(&mut rng)
|
balance: cl::BalanceWitness::random(&mut rng)
|
||||||
.commit(&cl::NoteWitness::basic(32, "NMO")),
|
.commit(&cl::NoteWitness::basic(32, nmo)),
|
||||||
..expected_public_inputs.input
|
..expected_public_inputs.input
|
||||||
},
|
},
|
||||||
..expected_public_inputs
|
..expected_public_inputs
|
||||||
|
|
|
@ -4,3 +4,5 @@ pub mod error;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod output;
|
pub mod output;
|
||||||
pub mod partial_tx;
|
pub mod partial_tx;
|
||||||
|
|
||||||
|
pub use death_constraint::DeathProof;
|
||||||
|
|
|
@ -55,16 +55,18 @@ impl ProvedOutput {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use rand::thread_rng;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use cl::note::unit_point;
|
||||||
|
use rand::thread_rng;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_output_prover() {
|
fn test_output_prover() {
|
||||||
|
let nmo = unit_point("NMO");
|
||||||
|
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
let output = cl::OutputWitness {
|
let output = cl::OutputWitness {
|
||||||
note: cl::NoteWitness::basic(32, "NMO"),
|
note: cl::NoteWitness::basic(32, nmo),
|
||||||
balance_blinding: cl::BalanceWitness::random(&mut rng),
|
balance_blinding: cl::BalanceWitness::random(&mut rng),
|
||||||
nf_pk: cl::NullifierSecret::random(&mut rng).commit(),
|
nf_pk: cl::NullifierSecret::random(&mut rng).commit(),
|
||||||
nonce: cl::NullifierNonce::random(&mut rng),
|
nonce: cl::NullifierNonce::random(&mut rng),
|
||||||
|
@ -79,19 +81,19 @@ mod test {
|
||||||
|
|
||||||
let wrong_output_cms = [
|
let wrong_output_cms = [
|
||||||
cl::Output {
|
cl::Output {
|
||||||
note_comm: cl::NoteWitness::basic(100, "NMO").commit(
|
note_comm: cl::NoteWitness::basic(100, nmo).commit(
|
||||||
cl::NullifierSecret::random(&mut rng).commit(),
|
cl::NullifierSecret::random(&mut rng).commit(),
|
||||||
cl::NullifierNonce::random(&mut rng),
|
cl::NullifierNonce::random(&mut rng),
|
||||||
),
|
),
|
||||||
..expected_output_cm
|
..expected_output_cm
|
||||||
},
|
},
|
||||||
cl::Output {
|
cl::Output {
|
||||||
note_comm: cl::NoteWitness::basic(100, "NMO").commit(
|
note_comm: cl::NoteWitness::basic(100, nmo).commit(
|
||||||
cl::NullifierSecret::random(&mut rng).commit(),
|
cl::NullifierSecret::random(&mut rng).commit(),
|
||||||
cl::NullifierNonce::random(&mut rng),
|
cl::NullifierNonce::random(&mut rng),
|
||||||
),
|
),
|
||||||
balance: cl::BalanceWitness::random(&mut rng)
|
balance: cl::BalanceWitness::random(&mut rng)
|
||||||
.commit(&cl::NoteWitness::basic(100, "NMO")),
|
.commit(&cl::NoteWitness::basic(100, nmo)),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -103,10 +105,11 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_zero_output_is_rejected() {
|
fn test_zero_output_is_rejected() {
|
||||||
|
let nmo = unit_point("NMO");
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
let output = cl::OutputWitness::random(
|
let output = cl::OutputWitness::random(
|
||||||
cl::NoteWitness::basic(0, "NMO"),
|
cl::NoteWitness::basic(0, nmo),
|
||||||
cl::NullifierSecret::random(&mut rng).commit(),
|
cl::NullifierSecret::random(&mut rng).commit(),
|
||||||
&mut rng,
|
&mut rng,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use proof_statements::death_constraint::DeathConstraintPublic;
|
use ledger_proof_statements::death_constraint::DeathConstraintPublic;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
death_constraint::DeathProof, error::Result, input::ProvedInput, output::ProvedOutput,
|
death_constraint::DeathProof, error::Result, input::ProvedInput, output::ProvedOutput,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use cl::note::unit_point;
|
||||||
use ledger::{bundle::ProvedBundle, death_constraint::DeathProof, partial_tx::ProvedPartialTx};
|
use ledger::{bundle::ProvedBundle, death_constraint::DeathProof, partial_tx::ProvedPartialTx};
|
||||||
use rand_core::CryptoRngCore;
|
use rand_core::CryptoRngCore;
|
||||||
|
|
||||||
|
@ -29,6 +30,8 @@ fn receive_utxo(
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simple_transfer() {
|
fn test_simple_transfer() {
|
||||||
|
let nmo = unit_point("NMO");
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
// alice is sending 8 NMO to bob.
|
// alice is sending 8 NMO to bob.
|
||||||
|
@ -38,19 +41,18 @@ fn test_simple_transfer() {
|
||||||
|
|
||||||
// Alice has an unspent note worth 10 NMO
|
// Alice has an unspent note worth 10 NMO
|
||||||
let utxo = receive_utxo(
|
let utxo = receive_utxo(
|
||||||
cl::NoteWitness::stateless(10, "NMO", DeathProof::nop_constraint()),
|
cl::NoteWitness::stateless(10, nmo, DeathProof::nop_constraint()),
|
||||||
alice.pk(),
|
alice.pk(),
|
||||||
&mut rng,
|
&mut rng,
|
||||||
);
|
);
|
||||||
let alices_input = cl::InputWitness::random(utxo, alice.sk(), &mut rng);
|
let alices_input = cl::InputWitness::random(utxo, alice.sk(), &mut rng);
|
||||||
|
|
||||||
// Alice wants to send 8 NMO to bob
|
// Alice wants to send 8 NMO to bob
|
||||||
let bobs_output =
|
let bobs_output = cl::OutputWitness::random(cl::NoteWitness::basic(8, nmo), bob.pk(), &mut rng);
|
||||||
cl::OutputWitness::random(cl::NoteWitness::basic(8, "NMO"), bob.pk(), &mut rng);
|
|
||||||
|
|
||||||
// .. and return the 2 NMO in change to herself.
|
// .. and return the 2 NMO in change to herself.
|
||||||
let change_output =
|
let change_output =
|
||||||
cl::OutputWitness::random(cl::NoteWitness::basic(2, "NMO"), alice.pk(), &mut rng);
|
cl::OutputWitness::random(cl::NoteWitness::basic(2, nmo), alice.pk(), &mut rng);
|
||||||
|
|
||||||
// Construct the ptx consuming Alices inputs and producing the two outputs.
|
// Construct the ptx consuming Alices inputs and producing the two outputs.
|
||||||
let ptx_witness = cl::PartialTxWitness {
|
let ptx_witness = cl::PartialTxWitness {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "proof_statements"
|
name = "ledger_proof_statements"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
pub mod death_constraint;
|
pub mod death_constraint;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod ptx;
|
|
|
@ -1,36 +0,0 @@
|
||||||
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.note_commitment().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))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,7 +9,7 @@ edition = "2021"
|
||||||
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
|
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
cl = { path = "../../cl" }
|
cl = { path = "../../cl" }
|
||||||
proof_statements = { path = "../../proof_statements" }
|
ledger_proof_statements = { path = "../../ledger_proof_statements" }
|
||||||
|
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
|
|
|
@ -9,7 +9,7 @@ edition = "2021"
|
||||||
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
|
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
cl = { path = "../../cl" }
|
cl = { path = "../../cl" }
|
||||||
proof_statements = { path = "../../proof_statements" }
|
ledger_proof_statements = { path = "../../ledger_proof_statements" }
|
||||||
|
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/// Death Constraint No-op Proof
|
/// Death Constraint No-op Proof
|
||||||
use proof_statements::death_constraint::DeathConstraintPublic;
|
use ledger_proof_statements::death_constraint::DeathConstraintPublic;
|
||||||
use risc0_zkvm::guest::env;
|
use risc0_zkvm::guest::env;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -9,7 +9,7 @@ edition = "2021"
|
||||||
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
|
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
cl = { path = "../../cl" }
|
cl = { path = "../../cl" }
|
||||||
proof_statements = { path = "../../proof_statements" }
|
ledger_proof_statements = { path = "../../ledger_proof_statements" }
|
||||||
|
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/// Input Proof
|
/// Input Proof
|
||||||
use cl::merkle;
|
use cl::merkle;
|
||||||
use proof_statements::input::{InputPrivate, InputPublic};
|
use ledger_proof_statements::input::{InputPrivate, InputPublic};
|
||||||
use risc0_zkvm::guest::env;
|
use risc0_zkvm::guest::env;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -9,7 +9,7 @@ edition = "2021"
|
||||||
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
|
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
cl = { path = "../../cl" }
|
cl = { path = "../../cl" }
|
||||||
proof_statements = { path = "../../proof_statements" }
|
ledger_proof_statements = { path = "../../ledger_proof_statements" }
|
||||||
|
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
|
|
Loading…
Reference in New Issue