use crate::{KeyPurpose, errors::OpenVTCError};
use affinidi_tdk::{
affinidi_crypto::ed25519::ed25519_private_to_x25519, secrets_resolver::secrets::Secret,
};
use ed25519_dalek_bip32::{DerivationPath, ExtendedSigningKey};
pub fn get_bip32_root(seed: &[u8]) -> Result<ExtendedSigningKey, OpenVTCError> {
ExtendedSigningKey::from_seed(seed).map_err(|e| {
OpenVTCError::BIP32(format!("Couldn't create BIP32 Master Key from seed: {}", e))
})
}
pub trait Bip32Extension {
fn get_secret_from_path(&self, path: &str, kp: KeyPurpose) -> Result<Secret, OpenVTCError>;
}
impl Bip32Extension for ExtendedSigningKey {
fn get_secret_from_path(&self, path: &str, kp: KeyPurpose) -> Result<Secret, OpenVTCError> {
let key = self
.derive(&path.parse::<DerivationPath>().map_err(|e| {
OpenVTCError::BIP32(format!(
"Invalid path ({}) for BIP32 key deriviation: {}",
path, e
))
})?)
.map_err(|e| {
OpenVTCError::BIP32(format!(
"Failed to create ed25519 key material from BIP32: {}",
e
))
})?;
let secret = match kp {
KeyPurpose::Signing | KeyPurpose::Authentication => {
Secret::generate_ed25519(None, Some(key.signing_key.as_bytes()))
}
KeyPurpose::Encryption => {
let x25519_seed = ed25519_private_to_x25519(key.signing_key.as_bytes());
Secret::generate_x25519(None, Some(&x25519_seed)).map_err(|e| {
OpenVTCError::Secret(format!("Failed to create derived encryption key: {}", e))
})?
}
_ => {
return Err(OpenVTCError::Secret(format!(
"Invalid key purpose used to generate key material ({})",
kp
)));
}
};
Ok(secret)
}
}
#[cfg(test)]
mod tests {
use bip39::Mnemonic;
const ENTROPY_BYTES: [u8; 32] = [
7, 26, 142, 230, 65, 85, 188, 182, 29, 129, 52, 229, 217, 159, 243, 182, 73, 89, 196, 246,
58, 28, 100, 144, 187, 21, 157, 39, 4, 188, 154, 180,
];
const MNEMONIC_WORDS: [&str; 24] = [
"alpha", "stamp", "ridge", "live", "forward", "force", "invite", "charge", "total",
"smooth", "woman", "hold", "night", "tiny", "suggest", "drum", "goose", "magic", "shell",
"demise", "icon", "furnace", "hello", "manual",
];
#[test]
fn test_generate_mnemonic() {
let mnemonic =
Mnemonic::from_entropy(&ENTROPY_BYTES).expect("Couldn't create mnemonic from entropy");
for (index, word) in mnemonic.words().enumerate() {
assert_eq!(MNEMONIC_WORDS[index], word);
}
}
#[test]
fn test_recover_mnemonic() {
let words = MNEMONIC_WORDS.join(" ");
let mnemonic = Mnemonic::parse_normalized(&words).unwrap();
assert_eq!(mnemonic.to_entropy(), ENTROPY_BYTES);
}
use super::{Bip32Extension, get_bip32_root};
use crate::KeyPurpose;
use affinidi_tdk::secrets_resolver::secrets::Secret;
use bip39::Language;
fn test_seed() -> Vec<u8> {
let mnemonic = Mnemonic::parse_in_normalized(Language::English, &MNEMONIC_WORDS.join(" "))
.expect("parse mnemonic");
mnemonic.to_seed("").to_vec()
}
fn public_multibase(secret: &Secret) -> String {
secret
.get_public_keymultibase()
.expect("derived secret should have a public multibase representation")
}
#[test]
fn kat_persona_signing_key_at_m_0h_0h_0h() {
let seed = test_seed();
let root = get_bip32_root(&seed).expect("master from seed");
let secret = root
.get_secret_from_path("m/0'/0'/0'", KeyPurpose::Signing)
.expect("derive signing");
let again = root
.get_secret_from_path("m/0'/0'/0'", KeyPurpose::Signing)
.expect("derive signing again");
assert_eq!(public_multibase(&secret), public_multibase(&again));
let auth = root
.get_secret_from_path("m/0'/0'/0'", KeyPurpose::Authentication)
.expect("derive auth");
assert_eq!(public_multibase(&secret), public_multibase(&auth));
}
#[test]
fn kat_persona_encryption_key_differs_from_signing_at_same_path() {
let seed = test_seed();
let root = get_bip32_root(&seed).expect("master from seed");
let signing = root
.get_secret_from_path("m/0'/0'/0'", KeyPurpose::Signing)
.expect("derive signing");
let encryption = root
.get_secret_from_path("m/0'/0'/0'", KeyPurpose::Encryption)
.expect("derive encryption");
assert_ne!(public_multibase(&signing), public_multibase(&encryption));
}
#[test]
fn kat_relationship_path_differs_from_persona_path() {
let seed = test_seed();
let root = get_bip32_root(&seed).expect("master from seed");
let persona = root
.get_secret_from_path("m/0'/0'/0'", KeyPurpose::Signing)
.expect("derive persona");
let relationship = root
.get_secret_from_path("m/3'/1'/1'/0'", KeyPurpose::Signing)
.expect("derive relationship");
assert_ne!(public_multibase(&persona), public_multibase(&relationship));
}
#[test]
fn kat_invalid_path_returns_error() {
let seed = test_seed();
let root = get_bip32_root(&seed).expect("master from seed");
assert!(
root.get_secret_from_path("not-a-bip32-path", KeyPurpose::Signing)
.is_err()
);
}
#[test]
fn kat_unsupported_purpose_returns_error() {
let seed = test_seed();
let root = get_bip32_root(&seed).expect("master from seed");
assert!(
root.get_secret_from_path("m/0'/0'/0'", KeyPurpose::Unknown)
.is_err()
);
}
#[test]
fn kat_distinct_seeds_produce_distinct_keys() {
let key_a = get_bip32_root(&test_seed())
.expect("seed a master")
.get_secret_from_path("m/0'/0'/0'", KeyPurpose::Signing)
.expect("derive a");
let mnemonic_b = bip39::Mnemonic::from_entropy(&[0xFFu8; 32]).expect("mnemonic b");
let seed_b = mnemonic_b.to_seed("");
let key_b = get_bip32_root(&seed_b)
.expect("seed b master")
.get_secret_from_path("m/0'/0'/0'", KeyPurpose::Signing)
.expect("derive b");
assert_ne!(public_multibase(&key_a), public_multibase(&key_b));
}
}