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)]
30pub struct StandardWallet {
31 private_key: PrivateKey,
33 public_key: CompressedPublicKey,
35 address: Address,
37 network: Network,
39 address_type: AddressType,
41}
42
43impl StandardWallet {
44 #[cfg(feature = "rand")]
59 pub fn generate(network: Network, address_type: AddressType) -> Result<Self, Error> {
60 let secp = bitcoin::secp256k1::Secp256k1::new();
61 let (secret_key, _) = secp.generate_keypair(&mut bitcoin::secp256k1::rand::thread_rng());
62
63 let private_key = PrivateKey::new(secret_key, network.to_bitcoin_network());
64 let public_key = CompressedPublicKey::from_private_key(&secp, &private_key)
65 .expect("valid private key always produces valid public key");
66
67 let address = create_address(&public_key, network, address_type);
68
69 Ok(Self {
70 private_key,
71 public_key,
72 address,
73 network,
74 address_type,
75 })
76 }
77
78 pub fn from_wif(wif: &str, address_type: AddressType) -> Result<Self, Error> {
89 let private_key: PrivateKey = wif.parse().map_err(|_| Error::InvalidWif)?;
90
91 let network = if private_key.network == NetworkKind::Main {
92 Network::Mainnet
93 } else {
94 Network::Testnet
95 };
96
97 let secp = bitcoin::secp256k1::Secp256k1::new();
98 let public_key = CompressedPublicKey::from_private_key(&secp, &private_key)
99 .expect("valid private key always produces valid public key");
100
101 let address = create_address(&public_key, network, address_type);
102
103 Ok(Self {
104 private_key,
105 public_key,
106 address,
107 network,
108 address_type,
109 })
110 }
111
112 pub fn from_hex(
123 hex_str: &str,
124 network: Network,
125 address_type: AddressType,
126 ) -> Result<Self, Error> {
127 let hex_str = hex_str.strip_prefix("0x").unwrap_or(hex_str);
128 let bytes = hex::decode(hex_str).map_err(|_| Error::InvalidHex)?;
129
130 if bytes.len() != 32 {
131 return Err(Error::InvalidPrivateKey);
132 }
133
134 let secret_key = bitcoin::secp256k1::SecretKey::from_slice(&bytes)
135 .map_err(|_| Error::InvalidPrivateKey)?;
136
137 let private_key = PrivateKey::new(secret_key, network.to_bitcoin_network());
138
139 let secp = bitcoin::secp256k1::Secp256k1::new();
140 let public_key = CompressedPublicKey::from_private_key(&secp, &private_key)
141 .expect("valid private key always produces valid public key");
142
143 let address = create_address(&public_key, network, address_type);
144
145 Ok(Self {
146 private_key,
147 public_key,
148 address,
149 network,
150 address_type,
151 })
152 }
153
154 #[inline]
156 #[must_use]
157 pub fn secret_bytes(&self) -> Zeroizing<[u8; 32]> {
158 Zeroizing::new(self.private_key.inner.secret_bytes())
159 }
160
161 #[inline]
163 #[must_use]
164 pub fn secret_hex(&self) -> Zeroizing<String> {
165 Zeroizing::new(hex::encode(self.private_key.inner.secret_bytes()))
166 }
167
168 #[inline]
170 #[must_use]
171 pub fn to_wif(&self) -> Zeroizing<String> {
172 Zeroizing::new(self.private_key.to_wif())
173 }
174
175 #[inline]
177 #[must_use]
178 pub fn pubkey_hex(&self) -> String {
179 self.public_key.to_string()
180 }
181
182 #[inline]
184 #[must_use]
185 pub fn address(&self) -> String {
186 self.address.to_string()
187 }
188
189 #[must_use]
191 pub const fn network(&self) -> Network {
192 self.network
193 }
194
195 #[must_use]
197 pub const fn address_type(&self) -> AddressType {
198 self.address_type
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[cfg(feature = "rand")]
207 #[test]
208 fn test_generate_mainnet_p2wpkh() {
209 let wallet = StandardWallet::generate(Network::Mainnet, AddressType::P2wpkh).unwrap();
210 assert!(wallet.address().starts_with("bc1q"));
211 assert_eq!(wallet.network(), Network::Mainnet);
212 }
213
214 #[cfg(feature = "rand")]
215 #[test]
216 fn test_generate_mainnet_p2pkh() {
217 let wallet = StandardWallet::generate(Network::Mainnet, AddressType::P2pkh).unwrap();
218 assert!(wallet.address().starts_with('1'));
219 }
220
221 #[cfg(feature = "rand")]
222 #[test]
223 fn test_generate_mainnet_p2sh() {
224 let wallet = StandardWallet::generate(Network::Mainnet, AddressType::P2shP2wpkh).unwrap();
225 assert!(wallet.address().starts_with('3'));
226 }
227
228 #[cfg(feature = "rand")]
229 #[test]
230 fn test_generate_mainnet_p2tr() {
231 let wallet = StandardWallet::generate(Network::Mainnet, AddressType::P2tr).unwrap();
232 assert!(wallet.address().starts_with("bc1p"));
233 }
234
235 #[cfg(feature = "rand")]
236 #[test]
237 fn test_generate_testnet() {
238 let wallet = StandardWallet::generate(Network::Testnet, AddressType::P2wpkh).unwrap();
239 assert!(wallet.address().starts_with("tb1q"));
240 }
241}