add tag computation

This commit is contained in:
Sergio Chouhy 2025-09-01 18:12:13 -03:00
parent a36592485c
commit 786d0b2899
2 changed files with 100 additions and 18 deletions

View File

@ -1,16 +1,45 @@
use nssa_core::{ use nssa_core::{
Commitment, CommitmentSetDigest, Nullifier, PrivacyPreservingCircuitOutput, Commitment, CommitmentSetDigest, Nullifier, NullifierPublicKey, PrivacyPreservingCircuitOutput,
account::{Account, Nonce}, account::{Account, Nonce},
encryption::{Ciphertext, EphemeralPublicKey}, encryption::{Ciphertext, EphemeralPublicKey, IncomingViewingPublicKey},
}; };
use sha2::{Digest, Sha256};
use crate::{Address, error::NssaError}; use crate::{Address, error::NssaError};
pub type ViewTag = u8;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncryptedAccountData { pub struct EncryptedAccountData {
pub(crate) ciphertext: Ciphertext, pub(crate) ciphertext: Ciphertext,
pub(crate) epk: EphemeralPublicKey, pub(crate) epk: EphemeralPublicKey,
pub(crate) view_tag: u8, pub(crate) view_tag: ViewTag,
}
impl EncryptedAccountData {
fn new(
ciphertext: Ciphertext,
npk: NullifierPublicKey,
ivk: IncomingViewingPublicKey,
epk: EphemeralPublicKey,
) -> Self {
let view_tag = Self::compute_view_tag(npk, ivk);
Self {
ciphertext,
epk,
view_tag,
}
}
/// Computes the tag as the first byte of SHA256("/NSSA/v0.1/ViewTag" || Npk || Ivk)
pub fn compute_view_tag(npk: NullifierPublicKey, ivk: IncomingViewingPublicKey) -> ViewTag {
let mut hasher = Sha256::new();
hasher.update(b"/NSSA/v0.1/ViewTag");
hasher.update(npk.to_byte_array());
hasher.update(ivk.to_bytes());
let digest: [u8; 32] = hasher.finalize().into();
digest[0]
}
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@ -27,10 +56,14 @@ impl Message {
pub fn try_from_circuit_output( pub fn try_from_circuit_output(
public_addresses: Vec<Address>, public_addresses: Vec<Address>,
nonces: Vec<Nonce>, nonces: Vec<Nonce>,
ephemeral_public_keys: Vec<EphemeralPublicKey>, public_keys: Vec<(
NullifierPublicKey,
IncomingViewingPublicKey,
EphemeralPublicKey,
)>,
output: PrivacyPreservingCircuitOutput, output: PrivacyPreservingCircuitOutput,
) -> Result<Self, NssaError> { ) -> Result<Self, NssaError> {
if ephemeral_public_keys.len() != output.ciphertexts.len() { if public_keys.len() != output.ciphertexts.len() {
return Err(NssaError::InvalidInput( return Err(NssaError::InvalidInput(
"Ephemeral public keys and ciphertexts length mismatch".into(), "Ephemeral public keys and ciphertexts length mismatch".into(),
)); ));
@ -39,11 +72,9 @@ impl Message {
let encrypted_private_post_states = output let encrypted_private_post_states = output
.ciphertexts .ciphertexts
.into_iter() .into_iter()
.zip(ephemeral_public_keys) .zip(public_keys)
.map(|(ciphertext, epk)| EncryptedAccountData { .map(|(ciphertext, (npk, ivk, epk))| {
ciphertext, EncryptedAccountData::new(ciphertext, npk, ivk, epk)
epk,
view_tag: 0, // TODO: implement
}) })
.collect(); .collect();
Ok(Self { Ok(Self {
@ -61,9 +92,17 @@ impl Message {
pub mod tests { pub mod tests {
use std::io::Cursor; use std::io::Cursor;
use nssa_core::{Commitment, Nullifier, NullifierPublicKey, account::Account}; use nssa_core::{
Commitment, EncryptionScheme, Nullifier, NullifierPublicKey, SharedSecretKey,
account::Account,
encryption::{EphemeralPublicKey, IncomingViewingPublicKey},
};
use sha2::{Digest, Sha256};
use crate::{Address, privacy_preserving_transaction::message::Message}; use crate::{
Address,
privacy_preserving_transaction::message::{EncryptedAccountData, Message},
};
pub fn message_for_tests() -> Message { pub fn message_for_tests() -> Message {
let account1 = Account::default(); let account1 = Account::default();
@ -108,4 +147,35 @@ pub mod tests {
assert_eq!(message, message_from_cursor); assert_eq!(message, message_from_cursor);
} }
#[test]
fn test_encrypted_account_data_constructor() {
let npk = NullifierPublicKey::from(&[1; 32]);
let ivk = IncomingViewingPublicKey::from(&[2; 32]);
let account = Account::default();
let commitment = Commitment::new(&npk, &account);
let esk = [3; 32];
let shared_secret = SharedSecretKey::new(&esk, &ivk);
let epk = EphemeralPublicKey::from_scalar(esk);
let ciphertext = EncryptionScheme::encrypt(&account, &shared_secret, &commitment, 2);
let encrypted_account_data =
EncryptedAccountData::new(ciphertext.clone(), npk.clone(), ivk.clone(), epk.clone());
let expected_view_tag = {
let mut hasher = Sha256::new();
hasher.update(b"/NSSA/v0.1/ViewTag");
hasher.update(npk.to_byte_array());
hasher.update(ivk.to_bytes());
let digest: [u8; 32] = hasher.finalize().into();
digest[0]
};
assert_eq!(encrypted_account_data.ciphertext, ciphertext);
assert_eq!(encrypted_account_data.epk, epk);
assert_eq!(
encrypted_account_data.view_tag,
EncryptedAccountData::compute_view_tag(npk, ivk)
);
assert_eq!(encrypted_account_data.view_tag, expected_view_tag);
}
} }

View File

@ -801,7 +801,7 @@ pub mod tests {
let message = Message::try_from_circuit_output( let message = Message::try_from_circuit_output(
vec![sender_keys.address()], vec![sender_keys.address()],
vec![sender_nonce], vec![sender_nonce],
vec![epk], vec![(recipient_keys.npk(), recipient_keys.ivk(), epk)],
output, output,
) )
.unwrap(); .unwrap();
@ -854,8 +854,16 @@ pub mod tests {
) )
.unwrap(); .unwrap();
let message = let message = Message::try_from_circuit_output(
Message::try_from_circuit_output(vec![], vec![], vec![epk_1, epk_2], output).unwrap(); vec![],
vec![],
vec![
(sender_keys.npk(), sender_keys.ivk(), epk_1),
(recipient_keys.npk(), recipient_keys.ivk(), epk_2),
],
output,
)
.unwrap();
let witness_set = WitnessSet::for_message(&message, proof, &[]); let witness_set = WitnessSet::for_message(&message, proof, &[]);
@ -899,9 +907,13 @@ pub mod tests {
) )
.unwrap(); .unwrap();
let message = let message = Message::try_from_circuit_output(
Message::try_from_circuit_output(vec![*recipient_address], vec![], vec![epk], output) vec![*recipient_address],
.unwrap(); vec![],
vec![(sender_keys.npk(), sender_keys.ivk(), epk)],
output,
)
.unwrap();
let witness_set = WitnessSet::for_message(&message, proof, &[]); let witness_set = WitnessSet::for_message(&message, proof, &[]);