//! # 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". 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., //! `0.5.x`) will be compatible, but `0.5.x` will not be compatible with `0.6.x`. For versions //! 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`. //! //! 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. //! //! ## Server Requests //! //! 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: //! //! ### 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. //! //! ```rust //! use std::str::FromStr; //! //! use keyforkd_client::Client; //! use keyfork_derive_util::DerivationPath; //! # use keyfork_derive_util::private_key::TestPrivateKey as PrivateKey; //! // use k256::SecretKey as PrivateKey; //! // use ed25519_dalek::SigningKey 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(()) //! } //! ``` //! //! --- //! //! 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; //! //! use keyforkd_client::Client; //! use keyfork_derive_util::DerivationPath; //! use keyfork_derive_util::private_key::TestPrivateKey as PrivateKey; //! //! let seed = b"funky accordion noises"; //! keyforkd::test_util::run_test(seed, |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::Panicable::Ok(()) //! }).unwrap(); //! ``` pub use std::os::unix::net::UnixStream; use std::{collections::HashMap, path::PathBuf}; use keyfork_derive_util::{ request::{AsAlgorithm, DerivationRequest}, DerivationPath, ExtendedPrivateKey, PrivateKey, }; use keyfork_frame::{try_decode_from, try_encode_to, DecodeError, EncodeError}; use keyforkd_models::{Error as KeyforkdError, Request, Response}; #[cfg(test)] mod tests; /// An error occurred while interacting with Keyforkd. #[derive(Debug, thiserror::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. #[error("Neither KEYFORK_SOCKET_PATH nor XDG_RUNTIME_DIR were set")] EnvVarsNotFound, /// The Keyforkd client was unable to connect to the soocket. #[error("Socket was unable to connect to {1}: {0} (make sure keyforkd is running)")] Connect(std::io::Error, PathBuf), /// Data could not be written to, or read from, the socket. #[error("Could not write to or from the socket: {0}")] Io(#[from] std::io::Error), /// Attempting to serialize or deserialize a type to or from bincode encountered an error. #[error("Could not perform bincode transformation: {0}")] Bincode(#[from] Box), /// A frame could not be encoded from the given data. #[error("Could not perform frame transformation: {0}")] FrameEnc(#[from] EncodeError), /// A frame could not be decoded from the given data. #[error("Could not perform frame transformation: {0}")] FrameDec(#[from] DecodeError), /// An error encountered in Keyforkd. #[error("Error in Keyforkd: {0}")] Keyforkd(#[from] KeyforkdError), /// An invalid key was returned. #[error("Invalid key returned")] InvalidKey, } #[allow(missing_docs)] pub type Result = std::result::Result; /// Create a [`UnixStream`] from the common Keyforkd socket paths. /// /// # Errors /// An error may be returned if the required environment variables were not set or if the socket /// could not be connected to. pub fn get_socket() -> Result { let socket_vars = std::env::vars() .filter(|(key, _)| ["XDG_RUNTIME_DIR", "KEYFORKD_SOCKET_PATH"].contains(&key.as_str())) .collect::>(); let mut socket_path: PathBuf; #[allow(clippy::single_match_else)] match socket_vars.get("KEYFORKD_SOCKET_PATH") { Some(occupied) => { socket_path = PathBuf::from(occupied); } None => { socket_path = PathBuf::from( socket_vars .get("XDG_RUNTIME_DIR") .ok_or(Error::EnvVarsNotFound)?, ); socket_path.extend(["keyforkd", "keyforkd.sock"]); } } UnixStream::connect(&socket_path).map_err(|e| Error::Connect(e, socket_path)) } /// A client to interact with Keyforkd. /// /// Upon creation, a socket is opened, and is kept open for the duration of the object's lifetime. /// Currently, Keyforkd does not support the reuse of sockets. Attempting to reuse the socket after /// previously using it will likely result in an error. #[derive(Debug)] pub struct Client { socket: UnixStream, } impl Client { /// Create a new client from a given already-connected [`UnixStream`]. This function is /// provided in case a specific UnixStream has to be used; otherwise, /// [`Client::discover_socket`] should be preferred. /// /// # Examples /// ```rust /// use keyforkd_client::{Client, get_socket}; /// /// # let seed = b"funky accordion noises"; /// # keyforkd::test_util::run_test(seed, |socket_path| { /// let mut socket = get_socket()?; /// let mut client = Client::new(socket); /// # Ok::<_, keyforkd_client::Error>(()) /// # }).unwrap(); /// ``` pub fn new(socket: UnixStream) -> Self { Self { socket } } /// Create a new client using well-known socket locations. /// /// # Errors /// An error may be returned if the required environment variables were not set or if the /// socket could not be connected to. /// /// # Examples /// ```rust /// use keyforkd_client::Client; /// /// # let seed = b"funky accordion noises"; /// # keyforkd::test_util::run_test(seed, |socket_path| { /// let mut client = Client::discover_socket()?; /// # Ok::<_, keyforkd_client::Error>(()) /// # }).unwrap(); /// ``` pub fn discover_socket() -> Result { 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. /// /// # Examples /// ```rust /// use std::str::FromStr; /// /// use keyforkd_client::Client; /// use keyfork_derive_util::DerivationPath; /// # use keyfork_derive_util::private_key::TestPrivateKey as PrivateKey; /// // 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| { /// 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::Panicable::Ok(()) /// # }).unwrap(); /// ``` pub fn request_xprv(&mut self, path: &DerivationPath) -> Result> 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; ExtendedPrivateKey::from_parts(&d.data, depth, d.chain_code) .map_err(|_| Error::InvalidKey) } _ => 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. 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)?; let resp: Result = bincode::deserialize(&resp)?; resp.map_err(From::from) } }