diff --git a/CHANGELOG.md b/CHANGELOG.md index ee2734fac..214f09119 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - [\#172](https://github.com/Manta-Network/manta-rs/pull/172) Add abstract Phase 2 for Groth16 trusted setup ### Changed +- [\#180](https://github.com/Manta-Network/manta-rs/pull/180) Start moving to new `arkworks` backend for `manta-crypto` - [\#191](https://github.com/Manta-Network/manta-rs/pull/191) Move HTTP Utilities to `manta-util` ### Deprecated diff --git a/Cargo.toml b/Cargo.toml index 70582fc61..fc22cab0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,7 @@ [workspace] resolver = "2" members = ["manta-*", "workspace-hack"] +exclude = ["forks/cocoon"] + +[patch.crates-io] +cocoon = { path = "forks/cocoon" } diff --git a/forks/cocoon/.github/workflows/rust.yaml b/forks/cocoon/.github/workflows/rust.yaml new file mode 100644 index 000000000..d4ec71096 --- /dev/null +++ b/forks/cocoon/.github/workflows/rust.yaml @@ -0,0 +1,60 @@ +name: Cocoon + +on: + push: + branches: + - main + + pull_request: + branches: + - main + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Run Rustfmt + run: cargo fmt -- --check + - name: Build + run: cargo build --verbose + - name: Build with no default features + run: cargo build --no-default-features + - name: Build with only "alloc" feature + run: cargo build --no-default-features --features="alloc" + - name: Run tests + run: cargo test --verbose + + coverage: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Select Rust nightly build + run: rustup default nightly + - name: Test with profiling + env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: >- + -Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code + -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort + RUSTDOCFLAGS: >- + -Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code + -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort + run: cargo test --all-features + - name: Install grcov + run: | + curl -L https://github.com/mozilla/grcov/releases/latest/download/grcov-x86_64-unknown-linux-gnu.tar.bz2 \ + | tar jxf - + - name: Run grcov + run: | + mkdir coverage + ./grcov ./target/debug/ -s . -t lcov --llvm --branch --ignore-not-existing --ignore "/*" \ + --excl-line '#\[|=> panic!|unreachable!|Io\(std::io::Error\)' \ + --excl-br-line '#\[|=> panic!|unreachable!|assert_..!' -o ./coverage/lcov.info + - name: Send to Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/forks/cocoon/.gitignore b/forks/cocoon/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/forks/cocoon/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/forks/cocoon/Cargo.toml b/forks/cocoon/Cargo.toml new file mode 100644 index 000000000..64ba7be4c --- /dev/null +++ b/forks/cocoon/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "cocoon" +version = "0.3.1" +description = "A simple protected container with strong encryption and format validation." +authors = ["Alexander Fadeev "] +edition = "2018" +license = "MIT" +keywords = ["encryption", "storage", "keystore", "parser", "container"] +categories = ["cryptography", "no-std", "parser-implementations"] +homepage = "https://github.com/fadeevab/cocoon" +repository = "https://github.com/fadeevab/cocoon" +documentation = "https://docs.rs/cocoon" +readme = "README.md" + +[dependencies] +aes-gcm = "0.9" +chacha20poly1305 = { version = "0.9", default-features = false } +hmac = "0.11" +pbkdf2 = { version = "0.9", default-features = false, features = ["sha2", "hmac"] } +rand = { version = "0.8", default-features = false, features = ["std_rng"] } +sha2 = { version = "0.9", default-features = false } +zeroize = { version = "1", default-features = false } + +[dev-dependencies] +borsh = "0.9" + +[features] +# Enables `std` feature by default. +default = ["std"] + +# Enables all features, including support of simplified Cocoon API, using `rand::thread_rng`, +# and API related to `std::io`: wrap to writer, unwrap from reader. +std = ["alloc", "rand/std"] + +# Enables `Vec` container. Can be used without `std` crate (in "no std" build). +alloc = ["chacha20poly1305/alloc"] + +# Enables support of Cocoon::from_entropy() which gets random bytes from OsRng. +getrandom = ["rand/getrandom"] + +# To speed up PBKDF2. +[profile.test] +opt-level = 3 + +# To speed up PBKDF2. +[profile.dev.package.sha2] +opt-level = 3 + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docs_rs"] diff --git a/forks/cocoon/LICENSE b/forks/cocoon/LICENSE new file mode 100644 index 000000000..aad332d62 --- /dev/null +++ b/forks/cocoon/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Alexander Fadeev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/forks/cocoon/README.md b/forks/cocoon/README.md new file mode 100644 index 000000000..dded10a63 --- /dev/null +++ b/forks/cocoon/README.md @@ -0,0 +1,167 @@ +[![Cocoon](https://github.com/fadeevab/cocoon/workflows/Cocoon/badge.svg?event=push)](https://github.com/fadeevab/cocoon) +[![crates.io](https://img.shields.io/crates/v/cocoon.svg)](https://crates.io/crates/cocoon) +[![docs.rs](https://docs.rs/cocoon/badge.svg)](https://docs.rs/cocoon/) +[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/fadeevab/cocoon/LICENSE) +[![coverage](https://coveralls.io/repos/github/fadeevab/cocoon/badge.svg?branch=master)](https://coveralls.io/github/fadeevab/cocoon?branch=master) + +# Cocoon + +Cocoon format + +`MiniCocoon` and `Cocoon` are protected containers to wrap sensitive data with strong +[encryption](#cryptography) and format validation. A format of `MiniCocoon` and `Cocoon` +is developed for the following practical cases: + +1. As an _encrypted file format_ to organize simple secure storage: + 1. Key store. + 2. Password store. + 3. Sensitive data store. +2. For _encrypted data transfer_: + * As a secure in-memory container. + +`Cocoon` is developed with security in mind. It aims to do the only one thing and do it +flawlessly. It has a minimal set of dependencies and a minimalist design to simplify control over +security aspects. It's a pure Rust implementation, and all dependencies are pure Rust +packages with disabled default features. + +# Problem + +Whenever you need to transmit and store data securely you reinvent the wheel: you have to +take care of how to encrypt data properly, how to handle randomly generated buffers, +then how to get data back, parse, and decrypt. Instead, you can use `MiniCocoon` +and `Cocoon`. + +# Basic Usage + +## 📌 Wrap/Unwrap + +One party wraps private data into a container using `MiniCocoon::wrap`. +Another party (or the same one, or whoever knows the key) unwraps data +out of the container using `MiniCocoon::unwrap`. + +`MiniCocoon` is preferred against `Cocoon` in a case of simple data encryption +because it generates a container with a smaller header without version control, and also +it allows to wrap data sequentially (wrap, wrap, wrap!) without performance drop +because of KDF calculation. +```rust +let cocoon = MiniCocoon::from_key(b"0123456789abcdef0123456789abcdef", &[0; 32]); + +let wrapped = cocoon.wrap(b"my secret data")?; +assert_ne!(&wrapped, b"my secret data"); + +let unwrapped = cocoon.unwrap(&wrapped)?; +assert_eq!(unwrapped, b"my secret data"); +``` + +## 📌 Dump/Parse + +You can store data to file. Put data into `Vec` container, the data is going to be +encrypted _in place_ and stored in a file using the "cocoon" [format](#cocoon). + +`Cocoon` is preferred as a long-time data storage, it has an extended header with a magic +number, options, and version control. +```rust +let mut data = b"my secret data".to_vec(); +let cocoon = Cocoon::new(b"password"); + +cocoon.dump(data, &mut file)?; + +let data = cocoon.parse(&mut file)?; +assert_eq!(&data, b"my secret data"); +``` + +## 📌 Encrypt/Decrypt + +You can encrypt data in place and avoid re-allocations. The method operates with a detached +meta-data (a container format prefix) in the array on the stack. It is suitable for "`no_std`" +build and whenever you want to evade re-allocations of a huge amount of data. You have to care +about how to store and transfer a data length and a container prefix though. + +Both `MiniCocoon` and `Cocoon` have the same API, but prefixes are of different sizes. +`MiniCocoon` doesn't have the overhead of generating KDF on each encryption call, therefore +it's recommended for simple sequential encryption/decryption operations. +```rust +let mut data = "my secret data".to_owned().into_bytes(); +let cocoon = MiniCocoon::from_key(b"0123456789abcdef0123456789abcdef", &[0; 32]); + +let detached_prefix = cocoon.encrypt(&mut data)?; +assert_ne!(data, b"my secret data"); + +cocoon.decrypt(&mut data, &detached_prefix)?; +assert_eq!(data, b"my secret data"); +``` + +# Study Case +You implement a database of secrets that must be stored in an encrypted file using a user +password. There are a lot of ways how your database can be represented in memory and how +it could be serialized. You handle these aspects on your own, e.g. you can use +`HashMap` to manage data and use `borsh`, or `bincode`, +to serialize the data. You can even compress a serialized buffer before encryption. + +In the end, you use `Cocoon` to put the final image into an encrypted container. + +```rust +use borsh::BorshSerialize; +use cocoon::{Cocoon, Error}; + +use std::collections::HashMap; +use std::fs::File; + +// Your data can be represented in any way. +#[derive(BorshSerialize)] +struct Database { + inner: HashMap, +} + +fn main() -> Result<(), Error> { + let mut file = File::create("target/test.db")?; + let mut db = Database { inner: HashMap::new() }; + + // Over time you collect some kind of data. + db.inner.insert("my.email@example.com".to_string(), "eKPV$PM8TV5A2".to_string()); + + // You can choose how to serialize data. Also, you can compress it. + let encoded = db.try_to_vec().unwrap(); + + // Finally, you want to store your data secretly. + // Supply some password to Cocoon: password is any byte array, basically. + // Don't use a hard-coded password in real life! + // It could be a user-supplied password. + let cocoon = Cocoon::new(b"secret password"); + + // Dump the serialized database into a file as an encrypted container. + let container = cocoon.dump(encoded, &mut file)?; + + Ok(()) +} +``` + +# Cryptography + +256-bit cryptography is chosen as a `Cocoon` baseline. + +| Cipher (AEAD) | Key Derivation Function (KDF) | +|-------------------|----------------------------------| +| Chacha20-Poly1305 | PBKDF2-SHA256: 100000 iterations | +| AES256-GCM | | + +* Key: 256-bit. +* Salt for KDF: random 128-bit + predefined part. +* Nonce for encryption: random 96-bit. + +Key derivation parameters comply with NIST SP 800-132 recommendations (salt, iterations), +and cipher parameters (key, nonce, length) fit requirements of a particular cipher. +AEAD is chosen in order to authenticate encrypted data together with an unencrypted header. + +# Zeroization + +Encryption key is wrapped into zeroizing container +(provided by `zeroize` crate), which means that the key is erased automatically once it is dropped. + +# How It Works + +See more implementation details on +[![docs.rs](https://docs.rs/cocoon/badge.svg)](https://docs.rs/cocoon/), e.g. +1. the process of [container creation](https://docs.rs/cocoon/#container-creation), +2. customizable [crate features](https://docs.rs/cocoon/#crate-features), +3. and of course [API](https://docs.rs/cocoon/#cocoon). \ No newline at end of file diff --git a/forks/cocoon/images/cocoon_creation_key.svg b/forks/cocoon/images/cocoon_creation_key.svg new file mode 100644 index 000000000..4b93ad12c --- /dev/null +++ b/forks/cocoon/images/cocoon_creation_key.svg @@ -0,0 +1,3 @@ + + +
password
password
salt for kdf
salt for kdf
master key
master key
kdf
kdf
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/forks/cocoon/images/cocoon_creation_rng.svg b/forks/cocoon/images/cocoon_creation_rng.svg new file mode 100644 index 000000000..8f747ae5c --- /dev/null +++ b/forks/cocoon/images/cocoon_creation_rng.svg @@ -0,0 +1,3 @@ + + +
salt for kdf
salt for kdf
nonce
nonce
header
header
options
options
rng
rng
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/forks/cocoon/images/cocoon_detached_prefix.svg b/forks/cocoon/images/cocoon_detached_prefix.svg new file mode 100644 index 000000000..47d96bf2c --- /dev/null +++ b/forks/cocoon/images/cocoon_detached_prefix.svg @@ -0,0 +1,3 @@ + + +
header
header
encrypted data
encrypted data
tag
tag
detached_prefix
detached_prefix
data
data
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/forks/cocoon/images/cocoon_encryption.svg b/forks/cocoon/images/cocoon_encryption.svg new file mode 100644 index 000000000..e61c6ce37 --- /dev/null +++ b/forks/cocoon/images/cocoon_encryption.svg @@ -0,0 +1,3 @@ + + +
nonce
nonce
cipher
cipher
master key
master key
header
header
data
data
header
header
tag
tag
encrypted data
encrypted data
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/forks/cocoon/images/cocoon_format.svg b/forks/cocoon/images/cocoon_format.svg new file mode 100644 index 000000000..39313eb9a --- /dev/null +++ b/forks/cocoon/images/cocoon_format.svg @@ -0,0 +1,3 @@ + + +
header
header
tag
tag
signature
signature
encrypted data
encrypted data
mini
mini
magic number
options
salt for KDF
encryption nonce
data length
magic number...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/forks/cocoon/images/cocoon_header_parsing.svg b/forks/cocoon/images/cocoon_header_parsing.svg new file mode 100644 index 000000000..38784a4e6 --- /dev/null +++ b/forks/cocoon/images/cocoon_header_parsing.svg @@ -0,0 +1,3 @@ + + +
salt for kdf
salt for kdf
nonce
nonce
header
header
options
options
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/forks/cocoon/images/cocoon_parsing.svg b/forks/cocoon/images/cocoon_parsing.svg new file mode 100644 index 000000000..4779a46a9 --- /dev/null +++ b/forks/cocoon/images/cocoon_parsing.svg @@ -0,0 +1,3 @@ + + +
nonce
nonce
cipher
cipher
master key
master key
header
header
tag
tag
encrypted data
encrypted data
data
data
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/forks/cocoon/src/error.rs b/forks/cocoon/src/error.rs new file mode 100644 index 000000000..8b7c397e6 --- /dev/null +++ b/forks/cocoon/src/error.rs @@ -0,0 +1,29 @@ +/// Error variants produced by the Cocoon API. +#[derive(Debug)] +pub enum Error { + /// I/o error during read/write operation (`Cocoon::dump`, `Cocoon::parse`). + #[cfg(feature = "std")] + Io(std::io::Error), + /// Format is not recognized. Probably corrupted. + UnrecognizedFormat, + /// Cryptographic error. There could be a few reasons: + /// 1. Integrity is compromised. + /// 2. Password is invalid. + Cryptography, + /// Container is too large to get processed on the current architecture. + /// E.g. it's not possible to process a container larger than 4 GB on 32-bit architecture. + TooLarge, + /// Buffer is too short and barely holds all data to decrypt, inconsistent length + /// encoded to the header. + TooShort, +} + +#[cfg(feature = "std")] +impl From for Error { + fn from(err: std::io::Error) -> Self { + match err.kind() { + std::io::ErrorKind::UnexpectedEof => Error::TooShort, + _ => Error::Io(err), + } + } +} diff --git a/forks/cocoon/src/format.rs b/forks/cocoon/src/format.rs new file mode 100644 index 000000000..415720fd3 --- /dev/null +++ b/forks/cocoon/src/format.rs @@ -0,0 +1,309 @@ +#[cfg(feature = "std")] +use std::io::Read; + +use super::{ + error::Error, + header::{CocoonHeader, CocoonVersion, MiniCocoonHeader}, +}; + +const HEADER_SIZE: usize = CocoonHeader::SIZE; +const TAG_SIZE: usize = 16; +const MAX_SIZE: usize = HEADER_SIZE + TAG_SIZE; + +const MINI_HEADER_SIZE: usize = MiniCocoonHeader::SIZE; +const MINI_SIZE: usize = MINI_HEADER_SIZE + TAG_SIZE; + +pub struct FormatPrefix { + header: CocoonHeader, + raw: [u8; MAX_SIZE], +} + +impl FormatPrefix { + pub const SERIALIZE_SIZE: usize = MAX_SIZE; + + // The idea is that having additional extensions we shell put them in the constructor. + // Meanwhile `tag` will be calculated later and it appears right on serialization. + // Also parameters are moved into the object to evade additional copying. + pub fn new(header: CocoonHeader) -> Self { + let mut raw = [0u8; MAX_SIZE]; + + match header.version() { + CocoonVersion::Version1 => { + header.serialize_into(&mut raw); + } + }; + + FormatPrefix { header, raw } + } + + pub fn serialize(mut self, tag: &[u8; TAG_SIZE]) -> [u8; Self::SERIALIZE_SIZE] { + match self.header().version() { + CocoonVersion::Version1 => (), + // _ => panic!("Prefix can be serialized into the latest version only!"), + } + + self.raw[HEADER_SIZE..HEADER_SIZE + TAG_SIZE].copy_from_slice(tag); + self.raw + } + + pub fn deserialize(start: &[u8]) -> Result { + let header = CocoonHeader::deserialize(&start)?; + + let mut raw = [0u8; MAX_SIZE]; + + match header.version() { + CocoonVersion::Version1 => { + if start.len() < HEADER_SIZE + TAG_SIZE { + return Err(Error::UnrecognizedFormat); + } + + raw[..HEADER_SIZE].copy_from_slice(&start[..HEADER_SIZE]); + raw[HEADER_SIZE..HEADER_SIZE + TAG_SIZE] + .copy_from_slice(&start[HEADER_SIZE..HEADER_SIZE + TAG_SIZE]); + } + } + + Ok(FormatPrefix { header, raw }) + } + + #[cfg(feature = "std")] + pub fn deserialize_from(reader: &mut impl Read) -> Result { + let mut raw = [0u8; MAX_SIZE]; + + reader.read_exact(&mut raw[..HEADER_SIZE])?; + let header = CocoonHeader::deserialize(&raw)?; + + match header.version() { + CocoonVersion::Version1 => { + reader.read_exact(&mut raw[HEADER_SIZE..HEADER_SIZE + TAG_SIZE])?; + } + } + + Ok(FormatPrefix { header, raw }) + } + + pub fn header(&self) -> &CocoonHeader { + &self.header + } + + pub fn prefix(&self) -> &[u8] { + &self.raw[..HEADER_SIZE] + } + + pub fn tag(&self) -> &[u8] { + &self.raw[HEADER_SIZE..HEADER_SIZE + TAG_SIZE] + } + + #[cfg(feature = "alloc")] + pub fn size(&self) -> usize { + match self.header.version() { + CocoonVersion::Version1 => HEADER_SIZE + TAG_SIZE, + } + } +} + +pub struct MiniFormatPrefix { + header: MiniCocoonHeader, + raw: [u8; MINI_SIZE], +} + +impl MiniFormatPrefix { + pub const SERIALIZE_SIZE: usize = MINI_SIZE; + + pub fn new(header: MiniCocoonHeader) -> Self { + let mut raw = [0u8; MINI_SIZE]; + + header.serialize_into(&mut raw); + + MiniFormatPrefix { header, raw } + } + + pub fn serialize(mut self, tag: &[u8; TAG_SIZE]) -> [u8; Self::SERIALIZE_SIZE] { + self.raw[MINI_HEADER_SIZE..MINI_HEADER_SIZE + TAG_SIZE].copy_from_slice(tag); + self.raw + } + + pub fn deserialize(start: &[u8]) -> Result { + let header = MiniCocoonHeader::deserialize(&start)?; + + let mut raw = [0u8; MINI_SIZE]; + + if start.len() < MINI_SIZE { + return Err(Error::UnrecognizedFormat); + } + + raw[..MINI_HEADER_SIZE].copy_from_slice(&start[..MINI_HEADER_SIZE]); + raw[MINI_HEADER_SIZE..MINI_HEADER_SIZE + TAG_SIZE] + .copy_from_slice(&start[MINI_HEADER_SIZE..MINI_HEADER_SIZE + TAG_SIZE]); + + Ok(MiniFormatPrefix { header, raw }) + } + + #[cfg(feature = "std")] + pub fn deserialize_from(reader: &mut impl Read) -> Result { + let mut raw = [0u8; MINI_SIZE]; + + reader.read_exact(&mut raw[..MINI_HEADER_SIZE])?; + let header = MiniCocoonHeader::deserialize(&raw)?; + + reader.read_exact(&mut raw[MINI_HEADER_SIZE..MINI_HEADER_SIZE + TAG_SIZE])?; + + Ok(MiniFormatPrefix { header, raw }) + } + + pub fn header(&self) -> &MiniCocoonHeader { + &self.header + } + + pub fn prefix(&self) -> &[u8] { + &self.raw[..MINI_HEADER_SIZE] + } + + pub fn tag(&self) -> &[u8] { + &self.raw[MINI_HEADER_SIZE..MINI_HEADER_SIZE + TAG_SIZE] + } +} + +#[cfg(test)] +mod test { + use super::*; + + use std::io::Cursor; + + use crate::header::{CocoonConfig, CocoonHeader, MiniCocoonHeader}; + + #[test] + fn format_prefix_good() { + const RANDOM_ADD: usize = 12; + let mut raw = [1u8; FormatPrefix::SERIALIZE_SIZE + RANDOM_ADD]; + + CocoonHeader::new(CocoonConfig::default(), [0; 16], [0; 12], 0).serialize_into(&mut raw); + + let prefix = FormatPrefix::deserialize(&raw).expect("Deserialized container's prefix"); + + assert_eq!(&raw[..HEADER_SIZE], prefix.prefix()); + assert_eq!(&raw[HEADER_SIZE..HEADER_SIZE + TAG_SIZE], prefix.tag()); + assert_eq!(prefix.size(), FormatPrefix::SERIALIZE_SIZE); + } + + #[test] + fn format_prefix_short() { + let mut raw = [1u8; FormatPrefix::SERIALIZE_SIZE]; + + CocoonHeader::new(CocoonConfig::default(), [0; 16], [0; 12], 0).serialize_into(&mut raw); + FormatPrefix::deserialize(&raw).expect("Deserialized container's prefix"); + + match FormatPrefix::deserialize(&raw[0..FormatPrefix::SERIALIZE_SIZE - 1]) { + Err(err) => match err { + Error::UnrecognizedFormat => (), + _ => panic!("Invalid error"), + }, + Ok(_) => panic!("Cocoon prefix has not to be parsed"), + }; + } + + #[test] + fn format_version1() { + assert_eq!(44 + 16, FormatPrefix::SERIALIZE_SIZE); + + let header = CocoonHeader::new(CocoonConfig::default(), [1; 16], [2; 12], 50); + let prefix = FormatPrefix::new(header); + let tag = [3; 16]; + + assert_eq!( + [ + 127, 192, 10, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 50, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3 + ][..], + prefix.serialize(&tag)[..] + ); + } + + #[test] + fn format_prefix_deserialize_from() { + let mut raw = [1u8; FormatPrefix::SERIALIZE_SIZE]; + + CocoonHeader::new(CocoonConfig::default(), [0; 16], [0; 12], 0).serialize_into(&mut raw); + + let mut file = Cursor::new(&raw[..]); + + FormatPrefix::deserialize_from(&mut file).expect("Deserialized prefix"); + + for i in 0..raw.len() - 1 { + let mut file = Cursor::new(&raw[0..i]); + match FormatPrefix::deserialize_from(&mut file) { + Err(_) => (), + _ => panic!("Short file cannot be deserialized"), + } + } + } + + #[test] + fn format_prefix_bad_prefix() { + let raw = [1u8; FormatPrefix::SERIALIZE_SIZE]; + + match FormatPrefix::deserialize(&raw) { + Err(_) => (), + _ => panic!("Bad prefix is expected"), + } + + let mut file = Cursor::new(&raw[..]); + + match FormatPrefix::deserialize_from(&mut file) { + Err(_) => (), + _ => panic!("Bad prefix is expected"), + } + } + + #[test] + fn mini_format_prefix_good() { + const RANDOM_ADD: usize = 12; + let mut raw = [1u8; MiniFormatPrefix::SERIALIZE_SIZE + RANDOM_ADD]; + + MiniCocoonHeader::new([1; 12], 13).serialize_into(&mut raw); + + let prefix = MiniFormatPrefix::deserialize(&raw).expect("Deserialized container's prefix"); + + assert_eq!(&raw[..MINI_HEADER_SIZE], prefix.prefix()); + assert_eq!( + &raw[MINI_HEADER_SIZE..MINI_HEADER_SIZE + TAG_SIZE], + prefix.tag() + ); + } + + #[test] + fn mini_format_prefix_short() { + let mut raw = [1u8; MiniFormatPrefix::SERIALIZE_SIZE]; + + MiniCocoonHeader::new([1; 12], 13).serialize_into(&mut raw); + MiniFormatPrefix::deserialize(&raw).expect("Deserialized container's prefix"); + + match MiniFormatPrefix::deserialize(&raw[0..MiniFormatPrefix::SERIALIZE_SIZE - 1]) { + Err(err) => match err { + Error::UnrecognizedFormat => (), + _ => panic!("Invalid error"), + }, + Ok(_) => panic!("Cocoon prefix has not to be parsed"), + }; + } + + #[test] + fn mini_format_prefix_deserialize_from() { + let mut raw = [1u8; MiniFormatPrefix::SERIALIZE_SIZE]; + + MiniCocoonHeader::new([1; 12], 13).serialize_into(&mut raw); + + let mut file = Cursor::new(&raw[..]); + + MiniFormatPrefix::deserialize_from(&mut file).expect("Deserialized prefix"); + + for i in 0..raw.len() - 1 { + let mut file = Cursor::new(&raw[0..i]); + match FormatPrefix::deserialize_from(&mut file) { + Err(_) => (), + _ => panic!("Short file cannot be deserialized"), + } + } + } +} diff --git a/forks/cocoon/src/header.rs b/forks/cocoon/src/header.rs new file mode 100644 index 000000000..522a2f4ab --- /dev/null +++ b/forks/cocoon/src/header.rs @@ -0,0 +1,531 @@ +use core::convert::{TryFrom, TryInto}; + +use super::error::Error; + +/// Safe deserializing from byte to enum. +macro_rules! match_enum { + ($m:expr, $($variant:expr),+) => { + match $m { + $(v if v == $variant as u8 => $variant),+, + _ => return Err(Error::UnrecognizedFormat), + } + }; +} + +/// 256-bit AEAD ciphers (Authenticated Encryption with Associated Data). +#[derive(Clone, Copy)] +#[cfg_attr(test, derive(Debug, PartialEq))] +pub enum CocoonCipher { + /// Chacha20-Poly1305. + Chacha20Poly1305 = 1, + /// AES256-GCM. + Aes256Gcm, +} + +/// Key derivation functions (KDF) to derive master key +/// from user password. PBKDF2 by default. +#[derive(Clone, Copy)] +#[cfg_attr(test, derive(Debug, PartialEq))] +pub enum CocoonKdf { + /// PBKDF2 with NIST SP 800-132 recommended parameters: + /// 1. Salt: 16 bytes (128-bit) + predefined salt. + /// 2. Iterations: 100 000 (10_000 when "weak" KDF is enabled). + Pbkdf2 = 1, +} + +#[derive(Copy, Clone)] +#[cfg_attr(test, derive(Debug, PartialEq))] +enum CocoonKdfVariant { + Strong = 1, + Weak, +} + +#[derive(Copy, Clone)] +#[cfg_attr(test, derive(Debug, PartialEq))] +pub enum CocoonVersion { + Version1 = 1, +} + +/// A set of `Cocoon` container capabilities. Config is embedded to a container in the header. +#[derive(Clone)] +#[cfg_attr(test, derive(Debug, PartialEq))] +pub struct CocoonConfig { + /// Cipher. + cipher: CocoonCipher, + /// Key derivation function (KDF). + kdf: CocoonKdf, + /// KDF variant. Not configurable from outside of the crate field. + kdf_variant: CocoonKdfVariant, + /// Reserved byte is for the explicit structure aligning + /// as well as for possible format upgrade in future. + _reserved: u8, +} + +impl Default for CocoonConfig { + fn default() -> CocoonConfig { + CocoonConfig { + cipher: CocoonCipher::Chacha20Poly1305, + kdf: CocoonKdf::Pbkdf2, + kdf_variant: CocoonKdfVariant::Strong, + _reserved: Default::default(), + } + } +} + +impl CocoonConfig { + pub fn cipher(&self) -> CocoonCipher { + self.cipher + } + + pub fn kdf(&self) -> CocoonKdf { + self.kdf + } + + pub fn kdf_iterations(&self) -> u32 { + match self.kdf { + CocoonKdf::Pbkdf2 => match self.kdf_variant { + // 1000 is the minimum according to NIST SP 800-132, however this recommendation + // is 20 years old at this moment. 100_000 iterations are extremely slow in debug + // builds without optimization, so 10_000 iterations could be used at least. + CocoonKdfVariant::Weak => 10_000, + // NIST SP 800-132 (PBKDF2) recommends to choose an iteration count + // somewhere between 1000 and 10_000_000, so the password derivation function + // can not be brute forced easily. + CocoonKdfVariant::Strong => 100_000, + }, + } + } + + pub fn with_cipher(mut self, cipher: CocoonCipher) -> Self { + self.cipher = cipher; + self + } + + pub fn with_weak_kdf(mut self) -> Self { + self.kdf_variant = CocoonKdfVariant::Weak; + self + } + + fn serialize(&self) -> [u8; 4] { + let mut buf = [0u8; 4]; + buf[0] = self.cipher as u8; + buf[1] = self.kdf as u8; + buf[2] = self.kdf_variant as u8; + buf[3] = Default::default(); + buf + } + + fn deserialize(buf: &[u8]) -> Result { + if buf.len() < 4 { + return Err(Error::UnrecognizedFormat); + } + + #[rustfmt::skip] + let cipher = match_enum!(buf[0], CocoonCipher::Chacha20Poly1305, CocoonCipher::Aes256Gcm); + let kdf = match_enum!(buf[1], CocoonKdf::Pbkdf2); + let kdf_variant = match_enum!(buf[2], CocoonKdfVariant::Weak, CocoonKdfVariant::Strong); + + Ok(CocoonConfig { + cipher, + kdf, + kdf_variant, + _reserved: Default::default(), + }) + } +} + +/// Header of the protected container. +/// +/// | Field | Length | Value | Notes | +/// |:-------------------|:---------|:--------------------|:---------------------------------------| +/// | Magic number | 3 Bytes | 0x7f 0xc0 '\n' | Constant | +/// | Version | 1 Byte | 0x01 | Version 1 | +/// | Options | 4 Bytes | 0x01 0x01 0x01 0x00 | Chacha20Poly1304, PBKDF2, 100_000 iter.| +/// | Random salt | 16 Bytes | | A salt is used to derive a master key | +/// | Random nonce | 12 Bytes | | A nonce is used for AEAD encryption | +/// | Payload length | 8 Bytes | | A length of encrypted (wrapped) data | +pub struct CocoonHeader { + /// A magic number: 0x7f 0xc0 b'\n'. + /// + /// It makes a container suitable to get stored in a file, + /// and basically it is to prevent an unintended processing of incompatible data structure. + magic: [u8; 3], + /// A version. + /// + /// Version is a hidden not configurable field, it allows to upgrade the format in the future. + version: CocoonVersion, + /// Container settings. + config: CocoonConfig, + /// 16 bytes of randomly generated salt. + /// + /// 128-bit is a minimum for PBKDF2 according to NIST recommendations. + /// Salt is used to generate a master key from a password. Salt itself is not a secret value. + salt: [u8; 16], + /// 12 bytes of a randomly generated nonce. + /// + /// A nonce is used to seed AEAD ciphers and to encrypt data. Nonce is not a secret value. + nonce: [u8; 12], + /// 8 bytes of data length at most. + /// + /// 64-bit of length allows to handle up to 256GB of Chacha20/AES256 cipher data. + length: usize, +} + +impl CocoonHeader { + const MAGIC: [u8; 3] = [0x7f, 0xc0, b'\n']; + + pub const SIZE: usize = 44; // Don't use size_of::() here because of #[repr(Rust)]. + + pub fn new(config: CocoonConfig, salt: [u8; 16], nonce: [u8; 12], length: usize) -> Self { + CocoonHeader { + magic: CocoonHeader::MAGIC, + version: CocoonVersion::Version1, + config, + salt, + nonce, + length, + } + } + + pub fn config(&self) -> &CocoonConfig { + &self.config + } + + pub fn data_length(&self) -> usize { + self.length + } + + pub fn salt(&self) -> &[u8] { + &self.salt + } + + pub fn nonce(&self) -> &[u8] { + &self.nonce + } + + pub fn version(&self) -> CocoonVersion { + self.version + } + + #[cfg(test)] + pub fn serialize(&self) -> [u8; Self::SIZE] { + let mut buf = [0u8; Self::SIZE]; + self.serialize_into(&mut buf); + buf + } + + pub fn serialize_into(&self, buf: &mut [u8]) { + debug_assert!(buf.len() >= Self::SIZE); + let length = u64::try_from(self.length).expect("Data too large"); + + buf[..3].copy_from_slice(&self.magic); + buf[3] = self.version as u8; + buf[4..8].copy_from_slice(&self.config.serialize()); + buf[8..24].copy_from_slice(&self.salt); + buf[24..36].copy_from_slice(&self.nonce); + buf[36..Self::SIZE].copy_from_slice(&length.to_be_bytes()); + } + + pub fn deserialize(buf: &[u8]) -> Result { + if buf.len() < Self::SIZE { + return Err(Error::UnrecognizedFormat); + } + + let mut magic = [0u8; 3]; + magic.copy_from_slice(&buf[..3]); + if magic != Self::MAGIC { + return Err(Error::UnrecognizedFormat); + } + + let version = match_enum!(buf[3], CocoonVersion::Version1); + let config = CocoonConfig::deserialize(&buf[4..8])?; + let mut salt = [0u8; 16]; + salt.copy_from_slice(&buf[8..24]); + let mut nonce = [0u8; 12]; + nonce.copy_from_slice(&buf[24..36]); + + let mut length_bytes = [0u8; 8]; + length_bytes.copy_from_slice(&buf[36..Self::SIZE]); + + // Covert to usize, that may fail on 32-bit platform. + let length: usize = u64::from_be_bytes(length_bytes) + .try_into() + .map_err(|_| Error::TooLarge)?; + + Ok(CocoonHeader { + magic, + version, + config, + salt, + nonce, + length, + }) + } +} + +/// Header of the mini Cocoon. +/// +/// | Field | Length | Value | Notes | +/// |:-------------------|:---------|:--------------------|:---------------------------------------| +/// | Random nonce | 12 Bytes | | A nonce is used for AEAD encryption | +/// | Payload length | 8 Bytes | | A length of encrypted (wrapped) data | +pub struct MiniCocoonHeader { + /// 12 bytes of a randomly generated nonce. + /// + /// A nonce is used to seed AEAD ciphers and to encrypt data. Nonce is not a secret value. + nonce: [u8; 12], + /// 8 bytes of data length at most. + /// + /// 64-bit of length allows to handle up to 256GB of Chacha20/AES256 cipher data. + length: usize, +} + +impl MiniCocoonHeader { + pub const SIZE: usize = 20; // Don't use size_of::() here because of #[repr(Rust)]. + + pub fn new(nonce: [u8; 12], length: usize) -> Self { + MiniCocoonHeader { nonce, length } + } + + pub fn data_length(&self) -> usize { + self.length + } + + pub fn nonce(&self) -> &[u8] { + &self.nonce + } + + #[cfg(test)] + pub fn serialize(&self) -> [u8; Self::SIZE] { + let mut buf = [0u8; Self::SIZE]; + self.serialize_into(&mut buf); + buf + } + + pub fn serialize_into(&self, buf: &mut [u8]) { + debug_assert!(buf.len() >= Self::SIZE); + let length = u64::try_from(self.length).expect("Data too large"); + + buf[..12].copy_from_slice(&self.nonce); + buf[12..Self::SIZE].copy_from_slice(&length.to_be_bytes()); + } + + pub fn deserialize(buf: &[u8]) -> Result { + if buf.len() < Self::SIZE { + return Err(Error::UnrecognizedFormat); + } + + let mut nonce = [0u8; 12]; + nonce.copy_from_slice(&buf[..12]); + + let mut length_bytes = [0u8; 8]; + length_bytes.copy_from_slice(&buf[12..Self::SIZE]); + let length = u64::from_be_bytes(length_bytes) + .try_into() + .map_err(|_| Error::TooLarge)?; + + Ok(MiniCocoonHeader { nonce, length }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn header_config_default() { + let config = CocoonConfig::default(); + assert_eq!(config.kdf(), CocoonKdf::Pbkdf2); + assert_eq!(config.cipher(), CocoonCipher::Chacha20Poly1305); + assert_eq!(config.kdf_iterations(), 100_000); + } + + #[test] + fn header_config_modify() { + let config = CocoonConfig::default() + .with_cipher(CocoonCipher::Aes256Gcm) + .with_weak_kdf(); + assert_eq!(config.cipher(), CocoonCipher::Aes256Gcm); + assert_eq!(config.kdf_iterations(), 10_000); + } + + #[test] + fn header_config_serialize() { + let config = CocoonConfig::default(); + assert_eq!(config.serialize(), [0x01, 0x01, 0x01, 0x00]); + + let config = config.with_cipher(CocoonCipher::Aes256Gcm); + assert_eq!(config.serialize(), [0x02, 0x01, 0x01, 0x00]); + + let config = config.with_weak_kdf(); + assert_eq!(config.serialize(), [0x02, 0x01, 0x02, 0x00]); + } + + #[test] + fn header_config_deserialize() { + let config = CocoonConfig::default().serialize(); + + for i in 0..3 { + match CocoonConfig::deserialize(&config[0..i]) { + Err(e) => match e { + Error::UnrecognizedFormat => (), + _ => panic!("Unexpected error, UnrecognizedFormat is expected only"), + }, + _ => panic!("Success is not expected"), + } + } + + CocoonConfig::deserialize(&config[0..4]).expect("Deserialized config"); + } + + #[test] + fn header_config_deserialize_with_options() { + let config = CocoonConfig::default() + .with_weak_kdf() + .with_cipher(CocoonCipher::Aes256Gcm) + .serialize(); + CocoonConfig::deserialize(&config).expect("Deserialized config"); + } + + #[test] + fn header_new() { + let header = CocoonHeader::new(CocoonConfig::default(), [0; 16], [0; 12], std::usize::MAX); + assert_eq!(header.config(), &CocoonConfig::default()); + assert_eq!(header.salt(), [0; 16]); + assert_eq!(header.nonce(), [0; 12]); + assert_eq!(header.data_length(), std::usize::MAX); + assert_eq!(header.version(), CocoonVersion::Version1); + } + + #[test] + fn header_serialize() { + let header = CocoonHeader::new( + CocoonConfig::default().with_cipher(CocoonCipher::Aes256Gcm), + [1; 16], + [2; 12], + std::usize::MAX, + ); + + assert_eq!( + header.serialize()[..], + [ + 0x7f, 0xc0, b'\n', 1, 2, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 255, 255, 255, 255, 255, 255, 255, 255 + ][..] + ); + } + + #[test] + fn header_deserialize() { + let header = CocoonHeader::new(CocoonConfig::default(), [1; 16], [2; 12], 50); + let header = match CocoonHeader::deserialize(&header.serialize()) { + Ok(h) => h, + Err(e) => panic!("Cannot deserialize serialized: {:?}", e), + }; + + assert_eq!(header.config(), &CocoonConfig::default()); + assert_eq!(header.salt(), [1; 16]); + assert_eq!(header.nonce(), [2; 12]); + assert_eq!(header.data_length(), 50); + assert_eq!(header.version(), CocoonVersion::Version1); + } + + #[test] + fn header_deserialize_small() { + let raw_header = [0u8; CocoonHeader::SIZE - 1]; + match CocoonHeader::deserialize(&raw_header) { + Err(e) => match e { + Error::UnrecognizedFormat => (), + _ => panic!("Unexpected error, UnrecognizedFormat is expected only"), + }, + _ => panic!("Success is not expected"), + } + } + + #[test] + fn header_deserialize_modified() { + let header = CocoonHeader::new(CocoonConfig::default(), [1; 16], [2; 12], 50); + + // Corrupt header: one byte per time. + for i in 0..7 { + let mut raw_header = header.serialize(); + raw_header[i] = 0xff; + match CocoonHeader::deserialize(&raw_header) { + Ok(_) => panic!("Header must not be deserialized on byte #{}", i), + Err(e) => match e { + Error::UnrecognizedFormat => (), + _ => panic!("Invalid error, expected Error::UnrecognizedFormat"), + }, + } + } + + // Corrupt header: the reserved byte is ignored, and random data and length can be any. + for i in 7..CocoonHeader::SIZE { + let mut raw_header = header.serialize(); + raw_header[i] = 0xff; + CocoonHeader::deserialize(&raw_header).expect("Header must be deserialized"); + } + } + + #[test] + fn mini_header_new() { + let header = MiniCocoonHeader::new([1; 12], std::usize::MAX); + assert_eq!(header.nonce(), [1; 12]); + assert_eq!(header.data_length(), std::usize::MAX); + } + + #[test] + fn mini_header_serialize() { + let header = MiniCocoonHeader::new([2; 12], std::usize::MAX); + + assert_eq!( + header.serialize()[..], + [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 255, 255, 255, 255, 255, 255, 255, 255][..] + ); + } + + #[test] + fn mini_header_deserialize() { + let header = MiniCocoonHeader::new([2; 12], 50); + let header = match MiniCocoonHeader::deserialize(&header.serialize()) { + Ok(h) => h, + Err(e) => panic!("Cannot deserialize serialized: {:?}", e), + }; + + assert_eq!(header.nonce(), [2; 12]); + assert_eq!(header.data_length(), 50); + } + + #[test] + fn mini_header_deserialize_modified() { + let header = MiniCocoonHeader::new([2; 12], 50); + + // Corrupt header: random data and length can be any. + for i in 0..MiniCocoonHeader::SIZE { + let mut raw_header = header.serialize(); + raw_header[i] = 0xff; + MiniCocoonHeader::deserialize(&raw_header).expect("Header must be deserialized"); + } + } + + #[test] + fn mini_header_deserialize_short() { + let header = [0u8; MiniCocoonHeader::SIZE]; + + for i in 0..header.len() - 1 { + match MiniCocoonHeader::deserialize(&header[0..i]) { + Err(e) => match e { + Error::UnrecognizedFormat => (), + _ => panic!("Unexpected error, UnrecognizedFormat is expected only"), + }, + _ => panic!("Success is not expected"), + } + } + } + + #[test] + fn mini_header_deserialize_long() { + let header = [0u8; MiniCocoonHeader::SIZE + 1]; + MiniCocoonHeader::deserialize(&header).expect("Header must be deserialized"); + } +} diff --git a/forks/cocoon/src/kdf.rs b/forks/cocoon/src/kdf.rs new file mode 100644 index 000000000..f9c4f6264 --- /dev/null +++ b/forks/cocoon/src/kdf.rs @@ -0,0 +1,64 @@ +pub const KEY_SIZE: usize = 32; +pub const SALT_MIN_SIZE: usize = 16; + +/// A 256-bit key derived from a password using PBKDF2 (HMAC-SHA256) with guaranteed zeroization. +pub mod pbkdf2 { + use hmac::Hmac; + use pbkdf2::pbkdf2; + use sha2::Sha256; + use zeroize::Zeroizing; + + use super::{KEY_SIZE, SALT_MIN_SIZE}; + + /// Derives a 256-bit symmetric key from a byte array (password or another key) using PBKDF2. + pub fn derive(salt: &[u8], password: &[u8], iterations: u32) -> Zeroizing<[u8; KEY_SIZE]> { + debug_assert!(salt.len() >= SALT_MIN_SIZE); + + // NIST SP 800-132 (PBKDF2) recommends to concatenate a constant purpose to the random part + // in order to narrow down a key usage domain to the scope of the current application. + // Salt = [constant string || random value]. + let ext_salt = [b"cocoon", salt].concat(); + + // Prepare an output buffer. + let mut derived_key = [0u8; KEY_SIZE]; + + pbkdf2::>(password, &ext_salt, iterations, &mut derived_key); + + Zeroizing::new(derived_key) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn encryption_key_new_salt0() { + let password = b"password"; + let salt = vec![0u8; 16]; + let key = pbkdf2::derive(&salt, password, 1000); + + assert_eq!( + key.as_ref(), + &[ + 110, 120, 137, 247, 90, 238, 41, 97, 25, 140, 207, 38, 9, 49, 201, 243, 10, 228, + 78, 48, 37, 238, 52, 193, 171, 157, 125, 89, 215, 246, 71, 6 + ] + ); + } + + #[test] + fn encryption_key_new_salt16() { + let password = b"password"; + let salt = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let key = pbkdf2::derive(&salt, &password[..], 1000); + + assert_eq!( + key.as_ref(), + &[ + 128, 112, 58, 101, 232, 184, 2, 133, 16, 237, 161, 220, 75, 102, 29, 102, 211, 88, + 204, 1, 46, 119, 49, 83, 180, 4, 67, 54, 14, 206, 250, 240 + ] + ); + } +} diff --git a/forks/cocoon/src/lib.rs b/forks/cocoon/src/lib.rs new file mode 100644 index 000000000..080e3ced0 --- /dev/null +++ b/forks/cocoon/src/lib.rs @@ -0,0 +1,1124 @@ +//! # Cocoon +//! +//! Cocoon format +//! +//! [`MiniCocoon`] and [`Cocoon`] are protected containers to wrap sensitive data with strong +//! [encryption](#cryptography) and format validation. A format of [`MiniCocoon`] and [`Cocoon`] +//! is developed for the following practical cases: +//! +//! 1. As an _encrypted file format_ to organize simple secure storage: +//! 1. Key store. +//! 2. Password store. +//! 3. Sensitive data store. +//! 2. For _encrypted data transfer_: +//! * As a secure in-memory container. +//! +//! Cocoon is developed with security in mind. It aims to do the only one thing and do it +//! flawlessly. It has a minimal set of dependencies and a minimalist design to simplify control +//! over security aspects. It's a pure Rust implementation, and all dependencies are pure Rust +//! packages with disabled default features. +//! +//! # Problem +//! +//! Whenever you need to transmit and store data securely you reinvent the wheel: you have to +//! take care of how to encrypt data properly, how to handle randomly generated buffers, +//! then how to get data back, parse, and decrypt. Instead, you can use [`MiniCocoon`] +//! and [`Cocoon`]. +//! +//! # Basic Usage +//! +//! ## Wrap/Unwrap +//! 📌 [`wrap`](MiniCocoon::wrap)/[`unwrap`](MiniCocoon::unwrap) +//! +//! One party wraps private data into a container using [`MiniCocoon::wrap`]. +//! Another party (or the same one, or whoever knows the key) unwraps data +//! out of the container using [`MiniCocoon::unwrap`]. +//! +//! [`MiniCocoon`] is preferred against [`Cocoon`] in a case of simple data encryption +//! because it generates a container with a smaller header without version control, and also +//! it allows to wrap data sequentially (wrap, wrap, wrap!) without performance drop +//! because of KDF calculation. +//! +//! ``` +//! # use cocoon::{MiniCocoon, Error}; +//! # +//! # fn main() -> Result<(), Error> { +//! let cocoon = MiniCocoon::from_key(b"0123456789abcdef0123456789abcdef", &[0; 32]); +//! +//! let wrapped = cocoon.wrap(b"my secret data")?; +//! assert_ne!(&wrapped, b"my secret data"); +//! +//! let unwrapped = cocoon.unwrap(&wrapped)?; +//! assert_eq!(unwrapped, b"my secret data"); +//! +//! # Ok(()) +//! # } +//! ``` +//! +//! ## Dump/Parse +//! 📌 [`dump`](Cocoon::dump)/[`parse`](Cocoon::parse) +//! +//! You can store data to file. Put data into [`Vec`] container, the data is going to be +//! encrypted _in place_ and stored in a file using the "cocoon" [format](#cocoon). +//! +//! [`Cocoon`] is preferred as a long-time data storage, it has an extended header with a magic +//! number, options, and version control. +//! ``` +//! # use cocoon::{Cocoon, Error}; +//! # use std::io::Cursor; +//! # +//! # fn main() -> Result<(), Error> { +//! let mut data = b"my secret data".to_vec(); +//! let cocoon = Cocoon::new(b"password"); +//! # let cocoon = cocoon.with_weak_kdf(); // Speed up doc tests. +//! # let mut file = Cursor::new(vec![0; 150]); +//! +//! cocoon.dump(data, &mut file)?; +//! # assert_ne!(file.get_ref(), b"my secret data"); +//! +//! # file.set_position(0); +//! # +//! let data = cocoon.parse(&mut file)?; +//! assert_eq!(&data, b"my secret data"); +//! +//! # Ok(()) +//! # } +//! ``` +//! +//! ## Encrypt/Decrypt +//! 📌 [`encrypt`](MiniCocoon::encrypt)/[`decrypt`](MiniCocoon::decrypt) +//! +//! You can encrypt data in place and avoid re-allocations. The method operates with a detached +//! meta-data (a container format prefix) in the array on the stack. It is suitable for "`no_std`" +//! build and whenever you want to evade re-allocations of a huge amount of data. You have to care +//! about how to store and transfer a data length and a container prefix though. +//! +//! Both [`MiniCocoon`] and [`Cocoon`] have the same API, but prefixes are of different sizes. +//! [`MiniCocoon`] doesn't have the overhead of generating KDF on each encryption call, therefore +//! it's recommended for simple sequential encryption/decryption operations. +//! ``` +//! # use cocoon::{MiniCocoon, Error}; +//! # +//! # fn main() -> Result<(), Error> { +//! let mut data = "my secret data".to_owned().into_bytes(); +//! let cocoon = MiniCocoon::from_key(b"0123456789abcdef0123456789abcdef", &[0; 32]); +//! +//! let detached_prefix = cocoon.encrypt(&mut data)?; +//! assert_ne!(data, b"my secret data"); +//! +//! cocoon.decrypt(&mut data, &detached_prefix)?; +//! assert_eq!(data, b"my secret data"); +//! +//! # Ok(()) +//! # } +//! ``` +//! +//! # Study Case +//! You implement a database of secrets that must be stored in an encrypted file using a user +//! password. There are a lot of ways how your database can be represented in memory and how +//! it could be serialized. You handle these aspects on your own, e.g. you can use +//! [`HashMap`](std::collections::HashMap) to manage data and use `borsh`, or `bincode`, +//! to serialize the data. You can even compress a serialized buffer before encryption. +//! +//! In the end, you use [`Cocoon`] to put the final image into an encrypted container. +//! +//! ``` +//! use borsh::BorshSerialize; +//! use cocoon::{Cocoon, Error}; +//! +//! use std::collections::HashMap; +//! use std::fs::File; +//! +//! // Your data can be represented in any way. +//! #[derive(BorshSerialize)] +//! struct Database { +//! inner: HashMap, +//! } +//! +//! fn main() -> Result<(), Error> { +//! let mut file = File::create("target/test.db")?; +//! let mut db = Database { inner: HashMap::new() }; +//! +//! // Over time you collect some kind of data. +//! db.inner.insert("my.email@example.com".to_string(), "eKPV$PM8TV5A2".to_string()); +//! +//! // You can choose how to serialize data. Also, you can compress it. +//! let encoded = db.try_to_vec().unwrap(); +//! +//! // Finally, you want to store your data secretly. +//! // Supply some password to Cocoon: it can be any byte array, basically. +//! // Don't use a hard-coded password in real life! +//! // It could be a user-supplied password. +//! let cocoon = Cocoon::new(b"secret password"); +//! +//! // Dump the serialized database into a file as an encrypted container. +//! let container = cocoon.dump(encoded, &mut file)?; +//! +//! Ok(()) +//! } +//! ``` +//! +//! # Crate Features +//! +//! You can customize the package compilation with the following feature set: +//! +//! | Feature | Description | +//! |--------------|------------------------------------------------------------------------------| +//! | `std` | Enables almost all API, including I/O, excluding `getrandom` feature. | +//! | `alloc` | Enables API with memory allocation, but without [`std`] dependency. | +//! | `getrandom` | Enables [`Cocoon::from_entropy`]. | +//! | no features | Creation and decryption a cocoon on the stack with no thread RNG, I/O, heap. | +//! +//! `std` is enabled by default, so you can just link the `cocoon` to you project: +//! ```toml +//! [dependencies] +//! cocoon = "0" +//! ``` +//! To use no features: +//! ```toml +//! [dependencies] +//! cocoon = { version = "0", default-features = false } +//! ``` +//! To use only `alloc` feature: +//! ```toml +//! [dependencies] +//! cocoon = { version = "0", default-features = false, features = ['alloc'] } +//! ``` +//! +//! # Cryptography +//! +//! 256-bit cryptography is chosen as a `Cocoon` baseline. +//! +//! | Cipher (AEAD) | Key Derivation Function (KDF) | +//! |-------------------|----------------------------------| +//! | Chacha20-Poly1305 | PBKDF2-SHA256: 100000 iterations | +//! | AES256-GCM | | +//! +//! * Key: 256-bit. +//! * Salt for KDF: random 128-bit + predefined part. +//! * Nonce for encryption: random 96-bit. +//! +//! Key derivation parameters comply with NIST SP 800-132 recommendations (salt, iterations), +//! and cipher parameters (key, nonce) fit requirements of a particular cipher. +//! AEAD is chosen in order to authenticate encrypted data together with an unencrypted header. +//! +//! # Zeroization +//! +//! The encryption key is wrapped into a zeroizing container +//! (provided by `zeroize` crate), which means that the key is erased automatically once it is dropped. +//! +//! # Container Creation +//! First, a random material is generated. A _salt_ is going to get mixed into a +//! master key, and a _nonce_ is used for AEAD encryption. All arrays are put +//! into a header which prefixes the final container. +//! +//! Salt and nonce +//! +//! Then a _master key_ is derived from a password using selected Key Derivation Function +//! (KDF, e.g. PBKDF2) and a random salt. +//! +//! Master key +//! +//! At this moment we have everything to encrypt data and to create a container. +//! Authenticated Encryption with Associated Data (AEAD) is used to encrypt data and to produce +//! a _tag_ which controls integrity of both _header_ and _data_. The tag is deliberately +//! placed at the beginning that allows to detach the whole prefix (header and tag) which helps +//! certain cases, e.g. it allows to work on stack, makes API more flexible, gets additional +//! control over the container format. +//! +//! Cocoon creation +//! +//! Container can be dumped to file, or it can be kept in the buffer. +//! +//! ## Container Parsing +//! +//! It starts from header parsing because random material is needed to restore a master key in +//! order to decrypt a data. +//! +//! Cocoon header parsing +//! +//! Random generator is not needed in this case. (That's why [`Cocoon::parse_only`] is provided +//! as an alternative way to initialize [`Cocoon`] to only parse a container without necessity +//! to initialize RNG.) +//! +//! A master key is derived from a password and a salt. +//! +//! Master key generation +//! +//! Finally, integrity of all parts is verified and data is decrypted. +//! +//! Cocoon parsing + +#![forbid(unsafe_code)] +#![warn(missing_docs, unused_qualifications)] +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(docs_rs, feature(doc_cfg))] + +mod error; +mod format; +mod header; +mod kdf; +mod mini; + +#[cfg(feature = "alloc")] +extern crate alloc; + +use aes_gcm::{AeadInPlace, Aes256Gcm}; +use chacha20poly1305::{ + aead::{generic_array::GenericArray, NewAead}, + ChaCha20Poly1305, +}; +#[cfg(feature = "std")] +use rand::rngs::ThreadRng; +use rand::{ + rngs::StdRng, + {RngCore, SeedableRng}, +}; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; +use core::marker::PhantomData; +#[cfg(feature = "std")] +use std::io::{Read, Write}; + +use format::FormatPrefix; +use header::{CocoonConfig, CocoonHeader}; + +// Enumeration is needed to avoid dynamic allocation (important for "nostd" build). +#[allow(clippy::large_enum_variant)] +enum RngVariant { + #[cfg(feature = "std")] + Thread(ThreadRng), + Std(StdRng), + None, +} + +pub use error::Error; +pub use header::{CocoonCipher, CocoonKdf}; + +/// Grouping creation methods via generics. +#[doc(hidden)] +pub struct Creation; + +/// Grouping parsing methods via generics. +#[doc(hidden)] +pub struct Parsing; + +/// The size of the cocoon prefix which appears in detached form in [`Cocoon::encrypt`]. +pub const PREFIX_SIZE: usize = FormatPrefix::SERIALIZE_SIZE; + +/// Re-export all MiniCocoon stuff. +pub use mini::*; + +/// Creates an encrypted container to hide your data inside of it using a user-supplied password. +/// +/// Every operation of [`Cocoon`] starts with an expensive key derivation from a password, +/// therefore prefer to use [`Cocoon`] to encrypt data at rest, and consider to use [`MiniCocoon`] +/// in order to wrap/encrypt/dump data often (e.g. in transit) withing a lightweight container +/// as a simple [`Vec`] (just wrap, wrap, wrap it!). +/// +/// # Basic Usage +/// ``` +/// # use cocoon::{Cocoon, Error}; +/// # +/// # fn main() -> Result<(), Error> { +/// let cocoon = Cocoon::new(b"password"); +/// # let cocoon = cocoon.with_weak_kdf(); // Speed up doc tests. +/// +/// let wrapped = cocoon.wrap(b"my secret data")?; +/// assert_ne!(&wrapped, b"my secret data"); +/// +/// let unwrapped = cocoon.unwrap(&wrapped)?; +/// assert_eq!(unwrapped, b"my secret data"); +/// +/// # Ok(()) +/// # } +/// ``` +/// +/// Scroll down to [Features and Methods Mapping](#features-and-methods-mapping), and also see +/// crate's documentation for more use cases. +/// +/// # Optimization +/// +/// Whenever a new container is created a new encryption key is generated from a supplied password +/// using Key Derivation Function (KDF). By default, PBKDF2 is used with 100 000 iterations of +/// SHA256. The reason for that is security: slower KDF - slower attacker brute-forces the password. +/// However, you may find it a bit _slow_ for debugging during _development_. If you experience +/// a slower runtime, try to use one of the two approaches to speed it up. +/// +/// ## Optimize Both `cocoon` And `sha2` +/// Add these lines to `Cargo.toml`: +/// ```toml +/// [profile.dev.package.cocoon] +/// opt-level = 3 +/// +/// [profile.dev.package.sha2] +/// opt-level = 3 +/// ``` +/// +/// ## Use Less KDF Iterations +/// You can configure [`Cocoon`] to use fewer iterations for KDF with [`Cocoon::with_weak_kdf`]. +/// Be careful, lower count of KDF iterations generate a _**weaker** encryption key_, therefore +/// try to use it in debug build only. +/// ``` +/// # use cocoon::Cocoon; +/// // Attention: don't use a weak password in real life! +/// let password = [1, 2, 3, 4, 5, 6]; +/// +/// let mut cocoon = if cfg!(debug_assertions) { +/// Cocoon::new(&password).with_weak_kdf() +/// } else { +/// Cocoon::new(&password) +/// }; +/// ``` +/// +/// # Using As a Struct Field +/// +/// Currently, [`Cocoon`] is not supposed to be used within the data types as a structure member. +/// [`Cocoon`] doesn't clone a password, instead, it uses a password reference and +/// shares its lifetime. Also, [`Cocoon`] uses generics to evade dynamic dispatching and +/// resolve variants at compile-time, so it makes its declaration in structures a little bit tricky. +/// A convenient way to declare [`Cocoon`] as a structure member _could be introduced_ once it's +/// needed by semantic, e.g. with introducing of KDF caching. +/// +/// # Default Configuration +/// | Option | Value | +/// |-----------------------------|--------------------------------| +/// | [Cipher](CocoonCipher) | Chacha20Poly1305 | +/// | [Key derivation](CocoonKdf) | PBKDF2 with 100 000 iterations | +/// | Random generator | [`ThreadRng`] | +/// +/// * Cipher can be customized using [`Cocoon::with_cipher`] method. +/// * Key derivation (KDF): only PBKDF2 is supported. +/// * Random generator: +/// - [`ThreadRng`] in `std` build. +/// - [`StdRng`] in "no std" build: use [`Cocoon::from_rng`] and other `from_*` methods. +/// - [`Cocoon::from_entropy`] functions. +/// +/// # Features and Methods Mapping +/// +/// _Note: This is a not complete list of API methods. Please, refer to the current +/// documentation below to get familiarized with the full set of methods._ +/// +/// | Method ↓ / Feature → | `std` | `alloc` | "no_std" | +/// |-----------------------------|:-----:|:-------:|:--------:| +/// | [`Cocoon::new`] | ✔️ | ❌ | ❌ | +/// | [`Cocoon::from_seed`] | ✔️ | ✔️ | ✔️ | +/// | [`Cocoon::from_entropy`] | ✔️[^1]| ✔️[^1] | ✔️[^1] | +/// | [`Cocoon::parse_only`][^2] | ✔️ | ✔️ | ✔️ | +/// | [`Cocoon::encrypt`] | ✔️ | ✔️ | ✔️ | +/// | [`Cocoon::decrypt`][^2] | ✔️ | ✔️ | ✔️ | +/// | [`Cocoon::wrap`] | ✔️ | ✔️ | ❌ | +/// | [`Cocoon::unwrap`][^2] | ✔️ | ✔️ | ❌ | +/// | [`Cocoon::dump`] | ✔️ | ❌ | ❌ | +/// | [`Cocoon::parse`][^2] | ✔️ | ❌ | ❌ | +/// +/// [^1]: [`from_entropy`](Cocoon:from_entropy) is enabled when `getrandom` feature is enabled. +/// +/// [^2]: [`parse_only`](Cocoon::parse_only) makes decryption API accessible only. +pub struct Cocoon<'a, M> { + password: &'a [u8], + rng: RngVariant, + config: CocoonConfig, + _methods_marker: PhantomData, +} + +#[cfg(feature = "std")] +#[cfg_attr(docs_rs, doc(cfg(feature = "std")))] +impl<'a> Cocoon<'a, Creation> { + /// Creates a new [`Cocoon`] with [`ThreadRng`] random generator under the hood + /// and a [Default Configuration](#default-configuration). + /// + /// * `password` - a shared reference to a password + /// + /// # Examples + /// ``` + /// use cocoon::Cocoon; + /// + /// let cocoon = Cocoon::new(b"my secret password"); + /// ``` + pub fn new(password: &'a [u8]) -> Self { + Cocoon { + password, + rng: RngVariant::Thread(ThreadRng::default()), + config: CocoonConfig::default(), + _methods_marker: PhantomData, + } + } +} + +impl<'a> Cocoon<'a, Creation> { + /// Creates a new [`Cocoon`] seeding a random generator using the given buffer. + /// + /// * `password` - a shared reference to a password + /// * `seed` - 32 bytes of a random seed obtained from an external RNG + /// + /// This method can be used when [`ThreadRng`] is not accessible with no [`std`]. + /// + /// # Examples + /// ``` + /// use cocoon::Cocoon; + /// use rand::Rng; + /// + /// // Seed can be obtained by any cryptographically secure random generator. + /// // ThreadRng is used just for example. + /// let seed = rand::thread_rng().gen::<[u8; 32]>(); + /// + /// let cocoon = Cocoon::from_seed(b"password", seed); + /// ``` + /// + /// **WARNING**: Use this method carefully, don't feed it with a static seed unless testing! + /// See [`SeedableRng::from_seed`], which is under the hood, for more details. + pub fn from_seed(password: &'a [u8], seed: [u8; 32]) -> Self { + Cocoon { + password, + rng: RngVariant::Std(StdRng::from_seed(seed)), + config: CocoonConfig::default(), + _methods_marker: PhantomData, + } + } + + /// Creates a new [`Cocoon`] applying a third party random generator. + /// + /// * `password` - a shared reference to a password + /// * `rng` - a source of random bytes + /// + /// This method can be used when [`ThreadRng`] is not accessible in build with no [`std`]. + /// + /// # Examples + /// ``` + /// use cocoon::Cocoon; + /// use rand; + /// + /// # // [`ThreadRng`] is used here just as an example. It is supposed to apply some other + /// # // cryptographically secure RNG when [`ThreadRng`] is not accessible. + /// # let mut good_rng = rand::rngs::ThreadRng::default(); + /// let cocoon = Cocoon::from_rng(b"password", good_rng).unwrap(); + /// ``` + pub fn from_rng(password: &'a [u8], rng: R) -> Result { + Ok(Cocoon { + password, + rng: RngVariant::Std(StdRng::from_rng(rng)?), + config: CocoonConfig::default(), + _methods_marker: PhantomData, + }) + } + + /// Creates a new [`Cocoon`] with OS random generator using `getrandom` crate via + /// [`SeedableRng::from_entropy`]. + /// + /// * `password` - a shared reference to a password + /// + /// The method can be used to create [`Cocoon`] when [`ThreadRng`] is not accessible + /// in build with no [`std`]. + /// + /// # Examples + /// ``` + /// use cocoon::Cocoon; + /// + /// let cocoon = Cocoon::from_entropy(b"password"); + /// ``` + #[cfg(any(feature = "getrandom", test))] + #[cfg_attr(docs_rs, doc(cfg(feature = "getrandom")))] + pub fn from_entropy(password: &'a [u8]) -> Self { + Cocoon { + password, + rng: RngVariant::Std(StdRng::from_entropy()), + config: CocoonConfig::default(), + _methods_marker: PhantomData, + } + } +} + +impl<'a> Cocoon<'a, Parsing> { + /// Creates a [`Cocoon`] instance allowing to only decrypt a container. It makes only decryption + /// methods accessible at compile-time: [`Cocoon::unwrap`], [`Cocoon::parse`] and + /// [`Cocoon::decrypt`]. + /// + /// * `password` - a shared reference to a password + /// + /// All encryption methods need a cryptographic random generator to generate a salt and a nonce, + /// at the same time the random generator is not needed for parsing. + /// + /// The [`wrap`](Cocoon::wrap)/[`encrypt`](Cocoon::encrypt)/[`dump`](Cocoon::dump) methods are + /// **not** accessible _at compile-time_ when [`Cocoon::parse_only`] is used. Therefore the + /// compilation of the following code snippet fails. + /// ```compile_fail + /// use cocoon::Cocoon; + /// + /// let cocoon = Cocoon::parse_only(b"password"); + /// + /// // The compilation process fails here denying to use any encryption method. + /// cocoon.wrap(b"my data"); + /// ``` + /// + /// Meanwhile, decryption methods are accessible. + /// ``` + /// use cocoon::{Cocoon, Error}; + /// + /// # fn main() -> Result<(), Error> { + /// let cocoon = Cocoon::parse_only(b"password"); + /// + /// # let mut data = [ + /// # 244, 85, 222, 144, 119, 169, 144, 11, 178, 216, 4, 57, 17, 47, 0, + /// # ]; + /// # let detached_prefix = [ + /// # 127, 192, 10, 1, 1, 1, 1, 0, 118, 184, 224, 173, 160, 241, 61, 144, 64, 93, 106, 229, + /// # 83, 134, 189, 40, 189, 210, 25, 184, 160, 141, 237, 26, 168, 54, 239, 204, 0, 0, 0, 0, + /// # 0, 0, 0, 14, 53, 9, 86, 247, 53, 186, 123, 217, 156, 132, 173, 200, 208, 134, 179, 12, + /// # ]; + /// # + /// cocoon.decrypt(&mut data, &detached_prefix)?; + /// # + /// # Ok(()) + /// # } + /// ``` + pub fn parse_only(password: &'a [u8]) -> Self { + Cocoon { + password, + rng: RngVariant::None, + config: CocoonConfig::default(), + _methods_marker: PhantomData, + } + } +} + +// Wrapping/encryption methods are accessible only when random generator is accessible. +impl<'a> Cocoon<'a, Creation> { + /// Sets an encryption algorithm to wrap data on. + /// + /// # Examples + /// ``` + /// use cocoon::{Cocoon, CocoonCipher}; + /// + /// let cocoon = Cocoon::new(b"password").with_cipher(CocoonCipher::Aes256Gcm); + /// cocoon.wrap(b"my secret data"); + /// ``` + pub fn with_cipher(mut self, cipher: CocoonCipher) -> Self { + self.config = self.config.with_cipher(cipher); + self + } + + /// Reduces the number of iterations for key derivation function (KDF). + /// + /// ⚠️ This modifier could be used for testing in debug mode, and it should not be used + /// in production and release builds. + /// + /// # Examples + /// ``` + /// use cocoon::Cocoon; + /// + /// let cocoon = Cocoon::new(b"password").with_weak_kdf(); + /// cocoon.wrap(b"my secret data").expect("New container"); + /// ``` + pub fn with_weak_kdf(mut self) -> Self { + self.config = self.config.with_weak_kdf(); + self + } + + /// Wraps data to an encrypted container. + /// + /// * `data` - a sensitive user data + /// + /// Examples: + /// ``` + /// # use cocoon::{Cocoon, Error}; + /// # + /// # fn main() -> Result<(), Error> { + /// let cocoon = Cocoon::new(b"password"); + /// # let cocoon = cocoon.with_weak_kdf(); // Speed up doc tests. + /// + /// let wrapped = cocoon.wrap(b"my secret data")?; + /// assert_ne!(&wrapped, b"my secret data"); + /// + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "alloc")] + #[cfg_attr(docs_rs, doc(cfg(any(feature = "alloc", feature = "std"))))] + pub fn wrap(&self, data: &[u8]) -> Result, Error> { + // Allocation is needed because there is no way to prefix encrypted + // data with a header without an allocation. It means that we need + // to copy data at least once. It's necessary to avoid any further copying. + let mut container = Vec::with_capacity(PREFIX_SIZE + data.len()); + container.extend_from_slice(&[0; PREFIX_SIZE]); + container.extend_from_slice(data); + + let body = &mut container[PREFIX_SIZE..]; + + // Encrypt in place and get a prefix part. + let detached_prefix = self.encrypt(body)?; + + container[..PREFIX_SIZE].copy_from_slice(&detached_prefix); + + Ok(container) + } + + /// Encrypts data in place, taking ownership over the buffer, and dumps the container + /// into [`File`](std::fs::File), [`Cursor`](std::io::Cursor), or any other writer. + /// * `data` - a sensitive data inside of [`Vec`] to be encrypted in place + /// * `writer` - [`File`](std::fs::File), [`Cursor`](`std::io::Cursor`), or any other output + /// + /// A data is going to be encrypted in place and stored in a file using the "cocoon" + /// [format](#format). + /// + /// # Examples + /// ``` + /// # use cocoon::{Cocoon, Error}; + /// # use std::io::Cursor; + /// # + /// # fn main() -> Result<(), Error> { + /// let mut data = b"my secret data".to_vec(); + /// let cocoon = Cocoon::new(b"password"); + /// # let cocoon = cocoon.with_weak_kdf(); // Speed up doc tests. + /// # let mut file = Cursor::new(vec![0; 150]); + /// + /// cocoon.dump(data, &mut file)?; + /// # assert_ne!(file.get_ref(), b"my secret data"); + /// + /// # Ok(()) + /// # } + #[cfg(feature = "std")] + #[cfg_attr(docs_rs, doc(cfg(feature = "std")))] + pub fn dump(&self, mut data: Vec, writer: &mut impl Write) -> Result<(), Error> { + let detached_prefix = self.encrypt(&mut data)?; + + writer.write_all(&detached_prefix)?; + writer.write_all(&data)?; + + Ok(()) + } + + /// Encrypts data in place and returns a detached prefix of the container. + /// + /// The prefix is needed to decrypt data with [`Cocoon::decrypt`]. + /// This method doesn't use memory allocation and it is suitable in the build + /// with no [`std`] and no [`alloc`]. + /// + /// + /// + /// # Examples + /// ``` + /// # use cocoon::{Cocoon, Error}; + /// # + /// # fn main() -> Result<(), Error> { + /// # // [`ThreadRng`] is used here just as an example. It is supposed to apply some other + /// # // cryptographically secure RNG when [`ThreadRng`] is not accessible. + /// # let mut good_rng = rand::rngs::ThreadRng::default(); + /// let mut data = "my secret data".to_owned().into_bytes(); + /// let cocoon = Cocoon::from_rng(b"password", good_rng).unwrap(); + /// # let cocoon = cocoon.with_weak_kdf(); // Speed up doc tests. + /// + /// let detached_prefix = cocoon.encrypt(&mut data)?; + /// assert_ne!(data, b"my secret data"); + /// # Ok(()) + /// # } + /// ``` + pub fn encrypt(&self, data: &mut [u8]) -> Result<[u8; PREFIX_SIZE], Error> { + let mut salt = [0u8; 16]; + let mut nonce = [0u8; 12]; + + match &self.rng { + #[cfg(feature = "std")] + RngVariant::Thread(rng) => { + let mut rng = rng.clone(); + rng.fill_bytes(&mut salt); + rng.fill_bytes(&mut nonce); + } + RngVariant::Std(rng) => { + let mut rng = rng.clone(); + rng.fill_bytes(&mut salt); + rng.fill_bytes(&mut nonce); + } + RngVariant::None => unreachable!(), + } + + let header = CocoonHeader::new(self.config.clone(), salt, nonce, data.len()); + let prefix = FormatPrefix::new(header); + + let master_key = match self.config.kdf() { + CocoonKdf::Pbkdf2 => { + kdf::pbkdf2::derive(&salt, self.password, self.config.kdf_iterations()) + } + }; + + let nonce = GenericArray::from_slice(&nonce); + let master_key = GenericArray::clone_from_slice(master_key.as_ref()); + + let tag: [u8; 16] = match self.config.cipher() { + CocoonCipher::Chacha20Poly1305 => { + let cipher = ChaCha20Poly1305::new(&master_key); + cipher.encrypt_in_place_detached(nonce, &prefix.prefix(), data) + } + CocoonCipher::Aes256Gcm => { + let cipher = Aes256Gcm::new(&master_key); + cipher.encrypt_in_place_detached(nonce, &prefix.prefix(), data) + } + } + .map_err(|_| Error::Cryptography)? + .into(); + + Ok(prefix.serialize(&tag)) + } +} + +/// Parsing methods are always accessible. They don't need random generator in general. +impl<'a, M> Cocoon<'a, M> { + /// Unwraps data from the encrypted container (see [`Cocoon::wrap`]). + /// + /// # Examples + /// ``` + /// # use cocoon::{Cocoon, Error}; + /// # + /// # fn main() -> Result<(), Error> { + /// let cocoon = Cocoon::new(b"password"); + /// # let cocoon = cocoon.with_weak_kdf(); // Speed up doc tests. + /// + /// # let wrapped = cocoon.wrap(b"my secret data")?; + /// # assert_ne!(&wrapped, b"my secret data"); + /// # + /// let unwrapped = cocoon.unwrap(&wrapped)?; + /// assert_eq!(unwrapped, b"my secret data"); + /// + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "alloc")] + #[cfg_attr(docs_rs, doc(cfg(any(feature = "alloc", feature = "std"))))] + pub fn unwrap(&self, container: &[u8]) -> Result, Error> { + let prefix = FormatPrefix::deserialize(container)?; + let header = prefix.header(); + + if container.len() < prefix.size() + header.data_length() { + return Err(Error::TooShort); + } + + let mut body = Vec::with_capacity(header.data_length()); + body.extend_from_slice(&container[prefix.size()..prefix.size() + body.capacity()]); + + self.decrypt_parsed(&mut body, &prefix)?; + + Ok(body) + } + + /// Parses container from the reader (file, cursor, etc.), validates format, + /// allocates memory and places decrypted data there. + /// + /// * `reader` - [`File`](std::fs::File), [`Cursor`](`std::io::Cursor`), or any other input + /// + /// # Examples + /// ``` + /// # use cocoon::{Cocoon, Error}; + /// # use std::io::Cursor; + /// # + /// # fn main() -> Result<(), Error> { + /// let mut data = b"my secret data".to_vec(); + /// let cocoon = Cocoon::new(b"password"); + /// # let cocoon = cocoon.with_weak_kdf(); // Speed up doc tests. + /// # let mut file = Cursor::new(vec![0; 150]); + /// + /// # cocoon.dump(data, &mut file)?; + /// # assert_ne!(file.get_ref(), b"my secret data"); + /// # + /// # file.set_position(0); + /// # + /// let data = cocoon.parse(&mut file)?; + /// assert_eq!(&data, b"my secret data"); + /// + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "std")] + #[cfg_attr(docs_rs, doc(cfg(feature = "std")))] + pub fn parse(&self, reader: &mut impl Read) -> Result, Error> { + let prefix = FormatPrefix::deserialize_from(reader)?; + let mut body = Vec::with_capacity(prefix.header().data_length()); + body.resize(body.capacity(), 0); + + // Too short error can be thrown right from here. + reader.read_exact(&mut body)?; + + self.decrypt_parsed(&mut body, &prefix)?; + + Ok(body) + } + + /// Decrypts data in place using the parts returned by [`Cocoon::encrypt`] method. + /// + /// The method doesn't use memory allocation and is suitable for "no std" and "no alloc" build. + /// + /// # Examples + /// ``` + /// # use cocoon::{Cocoon, Error}; + /// # + /// # fn main() -> Result<(), Error> { + /// # // [`ThreadRng`] is used here just as an example. It is supposed to apply some other + /// # // cryptographically secure RNG when [`ThreadRng`] is not accessible. + /// # let mut good_rng = rand::rngs::ThreadRng::default(); + /// let mut data = "my secret data".to_owned().into_bytes(); + /// let cocoon = Cocoon::from_rng(b"password", good_rng).unwrap(); + /// # let cocoon = cocoon.with_weak_kdf(); // Speed up doc tests. + /// + /// let detached_prefix = cocoon.encrypt(&mut data)?; + /// assert_ne!(data, b"my secret data"); + /// + /// cocoon.decrypt(&mut data, &detached_prefix)?; + /// assert_eq!(data, b"my secret data"); + /// # + /// # Ok(()) + /// # } + /// ``` + pub fn decrypt(&self, data: &mut [u8], detached_prefix: &[u8]) -> Result<(), Error> { + let prefix = FormatPrefix::deserialize(detached_prefix)?; + + self.decrypt_parsed(data, &prefix) + } + + fn decrypt_parsed(&self, data: &mut [u8], detached_prefix: &FormatPrefix) -> Result<(), Error> { + let mut salt = [0u8; 16]; + let mut nonce = [0u8; 12]; + + let header = detached_prefix.header(); + + if data.len() < header.data_length() { + return Err(Error::TooShort); + } + + let data = &mut data[..header.data_length()]; + + salt.copy_from_slice(header.salt()); + nonce.copy_from_slice(header.nonce()); + + let master_key = match header.config().kdf() { + CocoonKdf::Pbkdf2 => { + kdf::pbkdf2::derive(&salt, self.password, header.config().kdf_iterations()) + } + }; + + let nonce = GenericArray::from_slice(&nonce); + let master_key = GenericArray::clone_from_slice(master_key.as_ref()); + let tag = GenericArray::from_slice(&detached_prefix.tag()); + + match header.config().cipher() { + CocoonCipher::Chacha20Poly1305 => { + let cipher = ChaCha20Poly1305::new(&master_key); + cipher.decrypt_in_place_detached(nonce, &detached_prefix.prefix(), data, tag) + } + CocoonCipher::Aes256Gcm => { + let cipher = Aes256Gcm::new(&master_key); + cipher.decrypt_in_place_detached(nonce, &detached_prefix.prefix(), data, tag) + } + } + .map_err(|_| Error::Cryptography)?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use std::fs::File; + use std::io::Cursor; + + use super::*; + + #[test] + fn cocoon_create() { + Cocoon::new(b"password").with_cipher(CocoonCipher::Aes256Gcm); + Cocoon::from_seed(b"another password", [0; 32]).with_weak_kdf(); + Cocoon::from_entropy(b"new password"); + Cocoon::from_rng(b"password", rand::thread_rng()).unwrap(); + Cocoon::parse_only(b"password"); + } + + #[test] + fn cocoon_encrypt() { + let cocoon = Cocoon::from_seed(b"password", [0; 32]).with_weak_kdf(); + let mut data = "my secret data".to_owned().into_bytes(); + + let detached_prefix = cocoon.encrypt(&mut data).unwrap(); + + assert_eq!( + &[ + 127, 192, 10, 1, 1, 1, 2, 0, 155, 244, 154, 106, 7, 85, 249, 83, 129, 31, 206, 18, + 95, 38, 131, 213, 4, 41, 195, 187, 73, 224, 116, 20, 126, 0, 137, 165, 0, 0, 0, 0, + 0, 0, 0, 14, 114, 102, 232, 234, 188, 49, 190, 30, 41, 136, 238, 190, 46, 182, 211, + 244 + ][..], + &detached_prefix[..] + ); + + assert_eq!( + &[186, 240, 214, 29, 4, 147, 205, 72, 210, 7, 167, 234, 199, 53], + &data[..] + ); + } + + #[test] + fn cocoon_encrypt_aes() { + let cocoon = Cocoon::from_seed(b"password", [0; 32]) + .with_weak_kdf() + .with_cipher(CocoonCipher::Aes256Gcm); + let mut data = "my secret data".to_owned().into_bytes(); + + let detached_prefix = cocoon.encrypt(&mut data).unwrap(); + + assert_eq!( + &[ + 127, 192, 10, 1, 2, 1, 2, 0, 155, 244, 154, 106, 7, 85, 249, 83, 129, 31, 206, 18, + 95, 38, 131, 213, 4, 41, 195, 187, 73, 224, 116, 20, 126, 0, 137, 165, 0, 0, 0, 0, + 0, 0, 0, 14, 103, 127, 175, 154, 15, 80, 248, 145, 128, 241, 138, 15, 154, 128, + 201, 157 + ][..], + &detached_prefix[..] + ); + + assert_eq!( + &[88, 183, 11, 7, 192, 224, 203, 107, 144, 162, 48, 78, 61, 223], + &data[..] + ); + } + + #[test] + fn cocoon_decrypt() { + let detached_prefix = [ + 127, 192, 10, 1, 1, 1, 1, 0, 118, 184, 224, 173, 160, 241, 61, 144, 64, 93, 106, 229, + 83, 134, 189, 40, 189, 210, 25, 184, 160, 141, 237, 26, 168, 54, 239, 204, 0, 0, 0, 0, + 0, 0, 0, 14, 53, 9, 86, 247, 53, 186, 123, 217, 156, 132, 173, 200, 208, 134, 179, 12, + ]; + let mut data = [ + 244, 85, 222, 144, 119, 169, 144, 11, 178, 216, 4, 57, 17, 47, + ]; + let cocoon = Cocoon::parse_only(b"password"); + + cocoon + .decrypt(&mut data, &detached_prefix) + .expect("Decrypted data"); + + assert_eq!(b"my secret data", &data); + } + + #[test] + fn cocoon_wrap() { + let cocoon = Cocoon::from_seed(b"password", [0; 32]); + let wrapped = cocoon.wrap(b"data").expect("Wrapped container"); + + assert_eq!(wrapped[wrapped.len() - 4..], [27, 107, 178, 181]); + } + + #[test] + fn cocoon_wrap_unwrap() { + let cocoon = Cocoon::from_seed(b"password", [0; 32]); + let wrapped = cocoon.wrap(b"data").expect("Wrapped container"); + let original = cocoon.unwrap(&wrapped).expect("Unwrapped container"); + + assert_eq!(original, b"data"); + } + + #[test] + fn cocoon_wrap_unwrap_corrupted() { + let cocoon = Cocoon::from_seed(b"password", [0; 32]); + let mut wrapped = cocoon.wrap(b"data").expect("Wrapped container"); + + let last = wrapped.len() - 1; + wrapped[last] = wrapped[last] + 1; + cocoon.unwrap(&wrapped).expect_err("Unwrapped container"); + } + + #[test] + fn cocoon_unwrap_larger_is_ok() { + let cocoon = Cocoon::from_seed(b"password", [0; 32]); + let mut wrapped = cocoon.wrap(b"data").expect("Wrapped container"); + + wrapped.push(0); + let original = cocoon.unwrap(&wrapped).expect("Unwrapped container"); + + assert_eq!(original, b"data"); + } + + #[test] + fn cocoon_unwrap_too_short() { + let cocoon = Cocoon::from_seed(b"password", [0; 32]); + let mut wrapped = cocoon.wrap(b"data").expect("Wrapped container"); + + wrapped.pop(); + cocoon.unwrap(&wrapped).expect_err("Too short"); + } + + #[test] + fn cocoon_decrypt_wrong_sizes() { + let detached_prefix = [ + 127, 192, 10, 1, 1, 1, 1, 0, 118, 184, 224, 173, 160, 241, 61, 144, 64, 93, 106, 229, + 83, 134, 189, 40, 189, 210, 25, 184, 160, 141, 237, 26, 168, 54, 239, 204, 0, 0, 0, 0, + 0, 0, 0, 14, 53, 9, 86, 247, 53, 186, 123, 217, 156, 132, 173, 200, 208, 134, 179, 12, + ]; + let mut data = [ + 244, 85, 222, 144, 119, 169, 144, 11, 178, 216, 4, 57, 17, 47, 0, + ]; + let cocoon = Cocoon::parse_only(b"password"); + + cocoon + .decrypt(&mut data, &detached_prefix) + .expect("Decrypted data"); + + assert_eq!(b"my secret data\0", &data); + + cocoon + .decrypt(&mut data[..4], &detached_prefix) + .expect_err("Too short"); + } + + #[test] + fn cocoon_dump_parse() { + let buf = vec![0; 100]; + let mut file = Cursor::new(buf); + let cocoon = Cocoon::from_seed(b"password", [0; 32]).with_weak_kdf(); + + // Prepare data inside of `Vec` container. + let data = b"my data".to_vec(); + + cocoon.dump(data, &mut file).expect("Dumped container"); + assert_ne!(b"my data", file.get_ref().as_slice()); + + // "Re-open" the file. + file.set_position(0); + + let original = cocoon.parse(&mut file).expect("Parsed container"); + assert_eq!(b"my data", original.as_slice()); + } + + #[test] + fn cocoon_dump_io_error() { + File::create("target/read_only.txt").expect("Test file"); + let mut file = File::open("target/read_only.txt").expect("Test file"); + + let cocoon = Cocoon::from_seed(b"password", [0; 32]).with_weak_kdf(); + + // Prepare data inside of `Vec` container. + let data = b"my data".to_vec(); + + match cocoon.dump(data, &mut file) { + Err(e) => match e { + Error::Io(_) => (), + _ => panic!("Only unexpected I/O error is expected :)"), + }, + _ => panic!("Success is not expected"), + } + } + + #[test] + fn cocoon_parse_io_error() { + File::create("target/read_only.txt").expect("Test file"); + let mut file = File::open("target/read_only.txt").expect("Test file"); + + let cocoon = Cocoon::from_seed(b"password", [0; 32]).with_weak_kdf(); + + match cocoon.parse(&mut file) { + Err(e) => match e { + Error::TooShort => (), + _ => panic!("TooShort is expected for an empty file"), + }, + _ => panic!("Success is not expected"), + } + } +} diff --git a/forks/cocoon/src/mini.rs b/forks/cocoon/src/mini.rs new file mode 100644 index 000000000..681768b25 --- /dev/null +++ b/forks/cocoon/src/mini.rs @@ -0,0 +1,658 @@ +use aes_gcm::{ + aead::{generic_array::GenericArray, NewAead}, + AeadInPlace, Aes256Gcm, +}; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; +use chacha20poly1305::ChaCha20Poly1305; +use rand::{rngs::StdRng, RngCore, SeedableRng}; +#[cfg(feature = "std")] +use std::io::{Read, Write}; +use zeroize::Zeroizing; + +use super::{ + error::Error, + format::MiniFormatPrefix, + header::{CocoonCipher, CocoonConfig, CocoonKdf, MiniCocoonHeader}, + kdf::{self, KEY_SIZE}, +}; + +/// The size of the cocoon prefix which appears in detached form in [`MiniCocoon::encrypt`]. +pub const MINI_PREFIX_SIZE: usize = MiniFormatPrefix::SERIALIZE_SIZE; + +/// This is a mini cocoon for a convenient and cool encryption. +pub struct MiniCocoon { + key: Zeroizing<[u8; KEY_SIZE]>, + rng: StdRng, + config: CocoonConfig, +} + +/// Stores data securely inside of a simple encrypted container ("mini cocoon"). +/// +/// # Basic Usage +/// ``` +/// # use cocoon::{MiniCocoon, Error}; +/// # +/// # fn main() -> Result<(), Error> { +/// let cocoon = MiniCocoon::from_key(b"0123456789abcdef0123456789abcdef", &[0; 32]); +/// +/// let wrapped = cocoon.wrap(b"my secret data")?; +/// assert_ne!(&wrapped, b"my secret data"); +/// +/// let unwrapped = cocoon.unwrap(&wrapped)?; +/// assert_eq!(unwrapped, b"my secret data"); +/// +/// # Ok(()) +/// # } +/// ``` +/// +/// Scroll down to [Features and Methods Mapping](#features-and-methods-mapping), and also see +/// crate's documentation for more use cases. +/// +/// # Default Configuration +/// | Option | Value | +/// |-----------------------------|--------------------------------| +/// | [Cipher](CocoonCipher) | Chacha20Poly1305 | +/// | [Key derivation](CocoonKdf) | PBKDF2 with 100 000 iterations | +/// +/// * Cipher can be customized using [`MiniCocoon::with_cipher`] method. +/// * Key derivation (KDF): only PBKDF2 is supported. +/// +/// # Features and Methods Mapping +/// +/// _Note: It is maybe not a complete list of API methods. Please, refer to the current +/// documentation below to get familiarized with the full set of methods._ +/// +/// | Method ↓ / Feature → | `std` | `alloc` | "no_std" | +/// |-------------------------------|:-----:|:-------:|:--------:| +/// | [`MiniCocoon::from_key`] | ✔️ | ✔️ | ✔️ | +/// | [`MiniCocoon::from_password`] | ✔️ | ✔️ | ✔️ | +/// | [`MiniCocoon::encrypt`] | ✔️ | ✔️ | ✔️ | +/// | [`MiniCocoon::decrypt`] | ✔️ | ✔️ | ✔️ | +/// | [`MiniCocoon::wrap`] | ✔️ | ✔️ | ❌ | +/// | [`MiniCocoon::unwrap`] | ✔️ | ✔️ | ❌ | +/// | [`MiniCocoon::dump`] | ✔️ | ❌ | ❌ | +/// | [`MiniCocoon::parse`] | ✔️ | ❌ | ❌ | +impl MiniCocoon { + /// Creates a new [`MiniCocoon`] with a symmetric key seeding a random generator + /// using a given `seed` buffer. + /// + /// * `key` - a symmetric key of length 32 + /// * `seed` - 32 random bytes to initialize an internal random generator + /// + /// # Examples + /// ``` + /// use cocoon::MiniCocoon; + /// use rand::Rng; + /// + /// // Seed can be obtained by any cryptographically secure random generator. + /// // ThreadRng is used as an example. + /// let seed = rand::thread_rng().gen::<[u8; 32]>(); + /// + /// // Key must be 32 bytes of length. Let it be another 32 random bytes. + /// let key = rand::thread_rng().gen::<[u8; 32]>(); + /// + /// let cocoon = MiniCocoon::from_key(&key, &seed); + /// ``` + pub fn from_key(key: &[u8], seed: &[u8]) -> Self { + let mut k = [0u8; KEY_SIZE]; + let mut s = [0u8; KEY_SIZE]; + + k.copy_from_slice(key); + s.copy_from_slice(seed); + + let key = Zeroizing::new(k); + let rng = StdRng::from_seed(s); + + MiniCocoon { + key, + rng, + config: CocoonConfig::default(), + } + } + + /// Creates a new [`MiniCocoon`] with a password. Under the hood, an encryption key is created + /// from the password using PBKDF2 algorithm. + /// + /// * `password` - a password of any length + /// * `seed` - 32 random bytes to initialize an internal random generator + /// + /// # Examples + /// ``` + /// use cocoon::MiniCocoon; + /// use rand::Rng; + /// + /// // Seed can be obtained by any cryptographically secure random generator. + /// // ThreadRng is used as an example. + /// let seed = rand::thread_rng().gen::<[u8; 32]>(); + /// + /// let cocoon = MiniCocoon::from_password(b"my password", &seed); + /// ``` + pub fn from_password(password: &[u8], seed: &[u8]) -> Self { + let config = CocoonConfig::default(); + let key = match config.kdf() { + CocoonKdf::Pbkdf2 => kdf::pbkdf2::derive(&seed, password, config.kdf_iterations()), + }; + + let mut s = [0u8; KEY_SIZE]; + s.copy_from_slice(seed); + + let rng = StdRng::from_seed(s); + + MiniCocoon { key, rng, config } + } + + /// Sets an encryption algorithm to wrap data on. + /// + /// # Examples + /// ``` + /// use cocoon::{MiniCocoon, CocoonCipher}; + /// use rand::Rng; + /// + /// let seed = rand::thread_rng().gen::<[u8; 32]>(); + /// let key = rand::thread_rng().gen::<[u8; 32]>(); + /// + /// let cocoon = MiniCocoon::from_key(&key, &seed).with_cipher(CocoonCipher::Chacha20Poly1305); + /// cocoon.wrap(b"my secret data"); + /// ``` + pub fn with_cipher(mut self, cipher: CocoonCipher) -> Self { + self.config = self.config.with_cipher(cipher); + self + } + + /// Wraps data to an encrypted container. + /// + /// * `data` - a sensitive user data + /// + /// Examples: + /// ``` + /// # use cocoon::{MiniCocoon, Error}; + /// # use rand::Rng; + /// # + /// # fn main() -> Result<(), Error> { + /// let seed = rand::thread_rng().gen::<[u8; 32]>(); + /// let cocoon = MiniCocoon::from_password(b"password", &seed); + /// + /// let wrapped = cocoon.wrap(b"my secret data")?; + /// assert_ne!(&wrapped, b"my secret data"); + /// + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "alloc")] + #[cfg_attr(docs_rs, doc(cfg(any(feature = "alloc", feature = "std"))))] + pub fn wrap(&self, data: &[u8]) -> Result, Error> { + // Allocation is needed because there is no way to prefix encrypted + // data with a header without an allocation. It means that we need + // to copy data at least once. It's necessary to avoid any further copying. + let mut container = Vec::with_capacity(MINI_PREFIX_SIZE + data.len()); + container.extend_from_slice(&[0; MINI_PREFIX_SIZE]); + container.extend_from_slice(data); + + let body = &mut container[MINI_PREFIX_SIZE..]; + + // Encrypt in place and get a prefix part. + let detached_prefix = self.encrypt(body)?; + + container[..MINI_PREFIX_SIZE].copy_from_slice(&detached_prefix); + + Ok(container) + } + + /// Encrypts data in place, taking ownership over the buffer, and dumps the container + /// into [`File`](std::fs::File), [`Cursor`](std::io::Cursor), or any other writer. + /// * `data` - a sensitive data inside of [`Vec`] to be encrypted in place + /// * `writer` - [`File`](std::fs::File), [`Cursor`](`std::io::Cursor`), or any other output + /// + /// A data is going to be encrypted in place and stored in a file using the "mini cocoon" + /// [format](#format). + /// + /// # Examples + /// ``` + /// # use cocoon::{MiniCocoon, Error}; + /// # use rand::Rng; + /// # use std::io::Cursor; + /// # + /// # fn main() -> Result<(), Error> { + /// let key = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + /// 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]; + /// let seed = rand::thread_rng().gen::<[u8; 32]>(); + /// + /// let cocoon = MiniCocoon::from_key(&key, &seed); + /// # let mut file = Cursor::new(vec![0; 150]); + /// + /// let mut data = b"my secret data".to_vec(); + /// + /// cocoon.dump(data, &mut file)?; + /// # assert_ne!(file.get_ref(), b"my secret data"); + /// + /// # Ok(()) + /// # } + #[cfg(feature = "std")] + #[cfg_attr(docs_rs, doc(cfg(feature = "std")))] + pub fn dump(&self, mut data: Vec, writer: &mut impl Write) -> Result<(), Error> { + let detached_prefix = self.encrypt(&mut data)?; + + writer.write_all(&detached_prefix)?; + writer.write_all(&data)?; + + Ok(()) + } + + /// Encrypts data in place and returns a detached prefix of the container. + /// + /// The prefix is needed to decrypt data with [`MiniCocoon::decrypt`]. + /// This method doesn't use memory allocation and it is suitable in the build + /// with no [`std`] and no [`alloc`]. + /// + /// + /// + /// # Examples + /// ``` + /// # use cocoon::{MiniCocoon, Error}; + /// # + /// # fn main() -> Result<(), Error> { + /// let cocoon = MiniCocoon::from_password(b"password", &[1; 32]); + /// + /// let mut data = "my secret data".to_owned().into_bytes(); + /// + /// let detached_prefix = cocoon.encrypt(&mut data)?; + /// assert_ne!(data, b"my secret data"); + /// # Ok(()) + /// # } + /// ``` + pub fn encrypt(&self, data: &mut [u8]) -> Result<[u8; MINI_PREFIX_SIZE], Error> { + let mut rng = self.rng.clone(); + + let mut nonce = [0u8; 12]; + rng.fill_bytes(&mut nonce); + + let header = MiniCocoonHeader::new(nonce, data.len()); + let prefix = MiniFormatPrefix::new(header); + + let nonce = GenericArray::from_slice(&nonce); + let key = GenericArray::clone_from_slice(self.key.as_ref()); + + let tag: [u8; 16] = match self.config.cipher() { + CocoonCipher::Chacha20Poly1305 => { + let cipher = ChaCha20Poly1305::new(&key); + cipher.encrypt_in_place_detached(nonce, &prefix.prefix(), data) + } + CocoonCipher::Aes256Gcm => { + let cipher = Aes256Gcm::new(&key); + cipher.encrypt_in_place_detached(nonce, &prefix.prefix(), data) + } + } + .map_err(|_| Error::Cryptography)? + .into(); + + Ok(prefix.serialize(&tag)) + } + + /// Unwraps data from the encrypted container (see [`MiniCocoon::wrap`]). + /// + /// # Examples + /// ``` + /// # use cocoon::{MiniCocoon, Error}; + /// # use rand::Rng; + /// # + /// # fn main() -> Result<(), Error> { + /// let key = b"0123456789abcdef0123456789abcdef"; + /// let seed = rand::thread_rng().gen::<[u8; 32]>(); + /// + /// let cocoon = MiniCocoon::from_key(key, &seed); + /// + /// # let wrapped = cocoon.wrap(b"my secret data")?; + /// # assert_ne!(&wrapped, b"my secret data"); + /// # + /// let unwrapped = cocoon.unwrap(&wrapped)?; + /// assert_eq!(unwrapped, b"my secret data"); + /// + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "alloc")] + #[cfg_attr(docs_rs, doc(cfg(any(feature = "alloc", feature = "std"))))] + pub fn unwrap(&self, container: &[u8]) -> Result, Error> { + let prefix = MiniFormatPrefix::deserialize(container)?; + let header = prefix.header(); + + if container.len() < MINI_PREFIX_SIZE + header.data_length() { + return Err(Error::TooShort); + } + + let mut body = Vec::with_capacity(header.data_length()); + body.extend_from_slice(&container[MINI_PREFIX_SIZE..MINI_PREFIX_SIZE + body.capacity()]); + + self.decrypt_parsed(&mut body, &prefix)?; + + Ok(body) + } + + /// Parses container from the reader (file, cursor, etc.), validates format, + /// allocates memory and places decrypted data there. + /// + /// * `reader` - [`File`](std::fs::File), [`Cursor`](`std::io::Cursor`), or any other input + /// + /// # Examples + /// ``` + /// # use cocoon::{MiniCocoon, Error}; + /// # use rand::Rng; + /// # use std::io::Cursor; + /// # + /// # fn main() -> Result<(), Error> { + /// let key = b"0123456789abcdef0123456789abcdef"; + /// let seed = rand::thread_rng().gen::<[u8; 32]>(); + /// + /// let cocoon = MiniCocoon::from_key(key, &seed); + /// # let mut file = Cursor::new(vec![0; 150]); + /// # + /// # let mut data = b"my secret data".to_vec(); + /// # + /// # cocoon.dump(data, &mut file)?; + /// # assert_ne!(file.get_ref(), b"my secret data"); + /// # + /// # file.set_position(0); + /// + /// let data = cocoon.parse(&mut file)?; + /// assert_eq!(&data, b"my secret data"); + /// + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "std")] + #[cfg_attr(docs_rs, doc(cfg(feature = "std")))] + pub fn parse(&self, reader: &mut impl Read) -> Result, Error> { + let prefix = MiniFormatPrefix::deserialize_from(reader)?; + let mut body = Vec::with_capacity(prefix.header().data_length()); + body.resize(body.capacity(), 0); + + // Too short error can be thrown right from here. + reader.read_exact(&mut body)?; + + self.decrypt_parsed(&mut body, &prefix)?; + + Ok(body) + } + + /// Decrypts data in place using the parts returned by [`MiniCocoon::encrypt`] method. + /// + /// The method doesn't use memory allocation and is suitable for "no std" and "no alloc" build. + /// + /// # Examples + /// ``` + /// # use cocoon::{MiniCocoon, Error}; + /// # + /// # fn main() -> Result<(), Error> { + /// let mut data = "my secret data".to_owned().into_bytes(); + /// let cocoon = MiniCocoon::from_password(b"password", &[0; 32]); + /// + /// let detached_prefix = cocoon.encrypt(&mut data)?; + /// assert_ne!(data, b"my secret data"); + /// + /// cocoon.decrypt(&mut data, &detached_prefix)?; + /// assert_eq!(data, b"my secret data"); + /// # + /// # Ok(()) + /// # } + /// ``` + pub fn decrypt(&self, data: &mut [u8], detached_prefix: &[u8]) -> Result<(), Error> { + let prefix = MiniFormatPrefix::deserialize(detached_prefix)?; + + self.decrypt_parsed(data, &prefix) + } + + fn decrypt_parsed( + &self, + data: &mut [u8], + detached_prefix: &MiniFormatPrefix, + ) -> Result<(), Error> { + let mut nonce = [0u8; 12]; + + let header = detached_prefix.header(); + + if data.len() < header.data_length() { + return Err(Error::TooShort); + } + + let data = &mut data[..header.data_length()]; + + nonce.copy_from_slice(header.nonce()); + + let nonce = GenericArray::from_slice(&nonce); + let master_key = GenericArray::clone_from_slice(self.key.as_ref()); + let tag = GenericArray::from_slice(&detached_prefix.tag()); + + match self.config.cipher() { + CocoonCipher::Chacha20Poly1305 => { + let cipher = ChaCha20Poly1305::new(&master_key); + cipher.decrypt_in_place_detached(nonce, &detached_prefix.prefix(), data, tag) + } + CocoonCipher::Aes256Gcm => { + let cipher = Aes256Gcm::new(&master_key); + cipher.decrypt_in_place_detached(nonce, &detached_prefix.prefix(), data, tag) + } + } + .map_err(|_| Error::Cryptography)?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use std::fs::File; + use std::io::Cursor; + + use super::*; + + #[test] + fn mini_cocoon_create() { + MiniCocoon::from_password(b"password", &[0; 32]); + MiniCocoon::from_key(&[1; 32], &[0; 32]); + } + + #[test] + fn mini_cocoon_encrypt() { + let cocoon = MiniCocoon::from_password(b"password", &[0; 32]); + let mut data = "my secret data".to_owned().into_bytes(); + + let detached_prefix = cocoon.encrypt(&mut data).unwrap(); + + assert_eq!( + &[ + 155, 244, 154, 106, 7, 85, 249, 83, 129, 31, 206, 18, 0, 0, 0, 0, 0, 0, 0, 14, 88, + 114, 102, 98, 71, 228, 153, 231, 144, 157, 177, 113, 160, 209, 154, 83 + ][..], + &detached_prefix[..] + ); + + assert_eq!( + &[98, 34, 35, 62, 28, 121, 71, 223, 170, 151, 215, 104, 52, 187], + &data[..] + ); + } + + #[test] + fn mini_cocoon_encrypt_aes() { + let cocoon = + MiniCocoon::from_password(b"password", &[0; 32]).with_cipher(CocoonCipher::Aes256Gcm); + let mut data = "my secret data".to_owned().into_bytes(); + + let detached_prefix = cocoon.encrypt(&mut data).unwrap(); + + assert_eq!( + &[ + 155, 244, 154, 106, 7, 85, 249, 83, 129, 31, 206, 18, 0, 0, 0, 0, 0, 0, 0, 14, 95, + 1, 247, 191, 121, 127, 53, 49, 59, 241, 134, 122, 122, 207, 110, 138 + ][..], + &detached_prefix[..] + ); + + assert_eq!( + &[41, 58, 226, 219, 28, 132, 21, 216, 165, 46, 246, 120, 10, 92], + &data[..] + ); + } + + #[test] + fn mini_cocoon_decrypt() { + let detached_prefix = [ + 118, 184, 224, 173, 160, 241, 61, 144, 64, 93, 106, 229, 0, 0, 0, 0, 0, 0, 0, 14, 159, + 31, 100, 63, 43, 219, 99, 46, 201, 213, 205, 233, 174, 235, 43, 24, + ]; + let mut data = [ + 224, 50, 239, 254, 30, 140, 44, 135, 217, 94, 127, 67, 78, 31, + ]; + let cocoon = MiniCocoon::from_password(b"password", &[0; 32]); + + cocoon + .decrypt(&mut data, &detached_prefix) + .expect("Decrypted data"); + + assert_eq!(b"my secret data", &data); + } + + #[test] + fn mini_cocoon_decrypt_aes() { + let detached_prefix = [ + 118, 184, 224, 173, 160, 241, 61, 144, 64, 93, 106, 229, 0, 0, 0, 0, 0, 0, 0, 14, 165, + 83, 248, 230, 121, 148, 146, 253, 98, 153, 208, 174, 129, 31, 162, 13, + ]; + let mut data = [ + 178, 119, 26, 64, 67, 5, 235, 21, 238, 150, 245, 172, 197, 114, + ]; + let cocoon = + MiniCocoon::from_password(b"password", &[0; 32]).with_cipher(CocoonCipher::Aes256Gcm); + + cocoon + .decrypt(&mut data, &detached_prefix) + .expect("Decrypted data"); + + assert_eq!(b"my secret data", &data); + } + + #[test] + fn mini_cocoon_wrap() { + let cocoon = MiniCocoon::from_password(b"password", &[0; 32]); + let wrapped = cocoon.wrap(b"data").expect("Wrapped container"); + + assert_eq!(wrapped[wrapped.len() - 4..], [107, 58, 119, 44]); + } + + #[test] + fn mini_cocoon_wrap_unwrap() { + let cocoon = MiniCocoon::from_key(&[1; 32], &[0; 32]); + let wrapped = cocoon.wrap(b"data").expect("Wrapped container"); + let original = cocoon.unwrap(&wrapped).expect("Unwrapped container"); + + assert_eq!(original, b"data"); + } + + #[test] + fn mini_cocoon_wrap_unwrap_corrupted() { + let cocoon = MiniCocoon::from_key(&[1; 32], &[0; 32]); + let mut wrapped = cocoon.wrap(b"data").expect("Wrapped container"); + + let last = wrapped.len() - 1; + wrapped[last] = wrapped[last] + 1; + cocoon.unwrap(&wrapped).expect_err("Unwrapped container"); + } + + #[test] + fn mini_cocoon_unwrap_larger_is_ok() { + let cocoon = MiniCocoon::from_key(&[1; 32], &[0; 32]); + let mut wrapped = cocoon.wrap(b"data").expect("Wrapped container"); + + wrapped.push(0); + let original = cocoon.unwrap(&wrapped).expect("Unwrapped container"); + + assert_eq!(original, b"data"); + } + + #[test] + fn mini_cocoon_unwrap_too_short() { + let cocoon = MiniCocoon::from_key(&[1; 32], &[0; 32]); + let mut wrapped = cocoon.wrap(b"data").expect("Wrapped container"); + + wrapped.pop(); + cocoon.unwrap(&wrapped).expect_err("Too short"); + } + + #[test] + fn cocoon_decrypt_wrong_sizes() { + let detached_prefix = [ + 118, 184, 224, 173, 160, 241, 61, 144, 64, 93, 106, 229, 0, 0, 0, 0, 0, 0, 0, 14, 165, + 83, 248, 230, 121, 148, 146, 253, 98, 153, 208, 174, 129, 31, 162, 13, + ]; + let mut data = [ + 178, 119, 26, 64, 67, 5, 235, 21, 238, 150, 245, 172, 197, 114, 0, + ]; + let cocoon = + MiniCocoon::from_password(b"password", &[0; 32]).with_cipher(CocoonCipher::Aes256Gcm); + + cocoon + .decrypt(&mut data, &detached_prefix) + .expect("Decrypted data"); + + assert_eq!(b"my secret data\0", &data); + + cocoon + .decrypt(&mut data[..4], &detached_prefix) + .expect_err("Too short"); + } + + #[test] + fn mini_cocoon_dump_parse() { + let buf = vec![0; 100]; + let mut file = Cursor::new(buf); + let cocoon = MiniCocoon::from_key(&[1; 32], &[0; 32]); + + // Prepare data inside of `Vec` container. + let data = b"my data".to_vec(); + + cocoon.dump(data, &mut file).expect("Dumped container"); + assert_ne!(b"my data", file.get_ref().as_slice()); + + // "Re-open" the file. + file.set_position(0); + + let original = cocoon.parse(&mut file).expect("Parsed container"); + assert_eq!(b"my data", original.as_slice()); + } + + #[test] + fn mini_cocoon_dump_io_error() { + File::create("target/read_only.txt").expect("Test file"); + let mut file = File::open("target/read_only.txt").expect("Test file"); + + let cocoon = MiniCocoon::from_key(&[1; 32], &[0; 32]); + + // Prepare data inside of `Vec` container. + let data = b"my data".to_vec(); + + match cocoon.dump(data, &mut file) { + Err(e) => match e { + Error::Io(_) => (), + _ => panic!("Only unexpected I/O error is expected :)"), + }, + _ => panic!("Success is not expected"), + } + } + + #[test] + fn mini_cocoon_parse_io_error() { + File::create("target/read_only.txt").expect("Test file"); + let mut file = File::open("target/read_only.txt").expect("Test file"); + + let cocoon = MiniCocoon::from_key(&[1; 32], &[0; 32]); + + match cocoon.parse(&mut file) { + Err(e) => match e { + Error::TooShort => (), + _ => panic!("TooShort is expected for an empty file"), + }, + _ => panic!("Success is not expected"), + } + } +} diff --git a/manta-accounting/src/asset.rs b/manta-accounting/src/asset.rs index 993d19ca7..b168b6b04 100644 --- a/manta-accounting/src/asset.rs +++ b/manta-accounting/src/asset.rs @@ -38,7 +38,7 @@ use core::{ }; use derive_more::{Add, AddAssign, Display, From, Sub, SubAssign, Sum}; use manta_crypto::{ - constraint::{Allocate, Allocator, Secret, Variable}, + eclair::alloc::{mode::Secret, Allocate, Allocator, Variable}, rand::{Rand, RngCore, Sample}, }; use manta_util::{into_array_unchecked, Array, SizeLimit}; diff --git a/manta-accounting/src/transfer/mod.rs b/manta-accounting/src/transfer/mod.rs index bd5c6d725..75f89d558 100644 --- a/manta-accounting/src/transfer/mod.rs +++ b/manta-accounting/src/transfer/mod.rs @@ -34,9 +34,15 @@ use alloc::vec::Vec; use core::{fmt::Debug, hash::Hash, marker::PhantomData, ops::Deref}; use manta_crypto::{ accumulator::{self, AssertValidVerification, MembershipProof, Model}, - constraint::{ - self, Add, Allocate, Allocator, AssertEq, Bool, Constant, Derived, ProofSystem, - ProofSystemInput, Public, Secret, Variable, + constraint::{ProofSystem, ProofSystemInput}, + eclair::{ + self, + alloc::{ + mode::{Derived, Public, Secret}, + Allocate, Allocator, Constant, Variable, + }, + bool::{AssertEq, Bool}, + ops::Add, }, encryption::{self, hybrid::Hybrid, EncryptedMessage}, key::{self, agreement::Derive}, @@ -129,7 +135,7 @@ pub trait Configuration { /// Public Key Variable Type type PublicKeyVar: Variable> - + constraint::PartialEq; + + eclair::cmp::PartialEq; /// Key Agreement Scheme Variable Type type KeyAgreementSchemeVar: Constant @@ -151,7 +157,7 @@ pub trait Configuration { /// UTXO Variable Type type UtxoVar: Variable> + Variable> - + constraint::PartialEq; + + eclair::cmp::PartialEq; /// UTXO Commitment Scheme Variable Type type UtxoCommitmentSchemeVar: Constant @@ -175,7 +181,7 @@ pub trait Configuration { /// Void Number Variable Type type VoidNumberVar: Variable - + constraint::PartialEq; + + eclair::cmp::PartialEq; /// Void Number Commitment Scheme Variable Type type VoidNumberCommitmentSchemeVar: Constant @@ -217,13 +223,13 @@ pub trait Configuration { /// Asset Id Variable Type type AssetIdVar: Variable + Variable - + constraint::PartialEq; + + eclair::cmp::PartialEq; /// Asset Value Variable Type type AssetValueVar: Variable + Variable + Add - + constraint::PartialEq; + + eclair::cmp::PartialEq; /// Constraint System Type type Compiler: AssertEq; diff --git a/manta-accounting/src/transfer/receiver.rs b/manta-accounting/src/transfer/receiver.rs index 780bca94d..251b0b1f6 100644 --- a/manta-accounting/src/transfer/receiver.rs +++ b/manta-accounting/src/transfer/receiver.rs @@ -26,7 +26,14 @@ use crate::{ use core::{fmt::Debug, hash::Hash, iter}; use manta_crypto::{ accumulator::Accumulator, - constraint::{Allocate, Allocator, AssertEq, Derived, ProofSystemInput, Public, Variable}, + constraint::ProofSystemInput, + eclair::{ + alloc::{ + mode::{Derived, Public}, + Allocate, Allocator, Variable, + }, + bool::AssertEq, + }, encryption::{hybrid, Encrypt}, }; diff --git a/manta-accounting/src/transfer/sender.rs b/manta-accounting/src/transfer/sender.rs index 09611b9f6..e438ccd65 100644 --- a/manta-accounting/src/transfer/sender.rs +++ b/manta-accounting/src/transfer/sender.rs @@ -27,7 +27,11 @@ use crate::{ use core::{fmt::Debug, hash::Hash, iter}; use manta_crypto::{ accumulator::Accumulator, - constraint::{Allocate, Allocator, AssertEq, Derived, ProofSystemInput, Variable}, + constraint::ProofSystemInput, + eclair::{ + alloc::{mode::Derived, Allocate, Allocator, Variable}, + bool::AssertEq, + }, }; #[cfg(feature = "serde")] diff --git a/manta-crypto/Cargo.toml b/manta-crypto/Cargo.toml index fc3dc31c9..05738b9b0 100644 --- a/manta-crypto/Cargo.toml +++ b/manta-crypto/Cargo.toml @@ -25,6 +25,15 @@ is-it-maintained-open-issues = { repository = "Manta-Network/manta-rs" } maintenance = { status = "actively-developed" } [features] +# Arkworks Backend +arkworks = [ + "ark-ec", + "ark-ff", + "ark-r1cs-std", + "ark-relations", + "ark-serialize", +] + # Enable `getrandom` Entropy Source getrandom = ["rand_core/getrandom"] @@ -38,6 +47,11 @@ std = ["manta-util/std"] test = [] [dependencies] +ark-ec = { version = "0.3.0", optional = true, default-features = false } +ark-ff = { version = "0.3.0", optional = true, default-features = false } +ark-r1cs-std = { version = "0.3.1", optional = true, default-features = false } +ark-relations = { version = "0.3.0", optional = true, default-features = false } +ark-serialize = { version = "0.3.0", optional = true, default-features = false } derivative = { version = "2.2.0", default-features = false, features = ["use_core"] } manta-util = { path = "../manta-util", default-features = false, features = ["alloc"] } rand = { version = "0.8.4", optional = true, default-features = false, features = ["alloc"] } diff --git a/manta-crypto/src/accumulator.rs b/manta-crypto/src/accumulator.rs index 7943940c5..1e31d3358 100644 --- a/manta-crypto/src/accumulator.rs +++ b/manta-crypto/src/accumulator.rs @@ -16,7 +16,7 @@ //! Dynamic Cryptographic Accumulators -use crate::constraint::{Allocate, Allocator, Constant, Derived, Variable}; +use crate::eclair::alloc::{mode::Derived, Allocate, Allocator, Constant, Variable}; use core::marker::PhantomData; /// Accumulator Membership Model Types diff --git a/manta-crypto/src/arkworks/algebra.rs b/manta-crypto/src/arkworks/algebra.rs new file mode 100644 index 000000000..98dc69831 --- /dev/null +++ b/manta-crypto/src/arkworks/algebra.rs @@ -0,0 +1,106 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-rs. +// +// manta-rs is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-rs. If not, see . + +//! Arkworks Algebra Backend + +use crate::arkworks::{ + ec::ProjectiveCurve, + ff::{BigInteger, Field, FpParameters, PrimeField}, + r1cs_std::{fields::fp::FpVar, groups::CurveVar}, + serialize::CanonicalSerialize, +}; +use alloc::vec::Vec; +use core::marker::PhantomData; + +#[cfg(feature = "serde")] +use manta_util::serde::Serializer; + +/// Constraint Field Type +type ConstraintField = <::BaseField as Field>::BasePrimeField; + +/// Converts `scalar` to the bit representation of `O`. +#[inline] +pub fn convert_bits(scalar: T) -> O::BigInt +where + T: BigInteger, + O: PrimeField, +{ + O::BigInt::from_bits_le(&scalar.to_bits_le()) +} + +/// Checks that the modulus of `A` is smaller than that of `B`. +#[inline] +pub fn modulus_is_smaller() -> bool +where + A: PrimeField, + B: PrimeField, +{ + let modulus_a = A::Params::MODULUS; + let modulus_b = B::Params::MODULUS; + if modulus_a.num_bits() <= modulus_b.num_bits() { + convert_bits::<_, B>(modulus_a) < modulus_b + } else { + modulus_a < convert_bits::<_, A>(modulus_b) + } +} + +/// Converts `point` into its canonical byte-representation. +#[inline] +pub fn affine_point_as_bytes(point: &C::Affine) -> Vec +where + C: ProjectiveCurve, +{ + let mut buffer = Vec::new(); + point + .serialize(&mut buffer) + .expect("Serialization is not allowed to fail."); + buffer +} + +/// Uses `serializer` to serialize `point`. +#[cfg(feature = "serde")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))] +#[inline] +pub fn serialize_group_element(point: &C::Affine, serializer: S) -> Result +where + C: ProjectiveCurve, + S: Serializer, +{ + serializer.serialize_bytes(&affine_point_as_bytes::(point)) +} + +/// Elliptic Curve Scalar Element Variable +/// +/// # Safety +/// +/// This type can only be used whenever the embedded scalar field is **smaller** than the +/// outer scalar field. +pub struct ScalarVar(pub(crate) FpVar>, PhantomData) +where + C: ProjectiveCurve, + CV: CurveVar>; + +impl ScalarVar +where + C: ProjectiveCurve, + CV: CurveVar>, +{ + /// Builds a new [`ScalarVar`] from a given `scalar`. + #[inline] + pub fn new(scalar: FpVar>) -> Self { + Self(scalar, PhantomData) + } +} diff --git a/manta-crypto/src/arkworks/constraint.rs b/manta-crypto/src/arkworks/constraint.rs new file mode 100644 index 000000000..986892a0a --- /dev/null +++ b/manta-crypto/src/arkworks/constraint.rs @@ -0,0 +1,317 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-rs. +// +// manta-rs is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-rs. If not, see . + +//! Arkworks Constraint-System Backends + +use crate::{ + arkworks::{ + ff::{FpParameters, PrimeField}, + r1cs_std::{alloc::AllocVar, eq::EqGadget, select::CondSelectGadget, ToBitsGadget}, + relations::{ + ns, + r1cs::{ + ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, OptimizationGoal, + SynthesisMode, + }, + }, + }, + constraint::measure::{Count, Measure}, + eclair::{ + self, + alloc::{ + mode::{self, Public, Secret}, + Constant, Variable, + }, + bool::{Assert, ConditionalSwap}, + num::AssertWithinBitRange, + ops::Add, + Has, + }, +}; + +pub use crate::arkworks::{ + r1cs_std::{bits::boolean::Boolean, fields::fp::FpVar}, + relations::r1cs::SynthesisError, +}; + +/// Synthesis Result +pub type SynthesisResult = Result; + +/// Returns an empty variable assignment for setup mode. +/// +/// # Warning +/// +/// This does not work for all variable assignments! For some assignments, the variable inherits +/// some structure from its input, like its length or number of bits, which are only known at +/// run-time. For those cases, some mocking is required and this function can not be used directly. +#[inline] +pub fn empty() -> SynthesisResult { + Err(SynthesisError::AssignmentMissing) +} + +/// Returns a filled variable assignment with the given `value`. +#[inline] +pub fn full(value: T) -> impl FnOnce() -> SynthesisResult { + move || Ok(value) +} + +/// Arkworks Rank-1 Constraint System +#[derive(derivative::Derivative)] +#[derivative(Clone, Debug)] +pub struct R1CS(ConstraintSystemRef) +where + F: PrimeField; + +impl R1CS +where + F: PrimeField, +{ + /// Builds a new [`R1CS`] constraint system from `constraint_system` without checking its + /// optimization goal or synthesis mode. + #[inline] + pub fn new_unchecked(constraint_system: ConstraintSystemRef) -> Self { + Self(constraint_system) + } + + /// Constructs a new constraint system which is ready for unknown variables. + #[inline] + pub fn for_contexts() -> Self { + // FIXME: This might not be the right setup for all proof systems. + let constraint_system = ConstraintSystem::new_ref(); + constraint_system.set_optimization_goal(OptimizationGoal::Constraints); + constraint_system.set_mode(SynthesisMode::Setup); + Self::new_unchecked(constraint_system) + } + + /// Constructs a new constraint system which is ready for known variables. + #[inline] + pub fn for_proofs() -> Self { + // FIXME: This might not be the right setup for all proof systems. + let constraint_system = ConstraintSystem::new_ref(); + constraint_system.set_optimization_goal(OptimizationGoal::Constraints); + Self::new_unchecked(constraint_system) + } + + /// Check if all constraints are satisfied. + #[inline] + pub fn is_satisfied(&self) -> bool { + self.0 + .is_satisfied() + .expect("Checking circuit satisfaction is not allowed to fail.") + } +} + +impl Has for R1CS +where + F: PrimeField, +{ + type Type = Boolean; +} + +impl Assert for R1CS +where + F: PrimeField, +{ + #[inline] + fn assert(&mut self, b: &Boolean) { + b.enforce_equal(&Boolean::TRUE) + .expect("Enforcing equality is not allowed to fail."); + } +} + +impl AssertWithinBitRange, BITS> for R1CS +where + F: PrimeField, +{ + #[inline] + fn assert_within_range(&mut self, value: &FpVar) { + assert!( + BITS < F::Params::MODULUS_BITS as usize, + "BITS must be strictly less than modulus bits of `F`." + ); + let value_bits = value + .to_bits_le() + .expect("Bit decomposition is not allowed to fail."); + for bit in &value_bits[BITS..] { + bit.enforce_equal(&Boolean::FALSE) + .expect("Enforcing equality is not allowed to fail."); + } + } +} + +impl Count for R1CS where F: PrimeField {} + +impl Count for R1CS +where + F: PrimeField, +{ + #[inline] + fn count(&self) -> Option { + Some(self.0.num_instance_variables()) + } +} + +impl Count for R1CS +where + F: PrimeField, +{ + #[inline] + fn count(&self) -> Option { + Some(self.0.num_witness_variables()) + } +} + +impl Measure for R1CS +where + F: PrimeField, +{ + #[inline] + fn constraint_count(&self) -> usize { + self.0.num_constraints() + } +} + +impl ConstraintSynthesizer for R1CS +where + F: PrimeField, +{ + /// Generates constraints for `self` by copying them into `cs`. This method is necessary to hook + /// into the proof system traits defined in `arkworks`. + #[inline] + fn generate_constraints(self, cs: ConstraintSystemRef) -> SynthesisResult { + let precomputed_cs = self + .0 + .into_inner() + .expect("We own this constraint system so we can consume it."); + let mut target_cs = cs + .borrow_mut() + .expect("This is given to us to mutate so it can't be borrowed by anyone else."); + *target_cs = precomputed_cs; + Ok(()) + } +} + +impl Constant> for Boolean +where + F: PrimeField, +{ + type Type = bool; + + #[inline] + fn new_constant(this: &Self::Type, compiler: &mut R1CS) -> Self { + AllocVar::new_constant(ns!(compiler.0, "boolean constant"), this) + .expect("Variable allocation is not allowed to fail.") + } +} + +impl Variable> for Boolean +where + F: PrimeField, +{ + type Type = bool; + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut R1CS) -> Self { + Self::new_input(ns!(compiler.0, "boolean public input"), full(this)) + .expect("Variable allocation is not allowed to fail.") + } + + #[inline] + fn new_unknown(compiler: &mut R1CS) -> Self { + Self::new_input(ns!(compiler.0, "boolean public input"), empty::) + .expect("Variable allocation is not allowed to fail.") + } +} + +impl Variable> for Boolean +where + F: PrimeField, +{ + type Type = bool; + + #[inline] + fn new_known(this: &Self::Type, compiler: &mut R1CS) -> Self { + Self::new_witness(ns!(compiler.0, "boolean secret witness"), full(this)) + .expect("Variable allocation is not allowed to fail.") + } + + #[inline] + fn new_unknown(compiler: &mut R1CS) -> Self { + Self::new_witness(ns!(compiler.0, "boolean secret witness"), empty::) + .expect("Variable allocation is not allowed to fail.") + } +} + +impl eclair::cmp::PartialEq> for Boolean +where + F: PrimeField, +{ + #[inline] + fn eq(&self, rhs: &Self, compiler: &mut R1CS) -> Boolean { + let _ = compiler; + self.is_eq(rhs) + .expect("Equality checking is not allowed to fail.") + } +} + +impl eclair::cmp::PartialEq> for FpVar +where + F: PrimeField, +{ + #[inline] + fn eq(&self, rhs: &Self, compiler: &mut R1CS) -> Boolean { + let _ = compiler; + self.is_eq(rhs) + .expect("Equality checking is not allowed to fail.") + } +} + +/// Conditionally select from `lhs` and `rhs` depending on the value of `bit`. +#[inline] +fn conditionally_select(bit: &Boolean, lhs: &FpVar, rhs: &FpVar) -> FpVar +where + F: PrimeField, +{ + FpVar::conditionally_select(bit, lhs, rhs) + .expect("Conditionally selecting from two values is not allowed to fail.") +} + +impl ConditionalSwap> for FpVar +where + F: PrimeField, +{ + #[inline] + fn swap(bit: &Boolean, lhs: &Self, rhs: &Self, compiler: &mut R1CS) -> (Self, Self) { + let _ = compiler; + ( + conditionally_select(bit, rhs, lhs), + conditionally_select(bit, lhs, rhs), + ) + } +} + +impl Add> for FpVar +where + F: PrimeField, +{ + type Output = Self; + + #[inline] + fn add(self, rhs: Self, compiler: &mut R1CS) -> Self { + let _ = compiler; + self + rhs + } +} diff --git a/manta-crypto/src/arkworks/mod.rs b/manta-crypto/src/arkworks/mod.rs new file mode 100644 index 000000000..228994f98 --- /dev/null +++ b/manta-crypto/src/arkworks/mod.rs @@ -0,0 +1,28 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-rs. +// +// manta-rs is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-rs. If not, see . + +//! Arkworks Backend + +pub use ark_ec as ec; +pub use ark_ff as ff; +pub use ark_r1cs_std as r1cs_std; +pub use ark_relations as relations; +pub use ark_serialize as serialize; + +pub mod algebra; +pub mod constraint; +pub mod pairing; +pub mod rand; diff --git a/manta-trusted-setup/src/pairing.rs b/manta-crypto/src/arkworks/pairing.rs similarity index 85% rename from manta-trusted-setup/src/pairing.rs rename to manta-crypto/src/arkworks/pairing.rs index 6629f6c0f..d143a7522 100644 --- a/manta-trusted-setup/src/pairing.rs +++ b/manta-crypto/src/arkworks/pairing.rs @@ -16,8 +16,10 @@ //! Pairing -use ark_ec::{AffineCurve, PairingEngine}; -use ark_ff::PrimeField; +use crate::arkworks::{ + ec::{AffineCurve, PairingEngine}, + ff::PrimeField, +}; use core::iter; /// Pairing Configuration @@ -53,7 +55,7 @@ pub trait Pairing { } /// Pair from a [`PairingEngine`] -type Pair

= ( +pub type Pair

= (

::G1Prepared,

::G2Prepared, ); @@ -104,12 +106,12 @@ pub trait PairingEngineExt: PairingEngine { impl PairingEngineExt for E where E: PairingEngine {} -#[cfg(test)] -mod test { - use crate::{pairing::PairingEngineExt, util::Sample}; - use ark_bls12_381::{Bls12_381, Fr, G1Affine, G2Affine}; - use ark_ec::{AffineCurve, PairingEngine, ProjectiveCurve}; - use manta_crypto::rand::OsRng; +/// Testing Framework +#[cfg(feature = "test")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "test")))] +pub mod test { + use super::*; + use crate::arkworks::ec::ProjectiveCurve; /// Asserts that `g1` and `g1*scalar` are in the same ratio as `g2` and `g2*scalar`. #[inline] @@ -123,15 +125,4 @@ mod test { ) .is_some()); } - - /// Tests if bls13_381 pairing ratio is valid. - #[test] - fn has_valid_bls12_381_pairing_ratio() { - let mut rng = OsRng; - assert_valid_pairing_ratio::( - G1Affine::gen(&mut rng), - G2Affine::gen(&mut rng), - Fr::gen(&mut rng), - ); - } } diff --git a/manta-crypto/src/arkworks/rand.rs b/manta-crypto/src/arkworks/rand.rs new file mode 100644 index 000000000..90409f37e --- /dev/null +++ b/manta-crypto/src/arkworks/rand.rs @@ -0,0 +1,98 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-rs. +// +// manta-rs is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-rs is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-rs. If not, see . + +//! Arkworks Random Sampling Backends + +use crate::{ + arkworks::{ + ec::{ + models::{ + short_weierstrass_jacobian, twisted_edwards_extended, SWModelParameters, + TEModelParameters, + }, + AffineCurve, ProjectiveCurve, + }, + ff::{Fp256, Fp320, Fp384, Fp448, Fp64, Fp768, Fp832, UniformRand}, + }, + rand::{RngCore, Sample}, +}; + +/// Builds a [`Sample`] implementation for `$projective` and `$affine` curves over the `$P` model. +macro_rules! sample_curve { + ($P:tt, $trait:tt, $projective:path, $affine:path $(,)?) => { + impl<$P> Sample for $projective + where + $P: $trait, + { + #[inline] + fn sample(_: (), rng: &mut R) -> Self + where + R: RngCore + ?Sized, + { + Self::rand(rng) + } + } + + impl<$P> Sample for $affine + where + $P: $trait, + { + #[inline] + fn sample(distribution: (), rng: &mut R) -> Self + where + R: RngCore + ?Sized, + { + ::Projective::sample(distribution, rng).into_affine() + } + } + }; +} + +sample_curve!( + P, + SWModelParameters, + short_weierstrass_jacobian::GroupProjective

, + short_weierstrass_jacobian::GroupAffine

, +); + +sample_curve!( + P, + TEModelParameters, + twisted_edwards_extended::GroupProjective

, + twisted_edwards_extended::GroupAffine

, +); + +/// Builds a [`Sample`] implementation for all the `$fp` types. +macro_rules! sample_fp { + ($($fp:tt),* $(,)?) => { + $( + impl

Sample for $fp

+ where + $fp

: UniformRand, + { + #[inline] + fn sample(_: (), rng: &mut R) -> Self + where + R: RngCore + ?Sized, + { + Self::rand(rng) + } + } + )* + }; +} + +sample_fp!(Fp64, Fp256, Fp320, Fp384, Fp448, Fp768, Fp832); diff --git a/manta-crypto/src/constraint.rs b/manta-crypto/src/constraint.rs index 7d60318e1..25f70d11c 100644 --- a/manta-crypto/src/constraint.rs +++ b/manta-crypto/src/constraint.rs @@ -24,18 +24,6 @@ use crate::rand::{CryptoRng, RngCore}; -pub use crate::eclair::{ - alloc::{ - mode::{self, Derived, Public, Secret}, - Allocate, Allocator, Const, Constant, Var, Variable, - }, - bool::{Assert, AssertEq, Bool, ConditionalSelect, ConditionalSwap}, - cmp::{Eq, PartialEq}, - num::Zero, - ops::{Add, BitAnd, BitOr, Not, Sub}, - Has, Native, NonNative, -}; - /// Proof System pub trait ProofSystem { /// Context Compiler @@ -106,7 +94,7 @@ where /// Constraint System Measurement pub mod measure { - use super::mode::{Constant, Public, Secret}; + use crate::eclair::alloc::mode::{Constant, Public, Secret}; use alloc::{fmt::Display, format, string::String, vec::Vec}; use core::{ fmt::Debug, diff --git a/manta-crypto/src/encryption/hybrid.rs b/manta-crypto/src/encryption/hybrid.rs index 4257a4878..a6308fd69 100644 --- a/manta-crypto/src/encryption/hybrid.rs +++ b/manta-crypto/src/encryption/hybrid.rs @@ -21,9 +21,12 @@ //! encryption scheme inlines this complexity into the encryption interfaces. use crate::{ - constraint::{ - self, Allocate, Allocator, Assert, AssertEq, BitAnd, Bool, Constant, Derived, Has, Var, - Variable, + eclair::{ + self, + alloc::{mode::Derived, Allocate, Allocator, Constant, Var, Variable}, + bool::{Assert, AssertEq, Bool}, + ops::BitAnd, + Has, }, encryption::{ CiphertextType, Decrypt, DecryptedPlaintextType, DecryptionKeyType, Derive, Encrypt, @@ -199,14 +202,14 @@ where } } -impl constraint::PartialEq for Ciphertext +impl eclair::cmp::PartialEq for Ciphertext where COM: Has, Bool: BitAnd, COM, Output = Bool>, K: key::agreement::Types, E: CiphertextType, - K::PublicKey: constraint::PartialEq, - E::Ciphertext: constraint::PartialEq, + K::PublicKey: eclair::cmp::PartialEq, + E::Ciphertext: eclair::cmp::PartialEq, { #[inline] fn eq(&self, rhs: &Self, compiler: &mut COM) -> Bool { diff --git a/manta-crypto/src/encryption/mod.rs b/manta-crypto/src/encryption/mod.rs index b173fbf2f..5e4a5039b 100644 --- a/manta-crypto/src/encryption/mod.rs +++ b/manta-crypto/src/encryption/mod.rs @@ -21,9 +21,15 @@ //! [`Decrypt`] `trait`s for more. use crate::{ - constraint::{ - self, Allocate, Allocator, Assert, AssertEq, BitAnd, Bool, Constant, Derived, Has, Public, - Var, Variable, + eclair::{ + self, + alloc::{ + mode::{Derived, Public}, + Allocate, Allocator, Constant, Var, Variable, + }, + bool::{Assert, AssertEq, Bool}, + ops::BitAnd, + Has, }, rand::{Rand, RngCore, Sample}, }; @@ -485,13 +491,13 @@ where } } -impl constraint::PartialEq for EncryptedMessage +impl eclair::cmp::PartialEq for EncryptedMessage where E: CiphertextType + HeaderType + ?Sized, COM: Has, Bool: BitAnd, COM, Output = Bool>, - E::Ciphertext: constraint::PartialEq, - E::Header: constraint::PartialEq, + E::Ciphertext: eclair::cmp::PartialEq, + E::Header: eclair::cmp::PartialEq, { #[inline] fn eq(&self, rhs: &Self, compiler: &mut COM) -> Bool { @@ -510,13 +516,13 @@ where } } -impl constraint::Eq for EncryptedMessage +impl eclair::cmp::Eq for EncryptedMessage where E: CiphertextType + HeaderType + ?Sized, COM: Has, Bool: BitAnd, COM, Output = Bool>, - E::Ciphertext: constraint::Eq, - E::Header: constraint::Eq, + E::Ciphertext: eclair::cmp::Eq, + E::Header: eclair::cmp::Eq, { } diff --git a/manta-crypto/src/lib.rs b/manta-crypto/src/lib.rs index 55f688bf9..2b617b711 100644 --- a/manta-crypto/src/lib.rs +++ b/manta-crypto/src/lib.rs @@ -36,3 +36,7 @@ pub mod password; pub mod permutation; pub mod rand; pub mod signature; + +#[cfg(feature = "arkworks")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "arkworks")))] +pub mod arkworks; diff --git a/manta-crypto/src/merkle_tree/path.rs b/manta-crypto/src/merkle_tree/path.rs index 1e6b0ee1e..7f1f72d3e 100644 --- a/manta-crypto/src/merkle_tree/path.rs +++ b/manta-crypto/src/merkle_tree/path.rs @@ -930,8 +930,11 @@ where pub mod constraint { use super::*; use crate::{ - constraint::{ - Allocate, Allocator, Bool, ConditionalSwap, Constant, Has, PartialEq, Secret, Variable, + eclair::{ + alloc::{mode::Secret, Allocate, Allocator, Constant, Variable}, + bool::{Bool, ConditionalSwap}, + cmp::PartialEq, + Has, }, merkle_tree::path_length, }; diff --git a/manta-crypto/src/merkle_tree/tree.rs b/manta-crypto/src/merkle_tree/tree.rs index d3eacce84..be468b41b 100644 --- a/manta-crypto/src/merkle_tree/tree.rs +++ b/manta-crypto/src/merkle_tree/tree.rs @@ -29,7 +29,12 @@ use crate::{ self, Accumulator, ConstantCapacityAccumulator, ExactSizeAccumulator, MembershipProof, OptimizedAccumulator, }, - constraint::{self, Allocate, AssertEq, Bool, ConditionalSwap, Constant, Has, NonNative}, + eclair::{ + self, + alloc::{Allocate, Constant}, + bool::{AssertEq, Bool, ConditionalSwap}, + Has, NonNative, + }, merkle_tree::{ fork::{ForkedTree, Trunk}, inner_tree::InnerMap, @@ -555,7 +560,8 @@ where where C: Configuration, COM: Has, - InnerDigest: ConditionalSwap + constraint::PartialEq, COM>, + InnerDigest: + ConditionalSwap + eclair::cmp::PartialEq, COM>, LeafDigest: ConditionalSwap, { path.verify(self, root, leaf, compiler) @@ -703,7 +709,7 @@ impl accumulator::Types for Parameters where C: Configuration + ?Sized, COM: Has + NonNative, - InnerDigest: ConditionalSwap + constraint::PartialEq, COM>, + InnerDigest: ConditionalSwap + eclair::cmp::PartialEq, COM>, LeafDigest: ConditionalSwap, { type Item = Leaf; @@ -715,7 +721,7 @@ impl accumulator::Model for Parameters where C: Configuration + ?Sized, COM: Has + NonNative, - InnerDigest: ConditionalSwap + constraint::PartialEq, COM>, + InnerDigest: ConditionalSwap + eclair::cmp::PartialEq, COM>, LeafDigest: ConditionalSwap, { type Verification = Bool; @@ -736,7 +742,7 @@ impl accumulator::AssertValidVerification for Parameters where C: Configuration + ?Sized, COM: AssertEq + NonNative, - InnerDigest: ConditionalSwap + constraint::PartialEq, COM>, + InnerDigest: ConditionalSwap + eclair::cmp::PartialEq, COM>, LeafDigest: ConditionalSwap, { #[inline] diff --git a/manta-crypto/src/signature.rs b/manta-crypto/src/signature.rs index 780293a9c..07280e421 100644 --- a/manta-crypto/src/signature.rs +++ b/manta-crypto/src/signature.rs @@ -192,7 +192,7 @@ pub mod schnorr { use super::*; use crate::{ algebra::{security::DiscreteLogarithmHardness, Group, Scalar}, - constraint::{Bool, Has, PartialEq}, + eclair::{bool::Bool, cmp::PartialEq, Has}, hash::security::PreimageResistance, }; use core::{cmp, fmt::Debug, hash::Hash, marker::PhantomData}; diff --git a/manta-pay/Cargo.toml b/manta-pay/Cargo.toml index 59fdfc704..a2f69e358 100644 --- a/manta-pay/Cargo.toml +++ b/manta-pay/Cargo.toml @@ -40,13 +40,9 @@ required-features = ["clap", "groth16", "simulation"] # Enable Arkworks Backend arkworks = [ "ark-bls12-381", - "ark-ec", "ark-ed-on-bls12-381", - "ark-ff", - "ark-r1cs-std", - "ark-relations", - "ark-serialize", "ark-std", + "manta-crypto/arkworks", ] # Enable Download Parameters @@ -105,13 +101,8 @@ websocket = [ [dependencies] aes-gcm = { version = "0.9.4", default-features = false, features = ["aes", "alloc"] } ark-bls12-381 = { version = "0.3.0", optional = true, default-features = false, features = ["curve"] } -ark-ec = { version = "0.3.0", optional = true, default-features = false } ark-ed-on-bls12-381 = { version = "0.3.0", optional = true, default-features = false, features = ["r1cs"] } -ark-ff = { version = "0.3.0", optional = true, default-features = false } ark-groth16 = { version = "0.3.0", optional = true, default-features = false } -ark-r1cs-std = { version = "0.3.1", optional = true, default-features = false } -ark-relations = { version = "0.3.0", optional = true, default-features = false } -ark-serialize = { version = "0.3.0", optional = true, default-features = false, features = ["derive"] } ark-snark = { version = "0.3.0", optional = true, default-features = false } ark-std = { version = "0.3.0", optional = true, default-features = false } bip32 = { version = "0.4.0", optional = true, default-features = false, features = ["bip39", "secp256k1"] } diff --git a/manta-pay/src/bin/measure.rs b/manta-pay/src/bin/measure.rs index 8c56965d1..b854db483 100644 --- a/manta-pay/src/bin/measure.rs +++ b/manta-pay/src/bin/measure.rs @@ -17,7 +17,8 @@ //! Manta Pay Circuit Measurements use manta_crypto::{ - constraint::{measure::Instrument, Allocate, Allocator, Secret}, + constraint::measure::Instrument, + eclair::alloc::{mode::Secret, Allocate, Allocator}, hash::ArrayHashFunction, key::agreement::{Agree, Derive}, rand::{Sample, SeedableRng}, diff --git a/manta-pay/src/config.rs b/manta-pay/src/config.rs index 658250021..fb9b9ab94 100644 --- a/manta-pay/src/config.rs +++ b/manta-pay/src/config.rs @@ -24,8 +24,6 @@ use crate::crypto::{ poseidon::compat as poseidon, }; use alloc::vec::Vec; -use ark_ff::ToConstraintField; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError}; use blake2::{ digest::{Update, VariableOutput}, Blake2sVar, @@ -39,8 +37,18 @@ use manta_accounting::{ use manta_crypto::{ accumulator, algebra::DiffieHellman, - constraint::{ - self, Add, Allocate, Allocator, Constant, ProofSystemInput, Public, Secret, Variable, + arkworks::{ + ff::ToConstraintField, + serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError}, + }, + constraint::ProofSystemInput, + eclair::{ + self, + alloc::{ + mode::{Public, Secret}, + Allocate, Allocator, Constant, Variable, + }, + ops::Add, }, encryption, hash::ArrayHashFunction, @@ -63,6 +71,7 @@ use manta_crypto::rand::{Rand, RngCore, Sample}; #[doc(inline)] pub use ark_bls12_381 as bls12_381; + #[doc(inline)] pub use ark_ed_on_bls12_381 as bls12_381_ed; @@ -371,7 +380,7 @@ impl Constant for VoidNumberCommitmentSchemeVar { /// Asset ID Variable pub struct AssetIdVar(ConstraintFieldVar); -impl constraint::PartialEq for AssetIdVar { +impl eclair::cmp::PartialEq for AssetIdVar { #[inline] fn eq(&self, rhs: &Self, compiler: &mut Compiler) -> Boolean { ConstraintFieldVar::eq(&self.0, &rhs.0, compiler) @@ -418,7 +427,7 @@ impl Add for AssetValueVar { } } -impl constraint::PartialEq for AssetValueVar { +impl eclair::cmp::PartialEq for AssetValueVar { #[inline] fn eq(&self, rhs: &Self, compiler: &mut Compiler) -> Boolean { ConstraintFieldVar::eq(&self.0, &rhs.0, compiler) diff --git a/manta-pay/src/crypto/constraint/arkworks/codec.rs b/manta-pay/src/crypto/constraint/arkworks/codec.rs index 6f1668490..6887212cd 100644 --- a/manta-pay/src/crypto/constraint/arkworks/codec.rs +++ b/manta-pay/src/crypto/constraint/arkworks/codec.rs @@ -19,7 +19,9 @@ use ark_std::io::{self, Error, ErrorKind}; use manta_util::codec::{Read, ReadExactError, Write}; -pub use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError}; +pub use manta_crypto::arkworks::serialize::{ + CanonicalDeserialize, CanonicalSerialize, SerializationError, +}; /// Scale-Codec Input as Reader Wrapper #[cfg(feature = "scale")] diff --git a/manta-pay/src/crypto/constraint/arkworks/groth16.rs b/manta-pay/src/crypto/constraint/arkworks/groth16.rs index 58c88621b..ce15573df 100644 --- a/manta-pay/src/crypto/constraint/arkworks/groth16.rs +++ b/manta-pay/src/crypto/constraint/arkworks/groth16.rs @@ -22,12 +22,14 @@ use crate::crypto::constraint::arkworks::{ R1CS, }; use alloc::vec::Vec; -use ark_ec::PairingEngine; use ark_groth16::{Groth16 as ArkGroth16, PreparedVerifyingKey, ProvingKey}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, Write}; use ark_snark::SNARK; use core::marker::PhantomData; use manta_crypto::{ + arkworks::{ + ec::PairingEngine, + serialize::{CanonicalDeserialize, CanonicalSerialize, Read, Write}, + }, constraint::ProofSystem, rand::{CryptoRng, RngCore, SizedRng}, }; diff --git a/manta-pay/src/crypto/constraint/arkworks/mod.rs b/manta-pay/src/crypto/constraint/arkworks/mod.rs index 47cacc8ad..610bf29d0 100644 --- a/manta-pay/src/crypto/constraint/arkworks/mod.rs +++ b/manta-pay/src/crypto/constraint/arkworks/mod.rs @@ -17,21 +17,32 @@ //! Arkworks Constraint System and Proof System Implementations use alloc::vec::Vec; -use ark_ff::{Field, FpParameters, PrimeField}; -use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget, select::CondSelectGadget, ToBitsGadget}; -use ark_relations::{ - ns, r1cs as ark_r1cs, - r1cs::{ConstraintSynthesizer, ConstraintSystemRef}, -}; use manta_crypto::{ algebra, - constraint::{ + arkworks::{ + ff::{Field, FpParameters, PrimeField}, + r1cs_std::{alloc::AllocVar, eq::EqGadget, select::CondSelectGadget, ToBitsGadget}, + relations::{ + ns, + r1cs::{ + ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, OptimizationGoal, + SynthesisMode, + }, + }, + }, + constraint::measure::{Count, Measure}, + eclair::{ self, - measure::{Count, Measure}, - mode, Add, Assert, BitAnd, ConditionalSwap, Constant, Has, NonNative, Public, Secret, - Variable, + alloc::{ + mode, + mode::{Public, Secret}, + Constant, Variable, + }, + bool::{Assert, ConditionalSwap}, + num::AssertWithinBitRange, + ops::{Add, BitAnd}, + Has, NonNative, }, - eclair::num::AssertWithinBitRange, rand::{RngCore, Sample}, }; use manta_util::{ @@ -43,8 +54,10 @@ use manta_util::{ #[cfg(feature = "serde")] use manta_util::serde::{Deserialize, Serialize, Serializer}; -pub use ark_r1cs::SynthesisError; -pub use ark_r1cs_std::{bits::boolean::Boolean, fields::fp::FpVar}; +pub use manta_crypto::arkworks::{ + r1cs_std::{bits::boolean::Boolean, fields::fp::FpVar}, + relations::r1cs::SynthesisError, +}; pub mod codec; pub mod pairing; @@ -276,7 +289,7 @@ where F: PrimeField, { /// Constraint System - pub(crate) cs: ark_r1cs::ConstraintSystemRef, + pub(crate) cs: ConstraintSystemRef, } impl R1CS @@ -287,9 +300,9 @@ where #[inline] pub fn for_contexts() -> Self { // FIXME: This might not be the right setup for all proof systems. - let cs = ark_r1cs::ConstraintSystem::new_ref(); - cs.set_optimization_goal(ark_r1cs::OptimizationGoal::Constraints); - cs.set_mode(ark_r1cs::SynthesisMode::Setup); + let cs = ConstraintSystem::new_ref(); + cs.set_optimization_goal(OptimizationGoal::Constraints); + cs.set_mode(SynthesisMode::Setup); Self { cs } } @@ -297,8 +310,8 @@ where #[inline] pub fn for_proofs() -> Self { // FIXME: This might not be the right setup for all proof systems. - let cs = ark_r1cs::ConstraintSystem::new_ref(); - cs.set_optimization_goal(ark_r1cs::OptimizationGoal::Constraints); + let cs = ConstraintSystem::new_ref(); + cs.set_optimization_goal(OptimizationGoal::Constraints); Self { cs } } @@ -454,7 +467,7 @@ where } } -impl constraint::PartialEq> for Boolean +impl eclair::cmp::PartialEq> for Boolean where F: PrimeField, { @@ -543,7 +556,7 @@ where } } -impl constraint::PartialEq> for FpVar +impl eclair::cmp::PartialEq> for FpVar where F: PrimeField, { @@ -597,10 +610,10 @@ where mod tests { use super::*; use ark_bls12_381::Fr; - use ark_ff::BigInteger; use core::iter::repeat_with; use manta_crypto::{ - constraint::Allocate, + arkworks::ff::BigInteger, + eclair::alloc::Allocate, rand::{OsRng, Rand}, }; diff --git a/manta-pay/src/crypto/constraint/arkworks/pairing.rs b/manta-pay/src/crypto/constraint/arkworks/pairing.rs index 88336c19d..2f0802ec1 100644 --- a/manta-pay/src/crypto/constraint/arkworks/pairing.rs +++ b/manta-pay/src/crypto/constraint/arkworks/pairing.rs @@ -20,10 +20,10 @@ pub mod bls12 { use crate::crypto::constraint::arkworks::codec::{HasDeserialization, HasSerialization}; use alloc::vec::Vec; - use ark_ec::models::bls12::{g2, Bls12Parameters}; - use ark_ff::Fp2; - use ark_serialize::{ - CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write, + use manta_crypto::arkworks::{ + ec::models::bls12::{g2, Bls12Parameters}, + ff::Fp2, + serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}, }; /// Line Evaluation Coefficients diff --git a/manta-pay/src/crypto/ecc/arkworks.rs b/manta-pay/src/crypto/ecc/arkworks.rs index 9ca0c333e..835d38e1f 100644 --- a/manta-pay/src/crypto/ecc/arkworks.rs +++ b/manta-pay/src/crypto/ecc/arkworks.rs @@ -18,24 +18,34 @@ use crate::crypto::constraint::arkworks::{self, empty, full, Boolean, Fp, FpVar, R1CS}; use alloc::vec::Vec; -use ark_ff::{BigInteger, Field, FpParameters, PrimeField}; -use ark_r1cs_std::ToBitsGadget; -use ark_relations::ns; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError}; use core::marker::PhantomData; use manta_crypto::{ algebra, - constraint::{self, Allocate, Allocator, Constant, Public, Secret, Variable}, + arkworks::{ + algebra::{affine_point_as_bytes, modulus_is_smaller}, + ec::{AffineCurve, ProjectiveCurve}, + ff::{BigInteger, Field, PrimeField}, + r1cs_std::{groups::CurveVar, ToBitsGadget}, + relations::ns, + serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError}, + }, + eclair::{ + self, + alloc::{ + mode::{Public, Secret}, + Allocate, Allocator, Constant, Variable, + }, + }, key::kdf, rand::{RngCore, Sample}, }; use manta_util::codec; #[cfg(feature = "serde")] -use manta_util::serde::{Deserialize, Serialize, Serializer}; - -pub use ark_ec::{AffineCurve, ProjectiveCurve}; -pub use ark_r1cs_std::groups::CurveVar; +use { + manta_crypto::arkworks::algebra::serialize_group_element, + manta_util::serde::{Deserialize, Serialize}, +}; /// Constraint Field Type type ConstraintField = <::BaseField as Field>::BasePrimeField; @@ -46,32 +56,6 @@ type Compiler = R1CS>; /// Scalar Field Element pub type Scalar = Fp<::ScalarField>; -/// Converts `scalar` to the bit representation of `O`. -#[inline] -pub fn convert_bits(scalar: T) -> O::BigInt -where - T: BigInteger, - O: PrimeField, -{ - O::BigInt::from_bits_le(&scalar.to_bits_le()) -} - -/// Checks that the modulus of `A` is smaller than that of `B`. -#[inline] -pub fn modulus_is_smaller() -> bool -where - A: PrimeField, - B: PrimeField, -{ - let modulus_a = A::Params::MODULUS; - let modulus_b = B::Params::MODULUS; - if modulus_a.num_bits() <= modulus_b.num_bits() { - convert_bits::<_, B>(modulus_a) < modulus_b - } else { - modulus_a < convert_bits::<_, A>(modulus_b) - } -} - /// Lifts an embedded scalar to an outer scalar. /// /// # Safety @@ -281,30 +265,6 @@ where } } -/// Converts `point` into its canonical byte-representation. -#[inline] -pub fn affine_point_as_bytes(point: &C::Affine) -> Vec -where - C: ProjectiveCurve, -{ - let mut buffer = Vec::new(); - point - .serialize(&mut buffer) - .expect("Serialization is not allowed to fail."); - buffer -} - -/// Uses `serializer` to serialize `point`. -#[cfg(feature = "serde")] -#[inline] -fn serialize_group_element(point: &C::Affine, serializer: S) -> Result -where - C: ProjectiveCurve, - S: Serializer, -{ - serializer.serialize_bytes(&affine_point_as_bytes::(point)) -} - /// Elliptic Curve Scalar Element Variable /// /// # Safety @@ -467,7 +427,7 @@ where { } -impl constraint::PartialEq> for GroupVar +impl eclair::cmp::PartialEq> for GroupVar where C: ProjectiveCurve, CV: CurveVar>, diff --git a/manta-pay/src/crypto/poseidon/arkworks.rs b/manta-pay/src/crypto/poseidon/arkworks.rs index 1ef6035ea..afc90e475 100644 --- a/manta-pay/src/crypto/poseidon/arkworks.rs +++ b/manta-pay/src/crypto/poseidon/arkworks.rs @@ -23,9 +23,13 @@ use crate::crypto::{ ParameterFieldType, }, }; -use ark_ff::{BigInteger, Field as _, FpParameters, PrimeField}; -use ark_r1cs_std::fields::FieldVar; -use manta_crypto::constraint::Constant; +use manta_crypto::{ + arkworks::{ + ff::{BigInteger, Field as _, FpParameters, PrimeField}, + r1cs_std::fields::FieldVar, + }, + eclair::alloc::Constant, +}; /// Compiler Type. type Compiler = R1CS<::Field>; diff --git a/manta-pay/src/crypto/poseidon/compat.rs b/manta-pay/src/crypto/poseidon/compat.rs index 1d4e46c66..5b1ae79fd 100644 --- a/manta-pay/src/crypto/poseidon/compat.rs +++ b/manta-pay/src/crypto/poseidon/compat.rs @@ -328,9 +328,13 @@ pub type Output = #[cfg_attr(doc_cfg, doc(cfg(feature = "arkworks")))] pub mod arkworks { use crate::crypto::constraint::arkworks::{Fp, FpVar, R1CS}; - use ark_ff::{Field, PrimeField}; - use ark_r1cs_std::fields::FieldVar; - use manta_crypto::constraint::{Allocate, Constant}; + use manta_crypto::{ + arkworks::{ + ff::{Field, PrimeField}, + r1cs_std::fields::FieldVar, + }, + eclair::alloc::{Allocate, Constant}, + }; /// Compiler Type type Compiler = R1CS<::Field>; diff --git a/manta-pay/src/crypto/poseidon/hash.rs b/manta-pay/src/crypto/poseidon/hash.rs index 968f9f88a..cff32115d 100644 --- a/manta-pay/src/crypto/poseidon/hash.rs +++ b/manta-pay/src/crypto/poseidon/hash.rs @@ -22,7 +22,7 @@ use crate::crypto::poseidon::{ use alloc::vec::Vec; use core::{fmt::Debug, hash::Hash, marker::PhantomData}; use manta_crypto::{ - constraint::{Allocate, Const, Constant}, + eclair::alloc::{Allocate, Const, Constant}, hash::ArrayHashFunction, rand::{Rand, RngCore, Sample}, }; @@ -212,8 +212,10 @@ where mod test { use crate::{config::Poseidon2, crypto::constraint::arkworks::Fp}; use ark_bls12_381::Fr; - use ark_ff::field_new; - use manta_crypto::rand::{OsRng, Sample}; + use manta_crypto::{ + arkworks::ff::field_new, + rand::{OsRng, Sample}, + }; /// Tests if [`Poseidon2`](crate::config::Poseidon2) matches hardcoded sage outputs. #[test] diff --git a/manta-pay/src/crypto/poseidon/mds.rs b/manta-pay/src/crypto/poseidon/mds.rs index ce39b91e6..9cfff4bda 100644 --- a/manta-pay/src/crypto/poseidon/mds.rs +++ b/manta-pay/src/crypto/poseidon/mds.rs @@ -266,8 +266,10 @@ mod test { use super::*; use crate::crypto::{constraint::arkworks::Fp, poseidon::matrix::Matrix}; use ark_bls12_381::Fr; - use ark_ff::{field_new, UniformRand}; - use manta_crypto::rand::OsRng; + use manta_crypto::{ + arkworks::ff::{field_new, UniformRand}, + rand::OsRng, + }; /// Checks if creating mds matrices is correct. #[test] diff --git a/manta-pay/src/crypto/poseidon/mod.rs b/manta-pay/src/crypto/poseidon/mod.rs index 564c2a72b..e9bc374c6 100644 --- a/manta-pay/src/crypto/poseidon/mod.rs +++ b/manta-pay/src/crypto/poseidon/mod.rs @@ -22,7 +22,7 @@ use crate::crypto::poseidon::{ use alloc::{boxed::Box, vec::Vec}; use core::{fmt::Debug, hash::Hash, iter, marker::PhantomData, mem, slice}; use manta_crypto::{ - constraint::{Allocate, Const, Constant}, + eclair::alloc::{Allocate, Const, Constant}, permutation::PseudorandomPermutation, rand::{RngCore, Sample}, }; diff --git a/manta-pay/src/crypto/poseidon/round_constants.rs b/manta-pay/src/crypto/poseidon/round_constants.rs index 75c36343e..7c8f6a0e4 100644 --- a/manta-pay/src/crypto/poseidon/round_constants.rs +++ b/manta-pay/src/crypto/poseidon/round_constants.rs @@ -80,7 +80,7 @@ mod test { use super::*; use crate::crypto::constraint::arkworks::Fp; use ark_bls12_381::Fr; - use ark_ff::field_new; + use manta_crypto::arkworks::ff::field_new; /// Checks if [`GrainLFSR`] is consistent with hardcoded outputs from the `sage` script found at /// with the diff --git a/manta-pay/src/signer/base.rs b/manta-pay/src/signer/base.rs index 9cdb46453..cbbdd12ea 100644 --- a/manta-pay/src/signer/base.rs +++ b/manta-pay/src/signer/base.rs @@ -23,8 +23,6 @@ use crate::{ signer::Checkpoint, }; use alloc::collections::BTreeMap; -use ark_ec::ProjectiveCurve; -use ark_ff::PrimeField; use core::{cmp, marker::PhantomData, mem}; use manta_accounting::{ asset::HashAssetMap, @@ -35,7 +33,10 @@ use manta_accounting::{ }, }; use manta_crypto::{ - key::kdf::KeyDerivationFunction, merkle_tree, merkle_tree::forest::Configuration, + arkworks::{ec::ProjectiveCurve, ff::PrimeField}, + key::kdf::KeyDerivationFunction, + merkle_tree, + merkle_tree::forest::Configuration, }; #[cfg(feature = "serde")] diff --git a/manta-trusted-setup/Cargo.toml b/manta-trusted-setup/Cargo.toml index 79d96e896..91300d16d 100644 --- a/manta-trusted-setup/Cargo.toml +++ b/manta-trusted-setup/Cargo.toml @@ -32,27 +32,22 @@ rayon = ["manta-util/rayon"] std = [] # Testing Frameworks -test = [] +test = ["manta-crypto/test"] [dependencies] -ark-ec = { version = "0.3.0", default-features = false } -ark-ff = { version = "0.3.0", default-features = false } ark-groth16 = { version = "0.3.0", default-features = false } ark-poly = { version = "0.3.0", default-features = false } -ark-relations = { version = "0.3.0", default-features = false } -ark-serialize = { version = "0.3.0", default-features = false, features = ["derive"] } ark-std = { version = "0.3.0", default-features = false } blake2 = { version = "0.10.4", default-features = false } byteorder = { version = "1.4.3", default-features = false } derivative = { version = "2.2.0", default-features = false, features = ["use_core"] } -manta-crypto = { path = "../manta-crypto", default-features = false, features = ["getrandom"] } +manta-crypto = { path = "../manta-crypto", default-features = false, features = ["arkworks", "getrandom"] } manta-util = { path = "../manta-util", default-features = false } rand_chacha = { version = "0.3.1", default-features = false } workspace-hack = { version = "0.1.0", path = "../workspace-hack" } [dev-dependencies] ark-bls12-381 = { version = "0.3.0", default-features = false, features = ["curve", "scalar_field"] } -ark-r1cs-std = { version = "0.3.1", default-features = false } ark-snark = { version = "0.3.0", default-features = false } manta-pay = { path = "../manta-pay", default-features = false, features = ["groth16"] } # TODO: To be removed manta-trusted-setup = { path = ".", default-features = false, features = ["test"] } diff --git a/manta-trusted-setup/src/groth16/kzg.rs b/manta-trusted-setup/src/groth16/kzg.rs index 3df74c5f7..ef6432ea2 100644 --- a/manta-trusted-setup/src/groth16/kzg.rs +++ b/manta-trusted-setup/src/groth16/kzg.rs @@ -17,15 +17,19 @@ //! KZG Trusted Setup for Groth16 use crate::{ - pairing::{Pairing, PairingEngineExt}, ratio::{HashToGroup, RatioProof}, - util::{power_pairs, scalar_mul, Deserializer, NonZero, Sample, Serializer}, + util::{power_pairs, scalar_mul, Deserializer, NonZero, Serializer}, }; use alloc::{vec, vec::Vec}; -use ark_ff::{One, UniformRand}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; use core::{iter, ops::Mul}; -use manta_crypto::rand::{CryptoRng, RngCore}; +use manta_crypto::{ + arkworks::{ + ff::{One, UniformRand}, + pairing::{Pairing, PairingEngineExt}, + serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}, + }, + rand::{CryptoRng, RngCore, Sample}, +}; use manta_util::{cfg_iter, cfg_iter_mut, from_variant, vec::VecExt}; #[cfg(feature = "rayon")] diff --git a/manta-trusted-setup/src/groth16/mpc.rs b/manta-trusted-setup/src/groth16/mpc.rs index 1f3585b14..791c8c347 100644 --- a/manta-trusted-setup/src/groth16/mpc.rs +++ b/manta-trusted-setup/src/groth16/mpc.rs @@ -18,18 +18,22 @@ use crate::{ groth16::kzg::{self, Accumulator}, - pairing::{Pairing, PairingEngineExt}, ratio::{HashToGroup, RatioProof}, util::{batch_into_projective, batch_mul_fixed_scalar, merge_pairs_affine}, }; use alloc::{vec, vec::Vec}; -use ark_ec::{AffineCurve, ProjectiveCurve}; -use ark_ff::{Field, PrimeField, UniformRand, Zero}; use ark_groth16::{ProvingKey, VerifyingKey}; use ark_poly::{EvaluationDomain, Radix2EvaluationDomain}; -use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem, SynthesisError}; use core::clone::Clone; -use manta_crypto::rand::{CryptoRng, RngCore}; +use manta_crypto::{ + arkworks::{ + ec::{AffineCurve, ProjectiveCurve}, + ff::{Field, PrimeField, UniformRand, Zero}, + pairing::{Pairing, PairingEngineExt}, + relations::r1cs::{ConstraintSynthesizer, ConstraintSystem, SynthesisError}, + }, + rand::{CryptoRng, RngCore}, +}; /// Proving Key Hasher pub trait ProvingKeyHasher

diff --git a/manta-trusted-setup/src/groth16/test.rs b/manta-trusted-setup/src/groth16/test.rs index c60b84212..e9eed7618 100644 --- a/manta-trusted-setup/src/groth16/test.rs +++ b/manta-trusted-setup/src/groth16/test.rs @@ -22,23 +22,27 @@ use crate::{ mpc::{self, contribute, initialize, verify_transform, verify_transform_all, Proof, State}, }, mpc::{Transcript, Types}, - pairing::Pairing, ratio::test::assert_valid_ratio_proof, - util::{BlakeHasher, HasDistribution, KZGBlakeHasher, Sample}, + util::{BlakeHasher, HasDistribution, KZGBlakeHasher}, }; use alloc::vec::Vec; -use ark_bls12_381::Fr; -use ark_ec::{AffineCurve, PairingEngine}; -use ark_ff::{field_new, UniformRand}; +use ark_bls12_381::{Bls12_381, Fr, G1Affine, G2Affine}; use ark_groth16::{Groth16, ProvingKey}; -use ark_r1cs_std::eq::EqGadget; -use ark_serialize::CanonicalSerialize; use ark_snark::SNARK; use blake2::Digest; use manta_crypto::{ - constraint::Allocate, - eclair::alloc::mode::{Public, Secret}, - rand::{CryptoRng, OsRng, RngCore}, + arkworks::{ + ec::{AffineCurve, PairingEngine}, + ff::{field_new, UniformRand}, + pairing::{test::assert_valid_pairing_ratio, Pairing}, + r1cs_std::eq::EqGadget, + serialize::CanonicalSerialize, + }, + eclair::alloc::{ + mode::{Public, Secret}, + Allocate, + }, + rand::{CryptoRng, OsRng, RngCore, Sample}, }; use manta_pay::crypto::constraint::arkworks::{Fp, FpVar, R1CS}; use manta_util::into_array_unchecked; @@ -57,21 +61,21 @@ impl HasDistribution for Test { } impl Pairing for Test { - type Scalar = ark_bls12_381::Fr; - type G1 = ark_bls12_381::G1Affine; - type G1Prepared = ::G1Prepared; - type G2 = ark_bls12_381::G2Affine; - type G2Prepared = ::G2Prepared; - type Pairing = ark_bls12_381::Bls12_381; + type Scalar = Fr; + type G1 = G1Affine; + type G1Prepared = ::G1Prepared; + type G2 = G2Affine; + type G2Prepared = ::G2Prepared; + type Pairing = Bls12_381; #[inline] fn g1_prime_subgroup_generator() -> Self::G1 { - ark_bls12_381::G1Affine::prime_subgroup_generator() + G1Affine::prime_subgroup_generator() } #[inline] fn g2_prime_subgroup_generator() -> Self::G2 { - ark_bls12_381::G2Affine::prime_subgroup_generator() + G2Affine::prime_subgroup_generator() } } @@ -218,6 +222,17 @@ where ); } +/// Tests if bls13_381 pairing ratio is valid. +#[test] +fn has_valid_bls12_381_pairing_ratio() { + let mut rng = OsRng; + assert_valid_pairing_ratio::( + G1Affine::gen(&mut rng), + G2Affine::gen(&mut rng), + Fr::gen(&mut rng), + ); +} + /// Tests if proving and verifying ratio proof is correct. #[test] fn proving_and_verifying_ratio_proof_is_correct() { diff --git a/manta-trusted-setup/src/lib.rs b/manta-trusted-setup/src/lib.rs index cf390a1fa..60f92c197 100644 --- a/manta-trusted-setup/src/lib.rs +++ b/manta-trusted-setup/src/lib.rs @@ -25,6 +25,5 @@ extern crate alloc; pub mod groth16; pub mod mpc; -pub mod pairing; pub mod ratio; pub mod util; diff --git a/manta-trusted-setup/src/ratio.rs b/manta-trusted-setup/src/ratio.rs index 2755dd7a0..41fc38aa8 100644 --- a/manta-trusted-setup/src/ratio.rs +++ b/manta-trusted-setup/src/ratio.rs @@ -16,11 +16,15 @@ //! Ratio Proofs -use crate::pairing::{Pairing, PairingEngineExt}; -use ark_ec::{AffineCurve, ProjectiveCurve}; -use ark_ff::{PrimeField, UniformRand, Zero}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; -use manta_crypto::rand::{CryptoRng, RngCore}; +use manta_crypto::{ + arkworks::{ + ec::{AffineCurve, ProjectiveCurve}, + ff::{PrimeField, UniformRand, Zero}, + pairing::{Pairing, PairingEngineExt}, + serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}, + }, + rand::{CryptoRng, RngCore}, +}; /// Hash to Group Trait for Ratio Proof pub trait HashToGroup diff --git a/manta-trusted-setup/src/util.rs b/manta-trusted-setup/src/util.rs index 1c48b3d4a..08fec49eb 100644 --- a/manta-trusted-setup/src/util.rs +++ b/manta-trusted-setup/src/util.rs @@ -16,19 +16,21 @@ //! Utilities -use crate::{groth16::kzg, pairing::Pairing, ratio::HashToGroup}; +use crate::{groth16::kzg, ratio::HashToGroup}; use alloc::vec::Vec; -use ark_ec::{ - short_weierstrass_jacobian::GroupAffine, wnaf::WnafContext, AffineCurve, ProjectiveCurve, - SWModelParameters, -}; -use ark_ff::{BigInteger, Fp256, PrimeField, UniformRand, Zero}; -use ark_serialize::{CanonicalSerialize, Read, SerializationError, Write}; use ark_std::io; use blake2::{Blake2b512, Digest as Blake2Digest}; use byteorder::{BigEndian, ReadBytesExt}; use core::marker::PhantomData; -use manta_crypto::rand::{CryptoRng, OsRng, RngCore, SeedableRng}; +use manta_crypto::{ + arkworks::{ + ec::{wnaf::WnafContext, AffineCurve, ProjectiveCurve}, + ff::{BigInteger, PrimeField, UniformRand, Zero}, + pairing::Pairing, + serialize::{CanonicalSerialize, Read, SerializationError, Write}, + }, + rand::{OsRng, Sample, SeedableRng}, +}; use manta_util::{cfg_into_iter, cfg_iter, cfg_iter_mut, cfg_reduce, into_array_unchecked}; use rand_chacha::ChaCha20Rng; @@ -49,8 +51,7 @@ pub trait HasDistribution { /// interface for building a serialization over the type `T`. For deserialization see the /// [`Deserializer`] `trait`. /// -/// [`CanonicalSerialize`]: ark_serialize::CanonicalSerialize -/// [`CanonicalDeserialize`]: ark_serialize::CanonicalDeserialize +/// [`CanonicalDeserialize`]: manta_crypto::arkworks::serialize::CanonicalDeserialize pub trait Serializer { /// Serializes `item` in uncompressed form to the `writer` without performing any /// well-formedness checks. @@ -84,8 +85,7 @@ pub trait Serializer { /// interface for building a deserialization over the type `T`. For serialization see the /// [`Serializer`] `trait`. /// -/// [`CanonicalSerialize`]: ark_serialize::CanonicalSerialize -/// [`CanonicalDeserialize`]: ark_serialize::CanonicalDeserialize +/// [`CanonicalDeserialize`]: manta_crypto::arkworks::serialize::CanonicalDeserialize pub trait Deserializer { /// Deserialization Error Type type Error: Into; @@ -441,49 +441,3 @@ where base.mul_assign(*scalar); }) } - -/// Sampling Trait -pub trait Sample: Sized { - /// Returns a random value of type `Self`, sampled according to the given `distribution`, - /// generated from the `rng`. - fn sample(distribution: D, rng: &mut R) -> Self - where - R: CryptoRng + RngCore + ?Sized; - - /// Returns a random value of type `Self`, sampled according to the default distribution of - /// type `D`, generated from the `rng`. - #[inline] - fn gen(rng: &mut R) -> Self - where - D: Default, - R: CryptoRng + RngCore + ?Sized, - { - Self::sample(Default::default(), rng) - } -} - -impl

Sample for GroupAffine

-where - P: SWModelParameters, -{ - #[inline] - fn sample(_: (), rng: &mut R) -> Self - where - R: CryptoRng + RngCore + ?Sized, - { - as AffineCurve>::Projective::rand(rng).into_affine() - } -} - -impl

Sample for Fp256

-where - Fp256

: UniformRand, -{ - #[inline] - fn sample(_: (), rng: &mut R) -> Self - where - R: CryptoRng + RngCore + ?Sized, - { - Self::rand(rng) - } -} diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index 35e4e641e..c76967708 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -15,7 +15,8 @@ publish = false ### BEGIN HAKARI SECTION [dependencies] aes-gcm = { version = "0.9.4", features = ["aes", "alloc"] } -anyhow = { version = "1.0.58", features = ["std"] } +anyhow = { version = "1.0.59", features = ["std"] } +ark-serialize = { version = "0.3.0", default-features = false, features = ["ark-serialize-derive", "derive"] } bitflags = { version = "1.3.2" } blake3 = { version = "1.3.1", default-features = false, features = ["digest", "std"] } byteorder = { version = "1.4.3", features = ["std"] } @@ -29,7 +30,7 @@ futures-io = { version = "0.3.21", features = ["std"] } futures-sink = { version = "0.3.21", default-features = false, features = ["alloc", "std"] } futures-task = { version = "0.3.21", default-features = false, features = ["alloc", "std"] } futures-util = { version = "0.3.21", features = ["alloc", "async-await", "async-await-macro", "channel", "futures-channel", "futures-io", "futures-macro", "futures-sink", "io", "memchr", "sink", "slab", "std"] } -generic-array = { version = "0.14.5", default-features = false, features = ["more_lengths"] } +generic-array = { version = "0.14.6", default-features = false, features = ["more_lengths"] } getrandom = { version = "0.2.7", default-features = false, features = ["js", "js-sys", "std", "wasm-bindgen"] } indexmap = { version = "1.9.1", default-features = false, features = ["std"] } log = { version = "0.4.17", default-features = false, features = ["kv_unstable", "kv_unstable_std", "std", "value-bag"] } @@ -39,27 +40,26 @@ ppv-lite86 = { version = "0.2.16", default-features = false, features = ["simd", rand = { version = "0.8.5", features = ["alloc", "getrandom", "libc", "rand_chacha", "std", "std_rng"] } rand_chacha = { version = "0.3.1", default-features = false, features = ["std"] } rand_core = { version = "0.6.3", default-features = false, features = ["alloc", "getrandom", "std"] } -serde = { version = "1.0.140", features = ["alloc", "derive", "serde_derive", "std"] } +serde = { version = "1.0.141", features = ["alloc", "derive", "serde_derive", "std"] } serde_json = { version = "1.0.82", features = ["alloc", "std"] } sha2 = { version = "0.9.9", features = ["std"] } standback = { version = "0.2.17", default-features = false, features = ["std"] } subtle = { version = "2.4.1", default-features = false, features = ["i128"] } tide = { version = "0.16.0", default-features = false, features = ["async-h1", "h1-server"] } -tracing = { version = "0.1.36", default-features = false, features = ["attributes", "tracing-attributes"] } url = { version = "2.2.2", default-features = false, features = ["serde"] } web-sys = { version = "0.3.59", default-features = false, features = ["BinaryType", "Blob", "CloseEvent", "DomException", "Event", "EventTarget", "MessageEvent", "WebSocket", "console"] } zeroize = { version = "1.5.7", default-features = false, features = ["alloc", "zeroize_derive"] } [build-dependencies] -anyhow = { version = "1.0.58", features = ["std"] } +anyhow = { version = "1.0.59", features = ["std"] } blake3 = { version = "1.3.1", default-features = false, features = ["digest", "std"] } cc = { version = "1.0.73", default-features = false, features = ["jobserver", "parallel"] } crypto-common = { version = "0.1.6", default-features = false, features = ["std"] } digest-93f6ce9d446188ac = { package = "digest", version = "0.10.3", features = ["alloc", "block-buffer", "core-api", "mac", "std", "subtle"] } -generic-array = { version = "0.14.5", default-features = false, features = ["more_lengths"] } +generic-array = { version = "0.14.6", default-features = false, features = ["more_lengths"] } log = { version = "0.4.17", default-features = false, features = ["kv_unstable", "kv_unstable_std", "std", "value-bag"] } num-traits = { version = "0.2.15", features = ["i128", "libm", "std"] } -serde = { version = "1.0.140", features = ["alloc", "derive", "serde_derive", "std"] } +serde = { version = "1.0.141", features = ["alloc", "derive", "serde_derive", "std"] } standback = { version = "0.2.17", default-features = false, features = ["std"] } subtle = { version = "2.4.1", default-features = false, features = ["i128"] } syn = { version = "1.0.98", features = ["clone-impls", "derive", "extra-traits", "fold", "full", "parsing", "printing", "proc-macro", "quote", "visit", "visit-mut"] }