kobe_btc/
standard_wallet.rs1#[cfg(feature = "alloc")]
6use alloc::string::{String, ToString};
7
8use bitcoin::{
9 Address, NetworkKind, PrivateKey, PublicKey, key::CompressedPublicKey, secp256k1::Secp256k1,
10};
11use zeroize::Zeroizing;
12
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")]
44 pub fn generate(network: Network, address_type: AddressType) -> Result<Self, Error> {
45 let secp = bitcoin::secp256k1::Secp256k1::new();
46 let (secret_key, _) = secp.generate_keypair(&mut bitcoin::secp256k1::rand::thread_rng());
47
48 let private_key = PrivateKey::new(secret_key, network.to_bitcoin_network());
49 let public_key = CompressedPublicKey::from_private_key(&secp, &private_key)
50 .expect("valid private key always produces valid public key");
51
52 let address = Self::create_address(&public_key, network, address_type);
53
54 Ok(Self {
55 private_key,
56 public_key,
57 address,
58 network,
59 address_type,
60 })
61 }
62
63 pub fn from_wif(wif: &str, address_type: AddressType) -> Result<Self, Error> {
69 let private_key: PrivateKey = wif.parse().map_err(|_| Error::InvalidWif)?;
70
71 let network = if private_key.network == NetworkKind::Main {
72 Network::Mainnet
73 } else {
74 Network::Testnet
75 };
76
77 let secp = bitcoin::secp256k1::Secp256k1::new();
78 let public_key = CompressedPublicKey::from_private_key(&secp, &private_key)
79 .expect("valid private key always produces valid public key");
80
81 let address = Self::create_address(&public_key, network, address_type);
82
83 Ok(Self {
84 private_key,
85 public_key,
86 address,
87 network,
88 address_type,
89 })
90 }
91
92 fn create_address(
94 public_key: &CompressedPublicKey,
95 network: Network,
96 address_type: AddressType,
97 ) -> Address {
98 let btc_network = network.to_bitcoin_network();
99
100 match address_type {
101 AddressType::P2pkh => Address::p2pkh(PublicKey::from(*public_key), btc_network),
102 AddressType::P2shP2wpkh => Address::p2shwpkh(public_key, btc_network),
103 AddressType::P2wpkh => Address::p2wpkh(public_key, btc_network),
104 AddressType::P2tr => {
105 let secp = Secp256k1::verification_only();
106 let internal_key = public_key.0.x_only_public_key().0;
107 Address::p2tr(&secp, internal_key, None, btc_network)
108 }
109 }
110 }
111
112 #[must_use]
114 pub fn private_key_wif(&self) -> Zeroizing<String> {
115 Zeroizing::new(self.private_key.to_wif())
116 }
117
118 #[must_use]
120 pub fn public_key_hex(&self) -> String {
121 self.public_key.to_string()
122 }
123
124 #[must_use]
126 pub fn address(&self) -> &Address {
127 &self.address
128 }
129
130 #[must_use]
132 pub fn address_string(&self) -> String {
133 self.address.to_string()
134 }
135
136 #[must_use]
138 pub const fn network(&self) -> Network {
139 self.network
140 }
141
142 #[must_use]
144 pub const fn address_type(&self) -> AddressType {
145 self.address_type
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[cfg(feature = "rand")]
154 #[test]
155 fn test_generate_mainnet_p2wpkh() {
156 let wallet = StandardWallet::generate(Network::Mainnet, AddressType::P2wpkh).unwrap();
157 assert!(wallet.address_string().starts_with("bc1q"));
158 assert_eq!(wallet.network(), Network::Mainnet);
159 }
160
161 #[cfg(feature = "rand")]
162 #[test]
163 fn test_generate_mainnet_p2pkh() {
164 let wallet = StandardWallet::generate(Network::Mainnet, AddressType::P2pkh).unwrap();
165 assert!(wallet.address_string().starts_with('1'));
166 }
167
168 #[cfg(feature = "rand")]
169 #[test]
170 fn test_generate_mainnet_p2sh() {
171 let wallet = StandardWallet::generate(Network::Mainnet, AddressType::P2shP2wpkh).unwrap();
172 assert!(wallet.address_string().starts_with('3'));
173 }
174
175 #[cfg(feature = "rand")]
176 #[test]
177 fn test_generate_mainnet_p2tr() {
178 let wallet = StandardWallet::generate(Network::Mainnet, AddressType::P2tr).unwrap();
179 assert!(wallet.address_string().starts_with("bc1p"));
180 }
181
182 #[cfg(feature = "rand")]
183 #[test]
184 fn test_generate_testnet() {
185 let wallet = StandardWallet::generate(Network::Testnet, AddressType::P2wpkh).unwrap();
186 assert!(wallet.address_string().starts_with("tb1q"));
187 }
188}