Compare commits
5 Commits
8510e382d2
...
0f4bc3c78d
Author | SHA1 | Date |
---|---|---|
Ryan Heywood | 0f4bc3c78d | |
Ryan Heywood | 76779ee91c | |
Ryan Heywood | 71261e1323 | |
Ryan Heywood | f60b77254a | |
Ryan Heywood | 72666011a4 |
|
@ -26,6 +26,12 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.69"
|
version = "0.3.69"
|
||||||
|
@ -109,6 +115,31 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "console"
|
name = "console"
|
||||||
version = "0.15.7"
|
version = "0.15.7"
|
||||||
|
@ -448,6 +479,17 @@ dependencies = [
|
||||||
"sha2",
|
"sha2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "keyfork-seed"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bincode",
|
||||||
|
"clap",
|
||||||
|
"keyfork-derive-util",
|
||||||
|
"keyfork-frame",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "keyforkd"
|
name = "keyforkd"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -855,18 +897,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.47"
|
version = "1.0.48"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
|
checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.47"
|
version = "1.0.48"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
|
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
@ -5,6 +5,7 @@ members = [
|
||||||
"keyfork-mnemonic-generate",
|
"keyfork-mnemonic-generate",
|
||||||
"keyfork-mnemonic-util",
|
"keyfork-mnemonic-util",
|
||||||
"keyfork-derive-util",
|
"keyfork-derive-util",
|
||||||
|
"keyfork-seed",
|
||||||
"keyforkd",
|
"keyforkd",
|
||||||
"keyfork-frame"
|
"keyfork-frame"
|
||||||
]
|
]
|
||||||
|
|
|
@ -26,6 +26,8 @@ thiserror = "1.0.47"
|
||||||
# Optional, not personally audited
|
# Optional, not personally audited
|
||||||
k256 = { version = "0.13.1", default-features = false, features = ["std", "arithmetic"], optional = true }
|
k256 = { version = "0.13.1", default-features = false, features = ["std", "arithmetic"], optional = true }
|
||||||
ed25519-dalek = { version = "2.0.0", optional = true }
|
ed25519-dalek = { version = "2.0.0", optional = true }
|
||||||
|
|
||||||
|
# Workspace
|
||||||
keyfork-mnemonic-util = { version = "0.1.0", path = "../keyfork-mnemonic-util" }
|
keyfork-mnemonic-util = { version = "0.1.0", path = "../keyfork-mnemonic-util" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -29,6 +29,10 @@ pub enum Error {
|
||||||
/// The algorithm used mandates hardened derivation only.
|
/// The algorithm used mandates hardened derivation only.
|
||||||
#[error("The algorithm used mandates hardened derivation only")]
|
#[error("The algorithm used mandates hardened derivation only")]
|
||||||
HardenedDerivationRequired,
|
HardenedDerivationRequired,
|
||||||
|
|
||||||
|
/// The given slice was of an inappropriate size to create a Private Key.
|
||||||
|
#[error("The given slice was of an inappropriate size to create a Private Key")]
|
||||||
|
InvalidSliceError(#[from] std::array::TryFromSliceError),
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result<T, E = Error> = std::result::Result<T, E>;
|
type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
@ -41,9 +45,9 @@ type HmacSha512 = Hmac<Sha512>;
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct ExtendedPrivateKey<K: PrivateKey + Clone> {
|
pub struct ExtendedPrivateKey<K: PrivateKey + Clone> {
|
||||||
/// The internal private key data.
|
/// The internal private key data.
|
||||||
pub private_key: K,
|
private_key: K,
|
||||||
depth: u8,
|
depth: u8,
|
||||||
pub(crate) chain_code: ChainCode,
|
chain_code: ChainCode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: PrivateKey + Clone> std::fmt::Debug for ExtendedPrivateKey<K> {
|
impl<K: PrivateKey + Clone> std::fmt::Debug for ExtendedPrivateKey<K> {
|
||||||
|
@ -88,18 +92,42 @@ where
|
||||||
.into_bytes();
|
.into_bytes();
|
||||||
let (private_key, chain_code) = hash.split_at(KEY_SIZE / 8);
|
let (private_key, chain_code) = hash.split_at(KEY_SIZE / 8);
|
||||||
|
|
||||||
|
Self::new_from_parts(
|
||||||
|
private_key,
|
||||||
|
0,
|
||||||
|
// Checked: chain_code is always the same length, hash is static size
|
||||||
|
chain_code.try_into().expect("Invalid chain code length"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_from_parts(seed: &[u8], depth: u8, chain_code: [u8; 32]) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
private_key: K::from_bytes(private_key.try_into().expect("Invalid key length")),
|
private_key: K::from_bytes(seed.try_into()?),
|
||||||
depth: 0,
|
depth,
|
||||||
chain_code: chain_code.try_into().expect("Invalid chain code length"),
|
chain_code,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the [`PrivateKey`].
|
||||||
|
pub fn private_key(&self) -> &K {
|
||||||
|
&self.private_key
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a public key for the current [`PrivateKey`].
|
/// Return a public key for the current [`PrivateKey`].
|
||||||
pub fn public_key(&self) -> K::PublicKey {
|
pub fn public_key(&self) -> K::PublicKey {
|
||||||
self.private_key.public_key()
|
self.private_key.public_key()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the current depth.
|
||||||
|
pub fn depth(&self) -> u8 {
|
||||||
|
self.depth
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a copy of the current chain code.
|
||||||
|
pub fn chain_code(&self) -> [u8; 32] {
|
||||||
|
self.chain_code
|
||||||
|
}
|
||||||
|
|
||||||
/// Derive a child using the given [`DerivationPath`].
|
/// Derive a child using the given [`DerivationPath`].
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
|
|
|
@ -34,6 +34,14 @@ impl DerivationPath {
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &DerivationIndex> {
|
pub fn iter(&self) -> impl Iterator<Item = &DerivationIndex> {
|
||||||
self.path.iter()
|
self.path.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.path.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.path.is_empty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::str::FromStr for DerivationPath {
|
impl std::str::FromStr for DerivationPath {
|
||||||
|
|
|
@ -28,19 +28,19 @@ impl DerivationAlgorithm {
|
||||||
Self::Ed25519 => {
|
Self::Ed25519 => {
|
||||||
let key = ExtendedPrivateKey::<ed25519_dalek::SigningKey>::new(seed)?;
|
let key = ExtendedPrivateKey::<ed25519_dalek::SigningKey>::new(seed)?;
|
||||||
let derived_key = key.derive_path(path)?;
|
let derived_key = key.derive_path(path)?;
|
||||||
Ok(DerivationResponse {
|
Ok(DerivationResponse::with_algo_and_xprv(
|
||||||
algorithm: self.clone(),
|
self.clone(),
|
||||||
data: PrivateKey::to_bytes(&derived_key.private_key).to_vec(),
|
&derived_key,
|
||||||
})
|
))
|
||||||
}
|
}
|
||||||
#[cfg(feature = "secp256k1")]
|
#[cfg(feature = "secp256k1")]
|
||||||
Self::Secp256k1 => {
|
Self::Secp256k1 => {
|
||||||
let key = ExtendedPrivateKey::<k256::SecretKey>::new(seed)?;
|
let key = ExtendedPrivateKey::<k256::SecretKey>::new(seed)?;
|
||||||
let derived_key = key.derive_path(path)?;
|
let derived_key = key.derive_path(path)?;
|
||||||
Ok(DerivationResponse {
|
Ok(DerivationResponse::with_algo_and_xprv(
|
||||||
algorithm: self.clone(),
|
self.clone(),
|
||||||
data: PrivateKey::to_bytes(&derived_key.private_key).to_vec(),
|
&derived_key,
|
||||||
})
|
))
|
||||||
}
|
}
|
||||||
#[allow(unreachable_patterns)]
|
#[allow(unreachable_patterns)]
|
||||||
_ => Err(DerivationError::Algorithm),
|
_ => Err(DerivationError::Algorithm),
|
||||||
|
@ -48,6 +48,18 @@ impl DerivationAlgorithm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct DerivationRequest {
|
pub struct DerivationRequest {
|
||||||
algorithm: DerivationAlgorithm,
|
algorithm: DerivationAlgorithm,
|
||||||
|
@ -59,6 +71,10 @@ impl DerivationRequest {
|
||||||
Self { algorithm, path }
|
Self { algorithm, path }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> &DerivationPath {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
|
||||||
pub fn derive_with_mnemonic(&self, mnemonic: &Mnemonic) -> Result<DerivationResponse> {
|
pub fn derive_with_mnemonic(&self, mnemonic: &Mnemonic) -> Result<DerivationResponse> {
|
||||||
self.derive_with_master_seed(mnemonic.seed())
|
self.derive_with_master_seed(mnemonic.seed())
|
||||||
}
|
}
|
||||||
|
@ -72,4 +88,20 @@ impl DerivationRequest {
|
||||||
pub struct DerivationResponse {
|
pub struct DerivationResponse {
|
||||||
pub algorithm: DerivationAlgorithm,
|
pub algorithm: DerivationAlgorithm,
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
|
pub chain_code: [u8; 32],
|
||||||
|
pub depth: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerivationResponse {
|
||||||
|
pub fn with_algo_and_xprv<T: PrivateKey + Clone>(
|
||||||
|
algorithm: DerivationAlgorithm,
|
||||||
|
xprv: &ExtendedPrivateKey<T>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
algorithm,
|
||||||
|
data: PrivateKey::to_bytes(xprv.private_key()).to_vec(),
|
||||||
|
chain_code: xprv.chain_code(),
|
||||||
|
depth: xprv.depth(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,8 +62,8 @@ fn secp256k1() {
|
||||||
for (seed, chain, chain_code, private_key, public_key) in tests {
|
for (seed, chain, chain_code, private_key, public_key) in tests {
|
||||||
let xkey = ExtendedPrivateKey::<SecretKey>::new(seed).unwrap();
|
let xkey = ExtendedPrivateKey::<SecretKey>::new(seed).unwrap();
|
||||||
let derived_key = xkey.derive_path(&chain).unwrap();
|
let derived_key = xkey.derive_path(&chain).unwrap();
|
||||||
assert_eq!(derived_key.chain_code, chain_code);
|
assert_eq!(derived_key.chain_code(), chain_code);
|
||||||
assert_eq!(derived_key.private_key.to_bytes().as_slice(), private_key);
|
assert_eq!(derived_key.private_key().to_bytes().as_slice(), private_key);
|
||||||
assert_eq!(derived_key.public_key().to_bytes(), public_key);
|
assert_eq!(derived_key.public_key().to_bytes(), public_key);
|
||||||
let request = DerivationRequest::new(DerivationAlgorithm::Secp256k1, chain);
|
let request = DerivationRequest::new(DerivationAlgorithm::Secp256k1, chain);
|
||||||
let response = request.derive_with_master_seed(seed.to_vec()).unwrap();
|
let response = request.derive_with_master_seed(seed.to_vec()).unwrap();
|
||||||
|
@ -103,8 +103,8 @@ fn ed25519() {
|
||||||
for (seed, chain, chain_code, private_key, public_key) in tests {
|
for (seed, chain, chain_code, private_key, public_key) in tests {
|
||||||
let xkey = ExtendedPrivateKey::<SigningKey>::new(seed).unwrap();
|
let xkey = ExtendedPrivateKey::<SigningKey>::new(seed).unwrap();
|
||||||
let derived_key = xkey.derive_path(&chain).unwrap();
|
let derived_key = xkey.derive_path(&chain).unwrap();
|
||||||
assert_eq!(derived_key.chain_code, chain_code);
|
assert_eq!(derived_key.chain_code(), chain_code);
|
||||||
assert_eq!(PrivateKey::to_bytes(&derived_key.private_key), private_key);
|
assert_eq!(PrivateKey::to_bytes(derived_key.private_key()), private_key);
|
||||||
assert_eq!(PublicKey::to_bytes(&derived_key.public_key()), public_key);
|
assert_eq!(PublicKey::to_bytes(&derived_key.public_key()), public_key);
|
||||||
let request = DerivationRequest::new(DerivationAlgorithm::Ed25519, chain);
|
let request = DerivationRequest::new(DerivationAlgorithm::Ed25519, chain);
|
||||||
let response = request.derive_with_master_seed(seed.to_vec()).unwrap();
|
let response = request.derive_with_master_seed(seed.to_vec()).unwrap();
|
||||||
|
|
|
@ -15,7 +15,8 @@ pub async fn try_decode_from(
|
||||||
) -> Result<Vec<u8>, DecodeError> {
|
) -> Result<Vec<u8>, DecodeError> {
|
||||||
let len = readable.read_u32().await?;
|
let len = readable.read_u32().await?;
|
||||||
|
|
||||||
let mut data = Vec::with_capacity(len as usize);
|
// Note: Pre-filling the vec is *required* as read_exact uses len, not capacity.
|
||||||
|
let mut data = vec![0u8; len as usize];
|
||||||
readable.read_exact(&mut data[..]).await?;
|
readable.read_exact(&mut data[..]).await?;
|
||||||
|
|
||||||
let content = verify_checksum(&data[..])?;
|
let content = verify_checksum(&data[..])?;
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
//! | checksum: [u8; 32] sha256 hash of `raw_data` | raw_data: &[u8] |
|
//! | checksum: [u8; 32] sha256 hash of `raw_data` | raw_data: &[u8] |
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
pub mod asyncext;
|
pub mod asyncext;
|
||||||
|
|
||||||
|
@ -51,8 +53,6 @@ pub enum EncodeError {
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
const LEN_SIZE: usize = std::mem::size_of::<u32>();
|
|
||||||
|
|
||||||
pub(crate) fn hash(data: &[u8]) -> Vec<u8> {
|
pub(crate) fn hash(data: &[u8]) -> Vec<u8> {
|
||||||
let mut hashobj = Sha256::new();
|
let mut hashobj = Sha256::new();
|
||||||
hashobj.update(data);
|
hashobj.update(data);
|
||||||
|
@ -65,14 +65,19 @@ pub(crate) fn hash(data: &[u8]) -> Vec<u8> {
|
||||||
/// 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.
|
||||||
pub fn try_encode(data: &[u8]) -> Result<Vec<u8>, EncodeError> {
|
pub fn try_encode(data: &[u8]) -> Result<Vec<u8>, EncodeError> {
|
||||||
|
let mut output = vec![];
|
||||||
|
try_encode_to(data, &mut output)?;
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_encode_to(data: &[u8], writable: &mut impl Write) -> Result<(), EncodeError> {
|
||||||
let hash = hash(data);
|
let hash = hash(data);
|
||||||
let content = hash.iter().chain(data.iter()).copied().collect::<Vec<_>>();
|
let len = hash.len() + data.len();
|
||||||
let mut result = (u32::try_from(content.len())
|
let len = u32::try_from(len).map_err(|_| EncodeError::InputTooLarge(len))?;
|
||||||
.map_err(|_| EncodeError::InputTooLarge(content.len()))?)
|
writable.write_all(&len.to_be_bytes())?;
|
||||||
.to_be_bytes()
|
writable.write_all(&hash)?;
|
||||||
.to_vec();
|
writable.write_all(data)?;
|
||||||
result.extend(content);
|
Ok(())
|
||||||
Ok(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn verify_checksum(data: &[u8]) -> Result<&[u8], DecodeError> {
|
pub(crate) fn verify_checksum(data: &[u8]) -> Result<&[u8], DecodeError> {
|
||||||
|
@ -99,18 +104,16 @@ pub(crate) fn verify_checksum(data: &[u8]) -> Result<&[u8], DecodeError> {
|
||||||
/// * 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.
|
||||||
pub fn try_decode(data: &[u8]) -> Result<Vec<u8>, DecodeError> {
|
pub fn try_decode(data: &[u8]) -> Result<Vec<u8>, DecodeError> {
|
||||||
// check length and advance data pointer beyond length check
|
try_decode_from(&mut &data[..])
|
||||||
let len_bytes: [u8; LEN_SIZE] = data[..LEN_SIZE]
|
|
||||||
.try_into()
|
|
||||||
.map_err(DecodeError::InvalidLength)?;
|
|
||||||
let len = u32::from_be_bytes(len_bytes);
|
|
||||||
if len as usize + LEN_SIZE > data.len() {
|
|
||||||
return Err(DecodeError::IncorrectLength(data.len() - LEN_SIZE, len));
|
|
||||||
}
|
}
|
||||||
let data = &data[LEN_SIZE..];
|
|
||||||
|
|
||||||
let content = verify_checksum(data)?;
|
|
||||||
|
|
||||||
|
pub fn try_decode_from(readable: &mut impl Read) -> Result<Vec<u8>, DecodeError> {
|
||||||
|
let mut bytes = 0u32.to_be_bytes();
|
||||||
|
readable.read_exact(&mut bytes)?;
|
||||||
|
let len = u32::from_be_bytes(bytes);
|
||||||
|
let mut data = vec![0u8; len as usize];
|
||||||
|
readable.read_exact(&mut data)?;
|
||||||
|
let content = verify_checksum(&data)?;
|
||||||
Ok(content.to_vec())
|
Ok(content.to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "keyfork-seed"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bincode = { version = "1.3.3", default-features = false }
|
||||||
|
clap = { version = "4.4.2", default-features = false, features = ["std", "usage", "help"] }
|
||||||
|
keyfork-derive-util = { version = "0.1.0", path = "../keyfork-derive-util" }
|
||||||
|
keyfork-frame = { version = "0.1.0", path = "../keyfork-frame", default-features = false }
|
||||||
|
thiserror = "1.0.48"
|
|
@ -0,0 +1,19 @@
|
||||||
|
use clap::{arg, value_parser, ArgMatches, Command};
|
||||||
|
use keyfork_derive_util::{request::*, DerivationPath};
|
||||||
|
|
||||||
|
pub fn get_args() -> ArgMatches {
|
||||||
|
Command::new("keyfork-seed")
|
||||||
|
.arg(
|
||||||
|
arg!(--path <PATH>)
|
||||||
|
.required(true)
|
||||||
|
.help("string value of a DerivationPath")
|
||||||
|
.value_parser(value_parser!(DerivationPath)),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg!(--algorithm <ALGO>)
|
||||||
|
.required(true)
|
||||||
|
.help("string value of a DerivationAlgorithm")
|
||||||
|
.value_parser(value_parser!(DerivationAlgorithm)),
|
||||||
|
)
|
||||||
|
.get_matches()
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
use keyfork_derive_util::{request::*, DerivationPath};
|
||||||
|
use keyfork_frame::*;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
mod cli;
|
||||||
|
mod socket;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("The first argument to the command should be a derivation path")]
|
||||||
|
Args,
|
||||||
|
|
||||||
|
#[error("The given path was incorrectly formatted: {0}")]
|
||||||
|
ArgsFormat(#[from] keyfork_derive_util::path::Error),
|
||||||
|
|
||||||
|
#[error("Neither KEYFORK_SOCKET_PATH nor XDG_RUNTIME_DIR were set")]
|
||||||
|
EnvVarsNotFound,
|
||||||
|
|
||||||
|
#[error("Socket was unable to connect to {1}: {0}")]
|
||||||
|
Connect(std::io::Error, PathBuf),
|
||||||
|
|
||||||
|
#[error("Could not write to or from the socket: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
|
||||||
|
#[error("Could not perform bincode transformation: {0}")]
|
||||||
|
Bincode(#[from] Box<bincode::ErrorKind>),
|
||||||
|
|
||||||
|
#[error("Could not perform frame transformation: {0}")]
|
||||||
|
FrameEnc(#[from] EncodeError),
|
||||||
|
|
||||||
|
#[error("Could not perform frame transformation: {0}")]
|
||||||
|
FrameDec(#[from] DecodeError),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Error> {
|
||||||
|
let args = cli::get_args();
|
||||||
|
let mut socket = socket::get_socket()?;
|
||||||
|
let path = args.get_one::<DerivationPath>("path").expect("required");
|
||||||
|
let algo = args
|
||||||
|
.get_one::<DerivationAlgorithm>("algorithm")
|
||||||
|
.expect("required");
|
||||||
|
let req = DerivationRequest::new(algo.clone(), path.clone());
|
||||||
|
let ser_req = bincode::serialize(&req)?;
|
||||||
|
try_encode_to(&ser_req, &mut socket)?;
|
||||||
|
let ser_response = try_decode_from(&mut socket)?;
|
||||||
|
let response: DerivationResponse = bincode::deserialize(&ser_response)?;
|
||||||
|
dbg!(&response);
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
use super::Error;
|
||||||
|
use std::{collections::HashMap, os::unix::net::UnixStream, path::PathBuf};
|
||||||
|
|
||||||
|
pub fn get_socket() -> Result<UnixStream, Error> {
|
||||||
|
let socket_vars = std::env::vars()
|
||||||
|
.filter(|(key, _)| ["XDG_RUNTIME_DIR", "KEYFORKD_SOCKET_PATH"].contains(&key.as_str()))
|
||||||
|
.collect::<HashMap<String, String>>();
|
||||||
|
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))
|
||||||
|
}
|
|
@ -103,7 +103,7 @@ mod tests {
|
||||||
impl Test {
|
impl Test {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
field: "hello world!".to_string()
|
field: "hello world!".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,8 +135,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn can_serde_responses() {
|
async fn can_serde_responses() {
|
||||||
let content = serialize(&Test::new())
|
let content = serialize(&Test::new()).unwrap();
|
||||||
.unwrap();
|
|
||||||
let mut service = ServiceBuilder::new()
|
let mut service = ServiceBuilder::new()
|
||||||
.layer(BincodeLayer::<Test>::new())
|
.layer(BincodeLayer::<Test>::new())
|
||||||
.service(App);
|
.service(App);
|
||||||
|
|
|
@ -7,6 +7,15 @@ use tower::Service;
|
||||||
// NOTE: All values implemented in Keyforkd must implement Clone with low overhead, either by
|
// NOTE: All values implemented in Keyforkd must implement Clone with low overhead, either by
|
||||||
// using an Arc or by having a small signature. This is because Service<T> takes &mut self.
|
// using an Arc or by having a small signature. This is because Service<T> takes &mut self.
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum KeyforkdRequestError {
|
||||||
|
#[error("Invalid derivation length: Expected: 2, actual: {0}")]
|
||||||
|
InvalidDerivationLength(usize),
|
||||||
|
|
||||||
|
#[error("Derivation error: {0}")]
|
||||||
|
Derivation(#[from] DerivationError),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Keyforkd {
|
pub struct Keyforkd {
|
||||||
mnemonic: Arc<Mnemonic>,
|
mnemonic: Arc<Mnemonic>,
|
||||||
|
@ -23,7 +32,7 @@ impl Keyforkd {
|
||||||
impl Service<DerivationRequest> for Keyforkd {
|
impl Service<DerivationRequest> for Keyforkd {
|
||||||
type Response = DerivationResponse;
|
type Response = DerivationResponse;
|
||||||
|
|
||||||
type Error = DerivationError;
|
type Error = KeyforkdRequestError;
|
||||||
|
|
||||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
||||||
|
|
||||||
|
@ -37,7 +46,14 @@ impl Service<DerivationRequest> for Keyforkd {
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
|
||||||
fn call(&mut self, req: DerivationRequest) -> Self::Future {
|
fn call(&mut self, req: DerivationRequest) -> Self::Future {
|
||||||
let mnemonic = self.mnemonic.clone();
|
let mnemonic = self.mnemonic.clone();
|
||||||
Box::pin(async { req.derive_with_mnemonic(&mnemonic) })
|
Box::pin(async {
|
||||||
|
let len = req.path().len();
|
||||||
|
if len < 2 {
|
||||||
|
return Err(KeyforkdRequestError::InvalidDerivationLength(len));
|
||||||
|
}
|
||||||
|
req.derive_with_mnemonic(&mnemonic)
|
||||||
|
.map_err(KeyforkdRequestError::from)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +70,9 @@ mod tests {
|
||||||
async fn properly_derives_data() {
|
async fn properly_derives_data() {
|
||||||
// Pulled from keyfork-derive-util's tests, which is more extensively tested.
|
// Pulled from keyfork-derive-util's tests, which is more extensively tested.
|
||||||
let tests = [
|
let tests = [
|
||||||
|
/*
|
||||||
|
* Note: Tests excluded because the derivation path is not deep enough
|
||||||
|
* for the API's preferences.
|
||||||
(
|
(
|
||||||
&hex!("000102030405060708090a0b0c0d0e0f")[..],
|
&hex!("000102030405060708090a0b0c0d0e0f")[..],
|
||||||
DerivationPath::from_str("m").unwrap(),
|
DerivationPath::from_str("m").unwrap(),
|
||||||
|
@ -68,6 +87,7 @@ mod tests {
|
||||||
hex!("68e0fe46dfb67e368c75379acec591dad19df3cde26e63b93a8e704f1dade7a3"),
|
hex!("68e0fe46dfb67e368c75379acec591dad19df3cde26e63b93a8e704f1dade7a3"),
|
||||||
hex!("008c8a13df77a28f3445213a0f432fde644acaa215fc72dcdf300d5efaa85d350c"),
|
hex!("008c8a13df77a28f3445213a0f432fde644acaa215fc72dcdf300d5efaa85d350c"),
|
||||||
),
|
),
|
||||||
|
*/
|
||||||
(
|
(
|
||||||
&hex!("000102030405060708090a0b0c0d0e0f")[..],
|
&hex!("000102030405060708090a0b0c0d0e0f")[..],
|
||||||
DerivationPath::from_str("m/0'/1'/2'/2'/1000000000'").unwrap(),
|
DerivationPath::from_str("m/0'/1'/2'/2'/1000000000'").unwrap(),
|
||||||
|
@ -86,4 +106,46 @@ mod tests {
|
||||||
assert_eq!(response.data, private_key)
|
assert_eq!(response.data, private_key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[should_panic]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn errors_on_no_path() {
|
||||||
|
let tests = [(
|
||||||
|
&hex!("000102030405060708090a0b0c0d0e0f")[..],
|
||||||
|
DerivationPath::from_str("m").unwrap(),
|
||||||
|
hex!("90046a93de5380a72b5e45010748567d5ea02bbf6522f979e05c0d8d8ca9fffb"),
|
||||||
|
hex!("2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7"),
|
||||||
|
hex!("00a4b2856bfec510abab89753fac1ac0e1112364e7d250545963f135f2a33188ed"),
|
||||||
|
)];
|
||||||
|
let wordlist = Wordlist::default().arc();
|
||||||
|
for (seed, path, _, private_key, _) in tests {
|
||||||
|
let mnemonic = Mnemonic::from_entropy(&seed[..], wordlist.clone()).unwrap();
|
||||||
|
assert_eq!(mnemonic.seed(), seed);
|
||||||
|
let req = DerivationRequest::new(DerivationAlgorithm::Ed25519, path);
|
||||||
|
let mut keyforkd = Keyforkd::new(mnemonic);
|
||||||
|
let response = keyforkd.ready().await.unwrap().call(req).await.unwrap();
|
||||||
|
assert_eq!(response.data, private_key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[should_panic]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn errors_on_short_path() {
|
||||||
|
let tests = [(
|
||||||
|
&hex!("000102030405060708090a0b0c0d0e0f")[..],
|
||||||
|
DerivationPath::from_str("m/0'").unwrap(),
|
||||||
|
hex!("8b59aa11380b624e81507a27fedda59fea6d0b779a778918a2fd3590e16e9c69"),
|
||||||
|
hex!("68e0fe46dfb67e368c75379acec591dad19df3cde26e63b93a8e704f1dade7a3"),
|
||||||
|
hex!("008c8a13df77a28f3445213a0f432fde644acaa215fc72dcdf300d5efaa85d350c"),
|
||||||
|
)];
|
||||||
|
let wordlist = Wordlist::default().arc();
|
||||||
|
for (seed, path, _, private_key, _) in tests {
|
||||||
|
let mnemonic = Mnemonic::from_entropy(&seed[..], wordlist.clone()).unwrap();
|
||||||
|
assert_eq!(mnemonic.seed(), seed);
|
||||||
|
let req = DerivationRequest::new(DerivationAlgorithm::Ed25519, path);
|
||||||
|
let mut keyforkd = Keyforkd::new(mnemonic);
|
||||||
|
let response = keyforkd.ready().await.unwrap().call(req).await.unwrap();
|
||||||
|
assert_eq!(response.data, private_key)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue