Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AES cipher module #151

Merged
merged 5 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)]
xevisalle marked this conversation as resolved.
Show resolved Hide resolved
#[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)
}
15 changes: 13 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
//
// Copyright (c) DUSK NETWORK. All rights reserved.

use aes_gcm::Error as AesError;
use core::fmt;
use dusk_bytes::{BadLength, Error as DuskBytesError, InvalidChar};

extern crate alloc;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need this anymore.


/// All possible errors for Phoenix's Core
#[allow(missing_docs)]
#[derive(Debug, Clone)]
Expand All @@ -25,8 +28,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 +48,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