goas: replace input/output proofs with ptx proof

This commit is contained in:
David Rusu 2024-08-20 23:58:38 +04:00
parent ff0afeebd7
commit 310932818a
9 changed files with 165 additions and 64 deletions

View File

@ -77,10 +77,7 @@ fn test_deposit() {
assert!(deposit_proof.verify()); assert!(deposit_proof.verify());
assert_eq!( assert_eq!(deposit_proof.ptx.outputs[0], zone_end.state_note.commit());
deposit_proof.outputs[0].output,
zone_end.state_note.commit()
);
assert_eq!( assert_eq!(
zone_end.state_note.note.state, zone_end.state_note.note.state,
StateWitness { StateWitness {

View File

@ -97,10 +97,7 @@ fn test_withdrawal() {
assert!(withdraw_proof.verify()); assert!(withdraw_proof.verify());
assert_eq!( assert_eq!(withdraw_proof.ptx.outputs[0], zone_end.state_note.commit());
withdraw_proof.outputs[0].output,
zone_end.state_note.commit()
);
assert_eq!( assert_eq!(
zone_end.state_note.note.state, zone_end.state_note.note.state,
StateWitness { StateWitness {

View File

@ -34,13 +34,13 @@ impl PtxRoot {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PartialTx { pub struct PartialTx {
pub inputs: Vec<Input>, pub inputs: Vec<Input>,
pub outputs: Vec<Output>, pub outputs: Vec<Output>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PartialTxWitness { pub struct PartialTxWitness {
pub inputs: Vec<InputWitness>, pub inputs: Vec<InputWitness>,
pub outputs: Vec<OutputWitness>, pub outputs: Vec<OutputWitness>,

View File

@ -1,76 +1,120 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use ledger_proof_statements::death_constraint::DeathConstraintPublic; use ledger_proof_statements::{
death_constraint::DeathConstraintPublic,
use crate::{ ptx::{PtxPrivate, PtxPublic},
death_constraint::DeathProof, error::Result, input::ProvedInput, output::ProvedOutput,
}; };
#[derive(Debug, Clone)] use crate::{
pub struct PartialTxInput { death_constraint::DeathProof, error::{Error, Result}
pub input: ProvedInput, };
pub death: DeathProof,
} 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 struct ProvedPartialTx {
pub inputs: Vec<PartialTxInput>, pub ptx: cl::PartialTx,
pub outputs: Vec<ProvedOutput>, pub cm_root: [u8;32],
pub death_proofs: BTreeMap<cl::Nullifier, DeathProof>,
pub risc0_receipt: risc0_zkvm::Receipt,
} }
impl ProvedPartialTx { impl ProvedPartialTx {
pub fn prove( pub fn prove(
ptx: &cl::PartialTxWitness, ptx: &cl::PartialTxWitness,
mut death_proofs: BTreeMap<cl::Nullifier, DeathProof>, death_proofs: BTreeMap<cl::Nullifier, DeathProof>,
note_commitments: &[cl::NoteCommitment], note_commitments: &[cl::NoteCommitment],
) -> Result<ProvedPartialTx> { ) -> Result<ProvedPartialTx> {
let inputs = ptx let cm_leaves = note_commitment_leaves(note_commitments);
.inputs
let input_cm_paths = Vec::from_iter(ptx.inputs.iter().map(|input| {
let output_cm = input.note_commitment();
let cm_idx = note_commitments
.iter() .iter()
.map(|i| { .position(|c| c == &output_cm)
Ok(PartialTxInput { .unwrap();
input: ProvedInput::prove(i, note_commitments)?,
death: death_proofs cl::merkle::path(cm_leaves, cm_idx)
.remove(&i.nullifier()) }));
.expect("Input missing death proof"), 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,
}) })
})
.collect::<Result<_>>()?;
let outputs = ptx
.outputs
.iter()
.map(ProvedOutput::prove)
.collect::<Result<_>>()?;
Ok(Self { inputs, outputs })
} }
pub fn ptx(&self) -> cl::PartialTx { pub fn public(&self) -> Result<PtxPublic> {
cl::PartialTx { Ok(self.risc0_receipt.journal.decode()?)
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 verify(&self) -> bool { 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>(&note_comm_bytes);
cm_leaves
}

View File

@ -1,2 +1,3 @@
pub mod death_constraint; pub mod death_constraint;
pub mod input; pub mod input;
pub mod ptx;

View 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],
}

View File

@ -7,5 +7,5 @@ edition = "2021"
risc0-build = { version = "1.0" } risc0-build = { version = "1.0" }
[package.metadata.risc0] [package.metadata.risc0]
methods = ["input", "output", "bundle", "death_constraint_nop"] methods = ["input", "output", "bundle", "death_constraint_nop", "ptx"]

View 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" }

View 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,
});
}