goas: move nonce into note

This commit is contained in:
David Rusu 2024-08-27 01:32:53 +04:00
parent cffd687e50
commit 1fd4c6bd64
17 changed files with 219 additions and 251 deletions

View File

@ -1,6 +1,6 @@
pub mod mmr; pub mod mmr;
use cl::{balance::Unit, ConstraintCommitment, NoteCommitment}; use cl::{balance::Unit, Constraint, NoteCommitment};
use ed25519_dalek::{ use ed25519_dalek::{
ed25519::{signature::SignerMut, SignatureBytes}, ed25519::{signature::SignerMut, SignatureBytes},
Signature, SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH, Signature, SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH,
@ -27,8 +27,8 @@ pub fn new_account(mut rng: impl CryptoRngCore) -> SigningKey {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct ZoneMetadata { pub struct ZoneMetadata {
pub zone_constraint: ConstraintCommitment, pub zone_constraint: Constraint,
pub funds_constraint: ConstraintCommitment, pub funds_constraint: Constraint,
pub unit: Unit, pub unit: Unit,
} }

View File

@ -1,6 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use cl::ConstraintCommitment; use cl::Constraint;
use common::{ use common::{
mmr::MMR, AccountId, IncludedTxWitness, SignedBoundTx, StateWitness, Tx, ZoneMetadata, mmr::MMR, AccountId, IncludedTxWitness, SignedBoundTx, StateWitness, Tx, ZoneMetadata,
}; };
@ -49,22 +49,18 @@ impl ZoneNotes {
self.state = new_state; self.state = new_state;
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(cl::NoteWitness {
cl::NoteWitness { state: self.state.commit().0,
state: self.state.commit().0, nonce: state_in.evolved_nonce(b"STATE_NONCE"),
..state_in.note ..state_in.note
}, });
state_in.evolved_nonce(b"STATE_NONCE"),
);
let fund_in = self.fund_input_witness(); let fund_in = self.fund_input_witness();
self.fund_note = cl::OutputWitness::public( self.fund_note = cl::OutputWitness::public(cl::NoteWitness {
cl::NoteWitness { value: self.state.total_balance(),
value: self.state.total_balance(), nonce: state_in.evolved_nonce(b"FUND_NONCE"),
..fund_in.note ..fund_in.note
}, });
state_in.evolved_nonce(b"FUND_NONCE"),
);
(self, included_tx) (self, included_tx)
} }
@ -75,38 +71,34 @@ fn zone_fund_utxo(
zone_meta: ZoneMetadata, zone_meta: ZoneMetadata,
mut rng: impl CryptoRngCore, mut rng: impl CryptoRngCore,
) -> cl::OutputWitness { ) -> cl::OutputWitness {
cl::OutputWitness::public( cl::OutputWitness::public(cl::NoteWitness {
cl::NoteWitness { value,
value, unit: *common::ZONE_CL_FUNDS_UNIT,
unit: *common::ZONE_CL_FUNDS_UNIT, constraint: zone_meta.funds_constraint,
constraint: zone_meta.funds_constraint, state: zone_meta.id(),
state: zone_meta.id(), nonce: cl::Nonce::random(&mut rng),
}, })
cl::NullifierNonce::random(&mut rng),
)
} }
fn zone_state_utxo(zone: &StateWitness, mut rng: impl CryptoRngCore) -> cl::OutputWitness { fn zone_state_utxo(zone: &StateWitness, mut rng: impl CryptoRngCore) -> cl::OutputWitness {
cl::OutputWitness::public( cl::OutputWitness::public(cl::NoteWitness {
cl::NoteWitness { value: 1,
value: 1, unit: zone.zone_metadata.unit,
unit: zone.zone_metadata.unit, constraint: zone.zone_metadata.zone_constraint,
constraint: zone.zone_metadata.zone_constraint, state: zone.commit().0,
state: zone.commit().0, nonce: cl::Nonce::random(&mut rng),
}, })
cl::NullifierNonce::random(&mut rng),
)
} }
pub fn user_atomic_transfer_constraint() -> ConstraintCommitment { pub fn user_atomic_transfer_constraint() -> Constraint {
ledger::constraint::risc0_constraint(goas_risc0_proofs::USER_ATOMIC_TRANSFER_ID) ledger::constraint::risc0_constraint(goas_risc0_proofs::USER_ATOMIC_TRANSFER_ID)
} }
pub fn zone_state_constraint() -> ConstraintCommitment { pub fn zone_state_constraint() -> Constraint {
ledger::constraint::risc0_constraint(goas_risc0_proofs::ZONE_STATE_ID) ledger::constraint::risc0_constraint(goas_risc0_proofs::ZONE_STATE_ID)
} }
pub fn zone_fund_constraint() -> ConstraintCommitment { pub fn zone_fund_constraint() -> Constraint {
ledger::constraint::risc0_constraint(goas_risc0_proofs::SPEND_ZONE_FUNDS_ID) ledger::constraint::risc0_constraint(goas_risc0_proofs::SPEND_ZONE_FUNDS_ID)
} }
@ -217,8 +209,7 @@ pub fn prove_user_atomic_transfer(atomic_transfer: UserAtomicTransfer) -> ledger
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use cl::{ use cl::{
note::derive_unit, BalanceWitness, NoteWitness, NullifierNonce, OutputWitness, note::derive_unit, BalanceWitness, Nonce, NoteWitness, OutputWitness, PartialTxWitness,
PartialTxWitness,
}; };
use common::{BoundTx, Deposit, Withdraw}; use common::{BoundTx, Deposit, Withdraw};
use goas_proof_statements::user_note::UserIntent; use goas_proof_statements::user_note::UserIntent;
@ -237,10 +228,11 @@ mod tests {
let zone_start = let zone_start =
ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([(alice_vk, 32)]), &mut rng); ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([(alice_vk, 32)]), &mut rng);
let bind = OutputWitness::public( let bind = OutputWitness::public(NoteWitness::basic(
NoteWitness::basic(32, *common::ZONE_CL_FUNDS_UNIT), 32,
cl::NullifierNonce::random(&mut rng), *common::ZONE_CL_FUNDS_UNIT,
); &mut rng,
));
let signed_withdraw = SignedBoundTx::sign( let signed_withdraw = SignedBoundTx::sign(
BoundTx { BoundTx {
@ -324,15 +316,13 @@ mod tests {
amount: 32, amount: 32,
}, },
}; };
let user_note = cl::InputWitness::public(cl::OutputWitness::public( let user_note = cl::InputWitness::public(cl::OutputWitness::public(NoteWitness {
NoteWitness::new( value: 1,
1, unit: derive_unit("INTENT"),
derive_unit("INTENT"), constraint: ConstraintProof::nop_constraint(),
ConstraintProof::nop_constraint(), state: user_intent.commit(),
user_intent.commit(), nonce: Nonce::random(&mut rng),
), }));
NullifierNonce::random(&mut rng),
));
let (zone_a, withdraw_included_witnesss) = zone_a.run(Tx::Withdraw(user_intent.withdraw)); let (zone_a, withdraw_included_witnesss) = zone_a.run(Tx::Withdraw(user_intent.withdraw));
let (zone_b, deposit_included_witnesss) = zone_b.run(Tx::Deposit(user_intent.deposit)); let (zone_b, deposit_included_witnesss) = zone_b.run(Tx::Deposit(user_intent.deposit));

View File

@ -1,6 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use cl::{BalanceWitness, BundleWitness, NoteWitness, NullifierNonce}; use cl::{BalanceWitness, BundleWitness, Nonce, NoteWitness};
use common::{new_account, BoundTx, Deposit, SignedBoundTx, 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};
@ -30,15 +30,13 @@ fn test_atomic_transfer() {
}, },
}; };
let alice_intent_out = cl::OutputWitness::public( let alice_intent_out = cl::OutputWitness::public(NoteWitness {
NoteWitness { value: 1,
value: 1, unit: cl::note::derive_unit("INTENT"),
unit: cl::note::derive_unit("INTENT"), constraint: executor::user_atomic_transfer_constraint(),
constraint: executor::user_atomic_transfer_constraint(), state: alice_intent.commit(),
state: alice_intent.commit(), nonce: Nonce::random(&mut rng),
}, });
NullifierNonce::random(&mut rng),
);
let user_ptx = cl::PartialTxWitness { let user_ptx = cl::PartialTxWitness {
inputs: vec![], inputs: vec![],

View File

@ -23,14 +23,14 @@ fn test_deposit() {
let zone_end = zone_start.clone().run(Tx::Deposit(deposit)).0; let zone_end = zone_start.clone().run(Tx::Deposit(deposit)).0;
let alice_deposit = cl::InputWitness::from_output( let alice_deposit = cl::InputWitness::from_output(
cl::OutputWitness::random( cl::OutputWitness::new(
NoteWitness::stateless( NoteWitness::stateless(
78, 78,
*ZONE_CL_FUNDS_UNIT, *ZONE_CL_FUNDS_UNIT,
ConstraintProof::nop_constraint(), // alice should demand a tx inclusion proof for the deposit ConstraintProof::nop_constraint(), // alice should demand a tx inclusion proof for the deposit
&mut rng,
), ),
alice_cl_sk.commit(), alice_cl_sk.commit(),
&mut rng,
), ),
alice_cl_sk, alice_cl_sk,
); );

View File

@ -17,10 +17,14 @@ fn test_withdrawal() {
ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([(alice_vk, 100)]), &mut rng); ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([(alice_vk, 100)]), &mut rng);
let alice_intent = cl::InputWitness::from_output( let alice_intent = cl::InputWitness::from_output(
cl::OutputWitness::random( cl::OutputWitness::new(
NoteWitness::stateless(1, *ZONE_CL_FUNDS_UNIT, ConstraintProof::nop_constraint()), // TODO, intent should be in the constraint NoteWitness::stateless(
1,
*ZONE_CL_FUNDS_UNIT,
ConstraintProof::nop_constraint(),
&mut rng,
), // TODO, intent should be in the constraint
alice_cl_sk.commit(), alice_cl_sk.commit(),
&mut rng,
), ),
alice_cl_sk, alice_cl_sk,
); );
@ -32,14 +36,14 @@ fn test_withdrawal() {
let zone_end = zone_start.clone().run(Tx::Withdraw(withdraw)).0; let zone_end = zone_start.clone().run(Tx::Withdraw(withdraw)).0;
let alice_withdrawal = cl::OutputWitness::random( let alice_withdrawal = cl::OutputWitness::new(
NoteWitness::stateless( NoteWitness::stateless(
withdraw.amount, withdraw.amount,
*ZONE_CL_FUNDS_UNIT, *ZONE_CL_FUNDS_UNIT,
ConstraintProof::nop_constraint(), ConstraintProof::nop_constraint(),
&mut rng,
), ),
alice_cl_sk.commit(), alice_cl_sk.commit(),
&mut rng,
); );
let withdraw_ptx = cl::PartialTxWitness { let withdraw_ptx = cl::PartialTxWitness {

View File

@ -33,21 +33,19 @@ fn validate_zone_transition(
assert_eq!(in_note.input.nf_sk.commit(), out_note.output.nf_pk); assert_eq!(in_note.input.nf_sk.commit(), out_note.output.nf_pk);
// the nonce is correctly evolved // the nonce is correctly evolved
assert_eq!(in_note.input.evolved_nonce(b"STATE_NONCE"), out_note.output.nonce); assert_eq!(in_note.input.evolved_nonce(b"STATE_NONCE"), out_note.output.note.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 {
out_state.total_balance(), value: out_state.total_balance(),
*ZONE_CL_FUNDS_UNIT, unit: *ZONE_CL_FUNDS_UNIT,
metadata.funds_constraint, constraint: metadata.funds_constraint,
metadata.id(), state: metadata.id(),
); nonce: in_note.input.evolved_nonce(b"FUND_NONCE")
};
assert_eq!( assert_eq!(
out_funds.output, out_funds.output,
OutputWitness::public( OutputWitness::public(expected_note_witness)
expected_note_witness,
in_note.input.evolved_nonce(b"FUND_NONCE")
)
); );
// funds belong to the same partial tx // funds belong to the same partial tx
assert_eq!(out_funds.output_root(), out_note.output_root()); assert_eq!(out_funds.output_root(), out_note.output_root());

View File

@ -50,16 +50,14 @@ mod test {
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::new(NoteWitness::basic(10, nmo, &mut rng), nf_a.commit());
OutputWitness::random(NoteWitness::basic(10, nmo), nf_a.commit(), &mut rng);
let nmo_10_in = InputWitness::from_output(nmo_10_utxo, nf_a); let nmo_10_in = InputWitness::from_output(nmo_10_utxo, nf_a);
let eth_23_utxo = let eth_23_utxo = OutputWitness::new(NoteWitness::basic(23, eth, &mut rng), nf_b.commit());
OutputWitness::random(NoteWitness::basic(23, eth), nf_b.commit(), &mut rng);
let eth_23_in = InputWitness::from_output(eth_23_utxo, nf_b); let eth_23_in = InputWitness::from_output(eth_23_utxo, nf_b);
let crv_4840_out = let crv_4840_out =
OutputWitness::random(NoteWitness::basic(4840, crv), nf_c.commit(), &mut rng); OutputWitness::new(NoteWitness::basic(4840, crv, &mut rng), nf_c.commit());
let ptx_unbalanced = PartialTxWitness { let ptx_unbalanced = PartialTxWitness {
inputs: vec![nmo_10_in, eth_23_in], inputs: vec![nmo_10_in, eth_23_in],
@ -94,15 +92,13 @@ mod test {
); );
let crv_4840_in = InputWitness::from_output(crv_4840_out, nf_c); let crv_4840_in = InputWitness::from_output(crv_4840_out, nf_c);
let nmo_10_out = OutputWitness::random( let nmo_10_out = OutputWitness::new(
NoteWitness::basic(10, nmo), NoteWitness::basic(10, nmo, &mut rng),
NullifierSecret::random(&mut rng).commit(), // transferring to a random owner NullifierSecret::random(&mut rng).commit(), // transferring to a random owner
&mut rng,
); );
let eth_23_out = OutputWitness::random( let eth_23_out = OutputWitness::new(
NoteWitness::basic(23, eth), NoteWitness::basic(23, eth, &mut rng),
NullifierSecret::random(&mut rng).commit(), // transferring to a random owner NullifierSecret::random(&mut rng).commit(), // transferring to a random owner
&mut rng,
); );
let ptx_solved = PartialTxWitness { let ptx_solved = PartialTxWitness {

View File

@ -3,53 +3,59 @@
/// Partial transactions, as the name suggests, are transactions /// Partial transactions, as the name suggests, are transactions
/// which on their own may not balance (i.e. \sum inputs != \sum outputs) /// which on their own may not balance (i.e. \sum inputs != \sum outputs)
use crate::{ use crate::{
note::{ConstraintCommitment, NoteWitness}, note::{Constraint, NoteWitness},
nullifier::{Nullifier, NullifierNonce, NullifierSecret}, nullifier::{Nullifier, NullifierSecret},
Nonce,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Input { pub struct Input {
pub nullifier: Nullifier, pub nullifier: Nullifier,
pub constraint: ConstraintCommitment, pub constraint: Constraint,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct InputWitness { pub struct InputWitness {
pub note: NoteWitness, pub note: NoteWitness,
pub nf_sk: NullifierSecret, pub nf_sk: NullifierSecret,
pub nonce: NullifierNonce,
} }
impl InputWitness { impl InputWitness {
pub fn new(note: NoteWitness, nf_sk: NullifierSecret) -> Self {
Self { note, nf_sk }
}
pub fn from_output(output: crate::OutputWitness, nf_sk: NullifierSecret) -> Self { pub fn from_output(output: crate::OutputWitness, nf_sk: NullifierSecret) -> Self {
assert_eq!(nf_sk.commit(), output.nf_pk); assert_eq!(nf_sk.commit(), output.nf_pk);
Self { Self::new(output.note, nf_sk)
note: output.note,
nf_sk,
nonce: output.nonce,
}
} }
pub fn public(output: crate::OutputWitness) -> Self { pub fn public(output: crate::OutputWitness) -> Self {
let nf_sk = NullifierSecret::zero(); let nf_sk = NullifierSecret::zero();
assert_eq!(nf_sk.commit(), output.nf_pk); // ensure the output was a public UTXO assert_eq!(nf_sk.commit(), output.nf_pk); // ensure the output was a public UTXO
Self { Self::new(output.note, nf_sk)
note: output.note,
nf_sk,
nonce: output.nonce,
}
} }
pub fn evolved_nonce(&self, domain: &[u8]) -> NullifierNonce { pub fn evolved_nonce(&self, domain: &[u8]) -> Nonce {
self.nonce.evolve(domain, &self.nf_sk, &self.note) let mut hasher = Sha256::new();
hasher.update(b"NOMOS_COIN_EVOLVE");
hasher.update(domain);
hasher.update(self.nf_sk.0);
hasher.update(self.note.commit(self.nf_sk.commit()).0);
let nonce_bytes: [u8; 32] = hasher.finalize().into();
Nonce::from_bytes(nonce_bytes)
} }
pub fn evolve_output(&self, domain: &[u8]) -> crate::OutputWitness { pub fn evolve_output(&self, domain: &[u8]) -> crate::OutputWitness {
crate::OutputWitness { crate::OutputWitness {
note: self.note, note: NoteWitness {
nonce: self.evolved_nonce(domain),
..self.note
},
nf_pk: self.nf_sk.commit(), nf_pk: self.nf_sk.commit(),
nonce: self.evolved_nonce(domain),
} }
} }
@ -65,7 +71,7 @@ impl InputWitness {
} }
pub fn note_commitment(&self) -> crate::NoteCommitment { pub fn note_commitment(&self) -> crate::NoteCommitment {
self.note.commit(self.nf_sk.commit(), self.nonce) self.note.commit(self.nf_sk.commit())
} }
} }

View File

@ -12,8 +12,8 @@ pub mod partial_tx;
pub use balance::{Balance, BalanceWitness}; pub use balance::{Balance, BalanceWitness};
pub use bundle::{Bundle, BundleWitness}; pub use bundle::{Bundle, BundleWitness};
pub use input::{Input, InputWitness}; pub use input::{Input, InputWitness};
pub use note::{ConstraintCommitment, NoteCommitment, NoteWitness}; pub use note::{Constraint, Nonce, NoteCommitment, NoteWitness};
pub use nullifier::{Nullifier, NullifierCommitment, NullifierNonce, NullifierSecret}; pub use nullifier::{Nullifier, NullifierCommitment, NullifierSecret};
pub use output::{Output, OutputWitness}; pub use output::{Output, OutputWitness};
pub use partial_tx::{ pub use partial_tx::{
PartialTx, PartialTxInputWitness, PartialTxOutputWitness, PartialTxWitness, PtxRoot, PartialTx, PartialTxInputWitness, PartialTxOutputWitness, PartialTxWitness, PtxRoot,

View File

@ -1,15 +1,13 @@
use rand::RngCore;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use crate::{ use crate::{balance::Unit, nullifier::NullifierCommitment};
balance::Unit,
nullifier::{NullifierCommitment, NullifierNonce},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct ConstraintCommitment(pub [u8; 32]); pub struct Constraint(pub [u8; 32]);
impl ConstraintCommitment { impl Constraint {
pub fn from_vk(constraint_vk: &[u8]) -> Self { pub fn from_vk(constraint_vk: &[u8]) -> Self {
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
hasher.update(b"NOMOS_CL_CONSTRAINT_COMMIT"); hasher.update(b"NOMOS_CL_CONSTRAINT_COMMIT");
@ -41,29 +39,39 @@ impl NoteCommitment {
pub struct NoteWitness { pub struct NoteWitness {
pub value: u64, pub value: u64,
pub unit: Unit, pub unit: Unit,
pub constraint: ConstraintCommitment, pub constraint: Constraint,
pub state: [u8; 32], pub state: [u8; 32],
pub nonce: Nonce,
} }
impl NoteWitness { impl NoteWitness {
pub fn new(value: u64, unit: Unit, constraint: ConstraintCommitment, state: [u8; 32]) -> Self { pub fn new(
value: u64,
unit: Unit,
constraint: Constraint,
state: [u8; 32],
nonce: Nonce,
) -> Self {
Self { Self {
value, value,
unit, unit,
constraint, constraint,
state, state,
nonce,
} }
} }
pub fn basic(value: u64, unit: Unit) -> Self { pub fn basic(value: u64, unit: Unit, rng: impl RngCore) -> Self {
Self::new(value, unit, ConstraintCommitment([0u8; 32]), [0u8; 32]) let constraint = Constraint([0u8; 32]);
let nonce = Nonce::random(rng);
Self::new(value, unit, constraint, [0u8; 32], nonce)
} }
pub fn stateless(value: u64, unit: Unit, constraint: ConstraintCommitment) -> Self { pub fn stateless(value: u64, unit: Unit, constraint: Constraint, rng: impl RngCore) -> Self {
Self::new(value, unit, constraint, [0u8; 32]) Self::new(value, unit, constraint, [0u8; 32], Nonce::random(rng))
} }
pub fn commit(&self, nf_pk: NullifierCommitment, nonce: NullifierNonce) -> NoteCommitment { pub fn commit(&self, nf_pk: NullifierCommitment) -> NoteCommitment {
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
hasher.update(b"NOMOS_CL_NOTE_COMMIT"); hasher.update(b"NOMOS_CL_NOTE_COMMIT");
@ -78,15 +86,36 @@ impl NoteWitness {
// COMMIT TO CONSTRAINT // COMMIT TO CONSTRAINT
hasher.update(self.constraint.0); hasher.update(self.constraint.0);
// COMMIT TO NONCE
hasher.update(self.nonce.as_bytes());
// COMMIT TO NULLIFIER // COMMIT TO NULLIFIER
hasher.update(nf_pk.as_bytes()); hasher.update(nf_pk.as_bytes());
hasher.update(nonce.as_bytes());
let commit_bytes: [u8; 32] = hasher.finalize().into(); let commit_bytes: [u8; 32] = hasher.finalize().into();
NoteCommitment(commit_bytes) NoteCommitment(commit_bytes)
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Nonce([u8; 32]);
impl Nonce {
pub fn random(mut rng: impl RngCore) -> Self {
let mut nonce = [0u8; 32];
rng.fill_bytes(&mut nonce);
Self(nonce)
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@ -99,9 +128,8 @@ mod test {
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 reference_note = NoteWitness::basic(32, nmo); let reference_note = NoteWitness::basic(32, nmo, &mut rng);
// different notes under same nullifier produce different commitments // different notes under same nullifier produce different commitments
let mutation_tests = [ let mutation_tests = [
@ -114,38 +142,30 @@ mod test {
..reference_note ..reference_note
}, },
NoteWitness { NoteWitness {
constraint: ConstraintCommitment::from_vk(&[1u8; 32]), constraint: Constraint::from_vk(&[1u8; 32]),
..reference_note ..reference_note
}, },
NoteWitness { NoteWitness {
state: [1u8; 32], state: [1u8; 32],
..reference_note ..reference_note
}, },
NoteWitness {
nonce: Nonce::random(&mut rng),
..reference_note
},
]; ];
for n in mutation_tests { for n in mutation_tests {
assert_ne!( assert_ne!(n.commit(nf_pk), reference_note.commit(nf_pk));
n.commit(nf_pk, nf_nonce),
reference_note.commit(nf_pk, nf_nonce)
);
} }
// commitment to same note with different nullifiers produce different commitments // commitment to same note with different nullifiers produce different commitments
let other_nf_pk = NullifierSecret::random(&mut rng).commit(); let other_nf_pk = NullifierSecret::random(&mut rng).commit();
let other_nf_nonce = NullifierNonce::random(&mut rng);
assert_ne!( assert_ne!(
reference_note.commit(nf_pk, nf_nonce), reference_note.commit(nf_pk),
reference_note.commit(other_nf_pk, nf_nonce) reference_note.commit(other_nf_pk)
);
assert_ne!(
reference_note.commit(nf_pk, nf_nonce),
reference_note.commit(nf_pk, other_nf_nonce)
);
assert_ne!(
reference_note.commit(nf_pk, nf_nonce),
reference_note.commit(other_nf_pk, other_nf_nonce)
); );
} }
} }

View File

@ -5,17 +5,12 @@
// notes to allow users to hold fewer secrets. A note // notes to allow users to hold fewer secrets. A note
// nonce is used to disambiguate when the same nullifier // nonce is used to disambiguate when the same nullifier
// secret is used for multiple notes. // secret is used for multiple notes.
use rand_core::RngCore; use rand_core::RngCore;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use crate::{NoteCommitment, NoteWitness}; use crate::NoteCommitment;
// TODO: create a nullifier witness and use it throughout.
// struct NullifierWitness {
// nf_sk: NullifierSecret,
// nonce: NullifierNonce
// }
// Maintained privately by note holder // Maintained privately by note holder
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@ -27,12 +22,6 @@ pub struct NullifierSecret(pub [u8; 16]);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct NullifierCommitment([u8; 32]); pub struct NullifierCommitment([u8; 32]);
// To allow users to maintain fewer nullifier secrets, we
// provide a nonce to differentiate notes controlled by the same
// secret. Each note is assigned a unique nullifier nonce.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct NullifierNonce([u8; 32]);
// The nullifier attached to input notes to prove an input has not // The nullifier attached to input notes to prove an input has not
// already been spent. // already been spent.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
@ -77,33 +66,6 @@ impl NullifierCommitment {
} }
} }
impl NullifierNonce {
pub fn random(mut rng: impl RngCore) -> Self {
let mut nonce = [0u8; 32];
rng.fill_bytes(&mut nonce);
Self(nonce)
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
pub fn evolve(&self, domain: &[u8], nf_sk: &NullifierSecret, note: &NoteWitness) -> Self {
let mut hasher = Sha256::new();
hasher.update(b"NOMOS_COIN_EVOLVE");
hasher.update(domain);
hasher.update(nf_sk.0);
hasher.update(note.commit(nf_sk.commit(), *self).0);
let nonce_bytes: [u8; 32] = hasher.finalize().into();
Self(nonce_bytes)
}
}
impl Nullifier { impl Nullifier {
pub fn new(sk: NullifierSecret, note_cm: NoteCommitment) -> Self { pub fn new(sk: NullifierSecret, note_cm: NoteCommitment) -> Self {
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
@ -122,7 +84,7 @@ impl Nullifier {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::{note::derive_unit, NoteWitness}; use crate::{note::derive_unit, Constraint, Nonce, NoteWitness};
use super::*; use super::*;
@ -147,12 +109,20 @@ 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, derive_unit("NMO")); let note_1 = NoteWitness {
value: 1,
unit: derive_unit("NMO"),
constraint: Constraint::from_vk(&[]),
state: [0u8; 32],
nonce: Nonce::random(&mut rng),
};
let note_2 = NoteWitness {
nonce: Nonce::random(&mut rng),
..note_1
};
let nonce_1 = NullifierNonce::random(&mut rng); let note_cm_1 = note_1.commit(sk.commit());
let nonce_2 = NullifierNonce::random(&mut rng); let note_cm_2 = note_2.commit(sk.commit());
let note_cm_1 = note.commit(sk.commit(), nonce_1);
let note_cm_2 = note.commit(sk.commit(), nonce_2);
let nf_1 = Nullifier::new(sk, note_cm_1); let nf_1 = Nullifier::new(sk, note_cm_1);
let nf_2 = Nullifier::new(sk, note_cm_2); let nf_2 = Nullifier::new(sk, note_cm_2);
@ -163,12 +133,25 @@ mod test {
#[test] #[test]
fn test_same_sk_same_nonce_different_note() { fn test_same_sk_same_nonce_different_note() {
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_1 = NoteWitness::basic(1, derive_unit("NMO")); let nonce = Nonce::random(&mut rng);
let note_2 = NoteWitness::basic(1, derive_unit("ETH"));
let nonce = NullifierNonce::random(&mut rng); let note_1 = NoteWitness {
let note_cm_1 = note_1.commit(sk.commit(), nonce); value: 1,
let note_cm_2 = note_2.commit(sk.commit(), nonce); unit: derive_unit("NMO"),
constraint: Constraint::from_vk(&[]),
state: [0u8; 32],
nonce,
};
let note_2 = NoteWitness {
unit: derive_unit("ETH"),
..note_1
};
let note_cm_1 = note_1.commit(sk.commit());
let note_cm_2 = note_2.commit(sk.commit());
let nf_1 = Nullifier::new(sk, note_cm_1); let nf_1 = Nullifier::new(sk, note_cm_1);
let nf_2 = Nullifier::new(sk, note_cm_2); let nf_2 = Nullifier::new(sk, note_cm_2);

View File

@ -1,9 +1,8 @@
use rand_core::CryptoRngCore;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
note::{NoteCommitment, NoteWitness}, note::{NoteCommitment, NoteWitness},
nullifier::{NullifierCommitment, NullifierNonce}, nullifier::NullifierCommitment,
NullifierSecret, NullifierSecret,
}; };
@ -16,32 +15,20 @@ pub struct Output {
pub struct OutputWitness { pub struct OutputWitness {
pub note: NoteWitness, pub note: NoteWitness,
pub nf_pk: NullifierCommitment, pub nf_pk: NullifierCommitment,
pub nonce: NullifierNonce,
} }
impl OutputWitness { impl OutputWitness {
pub fn random( pub fn new(note: NoteWitness, nf_pk: NullifierCommitment) -> Self {
note: NoteWitness, Self { note, nf_pk }
owner: NullifierCommitment,
mut rng: impl CryptoRngCore,
) -> Self {
Self {
note,
nf_pk: owner,
nonce: NullifierNonce::random(&mut rng),
}
} }
pub fn public(note: NoteWitness, nonce: NullifierNonce) -> Self { pub fn public(note: NoteWitness) -> Self {
Self { let nf_pk = NullifierSecret::zero().commit();
note, Self { note, nf_pk }
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)
} }
pub fn commit(&self) -> Output { pub fn commit(&self) -> Output {

View File

@ -167,16 +167,13 @@ mod test {
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::new(NoteWitness::basic(10, nmo, &mut rng), nf_a.commit());
OutputWitness::random(NoteWitness::basic(10, nmo), nf_a.commit(), &mut rng);
let nmo_10 = InputWitness::from_output(nmo_10_utxo, nf_a); let nmo_10 = InputWitness::from_output(nmo_10_utxo, nf_a);
let eth_23_utxo = let eth_23_utxo = OutputWitness::new(NoteWitness::basic(23, eth, &mut rng), nf_b.commit());
OutputWitness::random(NoteWitness::basic(23, eth), nf_b.commit(), &mut rng);
let eth_23 = InputWitness::from_output(eth_23_utxo, nf_b); let eth_23 = InputWitness::from_output(eth_23_utxo, nf_b);
let crv_4840 = let crv_4840 = OutputWitness::new(NoteWitness::basic(4840, crv, &mut rng), nf_c.commit());
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],

View File

@ -1,12 +1,7 @@
use cl::{note::derive_unit, BalanceWitness}; use cl::{note::derive_unit, BalanceWitness};
use rand_core::CryptoRngCore;
fn receive_utxo( fn receive_utxo(note: cl::NoteWitness, nf_pk: cl::NullifierCommitment) -> cl::OutputWitness {
note: cl::NoteWitness, cl::OutputWitness::new(note, nf_pk)
nf_pk: cl::NullifierCommitment,
rng: impl CryptoRngCore,
) -> cl::OutputWitness {
cl::OutputWitness::random(note, nf_pk, rng)
} }
#[test] #[test]
@ -20,13 +15,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, &mut rng), sender_nf_pk);
// 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::new(cl::NoteWitness::basic(8, nmo, &mut rng), recipient_nf_pk);
let change_output = let change_output =
cl::OutputWitness::random(cl::NoteWitness::basic(2, nmo), sender_nf_pk, &mut rng); cl::OutputWitness::new(cl::NoteWitness::basic(2, nmo, &mut rng), sender_nf_pk);
let ptx_witness = cl::PartialTxWitness { let ptx_witness = cl::PartialTxWitness {
inputs: vec![cl::InputWitness::from_output(utxo, sender_nf_sk)], inputs: vec![cl::InputWitness::from_output(utxo, sender_nf_sk)],

View File

@ -1,4 +1,4 @@
use cl::ConstraintCommitment; use cl::Constraint;
use ledger_proof_statements::constraint::ConstraintPublic; use ledger_proof_statements::constraint::ConstraintPublic;
use crate::error::Result; use crate::error::Result;
@ -9,20 +9,20 @@ pub struct ConstraintProof {
pub risc0_receipt: risc0_zkvm::Receipt, pub risc0_receipt: risc0_zkvm::Receipt,
} }
pub fn risc0_constraint(risc0_id: [u32; 8]) -> ConstraintCommitment { pub fn risc0_constraint(risc0_id: [u32; 8]) -> Constraint {
// Commit to a RISC0 ID for use as a note constraint // Commit to a RISC0 ID for use as a note constraint
let mut bytes = [0u8; 32]; let mut bytes = [0u8; 32];
for (i, word) in risc0_id.iter().enumerate() { for (i, word) in risc0_id.iter().enumerate() {
let word_bytes = word.to_le_bytes(); let word_bytes = word.to_le_bytes();
bytes[i * 4 + 0] = word_bytes[0]; bytes[i * 4] = word_bytes[0];
bytes[i * 4 + 1] = word_bytes[1]; bytes[i * 4 + 1] = word_bytes[1];
bytes[i * 4 + 2] = word_bytes[2]; bytes[i * 4 + 2] = word_bytes[2];
bytes[i * 4 + 3] = word_bytes[3]; bytes[i * 4 + 3] = word_bytes[3];
} }
ConstraintCommitment::from_vk(&bytes) Constraint::from_vk(&bytes)
} }
impl ConstraintProof { impl ConstraintProof {
@ -33,7 +33,7 @@ impl ConstraintProof {
} }
} }
pub fn constraint(&self) -> ConstraintCommitment { pub fn constraint(&self) -> Constraint {
risc0_constraint(self.risc0_id) risc0_constraint(self.risc0_id)
} }
@ -49,7 +49,7 @@ impl ConstraintProof {
expected_public == public && self.risc0_receipt.verify(self.risc0_id).is_ok() expected_public == public && self.risc0_receipt.verify(self.risc0_id).is_ok()
} }
pub fn nop_constraint() -> ConstraintCommitment { pub fn nop_constraint() -> Constraint {
risc0_constraint(nomos_cl_risc0_proofs::CONSTRAINT_NOP_ID) risc0_constraint(nomos_cl_risc0_proofs::CONSTRAINT_NOP_ID)
} }

View File

@ -117,6 +117,5 @@ impl ProvedPartialTx {
fn note_commitment_leaves(note_commitments: &[cl::NoteCommitment]) -> [[u8; 32]; MAX_NOTE_COMMS] { fn note_commitment_leaves(note_commitments: &[cl::NoteCommitment]) -> [[u8; 32]; MAX_NOTE_COMMS] {
let note_comm_bytes = Vec::from_iter(note_commitments.iter().map(|c| c.as_bytes().to_vec())); let note_comm_bytes = Vec::from_iter(note_commitments.iter().map(|c| c.as_bytes().to_vec()));
let cm_leaves = cl::merkle::padded_leaves::<MAX_NOTE_COMMS>(&note_comm_bytes); cl::merkle::padded_leaves::<MAX_NOTE_COMMS>(&note_comm_bytes)
cm_leaves
} }

View File

@ -20,12 +20,8 @@ impl User {
} }
} }
fn receive_utxo( fn receive_utxo(note: cl::NoteWitness, nf_pk: cl::NullifierCommitment) -> cl::OutputWitness {
note: cl::NoteWitness, cl::OutputWitness::new(note, nf_pk)
nf_pk: cl::NullifierCommitment,
rng: impl CryptoRngCore,
) -> cl::OutputWitness {
cl::OutputWitness::random(note, nf_pk, rng)
} }
#[test] #[test]
@ -41,18 +37,17 @@ 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, ConstraintProof::nop_constraint()), cl::NoteWitness::stateless(10, nmo, ConstraintProof::nop_constraint(), &mut rng),
alice.pk(), alice.pk(),
&mut rng,
); );
let alices_input = cl::InputWitness::from_output(utxo, alice.sk()); let alices_input = cl::InputWitness::from_output(utxo, alice.sk());
// Alice wants to send 8 NMO to bob // Alice wants to send 8 NMO to bob
let bobs_output = cl::OutputWitness::random(cl::NoteWitness::basic(8, nmo), bob.pk(), &mut rng); let bobs_output = cl::OutputWitness::new(cl::NoteWitness::basic(8, nmo, &mut rng), bob.pk());
// .. 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::new(cl::NoteWitness::basic(2, nmo, &mut rng), alice.pk());
// 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 {