Clarify documentation and add more examples
Note: The type signature of smex::encode and smex::decode has changed, but will still accept values that were previously passed in.
This commit is contained in:
parent
33405ee4fc
commit
ece9f435d2
|
@ -40,6 +40,25 @@
|
||||||
//! # keyforkd::test_util::Infallible::Ok(())
|
//! # keyforkd::test_util::Infallible::Ok(())
|
||||||
//! # }).unwrap();
|
//! # }).unwrap();
|
||||||
//! ```
|
//! ```
|
||||||
|
//!
|
||||||
|
//! In tests, the Keyforkd test_util module and TestPrivateKeys can be used.
|
||||||
|
//!
|
||||||
|
//! ```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| {
|
||||||
|
//! 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::<PrivateKey>(&derivation_path).unwrap();
|
||||||
|
//! keyforkd::test_util::Infallible::Ok(())
|
||||||
|
//! }).unwrap();
|
||||||
|
//! ```
|
||||||
|
|
||||||
pub use std::os::unix::net::UnixStream;
|
pub use std::os::unix::net::UnixStream;
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
|
@ -34,3 +34,15 @@ request, as well as its best-effort guess on what path is being derived (using
|
||||||
the `keyfork-derive-path-data` crate), to inform the user of what keys are
|
the `keyfork-derive-path-data` crate), to inform the user of what keys are
|
||||||
requested. Once the server sends the client the new extended private key, the
|
requested. Once the server sends the client the new extended private key, the
|
||||||
client can then choose to use the key as-is, or derive further keys.
|
client can then choose to use the key as-is, or derive further keys.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
A Keyfork server can be automatically started by using [`test_util::run_test`].
|
||||||
|
The function accepts a closure, starting the server before the closure is run,
|
||||||
|
and closing the server after the closure has completed. This may be useful for
|
||||||
|
people writing software that interacts with the Keyfork server, such as a
|
||||||
|
deriver or a provisioner. A test seed must be provided, but can be any content.
|
||||||
|
The closure accepts one argument, the path of the UNIX socket from which the
|
||||||
|
server can be accessed.
|
||||||
|
|
||||||
|
Examples of the test utility can be seen in the `keyforkd-client` crate.
|
||||||
|
|
|
@ -25,14 +25,25 @@ pub struct InfallibleError {
|
||||||
/// ```
|
/// ```
|
||||||
pub type Infallible<T> = std::result::Result<T, InfallibleError>;
|
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
|
/// Run a test making use of a Keyforkd server. The test may use a seed (the first argument) from a
|
||||||
/// provided as the only argument to the closure. The closure is expected to return a Result; the
|
/// test suite, or (as shown in the example below) a simple seed may be used solely to ensure
|
||||||
/// Error field of the Result may be an error returned by a test.
|
/// the server is capable of being interacted with. The test is in the form of a closure, expected
|
||||||
|
/// to return a [`Result`] where success is a unit type (test passed) and the error is any error
|
||||||
|
/// that happened during the test (alternatively, a panic may be used, and will be returned as an
|
||||||
|
/// error).
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
|
/// The function may panic if any errors arise while configuring and using the Tokio multithreaded
|
||||||
|
/// runtime.
|
||||||
///
|
///
|
||||||
/// The function is not expected to run in production; therefore, the function plays "fast and
|
/// # Examples
|
||||||
/// loose" wih the usage of [`Result::expect`]. In normal usage, these should never be an issue.
|
/// ```rust
|
||||||
|
/// use std::os::unix::net::UnixStream;
|
||||||
|
/// let seed = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||||
|
/// keyforkd::test_util::run_test(seed.as_slice(), |path| {
|
||||||
|
/// UnixStream::connect(&path).map(|_| ())
|
||||||
|
/// }).unwrap();
|
||||||
|
/// ```
|
||||||
#[allow(clippy::missing_errors_doc)]
|
#[allow(clippy::missing_errors_doc)]
|
||||||
pub fn run_test<F, E>(seed: &[u8], closure: F) -> Result<(), E>
|
pub fn run_test<F, E>(seed: &[u8], closure: F) -> Result<(), E>
|
||||||
where
|
where
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//! Creation of OpenPGP certificates from BIP-0032 derived data.
|
//! Creation of OpenPGP Transferable Secret Keys from BIP-0032 derived data.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
|
|
|
@ -57,10 +57,21 @@ fn ensure_offline() {
|
||||||
/// # std::env::set_var("SHOOT_SELF_IN_FOOT", "1");
|
/// # std::env::set_var("SHOOT_SELF_IN_FOOT", "1");
|
||||||
/// keyfork_entropy::ensure_safe();
|
/// keyfork_entropy::ensure_safe();
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// When running on a system that's online, or running an outdated kernel:
|
||||||
|
///
|
||||||
|
/// ```rust,should_panic
|
||||||
|
/// # // NOTE: sometimes, the environment variable is set, for testing purposes. I'm not sure how
|
||||||
|
/// # // to un-set it. Set it to a sentinel value.
|
||||||
|
/// # std::env::set_var("SHOOT_SELF_IN_FOOT", "test-must-fail");
|
||||||
|
/// # std::env::set_var("INSECURE_HARDWARE_ALLOWED", "test-must-fail");
|
||||||
|
/// keyfork_entropy::ensure_safe();
|
||||||
|
/// ```
|
||||||
pub fn ensure_safe() {
|
pub fn ensure_safe() {
|
||||||
if !std::env::vars()
|
if !std::env::vars().any(|(name, value)| {
|
||||||
.any(|(name, _)| name == "SHOOT_SELF_IN_FOOT" || name == "INSECURE_HARDWARE_ALLOWED")
|
(name == "SHOOT_SELF_IN_FOOT" || name == "INSECURE_HARDWARE_ALLOWED")
|
||||||
{
|
&& value != "test-must-fail"
|
||||||
|
}) {
|
||||||
ensure_safe_kernel_version();
|
ensure_safe_kernel_version();
|
||||||
ensure_offline();
|
ensure_offline();
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,11 @@ pub(crate) fn hash(data: &[u8]) -> Vec<u8> {
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// An error may be returned if the given `data` is more than [`u32::MAX`] bytes. This is a
|
/// An error may be returned if the given `data` is more than [`u32::MAX`] bytes. This is a
|
||||||
/// constraint on a protocol level.
|
/// constraint on a protocol level.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
/// let data = keyfork_frame::try_encode(b"hello world!".as_slice()).unwrap();
|
||||||
|
/// ```
|
||||||
pub fn try_encode(data: &[u8]) -> Result<Vec<u8>, EncodeError> {
|
pub fn try_encode(data: &[u8]) -> Result<Vec<u8>, EncodeError> {
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
try_encode_to(data, &mut output)?;
|
try_encode_to(data, &mut output)?;
|
||||||
|
@ -77,6 +82,12 @@ pub fn try_encode(data: &[u8]) -> Result<Vec<u8>, EncodeError> {
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// An error may be returned if the givenu `data` is more than [`u32::MAX`] bytes, or if the writer
|
/// An error may be returned if the givenu `data` is more than [`u32::MAX`] bytes, or if the writer
|
||||||
/// is unable to write data.
|
/// is unable to write data.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
/// let mut output = vec![];
|
||||||
|
/// keyfork_frame::try_encode_to(b"hello world!".as_slice(), &mut output).unwrap();
|
||||||
|
/// ```
|
||||||
pub fn try_encode_to(data: &[u8], writable: &mut impl Write) -> Result<(), EncodeError> {
|
pub fn try_encode_to(data: &[u8], writable: &mut impl Write) -> Result<(), EncodeError> {
|
||||||
let hash = hash(data);
|
let hash = hash(data);
|
||||||
let len = hash.len() + data.len();
|
let len = hash.len() + data.len();
|
||||||
|
@ -107,18 +118,40 @@ pub(crate) fn verify_checksum(data: &[u8]) -> Result<&[u8], DecodeError> {
|
||||||
/// * The given `data` does not contain enough data to parse a length,
|
/// * The given `data` does not contain enough data to parse a length,
|
||||||
/// * The given `data` does not contain the given length's worth of data,
|
/// * The given `data` does not contain the given length's worth of data,
|
||||||
/// * The given `data` has a checksum that does not match what we build locally.
|
/// * The given `data` has a checksum that does not match what we build locally.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
/// let input = b"hello world!";
|
||||||
|
/// let encoded = keyfork_frame::try_encode(input.as_slice()).unwrap();
|
||||||
|
/// let decoded = keyfork_frame::try_decode(&encoded).unwrap();
|
||||||
|
/// assert_eq!(input.as_slice(), decoded.as_slice());
|
||||||
|
/// ```
|
||||||
pub fn try_decode(data: &[u8]) -> Result<Vec<u8>, DecodeError> {
|
pub fn try_decode(data: &[u8]) -> Result<Vec<u8>, DecodeError> {
|
||||||
try_decode_from(&mut &data[..])
|
try_decode_from(&mut &data[..])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read and decode a framed message into a `Vec<u8>`.
|
/// Read and decode a framed message into a `Vec<u8>`.
|
||||||
///
|
///
|
||||||
|
/// Note that unlike [`try_encode_to`], this method does not allow writing to an object
|
||||||
|
/// implementing Write. This is because the data must be stored entirely in memory to allow
|
||||||
|
/// verifying the data. The data is then returned using the same in-memory representation as is
|
||||||
|
/// used in memory, and a caller may then choose to use `writable.write_all()`.
|
||||||
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// An error may be returned if:
|
/// An error may be returned if:
|
||||||
/// * The given `data` does not contain enough data to parse a length,
|
/// * The given `data` does not contain enough data to parse a length,
|
||||||
/// * The given `data` does not contain the given length's worth of data,
|
/// * The given `data` does not contain the given length's worth of data,
|
||||||
/// * The given `data` has a checksum that does not match what we build locally.
|
/// * The given `data` has a checksum that does not match what we build locally.
|
||||||
/// * The source for the data returned an error.
|
/// * The source for the data returned an error.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
/// let input = b"hello world!";
|
||||||
|
/// let mut encoded = vec![];
|
||||||
|
/// keyfork_frame::try_encode_to(input.as_slice(), &mut encoded).unwrap();
|
||||||
|
/// let decoded = keyfork_frame::try_decode_from(&mut &encoded[..]).unwrap();
|
||||||
|
/// assert_eq!(input.as_slice(), decoded.as_slice());
|
||||||
|
/// ```
|
||||||
pub fn try_decode_from(readable: &mut impl Read) -> Result<Vec<u8>, DecodeError> {
|
pub fn try_decode_from(readable: &mut impl Read) -> Result<Vec<u8>, DecodeError> {
|
||||||
let mut bytes = 0u32.to_be_bytes();
|
let mut bytes = 0u32.to_be_bytes();
|
||||||
readable.read_exact(&mut bytes)?;
|
readable.read_exact(&mut bytes)?;
|
||||||
|
|
|
@ -231,6 +231,13 @@ impl Mnemonic {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// An error may be returned if the entropy is not within the acceptable lengths.
|
/// An error may be returned if the entropy is not within the acceptable lengths.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
/// use keyfork_mnemonic_util::Mnemonic;
|
||||||
|
/// let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||||
|
/// let mnemonic = Mnemonic::from_entropy(data.as_slice(), Default::default()).unwrap();
|
||||||
|
/// ```
|
||||||
pub fn from_entropy(
|
pub fn from_entropy(
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
wordlist: Arc<Wordlist>,
|
wordlist: Arc<Wordlist>,
|
||||||
|
@ -248,11 +255,36 @@ impl Mnemonic {
|
||||||
Ok(unsafe { Self::from_raw_entropy(bytes, wordlist) })
|
Ok(unsafe { Self::from_raw_entropy(bytes, wordlist) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a Mnemonic using an arbitrary length of given entropy. The length does not need to
|
||||||
|
/// conform to BIP-0039 standards.
|
||||||
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// This function can potentially produce mnemonics that are not BIP-0039 compliant or can't
|
/// This function can potentially produce mnemonics that are not BIP-0039 compliant or can't
|
||||||
/// properly be encoded as a mnemonic. It is assumed the caller asserts the byte count is `% 4
|
/// properly be encoded as a mnemonic. It is assumed the caller asserts the byte count is `% 4
|
||||||
/// == 0`.
|
/// == 0`. If the assumption is incorrect, code may panic.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
/// use keyfork_mnemonic_util::Mnemonic;
|
||||||
|
/// let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||||
|
/// let mnemonic = unsafe { Mnemonic::from_raw_entropy(data.as_slice(), Default::default()) };
|
||||||
|
/// let mnemonic_text = mnemonic.to_string();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// If given an invalid length, undefined behavior may follow, or code may panic.
|
||||||
|
///
|
||||||
|
/// ```rust,should_panic
|
||||||
|
/// use keyfork_mnemonic_util::Mnemonic;
|
||||||
|
/// use std::str::FromStr;
|
||||||
|
///
|
||||||
|
/// // NOTE: Data is of invalid length, 31
|
||||||
|
/// let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||||
|
/// let mnemonic = unsafe { Mnemonic::from_raw_entropy(data.as_slice(), Default::default()) };
|
||||||
|
/// let mnemonic_text = mnemonic.to_string();
|
||||||
|
/// // NOTE: panic happens here
|
||||||
|
/// let new_mnemonic = Mnemonic::from_str(&mnemonic_text).unwrap();
|
||||||
|
/// ```
|
||||||
pub unsafe fn from_raw_entropy(bytes: &[u8], wordlist: Arc<Wordlist>) -> Mnemonic {
|
pub unsafe fn from_raw_entropy(bytes: &[u8], wordlist: Arc<Wordlist>) -> Mnemonic {
|
||||||
Mnemonic {
|
Mnemonic {
|
||||||
entropy: bytes.to_vec(),
|
entropy: bytes.to_vec(),
|
||||||
|
@ -260,22 +292,22 @@ impl Mnemonic {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The internal representation of the decoded data.
|
/// A view to internal representation of the decoded data.
|
||||||
pub fn as_bytes(&self) -> &[u8] {
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
&self.entropy
|
&self.entropy
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The internal representation of the decoded data, as a [`Vec<u8>`].
|
/// A clone of the internal representation of the decoded data.
|
||||||
pub fn to_bytes(&self) -> Vec<u8> {
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
self.entropy.to_vec()
|
self.entropy.to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drop self, returning the decoded data.
|
/// Conver the Mnemonic into the internal representation of the decoded data.
|
||||||
pub fn into_bytes(self) -> Vec<u8> {
|
pub fn into_bytes(self) -> Vec<u8> {
|
||||||
self.entropy
|
self.entropy
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clone the existing entropy.
|
/// Clone the existing data.
|
||||||
#[deprecated = "Use as_bytes(), to_bytes(), or into_bytes() instead"]
|
#[deprecated = "Use as_bytes(), to_bytes(), or into_bytes() instead"]
|
||||||
pub fn entropy(&self) -> Vec<u8> {
|
pub fn entropy(&self) -> Vec<u8> {
|
||||||
self.entropy.clone()
|
self.entropy.clone()
|
||||||
|
|
|
@ -28,7 +28,15 @@ impl std::fmt::Display for DecodeError {
|
||||||
impl std::error::Error for DecodeError {}
|
impl std::error::Error for DecodeError {}
|
||||||
|
|
||||||
/// Encode a given input as a hex string.
|
/// Encode a given input as a hex string.
|
||||||
pub fn encode(input: &[u8]) -> String {
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
/// let data = b"hello world!";
|
||||||
|
/// let result = smex::encode(&data);
|
||||||
|
/// assert_eq!(result, "68656c6c6f20776f726c6421");
|
||||||
|
/// ```
|
||||||
|
pub fn encode(input: impl AsRef<[u8]>) -> String {
|
||||||
|
let input = input.as_ref();
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
for byte in input {
|
for byte in input {
|
||||||
write!(s, "{byte:02x}").unwrap();
|
write!(s, "{byte:02x}").unwrap();
|
||||||
|
@ -50,7 +58,26 @@ fn val(c: u8) -> Result<u8, DecodeError> {
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// The function may error if a non-hex character is encountered or if the character count is not
|
/// The function may error if a non-hex character is encountered or if the character count is not
|
||||||
/// evenly divisible by two.
|
/// evenly divisible by two.
|
||||||
pub fn decode(input: &str) -> Result<Vec<u8>, DecodeError> {
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
/// let data = b"hello world!";
|
||||||
|
/// let encoded = smex::encode(&data);
|
||||||
|
/// let decoded = smex::decode(&encoded).unwrap();
|
||||||
|
/// assert_eq!(data.as_slice(), decoded.as_slice());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The function may return an error if the given input is not valid hex.
|
||||||
|
///
|
||||||
|
/// ```rust,should_panic
|
||||||
|
/// let data = b"hello world!";
|
||||||
|
/// let mut encoded = smex::encode(&data);
|
||||||
|
/// encoded.push('G');
|
||||||
|
/// let decoded = smex::decode(&encoded).unwrap();
|
||||||
|
/// assert_eq!(data.as_slice(), decoded.as_slice());
|
||||||
|
/// ```
|
||||||
|
pub fn decode(input: impl AsRef<str>) -> Result<Vec<u8>, DecodeError> {
|
||||||
|
let input = input.as_ref();
|
||||||
let len = input.len();
|
let len = input.len();
|
||||||
if len % 2 != 0 {
|
if len % 2 != 0 {
|
||||||
return Err(DecodeError::InvalidCharacterCount(len));
|
return Err(DecodeError::InvalidCharacterCount(len));
|
||||||
|
|
Loading…
Reference in New Issue