diff --git a/Cargo.lock b/Cargo.lock index 8ff61d1fe..4d645f9b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -276,6 +276,22 @@ dependencies = [ "subtle", ] +[[package]] +name = "bip32" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e141fb0f8be1c7b45887af94c88b182472b57c96b56773250ae00cd6a14a164" +dependencies = [ + "bs58", + "hmac", + "rand_core", + "ripemd", + "secp256k1", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -661,6 +677,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -997,19 +1014,6 @@ dependencies = [ "hashbrown 0.14.5", ] -[[package]] -name = "hdwallet" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a03ba7d4c9ea41552cd4351965ff96883e629693ae85005c501bb4b9e1c48a7" -dependencies = [ - "lazy_static", - "rand_core", - "ring 0.16.20", - "secp256k1", - "thiserror", -] - [[package]] name = "heck" version = "0.4.1" @@ -1028,6 +1032,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.5" @@ -1958,21 +1971,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - [[package]] name = "ring" version = "0.17.8" @@ -1984,7 +1982,7 @@ dependencies = [ "getrandom", "libc", "spin 0.9.8", - "untrusted 0.9.0", + "untrusted", "windows-sys 0.52.0", ] @@ -2038,7 +2036,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.8", + "ring", "rustls-webpki", "sct", ] @@ -2049,8 +2047,8 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -2154,15 +2152,15 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] name = "secp256k1" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4124a35fe33ae14259c490fd70fa199a32b9ce9502f2ee6bc4f81ec06fa65894" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" dependencies = [ "secp256k1-sys", ] @@ -2651,12 +2649,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -3059,6 +3051,7 @@ dependencies = [ "async-trait", "base64", "bech32", + "bip32", "bls12_381", "bs58", "byteorder", @@ -3067,7 +3060,6 @@ dependencies = [ "futures-util", "group", "gumdrop", - "hdwallet", "hex", "incrementalmerkletree", "jubjub", @@ -3106,12 +3098,12 @@ name = "zcash_client_sqlite" version = "0.10.3" dependencies = [ "assert_matches", + "bip32", "bls12_381", "bs58", "byteorder", "document-features", "group", - "hdwallet", "incrementalmerkletree", "jubjub", "maybe-rayon", @@ -3185,13 +3177,13 @@ name = "zcash_keys" version = "0.2.0" dependencies = [ "bech32", + "bip32", "blake2b_simd", "bls12_381", "bs58", "byteorder", "document-features", "group", - "hdwallet", "hex", "jubjub", "memuse", @@ -3229,7 +3221,9 @@ version = "0.15.1" dependencies = [ "aes", "assert_matches", + "bip32", "blake2b_simd", + "bs58", "byteorder", "chacha20poly1305", "clap", @@ -3239,7 +3233,6 @@ dependencies = [ "ff", "fpe", "group", - "hdwallet", "hex", "incrementalmerkletree", "jubjub", diff --git a/Cargo.toml b/Cargo.toml index 79a358a43..fe77eb43a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,9 +62,9 @@ orchard = { version = "0.8.0", default-features = false } pasta_curves = "0.5" # - Transparent -hdwallet = "0.4" +bip32 = { version = "0.5", default-features = false, features = ["secp256k1-ffi"] } ripemd = "0.1" -secp256k1 = "0.26" +secp256k1 = "0.27" # CSPRNG rand = "0.8" diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index de7914380..4f26742b3 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -22,6 +22,15 @@ who = "Daira-Emma Hopwood " criteria = "safe-to-deploy" delta = "1.2.0 -> 1.3.0" +[[audits.bip32]] +who = "Jack Grigg " +criteria = "safe-to-deploy" +version = "0.5.1" +notes = """ +- Crate has no unsafe code, and sets `#![forbid(unsafe_code)]`. +- Crate has no powerful imports. Only filesystem acces is via `include_str!`, and is safe. +""" + [[audits.bytemuck]] who = "Daira-Emma Hopwood " criteria = "safe-to-run" @@ -249,6 +258,11 @@ who = "Daira-Emma Hopwood " criteria = "safe-to-run" delta = "1.0.17 -> 1.0.18" +[[audits.secp256k1]] +who = "Jack Grigg " +criteria = ["safe-to-deploy", "crypto-reviewed"] +delta = "0.26.0 -> 0.27.0" + [[audits.serde]] who = "Daira-Emma Hopwood " criteria = "safe-to-deploy" diff --git a/supply-chain/config.toml b/supply-chain/config.toml index b600af208..d18824cc3 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -308,10 +308,6 @@ criteria = "safe-to-deploy" version = "0.8.4" criteria = "safe-to-deploy" -[[exemptions.hdwallet]] -version = "0.4.1" -criteria = "safe-to-deploy" - [[exemptions.hermit-abi]] version = "0.3.3" criteria = "safe-to-deploy" @@ -358,7 +354,7 @@ criteria = "safe-to-deploy" [[exemptions.js-sys]] version = "0.3.65" -criteria = "safe-to-deploy" +criteria = "safe-to-run" [[exemptions.jubjub]] version = "0.10.0" @@ -552,10 +548,6 @@ criteria = "safe-to-deploy" version = "0.8.37" criteria = "safe-to-run" -[[exemptions.ring]] -version = "0.16.20" -criteria = "safe-to-deploy" - [[exemptions.ring]] version = "0.17.8" criteria = "safe-to-deploy" @@ -742,35 +734,31 @@ criteria = "safe-to-deploy" [[exemptions.wasm-bindgen]] version = "0.2.92" -criteria = "safe-to-deploy" +criteria = "safe-to-run" [[exemptions.wasm-bindgen-backend]] version = "0.2.88" -criteria = "safe-to-deploy" +criteria = "safe-to-run" [[exemptions.wasm-bindgen-macro]] version = "0.2.88" -criteria = "safe-to-deploy" +criteria = "safe-to-run" [[exemptions.web-sys]] version = "0.3.65" -criteria = "safe-to-deploy" +criteria = "safe-to-run" [[exemptions.which]] version = "4.4.2" criteria = "safe-to-deploy" -[[exemptions.winapi]] -version = "0.3.9" -criteria = "safe-to-deploy" - [[exemptions.winapi-i686-pc-windows-gnu]] version = "0.4.0" -criteria = "safe-to-deploy" +criteria = "safe-to-run" [[exemptions.winapi-x86_64-pc-windows-gnu]] version = "0.4.0" -criteria = "safe-to-deploy" +criteria = "safe-to-run" [[exemptions.wyz]] version = "0.5.1" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index b597908af..d2473d8ae 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -1127,6 +1127,17 @@ criteria = "safe-to-deploy" version = "0.9.4" aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" +[[audits.google.audits.winapi]] +who = "danakj@chromium.org" +criteria = "safe-to-run" +version = "0.3.9" +notes = """ +Reviewed in https://crrev.com/c/5171063 + +Previously reviewed during security review and the audit is grandparented in. +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + [[audits.google.audits.winapi-util]] who = "danakj@chromium.org" criteria = "safe-to-run" @@ -1198,6 +1209,11 @@ who = "David Cook " criteria = "safe-to-deploy" delta = "0.2.14 -> 0.2.15" +[[audits.isrg.audits.hmac]] +who = "David Cook " +criteria = "safe-to-deploy" +version = "0.12.1" + [[audits.isrg.audits.num-bigint]] who = "David Cook " criteria = "safe-to-deploy" @@ -1298,11 +1314,6 @@ who = "David Cook " criteria = "safe-to-deploy" delta = "0.5.0 -> 0.5.1" -[[audits.isrg.audits.untrusted]] -who = "David Cook " -criteria = "safe-to-deploy" -version = "0.7.1" - [[audits.isrg.audits.wasm-bindgen-shared]] who = "David Cook " criteria = "safe-to-deploy" diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index e66f571b6..1ddc7bfb5 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -58,7 +58,7 @@ bech32.workspace = true bs58.workspace = true # - Errors -hdwallet = { workspace = true, optional = true } +bip32 = { workspace = true, optional = true } # - Logging and metrics memuse.workspace = true @@ -133,7 +133,7 @@ lightwalletd-tonic-transport = ["lightwalletd-tonic", "tonic?/transport"] ## Enables receiving transparent funds and shielding them. transparent-inputs = [ - "dep:hdwallet", + "dep:bip32", "zcash_keys/transparent-inputs", "zcash_primitives/transparent-inputs", ] diff --git a/zcash_client_sqlite/CHANGELOG.md b/zcash_client_sqlite/CHANGELOG.md index d60043d39..738284fc4 100644 --- a/zcash_client_sqlite/CHANGELOG.md +++ b/zcash_client_sqlite/CHANGELOG.md @@ -8,6 +8,9 @@ and this library adheres to Rust's notion of ## [Unreleased] ### Changed - MSRV is now 1.70.0. +- `zcash_client_sqlite::error::SqliteClientError` has changed variants: + - Removed `HdwalletError`. + - Added `TransparentDerivation`. ## [0.10.3] - 2024-04-08 diff --git a/zcash_client_sqlite/Cargo.toml b/zcash_client_sqlite/Cargo.toml index 724cf4ce2..a1903acb4 100644 --- a/zcash_client_sqlite/Cargo.toml +++ b/zcash_client_sqlite/Cargo.toml @@ -37,8 +37,8 @@ zip32.workspace = true # Dependencies exposed in a public API: # (Breaking upgrades to these require a breaking upgrade to this crate.) # - Errors +bip32 = { workspace = true, optional = true } bs58.workspace = true -hdwallet = { workspace = true, optional = true } # - Logging and metrics tracing.workspace = true @@ -118,7 +118,7 @@ test-dependencies = [ ## Enables receiving transparent funds and sending to transparent recipients transparent-inputs = [ - "dep:hdwallet", + "dep:bip32", "zcash_keys/transparent-inputs", "zcash_client_backend/transparent-inputs" ] diff --git a/zcash_client_sqlite/src/error.rs b/zcash_client_sqlite/src/error.rs index 2f961853c..04897387d 100644 --- a/zcash_client_sqlite/src/error.rs +++ b/zcash_client_sqlite/src/error.rs @@ -39,7 +39,7 @@ pub enum SqliteClientError { /// An error produced in legacy transparent address derivation #[cfg(feature = "transparent-inputs")] - HdwalletError(hdwallet::error::Error), + TransparentDerivation(bip32::Error), /// An error encountered in decoding a transparent address from its /// serialized form. @@ -139,7 +139,7 @@ impl fmt::Display for SqliteClientError { write!(f, "A rewind must be either of less than {} blocks, or at least back to block {} for your wallet; the requested height was {}.", PRUNING_DEPTH, h, r), SqliteClientError::DecodingError(e) => write!(f, "{}", e), #[cfg(feature = "transparent-inputs")] - SqliteClientError::HdwalletError(e) => write!(f, "{:?}", e), + SqliteClientError::TransparentDerivation(e) => write!(f, "{:?}", e), #[cfg(feature = "transparent-inputs")] SqliteClientError::TransparentAddress(e) => write!(f, "{}", e), SqliteClientError::TableNotEmpty => write!(f, "Table is not empty"), @@ -190,9 +190,9 @@ impl From for SqliteClientError { } #[cfg(feature = "transparent-inputs")] -impl From for SqliteClientError { - fn from(e: hdwallet::error::Error) -> Self { - SqliteClientError::HdwalletError(e) +impl From for SqliteClientError { + fn from(e: bip32::Error) -> Self { + SqliteClientError::TransparentDerivation(e) } } diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 9fff7e3a3..26ec0e4a9 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -136,7 +136,9 @@ fn sqlite_client_error_to_wallet_migration_error(e: SqliteClientError) -> Wallet } SqliteClientError::DecodingError(e) => WalletMigrationError::CorruptedData(e.to_string()), #[cfg(feature = "transparent-inputs")] - SqliteClientError::HdwalletError(e) => WalletMigrationError::CorruptedData(e.to_string()), + SqliteClientError::TransparentDerivation(e) => { + WalletMigrationError::CorruptedData(e.to_string()) + } #[cfg(feature = "transparent-inputs")] SqliteClientError::TransparentAddress(e) => { WalletMigrationError::CorruptedData(e.to_string()) diff --git a/zcash_client_sqlite/src/wallet/init/migrations/add_utxo_account.rs b/zcash_client_sqlite/src/wallet/init/migrations/add_utxo_account.rs index fc64ff26c..45a5b45c4 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/add_utxo_account.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/add_utxo_account.rs @@ -223,7 +223,7 @@ fn get_legacy_transparent_address( .map(|tfvk| { tfvk.derive_external_ivk() .map(|tivk| tivk.default_address()) - .map_err(SqliteClientError::HdwalletError) + .map_err(SqliteClientError::TransparentDerivation) }) .transpose() } else { diff --git a/zcash_keys/CHANGELOG.md b/zcash_keys/CHANGELOG.md index 3e95ab948..6987ac04e 100644 --- a/zcash_keys/CHANGELOG.md +++ b/zcash_keys/CHANGELOG.md @@ -11,6 +11,9 @@ and this library adheres to Rust's notion of ### Changed - MSRV is now 1.70.0. +- `zcash_keys::keys`: + - The (unstable) encoding of `UnifiedSpendingKey` has changed. + - `DerivationError::Transparent` now contains `bip32::Error`. ## [0.2.0] - 2024-03-25 diff --git a/zcash_keys/Cargo.toml b/zcash_keys/Cargo.toml index 15cc254ab..8065a6018 100644 --- a/zcash_keys/Cargo.toml +++ b/zcash_keys/Cargo.toml @@ -36,7 +36,7 @@ bech32.workspace = true bs58.workspace = true # - Transparent protocols -hdwallet = { workspace = true, optional = true } +bip32 = { workspace = true, optional = true } # - Logging and metrics memuse.workspace = true @@ -76,7 +76,7 @@ zcash_primitives = { workspace = true, features = ["test-dependencies"] } [features] ## Enables use of transparent key parts and addresses -transparent-inputs = ["dep:hdwallet", "zcash_primitives/transparent-inputs"] +transparent-inputs = ["dep:bip32", "zcash_primitives/transparent-inputs"] ## Enables use of Orchard key parts and addresses orchard = ["dep:orchard"] diff --git a/zcash_keys/src/keys.rs b/zcash_keys/src/keys.rs index 0115e1528..151b9ca5b 100644 --- a/zcash_keys/src/keys.rs +++ b/zcash_keys/src/keys.rs @@ -93,7 +93,7 @@ pub enum DerivationError { #[cfg(feature = "orchard")] Orchard(orchard::zip32::Error), #[cfg(feature = "transparent-inputs")] - Transparent(hdwallet::error::Error), + Transparent(bip32::Error), } impl Display for DerivationError { @@ -398,11 +398,11 @@ impl UnifiedSpendingKey { } } Typecode::P2pkh => { - if len != 64 { + if len != 74 { return Err(DecodingError::LengthMismatch(Typecode::P2pkh, len)); } - let mut key = [0u8; 64]; + let mut key = [0u8; 74]; source .read_exact(&mut key) .map_err(|_| DecodingError::InsufficientData(Typecode::P2pkh))?; @@ -604,8 +604,8 @@ impl UnifiedAddressRequest { } #[cfg(feature = "transparent-inputs")] -impl From for DerivationError { - fn from(e: hdwallet::error::Error) -> Self { +impl From for DerivationError { + fn from(e: bip32::Error) -> Self { DerivationError::Transparent(e) } } @@ -1660,8 +1660,10 @@ mod tests { let len = len + 2 + 169; + // Transparent part is an `xprv` transparent extended key deserialized + // into bytes from Base58, minus the 4 prefix bytes. #[cfg(feature = "transparent-inputs")] - let len = len + 2 + 64; + let len = len + 2 + 74; len }; diff --git a/zcash_primitives/CHANGELOG.md b/zcash_primitives/CHANGELOG.md index 18ad30fe3..829f80844 100644 --- a/zcash_primitives/CHANGELOG.md +++ b/zcash_primitives/CHANGELOG.md @@ -6,13 +6,39 @@ and this library adheres to Rust's notion of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- `zcash_primitives::legacy::keys`: + - `impl From for bip32::ChildNumber` + - `impl From for bip32::ChildNumber` + - `impl TryFrom for NonHardenedChildIndex` + ### Changed - MSRV is now 1.70.0. +- Bumped dependencies to `secp256k1 0.27`. +- `zcash_primitives::legacy::keys`: + - `AccountPrivKey::{from_bytes, to_bytes}` now use the byte encoding from the + inside of a `xprv` Base58 string encoding from BIP 32, excluding the prefix + bytes (i.e. starting with `depth`). + - `AccountPrivKey::from_extended_privkey` now takes + `bip32::ExtendedPrivateKey`. + - The following methods now return `Result<_, bip32::Error>`: + - `AccountPrivKey::from_seed` + - `AccountPrivKey::derive_secret_key` + - `AccountPrivKey::derive_external_secret_key` + - `AccountPrivKey::derive_internal_secret_key` + - `AccountPubKey::derive_external_ivk` + - `AccountPubKey::derive_internal_ivk` + - `AccountPubKey::deserialize` + - `IncomingViewingKey::derive_address` ### Removed - The `zcash_primitives::zip339` module, which reexported parts of the API of the `bip0039` crate, has been removed. Use the `bip0039` crate directly instead. +- The `hdwallet` dependency and its effect on `zcash_primitives::legacy::keys`: + - `impl From for hdwallet::KeyIndex` + - `impl From for hdwallet::KeyIndex` + - `impl TryFrom for NonHardenedChildIndex` ## [0.15.1] - 2024-05-23 diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index d5d70f32e..375321283 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -59,7 +59,7 @@ proptest = { workspace = true, optional = true } # - Transparent inputs # - `Error` type exposed -hdwallet = { workspace = true, optional = true } +bip32 = { workspace = true, optional = true } # - `SecretKey` and `PublicKey` types exposed secp256k1 = { workspace = true, optional = true } @@ -69,6 +69,7 @@ secp256k1 = { workspace = true, optional = true } document-features.workspace = true # - Encodings +bs58.workspace = true byteorder.workspace = true hex.workspace = true @@ -109,7 +110,7 @@ default = ["multicore"] multicore = ["orchard/multicore", "sapling/multicore"] ## Enables spending transparent notes with the transaction builder. -transparent-inputs = ["dep:hdwallet", "dep:ripemd", "dep:secp256k1"] +transparent-inputs = ["dep:bip32", "dep:ripemd", "dep:secp256k1"] ### A temporary feature flag that exposes granular APIs needed by `zcashd`. These APIs ### should not be relied upon and will be removed in a future release. diff --git a/zcash_primitives/src/legacy/keys.rs b/zcash_primitives/src/legacy/keys.rs index fbed55028..3d204ed54 100644 --- a/zcash_primitives/src/legacy/keys.rs +++ b/zcash_primitives/src/legacy/keys.rs @@ -1,8 +1,7 @@ //! Transparent key components. -use hdwallet::{ - traits::{Deserialize, Serialize}, - ExtendedPrivKey, ExtendedPubKey, KeyIndex, +use bip32::{ + ChildNumber, ExtendedKey, ExtendedKeyAttrs, ExtendedPrivateKey, ExtendedPublicKey, Prefix, }; use secp256k1::PublicKey; use sha2::{Digest, Sha256}; @@ -40,9 +39,9 @@ impl From for TransparentKeyScope { } } -impl From for KeyIndex { +impl From for ChildNumber { fn from(value: TransparentKeyScope) -> Self { - KeyIndex::Normal(value.0) + ChildNumber::new(value.0, false).expect("TransparentKeyScope is correct by construction") } } @@ -84,20 +83,21 @@ impl NonHardenedChildIndex { } } -impl TryFrom for NonHardenedChildIndex { +impl TryFrom for NonHardenedChildIndex { type Error = (); - fn try_from(value: KeyIndex) -> Result { - match value { - KeyIndex::Normal(i) => NonHardenedChildIndex::from_index(i).ok_or(()), - KeyIndex::Hardened(_) => Err(()), + fn try_from(value: ChildNumber) -> Result { + if value.is_hardened() { + Err(()) + } else { + NonHardenedChildIndex::from_index(value.index()).ok_or(()) } } } -impl From for KeyIndex { +impl From for ChildNumber { fn from(value: NonHardenedChildIndex) -> Self { - Self::Normal(value.index()) + Self::new(value.index(), false).expect("NonHardenedChildIndex is correct by construction") } } @@ -105,7 +105,7 @@ impl From for KeyIndex { /// /// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki #[derive(Clone, Debug)] -pub struct AccountPrivKey(ExtendedPrivKey); +pub struct AccountPrivKey(ExtendedPrivateKey); impl AccountPrivKey { /// Performs derivation of the extended private key for the BIP44 path: @@ -117,20 +117,20 @@ impl AccountPrivKey { params: &P, seed: &[u8], account: AccountId, - ) -> Result { - ExtendedPrivKey::with_seed(seed)? - .derive_private_key(KeyIndex::hardened_from_normalize_index(44)?)? - .derive_private_key(KeyIndex::hardened_from_normalize_index(params.coin_type())?)? - .derive_private_key(KeyIndex::hardened_from_normalize_index(account.into())?) + ) -> Result { + ExtendedPrivateKey::new(seed)? + .derive_child(ChildNumber::new(44, true)?)? + .derive_child(ChildNumber::new(params.coin_type(), true)?)? + .derive_child(ChildNumber::new(account.into(), true)?) .map(AccountPrivKey) } - pub fn from_extended_privkey(extprivkey: ExtendedPrivKey) -> Self { + pub fn from_extended_privkey(extprivkey: ExtendedPrivateKey) -> Self { AccountPrivKey(extprivkey) } pub fn to_account_pubkey(&self) -> AccountPubKey { - AccountPubKey(ExtendedPubKey::from_private_key(&self.0)) + AccountPubKey(ExtendedPublicKey::from(&self.0)) } /// Derives the BIP44 private spending key for the child path @@ -139,11 +139,11 @@ impl AccountPrivKey { &self, scope: TransparentKeyScope, child_index: NonHardenedChildIndex, - ) -> Result { + ) -> Result { self.0 - .derive_private_key(scope.into())? - .derive_private_key(child_index.into()) - .map(|k| k.private_key) + .derive_child(scope.into())? + .derive_child(child_index.into()) + .map(|k| *k.private_key()) } /// Derives the BIP44 private spending key for the external (incoming payment) child path @@ -151,7 +151,7 @@ impl AccountPrivKey { pub fn derive_external_secret_key( &self, child_index: NonHardenedChildIndex, - ) -> Result { + ) -> Result { self.derive_secret_key(zip32::Scope::External.into(), child_index) } @@ -160,22 +160,40 @@ impl AccountPrivKey { pub fn derive_internal_secret_key( &self, child_index: NonHardenedChildIndex, - ) -> Result { + ) -> Result { self.derive_secret_key(zip32::Scope::Internal.into(), child_index) } /// Returns the `AccountPrivKey` serialized using the encoding for a - /// [BIP 32](https://en.bitcoin.it/wiki/BIP_0032) ExtendedPrivKey + /// [BIP 32](https://en.bitcoin.it/wiki/BIP_0032) ExtendedPrivateKey, excluding the + /// 4 prefix bytes. pub fn to_bytes(&self) -> Vec { - self.0.serialize() + // Convert to `xprv` encoding. + let xprv_encoded = self.0.to_extended_key(Prefix::XPRV).to_string(); + + // Now decode it and return the bytes we want. + bs58::decode(xprv_encoded) + .with_check(None) + .into_vec() + .expect("correct") + .split_off(Prefix::LENGTH) } /// Decodes the `AccountPrivKey` from the encoding specified for a - /// [BIP 32](https://en.bitcoin.it/wiki/BIP_0032) ExtendedPrivKey + /// [BIP 32](https://en.bitcoin.it/wiki/BIP_0032) ExtendedPrivateKey, excluding the + /// 4 prefix bytes. pub fn from_bytes(b: &[u8]) -> Option { - ExtendedPrivKey::deserialize(b) - .map(AccountPrivKey::from_extended_privkey) + // Convert to `xprv` encoding. + let mut bytes = Prefix::XPRV.to_bytes().to_vec(); + bytes.extend_from_slice(b); + let xprv_encoded = bs58::encode(bytes).with_check().into_string(); + + // Now we can parse it. + xprv_encoded + .parse::() .ok() + .and_then(|k| ExtendedPrivateKey::try_from(k).ok()) + .map(AccountPrivKey::from_extended_privkey) } } @@ -186,22 +204,22 @@ impl AccountPrivKey { /// /// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki #[derive(Clone, Debug)] -pub struct AccountPubKey(ExtendedPubKey); +pub struct AccountPubKey(ExtendedPublicKey); impl AccountPubKey { /// Derives the BIP44 public key at the external "change level" path /// `m/44'/'/'/0`. - pub fn derive_external_ivk(&self) -> Result { + pub fn derive_external_ivk(&self) -> Result { self.0 - .derive_public_key(KeyIndex::Normal(0)) + .derive_child(ChildNumber::new(0, false)?) .map(ExternalIvk) } /// Derives the BIP44 public key at the internal "change level" path /// `m/44'/'/'/1`. - pub fn derive_internal_ivk(&self) -> Result { + pub fn derive_internal_ivk(&self) -> Result { self.0 - .derive_public_key(KeyIndex::Normal(1)) + .derive_child(ChildNumber::new(1, false)?) .map(InternalIvk) } @@ -211,7 +229,7 @@ impl AccountPubKey { /// [transparent-ovk]: https://zips.z.cash/zip-0316#deriving-internal-keys pub fn ovks_for_shielding(&self) -> (InternalOvk, ExternalOvk) { let i_ovk = PrfExpand::TRANSPARENT_ZIP316_OVK - .with(&self.0.chain_code, &self.0.public_key.serialize()); + .with(&self.0.attrs().chain_code, &self.0.public_key().serialize()); let ovk_external = ExternalOvk(i_ovk[..32].try_into().unwrap()); let ovk_internal = InternalOvk(i_ovk[32..].try_into().unwrap()); @@ -229,18 +247,25 @@ impl AccountPubKey { } pub fn serialize(&self) -> Vec { - let mut buf = self.0.chain_code.clone(); - buf.extend(self.0.public_key.serialize().to_vec()); + let mut buf = self.0.attrs().chain_code.to_vec(); + buf.extend_from_slice(&self.0.public_key().serialize()); buf } - pub fn deserialize(data: &[u8; 65]) -> Result { - let chain_code = data[..32].to_vec(); + pub fn deserialize(data: &[u8; 65]) -> Result { + let chain_code = data[..32].try_into().expect("correct length"); let public_key = PublicKey::from_slice(&data[32..])?; - Ok(AccountPubKey(ExtendedPubKey { + Ok(AccountPubKey(ExtendedPublicKey::new( public_key, - chain_code, - })) + ExtendedKeyAttrs { + depth: 3, + // We do not expose the inner `ExtendedPublicKey`, so we can use dummy + // values for the fields that are not encoded in an `AccountPubKey`. + parent_fingerprint: [0xff, 0xff, 0xff, 0xff], + child_number: ChildNumber::new(0, true).expect("correct"), + chain_code, + }, + ))) } } @@ -253,10 +278,13 @@ pub fn pubkey_to_address(pubkey: &secp256k1::PublicKey) -> TransparentAddress { } pub(crate) mod private { - use hdwallet::ExtendedPubKey; + use super::TransparentKeyScope; + use bip32::ExtendedPublicKey; + use secp256k1::PublicKey; pub trait SealedChangeLevelKey { - fn extended_pubkey(&self) -> &ExtendedPubKey; - fn from_extended_pubkey(key: ExtendedPubKey) -> Self; + const SCOPE: TransparentKeyScope; + fn extended_pubkey(&self) -> &ExtendedPublicKey; + fn from_extended_pubkey(key: ExtendedPublicKey) -> Self; } } @@ -281,11 +309,9 @@ pub trait IncomingViewingKey: private::SealedChangeLevelKey + std::marker::Sized fn derive_address( &self, child_index: NonHardenedChildIndex, - ) -> Result { - let child_key = self - .extended_pubkey() - .derive_public_key(child_index.into())?; - Ok(pubkey_to_address(&child_key.public_key)) + ) -> Result { + let child_key = self.extended_pubkey().derive_child(child_index.into())?; + Ok(pubkey_to_address(child_key.public_key())) } /// Searches the space of child indexes for an index that will @@ -309,18 +335,26 @@ pub trait IncomingViewingKey: private::SealedChangeLevelKey + std::marker::Sized fn serialize(&self) -> Vec { let extpubkey = self.extended_pubkey(); - let mut buf = extpubkey.chain_code.clone(); - buf.extend(extpubkey.public_key.serialize().to_vec()); + let mut buf = extpubkey.attrs().chain_code.to_vec(); + buf.extend_from_slice(&extpubkey.public_key().serialize()); buf } - fn deserialize(data: &[u8; 65]) -> Result { - let chain_code = data[..32].to_vec(); + fn deserialize(data: &[u8; 65]) -> Result { + let chain_code = data[..32].try_into().expect("correct length"); let public_key = PublicKey::from_slice(&data[32..])?; - Ok(Self::from_extended_pubkey(ExtendedPubKey { + Ok(Self::from_extended_pubkey(ExtendedPublicKey::new( public_key, - chain_code, - })) + ExtendedKeyAttrs { + depth: 4, + // We do not expose the inner `ExtendedPublicKey`, so we can use a dummy + // value for the `parent_fingerprint` that is not encoded in an + // `IncomingViewingKey`. + parent_fingerprint: [0xff, 0xff, 0xff, 0xff], + child_number: Self::SCOPE.into(), + chain_code, + }, + ))) } } @@ -331,14 +365,16 @@ pub trait IncomingViewingKey: private::SealedChangeLevelKey + std::marker::Sized /// /// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki #[derive(Clone, Debug)] -pub struct ExternalIvk(ExtendedPubKey); +pub struct ExternalIvk(ExtendedPublicKey); impl private::SealedChangeLevelKey for ExternalIvk { - fn extended_pubkey(&self) -> &ExtendedPubKey { + const SCOPE: TransparentKeyScope = TransparentKeyScope(0); + + fn extended_pubkey(&self) -> &ExtendedPublicKey { &self.0 } - fn from_extended_pubkey(key: ExtendedPubKey) -> Self { + fn from_extended_pubkey(key: ExtendedPublicKey) -> Self { ExternalIvk(key) } } @@ -353,14 +389,16 @@ impl IncomingViewingKey for ExternalIvk {} /// /// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki #[derive(Clone, Debug)] -pub struct InternalIvk(ExtendedPubKey); +pub struct InternalIvk(ExtendedPublicKey); impl private::SealedChangeLevelKey for InternalIvk { - fn extended_pubkey(&self) -> &ExtendedPubKey { + const SCOPE: TransparentKeyScope = TransparentKeyScope(1); + + fn extended_pubkey(&self) -> &ExtendedPublicKey { &self.0 } - fn from_extended_pubkey(key: ExtendedPubKey) -> Self { + fn from_extended_pubkey(key: ExtendedPublicKey) -> Self { InternalIvk(key) } } @@ -388,7 +426,7 @@ impl ExternalOvk { #[cfg(test)] mod tests { - use hdwallet::KeyIndex; + use bip32::ChildNumber; use subtle::ConstantTimeEq; use super::AccountPubKey; @@ -684,9 +722,9 @@ mod tests { #[test] fn nonhardened_index_tryfrom_keyindex() { - let nh: NonHardenedChildIndex = KeyIndex::Normal(0).try_into().unwrap(); + let nh: NonHardenedChildIndex = ChildNumber::new(0, false).unwrap().try_into().unwrap(); assert_eq!(nh.index(), 0); - assert!(NonHardenedChildIndex::try_from(KeyIndex::Hardened(0)).is_err()); + assert!(NonHardenedChildIndex::try_from(ChildNumber::new(0, true).unwrap()).is_err()); } }