Merge branch 'ryansquared/keyfork-mnemonic-refactors'
This commit is contained in:
commit
35f57fcc41
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//!
|
||||
|
||||
use keyfork_mnemonic_util::Mnemonic;
|
||||
use keyfork_mnemonic::Mnemonic;
|
||||
|
||||
use tokio::io::{self, AsyncBufReadExt, BufReader};
|
||||
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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<KeyType>, UserID), Box<dyn std::error::Error>> {
|
||||
let mut pgp_u32 = [0u8; 4];
|
||||
pgp_u32[1..].copy_from_slice(&"pgp".bytes().collect::<Vec<u8>>());
|
||||
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}",
|
||||
);
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -2,32 +2,128 @@
|
|||
|
||||
#![allow(clippy::unreadable_literal)]
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
||||
|
||||
pub mod paths {
|
||||
use super::*;
|
||||
|
||||
/// The default derivation path for OpenPGP.
|
||||
pub static OPENPGP: DerivationIndex = DerivationIndex::new_unchecked(7366512, true);
|
||||
pub static OPENPGP: Lazy<DerivationPath> = 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<DerivationPath> = 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<DerivationPath> = 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<DerivationIndex> {
|
||||
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<Target> {
|
||||
Some(match path.iter().collect::<Vec<_>>()[..] {
|
||||
[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:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<dyn std::error::Error>> {
|
||||
|
|
|
@ -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;
|
||||
//!
|
||||
|
|
|
@ -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<dyn std::error::Error>> {
|
||||
/// 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 = //
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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<dyn std::error::Erro
|
|||
while iter_count.is_none() || iter_count.is_some_and(|i| i > 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")]
|
||||
{
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||
|
@ -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::<Vec<u8>>());
|
||||
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(),
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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<Cert> {
|
|||
KeyFlags::empty().set_authentication(),
|
||||
];
|
||||
|
||||
let mut pgp_u32 = [0u8; 4];
|
||||
pgp_u32[1..].copy_from_slice(&"pgp".bytes().collect::<Vec<u8>>());
|
||||
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::<Vec<u8>>());
|
||||
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());
|
||||
|
|
|
@ -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"
|
|
@ -1,6 +1,6 @@
|
|||
//!
|
||||
|
||||
use keyfork_mnemonic_util::Mnemonic;
|
||||
use keyfork_mnemonic::Mnemonic;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let input = std::io::stdin();
|
|
@ -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<MnemonicBase<W>, MnemonicGenerationError> {
|
||||
pub fn try_from_slice(bytes: &[u8]) -> Result<MnemonicBase<W>, 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<const N: usize>(bytes: [u8; N]) -> MnemonicBase<W> {
|
||||
pub fn from_array<const N: usize>(bytes: [u8; N]) -> MnemonicBase<W> {
|
||||
#[allow(clippy::let_unit_value)]
|
||||
{
|
||||
let () = AssertValidMnemonicSize::<N>::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<MnemonicBase<W>, 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<u8> {
|
||||
self.data.to_vec()
|
||||
}
|
||||
|
||||
/// A clone of the internal representation of the decoded data.
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
self.data.to_vec()
|
||||
}
|
||||
|
||||
/// Conver the Mnemonic into the internal representation of the decoded data.
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
self.data
|
||||
}
|
||||
|
||||
/// Conver the Mnemonic into the internal representation of the decoded data.
|
||||
pub fn into_vec(self) -> Vec<u8> {
|
||||
self.data
|
||||
}
|
||||
|
||||
/// Clone the existing data.
|
||||
#[deprecated = "Use as_bytes(), to_bytes(), or into_bytes() instead"]
|
||||
pub fn entropy(&self) -> Vec<u8> {
|
||||
|
@ -408,7 +413,7 @@ where
|
|||
&self,
|
||||
passphrase: impl Into<Option<&'a str>>,
|
||||
) -> Result<Vec<u8>, 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<Option<&'a str>>) -> Vec<u8> {
|
||||
pub fn generate_seed<'a>(&self, passphrase: impl Into<Option<&'a str>>) -> [u8; 64] {
|
||||
let passphrase = passphrase.into();
|
||||
|
||||
let mut seed = [0u8; 64];
|
||||
|
@ -425,7 +429,7 @@ where
|
|||
let salt = ["mnemonic", passphrase.unwrap_or("")].join("");
|
||||
pbkdf2::<Hmac<Sha512>>(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<W> MnemonicBase<W>
|
||||
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<MnemonicBase<W>, 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<MnemonicBase<W>, 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<const N: usize>(bytes: [u8; N]) -> MnemonicBase<W> {
|
||||
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);
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -7,7 +7,7 @@ use keyfork_prompt::{
|
|||
Terminal, PromptHandler,
|
||||
};
|
||||
|
||||
use keyfork_mnemonic_util::English;
|
||||
use keyfork_mnemonic::English;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut mgr = Terminal::new(stdin(), stdout())?;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use std::borrow::Borrow;
|
||||
|
||||
#[cfg(feature = "mnemonic")]
|
||||
use keyfork_mnemonic_util::Wordlist;
|
||||
use keyfork_mnemonic::Wordlist;
|
||||
|
||||
///
|
||||
pub mod terminal;
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue