Merge pull request #11 from logos-co/drusu/cl/add-death-constraints
cl: add death constraints to simple transfer scenario
This commit is contained in:
commit
94bef73ba4
|
@ -33,7 +33,7 @@ pub struct NullifierNonce([u8; 32]);
|
|||
|
||||
// The nullifier attached to input notes to prove an input has not
|
||||
// already been spent.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct Nullifier([u8; 32]);
|
||||
|
||||
impl NullifierSecret {
|
||||
|
@ -83,13 +83,13 @@ impl NullifierNonce {
|
|||
}
|
||||
|
||||
pub fn evolve(&self, nf_sk: &NullifierSecret) -> Self {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"NOMOS_COIN_EVOLVE");
|
||||
hasher.update(&self.0);
|
||||
hasher.update(nf_sk.0);
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"NOMOS_COIN_EVOLVE");
|
||||
hasher.update(&self.0);
|
||||
hasher.update(nf_sk.0);
|
||||
|
||||
let nonce_bytes: [u8; 32] = hasher.finalize().into();
|
||||
Self(nonce_bytes)
|
||||
let nonce_bytes: [u8; 32] = hasher.finalize().into();
|
||||
Self(nonce_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
BalanceWitness,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Output {
|
||||
pub note_comm: NoteCommitment,
|
||||
pub balance: Balance,
|
||||
|
|
|
@ -12,3 +12,4 @@ risc0-groth16 = { version = "1.0" }
|
|||
rand = "0.8.5"
|
||||
rand_core = "0.6.0"
|
||||
thiserror = "1.0.62"
|
||||
sha2 = "0.10"
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
use proof_statements::death_constraint::DeathConstraintPublic;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::error::Result;
|
||||
|
||||
pub type Risc0DeathConstraintId = [u32; 8];
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DeathProof {
|
||||
constraint: Risc0DeathConstraintId,
|
||||
risc0_receipt: risc0_zkvm::Receipt,
|
||||
}
|
||||
|
||||
fn risc0_id_to_cl_death_constraint(risc0_id: Risc0DeathConstraintId) -> [u8; 32] {
|
||||
// RISC0 proof ids have the format: [u32; 8], and cl death constraint ids have the format [u8; 32].
|
||||
// CL death constraints are meaningless beyond being binding, therefore we merely need a collision
|
||||
// resisitant mapping of RISC0 ids to cl death constraints.
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"NOMOS_RISC0_ID_TO_CL_DEATH_CONSTRAINT");
|
||||
for word in risc0_id {
|
||||
hasher.update(u32::to_ne_bytes(word));
|
||||
}
|
||||
let death_constraint: [u8; 32] = hasher.finalize().into();
|
||||
death_constraint
|
||||
}
|
||||
|
||||
impl DeathProof {
|
||||
pub fn death_commitment(&self) -> cl::DeathCommitment {
|
||||
cl::note::death_commitment(&risc0_id_to_cl_death_constraint(self.constraint))
|
||||
}
|
||||
|
||||
pub fn public(&self) -> Result<DeathConstraintPublic> {
|
||||
Ok(self.risc0_receipt.journal.decode()?)
|
||||
}
|
||||
|
||||
pub fn verify(&self, expected_public: DeathConstraintPublic) -> bool {
|
||||
let Ok(public) = self.public() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
expected_public == public && self.risc0_receipt.verify(self.constraint).is_ok()
|
||||
}
|
||||
|
||||
pub fn nop_constraint() -> [u8; 32] {
|
||||
risc0_id_to_cl_death_constraint(nomos_cl_risc0_proofs::DEATH_CONSTRAINT_NOP_ID)
|
||||
}
|
||||
|
||||
pub fn prove_nop(nf: cl::Nullifier, ptx_root: cl::PtxRoot) -> Self {
|
||||
let death_public = DeathConstraintPublic { nf, ptx_root };
|
||||
let env = risc0_zkvm::ExecutorEnv::builder()
|
||||
.write(&death_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::DEATH_CONSTRAINT_NOP_ELF, &opts)
|
||||
.unwrap();
|
||||
|
||||
println!(
|
||||
"STARK 'death-nop' prover time: {:.2?}, total_cycles: {}",
|
||||
start_t.elapsed(),
|
||||
prove_info.stats.total_cycles
|
||||
);
|
||||
|
||||
// extract the receipt.
|
||||
let receipt = prove_info.receipt;
|
||||
|
||||
Self {
|
||||
constraint: nomos_cl_risc0_proofs::DEATH_CONSTRAINT_NOP_ID,
|
||||
risc0_receipt: receipt,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ use crate::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,
|
||||
|
@ -44,7 +45,7 @@ impl ProvedInput {
|
|||
.unwrap();
|
||||
|
||||
println!(
|
||||
"STARK prover time: {:.2?}, total_cycles: {}",
|
||||
"STARK 'input' prover time: {:.2?}, total_cycles: {}",
|
||||
start_t.elapsed(),
|
||||
prove_info.stats.total_cycles
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// pub mod death_constraint;
|
||||
pub mod bundle;
|
||||
pub mod death_constraint;
|
||||
pub mod error;
|
||||
pub mod input;
|
||||
pub mod output;
|
||||
|
|
|
@ -1,26 +1,65 @@
|
|||
use crate::{input::ProvedInput, output::ProvedOutput};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use proof_statements::death_constraint::DeathConstraintPublic;
|
||||
|
||||
use crate::{death_constraint::DeathProof, input::ProvedInput, output::ProvedOutput};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PartialTxInput {
|
||||
pub input: ProvedInput,
|
||||
pub death: DeathProof,
|
||||
}
|
||||
|
||||
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<ProvedInput>,
|
||||
pub inputs: Vec<PartialTxInput>,
|
||||
pub outputs: Vec<ProvedOutput>,
|
||||
}
|
||||
|
||||
impl ProvedPartialTx {
|
||||
pub fn prove(
|
||||
ptx: &cl::PartialTxWitness,
|
||||
mut death_proofs: BTreeMap<cl::Nullifier, DeathProof>,
|
||||
note_commitments: &[cl::NoteCommitment],
|
||||
) -> ProvedPartialTx {
|
||||
Self {
|
||||
inputs: Vec::from_iter(
|
||||
ptx.inputs
|
||||
.iter()
|
||||
.map(|i| ProvedInput::prove(i, note_commitments)),
|
||||
),
|
||||
inputs: Vec::from_iter(ptx.inputs.iter().map(|i| {
|
||||
PartialTxInput {
|
||||
input: ProvedInput::prove(i, note_commitments),
|
||||
death: death_proofs
|
||||
.remove(&i.nullifier())
|
||||
.expect("Input missing death proof"),
|
||||
}
|
||||
})),
|
||||
outputs: Vec::from_iter(ptx.outputs.iter().map(ProvedOutput::prove)),
|
||||
}
|
||||
}
|
||||
|
||||
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 verify(&self) -> bool {
|
||||
self.inputs.iter().all(ProvedInput::verify) && self.outputs.iter().all(ProvedOutput::verify)
|
||||
self.verify_inputs() && self.verify_outputs()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use ledger::{bundle::ProvedBundle, partial_tx::ProvedPartialTx};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use ledger::{bundle::ProvedBundle, death_constraint::DeathProof, partial_tx::ProvedPartialTx};
|
||||
use rand_core::CryptoRngCore;
|
||||
|
||||
struct User(cl::NullifierSecret);
|
||||
|
@ -35,7 +37,11 @@ fn test_simple_transfer() {
|
|||
let bob = User::random(&mut rng);
|
||||
|
||||
// Alice has an unspent note worth 10 NMO
|
||||
let utxo = receive_utxo(cl::NoteWitness::basic(10, "NMO"), alice.pk(), &mut rng);
|
||||
let utxo = receive_utxo(
|
||||
cl::NoteWitness::stateless(10, "NMO", DeathProof::nop_constraint()),
|
||||
alice.pk(),
|
||||
&mut rng,
|
||||
);
|
||||
let alices_input = cl::InputWitness::random(utxo, alice.sk(), &mut rng);
|
||||
|
||||
// Alice wants to send 8 NMO to bob
|
||||
|
@ -52,9 +58,17 @@ fn test_simple_transfer() {
|
|||
outputs: vec![bobs_output, change_output],
|
||||
};
|
||||
|
||||
// Prove the death constraints for alices input (she uses the no-op death constraint)
|
||||
let death_proofs = BTreeMap::from_iter(ptx_witness.inputs.iter().map(|i| {
|
||||
(
|
||||
i.nullifier(),
|
||||
DeathProof::prove_nop(i.nullifier(), ptx_witness.commit().root()),
|
||||
)
|
||||
}));
|
||||
|
||||
// assume we only have one note commitment on chain for now ...
|
||||
let note_commitments = vec![utxo.commit_note()];
|
||||
let proved_ptx = ProvedPartialTx::prove(&ptx_witness, ¬e_commitments);
|
||||
let proved_ptx = ProvedPartialTx::prove(&ptx_witness, death_proofs, ¬e_commitments);
|
||||
|
||||
assert!(proved_ptx.verify()); // It's a valid ptx.
|
||||
|
||||
|
@ -63,7 +77,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);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use cl::{Nullifier, PtxRoot};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct DeathConstraintPublic {
|
||||
pub nf: Nullifier,
|
||||
pub ptx_root: PtxRoot,
|
||||
|
|
Loading…
Reference in New Issue