1use anyhow::Result;
2use bech32::{ToBase32, Variant};
3use bs58;
4use ripemd::Ripemd160;
5use secp256k1::{PublicKey, Secp256k1, SecretKey};
6use sha2::{Digest, Sha256};
7
8use crate::BitcoinClientType;
9
10pub struct BitcoinCrypto;
11
12impl BitcoinCrypto {
13 pub fn sha256(data: &[u8]) -> [u8; 32] {
15 let mut hasher = Sha256::new();
16 hasher.update(data);
17 hasher.finalize().into()
18 }
19
20 pub fn double_sha256(data: &[u8]) -> [u8; 32] {
22 let first = Self::sha256(data);
23 Self::sha256(&first)
24 }
25
26 pub fn ripemd160(data: &[u8]) -> [u8; 20] {
28 let mut hasher = Ripemd160::new();
29 hasher.update(data);
30 hasher.finalize().into()
31 }
32
33 pub fn hash160(data: &[u8]) -> [u8; 20] {
35 let sha256_hash = Self::sha256(data);
36 Self::ripemd160(&sha256_hash)
37 }
38
39 pub fn private_to_public(private_key: &[u8; 32], compressed: bool) -> Result<Vec<u8>> {
41 let secp = Secp256k1::new();
42 let secret_key = SecretKey::from_slice(private_key)?;
43 let public_key = PublicKey::from_secret_key(&secp, &secret_key);
44
45 if compressed {
46 Ok(public_key.serialize().to_vec())
47 } else {
48 Ok(public_key.serialize_uncompressed().to_vec())
49 }
50 }
51
52 pub fn public_key_to_p2pkh_address(
54 public_key: &[u8],
55 bitcoin_client_type: BitcoinClientType,
56 ) -> Result<String> {
57 let hash160 = Self::hash160(public_key);
58 Self::hash160_to_p2pkh_address(&hash160, bitcoin_client_type)
59 }
60
61 pub fn hash160_to_p2pkh_address(
63 hash160: &[u8; 20],
64 bitcoin_client_type: BitcoinClientType,
65 ) -> Result<String> {
66 let prefix = match bitcoin_client_type {
67 BitcoinClientType::Mainnet => 0x00,
68 BitcoinClientType::Testnet | BitcoinClientType::Signet | BitcoinClientType::Regtest => {
69 0x6f
70 }
71 };
72 let mut data = vec![prefix];
73 data.extend_from_slice(hash160);
74 let checksum = &Self::double_sha256(&data)[..4];
75 data.extend_from_slice(checksum);
76 Ok(bs58::encode(data).into_string())
77 }
78
79 pub fn hash160_to_p2sh_address(
81 hash160: &[u8; 20],
82 bitcoin_client_type: BitcoinClientType,
83 ) -> Result<String> {
84 let prefix = match bitcoin_client_type {
85 BitcoinClientType::Mainnet => 0x05,
86 BitcoinClientType::Testnet | BitcoinClientType::Signet | BitcoinClientType::Regtest => {
87 0xc4
88 }
89 };
90 let mut data = vec![prefix];
91 data.extend_from_slice(hash160);
92 let checksum = &Self::double_sha256(&data)[..4];
93 data.extend_from_slice(checksum);
94 Ok(bs58::encode(data).into_string())
95 }
96
97 pub fn validate_address(address: &str) -> bool {
99 if bech32::decode(address).is_ok() {
100 return true;
101 }
102 Self::decode_base58check(address).is_ok()
103 }
104
105 pub fn decode_base58check(encoded: &str) -> Result<Vec<u8>> {
107 let decoded = bs58::decode(encoded)
108 .into_vec()
109 .map_err(|e| anyhow::anyhow!("Base58 decode error: {}", e))?;
110
111 if decoded.len() < 4 {
112 return Err(anyhow::anyhow!("Data too short for checksum"));
113 }
114 let payload_len = decoded.len() - 4;
115 let data = &decoded[..payload_len];
116 let checksum = &decoded[payload_len..];
117 let calculated_checksum = &Self::double_sha256(data)[..4];
118 if checksum != calculated_checksum {
119 return Err(anyhow::anyhow!("Checksum mismatch"));
120 }
121 Ok(data.to_vec())
122 }
123
124 pub fn get_address_type(address: &str) -> Result<AddressType> {
126 if let Ok(decoded) = bech32::decode(address) {
127 return match decoded.0.as_str() {
128 "bc" => Ok(AddressType::Bech32Mainnet),
129 "tb" => Ok(AddressType::Bech32Testnet),
130 "bcrt" => Ok(AddressType::Bech32Regtest),
131 _ => Err(anyhow::anyhow!("Unknown Bech32 HRP: {}", decoded.0)),
132 };
133 }
134 let decoded = Self::decode_base58check(address)?;
135 if decoded.is_empty() {
136 return Err(anyhow::anyhow!("Empty address"));
137 }
138 match decoded[0] {
139 0x00 => Ok(AddressType::P2PKHMainnet),
140 0x6f => Ok(AddressType::P2PKHTestnet),
141 0x05 => Ok(AddressType::P2SHMainnet),
142 0xc4 => Ok(AddressType::P2SHTestnet),
143 _ => Err(anyhow::anyhow!(
144 "Unknown address prefix: 0x{:02x}",
145 decoded[0]
146 )),
147 }
148 }
149
150 pub fn public_key_to_bech32_address(
152 public_key: &[u8],
153 bitcoin_client_type: BitcoinClientType,
154 ) -> Result<String> {
155 let hash160 = Self::hash160(public_key);
156 Self::hash160_to_bech32_address(&hash160, bitcoin_client_type)
157 }
158
159 pub fn hash160_to_bech32_address(
161 hash160: &[u8; 20],
162 bitcoin_client_type: BitcoinClientType,
163 ) -> Result<String> {
164 let hrp = match bitcoin_client_type {
165 BitcoinClientType::Mainnet => "bc",
166 BitcoinClientType::Testnet | BitcoinClientType::Signet => "tb",
167 BitcoinClientType::Regtest => "bcrt",
168 };
169
170 let data = hash160.to_base32();
171 bech32::encode(hrp, data, Variant::Bech32)
172 .map_err(|e| anyhow::anyhow!("Bech32 encode error: {}", e))
173 }
174
175 pub fn decode_bech32_address(address: &str) -> Result<(String, Vec<u8>)> {
177 let decoded =
178 bech32::decode(address).map_err(|e| anyhow::anyhow!("Bech32 decode error: {}", e))?;
179 let bytes = bech32::FromBase32::from_base32(&decoded.1)
181 .map_err(|e| anyhow::anyhow!("Bech32 from_base32 error: {}", e))?;
182 Ok((decoded.0, bytes))
183 }
184
185 pub fn decode_address(address: &str) -> Result<(u8, Vec<u8>)> {
187 let decoded = Self::decode_base58check(address)?;
188 if decoded.len() < 2 {
189 return Err(anyhow::anyhow!("Address too short"));
190 }
191 let version = decoded[0];
192 let hash = decoded[1..].to_vec();
193 Ok((version, hash))
194 }
195
196 pub fn private_key_to_wif(
198 private_key: &[u8; 32],
199 compressed: bool,
200 bitcoin_client_type: BitcoinClientType,
201 ) -> Result<String> {
202 let prefix = match bitcoin_client_type {
203 BitcoinClientType::Mainnet => 0x80,
204 BitcoinClientType::Testnet | BitcoinClientType::Signet | BitcoinClientType::Regtest => {
205 0xef
206 }
207 };
208 let mut data = vec![prefix];
209 data.extend_from_slice(private_key);
210
211 if compressed {
212 data.push(0x01);
213 }
214 let checksum = &Self::double_sha256(&data)[..4];
215 data.extend_from_slice(checksum);
216 Ok(bs58::encode(data).into_string())
217 }
218
219 pub fn wif_to_private_key(wif: &str) -> Result<(Vec<u8>, bool, BitcoinClientType)> {
221 let decoded = Self::decode_base58check(wif)?;
222 if decoded.len() < 2 {
223 return Err(anyhow::anyhow!("WIF too short"));
224 }
225 let version = decoded[0];
226 let (network, compressed) = match version {
227 0x80 => (
228 BitcoinClientType::Mainnet,
229 decoded.len() == 34 && decoded[33] == 0x01,
230 ),
231 0xef => (
232 BitcoinClientType::Testnet,
233 decoded.len() == 34 && decoded[33] == 0x01,
234 ),
235 _ => return Err(anyhow::anyhow!("Unknown WIF version: 0x{:02x}", version)),
236 };
237 let private_key = if compressed {
238 decoded[1..33].to_vec() } else {
240 decoded[1..].to_vec() };
242
243 Ok((private_key, compressed, network))
244 }
245
246 pub fn base58_encode(data: &[u8]) -> String {
248 bs58::encode(data).into_string()
249 }
250
251 pub fn base58_decode(encoded: &str) -> Result<Vec<u8>> {
253 bs58::decode(encoded)
254 .into_vec()
255 .map_err(|e| anyhow::anyhow!("Base58 decode error: {}", e))
256 }
257
258 pub fn base58check_encode(data: &[u8]) -> String {
260 let checksum = &Self::double_sha256(data)[..4];
261 let mut payload = data.to_vec();
262 payload.extend_from_slice(checksum);
263 bs58::encode(payload).into_string()
264 }
265
266 pub fn base58check_decode(encoded: &str) -> Result<Vec<u8>> {
268 Self::decode_base58check(encoded)
269 }
270}
271
272#[derive(Debug, Clone, PartialEq)]
273pub enum AddressType {
274 P2PKHMainnet,
275 P2PKHTestnet,
276 P2SHMainnet,
277 P2SHTestnet,
278 Bech32Mainnet,
279 Bech32Testnet,
280 Bech32Regtest,
281}