kobe_btc/
standard_wallet.rs1#[cfg(feature = "alloc")]
7use alloc::string::{String, ToString};
8
9use bitcoin::{Address, NetworkKind, PrivateKey, key::CompressedPublicKey};
10use zeroize::Zeroizing;
11
12use crate::address::create_address;
13use crate::{AddressType, Error, Network};
14
15#[derive(Debug)]
20pub struct StandardWallet {
21 private_key: PrivateKey,
23 public_key: CompressedPublicKey,
25 address: Address,
27 network: Network,
29 address_type: AddressType,
31}
32
33impl StandardWallet {
34 #[cfg(feature = "rand")]
49 pub fn generate(network: Network, address_type: AddressType) -> Result<Self, Error> {
50 let secp = bitcoin::secp256k1::Secp256k1::new();
51 let (secret_key, _) = secp.generate_keypair(&mut bitcoin::secp256k1::rand::thread_rng());
52
53 let private_key = PrivateKey::new(secret_key, network.to_bitcoin_network());
54 let public_key = CompressedPublicKey::from_private_key(&secp, &private_key)
55 .expect("valid private key always produces valid public key");
56
57 let address = create_address(&public_key, network, address_type);
58
59 Ok(Self {
60 private_key,
61 public_key,
62 address,
63 network,
64 address_type,
65 })
66 }
67
68 pub fn from_wif(wif: &str, address_type: AddressType) -> Result<Self, Error> {
79 let private_key: PrivateKey = wif.parse().map_err(|_| Error::InvalidWif)?;
80
81 let network = if private_key.network == NetworkKind::Main {
82 Network::Mainnet
83 } else {
84 Network::Testnet
85 };
86
87 let secp = bitcoin::secp256k1::Secp256k1::new();
88 let public_key = CompressedPublicKey::from_private_key(&secp, &private_key)
89 .expect("valid private key always produces valid public key");
90
91 let address = create_address(&public_key, network, address_type);
92
93 Ok(Self {
94 private_key,
95 public_key,
96 address,
97 network,
98 address_type,
99 })
100 }
101
102 pub fn from_hex(
113 hex_str: &str,
114 network: Network,
115 address_type: AddressType,
116 ) -> Result<Self, Error> {
117 let hex_str = hex_str.strip_prefix("0x").unwrap_or(hex_str);
118 let bytes = hex::decode(hex_str).map_err(|_| Error::InvalidHex)?;
119
120 if bytes.len() != 32 {
121 return Err(Error::InvalidPrivateKey);
122 }
123
124 let secret_key = bitcoin::secp256k1::SecretKey::from_slice(&bytes)
125 .map_err(|_| Error::InvalidPrivateKey)?;
126
127 let private_key = PrivateKey::new(secret_key, network.to_bitcoin_network());
128
129 let secp = bitcoin::secp256k1::Secp256k1::new();
130 let public_key = CompressedPublicKey::from_private_key(&secp, &private_key)
131 .expect("valid private key always produces valid public key");
132
133 let address = create_address(&public_key, network, address_type);
134
135 Ok(Self {
136 private_key,
137 public_key,
138 address,
139 network,
140 address_type,
141 })
142 }
143
144 #[inline]
146 #[must_use]
147 pub fn secret_bytes(&self) -> Zeroizing<[u8; 32]> {
148 Zeroizing::new(self.private_key.inner.secret_bytes())
149 }
150
151 #[inline]
153 #[must_use]
154 pub fn secret_hex(&self) -> Zeroizing<String> {
155 Zeroizing::new(hex::encode(self.private_key.inner.secret_bytes()))
156 }
157
158 #[inline]
160 #[must_use]
161 pub fn to_wif(&self) -> Zeroizing<String> {
162 Zeroizing::new(self.private_key.to_wif())
163 }
164
165 #[inline]
167 #[must_use]
168 pub fn pubkey_hex(&self) -> String {
169 self.public_key.to_string()
170 }
171
172 #[inline]
174 #[must_use]
175 pub fn address(&self) -> String {
176 self.address.to_string()
177 }
178
179 #[must_use]
181 pub const fn network(&self) -> Network {
182 self.network
183 }
184
185 #[must_use]
187 pub const fn address_type(&self) -> AddressType {
188 self.address_type
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[cfg(feature = "rand")]
197 #[test]
198 fn test_generate_mainnet_p2wpkh() {
199 let wallet = StandardWallet::generate(Network::Mainnet, AddressType::P2wpkh).unwrap();
200 assert!(wallet.address().starts_with("bc1q"));
201 assert_eq!(wallet.network(), Network::Mainnet);
202 }
203
204 #[cfg(feature = "rand")]
205 #[test]
206 fn test_generate_mainnet_p2pkh() {
207 let wallet = StandardWallet::generate(Network::Mainnet, AddressType::P2pkh).unwrap();
208 assert!(wallet.address().starts_with('1'));
209 }
210
211 #[cfg(feature = "rand")]
212 #[test]
213 fn test_generate_mainnet_p2sh() {
214 let wallet = StandardWallet::generate(Network::Mainnet, AddressType::P2shP2wpkh).unwrap();
215 assert!(wallet.address().starts_with('3'));
216 }
217
218 #[cfg(feature = "rand")]
219 #[test]
220 fn test_generate_mainnet_p2tr() {
221 let wallet = StandardWallet::generate(Network::Mainnet, AddressType::P2tr).unwrap();
222 assert!(wallet.address().starts_with("bc1p"));
223 }
224
225 #[cfg(feature = "rand")]
226 #[test]
227 fn test_generate_testnet() {
228 let wallet = StandardWallet::generate(Network::Testnet, AddressType::P2wpkh).unwrap();
229 assert!(wallet.address().starts_with("tb1q"));
230 }
231}