Merge pull request #27 from logos-co/goas/collision-resistant-nullifier

goas: make nullifier collision resistant
This commit is contained in:
davidrusu 2024-08-19 13:01:01 +04:00 committed by GitHub
commit fea5dfbcbb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 201 additions and 123 deletions

View File

@ -9,3 +9,7 @@ cl = { path = "../../cl/cl" }
ledger_proof_statements = { path = "../../cl/ledger_proof_statements" } ledger_proof_statements = { path = "../../cl/ledger_proof_statements" }
once_cell = "1" once_cell = "1"
sha2 = "0.10" 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"

View File

@ -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 once_cell::sync::Lazy;
use rand_core::CryptoRngCore;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::collections::BTreeMap; use std::collections::BTreeMap;
@ -13,11 +18,15 @@ pub const MAX_EVENTS: usize = 1 << 8;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
pub struct StateCommitment(pub [u8; 32]); 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? // PLACEHOLDER: this is probably going to be NMO?
pub static ZONE_CL_FUNDS_UNIT: Lazy<Unit> = Lazy::new(|| cl::note::unit_point("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)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct ZoneMetadata { pub struct ZoneMetadata {
pub zone_vk: [u8; 32], pub zone_vk: [u8; 32],
@ -40,7 +49,6 @@ pub struct StateWitness {
pub balances: BTreeMap<AccountId, u64>, pub balances: BTreeMap<AccountId, u64>,
pub included_txs: Vec<Tx>, pub included_txs: Vec<Tx>,
pub zone_metadata: ZoneMetadata, pub zone_metadata: ZoneMetadata,
pub nonce: [u8; 32],
} }
impl StateWitness { impl StateWitness {
@ -50,7 +58,6 @@ impl StateWitness {
pub fn state_roots(&self) -> StateRoots { pub fn state_roots(&self) -> StateRoots {
StateRoots { StateRoots {
nonce: self.nonce,
tx_root: self.included_txs_root(), tx_root: self.included_txs_root(),
zone_id: self.zone_metadata.id(), zone_id: self.zone_metadata.id(),
balance_root: self.balances_root(), balance_root: self.balances_root(),
@ -95,7 +102,7 @@ impl StateWitness {
} }
pub fn included_tx_witness(&self, idx: usize) -> IncludedTxWitness { 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); let path = merkle::path(self.included_tx_merkle_leaves(), idx);
IncludedTxWitness { tx, path } IncludedTxWitness { tx, path }
} }
@ -103,7 +110,7 @@ impl StateWitness {
pub fn balances_root(&self) -> [u8; 32] { pub fn balances_root(&self) -> [u8; 32] {
let balance_bytes = Vec::from_iter(self.balances.iter().map(|(owner, balance)| { let balance_bytes = Vec::from_iter(self.balances.iter().map(|(owner, balance)| {
let mut bytes: Vec<u8> = vec![]; let mut bytes: Vec<u8> = vec![];
bytes.extend(owner.to_le_bytes()); bytes.extend(owner);
bytes.extend(balance.to_le_bytes()); bytes.extend(balance.to_le_bytes());
bytes bytes
})); }));
@ -115,19 +122,6 @@ impl StateWitness {
self.balances.values().sum() 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] { fn included_tx_merkle_leaves(&self) -> [[u8; 32]; MAX_TXS] {
let tx_bytes = self let tx_bytes = self
.included_txs .included_txs
@ -151,10 +145,10 @@ pub struct Withdraw {
} }
impl Withdraw { impl Withdraw {
pub fn to_bytes(&self) -> [u8; 12] { pub fn to_bytes(&self) -> [u8; 40] {
let mut bytes = [0; 12]; let mut bytes = [0; 40];
bytes[0..4].copy_from_slice(&self.from.to_le_bytes()); bytes[0..PUBLIC_KEY_LENGTH].copy_from_slice(&self.from);
bytes[4..12].copy_from_slice(&self.amount.to_le_bytes()); bytes[PUBLIC_KEY_LENGTH..PUBLIC_KEY_LENGTH + 8].copy_from_slice(&self.amount.to_le_bytes());
bytes bytes
} }
} }
@ -167,31 +161,58 @@ pub struct Deposit {
} }
impl Deposit { impl Deposit {
pub fn to_bytes(&self) -> [u8; 12] { pub fn to_bytes(&self) -> [u8; 40] {
let mut bytes = [0; 12]; let mut bytes = [0; 40];
bytes[0..4].copy_from_slice(&self.to.to_le_bytes()); bytes[0..PUBLIC_KEY_LENGTH].copy_from_slice(&self.to);
bytes[4..12].copy_from_slice(&self.amount.to_le_bytes()); bytes[PUBLIC_KEY_LENGTH..PUBLIC_KEY_LENGTH + 8].copy_from_slice(&self.amount.to_le_bytes());
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 /// A Tx that is executed in the zone if and only if the bind is
/// present is the same partial transaction /// 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 struct BoundTx {
pub tx: Tx, pub tx: Tx,
pub bind: PartialTxInputWitness, pub bind: NoteCommitment,
} }
impl BoundTx { impl BoundTx {
pub fn to_bytes(&self) -> Vec<u8> { pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = self.tx.to_bytes(); let mut bytes = Vec::new();
bytes.extend(self.bind.input.commit().to_bytes()); bytes.extend(self.tx.to_bytes());
bytes.extend(self.bind.as_bytes());
bytes bytes
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Tx { pub enum Tx {
Withdraw(Withdraw), Withdraw(Withdraw),
Deposit(Deposit), Deposit(Deposit),
@ -204,6 +225,13 @@ impl Tx {
Tx::Deposit(deposit) => deposit.to_bytes().to_vec(), 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)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -221,25 +249,19 @@ impl IncludedTxWitness {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct StateRoots { pub struct StateRoots {
pub nonce: [u8; 32],
pub tx_root: [u8; 32], pub tx_root: [u8; 32],
pub zone_id: [u8; 32], pub zone_id: [u8; 32],
pub balance_root: [u8; 32], pub balance_root: [u8; 32],
} }
impl StateRoots { impl StateRoots {
/// Merkle tree over: /// Merkle tree over: [txs, zoneid, balances]
/// root
/// / \
/// io state
/// / \ / \
/// nonce txs zoneid balances
pub fn commit(&self) -> StateCommitment { pub fn commit(&self) -> StateCommitment {
StateCommitment(cl::merkle::root([ let leaves = cl::merkle::padded_leaves::<4>(&[
self.nonce, self.tx_root.to_vec(),
self.tx_root, self.zone_id.to_vec(),
self.zone_id, self.balance_root.to_vec(),
self.balance_root, ]);
])) StateCommitment(cl::merkle::root(leaves))
} }
} }

View File

@ -1,6 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use common::{BoundTx, StateWitness, Tx, ZoneMetadata}; use common::{AccountId, SignedBoundTx, StateWitness, Tx, ZoneMetadata};
use goas_proof_statements::{ use goas_proof_statements::{
user_note::UserAtomicTransfer, zone_funds::SpendFundsPrivate, zone_state::ZoneStatePrivate, user_note::UserAtomicTransfer, zone_funds::SpendFundsPrivate, zone_state::ZoneStatePrivate,
}; };
@ -16,14 +16,13 @@ pub struct ZoneNotes {
impl ZoneNotes { impl ZoneNotes {
pub fn new_with_balances( pub fn new_with_balances(
zone_name: &str, zone_name: &str,
balances: BTreeMap<u32, u64>, balances: BTreeMap<AccountId, u64>,
mut rng: impl CryptoRngCore, mut rng: impl CryptoRngCore,
) -> Self { ) -> Self {
let state = StateWitness { let state = StateWitness {
balances, balances,
included_txs: vec![], included_txs: vec![],
zone_metadata: zone_metadata(zone_name), zone_metadata: zone_metadata(zone_name),
nonce: [0; 32],
}; };
let state_note = zone_state_utxo(&state, &mut rng); let state_note = zone_state_utxo(&state, &mut rng);
let fund_note = zone_fund_utxo(state.total_balance(), state.zone_metadata, &mut rng); let fund_note = zone_fund_utxo(state.total_balance(), state.zone_metadata, &mut rng);
@ -46,7 +45,6 @@ impl ZoneNotes {
for tx in txs { for tx in txs {
self.state = self.state.apply(tx); self.state = self.state.apply(tx);
} }
self.state = self.state.evolve_nonce();
let state_in = self.state_input_witness(); let state_in = self.state_input_witness();
self.state_note = cl::OutputWitness::public( self.state_note = cl::OutputWitness::public(
@ -54,7 +52,7 @@ impl ZoneNotes {
state: self.state.commit().0, state: self.state.commit().0,
..state_in.note ..state_in.note
}, },
state_in.evolved_nonce(), state_in.evolved_nonce(b"STATE_NONCE"),
); );
let fund_in = self.fund_input_witness(); let fund_in = self.fund_input_witness();
@ -63,7 +61,7 @@ impl ZoneNotes {
value: self.state.total_balance(), value: self.state.total_balance(),
..fund_in.note ..fund_in.note
}, },
cl::NullifierNonce::from_bytes(self.state.nonce), state_in.evolved_nonce(b"FUND_NONCE"),
); );
self self
} }
@ -123,7 +121,7 @@ pub fn zone_metadata(zone_mnemonic: &str) -> ZoneMetadata {
pub fn prove_zone_stf( pub fn prove_zone_stf(
state: StateWitness, state: StateWitness,
inputs: Vec<BoundTx>, inputs: Vec<(SignedBoundTx, cl::PartialTxInputWitness)>,
zone_in: cl::PartialTxInputWitness, zone_in: cl::PartialTxInputWitness,
zone_out: cl::PartialTxOutputWitness, zone_out: cl::PartialTxOutputWitness,
funds_out: cl::PartialTxOutputWitness, funds_out: cl::PartialTxOutputWitness,

View File

@ -1,7 +1,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use cl::{BundleWitness, NoteWitness, NullifierNonce}; use cl::{BundleWitness, NoteWitness, NullifierNonce};
use common::{BoundTx, Deposit, Tx, Withdraw}; use common::{new_account, BoundTx, Deposit, SignedBoundTx, Tx, Withdraw};
use executor::ZoneNotes; use executor::ZoneNotes;
use goas_proof_statements::user_note::{UserAtomicTransfer, UserIntent}; use goas_proof_statements::user_note::{UserAtomicTransfer, UserIntent};
@ -9,10 +9,11 @@ use goas_proof_statements::user_note::{UserAtomicTransfer, UserIntent};
fn test_atomic_transfer() { fn test_atomic_transfer() {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let alice = 42; let mut alice = new_account(&mut rng);
let alice_vk = alice.verifying_key().to_bytes();
let zone_a_start = let zone_a_start =
ZoneNotes::new_with_balances("ZONE_A", BTreeMap::from_iter([(alice, 100)]), &mut rng); 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 zone_b_start = ZoneNotes::new_with_balances("ZONE_B", BTreeMap::from_iter([]), &mut rng);
@ -20,11 +21,11 @@ fn test_atomic_transfer() {
zone_a_meta: zone_a_start.state.zone_metadata, zone_a_meta: zone_a_start.state.zone_metadata,
zone_b_meta: zone_b_start.state.zone_metadata, zone_b_meta: zone_b_start.state.zone_metadata,
withdraw: Withdraw { withdraw: Withdraw {
from: alice, from: alice_vk,
amount: 75, amount: 75,
}, },
deposit: Deposit { deposit: Deposit {
to: alice, to: alice_vk,
amount: 75, amount: 75,
}, },
}; };
@ -69,6 +70,21 @@ fn test_atomic_transfer() {
], ],
}; };
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([ let death_proofs = BTreeMap::from_iter([
( (
alice_intent_in.nullifier(), alice_intent_in.nullifier(),
@ -87,13 +103,10 @@ fn test_atomic_transfer() {
zone_a_start.state_input_witness().nullifier(), zone_a_start.state_input_witness().nullifier(),
executor::prove_zone_stf( executor::prove_zone_stf(
zone_a_start.state.clone(), zone_a_start.state.clone(),
vec![BoundTx { vec![(signed_withdraw, atomic_transfer_ptx.input_witness(0))], // withdraw bound to input intent note
tx: Tx::Withdraw(alice_intent.withdraw), atomic_transfer_ptx.input_witness(1), // input state note
bind: atomic_transfer_ptx.input_witness(0), // input intent note atomic_transfer_ptx.output_witness(0), // output state note
}], atomic_transfer_ptx.output_witness(1), // output funds 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
), ),
), ),
( (
@ -108,13 +121,10 @@ fn test_atomic_transfer() {
zone_b_start.state_input_witness().nullifier(), zone_b_start.state_input_witness().nullifier(),
executor::prove_zone_stf( executor::prove_zone_stf(
zone_b_start.state.clone(), zone_b_start.state.clone(),
vec![BoundTx { vec![(signed_deposit, atomic_transfer_ptx.input_witness(0))], // deposit bound to input intent note
tx: Tx::Deposit(alice_intent.deposit), atomic_transfer_ptx.input_witness(3), // input state note
bind: atomic_transfer_ptx.input_witness(0), // input intent note atomic_transfer_ptx.output_witness(2), // output state note
}], atomic_transfer_ptx.output_witness(3), // output funds 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
), ),
), ),
( (

View File

@ -1,7 +1,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use cl::{NoteWitness, NullifierSecret}; use cl::{NoteWitness, NullifierSecret};
use common::{BoundTx, StateWitness, Tx, ZONE_CL_FUNDS_UNIT}; use common::{new_account, BoundTx, SignedBoundTx, StateWitness, Tx, ZONE_CL_FUNDS_UNIT};
use executor::ZoneNotes; use executor::ZoneNotes;
use ledger::death_constraint::DeathProof; use ledger::death_constraint::DeathProof;
@ -9,13 +9,14 @@ use ledger::death_constraint::DeathProof;
fn test_deposit() { fn test_deposit() {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let alice = 42; let mut alice = new_account(&mut rng);
let alice_sk = NullifierSecret::random(&mut rng); let alice_vk = alice.verifying_key().to_bytes();
let alice_cl_sk = NullifierSecret::random(&mut rng);
let zone_start = ZoneNotes::new_with_balances("ZONE", BTreeMap::new(), &mut rng); let zone_start = ZoneNotes::new_with_balances("ZONE", BTreeMap::new(), &mut rng);
let deposit = common::Deposit { let deposit = common::Deposit {
to: alice, to: alice_vk,
amount: 78, amount: 78,
}; };
@ -28,10 +29,10 @@ fn test_deposit() {
*ZONE_CL_FUNDS_UNIT, *ZONE_CL_FUNDS_UNIT,
DeathProof::nop_constraint(), // alice should demand a tx inclusion proof for the deposit DeathProof::nop_constraint(), // alice should demand a tx inclusion proof for the deposit
), ),
alice_sk.commit(), alice_cl_sk.commit(),
&mut rng, &mut rng,
), ),
alice_sk, alice_cl_sk,
&mut rng, &mut rng,
); );
@ -40,16 +41,21 @@ fn test_deposit() {
outputs: vec![zone_end.state_note, zone_end.fund_note], 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([ let death_proofs = BTreeMap::from_iter([
( (
zone_start.state_input_witness().nullifier(), zone_start.state_input_witness().nullifier(),
executor::prove_zone_stf( executor::prove_zone_stf(
zone_start.state.clone(), zone_start.state.clone(),
vec![BoundTx { vec![(signed_deposit, deposit_ptx.input_witness(1))], // bind it to the deposit note)],
tx: Tx::Deposit(deposit), deposit_ptx.input_witness(0), // input state note (input #0)
bind: 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(0), // output state note (output #0)
deposit_ptx.output_witness(1), // output funds note (output #1) deposit_ptx.output_witness(1), // output funds note (output #1)
), ),
@ -78,10 +84,9 @@ fn test_deposit() {
assert_eq!( assert_eq!(
zone_end.state_note.note.state, zone_end.state_note.note.state,
StateWitness { StateWitness {
balances: BTreeMap::from_iter([(alice, 78)]), balances: BTreeMap::from_iter([(alice_vk, 78)]),
included_txs: vec![Tx::Deposit(deposit)], included_txs: vec![Tx::Deposit(deposit)],
zone_metadata: zone_start.state.zone_metadata, zone_metadata: zone_start.state.zone_metadata,
nonce: zone_start.state.evolve_nonce().nonce,
} }
.commit() .commit()
.0 .0

View File

@ -1,7 +1,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use cl::{NoteWitness, NullifierSecret}; use cl::{NoteWitness, NullifierSecret};
use common::{BoundTx, StateWitness, Tx, ZONE_CL_FUNDS_UNIT}; use common::{new_account, BoundTx, SignedBoundTx, StateWitness, Tx, ZONE_CL_FUNDS_UNIT};
use executor::ZoneNotes; use executor::ZoneNotes;
use ledger::death_constraint::DeathProof; use ledger::death_constraint::DeathProof;
@ -9,24 +9,25 @@ use ledger::death_constraint::DeathProof;
fn test_withdrawal() { fn test_withdrawal() {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let alice = 42; let mut alice = new_account(&mut rng);
let alice_sk = NullifierSecret::random(&mut rng); let alice_vk = alice.verifying_key().to_bytes();
let alice_cl_sk = NullifierSecret::random(&mut rng);
let zone_start = let zone_start =
ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([(alice, 100)]), &mut rng); ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([(alice_vk, 100)]), &mut rng);
let alice_intent = cl::InputWitness::random( let alice_intent = cl::InputWitness::random(
cl::OutputWitness::random( cl::OutputWitness::random(
NoteWitness::stateless(1, *ZONE_CL_FUNDS_UNIT, DeathProof::nop_constraint()), // TODO, intent should be in the death constraint 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, &mut rng,
), ),
alice_sk, alice_cl_sk,
&mut rng, &mut rng,
); );
let withdraw = common::Withdraw { let withdraw = common::Withdraw {
from: alice, from: alice_vk,
amount: 78, amount: 78,
}; };
@ -38,7 +39,7 @@ fn test_withdrawal() {
*ZONE_CL_FUNDS_UNIT, *ZONE_CL_FUNDS_UNIT,
DeathProof::nop_constraint(), DeathProof::nop_constraint(),
), ),
alice_sk.commit(), alice_cl_sk.commit(),
&mut rng, &mut rng,
); );
@ -51,15 +52,20 @@ fn test_withdrawal() {
outputs: vec![zone_end.state_note, zone_end.fund_note, alice_withdrawal], 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([ let death_proofs = BTreeMap::from_iter([
( (
zone_start.state_input_witness().nullifier(), zone_start.state_input_witness().nullifier(),
executor::prove_zone_stf( executor::prove_zone_stf(
zone_start.state.clone(), zone_start.state.clone(),
vec![BoundTx { vec![(signed_withdraw, withdraw_ptx.input_witness(2))],
tx: Tx::Withdraw(withdraw),
bind: withdraw_ptx.input_witness(2),
}],
withdraw_ptx.input_witness(0), // input state note (input #0) withdraw_ptx.input_witness(0), // input state note (input #0)
withdraw_ptx.output_witness(0), // output state note (output #0) withdraw_ptx.output_witness(0), // output state note (output #0)
withdraw_ptx.output_witness(1), // output funds note (output #1) withdraw_ptx.output_witness(1), // output funds note (output #1)
@ -98,10 +104,9 @@ fn test_withdrawal() {
assert_eq!( assert_eq!(
zone_end.state_note.note.state, zone_end.state_note.note.state,
StateWitness { StateWitness {
balances: BTreeMap::from_iter([(alice, 22)]), balances: BTreeMap::from_iter([(alice_vk, 22)]),
included_txs: vec![Tx::Withdraw(withdraw)], included_txs: vec![Tx::Withdraw(withdraw)],
zone_metadata: zone_start.state.zone_metadata, zone_metadata: zone_start.state.zone_metadata,
nonce: zone_start.state.evolve_nonce().nonce,
} }
.commit() .commit()
.0 .0

View File

@ -1,10 +1,10 @@
use common::{BoundTx, StateWitness}; use common::{SignedBoundTx, StateWitness};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ZoneStatePrivate { pub struct ZoneStatePrivate {
pub state: StateWitness, pub state: StateWitness,
pub inputs: Vec<BoundTx>, pub inputs: Vec<(SignedBoundTx, cl::PartialTxInputWitness)>,
pub zone_in: cl::PartialTxInputWitness, pub zone_in: cl::PartialTxInputWitness,
pub zone_out: cl::PartialTxOutputWitness, pub zone_out: cl::PartialTxOutputWitness,
/// While the absence of birth constraints does not guarantee uniqueness of a note that can be used as /// While the absence of birth constraints does not guarantee uniqueness of a note that can be used as

View File

@ -1,5 +1,5 @@
use cl::{ use cl::{
note::NoteWitness, nullifier::NullifierNonce, output::OutputWitness, note::NoteWitness, output::OutputWitness,
PtxRoot, PtxRoot,
}; };
@ -39,7 +39,7 @@ fn validate_zone_transition(
); );
// the nonce is correctly evolved // 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 // funds are still under control of the zone
let expected_note_witness = NoteWitness::new( let expected_note_witness = NoteWitness::new(
@ -52,7 +52,7 @@ fn validate_zone_transition(
out_funds.output, out_funds.output,
OutputWitness::public( OutputWitness::public(
expected_note_witness, expected_note_witness,
NullifierNonce::from_bytes(out_state.nonce) in_note.input.evolved_nonce(b"FUND_NONCE")
) )
); );
// funds belong to the same partial tx // funds belong to the same partial tx
@ -78,12 +78,18 @@ fn main() {
let in_state_cm = state.commit(); let in_state_cm = state.commit();
for BoundTx { tx, bind } in inputs { for (signed_bound_tx, ptx_input_witness) in inputs {
assert_eq!(bind.input_root(), input_root); // verify the signature
state = state.apply(tx) 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); validate_zone_transition(zone_in, zone_out, funds_out, in_state_cm, state);
env::commit(&pub_inputs); env::commit(&pub_inputs);

View File

@ -52,21 +52,25 @@ impl InputWitness {
} }
} }
pub fn evolved_nonce(&self) -> NullifierNonce { pub fn evolved_nonce(&self, domain: &[u8]) -> NullifierNonce {
self.nonce.evolve(&self.nf_sk) 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 { crate::OutputWitness {
note: self.note, note: self.note,
balance_blinding, balance_blinding,
nf_pk: self.nf_sk.commit(), nf_pk: self.nf_sk.commit(),
nonce: self.evolved_nonce(), nonce: self.evolved_nonce(domain),
} }
} }
pub fn nullifier(&self) -> Nullifier { 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 { pub fn commit(&self) -> Input {

View File

@ -23,7 +23,7 @@ pub fn unit_point(unit: &str) -> Unit {
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct NoteCommitment([u8; 32]); pub struct NoteCommitment(pub [u8; 32]);
impl NoteCommitment { impl NoteCommitment {
pub fn as_bytes(&self) -> &[u8; 32] { 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)] #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
pub struct NoteWitness { pub struct NoteWitness {
pub value: u64, pub value: u64,

View File

@ -9,6 +9,8 @@ use rand_core::RngCore;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use crate::{NoteCommitment, NoteWitness};
// TODO: create a nullifier witness and use it throughout. // TODO: create a nullifier witness and use it throughout.
// struct NullifierWitness { // struct NullifierWitness {
// nf_sk: NullifierSecret, // nf_sk: NullifierSecret,
@ -90,11 +92,12 @@ impl NullifierNonce {
Self(bytes) 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(); let mut hasher = Sha256::new();
hasher.update(b"NOMOS_COIN_EVOLVE"); hasher.update(b"NOMOS_COIN_EVOLVE");
hasher.update(&self.0); hasher.update(domain);
hasher.update(nf_sk.0); hasher.update(nf_sk.0);
hasher.update(note.commit(nf_sk.commit(), *self).0);
let nonce_bytes: [u8; 32] = hasher.finalize().into(); let nonce_bytes: [u8; 32] = hasher.finalize().into();
Self(nonce_bytes) Self(nonce_bytes)
@ -102,11 +105,11 @@ impl NullifierNonce {
} }
impl Nullifier { impl Nullifier {
pub fn new(sk: NullifierSecret, nonce: NullifierNonce) -> Self { pub fn new(sk: NullifierSecret, note_cm: NoteCommitment) -> Self {
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
hasher.update(b"NOMOS_CL_NULLIFIER"); hasher.update(b"NOMOS_CL_NULLIFIER");
hasher.update(sk.0); hasher.update(sk.0);
hasher.update(nonce.0); hasher.update(note_cm.0);
let nf_bytes: [u8; 32] = hasher.finalize().into(); let nf_bytes: [u8; 32] = hasher.finalize().into();
Self(nf_bytes) Self(nf_bytes)
@ -119,6 +122,8 @@ impl Nullifier {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::{note::unit_point, NoteWitness};
use super::*; use super::*;
#[ignore = "nullifier test vectors not stable yet"] #[ignore = "nullifier test vectors not stable yet"]
@ -142,10 +147,31 @@ mod test {
fn test_nullifier_same_sk_different_nonce() { fn test_nullifier_same_sk_different_nonce() {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let sk = NullifierSecret::random(&mut rng); let sk = NullifierSecret::random(&mut rng);
let note = NoteWitness::basic(1, unit_point("NMO"));
let nonce_1 = NullifierNonce::random(&mut rng); let nonce_1 = NullifierNonce::random(&mut rng);
let nonce_2 = NullifierNonce::random(&mut rng); let nonce_2 = NullifierNonce::random(&mut rng);
let nf_1 = Nullifier::new(sk, nonce_1); let note_cm_1 = note.commit(sk.commit(), nonce_1);
let nf_2 = Nullifier::new(sk, nonce_2); 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); assert_ne!(nf_1, nf_2);
} }

View File

@ -127,7 +127,7 @@ mod test {
input: cl::Input { input: cl::Input {
nullifier: cl::Nullifier::new( nullifier: cl::Nullifier::new(
cl::NullifierSecret::random(&mut rng), cl::NullifierSecret::random(&mut rng),
cl::NullifierNonce::random(&mut rng), input.note_commitment(),
), ),
..expected_public_inputs.input ..expected_public_inputs.input
}, },