From 00a2c4019b30ce1ffb502a9526159f8437b6b12e Mon Sep 17 00:00:00 2001 From: moana Date: Tue, 25 Jun 2024 11:22:56 +0200 Subject: [PATCH] core: Add `Sender` struct --- core/CHANGELOG.md | 2 + core/src/lib.rs | 4 +- core/src/note.rs | 236 ++++++++++++++++++++++++++------------ core/src/transaction.rs | 3 +- core/tests/note_test.rs | 41 ++++--- core/tests/tx_skeleton.rs | 146 +++++++++++++++++++++++ 6 files changed, 333 insertions(+), 99 deletions(-) create mode 100644 core/tests/tx_skeleton.rs diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 74cf2eb..ea76963 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add `Sender` struct [#222] - Add `encrypt_sender` function to encrypt the sender with the npk [#214] - Add `decrypt_sender` method to the `Note` [#214] - Add `elgamal::encrypt` and `elgamal::decrypt` @@ -351,6 +352,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Canonical implementation shielded by feature. +[#222]: https://github.com/dusk-network/phoenix/issues/222 [#214]: https://github.com/dusk-network/phoenix/issues/214 [#208]: https://github.com/dusk-network/phoenix/issues/208 [#201]: https://github.com/dusk-network/phoenix/issues/201 diff --git a/core/src/lib.rs b/core/src/lib.rs index c8b57e7..5740dfb 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -28,9 +28,7 @@ pub use keys::hash; pub use keys::public::PublicKey; pub use keys::secret::SecretKey; pub use keys::view::ViewKey; -pub use note::{ - encrypt_sender, Note, NoteType, VALUE_ENC_SIZE as NOTE_VAL_ENC_SIZE, -}; +pub use note::{Note, NoteType, Sender, VALUE_ENC_SIZE as NOTE_VAL_ENC_SIZE}; pub use stealth_address::StealthAddress; #[cfg(feature = "alloc")] diff --git a/core/src/note.rs b/core/src/note.rs index 9af02a4..7bbe6f5 100644 --- a/core/src/note.rs +++ b/core/src/note.rs @@ -78,12 +78,14 @@ pub struct Note { pub(crate) stealth_address: StealthAddress, pub(crate) pos: u64, pub(crate) value_enc: [u8; VALUE_ENC_SIZE], - pub(crate) sender_enc: [(JubJubAffine, JubJubAffine); 2], + pub(crate) sender: Sender, } impl PartialEq for Note { fn eq(&self, other: &Self) -> bool { - self.hash() == other.hash() + self.value_enc == other.value_enc + && self.sender == other.sender + && self.hash() == other.hash() } } @@ -133,7 +135,7 @@ impl Note { stealth_address, pos, value_enc, - sender_enc: encrypt_sender( + sender: Sender::encrypt( stealth_address.note_pk(), sender_pk, &sender_blinder, @@ -172,7 +174,7 @@ impl Note { pub fn transparent_stealth( stealth_address: StealthAddress, value: u64, - sender_enc: [(JubJubAffine, JubJubAffine); 2], + sender: impl Into, ) -> Self { let value_commitment = transparent_value_commitment(value); @@ -187,7 +189,7 @@ impl Note { stealth_address, pos, value_enc, - sender_enc, + sender: sender.into(), } } @@ -224,7 +226,9 @@ impl Note { stealth_address: StealthAddress::default(), pos: 0, value_enc: [0; VALUE_ENC_SIZE], - sender_enc: [(JubJubAffine::default(), JubJubAffine::default()); 2], + sender: Sender::Encryption( + [(JubJubAffine::default(), JubJubAffine::default()); 2], + ), } } @@ -321,8 +325,8 @@ impl Note { /// Returns elgamal encryption of the sender's [`PublicKey`] encrypted using /// the [`StealthAddress::note_pk`] so only the receiver of the [`Note`] /// can decrypt. - pub const fn sender_enc(&self) -> &[(JubJubAffine, JubJubAffine); 2] { - &self.sender_enc + pub const fn sender(&self) -> &Sender { + &self.sender } /// Attempt to decrypt the note value provided a [`ViewKey`]. Always @@ -359,50 +363,6 @@ impl Note { _ => Err(Error::MissingViewKey), } } - - /// Decrypts the [`PublicKey`] of the sender of the [`Note`], using the - /// [`NoteSecretKey`] generated by the receiver's [`SecretKey`] and the - /// [`StealthAddress`] of the [`Note`]. - /// - /// Note: Decryption with an incorrect [`NoteSecretKey`] will still yield a - /// [`PublicKey`], but it will a random one that has nothing to do with the - /// sender's [`PublicKey`]. - pub fn decrypt_sender(&self, note_sk: &NoteSecretKey) -> PublicKey { - let sender_enc_A = self.sender_enc()[0]; - let sender_enc_B = self.sender_enc()[1]; - - let decrypt_A = elgamal::decrypt( - note_sk.as_ref(), - &(sender_enc_A.0.into(), sender_enc_A.1.into()), - ); - let decrypt_B = elgamal::decrypt( - note_sk.as_ref(), - &(sender_enc_B.0.into(), sender_enc_B.1.into()), - ); - - PublicKey::new(decrypt_A, decrypt_B) - } -} - -/// Encrypt the sender [`PublicKey`] in a way that only the receiver of the note -/// can decrypt. -pub fn encrypt_sender( - note_pk: &NotePublicKey, - sender_pk: &PublicKey, - blinder: &[JubJubScalar; 2], -) -> [(JubJubAffine, JubJubAffine); 2] { - let sender_enc_A = - elgamal::encrypt(note_pk.as_ref(), sender_pk.A(), &blinder[0]); - - let sender_enc_B = - elgamal::encrypt(note_pk.as_ref(), sender_pk.B(), &blinder[1]); - - let sender_enc_A: (JubJubAffine, JubJubAffine) = - (sender_enc_A.0.into(), sender_enc_A.1.into()); - let sender_enc_B: (JubJubAffine, JubJubAffine) = - (sender_enc_B.0.into(), sender_enc_B.1.into()); - - [sender_enc_A, sender_enc_B] } const SIZE: usize = 1 @@ -410,7 +370,7 @@ const SIZE: usize = 1 + StealthAddress::SIZE + u64::SIZE + VALUE_ENC_SIZE - + 4 * JubJubAffine::SIZE; + + Sender::SIZE; impl Serializable for Note { type Error = BytesError; @@ -422,27 +382,23 @@ impl Serializable for Note { buf[0] = self.note_type as u8; let mut start = 1; + buf[start..start + JubJubAffine::SIZE] .copy_from_slice(&self.value_commitment.to_bytes()); start += JubJubAffine::SIZE; + buf[start..start + StealthAddress::SIZE] .copy_from_slice(&self.stealth_address.to_bytes()); start += StealthAddress::SIZE; + buf[start..start + u64::SIZE].copy_from_slice(&self.pos.to_le_bytes()); start += u64::SIZE; + buf[start..start + VALUE_ENC_SIZE].copy_from_slice(&self.value_enc); start += VALUE_ENC_SIZE; - buf[start..start + JubJubAffine::SIZE] - .copy_from_slice(&self.sender_enc[0].0.to_bytes()); - start += JubJubAffine::SIZE; - buf[start..start + JubJubAffine::SIZE] - .copy_from_slice(&self.sender_enc[0].1.to_bytes()); - start += JubJubAffine::SIZE; - buf[start..start + JubJubAffine::SIZE] - .copy_from_slice(&self.sender_enc[1].0.to_bytes()); - start += JubJubAffine::SIZE; - buf[start..start + JubJubAffine::SIZE] - .copy_from_slice(&self.sender_enc[1].1.to_bytes()); + + buf[start..start + Sender::SIZE] + .copy_from_slice(&self.sender.to_bytes()); buf } @@ -454,19 +410,18 @@ impl Serializable for Note { bytes[0].try_into().map_err(|_| BytesError::InvalidData)?; let mut buf = &bytes[1..]; + let value_commitment = JubJubAffine::from_reader(&mut buf)?; + let stealth_address = StealthAddress::from_reader(&mut buf)?; + let pos = u64::from_reader(&mut buf)?; let mut value_enc = [0u8; VALUE_ENC_SIZE]; value_enc.copy_from_slice(&buf[..VALUE_ENC_SIZE]); - buf = &buf[VALUE_ENC_SIZE..]; - let sender_enc_A_0 = JubJubAffine::from_reader(&mut buf)?; - let sender_enc_A_1 = JubJubAffine::from_reader(&mut buf)?; - let sender_enc_B_0 = JubJubAffine::from_reader(&mut buf)?; - let sender_enc_B_1 = JubJubAffine::from_reader(&mut buf)?; + let sender = Sender::from_reader(&mut buf)?; Ok(Note { note_type, @@ -474,10 +429,145 @@ impl Serializable for Note { stealth_address, pos, value_enc, - sender_enc: [ - (sender_enc_A_0, sender_enc_A_1), - (sender_enc_B_0, sender_enc_B_1), - ], + sender, }) } } + +/// The sender of the `Note`. +/// This can be either the encrypted sender's [`PublicKey`], if the [`Note`] was +/// created as an output note of a phoenix-transaction, or some contract-data if +/// the [`Note`] was created in another way, e.g. by withdrawing from a +/// contract. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr( + feature = "rkyv-impl", + derive(Archive, Serialize, Deserialize), + archive_attr(derive(bytecheck::CheckBytes)) +)] +pub enum Sender { + /// The sender's [`PublicKey`], encrypted using the note_pk of the + /// stealth-address. + Encryption([(JubJubAffine, JubJubAffine); 2]), + /// Information to identify the origin of a `Note`, if it wasn't created as + /// a phoenix-transaction output-note. + ContractInfo([u8; 4 * JubJubAffine::SIZE]), +} + +impl Sender { + /// Create a new [`Sender`] enum by encrypting the sender's [`PublicKey`] in + /// a way that only the receiver of the note can decrypt. + pub fn encrypt( + note_pk: &NotePublicKey, + sender_pk: &PublicKey, + blinder: &[JubJubScalar; 2], + ) -> Self { + let sender_enc_A = + elgamal::encrypt(note_pk.as_ref(), sender_pk.A(), &blinder[0]); + + let sender_enc_B = + elgamal::encrypt(note_pk.as_ref(), sender_pk.B(), &blinder[1]); + + let sender_enc_A: (JubJubAffine, JubJubAffine) = + (sender_enc_A.0.into(), sender_enc_A.1.into()); + let sender_enc_B: (JubJubAffine, JubJubAffine) = + (sender_enc_B.0.into(), sender_enc_B.1.into()); + + Self::Encryption([sender_enc_A, sender_enc_B]) + } + + /// Decrypts the [`PublicKey`] of the sender of the [`Note`], using the + /// [`NoteSecretKey`] generated by the receiver's [`SecretKey`] and the + /// [`StealthAddress`] of the [`Note`]. + /// + /// Note: Decryption with an *incorrect* [`NoteSecretKey`] will still yield + /// a [`PublicKey`], but in this case, the public-key will be a random one + /// that has nothing to do with the sender's [`PublicKey`]. + /// + /// Returns an error if the sender is of type [`Sender::ContractInfo`]. + pub fn decrypt(&self, note_sk: &NoteSecretKey) -> Result { + let sender_enc = match self { + Sender::Encryption(enc) => enc, + Sender::ContractInfo(_) => { + return Err(Error::InvalidEncryption); + } + }; + + let sender_enc_A = sender_enc[0]; + let sender_enc_B = sender_enc[1]; + + let decrypt_A = elgamal::decrypt( + note_sk.as_ref(), + &(sender_enc_A.0.into(), sender_enc_A.1.into()), + ); + let decrypt_B = elgamal::decrypt( + note_sk.as_ref(), + &(sender_enc_B.0.into(), sender_enc_B.1.into()), + ); + + Ok(PublicKey::new(decrypt_A, decrypt_B)) + } +} + +impl Serializable<{ 1 + 4 * JubJubAffine::SIZE }> for Sender { + type Error = BytesError; + + /// Converts a Note into a byte representation + fn to_bytes(&self) -> [u8; Self::SIZE] { + let mut buf = [0u8; Self::SIZE]; + + match self { + Sender::Encryption(sender_enc) => { + buf[0] = 0; + let mut start = 1; + + buf[start..start + JubJubAffine::SIZE] + .copy_from_slice(&sender_enc[0].0.to_bytes()); + start += JubJubAffine::SIZE; + + buf[start..start + JubJubAffine::SIZE] + .copy_from_slice(&sender_enc[0].1.to_bytes()); + start += JubJubAffine::SIZE; + + buf[start..start + JubJubAffine::SIZE] + .copy_from_slice(&sender_enc[1].0.to_bytes()); + start += JubJubAffine::SIZE; + + buf[start..start + JubJubAffine::SIZE] + .copy_from_slice(&sender_enc[1].1.to_bytes()); + } + Sender::ContractInfo(contract_data) => { + buf[0] = 1; + buf[1..].copy_from_slice(&contract_data[..]); + } + } + + buf + } + + /// Attempts to convert a byte representation of a note into a `Note`, + /// failing if the input is invalid + fn from_bytes(bytes: &[u8; Self::SIZE]) -> Result { + let sender = match bytes[0] { + 0 => { + let mut buf = &bytes[1..]; + let sender_enc_A_0 = JubJubAffine::from_reader(&mut buf)?; + let sender_enc_A_1 = JubJubAffine::from_reader(&mut buf)?; + let sender_enc_B_0 = JubJubAffine::from_reader(&mut buf)?; + let sender_enc_B_1 = JubJubAffine::from_reader(&mut buf)?; + Sender::Encryption([ + (sender_enc_A_0, sender_enc_A_1), + (sender_enc_B_0, sender_enc_B_1), + ]) + } + 1 => { + let mut contract_data = [0u8; 4 * JubJubAffine::SIZE]; + contract_data.copy_from_slice(&bytes[1..Self::SIZE]); + Sender::ContractInfo(contract_data) + } + _ => return Err(BytesError::InvalidData), + }; + + Ok(sender) + } +} diff --git a/core/src/transaction.rs b/core/src/transaction.rs index 5adbfb8..ef7b32c 100644 --- a/core/src/transaction.rs +++ b/core/src/transaction.rs @@ -69,7 +69,6 @@ impl TxSkeleton { let num_nullifiers = self.nullifiers.len() as u64; bytes.extend(num_nullifiers.to_bytes()); - self.nullifiers.iter().for_each(|nullifier| { bytes.extend(nullifier.to_bytes()); }); @@ -88,9 +87,9 @@ impl TxSkeleton { pub fn from_slice(buf: &[u8]) -> Result { let mut buffer = buf; let root = BlsScalar::from_reader(&mut buffer)?; + let num_nullifiers = u64::from_reader(&mut buffer)?; let mut nullifiers = Vec::with_capacity(num_nullifiers as usize); - for _ in 0..num_nullifiers { nullifiers.push(BlsScalar::from_reader(&mut buffer)?); } diff --git a/core/tests/note_test.rs b/core/tests/note_test.rs index eaa64b9..cf1b24a 100644 --- a/core/tests/note_test.rs +++ b/core/tests/note_test.rs @@ -4,14 +4,15 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use dusk_jubjub::JubJubScalar; +use dusk_bytes::Serializable; +use dusk_jubjub::{JubJubAffine, JubJubScalar}; use ff::Field; use phoenix_core::{ - encrypt_sender, value_commitment, Error, Note, NoteType, PublicKey, - SecretKey, ViewKey, + value_commitment, Error, Note, NoteType, PublicKey, SecretKey, Sender, + ViewKey, }; use rand::rngs::StdRng; -use rand::SeedableRng; +use rand::{RngCore, SeedableRng}; const TRANSPARENT_BLINDER: JubJubScalar = JubJubScalar::zero(); @@ -46,8 +47,10 @@ fn transparent_note() -> Result<(), Error> { assert_eq!(value, note.value(None)?); assert_eq!( sender_pk, - note.decrypt_sender(&receiver_sk.gen_note_sk(note.stealth_address())) + note.sender() + .decrypt(&receiver_sk.gen_note_sk(note.stealth_address()))? ); + assert_eq!(note, Note::from_bytes(¬e.to_bytes())?); Ok(()) } @@ -56,7 +59,6 @@ fn transparent_note() -> Result<(), Error> { fn transparent_stealth_note() -> Result<(), Error> { let mut rng = StdRng::seed_from_u64(0xc0b); - let sender_pk = PublicKey::from(&SecretKey::random(&mut rng)); let receiver_sk = SecretKey::random(&mut rng); let receiver_pk = PublicKey::from(&receiver_sk); @@ -65,16 +67,11 @@ fn transparent_stealth_note() -> Result<(), Error> { let value = 25; - let sender_enc = encrypt_sender( - stealth.note_pk(), - &sender_pk, - &[ - JubJubScalar::random(&mut rng), - JubJubScalar::random(&mut rng), - ], - ); + let mut sender_data = [0u8; 4 * JubJubAffine::SIZE]; + rng.fill_bytes(&mut sender_data); + let sender = Sender::ContractInfo(sender_data); - let note = Note::transparent_stealth(stealth, value, sender_enc); + let note = Note::transparent_stealth(stealth, value, sender); assert_eq!(note.note_type(), NoteType::Transparent); assert_eq!( @@ -83,10 +80,8 @@ fn transparent_stealth_note() -> Result<(), Error> { ); assert_eq!(value, note.value(None)?); assert_eq!(stealth, *note.stealth_address()); - assert_eq!( - sender_pk, - note.decrypt_sender(&receiver_sk.gen_note_sk(note.stealth_address())) - ); + assert_eq!(Sender::ContractInfo(sender_data), *note.sender()); + assert_eq!(note, Note::from_bytes(¬e.to_bytes())?); Ok(()) } @@ -125,8 +120,10 @@ fn obfuscated_note() -> Result<(), Error> { assert_eq!(value, note.value(Some(&receiver_vk))?); assert_eq!( sender_pk, - note.decrypt_sender(&receiver_sk.gen_note_sk(note.stealth_address())) + note.sender() + .decrypt(&receiver_sk.gen_note_sk(note.stealth_address()))? ); + assert_eq!(note, Note::from_bytes(¬e.to_bytes())?); Ok(()) } @@ -165,8 +162,10 @@ fn obfuscated_deterministic_note() -> Result<(), Error> { assert_eq!(value_blinder, note.value_blinder(Some(&receiver_vk))?); assert_eq!( sender_pk, - note.decrypt_sender(&receiver_sk.gen_note_sk(note.stealth_address())) + note.sender() + .decrypt(&receiver_sk.gen_note_sk(note.stealth_address()))? ); + assert_eq!(note, Note::from_bytes(¬e.to_bytes())?); Ok(()) } diff --git a/core/tests/tx_skeleton.rs b/core/tests/tx_skeleton.rs new file mode 100644 index 0000000..a071e55 --- /dev/null +++ b/core/tests/tx_skeleton.rs @@ -0,0 +1,146 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use dusk_bls12_381::BlsScalar; +use dusk_jubjub::JubJubScalar; +use ff::Field; +use phoenix_core::{Error, Note, PublicKey, SecretKey, TxSkeleton}; +use rand::rngs::StdRng; +use rand::SeedableRng; + +fn output_notes(rng: &mut StdRng) -> [Note; 2] { + let sender_pk = PublicKey::from(&SecretKey::random(rng)); + let receiver_pk = PublicKey::from(&SecretKey::random(rng)); + let value_blinder = JubJubScalar::random(&mut *rng); + let sender_blinder = [ + JubJubScalar::random(&mut *rng), + JubJubScalar::random(&mut *rng), + ]; + let note1 = Note::obfuscated( + rng, + &sender_pk, + &receiver_pk, + 3431, + value_blinder, + sender_blinder, + ); + let note2 = Note::transparent( + rng, + &sender_pk, + &receiver_pk, + 4115690, + sender_blinder, + ); + + [note1, note2] +} + +#[test] +fn serialize_1_2() -> Result<(), Error> { + let mut rng = StdRng::seed_from_u64(0xc0b); + + let nullifiers = vec![BlsScalar::random(&mut rng)]; + + let tx_skeleton = TxSkeleton { + root: BlsScalar::random(&mut rng), + outputs: output_notes(&mut rng), + nullifiers, + max_fee: 4671, + deposit: 3426, + }; + + let deserialized = TxSkeleton::from_slice(&tx_skeleton.to_var_bytes())?; + + assert_eq!(tx_skeleton, deserialized); + assert_eq!( + tx_skeleton.to_hash_input_bytes(), + deserialized.to_hash_input_bytes(), + ); + + Ok(()) +} + +#[test] +fn serialize_2_2() -> Result<(), Error> { + let mut rng = StdRng::seed_from_u64(0xc0b); + + let nullifiers = + vec![BlsScalar::random(&mut rng), BlsScalar::random(&mut rng)]; + + let tx_skeleton = TxSkeleton { + root: BlsScalar::random(&mut rng), + outputs: output_notes(&mut rng), + nullifiers, + max_fee: 4415, + deposit: 245, + }; + + let deserialized = TxSkeleton::from_slice(&tx_skeleton.to_var_bytes())?; + assert_eq!(tx_skeleton, deserialized,); + assert_eq!( + tx_skeleton.to_hash_input_bytes(), + deserialized.to_hash_input_bytes(), + ); + + Ok(()) +} + +#[test] +fn serialize_3_2() -> Result<(), Error> { + let mut rng = StdRng::seed_from_u64(0xc0b); + + let nullifiers = vec![ + BlsScalar::random(&mut rng), + BlsScalar::random(&mut rng), + BlsScalar::random(&mut rng), + ]; + + let tx_skeleton = TxSkeleton { + root: BlsScalar::random(&mut rng), + outputs: output_notes(&mut rng), + nullifiers, + max_fee: 612, + deposit: 793426, + }; + + let deserialized = TxSkeleton::from_slice(&tx_skeleton.to_var_bytes())?; + assert_eq!(tx_skeleton, deserialized,); + assert_eq!( + tx_skeleton.to_hash_input_bytes(), + deserialized.to_hash_input_bytes(), + ); + + Ok(()) +} + +#[test] +fn serialize_4_2() -> Result<(), Error> { + let mut rng = StdRng::seed_from_u64(0xc0b); + + let nullifiers = vec![ + BlsScalar::random(&mut rng), + BlsScalar::random(&mut rng), + BlsScalar::random(&mut rng), + BlsScalar::random(&mut rng), + ]; + + let tx_skeleton = TxSkeleton { + root: BlsScalar::random(&mut rng), + outputs: output_notes(&mut rng), + nullifiers, + max_fee: 451239, + deposit: 4776780, + }; + + let deserialized = TxSkeleton::from_slice(&tx_skeleton.to_var_bytes())?; + assert_eq!(tx_skeleton, deserialized,); + assert_eq!( + tx_skeleton.to_hash_input_bytes(), + deserialized.to_hash_input_bytes(), + ); + + Ok(()) +}