2024-07-09 17:12:38 +04:00

126 lines
3.6 KiB
Rust

// The Nullifier is used to detect if a note has
// already been consumed.
// The same nullifier secret may be used across multiple
// notes to allow users to hold fewer secrets. A note
// nonce is used to disambiguate when the same nullifier
// secret is used for multiple notes.
use blake2::{Blake2s256, Digest};
use rand_core::RngCore;
use serde::{Deserialize, Serialize};
// TODO: create a nullifier witness and use it throughout.
// struct NullifierWitness {
// nf_sk: NullifierSecret,
// nonce: NullifierNonce
// }
// Maintained privately by note holder
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct NullifierSecret([u8; 16]);
// Nullifier commitment is public information that
// can be provided to anyone wishing to transfer
// you a note
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct NullifierCommitment([u8; 32]);
// To allow users to maintain fewer nullifier secrets, we
// provide a nonce to differentiate notes controlled by the same
// secret. Each note is assigned a unique nullifier nonce.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
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, Serialize, Deserialize)]
pub struct Nullifier([u8; 32]);
impl NullifierSecret {
pub fn random(mut rng: impl RngCore) -> Self {
let mut sk = [0u8; 16];
rng.fill_bytes(&mut sk);
Self(sk)
}
pub fn commit(&self) -> NullifierCommitment {
let mut hasher = Blake2s256::new();
hasher.update(b"NOMOS_CL_NULL_COMMIT");
hasher.update(self.0);
let commit_bytes: [u8; 32] = hasher.finalize().into();
NullifierCommitment(commit_bytes)
}
}
impl NullifierCommitment {
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn hex(&self) -> String {
hex::encode(self.0)
}
}
impl NullifierNonce {
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 {
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);
let nf_bytes: [u8; 32] = hasher.finalize().into();
Self(nf_bytes)
}
pub(crate) fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::test_util::seed_rng;
#[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);
assert_ne!(nf_1, nf_2);
}
}