qp_rusty_crystals_hdwallet/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
6extern crate alloc;
7
8use crate::hderive::ExtendedPrivKey;
9use alloc::{
10 string::{String, ToString},
11 vec::Vec,
12};
13use bip39::{Language, Mnemonic};
14use core::str::FromStr;
15use qp_rusty_crystals_dilithium::ml_dsa_87::Keypair;
16
17use zeroize::Zeroize;
18
19#[cfg(test)]
20mod test_vectors;
21#[cfg(test)]
22mod tests;
23
24pub mod hderive;
25pub mod wormhole;
26
27pub use wormhole::WormholePair;
28
29pub use qp_rusty_crystals_dilithium::{SensitiveBytes32, SensitiveBytes64};
31
32#[derive(Debug, thiserror::Error, PartialEq, Eq)]
33pub enum HDLatticeError {
34 #[error("BIP39 error: {0}")]
35 Bip39Error(String),
36 #[error("Key derivation failed: {0}")]
37 KeyDerivationFailed(String),
38 #[error("Bad entropy bit count: {0}")]
39 BadEntropyBitCount(usize),
40 #[error("Mnemonic derivation failed: {0}")]
41 MnemonicDerivationFailed(String),
42 #[error("Invalid wormhole path: {0}")]
43 InvalidWormholePath(String),
44 #[error("Invalid BIP44 path: {0}")]
45 InvalidPath(String),
46 #[error("hderive error: {0:?}")]
47 GenericError(hderive::Error),
48}
49
50pub const ROOT_PATH: &str = "m";
51pub const PURPOSE: &str = "44'";
52pub const QUANTUS_DILITHIUM_CHAIN_ID: &str = "189189'";
53pub const QUANTUS_WORMHOLE_CHAIN_ID: &str = "189189189'";
54
55pub fn mnemonic_to_seed(
80 mut mnemonic: String,
81 passphrase: Option<&str>,
82) -> Result<[u8; 64], HDLatticeError> {
83 let parsed_mnemonic = Mnemonic::parse_in_normalized(Language::English, &mnemonic)
85 .map_err(|e| HDLatticeError::Bip39Error(e.to_string()))?;
86
87 let seed: [u8; 64] = parsed_mnemonic.to_seed_normalized(passphrase.unwrap_or(""));
89
90 mnemonic.zeroize();
92
93 Ok(seed)
94}
95
96pub fn derive_key_from_seed(seed: SensitiveBytes64, path: &str) -> Result<Keypair, HDLatticeError> {
102 check_derivation_path(path)?;
104
105 let xpriv = ExtendedPrivKey::derive(seed.as_bytes(), path)
107 .map_err(|_e| HDLatticeError::KeyDerivationFailed(path.to_string()))?;
108 let mut secret = xpriv.secret();
109 let derived_entropy = SensitiveBytes32::from(&mut secret);
110
111 let keypair = Keypair::generate(derived_entropy);
113
114 Ok(keypair)
117}
118
119pub fn derive_key_from_mnemonic(
121 mnemonic: &str,
122 passphrase: Option<&str>,
123 path: &str,
124) -> Result<Keypair, HDLatticeError> {
125 let mut seed = mnemonic_to_seed(mnemonic.to_string(), passphrase)?;
126 derive_key_from_seed(SensitiveBytes64::from(&mut seed), path)
127}
128
129pub fn derive_wormhole_from_mnemonic(
131 mnemonic: &str,
132 passphrase: Option<&str>,
133 path: &str,
134) -> Result<WormholePair, HDLatticeError> {
135 let mut seed = mnemonic_to_seed(mnemonic.to_string(), passphrase)?;
136 generate_wormhole_from_seed(SensitiveBytes64::from(&mut seed), path)
137}
138
139pub fn generate_wormhole_from_seed(
145 seed: SensitiveBytes64,
146 path: &str,
147) -> Result<WormholePair, HDLatticeError> {
148 if path.split("/").nth(2) != Some(QUANTUS_WORMHOLE_CHAIN_ID) {
150 return Err(HDLatticeError::InvalidWormholePath(path.to_string()));
151 }
152
153 check_derivation_path(path)?;
155
156 let xpriv = ExtendedPrivKey::derive(seed.as_bytes(), path)
158 .map_err(|_e| HDLatticeError::KeyDerivationFailed(path.to_string()))?;
159 let mut secret = xpriv.secret();
160 let derived_entropy = SensitiveBytes32::from(&mut secret);
161
162 let wormhole_pair = WormholePair::generate_new(derived_entropy);
164
165 Ok(wormhole_pair)
168}
169
170fn check_derivation_path(path: &str) -> Result<(), HDLatticeError> {
172 crate::hderive::DerivationPath::from_str(path).map_err(HDLatticeError::GenericError)?;
173 Ok(())
174}
175
176pub fn generate_mnemonic(entropy: SensitiveBytes32) -> Result<String, HDLatticeError> {
185 let mnemonic = Mnemonic::from_entropy(entropy.as_bytes())
187 .map_err(|e| HDLatticeError::MnemonicDerivationFailed(e.to_string()))?;
188
189 let result = mnemonic.words().collect::<Vec<&str>>().join(" ");
190
191 Ok(result)
194}