1use alloc::string::String;
4use alloc::vec::Vec;
5use ed25519_dalek::VerifyingKey;
6use zeroize::Zeroizing;
7
8use crate::Error;
9use crate::slip10::DerivedKey;
10use kobe_core::Wallet;
11
12#[derive(Debug, Clone)]
14pub struct DerivedAddress {
15 pub path: String,
17 pub private_key_hex: Zeroizing<String>,
19 pub public_key_hex: String,
21 pub address: String,
23}
24
25#[derive(Debug)]
27pub struct Deriver<'a> {
28 wallet: &'a Wallet,
29}
30
31impl<'a> Deriver<'a> {
32 #[inline]
34 #[must_use]
35 pub const fn new(wallet: &'a Wallet) -> Self {
36 Self { wallet }
37 }
38
39 #[inline]
47 pub fn derive(&self, account: u32) -> Result<DerivedAddress, Error> {
48 self.derive_with_change(account, 0)
49 }
50
51 pub fn derive_with_change(&self, account: u32, change: u32) -> Result<DerivedAddress, Error> {
59 let path = DerivedKey::format_path(account, change);
60 self.derive_at_path(account, change).map(|mut addr| {
61 addr.path = path;
62 addr
63 })
64 }
65
66 fn derive_at_path(&self, account: u32, change: u32) -> Result<DerivedAddress, Error> {
68 let derived = DerivedKey::derive_solana_path(self.wallet.seed(), account, change)?;
69 let signing_key = derived.to_signing_key();
70 let verifying_key: VerifyingKey = signing_key.verifying_key();
71
72 let public_key_bytes = verifying_key.as_bytes();
73 let address = bs58::encode(public_key_bytes).into_string();
74
75 Ok(DerivedAddress {
76 path: DerivedKey::format_path(account, change),
77 private_key_hex: Zeroizing::new(hex::encode(derived.private_key.as_slice())),
78 public_key_hex: hex::encode(public_key_bytes),
79 address,
80 })
81 }
82
83 pub fn derive_many(
89 &self,
90 start_account: u32,
91 count: u32,
92 ) -> Result<Vec<DerivedAddress>, Error> {
93 (start_account..start_account + count)
94 .map(|account| self.derive(account))
95 .collect()
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 fn test_wallet() -> Wallet {
104 Wallet::from_mnemonic(
105 "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
106 None,
107 )
108 .unwrap()
109 }
110
111 #[test]
112 fn test_derive_address() {
113 let wallet = test_wallet();
114 let deriver = Deriver::new(&wallet);
115 let addr = deriver.derive(0).unwrap();
116
117 assert!(addr.address.len() >= 32 && addr.address.len() <= 44);
119 assert_eq!(addr.path, "m/44'/501'/0'/0'");
120 }
121
122 #[test]
123 fn test_derive_many() {
124 let wallet = test_wallet();
125 let deriver = Deriver::new(&wallet);
126 let addresses = deriver.derive_many(0, 3).unwrap();
127
128 assert_eq!(addresses.len(), 3);
129 assert_eq!(addresses[0].path, "m/44'/501'/0'/0'");
130 assert_eq!(addresses[1].path, "m/44'/501'/1'/0'");
131 assert_eq!(addresses[2].path, "m/44'/501'/2'/0'");
132
133 assert_ne!(addresses[0].address, addresses[1].address);
135 assert_ne!(addresses[1].address, addresses[2].address);
136 }
137
138 #[test]
139 fn test_deterministic_derivation() {
140 let wallet = test_wallet();
141 let deriver = Deriver::new(&wallet);
142
143 let addr1 = deriver.derive(0).unwrap();
144 let addr2 = deriver.derive(0).unwrap();
145
146 assert_eq!(addr1.address, addr2.address);
147 assert_eq!(*addr1.private_key_hex, *addr2.private_key_hex);
148 }
149}