goas: move balance blinding to ptx level

This commit is contained in:
David Rusu 2024-08-21 13:00:35 +04:00
parent edd69d63bd
commit 7488dea9d1
12 changed files with 129 additions and 198 deletions

View File

@ -217,7 +217,10 @@ pub fn prove_user_atomic_transfer(atomic_transfer: UserAtomicTransfer) -> ledger
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use cl::{note::unit_point, NoteWitness, NullifierNonce, OutputWitness, PartialTxWitness}; use cl::{
note::unit_point, BalanceWitness, NoteWitness, NullifierNonce, OutputWitness,
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;
use ledger_proof_statements::death_constraint::DeathConstraintPublic; use ledger_proof_statements::death_constraint::DeathConstraintPublic;
@ -270,6 +273,7 @@ mod tests {
zone_start.fund_input_witness(), zone_start.fund_input_witness(),
], ],
outputs: vec![zone_end.state_note, zone_end.fund_note], outputs: vec![zone_end.state_note, zone_end.fund_note],
balance_blinding: BalanceWitness::random(&mut rng),
}; };
let txs = vec![ let txs = vec![
@ -293,12 +297,13 @@ mod tests {
#[test] #[test]
fn test_prove_zone_fund_constraint() { fn test_prove_zone_fund_constraint() {
let zone = let mut rng = rand::thread_rng();
ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([]), &mut rand::thread_rng()); let zone = ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([]), &mut rng);
let ptx = PartialTxWitness { let ptx = PartialTxWitness {
inputs: vec![zone.fund_input_witness()], inputs: vec![zone.fund_input_witness()],
outputs: vec![zone.state_note], outputs: vec![zone.state_note],
balance_blinding: BalanceWitness::random(&mut rng),
}; };
let proof = let proof =
@ -344,6 +349,7 @@ mod tests {
let ptx = PartialTxWitness { let ptx = PartialTxWitness {
inputs: vec![user_note], inputs: vec![user_note],
outputs: vec![zone_a.state_note, zone_b.state_note], outputs: vec![zone_a.state_note, zone_b.state_note],
balance_blinding: BalanceWitness::random(&mut rng),
}; };
let user_atomic_transfer = UserAtomicTransfer { let user_atomic_transfer = UserAtomicTransfer {

View File

@ -1,6 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use cl::{BundleWitness, NoteWitness, NullifierNonce}; use cl::{BalanceWitness, BundleWitness, NoteWitness, NullifierNonce};
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};
@ -43,6 +43,7 @@ fn test_atomic_transfer() {
let user_ptx = cl::PartialTxWitness { let user_ptx = cl::PartialTxWitness {
inputs: vec![], inputs: vec![],
outputs: vec![alice_intent_out], outputs: vec![alice_intent_out],
balance_blinding: BalanceWitness::random(&mut rng),
}; };
let zone_a_end = zone_a_start let zone_a_end = zone_a_start
@ -68,6 +69,7 @@ fn test_atomic_transfer() {
zone_b_end.state_note, zone_b_end.state_note,
zone_b_end.fund_note, zone_b_end.fund_note,
], ],
balance_blinding: BalanceWitness::random(&mut rng),
}; };
let signed_withdraw = SignedBoundTx::sign( let signed_withdraw = SignedBoundTx::sign(
@ -165,7 +167,7 @@ fn test_atomic_transfer() {
let bundle_witness = BundleWitness { let bundle_witness = BundleWitness {
balance_blinding: cl::BalanceWitness( balance_blinding: cl::BalanceWitness(
user_ptx.balance_blinding().0 + atomic_transfer_ptx.balance_blinding().0, user_ptx.balance_blinding.0 + atomic_transfer_ptx.balance_blinding.0,
), ),
}; };

View File

@ -1,6 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use cl::{NoteWitness, NullifierSecret}; use cl::{BalanceWitness, NoteWitness, NullifierSecret};
use common::{new_account, BoundTx, SignedBoundTx, 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;
@ -22,7 +22,7 @@ fn test_deposit() {
let zone_end = zone_start.clone().run([Tx::Deposit(deposit)]); let zone_end = zone_start.clone().run([Tx::Deposit(deposit)]);
let alice_deposit = cl::InputWitness::random( let alice_deposit = cl::InputWitness::from_output(
cl::OutputWitness::random( cl::OutputWitness::random(
NoteWitness::stateless( NoteWitness::stateless(
78, 78,
@ -33,12 +33,12 @@ fn test_deposit() {
&mut rng, &mut rng,
), ),
alice_cl_sk, alice_cl_sk,
&mut rng,
); );
let deposit_ptx = cl::PartialTxWitness { let deposit_ptx = cl::PartialTxWitness {
inputs: vec![zone_start.state_input_witness(), alice_deposit], inputs: vec![zone_start.state_input_witness(), alice_deposit],
outputs: vec![zone_end.state_note, zone_end.fund_note], outputs: vec![zone_end.state_note, zone_end.fund_note],
balance_blinding: BalanceWitness::random(&mut rng),
}; };
let signed_deposit = SignedBoundTx::sign( let signed_deposit = SignedBoundTx::sign(
@ -89,7 +89,7 @@ fn test_deposit() {
.0 .0
); );
assert_eq!( assert_eq!(
deposit_ptx.commit().balance(), deposit_ptx.commit().balance,
cl::Balance::zero(deposit_ptx.balance_blinding()) cl::Balance::zero(deposit_ptx.balance_blinding)
); );
} }

View File

@ -1,6 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use cl::{NoteWitness, NullifierSecret}; use cl::{BalanceWitness, NoteWitness, NullifierSecret};
use common::{new_account, BoundTx, SignedBoundTx, 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;
@ -16,14 +16,13 @@ fn test_withdrawal() {
let zone_start = let zone_start =
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::random( let alice_intent = cl::InputWitness::from_output(
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_cl_sk.commit(), alice_cl_sk.commit(),
&mut rng, &mut rng,
), ),
alice_cl_sk, alice_cl_sk,
&mut rng,
); );
let withdraw = common::Withdraw { let withdraw = common::Withdraw {
@ -50,6 +49,7 @@ fn test_withdrawal() {
alice_intent, alice_intent,
], ],
outputs: vec![zone_end.state_note, zone_end.fund_note, alice_withdrawal], outputs: vec![zone_end.state_note, zone_end.fund_note, alice_withdrawal],
balance_blinding: BalanceWitness::random(&mut rng),
}; };
let signed_withdraw = SignedBoundTx::sign( let signed_withdraw = SignedBoundTx::sign(

View File

@ -32,12 +32,6 @@ fn validate_zone_transition(
// nullifier secret is propagated // nullifier secret is propagated
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);
// balance blinding is propagated
assert_eq!(
in_note.input.balance_blinding,
out_note.output.balance_blinding
);
// 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.nonce);

View File

@ -1,4 +1,7 @@
use curve25519_dalek::{ristretto::RistrettoPoint, Scalar}; use curve25519_dalek::{
constants::RISTRETTO_BASEPOINT_POINT, ristretto::RistrettoPoint, traits::VartimeMultiscalarMul,
Scalar,
};
use rand_core::CryptoRngCore; use rand_core::CryptoRngCore;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -42,8 +45,33 @@ impl BalanceWitness {
Self::new(Scalar::random(&mut rng)) Self::new(Scalar::random(&mut rng))
} }
pub fn commit(&self, note: &NoteWitness) -> Balance { pub fn commit<'a>(
Balance(balance(note.value, note.unit, self.0)) &self,
inputs: impl IntoIterator<Item = &'a NoteWitness>,
outputs: impl IntoIterator<Item = &'a NoteWitness>,
) -> Balance {
let (input_points, input_scalars): (Vec<_>, Vec<_>) = inputs
.into_iter()
.map(|i| (i.unit, -Scalar::from(i.value)))
.unzip();
let (output_points, output_scalars): (Vec<_>, Vec<_>) = outputs
.into_iter()
.map(|o| (o.unit, Scalar::from(o.value)))
.unzip();
let points = input_points
.into_iter()
.chain(output_points)
.chain([RISTRETTO_BASEPOINT_POINT]);
let scalars = input_scalars
.into_iter()
.chain(output_scalars)
.chain([self.0]);
let blinded_balance = RistrettoPoint::vartime_multiscalar_mul(scalars, points);
Balance(blinded_balance)
} }
} }
@ -66,8 +94,8 @@ mod test {
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)], []),
); );
} }
@ -83,15 +111,15 @@ mod test {
let note = NoteWitness::basic(10, nmo); let note = NoteWitness::basic(10, nmo);
let a = bal_a.commit(&note); let a = bal_a.commit([&note], []);
let b = bal_b.commit(&note); let b = bal_b.commit([&note], []);
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
); );
} }
@ -104,7 +132,7 @@ mod test {
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(&eth)); assert_ne!(b.commit([&nmo], []), b.commit([&eth], []));
} }
#[test] #[test]
@ -123,14 +151,14 @@ mod test {
// Values of same unit are homomorphic // Values of same unit are homomorphic
assert_eq!( assert_eq!(
(b1.commit(&ten).0 - b1.commit(&eight).0), (b1.commit([&ten], []).0 - b1.commit([&eight], []).0),
b_zero.commit(&two).0 b_zero.commit([&two], []).0
); );
// Blinding factors are also homomorphic. // Blinding factors are also homomorphic.
assert_eq!( assert_eq!(
b1.commit(&ten).0 - b2.commit(&ten).0, b1.commit([&ten], []).0 - b2.commit([&ten], []).0,
BalanceWitness::new(b1.0 - b2.0).commit(&zero).0 BalanceWitness::new(b1.0 - b2.0).commit([&zero], []).0
); );
} }
} }

View File

@ -18,7 +18,7 @@ pub struct BundleWitness {
impl Bundle { impl Bundle {
pub fn balance(&self) -> Balance { pub fn balance(&self) -> Balance {
Balance(self.partials.iter().map(|ptx| ptx.balance().0).sum()) Balance(self.partials.iter().map(|ptx| ptx.balance.0).sum())
} }
pub fn is_balanced(&self, witness: BalanceWitness) -> bool { pub fn is_balanced(&self, witness: BalanceWitness) -> bool {
@ -28,6 +28,8 @@ impl Bundle {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, Scalar};
use crate::{ use crate::{
input::InputWitness, input::InputWitness,
note::{unit_point, NoteWitness}, note::{unit_point, NoteWitness},
@ -49,11 +51,11 @@ mod test {
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::from_output(nmo_10_utxo, nf_a);
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::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::random(NoteWitness::basic(4840, crv), nf_c.commit(), &mut rng);
@ -61,28 +63,28 @@ mod test {
let ptx_unbalanced = PartialTxWitness { let ptx_unbalanced = PartialTxWitness {
inputs: vec![nmo_10_in, eth_23_in], inputs: vec![nmo_10_in, eth_23_in],
outputs: vec![crv_4840_out], outputs: vec![crv_4840_out],
balance_blinding: BalanceWitness::random(&mut rng),
}; };
let bundle_witness = BundleWitness { let bundle_witness = BundleWitness {
balance_blinding: BalanceWitness::new( balance_blinding: ptx_unbalanced.balance_blinding,
crv_4840_out.balance_blinding.0
- nmo_10_in.balance_blinding.0
- eth_23_in.balance_blinding.0,
),
}; };
let mut bundle = Bundle { let mut bundle = Bundle {
partials: vec![ptx_unbalanced.commit()], partials: vec![ptx_unbalanced.commit()],
}; };
let crv_4840_out_bal = crv_4840_out.note.unit * Scalar::from(crv_4840_out.note.value);
let nmo_10_in_bal = nmo_10_in.note.unit * Scalar::from(nmo_10_in.note.value);
let eth_23_in_bal = eth_23_in.note.unit * Scalar::from(eth_23_in.note.value);
let unbalance_blinding = RISTRETTO_BASEPOINT_POINT * ptx_unbalanced.balance_blinding.0;
assert!(!bundle.is_balanced(bundle_witness.balance_blinding)); assert!(!bundle.is_balanced(bundle_witness.balance_blinding));
assert_eq!( assert_eq!(
bundle.balance().0, bundle.balance().0,
crv_4840_out.commit().balance.0 crv_4840_out_bal - (nmo_10_in_bal + eth_23_in_bal) + unbalance_blinding
- (nmo_10_in.commit().balance.0 + eth_23_in.commit().balance.0)
); );
let crv_4840_in = InputWitness::random(crv_4840_out, nf_c, &mut rng); let crv_4840_in = InputWitness::from_output(crv_4840_out, nf_c);
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
@ -94,21 +96,17 @@ mod test {
&mut rng, &mut rng,
); );
bundle.partials.push( let ptx_solved = PartialTxWitness {
PartialTxWitness { inputs: vec![crv_4840_in],
inputs: vec![crv_4840_in], outputs: vec![nmo_10_out, eth_23_out],
outputs: vec![nmo_10_out, eth_23_out], balance_blinding: BalanceWitness::random(&mut rng),
} };
.commit(),
); bundle.partials.push(ptx_solved.commit());
let witness = BundleWitness { let witness = BundleWitness {
balance_blinding: BalanceWitness::new( balance_blinding: BalanceWitness::new(
-nmo_10_in.balance_blinding.0 - eth_23_in.balance_blinding.0 ptx_unbalanced.balance_blinding.0 + ptx_solved.balance_blinding.0,
+ crv_4840_out.balance_blinding.0
- crv_4840_in.balance_blinding.0
+ nmo_10_out.balance_blinding.0
+ eth_23_out.balance_blinding.0,
), ),
}; };

View File

@ -3,39 +3,29 @@
/// 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::{
balance::Balance,
note::{DeathCommitment, NoteWitness}, note::{DeathCommitment, NoteWitness},
nullifier::{Nullifier, NullifierNonce, NullifierSecret}, nullifier::{Nullifier, NullifierNonce, NullifierSecret},
BalanceWitness,
}; };
use rand_core::CryptoRngCore;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[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 balance: Balance,
pub death_cm: DeathCommitment, pub death_cm: DeathCommitment,
} }
#[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 balance_blinding: BalanceWitness,
pub nf_sk: NullifierSecret, pub nf_sk: NullifierSecret,
pub nonce: NullifierNonce, pub nonce: NullifierNonce,
} }
impl InputWitness { impl InputWitness {
pub fn random( pub fn from_output(output: crate::OutputWitness, nf_sk: NullifierSecret) -> Self {
output: crate::OutputWitness,
nf_sk: NullifierSecret,
mut rng: impl CryptoRngCore,
) -> Self {
assert_eq!(nf_sk.commit(), output.nf_pk); assert_eq!(nf_sk.commit(), output.nf_pk);
Self { Self {
note: output.note, note: output.note,
balance_blinding: BalanceWitness::random(&mut rng),
nf_sk, nf_sk,
nonce: output.nonce, nonce: output.nonce,
} }
@ -46,7 +36,6 @@ impl InputWitness {
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 {
note: output.note, note: output.note,
balance_blinding: BalanceWitness::unblinded(),
nf_sk, nf_sk,
nonce: output.nonce, nonce: output.nonce,
} }
@ -56,14 +45,9 @@ impl InputWitness {
self.nonce.evolve(domain, &self.nf_sk, &self.note) self.nonce.evolve(domain, &self.nf_sk, &self.note)
} }
pub fn evolve_output( pub fn evolve_output(&self, domain: &[u8]) -> crate::OutputWitness {
&self,
domain: &[u8],
balance_blinding: BalanceWitness,
) -> crate::OutputWitness {
crate::OutputWitness { crate::OutputWitness {
note: self.note, note: self.note,
balance_blinding,
nf_pk: self.nf_sk.commit(), nf_pk: self.nf_sk.commit(),
nonce: self.evolved_nonce(domain), nonce: self.evolved_nonce(domain),
} }
@ -76,7 +60,6 @@ impl InputWitness {
pub fn commit(&self) -> Input { pub fn commit(&self) -> Input {
Input { Input {
nullifier: self.nullifier(), nullifier: self.nullifier(),
balance: self.balance_blinding.commit(&self.note),
death_cm: self.note.death_commitment(), death_cm: self.note.death_commitment(),
} }
} }
@ -87,11 +70,10 @@ impl InputWitness {
} }
impl Input { impl Input {
pub fn to_bytes(&self) -> [u8; 96] { pub fn to_bytes(&self) -> [u8; 64] {
let mut bytes = [0u8; 96]; let mut bytes = [0u8; 64];
bytes[..32].copy_from_slice(self.nullifier.as_bytes()); bytes[..32].copy_from_slice(self.nullifier.as_bytes());
bytes[32..64].copy_from_slice(&self.balance.to_bytes()); bytes[32..64].copy_from_slice(&self.death_cm.0);
bytes[64..96].copy_from_slice(&self.death_cm.0);
bytes bytes
} }
} }

View File

@ -2,23 +2,19 @@ use rand_core::CryptoRngCore;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
balance::Balance,
error::Error,
note::{NoteCommitment, NoteWitness}, note::{NoteCommitment, NoteWitness},
nullifier::{NullifierCommitment, NullifierNonce}, nullifier::{NullifierCommitment, NullifierNonce},
BalanceWitness, NullifierSecret, NullifierSecret,
}; };
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Output { pub struct Output {
pub note_comm: NoteCommitment, pub note_comm: NoteCommitment,
pub balance: Balance,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct OutputWitness { pub struct OutputWitness {
pub note: NoteWitness, pub note: NoteWitness,
pub balance_blinding: BalanceWitness,
pub nf_pk: NullifierCommitment, pub nf_pk: NullifierCommitment,
pub nonce: NullifierNonce, pub nonce: NullifierNonce,
} }
@ -31,7 +27,6 @@ impl OutputWitness {
) -> Self { ) -> Self {
Self { Self {
note, note,
balance_blinding: BalanceWitness::random(&mut rng),
nf_pk: owner, nf_pk: owner,
nonce: NullifierNonce::random(&mut rng), nonce: NullifierNonce::random(&mut rng),
} }
@ -40,7 +35,6 @@ impl OutputWitness {
pub fn public(note: NoteWitness, nonce: NullifierNonce) -> Self { pub fn public(note: NoteWitness, nonce: NullifierNonce) -> Self {
Self { Self {
note, note,
balance_blinding: BalanceWitness::unblinded(),
nf_pk: NullifierSecret::zero().commit(), nf_pk: NullifierSecret::zero().commit(),
nonce, nonce,
} }
@ -50,99 +44,15 @@ impl OutputWitness {
self.note.commit(self.nf_pk, self.nonce) self.note.commit(self.nf_pk, self.nonce)
} }
pub fn commit_balance(&self) -> Balance {
self.balance_blinding.commit(&self.note)
}
pub fn commit(&self) -> Output { pub fn commit(&self) -> Output {
Output { Output {
note_comm: self.commit_note(), note_comm: self.commit_note(),
balance: self.commit_balance(),
} }
} }
} }
// as we don't have SNARKS hooked up yet, the witness will be our proof
#[derive(Debug, Clone)]
pub struct OutputProof(OutputWitness);
impl Output { impl Output {
pub fn prove(&self, w: &OutputWitness) -> Result<OutputProof, Error> { pub fn to_bytes(&self) -> [u8; 32] {
if &w.commit() == self { self.note_comm.0
Ok(OutputProof(*w))
} else {
Err(Error::ProofFailed)
}
}
pub fn verify(&self, proof: &OutputProof) -> bool {
// verification checks the relation
// - note_comm == commit(note || nf_pk)
// - balance == v * hash_to_curve(Unit) + blinding * H
let witness = &proof.0;
self.note_comm == witness.commit_note() && self.balance == witness.commit_balance()
}
pub fn to_bytes(&self) -> [u8; 64] {
let mut bytes = [0u8; 64];
bytes[..32].copy_from_slice(self.note_comm.as_bytes());
bytes[32..64].copy_from_slice(&self.balance.to_bytes());
bytes
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{note::unit_point, nullifier::NullifierSecret};
#[test]
fn test_output_proof() {
let (nmo, eth) = (unit_point("NMO"), unit_point("ETH"));
let mut rng = rand::thread_rng();
let witness = OutputWitness {
note: NoteWitness::basic(10, nmo),
balance_blinding: BalanceWitness::random(&mut rng),
nf_pk: NullifierSecret::random(&mut rng).commit(),
nonce: NullifierNonce::random(&mut rng),
};
let output = witness.commit();
let proof = output.prove(&witness).unwrap();
assert!(output.verify(&proof));
let wrong_witnesses = [
OutputWitness {
note: NoteWitness::basic(11, nmo),
..witness
},
OutputWitness {
note: NoteWitness::basic(10, eth),
..witness
},
OutputWitness {
balance_blinding: BalanceWitness::random(&mut rng),
..witness
},
OutputWitness {
nf_pk: NullifierSecret::random(&mut rng).commit(),
..witness
},
OutputWitness {
nonce: NullifierNonce::random(&mut rng),
..witness
},
];
for wrong_witness in wrong_witnesses {
assert!(output.prove(&wrong_witness).is_err());
let wrong_output = wrong_witness.commit();
let wrong_proof = wrong_output.prove(&wrong_witness).unwrap();
assert!(!output.verify(&wrong_proof));
}
} }
} }

View File

@ -1,6 +1,4 @@
use curve25519_dalek::ristretto::RistrettoPoint; use rand_core::{CryptoRngCore, RngCore};
use curve25519_dalek::Scalar;
use rand_core::RngCore;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::balance::{Balance, BalanceWitness}; use crate::balance::{Balance, BalanceWitness};
@ -38,29 +36,40 @@ impl PtxRoot {
pub struct PartialTx { pub struct PartialTx {
pub inputs: Vec<Input>, pub inputs: Vec<Input>,
pub outputs: Vec<Output>, pub outputs: Vec<Output>,
pub balance: Balance,
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PartialTxWitness { pub struct PartialTxWitness {
pub inputs: Vec<InputWitness>, pub inputs: Vec<InputWitness>,
pub outputs: Vec<OutputWitness>, pub outputs: Vec<OutputWitness>,
pub balance_blinding: BalanceWitness,
} }
impl PartialTxWitness { impl PartialTxWitness {
pub fn random(
inputs: Vec<InputWitness>,
outputs: Vec<OutputWitness>,
mut rng: impl CryptoRngCore,
) -> Self {
Self {
inputs,
outputs,
balance_blinding: BalanceWitness::random(&mut rng),
}
}
pub fn commit(&self) -> PartialTx { pub fn commit(&self) -> PartialTx {
PartialTx { PartialTx {
inputs: Vec::from_iter(self.inputs.iter().map(InputWitness::commit)), inputs: Vec::from_iter(self.inputs.iter().map(InputWitness::commit)),
outputs: Vec::from_iter(self.outputs.iter().map(OutputWitness::commit)), outputs: Vec::from_iter(self.outputs.iter().map(OutputWitness::commit)),
balance: self.balance_blinding.commit(
self.inputs.iter().map(|i| &i.note),
self.outputs.iter().map(|o| &o.note),
),
} }
} }
pub fn balance_blinding(&self) -> BalanceWitness {
let in_sum: Scalar = self.inputs.iter().map(|i| i.balance_blinding.0).sum();
let out_sum: Scalar = self.outputs.iter().map(|o| o.balance_blinding.0).sum();
BalanceWitness(out_sum - in_sum)
}
pub fn input_witness(&self, idx: usize) -> PartialTxInputWitness { pub fn input_witness(&self, idx: usize) -> PartialTxInputWitness {
let input_bytes = let input_bytes =
Vec::from_iter(self.inputs.iter().map(|i| i.commit().to_bytes().to_vec())); Vec::from_iter(self.inputs.iter().map(|i| i.commit().to_bytes().to_vec()));
@ -107,13 +116,6 @@ impl PartialTx {
let root = merkle::node(input_root, output_root); let root = merkle::node(input_root, output_root);
PtxRoot(root) PtxRoot(root)
} }
pub fn balance(&self) -> Balance {
let in_sum: RistrettoPoint = self.inputs.iter().map(|i| i.balance.0).sum();
let out_sum: RistrettoPoint = self.outputs.iter().map(|o| o.balance.0).sum();
Balance(out_sum - in_sum)
}
} }
/// An input to a partial transaction /// An input to a partial transaction
@ -147,6 +149,8 @@ impl PartialTxOutputWitness {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, Scalar};
use crate::{ use crate::{
note::{unit_point, NoteWitness}, note::{unit_point, NoteWitness},
nullifier::NullifierSecret, nullifier::NullifierSecret,
@ -165,11 +169,11 @@ mod test {
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::from_output(nmo_10_utxo, nf_a);
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::from_output(eth_23_utxo, nf_b);
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);
@ -177,13 +181,18 @@ mod test {
let ptx_witness = PartialTxWitness { let ptx_witness = PartialTxWitness {
inputs: vec![nmo_10, eth_23], inputs: vec![nmo_10, eth_23],
outputs: vec![crv_4840], outputs: vec![crv_4840],
balance_blinding: BalanceWitness::random(&mut rng),
}; };
let ptx = ptx_witness.commit(); let ptx = ptx_witness.commit();
let crv_4840_bal = crv_4840.note.unit * Scalar::from(crv_4840.note.value);
let nmo_10_bal = nmo_10.note.unit * Scalar::from(nmo_10.note.value);
let eth_23_bal = eth_23.note.unit * Scalar::from(eth_23.note.value);
let blinding = RISTRETTO_BASEPOINT_POINT * ptx_witness.balance_blinding.0;
assert_eq!( assert_eq!(
ptx.balance().0, ptx.balance.0,
crv_4840.commit().balance.0 - (nmo_10.commit().balance.0 + eth_23.commit().balance.0) crv_4840_bal - (nmo_10_bal + eth_23_bal) + blinding,
); );
} }
} }

View File

@ -1,4 +1,4 @@
use cl::note::unit_point; use cl::{note::unit_point, BalanceWitness};
use rand_core::CryptoRngCore; use rand_core::CryptoRngCore;
fn receive_utxo( fn receive_utxo(
@ -29,8 +29,9 @@ fn test_simple_transfer() {
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::from_output(utxo, sender_nf_sk)],
outputs: vec![recipient_output, change_output], outputs: vec![recipient_output, change_output],
balance_blinding: BalanceWitness::random(&mut rng),
}; };
let ptx = ptx_witness.commit(); let ptx = ptx_witness.commit();
@ -39,5 +40,5 @@ fn test_simple_transfer() {
partials: vec![ptx], partials: vec![ptx],
}; };
assert!(bundle.is_balanced(ptx_witness.balance_blinding())) assert!(bundle.is_balanced(ptx_witness.balance_blinding))
} }

View File

@ -1,6 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use cl::note::unit_point; use cl::{note::unit_point, BalanceWitness};
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;
@ -45,7 +45,7 @@ fn test_simple_transfer() {
alice.pk(), alice.pk(),
&mut rng, &mut rng,
); );
let alices_input = cl::InputWitness::random(utxo, alice.sk(), &mut rng); 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::random(cl::NoteWitness::basic(8, nmo), bob.pk(), &mut rng);
@ -58,6 +58,7 @@ fn test_simple_transfer() {
let ptx_witness = cl::PartialTxWitness { let ptx_witness = cl::PartialTxWitness {
inputs: vec![alices_input], inputs: vec![alices_input],
outputs: vec![bobs_output, change_output], outputs: vec![bobs_output, change_output],
balance_blinding: BalanceWitness::random(&mut rng),
}; };
// Prove the death constraints for alices input (she uses the no-op death constraint) // Prove the death constraints for alices input (she uses the no-op death constraint)
@ -79,7 +80,7 @@ fn test_simple_transfer() {
}; };
let bundle_witness = cl::BundleWitness { let bundle_witness = cl::BundleWitness {
balance_blinding: ptx_witness.balance_blinding(), balance_blinding: ptx_witness.balance_blinding,
}; };
let proved_bundle = ProvedBundle::prove(&bundle, &bundle_witness).unwrap(); let proved_bundle = ProvedBundle::prove(&bundle, &bundle_witness).unwrap();