cl: partial tx can compute paths to inputs / outputs

This commit is contained in:
David Rusu 2024-06-27 02:31:56 -04:00
parent 9c1317f55b
commit 0b2ca37f59
7 changed files with 123 additions and 52 deletions

View File

@ -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<S>(scalar: &Scalar, serializer: S) -> Result<S::Ok, S::Error>
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<Scalar, D::Error>
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<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
let mut bytes = <jubjub::SubgroupPoint as group::GroupEncoding>::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;

View File

@ -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,
);

View File

@ -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);

View File

@ -1,9 +1,10 @@
use blake2::{Blake2s256, Digest};
use serde::{Deserialize, Serialize};
pub fn padded_leaves<const N: usize>(elements: &[Vec<u8>]) -> [[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<const N: usize>(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<const N: usize>(elements: [&[u8]; N], idx: usize) -> Vec<PathNode> {
let n = elements.len();
pub fn path<const N: usize>(leaves: [[u8; 32]; N], idx: usize) -> Vec<PathNode> {
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"))),

View File

@ -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<u8>, // serialized verification key of death constraint
@ -27,16 +27,11 @@ pub struct NoteWitness {
}
impl NoteWitness {
pub fn new(
value: u64,
unit: impl Into<String>,
death_constraint: Vec<u8>,
rng: impl RngCore,
) -> Self {
pub fn new(value: u64, unit: impl Into<String>, 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);

View File

@ -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 {

View File

@ -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::<MAX_INPUTS>(input_merkle_leaves);
merkle::root::<MAX_INPUTS>(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::<MAX_OUTPUTS>(output_merkle_leaves);
merkle::root::<MAX_OUTPUTS>(output_merkle_leaves)
}
pub fn input_merkle_path(&self, idx: usize) -> Vec<merkle::PathNode> {
let input_bytes =
Vec::from_iter(self.inputs.iter().map(Input::to_bytes).map(Vec::from_iter));
let input_merkle_leaves = merkle::padded_leaves::<MAX_INPUTS>(&input_bytes);
merkle::path(input_merkle_leaves, idx)
}
pub fn output_merkle_path(&self, idx: usize) -> Vec<merkle::PathNode> {
let output_bytes = Vec::from_iter(
self.outputs
.iter()
.map(Output::to_bytes)
.map(Vec::from_iter),
);
let output_merkle_leaves = merkle::padded_leaves::<MAX_OUTPUTS>(&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,
);