mod currency;
mod read;
mod write;
pub use currency::{Currency, ExpectedCurrency, G1_CURRENCY, G1_TEST_CURRENCY};
pub use read::{read_dewif_content, read_dewif_log_n, read_dewif_meta, DewifReadError};
pub use write::create_dewif_v1;
#[allow(deprecated)]
pub use write::create_dewif_v1_legacy;
#[cfg(feature = "bip32-ed25519")]
use crate::keys::{KeyPair as _, KeysAlgo};
use crate::scrypt::{params::ScryptParams, scrypt};
use crate::seeds::{Seed42, Seed64};
use crate::{hashs::Hash, rand::UnspecifiedRandError};
const HEADERS_LEN: usize = 8;
static VERSION_V1: &[u8] = &[0, 0, 0, 1];
const V1_CHECKSUM_LEN: usize = 8;
const V1_NONCE_LEN: usize = 12;
const V1_CLEAR_HEADERS_LEN: usize = 2 + V1_NONCE_LEN;
const V1_ED25519_ENCRYPTED_BYTES_LEN: usize = 64;
const V1_ED25519_DATA_LEN: usize = V1_ED25519_ENCRYPTED_BYTES_LEN + V1_CLEAR_HEADERS_LEN; const V1_ED25519_BYTES_LEN: usize = HEADERS_LEN + V1_ED25519_DATA_LEN; const V1_ED25519_UNENCRYPTED_BYTES_LEN: usize =
V1_ED25519_BYTES_LEN - V1_ED25519_ENCRYPTED_BYTES_LEN;
const V1_BIP32_ED25519_ENCRYPTED_BYTES_LEN: usize = 42;
const V1_BIP32_ED25519_DATA_LEN: usize =
V1_BIP32_ED25519_ENCRYPTED_BYTES_LEN + V1_CLEAR_HEADERS_LEN;
const V1_BIP32_ED25519_BYTES_LEN: usize = HEADERS_LEN + V1_BIP32_ED25519_DATA_LEN;
const V1_BIP32_ED25519_UNENCRYPTED_BYTES_LEN: usize =
V1_BIP32_ED25519_BYTES_LEN - V1_BIP32_ED25519_ENCRYPTED_BYTES_LEN;
type Checksum = [u8; V1_CHECKSUM_LEN];
type Nonce = [u8; V1_NONCE_LEN];
#[derive(Debug, PartialEq)]
pub struct DewifContent {
pub meta: DewifMeta,
pub payload: DewifPayload,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DewifMeta {
pub algo: KeysAlgo,
pub currency: Currency,
pub log_n: u8,
pub version: u32,
}
#[derive(Debug, PartialEq)]
pub enum DewifPayload {
Ed25519(crate::keys::ed25519::Ed25519KeyPair),
Bip32Ed25519(crate::mnemonic::Mnemonic),
}
pub fn change_dewif_passphrase(
file_content: &str,
old_passphrase: &str,
new_passphrase: &str,
) -> Result<String, DewifReadError> {
let DewifContent {
meta:
DewifMeta {
algo: _,
currency,
log_n,
version,
},
payload,
} = read_dewif_content(ExpectedCurrency::Any, file_content, old_passphrase)?;
if version == 1 {
let mut bytes = base64::decode(file_content).map_err(DewifReadError::InvalidBase64Str)?;
match payload {
DewifPayload::Ed25519(kp) => {
let seed = read::get_dewif_seed_unchecked(&mut bytes[8..], old_passphrase);
write::write_dewif_v1_ed25519(
currency,
log_n,
new_passphrase,
&kp.public_key(),
&seed,
)
.map_err(|_| DewifReadError::UnspecifiedRandError)
}
#[cfg(feature = "bip32-ed25519")]
DewifPayload::Bip32Ed25519(mnemonic) => {
write::write_dewif_v1_bip_ed25519(currency, log_n, new_passphrase, &mnemonic)
.map_err(|_| DewifReadError::UnspecifiedRandError)
}
}
} else {
Err(DewifReadError::UnsupportedVersion { actual: version })
}
}
fn compute_checksum(nonce: &Nonce, language_code: u8, mnemonic_entropy: &[u8]) -> Checksum {
let mut cs_bytes = [0u8; V1_CHECKSUM_LEN];
let hash = crate::hashs::Hash::compute_multipart(&[
&nonce[..],
&[language_code, mnemonic_entropy.len() as u8],
mnemonic_entropy,
]);
cs_bytes.copy_from_slice(&hash.0[..8]);
cs_bytes
}
#[cfg(not(test))]
fn gen_nonce() -> Result<Nonce, UnspecifiedRandError> {
let mut nonce = [0u8; V1_NONCE_LEN];
crate::rand::gen_random_bytes(&mut nonce[..])?;
Ok(nonce)
}
#[cfg(test)]
#[allow(clippy::unnecessary_wraps)]
fn gen_nonce() -> Result<Nonce, UnspecifiedRandError> {
Ok([42u8; V1_NONCE_LEN])
}
fn gen_xor_seed42(log_n: u8, nonce: Nonce, passphrase: &str) -> Seed42 {
let salt = Hash::compute_multipart(&[b"dewif", &nonce[..], passphrase.as_bytes()]);
let mut seed_bytes = [0u8; 42];
scrypt(
passphrase.as_bytes(),
salt.as_ref(),
&ScryptParams { log_n, r: 16, p: 1 },
&mut seed_bytes,
);
Seed42::new(seed_bytes)
}
fn gen_xor_seed64(log_n: u8, nonce: Nonce, passphrase: &str) -> Seed64 {
let salt = Hash::compute_multipart(&[b"dewif", &nonce[..], passphrase.as_bytes()]);
let mut seed_bytes = [0u8; 64];
scrypt(
passphrase.as_bytes(),
salt.as_ref(),
&ScryptParams { log_n, r: 16, p: 1 },
&mut seed_bytes,
);
Seed64::new(seed_bytes)
}
fn read_nonce(bytes: &[u8]) -> Nonce {
let mut nonce = [0u8; V1_NONCE_LEN];
nonce.copy_from_slice(bytes);
nonce
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bases::b58::ToBase58 as _;
use crate::keys::ed25519::KeyPairFromSeed32Generator;
use crate::seeds::Seed32;
use unwrap::unwrap;
#[test]
fn dewif_v1() {
let written_keypair = KeyPairFromSeed32Generator::generate(Seed32::new([0u8; 32]));
let currency = Currency::from(G1_TEST_CURRENCY);
let dewif_content_str = unwrap!(write::write_dewif_v1_ed25519(
currency,
12,
"toto",
&written_keypair.public_key(),
&written_keypair.seed(),
));
let dewif_content = unwrap!(read_dewif_content(
ExpectedCurrency::Specific(currency),
&dewif_content_str,
"toto"
));
assert_eq!(
DewifPayload::Ed25519(written_keypair.clone()),
dewif_content.payload,
);
let new_dewif_content =
unwrap!(change_dewif_passphrase(&dewif_content_str, "toto", "titi"));
let dewif_content = unwrap!(read_dewif_content(
ExpectedCurrency::Specific(currency),
&new_dewif_content,
"titi"
));
assert_eq!(
DewifPayload::Ed25519(written_keypair),
dewif_content.payload,
);
}
#[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 = unwrap!(write::write_dewif_v1_ed25519(
currency,
12,
"toto",
&written_keypair.public_key(),
&written_keypair.seed(),
));
let dewif_bytes_mut = unsafe { dewif_content.as_bytes_mut() };
dewif_bytes_mut[13] = 0x52;
if let Err(DewifReadError::CorruptedContent) =
read_dewif_content(ExpectedCurrency::Specific(currency), &dewif_content, "toto")
{
Ok(())
} else {
panic!("dewif content must be corrupted.")
}
}
#[test]
fn dewif_v1_bip32() -> Result<(), crate::mnemonic::MnemonicError> {
let mnemonic = crate::mnemonic::Mnemonic::from_phrase(
"crop cash unable insane eight faith inflict route frame loud box vibrant",
crate::mnemonic::Language::English,
)?;
let mnemonic_copy = crate::mnemonic::Mnemonic::from_phrase(
"crop cash unable insane eight faith inflict route frame loud box vibrant",
crate::mnemonic::Language::English,
)?;
let currency = Currency::from(G1_TEST_CURRENCY);
let dewif_content_str = unwrap!(write::write_dewif_v1_bip_ed25519(
currency, 12, "toto", &mnemonic
));
let dewif_content = unwrap!(read_dewif_content(
ExpectedCurrency::Specific(currency),
&dewif_content_str,
"toto"
));
assert_eq!(DewifPayload::Bip32Ed25519(mnemonic), dewif_content.payload,);
let new_dewif_content_str =
unwrap!(change_dewif_passphrase(&dewif_content_str, "toto", "titi"));
let new_dewif_content = unwrap!(read_dewif_content(
ExpectedCurrency::Specific(currency),
&new_dewif_content_str,
"titi"
));
assert_eq!(
DewifPayload::Bip32Ed25519(mnemonic_copy),
new_dewif_content.payload,
);
Ok(())
}
#[test]
#[allow(deprecated)]
fn dewif_v1_legacy() -> Result<(), DewifReadError> {
let currency = Currency::from(G1_CURRENCY);
let dewif = unwrap!(create_dewif_v1_legacy(
currency,
12,
"pass".to_owned(),
"salt".to_owned(),
"toto"
));
if let DewifContent {
payload: DewifPayload::Ed25519(key_pair),
..
} = read_dewif_content(ExpectedCurrency::Specific(currency), &dewif, "toto")?
{
assert_eq!(
"3YumN7F7D8c2hmkHLHf3ZD8wc3tBHiECEK9zLPkaJtAF",
&key_pair.public_key().to_base58()
);
} else {
panic!("corrupted dewif");
}
Ok(())
}
}