coins_core/
enc.rs

1//! Contains simplified access to `bech32` and `base58check` encoder/decoder for Bitcoin
2//! addresses. Also defines common encoder errors.
3
4use bech32::{
5    decode as b32_decode, encode as b32_encode, u5, Error as BechError, FromBase32, ToBase32,
6};
7
8use bs58::{
9    decode as bs58_decode, decode::Error as Bs58DecodeError, encode as bs58_encode,
10    encode::Error as Bs58EncodeError,
11};
12
13use thiserror::Error;
14
15/// Errors that can be returned by the Bitcoin `AddressEncoder`.
16#[derive(Debug, Error)]
17pub enum EncodingError {
18    /// Returned when ScriptPubkey type is unknown. May be non-standard or newer than lib version.
19    #[error("Non-standard ScriptPubkey type")]
20    UnknownScriptType,
21
22    /// Bech32 HRP does not match the current network.
23    #[error("Bech32 HRP does not match. \nGot {:?} expected {:?} Hint: Is this address for another network?", got, expected)]
24    WrongHrp {
25        /// The actual HRP.
26        got: String,
27        /// The expected HRP.
28        expected: String,
29    },
30
31    /// Base58Check version does not match the current network
32    #[error("Base58Check version does not match. \nGot {:?} expected {:?} Hint: Is this address for another network?", got, expected)]
33    WrongVersion {
34        /// The actual version byte.
35        got: u8,
36        /// The expected version byte.
37        expected: u8,
38    },
39
40    /// Bubbled up error from base58check library
41    #[error("{0}")]
42    Bs58Decode(#[from] Bs58DecodeError),
43
44    /// Bubbled up error from base58check library
45    #[error("{0}")]
46    Bs58Encode(#[from] Bs58EncodeError),
47
48    /// Bubbled up error from bech32 library
49    #[error(transparent)]
50    BechError(#[from] BechError),
51
52    /// Op Return ScriptPubkey was passed to encoder
53    #[error("Can't encode op return scripts as addresses")]
54    NullDataScript,
55
56    /// Invalid Segwit Version
57    #[error("Invalid Segwit Version: {0}")]
58    SegwitVersionError(u8),
59
60    /// Invalid Address Size
61    #[error("Invalid Address Size")]
62    InvalidSizeError,
63}
64
65/// A simple result type alias
66pub type EncodingResult<T> = Result<T, EncodingError>;
67
68/// Encode a byte vector to bech32. This function expects `v` to be a witness program, and will
69/// return an `UnknownScriptType` if it does not meet the witness program format.
70pub fn encode_bech32(hrp: &str, v: u8, h: &[u8]) -> EncodingResult<String> {
71    let mut v = vec![u5::try_from_u8(v)?];
72    v.extend(&h.to_base32());
73    b32_encode(hrp, &v, bech32::Variant::Bech32).map_err(|v| v.into())
74}
75
76/// Decode a witness program from a bech32 string. Caller specifies an expected HRP. If a
77/// different HRP is found, returns `WrongHrp`.
78pub fn decode_bech32(expected_hrp: &str, s: &str) -> EncodingResult<(u8, Vec<u8>)> {
79    let (hrp, data, _variant) = b32_decode(s)?;
80    if hrp != expected_hrp {
81        return Err(EncodingError::WrongHrp {
82            got: hrp,
83            expected: expected_hrp.to_owned(),
84        });
85    }
86
87    // Extract the witness version and payload
88    let (v, p) = data.split_at(1);
89    let payload = Vec::from_base32(p)?;
90
91    Ok((v[0].to_u8(), payload))
92}
93
94/// Encodes a byte slice to base58check with the specified version byte.
95pub fn encode_base58(v: &[u8]) -> String {
96    bs58_encode(v).with_check().into_string()
97}
98
99/// Decodes base58check into a byte string. Returns a
100/// `EncodingError::Bs58Decode` if unsuccesful
101pub fn decode_base58(expected_prefix: u8, s: &str) -> EncodingResult<Vec<u8>> {
102    let res = bs58_decode(s).with_check(None).into_vec()?;
103
104    if let Some(version) = res.first() {
105        if version != &expected_prefix {
106            return Err(EncodingError::Bs58Decode(Bs58DecodeError::InvalidVersion {
107                ver: *version,
108                expected_ver: expected_prefix,
109            }));
110        }
111    }
112
113    Ok(res)
114}
115
116#[cfg(test)]
117mod test {
118    use super::*;
119
120    #[test]
121    fn it_should_encode_and_decode_arbitrary_bech32() {
122        let cases = [
123            // Lightning invoice
124            ("lnbc20m", "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqscc6gd6ql3jrc5yzme8v4ntcewwz5cnw92tz0pc8qcuufvq7khhr8wpald05e92xw006sq94mg8v2ndf4sefvf9sygkshp5zfem29trqq2yxxz7"),
125            // Namecoin address
126            ("nc", "nc1qanwztr5zvd309vjf9ks9c2c3hyw3sqpppwkuut"),
127            // Handshake address
128            ("hs", "hs1q8vn02tnktq3tmztny8nysel6vtkuuy9k0whtty"),
129            // Random data
130            ("ab", "ab1qm7dpnrqefvf4ee67"),
131            ("lol", "lol1yrtmpa4p98nerppeu3h00my48ejmmyj629aeyqhur7wfrzfwqj99v875saeetusxtphs3q2"),
132        ];
133
134        for case in cases.iter() {
135            let (version, data) = decode_bech32(case.0, case.1).unwrap();
136            let reencoded = encode_bech32(case.0, version, &data).unwrap();
137            assert_eq!(case.1, reencoded);
138        }
139    }
140
141    #[test]
142    fn it_should_encode_and_decode_base58_pkh() {
143        let version = 0x00;
144        let addrs = [
145            "1AqE7oGF1EUoJviX1uuYrwpRBdEBTuGhES",
146            "1J2kECACFMDPyYjCBddKYbtzJMc6kv5FbA",
147            "1ADKfX19iy3EFUoG5qGLSHNXb4c1SSHFNF",
148            "12cKuAyj2jmrmMPBMtoeAt47DrJ5WRK2R5",
149            "19R4yak7BGX8fcWNvtuuTSjQGC43U4qadJ",
150            "1MT3dyC8YgEGY37yPwPtnvyau8HjGiMhhM",
151            "1NDyJtNTjmwk5xPNhjgAMu4HDHigtobu1s",
152            "1HMPBDt3HAD6o3zAxotBCS9o8KqCuYoapF",
153            "16o4roRP8dapRJraVNnw99xBh3J1Wkk5m8",
154        ];
155        for addr in addrs.iter() {
156            let s = decode_base58(version, addr).unwrap();
157            let reencoded = encode_base58(&s);
158            assert_eq!(*addr, reencoded);
159        }
160    }
161
162    #[test]
163    fn it_should_encode_and_decode_base58_sh() {
164        let version = 0x05;
165        let addrs = [
166            "3HXNFmJpxjgTVFN35Y9f6Waje5YFsLEQZ2",
167            "35mpC7r8fGrt2WTBTkQ56xBgm1k1QCY9CQ",
168            "345KNsztA2frN7V2TTZ2a9Vt6ojH8VSXFM",
169            "37QxcQb7U549M1QoDpXuRZMcTjRF52mfjx",
170            "377mKFYsaJPsxYSB5aFfx8SW3RaN5BzZVh",
171            "3GPM5uAPoqJ4CAst3GiraHPGFxSin6Ch2b",
172            "3LVq5zEBW48DjrqtmExR1YYDfJLmp8ryQE",
173            "3GfrmGENZFbV4rMWUxUxeo2yUnEnSDQ5BP",
174            "372sRbqCNQ1xboWCcc7XSbjptv8pzF9sBq",
175        ];
176        for addr in addrs.iter() {
177            let s = decode_base58(version, addr).unwrap();
178            let reencoded = encode_base58(&s);
179            assert_eq!(*addr, reencoded);
180        }
181    }
182
183    #[test]
184    fn it_should_error_on_wrong_version_and_hrp_and_invalid_addrs() {
185        match decode_bech32("tb", "bc1q233q49ve8ysdsztqh9ue57m6227627j8ztscl9") {
186            Ok(_) => panic!("expected an error"),
187            Err(EncodingError::WrongHrp {
188                got: _,
189                expected: _,
190            }) => {}
191            _ => panic!("Got the wrong error"),
192        }
193        match decode_base58(1, "3HXNFmJpxjgTVFN35Y9f6Waje5YFsLEQZ2") {
194            Ok(_) => panic!("expected an error"),
195            Err(EncodingError::Bs58Decode(Bs58DecodeError::InvalidVersion {
196                ver: 5,
197                expected_ver: 1,
198            })) => {}
199            _ => panic!("Got the wrong error"),
200        }
201        match decode_bech32("bc", "bc1qqh9ue57m6227627j8ztscl9") {
202            Ok(_) => panic!("expected an error"),
203            Err(EncodingError::BechError(_)) => {}
204            _ => panic!("Got the wrong error"),
205        }
206        match decode_base58(5, "3HXNf6Waje5YFsLEQZ2") {
207            Ok(_) => panic!("expected an error"),
208            Err(EncodingError::Bs58Decode(_)) => {}
209            _ => panic!("Got the wrong error"),
210        }
211    }
212}