mirror of
https://github.com/logos-blockchain/logos-blockchain-pocs.git
synced 2026-01-08 08:03:06 +00:00
Merge pull request #43 from logos-co/emmarin-poc
PoC ledger partition and note teleportation
This commit is contained in:
commit
0faceaab5e
2
emmarin/cl/.gitignore
vendored
Normal file
2
emmarin/cl/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
Cargo.lock
|
||||
target/
|
||||
11
emmarin/cl/Cargo.toml
Normal file
11
emmarin/cl/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [ "cl", "ledger", "ledger_proof_statements", "risc0_proofs", "ledger_validity_proof"]
|
||||
|
||||
# Always optimize; building and running the risc0_proofs takes much longer without optimization.
|
||||
[profile.dev]
|
||||
opt-level = 3
|
||||
|
||||
[profile.release]
|
||||
debug = 1
|
||||
lto = true
|
||||
15
emmarin/cl/cl/Cargo.toml
Normal file
15
emmarin/cl/cl/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "cl"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde = {version="1.0", features = ["derive"]}
|
||||
group = "0.13.0"
|
||||
rand = "0.8.5"
|
||||
rand_core = "0.6.0"
|
||||
hex = "0.4.3"
|
||||
curve25519-dalek = {version = "4.1", features = ["serde", "digest", "rand_core"]}
|
||||
sha2 = "0.10"
|
||||
149
emmarin/cl/cl/src/cl/balance.rs
Normal file
149
emmarin/cl/cl/src/cl/balance.rs
Normal file
@ -0,0 +1,149 @@
|
||||
use rand_core::CryptoRngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::cl::PartialTxWitness;
|
||||
|
||||
pub type Value = u64;
|
||||
pub type Unit = [u8; 32];
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct Balance([u8; 32]);
|
||||
|
||||
impl Balance {
|
||||
pub fn to_bytes(&self) -> [u8; 32] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
||||
pub struct UnitBalance {
|
||||
pub unit: Unit,
|
||||
pub pos: u64,
|
||||
pub neg: u64,
|
||||
}
|
||||
|
||||
impl UnitBalance {
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.pos == self.neg
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
||||
pub struct BalanceWitness {
|
||||
pub balances: Vec<UnitBalance>,
|
||||
pub blinding: [u8; 16],
|
||||
}
|
||||
|
||||
impl BalanceWitness {
|
||||
pub fn random_blinding(mut rng: impl CryptoRngCore) -> [u8; 16] {
|
||||
let mut blinding = [0u8; 16];
|
||||
rng.fill_bytes(&mut blinding);
|
||||
|
||||
blinding
|
||||
}
|
||||
|
||||
pub fn zero(blinding: [u8; 16]) -> Self {
|
||||
Self {
|
||||
balances: Default::default(),
|
||||
blinding,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_ptx(ptx: &PartialTxWitness, blinding: [u8; 16]) -> Self {
|
||||
let mut balance = Self::zero(blinding);
|
||||
|
||||
for input in ptx.inputs.iter() {
|
||||
balance.insert_negative(input.note.unit, input.note.value);
|
||||
}
|
||||
|
||||
for output in ptx.outputs.iter() {
|
||||
balance.insert_positive(output.note.unit, output.note.value);
|
||||
}
|
||||
|
||||
balance.clear_zeros();
|
||||
|
||||
balance
|
||||
}
|
||||
|
||||
pub fn insert_positive(&mut self, unit: Unit, value: Value) {
|
||||
for unit_bal in self.balances.iter_mut() {
|
||||
if unit_bal.unit == unit {
|
||||
unit_bal.pos += value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Unit was not found, so we must create one.
|
||||
self.balances.push(UnitBalance {
|
||||
unit,
|
||||
pos: value,
|
||||
neg: 0,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn insert_negative(&mut self, unit: Unit, value: Value) {
|
||||
for unit_bal in self.balances.iter_mut() {
|
||||
if unit_bal.unit == unit {
|
||||
unit_bal.neg += value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.balances.push(UnitBalance {
|
||||
unit,
|
||||
pos: 0,
|
||||
neg: value,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn clear_zeros(&mut self) {
|
||||
let mut i = 0usize;
|
||||
while i < self.balances.len() {
|
||||
if self.balances[i].is_zero() {
|
||||
self.balances.swap_remove(i);
|
||||
// don't increment `i` since the last element has been swapped into the
|
||||
// `i`'th place
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn combine(balances: impl IntoIterator<Item = Self>, blinding: [u8; 16]) -> Self {
|
||||
let mut combined = BalanceWitness::zero(blinding);
|
||||
|
||||
for balance in balances {
|
||||
for unit_bal in balance.balances.iter() {
|
||||
if unit_bal.pos > unit_bal.neg {
|
||||
combined.insert_positive(unit_bal.unit, unit_bal.pos - unit_bal.neg);
|
||||
} else {
|
||||
combined.insert_negative(unit_bal.unit, unit_bal.neg - unit_bal.pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
combined.clear_zeros();
|
||||
|
||||
combined
|
||||
}
|
||||
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.balances.is_empty()
|
||||
}
|
||||
|
||||
pub fn commit(&self) -> Balance {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"NOMOS_CL_BAL_COMMIT");
|
||||
|
||||
for unit_balance in self.balances.iter() {
|
||||
hasher.update(unit_balance.unit);
|
||||
hasher.update(unit_balance.pos.to_le_bytes());
|
||||
hasher.update(unit_balance.neg.to_le_bytes());
|
||||
}
|
||||
hasher.update(self.blinding);
|
||||
|
||||
let commit_bytes: [u8; 32] = hasher.finalize().into();
|
||||
Balance(commit_bytes)
|
||||
}
|
||||
}
|
||||
138
emmarin/cl/cl/src/cl/bundle.rs
Normal file
138
emmarin/cl/cl/src/cl/bundle.rs
Normal file
@ -0,0 +1,138 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{cl::partial_tx::PartialTx, zone_layer::notes::ZoneId};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::collections::HashSet;
|
||||
|
||||
/// 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, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct BundleId(pub [u8; 32]);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Bundle {
|
||||
pub partials: Vec<PartialTx>,
|
||||
}
|
||||
|
||||
impl Bundle {
|
||||
pub fn zones(&self) -> HashSet<ZoneId> {
|
||||
self.partials
|
||||
.iter()
|
||||
.flat_map(|ptx| {
|
||||
ptx.inputs
|
||||
.iter()
|
||||
.map(|i| i.zone_id)
|
||||
.chain(ptx.outputs.iter().map(|o| o.zone_id))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
///
|
||||
pub fn id(&self) -> BundleId {
|
||||
// TODO: change to merkle root
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"NOMOS_CL_BUNDLE_ID");
|
||||
for ptx in &self.partials {
|
||||
hasher.update(&ptx.root().0);
|
||||
}
|
||||
|
||||
BundleId(hasher.finalize().into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::cl::{
|
||||
balance::{BalanceWitness, UnitBalance},
|
||||
input::InputWitness,
|
||||
note::{derive_unit, NoteWitness},
|
||||
nullifier::NullifierSecret,
|
||||
output::OutputWitness,
|
||||
partial_tx::PartialTxWitness,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_bundle_balance() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let zone_id = [0; 32];
|
||||
let (nmo, eth, crv) = (derive_unit("NMO"), derive_unit("ETH"), derive_unit("CRV"));
|
||||
|
||||
let nf_a = NullifierSecret::random(&mut rng);
|
||||
let nf_b = NullifierSecret::random(&mut rng);
|
||||
let nf_c = NullifierSecret::random(&mut rng);
|
||||
|
||||
let nmo_10_utxo = OutputWitness::new(
|
||||
NoteWitness::basic(10, nmo, &mut rng),
|
||||
nf_a.commit(),
|
||||
zone_id,
|
||||
);
|
||||
let nmo_10_in = InputWitness::from_output(nmo_10_utxo, nf_a);
|
||||
|
||||
let eth_23_utxo = OutputWitness::new(
|
||||
NoteWitness::basic(23, eth, &mut rng),
|
||||
nf_b.commit(),
|
||||
zone_id,
|
||||
);
|
||||
let eth_23_in = InputWitness::from_output(eth_23_utxo, nf_b);
|
||||
|
||||
let crv_4840_out = OutputWitness::new(
|
||||
NoteWitness::basic(4840, crv, &mut rng),
|
||||
nf_c.commit(),
|
||||
zone_id,
|
||||
);
|
||||
|
||||
let ptx_unbalanced = PartialTxWitness {
|
||||
inputs: vec![nmo_10_in, eth_23_in],
|
||||
outputs: vec![crv_4840_out],
|
||||
balance_blinding: BalanceWitness::random_blinding(&mut rng),
|
||||
};
|
||||
|
||||
assert!(!ptx_unbalanced.balance().is_zero());
|
||||
assert_eq!(
|
||||
ptx_unbalanced.balance().balances,
|
||||
vec![
|
||||
UnitBalance {
|
||||
unit: nmo,
|
||||
pos: 0,
|
||||
neg: 10
|
||||
},
|
||||
UnitBalance {
|
||||
unit: eth,
|
||||
pos: 0,
|
||||
neg: 23
|
||||
},
|
||||
UnitBalance {
|
||||
unit: crv,
|
||||
pos: 4840,
|
||||
neg: 0
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
let crv_4840_in = InputWitness::from_output(crv_4840_out, nf_c);
|
||||
let nmo_10_out = OutputWitness::new(
|
||||
NoteWitness::basic(10, nmo, &mut rng),
|
||||
NullifierSecret::random(&mut rng).commit(), // transferring to a random owner
|
||||
zone_id,
|
||||
);
|
||||
let eth_23_out = OutputWitness::new(
|
||||
NoteWitness::basic(23, eth, &mut rng),
|
||||
NullifierSecret::random(&mut rng).commit(), // transferring to a random owner
|
||||
zone_id,
|
||||
);
|
||||
|
||||
let ptx_solved = PartialTxWitness {
|
||||
inputs: vec![crv_4840_in],
|
||||
outputs: vec![nmo_10_out, eth_23_out],
|
||||
balance_blinding: BalanceWitness::random_blinding(&mut rng),
|
||||
};
|
||||
|
||||
let bundle_balance =
|
||||
BalanceWitness::combine([ptx_unbalanced.balance(), ptx_solved.balance()], [0; 16]);
|
||||
|
||||
assert!(bundle_balance.is_zero());
|
||||
assert_eq!(bundle_balance.balances, vec![]);
|
||||
}
|
||||
}
|
||||
6
emmarin/cl/cl/src/cl/crypto.rs
Normal file
6
emmarin/cl/cl/src/cl/crypto.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use sha2::Sha512;
|
||||
|
||||
pub fn hash_to_curve(bytes: &[u8]) -> RistrettoPoint {
|
||||
RistrettoPoint::hash_from_bytes::<Sha512>(bytes)
|
||||
}
|
||||
4
emmarin/cl/cl/src/cl/error.rs
Normal file
4
emmarin/cl/cl/src/cl/error.rs
Normal file
@ -0,0 +1,4 @@
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
ProofFailed,
|
||||
}
|
||||
97
emmarin/cl/cl/src/cl/input.rs
Normal file
97
emmarin/cl/cl/src/cl/input.rs
Normal file
@ -0,0 +1,97 @@
|
||||
/// This module defines the partial transaction structure.
|
||||
///
|
||||
/// Partial transactions, as the name suggests, are transactions
|
||||
/// which on their own may not balance (i.e. \sum inputs != \sum outputs)
|
||||
use crate::{
|
||||
cl::{
|
||||
note::{Constraint, NoteWitness},
|
||||
nullifier::{Nullifier, NullifierSecret},
|
||||
Nonce, NoteCommitment, OutputWitness,
|
||||
},
|
||||
zone_layer::notes::ZoneId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Input {
|
||||
pub nullifier: Nullifier,
|
||||
pub constraint: Constraint,
|
||||
pub zone_id: ZoneId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct InputWitness {
|
||||
pub note: NoteWitness,
|
||||
pub nf_sk: NullifierSecret,
|
||||
pub zone_id: ZoneId,
|
||||
}
|
||||
|
||||
impl InputWitness {
|
||||
pub fn new(note: NoteWitness, nf_sk: NullifierSecret, zone_id: ZoneId) -> Self {
|
||||
Self {
|
||||
note,
|
||||
nf_sk,
|
||||
zone_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_output(output: OutputWitness, nf_sk: NullifierSecret) -> Self {
|
||||
assert_eq!(nf_sk.commit(), output.nf_pk);
|
||||
Self::new(output.note, nf_sk, output.zone_id)
|
||||
}
|
||||
|
||||
pub fn public(output: OutputWitness) -> Self {
|
||||
let nf_sk = NullifierSecret::zero();
|
||||
assert_eq!(nf_sk.commit(), output.nf_pk); // ensure the output was a public UTXO
|
||||
Self::new(output.note, nf_sk, output.zone_id)
|
||||
}
|
||||
|
||||
pub fn evolved_nonce(&self, domain: &[u8]) -> Nonce {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"NOMOS_COIN_EVOLVE");
|
||||
hasher.update(domain);
|
||||
hasher.update(self.nf_sk.0);
|
||||
hasher.update(self.note.commit(&self.zone_id, self.nf_sk.commit()).0);
|
||||
|
||||
let nonce_bytes: [u8; 32] = hasher.finalize().into();
|
||||
Nonce::from_bytes(nonce_bytes)
|
||||
}
|
||||
|
||||
pub fn evolve_output(&self, domain: &[u8]) -> OutputWitness {
|
||||
OutputWitness {
|
||||
note: NoteWitness {
|
||||
nonce: self.evolved_nonce(domain),
|
||||
..self.note
|
||||
},
|
||||
nf_pk: self.nf_sk.commit(),
|
||||
zone_id: self.zone_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nullifier(&self) -> Nullifier {
|
||||
Nullifier::new(&self.zone_id, self.nf_sk, self.note_commitment())
|
||||
}
|
||||
|
||||
pub fn commit(&self) -> Input {
|
||||
Input {
|
||||
nullifier: self.nullifier(),
|
||||
constraint: self.note.constraint,
|
||||
zone_id: self.zone_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn note_commitment(&self) -> NoteCommitment {
|
||||
self.note.commit(&self.zone_id, self.nf_sk.commit())
|
||||
}
|
||||
}
|
||||
|
||||
impl Input {
|
||||
pub fn to_bytes(&self) -> [u8; 96] {
|
||||
let mut bytes = [0u8; 96];
|
||||
bytes[..32].copy_from_slice(self.nullifier.as_bytes());
|
||||
bytes[32..64].copy_from_slice(&self.constraint.0);
|
||||
bytes[64..96].copy_from_slice(&self.zone_id);
|
||||
bytes
|
||||
}
|
||||
}
|
||||
239
emmarin/cl/cl/src/cl/merkle.rs
Normal file
239
emmarin/cl/cl/src/cl/merkle.rs
Normal file
@ -0,0 +1,239 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
pub fn padded_leaves<const N: usize>(elements: &[Vec<u8>]) -> [[u8; 32]; N] {
|
||||
let mut leaves = [[0u8; 32]; N];
|
||||
|
||||
for (i, element) in elements.iter().enumerate() {
|
||||
assert!(i < N);
|
||||
leaves[i] = leaf(element);
|
||||
}
|
||||
|
||||
leaves
|
||||
}
|
||||
|
||||
pub fn leaf(data: &[u8]) -> [u8; 32] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"NOMOS_MERKLE_LEAF");
|
||||
hasher.update(data);
|
||||
hasher.finalize().into()
|
||||
}
|
||||
|
||||
pub fn node(a: [u8; 32], b: [u8; 32]) -> [u8; 32] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"NOMOS_MERKLE_NODE");
|
||||
hasher.update(a);
|
||||
hasher.update(b);
|
||||
hasher.finalize().into()
|
||||
}
|
||||
|
||||
pub fn root<const N: usize>(elements: [[u8; 32]; N]) -> [u8; 32] {
|
||||
let n = elements.len();
|
||||
|
||||
assert!(n.is_power_of_two());
|
||||
|
||||
let mut nodes = elements;
|
||||
|
||||
for h in (1..=n.ilog2()).rev() {
|
||||
for i in 0..2usize.pow(h - 1) {
|
||||
nodes[i] = node(nodes[i * 2], nodes[i * 2 + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
nodes[0]
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum PathNode {
|
||||
Left([u8; 32]),
|
||||
Right([u8; 32]),
|
||||
}
|
||||
|
||||
pub fn path_root(leaf: [u8; 32], path: &[PathNode]) -> [u8; 32] {
|
||||
let mut computed_hash = leaf;
|
||||
|
||||
for path_node in path {
|
||||
match path_node {
|
||||
PathNode::Left(sibling_hash) => {
|
||||
computed_hash = node(*sibling_hash, computed_hash);
|
||||
}
|
||||
PathNode::Right(sibling_hash) => {
|
||||
computed_hash = node(computed_hash, *sibling_hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computed_hash
|
||||
}
|
||||
|
||||
pub fn path<const N: usize>(leaves: [[u8; 32]; N], idx: usize) -> Vec<PathNode> {
|
||||
assert!(N.is_power_of_two());
|
||||
assert!(idx < N);
|
||||
|
||||
let mut nodes = leaves;
|
||||
let mut path = Vec::new();
|
||||
let mut idx = idx;
|
||||
|
||||
for h in (1..=N.ilog2()).rev() {
|
||||
if idx % 2 == 0 {
|
||||
path.push(PathNode::Right(nodes[idx + 1]));
|
||||
} else {
|
||||
path.push(PathNode::Left(nodes[idx - 1]));
|
||||
}
|
||||
|
||||
idx /= 2;
|
||||
|
||||
for i in 0..2usize.pow(h - 1) {
|
||||
nodes[i] = node(nodes[i * 2], nodes[i * 2 + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
path
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_root_height_1() {
|
||||
let r = root::<1>(padded_leaves(&[b"sand".into()]));
|
||||
|
||||
let expected = leaf(b"sand");
|
||||
|
||||
assert_eq!(r, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_root_height_2() {
|
||||
let r = root::<2>(padded_leaves(&[b"desert".into(), b"sand".into()]));
|
||||
|
||||
let expected = node(leaf(b"desert"), leaf(b"sand"));
|
||||
|
||||
assert_eq!(r, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_root_height_3() {
|
||||
let r = root::<4>(padded_leaves(&[
|
||||
b"desert".into(),
|
||||
b"sand".into(),
|
||||
b"feels".into(),
|
||||
b"warm".into(),
|
||||
]));
|
||||
|
||||
let expected = node(
|
||||
node(leaf(b"desert"), leaf(b"sand")),
|
||||
node(leaf(b"feels"), leaf(b"warm")),
|
||||
);
|
||||
|
||||
assert_eq!(r, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_root_height_4() {
|
||||
let r = root::<8>(padded_leaves(&[
|
||||
b"desert".into(),
|
||||
b"sand".into(),
|
||||
b"feels".into(),
|
||||
b"warm".into(),
|
||||
b"at".into(),
|
||||
b"night".into(),
|
||||
]));
|
||||
|
||||
let expected = node(
|
||||
node(
|
||||
node(leaf(b"desert"), leaf(b"sand")),
|
||||
node(leaf(b"feels"), leaf(b"warm")),
|
||||
),
|
||||
node(
|
||||
node(leaf(b"at"), leaf(b"night")),
|
||||
node([0u8; 32], [0u8; 32]),
|
||||
),
|
||||
);
|
||||
|
||||
assert_eq!(r, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_height_1() {
|
||||
let leaves = padded_leaves(&[b"desert".into()]);
|
||||
let r = root::<1>(leaves);
|
||||
|
||||
let p = path::<1>(leaves, 0);
|
||||
let expected = vec![];
|
||||
assert_eq!(p, expected);
|
||||
assert_eq!(path_root(leaf(b"desert"), &p), r);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_height_2() {
|
||||
let leaves = padded_leaves(&[b"desert".into(), b"sand".into()]);
|
||||
let r = root::<2>(leaves);
|
||||
|
||||
// --- proof for element at idx 0
|
||||
|
||||
let p0 = path(leaves, 0);
|
||||
let expected0 = vec![PathNode::Right(leaf(b"sand"))];
|
||||
assert_eq!(p0, expected0);
|
||||
assert_eq!(path_root(leaf(b"desert"), &p0), r);
|
||||
|
||||
// --- proof for element at idx 1
|
||||
|
||||
let p1 = path(leaves, 1);
|
||||
let expected1 = vec![PathNode::Left(leaf(b"desert"))];
|
||||
assert_eq!(p1, expected1);
|
||||
assert_eq!(path_root(leaf(b"sand"), &p1), r);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_height_3() {
|
||||
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(leaves, 0);
|
||||
let expected0 = vec![
|
||||
PathNode::Right(leaf(b"sand")),
|
||||
PathNode::Right(node(leaf(b"feels"), leaf(b"warm"))),
|
||||
];
|
||||
assert_eq!(p0, expected0);
|
||||
assert_eq!(path_root(leaf(b"desert"), &p0), r);
|
||||
|
||||
// --- proof for element at idx 1
|
||||
|
||||
let p1 = path(leaves, 1);
|
||||
let expected1 = vec![
|
||||
PathNode::Left(leaf(b"desert")),
|
||||
PathNode::Right(node(leaf(b"feels"), leaf(b"warm"))),
|
||||
];
|
||||
assert_eq!(p1, expected1);
|
||||
assert_eq!(path_root(leaf(b"sand"), &p1), r);
|
||||
|
||||
// --- proof for element at idx 2
|
||||
|
||||
let p2 = path(leaves, 2);
|
||||
let expected2 = vec![
|
||||
PathNode::Right(leaf(b"warm")),
|
||||
PathNode::Left(node(leaf(b"desert"), leaf(b"sand"))),
|
||||
];
|
||||
assert_eq!(p2, expected2);
|
||||
assert_eq!(path_root(leaf(b"feels"), &p2), r);
|
||||
|
||||
// --- proof for element at idx 3
|
||||
|
||||
let p3 = path(leaves, 3);
|
||||
let expected3 = vec![
|
||||
PathNode::Left(leaf(b"feels")),
|
||||
PathNode::Left(node(leaf(b"desert"), leaf(b"sand"))),
|
||||
];
|
||||
assert_eq!(p3, expected3);
|
||||
assert_eq!(path_root(leaf(b"warm"), &p3), r);
|
||||
}
|
||||
}
|
||||
127
emmarin/cl/cl/src/cl/mmr.rs
Normal file
127
emmarin/cl/cl/src/cl/mmr.rs
Normal file
@ -0,0 +1,127 @@
|
||||
use crate::cl::merkle;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct MMR {
|
||||
pub roots: Vec<Root>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Root {
|
||||
pub root: [u8; 32],
|
||||
pub height: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct MMRProof {
|
||||
pub path: Vec<merkle::PathNode>,
|
||||
}
|
||||
|
||||
impl MMR {
|
||||
pub fn new() -> Self {
|
||||
Self { roots: vec![] }
|
||||
}
|
||||
|
||||
pub fn push(&mut self, elem: &[u8]) -> MMRProof {
|
||||
let new_root = Root {
|
||||
root: merkle::leaf(elem),
|
||||
height: 1,
|
||||
};
|
||||
self.roots.push(new_root);
|
||||
|
||||
let mut path = vec![];
|
||||
|
||||
for i in (1..self.roots.len()).rev() {
|
||||
if self.roots[i].height == self.roots[i - 1].height {
|
||||
path.push(merkle::PathNode::Left(self.roots[i - 1].root));
|
||||
|
||||
self.roots[i - 1] = Root {
|
||||
root: merkle::node(self.roots[i - 1].root, self.roots[i].root),
|
||||
height: self.roots[i - 1].height + 1,
|
||||
};
|
||||
|
||||
self.roots.remove(i);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MMRProof { path }
|
||||
}
|
||||
|
||||
pub fn verify_proof(&self, elem: &[u8], proof: &MMRProof) -> bool {
|
||||
let path_len = proof.path.len();
|
||||
let leaf = merkle::leaf(elem);
|
||||
let root = merkle::path_root(leaf, &proof.path);
|
||||
|
||||
for mmr_root in self.roots.iter() {
|
||||
if mmr_root.height == (path_len + 1) as u8 {
|
||||
return mmr_root.root == root;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn commit(&self) -> [u8; 32] {
|
||||
// todo: baggin the peaks
|
||||
let mut hasher = Sha256::new();
|
||||
for mmr_root in self.roots.iter() {
|
||||
hasher.update(mmr_root.root);
|
||||
hasher.update(mmr_root.height.to_le_bytes());
|
||||
}
|
||||
hasher.finalize().into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_mmr_push() {
|
||||
let mut mmr = MMR::new();
|
||||
let proof = mmr.push(b"hello");
|
||||
|
||||
assert_eq!(mmr.roots.len(), 1);
|
||||
assert_eq!(mmr.roots[0].height, 1);
|
||||
assert_eq!(mmr.roots[0].root, merkle::leaf(b"hello"));
|
||||
assert!(mmr.verify_proof(b"hello", &proof));
|
||||
|
||||
let proof = mmr.push(b"world");
|
||||
|
||||
assert_eq!(mmr.roots.len(), 1);
|
||||
assert_eq!(mmr.roots[0].height, 2);
|
||||
assert_eq!(
|
||||
mmr.roots[0].root,
|
||||
merkle::node(merkle::leaf(b"hello"), merkle::leaf(b"world"))
|
||||
);
|
||||
assert!(mmr.verify_proof(b"world", &proof));
|
||||
|
||||
let proof = mmr.push(b"!");
|
||||
|
||||
assert_eq!(mmr.roots.len(), 2);
|
||||
assert_eq!(mmr.roots[0].height, 2);
|
||||
assert_eq!(
|
||||
mmr.roots[0].root,
|
||||
merkle::node(merkle::leaf(b"hello"), merkle::leaf(b"world"))
|
||||
);
|
||||
assert_eq!(mmr.roots[1].height, 1);
|
||||
assert_eq!(mmr.roots[1].root, merkle::leaf(b"!"));
|
||||
assert!(mmr.verify_proof(b"!", &proof));
|
||||
|
||||
let proof = mmr.push(b"!");
|
||||
|
||||
assert_eq!(mmr.roots.len(), 1);
|
||||
assert_eq!(mmr.roots[0].height, 3);
|
||||
assert_eq!(
|
||||
mmr.roots[0].root,
|
||||
merkle::node(
|
||||
merkle::node(merkle::leaf(b"hello"), merkle::leaf(b"world")),
|
||||
merkle::node(merkle::leaf(b"!"), merkle::leaf(b"!"))
|
||||
)
|
||||
);
|
||||
assert!(mmr.verify_proof(b"!", &proof));
|
||||
}
|
||||
}
|
||||
21
emmarin/cl/cl/src/cl/mod.rs
Normal file
21
emmarin/cl/cl/src/cl/mod.rs
Normal file
@ -0,0 +1,21 @@
|
||||
pub mod balance;
|
||||
pub mod bundle;
|
||||
pub mod crypto;
|
||||
pub mod error;
|
||||
pub mod input;
|
||||
pub mod merkle;
|
||||
pub mod mmr;
|
||||
pub mod note;
|
||||
pub mod nullifier;
|
||||
pub mod output;
|
||||
pub mod partial_tx;
|
||||
|
||||
pub use balance::{Balance, BalanceWitness};
|
||||
pub use bundle::Bundle;
|
||||
pub use input::{Input, InputWitness};
|
||||
pub use note::{Constraint, Nonce, NoteCommitment, NoteWitness};
|
||||
pub use nullifier::{Nullifier, NullifierCommitment, NullifierSecret};
|
||||
pub use output::{Output, OutputWitness};
|
||||
pub use partial_tx::{
|
||||
PartialTx, PartialTxInputWitness, PartialTxOutputWitness, PartialTxWitness, PtxRoot,
|
||||
};
|
||||
172
emmarin/cl/cl/src/cl/note.rs
Normal file
172
emmarin/cl/cl/src/cl/note.rs
Normal file
@ -0,0 +1,172 @@
|
||||
use rand::RngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::cl::{balance::Unit, nullifier::NullifierCommitment};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
pub struct Constraint(pub [u8; 32]);
|
||||
|
||||
impl Constraint {
|
||||
pub fn from_vk(constraint_vk: &[u8]) -> Self {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"NOMOS_CL_CONSTRAINT_COMMIT");
|
||||
hasher.update(constraint_vk);
|
||||
let constraint_cm: [u8; 32] = hasher.finalize().into();
|
||||
|
||||
Self(constraint_cm)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn derive_unit(unit: &str) -> Unit {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"NOMOS_CL_UNIT");
|
||||
hasher.update(unit.as_bytes());
|
||||
let unit: Unit = hasher.finalize().into();
|
||||
unit
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
pub struct NoteCommitment(pub [u8; 32]);
|
||||
|
||||
impl NoteCommitment {
|
||||
pub fn as_bytes(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct NoteWitness {
|
||||
pub value: u64,
|
||||
pub unit: Unit,
|
||||
pub constraint: Constraint,
|
||||
pub state: [u8; 32],
|
||||
pub nonce: Nonce,
|
||||
}
|
||||
|
||||
impl NoteWitness {
|
||||
pub fn new(
|
||||
value: u64,
|
||||
unit: Unit,
|
||||
constraint: Constraint,
|
||||
state: [u8; 32],
|
||||
nonce: Nonce,
|
||||
) -> Self {
|
||||
Self {
|
||||
value,
|
||||
unit,
|
||||
constraint,
|
||||
state,
|
||||
nonce,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn basic(value: u64, unit: Unit, rng: impl RngCore) -> Self {
|
||||
let constraint = Constraint([0u8; 32]);
|
||||
let nonce = Nonce::random(rng);
|
||||
Self::new(value, unit, constraint, [0u8; 32], nonce)
|
||||
}
|
||||
|
||||
pub fn stateless(value: u64, unit: Unit, constraint: Constraint, rng: impl RngCore) -> Self {
|
||||
Self::new(value, unit, constraint, [0u8; 32], Nonce::random(rng))
|
||||
}
|
||||
|
||||
pub fn commit(&self, tag: &dyn AsRef<[u8]>, nf_pk: NullifierCommitment) -> NoteCommitment {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"NOMOS_CL_NOTE_CM");
|
||||
hasher.update(tag.as_ref());
|
||||
|
||||
// COMMIT TO BALANCE
|
||||
hasher.update(self.value.to_le_bytes());
|
||||
hasher.update(self.unit);
|
||||
// Important! we don't commit to the balance blinding factor as that may make the notes linkable.
|
||||
|
||||
// COMMIT TO STATE
|
||||
hasher.update(self.state);
|
||||
|
||||
// COMMIT TO CONSTRAINT
|
||||
hasher.update(self.constraint.0);
|
||||
|
||||
// COMMIT TO NONCE
|
||||
hasher.update(self.nonce.as_bytes());
|
||||
|
||||
// COMMIT TO NULLIFIER
|
||||
hasher.update(nf_pk.as_bytes());
|
||||
|
||||
let commit_bytes: [u8; 32] = hasher.finalize().into();
|
||||
NoteCommitment(commit_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Nonce([u8; 32]);
|
||||
|
||||
impl Nonce {
|
||||
pub fn random(mut rng: impl RngCore) -> Self {
|
||||
let mut nonce = [0u8; 32];
|
||||
rng.fill_bytes(&mut nonce);
|
||||
Self(nonce)
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: [u8; 32]) -> Self {
|
||||
Self(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// use super::*;
|
||||
// use crate::cl::nullifier::NullifierSecret;
|
||||
|
||||
// #[test]
|
||||
// fn test_note_commit_permutations() {
|
||||
// let (nmo, eth) = (derive_unit("NMO"), derive_unit("ETH"));
|
||||
|
||||
// let mut rng = rand::thread_rng();
|
||||
|
||||
// let nf_pk = NullifierSecret::random(&mut rng).commit();
|
||||
|
||||
// let reference_note = NoteWitness::basic(32, nmo, &mut rng);
|
||||
|
||||
// // different notes under same nullifier produce different commitments
|
||||
// let mutation_tests = [
|
||||
// NoteWitness {
|
||||
// value: 12,
|
||||
// ..reference_note
|
||||
// },
|
||||
// NoteWitness {
|
||||
// unit: eth,
|
||||
// ..reference_note
|
||||
// },
|
||||
// NoteWitness {
|
||||
// constraint: Constraint::from_vk(&[1u8; 32]),
|
||||
// ..reference_note
|
||||
// },
|
||||
// NoteWitness {
|
||||
// state: [1u8; 32],
|
||||
// ..reference_note
|
||||
// },
|
||||
// NoteWitness {
|
||||
// nonce: Nonce::random(&mut rng),
|
||||
// ..reference_note
|
||||
// },
|
||||
// ];
|
||||
|
||||
// for n in mutation_tests {
|
||||
// assert_ne!(n.commit(nf_pk), reference_note.commit(nf_pk));
|
||||
// }
|
||||
|
||||
// // commitment to same note with different nullifiers produce different commitments
|
||||
|
||||
// let other_nf_pk = NullifierSecret::random(&mut rng).commit();
|
||||
|
||||
// assert_ne!(
|
||||
// reference_note.commit(nf_pk),
|
||||
// reference_note.commit(other_nf_pk)
|
||||
// );
|
||||
// }
|
||||
}
|
||||
161
emmarin/cl/cl/src/cl/nullifier.rs
Normal file
161
emmarin/cl/cl/src/cl/nullifier.rs
Normal file
@ -0,0 +1,161 @@
|
||||
// The Nullifier is used to detect if a note has
|
||||
// already been consumed.
|
||||
|
||||
// The same nullifier secret may be used across multiple
|
||||
// notes to allow users to hold fewer secrets. A note
|
||||
// nonce is used to disambiguate when the same nullifier
|
||||
// secret is used for multiple notes.
|
||||
|
||||
use rand_core::RngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::cl::NoteCommitment;
|
||||
|
||||
// Maintained privately by note holder
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct NullifierSecret(pub [u8; 16]);
|
||||
|
||||
// Nullifier commitment is public information that
|
||||
// can be provided to anyone wishing to transfer
|
||||
// you a note
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct NullifierCommitment([u8; 32]);
|
||||
|
||||
// The nullifier attached to input notes to prove an input has not
|
||||
// already been spent.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct Nullifier([u8; 32]);
|
||||
|
||||
impl NullifierSecret {
|
||||
pub fn random(mut rng: impl RngCore) -> Self {
|
||||
let mut sk = [0u8; 16];
|
||||
rng.fill_bytes(&mut sk);
|
||||
Self(sk)
|
||||
}
|
||||
|
||||
pub const fn zero() -> Self {
|
||||
Self([0u8; 16])
|
||||
}
|
||||
|
||||
pub fn commit(&self) -> NullifierCommitment {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"NOMOS_CL_NULL_COMMIT");
|
||||
hasher.update(self.0);
|
||||
|
||||
let commit_bytes: [u8; 32] = hasher.finalize().into();
|
||||
NullifierCommitment(commit_bytes)
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: [u8; 16]) -> Self {
|
||||
Self(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl NullifierCommitment {
|
||||
pub fn as_bytes(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn hex(&self) -> String {
|
||||
hex::encode(self.0)
|
||||
}
|
||||
|
||||
pub const fn from_bytes(bytes: [u8; 32]) -> Self {
|
||||
Self(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl Nullifier {
|
||||
pub fn new(tag: &dyn AsRef<[u8]>, sk: NullifierSecret, note_cm: NoteCommitment) -> Self {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(tag.as_ref());
|
||||
hasher.update(sk.0);
|
||||
hasher.update(note_cm.0);
|
||||
|
||||
let nf_bytes: [u8; 32] = hasher.finalize().into();
|
||||
Self(nf_bytes)
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// use crate::cl::{note::derive_unit, Constraint, Nonce, NoteWitness};
|
||||
|
||||
// use super::*;
|
||||
|
||||
// #[ignore = "nullifier test vectors not stable yet"]
|
||||
// #[test]
|
||||
// fn test_nullifier_commitment_vectors() {
|
||||
// assert_eq!(
|
||||
// NullifierSecret([0u8; 16]).commit().hex(),
|
||||
// "384318f9864fe57647bac344e2afdc500a672dedb29d2dc63b004e940e4b382a"
|
||||
// );
|
||||
// assert_eq!(
|
||||
// NullifierSecret([1u8; 16]).commit().hex(),
|
||||
// "0fd667e6bb39fbdc35d6265726154b839638ea90bcf4e736953ccf27ca5f870b"
|
||||
// );
|
||||
// assert_eq!(
|
||||
// NullifierSecret([u8::MAX; 16]).commit().hex(),
|
||||
// "1cb78e487eb0b3116389311fdde84cd3f619a4d7f487b29bf5a002eed3784d75"
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_nullifier_same_sk_different_nonce() {
|
||||
// let mut rng = rand::thread_rng();
|
||||
// let sk = NullifierSecret::random(&mut rng);
|
||||
// let note_1 = NoteWitness {
|
||||
// value: 1,
|
||||
// unit: derive_unit("NMO"),
|
||||
// constraint: Constraint::from_vk(&[]),
|
||||
// state: [0u8; 32],
|
||||
// nonce: Nonce::random(&mut rng),
|
||||
// };
|
||||
// let note_2 = NoteWitness {
|
||||
// nonce: Nonce::random(&mut rng),
|
||||
// ..note_1
|
||||
// };
|
||||
|
||||
// let note_cm_1 = note_1.commit(sk.commit());
|
||||
// let note_cm_2 = note_2.commit(sk.commit());
|
||||
|
||||
// let nf_1 = Nullifier::new(sk, note_cm_1);
|
||||
// let nf_2 = Nullifier::new(sk, note_cm_2);
|
||||
|
||||
// assert_ne!(nf_1, nf_2);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_same_sk_same_nonce_different_note() {
|
||||
// let mut rng = rand::thread_rng();
|
||||
|
||||
// let sk = NullifierSecret::random(&mut rng);
|
||||
// let nonce = Nonce::random(&mut rng);
|
||||
|
||||
// let note_1 = NoteWitness {
|
||||
// value: 1,
|
||||
// unit: derive_unit("NMO"),
|
||||
// constraint: Constraint::from_vk(&[]),
|
||||
// state: [0u8; 32],
|
||||
// nonce,
|
||||
// };
|
||||
|
||||
// let note_2 = NoteWitness {
|
||||
// unit: derive_unit("ETH"),
|
||||
// ..note_1
|
||||
// };
|
||||
|
||||
// let note_cm_1 = note_1.commit(sk.commit());
|
||||
// let note_cm_2 = note_2.commit(sk.commit());
|
||||
|
||||
// let nf_1 = Nullifier::new(sk, note_cm_1);
|
||||
// let nf_2 = Nullifier::new(sk, note_cm_2);
|
||||
|
||||
// assert_ne!(nf_1, nf_2);
|
||||
// }
|
||||
}
|
||||
62
emmarin/cl/cl/src/cl/output.rs
Normal file
62
emmarin/cl/cl/src/cl/output.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
cl::{
|
||||
note::{NoteCommitment, NoteWitness},
|
||||
nullifier::NullifierCommitment,
|
||||
NullifierSecret,
|
||||
},
|
||||
zone_layer::notes::ZoneId,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Output {
|
||||
pub zone_id: ZoneId,
|
||||
pub note_comm: NoteCommitment,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct OutputWitness {
|
||||
pub note: NoteWitness,
|
||||
pub nf_pk: NullifierCommitment,
|
||||
pub zone_id: ZoneId,
|
||||
}
|
||||
|
||||
impl OutputWitness {
|
||||
pub fn new(note: NoteWitness, nf_pk: NullifierCommitment, zone_id: ZoneId) -> Self {
|
||||
Self {
|
||||
note,
|
||||
nf_pk,
|
||||
zone_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn public(note: NoteWitness, zone_id: ZoneId) -> Self {
|
||||
let nf_pk = NullifierSecret::zero().commit();
|
||||
Self {
|
||||
note,
|
||||
nf_pk,
|
||||
zone_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn commit_note(&self) -> NoteCommitment {
|
||||
self.note.commit(&self.zone_id, self.nf_pk)
|
||||
}
|
||||
|
||||
pub fn commit(&self) -> Output {
|
||||
Output {
|
||||
zone_id: self.zone_id,
|
||||
note_comm: self.commit_note(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Output {
|
||||
pub fn to_bytes(&self) -> [u8; 64] {
|
||||
let mut bytes = [0u8; 64];
|
||||
bytes[..32].copy_from_slice(&self.zone_id);
|
||||
bytes[32..].copy_from_slice(&self.note_comm.0);
|
||||
bytes
|
||||
}
|
||||
}
|
||||
213
emmarin/cl/cl/src/cl/partial_tx.rs
Normal file
213
emmarin/cl/cl/src/cl/partial_tx.rs
Normal file
@ -0,0 +1,213 @@
|
||||
use rand_core::{CryptoRngCore, RngCore};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::cl::{
|
||||
balance::{Balance, BalanceWitness},
|
||||
input::{Input, InputWitness},
|
||||
merkle,
|
||||
output::{Output, OutputWitness},
|
||||
};
|
||||
|
||||
pub const MAX_INPUTS: usize = 8;
|
||||
pub const MAX_OUTPUTS: usize = 8;
|
||||
|
||||
/// The partial transaction commitment couples an input to a partial transaction.
|
||||
/// Prevents partial tx unbundling.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
pub struct PtxRoot(pub [u8; 32]);
|
||||
|
||||
impl From<[u8; 32]> for PtxRoot {
|
||||
fn from(bytes: [u8; 32]) -> Self {
|
||||
Self(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl PtxRoot {
|
||||
pub fn random(mut rng: impl RngCore) -> Self {
|
||||
let mut sk = [0u8; 32];
|
||||
rng.fill_bytes(&mut sk);
|
||||
Self(sk)
|
||||
}
|
||||
|
||||
pub fn hex(&self) -> String {
|
||||
hex::encode(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PartialTx {
|
||||
pub inputs: Vec<Input>,
|
||||
pub outputs: Vec<Output>,
|
||||
pub balance: Balance,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PartialTxWitness {
|
||||
pub inputs: Vec<InputWitness>,
|
||||
pub outputs: Vec<OutputWitness>,
|
||||
pub balance_blinding: [u8; 16],
|
||||
}
|
||||
|
||||
impl PartialTxWitness {
|
||||
pub fn random(
|
||||
inputs: Vec<InputWitness>,
|
||||
outputs: Vec<OutputWitness>,
|
||||
mut rng: impl CryptoRngCore,
|
||||
) -> Self {
|
||||
Self {
|
||||
inputs,
|
||||
outputs,
|
||||
balance_blinding: BalanceWitness::random_blinding(&mut rng),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn balance(&self) -> BalanceWitness {
|
||||
BalanceWitness::from_ptx(self, self.balance_blinding)
|
||||
}
|
||||
|
||||
pub fn commit(&self) -> PartialTx {
|
||||
PartialTx {
|
||||
inputs: self.inputs.iter().map(InputWitness::commit).collect(),
|
||||
outputs: self.outputs.iter().map(OutputWitness::commit).collect(),
|
||||
balance: self.balance().commit(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn input_witness(&self, idx: usize) -> PartialTxInputWitness {
|
||||
let input_bytes =
|
||||
Vec::from_iter(self.inputs.iter().map(|i| i.commit().to_bytes().to_vec()));
|
||||
let input_merkle_leaves = merkle::padded_leaves::<MAX_INPUTS>(&input_bytes);
|
||||
|
||||
let path = merkle::path(input_merkle_leaves, idx);
|
||||
let input = self.inputs[idx];
|
||||
PartialTxInputWitness { input, path }
|
||||
}
|
||||
|
||||
pub fn output_witness(&self, idx: usize) -> PartialTxOutputWitness {
|
||||
let output_bytes =
|
||||
Vec::from_iter(self.outputs.iter().map(|o| o.commit().to_bytes().to_vec()));
|
||||
let output_merkle_leaves = merkle::padded_leaves::<MAX_OUTPUTS>(&output_bytes);
|
||||
|
||||
let path = merkle::path(output_merkle_leaves, idx);
|
||||
let output = self.outputs[idx];
|
||||
PartialTxOutputWitness { output, path }
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialTx {
|
||||
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);
|
||||
merkle::root::<MAX_INPUTS>(input_merkle_leaves)
|
||||
}
|
||||
|
||||
pub fn output_root(&self) -> [u8; 32] {
|
||||
let output_bytes = Vec::from_iter(
|
||||
self.outputs
|
||||
.iter()
|
||||
.map(Output::to_bytes)
|
||||
.map(Vec::from_iter),
|
||||
);
|
||||
let output_merkle_leaves = merkle::padded_leaves(&output_bytes);
|
||||
merkle::root::<MAX_OUTPUTS>(output_merkle_leaves)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// An input to a partial transaction
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PartialTxInputWitness {
|
||||
pub input: InputWitness,
|
||||
pub path: Vec<merkle::PathNode>,
|
||||
}
|
||||
|
||||
impl PartialTxInputWitness {
|
||||
pub fn input_root(&self) -> [u8; 32] {
|
||||
let leaf = merkle::leaf(&self.input.commit().to_bytes());
|
||||
merkle::path_root(leaf, &self.path)
|
||||
}
|
||||
}
|
||||
|
||||
/// An output to a partial transaction
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PartialTxOutputWitness {
|
||||
pub output: OutputWitness,
|
||||
pub path: Vec<merkle::PathNode>,
|
||||
}
|
||||
|
||||
impl PartialTxOutputWitness {
|
||||
pub fn output_root(&self) -> [u8; 32] {
|
||||
let leaf = merkle::leaf(&self.output.commit().to_bytes());
|
||||
merkle::path_root(leaf, &self.path)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
// use crate::cl::{
|
||||
// balance::UnitBalance,
|
||||
// note::{derive_unit, NoteWitness},
|
||||
// nullifier::NullifierSecret,
|
||||
// };
|
||||
|
||||
// use super::*;
|
||||
|
||||
// #[test]
|
||||
// fn test_partial_tx_balance() {
|
||||
// let (nmo, eth, crv) = (derive_unit("NMO"), derive_unit("ETH"), derive_unit("CRV"));
|
||||
// let mut rng = rand::thread_rng();
|
||||
|
||||
// let nf_a = NullifierSecret::random(&mut rng);
|
||||
// let nf_b = NullifierSecret::random(&mut rng);
|
||||
// let nf_c = NullifierSecret::random(&mut rng);
|
||||
|
||||
// let nmo_10_utxo = OutputWitness::new(NoteWitness::basic(10, nmo, &mut rng), nf_a.commit());
|
||||
// let nmo_10 = InputWitness::from_output(nmo_10_utxo, nf_a);
|
||||
|
||||
// let eth_23_utxo = OutputWitness::new(NoteWitness::basic(23, eth, &mut rng), nf_b.commit());
|
||||
// let eth_23 = InputWitness::from_output(eth_23_utxo, nf_b);
|
||||
|
||||
// let crv_4840 = OutputWitness::new(NoteWitness::basic(4840, crv, &mut rng), nf_c.commit());
|
||||
|
||||
// let ptx_witness = PartialTxWitness {
|
||||
// inputs: vec![nmo_10, eth_23],
|
||||
// outputs: vec![crv_4840],
|
||||
// balance_blinding: BalanceWitness::random_blinding(&mut rng),
|
||||
// };
|
||||
|
||||
// let ptx = ptx_witness.commit();
|
||||
|
||||
// assert_eq!(
|
||||
// ptx.balance,
|
||||
// BalanceWitness {
|
||||
// balances: vec![
|
||||
// UnitBalance {
|
||||
// unit: nmo,
|
||||
// pos: 0,
|
||||
// neg: 10
|
||||
// },
|
||||
// UnitBalance {
|
||||
// unit: eth,
|
||||
// pos: 0,
|
||||
// neg: 23
|
||||
// },
|
||||
// UnitBalance {
|
||||
// unit: crv,
|
||||
// pos: 4840,
|
||||
// neg: 0
|
||||
// },
|
||||
// ],
|
||||
// blinding: ptx_witness.balance_blinding
|
||||
// }
|
||||
// .commit()
|
||||
// );
|
||||
// }
|
||||
}
|
||||
2
emmarin/cl/cl/src/lib.rs
Normal file
2
emmarin/cl/cl/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod cl;
|
||||
pub mod zone_layer;
|
||||
34
emmarin/cl/cl/src/zone_layer/ledger.rs
Normal file
34
emmarin/cl/cl/src/zone_layer/ledger.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use crate::cl::{merkle, mmr::MMR, Nullifier};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const MAX_NULL: usize = 256;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Ledger {
|
||||
cm_root: [u8; 32],
|
||||
nf_root: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct LedgerWitness {
|
||||
pub commitments: MMR,
|
||||
pub nullifiers: Vec<Nullifier>,
|
||||
}
|
||||
|
||||
impl LedgerWitness {
|
||||
pub fn commit(&self) -> Ledger {
|
||||
Ledger {
|
||||
cm_root: self.commitments.commit(),
|
||||
nf_root: self.nf_root(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nf_root(&self) -> [u8; 32] {
|
||||
let bytes = self
|
||||
.nullifiers
|
||||
.iter()
|
||||
.map(|i| i.as_bytes().to_vec())
|
||||
.collect::<Vec<_>>();
|
||||
merkle::root(merkle::padded_leaves::<MAX_NULL>(&bytes))
|
||||
}
|
||||
}
|
||||
3
emmarin/cl/cl/src/zone_layer/mod.rs
Normal file
3
emmarin/cl/cl/src/zone_layer/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod ledger;
|
||||
pub mod notes;
|
||||
pub mod tx;
|
||||
14
emmarin/cl/cl/src/zone_layer/notes.rs
Normal file
14
emmarin/cl/cl/src/zone_layer/notes.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use super::ledger::Ledger;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ZoneNote {
|
||||
pub stf: Stf,
|
||||
pub state: State,
|
||||
pub ledger: Ledger,
|
||||
pub id: [u8; 32],
|
||||
}
|
||||
|
||||
pub type Stf = [u8; 32];
|
||||
pub type ZoneId = [u8; 32];
|
||||
pub type State = [u8; 32];
|
||||
24
emmarin/cl/cl/src/zone_layer/tx.rs
Normal file
24
emmarin/cl/cl/src/zone_layer/tx.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use super::notes::ZoneNote;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct UpdateBundle {
|
||||
pub updates: Vec<ZoneUpdate>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ZoneUpdate {
|
||||
pub old: ZoneNote,
|
||||
pub new: ZoneNote,
|
||||
}
|
||||
|
||||
impl ZoneUpdate {
|
||||
pub fn new(old: ZoneNote, new: ZoneNote) -> Self {
|
||||
assert_eq!(old.id, new.id);
|
||||
Self { old, new }
|
||||
}
|
||||
|
||||
pub fn well_formed(&self) -> bool {
|
||||
self.old.id == self.new.id
|
||||
}
|
||||
}
|
||||
43
emmarin/cl/cl/tests/simple_transfer.rs
Normal file
43
emmarin/cl/cl/tests/simple_transfer.rs
Normal file
@ -0,0 +1,43 @@
|
||||
use cl::{
|
||||
cl::{
|
||||
note::derive_unit, BalanceWitness, InputWitness, NoteWitness, NullifierCommitment,
|
||||
NullifierSecret, OutputWitness, PartialTxWitness,
|
||||
},
|
||||
zone_layer::notes::ZoneId,
|
||||
};
|
||||
|
||||
fn receive_utxo(note: NoteWitness, nf_pk: NullifierCommitment, zone_id: ZoneId) -> OutputWitness {
|
||||
OutputWitness::new(note, nf_pk, zone_id)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_transfer() {
|
||||
let nmo = derive_unit("NMO");
|
||||
let mut rng = rand::thread_rng();
|
||||
let zone_id = [0; 32];
|
||||
|
||||
let sender_nf_sk = NullifierSecret::random(&mut rng);
|
||||
let sender_nf_pk = sender_nf_sk.commit();
|
||||
|
||||
let recipient_nf_pk = NullifierSecret::random(&mut rng).commit();
|
||||
|
||||
// Assume the sender has received an unspent output from somewhere
|
||||
let utxo = receive_utxo(NoteWitness::basic(10, nmo, &mut rng), sender_nf_pk, zone_id);
|
||||
|
||||
// and wants to send 8 NMO to some recipient and return 2 NMO to itself.
|
||||
let recipient_output = OutputWitness::new(
|
||||
NoteWitness::basic(8, nmo, &mut rng),
|
||||
recipient_nf_pk,
|
||||
zone_id,
|
||||
);
|
||||
let change_output =
|
||||
OutputWitness::new(NoteWitness::basic(2, nmo, &mut rng), sender_nf_pk, zone_id);
|
||||
|
||||
let ptx_witness = PartialTxWitness {
|
||||
inputs: vec![InputWitness::from_output(utxo, sender_nf_sk)],
|
||||
outputs: vec![recipient_output, change_output],
|
||||
balance_blinding: BalanceWitness::random_blinding(&mut rng),
|
||||
};
|
||||
|
||||
assert!(ptx_witness.balance().is_zero())
|
||||
}
|
||||
16
emmarin/cl/ledger/Cargo.toml
Normal file
16
emmarin/cl/ledger/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "ledger"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
cl = { path = "../cl" }
|
||||
ledger_proof_statements = { path = "../ledger_proof_statements" }
|
||||
nomos_cl_risc0_proofs = { path = "../risc0_proofs" }
|
||||
ledger_validity_proof = { path = "../ledger_validity_proof" }
|
||||
risc0-zkvm = { version = "1.0", features = ["prove", "metal"] }
|
||||
risc0-groth16 = { version = "1.0" }
|
||||
rand = "0.8.5"
|
||||
rand_core = "0.6.0"
|
||||
thiserror = "1.0.62"
|
||||
sha2 = "0.10"
|
||||
57
emmarin/cl/ledger/src/balance.rs
Normal file
57
emmarin/cl/ledger/src/balance.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use crate::error::{Error, Result};
|
||||
use ledger_proof_statements::balance::{BalancePrivate, BalancePublic};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProvedBalance {
|
||||
pub bundle: BalancePublic,
|
||||
pub risc0_receipt: risc0_zkvm::Receipt,
|
||||
}
|
||||
|
||||
impl ProvedBalance {
|
||||
pub fn prove(balance_witness: &BalancePrivate) -> Result<Self> {
|
||||
//show that the sum of ptx balances is 0
|
||||
let env = risc0_zkvm::ExecutorEnv::builder()
|
||||
.write(&balance_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::BALANCE_ELF, &opts)
|
||||
.map_err(|_| Error::Risc0ProofFailed)?;
|
||||
|
||||
println!(
|
||||
"STARK 'bundle' prover time: {:.2?}, total_cycles: {}",
|
||||
start_t.elapsed(),
|
||||
prove_info.stats.total_cycles
|
||||
);
|
||||
|
||||
let receipt = prove_info.receipt;
|
||||
|
||||
Ok(Self {
|
||||
bundle: receipt.journal.decode()?,
|
||||
risc0_receipt: receipt,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn public(&self) -> Result<ledger_proof_statements::balance::BalancePublic> {
|
||||
Ok(self.risc0_receipt.journal.decode()?)
|
||||
}
|
||||
|
||||
pub fn verify(&self) -> bool {
|
||||
// let Ok(_bundle_public) = self.public() else {
|
||||
// return false;
|
||||
// };
|
||||
|
||||
// Vec::from_iter(self.bundle.partials.iter().map(|ptx| ptx.balance)) == bundle_public.balances
|
||||
// &&
|
||||
self.risc0_receipt
|
||||
.verify(nomos_cl_risc0_proofs::BALANCE_ID)
|
||||
.is_ok()
|
||||
}
|
||||
}
|
||||
75
emmarin/cl/ledger/src/constraint.rs
Normal file
75
emmarin/cl/ledger/src/constraint.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use cl::cl::{Constraint, Nullifier, PtxRoot};
|
||||
use ledger_proof_statements::constraint::ConstraintPublic;
|
||||
|
||||
use crate::error::Result;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConstraintProof {
|
||||
pub risc0_id: [u32; 8],
|
||||
pub risc0_receipt: risc0_zkvm::Receipt,
|
||||
}
|
||||
|
||||
pub fn risc0_constraint(risc0_id: [u32; 8]) -> Constraint {
|
||||
unsafe { Constraint(core::mem::transmute::<[u32; 8], [u8; 32]>(risc0_id)) }
|
||||
}
|
||||
|
||||
impl ConstraintProof {
|
||||
pub fn from_risc0(risc0_id: [u32; 8], risc0_receipt: risc0_zkvm::Receipt) -> Self {
|
||||
Self {
|
||||
risc0_id,
|
||||
risc0_receipt,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn constraint(&self) -> Constraint {
|
||||
risc0_constraint(self.risc0_id)
|
||||
}
|
||||
|
||||
pub fn public(&self) -> Result<ConstraintPublic> {
|
||||
Ok(self.risc0_receipt.journal.decode()?)
|
||||
}
|
||||
|
||||
pub fn verify(&self, expected_public: ConstraintPublic) -> bool {
|
||||
let Ok(public) = self.public() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
expected_public == public && self.risc0_receipt.verify(self.risc0_id).is_ok()
|
||||
}
|
||||
|
||||
pub fn nop_constraint() -> Constraint {
|
||||
risc0_constraint(nomos_cl_risc0_proofs::CONSTRAINT_NOP_ID)
|
||||
}
|
||||
|
||||
pub fn prove_nop(nf: Nullifier, ptx_root: PtxRoot) -> Self {
|
||||
let constraint_public = ConstraintPublic { nf, ptx_root };
|
||||
let env = risc0_zkvm::ExecutorEnv::builder()
|
||||
.write(&constraint_public)
|
||||
.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::CONSTRAINT_NOP_ELF, &opts)
|
||||
.unwrap();
|
||||
|
||||
println!(
|
||||
"STARK 'constraint-nop' prover time: {:.2?}, total_cycles: {}",
|
||||
start_t.elapsed(),
|
||||
prove_info.stats.total_cycles
|
||||
);
|
||||
|
||||
// extract the receipt.
|
||||
let receipt = prove_info.receipt;
|
||||
|
||||
Self::from_risc0(nomos_cl_risc0_proofs::CONSTRAINT_NOP_ID, receipt)
|
||||
}
|
||||
}
|
||||
11
emmarin/cl/ledger/src/error.rs
Normal file
11
emmarin/cl/ledger/src/error.rs
Normal file
@ -0,0 +1,11 @@
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("risc0 failed to serde")]
|
||||
Risc0Serde(#[from] risc0_zkvm::serde::Error),
|
||||
#[error("risc0 failed to prove execution of the zkvm")]
|
||||
Risc0ProofFailed,
|
||||
}
|
||||
100
emmarin/cl/ledger/src/ledger.rs
Normal file
100
emmarin/cl/ledger/src/ledger.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use ledger_proof_statements::{
|
||||
ledger::{LedgerProofPrivate, LedgerProofPublic},
|
||||
ptx::PtxPublic,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
balance::ProvedBalance,
|
||||
constraint::ConstraintProof,
|
||||
error::{Error, Result},
|
||||
partial_tx::ProvedPartialTx,
|
||||
};
|
||||
use cl::zone_layer::{ledger::LedgerWitness, notes::ZoneId};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProvedLedgerTransition {
|
||||
pub public: LedgerProofPublic,
|
||||
pub risc0_receipt: risc0_zkvm::Receipt,
|
||||
}
|
||||
|
||||
// TODO: find a better name
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProvedBundle {
|
||||
pub balance: ProvedBalance,
|
||||
pub ptxs: Vec<ProvedPartialTx>,
|
||||
}
|
||||
|
||||
impl ProvedBundle {
|
||||
fn to_public(&self) -> Vec<PtxPublic> {
|
||||
self.ptxs.iter().map(|p| p.public.clone()).collect()
|
||||
}
|
||||
|
||||
fn proofs(&self) -> Vec<risc0_zkvm::Receipt> {
|
||||
let mut proofs = vec![self.balance.risc0_receipt.clone()];
|
||||
proofs.extend(self.ptxs.iter().map(|p| p.risc0_receipt.clone()));
|
||||
proofs
|
||||
}
|
||||
}
|
||||
|
||||
impl ProvedLedgerTransition {
|
||||
pub fn prove(
|
||||
ledger: LedgerWitness,
|
||||
zone_id: ZoneId,
|
||||
bundles: Vec<ProvedBundle>,
|
||||
constraints: Vec<ConstraintProof>,
|
||||
) -> Result<Self> {
|
||||
let witness = LedgerProofPrivate {
|
||||
bundles: bundles.iter().map(|p| p.to_public()).collect(),
|
||||
ledger,
|
||||
id: zone_id,
|
||||
};
|
||||
|
||||
let mut env = risc0_zkvm::ExecutorEnv::builder();
|
||||
|
||||
for bundle in bundles {
|
||||
for proof in bundle.proofs() {
|
||||
env.add_assumption(proof);
|
||||
}
|
||||
}
|
||||
for covenant in constraints {
|
||||
env.add_assumption(covenant.risc0_receipt);
|
||||
}
|
||||
let env = env.write(&witness).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, ledger_validity_proof::LEDGER_ELF, &opts)
|
||||
.map_err(|e| {
|
||||
eprintln!("{e}");
|
||||
Error::Risc0ProofFailed
|
||||
})?;
|
||||
|
||||
println!(
|
||||
"STARK 'ledger' prover time: {:.2?}, total_cycles: {}",
|
||||
start_t.elapsed(),
|
||||
prove_info.stats.total_cycles
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
public: prove_info
|
||||
.receipt
|
||||
.journal
|
||||
.decode::<LedgerProofPublic>()
|
||||
.unwrap(),
|
||||
risc0_receipt: prove_info.receipt,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn verify(&self) -> bool {
|
||||
self.risc0_receipt
|
||||
.verify(ledger_validity_proof::LEDGER_ID)
|
||||
.is_ok()
|
||||
}
|
||||
}
|
||||
9
emmarin/cl/ledger/src/lib.rs
Normal file
9
emmarin/cl/ledger/src/lib.rs
Normal file
@ -0,0 +1,9 @@
|
||||
pub mod balance;
|
||||
pub mod constraint;
|
||||
pub mod error;
|
||||
pub mod ledger;
|
||||
pub mod partial_tx;
|
||||
pub mod stf;
|
||||
pub mod zone_update;
|
||||
|
||||
pub use constraint::ConstraintProof;
|
||||
59
emmarin/cl/ledger/src/partial_tx.rs
Normal file
59
emmarin/cl/ledger/src/partial_tx.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use ledger_proof_statements::ptx::{PtxPrivate, PtxPublic};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use cl::cl::{merkle, PartialTxWitness};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProvedPartialTx {
|
||||
pub public: PtxPublic,
|
||||
pub risc0_receipt: risc0_zkvm::Receipt,
|
||||
}
|
||||
|
||||
impl ProvedPartialTx {
|
||||
pub fn prove(
|
||||
ptx_witness: PartialTxWitness,
|
||||
input_cm_paths: Vec<Vec<merkle::PathNode>>,
|
||||
cm_roots: Vec<[u8; 32]>,
|
||||
) -> Result<ProvedPartialTx> {
|
||||
let ptx_private = PtxPrivate {
|
||||
ptx: ptx_witness,
|
||||
input_cm_paths,
|
||||
cm_roots: cm_roots.clone(),
|
||||
};
|
||||
|
||||
let env = risc0_zkvm::ExecutorEnv::builder()
|
||||
.write(&ptx_private)
|
||||
.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::PTX_ELF, &opts)
|
||||
.map_err(|_| Error::Risc0ProofFailed)?;
|
||||
|
||||
println!(
|
||||
"STARK 'ptx' prover time: {:.2?}, total_cycles: {}",
|
||||
start_t.elapsed(),
|
||||
prove_info.stats.total_cycles
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
public: prove_info.receipt.journal.decode()?,
|
||||
risc0_receipt: prove_info.receipt,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn verify(&self) -> bool {
|
||||
self.risc0_receipt
|
||||
.verify(nomos_cl_risc0_proofs::PTX_ID)
|
||||
.is_ok()
|
||||
}
|
||||
}
|
||||
63
emmarin/cl/ledger/src/stf.rs
Normal file
63
emmarin/cl/ledger/src/stf.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use cl::zone_layer::notes::Stf;
|
||||
use ledger_proof_statements::stf::StfPublic;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StfProof {
|
||||
pub risc0_id: [u32; 8],
|
||||
pub public: StfPublic,
|
||||
pub risc0_receipt: risc0_zkvm::Receipt,
|
||||
}
|
||||
|
||||
pub fn risc0_constraint(risc0_id: [u32; 8]) -> Stf {
|
||||
// TODO: hash
|
||||
|
||||
unsafe { core::mem::transmute::<[u32; 8], [u8; 32]>(risc0_id) }
|
||||
}
|
||||
|
||||
impl StfProof {
|
||||
pub fn from_risc0(risc0_id: [u32; 8], risc0_receipt: risc0_zkvm::Receipt) -> Self {
|
||||
Self {
|
||||
risc0_id,
|
||||
public: risc0_receipt.journal.decode().unwrap(),
|
||||
risc0_receipt,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stf(&self) -> Stf {
|
||||
risc0_constraint(self.risc0_id)
|
||||
}
|
||||
pub fn verify(&self) -> bool {
|
||||
self.risc0_receipt.verify(self.risc0_id).is_ok()
|
||||
}
|
||||
|
||||
pub fn prove_nop(public: StfPublic) -> Self {
|
||||
let env = risc0_zkvm::ExecutorEnv::builder()
|
||||
.write(&public)
|
||||
.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::STF_NOP_ELF, &opts)
|
||||
.unwrap();
|
||||
|
||||
println!(
|
||||
"STARK 'stf' prover time: {:.2?}, total_cycles: {}",
|
||||
start_t.elapsed(),
|
||||
prove_info.stats.total_cycles
|
||||
);
|
||||
|
||||
let receipt = prove_info.receipt;
|
||||
|
||||
Self {
|
||||
risc0_id: nomos_cl_risc0_proofs::STF_NOP_ID,
|
||||
public,
|
||||
risc0_receipt: receipt,
|
||||
}
|
||||
}
|
||||
}
|
||||
65
emmarin/cl/ledger/src/zone_update.rs
Normal file
65
emmarin/cl/ledger/src/zone_update.rs
Normal file
@ -0,0 +1,65 @@
|
||||
pub use crate::error::{Error, Result};
|
||||
use crate::{ledger::ProvedLedgerTransition, stf::StfProof};
|
||||
use cl::zone_layer::tx::UpdateBundle;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
pub struct ProvedUpdateBundle {
|
||||
pub bundle: UpdateBundle,
|
||||
pub ledger_proofs: Vec<ProvedLedgerTransition>,
|
||||
pub stf_proofs: Vec<StfProof>,
|
||||
}
|
||||
|
||||
impl ProvedUpdateBundle {
|
||||
pub fn verify(&self) -> bool {
|
||||
let mut expected_zones = HashMap::new();
|
||||
let mut actual_zones = HashMap::new();
|
||||
for proof in &self.ledger_proofs {
|
||||
if !proof.verify() {
|
||||
return false;
|
||||
}
|
||||
|
||||
for bundle in &proof.public.cross_bundles {
|
||||
expected_zones.insert(bundle.id, HashSet::from_iter(bundle.zones.clone()));
|
||||
actual_zones
|
||||
.entry(bundle.id)
|
||||
.or_insert_with(|| HashSet::new())
|
||||
.insert(proof.public.id);
|
||||
}
|
||||
}
|
||||
|
||||
println!("{:?} | {:?}", expected_zones, actual_zones);
|
||||
for (bundle, expected) in expected_zones.iter() {
|
||||
if let Some(actual) = actual_zones.get(bundle) {
|
||||
if actual != expected {
|
||||
panic!("{:?} | {:?}", actual, expected);
|
||||
}
|
||||
} else {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
|
||||
for ((update, stf_proof), ledger_proof) in self
|
||||
.bundle
|
||||
.updates
|
||||
.iter()
|
||||
.zip(self.stf_proofs.iter())
|
||||
.zip(self.ledger_proofs.iter())
|
||||
{
|
||||
if !update.well_formed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ledger_proof.public.old_ledger != update.old.ledger
|
||||
|| ledger_proof.public.ledger != update.new.ledger
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if stf_proof.public.old != update.old || stf_proof.public.new != update.new {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
220
emmarin/cl/ledger/tests/simple_transfer.rs
Normal file
220
emmarin/cl/ledger/tests/simple_transfer.rs
Normal file
@ -0,0 +1,220 @@
|
||||
use cl::{
|
||||
cl::{
|
||||
balance::Unit, merkle, mmr::MMR, note::derive_unit, BalanceWitness, InputWitness,
|
||||
NoteWitness, NullifierCommitment, NullifierSecret, OutputWitness, PartialTxWitness,
|
||||
},
|
||||
zone_layer::{
|
||||
ledger::LedgerWitness,
|
||||
notes::{ZoneId, ZoneNote},
|
||||
tx::{UpdateBundle, ZoneUpdate},
|
||||
},
|
||||
};
|
||||
use ledger::{
|
||||
balance::ProvedBalance,
|
||||
constraint::ConstraintProof,
|
||||
ledger::{ProvedBundle, ProvedLedgerTransition},
|
||||
partial_tx::ProvedPartialTx,
|
||||
stf::StfProof,
|
||||
zone_update::ProvedUpdateBundle,
|
||||
};
|
||||
use ledger_proof_statements::{balance::BalancePrivate, stf::StfPublic};
|
||||
use rand_core::CryptoRngCore;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
fn nmo() -> &'static Unit {
|
||||
static NMO: OnceLock<Unit> = OnceLock::new();
|
||||
NMO.get_or_init(|| derive_unit("NMO"))
|
||||
}
|
||||
|
||||
struct User(NullifierSecret);
|
||||
|
||||
impl User {
|
||||
fn random(mut rng: impl CryptoRngCore) -> Self {
|
||||
Self(NullifierSecret::random(&mut rng))
|
||||
}
|
||||
|
||||
fn pk(&self) -> NullifierCommitment {
|
||||
self.0.commit()
|
||||
}
|
||||
|
||||
fn sk(&self) -> NullifierSecret {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
fn receive_utxo(note: NoteWitness, nf_pk: NullifierCommitment, zone_id: ZoneId) -> OutputWitness {
|
||||
OutputWitness::new(note, nf_pk, zone_id)
|
||||
}
|
||||
|
||||
fn cross_transfer_transition(
|
||||
input: InputWitness,
|
||||
input_path: Vec<merkle::PathNode>,
|
||||
to: User,
|
||||
amount: u64,
|
||||
zone_a: ZoneId,
|
||||
zone_b: ZoneId,
|
||||
mut ledger_a: LedgerWitness,
|
||||
mut ledger_b: LedgerWitness,
|
||||
) -> (ProvedLedgerTransition, ProvedLedgerTransition) {
|
||||
let mut rng = rand::thread_rng();
|
||||
assert!(amount <= input.note.value);
|
||||
let change = input.note.value - amount;
|
||||
let transfer = OutputWitness::new(
|
||||
NoteWitness::basic(amount, *nmo(), &mut rng),
|
||||
to.pk(),
|
||||
zone_b,
|
||||
);
|
||||
let change = OutputWitness::new(
|
||||
NoteWitness::basic(change, *nmo(), &mut rng),
|
||||
input.nf_sk.commit(),
|
||||
zone_a,
|
||||
);
|
||||
|
||||
// Construct the ptx consuming the input and producing the two outputs.
|
||||
let ptx_witness = PartialTxWitness {
|
||||
inputs: vec![input],
|
||||
outputs: vec![transfer, change],
|
||||
balance_blinding: BalanceWitness::random_blinding(&mut rng),
|
||||
};
|
||||
let proved_ptx = ProvedPartialTx::prove(
|
||||
ptx_witness.clone(),
|
||||
vec![input_path],
|
||||
vec![ledger_a.commitments.roots[0].root],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let balance = ProvedBalance::prove(&BalancePrivate {
|
||||
balances: vec![ptx_witness.balance()],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let zone_tx = ProvedBundle {
|
||||
ptxs: vec![proved_ptx.clone()],
|
||||
balance,
|
||||
};
|
||||
|
||||
// Prove the constraints for alices input (she uses the no-op constraint)
|
||||
let constraint_proof =
|
||||
ConstraintProof::prove_nop(input.nullifier(), proved_ptx.public.ptx.root());
|
||||
|
||||
let ledger_a_transition = ProvedLedgerTransition::prove(
|
||||
ledger_a.clone(),
|
||||
zone_a,
|
||||
vec![zone_tx.clone()],
|
||||
vec![constraint_proof],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let ledger_b_transition =
|
||||
ProvedLedgerTransition::prove(ledger_b.clone(), zone_b, vec![zone_tx], vec![]).unwrap();
|
||||
|
||||
ledger_a.commitments.push(&change.commit_note().0);
|
||||
ledger_a.nullifiers.push(input.nullifier());
|
||||
|
||||
ledger_b.commitments.push(&transfer.commit_note().0);
|
||||
|
||||
assert_eq!(ledger_a_transition.public.ledger, ledger_a.commit());
|
||||
assert_eq!(ledger_b_transition.public.ledger, ledger_b.commit());
|
||||
|
||||
(ledger_a_transition, ledger_b_transition)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zone_update_cross() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let zone_a_id = [0; 32];
|
||||
let zone_b_id = [1; 32];
|
||||
|
||||
// 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(
|
||||
NoteWitness::stateless(10, *nmo(), ConstraintProof::nop_constraint(), &mut rng),
|
||||
alice.pk(),
|
||||
zone_a_id,
|
||||
);
|
||||
|
||||
let alice_input = InputWitness::from_output(utxo, alice.sk());
|
||||
|
||||
let mut mmr = MMR::new();
|
||||
let input_cm_path = mmr.push(&utxo.commit_note().0).path;
|
||||
|
||||
let ledger_a = LedgerWitness {
|
||||
commitments: mmr,
|
||||
nullifiers: vec![],
|
||||
};
|
||||
|
||||
let ledger_b = LedgerWitness {
|
||||
commitments: MMR::new(),
|
||||
nullifiers: vec![],
|
||||
};
|
||||
|
||||
let zone_a_old = ZoneNote {
|
||||
id: zone_a_id,
|
||||
state: [0; 32],
|
||||
ledger: ledger_a.commit(),
|
||||
stf: [0; 32],
|
||||
};
|
||||
let zone_b_old = ZoneNote {
|
||||
id: zone_b_id,
|
||||
state: [0; 32],
|
||||
ledger: ledger_b.commit(),
|
||||
stf: [0; 32],
|
||||
};
|
||||
|
||||
let (ledger_a_transition, ledger_b_transition) = cross_transfer_transition(
|
||||
alice_input,
|
||||
input_cm_path,
|
||||
bob,
|
||||
8,
|
||||
zone_a_id,
|
||||
zone_b_id,
|
||||
ledger_a,
|
||||
ledger_b,
|
||||
);
|
||||
|
||||
let zone_a_new = ZoneNote {
|
||||
ledger: ledger_a_transition.public.ledger,
|
||||
..zone_a_old
|
||||
};
|
||||
|
||||
let zone_b_new = ZoneNote {
|
||||
ledger: ledger_b_transition.public.ledger,
|
||||
..zone_b_old
|
||||
};
|
||||
|
||||
let stf_proof_a = StfProof::prove_nop(StfPublic {
|
||||
old: zone_a_old,
|
||||
new: zone_a_new,
|
||||
});
|
||||
|
||||
let stf_proof_b = StfProof::prove_nop(StfPublic {
|
||||
old: zone_b_old,
|
||||
new: zone_b_new,
|
||||
});
|
||||
|
||||
let update_bundle = UpdateBundle {
|
||||
updates: vec![
|
||||
ZoneUpdate {
|
||||
old: zone_a_old,
|
||||
new: zone_a_new,
|
||||
},
|
||||
ZoneUpdate {
|
||||
old: zone_b_old,
|
||||
new: zone_b_new,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let proved_bundle = ProvedUpdateBundle {
|
||||
bundle: update_bundle,
|
||||
ledger_proofs: vec![ledger_a_transition, ledger_b_transition],
|
||||
stf_proofs: vec![stf_proof_a, stf_proof_b],
|
||||
};
|
||||
|
||||
assert!(proved_bundle.verify());
|
||||
}
|
||||
8
emmarin/cl/ledger_proof_statements/Cargo.toml
Normal file
8
emmarin/cl/ledger_proof_statements/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "ledger_proof_statements"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
cl = { path = "../cl" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
12
emmarin/cl/ledger_proof_statements/src/balance.rs
Normal file
12
emmarin/cl/ledger_proof_statements/src/balance.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use cl::cl::{Balance, BalanceWitness};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct BalancePublic {
|
||||
pub balances: Vec<Balance>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct BalancePrivate {
|
||||
pub balances: Vec<BalanceWitness>,
|
||||
}
|
||||
8
emmarin/cl/ledger_proof_statements/src/constraint.rs
Normal file
8
emmarin/cl/ledger_proof_statements/src/constraint.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use cl::cl::{Nullifier, PtxRoot};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ConstraintPublic {
|
||||
pub nf: Nullifier,
|
||||
pub ptx_root: PtxRoot,
|
||||
}
|
||||
29
emmarin/cl/ledger_proof_statements/src/ledger.rs
Normal file
29
emmarin/cl/ledger_proof_statements/src/ledger.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use crate::ptx::PtxPublic;
|
||||
use cl::cl::{bundle::BundleId, Output};
|
||||
use cl::zone_layer::{
|
||||
ledger::{Ledger, LedgerWitness},
|
||||
notes::ZoneId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct LedgerProofPublic {
|
||||
pub old_ledger: Ledger,
|
||||
pub ledger: Ledger,
|
||||
pub id: ZoneId,
|
||||
pub cross_bundles: Vec<CrossZoneBundle>,
|
||||
pub outputs: Vec<Output>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct LedgerProofPrivate {
|
||||
pub ledger: LedgerWitness,
|
||||
pub id: ZoneId,
|
||||
pub bundles: Vec<Vec<PtxPublic>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct CrossZoneBundle {
|
||||
pub id: BundleId,
|
||||
pub zones: Vec<ZoneId>,
|
||||
}
|
||||
5
emmarin/cl/ledger_proof_statements/src/lib.rs
Normal file
5
emmarin/cl/ledger_proof_statements/src/lib.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod balance;
|
||||
pub mod constraint;
|
||||
pub mod ledger;
|
||||
pub mod ptx;
|
||||
pub mod stf;
|
||||
15
emmarin/cl/ledger_proof_statements/src/ptx.rs
Normal file
15
emmarin/cl/ledger_proof_statements/src/ptx.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use cl::cl::{merkle, PartialTx, PartialTxWitness};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PtxPublic {
|
||||
pub ptx: PartialTx,
|
||||
pub cm_roots: Vec<[u8; 32]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PtxPrivate {
|
||||
pub ptx: PartialTxWitness,
|
||||
pub input_cm_paths: Vec<Vec<merkle::PathNode>>,
|
||||
pub cm_roots: Vec<[u8; 32]>,
|
||||
}
|
||||
8
emmarin/cl/ledger_proof_statements/src/stf.rs
Normal file
8
emmarin/cl/ledger_proof_statements/src/stf.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use cl::zone_layer::notes::ZoneNote;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub struct StfPublic {
|
||||
pub old: ZoneNote,
|
||||
pub new: ZoneNote,
|
||||
}
|
||||
11
emmarin/cl/ledger_validity_proof/Cargo.toml
Normal file
11
emmarin/cl/ledger_validity_proof/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "ledger_validity_proof"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[build-dependencies]
|
||||
risc0-build = { version = "1.0" }
|
||||
|
||||
[package.metadata.risc0]
|
||||
methods = [ "ledger"]
|
||||
|
||||
3
emmarin/cl/ledger_validity_proof/build.rs
Normal file
3
emmarin/cl/ledger_validity_proof/build.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
risc0_build::embed_methods();
|
||||
}
|
||||
19
emmarin/cl/ledger_validity_proof/ledger/Cargo.toml
Normal file
19
emmarin/cl/ledger_validity_proof/ledger/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "ledger"
|
||||
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" }
|
||||
ledger_proof_statements = { path = "../../ledger_proof_statements" }
|
||||
nomos_cl_risc0_proofs = { path = "../../risc0_proofs" }
|
||||
|
||||
[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" }
|
||||
109
emmarin/cl/ledger_validity_proof/ledger/src/main.rs
Normal file
109
emmarin/cl/ledger_validity_proof/ledger/src/main.rs
Normal file
@ -0,0 +1,109 @@
|
||||
use cl::{
|
||||
cl::{Bundle, Output},
|
||||
zone_layer::{ledger::LedgerWitness, notes::ZoneId},
|
||||
};
|
||||
use ledger_proof_statements::{
|
||||
balance::BalancePublic,
|
||||
constraint::ConstraintPublic,
|
||||
ledger::{CrossZoneBundle, LedgerProofPrivate, LedgerProofPublic},
|
||||
ptx::PtxPublic,
|
||||
};
|
||||
use risc0_zkvm::{guest::env, serde};
|
||||
|
||||
fn main() {
|
||||
let LedgerProofPrivate {
|
||||
mut ledger,
|
||||
id,
|
||||
bundles,
|
||||
} = env::read();
|
||||
|
||||
let old_ledger = ledger.commit();
|
||||
let mut cross_bundles = vec![];
|
||||
let mut outputs = vec![];
|
||||
|
||||
let roots = ledger
|
||||
.commitments
|
||||
.roots
|
||||
.iter()
|
||||
.map(|r| r.root)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for bundle in bundles {
|
||||
let balance_public = BalancePublic {
|
||||
balances: bundle.iter().map(|ptx| ptx.ptx.balance).collect::<Vec<_>>(),
|
||||
};
|
||||
// verify bundle is balanced
|
||||
env::verify(
|
||||
nomos_cl_risc0_proofs::BALANCE_ID,
|
||||
&serde::to_vec(&balance_public).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
for ptx in &bundle {
|
||||
let (new_ledger, ptx_outputs) = process_ptx(ledger, ptx, id, &roots);
|
||||
ledger = new_ledger;
|
||||
outputs.extend(ptx_outputs);
|
||||
}
|
||||
|
||||
let bundle = Bundle {
|
||||
partials: bundle.into_iter().map(|ptx| ptx.ptx).collect(),
|
||||
};
|
||||
let zones = bundle.zones();
|
||||
if zones.len() > 1 {
|
||||
cross_bundles.push(CrossZoneBundle {
|
||||
id: bundle.id(),
|
||||
zones: zones.into_iter().collect(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
env::commit(&LedgerProofPublic {
|
||||
old_ledger,
|
||||
ledger: ledger.commit(),
|
||||
id,
|
||||
cross_bundles,
|
||||
outputs,
|
||||
});
|
||||
}
|
||||
|
||||
fn process_ptx(
|
||||
mut ledger: LedgerWitness,
|
||||
ptx: &PtxPublic,
|
||||
zone_id: ZoneId,
|
||||
roots: &[[u8; 32]],
|
||||
) -> (LedgerWitness, Vec<Output>) {
|
||||
// always verify the ptx to ensure outputs were derived with the correct zone id
|
||||
env::verify(nomos_cl_risc0_proofs::PTX_ID, &serde::to_vec(&ptx).unwrap()).unwrap();
|
||||
|
||||
let cm_roots = &ptx.cm_roots;
|
||||
let ptx = &ptx.ptx;
|
||||
|
||||
let mut outputs = vec![];
|
||||
|
||||
for (input, input_cm_root) in ptx.inputs.iter().zip(cm_roots) {
|
||||
if input.zone_id == zone_id {
|
||||
assert!(roots.contains(input_cm_root));
|
||||
assert!(!ledger.nullifiers.contains(&input.nullifier));
|
||||
ledger.nullifiers.push(input.nullifier);
|
||||
|
||||
env::verify(
|
||||
input.constraint.0,
|
||||
&serde::to_vec(&ConstraintPublic {
|
||||
ptx_root: ptx.root(),
|
||||
nf: input.nullifier,
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
for output in &ptx.outputs {
|
||||
if output.zone_id == zone_id {
|
||||
ledger.commitments.push(&output.note_comm.0);
|
||||
outputs.push(*output);
|
||||
}
|
||||
}
|
||||
|
||||
(ledger, outputs)
|
||||
}
|
||||
1
emmarin/cl/ledger_validity_proof/src/lib.rs
Normal file
1
emmarin/cl/ledger_validity_proof/src/lib.rs
Normal file
@ -0,0 +1 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/methods.rs"));
|
||||
11
emmarin/cl/risc0_proofs/Cargo.toml
Normal file
11
emmarin/cl/risc0_proofs/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "nomos_cl_risc0_proofs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[build-dependencies]
|
||||
risc0-build = { version = "1.0" }
|
||||
|
||||
[package.metadata.risc0]
|
||||
methods = ["balance", "constraint_nop", "ptx", "stf_nop"]
|
||||
|
||||
19
emmarin/cl/risc0_proofs/balance/Cargo.toml
Normal file
19
emmarin/cl/risc0_proofs/balance/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "balance"
|
||||
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" }
|
||||
ledger_proof_statements = { path = "../../ledger_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" }
|
||||
24
emmarin/cl/risc0_proofs/balance/src/main.rs
Normal file
24
emmarin/cl/risc0_proofs/balance/src/main.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use cl::cl::BalanceWitness;
|
||||
/// 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 balance_private: ledger_proof_statements::balance::BalancePrivate = env::read();
|
||||
|
||||
let balance_public = ledger_proof_statements::balance::BalancePublic {
|
||||
balances: Vec::from_iter(balance_private.balances.iter().map(|b| b.commit())),
|
||||
};
|
||||
|
||||
assert!(BalanceWitness::combine(balance_private.balances, [0u8; 16]).is_zero());
|
||||
|
||||
env::commit(&balance_public);
|
||||
}
|
||||
3
emmarin/cl/risc0_proofs/build.rs
Normal file
3
emmarin/cl/risc0_proofs/build.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
risc0_build::embed_methods();
|
||||
}
|
||||
19
emmarin/cl/risc0_proofs/constraint_nop/Cargo.toml
Normal file
19
emmarin/cl/risc0_proofs/constraint_nop/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "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" }
|
||||
ledger_proof_statements = { path = "../../ledger_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" }
|
||||
8
emmarin/cl/risc0_proofs/constraint_nop/src/main.rs
Normal file
8
emmarin/cl/risc0_proofs/constraint_nop/src/main.rs
Normal file
@ -0,0 +1,8 @@
|
||||
/// Constraint No-op Proof
|
||||
use ledger_proof_statements::constraint::ConstraintPublic;
|
||||
use risc0_zkvm::guest::env;
|
||||
|
||||
fn main() {
|
||||
let public: ConstraintPublic = env::read();
|
||||
env::commit(&public);
|
||||
}
|
||||
19
emmarin/cl/risc0_proofs/ptx/Cargo.toml
Normal file
19
emmarin/cl/risc0_proofs/ptx/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "ptx"
|
||||
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" }
|
||||
ledger_proof_statements = { path = "../../ledger_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" }
|
||||
28
emmarin/cl/risc0_proofs/ptx/src/main.rs
Normal file
28
emmarin/cl/risc0_proofs/ptx/src/main.rs
Normal file
@ -0,0 +1,28 @@
|
||||
/// Input Proof
|
||||
use cl::cl::merkle;
|
||||
use ledger_proof_statements::ptx::{PtxPrivate, PtxPublic};
|
||||
use risc0_zkvm::guest::env;
|
||||
|
||||
fn main() {
|
||||
let PtxPrivate {
|
||||
ptx,
|
||||
input_cm_paths,
|
||||
cm_roots,
|
||||
} = env::read();
|
||||
|
||||
assert_eq!(ptx.inputs.len(), input_cm_paths.len());
|
||||
for ((input, cm_path), cm_root) in ptx.inputs.iter().zip(input_cm_paths).zip(&cm_roots) {
|
||||
let note_cm = input.note_commitment();
|
||||
let cm_leaf = merkle::leaf(note_cm.as_bytes());
|
||||
assert_eq!(*cm_root, merkle::path_root(cm_leaf, &cm_path));
|
||||
}
|
||||
|
||||
for output in ptx.outputs.iter() {
|
||||
assert!(output.note.value > 0);
|
||||
}
|
||||
|
||||
env::commit(&PtxPublic {
|
||||
ptx: ptx.commit(),
|
||||
cm_roots,
|
||||
});
|
||||
}
|
||||
1
emmarin/cl/risc0_proofs/src/lib.rs
Normal file
1
emmarin/cl/risc0_proofs/src/lib.rs
Normal file
@ -0,0 +1 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/methods.rs"));
|
||||
19
emmarin/cl/risc0_proofs/stf_nop/Cargo.toml
Normal file
19
emmarin/cl/risc0_proofs/stf_nop/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "stf_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" }
|
||||
ledger_proof_statements = { path = "../../ledger_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" }
|
||||
8
emmarin/cl/risc0_proofs/stf_nop/src/main.rs
Normal file
8
emmarin/cl/risc0_proofs/stf_nop/src/main.rs
Normal file
@ -0,0 +1,8 @@
|
||||
/// Constraint No-op Proof
|
||||
use ledger_proof_statements::stf::StfPublic;
|
||||
use risc0_zkvm::guest::env;
|
||||
|
||||
fn main() {
|
||||
let public: StfPublic = env::read();
|
||||
env::commit(&public);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user