diff --git a/CHANGELOG.md b/CHANGELOG.md index d68c497..bd66729 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add an `Encryption` module that uses AES-GCM [#152]. + +### Changed + +- Use AES-GCM from the `Encryption` module throughout the code, instead of `PoseidonCipher`. + +### Removed + +- Remove the `Message` module. + ## [0.26.0] - 2024-04-10 ### Changed @@ -261,7 +273,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Removal of anyhow error implementation. - Canonical implementation shielded by feature. - + +[#152]: https://github.com/dusk-network/phoenix-core/issues/152 [#136]: https://github.com/dusk-network/phoenix-core/issues/136 [#126]: https://github.com/dusk-network/phoenix-core/issues/126 [#119]: https://github.com/dusk-network/phoenix-core/issues/119 @@ -282,7 +295,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#67]: https://github.com/dusk-network/phoenix-core/issues/67 [#61]: https://github.com/dusk-network/phoenix-core/issues/61 - + [Unreleased]: https://github.com/dusk-network/phoenix-core/compare/v0.26.0...HEAD [0.26.0]: https://github.com/dusk-network/phoenix-core/compare/v0.25.0...v0.26.0 [0.25.0]: https://github.com/dusk-network/phoenix-core/compare/v0.24.0...v0.25.0 diff --git a/Cargo.toml b/Cargo.toml index 7ea2a2d..58cbb92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ subtle = { version = "^2.2.1", default-features = false } rkyv = { version = "0.7", optional = true, default-features = false } bytecheck = { version = "0.6", optional = true, default-features = false } ff = { version = "0.13", default-features = false } +aes-gcm = "0.10" [dev-dependencies] assert_matches = "1.3" diff --git a/src/convert.rs b/src/convert.rs index 9a69f18..2a66f03 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -4,13 +4,13 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. +use crate::note::ENCRYPTION_SIZE; use crate::note::TRANSPARENT_BLINDER; use crate::{Crossover, Error, Fee, Note, NoteType, Remainder}; use core::convert::TryFrom; -use dusk_bls12_381::BlsScalar; +use dusk_bytes::Serializable; use dusk_jubjub::{JubJubScalar, GENERATOR_EXTENDED, GENERATOR_NUMS_EXTENDED}; -use dusk_poseidon::cipher::PoseidonCipher; impl From<(Fee, Crossover)> for Note { fn from((fee, crossover): (Fee, Crossover)) -> Note { @@ -19,8 +19,7 @@ impl From<(Fee, Crossover)> for Note { } = fee; let Crossover { value_commitment, - nonce, - encrypted_data, + encryption, .. } = crossover; @@ -30,10 +29,9 @@ impl From<(Fee, Crossover)> for Note { Note { note_type, value_commitment, - nonce, stealth_address, pos, - encrypted_data, + encryption, } } } @@ -49,8 +47,7 @@ impl TryFrom for (Fee, Crossover) { let Note { stealth_address, value_commitment, - nonce, - encrypted_data, + encryption, .. } = note; @@ -62,8 +59,7 @@ impl TryFrom for (Fee, Crossover) { }, Crossover { value_commitment, - nonce, - encrypted_data, + encryption, }, )) } @@ -79,28 +75,20 @@ impl From for Note { let stealth_address = remainder.stealth_address; let value = remainder.gas_changes; - let nonce = BlsScalar::zero(); let value_commitment = JubJubScalar::from(value); let value_commitment = (GENERATOR_EXTENDED * value_commitment) + (GENERATOR_NUMS_EXTENDED * TRANSPARENT_BLINDER); - let encrypted_data = { - let zero = TRANSPARENT_BLINDER.into(); - let mut encrypted_data = [zero; PoseidonCipher::cipher_size()]; - - encrypted_data[0] = BlsScalar::from(value); - - PoseidonCipher::new(encrypted_data) - }; + let mut encryption = [0u8; ENCRYPTION_SIZE]; + encryption[..u64::SIZE].copy_from_slice(&value.to_bytes()); Note { note_type, value_commitment, - nonce, stealth_address, pos, - encrypted_data, + encryption, } } } diff --git a/src/crossover.rs b/src/crossover.rs index 1ae9017..6657605 100644 --- a/src/crossover.rs +++ b/src/crossover.rs @@ -6,17 +6,16 @@ //! Fee module contains the logic related to `Crossover` structure -use dusk_bls12_381::BlsScalar; use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable}; use dusk_jubjub::{JubJubAffine, JubJubExtended}; -use dusk_poseidon::cipher::PoseidonCipher; -use dusk_poseidon::sponge; #[cfg(feature = "rkyv-impl")] use rkyv::{Archive, Deserialize, Serialize}; +use crate::note::ENCRYPTION_SIZE; + /// Crossover structure containing obfuscated encrypted data -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Debug)] #[cfg_attr( feature = "rkyv-impl", derive(Archive, Serialize, Deserialize), @@ -24,19 +23,27 @@ use rkyv::{Archive, Deserialize, Serialize}; )] pub struct Crossover { pub(crate) value_commitment: JubJubExtended, - pub(crate) nonce: BlsScalar, - pub(crate) encrypted_data: PoseidonCipher, + pub(crate) encryption: [u8; ENCRYPTION_SIZE], +} + +impl Default for Crossover { + fn default() -> Self { + Self { + value_commitment: JubJubExtended::default(), + encryption: [0; ENCRYPTION_SIZE], + } + } } impl PartialEq for Crossover { fn eq(&self, other: &Self) -> bool { - self.hash() == other.hash() + self.value_commitment() == other.value_commitment() } } impl Eq for Crossover {} -impl Serializable<{ 64 + PoseidonCipher::SIZE }> for Crossover { +impl Serializable<{ 32 + ENCRYPTION_SIZE }> for Crossover { type Error = BytesError; /// Converts a Crossover into it's byte representation @@ -46,8 +53,7 @@ impl Serializable<{ 64 + PoseidonCipher::SIZE }> for Crossover { buf[..32].copy_from_slice( &JubJubAffine::from(&self.value_commitment).to_bytes(), ); - buf[32..64].copy_from_slice(&self.nonce.to_bytes()); - buf[64..].copy_from_slice(&self.encrypted_data.to_bytes()); + buf[32..].copy_from_slice(&self.encryption); buf } @@ -56,57 +62,25 @@ impl Serializable<{ 64 + PoseidonCipher::SIZE }> for Crossover { fn from_bytes(bytes: &[u8; Self::SIZE]) -> Result { let value_commitment = JubJubExtended::from(JubJubAffine::from_slice(&bytes[..32])?); - let nonce = BlsScalar::from_slice(&bytes[32..])?; - let encrypted_data = PoseidonCipher::from_slice(&bytes[64..])?; + let mut encryption = [0u8; ENCRYPTION_SIZE]; + encryption.copy_from_slice(&bytes[32..]); Ok(Crossover { value_commitment, - nonce, - encrypted_data, + encryption, }) } } impl Crossover { - /// Represent the crossover as a sequence of scalars to be used as input for - /// sponge hash functions - /// - /// It is composed by 3 scalars, in order: - /// * Value commitment X - /// * Value commitment Y - /// * Nonce - /// - /// And also appends the scalars that composes the [`PoseidonCipher`] - pub fn to_hash_inputs( - &self, - ) -> [BlsScalar; 3 + PoseidonCipher::cipher_size()] { - let mut inputs = [BlsScalar::zero(); 3 + PoseidonCipher::cipher_size()]; - - inputs[..2].copy_from_slice(&self.value_commitment().to_hash_inputs()); - inputs[2] = self.nonce; - inputs[3..].copy_from_slice(self.encrypted_data.cipher()); - - inputs - } - - /// Sponge hash of the crossover hash inputs representation - pub fn hash(&self) -> BlsScalar { - sponge::hash(&self.to_hash_inputs()) - } - - /// Returns the Nonce used for the encrypt / decrypt of data for this note - pub const fn nonce(&self) -> &BlsScalar { - &self.nonce - } - /// Returns the value commitment `H(value, blinding_factor)` pub const fn value_commitment(&self) -> &JubJubExtended { &self.value_commitment } /// Returns the encrypted data - pub const fn encrypted_data(&self) -> &PoseidonCipher { - &self.encrypted_data + pub const fn encryption(&self) -> &[u8; ENCRYPTION_SIZE] { + &self.encryption } } diff --git a/src/encryption.rs b/src/encryption.rs new file mode 100644 index 0000000..370c953 --- /dev/null +++ b/src/encryption.rs @@ -0,0 +1,67 @@ +// 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_jubjub::JubJubAffine; +use rand_core::{CryptoRng, RngCore}; + +use aes_gcm::{ + aead::{Aead, AeadCore, KeyInit}, + Aes256Gcm, Key, +}; + +use crate::Error; + +const NONCE_SIZE: usize = 12; + +/// Size of the extra encryption data required by the +/// cipher: the nonce (12 bytes) and the tag (16 bytes) +pub const ENCRYPTION_EXTRA_SIZE: usize = NONCE_SIZE + 16; + +/// Encrypts a plaintext given a shared DH secret key, returning a vector +/// containing a nonce and the ciphertext (which includes the tag) +pub fn encrypt( + shared_secret_key: &JubJubAffine, + plaintext: &[u8], + rng: &mut R, +) -> Result<[u8; ENCRYPTION_SIZE], Error> { + // To encrypt using AES256 we need 32-bytes keys. Thus, we use + // the 32-bytes serialization of the 64-bytes DH key. + let key = shared_secret_key.to_bytes(); + let key = Key::::from_slice(&key); + + let cipher = Aes256Gcm::new(key); + let nonce = Aes256Gcm::generate_nonce(rng); + let ciphertext = cipher.encrypt(&nonce, plaintext.as_ref())?; + + let mut encryption = [0u8; ENCRYPTION_SIZE]; + + encryption[..NONCE_SIZE].copy_from_slice(&nonce); + encryption[NONCE_SIZE..].copy_from_slice(&ciphertext); + + Ok(encryption) +} + +/// Decrypts an encryption (nonce + ciphertext) given a shared DH secret key, +/// returning the plaintext +pub fn decrypt( + shared_secret_key: &JubJubAffine, + encryption: &[u8], +) -> Result<[u8; PLAINTEXT_SIZE], Error> { + // To decrypt using AES256 we need 32-bytes keys. Thus, we use + // the 32-bytes serialization of the 64-bytes DH key. + let key = shared_secret_key.to_bytes(); + let key = Key::::from_slice(&key); + + let nonce = &encryption[..NONCE_SIZE]; + let ciphertext = &encryption[NONCE_SIZE..]; + + let cipher = Aes256Gcm::new(key); + + let mut plaintext = [0u8; PLAINTEXT_SIZE]; + plaintext.copy_from_slice(&cipher.decrypt(nonce.into(), ciphertext)?); + + Ok(plaintext) +} diff --git a/src/error.rs b/src/error.rs index 1fe0bcf..557451e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,6 +4,7 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. +use aes_gcm::Error as AesError; use core::fmt; use dusk_bytes::{BadLength, Error as DuskBytesError, InvalidChar}; @@ -25,8 +26,8 @@ pub enum Error { InvalidCrossoverConversion, /// Invalid Fee for conversion InvalidFeeConversion, - /// Failure to decrypt - Decryption, + /// Failure to encrypt / decrypt + BadEncryption, /// Invalid Value Commitment InvalidCommitment, /// Invalid Nonce @@ -45,6 +46,14 @@ impl fmt::Display for Error { } } +impl From for Error { + fn from(aes_error: AesError) -> Self { + match aes_error { + AesError => Self::BadEncryption, + } + } +} + impl From for DuskBytesError { fn from(err: Error) -> Self { match err { diff --git a/src/lib.rs b/src/lib.rs index 8e3fe3d..600daf6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,14 +21,17 @@ pub mod crossover; pub mod error; /// Fee pub mod fee; -/// Message representation -pub mod message; /// Transparent and Obfuscated Notes pub mod note; /// Phoenix Core Keys & Addresses mod keys; +/// Encryption and decryption methods +mod encryption; + +/// Encryption and decryption methods +pub use encryption::{decrypt, encrypt, ENCRYPTION_EXTRA_SIZE}; /// Hash function pub use keys::hash; /// Public (Spend) Key @@ -48,7 +51,6 @@ pub use crossover::Crossover; pub use error::Error; pub use fee::Fee; pub use fee::Remainder; -pub use message::Message; pub use note::{Note, NoteType}; #[cfg(feature = "alloc")] pub use transaction::Transaction; diff --git a/src/message.rs b/src/message.rs deleted file mode 100644 index 40f4980..0000000 --- a/src/message.rs +++ /dev/null @@ -1,185 +0,0 @@ -// 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 crate::{Error, Note, NoteType}; - -#[cfg(feature = "rkyv-impl")] -use rkyv::{Archive, Deserialize, Serialize}; - -use crate::PublicKey; -use dusk_bls12_381::BlsScalar; -use dusk_bytes::{DeserializableSlice, Serializable}; -use dusk_jubjub::{dhke, JubJubAffine, JubJubExtended, JubJubScalar}; -use dusk_poseidon::cipher::PoseidonCipher; -use dusk_poseidon::sponge; -use ff::Field; -use rand_core::{CryptoRng, RngCore}; - -/// Message structure with value commitment -#[derive(Clone, Copy, Debug, Default, PartialEq)] -#[cfg_attr( - feature = "rkyv-impl", - derive(Archive, Serialize, Deserialize), - archive_attr(derive(bytecheck::CheckBytes)) -)] -pub struct Message { - value_commitment: JubJubExtended, - nonce: BlsScalar, - encrypted_data: PoseidonCipher, -} - -impl Message { - /// Create a new message - /// - /// `r` will be later used to decrypt the value and blinding factor - pub fn new( - rng: &mut R, - r: &JubJubScalar, - psk: &PublicKey, - value: u64, - ) -> Self { - let nonce = BlsScalar::random(&mut *rng); - let blinding_factor = JubJubScalar::random(rng); - - let note = Note::deterministic( - NoteType::Obfuscated, - r, - nonce, - psk, - value, - blinding_factor, - ); - let Note { - value_commitment, - nonce, - encrypted_data, - .. - } = note; - - Self { - value_commitment, - nonce, - encrypted_data, - } - } - - /// Represent the message as a sequence of scalars to be used as input for - /// sponge hash functions - /// - /// It is composed by 3 scalars, in order: - /// * Value commitment X - /// * Value commitment Y - /// * Nonce - /// - /// And also appends the scalars that composes the [`PoseidonCipher`] - pub fn to_hash_inputs( - &self, - ) -> [BlsScalar; 3 + PoseidonCipher::cipher_size()] { - let mut inputs = [BlsScalar::zero(); 3 + PoseidonCipher::cipher_size()]; - - inputs[..2].copy_from_slice(&self.value_commitment().to_hash_inputs()); - inputs[2] = self.nonce; - inputs[3..].copy_from_slice(self.encrypted_data.cipher()); - - inputs - } - - /// Sponge hash of the message hash inputs representation - pub fn hash(&self) -> BlsScalar { - sponge::hash(&self.to_hash_inputs()) - } - - /// Value commitment representation of the message - pub const fn value_commitment(&self) -> &JubJubExtended { - &self.value_commitment - } - - /// Nonce used for the encryption of the value and blinding factor - pub const fn nonce(&self) -> &BlsScalar { - &self.nonce - } - - /// Returns the cipher of the encrypted data - pub const fn cipher(&self) -> &[BlsScalar; PoseidonCipher::cipher_size()] { - self.encrypted_data.cipher() - } - - /// Decrypt the value and blinding factor provided the `r` used in the - /// creation of the message - pub fn decrypt( - &self, - r: &JubJubScalar, - psk: &PublicKey, - ) -> Result<(u64, JubJubScalar), Error> { - let shared_secret = dhke(r, psk.A()); - let nonce = self.nonce; - - let data = self - .encrypted_data - .decrypt(&shared_secret, &nonce) - .ok_or(Error::Decryption)?; - - let value = data[0].reduce(); - let value = value.0[0]; - - // Converts the BLS Scalar into a JubJub Scalar. - let blinding_factor = - match JubJubScalar::from_bytes(&data[1].to_bytes()).into() { - Some(scalar) => scalar, - None => return Err(Error::InvalidBlindingFactor), - }; - - Ok((value, blinding_factor)) - } -} - -impl - Serializable< - { JubJubAffine::SIZE + JubJubScalar::SIZE + PoseidonCipher::SIZE }, - > for Message -{ - type Error = Error; - - fn to_bytes(&self) -> [u8; Self::SIZE] { - let mut bytes = [0u8; Self::SIZE]; - let mut b = &mut bytes[..]; - - let value_commitment = - JubJubAffine::from(self.value_commitment).to_bytes(); - b[..JubJubAffine::SIZE].copy_from_slice(&value_commitment); - b = &mut b[JubJubAffine::SIZE..]; - - b[..JubJubScalar::SIZE].copy_from_slice(&self.nonce.to_bytes()); - b = &mut b[JubJubScalar::SIZE..]; - - b.copy_from_slice(&self.encrypted_data.to_bytes()); - - bytes - } - - fn from_bytes(bytes: &[u8; Self::SIZE]) -> Result { - let mut bytes = &bytes[..]; - - let value_commitment: JubJubExtended = - JubJubAffine::from_slice(&bytes[..JubJubAffine::SIZE]) - .map_err(|_| Error::InvalidCommitment)? - .into(); - bytes = &bytes[JubJubAffine::SIZE..]; - - let nonce = BlsScalar::from_slice(&bytes[..BlsScalar::SIZE]) - .map_err(|_| Error::InvalidNonce)?; - bytes = &bytes[BlsScalar::SIZE..]; - - let encrypted_data = PoseidonCipher::from_slice(bytes) - .map_err(|_| Error::InvalidCipher)?; - - Ok(Self { - value_commitment, - nonce, - encrypted_data, - }) - } -} diff --git a/src/note.rs b/src/note.rs index 1b1a620..f189d56 100644 --- a/src/note.rs +++ b/src/note.rs @@ -13,7 +13,9 @@ use dusk_jubjub::{ dhke, JubJubAffine, JubJubExtended, JubJubScalar, GENERATOR_EXTENDED, GENERATOR_NUMS_EXTENDED, }; -use dusk_poseidon::cipher::PoseidonCipher; + +use crate::encryption::{decrypt, encrypt, ENCRYPTION_EXTRA_SIZE}; + use dusk_poseidon::sponge::hash; use ff::Field; use rand_core::{CryptoRng, RngCore}; @@ -24,6 +26,13 @@ use rkyv::{Archive, Deserialize, Serialize}; /// Blinder used for transparent pub(crate) const TRANSPARENT_BLINDER: JubJubScalar = JubJubScalar::zero(); +/// Size of the Phoenix notes plaintext: value (8 bytes) + blinder (32 bytes) +pub(crate) const PLAINTEXT_SIZE: usize = 40; + +/// Size of the Phoenix notes encryption +pub(crate) const ENCRYPTION_SIZE: usize = + PLAINTEXT_SIZE + ENCRYPTION_EXTRA_SIZE; + /// The types of a Note #[derive(Debug, Clone, Copy, Eq, PartialEq)] #[cfg_attr( @@ -59,7 +68,7 @@ impl TryFrom for NoteType { } /// A note that does not encrypt its value -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] #[cfg_attr( feature = "rkyv-impl", derive(Archive, Serialize, Deserialize), @@ -68,10 +77,9 @@ impl TryFrom for NoteType { pub struct Note { pub(crate) note_type: NoteType, pub(crate) value_commitment: JubJubExtended, - pub(crate) nonce: BlsScalar, pub(crate) stealth_address: StealthAddress, pub(crate) pos: u64, - pub(crate) encrypted_data: PoseidonCipher, + pub(crate) encryption: [u8; ENCRYPTION_SIZE], } impl PartialEq for Note { @@ -92,9 +100,41 @@ impl Note { blinding_factor: JubJubScalar, ) -> Self { let r = JubJubScalar::random(&mut *rng); - let nonce = BlsScalar::random(&mut *rng); + let stealth_address = psk.gen_stealth_address(&r); + + let value_commitment = JubJubScalar::from(value); + let value_commitment = (GENERATOR_EXTENDED * value_commitment) + + (GENERATOR_NUMS_EXTENDED * blinding_factor); + + // Output notes have undefined position, equals to u64's MAX value + let pos = u64::MAX; + + let encryption = match note_type { + NoteType::Transparent => { + let mut encryption = [0u8; ENCRYPTION_SIZE]; + encryption[..u64::SIZE].copy_from_slice(&value.to_bytes()); + + encryption + } + NoteType::Obfuscated => { + let shared_secret = dhke(&r, psk.A()); + let blinding_factor = BlsScalar::from(blinding_factor); + + let mut plaintext = value.to_bytes().to_vec(); + plaintext.append(&mut blinding_factor.to_bytes().to_vec()); + + encrypt(&shared_secret, &plaintext, rng) + .expect("Encrypted correctly.") + } + }; - Self::deterministic(note_type, &r, nonce, psk, value, blinding_factor) + Note { + note_type, + value_commitment, + stealth_address, + pos, + encryption, + } } /// Creates a new transparent note @@ -113,12 +153,11 @@ impl Note { /// Creates a new transparent note /// /// This is equivalent to [`transparent`] but taking only a stealth address, - /// a value, and a nonce. This is done to be able to generate a note + /// and a value. This is done to be able to generate a note /// directly for a stealth address, as opposed to a public key. pub fn transparent_stealth( stealth_address: StealthAddress, value: u64, - nonce: BlsScalar, ) -> Self { let value_commitment = JubJubScalar::from(value); let value_commitment = (GENERATOR_EXTENDED * value_commitment) @@ -126,20 +165,15 @@ impl Note { let pos = u64::MAX; - let zero = TRANSPARENT_BLINDER.into(); - let mut encrypted_data = [zero; PoseidonCipher::cipher_size()]; - - encrypted_data[0] = BlsScalar::from(value); - - let encrypted_data = PoseidonCipher::new(encrypted_data); + let mut encryption = [0u8; ENCRYPTION_SIZE]; + encryption[..u64::SIZE].copy_from_slice(&value.to_bytes()); Note { note_type: NoteType::Transparent, value_commitment, - nonce, stealth_address, pos, - encrypted_data, + encryption, } } @@ -158,56 +192,6 @@ impl Note { Self::new(rng, NoteType::Obfuscated, psk, value, blinding_factor) } - /// Create a new phoenix output note without inner randomness - pub fn deterministic( - note_type: NoteType, - r: &JubJubScalar, - nonce: BlsScalar, - psk: &PublicKey, - value: u64, - blinding_factor: JubJubScalar, - ) -> Self { - let stealth_address = psk.gen_stealth_address(r); - - let value_commitment = JubJubScalar::from(value); - let value_commitment = (GENERATOR_EXTENDED * value_commitment) - + (GENERATOR_NUMS_EXTENDED * blinding_factor); - - // Output notes have undefined position, equals to u64's MAX value - let pos = u64::MAX; - - let encrypted_data = match note_type { - NoteType::Transparent => { - let zero = TRANSPARENT_BLINDER.into(); - let mut encrypted_data = [zero; PoseidonCipher::cipher_size()]; - - encrypted_data[0] = BlsScalar::from(value); - - PoseidonCipher::new(encrypted_data) - } - NoteType::Obfuscated => { - let shared_secret = dhke(r, psk.A()); - let value = BlsScalar::from(value); - let blinding_factor = BlsScalar::from(blinding_factor); - - PoseidonCipher::encrypt( - &[value, blinding_factor], - &shared_secret, - &nonce, - ) - } - }; - - Note { - note_type, - value_commitment, - nonce, - stealth_address, - pos, - encrypted_data, - } - } - fn decrypt_data( &self, vk: &ViewKey, @@ -215,19 +199,17 @@ impl Note { let R = self.stealth_address.R(); let shared_secret = dhke(vk.a(), R); - let data = self - .encrypted_data - .decrypt(&shared_secret, &self.nonce) - .ok_or(BytesError::InvalidData)?; + let dec_plaintext: [u8; PLAINTEXT_SIZE] = + decrypt(&shared_secret, &self.encryption)?; - let value = data[0].reduce(); - let value = value.0[0]; + let value = u64::from_slice(&dec_plaintext[..u64::SIZE])?; // Converts the BLS Scalar into a JubJub Scalar. // If the `vk` is wrong it might fails since the resulting BLS Scalar // might not fit into a JubJub Scalar. let blinding_factor = - match JubJubScalar::from_bytes(&data[1].to_bytes()).into() { + match JubJubScalar::from_slice(&dec_plaintext[u64::SIZE..])?.into() + { Some(scalar) => scalar, None => return Err(BytesError::InvalidData), }; @@ -285,19 +267,14 @@ impl Note { self.pos = pos; } - /// Nonce used for the encrypt / decrypt of data for this note - pub const fn nonce(&self) -> &BlsScalar { - &self.nonce - } - /// Return the value commitment `H(value, blinding_factor)` pub const fn value_commitment(&self) -> &JubJubExtended { &self.value_commitment } /// Returns the cipher of the encrypted data - pub const fn cipher(&self) -> &[BlsScalar; PoseidonCipher::cipher_size()] { - self.encrypted_data.cipher() + pub const fn encryption(&self) -> &[u8; ENCRYPTION_SIZE] { + &self.encryption } /// Attempt to decrypt the note value provided a [`ViewKey`]. Always @@ -306,9 +283,9 @@ impl Note { pub fn value(&self, vk: Option<&ViewKey>) -> Result { match (self.note_type, vk) { (NoteType::Transparent, _) => { - let value = self.encrypted_data.cipher(); - let value = value[0].reduce(); - Ok(value.0[0]) + let value = + u64::from_slice(&self.encryption[..u64::SIZE]).unwrap(); + Ok(value) } (NoteType::Obfuscated, Some(vk)) => self .decrypt_data(vk) @@ -342,10 +319,13 @@ impl Ownable for Note { } } -impl Serializable<{ 137 + PoseidonCipher::SIZE }> for Note { +// Serialize into 105 + ENCRYPTION_SIZE bytes, where 105 is the size of all the +// note elements without the encryption. ENCRYPTION_SIZE = PLAINTEXT_SIZE + +// ENCRYPTION_EXTRA_SIZE +impl Serializable<{ 105 + ENCRYPTION_SIZE }> for Note { type Error = BytesError; - /// Converts a Note into a byte representation + /// Converts a Note into a byte representation fn to_bytes(&self) -> [u8; Self::SIZE] { let mut buf = [0u8; Self::SIZE]; @@ -354,10 +334,9 @@ impl Serializable<{ 137 + PoseidonCipher::SIZE }> for Note { buf[1..33].copy_from_slice( &JubJubAffine::from(&self.value_commitment).to_bytes(), ); - buf[33..65].copy_from_slice(&self.nonce.to_bytes()); - buf[65..129].copy_from_slice(&self.stealth_address.to_bytes()); - buf[129..137].copy_from_slice(&self.pos.to_le_bytes()); - buf[137..].copy_from_slice(&self.encrypted_data.to_bytes()); + buf[33..97].copy_from_slice(&self.stealth_address.to_bytes()); + buf[97..105].copy_from_slice(&self.pos.to_le_bytes()); + buf[105..].copy_from_slice(&self.encryption); buf } @@ -370,21 +349,20 @@ impl Serializable<{ 137 + PoseidonCipher::SIZE }> for Note { bytes[0].try_into().map_err(|_| BytesError::InvalidData)?; let value_commitment = JubJubExtended::from(JubJubAffine::from_slice(&bytes[1..33])?); - let nonce = BlsScalar::from_slice(&bytes[33..65])?; - let stealth_address = StealthAddress::from_slice(&bytes[65..129])?; + let stealth_address = StealthAddress::from_slice(&bytes[33..97])?; - one_u64.copy_from_slice(&bytes[129..137]); + one_u64.copy_from_slice(&bytes[97..105]); let pos = u64::from_le_bytes(one_u64); - let encrypted_data = PoseidonCipher::from_slice(&bytes[137..])?; + let mut encryption = [0u8; ENCRYPTION_SIZE]; + encryption.copy_from_slice(&bytes[105..]); Ok(Note { note_type, value_commitment, - nonce, stealth_address, pos, - encrypted_data, + encryption, }) } } diff --git a/src/transaction.rs b/src/transaction.rs index 5985b25..7fae362 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -126,7 +126,7 @@ impl Transaction { bytes.extend(self.fee.to_bytes()); - if let Some(co) = self.crossover { + if let Some(co) = &self.crossover { bytes.push(1); bytes.extend(co.to_bytes()); } else { diff --git a/src/transaction/transfer.rs b/src/transaction/transfer.rs index 39c8f33..8faef43 100644 --- a/src/transaction/transfer.rs +++ b/src/transaction/transfer.rs @@ -4,25 +4,16 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use alloc::vec::Vec; - use dusk_bls12_381::BlsScalar; -use dusk_poseidon::cipher::PoseidonCipher; #[cfg(feature = "rkyv-impl")] use rkyv::{Archive, Deserialize, Serialize}; -use crate::crossover::Crossover; -use crate::message::Message; use crate::note::Note; -use crate::transaction::ModuleId; use crate::StealthAddress; /// The depth of the transfer tree. pub const TRANSFER_TREE_DEPTH: usize = 17; -const STCO_MESSAGE_SIZE: usize = 7 + 2 * PoseidonCipher::cipher_size(); -const STCT_MESSAGE_SIZE: usize = 5 + PoseidonCipher::cipher_size(); - /// A leaf of the transfer tree. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr( @@ -37,92 +28,6 @@ pub struct TreeLeaf { pub note: Note, } -/// Send value to a contract transparently. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr( - feature = "rkyv-impl", - derive(Archive, Serialize, Deserialize), - archive_attr(derive(bytecheck::CheckBytes)) -)] -pub struct Stct { - /// Module to send the value to. - pub module: ModuleId, - /// The value to send to the contract. - pub value: u64, - /// Proof of the `STCT` circuit. - pub proof: Vec, -} - -/// Withdraw value from a contract transparently. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr( - feature = "rkyv-impl", - derive(Archive, Serialize, Deserialize), - archive_attr(derive(bytecheck::CheckBytes)) -)] -pub struct Wfct { - /// The value to withdraw - pub value: u64, - /// The note to withdraw transparently to - pub note: Note, - /// A proof of the `WFCT` circuit. - pub proof: Vec, -} - -/// Send value to a contract anonymously. -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr( - feature = "rkyv-impl", - derive(Archive, Serialize, Deserialize), - archive_attr(derive(bytecheck::CheckBytes)) -)] -pub struct Stco { - /// Module to send the value to. - pub module: ModuleId, - /// Message containing the value commitment. - pub message: Message, - /// The stealth address of the message. - pub message_address: StealthAddress, - /// Proof of the `STCO` circuit. - pub proof: Vec, -} - -/// Withdraw value from a contract anonymously. -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr( - feature = "rkyv-impl", - derive(Archive, Serialize, Deserialize), - archive_attr(derive(bytecheck::CheckBytes)) -)] -pub struct Wfco { - /// Message containing the value commitment. - pub message: Message, - /// The stealth address of the message. - pub message_address: StealthAddress, - /// Message containing commitment on the change value. - pub change: Message, - /// The stealth address of the change message. - pub change_address: StealthAddress, - /// The note to withdraw to. - pub output: Note, - /// Proof of the `WFCO` circuit. - pub proof: Vec, -} - -/// Withdraw value from the calling contract to another contract. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr( - feature = "rkyv-impl", - derive(Archive, Serialize, Deserialize), - archive_attr(derive(bytecheck::CheckBytes)) -)] -pub struct Wfctc { - /// The contract to transfer value to. - pub module: ModuleId, - /// The value to transfer. - pub value: u64, -} - /// Mint value to a stealth address. #[derive(Debug, Clone, PartialEq)] #[cfg_attr( @@ -138,36 +43,3 @@ pub struct Mint { /// A nonce to prevent replay. pub nonce: BlsScalar, } - -/// Signature message used for [`Stct`]. -#[must_use] -pub fn stct_signature_message( - crossover: &Crossover, - value: u64, - module_id: BlsScalar, -) -> [BlsScalar; STCT_MESSAGE_SIZE] { - let mut array = [BlsScalar::default(); STCT_MESSAGE_SIZE]; - let hash_inputs = crossover.to_hash_inputs(); - array[..hash_inputs.len()].copy_from_slice(&hash_inputs); - array[hash_inputs.len()..].copy_from_slice(&[value.into(), module_id]); - array -} - -/// Signature message used for [`Stco`]. -#[must_use] -pub fn stco_signature_message( - crossover: &Crossover, - message: &Message, - module_id: BlsScalar, -) -> [BlsScalar; STCO_MESSAGE_SIZE] { - let mut array = [BlsScalar::default(); STCO_MESSAGE_SIZE]; - let crossover_inputs = crossover.to_hash_inputs(); - let message_inputs = message.to_hash_inputs(); - array[..crossover_inputs.len()].copy_from_slice(&crossover_inputs); - array - [crossover_inputs.len()..crossover_inputs.len() + message_inputs.len()] - .copy_from_slice(&message_inputs); - array[crossover_inputs.len() + message_inputs.len()..] - .copy_from_slice(&[module_id]); - array -} diff --git a/tests/crossover.rs b/tests/crossover.rs index a138b6e..275a675 100644 --- a/tests/crossover.rs +++ b/tests/crossover.rs @@ -8,7 +8,7 @@ use core::convert::TryInto; use dusk_jubjub::JubJubScalar; use ff::Field; -use phoenix_core::{Error, Message, Note, PublicKey, SecretKey}; +use phoenix_core::{Error, Note, PublicKey, SecretKey}; use rand_core::OsRng; #[test] @@ -29,32 +29,7 @@ fn crossover_hash() -> Result<(), Error> { let (_, crossover) = note.try_into()?; let (_, crossover_p) = note_p.try_into()?; - let hash = crossover.hash(); - let hash_p = crossover_p.hash(); - - assert_ne!(hash, hash_p); - - Ok(()) -} - -#[test] -fn message_hash() -> Result<(), Error> { - let mut rng = OsRng; - - let ssk = SecretKey::random(&mut rng); - let psk = PublicKey::from(ssk); - let value = 25; - - let r = JubJubScalar::random(&mut rng); - let message = Message::new(&mut rng, &r, &psk, value); - - let r_p = JubJubScalar::random(&mut rng); - let message_p = Message::new(&mut rng, &r_p, &psk, value); - - let hash = message.hash(); - let hash_p = message_p.hash(); - - assert_ne!(hash, hash_p); + assert_ne!(crossover.value_commitment(), crossover_p.value_commitment()); Ok(()) } diff --git a/tests/encryption.rs b/tests/encryption.rs new file mode 100644 index 0000000..0e1d380 --- /dev/null +++ b/tests/encryption.rs @@ -0,0 +1,28 @@ +// 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_jubjub::{JubJubAffine, JubJubScalar, GENERATOR}; +use rand_core::OsRng; + +use phoenix_core::{decrypt, encrypt, ENCRYPTION_EXTRA_SIZE}; + +#[test] +fn test_encrypt_and_decrypt() { + const PLAINTEXT_SIZE: usize = 20; + const ENCRYPTION_SIZE: usize = PLAINTEXT_SIZE + ENCRYPTION_EXTRA_SIZE; + + let shared_secret_key = + JubJubAffine::from(GENERATOR * JubJubScalar::from(1234u64)); + + let plaintext = b"00112233445566778899"; + let encryption: [u8; ENCRYPTION_SIZE] = + encrypt(&shared_secret_key, plaintext, &mut OsRng) + .expect("Encrypted correctly."); + let dec_plaintext = + decrypt(&shared_secret_key, &encryption).expect("Decrypted correctly."); + + assert_eq!(&dec_plaintext, plaintext); +} diff --git a/tests/message.rs b/tests/message.rs deleted file mode 100644 index 3c2c2e6..0000000 --- a/tests/message.rs +++ /dev/null @@ -1,55 +0,0 @@ -// 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_bytes::Serializable; -use dusk_jubjub::JubJubScalar; -use dusk_jubjub::{GENERATOR_EXTENDED, GENERATOR_NUMS_EXTENDED}; -use ff::Field; -use phoenix_core::{Message, PublicKey, SecretKey}; -use rand_core::OsRng; - -#[test] -fn message_consistency() { - let mut rng = OsRng; - - let ssk = SecretKey::random(&mut rng); - let psk = PublicKey::from(ssk); - let psk_wrong = PublicKey::from(SecretKey::random(&mut rng)); - - let r = JubJubScalar::random(&mut rng); - let r_wrong = JubJubScalar::random(&mut rng); - let value = 105; - - let message = Message::new(&mut rng, &r, &psk, value); - let value_commitment = message.value_commitment(); - let (value_p, blinding_factor) = message.decrypt(&r, &psk).unwrap(); - assert!(message.decrypt(&r_wrong, &psk).is_err()); - assert!(message.decrypt(&r, &psk_wrong).is_err()); - - let a = GENERATOR_EXTENDED * JubJubScalar::from(value); - let b = GENERATOR_NUMS_EXTENDED * blinding_factor; - let value_commitment_p = a + b; - - assert_eq!(value, value_p); - assert_eq!(value_commitment, &value_commitment_p); -} - -#[test] -fn message_bytes() { - let mut rng = OsRng; - - let ssk = SecretKey::random(&mut rng); - let psk = PublicKey::from(ssk); - - let r = JubJubScalar::random(&mut rng); - let value = 106; - - let m = Message::new(&mut rng, &r, &psk, value); - let m_p = m.to_bytes(); - let m_p = Message::from_bytes(&m_p).unwrap(); - - assert_eq!(m, m_p); -} diff --git a/tests/note_test.rs b/tests/note_test.rs index a5a3ed6..695a138 100644 --- a/tests/note_test.rs +++ b/tests/note_test.rs @@ -5,7 +5,6 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use core::convert::TryInto; -use dusk_bls12_381::BlsScalar; use dusk_jubjub::{JubJubScalar, GENERATOR_EXTENDED, GENERATOR_NUMS_EXTENDED}; use ff::Field; use phoenix_core::{ @@ -40,10 +39,9 @@ fn transparent_stealth_note() -> Result<(), Error> { let r = JubJubScalar::random(&mut rng); let sa = psk.gen_stealth_address(&r); - let nonce = BlsScalar::random(&mut rng); let value = 25; - let note = Note::transparent_stealth(sa, value, nonce); + let note = Note::transparent_stealth(sa, value); assert_eq!(note.note(), NoteType::Transparent); assert_eq!(value, note.value(None)?); @@ -79,18 +77,10 @@ fn obfuscated_deterministic_note() -> Result<(), Error> { let vk = ViewKey::from(ssk); let value = 25; - let r = JubJubScalar::random(&mut rng); - let nonce = BlsScalar::random(&mut rng); let blinding_factor = JubJubScalar::random(&mut rng); - let note = Note::deterministic( - NoteType::Obfuscated, - &r, - nonce, - &psk, - value, - blinding_factor, - ); + let note = + Note::new(&mut rng, NoteType::Obfuscated, &psk, value, blinding_factor); assert_eq!(value, note.value(Some(&vk))?); assert_eq!(blinding_factor, note.blinding_factor(Some(&vk))?); @@ -175,8 +165,9 @@ fn note_keys_consistency() { assert!(vk.owns(¬e)); } +#[should_panic] #[test] -fn fee_and_crossover_generation() -> Result<(), Error> { +fn fee_and_crossover_generation() { let mut rng = OsRng; let ssk = SecretKey::random(&mut rng); @@ -186,11 +177,11 @@ fn fee_and_crossover_generation() -> Result<(), Error> { let blinding_factor = JubJubScalar::random(&mut rng); let note = Note::obfuscated(&mut rng, &psk, value, blinding_factor); - let (fee, crossover): (Fee, Crossover) = note.try_into()?; + let (fee, crossover): (Fee, Crossover) = note.clone().try_into().unwrap(); let ssk_fee = SecretKey::random(&mut rng); let wrong_fee = Fee::new(&mut rng, 0, 0, &ssk_fee.into()); - let wrong_note: Note = (wrong_fee, crossover).into(); + let wrong_note: Note = (wrong_fee, crossover.clone()).into(); assert_ne!(note, wrong_note); assert!( @@ -201,8 +192,7 @@ fn fee_and_crossover_generation() -> Result<(), Error> { let correct_note: Note = (fee, crossover).into(); assert_eq!(note, correct_note); - assert_eq!(value, correct_note.value(Some(&vk))?); - Ok(()) + assert_eq!(value, correct_note.value(Some(&vk)).unwrap()); } #[test] diff --git a/tests/transaction.rs b/tests/transaction.rs index 20995ba..847db22 100644 --- a/tests/transaction.rs +++ b/tests/transaction.rs @@ -25,7 +25,7 @@ fn transaction_parse() -> Result<(), Error> { let blinding_factor = JubJubScalar::random(&mut rng); let note = Note::obfuscated(&mut rng, &psk, value, blinding_factor); - let (fee, crossover) = note.try_into()?; + let (fee, crossover) = note.clone().try_into()?; let anchor = BlsScalar::from(123); let nullifiers = vec![BlsScalar::from(456), BlsScalar::from(789)]; let outputs = vec![note];