mirror of
https://github.com/logos-blockchain/logos-blockchain-pocs.git
synced 2026-02-25 15:33:10 +00:00
Merge pull request #34 from logos-co/goas/ptx-proof
goas: aggregate input/output proofs into a single ptx proof
This commit is contained in:
commit
8aba1c8e68
@ -217,7 +217,10 @@ pub fn prove_user_atomic_transfer(atomic_transfer: UserAtomicTransfer) -> ledger
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use cl::{note::unit_point, NoteWitness, NullifierNonce, OutputWitness, PartialTxWitness};
|
||||
use cl::{
|
||||
note::unit_point, BalanceWitness, NoteWitness, NullifierNonce, OutputWitness,
|
||||
PartialTxWitness,
|
||||
};
|
||||
use common::{BoundTx, Deposit, Withdraw};
|
||||
use goas_proof_statements::user_note::UserIntent;
|
||||
use ledger_proof_statements::death_constraint::DeathConstraintPublic;
|
||||
@ -270,6 +273,7 @@ mod tests {
|
||||
zone_start.fund_input_witness(),
|
||||
],
|
||||
outputs: vec![zone_end.state_note, zone_end.fund_note],
|
||||
balance_blinding: BalanceWitness::random(&mut rng),
|
||||
};
|
||||
|
||||
let txs = vec![
|
||||
@ -293,12 +297,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_prove_zone_fund_constraint() {
|
||||
let zone =
|
||||
ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([]), &mut rand::thread_rng());
|
||||
let mut rng = rand::thread_rng();
|
||||
let zone = ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([]), &mut rng);
|
||||
|
||||
let ptx = PartialTxWitness {
|
||||
inputs: vec![zone.fund_input_witness()],
|
||||
outputs: vec![zone.state_note],
|
||||
balance_blinding: BalanceWitness::random(&mut rng),
|
||||
};
|
||||
|
||||
let proof =
|
||||
@ -344,6 +349,7 @@ mod tests {
|
||||
let ptx = PartialTxWitness {
|
||||
inputs: vec![user_note],
|
||||
outputs: vec![zone_a.state_note, zone_b.state_note],
|
||||
balance_blinding: BalanceWitness::random(&mut rng),
|
||||
};
|
||||
|
||||
let user_atomic_transfer = UserAtomicTransfer {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use cl::{BundleWitness, NoteWitness, NullifierNonce};
|
||||
use cl::{BalanceWitness, BundleWitness, NoteWitness, NullifierNonce};
|
||||
use common::{new_account, BoundTx, Deposit, SignedBoundTx, Tx, Withdraw};
|
||||
use executor::ZoneNotes;
|
||||
use goas_proof_statements::user_note::{UserAtomicTransfer, UserIntent};
|
||||
@ -43,6 +43,7 @@ fn test_atomic_transfer() {
|
||||
let user_ptx = cl::PartialTxWitness {
|
||||
inputs: vec![],
|
||||
outputs: vec![alice_intent_out],
|
||||
balance_blinding: BalanceWitness::random(&mut rng),
|
||||
};
|
||||
|
||||
let zone_a_end = zone_a_start
|
||||
@ -68,6 +69,7 @@ fn test_atomic_transfer() {
|
||||
zone_b_end.state_note,
|
||||
zone_b_end.fund_note,
|
||||
],
|
||||
balance_blinding: BalanceWitness::random(&mut rng),
|
||||
};
|
||||
|
||||
let signed_withdraw = SignedBoundTx::sign(
|
||||
@ -165,7 +167,7 @@ fn test_atomic_transfer() {
|
||||
|
||||
let bundle_witness = BundleWitness {
|
||||
balance_blinding: cl::BalanceWitness(
|
||||
user_ptx.balance_blinding().0 + atomic_transfer_ptx.balance_blinding().0,
|
||||
user_ptx.balance_blinding.0 + atomic_transfer_ptx.balance_blinding.0,
|
||||
),
|
||||
};
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use cl::{NoteWitness, NullifierSecret};
|
||||
use cl::{BalanceWitness, NoteWitness, NullifierSecret};
|
||||
use common::{new_account, BoundTx, SignedBoundTx, StateWitness, Tx, ZONE_CL_FUNDS_UNIT};
|
||||
use executor::ZoneNotes;
|
||||
use ledger::death_constraint::DeathProof;
|
||||
@ -22,7 +22,7 @@ fn test_deposit() {
|
||||
|
||||
let zone_end = zone_start.clone().run([Tx::Deposit(deposit)]);
|
||||
|
||||
let alice_deposit = cl::InputWitness::random(
|
||||
let alice_deposit = cl::InputWitness::from_output(
|
||||
cl::OutputWitness::random(
|
||||
NoteWitness::stateless(
|
||||
78,
|
||||
@ -33,12 +33,12 @@ fn test_deposit() {
|
||||
&mut rng,
|
||||
),
|
||||
alice_cl_sk,
|
||||
&mut rng,
|
||||
);
|
||||
|
||||
let deposit_ptx = cl::PartialTxWitness {
|
||||
inputs: vec![zone_start.state_input_witness(), alice_deposit],
|
||||
outputs: vec![zone_end.state_note, zone_end.fund_note],
|
||||
balance_blinding: BalanceWitness::random(&mut rng),
|
||||
};
|
||||
|
||||
let signed_deposit = SignedBoundTx::sign(
|
||||
@ -77,10 +77,7 @@ fn test_deposit() {
|
||||
|
||||
assert!(deposit_proof.verify());
|
||||
|
||||
assert_eq!(
|
||||
deposit_proof.outputs[0].output,
|
||||
zone_end.state_note.commit()
|
||||
);
|
||||
assert_eq!(deposit_proof.ptx.outputs[0], zone_end.state_note.commit());
|
||||
assert_eq!(
|
||||
zone_end.state_note.note.state,
|
||||
StateWitness {
|
||||
@ -92,7 +89,7 @@ fn test_deposit() {
|
||||
.0
|
||||
);
|
||||
assert_eq!(
|
||||
deposit_ptx.commit().balance(),
|
||||
cl::Balance::zero(deposit_ptx.balance_blinding())
|
||||
deposit_ptx.commit().balance,
|
||||
cl::Balance::zero(deposit_ptx.balance_blinding)
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use cl::{NoteWitness, NullifierSecret};
|
||||
use cl::{BalanceWitness, NoteWitness, NullifierSecret};
|
||||
use common::{new_account, BoundTx, SignedBoundTx, StateWitness, Tx, ZONE_CL_FUNDS_UNIT};
|
||||
use executor::ZoneNotes;
|
||||
use ledger::death_constraint::DeathProof;
|
||||
@ -16,14 +16,13 @@ fn test_withdrawal() {
|
||||
let zone_start =
|
||||
ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([(alice_vk, 100)]), &mut rng);
|
||||
|
||||
let alice_intent = cl::InputWitness::random(
|
||||
let alice_intent = cl::InputWitness::from_output(
|
||||
cl::OutputWitness::random(
|
||||
NoteWitness::stateless(1, *ZONE_CL_FUNDS_UNIT, DeathProof::nop_constraint()), // TODO, intent should be in the death constraint
|
||||
alice_cl_sk.commit(),
|
||||
&mut rng,
|
||||
),
|
||||
alice_cl_sk,
|
||||
&mut rng,
|
||||
);
|
||||
|
||||
let withdraw = common::Withdraw {
|
||||
@ -50,6 +49,7 @@ fn test_withdrawal() {
|
||||
alice_intent,
|
||||
],
|
||||
outputs: vec![zone_end.state_note, zone_end.fund_note, alice_withdrawal],
|
||||
balance_blinding: BalanceWitness::random(&mut rng),
|
||||
};
|
||||
|
||||
let signed_withdraw = SignedBoundTx::sign(
|
||||
@ -97,10 +97,7 @@ fn test_withdrawal() {
|
||||
|
||||
assert!(withdraw_proof.verify());
|
||||
|
||||
assert_eq!(
|
||||
withdraw_proof.outputs[0].output,
|
||||
zone_end.state_note.commit()
|
||||
);
|
||||
assert_eq!(withdraw_proof.ptx.outputs[0], zone_end.state_note.commit());
|
||||
assert_eq!(
|
||||
zone_end.state_note.note.state,
|
||||
StateWitness {
|
||||
|
||||
@ -32,12 +32,6 @@ fn validate_zone_transition(
|
||||
// nullifier secret is propagated
|
||||
assert_eq!(in_note.input.nf_sk.commit(), out_note.output.nf_pk);
|
||||
|
||||
// balance blinding is propagated
|
||||
assert_eq!(
|
||||
in_note.input.balance_blinding,
|
||||
out_note.output.balance_blinding
|
||||
);
|
||||
|
||||
// the nonce is correctly evolved
|
||||
assert_eq!(in_note.input.evolved_nonce(b"STATE_NONCE"), out_note.output.nonce);
|
||||
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
use curve25519_dalek::{ristretto::RistrettoPoint, Scalar};
|
||||
use curve25519_dalek::{
|
||||
constants::RISTRETTO_BASEPOINT_POINT, ristretto::RistrettoPoint, traits::VartimeMultiscalarMul,
|
||||
Scalar,
|
||||
};
|
||||
use rand_core::CryptoRngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -42,8 +45,33 @@ impl BalanceWitness {
|
||||
Self::new(Scalar::random(&mut rng))
|
||||
}
|
||||
|
||||
pub fn commit(&self, note: &NoteWitness) -> Balance {
|
||||
Balance(balance(note.value, note.unit, self.0))
|
||||
pub fn commit<'a>(
|
||||
&self,
|
||||
inputs: impl IntoIterator<Item = &'a NoteWitness>,
|
||||
outputs: impl IntoIterator<Item = &'a NoteWitness>,
|
||||
) -> Balance {
|
||||
let (input_points, input_scalars): (Vec<_>, Vec<_>) = inputs
|
||||
.into_iter()
|
||||
.map(|i| (i.unit, -Scalar::from(i.value)))
|
||||
.unzip();
|
||||
|
||||
let (output_points, output_scalars): (Vec<_>, Vec<_>) = outputs
|
||||
.into_iter()
|
||||
.map(|o| (o.unit, Scalar::from(o.value)))
|
||||
.unzip();
|
||||
|
||||
let points = input_points
|
||||
.into_iter()
|
||||
.chain(output_points)
|
||||
.chain([RISTRETTO_BASEPOINT_POINT]);
|
||||
let scalars = input_scalars
|
||||
.into_iter()
|
||||
.chain(output_scalars)
|
||||
.chain([self.0]);
|
||||
|
||||
let blinded_balance = RistrettoPoint::vartime_multiscalar_mul(scalars, points);
|
||||
|
||||
Balance(blinded_balance)
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,8 +94,8 @@ mod test {
|
||||
let mut rng = rand::thread_rng();
|
||||
let b = BalanceWitness::random(&mut rng);
|
||||
assert_eq!(
|
||||
b.commit(&NoteWitness::basic(0, nmo)),
|
||||
b.commit(&NoteWitness::basic(0, eth)),
|
||||
b.commit([&NoteWitness::basic(0, nmo)], []),
|
||||
b.commit([&NoteWitness::basic(0, eth)], []),
|
||||
);
|
||||
}
|
||||
|
||||
@ -83,15 +111,15 @@ mod test {
|
||||
|
||||
let note = NoteWitness::basic(10, nmo);
|
||||
|
||||
let a = bal_a.commit(¬e);
|
||||
let b = bal_b.commit(¬e);
|
||||
let a = bal_a.commit([¬e], []);
|
||||
let b = bal_b.commit([¬e], []);
|
||||
|
||||
assert_ne!(a, b);
|
||||
|
||||
let diff_note = NoteWitness::basic(0, nmo);
|
||||
assert_eq!(
|
||||
a.0 - b.0,
|
||||
BalanceWitness::new(r_a - r_b).commit(&diff_note).0
|
||||
BalanceWitness::new(r_a - r_b).commit([&diff_note], []).0
|
||||
);
|
||||
}
|
||||
|
||||
@ -104,7 +132,7 @@ mod test {
|
||||
|
||||
let nmo = NoteWitness::basic(10, nmo);
|
||||
let eth = NoteWitness::basic(10, eth);
|
||||
assert_ne!(b.commit(&nmo), b.commit(ð));
|
||||
assert_ne!(b.commit([&nmo], []), b.commit([ð], []));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -123,14 +151,14 @@ mod test {
|
||||
|
||||
// Values of same unit are homomorphic
|
||||
assert_eq!(
|
||||
(b1.commit(&ten).0 - b1.commit(&eight).0),
|
||||
b_zero.commit(&two).0
|
||||
(b1.commit([&ten], []).0 - b1.commit([&eight], []).0),
|
||||
b_zero.commit([&two], []).0
|
||||
);
|
||||
|
||||
// Blinding factors are also homomorphic.
|
||||
assert_eq!(
|
||||
b1.commit(&ten).0 - b2.commit(&ten).0,
|
||||
BalanceWitness::new(b1.0 - b2.0).commit(&zero).0
|
||||
b1.commit([&ten], []).0 - b2.commit([&ten], []).0,
|
||||
BalanceWitness::new(b1.0 - b2.0).commit([&zero], []).0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ pub struct BundleWitness {
|
||||
|
||||
impl Bundle {
|
||||
pub fn balance(&self) -> Balance {
|
||||
Balance(self.partials.iter().map(|ptx| ptx.balance().0).sum())
|
||||
Balance(self.partials.iter().map(|ptx| ptx.balance.0).sum())
|
||||
}
|
||||
|
||||
pub fn is_balanced(&self, witness: BalanceWitness) -> bool {
|
||||
@ -28,6 +28,8 @@ impl Bundle {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, Scalar};
|
||||
|
||||
use crate::{
|
||||
input::InputWitness,
|
||||
note::{unit_point, NoteWitness},
|
||||
@ -49,11 +51,11 @@ mod test {
|
||||
|
||||
let nmo_10_utxo =
|
||||
OutputWitness::random(NoteWitness::basic(10, nmo), nf_a.commit(), &mut rng);
|
||||
let nmo_10_in = InputWitness::random(nmo_10_utxo, nf_a, &mut rng);
|
||||
let nmo_10_in = InputWitness::from_output(nmo_10_utxo, nf_a);
|
||||
|
||||
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 eth_23_in = InputWitness::from_output(eth_23_utxo, nf_b);
|
||||
|
||||
let crv_4840_out =
|
||||
OutputWitness::random(NoteWitness::basic(4840, crv), nf_c.commit(), &mut rng);
|
||||
@ -61,28 +63,28 @@ mod test {
|
||||
let ptx_unbalanced = PartialTxWitness {
|
||||
inputs: vec![nmo_10_in, eth_23_in],
|
||||
outputs: vec![crv_4840_out],
|
||||
balance_blinding: BalanceWitness::random(&mut rng),
|
||||
};
|
||||
|
||||
let bundle_witness = BundleWitness {
|
||||
balance_blinding: BalanceWitness::new(
|
||||
crv_4840_out.balance_blinding.0
|
||||
- nmo_10_in.balance_blinding.0
|
||||
- eth_23_in.balance_blinding.0,
|
||||
),
|
||||
balance_blinding: ptx_unbalanced.balance_blinding,
|
||||
};
|
||||
|
||||
let mut bundle = Bundle {
|
||||
partials: vec![ptx_unbalanced.commit()],
|
||||
};
|
||||
|
||||
let crv_4840_out_bal = crv_4840_out.note.unit * Scalar::from(crv_4840_out.note.value);
|
||||
let nmo_10_in_bal = nmo_10_in.note.unit * Scalar::from(nmo_10_in.note.value);
|
||||
let eth_23_in_bal = eth_23_in.note.unit * Scalar::from(eth_23_in.note.value);
|
||||
let unbalance_blinding = RISTRETTO_BASEPOINT_POINT * ptx_unbalanced.balance_blinding.0;
|
||||
assert!(!bundle.is_balanced(bundle_witness.balance_blinding));
|
||||
assert_eq!(
|
||||
bundle.balance().0,
|
||||
crv_4840_out.commit().balance.0
|
||||
- (nmo_10_in.commit().balance.0 + eth_23_in.commit().balance.0)
|
||||
crv_4840_out_bal - (nmo_10_in_bal + eth_23_in_bal) + unbalance_blinding
|
||||
);
|
||||
|
||||
let crv_4840_in = InputWitness::random(crv_4840_out, nf_c, &mut rng);
|
||||
let crv_4840_in = InputWitness::from_output(crv_4840_out, nf_c);
|
||||
let nmo_10_out = OutputWitness::random(
|
||||
NoteWitness::basic(10, nmo),
|
||||
NullifierSecret::random(&mut rng).commit(), // transferring to a random owner
|
||||
@ -94,21 +96,17 @@ mod test {
|
||||
&mut rng,
|
||||
);
|
||||
|
||||
bundle.partials.push(
|
||||
PartialTxWitness {
|
||||
inputs: vec![crv_4840_in],
|
||||
outputs: vec![nmo_10_out, eth_23_out],
|
||||
}
|
||||
.commit(),
|
||||
);
|
||||
let ptx_solved = PartialTxWitness {
|
||||
inputs: vec![crv_4840_in],
|
||||
outputs: vec![nmo_10_out, eth_23_out],
|
||||
balance_blinding: BalanceWitness::random(&mut rng),
|
||||
};
|
||||
|
||||
bundle.partials.push(ptx_solved.commit());
|
||||
|
||||
let witness = BundleWitness {
|
||||
balance_blinding: BalanceWitness::new(
|
||||
-nmo_10_in.balance_blinding.0 - eth_23_in.balance_blinding.0
|
||||
+ crv_4840_out.balance_blinding.0
|
||||
- crv_4840_in.balance_blinding.0
|
||||
+ nmo_10_out.balance_blinding.0
|
||||
+ eth_23_out.balance_blinding.0,
|
||||
ptx_unbalanced.balance_blinding.0 + ptx_solved.balance_blinding.0,
|
||||
),
|
||||
};
|
||||
|
||||
|
||||
@ -3,39 +3,29 @@
|
||||
/// Partial transactions, as the name suggests, are transactions
|
||||
/// which on their own may not balance (i.e. \sum inputs != \sum outputs)
|
||||
use crate::{
|
||||
balance::Balance,
|
||||
note::{DeathCommitment, NoteWitness},
|
||||
nullifier::{Nullifier, NullifierNonce, NullifierSecret},
|
||||
BalanceWitness,
|
||||
};
|
||||
use rand_core::CryptoRngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Input {
|
||||
pub nullifier: Nullifier,
|
||||
pub balance: Balance,
|
||||
pub death_cm: DeathCommitment,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct InputWitness {
|
||||
pub note: NoteWitness,
|
||||
pub balance_blinding: BalanceWitness,
|
||||
pub nf_sk: NullifierSecret,
|
||||
pub nonce: NullifierNonce,
|
||||
}
|
||||
|
||||
impl InputWitness {
|
||||
pub fn random(
|
||||
output: crate::OutputWitness,
|
||||
nf_sk: NullifierSecret,
|
||||
mut rng: impl CryptoRngCore,
|
||||
) -> Self {
|
||||
pub fn from_output(output: crate::OutputWitness, nf_sk: NullifierSecret) -> Self {
|
||||
assert_eq!(nf_sk.commit(), output.nf_pk);
|
||||
Self {
|
||||
note: output.note,
|
||||
balance_blinding: BalanceWitness::random(&mut rng),
|
||||
nf_sk,
|
||||
nonce: output.nonce,
|
||||
}
|
||||
@ -46,7 +36,6 @@ impl InputWitness {
|
||||
assert_eq!(nf_sk.commit(), output.nf_pk); // ensure the output was a public UTXO
|
||||
Self {
|
||||
note: output.note,
|
||||
balance_blinding: BalanceWitness::unblinded(),
|
||||
nf_sk,
|
||||
nonce: output.nonce,
|
||||
}
|
||||
@ -56,14 +45,9 @@ impl InputWitness {
|
||||
self.nonce.evolve(domain, &self.nf_sk, &self.note)
|
||||
}
|
||||
|
||||
pub fn evolve_output(
|
||||
&self,
|
||||
domain: &[u8],
|
||||
balance_blinding: BalanceWitness,
|
||||
) -> crate::OutputWitness {
|
||||
pub fn evolve_output(&self, domain: &[u8]) -> crate::OutputWitness {
|
||||
crate::OutputWitness {
|
||||
note: self.note,
|
||||
balance_blinding,
|
||||
nf_pk: self.nf_sk.commit(),
|
||||
nonce: self.evolved_nonce(domain),
|
||||
}
|
||||
@ -76,7 +60,6 @@ impl InputWitness {
|
||||
pub fn commit(&self) -> Input {
|
||||
Input {
|
||||
nullifier: self.nullifier(),
|
||||
balance: self.balance_blinding.commit(&self.note),
|
||||
death_cm: self.note.death_commitment(),
|
||||
}
|
||||
}
|
||||
@ -87,11 +70,10 @@ impl InputWitness {
|
||||
}
|
||||
|
||||
impl Input {
|
||||
pub fn to_bytes(&self) -> [u8; 96] {
|
||||
let mut bytes = [0u8; 96];
|
||||
pub fn to_bytes(&self) -> [u8; 64] {
|
||||
let mut bytes = [0u8; 64];
|
||||
bytes[..32].copy_from_slice(self.nullifier.as_bytes());
|
||||
bytes[32..64].copy_from_slice(&self.balance.to_bytes());
|
||||
bytes[64..96].copy_from_slice(&self.death_cm.0);
|
||||
bytes[32..64].copy_from_slice(&self.death_cm.0);
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,23 +2,19 @@ use rand_core::CryptoRngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
balance::Balance,
|
||||
error::Error,
|
||||
note::{NoteCommitment, NoteWitness},
|
||||
nullifier::{NullifierCommitment, NullifierNonce},
|
||||
BalanceWitness, NullifierSecret,
|
||||
NullifierSecret,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Output {
|
||||
pub note_comm: NoteCommitment,
|
||||
pub balance: Balance,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct OutputWitness {
|
||||
pub note: NoteWitness,
|
||||
pub balance_blinding: BalanceWitness,
|
||||
pub nf_pk: NullifierCommitment,
|
||||
pub nonce: NullifierNonce,
|
||||
}
|
||||
@ -31,7 +27,6 @@ impl OutputWitness {
|
||||
) -> Self {
|
||||
Self {
|
||||
note,
|
||||
balance_blinding: BalanceWitness::random(&mut rng),
|
||||
nf_pk: owner,
|
||||
nonce: NullifierNonce::random(&mut rng),
|
||||
}
|
||||
@ -40,7 +35,6 @@ impl OutputWitness {
|
||||
pub fn public(note: NoteWitness, nonce: NullifierNonce) -> Self {
|
||||
Self {
|
||||
note,
|
||||
balance_blinding: BalanceWitness::unblinded(),
|
||||
nf_pk: NullifierSecret::zero().commit(),
|
||||
nonce,
|
||||
}
|
||||
@ -50,99 +44,15 @@ impl OutputWitness {
|
||||
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 {
|
||||
Output {
|
||||
note_comm: self.commit_note(),
|
||||
balance: self.commit_balance(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// as we don't have SNARKS hooked up yet, the witness will be our proof
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OutputProof(OutputWitness);
|
||||
|
||||
impl Output {
|
||||
pub fn prove(&self, w: &OutputWitness) -> Result<OutputProof, Error> {
|
||||
if &w.commit() == self {
|
||||
Ok(OutputProof(*w))
|
||||
} else {
|
||||
Err(Error::ProofFailed)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(&self, proof: &OutputProof) -> bool {
|
||||
// verification checks the relation
|
||||
// - note_comm == commit(note || nf_pk)
|
||||
// - balance == v * hash_to_curve(Unit) + blinding * H
|
||||
let witness = &proof.0;
|
||||
|
||||
self.note_comm == witness.commit_note() && self.balance == witness.commit_balance()
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> [u8; 64] {
|
||||
let mut bytes = [0u8; 64];
|
||||
bytes[..32].copy_from_slice(self.note_comm.as_bytes());
|
||||
bytes[32..64].copy_from_slice(&self.balance.to_bytes());
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{note::unit_point, nullifier::NullifierSecret};
|
||||
|
||||
#[test]
|
||||
fn test_output_proof() {
|
||||
let (nmo, eth) = (unit_point("NMO"), unit_point("ETH"));
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let witness = OutputWitness {
|
||||
note: NoteWitness::basic(10, nmo),
|
||||
balance_blinding: BalanceWitness::random(&mut rng),
|
||||
nf_pk: NullifierSecret::random(&mut rng).commit(),
|
||||
nonce: NullifierNonce::random(&mut rng),
|
||||
};
|
||||
|
||||
let output = witness.commit();
|
||||
let proof = output.prove(&witness).unwrap();
|
||||
|
||||
assert!(output.verify(&proof));
|
||||
|
||||
let wrong_witnesses = [
|
||||
OutputWitness {
|
||||
note: NoteWitness::basic(11, nmo),
|
||||
..witness
|
||||
},
|
||||
OutputWitness {
|
||||
note: NoteWitness::basic(10, eth),
|
||||
..witness
|
||||
},
|
||||
OutputWitness {
|
||||
balance_blinding: BalanceWitness::random(&mut rng),
|
||||
..witness
|
||||
},
|
||||
OutputWitness {
|
||||
nf_pk: NullifierSecret::random(&mut rng).commit(),
|
||||
..witness
|
||||
},
|
||||
OutputWitness {
|
||||
nonce: NullifierNonce::random(&mut rng),
|
||||
..witness
|
||||
},
|
||||
];
|
||||
|
||||
for wrong_witness in wrong_witnesses {
|
||||
assert!(output.prove(&wrong_witness).is_err());
|
||||
|
||||
let wrong_output = wrong_witness.commit();
|
||||
let wrong_proof = wrong_output.prove(&wrong_witness).unwrap();
|
||||
assert!(!output.verify(&wrong_proof));
|
||||
}
|
||||
pub fn to_bytes(&self) -> [u8; 32] {
|
||||
self.note_comm.0
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use curve25519_dalek::Scalar;
|
||||
use rand_core::RngCore;
|
||||
use rand_core::{CryptoRngCore, RngCore};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::balance::{Balance, BalanceWitness};
|
||||
@ -34,33 +32,44 @@ impl PtxRoot {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PartialTx {
|
||||
pub inputs: Vec<Input>,
|
||||
pub outputs: Vec<Output>,
|
||||
pub balance: Balance,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PartialTxWitness {
|
||||
pub inputs: Vec<InputWitness>,
|
||||
pub outputs: Vec<OutputWitness>,
|
||||
pub balance_blinding: BalanceWitness,
|
||||
}
|
||||
|
||||
impl PartialTxWitness {
|
||||
pub fn random(
|
||||
inputs: Vec<InputWitness>,
|
||||
outputs: Vec<OutputWitness>,
|
||||
mut rng: impl CryptoRngCore,
|
||||
) -> Self {
|
||||
Self {
|
||||
inputs,
|
||||
outputs,
|
||||
balance_blinding: BalanceWitness::random(&mut rng),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn commit(&self) -> PartialTx {
|
||||
PartialTx {
|
||||
inputs: Vec::from_iter(self.inputs.iter().map(InputWitness::commit)),
|
||||
outputs: Vec::from_iter(self.outputs.iter().map(OutputWitness::commit)),
|
||||
balance: self.balance_blinding.commit(
|
||||
self.inputs.iter().map(|i| &i.note),
|
||||
self.outputs.iter().map(|o| &o.note),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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()));
|
||||
@ -107,13 +116,6 @@ impl PartialTx {
|
||||
let root = merkle::node(input_root, output_root);
|
||||
PtxRoot(root)
|
||||
}
|
||||
|
||||
pub fn balance(&self) -> Balance {
|
||||
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();
|
||||
|
||||
Balance(out_sum - in_sum)
|
||||
}
|
||||
}
|
||||
|
||||
/// An input to a partial transaction
|
||||
@ -147,6 +149,8 @@ impl PartialTxOutputWitness {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, Scalar};
|
||||
|
||||
use crate::{
|
||||
note::{unit_point, NoteWitness},
|
||||
nullifier::NullifierSecret,
|
||||
@ -165,11 +169,11 @@ mod test {
|
||||
|
||||
let nmo_10_utxo =
|
||||
OutputWitness::random(NoteWitness::basic(10, nmo), nf_a.commit(), &mut rng);
|
||||
let nmo_10 = InputWitness::random(nmo_10_utxo, nf_a, &mut rng);
|
||||
let nmo_10 = InputWitness::from_output(nmo_10_utxo, nf_a);
|
||||
|
||||
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 eth_23 = InputWitness::from_output(eth_23_utxo, nf_b);
|
||||
|
||||
let crv_4840 =
|
||||
OutputWitness::random(NoteWitness::basic(4840, crv), nf_c.commit(), &mut rng);
|
||||
@ -177,13 +181,18 @@ mod test {
|
||||
let ptx_witness = PartialTxWitness {
|
||||
inputs: vec![nmo_10, eth_23],
|
||||
outputs: vec![crv_4840],
|
||||
balance_blinding: BalanceWitness::random(&mut rng),
|
||||
};
|
||||
|
||||
let ptx = ptx_witness.commit();
|
||||
|
||||
let crv_4840_bal = crv_4840.note.unit * Scalar::from(crv_4840.note.value);
|
||||
let nmo_10_bal = nmo_10.note.unit * Scalar::from(nmo_10.note.value);
|
||||
let eth_23_bal = eth_23.note.unit * Scalar::from(eth_23.note.value);
|
||||
let blinding = RISTRETTO_BASEPOINT_POINT * ptx_witness.balance_blinding.0;
|
||||
assert_eq!(
|
||||
ptx.balance().0,
|
||||
crv_4840.commit().balance.0 - (nmo_10.commit().balance.0 + eth_23.commit().balance.0)
|
||||
ptx.balance.0,
|
||||
crv_4840_bal - (nmo_10_bal + eth_23_bal) + blinding,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use cl::note::unit_point;
|
||||
use cl::{note::unit_point, BalanceWitness};
|
||||
use rand_core::CryptoRngCore;
|
||||
|
||||
fn receive_utxo(
|
||||
@ -29,8 +29,9 @@ fn test_simple_transfer() {
|
||||
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)],
|
||||
inputs: vec![cl::InputWitness::from_output(utxo, sender_nf_sk)],
|
||||
outputs: vec![recipient_output, change_output],
|
||||
balance_blinding: BalanceWitness::random(&mut rng),
|
||||
};
|
||||
|
||||
let ptx = ptx_witness.commit();
|
||||
@ -39,5 +40,5 @@ fn test_simple_transfer() {
|
||||
partials: vec![ptx],
|
||||
};
|
||||
|
||||
assert!(bundle.is_balanced(ptx_witness.balance_blinding()))
|
||||
assert!(bundle.is_balanced(ptx_witness.balance_blinding))
|
||||
}
|
||||
|
||||
@ -1,181 +0,0 @@
|
||||
use ledger_proof_statements::input::{InputPrivate, InputPublic};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
const MAX_NOTE_COMMS: usize = 2usize.pow(8);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProvedInput {
|
||||
pub input: InputPublic,
|
||||
pub risc0_receipt: risc0_zkvm::Receipt,
|
||||
}
|
||||
|
||||
impl ProvedInput {
|
||||
pub fn prove(
|
||||
input: &cl::InputWitness,
|
||||
note_commitments: &[cl::NoteCommitment],
|
||||
) -> Result<Self> {
|
||||
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)
|
||||
.map_err(|_| Error::Risc0ProofFailed)?;
|
||||
|
||||
println!(
|
||||
"STARK 'input' prover time: {:.2?}, total_cycles: {}",
|
||||
start_t.elapsed(),
|
||||
prove_info.stats.total_cycles
|
||||
);
|
||||
// extract the receipt.
|
||||
let receipt = prove_info.receipt;
|
||||
|
||||
Ok(Self {
|
||||
input: InputPublic {
|
||||
cm_root: cl::merkle::root(cm_leaves),
|
||||
input: input.commit(),
|
||||
},
|
||||
risc0_receipt: receipt,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn public(&self) -> Result<InputPublic> {
|
||||
Ok(self.risc0_receipt.journal.decode()?)
|
||||
}
|
||||
|
||||
pub fn verify(&self) -> bool {
|
||||
let Ok(proved_public_inputs) = self.public() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
self.input == proved_public_inputs
|
||||
&& self
|
||||
.risc0_receipt
|
||||
.verify(nomos_cl_risc0_proofs::INPUT_ID)
|
||||
.is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
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 cm_leaves = cl::merkle::padded_leaves::<MAX_NOTE_COMMS>(¬e_comm_bytes);
|
||||
cm_leaves
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use cl::note::unit_point;
|
||||
use rand::thread_rng;
|
||||
|
||||
#[test]
|
||||
fn test_input_prover() {
|
||||
let nmo = unit_point("NMO");
|
||||
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let input = cl::InputWitness {
|
||||
note: cl::NoteWitness::basic(32, nmo),
|
||||
balance_blinding: cl::BalanceWitness::random(&mut rng),
|
||||
nf_sk: cl::NullifierSecret::random(&mut rng),
|
||||
nonce: cl::NullifierNonce::random(&mut rng),
|
||||
};
|
||||
|
||||
let notes = vec![input.note_commitment()];
|
||||
|
||||
let mut proved_input = ProvedInput::prove(&input, ¬es).unwrap();
|
||||
|
||||
let expected_public_inputs = InputPublic {
|
||||
cm_root: cl::merkle::root(note_commitment_leaves(¬es)),
|
||||
input: input.commit(),
|
||||
};
|
||||
|
||||
assert_eq!(proved_input.input, expected_public_inputs);
|
||||
assert!(proved_input.verify());
|
||||
|
||||
let wrong_public_inputs = [
|
||||
InputPublic {
|
||||
cm_root: cl::merkle::root([cl::merkle::leaf(b"bad_root")]),
|
||||
..expected_public_inputs
|
||||
},
|
||||
InputPublic {
|
||||
input: cl::Input {
|
||||
nullifier: cl::Nullifier::new(
|
||||
cl::NullifierSecret::random(&mut rng),
|
||||
input.note_commitment(),
|
||||
),
|
||||
..expected_public_inputs.input
|
||||
},
|
||||
..expected_public_inputs
|
||||
},
|
||||
InputPublic {
|
||||
input: cl::Input {
|
||||
death_cm: cl::note::death_commitment(b"wrong death vk"),
|
||||
..expected_public_inputs.input
|
||||
},
|
||||
..expected_public_inputs
|
||||
},
|
||||
InputPublic {
|
||||
input: cl::Input {
|
||||
balance: cl::BalanceWitness::random(&mut rng)
|
||||
.commit(&cl::NoteWitness::basic(32, nmo)),
|
||||
..expected_public_inputs.input
|
||||
},
|
||||
..expected_public_inputs
|
||||
},
|
||||
];
|
||||
|
||||
for wrong_input in wrong_public_inputs {
|
||||
proved_input.input = wrong_input;
|
||||
assert!(!proved_input.verify());
|
||||
}
|
||||
}
|
||||
|
||||
// ----- The following tests still need to be built. -----
|
||||
// #[test]
|
||||
// fn test_input_ptx_coupling() {
|
||||
// let mut rng = rand::thread_rng();
|
||||
|
||||
// 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 witness = cl::InputWitness { note, nf_sk, nonce };
|
||||
|
||||
// let input = witness.commit();
|
||||
|
||||
// let ptx_root = cl::PtxRoot::random(&mut rng);
|
||||
// let proof = input.prove(&witness, ptx_root, vec![]).unwrap();
|
||||
|
||||
// assert!(input.verify(ptx_root, &proof));
|
||||
|
||||
// // The same input proof can not be used in another partial transaction.
|
||||
// let another_ptx_root = cl::PtxRoot::random(&mut rng);
|
||||
// assert!(!input.verify(another_ptx_root, &proof));
|
||||
// }
|
||||
}
|
||||
@ -1,8 +1,6 @@
|
||||
pub mod bundle;
|
||||
pub mod death_constraint;
|
||||
pub mod error;
|
||||
pub mod input;
|
||||
pub mod output;
|
||||
pub mod partial_tx;
|
||||
|
||||
pub use death_constraint::DeathProof;
|
||||
|
||||
@ -1,119 +0,0 @@
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
pub struct ProvedOutput {
|
||||
pub output: cl::Output,
|
||||
pub risc0_receipt: risc0_zkvm::Receipt,
|
||||
}
|
||||
|
||||
impl ProvedOutput {
|
||||
pub fn prove(witness: &cl::OutputWitness) -> Result<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)
|
||||
.map_err(|_| Error::Risc0ProofFailed)?;
|
||||
|
||||
println!(
|
||||
"STARK 'output' prover time: {:.2?}, total_cycles: {}",
|
||||
start_t.elapsed(),
|
||||
prove_info.stats.total_cycles
|
||||
);
|
||||
|
||||
let receipt = prove_info.receipt;
|
||||
|
||||
Ok(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 super::*;
|
||||
use cl::note::unit_point;
|
||||
use rand::thread_rng;
|
||||
|
||||
#[test]
|
||||
fn test_output_prover() {
|
||||
let nmo = unit_point("NMO");
|
||||
|
||||
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).unwrap();
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_output_is_rejected() {
|
||||
let nmo = unit_point("NMO");
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let output = cl::OutputWitness::random(
|
||||
cl::NoteWitness::basic(0, nmo),
|
||||
cl::NullifierSecret::random(&mut rng).commit(),
|
||||
&mut rng,
|
||||
);
|
||||
|
||||
assert!(ProvedOutput::prove(&output).is_err());
|
||||
}
|
||||
}
|
||||
@ -1,76 +1,120 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use ledger_proof_statements::death_constraint::DeathConstraintPublic;
|
||||
|
||||
use crate::{
|
||||
death_constraint::DeathProof, error::Result, input::ProvedInput, output::ProvedOutput,
|
||||
use ledger_proof_statements::{
|
||||
death_constraint::DeathConstraintPublic,
|
||||
ptx::{PtxPrivate, PtxPublic},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PartialTxInput {
|
||||
pub input: ProvedInput,
|
||||
pub death: DeathProof,
|
||||
}
|
||||
use crate::{
|
||||
death_constraint::DeathProof, error::{Error, Result}
|
||||
};
|
||||
|
||||
const MAX_NOTE_COMMS: usize = 2usize.pow(8);
|
||||
|
||||
impl PartialTxInput {
|
||||
fn verify(&self, ptx_root: cl::PtxRoot) -> bool {
|
||||
let nf = self.input.input.input.nullifier;
|
||||
self.input.input.input.death_cm == self.death.death_commitment() // ensure the death proof is actually for this input
|
||||
&& self.input.verify() // ensure the input proof is valid
|
||||
&& self.death.verify(DeathConstraintPublic { nf, ptx_root }) // verify the death constraint was satisfied
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProvedPartialTx {
|
||||
pub inputs: Vec<PartialTxInput>,
|
||||
pub outputs: Vec<ProvedOutput>,
|
||||
pub ptx: cl::PartialTx,
|
||||
pub cm_root: [u8;32],
|
||||
pub death_proofs: BTreeMap<cl::Nullifier, DeathProof>,
|
||||
pub risc0_receipt: risc0_zkvm::Receipt,
|
||||
}
|
||||
|
||||
impl ProvedPartialTx {
|
||||
pub fn prove(
|
||||
ptx: &cl::PartialTxWitness,
|
||||
mut death_proofs: BTreeMap<cl::Nullifier, DeathProof>,
|
||||
death_proofs: BTreeMap<cl::Nullifier, DeathProof>,
|
||||
note_commitments: &[cl::NoteCommitment],
|
||||
) -> Result<ProvedPartialTx> {
|
||||
let inputs = ptx
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|i| {
|
||||
Ok(PartialTxInput {
|
||||
input: ProvedInput::prove(i, note_commitments)?,
|
||||
death: death_proofs
|
||||
.remove(&i.nullifier())
|
||||
.expect("Input missing death proof"),
|
||||
})
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
let cm_leaves = note_commitment_leaves(note_commitments);
|
||||
|
||||
let outputs = ptx
|
||||
.outputs
|
||||
.iter()
|
||||
.map(ProvedOutput::prove)
|
||||
.collect::<Result<_>>()?;
|
||||
let input_cm_paths = Vec::from_iter(ptx.inputs.iter().map(|input| {
|
||||
let output_cm = input.note_commitment();
|
||||
|
||||
Ok(Self { inputs, outputs })
|
||||
let cm_idx = note_commitments
|
||||
.iter()
|
||||
.position(|c| c == &output_cm)
|
||||
.unwrap();
|
||||
|
||||
cl::merkle::path(cm_leaves, cm_idx)
|
||||
}));
|
||||
let cm_root = cl::merkle::root(cm_leaves);
|
||||
let ptx_private = PtxPrivate {
|
||||
ptx: ptx.clone(),
|
||||
input_cm_paths,
|
||||
cm_root,
|
||||
};
|
||||
|
||||
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
|
||||
);
|
||||
// extract the receipt.
|
||||
let receipt = prove_info.receipt;
|
||||
|
||||
Ok(Self {
|
||||
ptx: ptx.commit(),
|
||||
cm_root,
|
||||
risc0_receipt: receipt,
|
||||
death_proofs,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ptx(&self) -> cl::PartialTx {
|
||||
cl::PartialTx {
|
||||
inputs: Vec::from_iter(self.inputs.iter().map(|i| i.input.input.input)),
|
||||
outputs: Vec::from_iter(self.outputs.iter().map(|o| o.output)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_inputs(&self) -> bool {
|
||||
let ptx_root = self.ptx().root();
|
||||
self.inputs.iter().all(|i| i.verify(ptx_root))
|
||||
}
|
||||
|
||||
pub fn verify_outputs(&self) -> bool {
|
||||
self.outputs.iter().all(|o| o.verify())
|
||||
pub fn public(&self) -> Result<PtxPublic> {
|
||||
Ok(self.risc0_receipt.journal.decode()?)
|
||||
}
|
||||
|
||||
pub fn verify(&self) -> bool {
|
||||
self.verify_inputs() && self.verify_outputs()
|
||||
let Ok(proved_ptx_inputs) = self.public() else {
|
||||
return false;
|
||||
};
|
||||
if (PtxPublic { ptx: self.ptx.clone(), cm_root: self.cm_root }) != proved_ptx_inputs {
|
||||
return false;
|
||||
}
|
||||
|
||||
let ptx_root = self.ptx.root();
|
||||
|
||||
for input in self.ptx.inputs.iter() {
|
||||
let nf = input.nullifier;
|
||||
let Some(death_proof) = self.death_proofs.get(&nf) else {
|
||||
return false;
|
||||
};
|
||||
if input.death_cm != death_proof.death_commitment() {
|
||||
// ensure the death proof is actually for this input
|
||||
return false;
|
||||
}
|
||||
if !death_proof.verify(DeathConstraintPublic { nf, ptx_root }) {
|
||||
// verify the death constraint was satisfied
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
self.risc0_receipt
|
||||
.verify(nomos_cl_risc0_proofs::PTX_ID)
|
||||
.is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
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 cm_leaves = cl::merkle::padded_leaves::<MAX_NOTE_COMMS>(¬e_comm_bytes);
|
||||
cm_leaves
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use cl::note::unit_point;
|
||||
use cl::{note::unit_point, BalanceWitness};
|
||||
use ledger::{bundle::ProvedBundle, death_constraint::DeathProof, partial_tx::ProvedPartialTx};
|
||||
use rand_core::CryptoRngCore;
|
||||
|
||||
@ -45,7 +45,7 @@ fn test_simple_transfer() {
|
||||
alice.pk(),
|
||||
&mut rng,
|
||||
);
|
||||
let alices_input = cl::InputWitness::random(utxo, alice.sk(), &mut rng);
|
||||
let alices_input = cl::InputWitness::from_output(utxo, alice.sk());
|
||||
|
||||
// Alice wants to send 8 NMO to bob
|
||||
let bobs_output = cl::OutputWitness::random(cl::NoteWitness::basic(8, nmo), bob.pk(), &mut rng);
|
||||
@ -58,6 +58,7 @@ fn test_simple_transfer() {
|
||||
let ptx_witness = cl::PartialTxWitness {
|
||||
inputs: vec![alices_input],
|
||||
outputs: vec![bobs_output, change_output],
|
||||
balance_blinding: BalanceWitness::random(&mut rng),
|
||||
};
|
||||
|
||||
// Prove the death constraints for alices input (she uses the no-op death constraint)
|
||||
@ -79,7 +80,7 @@ fn test_simple_transfer() {
|
||||
};
|
||||
|
||||
let bundle_witness = cl::BundleWitness {
|
||||
balance_blinding: ptx_witness.balance_blinding(),
|
||||
balance_blinding: ptx_witness.balance_blinding,
|
||||
};
|
||||
|
||||
let proved_bundle = ProvedBundle::prove(&bundle, &bundle_witness).unwrap();
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// for public inputs `nf` (nullifier), `root_cm` (root of merkle tree over commitment set) and `death_cm` (commitment to death constraint).
|
||||
/// the prover has knowledge of `output = (note, nf_pk, nonce)`, `nf` and `path` s.t. that the following constraints hold
|
||||
/// 0. nf_pk = hash(nf_sk)
|
||||
/// 1. nf = hash(nonce||nf_sk)
|
||||
/// 2. note_cm = output_commitment(output)
|
||||
/// 3. verify_merkle_path(note_cm, root, path)
|
||||
/// 4. death_cm = death_commitment(note.death_constraint)
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct InputPublic {
|
||||
pub cm_root: [u8; 32],
|
||||
pub input: cl::Input,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct InputPrivate {
|
||||
pub input: cl::InputWitness,
|
||||
pub cm_path: Vec<cl::merkle::PathNode>,
|
||||
}
|
||||
@ -1,2 +1,2 @@
|
||||
pub mod death_constraint;
|
||||
pub mod input;
|
||||
pub mod ptx;
|
||||
|
||||
15
goas/cl/ledger_proof_statements/src/ptx.rs
Normal file
15
goas/cl/ledger_proof_statements/src/ptx.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use cl::{PartialTx, PartialTxWitness};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PtxPublic {
|
||||
pub ptx: PartialTx,
|
||||
pub cm_root: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PtxPrivate {
|
||||
pub ptx: PartialTxWitness,
|
||||
pub input_cm_paths: Vec<Vec<cl::merkle::PathNode>>,
|
||||
pub cm_root: [u8; 32],
|
||||
}
|
||||
@ -7,5 +7,5 @@ edition = "2021"
|
||||
risc0-build = { version = "1.0" }
|
||||
|
||||
[package.metadata.risc0]
|
||||
methods = ["input", "output", "bundle", "death_constraint_nop"]
|
||||
methods = ["bundle", "death_constraint_nop", "ptx"]
|
||||
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
/// Input Proof
|
||||
use cl::merkle;
|
||||
use ledger_proof_statements::input::{InputPrivate, InputPublic};
|
||||
use risc0_zkvm::guest::env;
|
||||
|
||||
fn main() {
|
||||
let secret: InputPrivate = env::read();
|
||||
|
||||
let out_cm = secret.input.note_commitment();
|
||||
let cm_leaf = merkle::leaf(out_cm.as_bytes());
|
||||
let cm_root = merkle::path_root(cm_leaf, &secret.cm_path);
|
||||
|
||||
env::commit(&InputPublic {
|
||||
input: secret.input.commit(),
|
||||
cm_root,
|
||||
});
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
[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" }
|
||||
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" }
|
||||
@ -1,18 +0,0 @@
|
||||
/// 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();
|
||||
|
||||
// 0 does not contribute to balance, implications of this are unclear
|
||||
// therefore out of an abundance of caution, we disallow these zero
|
||||
// valued "dummy notes".
|
||||
assert!(output.note.value > 0);
|
||||
|
||||
let output_cm = output.commit();
|
||||
env::commit(&output_cm);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "input"
|
||||
name = "ptx"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
28
goas/cl/risc0_proofs/ptx/src/main.rs
Normal file
28
goas/cl/risc0_proofs/ptx/src/main.rs
Normal file
@ -0,0 +1,28 @@
|
||||
/// Input Proof
|
||||
use cl::merkle;
|
||||
use ledger_proof_statements::ptx::{PtxPrivate, PtxPublic};
|
||||
use risc0_zkvm::guest::env;
|
||||
|
||||
fn main() {
|
||||
let PtxPrivate {
|
||||
ptx,
|
||||
input_cm_paths,
|
||||
cm_root,
|
||||
} = env::read();
|
||||
|
||||
assert_eq!(ptx.inputs.len(), input_cm_paths.len());
|
||||
for (input, cm_path) in ptx.inputs.iter().zip(input_cm_paths) {
|
||||
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_root,
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user