From 536e6da5ada1a2fbf8a3b8e983be527243f52300 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 1 Aug 2024 08:59:01 -0400 Subject: [PATCH] keyforkd{,-client}: lots of documentationings --- crates/daemon/keyforkd-client/src/lib.rs | 172 +++++++++++++++++---- crates/daemon/keyforkd-client/src/tests.rs | 4 +- crates/daemon/keyforkd/src/test_util.rs | 36 +++-- 3 files changed, 170 insertions(+), 42 deletions(-) diff --git a/crates/daemon/keyforkd-client/src/lib.rs b/crates/daemon/keyforkd-client/src/lib.rs index df6163f..f8fefbe 100644 --- a/crates/daemon/keyforkd-client/src/lib.rs +++ b/crates/daemon/keyforkd-client/src/lib.rs @@ -1,8 +1,10 @@ //! # The Keyforkd Client //! //! Keyfork allows securing the master key and highest-level derivation keys by having derivation -//! requests performed against a server, "Keyforkd" or the "Keyfork Server". The server is operated -//! on a UNIX socket with messages sent using the Keyfork Frame format. +//! requests performed against a server, "Keyforkd" or the "Keyfork Server". This allows +//! enforcement of policies, such as requiring at least two leves of a derivation path (for +//! instance, `m/0'` would not be allowed, but `m/0'/0'` would). The server is operated on a UNIX +//! socket with messages sent using the Keyfork Frame format. //! //! Programs using the Keyfork Client should ensure they are built against a compatible version of //! the Keyfork Server. For versions prior to `1.0.0`, all versions within a "minor" version (i.e., @@ -10,18 +12,23 @@ //! after `1.0.0`, all versions within a "major" version (i.e., `1.0.0`) will be compatible, but //! `1.x.y` will not be compatible with `2.0.0`. //! -//! Presently, the Keyfork server only supports the following requests: +//! The Keyfork Client documentation makes extensive use of the `keyforkd::test_util` module. +//! This provides testing infrastructure to set up a temporary Keyfork Daemon. In +//! your code, you should assume the daemon has already been initialized, whether by another +//! process, on another terminal, or some other instance. At no point should a program deriving an +//! "endpoint" key have control over a mnemonic or a seed. //! -//! * Derive Key +//! ## Server Requests //! -//! ## Extended Private Keys +//! Keyfork is designed as a client-request/server-response model. The client sends a request, such +//! as a derivation request, and the server sends its response. Presently, the Keyfork server +//! supports the following requests: //! -//! Keyfork doesn't need to be continuously called once a key has been derived. Once an Extended -//! Private Key (often shortened to "XPrv") has been created, further derivations can be performed. -//! The tests for this library ensure that all levels of Keyfork derivation beyond the required two -//! will be derived similarly between the server and the client. +//! ### Request: Derive Key +//! +//! The client creates a derivation path of at least two indices and requests a derived XPrv +//! (Extended Private Key) from the server. //! -//! # Examples //! ```rust //! use std::str::FromStr; //! @@ -31,17 +38,121 @@ //! // use k256::SecretKey as PrivateKey; //! // use ed25519_dalek::SigningKey as PrivateKey; //! -//! # let seed = b"funky accordion noises"; -//! # keyforkd::test_util::run_test(seed, |socket_path| { -//! # std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path); -//! let derivation_path = DerivationPath::from_str("m/44'/0'").unwrap(); -//! let mut client = Client::discover_socket().unwrap(); -//! let xprv = client.request_xprv::(&derivation_path).unwrap(); -//! # keyforkd::test_util::Infallible::Ok(()) -//! # }).unwrap(); +//! #[derive(Debug, thiserror::Error)] +//! enum Error { +//! #[error(transparent)] +//! Path(#[from] keyfork_derive_util::PathError), +//! +//! #[error(transparent)] +//! Keyforkd(#[from] keyforkd_client::Error), +//! } +//! +//! fn main() -> Result<(), Error> { +//! # let seed = b"funky accordion noises"; +//! # keyforkd::test_util::run_test(seed, |socket_path| { +//! let derivation_path = DerivationPath::from_str("m/44'/0'")?; +//! let mut client = Client::discover_socket()?; +//! let xprv = client.request_xprv::(&derivation_path)?; +//! # Ok::<_, Error>(()) +//! # })?; +//! Ok(()) +//! } //! ``` //! -//! In tests, the Keyforkd test_util module and TestPrivateKeys can be used. +//! --- +//! +//! Request objects are typically handled by the Keyfork Client library (such as with +//! [`Client::request_xprv`]). While unadvised, clients can also attempt to handle their own +//! requests, using [`Client::request`]. +//! +//! ## Extended Private Keys +//! +//! Keyfork doesn't need to be continuously called once a key has been derived. Once an Extended +//! Private Key (often shortened to "XPrv") has been created, further derivations can be performed. +//! The tests for this library ensure that all levels of Keyfork derivation beyond the required two +//! will be derived similarly between the server and the client. +//! +//! ```rust +//! use std::str::FromStr; +//! +//! use keyforkd_client::Client; +//! use keyfork_derive_util::{DerivationIndex, DerivationPath}; +//! # use keyfork_derive_util::private_key::TestPrivateKey as PrivateKey; +//! // use k256::SecretKey as PrivateKey; +//! // use ed25519_dalek::SigningKey as PrivateKey; +//! # fn check_wallet(_: T) {} +//! +//! #[derive(Debug, thiserror::Error)] +//! enum Error { +//! #[error(transparent)] +//! Index(#[from] keyfork_derive_util::IndexError), +//! +//! #[error(transparent)] +//! Path(#[from] keyfork_derive_util::PathError), +//! +//! #[error(transparent)] +//! PrivateKey(#[from] keyfork_derive_util::PrivateKeyError), +//! +//! #[error(transparent)] +//! Keyforkd(#[from] keyforkd_client::Error), +//! } +//! +//! fn main() -> Result<(), Error> { +//! # let seed = b"funky accordion noises"; +//! # keyforkd::test_util::run_test(seed, |socket_path| { +//! let derivation_path = DerivationPath::from_str("m/44'/0'/0'/0")?; +//! let mut client = Client::discover_socket()?; +//! let xprv = client.request_xprv::(&derivation_path)?; +//! // scan first 20 wallets +//! for index in 0..20 { +//! // use non-hardened derivation +//! let new_xprv = xprv.derive_child(&DerivationIndex::new(index, false)?); +//! check_wallet(new_xprv) +//! } +//! # Ok::<_, Error>(()) +//! # })?; +//! Ok(()) +//! } +//! ``` +//! +//! ## Testing Infrastructure +//! +//! In tests, the `keyforkd::test_util` module and TestPrivateKeys can be used. These provide +//! useful utilities for writing tests that interact with the Keyfork Server without needing to +//! manually create the server for the purpose of the test. The `run_test` method can be used to +//! run a test, which can handle both returning errors and correctly translating panics (though, +//! the panics definitely won't look tidy). +//! +//! ```rust +//! use std::str::FromStr; +//! +//! use keyforkd_client::Client; +//! use keyfork_derive_util::DerivationPath; +//! use keyfork_derive_util::private_key::TestPrivateKey as PrivateKey; +//! +//! #[derive(Debug, thiserror::Error)] +//! enum Error { +//! #[error(transparent)] +//! Path(#[from] keyfork_derive_util::PathError), +//! +//! #[error(transparent)] +//! Keyforkd(#[from] keyforkd_client::Error), +//! } +//! +//! fn main() -> Result<(), Error> { +//! let seed = b"funky accordion noises"; +//! keyforkd::test_util::run_test(seed, |socket_path| { +//! let derivation_path = DerivationPath::from_str("m/44'/0'")?; +//! let mut client = Client::discover_socket()?; +//! let xprv = client.request_xprv::(&derivation_path)?; +//! Ok::<_, Error>(()) +//! })?; +//! Ok(()) +//! } +//! ``` +//! +//! If you would rather write tests to panic rather than error, or would rather not deal with error +//! types, the Panicable type should be used, which will handle the Error type for the closure. //! //! ```rust //! use std::str::FromStr; @@ -52,11 +163,10 @@ //! //! let seed = b"funky accordion noises"; //! keyforkd::test_util::run_test(seed, |socket_path| { -//! std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path); //! let derivation_path = DerivationPath::from_str("m/44'/0'").unwrap(); //! let mut client = Client::discover_socket().unwrap(); //! let xprv = client.request_xprv::(&derivation_path).unwrap(); -//! keyforkd::test_util::Infallible::Ok(()) +//! keyforkd::test_util::Panicable::Ok(()) //! }).unwrap(); //! ``` @@ -165,10 +275,9 @@ impl Client { /// /// # let seed = b"funky accordion noises"; /// # keyforkd::test_util::run_test(seed, |socket_path| { - /// # std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path); - /// let mut socket = get_socket().unwrap(); + /// let mut socket = get_socket()?; /// let mut client = Client::new(socket); - /// # keyforkd::test_util::Infallible::Ok(()) + /// # Ok::<_, keyforkd_client::Error>(()) /// # }).unwrap(); /// ``` pub fn new(socket: UnixStream) -> Self { @@ -187,9 +296,8 @@ impl Client { /// /// # let seed = b"funky accordion noises"; /// # keyforkd::test_util::run_test(seed, |socket_path| { - /// # std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path); - /// let mut client = Client::discover_socket().unwrap(); - /// # keyforkd::test_util::Infallible::Ok(()) + /// let mut client = Client::discover_socket()?; + /// # Ok::<_, keyforkd_client::Error>(()) /// # }).unwrap(); /// ``` pub fn discover_socket() -> Result { @@ -217,11 +325,10 @@ impl Client { /// /// # let seed = b"funky accordion noises"; /// # keyforkd::test_util::run_test(seed, |socket_path| { - /// # std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path); /// let derivation_path = DerivationPath::from_str("m/44'/0'").unwrap(); /// let mut client = Client::discover_socket().unwrap(); /// let xprv = client.request_xprv::(&derivation_path).unwrap(); - /// # keyforkd::test_util::Infallible::Ok(()) + /// # keyforkd::test_util::Panicable::Ok(()) /// # }).unwrap(); /// ``` pub fn request_xprv(&mut self, path: &DerivationPath) -> Result> @@ -244,15 +351,20 @@ impl Client { _ => Err(Error::InvalidResponse), } } +} +impl Client { /// Serialize and send a [`Request`] to the server, awaiting a [`Result`]. /// + /// This function does not properly assert the association between a request type and a + /// response type, and does not perform any serialization of native objects into Request or + /// Response types, and should only be used when absolutely necessary. + /// /// # 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. - #[doc(hidden)] pub fn request(&mut self, req: &Request) -> Result { try_encode_to(&bincode::serialize(&req)?, &mut self.socket)?; let resp = try_decode_from(&mut self.socket)?; diff --git a/crates/daemon/keyforkd-client/src/tests.rs b/crates/daemon/keyforkd-client/src/tests.rs index ab396da..9bcb3e4 100644 --- a/crates/daemon/keyforkd-client/src/tests.rs +++ b/crates/daemon/keyforkd-client/src/tests.rs @@ -1,7 +1,7 @@ use crate::Client; use keyfork_derive_util::{request::*, DerivationPath}; use keyfork_slip10_test_data::test_data; -use keyforkd::test_util::{run_test, Infallible}; +use keyforkd::test_util::{run_test, Panicable}; use std::{os::unix::net::UnixStream, str::FromStr}; #[test] @@ -109,7 +109,7 @@ fn ed25519_test_suite() { DerivationResponse::try_from(client.request(&req.into()).unwrap()).unwrap(); assert_eq!(&response.data, test.private_key.as_slice()); } - Infallible::Ok(()) + Panicable::Ok(()) }) .unwrap(); } diff --git a/crates/daemon/keyforkd/src/test_util.rs b/crates/daemon/keyforkd/src/test_util.rs index ba72cf6..a926ff0 100644 --- a/crates/daemon/keyforkd/src/test_util.rs +++ b/crates/daemon/keyforkd/src/test_util.rs @@ -12,20 +12,21 @@ use keyfork_bug::bug; #[derive(Debug, thiserror::Error)] #[error("This error can never be instantiated")] #[doc(hidden)] -pub struct InfallibleError { - protected: (), -} +pub enum UninstantiableError {} -/// An infallible result. This type can be used to represent a function that should never error. +/// A panicable result. This type can be used when a closure chooses to panic instead of +/// returning an error. This doesn't necessarily mean a closure _has_ to panic, and its absence +/// doesn't imply a closure _can't_ panic, but this is a useful utility function for writing tests, +/// to avoid the necessity of making custom error types. /// /// ```rust -/// use keyforkd::test_util::Infallible; +/// use keyforkd::test_util::Panicable; /// let closure = || { -/// Infallible::Ok(()) +/// Panicable::Ok(()) /// }; /// assert!(closure().is_ok()); /// ``` -pub type Infallible = std::result::Result; +pub type Panicable = std::result::Result; /// Run a test making use of a Keyforkd server. The test may use a seed (the first argument) from a /// test suite, or (as shown in the example below) a simple seed may be used solely to ensure @@ -39,6 +40,8 @@ pub type Infallible = std::result::Result; /// runtime. /// /// # Examples +/// The test utility provides a socket that can be connected to for deriving keys. +/// /// ```rust /// use std::os::unix::net::UnixStream; /// let seed = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; @@ -46,6 +49,18 @@ pub type Infallible = std::result::Result; /// UnixStream::connect(&path).map(|_| ()) /// }).unwrap(); /// ``` +/// +/// The `keyforkd-client` crate uses the `KEYFORKD_SOCKET_PATH` variable to determine the default +/// socket path. The test will export the environment variable so it may be used by default. +/// +/// ```rust +/// use std::os::unix::net::UnixStream; +/// let seed = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; +/// keyforkd::test_util::run_test(seed.as_slice(), |path| { +/// assert_eq!(std::env::var_os("KEYFORKD_SOCKET_PATH").unwrap(), path.as_os_str()); +/// UnixStream::connect(&path).map(|_| ()) +/// }).unwrap(); +/// ``` #[allow(clippy::missing_errors_doc)] pub fn run_test(seed: &[u8], closure: F) -> Result<(), E> where @@ -82,6 +97,7 @@ where rx.recv() .await .expect(bug!("can't receive server start signal from channel")); + std::env::set_var("KEYFORKD_SOCKET_PATH", &socket_path); let test_handle = tokio::task::spawn_blocking(move || closure(&socket_path)); let result = test_handle.await; @@ -89,8 +105,8 @@ where result }); if let Err(e) = result { - if e.is_panic() { - std::panic::resume_unwind(e.into_panic()); + if let Ok(reason) = e.try_into_panic() { + std::panic::resume_unwind(reason); } } Ok(()) @@ -103,6 +119,6 @@ mod tests { #[test] fn test_run_test() { let seed = b"beefbeef"; - run_test(seed, |_path| Infallible::Ok(())).expect("infallible"); + run_test(seed, |_path| Panicable::Ok(())).expect("infallible"); } }