Skip to content

Commit

Permalink
Add AES cipher module (#151)
Browse files Browse the repository at this point in the history
  • Loading branch information
xevisalle authored Apr 18, 2024
1 parent cf78a7d commit 0daaba6
Show file tree
Hide file tree
Showing 16 changed files with 242 additions and 585 deletions.
17 changes: 15 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

<!-- VERSIONS -->
<!-- ISSUES -->
[#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
Expand All @@ -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

<!-- ISSUES -->
<!-- VERSIONS -->
[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
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
30 changes: 9 additions & 21 deletions src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -19,8 +19,7 @@ impl From<(Fee, Crossover)> for Note {
} = fee;
let Crossover {
value_commitment,
nonce,
encrypted_data,
encryption,
..
} = crossover;

Expand All @@ -30,10 +29,9 @@ impl From<(Fee, Crossover)> for Note {
Note {
note_type,
value_commitment,
nonce,
stealth_address,
pos,
encrypted_data,
encryption,
}
}
}
Expand All @@ -49,8 +47,7 @@ impl TryFrom<Note> for (Fee, Crossover) {
let Note {
stealth_address,
value_commitment,
nonce,
encrypted_data,
encryption,
..
} = note;

Expand All @@ -62,8 +59,7 @@ impl TryFrom<Note> for (Fee, Crossover) {
},
Crossover {
value_commitment,
nonce,
encrypted_data,
encryption,
},
))
}
Expand All @@ -79,28 +75,20 @@ impl From<Remainder> 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,
}
}
}
68 changes: 21 additions & 47 deletions src/crossover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,44 @@

//! 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),
archive_attr(derive(bytecheck::CheckBytes))
)]
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
Expand All @@ -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
}

Expand All @@ -56,57 +62,25 @@ impl Serializable<{ 64 + PoseidonCipher::SIZE }> for Crossover {
fn from_bytes(bytes: &[u8; Self::SIZE]) -> Result<Self, Self::Error> {
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
}
}
67 changes: 67 additions & 0 deletions src/encryption.rs
Original file line number Diff line number Diff line change
@@ -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<R: RngCore + CryptoRng, const ENCRYPTION_SIZE: usize>(
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::<Aes256Gcm>::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<const PLAINTEXT_SIZE: usize>(
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::<Aes256Gcm>::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)
}
13 changes: 11 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -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
Expand All @@ -45,6 +46,14 @@ impl fmt::Display for Error {
}
}

impl From<AesError> for Error {
fn from(aes_error: AesError) -> Self {
match aes_error {
AesError => Self::BadEncryption,
}
}
}

impl From<Error> for DuskBytesError {
fn from(err: Error) -> Self {
match err {
Expand Down
8 changes: 5 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Loading

0 comments on commit 0daaba6

Please sign in to comment.