Skip to content
New issue

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

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

Already on GitHub? # to your account

Implement encryption #7

Merged
merged 1 commit into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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::DecryptionFailed);
};

// 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),
]
}
4 changes: 4 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ pub enum Error {

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

/// Failed to decrypt the message from the cipher with the provided secret
/// and nonce.
DecryptionFailed,
}
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