Skip to content

Commit

Permalink
Add and use Cipher module
Browse files Browse the repository at this point in the history
  • Loading branch information
xevisalle committed Apr 9, 2024
1 parent 529035e commit bfd52f1
Show file tree
Hide file tree
Showing 13 changed files with 213 additions and 574 deletions.
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
89 changes: 89 additions & 0 deletions src/cipher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// 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,
};

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 EXTRA_DATA_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>(
secret_key: &JubJubAffine,
plaintext: &[u8],
rng: &mut R,
) -> [u8; ENCRYPTION_SIZE] {
// To encrypt using AES256 we need 32-bytes keys. Thus, we use
// the 32-bytes serialization of the 64-bytes DH key.
let key = secret_key.to_bytes();
let key = Key::<Aes256Gcm>::from_slice(&key);

let cipher = Aes256Gcm::new(key);
let nonce = Aes256Gcm::generate_nonce(rng);
let mut ciphertext = cipher
.encrypt(&nonce, plaintext.as_ref())
.expect("Encryption failed.");

let mut encryption = nonce.to_vec();
encryption.append(&mut ciphertext);

encryption.try_into().unwrap()
}

/// Decrypts an encryption (nonce + ciphertext) given a shared DH secret key,
/// returning the plaintext
pub fn decrypt<const PLAINTEXT_SIZE: usize>(
secret_key: &JubJubAffine,
encryption: &[u8],
) -> [u8; PLAINTEXT_SIZE] {
// To decrypt using AES256 we need 32-bytes keys. Thus, we use
// the 32-bytes serialization of the 64-bytes DH key.
let key = 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);
cipher
.decrypt(nonce.into(), ciphertext.as_ref())
.expect("Decryption failed.")
.try_into()
.unwrap()
}

#[cfg(test)]
mod tests {
use dusk_jubjub::{JubJubAffine, JubJubScalar, GENERATOR};
use rand_core::OsRng;

use crate::cipher::{decrypt, encrypt, EXTRA_DATA_SIZE};

#[test]
fn test_encrypt_and_decrypt() {
const PLAINTEXT_SIZE: usize = 20;
const ENCRYPTION_SIZE: usize = PLAINTEXT_SIZE + EXTRA_DATA_SIZE;

let secret_key =
JubJubAffine::from(GENERATOR * JubJubScalar::from(1234u64));

let plaintext = b"00112233445566778899";
let encryption: [u8; ENCRYPTION_SIZE] =
encrypt(&secret_key, plaintext, &mut OsRng);
let dec_plaintext = decrypt(&secret_key, &encryption);

assert_eq!(&dec_plaintext, plaintext);
}
}
30 changes: 10 additions & 20 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,22 @@ 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()];
let mut encryption = value.to_bytes().to_vec();
encryption.append(&mut [0; ENCRYPTION_SIZE - u64::SIZE].to_vec());

encrypted_data[0] = BlsScalar::from(value);

PoseidonCipher::new(encrypted_data)
};
let encryption = encryption.try_into().unwrap();

Note {
note_type,
value_commitment,
nonce,
stealth_address,
pos,
encrypted_data,
encryption,
}
}
}
67 changes: 20 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,24 @@ 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 encryption = bytes[32..].try_into().unwrap();

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
}
}
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ 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
pub mod cipher;

/// Hash function
pub use keys::hash;
/// Public (Spend) Key
Expand All @@ -48,7 +49,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 bfd52f1

Please sign in to comment.