Skip to content
Snippets Groups Projects
Commit d260efcf authored by Éloïs's avatar Éloïs
Browse files

[feat] dewif: add field currency

parent aa3213a5
No related branches found
No related tags found
No related merge requests found
......@@ -18,8 +18,9 @@
//! # Write ed25519 key-pair in DEWIF file
//!
//! ```
//! use dup_crypto::dewif::write_dewif_v1_content;
//! use dup_crypto::dewif::{Currency, G1_TEST_CURRENCY, write_dewif_v1_content};
//! use dup_crypto::keys::ed25519::{KeyPairFromSaltedPasswordGenerator, SaltedPassword};
//! use std::num::NonZeroU32;
//!
//! // Get user credentials (from cli prompt or gui)
//! let credentials = SaltedPassword::new("user salt".to_owned(), "user password".to_owned());
......@@ -31,10 +32,10 @@
//! let encryption_passphrase = "toto titi tata";
//!
//! // Serialize keypair in DEWIF format
//! let dewif_content = write_dewif_v1_content(&keypair, encryption_passphrase);
//! let dewif_content = write_dewif_v1_content(Currency::from(G1_TEST_CURRENCY), &keypair, encryption_passphrase);
//!
//! assert_eq!(
//! "AAAAATHfJ3vTvEPcXm22NwhJtnNdGuSjikpSYIMgX96Z9xVT0y8GoIlBL1HaxaWpu0jVDfuwtCGSP9bu2pj6HGbuYVA=",
//! "AAAAARAAAAEx3yd707xD3F5ttjcISbZzXRrko4pKUmCDIF/emfcVU9MvBqCJQS9R2sWlqbtI1Q37sLQhkj/W7tqY+hxm7mFQ",
//! dewif_content
//! )
//! ```
......@@ -42,18 +43,23 @@
//! # Read DEWIF file
//!
//! ```
//! use dup_crypto::dewif::read_dewif_file_content;
//! use dup_crypto::dewif::{Currency, ExpectedCurrency, read_dewif_file_content};
//! use dup_crypto::keys::{KeyPair, Signator};
//! use std::num::NonZeroU32;
//! use std::str::FromStr;
//!
//! // Get DEWIF file content (Usually from disk)
//! let dewif_file_content = "AAAAATHfJ3vTvEPcXm22NwhJtnNdGuSjikpSYIMgX96Z9xVT0y8GoIlBL1HaxaWpu0jVDfuwtCGSP9bu2pj6HGbuYVA=";
//! let dewif_file_content = "AAAAARAAAAEx3yd707xD3F5ttjcISbZzXRrko4pKUmCDIF/emfcVU9MvBqCJQS9R2sWlqbtI1Q37sLQhkj/W7tqY+hxm7mFQ";
//!
//! // Get user passphrase for DEWIF decryption (from cli prompt or gui)
//! let encryption_passphrase = "toto titi tata";
//!
//! // Expected currency
//! let expected_currency = ExpectedCurrency::Specific(Currency::from_str("g1-test").expect("unknown currency"));
//!
//! // Read DEWIF file content
//! // If the file content is correct, we get a key-pair iterator.
//! let mut key_pair_iter = read_dewif_file_content(dewif_file_content, encryption_passphrase)?;
//! let mut key_pair_iter = read_dewif_file_content(expected_currency, dewif_file_content, encryption_passphrase)?;
//!
//! // Get first key-pair
//! let key_pair = key_pair_iter
......@@ -81,9 +87,11 @@
//! ```
//!
mod currency;
mod read;
mod write;
pub use currency::{Currency, ExpectedCurrency, G1_CURRENCY, G1_TEST_CURRENCY};
pub use read::{read_dewif_file_content, DewifReadError};
pub use write::{write_dewif_v1_content, write_dewif_v2_content};
......@@ -92,17 +100,17 @@ use crate::seeds::Seed32;
use arrayvec::ArrayVec;
use unwrap::unwrap;
const VERSION_BYTES: usize = 4;
const UNENCRYPTED_BYTES_LEN: usize = 8;
// v1
static VERSION_V1: &[u8] = &[0, 0, 0, 1];
const V1_BYTES_LEN: usize = 68;
const V1_BYTES_LEN: usize = 72;
const V1_ENCRYPTED_BYTES_LEN: usize = 64;
const V1_AES_BLOCKS_COUNT: usize = 4;
// v2
static VERSION_V2: &[u8] = &[0, 0, 0, 2];
const V2_BYTES_LEN: usize = 132;
const V2_BYTES_LEN: usize = 136;
const V2_ENCRYPTED_BYTES_LEN: usize = 128;
fn gen_aes_seed(passphrase: &str) -> Seed32 {
......@@ -133,10 +141,12 @@ mod tests {
#[test]
fn dewif_v1() {
let written_keypair = KeyPairFromSeed32Generator::generate(Seed32::new([0u8; 32]));
let currency = Currency::from(G1_TEST_CURRENCY);
let dewif_content = write_dewif_v1_content(&written_keypair, "toto");
let dewif_content = write_dewif_v1_content(currency, &written_keypair, "toto");
let mut keypairs_iter = read_dewif_file_content(&dewif_content, "toto")
let mut keypairs_iter =
read_dewif_file_content(ExpectedCurrency::Specific(currency), &dewif_content, "toto")
.expect("dewif content must be readed successfully");
let keypair_read = keypairs_iter.next().expect("Must read one keypair");
......@@ -146,15 +156,16 @@ mod tests {
#[test]
fn dewif_v1_corrupted() -> Result<(), ()> {
let written_keypair = KeyPairFromSeed32Generator::generate(Seed32::new([0u8; 32]));
let currency = Currency::from(G1_TEST_CURRENCY);
let mut dewif_content = write_dewif_v1_content(&written_keypair, "toto");
let mut dewif_content = write_dewif_v1_content(currency, &written_keypair, "toto");
// Corrupt one byte in dewif_content
let dewif_bytes_mut = unsafe { dewif_content.as_bytes_mut() };
dewif_bytes_mut[13] = 0x52;
if let Err(DewifReadError::CorruptedContent) =
read_dewif_file_content(&dewif_content, "toto")
read_dewif_file_content(ExpectedCurrency::Specific(currency), &dewif_content, "toto")
{
Ok(())
} else {
......@@ -166,10 +177,13 @@ mod tests {
fn dewif_v2() {
let written_keypair1 = KeyPairFromSeed32Generator::generate(Seed32::new([0u8; 32]));
let written_keypair2 = KeyPairFromSeed32Generator::generate(Seed32::new([1u8; 32]));
let currency = Currency::from(G1_TEST_CURRENCY);
let dewif_content = write_dewif_v2_content(&written_keypair1, &written_keypair2, "toto");
let dewif_content =
write_dewif_v2_content(currency, &written_keypair1, &written_keypair2, "toto");
let mut keypairs_iter = read_dewif_file_content(&dewif_content, "toto")
let mut keypairs_iter =
read_dewif_file_content(ExpectedCurrency::Specific(currency), &dewif_content, "toto")
.expect("dewif content must be readed successfully");
let keypair1_read = keypairs_iter.next().expect("Must read one keypair");
let keypair2_read = keypairs_iter.next().expect("Must read one keypair");
......@@ -182,16 +196,17 @@ mod tests {
fn dewif_v2_corrupted() -> Result<(), ()> {
let written_keypair1 = KeyPairFromSeed32Generator::generate(Seed32::new([0u8; 32]));
let written_keypair2 = KeyPairFromSeed32Generator::generate(Seed32::new([1u8; 32]));
let currency = Currency::from(G1_TEST_CURRENCY);
let mut dewif_content =
write_dewif_v2_content(&written_keypair1, &written_keypair2, "toto");
write_dewif_v2_content(currency, &written_keypair1, &written_keypair2, "toto");
// Corrupt one byte in dewif_content
let dewif_bytes_mut = unsafe { dewif_content.as_bytes_mut() };
dewif_bytes_mut[13] = 0x52;
if let Err(DewifReadError::CorruptedContent) =
read_dewif_file_content(&dewif_content, "toto")
read_dewif_file_content(ExpectedCurrency::Specific(currency), &dewif_content, "toto")
{
Ok(())
} else {
......
// Copyright (C) 2020 Éloïs SANCHEZ.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Define DEWIF currency field
use std::fmt::Display;
use std::num::NonZeroU32;
use std::str::FromStr;
/// Ğ1 currency
pub const G1_CURRENCY: u32 = 1;
const G1_CURRENCY_STR: &str = "g1";
/// Ğ1-Test currency
pub const G1_TEST_CURRENCY: u32 = 268_435_457;
const G1_TEST_CURRENCY_STR: &str = "g1-test";
#[derive(Copy, Clone, Debug, PartialEq)]
/// Expected DEWIF currency
pub enum ExpectedCurrency {
/// Expected any currency (no limitation)
Any,
/// Expected specific currency
Specific(Currency),
}
impl ExpectedCurrency {
pub(crate) fn is_valid(self, currency: Currency) -> bool {
match self {
Self::Any => true,
Self::Specific(expected_currency) => expected_currency == currency,
}
}
}
impl Display for ExpectedCurrency {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
Self::Any => write!(f, "Any"),
Self::Specific(expected_currency) => expected_currency.fmt(f),
}
}
}
/// DEWIF currency
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Currency(Option<NonZeroU32>);
impl Currency {
/// None currency
pub fn none() -> Self {
Currency(None)
}
}
impl Display for Currency {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
if let Some(currency_code) = self.0 {
match currency_code.get() {
G1_CURRENCY => write!(f, "{}", G1_CURRENCY_STR),
G1_TEST_CURRENCY => write!(f, "{}", G1_TEST_CURRENCY_STR),
other => write!(f, "{}", other),
}
} else {
write!(f, "None")
}
}
}
impl From<u32> for Currency {
fn from(source: u32) -> Self {
Self(NonZeroU32::new(source))
}
}
impl Into<u32> for Currency {
fn into(self) -> u32 {
if let Some(currency_code) = self.0 {
currency_code.get()
} else {
0u32
}
}
}
/// Unknown currency name
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct UnknownCurrencyName;
impl FromStr for Currency {
type Err = UnknownCurrencyName;
fn from_str(source: &str) -> Result<Self, Self::Err> {
match source {
"" => Ok(Currency(None)),
G1_CURRENCY_STR => Ok(Currency(NonZeroU32::new(G1_CURRENCY))),
G1_TEST_CURRENCY_STR => Ok(Currency(NonZeroU32::new(G1_TEST_CURRENCY))),
_ => Err(UnknownCurrencyName),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_expected_currency() {
assert_eq!(
"None",
&format!("{}", ExpectedCurrency::Specific(Currency::from(0))),
);
assert_eq!("Any", &format!("{}", ExpectedCurrency::Any));
}
#[test]
fn display_currency() {
assert_eq!(G1_CURRENCY_STR, &format!("{}", Currency::from(G1_CURRENCY)),);
assert_eq!(
G1_TEST_CURRENCY_STR,
&format!("{}", Currency::from(G1_TEST_CURRENCY)),
);
assert_eq!("42", &format!("{}", Currency::from(42)),);
assert_eq!("None", &format!("{}", Currency::from(0)),);
}
#[test]
fn currency_from_str() {
assert_eq!(
Currency::from(G1_CURRENCY),
Currency::from_str(G1_CURRENCY_STR).expect("unknown currency"),
);
assert_eq!(
Currency::from(G1_TEST_CURRENCY),
Currency::from_str(G1_TEST_CURRENCY_STR).expect("unknown currency"),
);
assert_eq!(
Err(UnknownCurrencyName),
Currency::from_str("unknown currency"),
);
}
}
......@@ -15,6 +15,7 @@
//! Read [DEWIF](https://git.duniter.org/nodes/common/doc/blob/dewif/rfc/0013_Duniter_Encrypted_Wallet_Import_Format.md) file content
use super::{Currency, ExpectedCurrency};
use crate::keys::ed25519::{KeyPairFromSeed32Generator, PublicKey, PUBKEY_SIZE_IN_BYTES};
use crate::keys::KeyPairEnum;
use crate::seeds::Seed32;
......@@ -32,22 +33,30 @@ type KeyPairsIter = arrayvec::IntoIter<[KeyPairEnum; MAX_KEYPAIRS_COUNT]>;
#[derive(Clone, Debug, Error)]
pub enum DewifReadError {
/// DEWIF file content is corrupted
#[error("DEWIF file content is corrupted")]
#[error("DEWIF file content is corrupted.")]
CorruptedContent,
/// Invalid base 64 string
#[error("Invalid base 64 string: {0}")]
#[error("Invalid base 64 string: {0}.")]
InvalidBase64Str(base64::DecodeError),
/// Invalid format
#[error("Invalid format")]
#[error("Invalid format.")]
InvalidFormat,
/// Too short content
#[error("Too short content")]
#[error("Too short content.")]
TooShortContent,
/// Too long content
#[error("Too long content")]
#[error("Too long content.")]
TooLongContent,
/// Unexpected currency
#[error("Unexpected currency '{actual}', expected: '{expected}'.")]
UnexpectedCurrency {
/// Expected currency
expected: ExpectedCurrency,
/// Actual currency
actual: Currency,
},
/// Unsupported version
#[error("Version {actual:?} is not supported. Supported versions: [1, 2].")]
#[error("Version {actual} is not supported. Supported versions: [1, 2].")]
UnsupportedVersion {
/// Actual version
actual: u32,
......@@ -56,24 +65,33 @@ pub enum DewifReadError {
/// read dewif file content with user passphrase
pub fn read_dewif_file_content(
expected_currency: ExpectedCurrency,
file_content: &str,
passphrase: &str,
) -> Result<impl Iterator<Item = KeyPairEnum>, DewifReadError> {
let mut bytes = base64::decode(file_content).map_err(DewifReadError::InvalidBase64Str)?;
if bytes.len() < 4 {
if bytes.len() < 8 {
return Err(DewifReadError::TooShortContent);
}
let version = byteorder::BigEndian::read_u32(&bytes[0..4]);
let currency = Currency::from(byteorder::BigEndian::read_u32(&bytes[4..8]));
if !expected_currency.is_valid(currency) {
return Err(DewifReadError::UnexpectedCurrency {
expected: expected_currency,
actual: currency,
});
}
match version {
1 => Ok({
let mut array_keypairs = KeyPairsArray::new();
array_keypairs.push(read_dewif_v1(&mut bytes[4..], passphrase)?);
array_keypairs.push(read_dewif_v1(&mut bytes[8..], passphrase)?);
array_keypairs.into_iter()
}),
2 => read_dewif_v2(&mut bytes[4..], passphrase),
2 => read_dewif_v2(&mut bytes[8..], passphrase),
other_version => Err(DewifReadError::UnsupportedVersion {
actual: other_version,
}),
......@@ -142,7 +160,8 @@ mod tests {
#[test]
fn read_unsupported_version() -> Result<(), ()> {
if let Err(DewifReadError::UnsupportedVersion { .. }) = read_dewif_file_content(
"ABAAAfKjMzOFfhwgypF3mAx0QDXyozMzhX4cIMqRd5gMdEA1WZwQjCR49iZDK2QhYfdTbPz9AGB01edt4iRSzdTp3c4=",
ExpectedCurrency::Any,
"ABAAAb30ng3kI9QGMbR7TYCqPhS99J4N5CPUBjG0e02Aqj4U1UmOemI6pweNm1Ab1AR4V6ZWFnwkkp9QPxppVeMv7aaLWdop",
"toto"
) {
Ok(())
......@@ -153,7 +172,9 @@ mod tests {
#[test]
fn read_too_short_content() -> Result<(), ()> {
if let Err(DewifReadError::TooShortContent) = read_dewif_file_content("AAA", "toto") {
if let Err(DewifReadError::TooShortContent) =
read_dewif_file_content(ExpectedCurrency::Any, "AAA", "toto")
{
Ok(())
} else {
panic!("Read must be fail with error TooShortContent.")
......@@ -161,19 +182,39 @@ mod tests {
}
#[test]
fn tmp() {
fn read_unexpected_currency() -> Result<(), ()> {
if let Err(DewifReadError::UnexpectedCurrency { .. }) = read_dewif_file_content(
ExpectedCurrency::Specific(Currency::from(42)),
"AAAAARAAAAEx3yd707xD3F5ttjcISbZzXRrko4pKUmCDIF/emfcVU9MvBqCJQS9R2sWlqbtI1Q37sLQhkj/W7tqY+hxm7mFQ",
"toto titi tata"
) {
Ok(())
} else {
panic!("Read must be fail with error UnexpectedCurrency.")
}
}
#[test]
fn read_ok() {
use crate::dewif::Currency;
use crate::keys::{KeyPair, Signator};
use std::str::FromStr;
// Get DEWIF file content (Usually from disk)
let dewif_file_content = "AAAAATHfJ3vTvEPcXm22NwhJtnNdGuSjikpSYIMgX96Z9xVT0y8GoIlBL1HaxaWpu0jVDfuwtCGSP9bu2pj6HGbuYVA=";
let dewif_file_content = "AAAAARAAAAEx3yd707xD3F5ttjcISbZzXRrko4pKUmCDIF/emfcVU9MvBqCJQS9R2sWlqbtI1Q37sLQhkj/W7tqY+hxm7mFQ";
// Get user passphrase for DEWIF decryption (from cli prompt or gui)
let encryption_passphrase = "toto titi tata";
// Expected currency
let expected_currency =
ExpectedCurrency::Specific(Currency::from_str("g1-test").expect("unknown currency"));
// Read DEWIF file content
// If the file content is correct, we get a key-pair iterator.
let mut key_pair_iter = read_dewif_file_content(dewif_file_content, encryption_passphrase)
.expect("invalid DEWIF file.");
let mut key_pair_iter =
read_dewif_file_content(expected_currency, dewif_file_content, encryption_passphrase)
.expect("invalid DEWIF file");
// Get first key-pair
let key_pair = key_pair_iter
......
......@@ -15,38 +15,52 @@
//! Write [DEWIF](https://git.duniter.org/nodes/common/doc/blob/dewif/rfc/0013_Duniter_Encrypted_Wallet_Import_Format.md) file content
use super::Currency;
use crate::keys::ed25519::Ed25519KeyPair;
use arrayvec::ArrayVec;
use unwrap::unwrap;
/// Write dewif v1 file content with user passphrase
pub fn write_dewif_v1_content(keypair: &Ed25519KeyPair, passphrase: &str) -> String {
pub fn write_dewif_v1_content(
currency: Currency,
keypair: &Ed25519KeyPair,
passphrase: &str,
) -> String {
let mut bytes = ArrayVec::<[u8; super::V1_BYTES_LEN]>::new();
unwrap!(bytes.try_extend_from_slice(super::VERSION_V1));
let currency_code: u32 = currency.into();
unwrap!(bytes.try_extend_from_slice(&currency_code.to_be_bytes()));
unwrap!(bytes.try_extend_from_slice(keypair.seed().as_ref()));
unwrap!(bytes.try_extend_from_slice(keypair.pubkey().as_ref()));
let cipher = crate::aes256::new_cipher(super::gen_aes_seed(passphrase));
crate::aes256::encrypt::encrypt_n_blocks(&cipher, &mut bytes[4..], super::V1_AES_BLOCKS_COUNT);
crate::aes256::encrypt::encrypt_n_blocks(
&cipher,
&mut bytes[super::UNENCRYPTED_BYTES_LEN..],
super::V1_AES_BLOCKS_COUNT,
);
base64::encode(bytes.as_ref())
}
/// Write dewif v2 file content with user passphrase
pub fn write_dewif_v2_content(
currency: Currency,
keypair1: &Ed25519KeyPair,
keypair2: &Ed25519KeyPair,
passphrase: &str,
) -> String {
let mut bytes = ArrayVec::<[u8; super::V2_BYTES_LEN]>::new();
unwrap!(bytes.try_extend_from_slice(super::VERSION_V2));
let currency_code: u32 = currency.into();
unwrap!(bytes.try_extend_from_slice(&currency_code.to_be_bytes()));
unwrap!(bytes.try_extend_from_slice(keypair1.seed().as_ref()));
unwrap!(bytes.try_extend_from_slice(keypair1.pubkey().as_ref()));
unwrap!(bytes.try_extend_from_slice(keypair2.seed().as_ref()));
unwrap!(bytes.try_extend_from_slice(keypair2.pubkey().as_ref()));
let cipher = crate::aes256::new_cipher(super::gen_aes_seed(passphrase));
crate::aes256::encrypt::encrypt_8_blocks(&cipher, &mut bytes[super::VERSION_BYTES..]);
crate::aes256::encrypt::encrypt_8_blocks(&cipher, &mut bytes[super::UNENCRYPTED_BYTES_LEN..]);
base64::encode(bytes.as_ref())
}
......@@ -57,15 +71,44 @@ mod tests {
use super::*;
use crate::keys::ed25519::KeyPairFromSeed32Generator;
use crate::seeds::Seed32;
use std::str::FromStr;
#[test]
fn write_dewif_v1() {
let keypair = KeyPairFromSeed32Generator::generate(Seed32::new([0u8; 32]));
let dewif_content = write_dewif_v1_content(&keypair, "toto");
let dewif_content = write_dewif_v1_content(Currency::none(), &keypair, "toto");
println!("{}", dewif_content);
assert_eq!(
"AAAAAb30ng3kI9QGMbR7TYCqPhS99J4N5CPUBjG0e02Aqj4UElionaHOt0kv+eaWgGSGkrP1LQfuwivuvg7+9n0gd18=",
"AAAAAQAAAAC99J4N5CPUBjG0e02Aqj4UvfSeDeQj1AYxtHtNgKo+FBJYqJ2hzrdJL/nmloBkhpKz9S0H7sIr7r4O/vZ9IHdf",
dewif_content
)
}
#[test]
fn write_ok() {
use crate::dewif::write_dewif_v1_content;
use crate::keys::ed25519::{KeyPairFromSaltedPasswordGenerator, SaltedPassword};
// Get user credentials (from cli prompt or gui)
let credentials = SaltedPassword::new("user salt".to_owned(), "user password".to_owned());
// Generate ed25519 keypair
let keypair =
KeyPairFromSaltedPasswordGenerator::with_default_parameters().generate(credentials);
// Get user passphrase for DEWIF encryption
let encryption_passphrase = "toto titi tata";
// Expected currency
let expected_currency = Currency::from_str("g1-test").expect("unknown currency");
// Serialize keypair in DEWIF format
let dewif_content =
write_dewif_v1_content(expected_currency, &keypair, encryption_passphrase);
assert_eq!(
"AAAAARAAAAEx3yd707xD3F5ttjcISbZzXRrko4pKUmCDIF/emfcVU9MvBqCJQS9R2sWlqbtI1Q37sLQhkj/W7tqY+hxm7mFQ",
dewif_content
)
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment