bitcoin_utils/
lib.rs

1use std::str::FromStr;
2
3use bitcoin::hashes::{ripemd160, Hash};
4use bitcoin::util::base58::check_encode_slice;
5use bitcoin::util::base58::from_check;
6use bitcoin::util::taproot::TapTweakHash;
7use bitcoin_bech32::{u5, WitnessProgram};
8use hex_utilities::{decode_hex, encode_hex};
9use secp256k1::{Scalar, Secp256k1, SecretKey};
10use sha2::{Digest, Sha256};
11
12#[derive(Debug, Clone, Copy)]
13pub enum Network {
14    Mainnet,
15    Testnet,
16}
17
18#[derive(Copy, Clone)]
19pub enum AddressType {
20    /// Pay to pubkey hash.
21    P2PKH,
22    /// Pay to script hash.
23    P2SH,
24    // TODO: ADD P2WSH
25    //P2wsh,/// Pay to witness script hash.
26    // This should probably be named "Segwit<Something>", to differenciate from a bech32 taproot
27    // address. Maybe?
28    P2WPKH,
29    P2TR,
30}
31// TODO: Does this belong in this libarary?
32pub fn concat_u8(first: &[u8], second: &[u8]) -> Vec<u8> {
33    [first, second].concat()
34}
35
36pub fn sha256_non_hex(non_hex_string_to_hash: &str) -> String {
37    let byte_array = non_hex_string_to_hash.as_bytes();
38    let mut hasher = Sha256::new();
39    // write input message
40    hasher.update(&byte_array);
41    // read hash digest and consume hasher
42    let sha256_result = hasher.finalize();
43    let sha256_result_array = sha256_result.to_vec();
44    let hex_result = encode_hex(&sha256_result_array);
45    hex_result
46}
47pub fn sha256_hex(hex_to_hash: &String) -> String {
48    let hex_byte_array = decode_hex(&hex_to_hash).unwrap();
49    let mut hasher = Sha256::new();
50    // write input message
51    hasher.update(&hex_byte_array);
52    // read hash digest and consume hasher
53    let sha256_result = hasher.finalize();
54    let sha256_result_array = sha256_result.to_vec();
55    let hex_result = encode_hex(&sha256_result_array);
56    hex_result
57}
58pub fn double_sha256_hex(hex_to_hash: &String) -> String {
59    let hex_byte_array = decode_hex(&hex_to_hash).unwrap();
60    let mut hasher = Sha256::new();
61    // write input message
62    hasher.update(&hex_byte_array);
63    // read hash digest and consume hasher
64    let sha256_result = hasher.finalize();
65    let sha256_result_array = sha256_result.to_vec();
66
67    let hex_byte_array_2 = sha256_result_array;
68    let mut hasher_2 = Sha256::new();
69    // write input message
70    hasher_2.update(&hex_byte_array_2);
71    // read hash digest and consume hasher
72    let sha256_result_2 = hasher_2.finalize();
73    let sha256_result_array_2 = sha256_result_2.to_vec();
74    encode_hex(&sha256_result_array_2)
75}
76pub fn get_compressed_public_key_from_private_key(private_key: &str) -> String {
77    // Create 512 bit public key
78    let secp = Secp256k1::new();
79    let secret_key = SecretKey::from_str(private_key).unwrap();
80    // We're getting the NEWER compressed version of the public key:
81    //    Source: https://en.bitcoin.it/wiki/Elliptic_Curve_Digital_Signature_Algorithm
82    let public_key_uncompressed = secret_key.public_key(&secp).serialize();
83    encode_hex(&public_key_uncompressed)
84}
85pub fn get_wif_from_private_key(
86    private_key: &String,
87    network: Network,
88    should_compress: bool,
89) -> String {
90    // 0x80 is used for the version/application byte
91    // https://river.com/learn/terms/w/wallet-import-format-wif/#:~:text=WIF%20format%20adds%20a%20prefix,should%20use%20compressed%20SEC%20format.
92    let version_application_byte_for_mainnet = "80";
93    let version_application_byte_for_testnet = "ef";
94
95    let version_application_byte = match network {
96        Network::Mainnet => version_application_byte_for_mainnet,
97        Network::Testnet => version_application_byte_for_testnet,
98    };
99
100    let private_key_hex = decode_hex(&private_key).unwrap();
101    let version_array = decode_hex(version_application_byte).unwrap();
102    // What does check encodings do?
103    //   - does a sha25 twice, then gets the first 4 bytes of that Result
104    //   - takes those first four bites and appends them to the original (version + hex array)
105    //   - Read "Ecoding a private key" section here: https://en.bitcoin.it/wiki/Base58Check_encoding
106    let end = "01";
107    let end_array = decode_hex(end).unwrap();
108    let combined_version_and_private_key_hex = concat_u8(&version_array, &private_key_hex);
109    let combined_version_and_private_key_hex_with_end_array = if should_compress {
110        concat_u8(&combined_version_and_private_key_hex, &end_array)
111    } else {
112        combined_version_and_private_key_hex
113    };
114    // TODO: THIS IS ONLY FOR COMPRESSED. How would we do uncompressed?
115    let wif_private_key = check_encode_slice(&combined_version_and_private_key_hex_with_end_array);
116    wif_private_key
117}
118pub fn get_p2sh_address_from_script_hash(script_hash: &String, network: Network) -> String {
119    // https://bitcoin.stackexchange.com/questions/111483/parsing-p2sh-address-from-output-script
120    let p2sh_version_application_byte = "05";
121    let p2sh_testnet_version_application_byte = "c4";
122    let version_byte = match network {
123        Network::Mainnet => decode_hex(p2sh_version_application_byte).unwrap(),
124        Network::Testnet => decode_hex(p2sh_testnet_version_application_byte).unwrap(),
125    };
126    let script_hash_bytes = decode_hex(&script_hash).unwrap();
127    let script_hash_with_version_byte = concat_u8(&version_byte, &script_hash_bytes);
128    let address = check_encode_slice(&script_hash_with_version_byte);
129    address
130}
131pub fn get_p2sh_address_from_pubkey_hash(public_key_hash: &String, network: Network) -> String {
132    // https://bitcoin.stackexchange.com/questions/75910/how-to-generate-a-native-segwit-address-and-p2sh-segwit-address-from-a-standard
133    let prefix_bytes = decode_hex("0014").unwrap();
134    let public_key_hash_bytes = decode_hex(public_key_hash).unwrap();
135    let redeem_script = concat_u8(&prefix_bytes, &public_key_hash_bytes);
136    let redeem_script_sha256 = sha256::digest_bytes(&redeem_script);
137    let redeem_script_sha256_as_hex_array = decode_hex(&redeem_script_sha256).unwrap();
138    let redeem_script_ripemd160 = ripemd160::Hash::hash(&redeem_script_sha256_as_hex_array);
139    let hash160 = redeem_script_ripemd160.to_string();
140    return get_p2sh_address_from_script_hash(&hash160, network);
141    // Extracted this into get_p2sh_address_from_script_hash(script_hash: &String, network: Network) -> String {
142    // let hash160_bytes = decode_hex(&hash160).unwrap();
143    // let p2sh_version_application_byte = "05";
144    // let p2sh_testnet_version_application_byte = "c4";
145    // let version_byte = match network {
146    //     Network::Mainnet => decode_hex(p2sh_version_application_byte).unwrap(),
147    //     Network::Testnet => decode_hex(p2sh_testnet_version_application_byte).unwrap(),
148    // };
149    // let hash160_with_version_byte = concat_u8(&version_byte, &hash160_bytes);
150    // let address = check_encode_slice(&hash160_with_version_byte);
151    // println!("{:#?}", hash160_with_version_byte);
152    // address
153}
154pub fn get_p2pkh_address_from_pubkey_hash(public_key_hash: &String, network: Network) -> String {
155    // SEE ALL VERSION APPLICATION CODES HERE: https://en.bitcoin.it/wiki/List_of_address_prefixes
156    // TODO: ALL ALL TYPES OF ADDRESSES
157    let p2pkh_version_application_byte = "00";
158    let p2pkh_testnet_version_application_byte = "6f";
159
160    let version_application_byte = match network {
161        Network::Mainnet => p2pkh_version_application_byte,
162        Network::Testnet => p2pkh_testnet_version_application_byte,
163    };
164    // AddressType::P2SH => match network {
165    //     Network::Mainnet => p2sh_version_application_byte,
166    //     Network::Testnet => p2sh_testnet_version_application_byte,
167    // },
168
169    // let hex_array = Vec::from_hex(public_key_hash).unwrap();
170    let hex_array = decode_hex(&public_key_hash).unwrap();
171    let version_array = decode_hex(version_application_byte).unwrap();
172    let a = concat_u8(&version_array, &hex_array);
173    // What does check encodings do?
174    //   - does a sha25 twice, then gets the first 4 bytes of that Result
175    //   - takes those first four bites and appends them to the original (version + hex array)
176    //   - Read "Encoding a bitcoin address": https://en.bitcoin.it/wiki/Base58Check_encoding
177    let address = check_encode_slice(&a);
178    address
179}
180pub fn get_address_from_pub_key_hash(
181    public_key_hash: &String,
182    network: Network,
183    address_type: AddressType,
184) -> String {
185    match address_type {
186        AddressType::P2PKH => get_p2pkh_address_from_pubkey_hash(public_key_hash, network),
187        AddressType::P2SH => get_p2sh_address_from_pubkey_hash(public_key_hash, network),
188        AddressType::P2WPKH => get_p2wpkh_address_from_pubkey_hash(public_key_hash, network),
189        AddressType::P2TR => {
190            todo!("Not sure if you can get pub key hash from a taproot address. Instead, use get address from public key, not hash")
191        }
192    }
193}
194
195pub fn get_bech32_address_from_witness_program(
196    witness_version: u8,
197    program_hex: &String,
198    network: Network,
199) -> String {
200    let network_for_bech32_library = match network {
201        Network::Mainnet => bitcoin_bech32::constants::Network::Bitcoin,
202        Network::Testnet => bitcoin_bech32::constants::Network::Testnet,
203    };
204    let byte_array = decode_hex(&program_hex).unwrap();
205    let witness_program = WitnessProgram::new(
206        u5::try_from_u8(witness_version).unwrap(),
207        byte_array,
208        network_for_bech32_library,
209    )
210    .unwrap();
211    let address = witness_program.to_address();
212    address
213}
214
215pub fn get_p2tr_address_from_pubkey(public_key_hex: &String, network: Network) -> String {
216    // Helpful to check: https://slowli.github.io/bech32-buffer/
217    // Current version is 00
218    // Source: https://en.bitcoin.it/wiki/Bech32
219    // Source: https://www.youtube.com/watch?v=YGAeMnN4O_k&t=631s
220    //
221    let witness_version = 1;
222    let secp = Secp256k1::new();
223    let tweaked_x_only_public_key = get_tweaked_x_only_public_key_from_public_key(public_key_hex);
224    // let public_key =
225    //     secp256k1::PublicKey::from_str(&public_key_hex).expect("statistically impossible to hit");
226    // let (untweaked_x_only_public_key, _parity) = public_key.x_only_public_key();
227    // let merkle_root = None;
228    // let tweak =
229    //     TapTweakHash::from_key_and_tweak(untweaked_x_only_public_key, merkle_root).to_scalar();
230    // let (tweaked_x_only_public_key, _parity) = untweaked_x_only_public_key
231    //     .add_tweak(&secp, &tweak)
232    //     .expect("Tap tweak failed");
233    let address = get_bech32_address_from_witness_program(
234        witness_version,
235        &tweaked_x_only_public_key.to_string(),
236        network,
237    );
238    address
239}
240
241pub fn get_p2wpkh_address_from_pubkey_hash(pub_key_hash: &String, network: Network) -> String {
242    // Helpful to check: https://slowli.github.io/bech32-buffer/
243    // Current version is 00
244    // Source: https://en.bitcoin.it/wiki/Bech32
245    let witness_version = 0;
246    // TODO: Implement the conversion from public_key to bech32 myself
247    // We're using an external library
248    let address = get_bech32_address_from_witness_program(
249        witness_version,
250        &pub_key_hash.to_string(),
251        network,
252    );
253    address
254}
255pub fn get_tweaked_x_only_public_key_from_p2tr_address(address: &String) -> String {
256    let witness = WitnessProgram::from_address(address).unwrap();
257    encode_hex(&witness.program())
258}
259pub fn get_pubkey_hash_from_p2wpkh_address(address: &String) -> String {
260    let witness = WitnessProgram::from_address(address).unwrap();
261    encode_hex(&witness.program())
262}
263
264pub fn get_address_from_pub_key(
265    pub_key: &String,
266    network: Network,
267    address_type: AddressType,
268) -> String {
269    match address_type {
270        AddressType::P2PKH | AddressType::P2SH | AddressType::P2WPKH => {
271            let pub_key_hash = get_public_key_hash_from_public_key(&pub_key);
272
273            let address = get_address_from_pub_key_hash(&pub_key_hash, network, address_type);
274            address
275        }
276        AddressType::P2TR => get_p2tr_address_from_pubkey(pub_key, network),
277    }
278}
279
280pub fn get_public_key_from_wif(wif: &String) -> String {
281    // Check: https://coinb.in/#verify
282    let private_key = convert_wif_to_private_key(&wif);
283    let public_key = get_public_key_from_private_key(&private_key, is_wif_compressed(&wif));
284    public_key
285}
286pub fn is_wif_compressed(wif: &String) -> bool {
287    // Source:https://en.bitcoin.it/wiki/Wallet_import_format
288    let first_char_of_wif = wif.chars().nth(0).unwrap();
289    let is_compressed_wif = first_char_of_wif == 'K'
290        || first_char_of_wif == 'L'
291        || first_char_of_wif == 'M'
292        || first_char_of_wif == 'c';
293    is_compressed_wif
294}
295pub fn get_public_key_from_private_key(private_key: &String, is_compressed: bool) -> String {
296    // Create 512 bit public key
297    let secp = Secp256k1::new();
298    let secret_key = SecretKey::from_str(private_key).unwrap();
299    // We're getting the OLDER uncompressed version of the public key:
300    //    Source: https://en.bitcoin.it/wiki/Elliptic_Curve_Digital_Signature_Algorithm
301    let public_key = if is_compressed {
302        secret_key.public_key(&secp).serialize().to_vec()
303    } else {
304        secret_key
305            .public_key(&secp)
306            .serialize_uncompressed()
307            .to_vec()
308    };
309    encode_hex(&public_key)
310}
311
312pub fn hash160_for_non_hex(non_hex_string_to_hash: &String) -> String {
313    let string_as_array = non_hex_string_to_hash.as_bytes();
314    let sha256 = sha256::digest_bytes(&string_as_array);
315    let sha256_as_hex_array = sha256.as_bytes();
316    let ripemd160 = ripemd160::Hash::hash(&sha256_as_hex_array);
317    ripemd160.to_string()
318}
319pub fn hash160_for_hex(hex_to_hash: &String) -> String {
320    let hex_array = decode_hex(hex_to_hash).unwrap();
321    let sha256 = sha256_hex(hex_to_hash);
322    let sha256_as_hex_array = decode_hex(&sha256).unwrap();
323    let public_key_ripemd160 = ripemd160::Hash::hash(&sha256_as_hex_array);
324    public_key_ripemd160.to_string()
325}
326
327pub fn get_tweaked_x_only_public_key_from_public_key(public_key_hex: &String) -> String {
328    let secp = Secp256k1::new();
329    let public_key =
330        secp256k1::PublicKey::from_str(&public_key_hex).expect("statistically impossible to hit");
331    let (untweaked_x_only_public_key, _parity) = public_key.x_only_public_key();
332    let merkle_root = None;
333    let tweak =
334        TapTweakHash::from_key_and_tweak(untweaked_x_only_public_key, merkle_root).to_scalar();
335    let (tweaked_x_only_public_key, _parity) = untweaked_x_only_public_key
336        .add_tweak(&secp, &tweak)
337        .expect("Tap tweak failed");
338    tweaked_x_only_public_key.to_string()
339}
340
341pub fn get_public_key_hash_from_public_key(public_key: &String) -> String {
342    hash160_for_hex(public_key)
343}
344
345pub fn get_script_hash_from_p2sh_address(address: &str) -> String {
346    if bitcoin_address::is_p2sh(&address.to_string()) {
347        let address_base58check_decoded = from_check(&address).unwrap();
348        let address_base58check_decoded_without_first_byte =
349            address_base58check_decoded.get(1..).unwrap();
350        let script_hash = encode_hex(&address_base58check_decoded_without_first_byte);
351        script_hash
352    } else {
353        panic!("Address is not p2sh: {}", address);
354    }
355}
356
357pub fn get_public_key_hash_from_non_bech_32_address(address: &String) -> String {
358    if bitcoin_address::is_legacy(&address.to_string()) {
359        let address_base58check_decoded = from_check(&address).unwrap();
360        let address_base58check_decoded_without_first_byte =
361            address_base58check_decoded.get(1..).unwrap();
362        let pub_key_hash = encode_hex(&address_base58check_decoded_without_first_byte);
363        pub_key_hash
364    } else {
365        panic!("Address must be legacy: {}", address);
366    }
367}
368pub fn get_public_key_hash_from_address(address: &String) -> String {
369    // TODO: This should be exaustive and work for every address types
370    // TODO: Implement taproot
371    if bitcoin_address::is_legacy(address) {
372        get_public_key_hash_from_non_bech_32_address(address)
373    } else if bitcoin_address::is_segwit_native(address) {
374        get_pubkey_hash_from_p2wpkh_address(address)
375    } else if bitcoin_address::is_nested_segwit(address) {
376        panic!(
377            "Couldn't get public key hash from address ({}). Nested segwit addresses not supported. Instead, you should use get_script_hash_from_p2sh_address() function",
378            address
379        );
380    } else {
381        panic!("Couldn't get public key hash from address: {}", address);
382    }
383}
384pub fn convert_wif_to_private_key(wif: &String) -> String {
385    // Check: https://coinb.in/#verify
386    // Source:https://en.bitcoin.it/wiki/Wallet_import_format
387    // 1. decode the base58check
388
389    let is_compressed_wif = is_wif_compressed(wif);
390    let wif_base58check_decoded_result = from_check(&wif);
391    let wif_base58check_decoded = from_check(&wif).unwrap();
392    // 2. drop the fist byte
393    // TODO: It's more complicated than this: "Drop the first byte (it should be 0x80, however
394    // legacy Electrum[1][2] or some SegWit vanity address generators[3] may use 0x81-0x87). If
395    // the private key corresponded to a compressed public key, also drop the last byte (it
396    // should be 0x01). If it corresponded to a compressed public key, the WIF string will have
397    // started with K or L (or M, if it's exported from legacy Electrum[1][2] etc[3]) instead
398    // of 5 (or c instead of 9 on testnet). This is the private key."
399    // Source: https://en.bitcoin.it/wiki/Wallet_import_format
400    let wif_base58check_decoded_without_first_byte = wif_base58check_decoded.get(1..).unwrap();
401    let wif_base58check_decoded_without_first_byte_and_adjusted_for_compression =
402        if is_compressed_wif {
403            wif_base58check_decoded_without_first_byte
404                .get(..=(wif_base58check_decoded_without_first_byte.len() - 2))
405                .unwrap()
406        } else {
407            wif_base58check_decoded_without_first_byte
408        };
409    let wif_base58check_decoded_without_first_byte_and_adjusted_for_compression_hex =
410        encode_hex(wif_base58check_decoded_without_first_byte_and_adjusted_for_compression);
411    wif_base58check_decoded_without_first_byte_and_adjusted_for_compression_hex
412}
413
414#[cfg(test)]
415mod tests {
416    use super::*;
417
418    #[test]
419    fn it_works() {
420        // let result = add(2, 2);
421        // assert_eq!(result, 4);
422    }
423}