Merge pull request #4 from logos-co/drusu/cl/fix-balance-commitment
CL: unlink input and output balance commitments
This commit is contained in:
commit
57433f2201
|
@ -7,10 +7,6 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = {version="1.0", features = ["derive"]}
|
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"
|
group = "0.13.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rand_core = "0.6.0"
|
rand_core = "0.6.0"
|
||||||
|
|
|
@ -2,6 +2,9 @@ use curve25519_dalek::{ristretto::RistrettoPoint, traits::VartimeMultiscalarMul,
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use rand_core::CryptoRngCore;
|
use rand_core::CryptoRngCore;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::NoteWitness;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
// Precompute of ``
|
// Precompute of ``
|
||||||
static ref PEDERSON_COMMITMENT_BLINDING_POINT: RistrettoPoint = crate::crypto::hash_to_curve(b"NOMOS_CL_PEDERSON_COMMITMENT_BLINDING");
|
static ref PEDERSON_COMMITMENT_BLINDING_POINT: RistrettoPoint = crate::crypto::hash_to_curve(b"NOMOS_CL_PEDERSON_COMMITMENT_BLINDING");
|
||||||
|
@ -11,40 +14,38 @@ lazy_static! {
|
||||||
pub struct Balance(pub RistrettoPoint);
|
pub struct Balance(pub RistrettoPoint);
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
|
||||||
pub struct BalanceWitness {
|
pub struct BalanceWitness(pub Scalar);
|
||||||
pub value: u64,
|
|
||||||
pub unit: RistrettoPoint,
|
|
||||||
pub blinding: Scalar,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Balance {
|
impl Balance {
|
||||||
|
/// A commitment to zero, blinded by the provided balance witness
|
||||||
|
pub fn zero(blinding: BalanceWitness) -> Self {
|
||||||
|
// Since, balance commitments are `value * UnitPoint + blinding * H`, when value=0, the commmitment is unitless.
|
||||||
|
// So we use the generator point as a stand in for the unit point.
|
||||||
|
//
|
||||||
|
// TAI: we can optimize this further from `0*G + r*H` to just `r*H` to save a point scalar mult + point addition.
|
||||||
|
let unit = curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
|
||||||
|
Self(balance(0, unit, blinding.0))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_bytes(&self) -> [u8; 32] {
|
pub fn to_bytes(&self) -> [u8; 32] {
|
||||||
self.0.compress().to_bytes().into()
|
self.0.compress().to_bytes()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BalanceWitness {
|
impl BalanceWitness {
|
||||||
pub fn new(value: u64, unit: impl Into<String>, blinding: Scalar) -> Self {
|
pub fn new(blinding: Scalar) -> Self {
|
||||||
Self {
|
Self(blinding)
|
||||||
value,
|
|
||||||
unit: unit_point(&unit.into()),
|
|
||||||
blinding,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn random(value: u64, unit: impl Into<String>, mut rng: impl CryptoRngCore) -> Self {
|
pub fn random(mut rng: impl CryptoRngCore) -> Self {
|
||||||
Self::new(value, unit, Scalar::random(&mut rng))
|
Self::new(Scalar::random(&mut rng))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn commit(&self) -> Balance {
|
pub fn commit(&self, note: &NoteWitness) -> Balance {
|
||||||
Balance(balance(self.value, self.unit, self.blinding))
|
Balance(balance(note.value, note.unit, self.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unit_point(unit: &str) -> RistrettoPoint {
|
|
||||||
crate::crypto::hash_to_curve(unit.as_bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn balance(value: u64, unit: RistrettoPoint, blinding: Scalar) -> RistrettoPoint {
|
pub fn balance(value: u64, unit: RistrettoPoint, blinding: Scalar) -> RistrettoPoint {
|
||||||
let value_scalar = Scalar::from(value);
|
let value_scalar = Scalar::from(value);
|
||||||
// can vartime leak the number of cycles through the stark proof?
|
// can vartime leak the number of cycles through the stark proof?
|
||||||
|
@ -54,97 +55,6 @@ pub fn balance(value: u64, unit: RistrettoPoint, blinding: Scalar) -> RistrettoP
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
|
||||||
// use serde::de::{self, Visitor};
|
|
||||||
// use serde::{Deserializer, Serializer};
|
|
||||||
// use std::fmt;
|
|
||||||
|
|
||||||
// // Serialize a SubgroupPoint by converting it to bytes.
|
|
||||||
// pub fn serialize<S>(point: &SubgroupPoint, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
// 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<SubgroupPoint, D::Error>
|
|
||||||
// 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<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(SubgroupPoint::from_bytes(&bytes).unwrap())
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// deserializer.deserialize_bytes(BytesVisitor)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
|
@ -165,52 +75,67 @@ mod test {
|
||||||
fn test_balance_zero_unitless() {
|
fn test_balance_zero_unitless() {
|
||||||
// Zero is the same across all units
|
// Zero is the same across all units
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let r = Scalar::random(&mut rng);
|
let b = BalanceWitness::random(&mut rng);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
BalanceWitness::new(0, "NMO", r).commit(),
|
b.commit(&NoteWitness::basic(0, "NMO")),
|
||||||
BalanceWitness::new(0, "ETH", r).commit(),
|
b.commit(&NoteWitness::basic(0, "ETH")),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_balance_blinding() {
|
fn test_balance_blinding() {
|
||||||
// balances are blinded
|
// balances are blinded
|
||||||
let r1 = Scalar::from(12u32);
|
let r_a = Scalar::from(12u32);
|
||||||
let r2 = Scalar::from(8u32);
|
let r_b = Scalar::from(8u32);
|
||||||
let a_w = BalanceWitness::new(10, "NMO", r1);
|
let bal_a = BalanceWitness::new(r_a);
|
||||||
let b_w = BalanceWitness::new(10, "NMO", r2);
|
let bal_b = BalanceWitness::new(r_b);
|
||||||
let a = a_w.commit();
|
|
||||||
let b = b_w.commit();
|
let note = NoteWitness::basic(10, "NMO");
|
||||||
|
|
||||||
|
let a = bal_a.commit(¬e);
|
||||||
|
let b = bal_b.commit(¬e);
|
||||||
|
|
||||||
assert_ne!(a, b);
|
assert_ne!(a, b);
|
||||||
assert_eq!(a.0 - b.0, BalanceWitness::new(0, "NMO", r1 - r2).commit().0);
|
|
||||||
|
let diff_note = NoteWitness::basic(0, "NMO");
|
||||||
|
assert_eq!(
|
||||||
|
a.0 - b.0,
|
||||||
|
BalanceWitness::new(r_a - r_b).commit(&diff_note).0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_balance_units() {
|
fn test_balance_units() {
|
||||||
// Unit's differentiate between values.
|
// Unit's differentiate between values.
|
||||||
let r = Scalar::from(1337u32);
|
let b = BalanceWitness::new(Scalar::from(1337u32));
|
||||||
let nmo = BalanceWitness::new(10, "NMO", r);
|
|
||||||
let eth = BalanceWitness::new(10, "ETH", r);
|
let nmo = NoteWitness::basic(10, "NMO");
|
||||||
assert_ne!(nmo.commit(), eth.commit());
|
let eth = NoteWitness::basic(10, "ETH");
|
||||||
|
assert_ne!(b.commit(&nmo), b.commit(ð));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_balance_homomorphism() {
|
fn test_balance_homomorphism() {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let r1 = Scalar::random(&mut rng);
|
let b1 = BalanceWitness::random(&mut rng);
|
||||||
let r2 = Scalar::random(&mut rng);
|
let b2 = BalanceWitness::random(&mut rng);
|
||||||
let ten = BalanceWitness::new(10, "NMO", 0u32.into());
|
let b_zero = BalanceWitness::new(Scalar::ZERO);
|
||||||
let eight = BalanceWitness::new(8, "NMO", 0u32.into());
|
|
||||||
let two = BalanceWitness::new(2, "NMO", 0u32.into());
|
let ten = NoteWitness::basic(10, "NMO");
|
||||||
|
let eight = NoteWitness::basic(8, "NMO");
|
||||||
|
let two = NoteWitness::basic(2, "NMO");
|
||||||
|
let zero = NoteWitness::basic(0, "NMO");
|
||||||
|
|
||||||
// Values of same unit are homomorphic
|
// Values of same unit are homomorphic
|
||||||
assert_eq!(ten.commit().0 - eight.commit().0, two.commit().0);
|
assert_eq!(
|
||||||
|
(b1.commit(&ten).0 - b1.commit(&eight).0),
|
||||||
|
b_zero.commit(&two).0
|
||||||
|
);
|
||||||
|
|
||||||
// Blinding factors are also homomorphic.
|
// Blinding factors are also homomorphic.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
BalanceWitness::new(10, "NMO", r1).commit().0
|
b1.commit(&ten).0 - b2.commit(&ten).0,
|
||||||
- BalanceWitness::new(10, "NMO", r2).commit().0,
|
BalanceWitness::new(b1.0 - b2.0).commit(&zero).0
|
||||||
BalanceWitness::new(0, "NMO", r1 - r2).commit().0
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, ristretto::RistrettoPoint, Scalar};
|
use crate::{partial_tx::PartialTx, Balance, BalanceWitness};
|
||||||
|
|
||||||
use crate::partial_tx::PartialTx;
|
|
||||||
|
|
||||||
/// The transaction bundle is a collection of partial transactions.
|
/// The transaction bundle is a collection of partial transactions.
|
||||||
/// The goal in bundling transactions is to produce a set of partial transactions
|
/// The goal in bundling transactions is to produce a set of partial transactions
|
||||||
|
@ -13,27 +11,26 @@ pub struct Bundle {
|
||||||
pub partials: Vec<PartialTx>,
|
pub partials: Vec<PartialTx>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct BundleWitness {
|
pub struct BundleWitness {
|
||||||
pub balance_blinding: Scalar,
|
pub balance_blinding: BalanceWitness,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bundle {
|
impl Bundle {
|
||||||
pub fn balance(&self) -> RistrettoPoint {
|
pub fn balance(&self) -> Balance {
|
||||||
self.partials.iter().map(|ptx| ptx.balance()).sum()
|
Balance(self.partials.iter().map(|ptx| ptx.balance().0).sum())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_balanced(&self, balance_blinding_witness: Scalar) -> bool {
|
pub fn is_balanced(&self, witness: BalanceWitness) -> bool {
|
||||||
self.balance()
|
self.balance() == Balance::zero(witness)
|
||||||
== crate::balance::balance(0, RISTRETTO_BASEPOINT_POINT, balance_blinding_witness)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{
|
use crate::{
|
||||||
crypto::hash_to_curve, input::InputWitness, note::NoteWitness, nullifier::NullifierSecret,
|
input::InputWitness, note::NoteWitness, nullifier::NullifierSecret, output::OutputWitness,
|
||||||
output::OutputWitness, partial_tx::PartialTxWitness,
|
partial_tx::PartialTxWitness,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -42,86 +39,75 @@ mod test {
|
||||||
fn test_bundle_balance() {
|
fn test_bundle_balance() {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
let nmo_10_in =
|
let nf_a = NullifierSecret::random(&mut rng);
|
||||||
InputWitness::random(NoteWitness::new(10, "NMO", [0u8; 32], &mut rng), &mut rng);
|
let nf_b = NullifierSecret::random(&mut rng);
|
||||||
let eth_23_in =
|
let nf_c = NullifierSecret::random(&mut rng);
|
||||||
InputWitness::random(NoteWitness::new(23, "ETH", [0u8; 32], &mut rng), &mut rng);
|
|
||||||
let crv_4840_out = OutputWitness::random(
|
let nmo_10_utxo =
|
||||||
NoteWitness::new(4840, "CRV", [0u8; 32], &mut rng),
|
OutputWitness::random(NoteWitness::basic(10, "NMO"), nf_a.commit(), &mut rng);
|
||||||
NullifierSecret::random(&mut rng).commit(), // transferring to a random owner
|
let nmo_10_in = InputWitness::random(nmo_10_utxo, nf_a, &mut rng);
|
||||||
&mut rng,
|
|
||||||
);
|
let eth_23_utxo =
|
||||||
|
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 crv_4840_out =
|
||||||
|
OutputWitness::random(NoteWitness::basic(4840, "CRV"), nf_c.commit(), &mut rng);
|
||||||
|
|
||||||
let ptx_unbalanced = PartialTxWitness {
|
let ptx_unbalanced = PartialTxWitness {
|
||||||
inputs: vec![nmo_10_in.clone(), eth_23_in.clone()],
|
inputs: vec![nmo_10_in, eth_23_in],
|
||||||
outputs: vec![crv_4840_out.clone()],
|
outputs: vec![crv_4840_out],
|
||||||
};
|
};
|
||||||
|
|
||||||
let bundle_witness = BundleWitness {
|
let bundle_witness = BundleWitness {
|
||||||
balance_blinding: crv_4840_out.note.balance.blinding
|
balance_blinding: BalanceWitness::new(
|
||||||
- nmo_10_in.note.balance.blinding
|
crv_4840_out.balance_blinding.0
|
||||||
- eth_23_in.note.balance.blinding,
|
- nmo_10_in.balance_blinding.0
|
||||||
|
- eth_23_in.balance_blinding.0,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut bundle = Bundle {
|
let mut bundle = Bundle {
|
||||||
partials: vec![PartialTx::from_witness(ptx_unbalanced)],
|
partials: vec![ptx_unbalanced.commit()],
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(!bundle.is_balanced(bundle_witness.balance_blinding));
|
assert!(!bundle.is_balanced(bundle_witness.balance_blinding));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bundle.balance(),
|
bundle.balance().0,
|
||||||
crate::balance::balance(
|
crv_4840_out.commit().balance.0
|
||||||
4840,
|
- (nmo_10_in.commit().balance.0 + eth_23_in.commit().balance.0)
|
||||||
hash_to_curve(b"CRV"),
|
|
||||||
crv_4840_out.note.balance.blinding
|
|
||||||
) - (crate::balance::balance(
|
|
||||||
10,
|
|
||||||
hash_to_curve(b"NMO"),
|
|
||||||
nmo_10_in.note.balance.blinding
|
|
||||||
) + crate::balance::balance(
|
|
||||||
23,
|
|
||||||
hash_to_curve(b"ETH"),
|
|
||||||
eth_23_in.note.balance.blinding
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let crv_4840_in =
|
let crv_4840_in = InputWitness::random(crv_4840_out, nf_c, &mut rng);
|
||||||
InputWitness::random(NoteWitness::new(4840, "CRV", [0u8; 32], &mut rng), &mut rng);
|
|
||||||
let nmo_10_out = OutputWitness::random(
|
let nmo_10_out = OutputWitness::random(
|
||||||
NoteWitness::new(10, "NMO", [0u8; 32], &mut rng),
|
NoteWitness::basic(10, "NMO"),
|
||||||
NullifierSecret::random(&mut rng).commit(), // transferring to a random owner
|
NullifierSecret::random(&mut rng).commit(), // transferring to a random owner
|
||||||
&mut rng,
|
&mut rng,
|
||||||
);
|
);
|
||||||
let eth_23_out = OutputWitness::random(
|
let eth_23_out = OutputWitness::random(
|
||||||
NoteWitness::new(23, "ETH", [0u8; 32], &mut rng),
|
NoteWitness::basic(23, "ETH"),
|
||||||
NullifierSecret::random(&mut rng).commit(), // transferring to a random owner
|
NullifierSecret::random(&mut rng).commit(), // transferring to a random owner
|
||||||
&mut rng,
|
&mut rng,
|
||||||
);
|
);
|
||||||
|
|
||||||
bundle
|
bundle.partials.push(
|
||||||
.partials
|
PartialTxWitness {
|
||||||
.push(PartialTx::from_witness(PartialTxWitness {
|
inputs: vec![crv_4840_in],
|
||||||
inputs: vec![crv_4840_in.clone()],
|
outputs: vec![nmo_10_out, eth_23_out],
|
||||||
outputs: vec![nmo_10_out.clone(), eth_23_out.clone()],
|
}
|
||||||
}));
|
.commit(),
|
||||||
|
);
|
||||||
|
|
||||||
let witness = BundleWitness {
|
let witness = BundleWitness {
|
||||||
balance_blinding: -nmo_10_in.note.balance.blinding - eth_23_in.note.balance.blinding
|
balance_blinding: BalanceWitness::new(
|
||||||
+ crv_4840_out.note.balance.blinding
|
-nmo_10_in.balance_blinding.0 - eth_23_in.balance_blinding.0
|
||||||
- crv_4840_in.note.balance.blinding
|
+ crv_4840_out.balance_blinding.0
|
||||||
+ nmo_10_out.note.balance.blinding
|
- crv_4840_in.balance_blinding.0
|
||||||
+ eth_23_out.note.balance.blinding,
|
+ nmo_10_out.balance_blinding.0
|
||||||
|
+ eth_23_out.balance_blinding.0,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
bundle.balance(),
|
|
||||||
crate::balance::balance(
|
|
||||||
0,
|
|
||||||
curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT,
|
|
||||||
witness.balance_blinding
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(bundle.is_balanced(witness.balance_blinding));
|
assert!(bundle.is_balanced(witness.balance_blinding));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,9 @@ use crate::{
|
||||||
balance::Balance,
|
balance::Balance,
|
||||||
note::{DeathCommitment, NoteWitness},
|
note::{DeathCommitment, NoteWitness},
|
||||||
nullifier::{Nullifier, NullifierNonce, NullifierSecret},
|
nullifier::{Nullifier, NullifierNonce, NullifierSecret},
|
||||||
|
BalanceWitness,
|
||||||
};
|
};
|
||||||
use rand_core::RngCore;
|
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)]
|
||||||
|
@ -20,33 +21,40 @@ pub struct Input {
|
||||||
#[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(note: NoteWitness, mut rng: impl RngCore) -> Self {
|
pub fn random(
|
||||||
|
output: crate::OutputWitness,
|
||||||
|
nf_sk: NullifierSecret,
|
||||||
|
mut rng: impl CryptoRngCore,
|
||||||
|
) -> Self {
|
||||||
|
assert_eq!(nf_sk.commit(), output.nf_pk);
|
||||||
Self {
|
Self {
|
||||||
note,
|
note: output.note,
|
||||||
nf_sk: NullifierSecret::random(&mut rng),
|
balance_blinding: BalanceWitness::random(&mut rng),
|
||||||
nonce: NullifierNonce::random(&mut rng),
|
nf_sk,
|
||||||
|
nonce: output.nonce,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn nullifier(&self) -> Nullifier {
|
||||||
|
Nullifier::new(self.nf_sk, self.nonce)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn commit(&self) -> Input {
|
pub fn commit(&self) -> Input {
|
||||||
Input {
|
Input {
|
||||||
nullifier: Nullifier::new(self.nf_sk, self.nonce),
|
nullifier: self.nullifier(),
|
||||||
balance: self.note.balance(),
|
balance: self.balance_blinding.commit(&self.note),
|
||||||
death_cm: self.note.death_commitment(),
|
death_cm: self.note.death_commitment(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_output_witness(&self) -> crate::OutputWitness {
|
pub fn note_commitment(&self) -> crate::NoteCommitment {
|
||||||
crate::OutputWitness {
|
self.note.commit(self.nf_sk.commit(), self.nonce)
|
||||||
note: self.note.clone(),
|
|
||||||
nf_pk: self.nf_sk.commit(),
|
|
||||||
nonce: self.nonce,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
use rand_core::CryptoRngCore;
|
use curve25519_dalek::RistrettoPoint;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
use crate::{
|
use crate::nullifier::{NullifierCommitment, NullifierNonce};
|
||||||
balance::{Balance, BalanceWitness},
|
|
||||||
nullifier::{NullifierCommitment, NullifierNonce},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
pub struct DeathCommitment(pub [u8; 32]);
|
pub struct DeathCommitment(pub [u8; 32]);
|
||||||
|
@ -19,6 +16,10 @@ pub fn death_commitment(death_constraint: &[u8]) -> DeathCommitment {
|
||||||
DeathCommitment(death_cm)
|
DeathCommitment(death_cm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn unit_point(unit: &str) -> RistrettoPoint {
|
||||||
|
crate::crypto::hash_to_curve(unit.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
pub struct NoteCommitment([u8; 32]);
|
pub struct NoteCommitment([u8; 32]);
|
||||||
|
|
||||||
|
@ -32,7 +33,8 @@ impl NoteCommitment {
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
|
||||||
pub struct NoteWitness {
|
pub struct NoteWitness {
|
||||||
pub balance: BalanceWitness,
|
pub value: u64,
|
||||||
|
pub unit: RistrettoPoint,
|
||||||
pub death_constraint: [u8; 32], // death constraint verification key
|
pub death_constraint: [u8; 32], // death constraint verification key
|
||||||
pub state: [u8; 32],
|
pub state: [u8; 32],
|
||||||
}
|
}
|
||||||
|
@ -41,23 +43,32 @@ impl NoteWitness {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
value: u64,
|
value: u64,
|
||||||
unit: impl Into<String>,
|
unit: impl Into<String>,
|
||||||
|
death_constraint: [u8; 32],
|
||||||
state: [u8; 32],
|
state: [u8; 32],
|
||||||
rng: impl CryptoRngCore,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
balance: BalanceWitness::random(value, unit, rng),
|
value,
|
||||||
death_constraint: [0u8; 32],
|
unit: unit_point(&unit.into()),
|
||||||
|
death_constraint,
|
||||||
state,
|
state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn basic(value: u64, unit: impl Into<String>) -> Self {
|
||||||
|
Self::new(value, unit, [0u8; 32], [0u8; 32])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stateless(value: u64, unit: impl Into<String>, death_constraint: [u8; 32]) -> Self {
|
||||||
|
Self::new(value, unit, death_constraint, [0u8; 32])
|
||||||
|
}
|
||||||
|
|
||||||
pub fn commit(&self, nf_pk: NullifierCommitment, nonce: NullifierNonce) -> NoteCommitment {
|
pub fn commit(&self, nf_pk: NullifierCommitment, nonce: NullifierNonce) -> NoteCommitment {
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(b"NOMOS_CL_NOTE_COMMIT");
|
hasher.update(b"NOMOS_CL_NOTE_COMMIT");
|
||||||
|
|
||||||
// COMMIT TO BALANCE
|
// COMMIT TO BALANCE
|
||||||
hasher.update(self.balance.value.to_le_bytes());
|
hasher.update(self.value.to_le_bytes());
|
||||||
hasher.update(self.balance.unit.compress().to_bytes());
|
hasher.update(self.unit.compress().to_bytes());
|
||||||
// Important! we don't commit to the balance blinding factor as that may make the notes linkable.
|
// Important! we don't commit to the balance blinding factor as that may make the notes linkable.
|
||||||
|
|
||||||
// COMMIT TO STATE
|
// COMMIT TO STATE
|
||||||
|
@ -74,10 +85,6 @@ impl NoteWitness {
|
||||||
NoteCommitment(commit_bytes)
|
NoteCommitment(commit_bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn balance(&self) -> Balance {
|
|
||||||
self.balance.commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn death_commitment(&self) -> DeathCommitment {
|
pub fn death_commitment(&self) -> DeathCommitment {
|
||||||
death_commitment(&self.death_constraint)
|
death_commitment(&self.death_constraint)
|
||||||
}
|
}
|
||||||
|
@ -90,18 +97,57 @@ mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_note_commitments_dont_commit_to_balance_blinding() {
|
fn test_note_commit_permutations() {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_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 nf_pk = NullifierSecret::random(&mut rng).commit();
|
||||||
let nonce = NullifierNonce::random(&mut rng);
|
let nf_nonce = NullifierNonce::random(&mut rng);
|
||||||
|
|
||||||
// Balance blinding factors are different.
|
let reference_note = NoteWitness::basic(32, "NMO");
|
||||||
assert_ne!(n1.balance.blinding, n2.balance.blinding);
|
|
||||||
|
|
||||||
// But their commitments are the same.
|
// different notes under same nullifier produce different commitments
|
||||||
assert_eq!(n1.commit(nf_pk, nonce), n2.commit(nf_pk, nonce));
|
let mutation_tests = [
|
||||||
|
NoteWitness {
|
||||||
|
value: 12,
|
||||||
|
..reference_note
|
||||||
|
},
|
||||||
|
NoteWitness {
|
||||||
|
unit: unit_point("ETH"),
|
||||||
|
..reference_note
|
||||||
|
},
|
||||||
|
NoteWitness {
|
||||||
|
death_constraint: [1u8; 32],
|
||||||
|
..reference_note
|
||||||
|
},
|
||||||
|
NoteWitness {
|
||||||
|
state: [1u8; 32],
|
||||||
|
..reference_note
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for n in mutation_tests {
|
||||||
|
assert_ne!(
|
||||||
|
n.commit(nf_pk, nf_nonce),
|
||||||
|
reference_note.commit(nf_pk, nf_nonce)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// commitment to same note with different nullifiers produce different commitments
|
||||||
|
|
||||||
|
let other_nf_pk = NullifierSecret::random(&mut rng).commit();
|
||||||
|
let other_nf_nonce = NullifierNonce::random(&mut rng);
|
||||||
|
|
||||||
|
assert_ne!(
|
||||||
|
reference_note.commit(nf_pk, nf_nonce),
|
||||||
|
reference_note.commit(other_nf_pk, nf_nonce)
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
reference_note.commit(nf_pk, nf_nonce),
|
||||||
|
reference_note.commit(nf_pk, other_nf_nonce)
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
reference_note.commit(nf_pk, nf_nonce),
|
||||||
|
reference_note.commit(other_nf_pk, other_nf_nonce)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
// notes to allow users to hold fewer secrets. A note
|
// notes to allow users to hold fewer secrets. A note
|
||||||
// nonce is used to disambiguate when the same nullifier
|
// nonce is used to disambiguate when the same nullifier
|
||||||
// secret is used for multiple notes.
|
// secret is used for multiple notes.
|
||||||
use blake2::{Blake2s256, Digest};
|
|
||||||
use rand_core::RngCore;
|
use rand_core::RngCore;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
// TODO: create a nullifier witness and use it throughout.
|
// TODO: create a nullifier witness and use it throughout.
|
||||||
// struct NullifierWitness {
|
// struct NullifierWitness {
|
||||||
|
@ -44,7 +44,7 @@ impl NullifierSecret {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn commit(&self) -> NullifierCommitment {
|
pub fn commit(&self) -> NullifierCommitment {
|
||||||
let mut hasher = Blake2s256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(b"NOMOS_CL_NULL_COMMIT");
|
hasher.update(b"NOMOS_CL_NULL_COMMIT");
|
||||||
hasher.update(self.0);
|
hasher.update(self.0);
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ impl NullifierNonce {
|
||||||
|
|
||||||
impl Nullifier {
|
impl Nullifier {
|
||||||
pub fn new(sk: NullifierSecret, nonce: NullifierNonce) -> Self {
|
pub fn new(sk: NullifierSecret, nonce: NullifierNonce) -> Self {
|
||||||
let mut hasher = Blake2s256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(b"NOMOS_CL_NULLIFIER");
|
hasher.update(b"NOMOS_CL_NULLIFIER");
|
||||||
hasher.update(sk.0);
|
hasher.update(sk.0);
|
||||||
hasher.update(nonce.0);
|
hasher.update(nonce.0);
|
||||||
|
@ -103,6 +103,7 @@ impl Nullifier {
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[ignore = "nullifier test vectors not stable yet"]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_nullifier_commitment_vectors() {
|
fn test_nullifier_commitment_vectors() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use rand_core::RngCore;
|
use rand_core::CryptoRngCore;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -6,6 +6,7 @@ use crate::{
|
||||||
error::Error,
|
error::Error,
|
||||||
note::{NoteCommitment, NoteWitness},
|
note::{NoteCommitment, NoteWitness},
|
||||||
nullifier::{NullifierCommitment, NullifierNonce},
|
nullifier::{NullifierCommitment, NullifierNonce},
|
||||||
|
BalanceWitness,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
@ -14,17 +15,23 @@ pub struct Output {
|
||||||
pub balance: Balance,
|
pub balance: Balance,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutputWitness {
|
impl OutputWitness {
|
||||||
pub fn random(note: NoteWitness, owner: NullifierCommitment, mut rng: impl RngCore) -> Self {
|
pub fn random(
|
||||||
|
note: NoteWitness,
|
||||||
|
owner: NullifierCommitment,
|
||||||
|
mut rng: impl CryptoRngCore,
|
||||||
|
) -> 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),
|
||||||
}
|
}
|
||||||
|
@ -34,10 +41,14 @@ 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.note.balance(),
|
balance: self.commit_balance(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +60,7 @@ pub struct OutputProof(OutputWitness);
|
||||||
impl Output {
|
impl Output {
|
||||||
pub fn prove(&self, w: &OutputWitness) -> Result<OutputProof, Error> {
|
pub fn prove(&self, w: &OutputWitness) -> Result<OutputProof, Error> {
|
||||||
if &w.commit() == self {
|
if &w.commit() == self {
|
||||||
Ok(OutputProof(w.clone()))
|
Ok(OutputProof(*w))
|
||||||
} else {
|
} else {
|
||||||
Err(Error::ProofFailed)
|
Err(Error::ProofFailed)
|
||||||
}
|
}
|
||||||
|
@ -61,8 +72,7 @@ impl Output {
|
||||||
// - balance == v * hash_to_curve(Unit) + blinding * H
|
// - balance == v * hash_to_curve(Unit) + blinding * H
|
||||||
let witness = &proof.0;
|
let witness = &proof.0;
|
||||||
|
|
||||||
self.note_comm == witness.note.commit(witness.nf_pk, witness.nonce)
|
self.note_comm == witness.commit_note() && self.balance == witness.commit_balance()
|
||||||
&& self.balance == witness.note.balance()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_bytes(&self) -> [u8; 64] {
|
pub fn to_bytes(&self) -> [u8; 64] {
|
||||||
|
@ -82,11 +92,12 @@ mod test {
|
||||||
fn test_output_proof() {
|
fn test_output_proof() {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
let note = NoteWitness::new(10, "NMO", [0u8; 32], &mut rng);
|
let witness = OutputWitness {
|
||||||
let nf_pk = NullifierSecret::random(&mut rng).commit();
|
note: NoteWitness::basic(10, "NMO"),
|
||||||
let nonce = NullifierNonce::random(&mut rng);
|
balance_blinding: BalanceWitness::random(&mut rng),
|
||||||
|
nf_pk: NullifierSecret::random(&mut rng).commit(),
|
||||||
let witness = OutputWitness { note, nf_pk, nonce };
|
nonce: NullifierNonce::random(&mut rng),
|
||||||
|
};
|
||||||
|
|
||||||
let output = witness.commit();
|
let output = witness.commit();
|
||||||
let proof = output.prove(&witness).unwrap();
|
let proof = output.prove(&witness).unwrap();
|
||||||
|
@ -95,20 +106,24 @@ mod test {
|
||||||
|
|
||||||
let wrong_witnesses = [
|
let wrong_witnesses = [
|
||||||
OutputWitness {
|
OutputWitness {
|
||||||
note: NoteWitness::new(11, "NMO", [0u8; 32], &mut rng),
|
note: NoteWitness::basic(11, "NMO"),
|
||||||
..witness.clone()
|
..witness
|
||||||
},
|
},
|
||||||
OutputWitness {
|
OutputWitness {
|
||||||
note: NoteWitness::new(10, "ETH", [0u8; 32], &mut rng),
|
note: NoteWitness::basic(10, "ETH"),
|
||||||
..witness.clone()
|
..witness
|
||||||
|
},
|
||||||
|
OutputWitness {
|
||||||
|
balance_blinding: BalanceWitness::random(&mut rng),
|
||||||
|
..witness
|
||||||
},
|
},
|
||||||
OutputWitness {
|
OutputWitness {
|
||||||
nf_pk: NullifierSecret::random(&mut rng).commit(),
|
nf_pk: NullifierSecret::random(&mut rng).commit(),
|
||||||
..witness.clone()
|
..witness
|
||||||
},
|
},
|
||||||
OutputWitness {
|
OutputWitness {
|
||||||
nonce: NullifierNonce::random(&mut rng),
|
nonce: NullifierNonce::random(&mut rng),
|
||||||
..witness.clone()
|
..witness
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use rand_core::RngCore;
|
|
||||||
// use risc0_groth16::ProofJson;
|
|
||||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||||
|
use curve25519_dalek::Scalar;
|
||||||
|
use rand_core::RngCore;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::balance::{Balance, BalanceWitness};
|
||||||
use crate::input::{Input, InputWitness};
|
use crate::input::{Input, InputWitness};
|
||||||
use crate::merkle;
|
use crate::merkle;
|
||||||
use crate::output::{Output, OutputWitness};
|
use crate::output::{Output, OutputWitness};
|
||||||
|
@ -45,14 +46,23 @@ pub struct PartialTxWitness {
|
||||||
pub outputs: Vec<OutputWitness>,
|
pub outputs: Vec<OutputWitness>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialTx {
|
impl PartialTxWitness {
|
||||||
pub fn from_witness(w: PartialTxWitness) -> Self {
|
pub fn commit(&self) -> PartialTx {
|
||||||
Self {
|
PartialTx {
|
||||||
inputs: Vec::from_iter(w.inputs.iter().map(InputWitness::commit)),
|
inputs: Vec::from_iter(self.inputs.iter().map(InputWitness::commit)),
|
||||||
outputs: Vec::from_iter(w.outputs.iter().map(OutputWitness::commit)),
|
outputs: Vec::from_iter(self.outputs.iter().map(OutputWitness::commit)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialTx {
|
||||||
pub fn input_root(&self) -> [u8; 32] {
|
pub fn input_root(&self) -> [u8; 32] {
|
||||||
let input_bytes =
|
let input_bytes =
|
||||||
Vec::from_iter(self.inputs.iter().map(Input::to_bytes).map(Vec::from_iter));
|
Vec::from_iter(self.inputs.iter().map(Input::to_bytes).map(Vec::from_iter));
|
||||||
|
@ -96,18 +106,18 @@ impl PartialTx {
|
||||||
PtxRoot(root)
|
PtxRoot(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn balance(&self) -> RistrettoPoint {
|
pub fn balance(&self) -> Balance {
|
||||||
let in_sum: RistrettoPoint = self.inputs.iter().map(|i| i.balance.0).sum();
|
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();
|
let out_sum: RistrettoPoint = self.outputs.iter().map(|o| o.balance.0).sum();
|
||||||
|
|
||||||
out_sum - in_sum
|
Balance(out_sum - in_sum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
use crate::{crypto::hash_to_curve, note::NoteWitness, nullifier::NullifierSecret};
|
use crate::{note::NoteWitness, nullifier::NullifierSecret};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -115,35 +125,31 @@ mod test {
|
||||||
fn test_partial_tx_balance() {
|
fn test_partial_tx_balance() {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
let nmo_10 =
|
let nf_a = NullifierSecret::random(&mut rng);
|
||||||
InputWitness::random(NoteWitness::new(10, "NMO", [0u8; 32], &mut rng), &mut rng);
|
let nf_b = NullifierSecret::random(&mut rng);
|
||||||
let eth_23 =
|
let nf_c = NullifierSecret::random(&mut rng);
|
||||||
InputWitness::random(NoteWitness::new(23, "ETH", [0u8; 32], &mut rng), &mut rng);
|
|
||||||
let crv_4840 = OutputWitness::random(
|
let nmo_10_utxo =
|
||||||
NoteWitness::new(4840, "CRV", [0u8; 32], &mut rng),
|
OutputWitness::random(NoteWitness::basic(10, "NMO"), nf_a.commit(), &mut rng);
|
||||||
NullifierSecret::random(&mut rng).commit(), // transferring to a random owner
|
let nmo_10 = InputWitness::random(nmo_10_utxo, nf_a, &mut rng);
|
||||||
&mut rng,
|
|
||||||
);
|
let eth_23_utxo =
|
||||||
|
OutputWitness::random(NoteWitness::basic(23, "ETH"), nf_b.commit(), &mut rng);
|
||||||
|
let eth_23 = InputWitness::random(eth_23_utxo, nf_b, &mut rng);
|
||||||
|
|
||||||
|
let crv_4840 =
|
||||||
|
OutputWitness::random(NoteWitness::basic(4840, "CRV"), nf_c.commit(), &mut rng);
|
||||||
|
|
||||||
let ptx_witness = PartialTxWitness {
|
let ptx_witness = PartialTxWitness {
|
||||||
inputs: vec![nmo_10.clone(), eth_23.clone()],
|
inputs: vec![nmo_10, eth_23],
|
||||||
outputs: vec![crv_4840.clone()],
|
outputs: vec![crv_4840],
|
||||||
};
|
};
|
||||||
|
|
||||||
let ptx = PartialTx::from_witness(ptx_witness.clone());
|
let ptx = ptx_witness.commit();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ptx.balance(),
|
ptx.balance().0,
|
||||||
crate::balance::balance(4840, hash_to_curve(b"CRV"), crv_4840.note.balance.blinding)
|
crv_4840.commit().balance.0 - (nmo_10.commit().balance.0 + eth_23.commit().balance.0)
|
||||||
- (crate::balance::balance(
|
|
||||||
10,
|
|
||||||
hash_to_curve(b"NMO"),
|
|
||||||
nmo_10.note.balance.blinding
|
|
||||||
) + crate::balance::balance(
|
|
||||||
23,
|
|
||||||
hash_to_curve(b"ETH"),
|
|
||||||
eth_23.note.balance.blinding
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
use rand_core::CryptoRngCore;
|
||||||
|
|
||||||
|
fn receive_utxo(
|
||||||
|
note: cl::NoteWitness,
|
||||||
|
nf_pk: cl::NullifierCommitment,
|
||||||
|
rng: impl CryptoRngCore,
|
||||||
|
) -> cl::OutputWitness {
|
||||||
|
cl::OutputWitness::random(note, nf_pk, rng)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simple_transfer() {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let sender_nf_sk = cl::NullifierSecret::random(&mut rng);
|
||||||
|
let sender_nf_pk = sender_nf_sk.commit();
|
||||||
|
|
||||||
|
let recipient_nf_pk = cl::NullifierSecret::random(&mut rng).commit();
|
||||||
|
|
||||||
|
// Assume the sender has received an unspent output from somewhere
|
||||||
|
let utxo = receive_utxo(cl::NoteWitness::basic(10, "NMO"), sender_nf_pk, &mut rng);
|
||||||
|
|
||||||
|
// and wants to send 8 NMO to some recipient and return 2 NMO to itself.
|
||||||
|
let recipient_output =
|
||||||
|
cl::OutputWitness::random(cl::NoteWitness::basic(8, "NMO"), recipient_nf_pk, &mut rng);
|
||||||
|
let change_output =
|
||||||
|
cl::OutputWitness::random(cl::NoteWitness::basic(2, "NMO"), sender_nf_pk, &mut rng);
|
||||||
|
|
||||||
|
let ptx_witness = cl::PartialTxWitness {
|
||||||
|
inputs: vec![cl::InputWitness::random(utxo, sender_nf_sk, &mut rng)],
|
||||||
|
outputs: vec![recipient_output, change_output],
|
||||||
|
};
|
||||||
|
|
||||||
|
let ptx = ptx_witness.commit();
|
||||||
|
|
||||||
|
let bundle = cl::Bundle {
|
||||||
|
partials: vec![ptx],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(bundle.is_balanced(ptx_witness.balance_blinding()))
|
||||||
|
}
|
|
@ -10,4 +10,5 @@ nomos_cl_risc0_proofs = { path = "../risc0_proofs" }
|
||||||
risc0-zkvm = { version = "1.0", features = ["prove", "metal"] }
|
risc0-zkvm = { version = "1.0", features = ["prove", "metal"] }
|
||||||
risc0-groth16 = { version = "1.0" }
|
risc0-groth16 = { version = "1.0" }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
rand_core = "0.6.0"
|
||||||
thiserror = "1.0.62"
|
thiserror = "1.0.62"
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
use crate::error::Result;
|
||||||
|
|
||||||
|
pub struct ProvedBundle {
|
||||||
|
pub bundle: cl::Bundle,
|
||||||
|
pub risc0_receipt: risc0_zkvm::Receipt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProvedBundle {
|
||||||
|
pub fn prove(bundle: &cl::Bundle, bundle_witness: &cl::BundleWitness) -> Self {
|
||||||
|
// need to show that bundle is balanced.
|
||||||
|
// i.e. the sum of ptx balances is 0
|
||||||
|
|
||||||
|
let env = risc0_zkvm::ExecutorEnv::builder()
|
||||||
|
.write(&bundle_witness)
|
||||||
|
.unwrap()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let prover = risc0_zkvm::default_prover();
|
||||||
|
|
||||||
|
let start_t = std::time::Instant::now();
|
||||||
|
|
||||||
|
let opts = risc0_zkvm::ProverOpts::succinct();
|
||||||
|
let prove_info = prover
|
||||||
|
.prove_with_opts(env, nomos_cl_risc0_proofs::BUNDLE_ELF, &opts)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"STARK 'bundle' prover time: {:.2?}, total_cycles: {}",
|
||||||
|
start_t.elapsed(),
|
||||||
|
prove_info.stats.total_cycles
|
||||||
|
);
|
||||||
|
|
||||||
|
let receipt = prove_info.receipt;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
bundle: bundle.clone(),
|
||||||
|
risc0_receipt: receipt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn public(&self) -> Result<cl::Balance> {
|
||||||
|
Ok(self.risc0_receipt.journal.decode()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(&self) -> bool {
|
||||||
|
let Ok(zero_commitment) = self.public() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.bundle.balance() == zero_commitment
|
||||||
|
&& self
|
||||||
|
.risc0_receipt
|
||||||
|
.verify(nomos_cl_risc0_proofs::BUNDLE_ID)
|
||||||
|
.is_ok()
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,67 +4,79 @@ use crate::error::Result;
|
||||||
|
|
||||||
const MAX_NOTE_COMMS: usize = 2usize.pow(8);
|
const MAX_NOTE_COMMS: usize = 2usize.pow(8);
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
pub struct ProvedInput {
|
||||||
pub struct InputProof {
|
pub input: InputPublic,
|
||||||
receipt: risc0_zkvm::Receipt,
|
pub risc0_receipt: risc0_zkvm::Receipt,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputProof {
|
impl ProvedInput {
|
||||||
pub fn public(&self) -> Result<InputPublic> {
|
pub fn prove(input: &cl::InputWitness, note_commitments: &[cl::NoteCommitment]) -> Self {
|
||||||
Ok(self.receipt.journal.decode()?)
|
let output_cm = input.note_commitment();
|
||||||
|
|
||||||
|
let cm_leaves = note_commitment_leaves(note_commitments);
|
||||||
|
let cm_idx = note_commitments
|
||||||
|
.iter()
|
||||||
|
.position(|c| c == &output_cm)
|
||||||
|
.unwrap();
|
||||||
|
let cm_path = cl::merkle::path(cm_leaves, cm_idx);
|
||||||
|
|
||||||
|
let secrets = InputPrivate {
|
||||||
|
input: *input,
|
||||||
|
cm_path,
|
||||||
|
};
|
||||||
|
|
||||||
|
let env = risc0_zkvm::ExecutorEnv::builder()
|
||||||
|
.write(&secrets)
|
||||||
|
.unwrap()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Obtain the default prover.
|
||||||
|
let prover = risc0_zkvm::default_prover();
|
||||||
|
|
||||||
|
let start_t = std::time::Instant::now();
|
||||||
|
|
||||||
|
// Proof information by proving the specified ELF binary.
|
||||||
|
// This struct contains the receipt along with statistics about execution of the guest
|
||||||
|
let opts = risc0_zkvm::ProverOpts::succinct();
|
||||||
|
let prove_info = prover
|
||||||
|
.prove_with_opts(env, nomos_cl_risc0_proofs::INPUT_ELF, &opts)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"STARK prover time: {:.2?}, total_cycles: {}",
|
||||||
|
start_t.elapsed(),
|
||||||
|
prove_info.stats.total_cycles
|
||||||
|
);
|
||||||
|
// extract the receipt.
|
||||||
|
let receipt = prove_info.receipt;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
input: InputPublic {
|
||||||
|
cm_root: cl::merkle::root(cm_leaves),
|
||||||
|
input: input.commit(),
|
||||||
|
},
|
||||||
|
risc0_receipt: receipt,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify(&self, expected_public_inputs: &InputPublic) -> bool {
|
pub fn public(&self) -> Result<InputPublic> {
|
||||||
let Ok(public_inputs) = self.public() else {
|
Ok(self.risc0_receipt.journal.decode()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(&self) -> bool {
|
||||||
|
let Ok(proved_public_inputs) = self.public() else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
&public_inputs == expected_public_inputs
|
self.input == proved_public_inputs
|
||||||
&& self.receipt.verify(nomos_cl_risc0_proofs::INPUT_ID).is_ok()
|
&& self
|
||||||
|
.risc0_receipt
|
||||||
|
.verify(nomos_cl_risc0_proofs::INPUT_ID)
|
||||||
|
.is_ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prove_input(input: cl::InputWitness, note_commitments: &[cl::NoteCommitment]) -> InputProof {
|
|
||||||
let output_cm = input.to_output_witness().commit_note();
|
|
||||||
|
|
||||||
let cm_leaves = note_commitment_leaves(note_commitments);
|
|
||||||
let cm_idx = note_commitments
|
|
||||||
.iter()
|
|
||||||
.position(|c| c == &output_cm)
|
|
||||||
.unwrap();
|
|
||||||
let cm_path = cl::merkle::path(cm_leaves, cm_idx);
|
|
||||||
|
|
||||||
let secrets = InputPrivate { input, cm_path };
|
|
||||||
|
|
||||||
let env = risc0_zkvm::ExecutorEnv::builder()
|
|
||||||
.write(&secrets)
|
|
||||||
.unwrap()
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Obtain the default prover.
|
|
||||||
let prover = risc0_zkvm::default_prover();
|
|
||||||
|
|
||||||
use std::time::Instant;
|
|
||||||
let start_t = Instant::now();
|
|
||||||
|
|
||||||
// Proof information by proving the specified ELF binary.
|
|
||||||
// This struct contains the receipt along with statistics about execution of the guest
|
|
||||||
let opts = risc0_zkvm::ProverOpts::succinct();
|
|
||||||
let prove_info = prover
|
|
||||||
.prove_with_opts(env, nomos_cl_risc0_proofs::INPUT_ELF, &opts)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"STARK prover time: {:.2?}, total_cycles: {}",
|
|
||||||
start_t.elapsed(),
|
|
||||||
prove_info.stats.total_cycles
|
|
||||||
);
|
|
||||||
// extract the receipt.
|
|
||||||
let receipt = prove_info.receipt;
|
|
||||||
InputProof { receipt }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn note_commitment_leaves(note_commitments: &[cl::NoteCommitment]) -> [[u8; 32]; MAX_NOTE_COMMS] {
|
fn note_commitment_leaves(note_commitments: &[cl::NoteCommitment]) -> [[u8; 32]; MAX_NOTE_COMMS] {
|
||||||
let note_comm_bytes = Vec::from_iter(note_commitments.iter().map(|c| c.as_bytes().to_vec()));
|
let note_comm_bytes = Vec::from_iter(note_commitments.iter().map(|c| c.as_bytes().to_vec()));
|
||||||
let cm_leaves = cl::merkle::padded_leaves::<MAX_NOTE_COMMS>(¬e_comm_bytes);
|
let cm_leaves = cl::merkle::padded_leaves::<MAX_NOTE_COMMS>(¬e_comm_bytes);
|
||||||
|
@ -78,28 +90,27 @@ mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_input_nullifier_prover() {
|
fn test_input_prover() {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
let input = cl::InputWitness {
|
let input = cl::InputWitness {
|
||||||
note: cl::NoteWitness {
|
note: cl::NoteWitness::basic(32, "NMO"),
|
||||||
balance: cl::BalanceWitness::random(32, "NMO", &mut rng),
|
balance_blinding: cl::BalanceWitness::random(&mut rng),
|
||||||
death_constraint: [0u8; 32],
|
|
||||||
state: [0u8; 32],
|
|
||||||
},
|
|
||||||
nf_sk: cl::NullifierSecret::random(&mut rng),
|
nf_sk: cl::NullifierSecret::random(&mut rng),
|
||||||
nonce: cl::NullifierNonce::random(&mut rng),
|
nonce: cl::NullifierNonce::random(&mut rng),
|
||||||
};
|
};
|
||||||
|
|
||||||
let notes = vec![input.to_output_witness().commit_note()];
|
let notes = vec![input.note_commitment()];
|
||||||
|
|
||||||
let proof = prove_input(input, ¬es);
|
let mut proved_input = ProvedInput::prove(&input, ¬es);
|
||||||
|
|
||||||
let expected_public_inputs = InputPublic {
|
let expected_public_inputs = InputPublic {
|
||||||
cm_root: cl::merkle::root(note_commitment_leaves(¬es)),
|
cm_root: cl::merkle::root(note_commitment_leaves(¬es)),
|
||||||
input: input.commit(),
|
input: input.commit(),
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(proof.verify(&expected_public_inputs));
|
assert_eq!(proved_input.input, expected_public_inputs);
|
||||||
|
assert!(proved_input.verify());
|
||||||
|
|
||||||
let wrong_public_inputs = [
|
let wrong_public_inputs = [
|
||||||
InputPublic {
|
InputPublic {
|
||||||
|
@ -125,7 +136,8 @@ mod test {
|
||||||
},
|
},
|
||||||
InputPublic {
|
InputPublic {
|
||||||
input: cl::Input {
|
input: cl::Input {
|
||||||
balance: cl::BalanceWitness::random(32, "NMO", &mut rng).commit(),
|
balance: cl::BalanceWitness::random(&mut rng)
|
||||||
|
.commit(&cl::NoteWitness::basic(32, "NMO")),
|
||||||
..expected_public_inputs.input
|
..expected_public_inputs.input
|
||||||
},
|
},
|
||||||
..expected_public_inputs
|
..expected_public_inputs
|
||||||
|
@ -133,57 +145,12 @@ mod test {
|
||||||
];
|
];
|
||||||
|
|
||||||
for wrong_input in wrong_public_inputs {
|
for wrong_input in wrong_public_inputs {
|
||||||
assert!(!proof.verify(&wrong_input));
|
proved_input.input = wrong_input;
|
||||||
|
assert!(!proved_input.verify());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----- The following tests still need to be built. -----
|
// ----- The following tests still need to be built. -----
|
||||||
//
|
|
||||||
// #[test]
|
|
||||||
// fn test_input_proof() {
|
|
||||||
// let mut rng = rand::thread_rng();
|
|
||||||
|
|
||||||
// let ptx_root = cl::PtxRoot::default();
|
|
||||||
|
|
||||||
// let note = cl::NoteWitness::new(10, "NMO", [0u8; 32], &mut rng);
|
|
||||||
// let nf_sk = cl::NullifierSecret::random(&mut rng);
|
|
||||||
// let nonce = cl::NullifierNonce::random(&mut rng);
|
|
||||||
|
|
||||||
// let input_witness = cl::InputWitness { note, nf_sk, nonce };
|
|
||||||
|
|
||||||
// let input = input_witness.commit();
|
|
||||||
// let proof = input.prove(&input_witness, ptx_root, vec![]).unwrap();
|
|
||||||
|
|
||||||
// assert!(input.verify(ptx_root, &proof));
|
|
||||||
|
|
||||||
// let wrong_witnesses = [
|
|
||||||
// cl::InputWitness {
|
|
||||||
// note: cl::NoteWitness::new(11, "NMO", [0u8; 32], &mut rng),
|
|
||||||
// ..input_witness.clone()
|
|
||||||
// },
|
|
||||||
// cl::InputWitness {
|
|
||||||
// note: cl::NoteWitness::new(10, "ETH", [0u8; 32], &mut rng),
|
|
||||||
// ..input_witness.clone()
|
|
||||||
// },
|
|
||||||
// cl::InputWitness {
|
|
||||||
// nf_sk: cl::NullifierSecret::random(&mut rng),
|
|
||||||
// ..input_witness.clone()
|
|
||||||
// },
|
|
||||||
// cl::InputWitness {
|
|
||||||
// nonce: cl::NullifierNonce::random(&mut rng),
|
|
||||||
// ..input_witness.clone()
|
|
||||||
// },
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// for wrong_witness in wrong_witnesses {
|
|
||||||
// assert!(input.prove(&wrong_witness, ptx_root, vec![]).is_err());
|
|
||||||
|
|
||||||
// let wrong_input = wrong_witness.commit();
|
|
||||||
// let wrong_proof = wrong_input.prove(&wrong_witness, ptx_root, vec![]).unwrap();
|
|
||||||
// assert!(!input.verify(ptx_root, &wrong_proof));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn test_input_ptx_coupling() {
|
// fn test_input_ptx_coupling() {
|
||||||
// let mut rng = rand::thread_rng();
|
// let mut rng = rand::thread_rng();
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
|
// pub mod death_constraint;
|
||||||
|
pub mod bundle;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
|
pub mod output;
|
||||||
|
pub mod partial_tx;
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
use crate::error::Result;
|
||||||
|
|
||||||
|
pub struct ProvedOutput {
|
||||||
|
pub output: cl::Output,
|
||||||
|
pub risc0_receipt: risc0_zkvm::Receipt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProvedOutput {
|
||||||
|
pub fn prove(witness: &cl::OutputWitness) -> Self {
|
||||||
|
let env = risc0_zkvm::ExecutorEnv::builder()
|
||||||
|
.write(&witness)
|
||||||
|
.unwrap()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let prover = risc0_zkvm::default_prover();
|
||||||
|
|
||||||
|
let start_t = std::time::Instant::now();
|
||||||
|
|
||||||
|
let opts = risc0_zkvm::ProverOpts::succinct();
|
||||||
|
let prove_info = prover
|
||||||
|
.prove_with_opts(env, nomos_cl_risc0_proofs::OUTPUT_ELF, &opts)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"STARK 'output' prover time: {:.2?}, total_cycles: {}",
|
||||||
|
start_t.elapsed(),
|
||||||
|
prove_info.stats.total_cycles
|
||||||
|
);
|
||||||
|
|
||||||
|
let receipt = prove_info.receipt;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
output: witness.commit(),
|
||||||
|
risc0_receipt: receipt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn public(&self) -> Result<cl::Output> {
|
||||||
|
Ok(self.risc0_receipt.journal.decode()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(&self) -> bool {
|
||||||
|
let Ok(output_commitments) = self.public() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.output == output_commitments
|
||||||
|
&& self
|
||||||
|
.risc0_receipt
|
||||||
|
.verify(nomos_cl_risc0_proofs::OUTPUT_ID)
|
||||||
|
.is_ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use rand::thread_rng;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_output_prover() {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
|
let output = cl::OutputWitness {
|
||||||
|
note: cl::NoteWitness::basic(32, "NMO"),
|
||||||
|
balance_blinding: cl::BalanceWitness::random(&mut rng),
|
||||||
|
nf_pk: cl::NullifierSecret::random(&mut rng).commit(),
|
||||||
|
nonce: cl::NullifierNonce::random(&mut rng),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut proved_output = ProvedOutput::prove(&output);
|
||||||
|
|
||||||
|
let expected_output_cm = output.commit();
|
||||||
|
|
||||||
|
assert_eq!(proved_output.output, expected_output_cm);
|
||||||
|
assert!(proved_output.verify());
|
||||||
|
|
||||||
|
let wrong_output_cms = [
|
||||||
|
cl::Output {
|
||||||
|
note_comm: cl::NoteWitness::basic(100, "NMO").commit(
|
||||||
|
cl::NullifierSecret::random(&mut rng).commit(),
|
||||||
|
cl::NullifierNonce::random(&mut rng),
|
||||||
|
),
|
||||||
|
..expected_output_cm
|
||||||
|
},
|
||||||
|
cl::Output {
|
||||||
|
note_comm: cl::NoteWitness::basic(100, "NMO").commit(
|
||||||
|
cl::NullifierSecret::random(&mut rng).commit(),
|
||||||
|
cl::NullifierNonce::random(&mut rng),
|
||||||
|
),
|
||||||
|
balance: cl::BalanceWitness::random(&mut rng)
|
||||||
|
.commit(&cl::NoteWitness::basic(100, "NMO")),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for wrong_output_cm in wrong_output_cms {
|
||||||
|
proved_output.output = wrong_output_cm;
|
||||||
|
assert!(!proved_output.verify());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
use crate::{input::ProvedInput, output::ProvedOutput};
|
||||||
|
|
||||||
|
pub struct ProvedPartialTx {
|
||||||
|
pub inputs: Vec<ProvedInput>,
|
||||||
|
pub outputs: Vec<ProvedOutput>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProvedPartialTx {
|
||||||
|
pub fn prove(
|
||||||
|
ptx: &cl::PartialTxWitness,
|
||||||
|
note_commitments: &[cl::NoteCommitment],
|
||||||
|
) -> ProvedPartialTx {
|
||||||
|
Self {
|
||||||
|
inputs: Vec::from_iter(
|
||||||
|
ptx.inputs
|
||||||
|
.iter()
|
||||||
|
.map(|i| ProvedInput::prove(i, note_commitments)),
|
||||||
|
),
|
||||||
|
outputs: Vec::from_iter(ptx.outputs.iter().map(ProvedOutput::prove)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(&self) -> bool {
|
||||||
|
self.inputs.iter().all(ProvedInput::verify) && self.outputs.iter().all(ProvedOutput::verify)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
use ledger::{bundle::ProvedBundle, partial_tx::ProvedPartialTx};
|
||||||
|
use rand_core::CryptoRngCore;
|
||||||
|
|
||||||
|
struct User(cl::NullifierSecret);
|
||||||
|
|
||||||
|
impl User {
|
||||||
|
fn random(mut rng: impl CryptoRngCore) -> Self {
|
||||||
|
Self(cl::NullifierSecret::random(&mut rng))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pk(&self) -> cl::NullifierCommitment {
|
||||||
|
self.0.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sk(&self) -> cl::NullifierSecret {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive_utxo(
|
||||||
|
note: cl::NoteWitness,
|
||||||
|
nf_pk: cl::NullifierCommitment,
|
||||||
|
rng: impl CryptoRngCore,
|
||||||
|
) -> cl::OutputWitness {
|
||||||
|
cl::OutputWitness::random(note, nf_pk, rng)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simple_transfer() {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
// alice is sending 8 NMO to bob.
|
||||||
|
|
||||||
|
let alice = User::random(&mut rng);
|
||||||
|
let bob = User::random(&mut rng);
|
||||||
|
|
||||||
|
// Alice has an unspent note worth 10 NMO
|
||||||
|
let utxo = receive_utxo(cl::NoteWitness::basic(10, "NMO"), alice.pk(), &mut rng);
|
||||||
|
let alices_input = cl::InputWitness::random(utxo, alice.sk(), &mut rng);
|
||||||
|
|
||||||
|
// Alice wants to send 8 NMO to bob
|
||||||
|
let bobs_output =
|
||||||
|
cl::OutputWitness::random(cl::NoteWitness::basic(8, "NMO"), bob.pk(), &mut rng);
|
||||||
|
|
||||||
|
// .. and return the 2 NMO in change to herself.
|
||||||
|
let change_output =
|
||||||
|
cl::OutputWitness::random(cl::NoteWitness::basic(2, "NMO"), alice.pk(), &mut rng);
|
||||||
|
|
||||||
|
// Construct the ptx consuming Alices inputs and producing the two outputs.
|
||||||
|
let ptx_witness = cl::PartialTxWitness {
|
||||||
|
inputs: vec![alices_input],
|
||||||
|
outputs: vec![bobs_output, change_output],
|
||||||
|
};
|
||||||
|
|
||||||
|
// assume we only have one note commitment on chain for now ...
|
||||||
|
let note_commitments = vec![utxo.commit_note()];
|
||||||
|
let proved_ptx = ProvedPartialTx::prove(&ptx_witness, ¬e_commitments);
|
||||||
|
|
||||||
|
assert!(proved_ptx.verify()); // It's a valid ptx.
|
||||||
|
|
||||||
|
let bundle = cl::Bundle {
|
||||||
|
partials: vec![ptx_witness.commit()],
|
||||||
|
};
|
||||||
|
|
||||||
|
let bundle_witness = cl::BundleWitness {
|
||||||
|
balance_blinding: ptx_witness.balance_blinding(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let proved_bundle = ProvedBundle::prove(&bundle, &bundle_witness);
|
||||||
|
assert!(proved_bundle.verify()); // The bundle is balanced.
|
||||||
|
}
|
|
@ -3,7 +3,6 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct DeathConstraintPublic {
|
pub struct DeathConstraintPublic {
|
||||||
pub cm_root: [u8; 32],
|
|
||||||
pub nf: Nullifier,
|
pub nf: Nullifier,
|
||||||
pub ptx_root: PtxRoot,
|
pub ptx_root: PtxRoot,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use cl::{merkle, InputWitness, OutputWitness, PtxRoot};
|
use cl::{merkle, InputWitness, OutputWitness, PtxRoot};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// An input to a partial transaction
|
/// An input to a partial transaction
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct PartialTxInputPrivate {
|
pub struct PartialTxInputPrivate {
|
||||||
|
@ -15,7 +16,7 @@ impl PartialTxInputPrivate {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cm_root(&self) -> [u8; 32] {
|
pub fn cm_root(&self) -> [u8; 32] {
|
||||||
let leaf = merkle::leaf(self.input.to_output_witness().commit_note().as_bytes());
|
let leaf = merkle::leaf(self.input.note_commitment().as_bytes());
|
||||||
merkle::path_root(leaf, &self.cm_path)
|
merkle::path_root(leaf, &self.cm_path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,5 +7,5 @@ edition = "2021"
|
||||||
risc0-build = { version = "1.0" }
|
risc0-build = { version = "1.0" }
|
||||||
|
|
||||||
[package.metadata.risc0]
|
[package.metadata.risc0]
|
||||||
methods = ["input"]
|
methods = ["input", "output", "bundle", "death_constraint_nop"]
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "bundle"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
cl = { path = "../../cl" }
|
||||||
|
proof_statements = { path = "../../proof_statements" }
|
||||||
|
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
# add RISC Zero accelerator support for all downstream usages of the following crates.
|
||||||
|
sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.8-risczero.0" }
|
||||||
|
crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.5-risczero.0" }
|
||||||
|
curve25519-dalek = { git = "https://github.com/risc0/curve25519-dalek", tag = "curve25519-4.1.2-risczero.0" }
|
|
@ -0,0 +1,17 @@
|
||||||
|
/// Bundle Proof
|
||||||
|
///
|
||||||
|
/// The bundle proof demonstrates that the set of partial transactions
|
||||||
|
/// balance to zero. i.e. \sum inputs = \sum outputs.
|
||||||
|
///
|
||||||
|
/// This is done by proving knowledge of some blinding factor `r` s.t.
|
||||||
|
/// \sum outputs - \sum input = 0*G + r*H
|
||||||
|
///
|
||||||
|
/// To avoid doing costly ECC in stark, we compute only the RHS in stark.
|
||||||
|
/// The sums and equality is checked outside of stark during proof verification.
|
||||||
|
use risc0_zkvm::guest::env;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let bundle_witness: cl::BundleWitness = env::read();
|
||||||
|
let zero_balance = cl::Balance::zero(bundle_witness.balance_blinding);
|
||||||
|
env::commit(&zero_balance);
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "death_constraint_nop"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
cl = { path = "../../cl" }
|
||||||
|
proof_statements = { path = "../../proof_statements" }
|
||||||
|
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
# add RISC Zero accelerator support for all downstream usages of the following crates.
|
||||||
|
sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.8-risczero.0" }
|
||||||
|
crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.5-risczero.0" }
|
||||||
|
curve25519-dalek = { git = "https://github.com/risc0/curve25519-dalek", tag = "curve25519-4.1.2-risczero.0" }
|
|
@ -0,0 +1,8 @@
|
||||||
|
/// Death Constraint No-op Proof
|
||||||
|
use proof_statements::death_constraint::DeathConstraintPublic;
|
||||||
|
use risc0_zkvm::guest::env;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let public: DeathConstraintPublic = env::read();
|
||||||
|
env::commit(&public);
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ use risc0_zkvm::guest::env;
|
||||||
fn main() {
|
fn main() {
|
||||||
let secret: InputPrivate = env::read();
|
let secret: InputPrivate = env::read();
|
||||||
|
|
||||||
let out_cm = secret.input.to_output_witness().commit_note();
|
let out_cm = secret.input.note_commitment();
|
||||||
let cm_leaf = merkle::leaf(out_cm.as_bytes());
|
let cm_leaf = merkle::leaf(out_cm.as_bytes());
|
||||||
let cm_root = merkle::path_root(cm_leaf, &secret.cm_path);
|
let cm_root = merkle::path_root(cm_leaf, &secret.cm_path);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "output"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
cl = { path = "../../cl" }
|
||||||
|
proof_statements = { path = "../../proof_statements" }
|
||||||
|
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
# add RISC Zero accelerator support for all downstream usages of the following crates.
|
||||||
|
sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.8-risczero.0" }
|
||||||
|
crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.5-risczero.0" }
|
||||||
|
curve25519-dalek = { git = "https://github.com/risc0/curve25519-dalek", tag = "curve25519-4.1.2-risczero.0" }
|
|
@ -0,0 +1,12 @@
|
||||||
|
/// Output Proof
|
||||||
|
///
|
||||||
|
/// given randomness `r` and `note=(value, unit, ...)` prove that
|
||||||
|
/// - balance = balance_commit(value, unit, r)
|
||||||
|
/// - note_cm = note_commit(note)
|
||||||
|
use risc0_zkvm::guest::env;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let output: cl::OutputWitness = env::read();
|
||||||
|
let output_cm = output.commit();
|
||||||
|
env::commit(&output_cm);
|
||||||
|
}
|
|
@ -18,7 +18,6 @@ fn main() {
|
||||||
spend_event_state_path,
|
spend_event_state_path,
|
||||||
} = env::read();
|
} = env::read();
|
||||||
|
|
||||||
let cm_root = in_zone_funds.cm_root();
|
|
||||||
let ptx_root = in_zone_funds.ptx_root();
|
let ptx_root = in_zone_funds.ptx_root();
|
||||||
let nf = Nullifier::new(in_zone_funds.input.nf_sk, in_zone_funds.input.nonce);
|
let nf = Nullifier::new(in_zone_funds.input.nf_sk, in_zone_funds.input.nonce);
|
||||||
// check the zone funds note is the one in the spend event
|
// check the zone funds note is the one in the spend event
|
||||||
|
@ -39,19 +38,18 @@ fn main() {
|
||||||
let change = in_zone_funds
|
let change = in_zone_funds
|
||||||
.input
|
.input
|
||||||
.note
|
.note
|
||||||
.balance
|
|
||||||
.value
|
.value
|
||||||
.checked_sub(spend_event.amount)
|
.checked_sub(spend_event.amount)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(out_zone_funds.output.note.balance.value, change);
|
assert_eq!(out_zone_funds.output.note.value, change);
|
||||||
// zone funds output should have the same death constraints as the zone funds input
|
// zone funds output should have the same death constraints as the zone funds input
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out_zone_funds.output.note.death_constraint,
|
out_zone_funds.output.note.death_constraint,
|
||||||
in_zone_funds.input.note.death_constraint
|
in_zone_funds.input.note.death_constraint
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out_zone_funds.output.note.balance.unit,
|
out_zone_funds.output.note.unit,
|
||||||
in_zone_funds.input.note.balance.unit
|
in_zone_funds.input.note.unit
|
||||||
);
|
);
|
||||||
// zone funds nullifier, nonce and value blinding should be public so that everybody can spend it
|
// zone funds nullifier, nonce and value blinding should be public so that everybody can spend it
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -59,8 +57,8 @@ fn main() {
|
||||||
NullifierSecret::from_bytes([0; 16]).commit()
|
NullifierSecret::from_bytes([0; 16]).commit()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out_zone_funds.output.note.balance.blinding,
|
out_zone_funds.output.balance_blinding,
|
||||||
in_zone_funds.input.note.balance.blinding
|
in_zone_funds.input.balance_blinding
|
||||||
);
|
);
|
||||||
let mut evolved_nonce = [0; 16];
|
let mut evolved_nonce = [0; 16];
|
||||||
evolved_nonce[..16]
|
evolved_nonce[..16]
|
||||||
|
@ -73,16 +71,12 @@ fn main() {
|
||||||
assert_eq!(ptx_root, spent_note.ptx_root());
|
assert_eq!(ptx_root, spent_note.ptx_root());
|
||||||
|
|
||||||
// check the correct amount of funds is being spent
|
// check the correct amount of funds is being spent
|
||||||
assert_eq!(spent_note.output.note.balance.value, spend_event.amount);
|
assert_eq!(spent_note.output.note.value, spend_event.amount);
|
||||||
assert_eq!(
|
assert_eq!(spent_note.output.note.unit, in_zone_funds.input.note.unit);
|
||||||
spent_note.output.note.balance.unit,
|
|
||||||
in_zone_funds.input.note.balance.unit
|
|
||||||
);
|
|
||||||
// check the correct recipient is being paid
|
// check the correct recipient is being paid
|
||||||
assert_eq!(spent_note.output.nf_pk, spend_event.to);
|
assert_eq!(spent_note.output.nf_pk, spend_event.to);
|
||||||
|
|
||||||
env::commit(&DeathConstraintPublic {
|
env::commit(&DeathConstraintPublic {
|
||||||
cm_root,
|
|
||||||
ptx_root,
|
ptx_root,
|
||||||
nf,
|
nf,
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue