diff --git a/cl/Cargo.toml b/cl/Cargo.toml index 13a5a73..279c094 100644 --- a/cl/Cargo.toml +++ b/cl/Cargo.toml @@ -6,6 +6,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +serde = {version="1.0", features = ["derive"]} +bincode = "1.3.3" +risc0-groth16 = "1.0.1" blake2 = "0.10.6" jubjub = "0.10.0" group = "0.13.0" diff --git a/cl/src/balance.rs b/cl/src/balance.rs index e5bddf5..c79cf24 100644 --- a/cl/src/balance.rs +++ b/cl/src/balance.rs @@ -2,14 +2,15 @@ use group::{ff::Field, GroupEncoding}; use jubjub::{Scalar, SubgroupPoint}; use lazy_static::lazy_static; use rand_core::RngCore; +use serde::{Deserialize, Serialize}; lazy_static! { static ref PEDERSON_COMMITMENT_BLINDING_POINT: SubgroupPoint = crate::crypto::hash_to_curve(b"NOMOS_CL_PEDERSON_COMMITMENT_BLINDING"); } -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct Balance(pub SubgroupPoint); +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct Balance(#[serde(with = "serde_point")] pub SubgroupPoint); #[derive(Debug, PartialEq, Eq, Clone)] pub struct BalanceWitness { @@ -55,6 +56,51 @@ pub fn balance(value: u64, unit: &str, blinding: Scalar) -> SubgroupPoint { unit_point(unit) * value_scalar + *PEDERSON_COMMITMENT_BLINDING_POINT * blinding } +mod serde_point { + use super::SubgroupPoint; + use group::GroupEncoding; + use serde::de::{self, Visitor}; + use serde::{Deserializer, Serializer}; + use std::fmt; + + // Serialize a SubgroupPoint by converting it to bytes. + pub fn serialize(point: &SubgroupPoint, serializer: S) -> Result + where + S: Serializer, + { + let bytes = point.to_bytes(); + serializer.serialize_bytes(&bytes) + } + + // Deserialize a SubgroupPoint by converting it from bytes. + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct BytesVisitor; + + impl<'de> Visitor<'de> for BytesVisitor { + type Value = SubgroupPoint; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a valid SubgroupPoint in byte representation") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: de::Error, + { + let mut bytes = ::Repr::default(); + assert_eq!(bytes.len(), v.len()); + bytes.copy_from_slice(v); + + Ok(SubgroupPoint::from_bytes(&bytes).unwrap()) + } + } + + deserializer.deserialize_bytes(BytesVisitor) + } +} #[cfg(test)] mod test { diff --git a/cl/src/bundle.rs b/cl/src/bundle.rs index 7c6178a..6d39867 100644 --- a/cl/src/bundle.rs +++ b/cl/src/bundle.rs @@ -1,39 +1,35 @@ use std::collections::BTreeSet; use jubjub::{Scalar, SubgroupPoint}; +use serde::{Deserialize, Serialize}; use crate::{ error::Error, note::NoteCommitment, - partial_tx::{PartialTx, PartialTxProof, PartialTxWitness}, + partial_tx::{PartialTx, PartialTxProof}, }; /// The transaction bundle is a collection of partial transactions. /// The goal in bundling transactions is to produce a set of partial transactions /// that balance each other. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Bundle { pub partials: Vec, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct BundleWitness { - pub partials: Vec, + pub balance_blinding: Scalar, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug)] pub struct BundleProof { pub partials: Vec, + pub balance_blinding: Scalar, } impl Bundle { - pub fn from_witness(w: BundleWitness) -> Self { - Self { - partials: Vec::from_iter(w.partials.into_iter().map(PartialTx::from_witness)), - } - } - pub fn balance(&self) -> SubgroupPoint { self.partials.iter().map(|ptx| ptx.balance()).sum() } @@ -42,11 +38,12 @@ impl Bundle { self.balance() == crate::balance::balance(0, "", balance_blinding_witness) } - pub fn prove(&self, w: BundleWitness) -> Result { - if &Self::from_witness(w.clone()) != self { - return Err(Error::ProofFailed); - } - if w.partials.len() == self.partials.len() { + pub fn prove( + &self, + w: BundleWitness, + ptx_proofs: Vec, + ) -> Result { + if ptx_proofs.len() == self.partials.len() { return Err(Error::ProofFailed); } let input_notes: Vec = self @@ -67,15 +64,13 @@ impl Bundle { return Err(Error::ProofFailed); } - let ptx_proofs = self - .partials - .iter() - .zip(w.partials) - .map(|(ptx, p_w)| ptx.prove(p_w)) - .collect::, _>>()?; + if self.balance() != crate::balance::balance(0, "", w.balance_blinding) { + return Err(Error::ProofFailed); + } Ok(BundleProof { partials: ptx_proofs, + balance_blinding: w.balance_blinding, }) } diff --git a/cl/src/input.rs b/cl/src/input.rs index 286e103..5e155dc 100644 --- a/cl/src/input.rs +++ b/cl/src/input.rs @@ -10,15 +10,17 @@ use crate::{ partial_tx::PtxCommitment, }; use rand_core::RngCore; +use risc0_groth16::{ProofJson, PublicInputsJson, Verifier}; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Input { pub note_comm: NoteCommitment, pub nullifier: Nullifier, pub balance: Balance, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct InputWitness { pub note: Note, pub nf_sk: NullifierSecret, @@ -36,8 +38,19 @@ impl InputWitness { } // as we don't have SNARKS hooked up yet, the witness will be our proof -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct InputProof(InputWitness, PtxCommitment); +#[derive(Debug)] +pub struct InputProof { + input: InputWitness, + ptx_comm: PtxCommitment, + death_proof: ProofJson, +} + +impl InputProof { + fn clone_death_proof(&self) -> ProofJson { + let bytes = bincode::serialize(&self.death_proof).unwrap(); + bincode::deserialize(&bytes).unwrap() + } +} impl Input { pub fn from_witness(w: InputWitness) -> Self { @@ -48,11 +61,22 @@ impl Input { } } - pub fn prove(&self, w: &InputWitness, ptx_comm: PtxCommitment) -> Result { - if &Input::from_witness(w.clone()) != self { + pub fn prove( + &self, + w: &InputWitness, + ptx_comm: PtxCommitment, + death_proof: ProofJson, + ) -> Result { + if bincode::serialize(&Input::from_witness(w.clone())).unwrap() + != bincode::serialize(&self).unwrap() + { Err(Error::ProofFailed) } else { - Ok(InputProof(w.clone(), ptx_comm)) + Ok(InputProof { + input: w.clone(), + ptx_comm, + death_proof, + }) } } @@ -64,13 +88,29 @@ impl Input { // - balance == v * hash_to_curve(Unit) + blinding * H // - ptx_comm is the same one that was used in proving. - let witness = &proof.0; + let witness = &proof.input; let nf_pk = witness.nf_sk.commit(); + + // let death_constraint_was_committed_to = + // witness.note.death_constraint == bincode::serialize(&death_constraint).unwrap(); + + let death_constraint_is_satisfied: bool = Verifier::from_json( + proof.clone_death_proof(), + PublicInputsJson { + values: vec![ptx_comm.hex()], + }, + bincode::deserialize(&witness.note.death_constraint).unwrap(), + ) + .unwrap() + .verify() + .is_ok(); self.note_comm == witness.note.commit(nf_pk, witness.nonce) && self.nullifier == Nullifier::new(witness.nf_sk, witness.nonce) && self.balance == witness.note.balance() - && ptx_comm == proof.1 + && ptx_comm == proof.ptx_comm + // && death_constraint_was_committed_to + && death_constraint_is_satisfied } pub(crate) fn to_bytes(&self) -> [u8; 96] { diff --git a/cl/src/note.rs b/cl/src/note.rs index bc768ea..969bbd3 100644 --- a/cl/src/note.rs +++ b/cl/src/note.rs @@ -1,13 +1,15 @@ use blake2::{Blake2s256, Digest}; use group::GroupEncoding; use rand_core::RngCore; +use risc0_groth16::VerifyingKeyJson; +use serde::{Deserialize, Serialize}; use crate::{ balance::{Balance, BalanceWitness}, nullifier::{NullifierCommitment, NullifierNonce}, }; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct NoteCommitment([u8; 32]); impl NoteCommitment { @@ -18,24 +20,38 @@ impl NoteCommitment { // TODO: Rename Note to NoteWitness and NoteCommitment to Note -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct Note { pub balance: BalanceWitness, + pub death_constraint: Vec, // serialized death_constraint } impl Note { - pub fn random(value: u64, unit: impl Into, rng: impl RngCore) -> Self { + pub fn random( + value: u64, + unit: impl Into, + death_constraint: &VerifyingKeyJson, + rng: impl RngCore, + ) -> Self { Self { balance: BalanceWitness::random(value, unit, rng), + death_constraint: bincode::serialize(death_constraint).unwrap(), } } pub fn commit(&self, nf_pk: NullifierCommitment, nonce: NullifierNonce) -> NoteCommitment { let mut hasher = Blake2s256::new(); hasher.update(b"NOMOS_CL_NOTE_COMMIT"); + + // COMMIT TO BALANCE hasher.update(self.balance.value.to_le_bytes()); hasher.update(self.balance.unit_point().to_bytes()); // Important! we don't commit to the balance blinding factor as that may make the notes linkable. + + // COMMIT TO DEATH CONSTRAINT + hasher.update(&self.death_constraint); + + // COMMIT TO NULLIFIER hasher.update(nf_pk.as_bytes()); hasher.update(nonce.as_bytes()); diff --git a/cl/src/nullifier.rs b/cl/src/nullifier.rs index d9bbce0..b5567f9 100644 --- a/cl/src/nullifier.rs +++ b/cl/src/nullifier.rs @@ -7,6 +7,7 @@ // secret is used for multiple notes. use blake2::{Blake2s256, Digest}; use rand_core::RngCore; +use serde::{Deserialize, Serialize}; // Maintained privately by note holder #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -26,7 +27,7 @@ pub struct NullifierNonce([u8; 16]); // The nullifier attached to input notes to prove an input has not // already been spent. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Nullifier([u8; 32]); impl NullifierSecret { diff --git a/cl/src/output.rs b/cl/src/output.rs index 0c17ebe..f6f18fb 100644 --- a/cl/src/output.rs +++ b/cl/src/output.rs @@ -1,4 +1,5 @@ use rand_core::RngCore; +use serde::{Deserialize, Serialize}; use crate::{ balance::Balance, @@ -7,13 +8,13 @@ use crate::{ nullifier::{NullifierCommitment, NullifierNonce}, }; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Output { pub note_comm: NoteCommitment, pub balance: Balance, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct OutputWitness { pub note: Note, pub nf_pk: NullifierCommitment, @@ -31,7 +32,7 @@ impl OutputWitness { } // as we don't have SNARKS hooked up yet, the witness will be our proof -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct OutputProof(OutputWitness); impl Output { diff --git a/cl/src/partial_tx.rs b/cl/src/partial_tx.rs index c29d273..a302172 100644 --- a/cl/src/partial_tx.rs +++ b/cl/src/partial_tx.rs @@ -3,6 +3,8 @@ use std::collections::BTreeSet; use blake2::{Blake2s256, Digest}; use jubjub::SubgroupPoint; use rand_core::RngCore; +use risc0_groth16::ProofJson; +use serde::{Deserialize, Serialize}; use crate::error::Error; use crate::input::{Input, InputProof, InputWitness}; @@ -19,21 +21,25 @@ impl PtxCommitment { rng.fill_bytes(&mut sk); Self(sk) } + + pub fn hex(&self) -> String { + hex::encode(self.0) + } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct PartialTx { pub inputs: Vec, pub outputs: Vec, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct PartialTxWitness { pub inputs: Vec, pub outputs: Vec, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug)] pub struct PartialTxProof { pub inputs: Vec, pub outputs: Vec, @@ -63,8 +69,14 @@ impl PartialTx { PtxCommitment(commit_bytes) } - pub fn prove(&self, w: PartialTxWitness) -> Result { - if &Self::from_witness(w.clone()) != self { + pub fn prove( + &self, + w: PartialTxWitness, + death_proofs: Vec, + ) -> Result { + if bincode::serialize(&Self::from_witness(w.clone())).unwrap() + != bincode::serialize(&self).unwrap() + { return Err(Error::ProofFailed); } let input_note_comms = BTreeSet::from_iter(self.inputs.iter().map(|i| i.note_comm)); @@ -82,7 +94,8 @@ impl PartialTx { self.inputs .iter() .zip(&w.inputs) - .map(|(i, i_w)| i.prove(i_w, ptx_comm)), + .zip(death_proofs.into_iter()) + .map(|((i, i_w), death_p)| i.prove(i_w, ptx_comm, death_p)), )?; let output_proofs: Vec = Result::from_iter(