1use alloc::string::{String, ToString};
4use alloc::vec::Vec;
5
6use ed25519_dalek::VerifyingKey;
7use kobe::Wallet;
8use zeroize::Zeroizing;
9
10use crate::Error;
11use crate::derivation_style::DerivationStyle;
12use crate::slip10::DerivedKey;
13
14#[derive(Debug, Clone)]
16pub struct DerivedAddress {
17 pub path: String,
19 pub private_key_hex: Zeroizing<String>,
21 pub keypair_base58: Zeroizing<String>,
25 pub public_key_hex: String,
27 pub address: String,
29}
30
31#[derive(Debug)]
36pub struct Deriver<'a> {
37 wallet: &'a Wallet,
39}
40
41impl<'a> Deriver<'a> {
42 #[inline]
44 #[must_use]
45 pub const fn new(wallet: &'a Wallet) -> Self {
46 Self { wallet }
47 }
48
49 #[inline]
61 pub fn derive(&self, index: u32) -> Result<DerivedAddress, Error> {
62 self.derive_with(DerivationStyle::Standard, index)
63 }
64
65 #[allow(deprecated)]
82 pub fn derive_with(&self, style: DerivationStyle, index: u32) -> Result<DerivedAddress, Error> {
83 let derived = match style {
84 DerivationStyle::Standard => {
85 DerivedKey::derive_standard_path(self.wallet.seed(), index)?
86 }
87 DerivationStyle::Trust => DerivedKey::derive_trust_path(self.wallet.seed(), index)?,
88 DerivationStyle::LedgerLive => {
89 DerivedKey::derive_ledger_live_path(self.wallet.seed(), index)?
90 }
91 DerivationStyle::Legacy => DerivedKey::derive_legacy_path(self.wallet.seed(), index)?,
92 };
93
94 let signing_key = derived.to_signing_key();
95 let verifying_key: VerifyingKey = signing_key.verifying_key();
96 let public_key_bytes = verifying_key.as_bytes();
97
98 let mut keypair_bytes = [0u8; 64];
100 keypair_bytes[..32].copy_from_slice(derived.private_key.as_slice());
101 keypair_bytes[32..].copy_from_slice(public_key_bytes);
102 let keypair_b58 = bs58::encode(&keypair_bytes).into_string();
103 keypair_bytes.fill(0);
104
105 Ok(DerivedAddress {
106 path: style.path(index),
107 private_key_hex: Zeroizing::new(hex::encode(derived.private_key.as_slice())),
108 keypair_base58: Zeroizing::new(keypair_b58),
109 public_key_hex: hex::encode(public_key_bytes),
110 address: bs58::encode(public_key_bytes).into_string(),
111 })
112 }
113
114 #[inline]
125 pub fn derive_many(&self, start: u32, count: u32) -> Result<Vec<DerivedAddress>, Error> {
126 self.derive_many_with(DerivationStyle::Standard, start, count)
127 }
128
129 pub fn derive_many_with(
141 &self,
142 style: DerivationStyle,
143 start: u32,
144 count: u32,
145 ) -> Result<Vec<DerivedAddress>, Error> {
146 (start..start + count)
147 .map(|index| self.derive_with(style, index))
148 .collect()
149 }
150
151 pub fn derive_path(&self, path: &str) -> Result<DerivedAddress, Error> {
167 let derived = DerivedKey::derive_path(self.wallet.seed(), path)?;
168
169 let signing_key = derived.to_signing_key();
170 let verifying_key: VerifyingKey = signing_key.verifying_key();
171 let public_key_bytes = verifying_key.as_bytes();
172
173 let mut keypair_bytes = [0u8; 64];
175 keypair_bytes[..32].copy_from_slice(derived.private_key.as_slice());
176 keypair_bytes[32..].copy_from_slice(public_key_bytes);
177 let keypair_b58 = bs58::encode(&keypair_bytes).into_string();
178 keypair_bytes.fill(0);
179
180 Ok(DerivedAddress {
181 path: path.to_string(),
182 private_key_hex: Zeroizing::new(hex::encode(derived.private_key.as_slice())),
183 keypair_base58: Zeroizing::new(keypair_b58),
184 public_key_hex: hex::encode(public_key_bytes),
185 address: bs58::encode(public_key_bytes).into_string(),
186 })
187 }
188}
189
190#[cfg(test)]
191#[allow(deprecated)]
192mod tests {
193 use super::*;
194
195 fn test_wallet() -> Wallet {
196 Wallet::from_mnemonic(
197 "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
198 None,
199 )
200 .unwrap()
201 }
202
203 #[test]
204 fn test_derive_address() {
205 let wallet = test_wallet();
206 let deriver = Deriver::new(&wallet);
207 let addr = deriver.derive(0).unwrap();
208
209 assert!(addr.address.len() >= 32 && addr.address.len() <= 44);
211 assert_eq!(addr.path, "m/44'/501'/0'/0'");
212 }
213
214 #[test]
215 fn test_derive_many() {
216 let wallet = test_wallet();
217 let deriver = Deriver::new(&wallet);
218 let addresses = deriver.derive_many(0, 3).unwrap();
219
220 assert_eq!(addresses.len(), 3);
221 assert_eq!(addresses[0].path, "m/44'/501'/0'/0'");
222 assert_eq!(addresses[1].path, "m/44'/501'/1'/0'");
223 assert_eq!(addresses[2].path, "m/44'/501'/2'/0'");
224
225 assert_ne!(addresses[0].address, addresses[1].address);
227 assert_ne!(addresses[1].address, addresses[2].address);
228 }
229
230 #[test]
231 fn test_deterministic_derivation() {
232 let wallet = test_wallet();
233 let deriver = Deriver::new(&wallet);
234
235 let addr1 = deriver.derive(0).unwrap();
236 let addr2 = deriver.derive(0).unwrap();
237
238 assert_eq!(addr1.address, addr2.address);
239 assert_eq!(*addr1.private_key_hex, *addr2.private_key_hex);
240 }
241
242 #[test]
243 fn test_derive_with_trust() {
244 let wallet = test_wallet();
245 let deriver = Deriver::new(&wallet);
246 let addr = deriver.derive_with(DerivationStyle::Trust, 0).unwrap();
247
248 assert_eq!(addr.path, "m/44'/501'/0'");
249 assert!(addr.address.len() >= 32 && addr.address.len() <= 44);
250 }
251
252 #[test]
253 fn test_derive_with_ledger_live() {
254 let wallet = test_wallet();
255 let deriver = Deriver::new(&wallet);
256 let addr = deriver.derive_with(DerivationStyle::LedgerLive, 0).unwrap();
257
258 assert_eq!(addr.path, "m/44'/501'/0'/0'/0'");
259 assert!(addr.address.len() >= 32 && addr.address.len() <= 44);
260 }
261
262 #[test]
263 fn test_different_styles_produce_different_addresses() {
264 let wallet = test_wallet();
265 let deriver = Deriver::new(&wallet);
266
267 let standard = deriver.derive_with(DerivationStyle::Standard, 0).unwrap();
268 let trust = deriver.derive_with(DerivationStyle::Trust, 0).unwrap();
269 let ledger_live = deriver.derive_with(DerivationStyle::LedgerLive, 0).unwrap();
270 let legacy = deriver.derive_with(DerivationStyle::Legacy, 0).unwrap();
271
272 assert_ne!(standard.address, trust.address);
274 assert_ne!(standard.address, ledger_live.address);
275 assert_ne!(standard.address, legacy.address);
276 assert_ne!(trust.address, ledger_live.address);
277 }
278}