From 0b2ca37f59e1b822a78a68d7b90a5d950f4ad630 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Thu, 27 Jun 2024 02:31:56 -0400 Subject: [PATCH] cl: partial tx can compute paths to inputs / outputs --- cl/src/balance.rs | 48 +++++++++++++++++++++++++++++++++++++++++++- cl/src/bundle.rs | 12 +++++------ cl/src/input.rs | 8 ++++---- cl/src/merkle.rs | 36 ++++++++++++++++++--------------- cl/src/note.rs | 17 ++++++---------- cl/src/output.rs | 6 +++--- cl/src/partial_tx.rs | 48 ++++++++++++++++++++++++++++++++++---------- 7 files changed, 123 insertions(+), 52 deletions(-) diff --git a/cl/src/balance.rs b/cl/src/balance.rs index 7f7a1cb..fe19c43 100644 --- a/cl/src/balance.rs +++ b/cl/src/balance.rs @@ -12,10 +12,11 @@ lazy_static! { #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct Balance(#[serde(with = "serde_point")] pub SubgroupPoint); -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct BalanceWitness { pub value: u64, pub unit: String, + #[serde(with = "serde_scalar")] pub blinding: Scalar, } @@ -56,6 +57,51 @@ pub fn balance(value: u64, unit: &str, blinding: Scalar) -> SubgroupPoint { unit_point(unit) * value_scalar + *PEDERSON_COMMITMENT_BLINDING_POINT * blinding } +mod serde_scalar { + use super::Scalar; + use serde::de::{self, Visitor}; + use serde::{Deserializer, Serializer}; + use std::fmt; + + // Serialize a SubgroupPoint by converting it to bytes. + pub fn serialize(scalar: &Scalar, serializer: S) -> Result + where + S: Serializer, + { + let bytes = scalar.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 = Scalar; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a valid Scalar 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(Scalar::from_bytes(&bytes).unwrap()) + } + } + + deserializer.deserialize_bytes(BytesVisitor) + } +} + mod serde_point { use super::SubgroupPoint; use group::GroupEncoding; diff --git a/cl/src/bundle.rs b/cl/src/bundle.rs index 0ba556a..55a6b09 100644 --- a/cl/src/bundle.rs +++ b/cl/src/bundle.rs @@ -99,11 +99,11 @@ mod test { let mut rng = seed_rng(0); let nmo_10_in = - InputWitness::random(NoteWitness::new(10, "NMO", vec![], &mut rng), &mut rng); + InputWitness::random(NoteWitness::new(10, "NMO", [0u8; 32], &mut rng), &mut rng); let eth_23_in = - InputWitness::random(NoteWitness::new(23, "ETH", vec![], &mut rng), &mut rng); + InputWitness::random(NoteWitness::new(23, "ETH", [0u8; 32], &mut rng), &mut rng); let crv_4840_out = OutputWitness::random( - NoteWitness::new(4840, "CRV", vec![], &mut rng), + NoteWitness::new(4840, "CRV", [0u8; 32], &mut rng), NullifierSecret::random(&mut rng).commit(), // transferring to a random owner &mut rng, ); @@ -132,14 +132,14 @@ mod test { ); let crv_4840_in = - InputWitness::random(NoteWitness::new(4840, "CRV", vec![], &mut rng), &mut rng); + InputWitness::random(NoteWitness::new(4840, "CRV", [0u8; 32], &mut rng), &mut rng); let nmo_10_out = OutputWitness::random( - NoteWitness::new(10, "NMO", vec![], &mut rng), + NoteWitness::new(10, "NMO", [0u8; 32], &mut rng), NullifierSecret::random(&mut rng).commit(), // transferring to a random owner &mut rng, ); let eth_23_out = OutputWitness::random( - NoteWitness::new(23, "ETH", vec![], &mut rng), + NoteWitness::new(23, "ETH", [0u8; 32], &mut rng), NullifierSecret::random(&mut rng).commit(), // transferring to a random owner &mut rng, ); diff --git a/cl/src/input.rs b/cl/src/input.rs index b7b5e6e..bef23fd 100644 --- a/cl/src/input.rs +++ b/cl/src/input.rs @@ -125,7 +125,7 @@ mod test { let ptx_root = PtxRoot::default(); - let note = NoteWitness::new(10, "NMO", vec![], &mut rng); + let note = NoteWitness::new(10, "NMO", [0u8; 32], &mut rng); let nf_sk = NullifierSecret::random(&mut rng); let nonce = NullifierNonce::random(&mut rng); @@ -138,11 +138,11 @@ mod test { let wrong_witnesses = [ InputWitness { - note: NoteWitness::new(11, "NMO", vec![], &mut rng), + note: NoteWitness::new(11, "NMO", [0u8; 32], &mut rng), ..input_witness.clone() }, InputWitness { - note: NoteWitness::new(10, "ETH", vec![], &mut rng), + note: NoteWitness::new(10, "ETH", [0u8; 32], &mut rng), ..input_witness.clone() }, InputWitness { @@ -168,7 +168,7 @@ mod test { fn test_input_ptx_coupling() { let mut rng = seed_rng(0); - let note = NoteWitness::new(10, "NMO", vec![], &mut rng); + let note = NoteWitness::new(10, "NMO", [0u8; 32], &mut rng); let nf_sk = NullifierSecret::random(&mut rng); let nonce = NullifierNonce::random(&mut rng); diff --git a/cl/src/merkle.rs b/cl/src/merkle.rs index 99c092d..2aec034 100644 --- a/cl/src/merkle.rs +++ b/cl/src/merkle.rs @@ -1,9 +1,10 @@ use blake2::{Blake2s256, Digest}; +use serde::{Deserialize, Serialize}; pub fn padded_leaves(elements: &[Vec]) -> [[u8; 32]; N] { let mut leaves = [[0u8; 32]; N]; - for (i, element) in elements.into_iter().enumerate() { + for (i, element) in elements.iter().enumerate() { assert!(i < N); leaves[i] = leaf(element); } @@ -42,7 +43,7 @@ pub fn root(elements: [[u8; 32]; N]) -> [u8; 32] { nodes[0] } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum PathNode { Left([u8; 32]), Right([u8; 32]), @@ -65,12 +66,12 @@ pub fn verify_path(leaf: [u8; 32], path: &[PathNode], root: [u8; 32]) -> bool { computed_hash == root } -pub fn path(elements: [&[u8]; N], idx: usize) -> Vec { - let n = elements.len(); +pub fn path(leaves: [[u8; 32]; N], idx: usize) -> Vec { + let n = leaves.len(); assert!(n.is_power_of_two()); assert!(idx < n); - let mut nodes = Vec::from_iter(elements.into_iter().map(leaf)); + let mut nodes = leaves; let mut path = Vec::new(); let mut idx = idx; @@ -157,9 +158,10 @@ mod test { #[test] fn test_path_height_1() { - let r = root::<1>(padded_leaves(&[b"desert".into()])); + let leaves = padded_leaves(&[b"desert".into()]); + let r = root::<1>(leaves); - let p = path([b"desert"], 0); + let p = path::<1>(leaves, 0); let expected = vec![]; assert_eq!(p, expected); assert!(verify_path(leaf(b"desert"), &p, r)); @@ -167,18 +169,19 @@ mod test { #[test] fn test_path_height_2() { - let r = root::<2>(padded_leaves(&[b"desert".into(), b"sand".into()])); + let leaves = padded_leaves(&[b"desert".into(), b"sand".into()]); + let r = root::<2>(leaves); // --- proof for element at idx 0 - let p0 = path([b"desert", b"sand"], 0); + let p0 = path(leaves, 0); let expected0 = vec![PathNode::Right(leaf(b"sand"))]; assert_eq!(p0, expected0); assert!(verify_path(leaf(b"desert"), &p0, r)); // --- proof for element at idx 1 - let p1 = path([b"desert", b"sand"], 1); + let p1 = path(leaves, 1); let expected1 = vec![PathNode::Left(leaf(b"desert"))]; assert_eq!(p1, expected1); assert!(verify_path(leaf(b"sand"), &p1, r)); @@ -186,16 +189,17 @@ mod test { #[test] fn test_path_height_3() { - let r = root::<4>(padded_leaves(&[ + let leaves = padded_leaves(&[ b"desert".into(), b"sand".into(), b"feels".into(), b"warm".into(), - ])); + ]); + let r = root::<4>(leaves); // --- proof for element at idx 0 - let p0 = path([b"desert", b"sand", b"feels", b"warm"], 0); + let p0 = path(leaves, 0); let expected0 = vec![ PathNode::Right(leaf(b"sand")), PathNode::Right(node(leaf(b"feels"), leaf(b"warm"))), @@ -205,7 +209,7 @@ mod test { // --- proof for element at idx 1 - let p1 = path([b"desert", b"sand", b"feels", b"warm"], 1); + let p1 = path(leaves, 1); let expected1 = vec![ PathNode::Left(leaf(b"desert")), PathNode::Right(node(leaf(b"feels"), leaf(b"warm"))), @@ -215,7 +219,7 @@ mod test { // --- proof for element at idx 2 - let p2 = path([b"desert", b"sand", b"feels", b"warm"], 2); + let p2 = path(leaves, 2); let expected2 = vec![ PathNode::Right(leaf(b"warm")), PathNode::Left(node(leaf(b"desert"), leaf(b"sand"))), @@ -225,7 +229,7 @@ mod test { // --- proof for element at idx 3 - let p3 = path([b"desert", b"sand", b"feels", b"warm"], 3); + let p3 = path(leaves, 3); let expected3 = vec![ PathNode::Left(leaf(b"feels")), PathNode::Left(node(leaf(b"desert"), leaf(b"sand"))), diff --git a/cl/src/note.rs b/cl/src/note.rs index ae41e60..abb2876 100644 --- a/cl/src/note.rs +++ b/cl/src/note.rs @@ -19,7 +19,7 @@ impl NoteCommitment { // TODO: Rename Note to NoteWitness and NoteCommitment to Note -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct NoteWitness { pub balance: BalanceWitness, pub death_constraint: Vec, // serialized verification key of death constraint @@ -27,16 +27,11 @@ pub struct NoteWitness { } impl NoteWitness { - pub fn new( - value: u64, - unit: impl Into, - death_constraint: Vec, - rng: impl RngCore, - ) -> Self { + pub fn new(value: u64, unit: impl Into, state: [u8; 32], rng: impl RngCore) -> Self { Self { balance: BalanceWitness::random(value, unit, rng), - death_constraint, - state: [0u8; 32], + death_constraint: vec![], + state, } } @@ -77,8 +72,8 @@ mod test { #[test] fn test_note_commitments_dont_commit_to_balance_blinding() { let mut rng = seed_rng(0); - let n1 = NoteWitness::new(12, "NMO", vec![], &mut rng); - let n2 = NoteWitness::new(12, "NMO", vec![], &mut rng); + let n1 = NoteWitness::new(12, "NMO", [0u8; 32], &mut rng); + let n2 = NoteWitness::new(12, "NMO", [0u8; 32], &mut rng); let nf_pk = NullifierSecret::random(&mut rng).commit(); let nonce = NullifierNonce::random(&mut rng); diff --git a/cl/src/output.rs b/cl/src/output.rs index 7f43a7a..cbcb2bc 100644 --- a/cl/src/output.rs +++ b/cl/src/output.rs @@ -78,7 +78,7 @@ mod test { fn test_output_proof() { let mut rng = seed_rng(0); - let note = NoteWitness::new(10, "NMO", vec![], &mut rng); + let note = NoteWitness::new(10, "NMO", [0u8; 32], &mut rng); let nf_pk = NullifierSecret::random(&mut rng).commit(); let nonce = NullifierNonce::random(&mut rng); @@ -91,11 +91,11 @@ mod test { let wrong_witnesses = [ OutputWitness { - note: NoteWitness::new(11, "NMO", vec![], &mut rng), + note: NoteWitness::new(11, "NMO", [0u8; 32], &mut rng), ..witness.clone() }, OutputWitness { - note: NoteWitness::new(10, "ETH", vec![], &mut rng), + note: NoteWitness::new(10, "ETH", [0u8; 32], &mut rng), ..witness.clone() }, OutputWitness { diff --git a/cl/src/partial_tx.rs b/cl/src/partial_tx.rs index afaf3c4..3c56f7f 100644 --- a/cl/src/partial_tx.rs +++ b/cl/src/partial_tx.rs @@ -56,14 +56,14 @@ impl PartialTx { } } - pub fn root(&self) -> PtxRoot { - // COMMITMENT TO INPUTS + pub fn input_root(&self) -> [u8; 32] { let input_bytes = Vec::from_iter(self.inputs.iter().map(Input::to_bytes).map(Vec::from_iter)); let input_merkle_leaves = merkle::padded_leaves(&input_bytes); - let input_root = merkle::root::(input_merkle_leaves); + merkle::root::(input_merkle_leaves) + } - // COMMITMENT TO OUTPUTS + pub fn output_root(&self) -> [u8; 32] { let output_bytes = Vec::from_iter( self.outputs .iter() @@ -71,8 +71,30 @@ impl PartialTx { .map(Vec::from_iter), ); let output_merkle_leaves = merkle::padded_leaves(&output_bytes); - let output_root = merkle::root::(output_merkle_leaves); + merkle::root::(output_merkle_leaves) + } + pub fn input_merkle_path(&self, idx: usize) -> Vec { + let input_bytes = + Vec::from_iter(self.inputs.iter().map(Input::to_bytes).map(Vec::from_iter)); + let input_merkle_leaves = merkle::padded_leaves::(&input_bytes); + merkle::path(input_merkle_leaves, idx) + } + + pub fn output_merkle_path(&self, idx: usize) -> Vec { + let output_bytes = Vec::from_iter( + self.outputs + .iter() + .map(Output::to_bytes) + .map(Vec::from_iter), + ); + let output_merkle_leaves = merkle::padded_leaves::(&output_bytes); + merkle::path(output_merkle_leaves, idx) + } + + pub fn root(&self) -> PtxRoot { + let input_root = self.input_root(); + let output_root = self.output_root(); let root = merkle::node(input_root, output_root); PtxRoot(root) } @@ -154,10 +176,12 @@ mod test { fn test_partial_tx_proof() { let mut rng = seed_rng(0); - let nmo_10 = InputWitness::random(NoteWitness::new(10, "NMO", vec![], &mut rng), &mut rng); - let eth_23 = InputWitness::random(NoteWitness::new(23, "ETH", vec![], &mut rng), &mut rng); + let nmo_10 = + InputWitness::random(NoteWitness::new(10, "NMO", [0u8; 32], &mut rng), &mut rng); + let eth_23 = + InputWitness::random(NoteWitness::new(23, "ETH", [0u8; 32], &mut rng), &mut rng); let crv_4840 = OutputWitness::random( - NoteWitness::new(4840, "CRV", vec![], &mut rng), + NoteWitness::new(4840, "CRV", [0u8; 32], &mut rng), NullifierSecret::random(&mut rng).commit(), // transferring to a random owner &mut rng, ); @@ -178,10 +202,12 @@ mod test { fn test_partial_tx_balance() { let mut rng = seed_rng(0); - let nmo_10 = InputWitness::random(NoteWitness::new(10, "NMO", vec![], &mut rng), &mut rng); - let eth_23 = InputWitness::random(NoteWitness::new(23, "ETH", vec![], &mut rng), &mut rng); + let nmo_10 = + InputWitness::random(NoteWitness::new(10, "NMO", [0u8; 32], &mut rng), &mut rng); + let eth_23 = + InputWitness::random(NoteWitness::new(23, "ETH", [0u8; 32], &mut rng), &mut rng); let crv_4840 = OutputWitness::random( - NoteWitness::new(4840, "CRV", vec![], &mut rng), + NoteWitness::new(4840, "CRV", [0u8; 32], &mut rng), NullifierSecret::random(&mut rng).commit(), // transferring to a random owner &mut rng, );