cl: bundle of ptx
This commit is contained in:
parent
33ee6dea8c
commit
d6d3dcab12
|
@ -0,0 +1,158 @@
|
|||
use std::collections::BTreeSet;
|
||||
|
||||
use jubjub::{ExtendedPoint, Scalar};
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
note::NoteCommitment,
|
||||
partial_tx::{PartialTx, PartialTxProof, PartialTxWitness},
|
||||
};
|
||||
|
||||
/// 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)]
|
||||
pub struct Bundle {
|
||||
pub partials: Vec<PartialTx>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BundleWitness {
|
||||
pub partials: Vec<PartialTxWitness>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BundleProof {
|
||||
pub partials: Vec<PartialTxProof>,
|
||||
}
|
||||
|
||||
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) -> ExtendedPoint {
|
||||
self.partials.iter().map(|ptx| ptx.balance()).sum()
|
||||
}
|
||||
|
||||
pub fn is_balanced(&self, balance_blinding_witness: Scalar) -> bool {
|
||||
self.balance() == crate::balance::balance(0, "", balance_blinding_witness)
|
||||
}
|
||||
|
||||
pub fn prove(&self, w: BundleWitness) -> Result<BundleProof, Error> {
|
||||
if &Self::from_witness(w.clone()) != self {
|
||||
return Err(Error::ProofFailed);
|
||||
}
|
||||
if w.partials.len() == self.partials.len() {
|
||||
return Err(Error::ProofFailed);
|
||||
}
|
||||
let input_notes: Vec<NoteCommitment> = self
|
||||
.partials
|
||||
.iter()
|
||||
.flat_map(|ptx| ptx.inputs.iter().map(|i| i.note_comm))
|
||||
.collect();
|
||||
if input_notes.len() != BTreeSet::from_iter(input_notes.iter()).len() {
|
||||
return Err(Error::ProofFailed);
|
||||
}
|
||||
|
||||
let output_notes: Vec<NoteCommitment> = self
|
||||
.partials
|
||||
.iter()
|
||||
.flat_map(|ptx| ptx.outputs.iter().map(|o| o.note_comm))
|
||||
.collect();
|
||||
if output_notes.len() != BTreeSet::from_iter(output_notes.iter()).len() {
|
||||
return Err(Error::ProofFailed);
|
||||
}
|
||||
|
||||
let ptx_proofs = self
|
||||
.partials
|
||||
.iter()
|
||||
.zip(w.partials)
|
||||
.map(|(ptx, p_w)| ptx.prove(p_w))
|
||||
.collect::<Result<Vec<PartialTxProof>, _>>()?;
|
||||
|
||||
Ok(BundleProof {
|
||||
partials: ptx_proofs,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn verify(&self, proof: BundleProof) -> bool {
|
||||
proof.partials.len() == self.partials.len()
|
||||
&& self
|
||||
.partials
|
||||
.iter()
|
||||
.zip(&proof.partials)
|
||||
.all(|(p, p_proof)| p.verify(p_proof))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{
|
||||
input::InputWitness, note::Note, nullifier::NullifierSecret, output::OutputWitness,
|
||||
test_util::seed_rng,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_bundle_balance() {
|
||||
let mut rng = seed_rng(0);
|
||||
|
||||
let nmo_10_in = InputWitness::random(Note::new(10, "NMO"), &mut rng);
|
||||
let eth_23_in = InputWitness::random(Note::new(23, "ETH"), &mut rng);
|
||||
let crv_4840_out = OutputWitness::random(
|
||||
Note::new(4840, "CRV"),
|
||||
NullifierSecret::random(&mut rng).commit(), // transferring to a random owner
|
||||
&mut rng,
|
||||
);
|
||||
|
||||
let mut bundle_witness = BundleWitness {
|
||||
partials: vec![PartialTxWitness {
|
||||
inputs: vec![nmo_10_in.clone(), eth_23_in.clone()],
|
||||
outputs: vec![crv_4840_out.clone()],
|
||||
}],
|
||||
};
|
||||
|
||||
let bundle = Bundle::from_witness(bundle_witness.clone());
|
||||
|
||||
assert_eq!(
|
||||
bundle.balance(),
|
||||
crate::balance::balance(4840, "CRV", crv_4840_out.balance_blinding)
|
||||
- (crate::balance::balance(10, "NMO", nmo_10_in.balance_blinding)
|
||||
+ crate::balance::balance(23, "ETH", eth_23_in.balance_blinding))
|
||||
);
|
||||
|
||||
let crv_4840_in = InputWitness::random(Note::new(4840, "CRV"), &mut rng);
|
||||
let nmo_10_out = OutputWitness::random(
|
||||
Note::new(10, "NMO"),
|
||||
NullifierSecret::random(&mut rng).commit(), // transferring to a random owner
|
||||
&mut rng,
|
||||
);
|
||||
let eth_23_out = OutputWitness::random(
|
||||
Note::new(23, "ETH"),
|
||||
NullifierSecret::random(&mut rng).commit(), // transferring to a random owner
|
||||
&mut rng,
|
||||
);
|
||||
|
||||
bundle_witness.partials.push(PartialTxWitness {
|
||||
inputs: vec![crv_4840_in.clone()],
|
||||
outputs: vec![nmo_10_out.clone(), eth_23_out.clone()],
|
||||
});
|
||||
|
||||
let bundle = Bundle::from_witness(bundle_witness);
|
||||
|
||||
let blinding = -nmo_10_in.balance_blinding - eth_23_in.balance_blinding
|
||||
+ crv_4840_out.balance_blinding
|
||||
- crv_4840_in.balance_blinding
|
||||
+ nmo_10_out.balance_blinding
|
||||
+ eth_23_out.balance_blinding;
|
||||
|
||||
assert_eq!(bundle.balance(), crate::balance::balance(0, "", blinding));
|
||||
|
||||
assert!(bundle.is_balanced(blinding));
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
pub mod balance;
|
||||
pub mod bundle;
|
||||
pub mod crypto;
|
||||
pub mod error;
|
||||
pub mod input;
|
||||
|
|
|
@ -4,7 +4,7 @@ use jubjub::{ExtendedPoint, Scalar};
|
|||
|
||||
use crate::nullifier::{NullifierCommitment, NullifierNonce};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct NoteCommitment([u8; 32]);
|
||||
|
||||
impl NoteCommitment {
|
||||
|
|
|
@ -26,7 +26,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)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Nullifier([u8; 32]);
|
||||
|
||||
impl NullifierSecret {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::collections::BTreeSet;
|
||||
|
||||
use blake2::{Blake2s256, Digest};
|
||||
use jubjub::ExtendedPoint;
|
||||
use rand_core::RngCore;
|
||||
|
@ -21,20 +23,20 @@ impl PtxCommitment {
|
|||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PartialTx {
|
||||
inputs: Vec<Input>,
|
||||
outputs: Vec<Output>,
|
||||
pub inputs: Vec<Input>,
|
||||
pub outputs: Vec<Output>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PartialTxWitness {
|
||||
inputs: Vec<InputWitness>,
|
||||
outputs: Vec<OutputWitness>,
|
||||
pub inputs: Vec<InputWitness>,
|
||||
pub outputs: Vec<OutputWitness>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PartialTxProof {
|
||||
inputs: Vec<InputProof>,
|
||||
outputs: Vec<OutputProof>,
|
||||
pub inputs: Vec<InputProof>,
|
||||
pub outputs: Vec<OutputProof>,
|
||||
}
|
||||
|
||||
impl PartialTx {
|
||||
|
@ -65,6 +67,14 @@ impl PartialTx {
|
|||
if &Self::from_witness(w.clone()) != self {
|
||||
return Err(Error::ProofFailed);
|
||||
}
|
||||
let input_note_comms = BTreeSet::from_iter(self.inputs.iter().map(|i| i.note_comm));
|
||||
let output_note_comms = BTreeSet::from_iter(self.outputs.iter().map(|o| o.note_comm));
|
||||
|
||||
if input_note_comms.len() != self.inputs.len()
|
||||
|| output_note_comms.len() != self.outputs.len()
|
||||
{
|
||||
return Err(Error::ProofFailed);
|
||||
}
|
||||
|
||||
let ptx_comm = self.commitment();
|
||||
|
||||
|
|
Loading…
Reference in New Issue