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(), );