//! ## Path data guesswork for BIP-0032 derivation paths. #![allow(clippy::unreadable_literal)] use once_cell::sync::Lazy; use keyfork_derive_util::{DerivationIndex, DerivationPath}; /// All common paths for key derivation. pub mod paths { use super::*; /// The default derivation path for OpenPGP. pub static OPENPGP: Lazy = Lazy::new(|| { DerivationPath::default().chain_push(DerivationIndex::new_unchecked( u32::from_be_bytes(*b"\x00pgp"), true, )) }); /// The derivation path for OpenPGP certificates used for sharding. pub static OPENPGP_SHARD: Lazy = Lazy::new(|| { DerivationPath::default() .chain_push(DerivationIndex::new_unchecked( u32::from_be_bytes(*b"\x00pgp"), true, )) .chain_push(DerivationIndex::new_unchecked( u32::from_be_bytes(*b"shrd"), true, )) }); /// The derivation path for OpenPGP certificates used for disaster recovery. pub static OPENPGP_DISASTER_RECOVERY: Lazy = Lazy::new(|| { DerivationPath::default() .chain_push(DerivationIndex::new_unchecked( u32::from_be_bytes(*b"\x00pgp"), true, )) .chain_push(DerivationIndex::new_unchecked( u32::from_be_bytes(*b"\x00\x00dr"), true, )) }); } /// Determine if a prefix matches and whether the next index exists. fn prefix_matches(given: &DerivationPath, target: &DerivationPath) -> Option { if given.len() <= target.len() { return None; } if target .iter() .zip(given.iter()) .all(|(left, right)| left == right) { given.iter().nth(target.len()).cloned() } else { None } } /// A derivation target. #[derive(Debug)] #[non_exhaustive] pub enum Target { /// An OpenPGP key, whose account is the given index. OpenPGP(DerivationIndex), /// An OpenPGP key used for sharding. OpenPGPShard(DerivationIndex), /// An OpenPGP key used for disaster recovery. OpenPGPDisasterRecovery(DerivationIndex), } impl std::fmt::Display for Target { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Target::OpenPGP(account) => { write!(f, "OpenPGP key (account {account})") } Target::OpenPGPShard(shard_index) => { write!(f, "OpenPGP Shard key (shard index {shard_index})") } Target::OpenPGPDisasterRecovery(account) => { write!(f, "OpenPGP Disaster Recovery key (account {account})") } } } } macro_rules! test_match { ($var:ident, $shard:path, $target:path) => { if let Some(index) = prefix_matches($var, &$shard) { return Some($target(index)); } }; } /// Determine the closest [`Target`] for the given path. This method is intended to be used by /// `keyforkd` to provide an optional textual prompt to what a client is attempting to derive. pub fn guess_target(path: &DerivationPath) -> Option { test_match!(path, paths::OPENPGP_SHARD, Target::OpenPGPShard); test_match!( path, paths::OPENPGP_DISASTER_RECOVERY, Target::OpenPGPDisasterRecovery ); test_match!(path, paths::OPENPGP, Target::OpenPGP); None } #[cfg(test)] mod tests { use super::*; #[test] fn it_works() { let index = DerivationIndex::new(5312, false).unwrap(); let dr_key = paths::OPENPGP_DISASTER_RECOVERY .clone() .chain_push(index.clone()); match guess_target(&dr_key) { Some(Target::OpenPGPDisasterRecovery(idx)) if idx == index => (), bad => panic!("invalid value: {bad:?}"), } } }