diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..7b940b7
--- /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
+ env:
+ CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_TOKEN }}
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/.github/workflows/rust.yml b/.github/workflows/rust.yml
new file mode 100644
index 0000000..b76a34b
--- /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-no-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
+ build-and-test-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
+ - name: Run tests
+ run: cargo test --all-features --lib --tests
+ 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/.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..82a66b6
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,528 @@
+# 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 = "slip-10"
+version = "0.1.0"
+dependencies = [
+ "generic-array",
+ "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..c25a2ae
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "slip-10"
+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 }
+generic-array = "0.14"
+
+[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..d562bd5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,16 @@
+.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
+
+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/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..4c003e3
--- /dev/null
+++ b/src/errors.rs
@@ -0,0 +1,57 @@
+//! 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 {}
+
+/// 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
new file mode 100644
index 0000000..4002c39
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,607 @@
+//! 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](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 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],
+//! );
+//! # Ok::<(), Box>(())
+//! ```
+//!
+//! [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)]
+
+use core::ops;
+
+use generic_array::{
+ typenum::{U32, U64},
+ GenericArray,
+};
+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 slip_10::supported_curves::Secp256k1;
+///
+/// # 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(
+/// &master_key_pair,
+/// 1 + slip_10::H,
+/// );
+/// #
+/// # Ok::<(), slip_10::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 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 {
+ 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
+ }
+}
+
+/// 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
+/// 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, 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, 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(),
+ });
+ }
+ }
+
+ 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 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 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 = 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 {},
+ }
+}
+
+/// 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?);
+ }
+ Ok(derived_key)
+}
+
+/// Derives child extended public key from parent extended public key
+///
+/// ### Example
+/// Derive a master public key m/1
+/// ```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_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
+///
+/// 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 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);
+///
+/// let child_key = slip_10::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 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);
+///
+/// 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>(())
+/// ```
+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(),
+ },
+ };
+ }
+ }
+
+ i = hmac
+ .clone()
+ .chain_update([0x01])
+ .chain_update(i_right)
+ .chain_update(child_index.to_be_bytes())
+ .finalize()
+ .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)
+}
diff --git a/tests/test_vectors.rs b/tests/test_vectors.rs
new file mode 100644
index 0000000..d17fcd4
--- /dev/null
+++ b/tests/test_vectors.rs
@@ -0,0 +1,357 @@
+use generic_ec::Curve;
+use hex_literal::hex;
+
+struct TestVector {
+ curve_type: slip_10::CurveType,
+ seed: &'static [u8],
+ derivations: &'static [Derivation],
+}
+
+struct Derivation {
+ path: &'static [u32],
+
+ expected_chain_code: slip_10::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: slip_10::CurveType::Secp256k1,
+ derivations: &[
+ Derivation {
+ path: &[],
+ expected_chain_code: hex!(
+ "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508"
+ ),
+ expected_secret_key: hex!(
+ "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"
+ ),
+ expected_public_key: hex!(
+ "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2"
+ ),
+ },
+ Derivation {
+ path: &[0 + slip_10::H],
+ expected_chain_code: hex!(
+ "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141"
+ ),
+ expected_secret_key: hex!(
+ "edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea"
+ ),
+ expected_public_key: hex!(
+ "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56"
+ ),
+ },
+ Derivation {
+ path: &[0 + slip_10::H, 1],
+ expected_chain_code: hex!(
+ "2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19"
+ ),
+ expected_secret_key: hex!(
+ "3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368"
+ ),
+ expected_public_key: hex!(
+ "03501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c"
+ ),
+ },
+ Derivation {
+ path: &[0 + slip_10::H, 1, 2 + slip_10::H],
+ expected_chain_code: hex!(
+ "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f"
+ ),
+ expected_secret_key: hex!(
+ "cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca"
+ ),
+ expected_public_key: hex!(
+ "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2"
+ ),
+ },
+ Derivation {
+ path: &[0 + slip_10::H, 1, 2 + slip_10::H, 2],
+ expected_chain_code: hex!(
+ "cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd"
+ ),
+ expected_secret_key: hex!(
+ "0f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4"
+ ),
+ expected_public_key: hex!(
+ "02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29"
+ ),
+ },
+ Derivation {
+ path: &[0 + slip_10::H, 1, 2 + slip_10::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: slip_10::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 + slip_10::H],
+ expected_chain_code: hex!(
+ "3460cea53e6a6bb5fb391eeef3237ffd8724bf0a40e94943c98b83825342ee11"
+ ),
+ expected_secret_key: hex!(
+ "6939694369114c67917a182c59ddb8cafc3004e63ca5d3b84403ba8613debc0c"
+ ),
+ expected_public_key: hex!(
+ "0384610f5ecffe8fda089363a41f56a5c7ffc1d81b59a612d0d649b2d22355590c"
+ ),
+ },
+ Derivation {
+ path: &[0 + slip_10::H, 1],
+ expected_chain_code: hex!(
+ "4187afff1aafa8445010097fb99d23aee9f599450c7bd140b6826ac22ba21d0c"
+ ),
+ expected_secret_key: hex!(
+ "284e9d38d07d21e4e281b645089a94f4cf5a5a81369acf151a1c3a57f18b2129"
+ ),
+ expected_public_key: hex!(
+ "03526c63f8d0b4bbbf9c80df553fe66742df4676b241dabefdef67733e070f6844"
+ ),
+ },
+ Derivation {
+ path: &[0 + slip_10::H, 1, 2 + slip_10::H],
+ expected_chain_code: hex!(
+ "98c7514f562e64e74170cc3cf304ee1ce54d6b6da4f880f313e8204c2a185318"
+ ),
+ expected_secret_key: hex!(
+ "694596e8a54f252c960eb771a3c41e7e32496d03b954aeb90f61635b8e092aa7"
+ ),
+ expected_public_key: hex!(
+ "0359cf160040778a4b14c5f4d7b76e327ccc8c4a6086dd9451b7482b5a4972dda0"
+ ),
+ },
+ Derivation {
+ path: &[0 + slip_10::H, 1, 2 + slip_10::H, 2],
+ expected_chain_code: hex!(
+ "ba96f776a5c3907d7fd48bde5620ee374d4acfd540378476019eab70790c63a0"
+ ),
+ expected_secret_key: hex!(
+ "5996c37fd3dd2679039b23ed6f70b506c6b56b3cb5e424681fb0fa64caf82aaa"
+ ),
+ expected_public_key: hex!(
+ "029f871f4cb9e1c97f9f4de9ccd0d4a2f2a171110c61178f84430062230833ff20"
+ ),
+ },
+ Derivation {
+ path: &[0 + slip_10::H, 1, 2 + slip_10::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: slip_10::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 + slip_10::H],
+ expected_chain_code: hex!(
+ "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9"
+ ),
+ expected_secret_key: hex!(
+ "877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93"
+ ),
+ expected_public_key: hex!(
+ "03c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b"
+ ),
+ },
+ Derivation {
+ path: &[0, 2147483647 + slip_10::H, 1],
+ expected_chain_code: hex!(
+ "f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb"
+ ),
+ expected_secret_key: hex!(
+ "704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7"
+ ),
+ expected_public_key: hex!(
+ "03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9"
+ ),
+ },
+ Derivation {
+ path: &[0, 2147483647 + slip_10::H, 1, 2147483646 + slip_10::H],
+ expected_chain_code: hex!(
+ "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29"
+ ),
+ expected_secret_key: hex!(
+ "f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d"
+ ),
+ expected_public_key: hex!(
+ "02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0"
+ ),
+ },
+ Derivation {
+ path: &[0, 2147483647 + slip_10::H, 1, 2147483646 + slip_10::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: slip_10::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 + slip_10::H],
+ expected_chain_code: hex!(
+ "e94c8ebe30c2250a14713212f6449b20f3329105ea15b652ca5bdfc68f6c65c2"
+ ),
+ expected_secret_key: hex!(
+ "06f0db126f023755d0b8d86d4591718a5210dd8d024e3e14b6159d63f53aa669"
+ ),
+ expected_public_key: hex!(
+ "02519b5554a4872e8c9c1c847115363051ec43e93400e030ba3c36b52a3e70a5b7"
+ ),
+ },
+ Derivation {
+ path: &[28578 + slip_10::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: slip_10::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 {
+ slip_10::CurveType::Secp256k1 => {
+ run_vector::(vector)
+ }
+ slip_10::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);
+
+ for derivation in v.derivations {
+ 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!(
+ &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,
+ );
+ }
+}