bitcoin_address/
lib.rs

1//
2fn has_character_count(s: &str, char_count: u64) -> bool {
3    s.chars().count() as u64 == char_count
4}
5
6// P2PKH ----------------------------------------------------------------------------------------
7// PUB KEY HASH
8// P2PKH addresses are 33-34 characters long, use Base58Check encoding, and (on mainnet) start with "1".
9//
10// Pay to Pubkey Hash (P2PKH) addresses start with 1 and represent a single public key.
11pub fn is_p2pkh(address: &String) -> bool {
12    let p2pkh_leading_symbols = vec![
13        "1", // mainnet
14        "m", //testnet
15        "n", //testnet
16    ];
17
18    let has_p2pkh_leading_symbol = p2pkh_leading_symbols
19        .iter()
20        .any(|leading_symbol| address.starts_with(leading_symbol));
21    let has_correct_char_count =
22        has_character_count(address, 33) || has_character_count(address, 34);
23    has_correct_char_count && has_p2pkh_leading_symbol
24}
25// ----------------------------------------------------------------------------------------
26
27// P2SH ----------------------------------------------------------------------------------------
28// SCRIPT HASH
29// P2SH addresses are 34 characters long, use Base58Check encoding, and (on mainnet) start with "3".
30//
31// Pay to Script Hash (P2SH) addresses present a much more general validation scheme that can encode an arbitrary script. These start with "3" and are common for multi-signature setups.
32pub fn is_p2sh(address: &String) -> bool {
33    let p2sh_leading_symbols = vec![
34        "2", // mainnet
35        "3", //testnet
36    ];
37
38    let has_p2sh_leading_symbol = p2sh_leading_symbols
39        .iter()
40        .any(|leading_symbol| address.starts_with(leading_symbol));
41    let has_correct_char_count =
42        // mainnet
43        has_character_count(address, 34) 
44        // testnet
45        || has_character_count(address, 35); 
46    has_correct_char_count && has_p2sh_leading_symbol
47}
48// ----------------------------------------------------------------------------------------
49
50//P2SH_P2WPKH ----------------------------------------------------------------------------------
51// Pay To Witness Public Key Hash Wrapped In P2SH
52// Allows non-SegWit wallets to generate a SegWit transaction. Allows non-SegWit client accept SegWit transaction.
53// Source: https://www.btcschools.net/bitcoin/bitcoin_script_p2sh_p2wpkh.php
54//
55fn could_be_p2sh_p2wpkh(address: &String) -> bool {
56    is_p2sh(address)
57}
58//----------------------------------------------------------------------------------------
59
60//P2SH_P2WSH ----------------------------------------------------------------------------------
61// NESTED SEGWIT
62// Pay To Witness Script Hash Wrapped In P2SH
63// Allows non-SegWit wallets to generate a SegWit transaction. Allows non-SegWit client accept SegWit transaction.
64// Source: https://www.btcschools.net/bitcoin/bitcoin_script_p2sh_p2wsh.php
65//
66fn could_be_p2sh_p2wsh(address: &String) -> bool {
67    is_p2sh(address)
68}
69//----------------------------------------------------------------------------------------
70
71// P2WPKH ----------------------------------------------------------------------------------------
72// SEGWIT v0 PUBKEY HASH
73// P2WPKH addresses are 42 characters long, use bech32 encoding, and (on mainnet) start with "bc1q".
74//
75// Pay to Witness Pubkey Hash (P2WPKH) addresses are version 0 segregated witness (SegWit) programs and behave similarly to P2PKH. All version 0 SegWit addresses start with "bc1q"
76pub fn is_p2wpkh(address: &String) -> bool {
77    let p2wpkh_leading_symbols = vec![
78        "bc1q", // mainnet
79        "tb1q", //testnet
80    ];
81
82    let has_p2wpkh_leading_symbol = p2wpkh_leading_symbols
83        .iter()
84        .any(|leading_symbol| address.starts_with(leading_symbol));
85    has_character_count(address, 42) && has_p2wpkh_leading_symbol
86}
87// ----------------------------------------------------------------------------------------
88
89// P2WSH ----------------------------------------------------------------------------------------
90// SegWit v0 Script Hash
91// P2WSH addresses are 62 characters long, use bech32 encoding, and (on mainnet) start with "bc1q".
92// Pay to Witness Script Hash (P2WSH) addresses are version 0 segregated witness (SegWit) programs and behave similarly to P2SH. All version 0 SegWit addresses start with "bc1q"
93//
94pub fn is_p2wsh(address: &String) -> bool {
95    let p2wsh_leading_symbols = vec![
96        "bc1q", // mainnet
97        "tb1q", //testnet
98    ];
99
100    let has_p2wsh_leading_symbol = p2wsh_leading_symbols
101        .iter()
102        .any(|leading_symbol| address.starts_with(leading_symbol));
103    has_character_count(address, 62) && has_p2wsh_leading_symbol
104}
105//
106// ----------------------------------------------------------------------------------------
107
108// P2TR ----------------------------------------------------------------------------------------
109// Pay to taproot
110// P2TR addresses are 62 characters long, use bech32m encoding, and (on mainnet) start with "bc1p".
111
112pub fn is_p2tr(address: &String) -> bool {
113    let p2tr_leading_symbols = vec![
114        "bc1p", // mainnet
115        "tb1p", //testnet
116    ];
117
118    let has_p2tr_leading_symbol = p2tr_leading_symbols
119        .iter()
120        .any(|leading_symbol| address.starts_with(leading_symbol));
121    has_character_count(address, 62) && has_p2tr_leading_symbol
122}
123// ----------------------------------------------------------------------------------------
124
125pub fn is_legacy(address: &String) -> bool {
126    is_p2pkh(address)
127}
128
129pub fn is_nested_segwit(address: &String) -> bool {
130    is_p2sh(address)
131}
132// Nested segwit is also called wrapped segwit
133pub fn is_wrapped_segwit(address: &String) -> bool {
134    is_nested_segwit(address)
135}
136
137pub fn is_segwit_native(address: &String) -> bool {
138    is_p2wpkh(address) || is_p2wsh(address)
139}
140
141pub fn is_segwit_v0(address: &String) -> bool {
142    is_segwit_native(address) || is_p2sh(address)
143}
144pub fn is_segwit_v1(address: &String) -> bool {
145    is_taproot(address)
146}
147
148pub fn is_taproot(address: &String) -> bool {
149    is_p2tr(address)
150}
151
152// TODO: Add tests
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn is_legacy_works() {
159        let legacy_address = "1J9uwBYepTm5737RtzkSEePTevGgDGLP5S".to_string();
160        let nested_segwit_address = "37u4L57bLqZ8NL9bs1GNX2x52KxviDfvPp".to_string();
161        let native_segwit_address = "bc1qfvmj8jse4r7203mrchfyt24sjcpna3s2y35ylp".to_string();
162        let taproot_address =
163            "bc1p8denc9m4sqe9hluasrvxkkdqgkydrk5ctxre5nkk4qwdvefn0sdsc6eqxe".to_string();
164
165        assert_eq!(is_legacy(&legacy_address), true);
166        assert_eq!(is_legacy(&nested_segwit_address), false);
167        assert_eq!(is_legacy(&native_segwit_address), false);
168        assert_eq!(is_legacy(&taproot_address), false);
169
170        assert_eq!(is_nested_segwit(&nested_segwit_address), true);
171        assert_eq!(is_nested_segwit(&legacy_address), false);
172        assert_eq!(is_nested_segwit(&native_segwit_address), false);
173        assert_eq!(is_nested_segwit(&taproot_address), false);
174
175        assert_eq!(is_segwit_native(&native_segwit_address), true);
176        assert_eq!(is_segwit_native(&legacy_address), false);
177        assert_eq!(is_segwit_native(&nested_segwit_address), false);
178        assert_eq!(is_segwit_native(&taproot_address), false);
179
180        assert_eq!(is_segwit_v0(&nested_segwit_address), true);
181        assert_eq!(is_segwit_v0(&native_segwit_address), true);
182        assert_eq!(is_segwit_v0(&taproot_address), false);
183
184        assert_eq!(is_segwit_v1(&nested_segwit_address), false);
185        assert_eq!(is_segwit_v1(&native_segwit_address), false);
186        assert_eq!(is_segwit_v1(&taproot_address), true);
187
188        assert_eq!(is_taproot(&taproot_address), true);
189        assert_eq!(is_taproot(&legacy_address), false);
190        assert_eq!(is_taproot(&nested_segwit_address), false);
191        assert_eq!(is_taproot(&native_segwit_address), false);
192    }
193}