Compare commits
No commits in common. "12095495326ab637999674fb684a5c73e52eb974" and "4e2c4487e9fa608e04ec7f890a02b21f875ec53c" have entirely different histories.
1209549532
...
4e2c4487e9
|
@ -244,7 +244,7 @@ dependencies = [
|
||||||
"futures-lite 2.2.0",
|
"futures-lite 2.2.0",
|
||||||
"parking",
|
"parking",
|
||||||
"polling 3.3.2",
|
"polling 3.3.2",
|
||||||
"rustix 0.38.31",
|
"rustix 0.38.30",
|
||||||
"slab",
|
"slab",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
|
@ -1610,7 +1610,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
|
checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi",
|
||||||
"rustix 0.38.31",
|
"rustix 0.38.30",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1870,7 +1870,6 @@ dependencies = [
|
||||||
"keyfork-slip10-test-data",
|
"keyfork-slip10-test-data",
|
||||||
"keyforkd-models",
|
"keyforkd-models",
|
||||||
"serde",
|
"serde",
|
||||||
"tempfile",
|
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower",
|
"tower",
|
||||||
|
@ -2534,7 +2533,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"concurrent-queue",
|
"concurrent-queue",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rustix 0.38.31",
|
"rustix 0.38.30",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
@ -2807,9 +2806,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.31"
|
version = "0.38.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
|
checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.2",
|
"bitflags 2.4.2",
|
||||||
"errno",
|
"errno",
|
||||||
|
@ -3185,13 +3184,14 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.10.0"
|
version = "3.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67"
|
checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"fastrand 2.0.1",
|
"fastrand 2.0.1",
|
||||||
"rustix 0.38.31",
|
"redox_syscall",
|
||||||
|
"rustix 0.38.30",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3212,7 +3212,7 @@ version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
|
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustix 0.38.31",
|
"rustix 0.38.30",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3606,7 +3606,7 @@ dependencies = [
|
||||||
"either",
|
"either",
|
||||||
"home",
|
"home",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 0.38.31",
|
"rustix 0.38.30",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -2,13 +2,8 @@
|
||||||
|
|
||||||
use std::{collections::HashMap, os::unix::net::UnixStream, path::PathBuf};
|
use std::{collections::HashMap, os::unix::net::UnixStream, path::PathBuf};
|
||||||
|
|
||||||
use keyfork_derive_util::{
|
|
||||||
request::{AsAlgorithm, DerivationRequest},
|
|
||||||
DerivationPath, ExtendedPrivateKey, PrivateKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
use keyfork_frame::{try_decode_from, try_encode_to, DecodeError, EncodeError};
|
use keyfork_frame::{try_decode_from, try_encode_to, DecodeError, EncodeError};
|
||||||
use keyforkd_models::{Error as KeyforkdError, Request, Response};
|
use keyforkd_models::{Request, Response, Error as KeyforkdError};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
@ -16,10 +11,6 @@ mod tests;
|
||||||
/// An error occurred while interacting with Keyforkd.
|
/// An error occurred while interacting with Keyforkd.
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// The response from the server did not match the request.
|
|
||||||
#[error("The response from the server did not match the request")]
|
|
||||||
InvalidResponse,
|
|
||||||
|
|
||||||
/// The environment variables used for determining a Keyforkd socket path were not set.
|
/// The environment variables used for determining a Keyforkd socket path were not set.
|
||||||
#[error("Neither KEYFORK_SOCKET_PATH nor XDG_RUNTIME_DIR were set")]
|
#[error("Neither KEYFORK_SOCKET_PATH nor XDG_RUNTIME_DIR were set")]
|
||||||
EnvVarsNotFound,
|
EnvVarsNotFound,
|
||||||
|
@ -46,7 +37,7 @@ pub enum Error {
|
||||||
|
|
||||||
/// An error encountered in Keyforkd.
|
/// An error encountered in Keyforkd.
|
||||||
#[error("Error in Keyforkd: {0}")]
|
#[error("Error in Keyforkd: {0}")]
|
||||||
Keyforkd(#[from] KeyforkdError),
|
Keyforkd(#[from] KeyforkdError)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
|
@ -104,38 +95,6 @@ impl Client {
|
||||||
get_socket().map(|socket| Self { socket })
|
get_socket().map(|socket| Self { socket })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request an [`ExtendedPrivateKey`] for a given [`DerivationPath`].
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
/// An error may be returned if:
|
|
||||||
/// * Reading or writing from or to the socket encountered an error.
|
|
||||||
/// * Bincode could not serialize the request or deserialize the response.
|
|
||||||
/// * An error occurred in Keyforkd.
|
|
||||||
/// * Keyforkd returned invalid data.
|
|
||||||
pub fn request_xprv<K>(&mut self, path: &DerivationPath) -> Result<ExtendedPrivateKey<K>>
|
|
||||||
where
|
|
||||||
K: PrivateKey + Clone + AsAlgorithm,
|
|
||||||
{
|
|
||||||
let algo = K::as_algorithm();
|
|
||||||
let request = Request::Derivation(DerivationRequest::new(algo.clone(), path));
|
|
||||||
let response = self.request(&request)?;
|
|
||||||
match response {
|
|
||||||
Response::Derivation(d) => {
|
|
||||||
if d.algorithm != algo {
|
|
||||||
return Err(Error::InvalidResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
let depth = path.len() as u8;
|
|
||||||
Ok(ExtendedPrivateKey::new_from_parts(
|
|
||||||
&d.data,
|
|
||||||
depth,
|
|
||||||
d.chain_code,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
_ => Err(Error::InvalidResponse),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serialize and send a [`Request`] to the server, awaiting a [`Result<Response>`].
|
/// Serialize and send a [`Request`] to the server, awaiting a [`Result<Response>`].
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
|
|
|
@ -1,20 +1,40 @@
|
||||||
use crate::Client;
|
use crate::Client;
|
||||||
use keyforkd::test_util::{run_test, Infallible};
|
|
||||||
use keyfork_derive_util::{request::*, DerivationPath};
|
use keyfork_derive_util::{request::*, DerivationPath};
|
||||||
use keyfork_slip10_test_data::test_data;
|
use keyfork_slip10_test_data::test_data;
|
||||||
|
use std::sync::mpsc::channel;
|
||||||
use std::{os::unix::net::UnixStream, str::FromStr};
|
use std::{os::unix::net::UnixStream, str::FromStr};
|
||||||
|
use tokio::runtime::Builder;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn secp256k1_test_suite() {
|
fn secp256k1() {
|
||||||
let tests = test_data()
|
let tests = test_data()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.remove(&"secp256k1".to_string())
|
.remove(&"secp256k1".to_string())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for seed_test in tests {
|
// note: since client is non async, can't be single threaded
|
||||||
let seed = seed_test.seed;
|
let rt = Builder::new_multi_thread().enable_io().build().unwrap();
|
||||||
run_test(&seed, move |socket_path| {
|
let tempdir = tempfile::tempdir().unwrap();
|
||||||
for test in seed_test.tests {
|
for (i, per_seed) in tests.into_iter().enumerate() {
|
||||||
|
let mut socket_name = i.to_string();
|
||||||
|
socket_name.push_str("-keyforkd.sock");
|
||||||
|
let socket_path = tempdir.path().join(socket_name);
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
let handle = rt.spawn({
|
||||||
|
let socket_path = socket_path.clone();
|
||||||
|
async move {
|
||||||
|
let seed = per_seed.seed.clone();
|
||||||
|
let mut server = keyforkd::UnixServer::bind(&socket_path).unwrap();
|
||||||
|
tx.send(()).unwrap();
|
||||||
|
let service = keyforkd::ServiceBuilder::new()
|
||||||
|
.layer(keyforkd::middleware::BincodeLayer::new())
|
||||||
|
.service(keyforkd::Keyforkd::new(seed));
|
||||||
|
server.run(service).await.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rx.recv().unwrap();
|
||||||
|
|
||||||
|
for test in &per_seed.tests {
|
||||||
let socket = UnixStream::connect(&socket_path).unwrap();
|
let socket = UnixStream::connect(&socket_path).unwrap();
|
||||||
let mut client = Client::new(socket);
|
let mut client = Client::new(socket);
|
||||||
let chain = DerivationPath::from_str(test.chain).unwrap();
|
let chain = DerivationPath::from_str(test.chain).unwrap();
|
||||||
|
@ -27,24 +47,39 @@ fn secp256k1_test_suite() {
|
||||||
);
|
);
|
||||||
let response =
|
let response =
|
||||||
DerivationResponse::try_from(client.request(&req.into()).unwrap()).unwrap();
|
DerivationResponse::try_from(client.request(&req.into()).unwrap()).unwrap();
|
||||||
assert_eq!(&response.data, test.private_key.as_slice());
|
assert_eq!(response.data, test.private_key);
|
||||||
}
|
}
|
||||||
Infallible::Ok(())
|
|
||||||
}).unwrap();
|
handle.abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ed25519_test_suite() {
|
fn ed25519() {
|
||||||
let tests = test_data()
|
let tests = test_data().unwrap().remove(&"ed25519".to_string()).unwrap();
|
||||||
.unwrap()
|
|
||||||
.remove(&"ed25519".to_string())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
for seed_test in tests {
|
let rt = Builder::new_multi_thread().enable_io().build().unwrap();
|
||||||
let seed = seed_test.seed;
|
let tempdir = tempfile::tempdir().unwrap();
|
||||||
run_test(&seed, move |socket_path| {
|
for (i, per_seed) in tests.into_iter().enumerate() {
|
||||||
for test in seed_test.tests {
|
let mut socket_name = i.to_string();
|
||||||
|
socket_name.push_str("-keyforkd.sock");
|
||||||
|
let socket_path = tempdir.path().join(socket_name);
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
let handle = rt.spawn({
|
||||||
|
let socket_path = socket_path.clone();
|
||||||
|
async move {
|
||||||
|
let seed = per_seed.seed.clone();
|
||||||
|
let mut server = keyforkd::UnixServer::bind(&socket_path).unwrap();
|
||||||
|
tx.send(()).unwrap();
|
||||||
|
let service = keyforkd::ServiceBuilder::new()
|
||||||
|
.layer(keyforkd::middleware::BincodeLayer::new())
|
||||||
|
.service(keyforkd::Keyforkd::new(seed));
|
||||||
|
server.run(service).await.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rx.recv().unwrap();
|
||||||
|
|
||||||
|
for test in &per_seed.tests {
|
||||||
let socket = UnixStream::connect(&socket_path).unwrap();
|
let socket = UnixStream::connect(&socket_path).unwrap();
|
||||||
let mut client = Client::new(socket);
|
let mut client = Client::new(socket);
|
||||||
let chain = DerivationPath::from_str(test.chain).unwrap();
|
let chain = DerivationPath::from_str(test.chain).unwrap();
|
||||||
|
@ -57,9 +92,9 @@ fn ed25519_test_suite() {
|
||||||
);
|
);
|
||||||
let response =
|
let response =
|
||||||
DerivationResponse::try_from(client.request(&req.into()).unwrap()).unwrap();
|
DerivationResponse::try_from(client.request(&req.into()).unwrap()).unwrap();
|
||||||
assert_eq!(&response.data, test.private_key.as_slice());
|
assert_eq!(response.data, test.private_key);
|
||||||
}
|
}
|
||||||
Infallible::Ok(())
|
|
||||||
}).unwrap();
|
handle.abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,6 @@ tower = { version = "0.4.13", features = ["tokio", "util"] }
|
||||||
# Personally audited
|
# Personally audited
|
||||||
thiserror = "1.0.47"
|
thiserror = "1.0.47"
|
||||||
serde = { version = "1.0.186", features = ["derive"] }
|
serde = { version = "1.0.186", features = ["derive"] }
|
||||||
tempfile = { version = "3.10.0", default-features = false }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex-literal = "0.4.1"
|
hex-literal = "0.4.1"
|
||||||
|
|
|
@ -30,8 +30,6 @@ pub use error::Keyforkd as KeyforkdError;
|
||||||
pub use server::UnixServer;
|
pub use server::UnixServer;
|
||||||
pub use service::Keyforkd;
|
pub use service::Keyforkd;
|
||||||
|
|
||||||
pub mod test_util;
|
|
||||||
|
|
||||||
/// Set up a Tracing subscriber, defaulting to debug mode.
|
/// Set up a Tracing subscriber, defaulting to debug mode.
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
pub fn setup_registry() {
|
pub fn setup_registry() {
|
||||||
|
|
|
@ -76,7 +76,7 @@ impl Service<Request> for Keyforkd {
|
||||||
info!("Deriving path: {}", req.path());
|
info!("Deriving path: {}", req.path());
|
||||||
}
|
}
|
||||||
|
|
||||||
req.derive_with_master_seed(seed.as_ref())
|
req.derive_with_master_seed((*seed).clone())
|
||||||
.map(Response::Derivation)
|
.map(Response::Derivation)
|
||||||
.map_err(|e| DerivationError::Derivation(e.to_string()).into())
|
.map_err(|e| DerivationError::Derivation(e.to_string()).into())
|
||||||
}),
|
}),
|
||||||
|
@ -120,7 +120,7 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(&response.data, test.private_key.as_slice());
|
assert_eq!(response.data, test.private_key);
|
||||||
assert_eq!(response.chain_code.as_slice(), test.chain_code);
|
assert_eq!(response.chain_code.as_slice(), test.chain_code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,7 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(&response.data, test.private_key.as_slice());
|
assert_eq!(response.data, test.private_key);
|
||||||
assert_eq!(response.chain_code.as_slice(), test.chain_code);
|
assert_eq!(response.chain_code.as_slice(), test.chain_code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
//! # Keyforkd Test Utilities
|
|
||||||
//!
|
|
||||||
//! This module adds a helper to set up a Tokio runtime, start a Tokio runtime with a given seed,
|
|
||||||
//! start a Keyfork server on that runtime, and run a given test closure.
|
|
||||||
|
|
||||||
use crate::{middleware, Keyforkd, ServiceBuilder, UnixServer};
|
|
||||||
|
|
||||||
use tokio::runtime::Builder;
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[error("This error can never be instantiated")]
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub struct InfallibleError {
|
|
||||||
protected: (),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An infallible result. This type can be used to represent a function that should never error.
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use keyforkd::test_util::Infallible;
|
|
||||||
/// let closure = || {
|
|
||||||
/// Infallible::Ok(())
|
|
||||||
/// };
|
|
||||||
/// assert!(closure().is_ok());
|
|
||||||
/// ```
|
|
||||||
pub type Infallible<T> = std::result::Result<T, InfallibleError>;
|
|
||||||
|
|
||||||
/// Run a test making use of a Keyforkd server. The path to the socket of the Keyforkd server is
|
|
||||||
/// provided as the only argument to the closure. The closure is expected to return a Result; the
|
|
||||||
/// Error field of the Result may be an error returned by a test.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// The function is not expected to run in production; therefore, the function plays "fast and
|
|
||||||
/// loose" wih the usage of [`Result::expect`]. In normal usage, these should never be an issue.
|
|
||||||
#[allow(clippy::missing_errors_doc)]
|
|
||||||
pub fn run_test<F, E>(seed: &[u8], closure: F) -> Result<(), E>
|
|
||||||
where
|
|
||||||
F: FnOnce(&std::path::Path) -> Result<(), E> + Send + 'static,
|
|
||||||
E: Send + 'static,
|
|
||||||
{
|
|
||||||
let rt = Builder::new_multi_thread()
|
|
||||||
.worker_threads(2)
|
|
||||||
.enable_io()
|
|
||||||
.build()
|
|
||||||
.expect("tokio threaded IO runtime");
|
|
||||||
let socket_dir = tempfile::tempdir().expect("can't create tempdir");
|
|
||||||
let socket_path = socket_dir.path().join("keyforkd.sock");
|
|
||||||
rt.block_on(async move {
|
|
||||||
let (tx, mut rx) = tokio::sync::mpsc::channel(1);
|
|
||||||
let server_handle = tokio::spawn({
|
|
||||||
let socket_path = socket_path.clone();
|
|
||||||
let seed = seed.to_vec();
|
|
||||||
async move {
|
|
||||||
let mut server = UnixServer::bind(&socket_path).expect("can't bind unix socket");
|
|
||||||
tx.send(()).await.expect("couldn't send server start signal");
|
|
||||||
let service = ServiceBuilder::new()
|
|
||||||
.layer(middleware::BincodeLayer::new())
|
|
||||||
.service(Keyforkd::new(seed.to_vec()));
|
|
||||||
server.run(service).await.unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
rx.recv()
|
|
||||||
.await
|
|
||||||
.expect("can't receive server start signal from channel");
|
|
||||||
let test_handle = tokio::task::spawn_blocking(move || closure(&socket_path));
|
|
||||||
|
|
||||||
let result = test_handle.await;
|
|
||||||
server_handle.abort();
|
|
||||||
result
|
|
||||||
})
|
|
||||||
.expect("runtime could not join all threads")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_run_test() {
|
|
||||||
let seed = b"beefbeef";
|
|
||||||
run_test(seed, |_path| Infallible::Ok(())).expect("infallible");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,10 +2,13 @@
|
||||||
|
|
||||||
use std::time::{Duration, SystemTime, SystemTimeError};
|
use std::time::{Duration, SystemTime, SystemTimeError};
|
||||||
|
|
||||||
use derive_util::{DerivationIndex, ExtendedPrivateKey, IndexError, PrivateKey};
|
use derive_util::{
|
||||||
|
request::{DerivationResponse, TryFromDerivationResponseError},
|
||||||
|
DerivationIndex, ExtendedPrivateKey, PrivateKey,
|
||||||
|
IndexError,
|
||||||
|
};
|
||||||
use ed25519_dalek::SigningKey;
|
use ed25519_dalek::SigningKey;
|
||||||
pub use keyfork_derive_util as derive_util;
|
pub use keyfork_derive_util as derive_util;
|
||||||
pub use sequoia_openpgp as openpgp;
|
|
||||||
use sequoia_openpgp::{
|
use sequoia_openpgp::{
|
||||||
packet::{
|
packet::{
|
||||||
key::{Key4, PrimaryRole, SubordinateRole},
|
key::{Key4, PrimaryRole, SubordinateRole},
|
||||||
|
@ -15,9 +18,7 @@ use sequoia_openpgp::{
|
||||||
types::{KeyFlags, SignatureType},
|
types::{KeyFlags, SignatureType},
|
||||||
Cert, Packet,
|
Cert, Packet,
|
||||||
};
|
};
|
||||||
|
pub use sequoia_openpgp as openpgp;
|
||||||
pub type XPrvKey = SigningKey;
|
|
||||||
pub type XPrv = ExtendedPrivateKey<SigningKey>;
|
|
||||||
|
|
||||||
/// An error occurred while creating an OpenPGP key.
|
/// An error occurred while creating an OpenPGP key.
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
@ -31,6 +32,10 @@ pub enum Error {
|
||||||
#[error("Key configured with both encryption and non-encryption key flags: {0:?}")]
|
#[error("Key configured with both encryption and non-encryption key flags: {0:?}")]
|
||||||
InvalidKeyFlags(KeyFlags),
|
InvalidKeyFlags(KeyFlags),
|
||||||
|
|
||||||
|
/// The derivation response contained incorrect data.
|
||||||
|
#[error("Incorrect derived data: {0}")]
|
||||||
|
IncorrectDerivedData(#[from] TryFromDerivationResponseError),
|
||||||
|
|
||||||
/// A derivation index could not be created from the given index.
|
/// A derivation index could not be created from the given index.
|
||||||
#[error("Could not create derivation index: {0}")]
|
#[error("Could not create derivation index: {0}")]
|
||||||
Index(#[from] IndexError),
|
Index(#[from] IndexError),
|
||||||
|
@ -61,7 +66,7 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// The function may error for any condition mentioned in [`Error`].
|
/// The function may error for any condition mentioned in [`Error`].
|
||||||
pub fn derive(xprv: XPrv, keys: &[KeyFlags], userid: &UserID) -> Result<Cert> {
|
pub fn derive(data: DerivationResponse, keys: &[KeyFlags], userid: &UserID) -> Result<Cert> {
|
||||||
let primary_key_flags = match keys.get(0) {
|
let primary_key_flags = match keys.get(0) {
|
||||||
Some(kf) if kf.for_certification() => kf,
|
Some(kf) if kf.for_certification() => kf,
|
||||||
_ => return Err(Error::NotCert),
|
_ => return Err(Error::NotCert),
|
||||||
|
@ -71,6 +76,7 @@ pub fn derive(xprv: XPrv, keys: &[KeyFlags], userid: &UserID) -> Result<Cert> {
|
||||||
let one_day = SystemTime::now() + Duration::from_secs(60 * 60 * 24);
|
let one_day = SystemTime::now() + Duration::from_secs(60 * 60 * 24);
|
||||||
|
|
||||||
// Create certificate with initial key and signature
|
// Create certificate with initial key and signature
|
||||||
|
let xprv = ExtendedPrivateKey::<SigningKey>::try_from(data)?;
|
||||||
let derived_primary_key = xprv.derive_child(&DerivationIndex::new(0, true)?)?;
|
let derived_primary_key = xprv.derive_child(&DerivationIndex::new(0, true)?)?;
|
||||||
let primary_key = Key::from(Key4::<_, PrimaryRole>::import_secret_ed25519(
|
let primary_key = Key::from(Key4::<_, PrimaryRole>::import_secret_ed25519(
|
||||||
&PrivateKey::to_bytes(derived_primary_key.private_key()),
|
&PrivateKey::to_bytes(derived_primary_key.private_key()),
|
||||||
|
@ -112,14 +118,21 @@ pub fn derive(xprv: XPrv, keys: &[KeyFlags], userid: &UserID) -> Result<Cert> {
|
||||||
bytes[0] &= 0b1111_1000;
|
bytes[0] &= 0b1111_1000;
|
||||||
bytes[31] &= !0b1000_0000;
|
bytes[31] &= !0b1000_0000;
|
||||||
bytes[31] |= 0b0100_0000;
|
bytes[31] |= 0b0100_0000;
|
||||||
Key::from(Key4::<_, SubordinateRole>::import_secret_cv25519(
|
Key::from(
|
||||||
&bytes, None, None, epoch,
|
Key4::<_, SubordinateRole>::import_secret_cv25519(
|
||||||
)?)
|
&bytes,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
epoch,
|
||||||
|
)?
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Key::from(Key4::<_, SubordinateRole>::import_secret_ed25519(
|
Key::from(
|
||||||
|
Key4::<_, SubordinateRole>::import_secret_ed25519(
|
||||||
&PrivateKey::to_bytes(derived_key.private_key()),
|
&PrivateKey::to_bytes(derived_key.private_key()),
|
||||||
epoch,
|
epoch,
|
||||||
)?)
|
)?
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
// As per OpenPGP spec, signing keys must backsig the primary key
|
// As per OpenPGP spec, signing keys must backsig the primary key
|
||||||
|
|
|
@ -2,16 +2,12 @@
|
||||||
|
|
||||||
use std::{env, process::ExitCode, str::FromStr};
|
use std::{env, process::ExitCode, str::FromStr};
|
||||||
|
|
||||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
use keyfork_derive_util::{
|
||||||
use keyforkd_client::Client;
|
request::{DerivationAlgorithm, DerivationRequest, DerivationResponse},
|
||||||
|
DerivationIndex, DerivationPath,
|
||||||
use ed25519_dalek::SigningKey;
|
|
||||||
use sequoia_openpgp::{
|
|
||||||
armor::{Kind, Writer},
|
|
||||||
packet::UserID,
|
|
||||||
serialize::Marshal,
|
|
||||||
types::KeyFlags,
|
|
||||||
};
|
};
|
||||||
|
use keyforkd_client::Client;
|
||||||
|
use sequoia_openpgp::{packet::UserID, types::KeyFlags, armor::{Kind, Writer}, serialize::Marshal};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
enum Error {
|
enum Error {
|
||||||
|
@ -112,13 +108,16 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
_ => panic!("Usage: {program_name} path subkey_format default_userid"),
|
_ => panic!("Usage: {program_name} path subkey_format default_userid"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let derived_xprv = Client::discover_socket()?.request_xprv::<SigningKey>(&path)?;
|
let request = DerivationRequest::new(DerivationAlgorithm::Ed25519, &path);
|
||||||
|
let derived_data: DerivationResponse = Client::discover_socket()?
|
||||||
|
.request(&request.into())?
|
||||||
|
.try_into()?;
|
||||||
let subkeys = subkey_format
|
let subkeys = subkey_format
|
||||||
.iter()
|
.iter()
|
||||||
.map(|kt| kt.inner().clone())
|
.map(|kt| kt.inner().clone())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let cert = keyfork_derive_openpgp::derive(derived_xprv, subkeys.as_slice(), &default_userid)?;
|
let cert = keyfork_derive_openpgp::derive(derived_data, subkeys.as_slice(), &default_userid)?;
|
||||||
|
|
||||||
let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?;
|
let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?;
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let key1 = request.derive_with_mnemonic(&mnemonic)?;
|
let key1 = request.derive_with_mnemonic(&mnemonic)?;
|
||||||
|
|
||||||
let seed = mnemonic.seed(None)?;
|
let seed = mnemonic.seed(None)?;
|
||||||
let key2 = request.derive_with_master_seed(&seed)?;
|
let key2 = request.derive_with_master_seed(seed)?;
|
||||||
|
|
||||||
assert_eq!(key1, key2);
|
assert_eq!(key1, key2);
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,18 @@ const KEY_SIZE: usize = 256;
|
||||||
/// Errors associated with creating or deriving Extended Private Keys.
|
/// Errors associated with creating or deriving Extended Private Keys.
|
||||||
#[derive(Error, Clone, Debug)]
|
#[derive(Error, Clone, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
/// The seed has an unsuitable length; supported lengths are 16 bytes, 32 bytes, or 64 bytes.
|
||||||
|
#[error("Seed had an unsuitable length: {0}")]
|
||||||
|
BadSeedLength(usize),
|
||||||
|
|
||||||
/// The maximum depth for key derivation has been reached. The supported maximum depth is 255.
|
/// The maximum depth for key derivation has been reached. The supported maximum depth is 255.
|
||||||
#[error("Reached maximum depth for key derivation")]
|
#[error("Reached maximum depth for key derivation")]
|
||||||
Depth,
|
Depth,
|
||||||
|
|
||||||
|
/// This should never happen. HMAC keys should be able to take any size input.
|
||||||
|
#[error("Invalid length for HMAC key while generating master key (report me!)")]
|
||||||
|
HmacInvalidLength(#[from] hmac::digest::InvalidLength),
|
||||||
|
|
||||||
/// An unknown error occurred while deriving a child key.
|
/// An unknown error occurred while deriving a child key.
|
||||||
#[error("Unknown error while deriving child key")]
|
#[error("Unknown error while deriving child key")]
|
||||||
Derivation,
|
Derivation,
|
||||||
|
@ -31,104 +39,17 @@ type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
type ChainCode = [u8; 32];
|
type ChainCode = [u8; 32];
|
||||||
type HmacSha512 = Hmac<Sha512>;
|
type HmacSha512 = Hmac<Sha512>;
|
||||||
|
|
||||||
/// A reference to a variable-length seed. Keyfork automatically supports a seed of 128 bits,
|
|
||||||
/// 256 bits, or 512 bits, but because the master key is derived from a hashed seed, in theory
|
|
||||||
/// any amount of bytes could be used. It is not advised to use a variable-length seed longer
|
|
||||||
/// than 256 bits, as a brute-force attack on the master key could be performed in 2^256
|
|
||||||
/// attempts.
|
|
||||||
///
|
|
||||||
/// Mnemonics use a 512 bit seed, as knowledge of the mnemonics' words (such as through a side
|
|
||||||
/// channel attack) could leak which individual word is used, but not the order the words are
|
|
||||||
/// used in. Using a 512 bit hash to generate the seed results in a more computationally
|
|
||||||
/// expensive brute-force requirement.
|
|
||||||
pub struct VariableLengthSeed<'a> {
|
|
||||||
seed: &'a [u8],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> VariableLengthSeed<'a> {
|
|
||||||
/// Create a new VariableLengthSeed.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```rust
|
|
||||||
/// use sha2::{Sha256, Digest};
|
|
||||||
/// use keyfork_derive_util::VariableLengthSeed;
|
|
||||||
///
|
|
||||||
/// let data = b"the missile is very eepy and wants to take a small sleeb";
|
|
||||||
/// let seed = VariableLengthSeed::new(data);
|
|
||||||
/// ```
|
|
||||||
pub fn new(seed: &'a [u8]) -> Self {
|
|
||||||
Self { seed }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod as_private_key {
|
|
||||||
use super::VariableLengthSeed;
|
|
||||||
|
|
||||||
pub trait AsPrivateKey {
|
|
||||||
fn as_private_key(&self) -> &[u8];
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsPrivateKey for [u8; 16] {
|
|
||||||
fn as_private_key(&self) -> &[u8] {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsPrivateKey for [u8; 32] {
|
|
||||||
fn as_private_key(&self) -> &[u8] {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsPrivateKey for [u8; 64] {
|
|
||||||
fn as_private_key(&self) -> &[u8] {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsPrivateKey for VariableLengthSeed<'_> {
|
|
||||||
fn as_private_key(&self) -> &[u8] {
|
|
||||||
self.seed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extended private keys derived using BIP-0032.
|
/// Extended private keys derived using BIP-0032.
|
||||||
///
|
///
|
||||||
/// Generic over types implementing [`PrivateKey`].
|
/// Generic over types implementing [`PrivateKey`].
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct ExtendedPrivateKey<K: PrivateKey + Clone> {
|
pub struct ExtendedPrivateKey<K: PrivateKey + Clone> {
|
||||||
/// The internal private key data.
|
/// The internal private key data.
|
||||||
#[serde(with = "serde_with")]
|
|
||||||
private_key: K,
|
private_key: K,
|
||||||
depth: u8,
|
depth: u8,
|
||||||
chain_code: ChainCode,
|
chain_code: ChainCode,
|
||||||
}
|
}
|
||||||
|
|
||||||
mod serde_with {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub(crate) fn serialize<S, K>(value: &K, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
K: PrivateKey + Clone,
|
|
||||||
{
|
|
||||||
serializer.serialize_bytes(&value.to_bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn deserialize<'de, D, K>(deserializer: D) -> Result<K, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de>,
|
|
||||||
K: PrivateKey + Clone,
|
|
||||||
{
|
|
||||||
let variable_len_bytes = <&[u8]>::deserialize(deserializer)?;
|
|
||||||
let bytes: [u8; 32] = variable_len_bytes
|
|
||||||
.try_into()
|
|
||||||
.expect("unable to parse serialized private key; no support for static len");
|
|
||||||
Ok(K::from_bytes(&bytes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: PrivateKey + Clone> std::fmt::Debug for ExtendedPrivateKey<K> {
|
impl<K: PrivateKey + Clone> std::fmt::Debug for ExtendedPrivateKey<K> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("ExtendedPrivateKey")
|
f.debug_struct("ExtendedPrivateKey")
|
||||||
|
@ -152,7 +73,7 @@ where
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// An error may be returned if:
|
/// An error may be returned if:
|
||||||
/// * The given seed had an incorrect length.
|
/// * The given seed had an incorrect length.
|
||||||
/// * A `HmacSha512` can't be constructed.
|
/// * A `HmacSha512` can't be constructed - this should be impossible.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```rust
|
||||||
|
@ -161,26 +82,31 @@ where
|
||||||
/// # public_key::TestPublicKey as PublicKey,
|
/// # public_key::TestPublicKey as PublicKey,
|
||||||
/// # private_key::TestPrivateKey as PrivateKey,
|
/// # private_key::TestPrivateKey as PrivateKey,
|
||||||
/// # };
|
/// # };
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let seed: &[u8; 64] = //
|
/// let seed: &[u8; 64] = //
|
||||||
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||||
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new(*seed);
|
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?;
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new(seed: impl as_private_key::AsPrivateKey) -> Self {
|
pub fn new(seed: impl AsRef<[u8]>) -> Result<Self> {
|
||||||
Self::new_internal(seed.as_private_key())
|
Self::new_internal(seed.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_internal(seed: &[u8]) -> Self {
|
fn new_internal(seed: &[u8]) -> Result<Self> {
|
||||||
let hash = HmacSha512::new_from_slice(&K::key().bytes().collect::<Vec<_>>())
|
let len = seed.len();
|
||||||
.expect("HmacSha512 InvalidLength should be infallible")
|
if ![16, 32, 64].contains(&len) {
|
||||||
|
return Err(Error::BadSeedLength(len));
|
||||||
|
}
|
||||||
|
|
||||||
|
let hash = HmacSha512::new_from_slice(&K::key().bytes().collect::<Vec<_>>())?
|
||||||
.chain_update(seed)
|
.chain_update(seed)
|
||||||
.finalize()
|
.finalize()
|
||||||
.into_bytes();
|
.into_bytes();
|
||||||
let (private_key, chain_code) = hash.split_at(KEY_SIZE / 8);
|
let (private_key, chain_code) = hash.split_at(KEY_SIZE / 8);
|
||||||
|
|
||||||
Self::new_from_parts(
|
Self::new_from_parts(
|
||||||
private_key
|
private_key,
|
||||||
.try_into()
|
|
||||||
.expect("KEY_SIZE / 8 did not give a 32 byte slice"),
|
|
||||||
0,
|
0,
|
||||||
// Checked: chain_code is always the same length, hash is static size
|
// Checked: chain_code is always the same length, hash is static size
|
||||||
chain_code.try_into().expect("Invalid chain code length"),
|
chain_code.try_into().expect("Invalid chain code length"),
|
||||||
|
@ -199,18 +125,21 @@ where
|
||||||
/// # public_key::TestPublicKey as PublicKey,
|
/// # public_key::TestPublicKey as PublicKey,
|
||||||
/// # private_key::TestPrivateKey as PrivateKey,
|
/// # private_key::TestPrivateKey as PrivateKey,
|
||||||
/// # };
|
/// # };
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let key: &[u8; 32] = //
|
/// let key: &[u8; 32] = //
|
||||||
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||||
/// let chain_code: &[u8; 32] = //
|
/// let chain_code: &[u8; 32] = //
|
||||||
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
|
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
|
||||||
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code);
|
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code)?;
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new_from_parts(key: &[u8; 32], depth: u8, chain_code: [u8; 32]) -> Self {
|
pub fn new_from_parts(seed: &[u8], depth: u8, chain_code: [u8; 32]) -> Result<Self> {
|
||||||
Self {
|
Ok(Self {
|
||||||
private_key: K::from_bytes(key),
|
private_key: K::from_bytes(seed.try_into()?),
|
||||||
depth,
|
depth,
|
||||||
chain_code,
|
chain_code,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the [`PrivateKey`].
|
/// Returns a reference to the [`PrivateKey`].
|
||||||
|
@ -223,12 +152,15 @@ where
|
||||||
/// # public_key::TestPublicKey as PublicKey,
|
/// # public_key::TestPublicKey as PublicKey,
|
||||||
/// # private_key::TestPrivateKey as PrivateKey,
|
/// # private_key::TestPrivateKey as PrivateKey,
|
||||||
/// # };
|
/// # };
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let key: &[u8; 32] = //
|
/// let key: &[u8; 32] = //
|
||||||
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||||
/// let chain_code: &[u8; 32] = //
|
/// let chain_code: &[u8; 32] = //
|
||||||
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
|
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
|
||||||
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code);
|
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code)?;
|
||||||
/// assert_eq!(xprv.private_key(), &PrivateKey::from_bytes(key));
|
/// assert_eq!(xprv.private_key(), &PrivateKey::from_bytes(key));
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn private_key(&self) -> &K {
|
pub fn private_key(&self) -> &K {
|
||||||
&self.private_key
|
&self.private_key
|
||||||
|
@ -253,7 +185,7 @@ where
|
||||||
/// # 102, 201, 210, 159, 219, 222, 42, 201, 44, 196, 27,
|
/// # 102, 201, 210, 159, 219, 222, 42, 201, 44, 196, 27,
|
||||||
/// # 90, 221, 80, 85, 135, 79, 39, 253, 223, 35, 251
|
/// # 90, 221, 80, 85, 135, 79, 39, 253, 223, 35, 251
|
||||||
/// # ];
|
/// # ];
|
||||||
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new(*seed);
|
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?;
|
||||||
/// let xpub = xprv.extended_public_key();
|
/// let xpub = xprv.extended_public_key();
|
||||||
/// assert_eq!(known_key, xpub.public_key().to_bytes());
|
/// assert_eq!(known_key, xpub.public_key().to_bytes());
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
|
@ -277,7 +209,7 @@ where
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let seed: &[u8; 64] = //
|
/// let seed: &[u8; 64] = //
|
||||||
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||||
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new(*seed);
|
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?;
|
||||||
/// let pubkey = xprv.public_key();
|
/// let pubkey = xprv.public_key();
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
|
@ -295,12 +227,15 @@ where
|
||||||
/// # public_key::TestPublicKey as PublicKey,
|
/// # public_key::TestPublicKey as PublicKey,
|
||||||
/// # private_key::TestPrivateKey as PrivateKey,
|
/// # private_key::TestPrivateKey as PrivateKey,
|
||||||
/// # };
|
/// # };
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let key: &[u8; 32] = //
|
/// let key: &[u8; 32] = //
|
||||||
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||||
/// let chain_code: &[u8; 32] = //
|
/// let chain_code: &[u8; 32] = //
|
||||||
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
|
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
|
||||||
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code);
|
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code)?;
|
||||||
/// assert_eq!(xprv.depth(), 4);
|
/// assert_eq!(xprv.depth(), 4);
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn depth(&self) -> u8 {
|
pub fn depth(&self) -> u8 {
|
||||||
self.depth
|
self.depth
|
||||||
|
@ -315,12 +250,15 @@ where
|
||||||
/// # public_key::TestPublicKey as PublicKey,
|
/// # public_key::TestPublicKey as PublicKey,
|
||||||
/// # private_key::TestPrivateKey as PrivateKey,
|
/// # private_key::TestPrivateKey as PrivateKey,
|
||||||
/// # };
|
/// # };
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let key: &[u8; 32] = //
|
/// let key: &[u8; 32] = //
|
||||||
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||||
/// let chain_code: &[u8; 32] = //
|
/// let chain_code: &[u8; 32] = //
|
||||||
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
|
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
|
||||||
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code);
|
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code)?;
|
||||||
/// assert_eq!(chain_code, &xprv.chain_code());
|
/// assert_eq!(chain_code, &xprv.chain_code());
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn chain_code(&self) -> [u8; 32] {
|
pub fn chain_code(&self) -> [u8; 32] {
|
||||||
self.chain_code
|
self.chain_code
|
||||||
|
@ -342,7 +280,7 @@ where
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let seed: &[u8; 64] = //
|
/// let seed: &[u8; 64] = //
|
||||||
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||||
/// let root_xprv = ExtendedPrivateKey::<PrivateKey>::new(*seed);
|
/// let root_xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?;
|
||||||
/// let path = DerivationPath::default()
|
/// let path = DerivationPath::default()
|
||||||
/// .chain_push(DerivationIndex::new(44, true)?)
|
/// .chain_push(DerivationIndex::new(44, true)?)
|
||||||
/// .chain_push(DerivationIndex::new(0, true)?)
|
/// .chain_push(DerivationIndex::new(0, true)?)
|
||||||
|
@ -388,7 +326,7 @@ where
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let seed: &[u8; 64] = //
|
/// let seed: &[u8; 64] = //
|
||||||
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||||
/// let root_xprv = ExtendedPrivateKey::<PrivateKey>::new(*seed);
|
/// let root_xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?;
|
||||||
/// let bip44_wallet = DerivationPath::default()
|
/// let bip44_wallet = DerivationPath::default()
|
||||||
/// .chain_push(DerivationIndex::new(44, true)?)
|
/// .chain_push(DerivationIndex::new(44, true)?)
|
||||||
/// .chain_push(DerivationIndex::new(0, true)?)
|
/// .chain_push(DerivationIndex::new(0, true)?)
|
||||||
|
@ -404,8 +342,8 @@ where
|
||||||
pub fn derive_child(&self, index: &DerivationIndex) -> Result<Self> {
|
pub fn derive_child(&self, index: &DerivationIndex) -> Result<Self> {
|
||||||
let depth = self.depth.checked_add(1).ok_or(Error::Depth)?;
|
let depth = self.depth.checked_add(1).ok_or(Error::Depth)?;
|
||||||
|
|
||||||
let mut hmac = HmacSha512::new_from_slice(&self.chain_code)
|
let mut hmac =
|
||||||
.expect("HmacSha512 InvalidLength should be infallible");
|
HmacSha512::new_from_slice(&self.chain_code).map_err(Error::HmacInvalidLength)?;
|
||||||
if index.is_hardened() {
|
if index.is_hardened() {
|
||||||
hmac.update(&[0]);
|
hmac.update(&[0]);
|
||||||
hmac.update(&self.private_key.to_bytes());
|
hmac.update(&self.private_key.to_bytes());
|
||||||
|
|
|
@ -17,7 +17,7 @@ pub mod public_key;
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use crate::extended_key::{private_key::{ExtendedPrivateKey, Error as XPrvError, VariableLengthSeed}, public_key::{ExtendedPublicKey, Error as XPubError}};
|
pub use crate::extended_key::{private_key::ExtendedPrivateKey, public_key::ExtendedPublicKey};
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
index::{DerivationIndex, Error as IndexError},
|
index::{DerivationIndex, Error as IndexError},
|
||||||
|
|
|
@ -19,11 +19,10 @@
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
extended_key::private_key::{Error as XPrvError, VariableLengthSeed},
|
extended_key::private_key::Error as XPrvError,
|
||||||
private_key::{PrivateKey, TestPrivateKey},
|
private_key::{PrivateKey, TestPrivateKey},
|
||||||
DerivationPath, ExtendedPrivateKey,
|
DerivationPath, ExtendedPrivateKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
use keyfork_mnemonic_util::{Mnemonic, MnemonicGenerationError};
|
use keyfork_mnemonic_util::{Mnemonic, MnemonicGenerationError};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -65,12 +64,11 @@ impl DerivationAlgorithm {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// The method may error if the derivation fails or if the algorithm is not supported.
|
/// The method may error if the derivation fails or if the algorithm is not supported.
|
||||||
fn derive(&self, seed: &[u8], path: &DerivationPath) -> Result<DerivationResponse> {
|
fn derive(&self, seed: Vec<u8>, path: &DerivationPath) -> Result<DerivationResponse> {
|
||||||
let seed = VariableLengthSeed::new(seed);
|
|
||||||
match self {
|
match self {
|
||||||
#[cfg(feature = "ed25519")]
|
#[cfg(feature = "ed25519")]
|
||||||
Self::Ed25519 => {
|
Self::Ed25519 => {
|
||||||
let key = ExtendedPrivateKey::<ed25519_dalek::SigningKey>::new(seed);
|
let key = ExtendedPrivateKey::<ed25519_dalek::SigningKey>::new(seed)?;
|
||||||
let derived_key = key.derive_path(path)?;
|
let derived_key = key.derive_path(path)?;
|
||||||
Ok(DerivationResponse::with_algo_and_xprv(
|
Ok(DerivationResponse::with_algo_and_xprv(
|
||||||
self.clone(),
|
self.clone(),
|
||||||
|
@ -79,7 +77,7 @@ impl DerivationAlgorithm {
|
||||||
}
|
}
|
||||||
#[cfg(feature = "secp256k1")]
|
#[cfg(feature = "secp256k1")]
|
||||||
Self::Secp256k1 => {
|
Self::Secp256k1 => {
|
||||||
let key = ExtendedPrivateKey::<k256::SecretKey>::new(seed);
|
let key = ExtendedPrivateKey::<k256::SecretKey>::new(seed)?;
|
||||||
let derived_key = key.derive_path(path)?;
|
let derived_key = key.derive_path(path)?;
|
||||||
Ok(DerivationResponse::with_algo_and_xprv(
|
Ok(DerivationResponse::with_algo_and_xprv(
|
||||||
self.clone(),
|
self.clone(),
|
||||||
|
@ -87,7 +85,7 @@ impl DerivationAlgorithm {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Self::Internal => {
|
Self::Internal => {
|
||||||
let key = ExtendedPrivateKey::<TestPrivateKey>::new(seed);
|
let key = ExtendedPrivateKey::<TestPrivateKey>::new(seed)?;
|
||||||
let derived_key = key.derive_path(path)?;
|
let derived_key = key.derive_path(path)?;
|
||||||
Ok(DerivationResponse::with_algo_and_xprv(
|
Ok(DerivationResponse::with_algo_and_xprv(
|
||||||
self.clone(),
|
self.clone(),
|
||||||
|
@ -112,18 +110,6 @@ impl std::str::FromStr for DerivationAlgorithm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Acquire the associated [`DerivationAlgorithm`] for a [`PrivateKey`].
|
|
||||||
pub trait AsAlgorithm: PrivateKey {
|
|
||||||
/// Return the appropriate [`DerivationAlgorithm`].
|
|
||||||
fn as_algorithm() -> DerivationAlgorithm;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsAlgorithm for TestPrivateKey {
|
|
||||||
fn as_algorithm() -> DerivationAlgorithm {
|
|
||||||
DerivationAlgorithm::Internal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A derivation request.
|
/// A derivation request.
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct DerivationRequest {
|
pub struct DerivationRequest {
|
||||||
|
@ -209,7 +195,7 @@ impl DerivationRequest {
|
||||||
/// # }
|
/// # }
|
||||||
pub fn derive_with_mnemonic(&self, mnemonic: &Mnemonic) -> Result<DerivationResponse> {
|
pub fn derive_with_mnemonic(&self, mnemonic: &Mnemonic) -> Result<DerivationResponse> {
|
||||||
// TODO: passphrase support and/or store passphrase within mnemonic
|
// TODO: passphrase support and/or store passphrase within mnemonic
|
||||||
self.derive_with_master_seed(&mnemonic.seed(None)?)
|
self.derive_with_master_seed(mnemonic.seed(None)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Derive an [`ExtendedPrivateKey`] using the given seed.
|
/// Derive an [`ExtendedPrivateKey`] using the given seed.
|
||||||
|
@ -233,10 +219,10 @@ impl DerivationRequest {
|
||||||
/// let path: DerivationPath = //
|
/// let path: DerivationPath = //
|
||||||
/// # DerivationPath::default();
|
/// # DerivationPath::default();
|
||||||
/// let request = DerivationRequest::new(algo, &path);
|
/// let request = DerivationRequest::new(algo, &path);
|
||||||
/// let response = request.derive_with_master_seed(seed)?;
|
/// let response = request.derive_with_master_seed(seed.to_vec())?;
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
pub fn derive_with_master_seed(&self, seed: &[u8]) -> Result<DerivationResponse> {
|
pub fn derive_with_master_seed(&self, seed: Vec<u8>) -> Result<DerivationResponse> {
|
||||||
self.algorithm.derive(seed, &self.path)
|
self.algorithm.derive(seed, &self.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,7 +234,7 @@ pub struct DerivationResponse {
|
||||||
pub algorithm: DerivationAlgorithm,
|
pub algorithm: DerivationAlgorithm,
|
||||||
|
|
||||||
/// The derived private key.
|
/// The derived private key.
|
||||||
pub data: [u8; 32],
|
pub data: Vec<u8>,
|
||||||
|
|
||||||
/// The chain code, used for further derivation.
|
/// The chain code, used for further derivation.
|
||||||
pub chain_code: [u8; 32],
|
pub chain_code: [u8; 32],
|
||||||
|
@ -265,7 +251,7 @@ impl DerivationResponse {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
algorithm,
|
algorithm,
|
||||||
data: PrivateKey::to_bytes(xprv.private_key()),
|
data: PrivateKey::to_bytes(xprv.private_key()).to_vec(),
|
||||||
chain_code: xprv.chain_code(),
|
chain_code: xprv.chain_code(),
|
||||||
depth: xprv.depth(),
|
depth: xprv.depth(),
|
||||||
}
|
}
|
||||||
|
@ -286,71 +272,47 @@ pub enum TryFromDerivationResponseError {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "secp256k1")]
|
#[cfg(feature = "secp256k1")]
|
||||||
mod secp256k1 {
|
impl TryFrom<&DerivationResponse> for ExtendedPrivateKey<k256::SecretKey> {
|
||||||
use super::*;
|
|
||||||
use k256::SecretKey;
|
|
||||||
|
|
||||||
impl AsAlgorithm for SecretKey {
|
|
||||||
fn as_algorithm() -> DerivationAlgorithm {
|
|
||||||
DerivationAlgorithm::Secp256k1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&DerivationResponse> for ExtendedPrivateKey<SecretKey> {
|
|
||||||
type Error = TryFromDerivationResponseError;
|
type Error = TryFromDerivationResponseError;
|
||||||
|
|
||||||
fn try_from(value: &DerivationResponse) -> Result<Self, Self::Error> {
|
fn try_from(value: &DerivationResponse) -> std::result::Result<Self, Self::Error> {
|
||||||
match value.algorithm {
|
match value.algorithm {
|
||||||
DerivationAlgorithm::Secp256k1 => Ok(Self::new_from_parts(
|
DerivationAlgorithm::Secp256k1 => {
|
||||||
&value.data,
|
Self::new_from_parts(&value.data, value.depth, value.chain_code).map_err(From::from)
|
||||||
value.depth,
|
}
|
||||||
value.chain_code,
|
|
||||||
)),
|
|
||||||
_ => Err(Self::Error::Algorithm),
|
_ => Err(Self::Error::Algorithm),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<DerivationResponse> for ExtendedPrivateKey<SecretKey> {
|
#[cfg(feature = "secp256k1")]
|
||||||
|
impl TryFrom<DerivationResponse> for ExtendedPrivateKey<k256::SecretKey> {
|
||||||
type Error = TryFromDerivationResponseError;
|
type Error = TryFromDerivationResponseError;
|
||||||
|
|
||||||
fn try_from(value: DerivationResponse) -> Result<Self, Self::Error> {
|
fn try_from(value: DerivationResponse) -> std::result::Result<Self, Self::Error> {
|
||||||
ExtendedPrivateKey::<SecretKey>::try_from(&value)
|
ExtendedPrivateKey::<k256::SecretKey>::try_from(&value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
impl TryFrom<&DerivationResponse> for ExtendedPrivateKey<ed25519_dalek::SigningKey> {
|
||||||
|
type Error = TryFromDerivationResponseError;
|
||||||
|
|
||||||
|
fn try_from(value: &DerivationResponse) -> std::result::Result<Self, Self::Error> {
|
||||||
|
match value.algorithm {
|
||||||
|
DerivationAlgorithm::Ed25519 => {
|
||||||
|
Self::new_from_parts(&value.data, value.depth, value.chain_code).map_err(From::from)
|
||||||
|
}
|
||||||
|
_ => Err(Self::Error::Algorithm),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ed25519")]
|
#[cfg(feature = "ed25519")]
|
||||||
mod ed25519 {
|
impl TryFrom<DerivationResponse> for ExtendedPrivateKey<ed25519_dalek::SigningKey> {
|
||||||
use super::*;
|
|
||||||
use ed25519_dalek::SigningKey;
|
|
||||||
|
|
||||||
impl AsAlgorithm for SigningKey {
|
|
||||||
fn as_algorithm() -> DerivationAlgorithm {
|
|
||||||
DerivationAlgorithm::Ed25519
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&DerivationResponse> for ExtendedPrivateKey<SigningKey> {
|
|
||||||
type Error = TryFromDerivationResponseError;
|
type Error = TryFromDerivationResponseError;
|
||||||
|
|
||||||
fn try_from(value: &DerivationResponse) -> Result<Self, Self::Error> {
|
fn try_from(value: DerivationResponse) -> std::result::Result<Self, Self::Error> {
|
||||||
match value.algorithm {
|
ExtendedPrivateKey::<ed25519_dalek::SigningKey>::try_from(&value)
|
||||||
DerivationAlgorithm::Ed25519 => Ok(Self::new_from_parts(
|
|
||||||
&value.data,
|
|
||||||
value.depth,
|
|
||||||
value.chain_code,
|
|
||||||
)),
|
|
||||||
_ => Err(Self::Error::Algorithm),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<DerivationResponse> for ExtendedPrivateKey<SigningKey> {
|
|
||||||
type Error = TryFromDerivationResponseError;
|
|
||||||
|
|
||||||
fn try_from(value: DerivationResponse) -> Result<Self, Self::Error> {
|
|
||||||
ExtendedPrivateKey::<SigningKey>::try_from(&value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,7 @@ fn secp256k1() {
|
||||||
} = test;
|
} = test;
|
||||||
|
|
||||||
// Tests for ExtendedPrivateKey
|
// Tests for ExtendedPrivateKey
|
||||||
let varlen_seed = VariableLengthSeed::new(&seed);
|
let xkey = ExtendedPrivateKey::<SecretKey>::new(seed).unwrap();
|
||||||
let xkey = ExtendedPrivateKey::<SecretKey>::new(varlen_seed);
|
|
||||||
let derived_key = xkey.derive_path(&chain).unwrap();
|
let derived_key = xkey.derive_path(&chain).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
derived_key.chain_code().as_slice(),
|
derived_key.chain_code().as_slice(),
|
||||||
|
@ -51,8 +50,8 @@ fn secp256k1() {
|
||||||
|
|
||||||
// Tests for DerivationRequest
|
// Tests for DerivationRequest
|
||||||
let request = DerivationRequest::new(DerivationAlgorithm::Secp256k1, &chain);
|
let request = DerivationRequest::new(DerivationAlgorithm::Secp256k1, &chain);
|
||||||
let response = request.derive_with_master_seed(&seed).unwrap();
|
let response = request.derive_with_master_seed(seed.clone()).unwrap();
|
||||||
assert_eq!(&response.data, private_key.as_slice(), "test: {chain}");
|
assert_eq!(&response.data, private_key, "test: {chain}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,8 +75,7 @@ fn ed25519() {
|
||||||
} = test;
|
} = test;
|
||||||
|
|
||||||
// Tests for ExtendedPrivateKey
|
// Tests for ExtendedPrivateKey
|
||||||
let varlen_seed = VariableLengthSeed::new(&seed);
|
let xkey = ExtendedPrivateKey::<SigningKey>::new(seed).unwrap();
|
||||||
let xkey = ExtendedPrivateKey::<SigningKey>::new(varlen_seed);
|
|
||||||
let derived_key = xkey.derive_path(&chain).unwrap();
|
let derived_key = xkey.derive_path(&chain).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
derived_key.chain_code().as_slice(),
|
derived_key.chain_code().as_slice(),
|
||||||
|
@ -97,8 +95,8 @@ fn ed25519() {
|
||||||
|
|
||||||
// Tests for DerivationRequest
|
// Tests for DerivationRequest
|
||||||
let request = DerivationRequest::new(DerivationAlgorithm::Ed25519, &chain);
|
let request = DerivationRequest::new(DerivationAlgorithm::Ed25519, &chain);
|
||||||
let response = request.derive_with_master_seed(&seed).unwrap();
|
let response = request.derive_with_master_seed(seed.to_vec()).unwrap();
|
||||||
assert_eq!(&response.data, private_key.as_slice(), "test: {chain}");
|
assert_eq!(&response.data, private_key, "test: {chain}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +108,7 @@ fn panics_with_unhardened_derivation() {
|
||||||
use ed25519_dalek::SigningKey;
|
use ed25519_dalek::SigningKey;
|
||||||
|
|
||||||
let seed = hex!("000102030405060708090a0b0c0d0e0f");
|
let seed = hex!("000102030405060708090a0b0c0d0e0f");
|
||||||
let xkey = ExtendedPrivateKey::<SigningKey>::new(seed);
|
let xkey = ExtendedPrivateKey::<SigningKey>::new(seed).unwrap();
|
||||||
xkey.derive_path(&DerivationPath::from_str("m/0").unwrap())
|
xkey.derive_path(&DerivationPath::from_str("m/0").unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
@ -122,7 +120,7 @@ fn panics_at_depth() {
|
||||||
use ed25519_dalek::SigningKey;
|
use ed25519_dalek::SigningKey;
|
||||||
|
|
||||||
let seed = hex!("000102030405060708090a0b0c0d0e0f");
|
let seed = hex!("000102030405060708090a0b0c0d0e0f");
|
||||||
let mut xkey = ExtendedPrivateKey::<SigningKey>::new(seed);
|
let mut xkey = ExtendedPrivateKey::<SigningKey>::new(seed).unwrap();
|
||||||
for i in 0..=u32::from(u8::MAX) {
|
for i in 0..=u32::from(u8::MAX) {
|
||||||
xkey = xkey
|
xkey = xkey
|
||||||
.derive_child(&DerivationIndex::new(i, true).unwrap())
|
.derive_child(&DerivationIndex::new(i, true).unwrap())
|
||||||
|
|
|
@ -7,10 +7,11 @@ license = "AGPL-3.0-only"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["openpgp", "openpgp-card", "qrcode", "sequoia-openpgp/crypto-nettle", "keyfork-qrcode/decode-backend-rqrr"]
|
default = ["openpgp", "openpgp-card", "qrcode"]
|
||||||
openpgp = ["sequoia-openpgp", "anyhow"]
|
openpgp = ["sequoia-openpgp", "anyhow"]
|
||||||
openpgp-card = ["openpgp-card-sequoia", "card-backend-pcsc", "card-backend", "dep:openpgp-card"]
|
openpgp-card = ["openpgp-card-sequoia", "card-backend-pcsc", "card-backend", "dep:openpgp-card"]
|
||||||
qrcode = ["keyfork-qrcode"]
|
qrcode = ["keyfork-qrcode"]
|
||||||
|
bin = ["sequoia-openpgp/crypto-nettle", "keyfork-qrcode/decode-backend-rqrr"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
keyfork-prompt = { version = "0.1.0", path = "../util/keyfork-prompt", default-features = false, features = ["mnemonic"] }
|
keyfork-prompt = { version = "0.1.0", path = "../util/keyfork-prompt", default-features = false, features = ["mnemonic"] }
|
||||||
|
|
|
@ -13,9 +13,9 @@ use aes_gcm::{
|
||||||
Aes256Gcm, Error as AesError, KeyInit, Nonce,
|
Aes256Gcm, Error as AesError, KeyInit, Nonce,
|
||||||
};
|
};
|
||||||
use hkdf::{Hkdf, InvalidLength as HkdfInvalidLength};
|
use hkdf::{Hkdf, InvalidLength as HkdfInvalidLength};
|
||||||
use keyfork_derive_openpgp::{
|
use keyfork_derive_openpgp::derive_util::{
|
||||||
derive_util::{DerivationPath, PathError, VariableLengthSeed},
|
request::{DerivationAlgorithm, DerivationRequest},
|
||||||
XPrv,
|
DerivationPath, PathError,
|
||||||
};
|
};
|
||||||
use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError, MnemonicGenerationError, Wordlist};
|
use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError, MnemonicGenerationError, Wordlist};
|
||||||
use keyfork_prompt::{
|
use keyfork_prompt::{
|
||||||
|
@ -123,10 +123,6 @@ pub enum Error {
|
||||||
#[error("IO error: {0}")]
|
#[error("IO error: {0}")]
|
||||||
Io(#[source] std::io::Error),
|
Io(#[source] std::io::Error),
|
||||||
|
|
||||||
/// An error occurred while deriving data.
|
|
||||||
#[error("Derivation: {0}")]
|
|
||||||
Derivation(#[from] keyfork_derive_openpgp::derive_util::extended_key::private_key::Error),
|
|
||||||
|
|
||||||
/// An error occurred while parsing a derivation path.
|
/// An error occurred while parsing a derivation path.
|
||||||
#[error("Derivation path: {0}")]
|
#[error("Derivation path: {0}")]
|
||||||
DerivationPath(#[from] PathError),
|
DerivationPath(#[from] PathError),
|
||||||
|
@ -647,11 +643,13 @@ pub fn combine(
|
||||||
|
|
||||||
// TODO: extract as function
|
// TODO: extract as function
|
||||||
let userid = UserID::from("keyfork-sss");
|
let userid = UserID::from("keyfork-sss");
|
||||||
let path = DerivationPath::from_str("m/7366512'/0'")?;
|
let kdr = DerivationRequest::new(
|
||||||
let seed = VariableLengthSeed::new(&secret);
|
DerivationAlgorithm::Ed25519,
|
||||||
let xprv = XPrv::new(seed).derive_path(&path)?;
|
&DerivationPath::from_str("m/7366512'/0'")?,
|
||||||
|
)
|
||||||
|
.derive_with_master_seed(secret.clone())?;
|
||||||
let derived_cert = keyfork_derive_openpgp::derive(
|
let derived_cert = keyfork_derive_openpgp::derive(
|
||||||
xprv,
|
kdr,
|
||||||
&[KeyFlags::empty().set_certification().set_signing()],
|
&[KeyFlags::empty().set_certification().set_signing()],
|
||||||
&userid,
|
&userid,
|
||||||
)?;
|
)?;
|
||||||
|
@ -680,13 +678,15 @@ pub fn combine(
|
||||||
/// The function may panic if the metadata can't properly store the certificates used to generate
|
/// The function may panic if the metadata can't properly store the certificates used to generate
|
||||||
/// the encrypted shares.
|
/// the encrypted shares.
|
||||||
pub fn split(threshold: u8, certs: Vec<Cert>, secret: &[u8], output: impl Write) -> Result<()> {
|
pub fn split(threshold: u8, certs: Vec<Cert>, secret: &[u8], output: impl Write) -> Result<()> {
|
||||||
let seed = VariableLengthSeed::new(secret);
|
|
||||||
// build cert to sign encrypted shares
|
// build cert to sign encrypted shares
|
||||||
let userid = UserID::from("keyfork-sss");
|
let userid = UserID::from("keyfork-sss");
|
||||||
let path = DerivationPath::from_str("m/7366512'/0'")?;
|
let kdr = DerivationRequest::new(
|
||||||
let xprv = XPrv::new(seed).derive_path(&path)?;
|
DerivationAlgorithm::Ed25519,
|
||||||
|
&DerivationPath::from_str("m/7366512'/0'")?,
|
||||||
|
)
|
||||||
|
.derive_with_master_seed(secret.to_vec())?;
|
||||||
let derived_cert = keyfork_derive_openpgp::derive(
|
let derived_cert = keyfork_derive_openpgp::derive(
|
||||||
xprv,
|
kdr,
|
||||||
&[KeyFlags::empty().set_certification().set_signing()],
|
&[KeyFlags::empty().set_certification().set_signing()],
|
||||||
&userid,
|
&userid,
|
||||||
)?;
|
)?;
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
use super::Keyfork;
|
use super::Keyfork;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
use keyfork_derive_openpgp::{
|
use keyfork_derive_openpgp::openpgp::{
|
||||||
openpgp::{
|
|
||||||
armor::{Kind, Writer},
|
armor::{Kind, Writer},
|
||||||
packet::UserID,
|
packet::UserID,
|
||||||
serialize::Marshal,
|
serialize::Marshal,
|
||||||
types::KeyFlags,
|
types::KeyFlags,
|
||||||
},
|
|
||||||
XPrvKey,
|
|
||||||
};
|
};
|
||||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
use keyfork_derive_util::{
|
||||||
|
request::{DerivationAlgorithm, DerivationRequest, DerivationResponse},
|
||||||
|
DerivationIndex, DerivationPath,
|
||||||
|
};
|
||||||
use keyforkd_client::Client;
|
use keyforkd_client::Client;
|
||||||
|
|
||||||
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||||
|
@ -48,9 +48,12 @@ impl DeriveSubcommands {
|
||||||
.set_storage_encryption(),
|
.set_storage_encryption(),
|
||||||
KeyFlags::empty().set_authentication(),
|
KeyFlags::empty().set_authentication(),
|
||||||
];
|
];
|
||||||
let xprv = Client::discover_socket()?.request_xprv::<XPrvKey>(&path)?;
|
let request = DerivationRequest::new(DerivationAlgorithm::Ed25519, &path);
|
||||||
|
let derived_data: DerivationResponse = Client::discover_socket()?
|
||||||
|
.request(&request.into())?
|
||||||
|
.try_into()?;
|
||||||
let default_userid = UserID::from(user_id.as_str());
|
let default_userid = UserID::from(user_id.as_str());
|
||||||
let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &default_userid)?;
|
let cert = keyfork_derive_openpgp::derive(derived_data, &subkeys, &default_userid)?;
|
||||||
|
|
||||||
let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?;
|
let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?;
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,11 @@ use std::{collections::HashSet, fs::File, io::IsTerminal, path::PathBuf};
|
||||||
use card_backend_pcsc::PcscBackend;
|
use card_backend_pcsc::PcscBackend;
|
||||||
use openpgp_card_sequoia::{state::Open, types::KeyType, Card};
|
use openpgp_card_sequoia::{state::Open, types::KeyType, Card};
|
||||||
|
|
||||||
use keyfork_derive_openpgp::{
|
use keyfork_derive_openpgp::openpgp::{self, packet::UserID, types::KeyFlags, Cert};
|
||||||
openpgp::{self, packet::UserID, types::KeyFlags, Cert},
|
use keyfork_derive_util::{
|
||||||
XPrv,
|
request::{DerivationAlgorithm, DerivationRequest},
|
||||||
|
DerivationIndex, DerivationPath,
|
||||||
};
|
};
|
||||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
|
||||||
use keyfork_prompt::{
|
use keyfork_prompt::{
|
||||||
validators::{PinValidator, Validator},
|
validators::{PinValidator, Validator},
|
||||||
Message, PromptHandler, Terminal,
|
Message, PromptHandler, Terminal,
|
||||||
|
@ -21,7 +21,7 @@ pub struct PinLength(usize);
|
||||||
|
|
||||||
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||||
|
|
||||||
fn derive_key(seed: [u8; 32], index: u8) -> Result<Cert> {
|
fn derive_key(seed: &[u8], index: u8) -> Result<Cert> {
|
||||||
let subkeys = vec![
|
let subkeys = vec![
|
||||||
KeyFlags::empty().set_certification(),
|
KeyFlags::empty().set_certification(),
|
||||||
KeyFlags::empty().set_signing(),
|
KeyFlags::empty().set_signing(),
|
||||||
|
@ -42,9 +42,10 @@ fn derive_key(seed: [u8; 32], index: u8) -> Result<Cert> {
|
||||||
.chain_push(chain)
|
.chain_push(chain)
|
||||||
.chain_push(account)
|
.chain_push(account)
|
||||||
.chain_push(subkey);
|
.chain_push(subkey);
|
||||||
let xprv = XPrv::new(seed).derive_path(&path)?;
|
let request = DerivationRequest::new(DerivationAlgorithm::Ed25519, &path);
|
||||||
|
let response = request.derive_with_master_seed(seed.to_vec())?;
|
||||||
let userid = UserID::from(format!("Keyfork Shard {index}"));
|
let userid = UserID::from(format!("Keyfork Shard {index}"));
|
||||||
let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &userid)?;
|
let cert = keyfork_derive_openpgp::derive(response, &subkeys, &userid)?;
|
||||||
Ok(cert)
|
Ok(cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +103,7 @@ fn generate_shard_secret(
|
||||||
keys_per_shard: u8,
|
keys_per_shard: u8,
|
||||||
output_file: &Option<PathBuf>,
|
output_file: &Option<PathBuf>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let seed = keyfork_entropy::generate_entropy_of_const_size::<{256 / 8}>()?;
|
let seed = keyfork_entropy::generate_entropy_of_size(256 / 8)?;
|
||||||
let mut pm = Terminal::new(std::io::stdin(), std::io::stderr())?;
|
let mut pm = Terminal::new(std::io::stdin(), std::io::stderr())?;
|
||||||
let mut certs = vec![];
|
let mut certs = vec![];
|
||||||
let mut seen_cards: HashSet<String> = HashSet::new();
|
let mut seen_cards: HashSet<String> = HashSet::new();
|
||||||
|
@ -126,7 +127,7 @@ fn generate_shard_secret(
|
||||||
.to_fn();
|
.to_fn();
|
||||||
|
|
||||||
for index in 0..max {
|
for index in 0..max {
|
||||||
let cert = derive_key(seed, index)?;
|
let cert = derive_key(&seed, index)?;
|
||||||
for i in 0..keys_per_shard {
|
for i in 0..keys_per_shard {
|
||||||
pm.prompt_message(Message::Text(format!(
|
pm.prompt_message(Message::Text(format!(
|
||||||
"Please remove all keys and insert key #{} for user #{}",
|
"Please remove all keys and insert key #{} for user #{}",
|
||||||
|
|
|
@ -7,7 +7,7 @@ license = "MIT"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["bin"]
|
default = []
|
||||||
bin = ["smex"]
|
bin = ["smex"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
//! Utilities for reading entropy from secure sources.
|
//! Utilities for reading entropy from secure sources.
|
||||||
|
|
||||||
use std::{
|
use std::{fs::{read_dir, read_to_string, File}, io::Read};
|
||||||
fs::{read_dir, read_to_string, File},
|
|
||||||
io::Read,
|
|
||||||
};
|
|
||||||
|
|
||||||
static WARNING_LINKS: [&str; 1] =
|
static WARNING_LINKS: [&str; 1] =
|
||||||
["https://lore.kernel.org/lkml/20211223141113.1240679-2-Jason@zx2c4.com/"];
|
["https://lore.kernel.org/lkml/20211223141113.1240679-2-Jason@zx2c4.com/"];
|
||||||
|
@ -87,24 +84,3 @@ pub fn generate_entropy_of_size(byte_count: usize) -> Result<Vec<u8>, std::io::E
|
||||||
entropy_file.read_exact(&mut vec[..])?;
|
entropy_file.read_exact(&mut vec[..])?;
|
||||||
Ok(vec)
|
Ok(vec)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read system entropy of a constant size.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
/// An error may be returned if an error occurred while reading from the random source.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
/// # std::env::set_var("SHOOT_SELF_IN_FOOT", "1");
|
|
||||||
/// let entropy = keyfork_entropy::generate_entropy_of_const_size::<64>()?;
|
|
||||||
/// assert_eq!(entropy.len(), 64);
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn generate_entropy_of_const_size<const N: usize>() -> Result<[u8; N], std::io::Error> {
|
|
||||||
let mut output = [0u8; N];
|
|
||||||
let mut entropy_file = File::open("/dev/urandom")?;
|
|
||||||
entropy_file.read_exact(&mut output[..])?;
|
|
||||||
Ok(output)
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["bin"]
|
default = []
|
||||||
bin = ["smex"]
|
bin = ["smex"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
@ -276,7 +276,7 @@ impl Mnemonic {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clone the existing entropy.
|
/// Clone the existing entropy.
|
||||||
#[deprecated = "Use as_bytes(), to_bytes(), or into_bytes() instead"]
|
#[deprecated]
|
||||||
pub fn entropy(&self) -> Vec<u8> {
|
pub fn entropy(&self) -> Vec<u8> {
|
||||||
self.entropy.clone()
|
self.entropy.clone()
|
||||||
}
|
}
|
||||||
|
@ -353,8 +353,8 @@ mod tests {
|
||||||
random_handle.read_exact(&mut entropy[..]).unwrap();
|
random_handle.read_exact(&mut entropy[..]).unwrap();
|
||||||
let wordlist = Wordlist::default().arc();
|
let wordlist = Wordlist::default().arc();
|
||||||
let mnemonic = super::Mnemonic::from_entropy(&entropy[..256 / 8], wordlist).unwrap();
|
let mnemonic = super::Mnemonic::from_entropy(&entropy[..256 / 8], wordlist).unwrap();
|
||||||
let new_entropy = mnemonic.as_bytes();
|
let new_entropy = mnemonic.entropy();
|
||||||
assert_eq!(new_entropy, entropy);
|
assert_eq!(&new_entropy, entropy);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -22,8 +22,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
3,
|
3,
|
||||||
transport_validator.to_fn(),
|
transport_validator.to_fn(),
|
||||||
)?;
|
)?;
|
||||||
assert_eq!(mnemonics[0].as_bytes().len(), 12);
|
assert_eq!(mnemonics[0].entropy().len(), 12);
|
||||||
assert_eq!(mnemonics[1].as_bytes().len(), 32);
|
assert_eq!(mnemonics[1].entropy().len(), 32);
|
||||||
|
|
||||||
let mnemonics = mgr.prompt_validated_wordlist(
|
let mnemonics = mgr.prompt_validated_wordlist(
|
||||||
"Enter a 24 and 48-word mnemonic: ",
|
"Enter a 24 and 48-word mnemonic: ",
|
||||||
|
@ -31,8 +31,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
3,
|
3,
|
||||||
combine_validator.to_fn(),
|
combine_validator.to_fn(),
|
||||||
)?;
|
)?;
|
||||||
assert_eq!(mnemonics[0].as_bytes().len(), 32);
|
assert_eq!(mnemonics[0].entropy().len(), 32);
|
||||||
assert_eq!(mnemonics[1].as_bytes().len(), 64);
|
assert_eq!(mnemonics[1].entropy().len(), 64);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue