356 lines
11 KiB
Rust
356 lines
11 KiB
Rust
// Because all algorithms make use of wildcard matching
|
|
#![allow(clippy::match_wildcard_for_single_variants)]
|
|
|
|
//! # Derivation Requests
|
|
//!
|
|
//! Derivation requests can be sent to Keyforkd using Keyforkd Client to request derivation from a
|
|
//! mnemonic or seed that has been loaded into Keyforkd.
|
|
//!
|
|
//! # Examples
|
|
//! ```rust
|
|
//! use std::str::FromStr;
|
|
//! use keyfork_derive_util::{DerivationPath, request::{DerivationRequest, DerivationAlgorithm}};
|
|
//!
|
|
//! let path = DerivationPath::from_str("m/44'/0'/0'/0/0").unwrap();
|
|
//! let request = DerivationRequest::new(
|
|
//! DerivationAlgorithm::Secp256k1,
|
|
//! &path
|
|
//! );
|
|
//! ```
|
|
|
|
use crate::{
|
|
extended_key::private_key::{Error as XPrvError, VariableLengthSeed},
|
|
private_key::{PrivateKey, TestPrivateKey},
|
|
DerivationPath, ExtendedPrivateKey,
|
|
};
|
|
|
|
use keyfork_mnemonic_util::{Mnemonic, MnemonicGenerationError};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// An error encountered while deriving a key.
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum DerivationError {
|
|
/// The algorithm requested was not supported. This may occur when a feature adding support for
|
|
/// an algorithm has not been enabled.
|
|
#[error("Algorithm not supported")]
|
|
Algorithm,
|
|
|
|
/// A seed was unable to be created from the mnemonic.
|
|
#[error("Unable to create seed from mnemonic: {0}")]
|
|
Mnemonic(#[from] MnemonicGenerationError),
|
|
|
|
/// Generating an [`ExtendedPrivateKey`] resulted in an error.
|
|
#[error("{0}")]
|
|
ExtendedPrivateKey(#[from] XPrvError),
|
|
}
|
|
|
|
#[allow(missing_docs)]
|
|
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, PartialEq, Eq)]
|
|
#[non_exhaustive]
|
|
pub enum DerivationAlgorithm {
|
|
#[allow(missing_docs)]
|
|
Ed25519,
|
|
#[allow(missing_docs)]
|
|
Secp256k1,
|
|
#[doc(hidden)]
|
|
Internal,
|
|
}
|
|
|
|
impl DerivationAlgorithm {
|
|
/// Given a mnemonic seed and a derivation path, derive an [`ExtendedPrivateKey`].
|
|
///
|
|
/// # Errors
|
|
/// The method may error if the derivation fails or if the algorithm is not supported.
|
|
fn derive(&self, seed: &[u8], path: &DerivationPath) -> Result<DerivationResponse> {
|
|
let seed = VariableLengthSeed::new(seed);
|
|
match self {
|
|
#[cfg(feature = "ed25519")]
|
|
Self::Ed25519 => {
|
|
let key = ExtendedPrivateKey::<ed25519_dalek::SigningKey>::new(seed);
|
|
let derived_key = key.derive_path(path)?;
|
|
Ok(DerivationResponse::with_algo_and_xprv(
|
|
self.clone(),
|
|
&derived_key,
|
|
))
|
|
}
|
|
#[cfg(feature = "secp256k1")]
|
|
Self::Secp256k1 => {
|
|
let key = ExtendedPrivateKey::<k256::SecretKey>::new(seed);
|
|
let derived_key = key.derive_path(path)?;
|
|
Ok(DerivationResponse::with_algo_and_xprv(
|
|
self.clone(),
|
|
&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),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::str::FromStr for DerivationAlgorithm {
|
|
type Err = DerivationError;
|
|
|
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
|
Ok(match s {
|
|
"ed25519" => Self::Ed25519,
|
|
"secp256k1" => Self::Secp256k1,
|
|
_ => return Err(DerivationError::Algorithm),
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Acquire the associated [`DerivationAlgorithm`] for a [`PrivateKey`].
|
|
pub trait AsAlgorithm: PrivateKey {
|
|
/// Return the appropriate [`DerivationAlgorithm`].
|
|
fn as_algorithm() -> DerivationAlgorithm;
|
|
}
|
|
|
|
impl AsAlgorithm for TestPrivateKey {
|
|
fn as_algorithm() -> DerivationAlgorithm {
|
|
DerivationAlgorithm::Internal
|
|
}
|
|
}
|
|
|
|
/// A derivation request.
|
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
|
pub struct DerivationRequest {
|
|
algorithm: DerivationAlgorithm,
|
|
path: DerivationPath,
|
|
}
|
|
|
|
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,
|
|
path: path.clone(),
|
|
}
|
|
}
|
|
|
|
/// 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
|
|
}
|
|
|
|
/// Derive an [`ExtendedPrivateKey`] using the seed from the given mnemonic.
|
|
///
|
|
/// # 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",
|
|
/// # )?;
|
|
/// 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.generate_seed(None))
|
|
}
|
|
|
|
/// Derive an [`ExtendedPrivateKey`] using the given seed.
|
|
///
|
|
/// # 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)?;
|
|
/// # Ok(())
|
|
/// # }
|
|
pub fn derive_with_master_seed(&self, seed: &[u8]) -> Result<DerivationResponse> {
|
|
self.algorithm.derive(seed, &self.path)
|
|
}
|
|
}
|
|
|
|
/// A response to a [`DerivationRequest`]
|
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
|
pub struct DerivationResponse {
|
|
/// The algorithm used to derive the data.
|
|
pub algorithm: DerivationAlgorithm,
|
|
|
|
/// The derived private key.
|
|
pub data: [u8; 32],
|
|
|
|
/// The chain code, used for further derivation.
|
|
pub chain_code: [u8; 32],
|
|
|
|
/// The depth, used for further derivation.
|
|
pub depth: u8,
|
|
}
|
|
|
|
impl DerivationResponse {
|
|
/// Create a [`DerivationResponse`] with the given values.
|
|
fn with_algo_and_xprv<T: PrivateKey + Clone>(
|
|
algorithm: DerivationAlgorithm,
|
|
xprv: &ExtendedPrivateKey<T>,
|
|
) -> Self {
|
|
Self {
|
|
algorithm,
|
|
data: PrivateKey::to_bytes(xprv.private_key()),
|
|
chain_code: xprv.chain_code(),
|
|
depth: xprv.depth(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An error when creating a [`DerivationResponse`].
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum TryFromDerivationResponseError {
|
|
/// The algorithm used to derive the data does not match the algorithm of the
|
|
/// [`ExtendedPrivateKey`] being created.
|
|
#[error("incorrect algorithm provided")]
|
|
Algorithm,
|
|
|
|
/// An error occurred while creating an [`ExtendedPrivateKey`] from the given response.
|
|
#[error("{0}")]
|
|
ExtendedPrivateKey(#[from] XPrvError),
|
|
}
|
|
|
|
#[cfg(feature = "secp256k1")]
|
|
mod secp256k1 {
|
|
use super::*;
|
|
use k256::SecretKey;
|
|
|
|
impl AsAlgorithm for SecretKey {
|
|
fn as_algorithm() -> DerivationAlgorithm {
|
|
DerivationAlgorithm::Secp256k1
|
|
}
|
|
}
|
|
|
|
impl TryFrom<&DerivationResponse> for ExtendedPrivateKey<SecretKey> {
|
|
type Error = TryFromDerivationResponseError;
|
|
|
|
fn try_from(value: &DerivationResponse) -> Result<Self, Self::Error> {
|
|
match value.algorithm {
|
|
DerivationAlgorithm::Secp256k1 => Ok(Self::new_from_parts(
|
|
&value.data,
|
|
value.depth,
|
|
value.chain_code,
|
|
)),
|
|
_ => Err(Self::Error::Algorithm),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<DerivationResponse> for ExtendedPrivateKey<SecretKey> {
|
|
type Error = TryFromDerivationResponseError;
|
|
|
|
fn try_from(value: DerivationResponse) -> Result<Self, Self::Error> {
|
|
ExtendedPrivateKey::<SecretKey>::try_from(&value)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "ed25519")]
|
|
mod ed25519 {
|
|
use super::*;
|
|
use ed25519_dalek::SigningKey;
|
|
|
|
impl AsAlgorithm for SigningKey {
|
|
fn as_algorithm() -> DerivationAlgorithm {
|
|
DerivationAlgorithm::Ed25519
|
|
}
|
|
}
|
|
|
|
impl TryFrom<&DerivationResponse> for ExtendedPrivateKey<SigningKey> {
|
|
type Error = TryFromDerivationResponseError;
|
|
|
|
fn try_from(value: &DerivationResponse) -> Result<Self, Self::Error> {
|
|
match value.algorithm {
|
|
DerivationAlgorithm::Ed25519 => Ok(Self::new_from_parts(
|
|
&value.data,
|
|
value.depth,
|
|
value.chain_code,
|
|
)),
|
|
_ => Err(Self::Error::Algorithm),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<DerivationResponse> for ExtendedPrivateKey<SigningKey> {
|
|
type Error = TryFromDerivationResponseError;
|
|
|
|
fn try_from(value: DerivationResponse) -> Result<Self, Self::Error> {
|
|
ExtendedPrivateKey::<SigningKey>::try_from(&value)
|
|
}
|
|
}
|
|
}
|