diff --git a/Cargo.lock b/Cargo.lock index ae7b4e5..547536a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -529,9 +529,9 @@ dependencies = [ "bincode", "clap", "ed25519-dalek", - "hex-literal", "keyfork-derive-util", "keyfork-frame", + "keyfork-slip10-test-data", "keyforkd", "tempdir", "thiserror", @@ -607,6 +607,7 @@ dependencies = [ "keyfork-derive-util", "keyfork-frame", "keyfork-mnemonic-util", + "keyfork-slip10-test-data", "serde", "thiserror", "tokio", diff --git a/keyfork-derive-key/Cargo.toml b/keyfork-derive-key/Cargo.toml index 9f2f329..6ffd8cd 100644 --- a/keyfork-derive-key/Cargo.toml +++ b/keyfork-derive-key/Cargo.toml @@ -14,7 +14,7 @@ thiserror = "1.0.48" [dev-dependencies] ed25519-dalek = "2.0.0" -hex-literal = "0.4.1" +keyfork-slip10-test-data = { path = "../keyfork-slip10-test-data" } keyforkd = { path = "../keyforkd", default-features = false } tempdir = "0.3.7" tokio = { version = "1.32.0", features = ["rt", "rt-multi-thread"] } diff --git a/keyfork-derive-key/src/tests.rs b/keyfork-derive-key/src/tests.rs index 761656b..da9383c 100644 --- a/keyfork-derive-key/src/tests.rs +++ b/keyfork-derive-key/src/tests.rs @@ -1,93 +1,103 @@ use crate::client::Client; -use hex_literal::hex; -use keyfork_derive_util::{request::*, DerivationIndex, DerivationPath, ExtendedPrivateKey}; +use keyfork_derive_util::{request::*, DerivationPath}; +use keyfork_slip10_test_data::test_data; use std::sync::mpsc::channel; use std::{os::unix::net::UnixStream, str::FromStr}; use tempdir::TempDir; use tokio::runtime::Builder; #[test] -fn it_works() { - // Test literals taken from keyfork-derive-util. - // Setup - let entropy = &hex!("000102030405060708090a0b0c0d0e0f")[..]; - let mnemonic = keyforkd::Mnemonic::from_entropy(entropy, Default::default()).unwrap(); +fn secp256k1() { + let tests = test_data() + .unwrap() + .remove(&"secp256k1".to_string()) + .unwrap(); + let rt = Builder::new_multi_thread().enable_io().build().unwrap(); let tempdir = TempDir::new("keyfork-seed").unwrap(); - let socket_path = tempdir.path().join("keyforkd.sock"); - let (tx, rx) = channel(); + for (i, per_seed) in tests.into_iter().enumerate() { - let handle = rt.spawn({ - let socket_path = socket_path.clone(); - async move { - let mut server = keyforkd::UnixServer::bind(&socket_path).unwrap(); - // Connections can be pending for a few seconds, so signal to the main - // test we're ready to start accepting. - tx.send(()).unwrap(); - let service = keyforkd::ServiceBuilder::new() - .layer(keyforkd::middleware::BincodeLayer::new()) - .service(keyforkd::Keyforkd::new(mnemonic)); - server.run(service).await.unwrap(); + 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 mut client = Client::new(socket); + let chain = DerivationPath::from_str(test.chain).unwrap(); + if chain.len() < 2 { + continue; + } + let req = DerivationRequest::new( + DerivationAlgorithm::Secp256k1, + &DerivationPath::from_str(test.chain).unwrap(), + ); + let response = client.request(&req).unwrap(); + assert_eq!(response.data, test.private_key); } - }); - rx.recv().unwrap(); - let socket = UnixStream::connect(&socket_path).unwrap(); - let mut client = Client::new(socket); - let req = DerivationRequest::new( - DerivationAlgorithm::Ed25519, - DerivationPath::from_str("m/0'/1'/2'/2'/1000000000'").unwrap(), - ); - let response = client.request(&req).unwrap(); - assert_eq!( - response.data, - hex!("8f94d394a8e8fd6b1bc2f3f49f5c47e385281d5c17e65324b0f62483e37e8793") - ); - handle.abort(); + handle.abort(); + } } #[test] -fn misc_multi_requests() { - // Setup - let entropy = &hex!("000102030405060708090a0b0c0d0e0f")[..]; - let mnemonic = keyforkd::Mnemonic::from_entropy(entropy, Default::default()).unwrap(); +fn ed25519() { + let tests = test_data() + .unwrap() + .remove(&"ed25519".to_string()) + .unwrap(); + let rt = Builder::new_multi_thread().enable_io().build().unwrap(); let tempdir = TempDir::new("keyfork-seed").unwrap(); - let socket_path = tempdir.path().join("keyforkd.sock"); - let (tx, rx) = channel(); + for (i, per_seed) in tests.into_iter().enumerate() { - let handle = rt.spawn({ - let socket_path = socket_path.clone(); - async move { - let mut server = keyforkd::UnixServer::bind(&socket_path).unwrap(); - // Connections can be pending for a few seconds, so signal to the main - // test we're ready to start accepting. - tx.send(()).unwrap(); - let service = keyforkd::ServiceBuilder::new() - .layer(keyforkd::middleware::BincodeLayer::new()) - .service(keyforkd::Keyforkd::new(mnemonic)); - server.run(service).await.unwrap(); + 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 mut client = Client::new(socket); + let chain = DerivationPath::from_str(test.chain).unwrap(); + if chain.len() < 2 { + continue; + } + let req = DerivationRequest::new( + DerivationAlgorithm::Ed25519, + &DerivationPath::from_str(test.chain).unwrap(), + ); + let response = client.request(&req).unwrap(); + assert_eq!(response.data, test.private_key); } - }); - rx.recv().unwrap(); - let socket = UnixStream::connect(&socket_path).unwrap(); - let mut client = Client::new(socket); - let req = DerivationRequest::new( - DerivationAlgorithm::Ed25519, - DerivationPath::from_str("m/7366512'/0'").unwrap(), - ); - let response = client.request(&req).unwrap(); - let key = ExtendedPrivateKey::::new_from_parts( - &response.data, - response.depth, - response.chain_code, - ) - .unwrap(); - - for i in 0..255 { - key.derive_child(&DerivationIndex::new(i, true).unwrap()) - .unwrap(); + handle.abort(); } - handle.abort(); } diff --git a/keyfork-derive-util/Cargo.toml b/keyfork-derive-util/Cargo.toml index b812713..20e546c 100644 --- a/keyfork-derive-util/Cargo.toml +++ b/keyfork-derive-util/Cargo.toml @@ -29,7 +29,7 @@ ed25519-dalek = { version = "2.0.0", optional = true } # Workspace keyfork-mnemonic-util = { version = "0.1.0", path = "../keyfork-mnemonic-util" } -keyfork-slip10-test-data = { version = "0.1.0", path = "../keyfork-slip10-test-data" } [dev-dependencies] hex-literal = "0.4.1" +keyfork-slip10-test-data = { version = "0.1.0", path = "../keyfork-slip10-test-data" } diff --git a/keyforkd/Cargo.toml b/keyforkd/Cargo.toml index bf2ee86..e1afadc 100644 --- a/keyforkd/Cargo.toml +++ b/keyforkd/Cargo.toml @@ -31,3 +31,4 @@ serde = { version = "1.0.186", features = ["derive"] } [dev-dependencies] hex-literal = "0.4.1" +keyfork-slip10-test-data = { path = "../keyfork-slip10-test-data" } diff --git a/keyforkd/src/lib.rs b/keyforkd/src/lib.rs index 5377759..6287970 100644 --- a/keyforkd/src/lib.rs +++ b/keyforkd/src/lib.rs @@ -23,7 +23,7 @@ pub async fn start_and_run_server_on( ) -> Result<(), Box> { let service = ServiceBuilder::new() .layer(middleware::BincodeLayer::new()) - .service(Keyforkd::new(mnemonic)); + .service(Keyforkd::new(mnemonic.seed())); let mut server = match UnixServer::bind(socket_path) { Ok(s) => s, diff --git a/keyforkd/src/service.rs b/keyforkd/src/service.rs index d5da853..b56d691 100644 --- a/keyforkd/src/service.rs +++ b/keyforkd/src/service.rs @@ -1,7 +1,6 @@ use std::{future::Future, pin::Pin, sync::Arc, task::Poll}; use keyfork_derive_util::request::{DerivationError, DerivationRequest, DerivationResponse}; -use keyfork_mnemonic_util::Mnemonic; use tower::Service; // NOTE: All values implemented in Keyforkd must implement Clone with low overhead, either by @@ -18,13 +17,13 @@ pub enum KeyforkdRequestError { #[derive(Clone, Debug)] pub struct Keyforkd { - mnemonic: Arc, + seed: Arc>, } impl Keyforkd { - pub fn new(mnemonic: Mnemonic) -> Self { + pub fn new(seed: Vec) -> Self { Self { - mnemonic: Arc::new(mnemonic), + seed: Arc::new(seed), } } } @@ -45,13 +44,13 @@ impl Service for Keyforkd { #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))] fn call(&mut self, req: DerivationRequest) -> Self::Future { - let mnemonic = self.mnemonic.clone(); + let seed = self.seed.clone(); Box::pin(async move { let len = req.path().len(); if len < 2 { return Err(KeyforkdRequestError::InvalidDerivationLength(len)); } - req.derive_with_mnemonic(&mnemonic) + req.derive_with_master_seed((*seed).clone()) .map_err(KeyforkdRequestError::from) }) } @@ -62,48 +61,94 @@ mod tests { use super::*; use hex_literal::hex; use keyfork_derive_util::{request::*, DerivationPath}; - use keyfork_mnemonic_util::Wordlist; + use keyfork_mnemonic_util::{Mnemonic, Wordlist}; + use keyfork_slip10_test_data::test_data; use std::str::FromStr; use tower::ServiceExt; #[tokio::test] - async fn properly_derives_data() { - // Pulled from keyfork-derive-util's tests, which is more extensively tested. - let tests = [ - /* - * Note: Tests excluded because the derivation path is not deep enough - * for the API's preferences. - ( - &hex!("000102030405060708090a0b0c0d0e0f")[..], - DerivationPath::from_str("m").unwrap(), - hex!("90046a93de5380a72b5e45010748567d5ea02bbf6522f979e05c0d8d8ca9fffb"), - hex!("2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7"), - hex!("00a4b2856bfec510abab89753fac1ac0e1112364e7d250545963f135f2a33188ed"), - ), - ( - &hex!("000102030405060708090a0b0c0d0e0f")[..], - DerivationPath::from_str("m/0'").unwrap(), - hex!("8b59aa11380b624e81507a27fedda59fea6d0b779a778918a2fd3590e16e9c69"), - hex!("68e0fe46dfb67e368c75379acec591dad19df3cde26e63b93a8e704f1dade7a3"), - hex!("008c8a13df77a28f3445213a0f432fde644acaa215fc72dcdf300d5efaa85d350c"), - ), - */ - ( - &hex!("000102030405060708090a0b0c0d0e0f")[..], - DerivationPath::from_str("m/0'/1'/2'/2'/1000000000'").unwrap(), - hex!("68789923a0cac2cd5a29172a475fe9e0fb14cd6adb5ad98a3fa70333e7afa230"), - hex!("8f94d394a8e8fd6b1bc2f3f49f5c47e385281d5c17e65324b0f62483e37e8793"), - hex!("003c24da049451555d51a7014a37337aa4e12d41e485abccfa46b47dfb2af54b7a"), - ), - ]; + async fn properly_derives_secp256k1() { + let tests = test_data() + .unwrap() + .remove(&"secp256k1".to_string()) + .unwrap(); + let wordlist = Wordlist::default().arc(); - for (seed, path, _, private_key, _) in tests { - let mnemonic = Mnemonic::from_entropy(&seed[..], wordlist.clone()).unwrap(); - assert_eq!(mnemonic.seed(), seed); - let req = DerivationRequest::new(DerivationAlgorithm::Ed25519, path); - let mut keyforkd = Keyforkd::new(mnemonic); - let response = keyforkd.ready().await.unwrap().call(req).await.unwrap(); - assert_eq!(response.data, private_key) + + for per_seed in tests { + let seed = &per_seed.seed; + + // Test mnemonic path + if [128 / 8, 256 / 8].contains(&seed.len()) { + let mnemonic = Mnemonic::from_entropy(seed, wordlist.clone()).unwrap(); + let mut keyforkd = Keyforkd::new(mnemonic.seed()); + for test in &per_seed.tests { + let chain = DerivationPath::from_str(test.chain).unwrap(); + if chain.len() < 2 { + continue; + } + let req = DerivationRequest::new(DerivationAlgorithm::Secp256k1, &chain); + let response = keyforkd.ready().await.unwrap().call(req).await.unwrap(); + assert_eq!(response.data, test.private_key); + assert_eq!(response.chain_code.as_slice(), test.chain_code); + } + } + + // Test seed path + let mut keyforkd = Keyforkd::new(seed.to_vec()); + for test in &per_seed.tests { + let chain = DerivationPath::from_str(test.chain).unwrap(); + if chain.len() < 2 { + continue; + } + let req = DerivationRequest::new(DerivationAlgorithm::Secp256k1, &chain); + let response = keyforkd.ready().await.unwrap().call(req).await.unwrap(); + assert_eq!(response.data, test.private_key); + assert_eq!(response.chain_code.as_slice(), test.chain_code); + } + } + } + + #[tokio::test] + async fn properly_derives_ed25519() { + let tests = test_data() + .unwrap() + .remove(&"ed25519".to_string()) + .unwrap(); + + let wordlist = Wordlist::default().arc(); + + for per_seed in tests { + let seed = &per_seed.seed; + + // Test mnemonic path + if [128 / 8, 256 / 8].contains(&seed.len()) { + let mnemonic = Mnemonic::from_entropy(seed, wordlist.clone()).unwrap(); + let mut keyforkd = Keyforkd::new(mnemonic.seed()); + for test in &per_seed.tests { + let chain = DerivationPath::from_str(test.chain).unwrap(); + if chain.len() < 2 { + continue; + } + let req = DerivationRequest::new(DerivationAlgorithm::Ed25519, &chain); + let response = keyforkd.ready().await.unwrap().call(req).await.unwrap(); + assert_eq!(response.data, test.private_key); + assert_eq!(response.chain_code.as_slice(), test.chain_code); + } + } + + // Test seed path + let mut keyforkd = Keyforkd::new(seed.to_vec()); + for test in &per_seed.tests { + let chain = DerivationPath::from_str(test.chain).unwrap(); + if chain.len() < 2 { + continue; + } + let req = DerivationRequest::new(DerivationAlgorithm::Ed25519, &chain); + let response = keyforkd.ready().await.unwrap().call(req).await.unwrap(); + assert_eq!(response.data, test.private_key); + assert_eq!(response.chain_code.as_slice(), test.chain_code); + } } } @@ -121,8 +166,8 @@ mod tests { for (seed, path, _, private_key, _) in tests { let mnemonic = Mnemonic::from_entropy(&seed[..], wordlist.clone()).unwrap(); assert_eq!(mnemonic.seed(), seed); - let req = DerivationRequest::new(DerivationAlgorithm::Ed25519, path); - let mut keyforkd = Keyforkd::new(mnemonic); + let req = DerivationRequest::new(DerivationAlgorithm::Ed25519, &path); + let mut keyforkd = Keyforkd::new(mnemonic.seed()); let response = keyforkd.ready().await.unwrap().call(req).await.unwrap(); assert_eq!(response.data, private_key) } @@ -142,8 +187,8 @@ mod tests { for (seed, path, _, private_key, _) in tests { let mnemonic = Mnemonic::from_entropy(&seed[..], wordlist.clone()).unwrap(); assert_eq!(mnemonic.seed(), seed); - let req = DerivationRequest::new(DerivationAlgorithm::Ed25519, path); - let mut keyforkd = Keyforkd::new(mnemonic); + let req = DerivationRequest::new(DerivationAlgorithm::Ed25519, &path); + let mut keyforkd = Keyforkd::new(mnemonic.seed()); let response = keyforkd.ready().await.unwrap().call(req).await.unwrap(); assert_eq!(response.data, private_key) }