cl: integrate groth16 death constraint validation
This commit is contained in:
parent
993ecf13b5
commit
96482b219a
|
@ -6,6 +6,9 @@ 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"]}
|
||||
bincode = "1.3.3"
|
||||
risc0-groth16 = "1.0.1"
|
||||
blake2 = "0.10.6"
|
||||
jubjub = "0.10.0"
|
||||
group = "0.13.0"
|
||||
|
|
|
@ -2,14 +2,15 @@ use group::{ff::Field, GroupEncoding};
|
|||
use jubjub::{Scalar, SubgroupPoint};
|
||||
use lazy_static::lazy_static;
|
||||
use rand_core::RngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
lazy_static! {
|
||||
static ref PEDERSON_COMMITMENT_BLINDING_POINT: SubgroupPoint =
|
||||
crate::crypto::hash_to_curve(b"NOMOS_CL_PEDERSON_COMMITMENT_BLINDING");
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Balance(pub SubgroupPoint);
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
||||
pub struct Balance(#[serde(with = "serde_point")] pub SubgroupPoint);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct BalanceWitness {
|
||||
|
@ -55,6 +56,51 @@ pub fn balance(value: u64, unit: &str, blinding: Scalar) -> SubgroupPoint {
|
|||
unit_point(unit) * value_scalar + *PEDERSON_COMMITMENT_BLINDING_POINT * blinding
|
||||
}
|
||||
|
||||
mod serde_point {
|
||||
use super::SubgroupPoint;
|
||||
use group::GroupEncoding;
|
||||
use serde::de::{self, Visitor};
|
||||
use serde::{Deserializer, Serializer};
|
||||
use std::fmt;
|
||||
|
||||
// Serialize a SubgroupPoint by converting it to bytes.
|
||||
pub fn serialize<S>(point: &SubgroupPoint, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let bytes = point.to_bytes();
|
||||
serializer.serialize_bytes(&bytes)
|
||||
}
|
||||
|
||||
// Deserialize a SubgroupPoint by converting it from bytes.
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<SubgroupPoint, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct BytesVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for BytesVisitor {
|
||||
type Value = SubgroupPoint;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a valid SubgroupPoint in byte representation")
|
||||
}
|
||||
|
||||
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
let mut bytes = <jubjub::SubgroupPoint as group::GroupEncoding>::Repr::default();
|
||||
assert_eq!(bytes.len(), v.len());
|
||||
bytes.copy_from_slice(v);
|
||||
|
||||
Ok(SubgroupPoint::from_bytes(&bytes).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_bytes(BytesVisitor)
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
|
|
|
@ -1,39 +1,35 @@
|
|||
use std::collections::BTreeSet;
|
||||
|
||||
use jubjub::{Scalar, SubgroupPoint};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
note::NoteCommitment,
|
||||
partial_tx::{PartialTx, PartialTxProof, PartialTxWitness},
|
||||
partial_tx::{PartialTx, PartialTxProof},
|
||||
};
|
||||
|
||||
/// 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, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Bundle {
|
||||
pub partials: Vec<PartialTx>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BundleWitness {
|
||||
pub partials: Vec<PartialTxWitness>,
|
||||
pub balance_blinding: Scalar,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug)]
|
||||
pub struct BundleProof {
|
||||
pub partials: Vec<PartialTxProof>,
|
||||
pub balance_blinding: Scalar,
|
||||
}
|
||||
|
||||
impl Bundle {
|
||||
pub fn from_witness(w: BundleWitness) -> Self {
|
||||
Self {
|
||||
partials: Vec::from_iter(w.partials.into_iter().map(PartialTx::from_witness)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn balance(&self) -> SubgroupPoint {
|
||||
self.partials.iter().map(|ptx| ptx.balance()).sum()
|
||||
}
|
||||
|
@ -42,11 +38,12 @@ impl Bundle {
|
|||
self.balance() == crate::balance::balance(0, "", balance_blinding_witness)
|
||||
}
|
||||
|
||||
pub fn prove(&self, w: BundleWitness) -> Result<BundleProof, Error> {
|
||||
if &Self::from_witness(w.clone()) != self {
|
||||
return Err(Error::ProofFailed);
|
||||
}
|
||||
if w.partials.len() == self.partials.len() {
|
||||
pub fn prove(
|
||||
&self,
|
||||
w: BundleWitness,
|
||||
ptx_proofs: Vec<PartialTxProof>,
|
||||
) -> Result<BundleProof, Error> {
|
||||
if ptx_proofs.len() == self.partials.len() {
|
||||
return Err(Error::ProofFailed);
|
||||
}
|
||||
let input_notes: Vec<NoteCommitment> = self
|
||||
|
@ -67,15 +64,13 @@ impl Bundle {
|
|||
return Err(Error::ProofFailed);
|
||||
}
|
||||
|
||||
let ptx_proofs = self
|
||||
.partials
|
||||
.iter()
|
||||
.zip(w.partials)
|
||||
.map(|(ptx, p_w)| ptx.prove(p_w))
|
||||
.collect::<Result<Vec<PartialTxProof>, _>>()?;
|
||||
if self.balance() != crate::balance::balance(0, "", w.balance_blinding) {
|
||||
return Err(Error::ProofFailed);
|
||||
}
|
||||
|
||||
Ok(BundleProof {
|
||||
partials: ptx_proofs,
|
||||
balance_blinding: w.balance_blinding,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -10,15 +10,17 @@ use crate::{
|
|||
partial_tx::PtxCommitment,
|
||||
};
|
||||
use rand_core::RngCore;
|
||||
use risc0_groth16::{ProofJson, PublicInputsJson, Verifier};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Input {
|
||||
pub note_comm: NoteCommitment,
|
||||
pub nullifier: Nullifier,
|
||||
pub balance: Balance,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InputWitness {
|
||||
pub note: Note,
|
||||
pub nf_sk: NullifierSecret,
|
||||
|
@ -36,8 +38,19 @@ impl InputWitness {
|
|||
}
|
||||
|
||||
// as we don't have SNARKS hooked up yet, the witness will be our proof
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct InputProof(InputWitness, PtxCommitment);
|
||||
#[derive(Debug)]
|
||||
pub struct InputProof {
|
||||
input: InputWitness,
|
||||
ptx_comm: PtxCommitment,
|
||||
death_proof: ProofJson,
|
||||
}
|
||||
|
||||
impl InputProof {
|
||||
fn clone_death_proof(&self) -> ProofJson {
|
||||
let bytes = bincode::serialize(&self.death_proof).unwrap();
|
||||
bincode::deserialize(&bytes).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Input {
|
||||
pub fn from_witness(w: InputWitness) -> Self {
|
||||
|
@ -48,11 +61,22 @@ impl Input {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn prove(&self, w: &InputWitness, ptx_comm: PtxCommitment) -> Result<InputProof, Error> {
|
||||
if &Input::from_witness(w.clone()) != self {
|
||||
pub fn prove(
|
||||
&self,
|
||||
w: &InputWitness,
|
||||
ptx_comm: PtxCommitment,
|
||||
death_proof: ProofJson,
|
||||
) -> Result<InputProof, Error> {
|
||||
if bincode::serialize(&Input::from_witness(w.clone())).unwrap()
|
||||
!= bincode::serialize(&self).unwrap()
|
||||
{
|
||||
Err(Error::ProofFailed)
|
||||
} else {
|
||||
Ok(InputProof(w.clone(), ptx_comm))
|
||||
Ok(InputProof {
|
||||
input: w.clone(),
|
||||
ptx_comm,
|
||||
death_proof,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,13 +88,29 @@ impl Input {
|
|||
// - balance == v * hash_to_curve(Unit) + blinding * H
|
||||
// - ptx_comm is the same one that was used in proving.
|
||||
|
||||
let witness = &proof.0;
|
||||
let witness = &proof.input;
|
||||
|
||||
let nf_pk = witness.nf_sk.commit();
|
||||
|
||||
// let death_constraint_was_committed_to =
|
||||
// witness.note.death_constraint == bincode::serialize(&death_constraint).unwrap();
|
||||
|
||||
let death_constraint_is_satisfied: bool = Verifier::from_json(
|
||||
proof.clone_death_proof(),
|
||||
PublicInputsJson {
|
||||
values: vec![ptx_comm.hex()],
|
||||
},
|
||||
bincode::deserialize(&witness.note.death_constraint).unwrap(),
|
||||
)
|
||||
.unwrap()
|
||||
.verify()
|
||||
.is_ok();
|
||||
self.note_comm == witness.note.commit(nf_pk, witness.nonce)
|
||||
&& self.nullifier == Nullifier::new(witness.nf_sk, witness.nonce)
|
||||
&& self.balance == witness.note.balance()
|
||||
&& ptx_comm == proof.1
|
||||
&& ptx_comm == proof.ptx_comm
|
||||
// && death_constraint_was_committed_to
|
||||
&& death_constraint_is_satisfied
|
||||
}
|
||||
|
||||
pub(crate) fn to_bytes(&self) -> [u8; 96] {
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
use blake2::{Blake2s256, Digest};
|
||||
use group::GroupEncoding;
|
||||
use rand_core::RngCore;
|
||||
use risc0_groth16::VerifyingKeyJson;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
balance::{Balance, BalanceWitness},
|
||||
nullifier::{NullifierCommitment, NullifierNonce},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
pub struct NoteCommitment([u8; 32]);
|
||||
|
||||
impl NoteCommitment {
|
||||
|
@ -18,24 +20,38 @@ impl NoteCommitment {
|
|||
|
||||
// TODO: Rename Note to NoteWitness and NoteCommitment to Note
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Note {
|
||||
pub balance: BalanceWitness,
|
||||
pub death_constraint: Vec<u8>, // serialized death_constraint
|
||||
}
|
||||
|
||||
impl Note {
|
||||
pub fn random(value: u64, unit: impl Into<String>, rng: impl RngCore) -> Self {
|
||||
pub fn random(
|
||||
value: u64,
|
||||
unit: impl Into<String>,
|
||||
death_constraint: &VerifyingKeyJson,
|
||||
rng: impl RngCore,
|
||||
) -> Self {
|
||||
Self {
|
||||
balance: BalanceWitness::random(value, unit, rng),
|
||||
death_constraint: bincode::serialize(death_constraint).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn commit(&self, nf_pk: NullifierCommitment, nonce: NullifierNonce) -> NoteCommitment {
|
||||
let mut hasher = Blake2s256::new();
|
||||
hasher.update(b"NOMOS_CL_NOTE_COMMIT");
|
||||
|
||||
// COMMIT TO BALANCE
|
||||
hasher.update(self.balance.value.to_le_bytes());
|
||||
hasher.update(self.balance.unit_point().to_bytes());
|
||||
// Important! we don't commit to the balance blinding factor as that may make the notes linkable.
|
||||
|
||||
// COMMIT TO DEATH CONSTRAINT
|
||||
hasher.update(&self.death_constraint);
|
||||
|
||||
// COMMIT TO NULLIFIER
|
||||
hasher.update(nf_pk.as_bytes());
|
||||
hasher.update(nonce.as_bytes());
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
// secret is used for multiple notes.
|
||||
use blake2::{Blake2s256, Digest};
|
||||
use rand_core::RngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// Maintained privately by note holder
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
@ -26,7 +27,7 @@ pub struct NullifierNonce([u8; 16]);
|
|||
|
||||
// The nullifier attached to input notes to prove an input has not
|
||||
// already been spent.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct Nullifier([u8; 32]);
|
||||
|
||||
impl NullifierSecret {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use rand_core::RngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
balance::Balance,
|
||||
|
@ -7,13 +8,13 @@ use crate::{
|
|||
nullifier::{NullifierCommitment, NullifierNonce},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Output {
|
||||
pub note_comm: NoteCommitment,
|
||||
pub balance: Balance,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OutputWitness {
|
||||
pub note: Note,
|
||||
pub nf_pk: NullifierCommitment,
|
||||
|
@ -31,7 +32,7 @@ impl OutputWitness {
|
|||
}
|
||||
|
||||
// as we don't have SNARKS hooked up yet, the witness will be our proof
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OutputProof(OutputWitness);
|
||||
|
||||
impl Output {
|
||||
|
|
|
@ -3,6 +3,8 @@ use std::collections::BTreeSet;
|
|||
use blake2::{Blake2s256, Digest};
|
||||
use jubjub::SubgroupPoint;
|
||||
use rand_core::RngCore;
|
||||
use risc0_groth16::ProofJson;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::input::{Input, InputProof, InputWitness};
|
||||
|
@ -19,21 +21,25 @@ impl PtxCommitment {
|
|||
rng.fill_bytes(&mut sk);
|
||||
Self(sk)
|
||||
}
|
||||
|
||||
pub fn hex(&self) -> String {
|
||||
hex::encode(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PartialTx {
|
||||
pub inputs: Vec<Input>,
|
||||
pub outputs: Vec<Output>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PartialTxWitness {
|
||||
pub inputs: Vec<InputWitness>,
|
||||
pub outputs: Vec<OutputWitness>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug)]
|
||||
pub struct PartialTxProof {
|
||||
pub inputs: Vec<InputProof>,
|
||||
pub outputs: Vec<OutputProof>,
|
||||
|
@ -63,8 +69,14 @@ impl PartialTx {
|
|||
PtxCommitment(commit_bytes)
|
||||
}
|
||||
|
||||
pub fn prove(&self, w: PartialTxWitness) -> Result<PartialTxProof, Error> {
|
||||
if &Self::from_witness(w.clone()) != self {
|
||||
pub fn prove(
|
||||
&self,
|
||||
w: PartialTxWitness,
|
||||
death_proofs: Vec<ProofJson>,
|
||||
) -> Result<PartialTxProof, Error> {
|
||||
if bincode::serialize(&Self::from_witness(w.clone())).unwrap()
|
||||
!= bincode::serialize(&self).unwrap()
|
||||
{
|
||||
return Err(Error::ProofFailed);
|
||||
}
|
||||
let input_note_comms = BTreeSet::from_iter(self.inputs.iter().map(|i| i.note_comm));
|
||||
|
@ -82,7 +94,8 @@ impl PartialTx {
|
|||
self.inputs
|
||||
.iter()
|
||||
.zip(&w.inputs)
|
||||
.map(|(i, i_w)| i.prove(i_w, ptx_comm)),
|
||||
.zip(death_proofs.into_iter())
|
||||
.map(|((i, i_w), death_p)| i.prove(i_w, ptx_comm, death_p)),
|
||||
)?;
|
||||
|
||||
let output_proofs: Vec<OutputProof> = Result::from_iter(
|
||||
|
|
Loading…
Reference in New Issue