Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wallet: change the derivation path for modified ZIP32 #4307

Merged
merged 5 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Changed the modified ZIP32 derivation path to a non-default value
(`"m/44'/877'/0'/0'/2147483647'"`).
([\#4307](https://github.com/anoma/namada/pull/4307))
48 changes: 20 additions & 28 deletions crates/apps_lib/src/cli/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,20 +544,16 @@ async fn transparent_key_and_address_derive(
}
let alias = alias.to_lowercase();
let alias = if !use_device {
if let SchemeType::Ed25519 = scheme {
let default_path = DerivationPath::default_for_transparent_scheme(
SchemeType::Ed25519,
let (zip32_seed_path, zip32_scheme) = DerivationPath::modified_zip32();
if scheme == zip32_scheme && derivation_path == zip32_seed_path {
display_line!(
io,
"Path {} is not allowed for safety as it's used in modified \
ZIP32 as a seed for deriving shielded keys.",
zip32_seed_path
);
if derivation_path == default_path {
display_line!(
io,
"Path {} is not allowed for safety as it's used in \
modified ZIP32 as a seed for deriving shielded keys.",
default_path
);
display_line!(io, "No changes are persisted. Exiting.");
cli::safe_exit(1)
}
display_line!(io, "No changes are persisted. Exiting.");
cli::safe_exit(1)
}
let encryption_password =
read_and_confirm_encryption_password(unsafe_dont_encrypt);
Expand Down Expand Up @@ -671,21 +667,17 @@ fn transparent_key_and_address_gen(
cli::safe_exit(1)
}
if unsafe_dont_encrypt {
if let SchemeType::Ed25519 = scheme {
let default_path =
DerivationPath::default_for_transparent_scheme(
SchemeType::Ed25519,
);
if derivation_path == default_path {
display_line!(
io,
"Path {} is also used in modified ZIP32 as a seed for \
deriving shielded keys. Because you requested to \
store this key unencrypted, this may inadvertently \
expose the derived shielded key.",
default_path
);
}
let (zip32_seed_path, zip32_scheme) =
DerivationPath::modified_zip32();
if scheme == zip32_scheme && derivation_path == zip32_seed_path {
display_line!(
io,
"Path {} is also used in modified ZIP32 as a seed for \
deriving shielded keys. Because you requested to store \
this key unencrypted, this may inadvertently expose the \
derived shielded key.",
zip32_seed_path
);
}
}
let (_mnemonic, seed) = Wallet::<CliWalletUtils>::gen_hd_seed(
Expand Down
1 change: 1 addition & 0 deletions crates/wallet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ toml.workspace = true
zeroize.workspace = true

[dev-dependencies]
namada_core = { path = "../core", features = ["testing"]}
base58.workspace = true
19 changes: 19 additions & 0 deletions crates/wallet/src/derivation_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use tiny_hderive::Error as HDeriveError;

const BIP44_PURPOSE: u32 = 44;
const ZIP32_PURPOSE: u32 = 32;
// Maximum allowed value in BIP44
const MODIFIED_ZIP32_ADDR: u32 = 0x7FFFFFFF;

const ETH_COIN_TYPE: u32 = 60;
const NAMADA_COIN_TYPE: u32 = 877;
Expand Down Expand Up @@ -151,6 +153,15 @@ impl DerivationPath {
self.is_zip32_conform() && self.has_shielded_compatible_coin_type()
}

/// Modified ZIP32 path is used to derive Ed25519 keys to derive shielded
/// keys in way that's compatible with the Ledger device app, in which the
/// pure ZIP32 cannot be used because its secret is inaccessible.
pub fn modified_zip32() -> (Self, SchemeType) {
let scheme = SchemeType::Ed25519;
let path = Self::bip44(scheme, 0, 0, MODIFIED_ZIP32_ADDR);
(path.hardened(scheme), scheme)
}

fn bip44_base_indexes_for_scheme(scheme: SchemeType) -> Vec<ChildIndex> {
vec![
ChildIndex::Hardened(BIP44_PURPOSE),
Expand Down Expand Up @@ -396,5 +407,13 @@ mod tests {
!path_z_2.is_namada_transparent_compliant(SchemeType::Secp256k1)
);
assert!(path_z_2.is_namada_shielded_compliant());

let (modified_zip32, zip32_scheme) = DerivationPath::modified_zip32();
assert!(matches!(zip32_scheme, SchemeType::Ed25519));
assert_eq!(
modified_zip32,
DerivationPath::from_path_string("m/44'/877'/0'/0'/2147483647'")
.expect("Path construction cannot fail.")
);
}
}
27 changes: 10 additions & 17 deletions crates/wallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,20 +598,13 @@ impl<U: WalletIo> Wallet<U> {
seed.as_bytes()
} else {
// Path to obtain the ZIP32 seed
let zip32_seed_path =
DerivationPath::default_for_transparent_scheme(
SchemeType::Ed25519,
);
let (zip32_seed_path, scheme) = DerivationPath::modified_zip32();
// Obtain the ZIP32 seed using SLIP10
&derive_hd_secret_key(
SchemeType::Ed25519,
seed.as_bytes(),
zip32_seed_path,
)
.try_to_sk::<ed25519::SecretKey>()
.expect("Expected Ed25519 key")
.0
.to_bytes()[..]
&derive_hd_secret_key(scheme, seed.as_bytes(), zip32_seed_path)
.try_to_sk::<ed25519::SecretKey>()
.expect("Expected Ed25519 key")
.0
.to_bytes()[..]
};
// Now ZIP32 derive the extended spending key from the new seed
let spend_key = derive_hd_spending_key(seed, derivation_path.clone());
Expand Down Expand Up @@ -649,8 +642,8 @@ impl<U: WalletIo> Wallet<U> {
///
/// Any usage of this function should be careful not expose a shielded key
/// that may be derived via modified ZIP32 from this key (specifically when
/// the scheme is Ed25519 with the default HD derivation path and no
/// encryption password is being used).
/// the derivation path and schema matching `DerivationPath::modified_zip32`
/// and no encryption password is being used).
#[allow(clippy::too_many_arguments)]
pub fn derive_store_key_from_mnemonic_code(
&mut self,
Expand Down Expand Up @@ -781,8 +774,8 @@ impl<U: WalletIo> Wallet<U> {
///
/// Any usage of this function should be careful not expose a shielded key
/// that may be derived via modified ZIP32 from this key (specifically when
/// the scheme is Ed25519 with the default HD derivation path and no
/// encryption password is being used).
/// the derivation path and schema matching `DerivationPath::modified_zip32`
/// and no encryption password is being used).
pub fn derive_store_hd_secret_key(
&mut self,
scheme: SchemeType,
Expand Down
Loading