Skip to main content

bitcoin_sdk/
crypto.rs

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    // Calculating the SHA256 hash
14    pub fn sha256(data: &[u8]) -> [u8; 32] {
15        let mut hasher = Sha256::new();
16        hasher.update(data);
17        hasher.finalize().into()
18    }
19
20    // Calculate double SHA256 hash
21    pub fn double_sha256(data: &[u8]) -> [u8; 32] {
22        let first = Self::sha256(data);
23        Self::sha256(&first)
24    }
25
26    // Calculating the RIPEMD160 hash
27    pub fn ripemd160(data: &[u8]) -> [u8; 20] {
28        let mut hasher = Ripemd160::new();
29        hasher.update(data);
30        hasher.finalize().into()
31    }
32
33    // Calculate hash 160 (RIPEMD160(SHA256(data)))
34    pub fn hash160(data: &[u8]) -> [u8; 20] {
35        let sha256_hash = Self::sha256(data);
36        Self::ripemd160(&sha256_hash)
37    }
38
39    // Generate a public key from a private key
40    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    // Creating a P2PKH Address
53    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    // Create a P2PKH address from hash 160
62    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    // Create a P2SH address from hash 160
80    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    // Verify address format
98    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    // Decoding a Base58Check address
106    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    // Get address type
125    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    // Creating a Bech32 Address (P2WPKH)
151    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    // Create a Bech32 address from hash 160
160    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    // Decoding Bech32 addresses
176    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        // to bytes
180        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    // Decodes a Base58 address (returns version bytes and a hash)
186    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    // Create a private key in WIF format
197    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    // wif to private key
220    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() // Skip version byte and compression flag
239        } else {
240            decoded[1..].to_vec() // Skip version bytes
241        };
242
243        Ok((private_key, compressed, network))
244    }
245
246    // Base58 encoding (without checksum)
247    pub fn base58_encode(data: &[u8]) -> String {
248        bs58::encode(data).into_string()
249    }
250
251    // Base58 decoding (without checksum)
252    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    // Base58Check encoding
259    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    // Base58Check decode
267    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}