Skip to content

Commit

Permalink
Implement encryption
Browse files Browse the repository at this point in the history
Resolves #6
  • Loading branch information
moCello committed Mar 13, 2024
1 parent 31489f4 commit bfcefa2
Show file tree
Hide file tree
Showing 7 changed files with 452 additions and 12 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add authenticated encryption and decryption [#6]

### Changed

- Let `Sponge::start` take the io-pattern as `impl Into<Vec<Call>>` [#4]
Expand All @@ -24,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add documentation

<!-- ISSUES -->
[#6]: https://github.com/dusk-network/safe/issues/6
[#4]: https://github.com/dusk-network/safe/issues/4
[#3]: https://github.com/dusk-network/safe/issues/3

Expand Down
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ license = "MPL-2.0"
zeroize = "1"

[dev-dependencies]
dusk-bls12_381 = { version = "0.13", default-features = false }
dusk-bls12_381 = { version = "0.13", default-features = false, features = ["zeroize"] }
dusk-jubjub = { version = "0.14", default-features = false }
ff = { version = "0.13", default-features = false }
rand = { version = "0.8", default-features = false, features = ["getrandom", "std_rng"] }

[features]
encryption = []
167 changes: 167 additions & 0 deletions src/encryption.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// 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 alloc::vec::Vec;

use crate::{Call, Error, Safe, Sponge};
use zeroize::Zeroize;

/// Trait that together with the [`Safe`] trait, implements everything that is
/// needed for encryption using the SAFE framework.
///
/// The specific implementation of subtraction and equality is needed for being
/// able to use the trait in a zero-knowledge circuit.
pub trait Encryption<T, const W: usize> {
/// Specifically implement subtraction for the type `T`:
/// minuend - subtrahend = difference
fn subtract(&mut self, minuend: &T, subtrahend: &T) -> T;

/// Implement equality over the type `T`.
fn assert_equal(&mut self, lhs: &T, rhs: &T) -> bool;
}

fn prepare_sponge<E, T, const W: usize>(
safe: E,
domain_sep: u64,
message_len: usize,
shared_secret: &[T; 2],
nonce: &T,
) -> Result<Sponge<E, T, W>, Error>
where
E: Safe<T, W> + Encryption<T, W>,
T: Default + Copy + Zeroize,
{
// start sponge
let mut sponge = Sponge::start(safe, io_pattern(message_len), domain_sep)?;

// absorb shared secret
// absorb nonce
sponge.absorb(2, shared_secret)?;
sponge.absorb(1, [*nonce])?;

// squeeze message_len elements
sponge.squeeze(message_len)?;

Ok(sponge)
}

/// This function encrypts a message using a shared secret and nonce, and
/// returns the cipher-text.
pub fn encrypt<E, T, const W: usize>(
safe: E,
domain_sep: impl Into<u64>,
message: impl AsRef<[T]>,
shared_secret: &[T; 2],
nonce: T,
) -> Result<Vec<T>, Error>
where
E: Safe<T, W> + Encryption<T, W>,
T: Default + Copy + Zeroize,
{
let message = message.as_ref();
let message_len = message.len();

let mut sponge = prepare_sponge(
safe,
domain_sep.into(),
message_len,
shared_secret,
&nonce,
)?;

// absorb message
sponge.absorb(message_len, message)?;

// squeeze one last element
sponge.squeeze(1)?;

// encryption cipher is the sponge.output with the message elements added to
// the first message_len elements
let mut cipher = Vec::from(&sponge.output[..]);
for i in 0..message_len {
cipher[i] = sponge.safe.add(&cipher[i], &message[i]);
}

// finish the sponge, erase cipher upon error
match sponge.finish() {
Ok(mut output) => {
output.zeroize();
Ok(cipher)
}
Err(e) => {
cipher.zeroize();
Err(e.into())
}
}
}

/// This function decrypts a cipher-text, using a shared secret and nonce, and
/// returns the decrypted message upon success.
pub fn decrypt<E, T, const W: usize>(
safe: E,
domain_sep: impl Into<u64>,
cipher: impl AsRef<[T]>,
shared_secret: &[T; 2],
nonce: T,
) -> Result<Vec<T>, Error>
where
E: Safe<T, W> + Encryption<T, W>,
T: Default + Copy + Zeroize,
{
let cipher = cipher.as_ref();
let message_len = cipher.len() - 1;

let mut sponge = prepare_sponge(
safe,
domain_sep.into(),
message_len,
&shared_secret,
&nonce,
)?;

// construct the message by subtracting sponge.output from the cipher
let mut message = Vec::from(&sponge.output[..]);
for i in 0..message_len {
message[i] = sponge.safe.subtract(&cipher[i], &message[i]);
}

// absorb the obtained message
sponge.absorb(message_len, &message)?;

// squeeze 1 element
sponge.squeeze(1)?;

// assert that the last element of the cipher is equal to the last element
// of the sponge output
let s = sponge.output[message_len];
if !sponge.safe.assert_equal(&s, &cipher[message_len]) {
message.zeroize();
sponge.zeroize();
return Err(Error::IncorrectEncryption);
};

// finish sponge, erase message upon error
match sponge.finish() {
Ok(mut output) => {
output.zeroize();
Ok(message)
}
Err(e) => {
message.zeroize();
Err(e.into())
}
}
}

const fn io_pattern(message_len: usize) -> [Call; 5] {
[
Call::Absorb(2),
Call::Absorb(1),
Call::Squeeze(message_len),
Call::Absorb(message_len),
Call::Squeeze(1),
]
}
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ pub enum Error {

/// The input doesn't yield enough input elements.
TooFewInputElements,

/// Failed to decrypt the message with the provided secret and nonce
IncorrectEncryption,
}
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ mod sponge;
pub use error::Error;
pub use sponge::{Safe, Sponge};

#[cfg(feature = "encryption")]
mod encryption;
#[cfg(feature = "encryption")]
pub use encryption::{decrypt, encrypt, Encryption};

/// Enum to encode the calls to [`Sponge::absorb`] and [`Sponge::squeeze`] that
/// make the io-pattern.
///
Expand Down
22 changes: 11 additions & 11 deletions src/sponge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::{tag_input, Call, Error};
/// Trait to implement the Sponge API
pub trait Safe<T, const W: usize>
where
T: Default + Copy,
T: Default + Copy + Zeroize,
{
/// Apply one permutation to the state.
fn permute(&mut self, state: &mut [T; W]);
Expand Down Expand Up @@ -43,22 +43,22 @@ where
pub struct Sponge<S, T, const W: usize>
where
S: Safe<T, W>,
T: Default + Copy,
T: Default + Copy + Zeroize,
{
state: [T; W],
safe: S,
pub(crate) safe: S,
pos_absorb: usize,
pos_squeeze: usize,
io_count: usize,
iopattern: Vec<Call>,
domain_sep: u64,
output: Vec<T>,
pub(crate) output: Vec<T>,
}

impl<S, T, const W: usize> Sponge<S, T, W>
where
S: Safe<T, W>,
T: Default + Copy,
T: Default + Copy + Zeroize,
{
/// The capacity of the sponge.
const CAPACITY: usize = 1;
Expand Down Expand Up @@ -202,7 +202,7 @@ where
impl<S, T, const W: usize> Drop for Sponge<S, T, W>
where
S: Safe<T, W>,
T: Default + Copy,
T: Default + Copy + Zeroize,
{
fn drop(&mut self) {
self.zeroize();
Expand All @@ -212,12 +212,12 @@ where
impl<S, T, const W: usize> Zeroize for Sponge<S, T, W>
where
S: Safe<T, W>,
T: Default + Copy,
T: Default + Copy + Zeroize,
{
fn zeroize(&mut self) {
self.state.iter_mut().for_each(|elem| *elem = T::default());
self.pos_absorb = 0;
self.pos_squeeze = 0;
self.output.iter_mut().for_each(|elem| *elem = T::default());
self.state.zeroize();
self.pos_absorb.zeroize();
self.pos_squeeze.zeroize();
self.output.zeroize();
}
}
Loading

0 comments on commit bfcefa2

Please # to comment.