├── .github └── workflows │ ├── audit.yml │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── README.md └── src └── lib.rs /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | push: 4 | paths: 5 | - '**/Cargo.toml' 6 | - '**/Cargo.lock' 7 | schedule: 8 | - cron: '0 0 * * *' 9 | jobs: 10 | security_audit: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v1 14 | - uses: actions-rs/audit-check@v1 15 | with: 16 | token: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Continuous integration 4 | 5 | jobs: 6 | test: 7 | name: Test Suite 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | - uses: actions-rs/cargo@v1 17 | with: 18 | command: test 19 | 20 | fmt: 21 | name: Rustfmt 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: actions-rs/toolchain@v1 26 | with: 27 | profile: minimal 28 | toolchain: stable 29 | override: true 30 | - run: rustup component add rustfmt 31 | - uses: actions-rs/cargo@v1 32 | with: 33 | command: fmt 34 | args: --all -- --check 35 | 36 | clippy: 37 | name: Clippy 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v2 41 | - uses: actions-rs/toolchain@v1 42 | with: 43 | profile: minimal 44 | toolchain: stable 45 | override: true 46 | - run: rustup component add clippy 47 | - uses: actions-rs/cargo@v1 48 | with: 49 | command: clippy 50 | args: -- -D warnings 51 | 52 | no-std-check: 53 | name: no_std Check 54 | runs-on: ubuntu-latest 55 | steps: 56 | - uses: actions/checkout@v2 57 | - uses: actions-rs/toolchain@v1 58 | with: 59 | profile: minimal 60 | toolchain: nightly 61 | override: true 62 | - uses: actions-rs/cargo@v1 63 | with: 64 | command: install 65 | args: cargo-no-std-check 66 | - run: cargo-no-std-check --no-default-features 67 | env: 68 | RUSTFLAGS: -D warnings 69 | 70 | doc: 71 | name: Doc generation 72 | runs-on: ubuntu-latest 73 | steps: 74 | - uses: actions/checkout@v2 75 | - uses: actions-rs/toolchain@v1 76 | with: 77 | profile: minimal 78 | toolchain: stable 79 | override: true 80 | - uses: actions-rs/cargo@v1 81 | with: 82 | command: doc 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ed25519-dalek-bip32" 3 | version = "0.3.0" 4 | authors = ["Julian Popescu "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | readme = "README.md" 8 | homepage = "https://github.com/jpopesculian/ed25519-dalek-bip32" 9 | documentation = "https://docs.rs/ed25519-dalek-bip32/" 10 | repository = "https://github.com/jpopesculian/ed25519-dalek-bip32" 11 | description = "Simplified ed25519 BIP32 derivations" 12 | keywords = ["derivation", "BIP32", "ed25519", "trezor", "blockchain"] 13 | categories = ["cryptography", "no-std"] 14 | 15 | [features] 16 | default = ["std"] 17 | std = ["derivation-path/std", "sha2/std", "ed25519-dalek/std"] 18 | 19 | [dependencies] 20 | derivation-path = { version = "0.2.0", default-features = false } 21 | sha2 = { version = "0.10.1", default-features = false } 22 | hmac = { version = "0.12.0", default-features = false } 23 | ed25519-dalek = { version = "2.0.0", default-features = false, features = ["rand_core"] } 24 | 25 | [dev-dependencies] 26 | hex = "0.4.2" 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ed25519-dalek-bip32 2 | 3 | A simple BIP32 implementation for ed25519 public keys. Although there exists [another very good 4 | library that does this](https://docs.rs/ed25519-bip32), this library preserves 32 byte secret 5 | keys and doesn't allow for extended public keys or "normal" child indexes, so that it can be as 6 | close to the BIP32 specifications as possible, allowing for compatibility with libraries like 7 | `trezor-crypto` 8 | 9 | License: MIT OR Apache-2.0 10 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A simple BIP32 implementation for ed25519 public keys. Although there exists [another very good 2 | //! library that does this](https://docs.rs/ed25519-bip32), this library preserves 32 byte secret 3 | //! keys and doesn't allow for extended public keys or "normal" child indexes, so that it can be as 4 | //! close to the BIP32 specifications as possible, allowing for compatibility with libraries like 5 | //! `trezor-crypto` 6 | 7 | #![cfg_attr(not(feature = "std"), no_std)] 8 | 9 | pub extern crate derivation_path; 10 | pub extern crate ed25519_dalek; 11 | 12 | pub use derivation_path::{ChildIndex, DerivationPath}; 13 | pub use ed25519_dalek::{SigningKey, VerifyingKey}; 14 | 15 | use core::fmt; 16 | use hmac::{Hmac, Mac}; 17 | use sha2::Sha512; 18 | 19 | const ED25519_BIP32_NAME: &str = "ed25519 seed"; 20 | 21 | /// Errors thrown while deriving secret keys 22 | #[derive(Debug)] 23 | pub enum Error { 24 | Ed25519, 25 | ExpectedHardenedIndex(ChildIndex), 26 | } 27 | 28 | impl fmt::Display for Error { 29 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 30 | match self { 31 | Self::Ed25519 => f.write_str("ed25519 error"), 32 | Self::ExpectedHardenedIndex(index) => { 33 | f.write_fmt(format_args!("expected hardened child index: {}", index)) 34 | } 35 | } 36 | } 37 | } 38 | 39 | #[cfg(feature = "std")] 40 | impl std::error::Error for Error {} 41 | 42 | /// An expanded secret key with chain code and meta data 43 | #[derive(Debug)] 44 | pub struct ExtendedSigningKey { 45 | /// How many derivations this key is from the root (0 for root) 46 | pub depth: u8, 47 | /// Child index of the key used to derive from parent (`Normal(0)` for root) 48 | pub child_index: ChildIndex, 49 | /// Signing Key 50 | pub signing_key: SigningKey, 51 | /// Chain code 52 | pub chain_code: [u8; 32], 53 | } 54 | 55 | type HmacSha512 = Hmac; 56 | 57 | /// A convenience wrapper for a [`core::result::Result`] with an [`Error`] 58 | pub type Result = core::result::Result; 59 | 60 | impl ExtendedSigningKey { 61 | /// Create a new extended secret key from a seed 62 | pub fn from_seed(seed: &[u8]) -> Result { 63 | let mut mac = HmacSha512::new_from_slice(ED25519_BIP32_NAME.as_ref()).unwrap(); 64 | mac.update(seed); 65 | let bytes = mac.finalize().into_bytes(); 66 | 67 | let secret_key_bytes: [u8; 32] = bytes[0..32].try_into().unwrap(); 68 | let signing_key = SigningKey::from_bytes(&secret_key_bytes); 69 | 70 | let mut chain_code = [0; 32]; 71 | chain_code.copy_from_slice(&bytes[32..]); 72 | 73 | Ok(Self { 74 | depth: 0, 75 | child_index: ChildIndex::Normal(0), 76 | signing_key, 77 | chain_code, 78 | }) 79 | } 80 | 81 | /// Derive an extended secret key fom the current using a derivation path 82 | pub fn derive>(&self, path: &P) -> Result { 83 | let mut path = path.as_ref().iter(); 84 | let mut next = match path.next() { 85 | Some(index) => self.derive_child(*index)?, 86 | None => self.clone(), 87 | }; 88 | for index in path { 89 | next = next.derive_child(*index)?; 90 | } 91 | Ok(next) 92 | } 93 | 94 | /// Derive a child extended secret key with an index 95 | pub fn derive_child(&self, index: ChildIndex) -> Result { 96 | if index.is_normal() { 97 | return Err(Error::ExpectedHardenedIndex(index)); 98 | } 99 | 100 | let mut mac = HmacSha512::new_from_slice(&self.chain_code).unwrap(); 101 | mac.update(&[0u8]); 102 | mac.update(self.signing_key.to_bytes().as_ref()); 103 | mac.update(index.to_bits().to_be_bytes().as_ref()); 104 | let bytes = mac.finalize().into_bytes(); 105 | 106 | let secret_key_bytes: [u8; 32] = bytes[0..32].try_into().unwrap(); 107 | let signing_key = SigningKey::from_bytes(&secret_key_bytes); 108 | 109 | let mut chain_code = [0; 32]; 110 | chain_code.copy_from_slice(&bytes[32..]); 111 | 112 | Ok(Self { 113 | depth: self.depth + 1, 114 | child_index: index, 115 | signing_key, 116 | chain_code, 117 | }) 118 | } 119 | 120 | /// Get the associated verifying key 121 | #[inline] 122 | pub fn verifying_key(&self) -> VerifyingKey { 123 | self.signing_key.verifying_key() 124 | } 125 | 126 | #[inline] 127 | fn clone(&self) -> Self { 128 | Self { 129 | depth: self.depth, 130 | child_index: self.child_index, 131 | signing_key: SigningKey::from_bytes(&self.signing_key.to_bytes()), 132 | chain_code: self.chain_code, 133 | } 134 | } 135 | } 136 | 137 | impl From for Error { 138 | fn from(_: ed25519_dalek::SignatureError) -> Self { 139 | Self::Ed25519 140 | } 141 | } 142 | 143 | #[cfg(test)] 144 | mod tests { 145 | use super::*; 146 | 147 | extern crate alloc; 148 | use alloc::vec::Vec; 149 | use hex::FromHex; 150 | 151 | fn hex_str(string: &str) -> Vec { 152 | hex::decode(string).unwrap() 153 | } 154 | 155 | fn key_hex_str(input_str: &str) -> [u8; 32] { 156 | <[u8; 32]>::from_hex(input_str).expect("Failed to convert Hex Str into Key") 157 | } 158 | 159 | fn root(seed: &str) -> ExtendedSigningKey { 160 | ExtendedSigningKey::from_seed(&hex_str(seed)).unwrap() 161 | } 162 | 163 | #[test] 164 | fn derivation_path() { 165 | let vector1_path: DerivationPath = "m/0'/1'/2'/2'/1000000000'".parse().unwrap(); 166 | let vector2_path: DerivationPath = "m/0'/2147483647'/1'/2147483646'/2'".parse().unwrap(); 167 | 168 | let node = root("000102030405060708090a0b0c0d0e0f") 169 | .derive(&vector1_path) 170 | .unwrap(); 171 | assert_eq!(node.depth, 5); 172 | assert_eq!(node.child_index, ChildIndex::Hardened(1000000000)); 173 | assert_eq!( 174 | node.chain_code.as_ref(), 175 | hex_str("68789923a0cac2cd5a29172a475fe9e0fb14cd6adb5ad98a3fa70333e7afa230") 176 | ); 177 | let secret = SigningKey::from_bytes(&key_hex_str( 178 | "8f94d394a8e8fd6b1bc2f3f49f5c47e385281d5c17e65324b0f62483e37e8793", 179 | )); 180 | 181 | assert_eq!(node.signing_key.to_bytes(), secret.to_bytes()); 182 | let public = VerifyingKey::from_bytes(&key_hex_str( 183 | "3c24da049451555d51a7014a37337aa4e12d41e485abccfa46b47dfb2af54b7a", 184 | )) 185 | .unwrap(); 186 | assert_eq!(node.verifying_key().to_bytes(), public.to_bytes()); 187 | 188 | let node = root("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542").derive(&vector2_path).unwrap(); 189 | assert_eq!(node.depth, 5); 190 | assert_eq!(node.child_index, ChildIndex::Hardened(2)); 191 | assert_eq!( 192 | node.chain_code.as_ref(), 193 | hex_str("5d70af781f3a37b829f0d060924d5e960bdc02e85423494afc0b1a41bbe196d4") 194 | ); 195 | let secret = SigningKey::from_bytes(&key_hex_str( 196 | "551d333177df541ad876a60ea71f00447931c0a9da16f227c11ea080d7391b8d", 197 | )); 198 | 199 | assert_eq!(node.signing_key.to_bytes(), secret.to_bytes()); 200 | let public = VerifyingKey::from_bytes(&key_hex_str( 201 | "47150c75db263559a70d5778bf36abbab30fb061ad69f69ece61a72b0cfa4fc0", 202 | )) 203 | .unwrap(); 204 | assert_eq!(node.verifying_key().to_bytes(), public.to_bytes()); 205 | } 206 | 207 | #[test] 208 | fn normal_errs() { 209 | let node = root("000102030405060708090a0b0c0d0e0f"); 210 | 211 | let res = node.derive_child(ChildIndex::Normal(0)); 212 | assert!(matches!( 213 | res, 214 | Err(Error::ExpectedHardenedIndex(ChildIndex::Normal(0))) 215 | )); 216 | 217 | let res = node.derive_child(ChildIndex::Normal(100000)); 218 | assert!(matches!( 219 | res, 220 | Err(Error::ExpectedHardenedIndex(ChildIndex::Normal(100000))) 221 | )); 222 | 223 | let soft_path: DerivationPath = "m/0'/1'/2'/3/4'".parse().unwrap(); 224 | let res = node.derive(&soft_path); 225 | assert!(matches!( 226 | res, 227 | Err(Error::ExpectedHardenedIndex(ChildIndex::Normal(3))) 228 | )); 229 | } 230 | 231 | #[test] 232 | fn vector1() { 233 | // Chain m 234 | let node = root("000102030405060708090a0b0c0d0e0f"); 235 | assert_eq!(node.depth, 0); 236 | assert_eq!(node.child_index, ChildIndex::Normal(0)); 237 | assert_eq!( 238 | node.chain_code.as_ref(), 239 | hex_str("90046a93de5380a72b5e45010748567d5ea02bbf6522f979e05c0d8d8ca9fffb") 240 | ); 241 | let secret = SigningKey::from_bytes(&key_hex_str( 242 | "2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7", 243 | )); 244 | assert_eq!(node.signing_key.to_bytes(), secret.to_bytes()); 245 | let public = VerifyingKey::from_bytes(&key_hex_str( 246 | "a4b2856bfec510abab89753fac1ac0e1112364e7d250545963f135f2a33188ed", 247 | )) 248 | .unwrap(); 249 | assert_eq!(node.verifying_key().to_bytes(), public.to_bytes()); 250 | 251 | // Chain m/0' 252 | let node = node.derive_child(ChildIndex::Hardened(0)).unwrap(); 253 | assert_eq!(node.depth, 1); 254 | assert_eq!(node.child_index, ChildIndex::Hardened(0)); 255 | assert_eq!( 256 | node.chain_code.as_ref(), 257 | hex_str("8b59aa11380b624e81507a27fedda59fea6d0b779a778918a2fd3590e16e9c69") 258 | ); 259 | let secret = SigningKey::from_bytes(&key_hex_str( 260 | "68e0fe46dfb67e368c75379acec591dad19df3cde26e63b93a8e704f1dade7a3", 261 | )); 262 | assert_eq!(node.signing_key.to_bytes(), secret.to_bytes()); 263 | let public = VerifyingKey::from_bytes(&key_hex_str( 264 | "8c8a13df77a28f3445213a0f432fde644acaa215fc72dcdf300d5efaa85d350c", 265 | )) 266 | .unwrap(); 267 | assert_eq!(node.verifying_key().to_bytes(), public.to_bytes()); 268 | 269 | // Chain m/0'/1' 270 | let node = node.derive_child(ChildIndex::Hardened(1)).unwrap(); 271 | assert_eq!(node.depth, 2); 272 | assert_eq!(node.child_index, ChildIndex::Hardened(1)); 273 | assert_eq!( 274 | node.chain_code.as_ref(), 275 | hex_str("a320425f77d1b5c2505a6b1b27382b37368ee640e3557c315416801243552f14") 276 | ); 277 | let secret = SigningKey::from_bytes(&key_hex_str( 278 | "b1d0bad404bf35da785a64ca1ac54b2617211d2777696fbffaf208f746ae84f2", 279 | )); 280 | assert_eq!(node.signing_key.to_bytes(), secret.to_bytes()); 281 | let public = VerifyingKey::from_bytes(&key_hex_str( 282 | "1932a5270f335bed617d5b935c80aedb1a35bd9fc1e31acafd5372c30f5c1187", 283 | )) 284 | .unwrap(); 285 | assert_eq!(node.verifying_key().to_bytes(), public.to_bytes()); 286 | 287 | // Chain m/0'/1'/2' 288 | let node = node.derive_child(ChildIndex::Hardened(2)).unwrap(); 289 | assert_eq!(node.depth, 3); 290 | assert_eq!(node.child_index, ChildIndex::Hardened(2)); 291 | assert_eq!( 292 | node.chain_code.as_ref(), 293 | hex_str("2e69929e00b5ab250f49c3fb1c12f252de4fed2c1db88387094a0f8c4c9ccd6c") 294 | ); 295 | let secret = SigningKey::from_bytes(&key_hex_str( 296 | "92a5b23c0b8a99e37d07df3fb9966917f5d06e02ddbd909c7e184371463e9fc9", 297 | )); 298 | assert_eq!(node.signing_key.to_bytes(), secret.to_bytes()); 299 | let public = VerifyingKey::from_bytes(&key_hex_str( 300 | "ae98736566d30ed0e9d2f4486a64bc95740d89c7db33f52121f8ea8f76ff0fc1", 301 | )) 302 | .unwrap(); 303 | assert_eq!(node.verifying_key().to_bytes(), public.to_bytes()); 304 | 305 | // Chain m/0'/1'/2'/2' 306 | let node = node.derive_child(ChildIndex::Hardened(2)).unwrap(); 307 | assert_eq!(node.depth, 4); 308 | assert_eq!(node.child_index, ChildIndex::Hardened(2)); 309 | assert_eq!( 310 | node.chain_code.as_ref(), 311 | hex_str("8f6d87f93d750e0efccda017d662a1b31a266e4a6f5993b15f5c1f07f74dd5cc") 312 | ); 313 | let secret = SigningKey::from_bytes(&key_hex_str( 314 | "30d1dc7e5fc04c31219ab25a27ae00b50f6fd66622f6e9c913253d6511d1e662", 315 | )); 316 | assert_eq!(node.signing_key.to_bytes(), secret.to_bytes()); 317 | let public = VerifyingKey::from_bytes(&key_hex_str( 318 | "8abae2d66361c879b900d204ad2cc4984fa2aa344dd7ddc46007329ac76c429c", 319 | )) 320 | .unwrap(); 321 | assert_eq!(node.verifying_key().to_bytes(), public.to_bytes()); 322 | 323 | // Chain m/0'/1'/2'/2'/1000000000' 324 | let node = node.derive_child(ChildIndex::Hardened(1000000000)).unwrap(); 325 | assert_eq!(node.depth, 5); 326 | assert_eq!(node.child_index, ChildIndex::Hardened(1000000000)); 327 | assert_eq!( 328 | node.chain_code.as_ref(), 329 | hex_str("68789923a0cac2cd5a29172a475fe9e0fb14cd6adb5ad98a3fa70333e7afa230") 330 | ); 331 | let secret = SigningKey::from_bytes(&key_hex_str( 332 | "8f94d394a8e8fd6b1bc2f3f49f5c47e385281d5c17e65324b0f62483e37e8793", 333 | )); 334 | assert_eq!(node.signing_key.to_bytes(), secret.to_bytes()); 335 | let public = VerifyingKey::from_bytes(&key_hex_str( 336 | "3c24da049451555d51a7014a37337aa4e12d41e485abccfa46b47dfb2af54b7a", 337 | )) 338 | .unwrap(); 339 | assert_eq!(node.verifying_key().to_bytes(), public.to_bytes()); 340 | } 341 | 342 | #[test] 343 | fn vector2() { 344 | // Chain m 345 | let node = root("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"); 346 | assert_eq!(node.depth, 0); 347 | assert_eq!(node.child_index, ChildIndex::Normal(0)); 348 | assert_eq!( 349 | node.chain_code.as_ref(), 350 | hex_str("ef70a74db9c3a5af931b5fe73ed8e1a53464133654fd55e7a66f8570b8e33c3b") 351 | ); 352 | let secret = SigningKey::from_bytes(&key_hex_str( 353 | "171cb88b1b3c1db25add599712e36245d75bc65a1a5c9e18d76f9f2b1eab4012", 354 | )); 355 | assert_eq!(node.signing_key.to_bytes(), secret.to_bytes()); 356 | let public = VerifyingKey::from_bytes(&key_hex_str( 357 | "8fe9693f8fa62a4305a140b9764c5ee01e455963744fe18204b4fb948249308a", 358 | )) 359 | .unwrap(); 360 | assert_eq!(node.verifying_key().to_bytes(), public.to_bytes()); 361 | 362 | // Chain m/0' 363 | let node = node.derive_child(ChildIndex::Hardened(0)).unwrap(); 364 | assert_eq!(node.depth, 1); 365 | assert_eq!(node.child_index, ChildIndex::Hardened(0)); 366 | assert_eq!( 367 | node.chain_code.as_ref(), 368 | hex_str("0b78a3226f915c082bf118f83618a618ab6dec793752624cbeb622acb562862d") 369 | ); 370 | let secret = SigningKey::from_bytes(&key_hex_str( 371 | "1559eb2bbec5790b0c65d8693e4d0875b1747f4970ae8b650486ed7470845635", 372 | )); 373 | assert_eq!(node.signing_key.to_bytes(), secret.to_bytes()); 374 | let public = VerifyingKey::from_bytes(&key_hex_str( 375 | "86fab68dcb57aa196c77c5f264f215a112c22a912c10d123b0d03c3c28ef1037", 376 | )) 377 | .unwrap(); 378 | assert_eq!(node.verifying_key().to_bytes(), public.to_bytes()); 379 | 380 | // Chain m/0'/2147483647' 381 | let node = node.derive_child(ChildIndex::Hardened(2147483647)).unwrap(); 382 | assert_eq!(node.depth, 2); 383 | assert_eq!(node.child_index, ChildIndex::Hardened(2147483647)); 384 | assert_eq!( 385 | node.chain_code.as_ref(), 386 | hex_str("138f0b2551bcafeca6ff2aa88ba8ed0ed8de070841f0c4ef0165df8181eaad7f") 387 | ); 388 | let secret = SigningKey::from_bytes(&key_hex_str( 389 | "ea4f5bfe8694d8bb74b7b59404632fd5968b774ed545e810de9c32a4fb4192f4", 390 | )); 391 | assert_eq!(node.signing_key.to_bytes(), secret.to_bytes()); 392 | let public = VerifyingKey::from_bytes(&key_hex_str( 393 | "5ba3b9ac6e90e83effcd25ac4e58a1365a9e35a3d3ae5eb07b9e4d90bcf7506d", 394 | )) 395 | .unwrap(); 396 | assert_eq!(node.verifying_key().to_bytes(), public.to_bytes()); 397 | 398 | // Chain m/0'/2147483647'/1' 399 | let node = node.derive_child(ChildIndex::Hardened(1)).unwrap(); 400 | assert_eq!(node.depth, 3); 401 | assert_eq!(node.child_index, ChildIndex::Hardened(1)); 402 | assert_eq!( 403 | node.chain_code.as_ref(), 404 | hex_str("73bd9fff1cfbde33a1b846c27085f711c0fe2d66fd32e139d3ebc28e5a4a6b90") 405 | ); 406 | let secret = SigningKey::from_bytes(&key_hex_str( 407 | "3757c7577170179c7868353ada796c839135b3d30554bbb74a4b1e4a5a58505c", 408 | )); 409 | assert_eq!(node.signing_key.to_bytes(), secret.to_bytes()); 410 | let public = VerifyingKey::from_bytes(&key_hex_str( 411 | "2e66aa57069c86cc18249aecf5cb5a9cebbfd6fadeab056254763874a9352b45", 412 | )) 413 | .unwrap(); 414 | assert_eq!(node.verifying_key().to_bytes(), public.to_bytes()); 415 | 416 | // Chain m/0'/2147483647'/1'/2147483646' 417 | let node = node.derive_child(ChildIndex::Hardened(2147483646)).unwrap(); 418 | assert_eq!(node.depth, 4); 419 | assert_eq!(node.child_index, ChildIndex::Hardened(2147483646)); 420 | assert_eq!( 421 | node.chain_code.as_ref(), 422 | hex_str("0902fe8a29f9140480a00ef244bd183e8a13288e4412d8389d140aac1794825a") 423 | ); 424 | let secret = SigningKey::from_bytes(&key_hex_str( 425 | "5837736c89570de861ebc173b1086da4f505d4adb387c6a1b1342d5e4ac9ec72", 426 | )); 427 | assert_eq!(node.signing_key.to_bytes(), secret.to_bytes()); 428 | let public = VerifyingKey::from_bytes(&key_hex_str( 429 | "e33c0f7d81d843c572275f287498e8d408654fdf0d1e065b84e2e6f157aab09b", 430 | )) 431 | .unwrap(); 432 | assert_eq!(node.verifying_key().to_bytes(), public.to_bytes()); 433 | 434 | // Chain m/0'/2147483647'/1'/2147483646'/2' 435 | let node = node.derive_child(ChildIndex::Hardened(2)).unwrap(); 436 | assert_eq!(node.depth, 5); 437 | assert_eq!(node.child_index, ChildIndex::Hardened(2)); 438 | assert_eq!( 439 | node.chain_code.as_ref(), 440 | hex_str("5d70af781f3a37b829f0d060924d5e960bdc02e85423494afc0b1a41bbe196d4") 441 | ); 442 | let secret = SigningKey::from_bytes(&key_hex_str( 443 | "551d333177df541ad876a60ea71f00447931c0a9da16f227c11ea080d7391b8d", 444 | )); 445 | assert_eq!(node.signing_key.to_bytes(), secret.to_bytes()); 446 | let public = VerifyingKey::from_bytes(&key_hex_str( 447 | "47150c75db263559a70d5778bf36abbab30fb061ad69f69ece61a72b0cfa4fc0", 448 | )) 449 | .unwrap(); 450 | assert_eq!(node.verifying_key().to_bytes(), public.to_bytes()); 451 | } 452 | } 453 | --------------------------------------------------------------------------------