diff --git a/Cargo.lock b/Cargo.lock
index 27b5fcb..70ab4d2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,9 +4,9 @@ version = 3
[[package]]
name = "autocfg"
-version = "1.1.0"
+version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "base16ct"
@@ -23,6 +23,12 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -31,15 +37,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "const-oid"
-version = "0.9.5"
+version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "cpufeatures"
-version = "0.2.11"
+version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
+checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
dependencies = [
"libc",
]
@@ -66,11 +72,39 @@ dependencies = [
"typenum",
]
+[[package]]
+name = "curve25519-dalek"
+version = "4.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "curve25519-dalek-derive",
+ "fiat-crypto",
+ "group",
+ "rand_core",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.77",
+]
+
[[package]]
name = "darling"
-version = "0.20.5"
+version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc5d6b04b3fd0ba9926f945895de7d806260a2d7431ba82e7edaecb043c4c6b8"
+checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [
"darling_core",
"darling_macro",
@@ -78,34 +112,34 @@ dependencies = [
[[package]]
name = "darling_core"
-version = "0.20.5"
+version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04e48a959bcd5c761246f5d090ebc2fbf7b9cd527a492b07a67510c108f1e7e3"
+checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
- "syn 2.0.41",
+ "syn 2.0.77",
]
[[package]]
name = "darling_macro"
-version = "0.20.5"
+version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d1545d67a2149e1d93b7e5c7752dce5a7426eb5d1357ddcfd89336b94444f77"
+checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
- "syn 2.0.41",
+ "syn 2.0.77",
]
[[package]]
name = "der"
-version = "0.7.8"
+version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
+checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
dependencies = [
"const-oid",
"zeroize",
@@ -162,7 +196,7 @@ dependencies = [
"num-traits",
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.77",
]
[[package]]
@@ -175,6 +209,12 @@ dependencies = [
"subtle",
]
+[[package]]
+name = "fiat-crypto"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
+
[[package]]
name = "fnv"
version = "1.0.7"
@@ -194,9 +234,11 @@ dependencies = [
[[package]]
name = "generic-ec"
-version = "0.4.0-rc4"
-source = "git+https://github.com/dfns/generic-ec?branch=update-udigest#8fbc75205c10dd9677fb67cf6eea67c6b44d836e"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a88871287d5618d728c21ef0bea4dc5dc3097cbfa2efd3e1b6e85a33df74083"
dependencies = [
+ "curve25519-dalek",
"generic-ec-core",
"generic-ec-curves",
"hex",
@@ -211,7 +253,8 @@ dependencies = [
[[package]]
name = "generic-ec-core"
version = "0.2.0"
-source = "git+https://github.com/dfns/generic-ec?branch=update-udigest#8fbc75205c10dd9677fb67cf6eea67c6b44d836e"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f156564cc8aa47456da807826b1a0aa9cf420474d9f41593ffbdde65133d4bea"
dependencies = [
"generic-array",
"rand_core",
@@ -223,10 +266,13 @@ dependencies = [
[[package]]
name = "generic-ec-curves"
version = "0.2.1"
-source = "git+https://github.com/dfns/generic-ec?branch=update-udigest#8fbc75205c10dd9677fb67cf6eea67c6b44d836e"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5926949b758d01801c7edd75357495fe54c5fc25580a193de4c994c94d22307"
dependencies = [
+ "curve25519-dalek",
"elliptic-curve",
"generic-ec-core",
+ "group",
"k256",
"p256",
"rand_core",
@@ -235,6 +281,17 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "getrandom"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
[[package]]
name = "group"
version = "0.13.0"
@@ -246,6 +303,21 @@ dependencies = [
"subtle",
]
+[[package]]
+name = "hd-wallet"
+version = "0.5.0"
+dependencies = [
+ "generic-array",
+ "generic-ec",
+ "hex",
+ "hex-literal",
+ "hmac",
+ "rand",
+ "serde",
+ "sha2",
+ "subtle",
+]
+
[[package]]
name = "hex"
version = "0.4.3"
@@ -275,9 +347,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "k256"
-version = "0.13.2"
+version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b"
+checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b"
dependencies = [
"cfg-if",
"elliptic-curve",
@@ -285,36 +357,34 @@ dependencies = [
[[package]]
name = "libc"
-version = "0.2.151"
+version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
+checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]]
name = "num-bigint"
-version = "0.4.4"
+version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
- "autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-integer"
-version = "0.1.45"
+version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
- "autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
-version = "0.2.17"
+version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
@@ -338,6 +408,15 @@ dependencies = [
"educe",
]
+[[package]]
+name = "ppv-lite86"
+version = "0.2.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
+dependencies = [
+ "zerocopy",
+]
+
[[package]]
name = "primeorder"
version = "0.13.6"
@@ -349,27 +428,60 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.70"
+version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
-version = "1.0.33"
+version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
[[package]]
name = "sec1"
@@ -384,24 +496,30 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "semver"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
+
[[package]]
name = "serde"
-version = "1.0.193"
+version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
+checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.193"
+version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
+checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.77",
]
[[package]]
@@ -423,7 +541,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.77",
]
[[package]]
@@ -437,30 +555,17 @@ dependencies = [
"digest",
]
-[[package]]
-name = "slip-10"
-version = "0.4.0-rc4"
-dependencies = [
- "generic-array",
- "generic-ec",
- "hex-literal",
- "hmac",
- "serde",
- "sha2",
- "subtle",
-]
-
[[package]]
name = "strsim"
-version = "0.10.0"
+version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
-version = "2.5.0"
+version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
@@ -475,9 +580,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.41"
+version = "2.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269"
+checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
dependencies = [
"proc-macro2",
"quote",
@@ -492,21 +597,48 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
-version = "1.0.12"
+version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "version_check"
-version = "0.9.4"
+version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "byteorder",
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.77",
+]
[[package]]
name = "zeroize"
-version = "1.7.0"
+version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
dependencies = [
"zeroize_derive",
]
@@ -519,5 +651,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.77",
]
diff --git a/Cargo.toml b/Cargo.toml
index b6b4879..187135b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,9 +1,9 @@
[package]
-name = "slip-10"
-version = "0.4.0"
+name = "hd-wallet"
+version = "0.5.0"
edition = "2021"
license = "MIT OR Apache-2.0"
-description = "SLIP10 implementation in Rust"
+description = "HD wallets derivation"
repository = "https://github.com/dfns/slip-10"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -19,18 +19,25 @@ generic-array = "0.14"
serde = { version = "1", default-features = false, features = ["derive"], optional = true }
[dev-dependencies]
+hex = "0.4"
hex-literal = "0.4"
+rand = "0.8"
[features]
std = []
curve-secp256k1 = ["generic-ec/curve-secp256k1"]
curve-secp256r1 = ["generic-ec/curve-secp256r1"]
+curve-ed25519 = ["generic-ec/curve-ed25519"]
all-curves = ["curve-secp256k1", "curve-secp256r1"]
serde = ["dep:serde", "generic-ec/serde"]
[[test]]
-name = "test_vectors"
-required-features = ["all-curves"]
+name = "slip10_test_vector"
+required-features = ["curve-secp256k1", "curve-secp256r1"]
+
+[[test]]
+name = "edwards_test_vector"
+required-features = ["curve-ed25519"]
[package.metadata.docs.rs]
all-features = true
diff --git a/README.md b/README.md
index c3614d2..a51d98a 100644
--- a/README.md
+++ b/README.md
@@ -1,40 +1,56 @@
-# SLIP-10: Deterministic key generation
+# # HD wallets derivation
-[SLIP10][slip10-spec] is a specification for implementing HD wallets. It aims at supporting many
-curves while being compatible with [BIP32][bip32-spec].
+This crate supports the following HD derivations:
+* [SLIP10][slip10-spec] (compatible with [BIP32][bip32-spec]), see `Slip10`
+* Non-standard `Edwards` derivation for ed25519 curve
-The implementation is based on generic-ec library that provides generic
-elliptic curve arithmetic. The crate is `no_std` and `no_alloc` friendly.
+To perform HD derivation, use `HdWallet` trait.
-### Curves support
-Implementation currently does not support ed25519 curve. All other curves are
-supported: both secp256k1 and secp256r1. In fact, implementation may work with any
-curve, but only those are covered by the SLIP10 specs.
-
-The crate also re-exports supported curves in supported_curves module (requires
-enabling a feature), but any other curve implementation will work with the crate.
-
-### Features
-* `std`: enables std library support (mainly, it just implements `Error`
- trait for the error types)
-* `curve-secp256k1` and `curve-secp256r1` add curve implementation into the crate supported_curves
- module
-
-### Examples
+### Example: SLIP10 derivation
Derive a master key from the seed, and then derive a child key m/1H/10:
```rust
-use slip_10::supported_curves::Secp256k1;
+use hd_wallet::{slip10, curves::Secp256k1};
let seed = b"16-64 bytes of high entropy".as_slice();
-let master_key = slip_10::derive_master_key::(seed)?;
-let master_key_pair = slip_10::ExtendedKeyPair::from(master_key);
+let master_key = slip10::derive_master_key::(seed)?;
+let master_key_pair = hd_wallet::ExtendedKeyPair::from(master_key);
-let child_key_pair = slip_10::derive_child_key_pair_with_path(
+let child_key_pair = slip10::derive_child_key_pair_with_path(
&master_key_pair,
- [1 + slip_10::H, 10],
+ [1 + hd_wallet::H, 10],
);
```
+### Example: via HdWallet trait
+
+`HdWallet` trait generalizes HD derivation algorithm, you can use it with generics:
+```rust
+use hd_wallet::{Slip10Like, curves::Secp256r1};
+
+fn derive_using_generic_algo>(
+ master_key: hd_wallet::ExtendedKeyPair,
+) -> hd_wallet::ExtendedKeyPair
+{
+ Hd::derive_child_key_pair_with_path(
+ &master_key,
+ [1 + hd_wallet::H, 10],
+ )
+}
+
+// Use it with any HD derivation:
+let seed = b"16-64 bytes of high entropy".as_slice();
+let master_key = hd_wallet::slip10::derive_master_key(seed)?;
+let master_key_pair = hd_wallet::ExtendedKeyPair::from(master_key);
+let child_key = derive_using_generic_algo::(master_key_pair);
+
+```
+
+### Features
+* `std`: enables std library support (mainly, it just implements `Error`
+ trait for the error types)
+* `curve-secp256k1`, `curve-secp256r1`, `curve-ed25519` add curve implementation into the crate
+ curves module
+
[slip10-spec]: https://github.com/satoshilabs/slips/blob/master/slip-0010.md
[bip32-spec]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
diff --git a/src/lib.rs b/src/lib.rs
index a025815..352a0b9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,42 +1,59 @@
-//! SLIP-10: Deterministic key generation
+//! # HD wallets derivation
//!
-//! [SLIP10][slip10-spec] is a specification for implementing HD wallets. It aims at supporting many
-//! curves while being compatible with [BIP32][bip32-spec].
+//! This crate supports the following HD derivations:
+//! * [SLIP10][slip10-spec] (compatible with [BIP32][bip32-spec]), see [`Slip10`]
+//! * Non-standard [`Edwards`] derivation for ed25519 curve
//!
-//! The implementation is based on [generic-ec](generic_ec) library that provides generic
-//! elliptic curve arithmetic. The crate is `no_std` and `no_alloc` friendly.
+//! To perform HD derivation, use [`HdWallet`] trait.
//!
-//! ### Curves support
-//! Implementation currently does not support ed25519 curve. All other curves are
-//! supported: both secp256k1 and secp256r1. In fact, implementation may work with any
-//! curve, but only those are covered by the SLIP10 specs.
-//!
-//! The crate also re-exports supported curves in [supported_curves] module (requires
-//! enabling a feature), but any other curve implementation will work with the crate.
-//!
-//! ### Features
-//! * `std`: enables std library support (mainly, it just implements [`Error`](std::error::Error)
-//! trait for the error types)
-//! * `curve-secp256k1` and `curve-secp256r1` add curve implementation into the crate [supported_curves]
-//! module
-//!
-//! ### Examples
+//! ### Example: SLIP10 derivation
//!
//! Derive a master key from the seed, and then derive a child key m/1H/10:
//! ```rust
-//! use slip_10::supported_curves::Secp256k1;
+//! use hd_wallet::{slip10, curves::Secp256k1};
//!
//! let seed = b"16-64 bytes of high entropy".as_slice();
-//! let master_key = slip_10::derive_master_key::(seed)?;
-//! let master_key_pair = slip_10::ExtendedKeyPair::from(master_key);
+//! let master_key = slip10::derive_master_key::(seed)?;
+//! let master_key_pair = hd_wallet::ExtendedKeyPair::from(master_key);
//!
-//! let child_key_pair = slip_10::derive_child_key_pair_with_path(
+//! let child_key_pair = slip10::derive_child_key_pair_with_path(
//! &master_key_pair,
-//! [1 + slip_10::H, 10],
+//! [1 + hd_wallet::H, 10],
//! );
//! # Ok::<(), Box>(())
//! ```
//!
+//! ### Example: via [HdWallet] trait
+//!
+//! [`HdWallet`] trait generalizes HD derivation algorithm, you can use it with generics:
+//! ```rust
+//! use hd_wallet::{Slip10Like, curves::Secp256r1};
+//!
+//! fn derive_using_generic_algo>(
+//! master_key: hd_wallet::ExtendedKeyPair,
+//! ) -> hd_wallet::ExtendedKeyPair
+//! {
+//! Hd::derive_child_key_pair_with_path(
+//! &master_key,
+//! [1 + hd_wallet::H, 10],
+//! )
+//! }
+//!
+//! // Use it with any HD derivation:
+//! let seed = b"16-64 bytes of high entropy".as_slice();
+//! let master_key = hd_wallet::slip10::derive_master_key(seed)?;
+//! let master_key_pair = hd_wallet::ExtendedKeyPair::from(master_key);
+//! let child_key = derive_using_generic_algo::(master_key_pair);
+//!
+//! # Ok::<(), Box>(())
+//! ```
+//!
+//! ### Features
+//! * `std`: enables std library support (mainly, it just implements [`Error`](std::error::Error)
+//! trait for the error types)
+//! * `curve-secp256k1`, `curve-secp256r1`, `curve-ed25519` add curve implementation into the crate
+//! [curves] module
+//!
//! [slip10-spec]: https://github.com/satoshilabs/slips/blob/master/slip-0010.md
//! [bip32-spec]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
@@ -50,37 +67,56 @@ use generic_array::{
GenericArray,
};
use generic_ec::{Curve, Point, Scalar, SecretScalar};
-use hmac::Mac as _;
+use hmac::Mac;
-#[cfg(any(
- feature = "curve-secp256k1",
- feature = "curve-secp256r1",
- feature = "all-curves"
-))]
-pub use generic_ec::curves as supported_curves;
+pub use generic_ec::curves;
pub mod errors;
+pub mod slip10;
+
+/// Slip10-like HD derivation
+///
+/// This module provides aliases for calling `>::*` methods for convenience
+/// when you don't need to support generic HD derivation algorithm.
+///
+/// See [`Slip10Like`] docs to learn more about the derivation method.
+pub mod slip10_like {
+ pub use crate::Slip10Like;
+ super::create_aliases!(Slip10Like, slip10_like);
+}
+
+/// Edwards HD derivation
+///
+/// This module provides aliases for calling `>::*` methods for convenience
+/// when you don't need to support generic HD derivation algorithm.
+///
+/// See [`Edwards`] docs to learn more about the derivation method.
+pub mod edwards {
+ pub use crate::Edwards;
+ super::create_aliases!(Edwards, edwards, hd_wallet::curves::Ed25519);
+}
type HmacSha512 = hmac::Hmac;
-/// Beggining of hardened child indexes
+
+/// Beginning of hardened child indexes
///
/// $H = 2^{31}$ defines the range of hardened indexes. All indexes $i$ such that $H \le i$ are hardened.
///
/// ## Example
/// Derive a child key with a path m/1H
/// ```rust
-/// use slip_10::supported_curves::Secp256k1;
+/// use hd_wallet::HdWallet;
///
/// # let seed = b"do not use this seed in prod :)".as_slice();
-/// let master_key = slip_10::derive_master_key::(seed)?;
-/// let master_key_pair = slip_10::ExtendedKeyPair::from(master_key);
-///
-/// let hardened_child = slip_10::derive_child_key_pair(
+/// # let master_key = hd_wallet::slip10::derive_master_key::(seed)?;
+/// # let master_key_pair = hd_wallet::ExtendedKeyPair::from(master_key);
+/// #
+/// let hardened_child = hd_wallet::Slip10::derive_child_key_pair(
/// &master_key_pair,
-/// 1 + slip_10::H,
+/// 1 + hd_wallet::H,
/// );
/// #
-/// # Ok::<(), slip_10::errors::InvalidLength>(())
+/// # Ok::<(), hd_wallet::errors::InvalidLength>(())
/// ```
pub const H: u32 = 1 << 31;
@@ -95,13 +131,13 @@ pub enum ChildIndex {
NonHardened(NonHardenedIndex),
}
-/// Child index in range $2^{31} \le i < 2^{32}$ corresponing to a hardened wallet
+/// Child index in range $2^{31} \le i < 2^{32}$ corresponding to a hardened wallet
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(into = "u32"))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(try_from = "u32"))]
pub struct HardenedIndex(u32);
-/// Child index in range $0 \le i < 2^{31}$ corresponing to a non-hardened wallet
+/// Child index in range $0 \le i < 2^{31}$ corresponding to a non-hardened wallet
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(into = "u32"))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(try_from = "u32"))]
@@ -321,347 +357,722 @@ impl<'de, E: Curve> serde::Deserialize<'de> for ExtendedKeyPair {
}
}
-/// Marker for a curve supported by SLIP10 specs and this library
-///
-/// Only implement this trait for the curves that are supported by SLIP10 specs.
-/// Curves provided by the crate out-of-box in [supported_curves] module already
-/// implement this trait.
-pub trait SupportedCurve {
- /// Specifies which curve it is
- const CURVE_TYPE: CurveType;
-}
-#[cfg(feature = "curve-secp256k1")]
-impl SupportedCurve for supported_curves::Secp256k1 {
- const CURVE_TYPE: CurveType = CurveType::Secp256k1;
-}
-#[cfg(feature = "curve-secp256r1")]
-impl SupportedCurve for supported_curves::Secp256r1 {
- const CURVE_TYPE: CurveType = CurveType::Secp256r1;
-}
+/// * `$t` - type to monomorphise for, like `Slip10` or `Edwards`
+/// * `$m` - current module, module where these functions will appear. Used in doc
+/// tests only
+/// * `$e` - curve supported by this HD derivation, used in doc tests only
+macro_rules! create_aliases {
+ ($t:ty, $m:expr) => { $crate::create_aliases!($t, $m, hd_wallet::curves::Secp256k1); };
+ ($t:ty, $m:expr, $e:ty) => {
+ /// Derives a shift for non-hardened child
+ ///
+ #[doc = concat!("Alias to [`<", stringify!($t), " as DeriveShift>::derive_public_shift`](crate::DeriveShift::derive_public_shift)")]
+ pub fn derive_public_shift(
+ parent_public_key: &crate::ExtendedPublicKey,
+ child_index: crate::NonHardenedIndex,
+ ) -> crate::DerivedShift
+ where
+ E: generic_ec::Curve,
+ $t: crate::DeriveShift,
+ {
+ <$t as crate::DeriveShift>::derive_public_shift(parent_public_key, child_index)
+ }
-/// Curves supported by SLIP-10 spec
-///
-/// It's either secp256k1 or secp256r1. Note that SLIP-10 also supports ed25519 curve, but this library
-/// does not support it.
-///
-/// `CurveType` is only needed for master key derivation.
-#[derive(Clone, Copy, Debug)]
-pub enum CurveType {
- /// Secp256k1 curve
- Secp256k1,
- /// Secp256r1 curve
- Secp256r1,
-}
+ /// Derive a shift for hardened child
+ ///
+ #[doc = concat!("Alias to [`<", stringify!($t), " as DeriveShift>::derive_hardened_shift`](crate::DeriveShift::derive_hardened_shift)")]
+ pub fn derive_hardened_shift(
+ parent_key: &crate::ExtendedKeyPair,
+ child_index: crate::HardenedIndex,
+ ) -> crate::DerivedShift
+ where
+ E: generic_ec::Curve,
+ $t: crate::DeriveShift,
+ {
+ <$t as crate::DeriveShift>::derive_hardened_shift(parent_key, child_index)
+ }
-/// Derives a master key from the seed
-///
-/// Seed must be 16-64 bytes long, otherwise an error is returned
-pub fn derive_master_key(
- seed: &[u8],
-) -> Result, errors::InvalidLength> {
- let curve_tag = match E::CURVE_TYPE {
- CurveType::Secp256k1 => "Bitcoin seed",
- CurveType::Secp256r1 => "Nist256p1 seed",
+ /// Derives child extended public key from parent extended public key
+ ///
+ #[doc = concat!("Alias to [`<", stringify!($t), " as HdWallet>::derive_child_public_key`](crate::HdWallet::derive_child_public_key)")]
+ ///
+ /// ### Example
+ /// Derive a master public key m/1
+ /// ```rust,no_run
+ #[doc = concat!( "# type E = ", stringify!($e), ";" )]
+ /// # let seed = b"do not use this seed :)".as_slice();
+ /// # let master_key: hd_wallet::ExtendedSecretKey = todo!();
+ /// # let master_public_key = hd_wallet::ExtendedPublicKey::from(&master_key);
+ /// #
+ #[doc = concat!("let derived_key = hd_wallet::", stringify!($m), "::derive_child_public_key(")]
+ /// &master_public_key,
+ /// 1.try_into()?,
+ /// );
+ /// # Ok::<(), Box>(())
+ /// ```
+ pub fn derive_child_public_key(
+ parent_public_key: &crate::ExtendedPublicKey,
+ child_index: crate::NonHardenedIndex,
+ ) -> crate::ExtendedPublicKey
+ where
+ E: generic_ec::Curve,
+ $t: crate::HdWallet,
+ {
+ <$t as crate::HdWallet>::derive_child_public_key(parent_public_key, child_index)
+ }
+
+ /// Derives child key pair (extended secret key + public key) from parent key pair
+ ///
+ #[doc = concat!("Alias to [`<", stringify!($t), " as HdWallet>::derive_child_key_pair`](crate::HdWallet::derive_child_key_pair)")]
+ ///
+ /// ### Example
+ /// Derive child key m/1H from master key
+ /// ```rust,no_run
+ #[doc = concat!( "# type E = ", stringify!($e), ";" )]
+ /// # let seed = b"do not use this seed :)".as_slice();
+ /// # let master_key: hd_wallet::ExtendedSecretKey = todo!();
+ /// # let master_key_pair = hd_wallet::ExtendedKeyPair::from(master_key);
+ /// #
+ #[doc = concat!("let derived_key = hd_wallet::", stringify!($m), "::derive_child_key_pair(")]
+ /// &master_key_pair,
+ /// 1 + hd_wallet::H,
+ /// );
+ /// # Ok::<(), Box>(())
+ /// ```
+ pub fn derive_child_key_pair(
+ parent_key: &crate::ExtendedKeyPair,
+ child_index: impl Into,
+ ) -> crate::ExtendedKeyPair
+ where
+ E: generic_ec::Curve,
+ $t: crate::HdWallet,
+ {
+ <$t as crate::HdWallet>::derive_child_key_pair(parent_key, child_index)
+ }
+
+ /// Derives a child key pair with specified derivation path from parent key pair
+ ///
+ /// Derivation path is a fallible iterator that yields child indexes. If iterator
+ /// yields an error, it's propagated to the caller.
+ ///
+ /// Returns:
+ /// * `Ok(child_key_pair)` if derivation was successful
+ /// * `Err(index_err)` if path contained `Err(index_err)`
+ ///
+ #[doc = concat!("Alias to [`<", stringify!($t), " as HdWallet>::try_derive_child_key_pair_with_path`](crate::HdWallet::try_derive_child_key_pair_with_path)")]
+ ///
+ /// ### Example
+ /// Parse a path from the string and derive a child without extra allocations:
+ /// ```rust,no_run
+ /// use hd_wallet::HdWallet;
+ #[doc = concat!( "# type E = ", stringify!($e), ";" )]
+ /// # let seed = b"16-64 bytes of high entropy".as_slice();
+ /// # let master_key: hd_wallet::ExtendedSecretKey = todo!();
+ /// # let master_key_pair = hd_wallet::ExtendedKeyPair::from(master_key);
+ ///
+ /// let path = "1/10/2";
+ /// let child_indexes = path.split('/').map(str::parse::);
+ #[doc = concat!("let child_key = hd_wallet::", stringify!($m), "::try_derive_child_key_pair_with_path(")]
+ /// &master_key_pair,
+ /// child_indexes,
+ /// )?;
+ /// # Ok::<_, Box>(())
+ /// ```
+ pub fn try_derive_child_key_pair_with_path(
+ parent_key: &crate::ExtendedKeyPair,
+ path: impl IntoIterator- , Err>>,
+ ) -> Result, Err>
+ where
+ E: generic_ec::Curve,
+ $t: crate::HdWallet,
+ {
+ <$t as crate::HdWallet>::try_derive_child_key_pair_with_path(parent_key, path)
+ }
+ /// Derives a child key pair with specified derivation path from parent key pair
+ ///
+ /// Derivation path is an iterator that yields child indexes.
+ ///
+ /// If derivation path is empty, `parent_key` is returned
+ ///
+ #[doc = concat!("Alias to [`<", stringify!($t), " as HdWallet>::derive_child_key_pair_with_path`](crate::HdWallet::derive_child_key_pair_with_path)")]
+ ///
+ /// ### Example
+ /// Derive a child key with path m/1/10/1H
+ /// ```rust,no_run
+ /// use hd_wallet::HdWallet;
+ #[doc = concat!( "# type E = ", stringify!($e), ";" )]
+ /// # let seed = b"16-64 bytes of high entropy".as_slice();
+ /// # let master_key: hd_wallet::ExtendedSecretKey = todo!();
+ /// # let master_key_pair = hd_wallet::ExtendedKeyPair::from(master_key);
+ ///
+ #[doc = concat!("let child_key = hd_wallet::", stringify!($m), "::derive_child_key_pair_with_path(")]
+ /// &master_key_pair,
+ /// [1, 10, 1 + hd_wallet::H],
+ /// );
+ /// # Ok::<(), Box>(())
+ /// ```
+ pub fn derive_child_key_pair_with_path(
+ parent_key: &crate::ExtendedKeyPair,
+ path: impl IntoIterator
- >,
+ ) -> crate::ExtendedKeyPair
+ where
+ E: generic_ec::Curve,
+ $t: crate::HdWallet,
+ {
+ <$t as crate::HdWallet>::derive_child_key_pair_with_path(parent_key, path)
+ }
+
+ /// Derives a child public key with specified derivation path
+ ///
+ /// Derivation path is a fallible iterator that yields child indexes. If iterator
+ /// yields an error, it's propagated to the caller.
+ ///
+ /// Returns:
+ /// * `Ok(child_pk)` if derivation was successful
+ /// * `Err(index_err)` if path contained `Err(index_err)`
+ ///
+ #[doc = concat!("Alias to [`<", stringify!($t), " as HdWallet>::try_derive_child_public_key_with_path`](crate::HdWallet::try_derive_child_public_key_with_path)")]
+ ///
+ /// ### Example
+ /// Parse a path from the string and derive a child without extra allocations:
+ /// ```rust,no_run
+ /// use hd_wallet::HdWallet;
+ #[doc = concat!( "# type E = ", stringify!($e), ";" )]
+ /// # let seed = b"16-64 bytes of high entropy".as_slice();
+ /// # let master_key: hd_wallet::ExtendedSecretKey = todo!();
+ /// # let master_public_key = hd_wallet::ExtendedPublicKey::from(&master_key);
+ ///
+ /// let path = "1/10/2";
+ /// let child_indexes = path.split('/').map(str::parse);
+ #[doc = concat!("let child_key = hd_wallet::", stringify!($m), "::try_derive_child_public_key_with_path(")]
+ /// &master_public_key,
+ /// child_indexes,
+ /// )?;
+ /// # Ok::<_, Box>(())
+ /// ```
+ pub fn try_derive_child_public_key_with_path(
+ parent_public_key: &crate::ExtendedPublicKey,
+ path: impl IntoIterator
- >,
+ ) -> Result, Err>
+ where
+ E: generic_ec::Curve,
+ $t: crate::HdWallet,
+ {
+ <$t as crate::HdWallet>::try_derive_child_public_key_with_path(parent_public_key, path)
+ }
+
+ /// Derives a child public key with specified derivation path
+ ///
+ /// Derivation path is an iterator that yields child indexes.
+ ///
+ /// If derivation path is empty, `parent_public_key` is returned
+ ///
+ #[doc = concat!("Alias to [`<", stringify!($t), " as HdWallet>::derive_child_public_key_with_path`](crate::HdWallet::derive_child_public_key_with_path)")]
+ ///
+ /// ### Example
+ /// Derive a child key with path m/1/10
+ /// ```rust,no_run
+ /// use hd_wallet::HdWallet;
+ #[doc = concat!( "# type E = ", stringify!($e), ";" )]
+ /// # let seed = b"16-64 bytes of high entropy".as_slice();
+ /// # let master_key: hd_wallet::ExtendedSecretKey = todo!();
+ /// # let master_public_key = hd_wallet::ExtendedPublicKey::from(&master_key);
+ ///
+ #[doc = concat!("let child_key = hd_wallet::", stringify!($m), "::derive_child_public_key_with_path(")]
+ /// &master_public_key,
+ /// [1.try_into()?, 10.try_into()?],
+ /// );
+ /// # Ok::<(), Box>(())
+ /// ```
+ pub fn derive_child_public_key_with_path(
+ parent_public_key: &crate::ExtendedPublicKey,
+ path: impl IntoIterator
- ,
+ ) -> crate::ExtendedPublicKey
+ where
+ E: generic_ec::Curve,
+ $t: crate::HdWallet,
+ {
+ <$t as crate::HdWallet>::derive_child_public_key_with_path(parent_public_key, path)
+ }
};
- derive_master_key_with_curve_tag(curve_tag.as_bytes(), seed)
}
+pub(crate) use create_aliases;
-/// Derives a master key from the seed and the curve tag as defined in SLIP10
-///
-/// It's preferred to use [derive_master_key] instead, as it automatically infers
-/// the curve tag for supported curves. The curve tag is not validated by the function,
-/// it's caller's responsibility to make sure that it complies with SLIP10.
-///
-/// Seed must be 16-64 bytes long, otherwise an error is returned
-pub fn derive_master_key_with_curve_tag(
- curve_tag: &[u8],
- seed: &[u8],
-) -> Result, errors::InvalidLength> {
- if !(16 <= seed.len() && seed.len() <= 64) {
- return Err(errors::InvalidLength);
- }
-
- let hmac = HmacSha512::new_from_slice(curve_tag)
- .expect("this never fails: hmac can handle keys of any size");
- let mut i = hmac.clone().chain_update(seed).finalize().into_bytes();
-
- loop {
- let (i_left, i_right) = split_into_two_halfes(&i);
-
- if let Ok(mut sk) = Scalar::::from_be_bytes(i_left) {
- if !bool::from(subtle::ConstantTimeEq::ct_eq(&sk, &Scalar::zero())) {
- return Ok(ExtendedSecretKey {
- secret_key: SecretScalar::new(&mut sk),
- chain_code: (*i_right).into(),
- });
- }
+/// HD derivation
+pub trait HdWallet: DeriveShift {
+ /// Derives child extended public key from parent extended public key
+ ///
+ /// ### Example
+ /// Derive a master public key m/1
+ /// ```rust
+ /// use hd_wallet::HdWallet;
+ ///
+ /// # let seed = b"do not use this seed :)".as_slice();
+ /// # let master_key = hd_wallet::slip10::derive_master_key::(seed)?;
+ /// # let master_public_key = hd_wallet::ExtendedPublicKey::from(&master_key);
+ /// #
+ /// let derived_key = hd_wallet::Slip10::derive_child_public_key(
+ /// &master_public_key,
+ /// 1.try_into()?,
+ /// );
+ /// # Ok::<(), Box>(())
+ /// ```
+ fn derive_child_public_key(
+ parent_public_key: &ExtendedPublicKey,
+ child_index: NonHardenedIndex,
+ ) -> ExtendedPublicKey {
+ Self::derive_public_shift(parent_public_key, child_index).child_public_key
+ }
+
+ /// Derives child key pair (extended secret key + public key) from parent key pair
+ ///
+ /// ### Example
+ /// Derive child key m/1H from master key
+ /// ```rust
+ /// use hd_wallet::HdWallet;
+ ///
+ /// # let seed = b"do not use this seed :)".as_slice();
+ /// # let master_key = hd_wallet::slip10::derive_master_key::(seed)?;
+ /// # let master_key_pair = hd_wallet::ExtendedKeyPair::from(master_key);
+ /// #
+ /// let derived_key = hd_wallet::Slip10::derive_child_key_pair(
+ /// &master_key_pair,
+ /// 1 + hd_wallet::H,
+ /// );
+ /// # Ok::<(), Box>(())
+ /// ```
+ fn derive_child_key_pair(
+ parent_key: &ExtendedKeyPair,
+ child_index: impl Into,
+ ) -> ExtendedKeyPair {
+ let child_index = child_index.into();
+ let shift = match child_index {
+ ChildIndex::Hardened(i) => Self::derive_hardened_shift(parent_key, i),
+ ChildIndex::NonHardened(i) => Self::derive_public_shift(&parent_key.public_key, i),
+ };
+ let mut child_sk = &parent_key.secret_key.secret_key + shift.shift;
+ let child_sk = SecretScalar::new(&mut child_sk);
+ ExtendedKeyPair {
+ secret_key: ExtendedSecretKey {
+ secret_key: child_sk,
+ chain_code: shift.child_public_key.chain_code,
+ },
+ public_key: shift.child_public_key,
}
+ }
- i = hmac.clone().chain_update(&i[..]).finalize().into_bytes()
+ /// Derives a child key pair with specified derivation path from parent key pair
+ ///
+ /// Derivation path is a fallible iterator that yields child indexes. If iterator
+ /// yields an error, it's propagated to the caller.
+ ///
+ /// Returns:
+ /// * `Ok(child_key_pair)` if derivation was successful
+ /// * `Err(index_err)` if path contained `Err(index_err)`
+ ///
+ /// ### Example
+ /// Parse a path from the string and derive a child without extra allocations:
+ /// ```rust
+ /// use hd_wallet::HdWallet;
+ /// # let seed = b"16-64 bytes of high entropy".as_slice();
+ /// # let master_key = hd_wallet::slip10::derive_master_key::(seed)?;
+ /// # let master_key_pair = hd_wallet::ExtendedKeyPair::from(master_key);
+ ///
+ /// let path = "1/10/2";
+ /// let child_indexes = path.split('/').map(str::parse::);
+ /// let child_key = hd_wallet::Slip10::try_derive_child_key_pair_with_path(
+ /// &master_key_pair,
+ /// child_indexes,
+ /// )?;
+ /// # Ok::<_, Box>(())
+ /// ```
+ fn try_derive_child_key_pair_with_path(
+ parent_key: &ExtendedKeyPair,
+ path: impl IntoIterator
- , Err>>,
+ ) -> Result, Err> {
+ let mut derived_key = parent_key.clone();
+ for child_index in path {
+ derived_key = Self::derive_child_key_pair(&derived_key, child_index?);
+ }
+ Ok(derived_key)
}
-}
-/// Derives child key pair (extended secret key + public key) from parent key pair
-///
-/// ### Example
-/// Derive child key m/1H from master key
-/// ```rust
-/// use slip_10::supported_curves::Secp256k1;
-///
-/// # let seed = b"do not use this seed :)".as_slice();
-/// let master_key = slip_10::derive_master_key::(seed)?;
-/// let master_key_pair = slip_10::ExtendedKeyPair::from(master_key);
-///
-/// let derived_key = slip_10::derive_child_key_pair(
-/// &master_key_pair,
-/// 1 + slip_10::H,
-/// );
-/// # Ok::<(), Box>(())
-/// ```
-pub fn derive_child_key_pair(
- parent_key: &ExtendedKeyPair,
- child_index: impl Into,
-) -> ExtendedKeyPair {
- let child_index = child_index.into();
- let shift = match child_index {
- ChildIndex::Hardened(i) => derive_hardened_shift(parent_key, i),
- ChildIndex::NonHardened(i) => derive_public_shift(&parent_key.public_key, i),
- };
- let mut child_sk = &parent_key.secret_key.secret_key + shift.shift;
- let child_sk = SecretScalar::new(&mut child_sk);
- ExtendedKeyPair {
- secret_key: ExtendedSecretKey {
- secret_key: child_sk,
- chain_code: shift.child_public_key.chain_code,
- },
- public_key: shift.child_public_key,
+ /// Derives a child key pair with specified derivation path from parent key pair
+ ///
+ /// Derivation path is an iterator that yields child indexes.
+ ///
+ /// If derivation path is empty, `parent_key` is returned
+ ///
+ /// ### Example
+ /// Derive a child key with path m/1/10/1H
+ /// ```rust
+ /// use hd_wallet::HdWallet;
+ /// # let seed = b"16-64 bytes of high entropy".as_slice();
+ /// # let master_key = hd_wallet::slip10::derive_master_key::(seed)?;
+ /// # let master_key_pair = hd_wallet::ExtendedKeyPair::from(master_key);
+ ///
+ /// let child_key = hd_wallet::Slip10::derive_child_key_pair_with_path(
+ /// &master_key_pair,
+ /// [1, 10, 1 + hd_wallet::H],
+ /// );
+ /// # Ok::<(), Box>(())
+ /// ```
+ fn derive_child_key_pair_with_path(
+ parent_key: &ExtendedKeyPair,
+ path: impl IntoIterator
- >,
+ ) -> ExtendedKeyPair {
+ let result = Self::try_derive_child_key_pair_with_path(
+ parent_key,
+ path.into_iter().map(Ok::<_, core::convert::Infallible>),
+ );
+ match result {
+ Ok(key) => key,
+ Err(err) => match err {},
+ }
+ }
+
+ /// Derives a child public key with specified derivation path
+ ///
+ /// Derivation path is a fallible iterator that yields child indexes. If iterator
+ /// yields an error, it's propagated to the caller.
+ ///
+ /// Returns:
+ /// * `Ok(child_pk)` if derivation was successful
+ /// * `Err(index_err)` if path contained `Err(index_err)`
+ ///
+ /// ### Example
+ /// Parse a path from the string and derive a child without extra allocations:
+ /// ```rust
+ /// use hd_wallet::HdWallet;
+ /// # let seed = b"16-64 bytes of high entropy".as_slice();
+ /// # let master_key = hd_wallet::slip10::derive_master_key::(seed)?;
+ /// # let master_public_key = hd_wallet::ExtendedPublicKey::from(&master_key);
+ ///
+ /// let path = "1/10/2";
+ /// let child_indexes = path.split('/').map(str::parse);
+ /// let child_key = hd_wallet::Slip10::try_derive_child_public_key_with_path(
+ /// &master_public_key,
+ /// child_indexes,
+ /// )?;
+ /// # Ok::<_, Box>(())
+ /// ```
+ fn try_derive_child_public_key_with_path(
+ parent_public_key: &ExtendedPublicKey,
+ path: impl IntoIterator
- >,
+ ) -> Result, Err> {
+ let mut derived_key = *parent_public_key;
+ for child_index in path {
+ derived_key = Self::derive_child_public_key(&derived_key, child_index?);
+ }
+ Ok(derived_key)
+ }
+
+ /// Derives a child public key with specified derivation path
+ ///
+ /// Derivation path is an iterator that yields child indexes.
+ ///
+ /// If derivation path is empty, `parent_public_key` is returned
+ ///
+ /// ### Example
+ /// Derive a child key with path m/1/10
+ /// ```rust
+ /// use hd_wallet::HdWallet;
+ /// # let seed = b"16-64 bytes of high entropy".as_slice();
+ /// # let master_key = hd_wallet::slip10::derive_master_key::(seed)?;
+ /// # let master_public_key = hd_wallet::ExtendedPublicKey::from(&master_key);
+ ///
+ /// let child_key = hd_wallet::Slip10::derive_child_public_key_with_path(
+ /// &master_public_key,
+ /// [1.try_into()?, 10.try_into()?],
+ /// );
+ /// # Ok::<(), Box>(())
+ /// ```
+ fn derive_child_public_key_with_path(
+ parent_public_key: &ExtendedPublicKey,
+ path: impl IntoIterator
- ,
+ ) -> ExtendedPublicKey {
+ let result = Self::try_derive_child_public_key_with_path(
+ parent_public_key,
+ path.into_iter().map(Ok::<_, core::convert::Infallible>),
+ );
+ match result {
+ Ok(key) => key,
+ Err(err) => match err {},
+ }
}
}
-/// Derives a child key pair with specified derivation path from parent key pair
+impl> HdWallet for S {}
+
+/// Core functionality of HD wallet derivation, everything is defined on top of it
+pub trait DeriveShift {
+ /// Derives a shift for non-hardened child
+ ///
+ /// We support only HD derivations that are always defined. This function may not panic.
+ fn derive_public_shift(
+ parent_public_key: &ExtendedPublicKey,
+ child_index: NonHardenedIndex,
+ ) -> DerivedShift;
+
+ /// Derive a shift for hardened child
+ ///
+ /// We support only HD derivations that are always defined. This function may not panic.
+ fn derive_hardened_shift(
+ parent_key: &ExtendedKeyPair,
+ child_index: HardenedIndex,
+ ) -> DerivedShift;
+}
+
+/// SLIP10-like HD wallet derivation
///
-/// Derivation path is an iterator that yields child indexes.
+/// `Slip10Like` is generalization of [`Slip10`], which is defined for any curve that meets
+/// constraints listed below.
///
-/// If derivation path is empty, `parent_key` is returned
+/// When `Slip10Like` is instantiated with secp256k1 or secp256r1 curves, it follows exactly
+/// SLIP10 derivation rules.
///
-/// ### Example
-/// Derive a child key with path m/1/10/1H
-/// ```rust
-/// use slip_10::supported_curves::Secp256k1;
-/// # let seed = b"16-64 bytes of high entropy".as_slice();
-/// let master_key = slip_10::derive_master_key::(seed)?;
-/// let master_key_pair = slip_10::ExtendedKeyPair::from(master_key);
+/// ## Constraints
+/// `Slip10Like` must be used with curves which operate on 32 bytes scalars.
///
-/// let child_key = slip_10::derive_child_key_pair_with_path(
-/// &master_key_pair,
-/// [1, 10, 1 + slip_10::H],
-/// );
-/// # Ok::<(), Box>(())
-/// ```
-pub fn derive_child_key_pair_with_path(
- parent_key: &ExtendedKeyPair,
- path: impl IntoIterator
- >,
-) -> ExtendedKeyPair {
- let result = try_derive_child_key_pair_with_path(
- parent_key,
- path.into_iter().map(Ok::<_, core::convert::Infallible>),
- );
- match result {
- Ok(key) => key,
- Err(err) => match err {},
+/// `Slip10Like` is not recommended to be used with curves with order significantly lower
+/// than $2^{256}$ (e.g. ed25519) as it worsens the performance.
+///
+/// ### Ed25519 curve
+/// Although `Slip10Like` will work on ed25519 curve, we do not recommend using it, because:
+/// 1. it's confusing as ed25519 curve is defined in SLIP10, however,
+/// `Slip10Like` will not follow SLIP10 standard
+/// 2. it's quite inefficient
+///
+/// Prefer using [`Edwards`] derivation method for ed25519 curve.
+pub struct Slip10Like;
+
+impl DeriveShift for Slip10Like {
+ fn derive_public_shift(
+ parent_public_key: &ExtendedPublicKey,
+ child_index: NonHardenedIndex,
+ ) -> DerivedShift {
+ let hmac = HmacSha512::new_from_slice(&parent_public_key.chain_code)
+ .expect("this never fails: hmac can handle keys of any size");
+ let i = hmac
+ .clone()
+ .chain_update(parent_public_key.public_key.to_bytes(true))
+ .chain_update(child_index.to_be_bytes())
+ .finalize()
+ .into_bytes();
+ Self::calculate_shift(&hmac, parent_public_key, *child_index, i)
+ }
+
+ fn derive_hardened_shift(
+ parent_key: &ExtendedKeyPair,
+ child_index: HardenedIndex,
+ ) -> DerivedShift {
+ let hmac = HmacSha512::new_from_slice(parent_key.chain_code())
+ .expect("this never fails: hmac can handle keys of any size");
+ let i = hmac
+ .clone()
+ .chain_update([0x00])
+ .chain_update(parent_key.secret_key.secret_key.as_ref().to_be_bytes())
+ .chain_update(child_index.to_be_bytes())
+ .finalize()
+ .into_bytes();
+ Self::calculate_shift(&hmac, &parent_key.public_key, *child_index, i)
}
}
-/// Derives a child key pair with specified derivation path from parent key pair
-///
-/// Derivation path is a fallible iterator that yields child indexes. If iterator
-/// yields an error, it's propagated to the caller.
-///
-/// ### Example
-/// Parse a path from the string and derive a child without extra allocations:
-/// ```rust
-/// use slip_10::supported_curves::Secp256k1;
-/// # let seed = b"16-64 bytes of high entropy".as_slice();
-/// let master_key = slip_10::derive_master_key::(seed)?;
-/// let master_key_pair = slip_10::ExtendedKeyPair::from(master_key);
-///
-/// let path = "1/10/2";
-/// let child_indexes = path.split('/').map(str::parse::);
-/// let child_key = slip_10::try_derive_child_key_pair_with_path(
-/// &master_key_pair,
-/// child_indexes,
-/// )?;
-/// # Ok::<_, Box>(())
-/// ```
-pub fn try_derive_child_key_pair_with_path(
- parent_key: &ExtendedKeyPair,
- path: impl IntoIterator
- , Err>>,
-) -> Result, Err> {
- let mut derived_key = parent_key.clone();
- for child_index in path {
- derived_key = derive_child_key_pair(&derived_key, child_index?);
+impl Slip10Like {
+ fn calculate_shift(
+ hmac: &HmacSha512,
+ parent_public_key: &ExtendedPublicKey,
+ child_index: u32,
+ mut i: hmac::digest::Output,
+ ) -> DerivedShift {
+ loop {
+ let (i_left, i_right) = split_into_two_halves(&i);
+
+ if let Ok(shift) = Scalar::::from_be_bytes(i_left) {
+ let child_pk = parent_public_key.public_key + Point::generator() * shift;
+ if !child_pk.is_zero() {
+ return DerivedShift {
+ shift,
+ child_public_key: ExtendedPublicKey {
+ public_key: child_pk,
+ chain_code: (*i_right).into(),
+ },
+ };
+ }
+ }
+
+ i = hmac
+ .clone()
+ .chain_update([0x01])
+ .chain_update(i_right)
+ .chain_update(child_index.to_be_bytes())
+ .finalize()
+ .into_bytes()
+ }
}
- Ok(derived_key)
}
-/// Derives child extended public key from parent extended public key
+/// [SLIP10][slip10-spec] HD wallet derivation
///
-/// ### Example
-/// Derive a master public key m/1
-/// ```rust
-/// use slip_10::supported_curves::Secp256k1;
+/// Performs HD derivation as defined in the spec. Only supports secp256k1 and secp256r1 curves.
///
-/// # let seed = b"do not use this seed :)".as_slice();
-/// let master_key = slip_10::derive_master_key::(seed)?;
-/// let master_public_key = slip_10::ExtendedPublicKey::from(&master_key);
-///
-/// let derived_key = slip_10::derive_child_public_key(
-/// &master_public_key,
-/// 1.try_into()?,
-/// );
-/// # Ok::<(), Box>(())
-/// ```
-pub fn derive_child_public_key(
- parent_public_key: &ExtendedPublicKey,
- child_index: NonHardenedIndex,
-) -> ExtendedPublicKey {
- derive_public_shift(parent_public_key, child_index).child_public_key
-}
-
-/// Derives a child public key with specified derivation path
+/// ## Limitations
+/// We do not support SLIP10 instantiated with ed25519 or curve25519 due to the limitations.
+/// Ed25519 and curve25519 are special-cases in SLIP10 standard, they only support hardened
+/// derivation, and they operate on EdDSA and X25519 private keys instead of elliptic points
+/// and scalars as in other cases. This library only supports HD derivations in which
+/// secret keys are represented as scalars and public keys as points, see [`ExtendedSecretKey`]
+/// and [`ExtendedPublicKey`].
///
-/// Derivation path is an iterator that yields child indexes.
+/// If you need HD derivation on Ed25519 curve, we recommend using [`Edwards`] HD derivation,
+/// which supports both hardened and non-hardened derivation.
///
-/// If derivation path is empty, `parent_public_key` is returned
+/// ## Master key derivation from the seed
+/// [`slip10::derive_master_key`] can be used to derive a master key from the seed as defined
+/// in the spec.
///
-/// ### Example
-/// Derive a child key with path m/1/10
+/// ## Example
+/// Derive a master key from the seed, and then derive a child key m/1H/10:
/// ```rust
-/// use slip_10::supported_curves::Secp256k1;
-/// # let seed = b"16-64 bytes of high entropy".as_slice();
-/// let master_key = slip_10::derive_master_key::(seed)?;
-/// let master_public_key = slip_10::ExtendedPublicKey::from(&master_key);
+/// use hd_wallet::{HdWallet, Slip10, curves::Secp256k1};
+///
+/// let seed = b"16-64 bytes of high entropy".as_slice();
+/// let master_key = hd_wallet::slip10::derive_master_key::(seed)?;
+/// let master_key_pair = hd_wallet::ExtendedKeyPair::from(master_key);
///
-/// let child_key = slip_10::derive_child_public_key_with_path(
-/// &master_public_key,
-/// [1.try_into()?, 10.try_into()?],
+/// let child_key_pair = Slip10::derive_child_key_pair_with_path(
+/// &master_key_pair,
+/// [1 + hd_wallet::H, 10],
/// );
/// # Ok::<(), Box>(())
/// ```
-pub fn derive_child_public_key_with_path(
- parent_public_key: &ExtendedPublicKey,
- path: impl IntoIterator
- ,
-) -> ExtendedPublicKey {
- let result = try_derive_child_public_key_with_path(
- parent_public_key,
- path.into_iter().map(Ok::<_, core::convert::Infallible>),
- );
- match result {
- Ok(key) => key,
- Err(err) => match err {},
+///
+/// ## SLIP10-like derivation
+/// SLIP10 is only defined for a few curves, but it can be extended to support any curve.
+/// See [`Slip10Like`] if you need other curves than is supported by SLIP10.
+///
+/// [slip10-spec]: https://github.com/satoshilabs/slips/blob/master/slip-0010.md
+pub struct Slip10;
+
+#[cfg(feature = "curve-secp256k1")]
+impl DeriveShift for Slip10 {
+ fn derive_public_shift(
+ parent_public_key: &ExtendedPublicKey,
+ child_index: NonHardenedIndex,
+ ) -> DerivedShift {
+ Slip10Like::derive_public_shift(parent_public_key, child_index)
+ }
+ fn derive_hardened_shift(
+ parent_key: &ExtendedKeyPair,
+ child_index: HardenedIndex,
+ ) -> DerivedShift {
+ Slip10Like::derive_hardened_shift(parent_key, child_index)
+ }
+}
+#[cfg(feature = "curve-secp256r1")]
+impl DeriveShift for Slip10 {
+ fn derive_public_shift(
+ parent_public_key: &ExtendedPublicKey,
+ child_index: NonHardenedIndex,
+ ) -> DerivedShift {
+ Slip10Like::derive_public_shift(parent_public_key, child_index)
+ }
+ fn derive_hardened_shift(
+ parent_key: &ExtendedKeyPair,
+ child_index: HardenedIndex,
+ ) -> DerivedShift {
+ Slip10Like::derive_hardened_shift(parent_key, child_index)
}
}
-/// Derives a child public key with specified derivation path
+/// Splits array `I` of 64 bytes into two arrays `I_L = I[..32]` and `I_R = I[32..]`
+fn split_into_two_halves(
+ i: &GenericArray,
+) -> (&GenericArray, &GenericArray) {
+ generic_array::sequence::Split::split(i)
+}
+
+/// HD derivation for Ed25519 curve
///
-/// Derivation path is a fallible iterator that yields child indexes. If iterator
-/// yields an error, it's propagated to the caller.
+/// This type of derivation isn't defined in any known to us standards, but it can be often
+/// found in other libraries. It is secure and efficient (much more efficient than using
+/// [`Slip10Like`](Slip10Like), for instance).
///
-/// ### Example
-/// Parse a path from the string and derive a child without extra allocations:
+/// ## Example
/// ```rust
-/// use slip_10::supported_curves::Secp256k1;
-/// # let seed = b"16-64 bytes of high entropy".as_slice();
-/// let master_key = slip_10::derive_master_key::(seed)?;
-/// let master_public_key = slip_10::ExtendedPublicKey::from(&master_key);
+/// use hd_wallet::{HdWallet, Edwards, curves::Ed25519};
///
-/// let path = "1/10/2";
-/// let child_indexes = path.split('/').map(str::parse);
-/// let child_key = slip_10::try_derive_child_public_key_with_path(
-/// &master_public_key,
-/// child_indexes,
-/// )?;
-/// # Ok::<_, Box>(())
+/// # fn load_key() -> hd_wallet::ExtendedKeyPair {
+/// # hd_wallet::ExtendedSecretKey {
+/// # secret_key: generic_ec::SecretScalar::random(&mut rand::rngs::OsRng),
+/// # chain_code: rand::Rng::gen(&mut rand::rngs::OsRng),
+/// # }.into()
+/// # }
+/// #
+/// let parent_key: hd_wallet::ExtendedKeyPair = load_key();
+///
+/// let child_key_pair = Edwards::derive_child_key_pair_with_path(
+/// &parent_key,
+/// [1 + hd_wallet::H, 10],
+/// );
+/// # Ok::<(), Box>(())
/// ```
-pub fn try_derive_child_public_key_with_path(
- parent_public_key: &ExtendedPublicKey,
- path: impl IntoIterator
- >,
-) -> Result, Err> {
- let mut derived_key = *parent_public_key;
- for child_index in path {
- derived_key = derive_child_public_key(&derived_key, child_index?);
- }
- Ok(derived_key)
-}
-
-/// Derive a shift for hardened child
-pub fn derive_hardened_shift(
- parent_key: &ExtendedKeyPair,
- child_index: HardenedIndex,
-) -> DerivedShift {
- let hmac = HmacSha512::new_from_slice(parent_key.chain_code())
- .expect("this never fails: hmac can handle keys of any size");
- let i = hmac
- .clone()
- .chain_update([0x00])
- .chain_update(parent_key.secret_key.secret_key.as_ref().to_be_bytes())
- .chain_update(child_index.to_be_bytes())
- .finalize()
- .into_bytes();
- calculate_shift(&hmac, &parent_key.public_key, *child_index, i)
-}
-
-/// Derives a shift for non-hardened child
-pub fn derive_public_shift(
- parent_public_key: &ExtendedPublicKey,
- child_index: NonHardenedIndex,
-) -> DerivedShift {
- let hmac = HmacSha512::new_from_slice(&parent_public_key.chain_code)
- .expect("this never fails: hmac can handle keys of any size");
- let i = hmac
- .clone()
- .chain_update(&parent_public_key.public_key.to_bytes(true))
- .chain_update(child_index.to_be_bytes())
- .finalize()
- .into_bytes();
- calculate_shift(&hmac, parent_public_key, *child_index, i)
-}
-
-fn calculate_shift(
- hmac: &HmacSha512,
- parent_public_key: &ExtendedPublicKey,
- child_index: u32,
- mut i: hmac::digest::Output,
-) -> DerivedShift {
- loop {
- let (i_left, i_right) = split_into_two_halfes(&i);
-
- if let Ok(shift) = Scalar::::from_be_bytes(i_left) {
- let child_pk = parent_public_key.public_key + Point::generator() * shift;
- if !child_pk.is_zero() {
- return DerivedShift {
- shift,
- child_public_key: ExtendedPublicKey {
- public_key: child_pk,
- chain_code: (*i_right).into(),
- },
- };
- }
- }
+pub struct Edwards;
- i = hmac
+#[cfg(feature = "curve-ed25519")]
+impl DeriveShift for Edwards {
+ fn derive_public_shift(
+ parent_public_key: &ExtendedPublicKey,
+ child_index: NonHardenedIndex,
+ ) -> DerivedShift {
+ let hmac = HmacSha512::new_from_slice(&parent_public_key.chain_code)
+ .expect("this never fails: hmac can handle keys of any size");
+ let i = hmac
.clone()
- .chain_update([0x01])
- .chain_update(i_right)
+ .chain_update(parent_public_key.public_key.to_bytes(true))
+ // we append 0 byte to the public key for compatibility with other libs
+ .chain_update([0x00])
.chain_update(child_index.to_be_bytes())
.finalize()
- .into_bytes()
+ .into_bytes();
+ Self::calculate_shift(parent_public_key, i)
+ }
+
+ fn derive_hardened_shift(
+ parent_key: &ExtendedKeyPair,
+ child_index: HardenedIndex,
+ ) -> DerivedShift {
+ let hmac = HmacSha512::new_from_slice(parent_key.chain_code())
+ .expect("this never fails: hmac can handle keys of any size");
+ let i = hmac
+ .clone()
+ .chain_update([0x00])
+ .chain_update(parent_key.secret_key.secret_key.as_ref().to_be_bytes())
+ .chain_update(child_index.to_be_bytes())
+ .finalize()
+ .into_bytes();
+ Self::calculate_shift(&parent_key.public_key, i)
}
}
-/// Splits array `I` of 64 bytes into two arrays `I_L = I[..32]` and `I_R = I[32..]`
-fn split_into_two_halfes(
- i: &GenericArray,
-) -> (&GenericArray, &GenericArray) {
- generic_array::sequence::Split::split(i)
+#[cfg(feature = "curve-ed25519")]
+impl Edwards {
+ fn calculate_shift(
+ parent_public_key: &ExtendedPublicKey,
+ i: hmac::digest::Output,
+ ) -> DerivedShift {
+ let (i_left, i_right) = split_into_two_halves(&i);
+
+ let shift = Scalar::from_be_bytes_mod_order(i_left);
+ let child_pk = parent_public_key.public_key + Point::generator() * shift;
+
+ DerivedShift {
+ shift,
+ child_public_key: ExtendedPublicKey {
+ public_key: child_pk,
+ chain_code: (*i_right).into(),
+ },
+ }
+ }
}
diff --git a/src/slip10.rs b/src/slip10.rs
new file mode 100644
index 0000000..2db3ee9
--- /dev/null
+++ b/src/slip10.rs
@@ -0,0 +1,105 @@
+//! SLIP10 derivation
+//!
+//! [SLIP10][slip10-spec] is a specification for implementing HD wallets. It aims at supporting many
+//! curves while being compatible with [BIP32][bip32-spec].
+//!
+//! Refer to [`Slip10`] docs to learn more about the derivation method.
+//!
+//! This module provides [`derive_master_key`] function that can be used to derive a master key
+//! from the seed, as well as aliases for calling `::*` methods for convenience
+//! when you don't need to support generic HD derivation algorithm.
+//!
+//! [slip10-spec]: https://github.com/satoshilabs/slips/blob/master/slip-0010.md
+//! [bip32-spec]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
+
+use hmac::Mac as _;
+
+pub use crate::Slip10;
+
+/// Marker for a curve supported by SLIP10 specs and this library
+///
+/// Only implement this trait for the curves that are supported by SLIP10 specs.
+/// Curves provided by the crate out-of-box in [curves](crate::curves) module already
+/// implement this trait.
+///
+/// Note: this library does not support ed25519 or curve25519 key types to
+/// be used with slip10. Only secp256k1 and secp256r1 are supported.
+pub trait SupportedCurve {
+ /// Specifies which curve it is
+ const CURVE_TYPE: CurveType;
+}
+#[cfg(feature = "curve-secp256k1")]
+impl SupportedCurve for generic_ec::curves::Secp256k1 {
+ const CURVE_TYPE: CurveType = CurveType::Secp256k1;
+}
+#[cfg(feature = "curve-secp256r1")]
+impl SupportedCurve for generic_ec::curves::Secp256r1 {
+ const CURVE_TYPE: CurveType = CurveType::Secp256r1;
+}
+
+/// Curves supported by SLIP-10 spec
+///
+/// It's either secp256k1 or secp256r1. Note that SLIP-10 also supports ed25519 curve, but this library
+/// does not support it.
+///
+/// `CurveType` is only needed for master key derivation.
+#[derive(Clone, Copy, Debug)]
+pub enum CurveType {
+ /// Secp256k1 curve
+ Secp256k1,
+ /// Secp256r1 curve
+ Secp256r1,
+}
+
+/// Derives a master key from the seed
+///
+/// Seed must be 16-64 bytes long, otherwise an error is returned
+pub fn derive_master_key(
+ seed: &[u8],
+) -> Result, crate::errors::InvalidLength> {
+ let curve_tag = match E::CURVE_TYPE {
+ CurveType::Secp256k1 => "Bitcoin seed",
+ CurveType::Secp256r1 => "Nist256p1 seed",
+ };
+ derive_master_key_with_curve_tag(curve_tag.as_bytes(), seed)
+}
+
+/// Derives a master key from the seed and the curve tag as defined in SLIP10
+///
+/// It's preferred to use [derive_master_key] instead, as it automatically infers
+/// the curve tag for supported curves. The curve tag is not validated by the function,
+/// it's caller's responsibility to make sure that it complies with SLIP10.
+///
+/// Seed must be 16-64 bytes long, otherwise an error is returned
+pub fn derive_master_key_with_curve_tag(
+ curve_tag: &[u8],
+ seed: &[u8],
+) -> Result, crate::errors::InvalidLength> {
+ if !(16 <= seed.len() && seed.len() <= 64) {
+ return Err(crate::errors::InvalidLength);
+ }
+
+ let hmac = crate::HmacSha512::new_from_slice(curve_tag)
+ .expect("this never fails: hmac can handle keys of any size");
+ let mut i = hmac.clone().chain_update(seed).finalize().into_bytes();
+
+ loop {
+ let (i_left, i_right) = crate::split_into_two_halves(&i);
+
+ if let Ok(mut sk) = generic_ec::Scalar::::from_be_bytes(i_left) {
+ if !bool::from(subtle::ConstantTimeEq::ct_eq(
+ &sk,
+ &generic_ec::Scalar::zero(),
+ )) {
+ return Ok(crate::ExtendedSecretKey {
+ secret_key: generic_ec::SecretScalar::new(&mut sk),
+ chain_code: (*i_right).into(),
+ });
+ }
+ }
+
+ i = hmac.clone().chain_update(&i[..]).finalize().into_bytes()
+ }
+}
+
+super::create_aliases!(Slip10, slip10);
diff --git a/tests/edwards_test_vector.rs b/tests/edwards_test_vector.rs
new file mode 100644
index 0000000..5a9af77
--- /dev/null
+++ b/tests/edwards_test_vector.rs
@@ -0,0 +1,156 @@
+use hd_wallet::HdWallet;
+use hex_literal::hex;
+
+struct TestVector {
+ root_secret_key: [u8; 32],
+ root_public_key: [u8; 32],
+ chain_code: hd_wallet::ChainCode,
+ derivations: &'static [Derivation],
+}
+
+struct Derivation {
+ path: &'static [u32],
+
+ expected_secret_key: [u8; 32],
+ expected_public_key: [u8; 32],
+}
+
+const TEST_VECTORS: &[TestVector] = &[TestVector {
+ root_secret_key: hex!("09ba1ad29fabe87a0cf23fec142db2adfb8f9e7089928000dcba5714e08236ec"),
+ root_public_key: hex!("6fa093b0e855f5fdb40d77f6efe9b67b709092a71d73f35de6afc70cac40d57a"),
+ chain_code: hex!("64ae4b48b206ef11f75059af10d209546586baf8418222c6f4b989b75d008ddd"),
+ derivations: &[
+ // Non-hardened derivation
+ Derivation {
+ path: &[0],
+ expected_secret_key: hex!(
+ "0542fcce4962a2e1785bc9d183e6d80f754d7ad70251b7d9239b434bc6b117d4"
+ ),
+ expected_public_key: hex!(
+ "4fefbecb1d4a584b289e457ffe868d87b27ab421f66e32a078616552a837c7e2"
+ ),
+ },
+ Derivation {
+ path: &[1],
+ expected_secret_key: hex!(
+ "0a25f8ffae098918dff9b24afbe8dae365015f14b4b559f5097684747a62ec97"
+ ),
+ expected_public_key: hex!(
+ "0dc358776f744c4d6c282dbca128d123a0ac38fd7cda5cf87805b82c70a0e2cd"
+ ),
+ },
+ Derivation {
+ path: &[2],
+ expected_secret_key: hex!(
+ "0b68998b58f5b96ec64b754d421339869a439c6249b843e78610c675d51f01f9"
+ ),
+ expected_public_key: hex!(
+ "d634d478dfb41b9bc8cc6653febd00f89bf5bf8c6f665dbdf541a203abef0882"
+ ),
+ },
+ Derivation {
+ path: &[1245290303, 456055179, 1419108629, 261968456],
+ expected_secret_key: hex!(
+ "089f32db21f3027a39ee9a6bebae1ffa0bd07527120f5fe943a7d6363bd90ff6"
+ ),
+ expected_public_key: hex!(
+ "4671c7c639c8421d16488a59618bc4d06dbae56741df740eea6be993eb99f734"
+ ),
+ },
+ Derivation {
+ path: &[1478344788, 731157828, 912233245, 1553129543],
+ expected_secret_key: hex!(
+ "04fe0f016a5b070f49f5b8f76de8862f5520661461b7914463d9ecd81a893f90"
+ ),
+ expected_public_key: hex!(
+ "d983da0a4f2a368bbc5ada8af0c5a003adea602c2e7ad1feca60c73401dc606e"
+ ),
+ },
+ // Hardened derivation
+ Derivation {
+ path: &[0 + hd_wallet::H],
+ expected_secret_key: hex!(
+ "098b5d8be3cd71cecf390facd083ca0e3e03cc78a10920094e2cee300f8de291"
+ ),
+ expected_public_key: hex!(
+ "5963e6410d44538fec067ff59d54814de6dfd5daf03d693c655f44e2fd89ae86"
+ ),
+ },
+ Derivation {
+ path: &[1 + hd_wallet::H],
+ expected_secret_key: hex!(
+ "06928b571aa8659d2976ab000e27f962b62b9d4e61ce6ab76380bd5f9ab6b1f9"
+ ),
+ expected_public_key: hex!(
+ "97d28095b4cc43ef45eb10da1b5c01ff85a0695472252f218c93f21a3ebe8a42"
+ ),
+ },
+ Derivation {
+ path: &[2 + hd_wallet::H],
+ expected_secret_key: hex!(
+ "009193ef5345093a2c787c93ff3099731a605ffde2836cbc5d4979ed9a20a3be"
+ ),
+ expected_public_key: hex!(
+ "d98a33fcb65f6ace1c6599c5895c8cee338d34f6fd21f883f306086e2e0af2bf"
+ ),
+ },
+ // Mixed hardened and non-hardened derivation
+ Derivation {
+ path: &[2805853951, 2012627329, 3396580781, 1663824773],
+ expected_secret_key: hex!(
+ "0f0e4dfa88132151409f014584d112152dbd78c238afc1fa095cc852c49ffd46"
+ ),
+ expected_public_key: hex!(
+ "51b8f57fa35e2d95ed518dc9c0defcc7268e600781cb5d65f20f1e2898c92905"
+ ),
+ },
+ Derivation {
+ path: &[3136119273, 140597163, 2240167577, 148040763],
+ expected_secret_key: hex!(
+ "08019f789391f195786891702464f7302e669c51f3e8af7c7f6f46f8d14b0182"
+ ),
+ expected_public_key: hex!(
+ "3b2cbb00f208011f4322a6c09020a437b1676e86e83f5d6953f94f3b1f0d4a39"
+ ),
+ },
+ ],
+}];
+
+#[test]
+fn test_vectors() {
+ for vector in TEST_VECTORS {
+ let mut root_sk = generic_ec::Scalar::::from_be_bytes(
+ &vector.root_secret_key,
+ )
+ .expect("invalid root_sk");
+ let root_sk = generic_ec::SecretScalar::new(&mut root_sk);
+
+ let esk = hd_wallet::ExtendedSecretKey {
+ secret_key: root_sk,
+ chain_code: vector.chain_code,
+ };
+ let ekey = hd_wallet::ExtendedKeyPair::from(esk);
+
+ assert_eq!(
+ hex::encode(ekey.public_key().public_key.to_bytes(true)),
+ hex::encode(vector.root_public_key)
+ );
+
+ for derivation in vector.derivations {
+ eprintln!("path: {:?}", derivation.path);
+ let child_key = hd_wallet::Edwards::derive_child_key_pair_with_path(
+ &ekey,
+ derivation.path.iter().copied(),
+ );
+
+ assert_eq!(
+ hex::encode(child_key.secret_key().secret_key.as_ref().to_be_bytes()),
+ hex::encode(derivation.expected_secret_key)
+ );
+ assert_eq!(
+ hex::encode(child_key.public_key().public_key.to_bytes(true)),
+ hex::encode(derivation.expected_public_key)
+ );
+ }
+ }
+}
diff --git a/tests/test_vectors.rs b/tests/slip10_test_vector.rs
similarity index 86%
rename from tests/test_vectors.rs
rename to tests/slip10_test_vector.rs
index d17fcd4..c8cdb15 100644
--- a/tests/test_vectors.rs
+++ b/tests/slip10_test_vector.rs
@@ -2,7 +2,7 @@ use generic_ec::Curve;
use hex_literal::hex;
struct TestVector {
- curve_type: slip_10::CurveType,
+ curve_type: hd_wallet::slip10::CurveType,
seed: &'static [u8],
derivations: &'static [Derivation],
}
@@ -10,7 +10,7 @@ struct TestVector {
struct Derivation {
path: &'static [u32],
- expected_chain_code: slip_10::ChainCode,
+ expected_chain_code: hd_wallet::ChainCode,
expected_secret_key: [u8; 32],
expected_public_key: [u8; 33],
}
@@ -21,7 +21,7 @@ const TEST_VECTORS: &[TestVector] = &[
// Test vector 1 for secp256k1
TestVector {
seed: &hex!("000102030405060708090a0b0c0d0e0f"),
- curve_type: slip_10::CurveType::Secp256k1,
+ curve_type: hd_wallet::slip10::CurveType::Secp256k1,
derivations: &[
Derivation {
path: &[],
@@ -36,7 +36,7 @@ const TEST_VECTORS: &[TestVector] = &[
),
},
Derivation {
- path: &[0 + slip_10::H],
+ path: &[0 + hd_wallet::H],
expected_chain_code: hex!(
"47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141"
),
@@ -48,7 +48,7 @@ const TEST_VECTORS: &[TestVector] = &[
),
},
Derivation {
- path: &[0 + slip_10::H, 1],
+ path: &[0 + hd_wallet::H, 1],
expected_chain_code: hex!(
"2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19"
),
@@ -60,7 +60,7 @@ const TEST_VECTORS: &[TestVector] = &[
),
},
Derivation {
- path: &[0 + slip_10::H, 1, 2 + slip_10::H],
+ path: &[0 + hd_wallet::H, 1, 2 + hd_wallet::H],
expected_chain_code: hex!(
"04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f"
),
@@ -72,7 +72,7 @@ const TEST_VECTORS: &[TestVector] = &[
),
},
Derivation {
- path: &[0 + slip_10::H, 1, 2 + slip_10::H, 2],
+ path: &[0 + hd_wallet::H, 1, 2 + hd_wallet::H, 2],
expected_chain_code: hex!(
"cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd"
),
@@ -84,7 +84,7 @@ const TEST_VECTORS: &[TestVector] = &[
),
},
Derivation {
- path: &[0 + slip_10::H, 1, 2 + slip_10::H, 2, 1000000000],
+ path: &[0 + hd_wallet::H, 1, 2 + hd_wallet::H, 2, 1000000000],
expected_chain_code: hex!(
"c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e"
),
@@ -99,7 +99,7 @@ const TEST_VECTORS: &[TestVector] = &[
},
// Test vector 1 for nist256p1
TestVector {
- curve_type: slip_10::CurveType::Secp256r1,
+ curve_type: hd_wallet::slip10::CurveType::Secp256r1,
seed: &hex!("000102030405060708090a0b0c0d0e0f"),
derivations: &[
Derivation {
@@ -115,7 +115,7 @@ const TEST_VECTORS: &[TestVector] = &[
),
},
Derivation {
- path: &[0 + slip_10::H],
+ path: &[0 + hd_wallet::H],
expected_chain_code: hex!(
"3460cea53e6a6bb5fb391eeef3237ffd8724bf0a40e94943c98b83825342ee11"
),
@@ -127,7 +127,7 @@ const TEST_VECTORS: &[TestVector] = &[
),
},
Derivation {
- path: &[0 + slip_10::H, 1],
+ path: &[0 + hd_wallet::H, 1],
expected_chain_code: hex!(
"4187afff1aafa8445010097fb99d23aee9f599450c7bd140b6826ac22ba21d0c"
),
@@ -139,7 +139,7 @@ const TEST_VECTORS: &[TestVector] = &[
),
},
Derivation {
- path: &[0 + slip_10::H, 1, 2 + slip_10::H],
+ path: &[0 + hd_wallet::H, 1, 2 + hd_wallet::H],
expected_chain_code: hex!(
"98c7514f562e64e74170cc3cf304ee1ce54d6b6da4f880f313e8204c2a185318"
),
@@ -151,7 +151,7 @@ const TEST_VECTORS: &[TestVector] = &[
),
},
Derivation {
- path: &[0 + slip_10::H, 1, 2 + slip_10::H, 2],
+ path: &[0 + hd_wallet::H, 1, 2 + hd_wallet::H, 2],
expected_chain_code: hex!(
"ba96f776a5c3907d7fd48bde5620ee374d4acfd540378476019eab70790c63a0"
),
@@ -163,7 +163,7 @@ const TEST_VECTORS: &[TestVector] = &[
),
},
Derivation {
- path: &[0 + slip_10::H, 1, 2 + slip_10::H, 2, 1000000000],
+ path: &[0 + hd_wallet::H, 1, 2 + hd_wallet::H, 2, 1000000000],
expected_chain_code: hex!(
"b9b7b82d326bb9cb5b5b121066feea4eb93d5241103c9e7a18aad40f1dde8059"
),
@@ -178,7 +178,7 @@ const TEST_VECTORS: &[TestVector] = &[
},
// Test vector 2 for secp256k1
TestVector {
- curve_type: slip_10::CurveType::Secp256k1,
+ curve_type: hd_wallet::slip10::CurveType::Secp256k1,
seed: &hex!(
"fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a2
9f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"
@@ -209,7 +209,7 @@ const TEST_VECTORS: &[TestVector] = &[
),
},
Derivation {
- path: &[0, 2147483647 + slip_10::H],
+ path: &[0, 2147483647 + hd_wallet::H],
expected_chain_code: hex!(
"be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9"
),
@@ -221,7 +221,7 @@ const TEST_VECTORS: &[TestVector] = &[
),
},
Derivation {
- path: &[0, 2147483647 + slip_10::H, 1],
+ path: &[0, 2147483647 + hd_wallet::H, 1],
expected_chain_code: hex!(
"f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb"
),
@@ -233,7 +233,7 @@ const TEST_VECTORS: &[TestVector] = &[
),
},
Derivation {
- path: &[0, 2147483647 + slip_10::H, 1, 2147483646 + slip_10::H],
+ path: &[0, 2147483647 + hd_wallet::H, 1, 2147483646 + hd_wallet::H],
expected_chain_code: hex!(
"637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29"
),
@@ -245,7 +245,13 @@ const TEST_VECTORS: &[TestVector] = &[
),
},
Derivation {
- path: &[0, 2147483647 + slip_10::H, 1, 2147483646 + slip_10::H, 2],
+ path: &[
+ 0,
+ 2147483647 + hd_wallet::H,
+ 1,
+ 2147483646 + hd_wallet::H,
+ 2,
+ ],
expected_chain_code: hex!(
"9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271"
),
@@ -260,7 +266,7 @@ const TEST_VECTORS: &[TestVector] = &[
},
// Test derivation retry for nist256p1
TestVector {
- curve_type: slip_10::CurveType::Secp256r1,
+ curve_type: hd_wallet::slip10::CurveType::Secp256r1,
seed: &hex!("000102030405060708090a0b0c0d0e0f"),
derivations: &[
Derivation {
@@ -276,7 +282,7 @@ const TEST_VECTORS: &[TestVector] = &[
),
},
Derivation {
- path: &[28578 + slip_10::H],
+ path: &[28578 + hd_wallet::H],
expected_chain_code: hex!(
"e94c8ebe30c2250a14713212f6449b20f3329105ea15b652ca5bdfc68f6c65c2"
),
@@ -288,7 +294,7 @@ const TEST_VECTORS: &[TestVector] = &[
),
},
Derivation {
- path: &[28578 + slip_10::H, 33941],
+ path: &[28578 + hd_wallet::H, 33941],
expected_chain_code: hex!(
"9e87fe95031f14736774cd82f25fd885065cb7c358c1edf813c72af535e83071"
),
@@ -303,7 +309,7 @@ const TEST_VECTORS: &[TestVector] = &[
},
// Test seed retry for nist256p1
TestVector {
- curve_type: slip_10::CurveType::Secp256r1,
+ curve_type: hd_wallet::slip10::CurveType::Secp256r1,
seed: &hex!("a7305bc8df8d0951f0cb224c0e95d7707cbdf2c6ce7e8d481fec69c7ff5e9446"),
derivations: &[Derivation {
path: &[],
@@ -324,22 +330,24 @@ const TEST_VECTORS: &[TestVector] = &[
fn test_vectors() {
for vector in TEST_VECTORS {
match vector.curve_type {
- slip_10::CurveType::Secp256k1 => {
- run_vector::(vector)
+ hd_wallet::slip10::CurveType::Secp256k1 => {
+ run_vector::(vector)
}
- slip_10::CurveType::Secp256r1 => {
- run_vector::(vector)
+ hd_wallet::slip10::CurveType::Secp256r1 => {
+ run_vector::(vector)
}
}
}
}
-fn run_vector(v: &TestVector) {
- let master_key = slip_10::derive_master_key::(&v.seed).unwrap();
- let master_key_pair = slip_10::ExtendedKeyPair::from(master_key);
+fn run_vector(v: &TestVector) {
+ use hd_wallet::HdWallet;
+
+ let master_key = hd_wallet::slip10::derive_master_key::(&v.seed).unwrap();
+ let master_key_pair = hd_wallet::ExtendedKeyPair::from(master_key);
for derivation in v.derivations {
- let key = slip_10::derive_child_key_pair_with_path(
+ let key = hd_wallet::Slip10Like::derive_child_key_pair_with_path(
&master_key_pair,
derivation.path.iter().copied(),
);