From 6026e32995ba1a6cb5613765e87ce1e07460a492 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Thu, 14 Dec 2023 15:45:31 +0000 Subject: [PATCH 01/13] Upload the first version --- .gitignore | 3 + Cargo.lock | 527 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 30 +++ Makefile | 11 + katex-header.html | 50 ++++ src/errors.rs | 29 +++ src/lib.rs | 432 ++++++++++++++++++++++++++++++++++ tests/test_vectors.rs | 358 ++++++++++++++++++++++++++++ 8 files changed, 1440 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Makefile create mode 100644 katex-header.html create mode 100644 src/errors.rs create mode 100644 src/lib.rs create mode 100644 tests/test_vectors.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c7fb4a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target + +.helix/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b2a4ab3 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,527 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-oid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "educe" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0042ff8246a363dbe77d2ceedb073339e85a804b9a47636c6e016a9a32c05f" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "enum-ordinalize" +version = "3.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf1fa3f06bbff1ea5b1a9c7b14aa992a39657db60a2759457328d7e058f49ee" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.41", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "generic-ec" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81960ce6c780f5a63f6ab4e94b3b34212f839ce7e7768b953413e4ee3c4d1438" +dependencies = [ + "generic-ec-core", + "generic-ec-curves", + "getrandom", + "phantom-type", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "generic-ec-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1193740c17f0324ea5d7db0635230f7538e5d33e0f9732af92d3f9fd4b87663e" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "generic-ec-curves" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cb0230c551d3f3c0d272092e961de40cdaf7661ad5560948f6a262b64cd9bc2" +dependencies = [ + "crypto-bigint", + "elliptic-curve", + "generic-ec-core", + "k256", + "p256", + "rand_core", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "k256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "phantom-type" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e68f5dc797c2a743e024e1c53215474598faf0408826a90249569ad7f47adeaa" +dependencies = [ + "educe", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slip10" +version = "0.1.0" +dependencies = [ + "generic-ec", + "hex-literal", + "hmac", + "sha2", + "subtle", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.41", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..29f5ab4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "slip10" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +generic-ec = { version = "0.1", default-features = false } + +hmac = { version = "0.12", default-features = false } +sha2 = { version = "0.10", default-features = false } +subtle = { version = "2", default-features = false } + +[dev-dependencies] +hex-literal = "0.4" + +[features] +std = [] +curve-secp256k1 = ["generic-ec/curve-secp256k1"] +curve-secp256r1 = ["generic-ec/curve-secp256r1"] +all-curves = ["curve-secp256k1", "curve-secp256r1"] + +[[test]] +name = "test_vectors" +required-features = ["all-curves"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..afc92f0 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +.PHONY: docs docs-open + +docs: + RUSTDOCFLAGS="--html-in-header katex-header.html" cargo +nightly doc --no-deps --all-features + +docs-open: + RUSTDOCFLAGS="--html-in-header katex-header.html" cargo +nightly doc --no-deps --all-features --open + +docs-private: + RUSTDOCFLAGS="--html-in-header katex-header.html" cargo +nightly doc --no-deps --all-features --document-private-items + diff --git a/katex-header.html b/katex-header.html new file mode 100644 index 0000000..9d8f376 --- /dev/null +++ b/katex-header.html @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..c5647f7 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,29 @@ +//! When something goes wrong + +use core::fmt; + +/// Length of the argument is not valid +#[derive(Debug)] +pub struct InvalidLength; + +impl fmt::Display for InvalidLength { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("invalid length") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for InvalidLength {} + +/// Value was out of range +#[derive(Debug)] +pub struct OutOfRange; + +impl fmt::Display for OutOfRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("out of range") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for OutOfRange {} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..519cec7 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,432 @@ +//! SLIP-10: Deterministic key generation +//! +//! [SLIP10] is a specification for implementing HD wallets. It aims at supporting many +//! curves while being compatible with [BIP32]. +//! +//! 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. +//! +//! ### 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 +//! +//! Derive a master key from the seed, and then derive a child key m/1H/10: +//! ```rust +//! use slip10::supported_curves::Secp256k1; +//! +//! let seed = b"16-64 bytes of high entropy".as_slice(); +//! let master_key = slip10::derive_master_key::( +//! slip10::CurveType::Secp256k1, +//! seed, +//! )?; +//! let master_key_pair = slip10::ExtendedKeyPair::from(master_key); +//! +//! let derivation_path = [1 + slip10::H, 10]; +//! let mut derived_key = master_key_pair; +//! for child_index in derivation_path { +//! derived_key = slip10::derive_child_key_pair( +//! &derived_key, +//! child_index, +//! ); +//! } +//! # Ok::<(), Box>(()) +//! ``` +//! +//! [SLIP10]: https://github.com/satoshilabs/slips/blob/master/slip-0010.md +//! [BIP32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki + +#![cfg_attr(not(feature = "std"), no_std)] +#![forbid(missing_docs)] + +use core::ops; + +use generic_ec::{Curve, Point, Scalar, SecretScalar}; +use hmac::Mac as _; + +#[cfg(any( + feature = "curve-secp256k1", + feature = "curve-secp256r1", + feature = "all-curves" +))] +pub use generic_ec::curves as supported_curves; + +pub mod errors; + +type HmacSha512 = hmac::Hmac; +/// Beggining 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 slip10::supported_curves::Secp256k1; +/// +/// # let seed = b"do not use this seed in prod :)".as_slice(); +/// let master_key = slip10::derive_master_key::( +/// slip10::CurveType::Secp256k1, +/// seed, +/// )?; +/// let master_key_pair = slip10::ExtendedKeyPair::from(master_key); +/// +/// let hardened_child = slip10::derive_child_key_pair( +/// &master_key_pair, +/// 1 + slip10::H, +/// ); +/// # +/// # Ok::<(), slip10::errors::InvalidLength>(()) +/// ``` +pub const H: u32 = 1 << 31; + +/// Child index, whether hardened or not +#[derive(Clone, Copy, Debug)] +pub enum ChildIndex { + /// Hardened index + Hardened(HardenedIndex), + /// Non-hardened index + NonHardened(NonHardenedIndex), +} + +/// Child index in range $2^{31} \le i < 2^{32}$ corresponing to a hardened wallet +#[derive(Clone, Copy, Debug)] +pub struct HardenedIndex(u32); + +/// Child index in range $0 \le i < 2^{31}$ corresponing to a non-hardened wallet +#[derive(Clone, Copy, Debug)] +pub struct NonHardenedIndex(u32); + +/// Extended public key +#[derive(Clone, Copy, Debug)] +pub struct ExtendedPublicKey { + /// The public key that can be used for signature verification + pub public_key: Point, + /// A chain code that is used to derive child keys + pub chain_code: ChainCode, +} + +/// Extended secret key +#[derive(Clone, Debug)] +pub struct ExtendedSecretKey { + /// The secret key that can be used for signing + pub secret_key: SecretScalar, + /// A chain code that is used to derive child keys + pub chain_code: ChainCode, +} + +/// Pair of extended secret and public keys +#[derive(Clone, Debug)] +pub struct ExtendedKeyPair { + public_key: ExtendedPublicKey, + secret_key: ExtendedSecretKey, +} + +/// A shift that can be applied to parent key to obtain a child key +/// +/// It contains an already derived child public key as it needs to be derived +/// in process of calculating the shift value +#[derive(Clone, Copy, Debug)] +pub struct DerivedShift { + /// Derived shift + pub shift: Scalar, + /// Derived child extended public key + pub child_public_key: ExtendedPublicKey, +} + +/// Chain code of extended key as defined in SLIP-10 +pub type ChainCode = [u8; 32]; + +impl HardenedIndex { + /// The smallest possible value of hardened index. Equals to $2^{31}$ + pub const MIN: Self = Self(H); + /// The largest possible value of hardened index. Equals to $2^{32} - 1$ + pub const MAX: Self = Self(u32::MAX); +} +impl NonHardenedIndex { + /// The smallest possible value of non-hardened index. Equals to $0$ + pub const MIN: Self = Self(0); + /// The largest possible value of non-hardened index. Equals to $2^{31} - 1$ + pub const MAX: Self = Self(H - 1); +} +impl ops::Deref for HardenedIndex { + type Target = u32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl ops::Deref for NonHardenedIndex { + type Target = u32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl ops::Deref for ChildIndex { + type Target = u32; + fn deref(&self) -> &Self::Target { + match self { + Self::Hardened(i) => &*i, + Self::NonHardened(i) => &*i, + } + } +} +impl From for ChildIndex { + fn from(value: u32) -> Self { + match value { + H.. => Self::Hardened(HardenedIndex(value)), + _ => Self::NonHardened(NonHardenedIndex(value)), + } + } +} +impl TryFrom for HardenedIndex { + type Error = errors::OutOfRange; + fn try_from(value: u32) -> Result { + match ChildIndex::from(value) { + ChildIndex::Hardened(v) => Ok(v), + _ => Err(errors::OutOfRange), + } + } +} +impl TryFrom for NonHardenedIndex { + type Error = errors::OutOfRange; + fn try_from(value: u32) -> Result { + match ChildIndex::from(value) { + ChildIndex::NonHardened(v) => Ok(v), + _ => Err(errors::OutOfRange), + } + } +} + +impl From<&ExtendedSecretKey> for ExtendedPublicKey { + fn from(sk: &ExtendedSecretKey) -> Self { + ExtendedPublicKey { + public_key: Point::generator() * &sk.secret_key, + chain_code: sk.chain_code, + } + } +} + +impl From> for ExtendedKeyPair { + fn from(secret_key: ExtendedSecretKey) -> Self { + Self { + public_key: (&secret_key).into(), + secret_key, + } + } +} + +impl ExtendedKeyPair { + /// Returns chain code of the key + pub fn chain_code(&self) -> &ChainCode { + debug_assert_eq!(self.public_key.chain_code, self.secret_key.chain_code); + &self.public_key.chain_code + } + + /// Returns extended public key + pub fn public_key(&self) -> &ExtendedPublicKey { + &self.public_key + } + + /// Returns extended secret key + pub fn secret_key(&self) -> &ExtendedSecretKey { + &self.secret_key + } +} + +/// 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( + curve_type: CurveType, + seed: &[u8], +) -> Result, errors::InvalidLength> { + if !(16 <= seed.len() && seed.len() <= 64) { + return Err(errors::InvalidLength); + } + + let curve = match curve_type { + CurveType::Secp256k1 => "Bitcoin seed", + CurveType::Secp256r1 => "Nist256p1 seed", + }; + + let hmac = HmacSha512::new_from_slice(curve.as_bytes()) + .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[..32]; + let i_right: [u8; 32] = i[32..] + .try_into() + .expect("this should never fail as size of output is fixed"); + + 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, + }); + } + } + + i = hmac.clone().chain_update(&i[..]).finalize().into_bytes() + } +} + +/// Derives child key pair (extended secret key + public key) from parent key pair +/// +/// ### Example +/// Derive child key m/1H from master key +/// ```rust +/// use slip10::supported_curves::Secp256k1; +/// +/// # let seed = b"do not use this seed :)".as_slice(); +/// let master_key = slip10::derive_master_key::( +/// slip10::CurveType::Secp256k1, +/// seed, +/// )?; +/// let master_key_pair = slip10::ExtendedKeyPair::from(master_key); +/// +/// let derived_key = slip10::derive_child_key_pair( +/// &master_key_pair, +/// 1 + slip10::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 child extended public key from parent extended public key +/// +/// ### Example +/// Derive a master public key m/1 +/// ```rust +/// use slip10::supported_curves::Secp256k1; +/// +/// # let seed = b"do not use this seed :)".as_slice(); +/// let master_key = slip10::derive_master_key::( +/// slip10::CurveType::Secp256k1, +/// seed, +/// )?; +/// let master_public_key = slip10::ExtendedPublicKey::from(&master_key); +/// +/// let derived_key = slip10::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 +} + +/// 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[..32]; + let i_right: [u8; 32] = i[32..] + .try_into() + .expect("this should never fail as size of output is fixed"); + + 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, + }, + }; + } + } + + i = hmac + .clone() + .chain_update([0x01]) + .chain_update(i_right) + .chain_update(child_index.to_be_bytes()) + .finalize() + .into_bytes() + } +} diff --git a/tests/test_vectors.rs b/tests/test_vectors.rs new file mode 100644 index 0000000..4cfda05 --- /dev/null +++ b/tests/test_vectors.rs @@ -0,0 +1,358 @@ +use generic_ec::Curve; +use hex_literal::hex; + +struct TestVector { + curve_type: slip10::CurveType, + seed: &'static [u8], + derivations: &'static [Derivation], +} + +struct Derivation { + path: &'static [u32], + + expected_chain_code: slip10::ChainCode, + expected_secret_key: [u8; 32], + expected_public_key: [u8; 33], +} + +/// Test vectors defined in +/// https://github.com/satoshilabs/slips/blob/817d54acc9989793288910a40f9eb59bebef3c6e/slip-0010.md#test-vectors +const TEST_VECTORS: &[TestVector] = &[ + // Test vector 1 for secp256k1 + TestVector { + seed: &hex!("000102030405060708090a0b0c0d0e0f"), + curve_type: slip10::CurveType::Secp256k1, + derivations: &[ + Derivation { + path: &[], + expected_chain_code: hex!( + "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508" + ), + expected_secret_key: hex!( + "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35" + ), + expected_public_key: hex!( + "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2" + ), + }, + Derivation { + path: &[0 + slip10::H], + expected_chain_code: hex!( + "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141" + ), + expected_secret_key: hex!( + "edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea" + ), + expected_public_key: hex!( + "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56" + ), + }, + Derivation { + path: &[0 + slip10::H, 1], + expected_chain_code: hex!( + "2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19" + ), + expected_secret_key: hex!( + "3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368" + ), + expected_public_key: hex!( + "03501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c" + ), + }, + Derivation { + path: &[0 + slip10::H, 1, 2 + slip10::H], + expected_chain_code: hex!( + "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f" + ), + expected_secret_key: hex!( + "cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca" + ), + expected_public_key: hex!( + "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2" + ), + }, + Derivation { + path: &[0 + slip10::H, 1, 2 + slip10::H, 2], + expected_chain_code: hex!( + "cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd" + ), + expected_secret_key: hex!( + "0f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4" + ), + expected_public_key: hex!( + "02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29" + ), + }, + Derivation { + path: &[0 + slip10::H, 1, 2 + slip10::H, 2, 1000000000], + expected_chain_code: hex!( + "c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e" + ), + expected_secret_key: hex!( + "471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8" + ), + expected_public_key: hex!( + "022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011" + ), + }, + ], + }, + // Test vector 1 for nist256p1 + TestVector { + curve_type: slip10::CurveType::Secp256r1, + seed: &hex!("000102030405060708090a0b0c0d0e0f"), + derivations: &[ + Derivation { + path: &[], + expected_chain_code: hex!( + "beeb672fe4621673f722f38529c07392fecaa61015c80c34f29ce8b41b3cb6ea" + ), + expected_secret_key: hex!( + "612091aaa12e22dd2abef664f8a01a82cae99ad7441b7ef8110424915c268bc2" + ), + expected_public_key: hex!( + "0266874dc6ade47b3ecd096745ca09bcd29638dd52c2c12117b11ed3e458cfa9e8" + ), + }, + Derivation { + path: &[0 + slip10::H], + expected_chain_code: hex!( + "3460cea53e6a6bb5fb391eeef3237ffd8724bf0a40e94943c98b83825342ee11" + ), + expected_secret_key: hex!( + "6939694369114c67917a182c59ddb8cafc3004e63ca5d3b84403ba8613debc0c" + ), + expected_public_key: hex!( + "0384610f5ecffe8fda089363a41f56a5c7ffc1d81b59a612d0d649b2d22355590c" + ), + }, + Derivation { + path: &[0 + slip10::H, 1], + expected_chain_code: hex!( + "4187afff1aafa8445010097fb99d23aee9f599450c7bd140b6826ac22ba21d0c" + ), + expected_secret_key: hex!( + "284e9d38d07d21e4e281b645089a94f4cf5a5a81369acf151a1c3a57f18b2129" + ), + expected_public_key: hex!( + "03526c63f8d0b4bbbf9c80df553fe66742df4676b241dabefdef67733e070f6844" + ), + }, + Derivation { + path: &[0 + slip10::H, 1, 2 + slip10::H], + expected_chain_code: hex!( + "98c7514f562e64e74170cc3cf304ee1ce54d6b6da4f880f313e8204c2a185318" + ), + expected_secret_key: hex!( + "694596e8a54f252c960eb771a3c41e7e32496d03b954aeb90f61635b8e092aa7" + ), + expected_public_key: hex!( + "0359cf160040778a4b14c5f4d7b76e327ccc8c4a6086dd9451b7482b5a4972dda0" + ), + }, + Derivation { + path: &[0 + slip10::H, 1, 2 + slip10::H, 2], + expected_chain_code: hex!( + "ba96f776a5c3907d7fd48bde5620ee374d4acfd540378476019eab70790c63a0" + ), + expected_secret_key: hex!( + "5996c37fd3dd2679039b23ed6f70b506c6b56b3cb5e424681fb0fa64caf82aaa" + ), + expected_public_key: hex!( + "029f871f4cb9e1c97f9f4de9ccd0d4a2f2a171110c61178f84430062230833ff20" + ), + }, + Derivation { + path: &[0 + slip10::H, 1, 2 + slip10::H, 2, 1000000000], + expected_chain_code: hex!( + "b9b7b82d326bb9cb5b5b121066feea4eb93d5241103c9e7a18aad40f1dde8059" + ), + expected_secret_key: hex!( + "21c4f269ef0a5fd1badf47eeacebeeaa3de22eb8e5b0adcd0f27dd99d34d0119" + ), + expected_public_key: hex!( + "02216cd26d31147f72427a453c443ed2cde8a1e53c9cc44e5ddf739725413fe3f4" + ), + }, + ], + }, + // Test vector 2 for secp256k1 + TestVector { + curve_type: slip10::CurveType::Secp256k1, + seed: &hex!( + "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a2 + 9f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542" + ), + derivations: &[ + Derivation { + path: &[], + expected_chain_code: hex!( + "60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689" + ), + expected_secret_key: hex!( + "4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e" + ), + expected_public_key: hex!( + "03cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7" + ), + }, + Derivation { + path: &[0], + expected_chain_code: hex!( + "f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c" + ), + expected_secret_key: hex!( + "abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e" + ), + expected_public_key: hex!( + "02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea" + ), + }, + Derivation { + path: &[0, 2147483647 + slip10::H], + expected_chain_code: hex!( + "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9" + ), + expected_secret_key: hex!( + "877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93" + ), + expected_public_key: hex!( + "03c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b" + ), + }, + Derivation { + path: &[0, 2147483647 + slip10::H, 1], + expected_chain_code: hex!( + "f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb" + ), + expected_secret_key: hex!( + "704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7" + ), + expected_public_key: hex!( + "03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9" + ), + }, + Derivation { + path: &[0, 2147483647 + slip10::H, 1, 2147483646 + slip10::H], + expected_chain_code: hex!( + "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29" + ), + expected_secret_key: hex!( + "f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d" + ), + expected_public_key: hex!( + "02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0" + ), + }, + Derivation { + path: &[0, 2147483647 + slip10::H, 1, 2147483646 + slip10::H, 2], + expected_chain_code: hex!( + "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271" + ), + expected_secret_key: hex!( + "bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23" + ), + expected_public_key: hex!( + "024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c" + ), + }, + ], + }, + // Test derivation retry for nist256p1 + TestVector { + curve_type: slip10::CurveType::Secp256r1, + seed: &hex!("000102030405060708090a0b0c0d0e0f"), + derivations: &[ + Derivation { + path: &[], + expected_chain_code: hex!( + "beeb672fe4621673f722f38529c07392fecaa61015c80c34f29ce8b41b3cb6ea" + ), + expected_secret_key: hex!( + "612091aaa12e22dd2abef664f8a01a82cae99ad7441b7ef8110424915c268bc2" + ), + expected_public_key: hex!( + "0266874dc6ade47b3ecd096745ca09bcd29638dd52c2c12117b11ed3e458cfa9e8" + ), + }, + Derivation { + path: &[28578 + slip10::H], + expected_chain_code: hex!( + "e94c8ebe30c2250a14713212f6449b20f3329105ea15b652ca5bdfc68f6c65c2" + ), + expected_secret_key: hex!( + "06f0db126f023755d0b8d86d4591718a5210dd8d024e3e14b6159d63f53aa669" + ), + expected_public_key: hex!( + "02519b5554a4872e8c9c1c847115363051ec43e93400e030ba3c36b52a3e70a5b7" + ), + }, + Derivation { + path: &[28578 + slip10::H, 33941], + expected_chain_code: hex!( + "9e87fe95031f14736774cd82f25fd885065cb7c358c1edf813c72af535e83071" + ), + expected_secret_key: hex!( + "092154eed4af83e078ff9b84322015aefe5769e31270f62c3f66c33888335f3a" + ), + expected_public_key: hex!( + "0235bfee614c0d5b2cae260000bb1d0d84b270099ad790022c1ae0b2e782efe120" + ), + }, + ], + }, + // Test seed retry for nist256p1 + TestVector { + curve_type: slip10::CurveType::Secp256r1, + seed: &hex!("a7305bc8df8d0951f0cb224c0e95d7707cbdf2c6ce7e8d481fec69c7ff5e9446"), + derivations: &[Derivation { + path: &[], + expected_chain_code: hex!( + "7762f9729fed06121fd13f326884c82f59aa95c57ac492ce8c9654e60efd130c" + ), + expected_secret_key: hex!( + "3b8c18469a4634517d6d0b65448f8e6c62091b45540a1743c5846be55d47d88f" + ), + expected_public_key: hex!( + "0383619fadcde31063d8c5cb00dbfe1713f3e6fa169d8541a798752a1c1ca0cb20" + ), + }], + }, +]; + +#[test] +fn test_vectors() { + for vector in TEST_VECTORS { + match vector.curve_type { + slip10::CurveType::Secp256k1 => { + run_vector::(vector) + } + slip10::CurveType::Secp256r1 => { + run_vector::(vector) + } + } + } +} + +fn run_vector(v: &TestVector) { + let master_key = slip10::derive_master_key::(v.curve_type, &v.seed).unwrap(); + let master_key_pair = slip10::ExtendedKeyPair::from(master_key); + + for derivation in v.derivations { + let mut key = master_key_pair.clone(); + + for &child_index in derivation.path { + key = slip10::derive_child_key_pair(&key, child_index); + } + + assert_eq!(key.chain_code(), &derivation.expected_chain_code); + assert_eq!( + &key.public_key().public_key.to_bytes(true)[..], + &derivation.expected_public_key, + ); + assert_eq!( + &key.secret_key().secret_key.as_ref().to_be_bytes()[..], + &derivation.expected_secret_key, + ); + } +} From 613e0b73f58cd214da8faaec6653c88b3ca92846 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Thu, 14 Dec 2023 15:59:30 +0000 Subject: [PATCH 02/13] Add CI --- .github/workflows/publish.yml | 22 ++++++++++++ .github/workflows/rust.yml | 64 +++++++++++++++++++++++++++++++++++ src/lib.rs | 2 +- 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/rust.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..1ebab04 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,22 @@ +on: + push: + tags: + - 'v*' + workflow_dispatch: + +name: Publish + +env: + CARGO_TERM_COLOR: always + CARGO_NET_GIT_FETCH_WITH_CLI: true + +jobs: + publish-round-based: + name: Publish crate + environment: crates.io + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: cargo publish --token ${CRATES_TOKEN} + env: + CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..793804c --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,64 @@ +name: Rust + +on: + pull_request: + branches: [ "*" ] + +env: + CARGO_TERM_COLOR: always + CARGO_NET_GIT_FETCH_WITH_CLI: true + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" + - name: Build + run: cargo build + - name: Run tests + run: cargo test --lib --tests + build-all-features: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" + - name: Build + run: cargo build --all-features + doctest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" + - name: Doctests + run: cargo test --doc --all-features + check-fmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Check formatting + run: cargo fmt --all -- --check + check-doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" + - name: Check docs + run: RUSTDOCFLAGS="-D warnings" cargo doc --all-features --no-deps + check-clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" + - name: Run clippy + run: cargo clippy -- -D clippy::all diff --git a/src/lib.rs b/src/lib.rs index 519cec7..69c41c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,7 @@ //! [BIP32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki #![cfg_attr(not(feature = "std"), no_std)] -#![forbid(missing_docs)] +#![forbid(missing_docs, unsafe_code)] use core::ops; From 1064a4c971ead0553830aa7e9455b500b799748c Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Thu, 14 Dec 2023 16:03:46 +0000 Subject: [PATCH 03/13] Address clippy warnings --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 69c41c8..9e3a75d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -175,8 +175,8 @@ impl ops::Deref for ChildIndex { type Target = u32; fn deref(&self) -> &Self::Target { match self { - Self::Hardened(i) => &*i, - Self::NonHardened(i) => &*i, + Self::Hardened(i) => i, + Self::NonHardened(i) => i, } } } @@ -409,7 +409,7 @@ fn calculate_shift( .expect("this should never fail as size of output is fixed"); if let Ok(shift) = Scalar::::from_be_bytes(i_left) { - let child_pk = parent_public_key.public_key + Point::generator() * &shift; + let child_pk = parent_public_key.public_key + Point::generator() * shift; if !child_pk.is_zero() { return DerivedShift { shift, From eda5afdb50436de5860acdb3dff64f80a5ebb907 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 15 Dec 2023 11:05:37 +0000 Subject: [PATCH 04/13] Use `generic_array::sequence::Split` --- Cargo.lock | 1 + Cargo.toml | 1 + src/lib.rs | 25 +++++++++++++++---------- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b2a4ab3..de2a22f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -437,6 +437,7 @@ dependencies = [ name = "slip10" version = "0.1.0" dependencies = [ + "generic-array", "generic-ec", "hex-literal", "hmac", diff --git a/Cargo.toml b/Cargo.toml index 29f5ab4..f5690a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ generic-ec = { version = "0.1", default-features = false } hmac = { version = "0.12", default-features = false } sha2 = { version = "0.10", default-features = false } subtle = { version = "2", default-features = false } +generic-array = "0.14" [dev-dependencies] hex-literal = "0.4" diff --git a/src/lib.rs b/src/lib.rs index 9e3a75d..9399175 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,6 +52,10 @@ use core::ops; +use generic_array::{ + typenum::{U32, U64}, + GenericArray, +}; use generic_ec::{Curve, Point, Scalar, SecretScalar}; use hmac::Mac as _; @@ -278,16 +282,13 @@ pub fn derive_master_key( let mut i = hmac.clone().chain_update(seed).finalize().into_bytes(); loop { - let i_left = &i[..32]; - let i_right: [u8; 32] = i[32..] - .try_into() - .expect("this should never fail as size of output is fixed"); + 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, + chain_code: (*i_right).into(), }); } } @@ -403,10 +404,7 @@ fn calculate_shift( mut i: hmac::digest::Output, ) -> DerivedShift { loop { - let i_left = &i[..32]; - let i_right: [u8; 32] = i[32..] - .try_into() - .expect("this should never fail as size of output is fixed"); + 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; @@ -415,7 +413,7 @@ fn calculate_shift( shift, child_public_key: ExtendedPublicKey { public_key: child_pk, - chain_code: i_right, + chain_code: (*i_right).into(), }, }; } @@ -430,3 +428,10 @@ fn calculate_shift( .into_bytes() } } + +/// 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) +} From 87c96ccef4c54b19801f20850c0ed07d3cbb8b4d Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 15 Dec 2023 11:24:36 +0000 Subject: [PATCH 05/13] Add SupportedCurve trait --- src/lib.rs | 46 +++++++++++++++++++++++++++++++++++-------- tests/test_vectors.rs | 4 ++-- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9399175..ccf9eff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -247,6 +247,24 @@ impl 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; +} + /// Curves supported by SLIP-10 spec /// /// It's either secp256k1 or secp256r1. Note that SLIP-10 also supports ed25519 curve, but this library @@ -264,20 +282,32 @@ pub enum CurveType { /// Derives a master key from the seed /// /// Seed must be 16-64 bytes long, otherwise an error is returned -pub fn derive_master_key( - curve_type: CurveType, +pub fn derive_master_key( seed: &[u8], ) -> Result, errors::InvalidLength> { - if !(16 <= seed.len() && seed.len() <= 64) { - return Err(errors::InvalidLength); - } - - let curve = match curve_type { + 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, errors::InvalidLength> { + if !(16 <= seed.len() && seed.len() <= 64) { + return Err(errors::InvalidLength); + } - let hmac = HmacSha512::new_from_slice(curve.as_bytes()) + 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(); diff --git a/tests/test_vectors.rs b/tests/test_vectors.rs index 4cfda05..51290f7 100644 --- a/tests/test_vectors.rs +++ b/tests/test_vectors.rs @@ -334,8 +334,8 @@ fn test_vectors() { } } -fn run_vector(v: &TestVector) { - let master_key = slip10::derive_master_key::(v.curve_type, &v.seed).unwrap(); +fn run_vector(v: &TestVector) { + let master_key = slip10::derive_master_key::(&v.seed).unwrap(); let master_key_pair = slip10::ExtendedKeyPair::from(master_key); for derivation in v.derivations { From fb0fab988dc2d388fafe000a32bac879a5d7d83e Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 15 Dec 2023 11:27:11 +0000 Subject: [PATCH 06/13] Update docs --- src/lib.rs | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ccf9eff..cc7903e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,10 +27,7 @@ //! use slip10::supported_curves::Secp256k1; //! //! let seed = b"16-64 bytes of high entropy".as_slice(); -//! let master_key = slip10::derive_master_key::( -//! slip10::CurveType::Secp256k1, -//! seed, -//! )?; +//! let master_key = slip10::derive_master_key::(seed)?; //! let master_key_pair = slip10::ExtendedKeyPair::from(master_key); //! //! let derivation_path = [1 + slip10::H, 10]; @@ -79,10 +76,7 @@ type HmacSha512 = hmac::Hmac; /// use slip10::supported_curves::Secp256k1; /// /// # let seed = b"do not use this seed in prod :)".as_slice(); -/// let master_key = slip10::derive_master_key::( -/// slip10::CurveType::Secp256k1, -/// seed, -/// )?; +/// let master_key = slip10::derive_master_key::(seed)?; /// let master_key_pair = slip10::ExtendedKeyPair::from(master_key); /// /// let hardened_child = slip10::derive_child_key_pair( @@ -335,10 +329,7 @@ pub fn derive_master_key_with_curve_tag( /// use slip10::supported_curves::Secp256k1; /// /// # let seed = b"do not use this seed :)".as_slice(); -/// let master_key = slip10::derive_master_key::( -/// slip10::CurveType::Secp256k1, -/// seed, -/// )?; +/// let master_key = slip10::derive_master_key::(seed)?; /// let master_key_pair = slip10::ExtendedKeyPair::from(master_key); /// /// let derived_key = slip10::derive_child_key_pair( @@ -375,10 +366,7 @@ pub fn derive_child_key_pair( /// use slip10::supported_curves::Secp256k1; /// /// # let seed = b"do not use this seed :)".as_slice(); -/// let master_key = slip10::derive_master_key::( -/// slip10::CurveType::Secp256k1, -/// seed, -/// )?; +/// let master_key = slip10::derive_master_key::(seed)?; /// let master_public_key = slip10::ExtendedPublicKey::from(&master_key); /// /// let derived_key = slip10::derive_child_public_key( From 6580c8a381c16b832338c6696ab0f77b59bac381 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 15 Dec 2023 13:18:08 +0000 Subject: [PATCH 07/13] Update CI --- .github/workflows/publish.yml | 4 ++-- .github/workflows/rust.yml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1ebab04..7b940b7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,6 +17,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - run: cargo publish --token ${CRATES_TOKEN} + - run: cargo publish env: - CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_TOKEN }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 793804c..b76a34b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -9,7 +9,7 @@ env: CARGO_NET_GIT_FETCH_WITH_CLI: true jobs: - build-and-test: + build-no-features: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -18,9 +18,7 @@ jobs: cache-on-failure: "true" - name: Build run: cargo build - - name: Run tests - run: cargo test --lib --tests - build-all-features: + build-and-test-all-features: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -29,6 +27,8 @@ jobs: cache-on-failure: "true" - name: Build run: cargo build --all-features + - name: Run tests + run: cargo test --all-features --lib --tests doctest: runs-on: ubuntu-latest steps: From b9e93fe6eedad4547d438452642bc03326d5c11e Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 15 Dec 2023 14:18:44 +0000 Subject: [PATCH 08/13] Add deriving keys with specified path --- src/errors.rs | 28 +++++++++ src/lib.rs | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+) diff --git a/src/errors.rs b/src/errors.rs index c5647f7..4c003e3 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -27,3 +27,31 @@ impl fmt::Display for OutOfRange { #[cfg(feature = "std")] impl std::error::Error for OutOfRange {} + +/// Error returned by parsing child index +#[derive(Debug)] +pub enum ParseChildIndexError { + /// Indicates that parsing an `u32` integer failed + ParseInt(core::num::ParseIntError), + /// Parsed index was out of acceptable range + IndexNotInRange(OutOfRange), +} + +impl fmt::Display for ParseChildIndexError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::ParseInt(_) => f.write_str("child index is not valid u32 integer"), + Self::IndexNotInRange(_) => f.write_str("child index is not in acceptable range"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseChildIndexError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + ParseChildIndexError::ParseInt(e) => Some(e), + ParseChildIndexError::IndexNotInRange(e) => Some(e), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index cc7903e..bf8fa6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -204,6 +204,30 @@ impl TryFrom for NonHardenedIndex { } } } +impl core::str::FromStr for ChildIndex { + type Err = core::num::ParseIntError; + fn from_str(s: &str) -> Result { + s.parse::().map(Into::into) + } +} +impl core::str::FromStr for HardenedIndex { + type Err = errors::ParseChildIndexError; + fn from_str(s: &str) -> Result { + let index = s + .parse::() + .map_err(errors::ParseChildIndexError::ParseInt)?; + HardenedIndex::try_from(index).map_err(errors::ParseChildIndexError::IndexNotInRange) + } +} +impl core::str::FromStr for NonHardenedIndex { + type Err = errors::ParseChildIndexError; + fn from_str(s: &str) -> Result { + let index = s + .parse::() + .map_err(errors::ParseChildIndexError::ParseInt)?; + NonHardenedIndex::try_from(index).map_err(errors::ParseChildIndexError::IndexNotInRange) + } +} impl From<&ExtendedSecretKey> for ExtendedPublicKey { fn from(sk: &ExtendedSecretKey) -> Self { @@ -358,6 +382,72 @@ pub fn derive_child_key_pair( } } +/// 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 slip10::supported_curves::Secp256k1; +/// # let seed = b"16-64 bytes of high entropy".as_slice(); +/// let master_key = slip10::derive_master_key::(seed)?; +/// let master_key_pair = slip10::ExtendedKeyPair::from(master_key); +/// +/// let child_key = slip10::derive_child_key_pair_with_path( +/// &master_key_pair, +/// [1, 10, 1 + slip10::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 {}, + } +} + +/// 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 slip10::supported_curves::Secp256k1; +/// # let seed = b"16-64 bytes of high entropy".as_slice(); +/// let master_key = slip10::derive_master_key::(seed)?; +/// let master_key_pair = slip10::ExtendedKeyPair::from(master_key); +/// +/// let path = "1/10/2"; +/// let child_indexes = path.split('/').map(str::parse::); +/// let child_key = slip10::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?); + } + Ok(derived_key) +} + /// Derives child extended public key from parent extended public key /// /// ### Example @@ -382,6 +472,72 @@ pub fn derive_child_public_key( derive_public_shift(parent_public_key, child_index).child_public_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 slip10::supported_curves::Secp256k1; +/// # let seed = b"16-64 bytes of high entropy".as_slice(); +/// let master_key = slip10::derive_master_key::(seed)?; +/// let master_public_key = slip10::ExtendedPublicKey::from(&master_key); +/// +/// let child_key = slip10::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: &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 {}, + } +} + +/// 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. +/// +/// ### Example +/// Parse a path from the string and derive a child without extra allocations: +/// ```rust +/// use slip10::supported_curves::Secp256k1; +/// # let seed = b"16-64 bytes of high entropy".as_slice(); +/// let master_key = slip10::derive_master_key::(seed)?; +/// let master_public_key = slip10::ExtendedPublicKey::from(&master_key); +/// +/// let path = "1/10/2"; +/// let child_indexes = path.split('/').map(str::parse); +/// let child_key = slip10::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: &ExtendedPublicKey, + path: impl IntoIterator>, +) -> Result, Err> { + let mut derived_key = parent_public_key.clone(); + 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, From ed1c129b87bcea8885c3bc833a4b5f03ec5cf8bd Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 15 Dec 2023 14:23:38 +0000 Subject: [PATCH 09/13] Address clippy warnings --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index bf8fa6b..e0c5c85 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -531,7 +531,7 @@ 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.clone(); + let mut derived_key = *parent_public_key; for child_index in path { derived_key = derive_child_public_key(&derived_key, child_index?); } From 2b3e123dc77c5c3d8c2c7e0bec1fc2166bbf17fb Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 15 Dec 2023 14:47:31 +0000 Subject: [PATCH 10/13] Update docs examples --- src/lib.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e0c5c85..2ea1897 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,14 +30,10 @@ //! let master_key = slip10::derive_master_key::(seed)?; //! let master_key_pair = slip10::ExtendedKeyPair::from(master_key); //! -//! let derivation_path = [1 + slip10::H, 10]; -//! let mut derived_key = master_key_pair; -//! for child_index in derivation_path { -//! derived_key = slip10::derive_child_key_pair( -//! &derived_key, -//! child_index, -//! ); -//! } +//! let child_key_pair = slip10::derive_child_key_pair_with_path( +//! &master_key_pair, +//! [1 + slip10::H, 10], +//! ); //! # Ok::<(), Box>(()) //! ``` //! From 74971f694365977bb316c02a886d4b8c43a28b2f Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 15 Dec 2023 15:06:08 +0000 Subject: [PATCH 11/13] Change lib name --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/lib.rs | 74 +++++++++++++++++++++---------------------- tests/test_vectors.rs | 62 ++++++++++++++++++------------------ 4 files changed, 70 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de2a22f..82a66b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -434,7 +434,7 @@ dependencies = [ ] [[package]] -name = "slip10" +name = "slip-10" version = "0.1.0" dependencies = [ "generic-array", diff --git a/Cargo.toml b/Cargo.toml index f5690a9..c25a2ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "slip10" +name = "slip-10" version = "0.1.0" edition = "2021" diff --git a/src/lib.rs b/src/lib.rs index 2ea1897..109dc21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,15 +24,15 @@ //! //! Derive a master key from the seed, and then derive a child key m/1H/10: //! ```rust -//! use slip10::supported_curves::Secp256k1; +//! use slip_10::supported_curves::Secp256k1; //! //! let seed = b"16-64 bytes of high entropy".as_slice(); -//! let master_key = slip10::derive_master_key::(seed)?; -//! let master_key_pair = slip10::ExtendedKeyPair::from(master_key); +//! let master_key = slip_10::derive_master_key::(seed)?; +//! let master_key_pair = slip_10::ExtendedKeyPair::from(master_key); //! -//! let child_key_pair = slip10::derive_child_key_pair_with_path( +//! let child_key_pair = slip_10::derive_child_key_pair_with_path( //! &master_key_pair, -//! [1 + slip10::H, 10], +//! [1 + slip_10::H, 10], //! ); //! # Ok::<(), Box>(()) //! ``` @@ -69,18 +69,18 @@ type HmacSha512 = hmac::Hmac; /// ## Example /// Derive a child key with a path m/1H /// ```rust -/// use slip10::supported_curves::Secp256k1; +/// use slip_10::supported_curves::Secp256k1; /// /// # let seed = b"do not use this seed in prod :)".as_slice(); -/// let master_key = slip10::derive_master_key::(seed)?; -/// let master_key_pair = slip10::ExtendedKeyPair::from(master_key); +/// let master_key = slip_10::derive_master_key::(seed)?; +/// let master_key_pair = slip_10::ExtendedKeyPair::from(master_key); /// -/// let hardened_child = slip10::derive_child_key_pair( +/// let hardened_child = slip_10::derive_child_key_pair( /// &master_key_pair, -/// 1 + slip10::H, +/// 1 + slip_10::H, /// ); /// # -/// # Ok::<(), slip10::errors::InvalidLength>(()) +/// # Ok::<(), slip_10::errors::InvalidLength>(()) /// ``` pub const H: u32 = 1 << 31; @@ -346,15 +346,15 @@ pub fn derive_master_key_with_curve_tag( /// ### Example /// Derive child key m/1H from master key /// ```rust -/// use slip10::supported_curves::Secp256k1; +/// use slip_10::supported_curves::Secp256k1; /// /// # let seed = b"do not use this seed :)".as_slice(); -/// let master_key = slip10::derive_master_key::(seed)?; -/// let master_key_pair = slip10::ExtendedKeyPair::from(master_key); +/// let master_key = slip_10::derive_master_key::(seed)?; +/// let master_key_pair = slip_10::ExtendedKeyPair::from(master_key); /// -/// let derived_key = slip10::derive_child_key_pair( +/// let derived_key = slip_10::derive_child_key_pair( /// &master_key_pair, -/// 1 + slip10::H, +/// 1 + slip_10::H, /// ); /// # Ok::<(), Box>(()) /// ``` @@ -387,14 +387,14 @@ pub fn derive_child_key_pair( /// ### Example /// Derive a child key with path m/1/10/1H /// ```rust -/// use slip10::supported_curves::Secp256k1; +/// use slip_10::supported_curves::Secp256k1; /// # let seed = b"16-64 bytes of high entropy".as_slice(); -/// let master_key = slip10::derive_master_key::(seed)?; -/// let master_key_pair = slip10::ExtendedKeyPair::from(master_key); +/// let master_key = slip_10::derive_master_key::(seed)?; +/// let master_key_pair = slip_10::ExtendedKeyPair::from(master_key); /// -/// let child_key = slip10::derive_child_key_pair_with_path( +/// let child_key = slip_10::derive_child_key_pair_with_path( /// &master_key_pair, -/// [1, 10, 1 + slip10::H], +/// [1, 10, 1 + slip_10::H], /// ); /// # Ok::<(), Box>(()) /// ``` @@ -420,14 +420,14 @@ pub fn derive_child_key_pair_with_path( /// ### Example /// Parse a path from the string and derive a child without extra allocations: /// ```rust -/// use slip10::supported_curves::Secp256k1; +/// use slip_10::supported_curves::Secp256k1; /// # let seed = b"16-64 bytes of high entropy".as_slice(); -/// let master_key = slip10::derive_master_key::(seed)?; -/// let master_key_pair = slip10::ExtendedKeyPair::from(master_key); +/// 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 = slip10::try_derive_child_key_pair_with_path( +/// let child_key = slip_10::try_derive_child_key_pair_with_path( /// &master_key_pair, /// child_indexes, /// )?; @@ -449,13 +449,13 @@ pub fn try_derive_child_key_pair_with_path( /// ### Example /// Derive a master public key m/1 /// ```rust -/// use slip10::supported_curves::Secp256k1; +/// use slip_10::supported_curves::Secp256k1; /// /// # let seed = b"do not use this seed :)".as_slice(); -/// let master_key = slip10::derive_master_key::(seed)?; -/// let master_public_key = slip10::ExtendedPublicKey::from(&master_key); +/// let master_key = slip_10::derive_master_key::(seed)?; +/// let master_public_key = slip_10::ExtendedPublicKey::from(&master_key); /// -/// let derived_key = slip10::derive_child_public_key( +/// let derived_key = slip_10::derive_child_public_key( /// &master_public_key, /// 1.try_into()?, /// ); @@ -477,12 +477,12 @@ pub fn derive_child_public_key( /// ### Example /// Derive a child key with path m/1/10 /// ```rust -/// use slip10::supported_curves::Secp256k1; +/// use slip_10::supported_curves::Secp256k1; /// # let seed = b"16-64 bytes of high entropy".as_slice(); -/// let master_key = slip10::derive_master_key::(seed)?; -/// let master_public_key = slip10::ExtendedPublicKey::from(&master_key); +/// let master_key = slip_10::derive_master_key::(seed)?; +/// let master_public_key = slip_10::ExtendedPublicKey::from(&master_key); /// -/// let child_key = slip10::derive_child_public_key_with_path( +/// let child_key = slip_10::derive_child_public_key_with_path( /// &master_public_key, /// [1.try_into()?, 10.try_into()?], /// ); @@ -510,14 +510,14 @@ pub fn derive_child_public_key_with_path( /// ### Example /// Parse a path from the string and derive a child without extra allocations: /// ```rust -/// use slip10::supported_curves::Secp256k1; +/// use slip_10::supported_curves::Secp256k1; /// # let seed = b"16-64 bytes of high entropy".as_slice(); -/// let master_key = slip10::derive_master_key::(seed)?; -/// let master_public_key = slip10::ExtendedPublicKey::from(&master_key); +/// let master_key = slip_10::derive_master_key::(seed)?; +/// let master_public_key = slip_10::ExtendedPublicKey::from(&master_key); /// /// let path = "1/10/2"; /// let child_indexes = path.split('/').map(str::parse); -/// let child_key = slip10::try_derive_child_public_key_with_path( +/// let child_key = slip_10::try_derive_child_public_key_with_path( /// &master_public_key, /// child_indexes, /// )?; diff --git a/tests/test_vectors.rs b/tests/test_vectors.rs index 51290f7..2b4db99 100644 --- a/tests/test_vectors.rs +++ b/tests/test_vectors.rs @@ -2,7 +2,7 @@ use generic_ec::Curve; use hex_literal::hex; struct TestVector { - curve_type: slip10::CurveType, + curve_type: slip_10::CurveType, seed: &'static [u8], derivations: &'static [Derivation], } @@ -10,7 +10,7 @@ struct TestVector { struct Derivation { path: &'static [u32], - expected_chain_code: slip10::ChainCode, + expected_chain_code: slip_10::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: slip10::CurveType::Secp256k1, + curve_type: slip_10::CurveType::Secp256k1, derivations: &[ Derivation { path: &[], @@ -36,7 +36,7 @@ const TEST_VECTORS: &[TestVector] = &[ ), }, Derivation { - path: &[0 + slip10::H], + path: &[0 + slip_10::H], expected_chain_code: hex!( "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141" ), @@ -48,7 +48,7 @@ const TEST_VECTORS: &[TestVector] = &[ ), }, Derivation { - path: &[0 + slip10::H, 1], + path: &[0 + slip_10::H, 1], expected_chain_code: hex!( "2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19" ), @@ -60,7 +60,7 @@ const TEST_VECTORS: &[TestVector] = &[ ), }, Derivation { - path: &[0 + slip10::H, 1, 2 + slip10::H], + path: &[0 + slip_10::H, 1, 2 + slip_10::H], expected_chain_code: hex!( "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f" ), @@ -72,7 +72,7 @@ const TEST_VECTORS: &[TestVector] = &[ ), }, Derivation { - path: &[0 + slip10::H, 1, 2 + slip10::H, 2], + path: &[0 + slip_10::H, 1, 2 + slip_10::H, 2], expected_chain_code: hex!( "cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd" ), @@ -84,7 +84,7 @@ const TEST_VECTORS: &[TestVector] = &[ ), }, Derivation { - path: &[0 + slip10::H, 1, 2 + slip10::H, 2, 1000000000], + path: &[0 + slip_10::H, 1, 2 + slip_10::H, 2, 1000000000], expected_chain_code: hex!( "c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e" ), @@ -99,7 +99,7 @@ const TEST_VECTORS: &[TestVector] = &[ }, // Test vector 1 for nist256p1 TestVector { - curve_type: slip10::CurveType::Secp256r1, + curve_type: slip_10::CurveType::Secp256r1, seed: &hex!("000102030405060708090a0b0c0d0e0f"), derivations: &[ Derivation { @@ -115,7 +115,7 @@ const TEST_VECTORS: &[TestVector] = &[ ), }, Derivation { - path: &[0 + slip10::H], + path: &[0 + slip_10::H], expected_chain_code: hex!( "3460cea53e6a6bb5fb391eeef3237ffd8724bf0a40e94943c98b83825342ee11" ), @@ -127,7 +127,7 @@ const TEST_VECTORS: &[TestVector] = &[ ), }, Derivation { - path: &[0 + slip10::H, 1], + path: &[0 + slip_10::H, 1], expected_chain_code: hex!( "4187afff1aafa8445010097fb99d23aee9f599450c7bd140b6826ac22ba21d0c" ), @@ -139,7 +139,7 @@ const TEST_VECTORS: &[TestVector] = &[ ), }, Derivation { - path: &[0 + slip10::H, 1, 2 + slip10::H], + path: &[0 + slip_10::H, 1, 2 + slip_10::H], expected_chain_code: hex!( "98c7514f562e64e74170cc3cf304ee1ce54d6b6da4f880f313e8204c2a185318" ), @@ -151,7 +151,7 @@ const TEST_VECTORS: &[TestVector] = &[ ), }, Derivation { - path: &[0 + slip10::H, 1, 2 + slip10::H, 2], + path: &[0 + slip_10::H, 1, 2 + slip_10::H, 2], expected_chain_code: hex!( "ba96f776a5c3907d7fd48bde5620ee374d4acfd540378476019eab70790c63a0" ), @@ -163,7 +163,7 @@ const TEST_VECTORS: &[TestVector] = &[ ), }, Derivation { - path: &[0 + slip10::H, 1, 2 + slip10::H, 2, 1000000000], + path: &[0 + slip_10::H, 1, 2 + slip_10::H, 2, 1000000000], expected_chain_code: hex!( "b9b7b82d326bb9cb5b5b121066feea4eb93d5241103c9e7a18aad40f1dde8059" ), @@ -178,7 +178,7 @@ const TEST_VECTORS: &[TestVector] = &[ }, // Test vector 2 for secp256k1 TestVector { - curve_type: slip10::CurveType::Secp256k1, + curve_type: slip_10::CurveType::Secp256k1, seed: &hex!( "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a2 9f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542" @@ -209,7 +209,7 @@ const TEST_VECTORS: &[TestVector] = &[ ), }, Derivation { - path: &[0, 2147483647 + slip10::H], + path: &[0, 2147483647 + slip_10::H], expected_chain_code: hex!( "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9" ), @@ -221,7 +221,7 @@ const TEST_VECTORS: &[TestVector] = &[ ), }, Derivation { - path: &[0, 2147483647 + slip10::H, 1], + path: &[0, 2147483647 + slip_10::H, 1], expected_chain_code: hex!( "f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb" ), @@ -233,7 +233,7 @@ const TEST_VECTORS: &[TestVector] = &[ ), }, Derivation { - path: &[0, 2147483647 + slip10::H, 1, 2147483646 + slip10::H], + path: &[0, 2147483647 + slip_10::H, 1, 2147483646 + slip_10::H], expected_chain_code: hex!( "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29" ), @@ -245,7 +245,7 @@ const TEST_VECTORS: &[TestVector] = &[ ), }, Derivation { - path: &[0, 2147483647 + slip10::H, 1, 2147483646 + slip10::H, 2], + path: &[0, 2147483647 + slip_10::H, 1, 2147483646 + slip_10::H, 2], expected_chain_code: hex!( "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271" ), @@ -260,7 +260,7 @@ const TEST_VECTORS: &[TestVector] = &[ }, // Test derivation retry for nist256p1 TestVector { - curve_type: slip10::CurveType::Secp256r1, + curve_type: slip_10::CurveType::Secp256r1, seed: &hex!("000102030405060708090a0b0c0d0e0f"), derivations: &[ Derivation { @@ -276,7 +276,7 @@ const TEST_VECTORS: &[TestVector] = &[ ), }, Derivation { - path: &[28578 + slip10::H], + path: &[28578 + slip_10::H], expected_chain_code: hex!( "e94c8ebe30c2250a14713212f6449b20f3329105ea15b652ca5bdfc68f6c65c2" ), @@ -288,7 +288,7 @@ const TEST_VECTORS: &[TestVector] = &[ ), }, Derivation { - path: &[28578 + slip10::H, 33941], + path: &[28578 + slip_10::H, 33941], expected_chain_code: hex!( "9e87fe95031f14736774cd82f25fd885065cb7c358c1edf813c72af535e83071" ), @@ -303,7 +303,7 @@ const TEST_VECTORS: &[TestVector] = &[ }, // Test seed retry for nist256p1 TestVector { - curve_type: slip10::CurveType::Secp256r1, + curve_type: slip_10::CurveType::Secp256r1, seed: &hex!("a7305bc8df8d0951f0cb224c0e95d7707cbdf2c6ce7e8d481fec69c7ff5e9446"), derivations: &[Derivation { path: &[], @@ -324,25 +324,25 @@ const TEST_VECTORS: &[TestVector] = &[ fn test_vectors() { for vector in TEST_VECTORS { match vector.curve_type { - slip10::CurveType::Secp256k1 => { - run_vector::(vector) + slip_10::CurveType::Secp256k1 => { + run_vector::(vector) } - slip10::CurveType::Secp256r1 => { - run_vector::(vector) + slip_10::CurveType::Secp256r1 => { + run_vector::(vector) } } } } -fn run_vector(v: &TestVector) { - let master_key = slip10::derive_master_key::(&v.seed).unwrap(); - let master_key_pair = slip10::ExtendedKeyPair::from(master_key); +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); for derivation in v.derivations { let mut key = master_key_pair.clone(); for &child_index in derivation.path { - key = slip10::derive_child_key_pair(&key, child_index); + key = slip_10::derive_child_key_pair(&key, child_index); } assert_eq!(key.chain_code(), &derivation.expected_chain_code); From 4f510f23ff4459cebbdf598cb431f38fa7da58b7 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 15 Dec 2023 15:09:48 +0000 Subject: [PATCH 12/13] Update test_vectors --- tests/test_vectors.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/test_vectors.rs b/tests/test_vectors.rs index 2b4db99..d17fcd4 100644 --- a/tests/test_vectors.rs +++ b/tests/test_vectors.rs @@ -339,11 +339,10 @@ fn run_vector(v: &TestVector) { let master_key_pair = slip_10::ExtendedKeyPair::from(master_key); for derivation in v.derivations { - let mut key = master_key_pair.clone(); - - for &child_index in derivation.path { - key = slip_10::derive_child_key_pair(&key, child_index); - } + let key = slip_10::derive_child_key_pair_with_path( + &master_key_pair, + derivation.path.iter().copied(), + ); assert_eq!(key.chain_code(), &derivation.expected_chain_code); assert_eq!( From cb271ede342f006ea9bd85e5ab5588cb845a8879 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 15 Dec 2023 15:52:56 +0000 Subject: [PATCH 13/13] Add readme --- .github/workflows/readme.yml | 24 ++++++++++++++++++++++ Makefile | 5 +++++ README.md | 39 ++++++++++++++++++++++++++++++++++++ README.tpl | 1 + src/lib.rs | 8 ++++---- 5 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/readme.yml create mode 100644 README.tpl diff --git a/.github/workflows/readme.yml b/.github/workflows/readme.yml new file mode 100644 index 0000000..4907af8 --- /dev/null +++ b/.github/workflows/readme.yml @@ -0,0 +1,24 @@ +name: Check README + +on: + pull_request: + branches: [ "*" ] + +env: + CARGO_TERM_COLOR: always + CARGO_NET_GIT_FETCH_WITH_CLI: true + +jobs: + check_readme: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install cargo-hakari + uses: baptiste0928/cargo-install@v1 + with: + crate: cargo-readme + - name: Check that readme matches lib.rs + run: | + cp README.md README-copy.md + make readme + diff README.md README-copy.md \ No newline at end of file diff --git a/Makefile b/Makefile index afc92f0..d562bd5 100644 --- a/Makefile +++ b/Makefile @@ -9,3 +9,8 @@ docs-open: docs-private: RUSTDOCFLAGS="--html-in-header katex-header.html" cargo +nightly doc --no-deps --all-features --document-private-items +readme: + cargo readme -i src/lib.rs --no-indent-headings \ + | perl -ne 's/(? README.md diff --git a/README.md b/README.md index 8b13789..c3614d2 100644 --- a/README.md +++ b/README.md @@ -1 +1,40 @@ +# SLIP-10: Deterministic key generation +[SLIP10][slip10-spec] is a specification for implementing HD wallets. It aims at supporting many +curves while being compatible with [BIP32][bip32-spec]. + +The implementation is based on generic-ec library that provides generic +elliptic curve arithmetic. The crate is `no_std` and `no_alloc` friendly. + +### 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 + +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_key_pair = slip_10::ExtendedKeyPair::from(master_key); + +let child_key_pair = slip_10::derive_child_key_pair_with_path( + &master_key_pair, + [1 + slip_10::H, 10], +); +``` + +[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/README.tpl b/README.tpl new file mode 100644 index 0000000..37dc16b --- /dev/null +++ b/README.tpl @@ -0,0 +1 @@ +# {{readme}} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 109dc21..4002c39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! SLIP-10: Deterministic key generation //! -//! [SLIP10] is a specification for implementing HD wallets. It aims at supporting many -//! curves while being compatible with [BIP32]. +//! [SLIP10][slip10-spec] is a specification for implementing HD wallets. It aims at supporting many +//! curves while being compatible with [BIP32][bip32-spec]. //! //! 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. @@ -37,8 +37,8 @@ //! # Ok::<(), Box>(()) //! ``` //! -//! [SLIP10]: https://github.com/satoshilabs/slips/blob/master/slip-0010.md -//! [BIP32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki +//! [slip10-spec]: https://github.com/satoshilabs/slips/blob/master/slip-0010.md +//! [bip32-spec]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki #![cfg_attr(not(feature = "std"), no_std)] #![forbid(missing_docs, unsafe_code)]