keyfork-derive-util: add ed25519 support

This commit is contained in:
Ryan Heywood 2023-09-06 10:21:47 -05:00
parent 69b4aa9a18
commit 1a13acdfe3
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
7 changed files with 203 additions and 7 deletions

79
Cargo.lock generated
View File

@ -158,6 +158,34 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "curve25519-dalek"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622178105f911d937a42cdb140730ba4a3ed2becd8ae6ce39c7d28b5d75d4588"
dependencies = [
"cfg-if",
"cpufeatures",
"curve25519-dalek-derive",
"digest",
"fiat-crypto",
"platforms",
"rustc_version",
"subtle",
"zeroize",
]
[[package]]
name = "curve25519-dalek-derive"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "der" name = "der"
version = "0.7.8" version = "0.7.8"
@ -191,6 +219,29 @@ dependencies = [
"spki", "spki",
] ]
[[package]]
name = "ed25519"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d"
dependencies = [
"pkcs8",
"signature",
]
[[package]]
name = "ed25519-dalek"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980"
dependencies = [
"curve25519-dalek",
"ed25519",
"serde",
"sha2",
"zeroize",
]
[[package]] [[package]]
name = "elliptic-curve" name = "elliptic-curve"
version = "0.13.5" version = "0.13.5"
@ -226,6 +277,12 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "fiat-crypto"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0870c84016d4b481be5c9f323c24f65e31e901ae618f0e80f4308fb00de1d2d"
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.28" version = "0.3.28"
@ -352,6 +409,7 @@ name = "keyfork-derive-util"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"digest", "digest",
"ed25519-dalek",
"hex-literal", "hex-literal",
"hmac", "hmac",
"k256", "k256",
@ -548,6 +606,12 @@ dependencies = [
"spki", "spki",
] ]
[[package]]
name = "platforms"
version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4503fa043bf02cee09a9582e9554b4c6403b2ef55e4612e96561d294419429f8"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.66" version = "1.0.66"
@ -634,6 +698,15 @@ version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.15" version = "1.0.15"
@ -654,6 +727,12 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "semver"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.186" version = "1.0.186"

View File

@ -6,8 +6,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]
default = ["secp256k1"] default = ["secp256k1", "ed25519"]
secp256k1 = ["k256"] secp256k1 = ["k256"]
ed25519 = ["ed25519-dalek"]
[dependencies] [dependencies]
# Included in Rust # Included in Rust
@ -24,6 +25,7 @@ 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 }
[dev-dependencies] [dev-dependencies]
hex-literal = "0.4.1" hex-literal = "0.4.1"

View File

@ -25,6 +25,10 @@ pub enum Error {
/// An unknown error occurred while deriving a child key. /// An unknown error occurred while deriving a child key.
#[error("Unknown error while deriving child key")] #[error("Unknown error while deriving child key")]
Derivation, Derivation,
/// The algorithm used mandates hardened derivation only.
#[error("The algorithm used mandates hardened derivation only")]
HardenedDerivationRequired,
} }
type Result<T, E = Error> = std::result::Result<T, E>; type Result<T, E = Error> = std::result::Result<T, E>;
@ -132,8 +136,10 @@ where
if index.is_hardened() { if index.is_hardened() {
hmac.update(&[0]); hmac.update(&[0]);
hmac.update(&self.private_key.to_bytes()); hmac.update(&self.private_key.to_bytes());
} else { } else if !K::requires_hardened_derivation() {
hmac.update(&self.private_key.public_key().to_bytes()); hmac.update(&self.private_key.public_key().to_bytes());
} else {
return Err(Error::HardenedDerivationRequired);
} }
hmac.update(&index.to_bytes()); hmac.update(&index.to_bytes());
let result = hmac.finalize().into_bytes(); let result = hmac.finalize().into_bytes();

View File

@ -48,7 +48,7 @@ impl std::str::FromStr for DerivationPath {
path: iter path: iter
.map(DerivationIndex::from_str) .map(DerivationIndex::from_str)
.map(|maybe_err| maybe_err.map_err(From::from)) .map(|maybe_err| maybe_err.map_err(From::from))
.collect::<Result<Vec<DerivationIndex>>>()?, .collect::<Result<_>>()?,
}) })
} }
} }

View File

@ -18,10 +18,13 @@ pub trait PrivateKey: Sized {
/// Convert a &Self to bytes. /// Convert a &Self to bytes.
fn to_bytes(&self) -> PrivateKeyBytes; fn to_bytes(&self) -> PrivateKeyBytes;
/*
* Freak of nature, unsupported?
/// Whether or not zero is a valid public key (such as with ed25519 keys). /// Whether or not zero is a valid public key (such as with ed25519 keys).
fn is_zero_valid_public_key() -> bool { fn is_zero_valid_public_key() -> bool {
false false
} }
*/
/// The initial key for BIP-0032 and SLIP-0010 derivation, such as secp256k1's "Bitcoin seed". /// The initial key for BIP-0032 and SLIP-0010 derivation, such as secp256k1's "Bitcoin seed".
fn key() -> &'static str; fn key() -> &'static str;
@ -37,6 +40,12 @@ pub trait PrivateKey: Sized {
/// * A nonzero `other` is provided. /// * A nonzero `other` is provided.
/// * An error specific to the given algorithm was encountered. /// * An error specific to the given algorithm was encountered.
fn derive_child(&self, other: &PrivateKeyBytes) -> Result<Self, Self::Err>; fn derive_child(&self, other: &PrivateKeyBytes) -> Result<Self, Self::Err>;
/// Whether the algorithm requires hardened derivation, such as for ed25519.
fn requires_hardened_derivation() -> bool {
false
}
} }
/// Errors associated with creating and arithmetic on private keys. This specific error is only /// Errors associated with creating and arithmetic on private keys. This specific error is only
@ -73,6 +82,10 @@ impl PrivateKey for k256::SecretKey {
self.to_bytes().into() self.to_bytes().into()
} }
fn public_key(&self) -> Self::PublicKey {
self.public_key()
}
fn derive_child(&self, other: &PrivateKeyBytes) -> Result<Self, Self::Err> { fn derive_child(&self, other: &PrivateKeyBytes) -> Result<Self, Self::Err> {
if other.iter().all(|n| n == &0) { if other.iter().all(|n| n == &0) {
return Err(PrivateKeyError::NonZero); return Err(PrivateKeyError::NonZero);
@ -89,8 +102,35 @@ impl PrivateKey for k256::SecretKey {
.expect("Should be able to make Key"), .expect("Should be able to make Key"),
) )
} }
}
#[cfg(feature = "ed25519")]
impl PrivateKey for ed25519_dalek::SigningKey {
type Err = PrivateKeyError;
type PublicKey = ed25519_dalek::VerifyingKey;
fn key() -> &'static str {
"ed25519 seed"
}
fn from_bytes(b: &PrivateKeyBytes) -> Self {
Self::from_bytes(b)
}
fn to_bytes(&self) -> PrivateKeyBytes {
self.to_bytes()
}
fn public_key(&self) -> Self::PublicKey { fn public_key(&self) -> Self::PublicKey {
self.public_key() self.verifying_key()
}
fn derive_child(&self, other: &PrivateKeyBytes) -> Result<Self, Self::Err> {
// SLIP-0010: No arithmetic required for ed25519 keys.
Ok(Self::from_bytes(other))
}
fn requires_hardened_derivation() -> bool {
true
} }
} }

View File

@ -12,10 +12,13 @@ pub trait PublicKey: Sized {
/// The error returned by [`PublicKey::derive_child()`]. /// The error returned by [`PublicKey::derive_child()`].
type Err: std::error::Error; type Err: std::error::Error;
/*
* This may not be doable given ed25519 public keys must be derived from the private key.
/// Create a Self from bytes. /// Create a Self from bytes.
fn from_bytes(b: &PublicKeyBytes) -> Self; fn from_bytes(b: &PublicKeyBytes) -> Self;
*/
/// Convert a &Self to bytse. /// Convert a &Self to bytes.
fn to_bytes(&self) -> PublicKeyBytes; fn to_bytes(&self) -> PublicKeyBytes;
/// Derive a child [`PublicKey`] with given `PrivateKeyBytes`. /// Derive a child [`PublicKey`] with given `PrivateKeyBytes`.
@ -47,6 +50,10 @@ pub enum PublicKeyError {
/// Unable to convert a point to a key. /// Unable to convert a point to a key.
#[error("Unable to convert point to key")] #[error("Unable to convert point to key")]
PointToKey(#[from] k256::elliptic_curve::Error), PointToKey(#[from] k256::elliptic_curve::Error),
/// Public key derivation is unsupported for this algorithm.
#[error("Public key derivation is unsupported for this algorithm")]
DerivationUnsupported,
} }
#[cfg(feature = "secp256k1")] #[cfg(feature = "secp256k1")]
@ -59,9 +66,11 @@ use k256::{
impl PublicKey for k256::PublicKey { impl PublicKey for k256::PublicKey {
type Err = PublicKeyError; type Err = PublicKeyError;
/*
fn from_bytes(b: &PublicKeyBytes) -> Self { fn from_bytes(b: &PublicKeyBytes) -> Self {
Self::from_sec1_bytes(b).expect("Invalid public key bytes") Self::from_sec1_bytes(b).expect("Invalid public key bytes")
} }
*/
fn to_bytes(&self) -> PublicKeyBytes { fn to_bytes(&self) -> PublicKeyBytes {
// Note: Safety assured by type returned from EncodedPoint // Note: Safety assured by type returned from EncodedPoint
@ -80,3 +89,27 @@ impl PublicKey for k256::PublicKey {
Self::from_affine(point.into()).map_err(From::from) Self::from_affine(point.into()).map_err(From::from)
} }
} }
#[cfg(feature = "ed25519")]
use ed25519_dalek::VerifyingKey;
#[cfg(feature = "ed25519")]
impl PublicKey for VerifyingKey {
type Err = PublicKeyError;
/*
fn from_bytes(b: &PublicKeyBytes) -> Self {
Self::from_bytes(b).expect("Invalid public key bytes")
}
*/
fn to_bytes(&self) -> PublicKeyBytes {
let mut result = [0u8; 33];
result[1..33].copy_from_slice(&self.to_bytes()[..]);
result
}
fn derive_child(&self, _other: PrivateKeyBytes) -> Result<Self, Self::Err> {
Err(Self::Err::DerivationUnsupported)
}
}

View File

@ -1,12 +1,14 @@
use crate::*; use crate::*;
use hex_literal::hex; use hex_literal::hex;
use k256::SecretKey;
use std::str::FromStr; use std::str::FromStr;
// Pulled from: https://github.com/satoshilabs/slips/blob/master/slip-0010.md // Pulled from: https://github.com/satoshilabs/slips/blob/master/slip-0010.md
#[cfg(feature = "secp256k1")]
#[test] #[test]
fn example() { fn secp256k1() {
use k256::SecretKey;
// seed, chain, chain code, private, public // seed, chain, chain code, private, public
let tests = [( let tests = [(
&hex!("000102030405060708090a0b0c0d0e0f")[..], &hex!("000102030405060708090a0b0c0d0e0f")[..],
@ -65,3 +67,37 @@ fn example() {
assert_eq!(derived_key.public_key().to_bytes(), public_key); assert_eq!(derived_key.public_key().to_bytes(), public_key);
} }
} }
#[cfg(feature = "ed25519")]
#[test]
fn ed25519() {
use ed25519_dalek::SigningKey;
// seed, chain, chain code, private, public
let tests = [(
&hex!("000102030405060708090a0b0c0d0e0f")[..],
DerivationPath::from_str("m").unwrap(),
hex!("90046a93de5380a72b5e45010748567d5ea02bbf6522f979e05c0d8d8ca9fffb"),
hex!("2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7"),
hex!("00a4b2856bfec510abab89753fac1ac0e1112364e7d250545963f135f2a33188ed"),
), (
&hex!("000102030405060708090a0b0c0d0e0f")[..],
DerivationPath::from_str("m/0'").unwrap(),
hex!("8b59aa11380b624e81507a27fedda59fea6d0b779a778918a2fd3590e16e9c69"),
hex!("68e0fe46dfb67e368c75379acec591dad19df3cde26e63b93a8e704f1dade7a3"),
hex!("008c8a13df77a28f3445213a0f432fde644acaa215fc72dcdf300d5efaa85d350c"),
), (
&hex!("000102030405060708090a0b0c0d0e0f")[..],
DerivationPath::from_str("m/0'/1'/2'/2'/1000000000'").unwrap(),
hex!("68789923a0cac2cd5a29172a475fe9e0fb14cd6adb5ad98a3fa70333e7afa230"),
hex!("8f94d394a8e8fd6b1bc2f3f49f5c47e385281d5c17e65324b0f62483e37e8793"),
hex!("003c24da049451555d51a7014a37337aa4e12d41e485abccfa46b47dfb2af54b7a"),
)];
for (seed, chain, chain_code, private_key, public_key) in tests {
let xkey = ExtendedPrivateKey::<SigningKey>::new(seed).unwrap();
let derived_key = xkey.derive_path(&chain).unwrap();
assert_eq!(derived_key.chain_code, chain_code);
assert_eq!(PrivateKey::to_bytes(&derived_key.private_key), private_key);
assert_eq!(PublicKey::to_bytes(&derived_key.public_key()), public_key);
}
}