1#![allow(unknown_lints)]
55#![allow(bare_trait_objects)]
56#![deny(missing_docs)]
57#![deny(non_upper_case_globals)]
58#![deny(non_camel_case_types)]
59#![deny(non_snake_case)]
60#![deny(unused_mut)]
61#![cfg_attr(feature = "strict", deny(warnings))]
62#![cfg_attr(not(feature = "std"), no_std)]
63
64#[cfg_attr(not(feature = "std"), macro_use)]
65#[cfg(not(feature = "std"))]
66extern crate alloc;
67
68#[cfg(not(feature = "std"))]
69pub(crate) mod imports {
70 pub use alloc::string::String;
71 pub use alloc::string::ToString;
72 pub use alloc::vec::Vec;
73 pub use core::fmt;
74 pub use core::str::FromStr;
75}
76#[cfg(feature = "std")]
77pub(crate) mod imports {
78 pub use std::str::FromStr;
79 pub use std::string::ToString;
80 pub use std::{error, fmt};
81}
82
83use imports::*;
84
85extern crate bech32;
86pub use bech32::u5;
87use bech32::{decode, encode, FromBase32, ToBase32, Variant};
88
89pub mod constants;
90use constants::Network;
91
92#[derive(PartialEq, Eq, Debug, Clone, PartialOrd, Ord, Hash)]
94pub struct WitnessProgram {
95 version: u5,
97 program: Vec<u8>,
99 network: Network,
101 bech32: String,
103}
104
105impl WitnessProgram {
106 pub fn new(version: u5, program: Vec<u8>, network: Network) -> Result<WitnessProgram, Error> {
108 let hrp = constants::hrp(&network);
110 let mut b32_data: Vec<u5> = vec![version];
111 let p5 = program.to_base32();
112 b32_data.extend_from_slice(&p5);
113 let variant = program_version_to_variant(version).ok_or(Error::InvalidScriptVersion)?;
114 let bech32 = encode(&hrp, b32_data, variant)?;
115
116 let ret = WitnessProgram {
118 version,
119 program,
120 network,
121 bech32,
122 };
123
124 ret.validate()?;
126 Ok(ret)
127 }
128
129 pub fn to_address(&self) -> String {
131 self.to_string()
132 }
133
134 pub fn from_address(address: &str) -> Result<WitnessProgram, Error> {
140 WitnessProgram::from_str(address)
141 }
142
143 pub fn to_scriptpubkey(&self) -> Vec<u8> {
148 let mut pubkey: Vec<u8> = Vec::new();
149 let mut v: u8 = self.version.into();
150 if v > 0 {
151 v += 0x50;
152 }
153 pubkey.push(v);
154 pubkey.push(self.program.len() as u8);
155 pubkey.extend_from_slice(&self.program);
156 pubkey
157 }
158
159 pub fn from_scriptpubkey(pubkey: &[u8], network: Network) -> Result<WitnessProgram, Error> {
161 if pubkey.len() < 4 {
164 return Err(Error::ScriptPubkeyTooShort);
165 }
166 let proglen: usize = pubkey[1] as usize;
167 if pubkey.len() != 2 + proglen {
169 return Err(Error::ScriptPubkeyInvalidLength);
170 }
171 let mut v: u8 = pubkey[0];
173 if v > 0x50 {
174 v -= 0x50;
175 }
176
177 let v = u5::try_from_u8(v).expect("range is already guaranteed by code above");
178 let program = &pubkey[2..];
179
180 WitnessProgram::new(v, program.to_vec(), network)
181 }
182
183 pub fn validate(&self) -> Result<(), Error> {
185 if self.version.to_u8() > 16 {
186 return Err(Error::InvalidScriptVersion);
188 }
189 if self.program.len() < 2 || self.program.len() > 40 {
190 return Err(Error::InvalidLength);
191 }
192 if self.version.to_u8() == 0 && self.program.len() != 20 && self.program.len() != 32 {
194 return Err(Error::InvalidVersionLength);
195 }
196 Ok(())
197 }
198
199 pub fn version(&self) -> u5 {
201 self.version
202 }
203
204 pub fn program(&self) -> &[u8] {
206 &self.program
207 }
208
209 pub fn network(&self) -> Network {
211 self.network
212 }
213}
214
215impl ToString for WitnessProgram {
216 fn to_string(&self) -> String {
217 self.bech32.to_string()
218 }
219}
220
221impl FromStr for WitnessProgram {
222 type Err = Error;
223
224 fn from_str(s: &str) -> Result<WitnessProgram, Error> {
225 let (hrp, data, variant) = decode(s)?;
226 let network_classified = match constants::classify(&hrp) {
227 Some(nc) => nc,
228 None => return Err(Error::InvalidHumanReadablePart),
229 };
230 if data.is_empty() || data.len() > 65 {
231 return Err(Error::Bech32(bech32::Error::InvalidLength));
232 }
233 let (version, program) = {
235 let (v, p5) = data.split_at(1);
236 let program = Vec::from_base32(p5)?;
237 (v[0], program)
238 };
239 let wp = WitnessProgram {
240 version,
241 program,
242 network: network_classified,
243 bech32: s.to_string(),
244 };
245 wp.validate()?;
246 if let Some(version_variant) = program_version_to_variant(version) {
247 if version_variant != variant {
248 return Err(Error::InvalidEncoding);
249 }
250 }
251 Ok(wp)
252 }
253}
254
255fn program_version_to_variant(version: u5) -> Option<Variant> {
256 match version.to_u8() {
257 0 => Some(Variant::Bech32),
258 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 => {
259 Some(Variant::Bech32m)
260 }
261 _ => None,
262 }
263}
264
265#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
270pub enum Error {
271 Bech32(bech32::Error),
273 InvalidHumanReadablePart,
275 ScriptPubkeyTooShort,
277 ScriptPubkeyInvalidLength,
279 InvalidLength,
283 InvalidVersionLength,
287 InvalidScriptVersion,
289 InvalidEncoding,
294}
295
296impl From<bech32::Error> for Error {
297 fn from(e: bech32::Error) -> Error {
298 Error::Bech32(e)
299 }
300}
301
302impl fmt::Display for Error {
303 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
304 match *self {
305 Error::Bech32(ref e) => write!(f, "{}", e),
306 Error::InvalidHumanReadablePart => write!(f, "invalid human-readable part"),
307 Error::ScriptPubkeyTooShort => write!(f, "scriptpubkey too short"),
308 Error::ScriptPubkeyInvalidLength => write!(f, "scriptpubkey length mismatch"),
309 Error::InvalidLength => write!(f, "invalid length"),
310 Error::InvalidVersionLength => write!(f, "program length incompatible with version"),
311 Error::InvalidScriptVersion => write!(f, "invalid script version"),
312 Error::InvalidEncoding => write!(f, "invalid Bech32 encoding"),
313 }
314 }
315}
316
317#[cfg(feature = "std")]
318impl error::Error for Error {
319 fn description(&self) -> &str {
320 match *self {
321 Error::Bech32(_) => "Bech32 error",
322 Error::InvalidHumanReadablePart => "invalid human-readable part",
323 Error::ScriptPubkeyTooShort => "scriptpubkey too short",
324 Error::ScriptPubkeyInvalidLength => "scriptpubkey length mismatch",
325 Error::InvalidLength => "invalid length",
326 Error::InvalidVersionLength => "program length incompatible with version",
327 Error::InvalidScriptVersion => "invalid script version",
328 Error::InvalidEncoding => "invalid Bech32 encoding",
329 }
330 }
331
332 fn cause(&self) -> Option<&std::error::Error> {
333 match *self {
334 Error::Bech32(ref e) => Some(e),
335 _ => None,
336 }
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use bech32;
343 use constants::Network;
344 use *;
345
346 #[test]
347 fn valid_address() {
348 let pairs: Vec<(&str, Vec<u8>, Network)> = vec![
349 (
350 "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4",
351 vec![
352 0x00, 0x14, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c,
353 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6,
354 ],
355 Network::Bitcoin,
356 ),
357 (
358 "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
359 vec![
360 0x00, 0x20, 0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68, 0x04, 0xbd, 0x19,
361 0x20, 0x33, 0x56, 0xda, 0x13, 0x6c, 0x98, 0x56, 0x78, 0xcd, 0x4d, 0x27, 0xa1,
362 0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32, 0x62,
363 ],
364 Network::Testnet,
365 ),
366 (
367 "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
368 vec![
369 0x51, 0x28, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c,
370 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6, 0x75, 0x1e, 0x76, 0xe8,
371 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1,
372 0x43, 0x3b, 0xd6,
373 ],
374 Network::Bitcoin,
375 ),
376 (
377 "BC1SW50QGDZ25J",
378 vec![0x60, 0x02, 0x75, 0x1e],
379 Network::Bitcoin,
380 ),
381 (
382 "bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs",
383 vec![
384 0x52, 0x10, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c,
385 0x45, 0xd1, 0xb3, 0xa3, 0x23,
386 ],
387 Network::Bitcoin,
388 ),
389 (
390 "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
391 vec![
392 0x00, 0x20, 0x00, 0x00, 0x00, 0xc4, 0xa5, 0xca, 0xd4, 0x62, 0x21, 0xb2, 0xa1,
393 0x87, 0x90, 0x5e, 0x52, 0x66, 0x36, 0x2b, 0x99, 0xd5, 0xe9, 0x1c, 0x6c, 0xe2,
394 0x4d, 0x16, 0x5d, 0xab, 0x93, 0xe8, 0x64, 0x33,
395 ],
396 Network::Testnet,
397 ),
398 (
399 "bcrt1qn3h68k2u0rr49skx05qw7veynpf4lfppd2demt",
400 vec![
401 0x00, 0x14, 0x9c, 0x6f, 0xa3, 0xd9, 0x5c, 0x78, 0xc7, 0x52, 0xc2, 0xc6, 0x7d,
402 0x00, 0xef, 0x33, 0x24, 0x98, 0x53, 0x5f, 0xa4, 0x21,
403 ],
404 Network::Regtest,
405 ),
406 (
407 "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c",
408 vec![
409 0x51, 0x20, 0x00, 0x00, 0x00, 0xc4, 0xa5, 0xca, 0xd4, 0x62, 0x21, 0xb2, 0xa1,
410 0x87, 0x90, 0x5e, 0x52, 0x66, 0x36, 0x2b, 0x99, 0xd5, 0xe9, 0x1c, 0x6c, 0xe2,
411 0x4d, 0x16, 0x5d, 0xab, 0x93, 0xe8, 0x64, 0x33,
412 ],
413 Network::Testnet,
414 ),
415 (
416 "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0",
417 vec![
418 0x51, 0x20, 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62,
419 0x95, 0xce, 0x87, 0x0b, 0x07, 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9,
420 0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98,
421 ],
422 Network::Bitcoin,
423 ),
424 ];
425 for p in pairs {
426 let (address, scriptpubkey, network) = p;
427 let version = if scriptpubkey[0] == 0 {
428 0
429 } else {
430 scriptpubkey[0] - 0x50
431 };
432 let prog = match WitnessProgram::from_address(&address) {
433 Ok(prog) => prog,
434 Err(e) => panic!("{}, {:?}", address, e),
435 };
436
437 let pubkey = prog.to_scriptpubkey();
438 assert_eq!(pubkey, scriptpubkey);
439
440 assert_eq!(prog.network(), network);
441 assert_eq!(prog.version().to_u8(), version);
442 assert_eq!(prog.program(), &scriptpubkey[2..]); match WitnessProgram::from_scriptpubkey(&scriptpubkey, prog.network) {
445 Ok(prog) => {
446 assert_eq!(
447 prog.to_string().to_lowercase(),
448 prog.to_string().to_lowercase()
449 );
450 let enc_address = prog.to_address();
451 assert_eq!(address.to_lowercase(), enc_address.to_lowercase());
452 }
453 Err(e) => panic!("{:?}, {:?}", scriptpubkey, e),
454 }
455 }
456 }
457
458 #[test]
459 fn invalid_address() {
460 let pairs: Vec<(&str, Error)> = vec![
461 (
463 "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty",
464 Error::InvalidHumanReadablePart,
465 ),
466 (
467 "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5",
468 Error::Bech32(bech32::Error::InvalidChecksum),
469 ),
470 (
471 "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2",
472 Error::InvalidScriptVersion,
473 ),
474 ("bc1rw5uspcuh", Error::InvalidLength),
475 (
476 "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90",
477 Error::Bech32(bech32::Error::InvalidLength),
478 ),
479 (
480 "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7",
481 Error::Bech32(bech32::Error::MixedCase),
482 ),
483 (
484 "tb1pw508d6qejxtdg4y5r3zarqfsj6c3",
485 Error::Bech32(bech32::Error::InvalidPadding),
486 ),
487 (
488 "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du",
489 Error::Bech32(bech32::Error::InvalidPadding),
490 ),
491 (
492 "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv",
493 Error::Bech32(bech32::Error::InvalidPadding),
494 ),
495 (
497 "tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut",
498 Error::InvalidHumanReadablePart,
499 ),
500 (
501 "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd",
502 Error::InvalidEncoding,
503 ),
504 (
505 "tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf",
506 Error::InvalidEncoding,
507 ),
508 (
509 "BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL",
510 Error::InvalidEncoding,
511 ),
512 (
513 "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh",
514 Error::InvalidEncoding,
515 ),
516 (
517 "tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47",
518 Error::InvalidEncoding,
519 ),
520 (
521 "bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4",
522 Error::Bech32(bech32::Error::InvalidChar('o')),
523 ),
524 (
525 "BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R",
526 Error::InvalidScriptVersion,
527 ),
528 ("bc1pw5dgrnzv", Error::InvalidLength),
529 (
530 "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav",
531 Error::Bech32(bech32::Error::InvalidLength),
532 ),
533 (
534 "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
535 Error::InvalidVersionLength,
536 ),
537 (
538 "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq",
539 Error::Bech32(bech32::Error::MixedCase),
540 ),
541 (
542 "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf",
543 Error::Bech32(bech32::Error::InvalidPadding),
544 ),
545 (
546 "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j",
547 Error::Bech32(bech32::Error::InvalidPadding),
548 ),
549 ("bc1gmk9yu", Error::Bech32(bech32::Error::InvalidLength)),
550 ];
551 for p in pairs {
552 let (address, desired_error) = p;
553 match WitnessProgram::from_address(&address) {
554 Ok(_) => panic!("Should be invalid: {:?}", address),
555 Err(e) => assert_eq!(e, desired_error, "{}", address),
556 }
557 }
558 }
559}