Compare commits

...

5 Commits

18 changed files with 636 additions and 21 deletions

View File

@ -68,14 +68,27 @@ where
/// mnemonic, but may take 16-byte seeds.
///
/// # Panics
///
/// The method performs unchecked `try_into()` operations on a constant-sized slice.
///
/// # Errors
///
/// An error may be returned if:
/// * The given seed had an incorrect length.
/// * A `HmacSha512` can't be constructed - this should be impossible.
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey,
/// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let seed: &[u8; 64] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?;
/// # Ok(())
/// # }
/// ```
pub fn new(seed: impl AsRef<[u8]>) -> Result<Self> {
Self::new_internal(seed.as_ref())
}
@ -104,6 +117,23 @@ where
///
/// # Errors
/// The function may error if a private key can't be created from the seed.
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey,
/// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let key: &[u8; 32] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let chain_code: &[u8; 32] = //
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code)?;
/// # Ok(())
/// # }
/// ```
pub fn new_from_parts(seed: &[u8], depth: u8, chain_code: [u8; 32]) -> Result<Self> {
Ok(Self {
private_key: K::from_bytes(seed.try_into()?),
@ -113,26 +143,123 @@ where
}
/// Returns a reference to the [`PrivateKey`].
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # private_key::PrivateKey as _,
/// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey,
/// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let key: &[u8; 32] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let chain_code: &[u8; 32] = //
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code)?;
/// assert_eq!(xprv.private_key(), &PrivateKey::from_bytes(key));
/// # Ok(())
/// # }
/// ```
pub fn private_key(&self) -> &K {
&self.private_key
}
/// Create an [`ExtendedPublicKey`] for the current [`PrivateKey`].
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # private_key::PrivateKey as _,
/// # public_key::PublicKey as _,
/// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey,
/// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let seed: &[u8; 64] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// # let known_key: [u8; 33] = [
/// # 0, 242, 26, 9, 159, 68, 199, 0, 206, 71, 248,
/// # 102, 201, 210, 159, 219, 222, 42, 201, 44, 196, 27,
/// # 90, 221, 80, 85, 135, 79, 39, 253, 223, 35, 251
/// # ];
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?;
/// let xpub = xprv.extended_public_key();
/// assert_eq!(known_key, xpub.public_key().to_bytes());
/// # Ok(())
/// # }
/// ```
pub fn extended_public_key(&self) -> ExtendedPublicKey<K::PublicKey> {
ExtendedPublicKey::new(self.public_key(), self.chain_code)
ExtendedPublicKey::new_from_parts(self.public_key(), self.depth, self.chain_code)
}
/// Return a public key for the current [`PrivateKey`].
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # private_key::PrivateKey as _,
/// # public_key::PublicKey as _,
/// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey,
/// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let seed: &[u8; 64] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?;
/// let pubkey = xprv.public_key();
/// # Ok(())
/// # }
/// ```
pub fn public_key(&self) -> K::PublicKey {
self.private_key.public_key()
}
/// Returns the current depth.
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey,
/// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let key: &[u8; 32] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let chain_code: &[u8; 32] = //
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code)?;
/// assert_eq!(xprv.depth(), 4);
/// # Ok(())
/// # }
/// ```
pub fn depth(&self) -> u8 {
self.depth
}
/// Returns a copy of the current chain code.
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey,
/// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let key: &[u8; 32] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let chain_code: &[u8; 32] = //
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code)?;
/// assert_eq!(chain_code, &xprv.chain_code());
/// # Ok(())
/// # }
/// ```
pub fn chain_code(&self) -> [u8; 32] {
self.chain_code
}
@ -140,9 +267,29 @@ where
/// Derive a child using the given [`DerivationPath`].
///
/// # Errors
///
/// An error may be returned under the same circumstances as
/// [`ExtendedPrivateKey::derive_child`].
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey,
/// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let seed: &[u8; 64] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let root_xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?;
/// let path = DerivationPath::default()
/// .chain_push(DerivationIndex::new(44, true)?)
/// .chain_push(DerivationIndex::new(0, true)?)
/// .chain_push(DerivationIndex::new(0, true)?)
/// .chain_push(DerivationIndex::new(0, false)?);
/// let derived_xprv = root_xprv.derive_path(&path)?;
/// # Ok(())
/// # }
/// ```
pub fn derive_path(&self, path: &DerivationPath) -> Result<Self> {
if path.path.is_empty() {
Ok(self.clone())
@ -165,6 +312,33 @@ where
/// * The depth exceeds the maximum depth [`u8::MAX`].
/// * A `HmacSha512` can't be constructed - this should be impossible.
/// * Deriving a child key fails. Check the documentation for your [`PrivateKey`].
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey,
/// # };
/// # fn check_empty(p: &ExtendedPrivateKey<PrivateKey>) -> Result<(), std::io::Error> {
/// # Ok(())
/// # }
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let seed: &[u8; 64] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let root_xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?;
/// let bip44_wallet = DerivationPath::default()
/// .chain_push(DerivationIndex::new(44, true)?)
/// .chain_push(DerivationIndex::new(0, true)?)
/// .chain_push(DerivationIndex::new(0, true)?)
/// .chain_push(DerivationIndex::new(0, false)?);
/// let change_xprv = root_xprv.derive_path(&bip44_wallet)?;
/// for account in (0..20).map(|i| DerivationIndex::new(i, false).unwrap()) {
/// let account_xprv = change_xprv.derive_child(&account)?;
/// check_empty(&account_xprv)?;
/// }
/// # Ok(())
/// # }
pub fn derive_child(&self, index: &DerivationIndex) -> Result<Self> {
let depth = self.depth.checked_add(1).ok_or(Error::Depth)?;

View File

@ -44,15 +44,51 @@ where
K: PublicKey,
{
/// Create a new [`ExtendedPublicKey`] from previously known values.
pub fn new(public_key: K, chain_code: ChainCode) -> Self {
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # public_key::PublicKey as _,
/// # public_key::TestPublicKey as PublicKey,
/// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let key: &[u8; 33] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let chain_code: &[u8; 32] = //
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// let pubkey = PublicKey::from_bytes(key);
/// let xpub = ExtendedPublicKey::<PublicKey>::new_from_parts(pubkey, 0, *chain_code);
/// # Ok(())
/// # }
/// ```
pub fn new_from_parts(public_key: K, depth: u8, chain_code: ChainCode) -> Self {
Self {
public_key,
depth: 0,
depth,
chain_code,
}
}
/// Return the internal [`PublicKey`].
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # public_key::PublicKey as _,
/// # public_key::TestPublicKey as PublicKey,
/// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # let key: &[u8; 33] = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// # let chain_code: &[u8; 32] = b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// # let pubkey = PublicKey::from_bytes(key);
/// let xpub = //
/// # ExtendedPublicKey::<PublicKey>::new_from_parts(pubkey, 0, *chain_code);
/// let pubkey = xpub.public_key();
/// # Ok(())
/// # }
/// ```
pub fn public_key(&self) -> &K {
&self.public_key
}
@ -70,6 +106,25 @@ where
/// * The depth exceeds the maximum depth [`u8::MAX`].
/// * A `HmacSha512` can't be constructed - this should be impossible.
/// * Deriving a child key fails. Check the documentation for your [`PublicKey`].
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # public_key::PublicKey as _,
/// # public_key::TestPublicKey as PublicKey,
/// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # let key: &[u8; 33] = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// # let chain_code: &[u8; 32] = b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// # let pubkey = PublicKey::from_bytes(key);
/// let xpub = //
/// # ExtendedPublicKey::<PublicKey>::new_from_parts(pubkey, 0, *chain_code);
/// let index = DerivationIndex::new(0, false)?;
/// let child = xpub.derive_child(&index)?;
/// # Ok(())
/// # }
/// ```
pub fn derive_child(&self, index: &DerivationIndex) -> Result<Self> {
if index.is_hardened() {
return Err(Error::HardenedIndex);

View File

@ -23,8 +23,20 @@ impl DerivationIndex {
/// Creates a new [`DerivationIndex`].
///
/// # Errors
///
/// Returns an error if the index is larger than the hardened flag.
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::*;
/// let bip44 = DerivationIndex::new(44, true);
/// ```
///
/// Using a derivation index that is higher than 2^31 returns an error:
///
/// ```rust,should_panic
/// # use keyfork_derive_util::*;
/// let too_high = DerivationIndex::new(u32::MAX, true).unwrap();
/// ```
pub const fn new(index: u32, hardened: bool) -> Result<Self> {
if index & (0b1 << 31) > 0 {
return Err(Error::IndexTooLarge(index));
@ -46,6 +58,13 @@ impl DerivationIndex {
/// Return the internal derivation index. Note that if the derivation index is hardened, the
/// highest bit will be set, and the value can't be used to create a new derivation index.
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::*;
/// assert_eq!(DerivationIndex::new(44, true).unwrap().inner(), 2147483692);
/// assert_eq!(DerivationIndex::new(200, false).unwrap().inner(), 200);
/// ```
pub fn inner(&self) -> u32 {
self.0
}
@ -54,7 +73,15 @@ impl DerivationIndex {
self.0.to_be_bytes()
}
/// Whether or not the index is hardened, allowing deriving the key from a known parent key.
/// Whether or not the index is hardened, allowing deriving the key from a known parent public
/// key.
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::*;
/// assert_eq!(DerivationIndex::new(0, true).unwrap().is_hardened(), true);
/// assert_eq!(DerivationIndex::new(0, false).unwrap().is_hardened(), false);
/// ```
pub fn is_hardened(&self) -> bool {
self.0 & (0b1 << 31) != 0
}

View File

@ -52,6 +52,24 @@ impl DerivationPath {
}
/// Append an index to the path, returning self to allow chaining method calls.
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::*;
/// # fn discover_wallet(_p: DerivationPath) -> Result<bool, std::io::Error> { Ok(true) }
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let account = 0;
/// let path = DerivationPath::default()
/// .chain_push(DerivationIndex::new(44, true)?)
/// .chain_push(DerivationIndex::new(0, true)?)
/// .chain_push(DerivationIndex::new(account, true)?);
/// let mut has_wallet = false;
/// for index in (0..20).map(|i| DerivationIndex::new(i, true).unwrap()) {
/// has_wallet = has_wallet || discover_wallet(path.clone().chain_push(index))?;
/// }
/// # Ok(())
/// # }
/// ```
pub fn chain_push(mut self, index: DerivationIndex) -> Self {
self.path.push(index);
self

View File

@ -13,9 +13,32 @@ pub trait PrivateKey: Sized {
type Err: std::error::Error;
/// Create a Self from bytes.
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # private_key::TestPrivateKey as OurPrivateKey,
/// # };
/// let key_data: &[u8; 32] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let private_key = OurPrivateKey::from_bytes(key_data);
/// ```
fn from_bytes(b: &PrivateKeyBytes) -> Self;
/// Convert a &Self to bytes.
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # private_key::TestPrivateKey as OurPrivateKey,
/// # };
/// let key_data: &[u8; 32] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let private_key = OurPrivateKey::from_bytes(key_data);
/// assert_eq!(key_data, &private_key.to_bytes());
/// ```
fn to_bytes(&self) -> PrivateKeyBytes;
/*
@ -27,12 +50,35 @@ pub trait PrivateKey: Sized {
*/
/// The initial key for BIP-0032 and SLIP-0010 derivation, such as secp256k1's "Bitcoin seed".
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # private_key::TestPrivateKey as OurPrivateKey,
/// # };
/// assert_eq!(OurPrivateKey::key(), "testing seed");
/// ```
fn key() -> &'static str;
/// Generate a [`Self::PublicKey`].
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # private_key::TestPrivateKey as OurPrivateKey,
/// # };
/// let key_data: &[u8; 32] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let private_key = OurPrivateKey::from_bytes(key_data);
/// let public_key = private_key.public_key();
/// ```
fn public_key(&self) -> Self::PublicKey;
/// Derive a child [`PrivateKey`] with given `PrivateKeyBytes`.
/// Derive a child [`PrivateKey`] with given `PrivateKeyBytes`. The implementation of
/// derivation is algorithm-specific and a specification should be consulted when implementing
/// this method.
///
/// # Errors
///
@ -129,3 +175,48 @@ impl PrivateKey for ed25519_dalek::SigningKey {
true
}
}
use crate::public_key::TestPublicKey;
#[doc(hidden)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TestPrivateKey {
key: [u8; 32],
}
impl TestPrivateKey {
pub(crate) fn public_key(&self) -> TestPublicKey {
let mut bytes = [0u8; 33];
for (i, byte) in self.key.iter().enumerate() {
bytes[i + 1] = byte ^ 0xFF;
}
TestPublicKey { key: bytes }
}
}
impl PrivateKey for TestPrivateKey {
type PublicKey = TestPublicKey;
type Err = PrivateKeyError;
fn from_bytes(b: &PrivateKeyBytes) -> Self {
Self {
key: *b
}
}
fn to_bytes(&self) -> PrivateKeyBytes {
self.key
}
fn key() -> &'static str {
"testing seed"
}
fn public_key(&self) -> Self::PublicKey {
self.public_key()
}
fn derive_child(&self, other: &PrivateKeyBytes) -> Result<Self, Self::Err> {
Ok(Self { key: *other })
}
}

View File

@ -19,9 +19,23 @@ pub trait PublicKey: Sized {
*/
/// Convert a &Self to bytes.
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # private_key::TestPrivateKey as OurPrivateKey,
/// # };
/// let key_data: &[u8; 32] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let private_key = OurPrivateKey::from_bytes(key_data);
/// let public_key_bytes = private_key.public_key().to_bytes();
/// ```
fn to_bytes(&self) -> PublicKeyBytes;
/// Derive a child [`PublicKey`] with given `PrivateKeyBytes`.
/// Derive a child [`PublicKey`] with given `PrivateKeyBytes`. The implementation of
/// derivation is algorithm-specific and a specification should be consulted when implementing
/// this method.
///
/// # Errors
///
@ -31,6 +45,18 @@ pub trait PublicKey: Sized {
fn derive_child(&self, other: PrivateKeyBytes) -> Result<Self, Self::Err>;
/// Create a BIP-0032/SLIP-0010 fingerprint from the public key.
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # private_key::TestPrivateKey as OurPrivateKey,
/// # };
/// let key_data: &[u8; 32] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let private_key = OurPrivateKey::from_bytes(key_data);
/// let fingerprint = private_key.public_key().fingerprint();
/// ```
fn fingerprint(&self) -> [u8; 4] {
let hash = Sha256::new().chain_update(self.to_bytes()).finalize();
let hash = Ripemd160::new().chain_update(hash).finalize();
@ -112,3 +138,32 @@ impl PublicKey for VerifyingKey {
Err(Self::Err::DerivationUnsupported)
}
}
#[doc(hidden)]
#[derive(Clone)]
pub struct TestPublicKey {
pub(crate) key: [u8; 33],
}
impl TestPublicKey {
#[doc(hidden)]
pub fn from_bytes(b: &[u8]) -> Self {
Self {
key: b.try_into().unwrap(),
}
}
}
impl PublicKey for TestPublicKey {
type Err = PublicKeyError;
fn to_bytes(&self) -> PublicKeyBytes {
self.key
}
fn derive_child(&self, _other: PrivateKeyBytes) -> Result<Self, Self::Err> {
// whatever it takes for tests to pass...
Ok(self.clone())
}
}

View File

@ -2,7 +2,9 @@
#![allow(clippy::match_wildcard_for_single_variants)]
use crate::{
extended_key::private_key::Error as XPrvError, DerivationPath, ExtendedPrivateKey, PrivateKey,
extended_key::private_key::Error as XPrvError,
private_key::{PrivateKey, TestPrivateKey},
DerivationPath, ExtendedPrivateKey,
};
use keyfork_mnemonic_util::{Mnemonic, MnemonicGenerationError};
use serde::{Deserialize, Serialize};
@ -30,11 +32,14 @@ pub type Result<T, E = DerivationError> = std::result::Result<T, E>;
/// The algorithm to derive a key for. The choice of algorithm will result in a different resulting
/// derivation.
#[derive(Serialize, Deserialize, Clone, Debug)]
#[non_exhaustive]
pub enum DerivationAlgorithm {
#[allow(missing_docs)]
Ed25519,
#[allow(missing_docs)]
Secp256k1,
#[doc(hidden)]
Internal,
}
impl DerivationAlgorithm {
@ -42,7 +47,7 @@ impl DerivationAlgorithm {
///
/// # Errors
/// The method may error if the derivation fails or if the algorithm is not supported.
pub fn derive(&self, seed: Vec<u8>, path: &DerivationPath) -> Result<DerivationResponse> {
fn derive(&self, seed: Vec<u8>, path: &DerivationPath) -> Result<DerivationResponse> {
match self {
#[cfg(feature = "ed25519")]
Self::Ed25519 => {
@ -62,6 +67,14 @@ impl DerivationAlgorithm {
&derived_key,
))
}
Self::Internal => {
let key = ExtendedPrivateKey::<TestPrivateKey>::new(seed)?;
let derived_key = key.derive_path(path)?;
Ok(DerivationResponse::with_algo_and_xprv(
self.clone(),
&derived_key,
))
}
#[allow(unreachable_patterns)]
_ => Err(DerivationError::Algorithm),
}
@ -89,6 +102,23 @@ pub struct DerivationRequest {
impl DerivationRequest {
/// Create a new derivation request.
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # request::*,
/// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey,
/// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let algo: DerivationAlgorithm = //
/// # DerivationAlgorithm::Internal;
/// let path: DerivationPath = //
/// # DerivationPath::default();
/// let request = DerivationRequest::new(algo, &path);
/// # Ok(())
/// # }
pub fn new(algorithm: DerivationAlgorithm, path: &DerivationPath) -> Self {
Self {
algorithm,
@ -97,6 +127,24 @@ impl DerivationRequest {
}
/// Return the path of the derivation request.
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # request::*,
/// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey,
/// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let algo: DerivationAlgorithm = //
/// # DerivationAlgorithm::Internal;
/// let path: DerivationPath = //
/// # DerivationPath::default();
/// let request = DerivationRequest::new(algo, &path);
/// assert_eq!(&path, request.path());
/// # Ok(())
/// # }
pub fn path(&self) -> &DerivationPath {
&self.path
}
@ -105,6 +153,29 @@ impl DerivationRequest {
///
/// # Errors
/// The method may error if the derivation fails or if the algorithm is not supported.
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # request::*,
/// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey,
/// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mnemonic: keyfork_mnemonic_util::Mnemonic = //
/// # keyfork_mnemonic_util::Mnemonic::from_entropy(
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
/// # Default::default(),
/// # )?;
/// let algo: DerivationAlgorithm = //
/// # DerivationAlgorithm::Internal;
/// let path: DerivationPath = //
/// # DerivationPath::default();
/// let request = DerivationRequest::new(algo, &path);
/// let response = request.derive_with_mnemonic(&mnemonic)?;
/// # Ok(())
/// # }
pub fn derive_with_mnemonic(&self, mnemonic: &Mnemonic) -> Result<DerivationResponse> {
// TODO: passphrase support and/or store passphrase within mnemonic
self.derive_with_master_seed(mnemonic.seed(None)?)
@ -114,6 +185,26 @@ impl DerivationRequest {
///
/// # Errors
/// The method may error if the derivation fails or if the algorithm is not supported.
///
/// # Examples
/// ```rust
/// # use keyfork_derive_util::{
/// # *,
/// # request::*,
/// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey,
/// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let seed: &[u8; 64] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let algo: DerivationAlgorithm = //
/// # DerivationAlgorithm::Internal;
/// let path: DerivationPath = //
/// # DerivationPath::default();
/// let request = DerivationRequest::new(algo, &path);
/// let response = request.derive_with_master_seed(seed.to_vec())?;
/// # Ok(())
/// # }
pub fn derive_with_master_seed(&self, seed: Vec<u8>) -> Result<DerivationResponse> {
self.algorithm.derive(seed, &self.path)
}
@ -137,7 +228,7 @@ pub struct DerivationResponse {
impl DerivationResponse {
/// Create a [`DerivationResponse`] with the given values.
pub fn with_algo_and_xprv<T: PrivateKey + Clone>(
fn with_algo_and_xprv<T: PrivateKey + Clone>(
algorithm: DerivationAlgorithm,
xprv: &ExtendedPrivateKey<T>,
) -> Self {

55
crates/keyfork/README.md Normal file
View File

@ -0,0 +1,55 @@
# Keyfork: The Kitchen Sink of Entropy
**Note:** Keyfork operations are meant to be run on an airgapped machine and
Keyfork will error if either any network interfaces are detected or if Keyfork
is running on a system with a kernel using an insecure random number generator.
An all-inclusive crate encapsulating end-user functionality of the Keyfork
ecosystem, the Keyfork binary includes all mechanisms that should be exposed to
the user when running Keyfork. Information about what operations Keyfork
performs are available in detail by running `keyfork help` (each subcommand has
thorough documentation) or in the [`docs`] mdBook, but here's a quick overview:
## Getting Started with Keyfork
Keyfork offers two options for getting started. For multi-user setups, it is
best to look at the detailed documentation for Keyfork Shard. For single-user
setups, `keyfork mnemonic generate` will (by default) create a 256-bit mnemonic
phrase that can be used to start Keyfork. *Store this phrase*, as it's the only
way you'll be able to start Keyfork in the future. It is recommended to use a
mnemonic recovery sheet or a printed-steel solution such as the [Billfodl] or
[Cryptosteel Capsule].
```sh
keyfork mnemonic generate
```
Once a mnemonic has been generated and stored in a secure manner, Keyfork can
be started by "recovering" the server from the mnemonic backup mechanism:
```sh
keyfork recover mnemonic
```
## Deriving Keys
Keyfork's primary goal is to derive keys. These keys can later be used for
things such as signing documents and artifacts or decrypting payloads.
Keyfork's first derivation target is OpenPGP, a protocol supporting many
cryptographic operations. OpenPGP keys require a User ID, which can be used to
identify the owner of the key, either by name or by email. To get an OpenPGP
public key (more accurately known as a "cert"), the [`sq`][sq] tool is used to
convert a key to a certificate:
```sh
keyfork derive openpgp "John Doe <jdoe@example.com>" | sq key extract-cert
```
All Keyfork derivations are intended to be reproducible. Because of this,
Keyfork derived keys can be recreated at any time, only requiring the knowledge
of how the key was made.
[`docs`]: /public/keyfork/src/branch/main/docs/src/SUMMARY.md
[Billfodl]: https://privacypros.io/products/the-billfodl/
[Cryptosteel Capsule]: https://cryptosteel.com/product/cryptosteel-capsule-solo/
[sq]: https://gitlab.com/sequoia-pgp/sequoia-sq/

View File

@ -19,6 +19,9 @@ type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
pub enum DeriveSubcommands {
/// Derive an OpenPGP Transferable Secret Key (private key). The key is encoded using OpenPGP
/// ASCII Armor, a format usable by most programs using OpenPGP.
///
/// The key is generated with a 24-hour expiration time. The operation to set the expiration
/// time to a higher value is left to the user to ensure the key is usable by the user.
#[command(name = "openpgp")]
OpenPGP {
/// Default User ID for the certificate, using the OpenPGP User ID format.
@ -72,6 +75,10 @@ pub struct Derive {
command: DeriveSubcommands,
/// Account ID. Required for all derivations.
///
/// An account ID may not be relevant for the derivation being performed, but the lack of an
/// account ID can often come as a hindrance in the future. As such, it is always required. If
/// the account ID is not relevant, it is assumed to be `0`.
#[arg(long, global = true, default_value = "0")]
account_id: u32,
}

View File

@ -9,7 +9,7 @@ mod wizard;
/// The Kitchen Sink of Entropy.
#[derive(Parser, Clone, Debug)]
#[command(author, version, about, long_about = None)]
#[command(author, version, about, long_about)]
pub struct Keyfork {
// Global options
#[command(subcommand)]
@ -20,25 +20,51 @@ pub struct Keyfork {
pub enum KeyforkCommands {
/// Derive keys of various formats. These commands require that the Keyfork server is running,
/// which can be started by running a `keyfork recover` command.
///
/// Derived keys are reproducible: assuming the same arguments are used when deriving a key for
/// a second time, the key will be _functionally_ equivalent. This means keys don't need to be
/// persisted to cold storage or left hot in a running program. They can be derived when
/// they're needed and forgotten when they're not.
Derive(derive::Derive),
/// Mnemonic generation and persistence utilities.
Mnemonic(mnemonic::Mnemonic),
/// Splitting and combining secrets, using Shamir's Secret Sharing.
///
/// Keys can be split such that a certain amount of users, from a potentially even-larger
/// amount of users, can be used to recreate a key. This creates resilience for a key, as in a
/// "seven of nine" scenario, nine people in total are capable of recreating a key, but only
/// seven may be required.
Shard(shard::Shard),
/// Derive and deploy keys to hardware.
///
/// Keys existing in hardware creates a situation where it is unlikely (but not impossible) for
/// a key to be extracted. While a key in memory could be captured by a rootkit or some other
/// privilege escalation mechanism, a key in hardware would require a hardware exploit to
/// extract the key.
///
/// It is recommended to provision keys whenever possible, as opposed to deriving them.
#[command(subcommand_negates_reqs(true))]
Provision(provision::Provision),
/// Recover a seed using the requested recovery mechanism and start the Keyfork server.
///
/// Once the Keyfork server is started, derivation requests can be performed. The Keyfork seed
/// is kept solely in the Keyfork server. Derivations with less than two indices are not
/// permitted, to ensure a seed often used to derive keys for multiple different paths is not
/// leaked by any individual deriver.
Recover(recover::Recover),
/// Utilities to automatically manage the setup of Keyfork.
Wizard(wizard::Wizard),
/// Print an autocompletion file to standard output.
///
/// Keyfork does not manage the installation of completion files. Consult the documentation for
/// the shell for which documentation has been generated on the appropriate location to store
/// completion files.
#[cfg(feature = "completion")]
Completion {
#[arg(value_enum)]

View File

@ -1,4 +1,4 @@
#![doc = include_str!("../../../README.md")]
#![doc = include_str!("../README.md")]
#![allow(clippy::module_name_repetitions)]

View File

@ -13,7 +13,7 @@ use crate::{
/// The top left cell is represented as `(0, 0)`.
///
/// On unix systems, this function will block and possibly time out while
/// [`crossterm::event::read`](crate::event::read) or [`crossterm::event::poll`](crate::event::poll) are being called.
/// [`crossterm::event::read`](crate::event::read()) or [`crossterm::event::poll`](crate::event::poll) are being called.
pub fn position() -> io::Result<(u16, u16)> {
if is_raw_mode_enabled() {
read_position_raw()

View File

@ -170,7 +170,7 @@ pub fn available_color_count() -> u16 {
///
/// # Notes
///
/// crossterm supports NO_COLOR (https://no-color.org/) to disabled colored output.
/// crossterm supports NO_COLOR (<https://no-color.org/>) to disabled colored output.
///
/// This API allows applications to override that behavior and force colorized output
/// even if NO_COLOR is set.

View File

@ -71,7 +71,7 @@ impl Colored {
}
/// Checks whether ansi color sequences are disabled by setting of NO_COLOR
/// in environment as per https://no-color.org/
/// in environment as per <https://no-color.org/>
pub fn ansi_color_disabled() -> bool {
!std::env::var("NO_COLOR")
.unwrap_or("".to_string())

View File

@ -199,7 +199,7 @@ pub struct WindowSize {
/// Returns the terminal size `[WindowSize]`.
///
/// The width and height in pixels may not be reliably implemented or default to 0.
/// For unix, https://man7.org/linux/man-pages/man4/tty_ioctl.4.html documents them as "unused".
/// For unix, <https://man7.org/linux/man-pages/man4/tty_ioctl.4.html> documents them as "unused".
/// For windows it is not implemented.
pub fn window_size() -> io::Result<WindowSize> {
sys::window_size()

View File

@ -144,7 +144,7 @@ pub(crate) fn disable_raw_mode() -> io::Result<()> {
/// Queries the terminal's support for progressive keyboard enhancement.
///
/// On unix systems, this function will block and possibly time out while
/// [`crossterm::event::read`](crate::event::read) or [`crossterm::event::poll`](crate::event::poll) are being called.
/// [`crossterm::event::read`](crate::event::read()) or [`crossterm::event::poll`](crate::event::poll) are being called.
#[cfg(feature = "events")]
pub fn supports_keyboard_enhancement() -> io::Result<bool> {
if is_raw_mode_enabled() {

View File

@ -48,6 +48,12 @@ fn ensure_offline() {
}
/// Ensure the system is safe.
///
/// # Examples
/// ```rust
/// # std::env::set_var("SHOOT_SELF_IN_FOOT", "1");
/// keyfork_entropy::ensure_safe();
/// ```
pub fn ensure_safe() {
if !std::env::vars()
.any(|(name, _)| name == "SHOOT_SELF_IN_FOOT" || name == "INSECURE_HARDWARE_ALLOWED")
@ -61,6 +67,16 @@ pub fn ensure_safe() {
///
/// # 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_size(64)?;
/// assert_eq!(entropy.len(), 64);
/// # Ok(())
/// # }
/// ```
pub fn generate_entropy_of_size(byte_count: usize) -> Result<Vec<u8>, std::io::Error> {
ensure_safe();
let mut vec = vec![0u8; byte_count];

View File

@ -1,5 +1,5 @@
//! SLIP-0010 test data for use by derivation tests.
//! Source: https://github.com/satoshilabs/slips/blob/master/slip-0010.md#test-vectors
//! Source: <https://github.com/satoshilabs/slips/blob/master/slip-0010.md#test-vectors>
use std::collections::HashMap;