use std::convert::TryFrom;
use bip39::{Mnemonic, Seed};
use crypto_box::SecretKey as EncryptionSecretKey;
use ed25519_dalek_bip32::{DerivationPath, ExtendedSecretKey};
use sha2::{Digest, Sha256};
use super::encrypting_keypair::EncryptingKeypair;
use crate::error::{CryptoError, KeyDeriveError};
use crate::identity::seed::extend_seed;
use crate::identity::signing_keypair::SigningKeypair;
use crate::identity::{MnemonicPhrase, MNEMONIC_LEN};
use crate::utils;
fn signing_key_path(forest_index: u64) -> String {
format!("m/5721156'/0'/{forest_index}'")
}
fn encryption_key_path(forest_index: u64, index: u64) -> String {
format!("m/5721156'/1'/{forest_index}'/{index}'")
}
fn single_use_encryption_key_path(index: u64) -> String {
format!("m/5721156'/2'/{index}'")
}
fn backup_key_path() -> String {
"m/5721156'/3'".to_string()
}
#[derive(Debug)]
pub struct Identity {
extended_seckey: ExtendedSecretKey,
words: MnemonicPhrase,
}
impl TryFrom<&MnemonicPhrase> for Identity {
type Error = CryptoError;
fn try_from(mnemonic_phrase: &MnemonicPhrase) -> Result<Self, Self::Error> {
Self::from_mnemonic(utils::new_mnemonic_from_phrase(&mnemonic_phrase.join(" "))?)
}
}
impl TryFrom<&[u8]> for Identity {
type Error = CryptoError;
fn try_from(entropy: &[u8]) -> Result<Self, CryptoError> {
if (entropy.len() * 8) < 128 {
return Err(CryptoError::EntropyTooLow);
}
let mut hasher = Sha256::new();
hasher.update(entropy);
let hashed_entropy = hasher.finalize();
Self::from_mnemonic(utils::new_mnemonic_from_entropy(&hashed_entropy[0..16])?)
}
}
impl Identity {
pub fn forest_keypair(&self, forest_index: u64) -> Result<SigningKeypair, KeyDeriveError> {
tracing::debug!("deriving forest keypair");
self.derive_forest_keypair(&signing_key_path(forest_index))
}
pub fn encryption_keypair(
&self,
forest_index: u64,
index: u64,
) -> Result<EncryptingKeypair, KeyDeriveError> {
tracing::debug!("deriving encryption keypair");
self.derive_encryption_keypair(&encryption_key_path(forest_index, index))
}
pub fn single_use_encryption_keypair(
&self,
index: u64,
) -> Result<EncryptingKeypair, KeyDeriveError> {
self.derive_encryption_keypair(&single_use_encryption_key_path(index))
}
pub fn backup_keypair(&self) -> Result<EncryptingKeypair, KeyDeriveError> {
self.derive_encryption_keypair(&backup_key_path())
}
pub fn get_mnemonic(&self) -> MnemonicPhrase {
self.words.clone()
}
#[tracing::instrument(level = "debug", skip_all)]
fn from_mnemonic(mnemonic: Mnemonic) -> Result<Self, CryptoError> {
tracing::debug!("Deriving Identity from mnemonic");
let passphrase = "";
let seed = Seed::new(&mnemonic, passphrase);
let mut output_key_material = [0u8; 96];
extend_seed(seed.as_bytes(), &mut output_key_material);
let extended_secret_key =
ExtendedSecretKey::from_seed(output_key_material.as_slice()).unwrap();
Ok(Identity {
extended_seckey: extended_secret_key,
words: mnemonic
.phrase()
.split(' ')
.map(|word| word.to_owned())
.collect::<Vec<_>>()
.try_into()
.map_err(|e: Vec<_>| {
CryptoError::IdentityGenerationError(format!(
"Invalid mnemonic phrase length: {} - expected {}",
e.len(),
MNEMONIC_LEN
))
})?,
})
}
#[tracing::instrument(level = "debug", skip_all)]
fn derive_forest_keypair(&self, path: &str) -> Result<SigningKeypair, KeyDeriveError> {
let derived_extended_seckey = self.derive_private_key_from_path(path)?;
let sec_key = *derived_extended_seckey.secret_key.as_bytes();
SigningKeypair::try_from_secret_bytes(&sec_key).map_err(|e| KeyDeriveError(e.to_string()))
}
#[tracing::instrument(level = "debug", skip_all)]
fn derive_encryption_keypair(&self, path: &str) -> Result<EncryptingKeypair, KeyDeriveError> {
let derived_extended_seckey = self.derive_private_key_from_path(path)?;
let curve25519_sk =
EncryptionSecretKey::from(*derived_extended_seckey.secret_key.as_bytes());
let curve25519_pk = curve25519_sk.public_key();
Ok(EncryptingKeypair {
secret: curve25519_sk,
public: curve25519_pk,
})
}
#[tracing::instrument(level = "debug", skip_all)]
fn derive_private_key_from_path(
&self,
path: &str,
) -> Result<ExtendedSecretKey, KeyDeriveError> {
let derivation_path: DerivationPath = path.parse().unwrap();
self.extended_seckey
.derive(&derivation_path)
.map_err(|e| KeyDeriveError(e.to_string()))
}
}
#[cfg(test)]
mod tests {
use crypto_box::aead::{Aead, AeadCore};
use crypto_box::SalsaBox;
use hex::encode;
use hex_literal::hex;
use salsa20::XNonce;
use super::*;
use crate::common::test_utilities::MNEMONIC_PHRASE;
const MSG: &[u8] = b"Hello World";
fn user() -> Identity {
let mnemonic = utils::new_mnemonic_from_phrase(MNEMONIC_PHRASE).unwrap();
Identity::from_mnemonic(mnemonic).unwrap()
}
fn encrypt(
nonce: &XNonce,
alice_keypair: &EncryptingKeypair,
bob_keypair: &EncryptingKeypair,
) -> Vec<u8> {
let salsa_box =
crypto_box::SalsaBox::new(&alice_keypair.public.clone(), &bob_keypair.secret.clone());
salsa_box.encrypt(nonce, MSG).unwrap()
}
fn decrypt(
ciphertext: &[u8],
nonce: &XNonce,
alice_keypair: &EncryptingKeypair,
bob_keypair: &EncryptingKeypair,
) -> crypto_box::aead::Result<Vec<u8>> {
let salsa_box =
crypto_box::SalsaBox::new(&bob_keypair.public.clone(), &alice_keypair.secret.clone());
salsa_box.decrypt(nonce, ciphertext)
}
#[test]
fn can_encrypt_and_decrypt_message_with_encryption_key() {
let user = user();
let alice_keypair: EncryptingKeypair = user.encryption_keypair(0, 0).unwrap();
let bob_keypair: EncryptingKeypair = user.encryption_keypair(1, 0).unwrap();
let mut rng = rand_core::OsRng;
let nonce = SalsaBox::generate_nonce(&mut rng);
let ciphertext = encrypt(&nonce, &alice_keypair, &bob_keypair);
let result = decrypt(ciphertext.as_slice(), &nonce, &bob_keypair, &alice_keypair);
assert_eq!(MSG, result.unwrap().as_slice())
}
#[test]
fn can_sign_and_check_signatures_with_derived_keypair() {
let user = user();
let keypair = user.forest_keypair(0).unwrap();
let signature = keypair.sign(MSG);
assert!(signature.verify(MSG, &keypair.public()).is_ok());
}
#[test]
fn cannot_verify_signature_for_other_message() {
let user = user();
let keypair = user.forest_keypair(0).unwrap();
let signature = keypair.sign(MSG);
assert!(signature.verify(MSG, &keypair.public()).is_ok());
}
#[test]
fn can_generate_distinct_keypairs() {
let user = user();
let skeypair = user.forest_keypair(0).unwrap();
let e0key = user.encryption_keypair(0, 0).unwrap();
let e1key = user.encryption_keypair(0, 1).unwrap();
assert_ne!(skeypair.secret(), e0key.secret.to_bytes());
assert_ne!(e0key.secret.to_bytes(), e1key.secret.to_bytes());
assert_eq!(encode(skeypair.secret()).len(), 64);
assert_eq!(encode(skeypair.public()).len(), 64);
}
const TEST_MNEMONIC_12: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
const TEST_MNEMONIC_ITALIAN: &str =
"abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abaco abbaglio";
const ROOT_XPRV: [u8; 96] = [
24, 97, 125, 255, 78, 254, 242, 4, 80, 221, 94, 175, 192, 96, 253, 133, 250, 172, 202, 19,
217, 90, 206, 59, 218, 11, 227, 46, 70, 148, 252, 215, 161, 178, 196, 120, 102, 114, 194,
12, 205, 218, 138, 151, 244, 166, 214, 35, 131, 140, 194, 70, 236, 205, 123, 72, 70, 215,
44, 36, 182, 15, 25, 158, 117, 161, 211, 29, 125, 195, 12, 236, 138, 155, 206, 3, 16, 11,
54, 143, 209, 223, 7, 250, 9, 252, 142, 87, 79, 214, 211, 69, 2, 147, 159, 63,
];
#[test]
fn can_generate_from_entropy() {
let entropy = hex!(
"
65426aa1176159d1929caea10514cddd
d11235741001f125922f258a58716b58
da63e3060fe461fe37e4ed201d76b132
e35830929b0f4764e577d3da09ecb6d2
12
"
);
let user = Identity::try_from(entropy.as_ref()).ok().unwrap();
assert_eq!(
[
"expect".to_owned(),
"cruel".to_owned(),
"stadium".to_owned(),
"sand".to_owned(),
"couch".to_owned(),
"garden".to_owned(),
"nothing".to_owned(),
"wool".to_owned(),
"grocery".to_owned(),
"shop".to_owned(),
"noise".to_owned(),
"voice".to_owned()
],
user.get_mnemonic()
);
}
#[test]
fn will_crash_on_low_entropy_source() {
let entropy = hex!(
"
65426aa1176159d1929caea10514
"
);
assert!(Identity::try_from(entropy.as_ref()).is_err());
}
#[test]
fn can_generate_from_mnemonic() {
let mnemonic_array: MnemonicPhrase = TEST_MNEMONIC_12
.split(' ')
.map(|s| s.to_string())
.collect::<Vec<String>>()
.try_into()
.unwrap();
let user = Identity::try_from(&mnemonic_array).unwrap();
assert_eq!(
user.extended_seckey.secret_key.to_bytes(),
ExtendedSecretKey::from_seed(&ROOT_XPRV)
.unwrap()
.secret_key
.to_bytes()
)
}
#[test]
fn should_fail_on_not_english_mnemonic() {
let mnemonic_array: MnemonicPhrase = TEST_MNEMONIC_ITALIAN
.split(' ')
.map(|s| s.to_string())
.collect::<Vec<String>>()
.try_into()
.unwrap();
assert!(Identity::try_from(&mnemonic_array).is_err());
}
#[test]
fn can_recover_mnemonic() {
let mnemonic: MnemonicPhrase = TEST_MNEMONIC_12
.split(' ')
.map(|s| s.to_string())
.collect::<Vec<String>>()
.try_into()
.unwrap();
let user = Identity::try_from(&mnemonic).unwrap();
assert_eq!(user.get_mnemonic().join(" "), TEST_MNEMONIC_12);
}
}