use borsh::{BorshDeserialize, BorshSerialize}; use risc0_zkvm::sha::{Impl, Sha256 as _}; use serde::{Deserialize, Serialize}; use crate::{Commitment, account::AccountId}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[cfg_attr(any(feature = "host", test), derive(Clone, Hash))] pub struct NullifierPublicKey(pub [u8; 32]); impl AsRef<[u8]> for NullifierPublicKey { fn as_ref(&self) -> &[u8] { self.0.as_slice() } } impl From<&NullifierSecretKey> for NullifierPublicKey { fn from(value: &NullifierSecretKey) -> Self { const PREFIX: &[u8; 8] = b"LEE/keys"; const SUFFIX_1: &[u8; 1] = &[7]; const SUFFIX_2: &[u8; 23] = &[0; 23]; let mut bytes = Vec::new(); bytes.extend_from_slice(PREFIX); bytes.extend_from_slice(value); bytes.extend_from_slice(SUFFIX_1); bytes.extend_from_slice(SUFFIX_2); Self( Impl::hash_bytes(&bytes) .as_bytes() .try_into() .expect("hash should be exactly 32 bytes long"), ) } } pub type NullifierSecretKey = [u8; 32]; #[derive(Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[cfg_attr( any(feature = "host", test), derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash) )] pub struct Nullifier(pub(super) [u8; 32]); #[cfg(any(feature = "host", test))] impl std::fmt::Debug for Nullifier { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use std::fmt::Write as _; let hex: String = self.0.iter().fold(String::new(), |mut acc, b| { write!(acc, "{b:02x}").expect("writing to string should not fail"); acc }); write!(f, "Nullifier({hex})") } } impl Nullifier { /// Computes a nullifier for an account update. #[must_use] pub fn for_account_update(commitment: &Commitment, nsk: &NullifierSecretKey) -> Self { const UPDATE_PREFIX: &[u8; 32] = b"/LEE/v0.3/Nullifier/Update/\x00\x00\x00\x00\x00"; let mut bytes = UPDATE_PREFIX.to_vec(); bytes.extend_from_slice(&commitment.to_byte_array()); bytes.extend_from_slice(nsk); Self(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap()) } /// Computes a nullifier for an account initialization. #[must_use] pub fn for_account_initialization(account_id: &AccountId) -> Self { const INIT_PREFIX: &[u8; 32] = b"/LEE/v0.3/Nullifier/Initialize/\x00"; let mut bytes = INIT_PREFIX.to_vec(); bytes.extend_from_slice(account_id.value()); Self(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap()) } } #[cfg(test)] mod tests { use super::*; use crate::account::Identifier; #[test] fn constructor_for_account_update() { let commitment = Commitment((0..32_u8).collect::>().try_into().unwrap()); let nsk = [0x42; 32]; let expected_nullifier = Nullifier([ 70, 162, 122, 15, 33, 237, 244, 216, 89, 223, 90, 50, 94, 184, 210, 144, 174, 64, 189, 254, 62, 255, 5, 1, 139, 227, 194, 185, 16, 30, 55, 48, ]); let nullifier = Nullifier::for_account_update(&commitment, &nsk); assert_eq!(nullifier, expected_nullifier); } #[test] fn constructor_for_account_initialization() { let npk = NullifierPublicKey([ 112, 188, 193, 129, 150, 55, 228, 67, 88, 168, 29, 151, 5, 92, 23, 190, 17, 162, 164, 255, 29, 105, 42, 186, 43, 11, 157, 168, 132, 225, 17, 163, ]); let identifier = Identifier(0_u128); let account_id = AccountId::private_account_id(&npk, identifier); let expected_nullifier = Nullifier([ 63, 58, 51, 159, 15, 100, 240, 243, 60, 143, 151, 108, 116, 144, 101, 6, 134, 72, 198, 249, 108, 80, 237, 194, 143, 66, 225, 191, 111, 49, 66, 54, ]); let nullifier = Nullifier::for_account_initialization(&account_id); assert_eq!(nullifier, expected_nullifier); } #[test] fn from_secret_key() { let nsk = [ 57, 5, 64, 115, 153, 56, 184, 51, 207, 238, 99, 165, 147, 214, 213, 151, 30, 251, 30, 196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28, ]; let expected_npk = NullifierPublicKey([ 78, 20, 20, 5, 177, 198, 233, 100, 175, 134, 174, 200, 24, 205, 68, 215, 130, 74, 35, 54, 154, 184, 219, 42, 168, 106, 126, 147, 133, 244, 18, 218, ]); let npk = NullifierPublicKey::from(&nsk); assert_eq!(npk, expected_npk); } }