mirror of
https://github.com/logos-blockchain/logos-blockchain-specs.git
synced 2026-01-07 07:33:09 +00:00
cl: partial_tx; input; output
This commit is contained in:
parent
26f6fe54f6
commit
cc8c6e31cb
4
cl/src/error.rs
Normal file
4
cl/src/error.rs
Normal file
@ -0,0 +1,4 @@
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
ProofFailed,
|
||||
}
|
||||
123
cl/src/input.rs
Normal file
123
cl/src/input.rs
Normal file
@ -0,0 +1,123 @@
|
||||
/// 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::{
|
||||
error::Error,
|
||||
note::{Note, NoteCommitment},
|
||||
nullifier::{Nullifier, NullifierNonce, NullifierSecret},
|
||||
};
|
||||
use jubjub::{ExtendedPoint, Scalar};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Input {
|
||||
pub note_comm: NoteCommitment,
|
||||
pub nullifier: Nullifier,
|
||||
pub balance: ExtendedPoint,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct InputWitness {
|
||||
note: Note,
|
||||
nf_sk: NullifierSecret,
|
||||
nonce: NullifierNonce,
|
||||
balance_blinding: Scalar,
|
||||
}
|
||||
|
||||
// as we don't have SNARKS hooked up yet, the witness will be our proof
|
||||
pub struct InputProof(InputWitness);
|
||||
|
||||
impl Input {
|
||||
pub fn from_witness(w: InputWitness) -> Self {
|
||||
Self {
|
||||
note_comm: w.note.commit(w.nf_sk.commit(), w.nonce),
|
||||
nullifier: Nullifier::new(w.nf_sk, w.nonce),
|
||||
balance: w.note.balance(w.balance_blinding),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prove(&self, w: &InputWitness) -> Result<InputProof, Error> {
|
||||
if &Input::from_witness(w.clone()) != self {
|
||||
Err(Error::ProofFailed)
|
||||
} else {
|
||||
Ok(InputProof(w.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(&self, proof: &InputProof) -> bool {
|
||||
// verification checks the relation
|
||||
// - nf_pk == hash(nf_sk)
|
||||
// - note_comm == commit(note || nf_pk)
|
||||
// - nullifier == hash(nf_sk || nonce)
|
||||
// - balance == v * hash_to_curve(Unit) + blinding * H
|
||||
|
||||
let witness = &proof.0;
|
||||
|
||||
let nf_pk = witness.nf_sk.commit();
|
||||
self.note_comm == witness.note.commit(nf_pk, witness.nonce)
|
||||
&& self.nullifier == Nullifier::new(witness.nf_sk, witness.nonce)
|
||||
&& self.balance == witness.note.balance(witness.balance_blinding)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use group::ff::Field;
|
||||
|
||||
use super::*;
|
||||
use crate::{nullifier::NullifierNonce, test_util::seed_rng};
|
||||
|
||||
#[test]
|
||||
fn test_input_proof() {
|
||||
let mut rng = seed_rng(0);
|
||||
|
||||
let note = Note::new(10, "NMO");
|
||||
let nf_sk = NullifierSecret::random(&mut rng);
|
||||
let nonce = NullifierNonce::random(&mut rng);
|
||||
let balance_blinding = Scalar::random(&mut rng);
|
||||
|
||||
let witness = InputWitness {
|
||||
note,
|
||||
nf_sk,
|
||||
nonce,
|
||||
balance_blinding,
|
||||
};
|
||||
|
||||
let input = Input::from_witness(witness.clone());
|
||||
let proof = input.prove(&witness).unwrap();
|
||||
|
||||
assert!(input.verify(&proof));
|
||||
|
||||
let wrong_witnesses = [
|
||||
InputWitness {
|
||||
note: Note::new(11, "NMO"),
|
||||
..witness.clone()
|
||||
},
|
||||
InputWitness {
|
||||
note: Note::new(10, "ETH"),
|
||||
..witness.clone()
|
||||
},
|
||||
InputWitness {
|
||||
nf_sk: NullifierSecret::random(&mut rng),
|
||||
..witness.clone()
|
||||
},
|
||||
InputWitness {
|
||||
nonce: NullifierNonce::random(&mut rng),
|
||||
..witness.clone()
|
||||
},
|
||||
InputWitness {
|
||||
balance_blinding: Scalar::random(&mut rng),
|
||||
..witness.clone()
|
||||
},
|
||||
];
|
||||
|
||||
for wrong_witness in wrong_witnesses {
|
||||
assert!(input.prove(&wrong_witness).is_err());
|
||||
|
||||
let wrong_note = Input::from_witness(wrong_witness.clone());
|
||||
let wrong_proof = wrong_note.prove(&wrong_witness).unwrap();
|
||||
|
||||
assert!(!input.verify(&wrong_proof));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,13 @@
|
||||
mod crypto;
|
||||
mod note;
|
||||
mod nullifier;
|
||||
pub mod crypto;
|
||||
pub mod error;
|
||||
pub mod input;
|
||||
pub mod note;
|
||||
pub mod nullifier;
|
||||
pub mod output;
|
||||
pub mod partial_tx;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_util;
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
|
||||
108
cl/src/note.rs
108
cl/src/note.rs
@ -1,13 +1,22 @@
|
||||
use blake2::{Blake2s256, Digest};
|
||||
use group::GroupEncoding;
|
||||
use jubjub::{ExtendedPoint, Scalar};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use crate::crypto;
|
||||
use crate::{
|
||||
crypto,
|
||||
nullifier::{NullifierCommitment, NullifierNonce},
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
static ref PEDERSON_COMMITMENT_BLINDING_POINT: ExtendedPoint =
|
||||
crypto::hash_to_curve(b"NOMOS_CL_PEDERSON_COMMITMENT_BLINDING");
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct NoteCommitment([u8; 32]);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Note {
|
||||
pub value: u64,
|
||||
pub unit: String,
|
||||
@ -21,53 +30,72 @@ impl Note {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unit_point(&self) -> ExtendedPoint {
|
||||
crypto::hash_to_curve(self.unit.as_bytes())
|
||||
}
|
||||
|
||||
pub fn balance(&self, blinding: Scalar) -> ExtendedPoint {
|
||||
let value_scalar = Scalar::from(self.value);
|
||||
let unit_point = crypto::hash_to_curve(self.unit.as_bytes());
|
||||
self.unit_point() * value_scalar + *PEDERSON_COMMITMENT_BLINDING_POINT * blinding
|
||||
}
|
||||
|
||||
unit_point * value_scalar + *PEDERSON_COMMITMENT_BLINDING_POINT * blinding
|
||||
pub fn commit(&self, nf_pk: NullifierCommitment, nonce: NullifierNonce) -> NoteCommitment {
|
||||
let mut hasher = Blake2s256::new();
|
||||
hasher.update(b"NOMOS_CL_NOTE_COMMIT");
|
||||
hasher.update(self.value.to_le_bytes());
|
||||
hasher.update(self.unit_point().to_bytes());
|
||||
hasher.update(nf_pk.as_bytes());
|
||||
hasher.update(nonce.as_bytes());
|
||||
|
||||
let commit_bytes: [u8; 32] = hasher.finalize().into();
|
||||
NoteCommitment(commit_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_balance_zero_unitless() {
|
||||
// Zero is the same across all units
|
||||
let r = Scalar::from(32);
|
||||
assert_eq!(
|
||||
Note::new(0, "NMO").balance(r),
|
||||
Note::new(0, "ETH").balance(r)
|
||||
);
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_balance_blinding() {
|
||||
// balances are blinded
|
||||
let r1 = Scalar::from(12);
|
||||
let r2 = Scalar::from(8);
|
||||
let a = Note::new(10, "NMO");
|
||||
assert_ne!(a.balance(r1), a.balance(r2));
|
||||
assert_eq!(a.balance(r1), a.balance(r1));
|
||||
}
|
||||
#[test]
|
||||
fn test_balance_zero_unitless() {
|
||||
// Zero is the same across all units
|
||||
let r = Scalar::from(32);
|
||||
assert_eq!(
|
||||
Note::new(0, "NMO").balance(r),
|
||||
Note::new(0, "ETH").balance(r)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_balance_units() {
|
||||
// Unit's differentiate between values.
|
||||
let nmo = Note::new(10, "NMO");
|
||||
let eth = Note::new(10, "ETH");
|
||||
let r = Scalar::from(1337);
|
||||
assert_ne!(nmo.balance(r), eth.balance(r));
|
||||
}
|
||||
#[test]
|
||||
fn test_balance_blinding() {
|
||||
// balances are blinded
|
||||
let r1 = Scalar::from(12);
|
||||
let r2 = Scalar::from(8);
|
||||
let a = Note::new(10, "NMO");
|
||||
assert_ne!(a.balance(r1), a.balance(r2));
|
||||
assert_eq!(a.balance(r1), a.balance(r1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_balance_homomorphism() {
|
||||
let r = Scalar::from(32);
|
||||
let ten = Note::new(10, "NMO");
|
||||
let eight = Note::new(8, "NMO");
|
||||
let two = Note::new(2, "NMO");
|
||||
assert_eq!(ten.balance(r) - eight.balance(r), two.balance(0.into()));
|
||||
#[test]
|
||||
fn test_balance_units() {
|
||||
// Unit's differentiate between values.
|
||||
let nmo = Note::new(10, "NMO");
|
||||
let eth = Note::new(10, "ETH");
|
||||
let r = Scalar::from(1337);
|
||||
assert_ne!(nmo.balance(r), eth.balance(r));
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
ten.balance(54.into()) - ten.balance(48.into()),
|
||||
Note::new(0, "NMO").balance(6.into())
|
||||
);
|
||||
#[test]
|
||||
fn test_balance_homomorphism() {
|
||||
let r = Scalar::from(32);
|
||||
let ten = Note::new(10, "NMO");
|
||||
let eight = Note::new(8, "NMO");
|
||||
let two = Note::new(2, "NMO");
|
||||
assert_eq!(ten.balance(r) - eight.balance(r), two.balance(0.into()));
|
||||
|
||||
assert_eq!(
|
||||
ten.balance(54.into()) - ten.balance(48.into()),
|
||||
Note::new(0, "NMO").balance(6.into())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,8 +6,7 @@
|
||||
// nonce is used to disambiguate when the same nullifier
|
||||
// secret is used for multiple notes.
|
||||
use blake2::{Blake2s256, Digest};
|
||||
use hex;
|
||||
use rand_core::{RngCore, SeedableRng};
|
||||
use rand_core::RngCore;
|
||||
|
||||
// Maintained privately by note holder
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@ -31,16 +30,16 @@ pub struct NullifierNonce([u8; 16]);
|
||||
pub struct Nullifier([u8; 32]);
|
||||
|
||||
impl NullifierSecret {
|
||||
fn random(mut rng: impl RngCore) -> Self {
|
||||
pub fn random(mut rng: impl RngCore) -> Self {
|
||||
let mut sk = [0u8; 16];
|
||||
rng.fill_bytes(&mut sk);
|
||||
Self(sk)
|
||||
}
|
||||
|
||||
fn commit(&self) -> NullifierCommitment {
|
||||
pub fn commit(&self) -> NullifierCommitment {
|
||||
let mut hasher = Blake2s256::new();
|
||||
hasher.update(b"NOMOS_CL_NULL_COMMIT");
|
||||
hasher.update(&self.0);
|
||||
hasher.update(self.0);
|
||||
|
||||
let commit_bytes: [u8; 32] = hasher.finalize().into();
|
||||
NullifierCommitment(commit_bytes)
|
||||
@ -48,60 +47,68 @@ impl NullifierSecret {
|
||||
}
|
||||
|
||||
impl NullifierCommitment {
|
||||
pub fn to_hex(&self) -> String {
|
||||
hex::encode(&self.0)
|
||||
pub fn as_bytes(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn hex(&self) -> String {
|
||||
hex::encode(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl NullifierNonce {
|
||||
fn random(mut rng: impl RngCore) -> Self {
|
||||
pub fn random(mut rng: impl RngCore) -> Self {
|
||||
let mut nonce = [0u8; 16];
|
||||
rng.fill_bytes(&mut nonce);
|
||||
Self(nonce)
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8; 16] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Nullifier {
|
||||
fn new(sk: NullifierSecret, nonce: NullifierNonce) -> Self {
|
||||
pub fn new(sk: NullifierSecret, nonce: NullifierNonce) -> Self {
|
||||
let mut hasher = Blake2s256::new();
|
||||
hasher.update(b"NOMOS_CL_NULLIFIER");
|
||||
hasher.update(&sk.0);
|
||||
hasher.update(&nonce.0);
|
||||
hasher.update(sk.0);
|
||||
hasher.update(nonce.0);
|
||||
|
||||
let nf_bytes: [u8; 32] = hasher.finalize().into();
|
||||
Self(nf_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
fn seed_rng(seed: u64) -> impl rand_core::RngCore {
|
||||
let mut bytes = [0u8; 32];
|
||||
(&mut bytes[..8]).copy_from_slice(&seed.to_le_bytes());
|
||||
rand_chacha::ChaCha12Rng::from_seed(bytes)
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::test_util::seed_rng;
|
||||
|
||||
#[test]
|
||||
fn test_nullifier_commitment_vectors() {
|
||||
assert_eq!(
|
||||
NullifierSecret([0u8; 16]).commit().to_hex(),
|
||||
"384318f9864fe57647bac344e2afdc500a672dedb29d2dc63b004e940e4b382a"
|
||||
);
|
||||
assert_eq!(
|
||||
NullifierSecret([1u8; 16]).commit().to_hex(),
|
||||
"0fd667e6bb39fbdc35d6265726154b839638ea90bcf4e736953ccf27ca5f870b"
|
||||
);
|
||||
assert_eq!(
|
||||
NullifierSecret([u8::MAX; 16]).commit().to_hex(),
|
||||
"1cb78e487eb0b3116389311fdde84cd3f619a4d7f487b29bf5a002eed3784d75"
|
||||
);
|
||||
}
|
||||
#[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 sk = NullifierSecret::random(seed_rng(0));
|
||||
let nonce_1 = NullifierNonce::random(seed_rng(1));
|
||||
let nonce_2 = NullifierNonce::random(seed_rng(2));
|
||||
let nf_1 = Nullifier::new(sk, nonce_1);
|
||||
let nf_2 = Nullifier::new(sk, nonce_2);
|
||||
#[test]
|
||||
fn test_nullifier_same_sk_different_nonce() {
|
||||
let sk = NullifierSecret::random(seed_rng(0));
|
||||
let nonce_1 = NullifierNonce::random(seed_rng(1));
|
||||
let nonce_2 = NullifierNonce::random(seed_rng(2));
|
||||
let nf_1 = Nullifier::new(sk, nonce_1);
|
||||
let nf_2 = Nullifier::new(sk, nonce_2);
|
||||
|
||||
assert_ne!(nf_1, nf_2);
|
||||
assert_ne!(nf_1, nf_2);
|
||||
}
|
||||
}
|
||||
|
||||
38
cl/src/output.rs
Normal file
38
cl/src/output.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use jubjub::{ExtendedPoint, Scalar};
|
||||
|
||||
use crate::{
|
||||
note::{Note, NoteCommitment},
|
||||
nullifier::{NullifierCommitment, NullifierNonce},
|
||||
};
|
||||
|
||||
pub struct Output {
|
||||
pub note_comm: NoteCommitment,
|
||||
pub balance: ExtendedPoint,
|
||||
}
|
||||
|
||||
pub struct OutputWitness {
|
||||
note: Note,
|
||||
nf_pk: NullifierCommitment,
|
||||
nonce: NullifierNonce,
|
||||
balance_blinding: Scalar,
|
||||
}
|
||||
|
||||
// as we don't have SNARKS hooked up yet, the witness will be our proof
|
||||
pub struct OutputProof(OutputWitness);
|
||||
|
||||
impl Output {
|
||||
pub fn prove(&self, w: OutputWitness) -> OutputProof {
|
||||
OutputProof(w)
|
||||
}
|
||||
|
||||
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.note.commit(witness.nf_pk, witness.nonce)
|
||||
&& self.balance == witness.note.balance(witness.balance_blinding)
|
||||
}
|
||||
}
|
||||
31
cl/src/partial_tx.rs
Normal file
31
cl/src/partial_tx.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use jubjub::ExtendedPoint;
|
||||
|
||||
use crate::input::{Input, InputProof};
|
||||
use crate::output::{Output, OutputProof};
|
||||
|
||||
pub struct PartialTx {
|
||||
inputs: Vec<(Input, InputProof)>,
|
||||
outputs: Vec<(Output, OutputProof)>,
|
||||
}
|
||||
|
||||
impl PartialTx {
|
||||
pub fn verify(&self) -> bool {
|
||||
self.inputs.iter().all(|(i, p)| i.verify(p))
|
||||
&& self.outputs.iter().all(|(o, p)| o.verify(p))
|
||||
}
|
||||
|
||||
pub fn balance(&self) -> ExtendedPoint {
|
||||
let in_sum = self
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|(i, _)| i.balance)
|
||||
.sum::<ExtendedPoint>();
|
||||
let out_sum = self
|
||||
.outputs
|
||||
.iter()
|
||||
.map(|(o, _)| o.balance)
|
||||
.sum::<ExtendedPoint>();
|
||||
|
||||
in_sum - out_sum
|
||||
}
|
||||
}
|
||||
7
cl/src/test_util.rs
Normal file
7
cl/src/test_util.rs
Normal file
@ -0,0 +1,7 @@
|
||||
use rand_core::SeedableRng;
|
||||
|
||||
pub fn seed_rng(seed: u64) -> impl rand_core::RngCore {
|
||||
let mut bytes = [0u8; 32];
|
||||
(&mut bytes[..8]).copy_from_slice(&seed.to_le_bytes());
|
||||
rand_chacha::ChaCha12Rng::from_seed(bytes)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user