diff --git a/Cargo.lock b/Cargo.lock index 060a0bd..8c9dcee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1805,9 +1805,10 @@ dependencies = [ "clap_complete", "keyfork-bin", "keyfork-derive-openpgp", + "keyfork-derive-path-data", "keyfork-derive-util", "keyfork-entropy", - "keyfork-mnemonic-util", + "keyfork-mnemonic", "keyfork-prompt", "keyfork-qrcode", "keyfork-shard", @@ -1872,6 +1873,7 @@ version = "0.1.2" dependencies = [ "anyhow", "ed25519-dalek", + "keyfork-derive-path-data", "keyfork-derive-util", "keyforkd-client", "sequoia-openpgp", @@ -1883,6 +1885,7 @@ name = "keyfork-derive-path-data" version = "0.1.1" dependencies = [ "keyfork-derive-util", + "once_cell", ] [[package]] @@ -1895,7 +1898,7 @@ dependencies = [ "hmac", "k256", "keyfork-bug", - "keyfork-mnemonic-util", + "keyfork-mnemonic", "keyfork-slip10-test-data", "ripemd", "serde", @@ -1922,7 +1925,7 @@ dependencies = [ ] [[package]] -name = "keyfork-mnemonic-util" +name = "keyfork-mnemonic" version = "0.3.0" dependencies = [ "bip39", @@ -1941,7 +1944,7 @@ version = "0.1.1" dependencies = [ "keyfork-bug", "keyfork-crossterm", - "keyfork-mnemonic-util", + "keyfork-mnemonic", "thiserror", ] @@ -1969,7 +1972,7 @@ dependencies = [ "hkdf", "keyfork-bug", "keyfork-derive-openpgp", - "keyfork-mnemonic-util", + "keyfork-mnemonic", "keyfork-prompt", "keyfork-qrcode", "openpgp-card", @@ -2017,7 +2020,7 @@ dependencies = [ "keyfork-derive-path-data", "keyfork-derive-util", "keyfork-frame", - "keyfork-mnemonic-util", + "keyfork-mnemonic", "keyfork-slip10-test-data", "keyforkd-models", "serde", diff --git a/Cargo.toml b/Cargo.toml index 062acbe..aa66631 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ members = [ "crates/util/keyfork-crossterm", "crates/util/keyfork-entropy", "crates/util/keyfork-frame", - "crates/util/keyfork-mnemonic-util", + "crates/util/keyfork-mnemonic", "crates/util/keyfork-prompt", "crates/util/keyfork-slip10-test-data", "crates/util/smex", diff --git a/crates/daemon/keyforkd/Cargo.toml b/crates/daemon/keyforkd/Cargo.toml index a4bc0ec..519bdab 100644 --- a/crates/daemon/keyforkd/Cargo.toml +++ b/crates/daemon/keyforkd/Cargo.toml @@ -15,7 +15,7 @@ multithread = ["tokio/rt-multi-thread"] keyfork-bug = { version = "0.1.0", path = "../../util/keyfork-bug", registry = "distrust" } keyfork-derive-util = { version = "0.2.0", path = "../../derive/keyfork-derive-util", registry = "distrust" } keyfork-frame = { version = "0.1.0", path = "../../util/keyfork-frame", features = ["async"], registry = "distrust" } -keyfork-mnemonic-util = { version = "0.3.0", path = "../../util/keyfork-mnemonic-util", registry = "distrust" } +keyfork-mnemonic = { version = "0.3.0", path = "../../util/keyfork-mnemonic", registry = "distrust" } keyfork-derive-path-data = { version = "0.1.0", path = "../../derive/keyfork-derive-path-data", registry = "distrust" } keyforkd-models = { version = "0.2.0", path = "../keyforkd-models", registry = "distrust" } diff --git a/crates/daemon/keyforkd/src/lib.rs b/crates/daemon/keyforkd/src/lib.rs index 40eafd4..123c63c 100644 --- a/crates/daemon/keyforkd/src/lib.rs +++ b/crates/daemon/keyforkd/src/lib.rs @@ -5,7 +5,7 @@ use std::{ path::{Path, PathBuf}, }; -pub use keyfork_mnemonic_util::Mnemonic; +pub use keyfork_mnemonic::Mnemonic; pub use tower::ServiceBuilder; #[cfg(feature = "tracing")] @@ -57,7 +57,7 @@ pub async fn start_and_run_server_on( let service = ServiceBuilder::new() .layer(middleware::BincodeLayer::new()) // TODO: passphrase support and/or store passphrase with mnemonic - .service(Keyforkd::new(mnemonic.generate_seed(None))); + .service(Keyforkd::new(mnemonic.generate_seed(None).to_vec())); let mut server = match UnixServer::bind(socket_path) { Ok(s) => s, diff --git a/crates/daemon/keyforkd/src/main.rs b/crates/daemon/keyforkd/src/main.rs index f8a4524..06e6485 100644 --- a/crates/daemon/keyforkd/src/main.rs +++ b/crates/daemon/keyforkd/src/main.rs @@ -1,6 +1,6 @@ //! -use keyfork_mnemonic_util::Mnemonic; +use keyfork_mnemonic::Mnemonic; use tokio::io::{self, AsyncBufReadExt, BufReader}; diff --git a/crates/derive/keyfork-derive-openpgp/Cargo.toml b/crates/derive/keyfork-derive-openpgp/Cargo.toml index bd71c94..f679ed9 100644 --- a/crates/derive/keyfork-derive-openpgp/Cargo.toml +++ b/crates/derive/keyfork-derive-openpgp/Cargo.toml @@ -16,3 +16,4 @@ ed25519-dalek = "2.0.0" sequoia-openpgp = { version = "1.17.0", default-features = false } anyhow = "1.0.75" thiserror = "1.0.49" +keyfork-derive-path-data = { version = "0.1.1", path = "../keyfork-derive-path-data" } diff --git a/crates/derive/keyfork-derive-openpgp/src/main.rs b/crates/derive/keyfork-derive-openpgp/src/main.rs index 6070612..974cd25 100644 --- a/crates/derive/keyfork-derive-openpgp/src/main.rs +++ b/crates/derive/keyfork-derive-openpgp/src/main.rs @@ -3,6 +3,7 @@ use std::{env, process::ExitCode, str::FromStr}; use keyfork_derive_util::{DerivationIndex, DerivationPath}; +use keyfork_derive_path_data::paths; use keyforkd_client::Client; use ed25519_dalek::SigningKey; @@ -78,16 +79,14 @@ fn validate( subkey_format: &str, default_userid: &str, ) -> Result<(DerivationPath, Vec, UserID), Box> { - let mut pgp_u32 = [0u8; 4]; - pgp_u32[1..].copy_from_slice(&"pgp".bytes().collect::>()); - let index = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?; + let index = paths::OPENPGP.inner().first().unwrap(); let path = DerivationPath::from_str(path)?; assert!(path.len() >= 2, "Expected path of at least m/{index}/account_id'"); let given_index = path.iter().next().expect("checked .len() above"); assert_eq!( - &index, given_index, + index, given_index, "Expected derivation path starting with m/{index}, got: {given_index}", ); diff --git a/crates/derive/keyfork-derive-path-data/Cargo.toml b/crates/derive/keyfork-derive-path-data/Cargo.toml index 7b3c903..520b58b 100644 --- a/crates/derive/keyfork-derive-path-data/Cargo.toml +++ b/crates/derive/keyfork-derive-path-data/Cargo.toml @@ -8,3 +8,4 @@ license = "MIT" [dependencies] keyfork-derive-util = { version = "0.2.0", path = "../keyfork-derive-util", default-features = false, registry = "distrust" } +once_cell = "1.19.0" diff --git a/crates/derive/keyfork-derive-path-data/src/lib.rs b/crates/derive/keyfork-derive-path-data/src/lib.rs index 24cbe7c..97822ad 100644 --- a/crates/derive/keyfork-derive-path-data/src/lib.rs +++ b/crates/derive/keyfork-derive-path-data/src/lib.rs @@ -2,32 +2,128 @@ #![allow(clippy::unreadable_literal)] +use once_cell::sync::Lazy; + use keyfork_derive_util::{DerivationIndex, DerivationPath}; -/// The default derivation path for OpenPGP. -pub static OPENPGP: DerivationIndex = DerivationIndex::new_unchecked(7366512, true); +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 { - Self::OpenPGP(account) => { + 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 { - Some(match path.iter().collect::>()[..] { - [t, index] if t == &OPENPGP => Target::OpenPGP(index.clone()), - _ => return None, - }) + 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:?}"), + } + } } diff --git a/crates/derive/keyfork-derive-util/Cargo.toml b/crates/derive/keyfork-derive-util/Cargo.toml index 9a76193..a6a89a4 100644 --- a/crates/derive/keyfork-derive-util/Cargo.toml +++ b/crates/derive/keyfork-derive-util/Cargo.toml @@ -12,7 +12,7 @@ secp256k1 = ["k256"] ed25519 = ["ed25519-dalek"] [dependencies] -keyfork-mnemonic-util = { version = "0.3.0", path = "../../util/keyfork-mnemonic-util", registry = "distrust" } +keyfork-mnemonic = { version = "0.3.0", path = "../../util/keyfork-mnemonic", registry = "distrust" } keyfork-bug = { version = "0.1.0", path = "../../util/keyfork-bug", registry = "distrust" } # Included in Rust diff --git a/crates/derive/keyfork-derive-util/README.md b/crates/derive/keyfork-derive-util/README.md index 55fb1e1..e11196b 100644 --- a/crates/derive/keyfork-derive-util/README.md +++ b/crates/derive/keyfork-derive-util/README.md @@ -23,7 +23,7 @@ performed directly on a master seed. This is how Keyforkd works internally. ```rust use std::str::FromStr; -use keyfork_mnemonic_util::Mnemonic; +use keyfork_mnemonic::Mnemonic; use keyfork_derive_util::{*, request::*}; fn main() -> Result<(), Box> { diff --git a/crates/derive/keyfork-derive-util/src/extended_key/mod.rs b/crates/derive/keyfork-derive-util/src/extended_key/mod.rs index a3d4314..dec928f 100644 --- a/crates/derive/keyfork-derive-util/src/extended_key/mod.rs +++ b/crates/derive/keyfork-derive-util/src/extended_key/mod.rs @@ -11,7 +11,7 @@ //! # Examples //! ```rust //! use std::str::FromStr; -//! use keyfork_mnemonic_util::Mnemonic; +//! use keyfork_mnemonic::Mnemonic; //! use keyfork_derive_util::{*, request::*}; //! use k256::SecretKey; //! diff --git a/crates/derive/keyfork-derive-util/src/request.rs b/crates/derive/keyfork-derive-util/src/request.rs index cec0b54..78eb7b1 100644 --- a/crates/derive/keyfork-derive-util/src/request.rs +++ b/crates/derive/keyfork-derive-util/src/request.rs @@ -24,7 +24,7 @@ use crate::{ DerivationPath, ExtendedPrivateKey, }; -use keyfork_mnemonic_util::{Mnemonic, MnemonicGenerationError}; +use keyfork_mnemonic::{Mnemonic, MnemonicGenerationError}; use serde::{Deserialize, Serialize}; /// An error encountered while deriving a key. @@ -194,8 +194,8 @@ impl DerivationRequest { /// # private_key::TestPrivateKey as PrivateKey, /// # }; /// # fn main() -> Result<(), Box> { - /// let mnemonic: keyfork_mnemonic_util::Mnemonic = // - /// # keyfork_mnemonic_util::Mnemonic::from_entropy( + /// let mnemonic: keyfork_mnemonic::Mnemonic = // + /// # keyfork_mnemonic::Mnemonic::from_entropy( /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", /// # )?; /// let algo: DerivationAlgorithm = // diff --git a/crates/keyfork-shard/Cargo.toml b/crates/keyfork-shard/Cargo.toml index 19495bb..31b7e09 100644 --- a/crates/keyfork-shard/Cargo.toml +++ b/crates/keyfork-shard/Cargo.toml @@ -23,7 +23,7 @@ sharks = "0.5.0" thiserror = "1.0.50" # Remote operator mode -keyfork-mnemonic-util = { version = "0.3.0", path = "../util/keyfork-mnemonic-util", registry = "distrust" } +keyfork-mnemonic = { version = "0.3.0", path = "../util/keyfork-mnemonic", registry = "distrust" } x25519-dalek = { version = "2.0.0", features = ["getrandom"] } aes-gcm = { version = "0.10.3", features = ["std"] } hkdf = { version = "0.12.4", features = ["std"] } diff --git a/crates/keyfork-shard/src/lib.rs b/crates/keyfork-shard/src/lib.rs index cd5bb30..ea72635 100644 --- a/crates/keyfork-shard/src/lib.rs +++ b/crates/keyfork-shard/src/lib.rs @@ -13,7 +13,7 @@ use aes_gcm::{ use base64::prelude::{Engine, BASE64_STANDARD}; use hkdf::Hkdf; use keyfork_bug::{bug, POISONED_MUTEX}; -use keyfork_mnemonic_util::{English, Mnemonic}; +use keyfork_mnemonic::{English, Mnemonic}; use keyfork_prompt::{ validators::{ mnemonic::{MnemonicSetValidator, MnemonicValidator, WordLength}, @@ -249,7 +249,7 @@ pub trait Format { // create our shared key let our_key = EphemeralSecret::random(); - let our_pubkey_mnemonic = Mnemonic::from_bytes(PublicKey::from(&our_key).as_bytes())?; + let our_pubkey_mnemonic = Mnemonic::try_from_slice(PublicKey::from(&our_key).as_bytes())?; let shared_secret = our_key.diffie_hellman(&PublicKey::from(their_pubkey)); assert!( shared_secret.was_contributory(), @@ -302,7 +302,7 @@ pub trait Format { let mut mnemonic_bytes = [0u8; ENCRYPTED_LENGTH as usize]; mnemonic_bytes.copy_from_slice(&encrypted_bytes); - let payload_mnemonic = Mnemonic::from_nonstandard_bytes(mnemonic_bytes); + let payload_mnemonic = Mnemonic::from_array(mnemonic_bytes); #[cfg(feature = "qrcode")] { @@ -439,7 +439,7 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box 0) { iter += 1; let our_key = EphemeralSecret::random(); - let key_mnemonic = Mnemonic::from_bytes(PublicKey::from(&our_key).as_bytes())?; + let key_mnemonic = Mnemonic::try_from_slice(PublicKey::from(&our_key).as_bytes())?; #[cfg(feature = "qrcode")] { diff --git a/crates/keyfork/Cargo.toml b/crates/keyfork/Cargo.toml index 6d40fa5..5a732a8 100644 --- a/crates/keyfork/Cargo.toml +++ b/crates/keyfork/Cargo.toml @@ -29,7 +29,7 @@ keyforkd-client = { version = "0.2.0", path = "../daemon/keyforkd-client", defau keyfork-derive-openpgp = { version = "0.1.1", path = "../derive/keyfork-derive-openpgp", registry = "distrust" } keyfork-derive-util = { version = "0.2.0", path = "../derive/keyfork-derive-util", default-features = false, features = ["ed25519"], registry = "distrust" } keyfork-entropy = { version = "0.1.0", path = "../util/keyfork-entropy", registry = "distrust" } -keyfork-mnemonic-util = { version = "0.3.0", path = "../util/keyfork-mnemonic-util", registry = "distrust" } +keyfork-mnemonic = { version = "0.3.0", path = "../util/keyfork-mnemonic", registry = "distrust" } keyfork-prompt = { version = "0.1.0", path = "../util/keyfork-prompt", registry = "distrust" } keyfork-qrcode = { version = "0.1.0", path = "../qrcode/keyfork-qrcode", default-features = false, registry = "distrust" } keyfork-shard = { version = "0.2.0", path = "../keyfork-shard", default-features = false, features = ["openpgp", "openpgp-card", "qrcode"], registry = "distrust" } @@ -44,3 +44,4 @@ openpgp-card-sequoia = { version = "0.2.0", default-features = false } openpgp-card = "0.4.1" clap_complete = { version = "4.4.6", optional = true } sequoia-openpgp = { version = "1.17.0", default-features = false, features = ["compression"] } +keyfork-derive-path-data = { version = "0.1.1", path = "../derive/keyfork-derive-path-data" } diff --git a/crates/keyfork/src/cli/derive.rs b/crates/keyfork/src/cli/derive.rs index 197a8ec..e61553d 100644 --- a/crates/keyfork/src/cli/derive.rs +++ b/crates/keyfork/src/cli/derive.rs @@ -11,6 +11,7 @@ use keyfork_derive_openpgp::{ XPrvKey, }; use keyfork_derive_util::{DerivationIndex, DerivationPath}; +use keyfork_derive_path_data::paths; use keyforkd_client::Client; type Result> = std::result::Result; @@ -46,12 +47,7 @@ impl DeriveSubcommands { impl OpenPGP { pub fn handle(&self, account: DerivationIndex) -> Result<()> { - let mut pgp_u32 = [0u8; 4]; - pgp_u32[1..].copy_from_slice(&"pgp".bytes().collect::>()); - let chain = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?; - let path = DerivationPath::default() - .chain_push(chain) - .chain_push(account); + let path = paths::OPENPGP.clone().chain_push(account); // TODO: should this be customizable? let subkeys = vec![ KeyFlags::empty().set_certification(), diff --git a/crates/keyfork/src/cli/mnemonic.rs b/crates/keyfork/src/cli/mnemonic.rs index ef99948..05403a6 100644 --- a/crates/keyfork/src/cli/mnemonic.rs +++ b/crates/keyfork/src/cli/mnemonic.rs @@ -109,7 +109,7 @@ impl MnemonicSeedSource { MnemonicSeedSource::Tarot => todo!(), MnemonicSeedSource::Dice => todo!(), }; - let mnemonic = keyfork_mnemonic_util::Mnemonic::from_bytes(&seed)?; + let mnemonic = keyfork_mnemonic::Mnemonic::try_from_slice(&seed)?; Ok(mnemonic.to_string()) } } diff --git a/crates/keyfork/src/cli/recover.rs b/crates/keyfork/src/cli/recover.rs index 6a9b655..96d3e6a 100644 --- a/crates/keyfork/src/cli/recover.rs +++ b/crates/keyfork/src/cli/recover.rs @@ -2,7 +2,7 @@ use super::Keyfork; use clap::{Parser, Subcommand}; use std::path::PathBuf; -use keyfork_mnemonic_util::{English, Mnemonic}; +use keyfork_mnemonic::{English, Mnemonic}; use keyfork_prompt::{default_terminal, DefaultTerminal}; use keyfork_shard::{remote_decrypt, Format}; @@ -85,7 +85,7 @@ pub struct Recover { impl Recover { pub fn handle(&self, _k: &Keyfork) -> Result<()> { let seed = self.command.handle()?; - let mnemonic = Mnemonic::from_bytes(&seed)?; + let mnemonic = Mnemonic::try_from_slice(&seed)?; tokio::runtime::Builder::new_multi_thread() .enable_all() .build() diff --git a/crates/keyfork/src/cli/wizard.rs b/crates/keyfork/src/cli/wizard.rs index 618a9e2..b0187ec 100644 --- a/crates/keyfork/src/cli/wizard.rs +++ b/crates/keyfork/src/cli/wizard.rs @@ -16,8 +16,9 @@ use keyfork_derive_openpgp::{ }, XPrv, }; -use keyfork_derive_util::{DerivationIndex, DerivationPath, VariableLengthSeed}; -use keyfork_mnemonic_util::Mnemonic; +use keyfork_derive_path_data::paths; +use keyfork_derive_util::{DerivationIndex, DerivationPath}; +use keyfork_mnemonic::Mnemonic; use keyfork_prompt::{ default_terminal, validators::{SecurePinValidator, Validator}, @@ -44,17 +45,8 @@ fn derive_key(seed: [u8; 32], index: u8) -> Result { KeyFlags::empty().set_authentication(), ]; - let mut pgp_u32 = [0u8; 4]; - pgp_u32[1..].copy_from_slice(&"pgp".bytes().collect::>()); - let chain = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?; - let mut shrd_u32 = [0u8; 4]; - shrd_u32[..].copy_from_slice(&"shrd".bytes().collect::>()); - let account = DerivationIndex::new(u32::from_be_bytes(shrd_u32), true)?; let subkey = DerivationIndex::new(u32::from(index), true)?; - let path = DerivationPath::default() - .chain_push(chain) - .chain_push(account) - .chain_push(subkey); + let path = paths::OPENPGP_SHARD.clone().chain_push(subkey); let xprv = XPrv::new(seed) .expect("could not construct master key from seed") .derive_path(&path)?; @@ -263,18 +255,13 @@ impl GenerateShardSecret { impl BottomsUp { fn handle(&self) -> Result<()> { let entropy = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?; - let mnemonic = Mnemonic::from_nonstandard_bytes(entropy); - // TODO: make this return const size, since is hash based + let mnemonic = Mnemonic::from_array(entropy); let seed = mnemonic.generate_seed(None); // TODO: should this allow for customizing the account index from 0? Potential for key reuse // errors. - let path = DerivationPath::default() - .chain_push(DerivationIndex::new(u32::from_be_bytes(*b"\x00pgp"), true)?) - .chain_push(DerivationIndex::new( - u32::from_be_bytes(*b"\x00\x00dr"), - true, - )?) + let path = paths::OPENPGP_DISASTER_RECOVERY + .clone() .chain_push(DerivationIndex::new(0, true)?); let subkeys = [ KeyFlags::empty().set_certification(), @@ -284,7 +271,7 @@ impl BottomsUp { .set_storage_encryption(), KeyFlags::empty().set_authentication(), ]; - let xprv = XPrv::new(VariableLengthSeed::new(&seed)) + let xprv = XPrv::new(seed) .expect("could not construct master key from seed") .derive_path(&path)?; let userid = UserID::from(self.user_id.as_str()); diff --git a/crates/util/keyfork-mnemonic-util/Cargo.toml b/crates/util/keyfork-mnemonic/Cargo.toml similarity index 94% rename from crates/util/keyfork-mnemonic-util/Cargo.toml rename to crates/util/keyfork-mnemonic/Cargo.toml index 6e87205..f1e1b50 100644 --- a/crates/util/keyfork-mnemonic-util/Cargo.toml +++ b/crates/util/keyfork-mnemonic/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "keyfork-mnemonic-util" +name = "keyfork-mnemonic" version = "0.3.0" description = "Utilities to generate and manage seeds based on BIP-0039 mnemonics." repository = "https://git.distrust.co/public/keyfork" diff --git a/crates/util/keyfork-mnemonic-util/src/bin/keyfork-mnemonic-from-seed.rs b/crates/util/keyfork-mnemonic/src/bin/keyfork-mnemonic-from-seed.rs similarity index 89% rename from crates/util/keyfork-mnemonic-util/src/bin/keyfork-mnemonic-from-seed.rs rename to crates/util/keyfork-mnemonic/src/bin/keyfork-mnemonic-from-seed.rs index 4a129e9..39cfe46 100644 --- a/crates/util/keyfork-mnemonic-util/src/bin/keyfork-mnemonic-from-seed.rs +++ b/crates/util/keyfork-mnemonic/src/bin/keyfork-mnemonic-from-seed.rs @@ -1,6 +1,6 @@ //! -use keyfork_mnemonic_util::Mnemonic; +use keyfork_mnemonic::Mnemonic; fn main() -> Result<(), Box> { let input = std::io::stdin(); diff --git a/crates/util/keyfork-mnemonic-util/src/data/vectors.json b/crates/util/keyfork-mnemonic/src/data/vectors.json similarity index 100% rename from crates/util/keyfork-mnemonic-util/src/data/vectors.json rename to crates/util/keyfork-mnemonic/src/data/vectors.json diff --git a/crates/util/keyfork-mnemonic-util/src/data/wordlist.txt b/crates/util/keyfork-mnemonic/src/data/wordlist.txt similarity index 100% rename from crates/util/keyfork-mnemonic-util/src/data/wordlist.txt rename to crates/util/keyfork-mnemonic/src/data/wordlist.txt diff --git a/crates/util/keyfork-mnemonic-util/src/lib.rs b/crates/util/keyfork-mnemonic/src/lib.rs similarity index 87% rename from crates/util/keyfork-mnemonic-util/src/lib.rs rename to crates/util/keyfork-mnemonic/src/lib.rs index 1ba5405..112524a 100644 --- a/crates/util/keyfork-mnemonic-util/src/lib.rs +++ b/crates/util/keyfork-mnemonic/src/lib.rs @@ -3,17 +3,17 @@ //! Mnemonics can be used to safely encode data of 32, 48, and 64 bytes as a phrase: //! //! ```rust -//! use keyfork_mnemonic_util::Mnemonic; +//! use keyfork_mnemonic::Mnemonic; //! let data = b"Hello, world! I am a mnemonic :)"; //! assert_eq!(data.len(), 32); -//! let mnemonic = Mnemonic::from_bytes(data).unwrap(); +//! let mnemonic = Mnemonic::try_from_slice(data).unwrap(); //! println!("Our mnemonic is: {mnemonic}"); //! ``` //! //! A mnemonic can also be parsed from a string: //! //! ```rust -//! use keyfork_mnemonic_util::Mnemonic; +//! use keyfork_mnemonic::Mnemonic; //! use std::str::FromStr; //! //! let data = b"Hello, world! I am a mnemonic :)"; @@ -28,7 +28,7 @@ //! verified to be safe: //! //! ```rust -//! use keyfork_mnemonic_util::Mnemonic; +//! use keyfork_mnemonic::Mnemonic; //! let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; //! let mnemonic = unsafe { Mnemonic::from_raw_bytes(data.as_slice()) }; //! let mnemonic_text = mnemonic.to_string(); @@ -37,7 +37,7 @@ //! If given an invalid length, undefined behavior may follow, or code may panic. //! //! ```rust,should_panic -//! use keyfork_mnemonic_util::Mnemonic; +//! use keyfork_mnemonic::Mnemonic; //! use std::str::FromStr; //! //! // NOTE: Data is of invalid length, 31 @@ -268,11 +268,11 @@ where /// /// # Examples /// ```rust - /// use keyfork_mnemonic_util::Mnemonic; + /// use keyfork_mnemonic::Mnemonic; /// let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; - /// let mnemonic = Mnemonic::from_bytes(data.as_slice()).unwrap(); + /// let mnemonic = Mnemonic::try_from_slice(data.as_slice()).unwrap(); /// ``` - pub fn from_bytes(bytes: &[u8]) -> Result, MnemonicGenerationError> { + pub fn try_from_slice(bytes: &[u8]) -> Result, MnemonicGenerationError> { let bit_count = bytes.len() * 8; if bit_count % 32 != 0 { @@ -290,23 +290,23 @@ where /// of a factor of 4, up to 1024 bytes. /// /// ```rust - /// use keyfork_mnemonic_util::Mnemonic; + /// use keyfork_mnemonic::Mnemonic; /// let data = b"hello world!"; - /// let mnemonic = Mnemonic::from_nonstandard_bytes(*data); + /// let mnemonic = Mnemonic::from_array(*data); /// ``` /// /// If an invalid size is requested, the code will fail to compile: /// /// ```rust,compile_fail - /// use keyfork_mnemonic_util::Mnemonic; - /// let mnemonic = Mnemonic::from_nonstandard_bytes([0u8; 53]); + /// use keyfork_mnemonic::Mnemonic; + /// let mnemonic = Mnemonic::from_array([0u8; 53]); /// ``` /// /// ```rust,compile_fail - /// use keyfork_mnemonic_util::Mnemonic; - /// let mnemonic = Mnemonic::from_nonstandard_bytes([0u8; 1024 + 4]); + /// use keyfork_mnemonic::Mnemonic; + /// let mnemonic = Mnemonic::from_array([0u8; 1024 + 4]); /// ``` - pub fn from_nonstandard_bytes(bytes: [u8; N]) -> MnemonicBase { + pub fn from_array(bytes: [u8; N]) -> MnemonicBase { #[allow(clippy::let_unit_value)] { let () = AssertValidMnemonicSize::::OK_CHUNKS; @@ -315,16 +315,6 @@ where Self::from_raw_bytes(&bytes) } - /// Generate a [`Mnemonic`] from the provided data and [`Wordlist`]. The data is expected to be - /// of 128, 192, or 256 bits, as per BIP-0039. - /// - /// # Errors - /// An error may be returned if the data is not within the expected lengths. - #[deprecated = "use Mnemonic::from_bytes"] - pub fn from_entropy(bytes: &[u8]) -> Result, MnemonicGenerationError> { - MnemonicBase::from_bytes(bytes) - } - /// Create a Mnemonic using an arbitrary length of given data. The length does not need to /// conform to BIP-0039 standards, but should be a multiple of 32 bits or 4 bytes. /// @@ -332,12 +322,12 @@ where /// This function can potentially produce mnemonics that are not BIP-0039 compliant or can't /// properly be encoded as a mnemonic. It is assumed the caller asserts the byte count is `% 4 /// == 0`. If the assumption is incorrect, code may panic. The - /// [`MnemonicBase::from_nonstandard_bytes`] function may be used to generate entropy if the - /// length of the data is known at compile-time. + /// [`MnemonicBase::from_array`] function may be used to generate entropy if the length of the + /// data is known at compile-time. /// /// # Examples /// ```rust - /// use keyfork_mnemonic_util::Mnemonic; + /// use keyfork_mnemonic::Mnemonic; /// let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// let mnemonic = unsafe { Mnemonic::from_raw_bytes(data.as_slice()) }; /// let mnemonic_text = mnemonic.to_string(); @@ -346,7 +336,7 @@ where /// If given an invalid length, undefined behavior may follow, or code may panic. /// /// ```rust,should_panic - /// use keyfork_mnemonic_util::Mnemonic; + /// use keyfork_mnemonic::Mnemonic; /// use std::str::FromStr; /// /// // NOTE: Data is of invalid length, 31 @@ -383,16 +373,31 @@ where &self.data } + /// A view to internal representation of the decoded data. + pub fn as_slice(&self) -> &[u8] { + &self.data + } + /// A clone of the internal representation of the decoded data. pub fn to_bytes(&self) -> Vec { self.data.to_vec() } + /// A clone of the internal representation of the decoded data. + pub fn to_vec(&self) -> Vec { + self.data.to_vec() + } + /// Conver the Mnemonic into the internal representation of the decoded data. pub fn into_bytes(self) -> Vec { self.data } + /// Conver the Mnemonic into the internal representation of the decoded data. + pub fn into_vec(self) -> Vec { + self.data + } + /// Clone the existing data. #[deprecated = "Use as_bytes(), to_bytes(), or into_bytes() instead"] pub fn entropy(&self) -> Vec { @@ -408,7 +413,7 @@ where &self, passphrase: impl Into>, ) -> Result, MnemonicGenerationError> { - Ok(self.generate_seed(passphrase)) + Ok(self.generate_seed(passphrase).to_vec()) } /// Create a BIP-0032 seed from the provided data and an optional passphrase. @@ -416,8 +421,7 @@ where /// # Panics /// The function may panic if the HmacSha512 function returns an error. The only error the /// HmacSha512 function should return is an invalid length, which should not be possible. - /// - pub fn generate_seed<'a>(&self, passphrase: impl Into>) -> Vec { + pub fn generate_seed<'a>(&self, passphrase: impl Into>) -> [u8; 64] { let passphrase = passphrase.into(); let mut seed = [0u8; 64]; @@ -425,7 +429,7 @@ where let salt = ["mnemonic", passphrase.unwrap_or("")].join(""); pbkdf2::>(mnemonic.as_bytes(), salt.as_bytes(), 2048, &mut seed) .expect(bug!("HmacSha512 InvalidLength should be infallible")); - seed.to_vec() + seed } /// Encode the mnemonic into a list of integers 11 bits in length, matching the length of a @@ -462,6 +466,39 @@ where } } +impl MnemonicBase +where + W: Wordlist, +{ + /// Generate a [`Mnemonic`] from the provided data and [`Wordlist`]. The data is expected to be + /// of 128, 192, or 256 bits, as per BIP-0039. + /// + /// # Errors + /// An error may be returned if the data is not within the expected lengths. + #[deprecated = "use Mnemonic::try_from_slice"] + pub fn from_bytes(bytes: &[u8]) -> Result, MnemonicGenerationError> { + MnemonicBase::try_from_slice(bytes) + } + + /// Generate a [`Mnemonic`] from the provided data and [`Wordlist`]. The data is expected to be + /// of 128, 192, or 256 bits, as per BIP-0039. + /// + /// # Errors + /// An error may be returned if the data is not within the expected lengths. + #[deprecated = "use Mnemonic::try_from_slice"] + pub fn from_entropy(bytes: &[u8]) -> Result, MnemonicGenerationError> { + MnemonicBase::try_from_slice(bytes) + } + + /// Generate a [`Mnemonic`] from the provided data and [`Wordlist`]. The data may be of a size + /// of a factor of 4, up to 1024 bytes. + /// + #[deprecated = "Use Mnemonic::from_array"] + pub fn from_nonstandard_bytes(bytes: [u8; N]) -> MnemonicBase { + MnemonicBase::from_array(bytes) + } +} + #[cfg(test)] mod tests { use std::{collections::HashSet, fs::File, io::Read}; @@ -478,7 +515,7 @@ mod tests { let mut random_handle = File::open("/dev/random").unwrap(); let entropy = &mut [0u8; 256 / 8]; random_handle.read_exact(&mut entropy[..]).unwrap(); - let mnemonic = super::Mnemonic::from_bytes(&entropy[..256 / 8]).unwrap(); + let mnemonic = super::Mnemonic::try_from_slice(&entropy[..256 / 8]).unwrap(); let new_entropy = mnemonic.as_bytes(); assert_eq!(new_entropy, entropy); } @@ -494,7 +531,7 @@ mod tests { }; let hex = hex::decode(hex_.as_str().unwrap()).unwrap(); - let mnemonic = Mnemonic::from_bytes(&hex).unwrap(); + let mnemonic = Mnemonic::try_from_slice(&hex).unwrap(); assert_eq!(mnemonic.to_string(), seed.as_str().unwrap()); } @@ -505,7 +542,7 @@ mod tests { let mut random_handle = File::open("/dev/random").unwrap(); let entropy = &mut [0u8; 256 / 8]; random_handle.read_exact(&mut entropy[..]).unwrap(); - let my_mnemonic = Mnemonic::from_bytes(&entropy[..256 / 8]).unwrap(); + let my_mnemonic = Mnemonic::try_from_slice(&entropy[..256 / 8]).unwrap(); let their_mnemonic = bip39::Mnemonic::from_entropy(&entropy[..256 / 8]).unwrap(); assert_eq!(my_mnemonic.to_string(), their_mnemonic.to_string()); assert_eq!(my_mnemonic.generate_seed(None), their_mnemonic.to_seed("")); @@ -529,7 +566,7 @@ mod tests { for _ in 0..tests { random.read_exact(&mut entropy[..]).unwrap(); - let mnemonic = Mnemonic::from_bytes(&entropy[..256 / 8]).unwrap(); + let mnemonic = Mnemonic::try_from_slice(&entropy[..256 / 8]).unwrap(); let words = mnemonic.words(); hs.clear(); hs.extend(words); @@ -560,7 +597,7 @@ mod tests { let mut entropy = [0u8; 1024]; let mut random = std::fs::File::open("/dev/urandom").unwrap(); random.read_exact(&mut entropy[..]).unwrap(); - let mnemonic = Mnemonic::from_nonstandard_bytes(entropy); + let mnemonic = Mnemonic::from_array(entropy); let words = mnemonic.words(); assert_eq!(words.len(), 768); } diff --git a/crates/util/keyfork-prompt/Cargo.toml b/crates/util/keyfork-prompt/Cargo.toml index 6fc6d15..ec4a51b 100644 --- a/crates/util/keyfork-prompt/Cargo.toml +++ b/crates/util/keyfork-prompt/Cargo.toml @@ -10,10 +10,10 @@ license = "MIT" [features] default = ["mnemonic"] -mnemonic = ["keyfork-mnemonic-util"] +mnemonic = ["keyfork-mnemonic"] [dependencies] keyfork-bug = { version = "0.1.0", path = "../keyfork-bug", registry = "distrust" } keyfork-crossterm = { version = "0.27.1", path = "../keyfork-crossterm", default-features = false, features = ["use-dev-tty", "events", "bracketed-paste"], registry = "distrust" } -keyfork-mnemonic-util = { version = "0.3.0", path = "../keyfork-mnemonic-util", optional = true, registry = "distrust" } +keyfork-mnemonic = { version = "0.3.0", path = "../keyfork-mnemonic", optional = true, registry = "distrust" } thiserror = "1.0.51" diff --git a/crates/util/keyfork-prompt/examples/test-basic-prompt.rs b/crates/util/keyfork-prompt/examples/test-basic-prompt.rs index 04c7391..5ee3269 100644 --- a/crates/util/keyfork-prompt/examples/test-basic-prompt.rs +++ b/crates/util/keyfork-prompt/examples/test-basic-prompt.rs @@ -7,7 +7,7 @@ use keyfork_prompt::{ Terminal, PromptHandler, }; -use keyfork_mnemonic_util::English; +use keyfork_mnemonic::English; fn main() -> Result<(), Box> { let mut mgr = Terminal::new(stdin(), stdout())?; diff --git a/crates/util/keyfork-prompt/src/lib.rs b/crates/util/keyfork-prompt/src/lib.rs index e12597b..3cf6eda 100644 --- a/crates/util/keyfork-prompt/src/lib.rs +++ b/crates/util/keyfork-prompt/src/lib.rs @@ -3,7 +3,7 @@ use std::borrow::Borrow; #[cfg(feature = "mnemonic")] -use keyfork_mnemonic_util::Wordlist; +use keyfork_mnemonic::Wordlist; /// pub mod terminal; diff --git a/crates/util/keyfork-prompt/src/validators.rs b/crates/util/keyfork-prompt/src/validators.rs index 8f2913c..d5a516f 100644 --- a/crates/util/keyfork-prompt/src/validators.rs +++ b/crates/util/keyfork-prompt/src/validators.rs @@ -158,7 +158,7 @@ pub mod mnemonic { use super::Validator; use keyfork_bug::bug; - use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError}; + use keyfork_mnemonic::{Mnemonic, MnemonicFromStrError}; /// A mnemonic could not be validated from the given input. #[derive(thiserror::Error, Debug)] diff --git a/docs/src/dev-guide/auditing.md b/docs/src/dev-guide/auditing.md index ae55ad4..5984fc5 100644 --- a/docs/src/dev-guide/auditing.md +++ b/docs/src/dev-guide/auditing.md @@ -45,7 +45,7 @@ A command line interface for generating, deriving from, and managing secrets. * [`keyfork-derive-openpgp`] * [`keyfork-derive-util`] * [`keyfork-entropy`] -* [`keyfork-mnemonic-util`] +* [`keyfork-mnemonic`] * [`keyfork-prompt`] * [`keyfork-qrcode`] * [`keyfork-shard`] @@ -68,7 +68,7 @@ seed or close-to-root derivations. * [`keyfork-derive-path-data`] * [`keyfork-derive-util`] * [`keyfork-frame`] -* [`keyfork-mnemonic-util`] +* [`keyfork-mnemonic`] * [`keyforkd-models`] * [`serde`] * [`thiserror`] @@ -129,7 +129,7 @@ BIP-0032 derivation. * [`ed25519-dalek`]: Ed25519 key parsing and arithmetic. * [`hmac`]: Derivation of keys using HMAC. * [`k256`]: secp256k1 (K-256) key parsing and arithmetic. -* [`keyfork-mnemonic-util`] +* [`keyfork-mnemonic`] * [`ripemd`]: Generating hash for fingerprinting of BIP-0032 derived data. * [`serde`] * [`sha2`]: Generating hashes for fingerprinting and derivation of data. @@ -145,7 +145,7 @@ M-of-N recombination of secret data using Shamir's Secret Sharing. * [`card-backend-pcsc`]: PCSC support for OpenPGP-card. * [`hkdf`]: Key derivation for transport encryption keys. * [`keyfork-derive-openpgp`] -* [`keyfork-mnemonic-util`]: Encoding encrypted shards using mnemonics. +* [`keyfork-mnemonic`]: Encoding encrypted shards using mnemonics. * [`keyfork-prompt`] * [`keyfork-qrcode`]: Encoding and decoding of encrypted shards using QR codes. * [`openpgp-card`]: OpenPGP card support. @@ -193,7 +193,7 @@ Frame data in a length-storing checksum-verified format. * [`thiserror`] * [`tokio`]: Read and write from AsyncRead and AsyncWrite sources. -## `keyfork-mnemonic-util` +## `keyfork-mnemonic` * [`hmac`]: Hash utilities. * [`sha2`]: Checksum of mnemonic data and hash for pbkdf2 @@ -202,7 +202,7 @@ Frame data in a length-storing checksum-verified format. ## `keyfork-prompt` * [`keyfork-crossterm`]: Interacting with the terminal. -* [`keyfork-mnemonic-util`] +* [`keyfork-mnemonic`] * [`thiserror`] ## `keyfork-plumbing` @@ -210,7 +210,7 @@ Frame data in a length-storing checksum-verified format. Binaries for `keyfork-entropy` and `keyfork-mnemonic-from-seed`. * [`keyfork-entropy`] -* [`keyfork-mnemonic-util`] +* [`keyfork-mnemonic`] * [`smex`] ## `keyfork-slip10-test-data` @@ -229,7 +229,7 @@ Zero-dependency hex encoding and decoding. [`keyfork-derive-util`]: #keyfork-derive-util [`keyfork-entropy`]: #keyfork-entropy [`keyfork-frame`]: #keyfork-frame -[`keyfork-mnemonic-util`]: #keyfork-mnemonic-util +[`keyfork-mnemonic`]: #keyfork-mnemonic [`keyfork-prompt`]: #keyfork-prompt [`keyfork-qrcode`]: #keyfork-qrcode [`keyfork-shard`]: #keyfork-shard