syscoin_bech32/
lib.rs

1// Copyright (c) 2017 Clark Moody
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE.
20
21//! Encoding and decoding Bech32 Bitcoin Segwit Addresses
22//!
23//! Encoding and decoding for Bitcoin Segregated Witness addresses. Bech32 is an
24//! encoding scheme described in [BIP-0173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki),
25//! and segregated witness addresses encoded by Bech32 simply combine a coin-specific
26//! human-readable part with the data of the witness program as the Bech32 data
27//! payload.
28//!
29//! # Examples
30//!
31//! ```rust
32//! use syscoin_bech32::{WitnessProgram, u5};
33//! use syscoin_bech32::constants::Network;
34//!
35//! let witness_program = WitnessProgram::new(
36//!     u5::try_from_u8(0).unwrap(),
37//!     vec![
38//!         0x00, 0x00, 0x00, 0xc4, 0xa5, 0xca, 0xd4, 0x62,
39//!         0x21, 0xb2, 0xa1, 0x87, 0x90, 0x5e, 0x52, 0x66,
40//!         0x36, 0x2b, 0x99, 0xd5, 0xe9, 0x1c, 0x6c, 0xe2,
41//!         0x4d, 0x16, 0x5d, 0xab, 0x93, 0xe8, 0x64, 0x33],
42//!     Network::Testnet
43//! ).unwrap();
44//!
45//! let address = witness_program.to_address();
46//! assert_eq!(address,
47//!     "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy".to_string());
48//!
49//! let decoded = WitnessProgram::from_address(&address).unwrap();
50//! assert_eq!(decoded, witness_program);
51//! ```
52
53#![deny(missing_docs)]
54#![deny(non_upper_case_globals)]
55#![deny(non_camel_case_types)]
56#![deny(non_snake_case)]
57#![deny(unused_mut)]
58
59extern crate bech32;
60use bech32::{Bech32, ToBase32, FromBase32};
61pub use bech32::u5;
62
63use std::{error, fmt};
64use std::str::FromStr;
65use std::string::ToString;
66
67pub mod constants;
68use constants::Network;
69
70/// Witness version and program data
71#[derive(PartialEq, Eq, Debug, Clone, PartialOrd, Ord, Hash)]
72pub struct WitnessProgram {
73    /// Witness program version
74    version: u5,
75    /// Witness program content
76    program: Vec<u8>,
77    /// Cryptocurrency network
78    network: Network,
79    /// Cached bech32 representation of the witness program
80    bech32: Bech32,
81}
82
83impl WitnessProgram {
84    /// Construct a new WitnessProgram given the constituent version, witness program and network version
85    pub fn new(version: u5, program: Vec<u8>, network: Network) -> Result<WitnessProgram, Error> {
86        // Compute bech32
87        let hrp = constants::hrp(&network);
88        let mut b32_data: Vec<u5> = vec![version];
89        let p5 = program.to_base32();
90        b32_data.extend_from_slice(&p5);
91        let bech32 = Bech32::new(hrp, b32_data)?;
92
93        // Create return object
94        let ret = WitnessProgram {
95            version: version,
96            program: program,
97            network: network,
98            bech32: bech32,
99        };
100
101        // Verify that the program is valid
102        ret.validate()?;
103        Ok(ret)
104    }
105
106    /// Converts a Witness Program to a SegWit Address
107    pub fn to_address(&self) -> String {
108        self.to_string()
109    }
110
111    /// Decodes a segwit address into a Witness Program
112    ///
113    /// Verifies that the `address` contains a known human-readable part
114    /// `hrp` and decodes as proper Bech32-encoded string. Allowed values of
115    /// the human-readable part correspond to the defined types in `constants`
116    pub fn from_address(address: &str) -> Result<WitnessProgram, Error> {
117        WitnessProgram::from_str(address)
118    }
119
120    /// Converts a `WitnessProgram` to a script public key
121    ///
122    /// The format for the output is
123    /// `[version, program length, <program>]`
124    pub fn to_scriptpubkey(&self) -> Vec<u8> {
125        let mut pubkey: Vec<u8> = Vec::new();
126        let mut v: u8 = self.version.into();
127        if v > 0 {
128            v += 0x50;
129        }
130        pubkey.push(v);
131        pubkey.push(self.program.len() as u8);
132        pubkey.extend_from_slice(&self.program);
133        pubkey
134    }
135
136    /// Extracts a WitnessProgram out of a provided script public key
137    pub fn from_scriptpubkey(pubkey: &[u8], network: Network) -> Result<WitnessProgram, Error> {
138        // We need a version byte and a program length byte, with a program at
139        // least 2 bytes long.
140        if pubkey.len() < 4 {
141            return Err(Error::ScriptPubkeyTooShort)
142        }
143        let proglen: usize = pubkey[1] as usize;
144        // Check that program length byte is consistent with pubkey length
145        if pubkey.len() != 2 + proglen {
146            return Err(Error::ScriptPubkeyInvalidLength)
147        }
148        // Process script version
149        let mut v: u8 = pubkey[0];
150        if v > 0x50 {
151            v -= 0x50;
152        }
153
154        let v = u5::try_from_u8(v).expect("range is already guaranteed by code above");
155        let program = &pubkey[2..];
156
157        WitnessProgram::new(v, program.to_vec(), network)
158    }
159
160    /// Validates the WitnessProgram against version and length constraints
161    pub fn validate(&self) -> Result<(), Error> {
162        if self.version.to_u8() > 16 {
163            // Invalid script version
164            return Err(Error::InvalidScriptVersion)
165        }
166        if self.program.len() < 2 || self.program.len() > 40 {
167            return Err(Error::InvalidLength)
168        }
169        // Check proper script length
170        if self.version.to_u8() == 0 &&
171                self.program.len() != 20 && self.program.len() != 32 {
172            return Err(Error::InvalidVersionLength)
173        }
174        Ok(())
175    }
176
177    /// Witness program version
178    pub fn version(&self) -> u5 {
179        self.version
180    }
181
182    /// Witness program serialized as 8-bit bytes
183    pub fn program(&self) -> &[u8] {
184        &self.program
185    }
186
187    /// Which network this witness program is intended to be run on
188    pub fn network(&self) -> Network {
189        self.network
190    }
191}
192
193impl ToString for WitnessProgram {
194    fn to_string(&self) -> String {
195        self.bech32.to_string()
196    }
197}
198
199impl FromStr for WitnessProgram {
200    type Err = Error;
201
202    fn from_str(s: &str) -> Result<WitnessProgram, Error> {
203        let b32 = s.parse::<Bech32>()?;
204        let network_classified = match constants::classify(b32.hrp()) {
205            Some(nc) => nc,
206            None => return Err(Error::InvalidHumanReadablePart)
207        };
208        if b32.data().is_empty() || b32.data().len() > 65 {
209            return Err(Error::Bech32(bech32::Error::InvalidLength))
210        }
211        // Get the script version and program (converted from 5-bit to 8-bit)
212        let (version, program) = {
213            let (v, p5) = b32.data().split_at(1);
214            let program = Vec::from_base32(p5)?;
215            (v[0], program)
216        };
217        let wp = WitnessProgram {
218            version: version,
219            program: program,
220            network: network_classified,
221            bech32: b32,
222        };
223        wp.validate()?;
224        Ok(wp)
225    }
226}
227
228/// Error types for witness programs
229///
230/// BIP141 specifies Segregated Witness and defines valid program lengths
231/// for Version 0 scripts. Script version is also limited to values 0-16.
232#[derive(PartialEq, Debug)]
233pub enum Error {
234    /// Some Bech32 conversion error
235    Bech32(bech32::Error),
236    /// The human-readable part is invalid (must be "bc" or "tb")
237    InvalidHumanReadablePart,
238    /// scriptpubkeys does not have enough data
239    ScriptPubkeyTooShort,
240    /// The provided length byte does not match the data
241    ScriptPubkeyInvalidLength,
242    /// Denotes that the WitnessProgram is too long or too short
243    ///
244    /// Programs must be between 2 and 40 bytes
245    InvalidLength,
246    /// Given the program version, the length is invalid
247    ///
248    /// Version 0 scripts must be either 20 or 32 bytes
249    InvalidVersionLength,
250    /// Script version must be 0 to 16 inclusive
251    InvalidScriptVersion,
252}
253
254impl From<bech32::Error> for Error {
255    fn from(e: bech32::Error) -> Error {
256        Error::Bech32(e)
257    }
258}
259
260impl fmt::Display for Error {
261    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
262        match *self {
263            Error::Bech32(ref e) => write!(f, "{}", e),
264            Error::InvalidHumanReadablePart => write!(f, "invalid human-readable part"),
265            Error::ScriptPubkeyTooShort => write!(f, "scriptpubkey too short"),
266            Error::ScriptPubkeyInvalidLength => write!(f, "scriptpubkey length mismatch"),
267            Error::InvalidLength => write!(f, "invalid length"),
268            Error::InvalidVersionLength => write!(f, "program length incompatible with version"),
269            Error::InvalidScriptVersion => write!(f, "invalid script versio"),
270        }
271    }
272}
273
274impl error::Error for Error {
275    fn description(&self) -> &str {
276        match *self {
277            Error::Bech32(_) => "Bech32 error",
278            Error::InvalidHumanReadablePart => "invalid human-readable part",
279            Error::ScriptPubkeyTooShort => "scriptpubkey too short",
280            Error::ScriptPubkeyInvalidLength => "scriptpubkey length mismatch",
281            Error::InvalidLength => "invalid length",
282            Error::InvalidVersionLength => "program length incompatible with version",
283            Error::InvalidScriptVersion => "invalid script version"
284        }
285    }
286
287    fn cause(&self) -> Option<&std::error::Error> {
288        match *self {
289            Error::Bech32(ref e) => Some(e),
290            _ => None,
291        }
292    }
293}
294
295#[cfg(test)]
296mod tests {
297    use ::*;
298    use ::constants::Network;
299    use bech32;
300
301    #[test]
302    fn valid_address() {
303        let pairs: Vec<(&str, Vec<u8>, Network)> = vec![
304            (
305                "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4",
306                vec![
307                    0x00, 0x14, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54,
308                    0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6
309                ],
310                Network::Bitcoin,
311            ),
312            (
313                "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
314                vec![
315                    0x00, 0x20, 0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68, 0x04,
316                    0xbd, 0x19, 0x20, 0x33, 0x56, 0xda, 0x13, 0x6c, 0x98, 0x56, 0x78,
317                    0xcd, 0x4d, 0x27, 0xa1, 0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32,
318                    0x62
319                ],
320                Network::Testnet
321            ),
322            (
323                "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx",
324                vec![
325                    0x51, 0x28, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54,
326                    0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6,
327                    0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c,
328                    0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6
329                ],
330                Network::Bitcoin,
331            ),
332            (
333                "BC1SW50QA3JX3S",
334                vec![
335                   0x60, 0x02, 0x75, 0x1e
336                ],
337                Network::Bitcoin,
338            ),
339            (
340                "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj",
341                vec![
342                    0x52, 0x10, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54,
343                    0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23
344                ],
345                Network::Bitcoin,
346            ),
347            (
348                "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
349                vec![
350                    0x00, 0x20, 0x00, 0x00, 0x00, 0xc4, 0xa5, 0xca, 0xd4, 0x62, 0x21,
351                    0xb2, 0xa1, 0x87, 0x90, 0x5e, 0x52, 0x66, 0x36, 0x2b, 0x99, 0xd5,
352                    0xe9, 0x1c, 0x6c, 0xe2, 0x4d, 0x16, 0x5d, 0xab, 0x93, 0xe8, 0x64,
353                    0x33
354                ],
355                Network::Testnet,
356            ),
357            (
358                "bcrt1qn3h68k2u0rr49skx05qw7veynpf4lfppd2demt",
359                vec![
360                    0x00, 0x14, 0x9c, 0x6f, 0xa3, 0xd9, 0x5c, 0x78, 0xc7, 0x52, 0xc2,
361                    0xc6, 0x7d, 0x00, 0xef, 0x33, 0x24, 0x98, 0x53, 0x5f, 0xa4, 0x21,
362                ],
363                Network::Regtest,
364            ),
365        ];
366        for p in pairs {
367            let (address, scriptpubkey, network) = p;
368            let version = if scriptpubkey[0] == 0 { 0 } else { scriptpubkey[0] - 0x50 };
369            let dec_result = WitnessProgram::from_address(&address);
370            assert!(dec_result.is_ok());
371
372            let prog = dec_result.unwrap();
373            let pubkey = prog.to_scriptpubkey();
374            assert_eq!(pubkey, scriptpubkey);
375
376            assert_eq!(prog.network(), network);
377            assert_eq!(prog.version().to_u8(), version);
378            assert_eq!(prog.program(), &scriptpubkey[2..]); // skip version and length
379
380            let spk_result = WitnessProgram::from_scriptpubkey(&scriptpubkey, prog.network);
381            assert!(spk_result.is_ok());
382            assert_eq!(prog, spk_result.unwrap());
383
384            let enc_address = prog.to_address();
385            assert_eq!(address.to_lowercase(), enc_address.to_lowercase());
386        }
387    }
388
389    #[test]
390    fn invalid_address() {
391        let pairs: Vec<(&str, Error)> = vec!(
392            ("tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty",
393                Error::InvalidHumanReadablePart),
394            ("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5",
395                Error::Bech32(bech32::Error::InvalidChecksum)),
396            ("BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2",
397                Error::InvalidScriptVersion),
398            ("bc1rw5uspcuh",
399                Error::InvalidLength),
400            ("bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90",
401                Error::Bech32(bech32::Error::InvalidLength)),
402            ("BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
403                Error::InvalidVersionLength),
404            ("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7",
405                Error::Bech32(bech32::Error::MixedCase)),
406            ("tb1pw508d6qejxtdg4y5r3zarqfsj6c3",
407                Error::Bech32(bech32::Error::InvalidPadding)),
408            ("bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du",
409                Error::Bech32(bech32::Error::InvalidPadding)),
410            ("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv",
411                Error::Bech32(bech32::Error::InvalidPadding)),
412            ("bc1gmk9yu",
413                Error::Bech32(bech32::Error::InvalidLength)),
414        );
415        for p in pairs {
416            let (address, desired_error) = p;
417            let dec_result = WitnessProgram::from_address(&address);
418            if dec_result.is_ok() {
419                panic!("Should be invalid: {:?}", address);
420            }
421            assert_eq!(dec_result.unwrap_err(), desired_error);
422        }
423    }
424}