keyfork-derive-util: add ed25519 support
This commit is contained in:
parent
69b4aa9a18
commit
1a13acdfe3
|
@ -158,6 +158,34 @@ dependencies = [
|
|||
"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]]
|
||||
name = "der"
|
||||
version = "0.7.8"
|
||||
|
@ -191,6 +219,29 @@ dependencies = [
|
|||
"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]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.13.5"
|
||||
|
@ -226,6 +277,12 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fiat-crypto"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0870c84016d4b481be5c9f323c24f65e31e901ae618f0e80f4308fb00de1d2d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.28"
|
||||
|
@ -352,6 +409,7 @@ name = "keyfork-derive-util"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"ed25519-dalek",
|
||||
"hex-literal",
|
||||
"hmac",
|
||||
"k256",
|
||||
|
@ -548,6 +606,12 @@ dependencies = [
|
|||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platforms"
|
||||
version = "3.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4503fa043bf02cee09a9582e9554b4c6403b2ef55e4612e96561d294419429f8"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
|
@ -634,6 +698,15 @@ version = "0.1.23"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "ryu"
|
||||
version = "1.0.15"
|
||||
|
@ -654,6 +727,12 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.186"
|
||||
|
|
|
@ -6,8 +6,9 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
default = ["secp256k1"]
|
||||
default = ["secp256k1", "ed25519"]
|
||||
secp256k1 = ["k256"]
|
||||
ed25519 = ["ed25519-dalek"]
|
||||
|
||||
[dependencies]
|
||||
# Included in Rust
|
||||
|
@ -24,6 +25,7 @@ thiserror = "1.0.47"
|
|||
|
||||
# Optional, not personally audited
|
||||
k256 = { version = "0.13.1", default-features = false, features = ["std", "arithmetic"], optional = true }
|
||||
ed25519-dalek = { version = "2.0.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.4.1"
|
||||
|
|
|
@ -25,6 +25,10 @@ pub enum Error {
|
|||
/// An unknown error occurred while deriving a child key.
|
||||
#[error("Unknown error while deriving child key")]
|
||||
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>;
|
||||
|
@ -132,8 +136,10 @@ where
|
|||
if index.is_hardened() {
|
||||
hmac.update(&[0]);
|
||||
hmac.update(&self.private_key.to_bytes());
|
||||
} else {
|
||||
} else if !K::requires_hardened_derivation() {
|
||||
hmac.update(&self.private_key.public_key().to_bytes());
|
||||
} else {
|
||||
return Err(Error::HardenedDerivationRequired);
|
||||
}
|
||||
hmac.update(&index.to_bytes());
|
||||
let result = hmac.finalize().into_bytes();
|
||||
|
|
|
@ -48,7 +48,7 @@ impl std::str::FromStr for DerivationPath {
|
|||
path: iter
|
||||
.map(DerivationIndex::from_str)
|
||||
.map(|maybe_err| maybe_err.map_err(From::from))
|
||||
.collect::<Result<Vec<DerivationIndex>>>()?,
|
||||
.collect::<Result<_>>()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,13 @@ pub trait PrivateKey: Sized {
|
|||
/// Convert a &Self to bytes.
|
||||
fn to_bytes(&self) -> PrivateKeyBytes;
|
||||
|
||||
/*
|
||||
* Freak of nature, unsupported?
|
||||
/// Whether or not zero is a valid public key (such as with ed25519 keys).
|
||||
fn is_zero_valid_public_key() -> bool {
|
||||
false
|
||||
}
|
||||
*/
|
||||
|
||||
/// The initial key for BIP-0032 and SLIP-0010 derivation, such as secp256k1's "Bitcoin seed".
|
||||
fn key() -> &'static str;
|
||||
|
@ -37,6 +40,12 @@ pub trait PrivateKey: Sized {
|
|||
/// * A nonzero `other` is provided.
|
||||
/// * An error specific to the given algorithm was encountered.
|
||||
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
|
||||
|
@ -73,6 +82,10 @@ impl PrivateKey for k256::SecretKey {
|
|||
self.to_bytes().into()
|
||||
}
|
||||
|
||||
fn public_key(&self) -> Self::PublicKey {
|
||||
self.public_key()
|
||||
}
|
||||
|
||||
fn derive_child(&self, other: &PrivateKeyBytes) -> Result<Self, Self::Err> {
|
||||
if other.iter().all(|n| n == &0) {
|
||||
return Err(PrivateKeyError::NonZero);
|
||||
|
@ -89,8 +102,35 @@ impl PrivateKey for k256::SecretKey {
|
|||
.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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,10 +12,13 @@ pub trait PublicKey: Sized {
|
|||
/// The error returned by [`PublicKey::derive_child()`].
|
||||
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.
|
||||
fn from_bytes(b: &PublicKeyBytes) -> Self;
|
||||
*/
|
||||
|
||||
/// Convert a &Self to bytse.
|
||||
/// Convert a &Self to bytes.
|
||||
fn to_bytes(&self) -> PublicKeyBytes;
|
||||
|
||||
/// Derive a child [`PublicKey`] with given `PrivateKeyBytes`.
|
||||
|
@ -47,6 +50,10 @@ pub enum PublicKeyError {
|
|||
/// Unable to convert a point to a key.
|
||||
#[error("Unable to convert point to key")]
|
||||
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")]
|
||||
|
@ -59,9 +66,11 @@ use k256::{
|
|||
impl PublicKey for k256::PublicKey {
|
||||
type Err = PublicKeyError;
|
||||
|
||||
/*
|
||||
fn from_bytes(b: &PublicKeyBytes) -> Self {
|
||||
Self::from_sec1_bytes(b).expect("Invalid public key bytes")
|
||||
}
|
||||
*/
|
||||
|
||||
fn to_bytes(&self) -> PublicKeyBytes {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
use crate::*;
|
||||
use hex_literal::hex;
|
||||
use k256::SecretKey;
|
||||
use std::str::FromStr;
|
||||
|
||||
// Pulled from: https://github.com/satoshilabs/slips/blob/master/slip-0010.md
|
||||
|
||||
#[cfg(feature = "secp256k1")]
|
||||
#[test]
|
||||
fn example() {
|
||||
fn secp256k1() {
|
||||
use k256::SecretKey;
|
||||
|
||||
// seed, chain, chain code, private, public
|
||||
let tests = [(
|
||||
&hex!("000102030405060708090a0b0c0d0e0f")[..],
|
||||
|
@ -65,3 +67,37 @@ fn example() {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue