bitcoincash 0.32.4

General purpose library for using and interoperating with Bitcoin Cash.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
// SPDX-License-Identifier: CC0-1.0

//! Bitcoin Cash cashaddr addresses.
//!
//! Implementation of the [CashAddr specification], including the token-aware address types
//! defined by the [CashTokens specification] and 32-byte script hashes (P2SH32, activated on
//! Bitcoin Cash in the May 2023 upgrade).
//!
//! The primary type of this module is [`CashAddress`], the cashaddr counterpart of [`Address`].
//! It follows the same network validation model: parsing produces a
//! `CashAddress<NetworkUnchecked>` which must be checked with
//! [`require_network`](CashAddress::require_network) (or explicitly waved through with
//! [`assume_checked`](CashAddress::assume_checked)) before it can be displayed or converted to a
//! script.
//!
//! The low-level codec is available through [`encode`], [`decode`] and [`decode_with_prefix`]
//! for use cases that need arbitrary prefixes or not-yet-defined address types.
//!
//! # Examples
//!
//! ```
//! use bitcoincash::address::cashaddr::CashAddress;
//! use bitcoincash::Network;
//!
//! // Parsing and network validation.
//! let addr = "bitcoincash:qr7fzmep8g7h7ymfxy74lgc0v950j3r2959lhtxxsl"
//!     .parse::<CashAddress<_>>()
//!     .unwrap()
//!     .require_network(Network::Bitcoin)
//!     .unwrap();
//!
//! // The prefix may be omitted; it is recovered through the checksum.
//! let no_prefix = "qr7fzmep8g7h7ymfxy74lgc0v950j3r2959lhtxxsl"
//!     .parse::<CashAddress<_>>()
//!     .unwrap()
//!     .require_network(Network::Bitcoin)
//!     .unwrap();
//! assert_eq!(addr, no_prefix);
//!
//! // Token-aware form of the same address (CashTokens).
//! let token_addr = addr.clone().with_token_awareness(true);
//! assert_eq!(
//!     token_addr.to_string(),
//!     "bitcoincash:zr7fzmep8g7h7ymfxy74lgc0v950j3r295z4y4gq0v",
//! );
//! // Both encumber outputs with the same script.
//! assert_eq!(addr.script_pubkey(), token_addr.script_pubkey());
//! ```
//!
//! [CashAddr specification]: <https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md>
//! [CashTokens specification]: <https://github.com/cashtokens/cashtokens>

use core::fmt;
use core::marker::PhantomData;
use core::str::FromStr;

use hashes::Hash;
use internals::write_err;

use super::{Address, AddressType, NetworkChecked, NetworkUnchecked, NetworkValidation};
use crate::blockdata::constants::MAX_SCRIPT_ELEMENT_SIZE;
use crate::blockdata::script::{Script, ScriptBuf, ScriptHash, ScriptHash32};
use crate::crypto::key::PubkeyHash;
use crate::network::{Network, NetworkKind};
use crate::prelude::*;

/// The cashaddr character set for encoding.
const CHARSET: [u8; 32] = *b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";

/// Reverse of [`CHARSET`], accepting both lower and upper case. `-1` marks an invalid character.
const CHARSET_REV: [i8; 128] = {
    let mut rev = [-1i8; 128];
    let mut i = 0;
    while i < 32 {
        rev[CHARSET[i] as usize] = i as i8;
        rev[CHARSET[i].to_ascii_uppercase() as usize] = i as i8;
        i += 1;
    }
    rev
};

/// Hash size in bytes for each of the eight size-bits values of the version byte.
const HASH_SIZES: [usize; 8] = [20, 24, 28, 32, 40, 48, 56, 64];

/// Type bits of a P2PKH address.
const TYPE_P2PKH: u8 = 0;
/// Type bits of a P2SH address.
const TYPE_P2SH: u8 = 1;
/// Type bits of a token-aware P2PKH address.
const TYPE_P2PKH_TOKEN: u8 = 2;
/// Type bits of a token-aware P2SH address.
const TYPE_P2SH_TOKEN: u8 = 3;

/// One step of the cashaddr BCH checksum, as defined in the specification.
fn polymod_step(c: u64, d: u8) -> u64 {
    let c0 = (c >> 35) as u8;
    let mut c = ((c & 0x0007_ffff_ffff) << 5) ^ u64::from(d);
    if c0 & 0x01 != 0 {
        c ^= 0x0098_f2bc_8e61;
    }
    if c0 & 0x02 != 0 {
        c ^= 0x0079_b76d_99e2;
    }
    if c0 & 0x04 != 0 {
        c ^= 0x00f3_3e5f_b3c4;
    }
    if c0 & 0x08 != 0 {
        c ^= 0x00ae_2eab_e2a8;
    }
    if c0 & 0x10 != 0 {
        c ^= 0x001e_4f43_e470;
    }
    c
}

/// Feeds the expanded `prefix` (lower 5 bits of every character plus the zero separator) into
/// the checksum state.
fn polymod_prefix(mut c: u64, prefix: &str) -> u64 {
    for b in prefix.bytes() {
        c = polymod_step(c, b & 0x1f);
    }
    polymod_step(c, 0)
}

/// Maps a payload character to its 5-bit value, accepting both cases.
fn char_value(c: char) -> Option<u8> {
    let v = *CHARSET_REV.get(c as usize)?;
    u8::try_from(v).ok()
}

/// Rejects strings mixing lower and upper case ASCII, as required by the specification.
fn check_case(s: &str) -> Result<(), DecodeError> {
    let mut lower = false;
    let mut upper = false;
    for c in s.chars() {
        lower |= c.is_ascii_lowercase();
        upper |= c.is_ascii_uppercase();
    }
    if lower && upper {
        Err(DecodeError::MixedCase)
    } else {
        Ok(())
    }
}

/// Checks that `prefix` is non-empty, lowercase ASCII alphabetic.
fn check_prefix(prefix: &str) -> Result<(), EncodeError> {
    if prefix.is_empty() || !prefix.bytes().all(|b| b.is_ascii_lowercase()) {
        return Err(EncodeError::InvalidPrefix);
    }
    Ok(())
}

/// Computes the version byte for `type_bits` and a payload of `len` bytes.
fn version_byte(type_bits: u8, len: usize) -> Result<u8, EncodeError> {
    if type_bits > 15 {
        return Err(EncodeError::InvalidTypeBits(type_bits));
    }
    let size_bits = HASH_SIZES
        .iter()
        .position(|&size| size == len)
        .ok_or(EncodeError::InvalidPayloadLength(len))?;
    Ok((type_bits << 3) | size_bits as u8)
}

/// Streams the cashaddr encoding of `version_byte || data` into `f` without allocating.
///
/// The caller is responsible for `prefix` being valid and `version_byte` matching `data.len()`.
fn encode_to_fmt(
    f: &mut dyn fmt::Write,
    prefix: &str,
    version_byte: u8,
    data: &[u8],
    uppercase: bool,
) -> fmt::Result {
    let write_value = |f: &mut dyn fmt::Write, v: u8| {
        let c = CHARSET[usize::from(v)];
        f.write_char(char::from(if uppercase { c.to_ascii_uppercase() } else { c }))
    };

    let mut chk = polymod_prefix(1, prefix);
    if uppercase {
        for c in prefix.chars() {
            f.write_char(c.to_ascii_uppercase())?;
        }
    } else {
        f.write_str(prefix)?;
    }
    f.write_char(':')?;

    // Convert 8-bit bytes to 5-bit groups, zero-padding the final group.
    let mut acc = 0u32;
    let mut bits = 0u32;
    for &b in core::iter::once(&version_byte).chain(data) {
        acc = (acc << 8) | u32::from(b);
        bits += 8;
        while bits >= 5 {
            bits -= 5;
            let v = ((acc >> bits) & 0x1f) as u8;
            chk = polymod_step(chk, v);
            write_value(f, v)?;
        }
        acc &= (1 << bits) - 1;
    }
    if bits > 0 {
        let v = ((acc << (5 - bits)) & 0x1f) as u8;
        chk = polymod_step(chk, v);
        write_value(f, v)?;
    }

    // Eight zeros as the checksum template, then the checksum itself (MSB first).
    for _ in 0..8 {
        chk = polymod_step(chk, 0);
    }
    let chk = chk ^ 1;
    for i in (0..8).rev() {
        write_value(f, ((chk >> (5 * i)) & 0x1f) as u8)?;
    }
    Ok(())
}

/// Encodes `data` as a cashaddr string with the given prefix and type bits.
///
/// This is the low-level codec; it accepts any valid prefix (non-empty, lowercase ASCII
/// alphabetic), any type bits value in `0..=15` and any of the eight payload sizes defined by
/// the specification (20, 24, 28, 32, 40, 48, 56 or 64 bytes). For regular Bitcoin Cash
/// addresses prefer [`CashAddress`].
pub fn encode(prefix: &str, type_bits: u8, data: &[u8]) -> Result<String, EncodeError> {
    check_prefix(prefix)?;
    let version_byte = version_byte(type_bits, data.len())?;
    // prefix + ':' + payload (5-bit groups of version byte + data) + 8 checksum characters.
    let payload_len = ((1 + data.len()) * 8 + 4) / 5; // number of 5-bit groups, rounded up
    let mut s = String::with_capacity(prefix.len() + 1 + payload_len + 8);
    encode_to_fmt(&mut s, prefix, version_byte, data, false)
        .expect("writing to string is infallible");
    Ok(s)
}

/// The raw contents of a cashaddr string, decoded but not yet interpreted.
///
/// Returned by the low-level [`decode`] and [`decode_with_prefix`] functions. Most users want
/// [`CashAddress`] instead, which interprets the type bits and hash length.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub struct DecodedCashAddr {
    /// The address prefix, lowercase (e.g. `bitcoincash`).
    pub prefix: String,
    /// The type bits of the version byte (`0..=15`).
    pub type_bits: u8,
    /// The raw hash payload (20-64 bytes, as encoded by the size bits of the version byte).
    pub data: Vec<u8>,
}

/// Decodes a cashaddr string that includes its `prefix:` part.
///
/// This is the low-level codec: any valid prefix and all type bits values are accepted. Use
/// [`decode_with_prefix`] for strings without the prefix part, or [`CashAddress`] for regular
/// Bitcoin Cash addresses (which also recovers an omitted prefix through the checksum).
///
/// Beyond the character set, case rules and checksum, this validates the structure of the
/// payload itself: canonical padding, a cleared reserved bit and size bits consistent with the
/// actual hash length. This matches the "content" decoding layer of the node implementations
/// (`DecodeCashAddrContent`), so strings with a valid checksum but an inconsistent payload,
/// such as the checksum-only test vectors of the specification, are rejected.
pub fn decode(addr: &str) -> Result<DecodedCashAddr, DecodeError> {
    let (prefix, body) = addr.split_once(':').ok_or(DecodeError::MissingPrefix)?;
    check_case(addr)?;
    let prefix = prefix.to_ascii_lowercase();
    if check_prefix(&prefix).is_err() {
        return Err(match prefix.chars().find(|c| !c.is_ascii_lowercase()) {
            Some(c) => DecodeError::InvalidChar(c),
            None => DecodeError::InvalidPrefix,
        });
    }
    let (type_bits, data) = decode_body(&prefix, body)?;
    Ok(DecodedCashAddr { prefix, type_bits, data })
}

/// Decodes a cashaddr string without a `prefix:` part against the given prefix.
///
/// The checksum covers the prefix, so decoding succeeds only if `body` was encoded for
/// `prefix`. The prefix must be non-empty, lowercase ASCII alphabetic.
///
/// The payload structure is validated as described on [`decode`].
pub fn decode_with_prefix(prefix: &str, body: &str) -> Result<DecodedCashAddr, DecodeError> {
    if check_prefix(prefix).is_err() {
        return Err(match prefix.chars().find(|c| !c.is_ascii_lowercase()) {
            Some(c) => DecodeError::InvalidChar(c),
            None => DecodeError::InvalidPrefix,
        });
    }
    check_case(body)?;
    let (type_bits, data) = decode_body(prefix, body)?;
    Ok(DecodedCashAddr { prefix: prefix.to_owned(), type_bits, data })
}

/// Decodes and validates the payload part (`body`) of a cashaddr against a lowercase `prefix`.
///
/// Assumes case consistency has already been checked. Performs full validation: character set,
/// checksum, strict padding, reserved version bit and hash length against the size bits.
fn decode_body(prefix: &str, body: &str) -> Result<(u8, Vec<u8>), DecodeError> {
    // 8 checksum characters plus at least 2 characters for the version byte. Real payloads are
    // longer still; the hash length check below enforces the rest.
    if body.len() < 10 {
        return Err(DecodeError::InvalidLength(body.len()));
    }

    let mut values = Vec::with_capacity(body.len());
    for c in body.chars() {
        values.push(char_value(c).ok_or(DecodeError::InvalidChar(c))?);
    }

    let mut chk = polymod_prefix(1, prefix);
    for &v in &values {
        chk = polymod_step(chk, v);
    }
    // PolyMod of a valid address is zero, i.e. the pre-finalization state is one.
    if chk != 1 {
        return Err(DecodeError::Checksum);
    }

    // Convert the 5-bit groups (excluding the checksum) back to bytes, rejecting non-canonical
    // padding: at most four padding bits, all zero.
    let payload = &values[..values.len() - 8];
    let mut data = Vec::with_capacity(payload.len() * 5 / 8);
    let mut acc = 0u32;
    let mut bits = 0u32;
    for &v in payload {
        acc = (acc << 5) | u32::from(v);
        bits += 5;
        if bits >= 8 {
            bits -= 8;
            data.push((acc >> bits) as u8);
            acc &= (1 << bits) - 1;
        }
    }
    if bits >= 5 || acc != 0 {
        return Err(DecodeError::InvalidPadding);
    }

    let version_byte = data.remove(0);
    if version_byte & 0x80 != 0 {
        return Err(DecodeError::ReservedBitSet);
    }
    let expected = HASH_SIZES[usize::from(version_byte & 0x07)];
    if data.len() != expected {
        return Err(DecodeError::InvalidLength(data.len()));
    }
    Ok((version_byte >> 3, data))
}

/// Known cashaddr prefixes.
///
/// This is the human-readable part before the separator (`:`) in a cashaddr encoded address,
/// e.g. the "bitcoincash" in "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2".
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum KnownPrefix {
    /// The main Bitcoin Cash network, `bitcoincash`.
    Mainnet,
    /// The test networks (testnet3, testnet4, scalenet, chipnet), `bchtest`.
    Testnets,
    /// The regtest network, `bchreg`.
    Regtest,
}

impl KnownPrefix {
    /// Creates a `KnownPrefix` from `network`.
    fn from_network(network: Network) -> Self {
        use Network::*;

        match network {
            Bitcoin => Self::Mainnet,
            Testnet | Testnet4 | Scalenet | Chipnet => Self::Testnets,
            Regtest => Self::Regtest,
        }
    }

    /// Creates a `KnownPrefix` from a prefix string, case-insensitively.
    fn from_prefix_str(prefix: &str) -> Option<Self> {
        if prefix.eq_ignore_ascii_case("bitcoincash") {
            Some(Self::Mainnet)
        } else if prefix.eq_ignore_ascii_case("bchtest") {
            Some(Self::Testnets)
        } else if prefix.eq_ignore_ascii_case("bchreg") {
            Some(Self::Regtest)
        } else {
            None
        }
    }

    /// Returns the prefix string, e.g. `bitcoincash`.
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Mainnet => "bitcoincash",
            Self::Testnets => "bchtest",
            Self::Regtest => "bchreg",
        }
    }
}

impl From<Network> for KnownPrefix {
    fn from(n: Network) -> Self { Self::from_network(n) }
}

impl From<NetworkKind> for KnownPrefix {
    fn from(n: NetworkKind) -> Self {
        match n {
            NetworkKind::Main => Self::Mainnet,
            NetworkKind::Test => Self::Testnets,
        }
    }
}

impl fmt::Display for KnownPrefix {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(self.as_str()) }
}

/// The destination encoded by a [`CashAddress`], excluding prefix and token awareness.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum CashPayload {
    /// Pay to pubkey hash.
    P2pkh(PubkeyHash),
    /// Pay to a 160-bit script hash.
    P2sh(ScriptHash),
    /// Pay to a 256-bit script hash (P2SH32).
    P2sh32(ScriptHash32),
}

/// The inner representation of a [`CashAddress`], without the network validation tag.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct CashAddressInner {
    payload: CashPayload,
    token_aware: bool,
    prefix: KnownPrefix,
}

impl CashAddressInner {
    fn type_bits(&self) -> u8 {
        match (&self.payload, self.token_aware) {
            (CashPayload::P2pkh(_), false) => TYPE_P2PKH,
            (CashPayload::P2pkh(_), true) => TYPE_P2PKH_TOKEN,
            (CashPayload::P2sh(_) | CashPayload::P2sh32(_), false) => TYPE_P2SH,
            (CashPayload::P2sh(_) | CashPayload::P2sh32(_), true) => TYPE_P2SH_TOKEN,
        }
    }

    fn data(&self) -> &[u8] {
        match &self.payload {
            CashPayload::P2pkh(hash) => hash.as_ref(),
            CashPayload::P2sh(hash) => hash.as_ref(),
            CashPayload::P2sh32(hash) => hash.as_ref(),
        }
    }
}

/// Formats the address as upper case if alternate formatting is chosen (`{:#}`).
impl fmt::Display for CashAddressInner {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let data = self.data();
        let version_byte =
            version_byte(self.type_bits(), data.len()).expect("payload sizes are always valid");
        let uppercase = f.alternate();
        encode_to_fmt(f, self.prefix.as_str(), version_byte, data, uppercase)
    }
}

/// A Bitcoin Cash cashaddr address.
///
/// The cashaddr counterpart of [`Address`]. In addition to the P2PKH and P2SH (160-bit) types
/// representable as legacy base58 addresses, a `CashAddress` can carry a 256-bit script hash
/// ([P2SH32]) and can be *token-aware*: an otherwise identical address type signalling that the
/// receiver's wallet supports [CashTokens]. Token awareness does not change the script a payment
/// is encumbered with, only the address encoding.
///
/// ### Parsing addresses
///
/// `CashAddress` uses the same compile-time network validation model as [`Address`]: parsing
/// produces a `CashAddress<NetworkUnchecked>` which has to be validated with
/// [`require_network`](CashAddress<NetworkUnchecked>::require_network) (or waved through with
/// [`assume_checked`](CashAddress<NetworkUnchecked>::assume_checked)) before use. The prefix may
/// be omitted from the parsed string; it is then recovered through the checksum, which covers
/// the prefix.
///
/// ```
/// use bitcoincash::address::cashaddr::CashAddress;
/// use bitcoincash::Network;
///
/// let address = "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2"
///     .parse::<CashAddress<_>>()
///     .unwrap()
///     .require_network(Network::Bitcoin)
///     .unwrap();
/// assert_eq!(address.to_string(), "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2");
/// ```
///
/// [P2SH32]: <https://gitlab.com/0353F40E/p2sh32/-/blob/main/CHIP-2022-05_Pay-to-Script-Hash-32_%28P2SH32%29_for_Bitcoin_Cash.md>
/// [CashTokens]: <https://github.com/cashtokens/cashtokens>
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
// The `#[repr(transparent)]` attribute guarantees the layout of the `CashAddress` struct so
// that the pointer casts in `as_unchecked`/`assume_checked_ref` are sound. It is an
// implementation detail and users should not rely on it in their code.
#[repr(transparent)]
pub struct CashAddress<V = NetworkChecked>(CashAddressInner, PhantomData<V>)
where
    V: NetworkValidation;

#[cfg(feature = "serde")]
struct DisplayUnchecked<'a, N: NetworkValidation>(&'a CashAddress<N>);

#[cfg(feature = "serde")]
impl<N: NetworkValidation> fmt::Display for DisplayUnchecked<'_, N> {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0 .0, fmt) }
}

#[cfg(feature = "serde")]
crate::serde_utils::serde_string_deserialize_impl!(
    CashAddress<NetworkUnchecked>,
    "a Bitcoin Cash cashaddr address"
);

#[cfg(feature = "serde")]
impl<N: NetworkValidation> serde::Serialize for CashAddress<N> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.collect_str(&DisplayUnchecked(self))
    }
}

/// Methods on [`CashAddress`] that can be called on both `CashAddress<NetworkChecked>` and
/// `CashAddress<NetworkUnchecked>`.
impl<V: NetworkValidation> CashAddress<V> {
    /// Returns a reference to the address as if it was unchecked.
    pub fn as_unchecked(&self) -> &CashAddress<NetworkUnchecked> {
        unsafe { &*(self as *const CashAddress<V> as *const CashAddress<NetworkUnchecked>) }
    }

    /// Marks the network of this address as unchecked.
    pub fn into_unchecked(self) -> CashAddress<NetworkUnchecked> {
        CashAddress(self.0, PhantomData)
    }

    /// Returns whether this address is token-aware.
    ///
    /// Token-aware addresses (type bits 2 and 3, first payload character `z` or `r`) signal
    /// that the receiver's wallet supports [CashTokens]. They pay to the same script as their
    /// non-token-aware counterparts.
    ///
    /// [CashTokens]: <https://github.com/cashtokens/cashtokens>
    pub fn is_token_aware(&self) -> bool { self.0.token_aware }

    /// Returns this address with token awareness set to `token_aware`.
    ///
    /// This changes only the address encoding, not the script a payment is encumbered with.
    pub fn with_token_awareness(mut self, token_aware: bool) -> Self {
        self.0.token_aware = token_aware;
        self
    }

    /// Gets the address type of the address.
    ///
    /// Token awareness is not reflected in the address type; use
    /// [`is_token_aware`](Self::is_token_aware).
    pub fn address_type(&self) -> AddressType {
        match &self.0.payload {
            CashPayload::P2pkh(_) => AddressType::P2pkh,
            CashPayload::P2sh(_) => AddressType::P2sh,
            CashPayload::P2sh32(_) => AddressType::P2sh32,
        }
    }
}

/// Methods and functions that can be called only on `CashAddress<NetworkChecked>`.
impl CashAddress {
    /// Creates a pay to pubkey hash address from a public key.
    #[inline]
    pub fn p2pkh(pk: impl Into<PubkeyHash>, prefix: impl Into<KnownPrefix>) -> CashAddress {
        let inner = CashAddressInner {
            payload: CashPayload::P2pkh(pk.into()),
            token_aware: false,
            prefix: prefix.into(),
        };
        Self(inner, PhantomData)
    }

    /// Creates a pay to script hash (160-bit) address from a redeem script.
    #[inline]
    pub fn p2sh(
        script: &Script,
        prefix: impl Into<KnownPrefix>,
    ) -> Result<CashAddress, super::P2shError> {
        if script.len() > MAX_SCRIPT_ELEMENT_SIZE {
            return Err(super::P2shError::ExcessiveScriptSize);
        }
        Ok(Self::p2sh_from_hash(script.script_hash(), prefix))
    }

    /// Creates a pay to script hash (160-bit) address from a script hash.
    ///
    /// # Warning
    ///
    /// The `hash` pre-image (redeem script) must not exceed 520 bytes in length otherwise
    /// outputs created from the returned address will be un-spendable.
    pub fn p2sh_from_hash(hash: ScriptHash, prefix: impl Into<KnownPrefix>) -> CashAddress {
        let inner = CashAddressInner {
            payload: CashPayload::P2sh(hash),
            token_aware: false,
            prefix: prefix.into(),
        };
        Self(inner, PhantomData)
    }

    /// Creates a pay to 256-bit script hash (P2SH32) address from a redeem script.
    ///
    /// P2SH32 was activated on Bitcoin Cash in the May 2023 upgrade.
    #[inline]
    pub fn p2sh32(
        script: &Script,
        prefix: impl Into<KnownPrefix>,
    ) -> Result<CashAddress, super::P2shError> {
        if script.len() > MAX_SCRIPT_ELEMENT_SIZE {
            return Err(super::P2shError::ExcessiveScriptSize);
        }
        Ok(Self::p2sh32_from_hash(script.script_hash32(), prefix))
    }

    /// Creates a pay to 256-bit script hash (P2SH32) address from a script hash.
    ///
    /// # Warning
    ///
    /// The `hash` pre-image (redeem script) must not exceed 520 bytes in length otherwise
    /// outputs created from the returned address will be un-spendable.
    pub fn p2sh32_from_hash(hash: ScriptHash32, prefix: impl Into<KnownPrefix>) -> CashAddress {
        let inner = CashAddressInner {
            payload: CashPayload::P2sh32(hash),
            token_aware: false,
            prefix: prefix.into(),
        };
        Self(inner, PhantomData)
    }

    /// Constructs a [`CashAddress`] from an output script (`scriptPubkey`).
    ///
    /// The returned address is not token-aware; use
    /// [`with_token_awareness`](Self::with_token_awareness) if required.
    pub fn from_script(
        script: &Script,
        prefix: impl Into<KnownPrefix>,
    ) -> Result<CashAddress, super::FromScriptError> {
        if script.is_p2pkh() {
            let bytes = script.as_bytes()[3..23].try_into().expect("statically 20B long");
            Ok(CashAddress::p2pkh(PubkeyHash::from_byte_array(bytes), prefix))
        } else if script.is_p2sh() {
            let bytes = script.as_bytes()[2..22].try_into().expect("statically 20B long");
            Ok(CashAddress::p2sh_from_hash(ScriptHash::from_byte_array(bytes), prefix))
        } else if script.is_p2sh32() {
            let bytes = script.as_bytes()[2..34].try_into().expect("statically 32B long");
            Ok(CashAddress::p2sh32_from_hash(ScriptHash32::from_byte_array(bytes), prefix))
        } else {
            Err(super::FromScriptError::UnrecognizedScript)
        }
    }

    /// Generates a script pubkey spending to this address.
    ///
    /// Token awareness does not affect the script.
    pub fn script_pubkey(&self) -> ScriptBuf {
        match &self.0.payload {
            CashPayload::P2pkh(hash) => ScriptBuf::new_p2pkh(hash),
            CashPayload::P2sh(hash) => ScriptBuf::new_p2sh(hash),
            CashPayload::P2sh32(hash) => ScriptBuf::new_p2sh32(hash),
        }
    }

    /// Returns true if the address creates a particular script.
    /// This function doesn't make any allocations.
    pub fn matches_script_pubkey(&self, script: &Script) -> bool {
        match &self.0.payload {
            CashPayload::P2pkh(hash) if script.is_p2pkh() =>
                &script.as_bytes()[3..23] == <PubkeyHash as AsRef<[u8; 20]>>::as_ref(hash),
            CashPayload::P2sh(hash) if script.is_p2sh() =>
                &script.as_bytes()[2..22] == <ScriptHash as AsRef<[u8; 20]>>::as_ref(hash),
            CashPayload::P2sh32(hash) if script.is_p2sh32() =>
                &script.as_bytes()[2..34] == <ScriptHash32 as AsRef<[u8; 32]>>::as_ref(hash),
            _ => false,
        }
    }

    /// Gets the prefix of this address.
    pub fn prefix(&self) -> KnownPrefix { self.0.prefix }

    /// Gets the pubkey hash for this address if this is a P2PKH address.
    pub fn pubkey_hash(&self) -> Option<PubkeyHash> {
        match &self.0.payload {
            CashPayload::P2pkh(hash) => Some(*hash),
            _ => None,
        }
    }

    /// Gets the script hash for this address if this is a P2SH (160-bit) address.
    pub fn script_hash(&self) -> Option<ScriptHash> {
        match &self.0.payload {
            CashPayload::P2sh(hash) => Some(*hash),
            _ => None,
        }
    }

    /// Gets the script hash for this address if this is a P2SH32 address.
    pub fn script_hash32(&self) -> Option<ScriptHash32> {
        match &self.0.payload {
            CashPayload::P2sh32(hash) => Some(*hash),
            _ => None,
        }
    }

    /// Converts this address to a legacy base58 [`Address`].
    ///
    /// Returns [`None`] for P2SH32 addresses, which cannot be represented in the legacy format.
    /// Token awareness is lost in the conversion since the legacy format cannot express it.
    ///
    /// The legacy format only distinguishes main and test networks, so both the `bchtest` and
    /// `bchreg` prefixes convert to a testnet legacy address; a `bchreg:` address round-tripped
    /// through [`Address::to_cashaddr`] therefore comes back with the `bchtest:` prefix.
    pub fn to_legacy(&self) -> Option<Address> {
        let network = match self.0.prefix {
            KnownPrefix::Mainnet => NetworkKind::Main,
            KnownPrefix::Testnets | KnownPrefix::Regtest => NetworkKind::Test,
        };
        match &self.0.payload {
            CashPayload::P2pkh(hash) => Some(Address::p2pkh(*hash, network)),
            CashPayload::P2sh(hash) => Some(Address::p2sh_from_hash(*hash, network)),
            CashPayload::P2sh32(_) => None,
        }
    }
}

/// Methods that can be called only on `CashAddress<NetworkUnchecked>`.
impl CashAddress<NetworkUnchecked> {
    /// Returns a reference to the checked address.
    ///
    /// This function is dangerous in case the address is not a valid checked address.
    pub fn assume_checked_ref(&self) -> &CashAddress {
        unsafe { &*(self as *const CashAddress<NetworkUnchecked> as *const CashAddress) }
    }

    /// Returns whether the address is valid on `n`.
    ///
    /// The `bchtest` prefix is shared by all test networks (testnet3, testnet4, scalenet and
    /// chipnet), so an address parsed from a `bchtest` string is valid for all of them.
    pub fn is_valid_for_network(&self, n: Network) -> bool {
        self.0.prefix == KnownPrefix::from_network(n)
    }

    /// Checks whether network of this address is as required.
    ///
    /// For details about this mechanism, see section [*Parsing addresses*](Address#parsing-addresses)
    /// on [`Address`].
    #[inline]
    pub fn require_network(self, required: Network) -> Result<CashAddress, ParseError> {
        if self.is_valid_for_network(required) {
            Ok(self.assume_checked())
        } else {
            Err(NetworkValidationError { required, address: self }.into())
        }
    }

    /// Marks, without any additional checks, network of this address as checked.
    ///
    /// Improper use of this method may lead to loss of funds. Reader will most likely prefer
    /// [`require_network`](CashAddress<NetworkUnchecked>::require_network) as a safe variant.
    #[inline]
    pub fn assume_checked(self) -> CashAddress { CashAddress(self.0, PhantomData) }
}

impl From<CashAddress> for ScriptBuf {
    fn from(a: CashAddress) -> Self { a.script_pubkey() }
}

/// Alternate formatting `{:#}` formats the address as upper case, which should be used in QR
/// codes as it permits the more compact alphanumeric encoding mode.
impl fmt::Display for CashAddress {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, fmt) }
}

impl<V: NetworkValidation> fmt::Debug for CashAddress<V> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if V::IS_CHECKED {
            fmt::Display::fmt(&self.0, f)
        } else {
            write!(f, "CashAddress<NetworkUnchecked>(")?;
            fmt::Display::fmt(&self.0, f)?;
            write!(f, ")")
        }
    }
}

/// Interprets a decoded payload as a Bitcoin Cash address.
fn interpret(
    prefix: KnownPrefix,
    type_bits: u8,
    data: &[u8],
) -> Result<CashAddressInner, ParseError> {
    let token_aware = match type_bits {
        TYPE_P2PKH | TYPE_P2SH => false,
        TYPE_P2PKH_TOKEN | TYPE_P2SH_TOKEN => true,
        _ => return Err(UnsupportedTypeError { type_bits }.into()),
    };
    let is_script_hash = type_bits & 0x01 != 0;
    let payload = match (is_script_hash, data.len()) {
        (false, 20) => CashPayload::P2pkh(PubkeyHash::from_byte_array(
            data.try_into().expect("length just checked"),
        )),
        (true, 20) => CashPayload::P2sh(ScriptHash::from_byte_array(
            data.try_into().expect("length just checked"),
        )),
        (true, 32) => CashPayload::P2sh32(ScriptHash32::from_byte_array(
            data.try_into().expect("length just checked"),
        )),
        (_, length) => return Err(UnsupportedHashLengthError { type_bits, length }.into()),
    };
    Ok(CashAddressInner { payload, token_aware, prefix })
}

/// A [`CashAddress`] can be parsed only with `NetworkUnchecked`.
///
/// The prefix part is optional; without it, the known prefixes (`bitcoincash`, `bchtest`,
/// `bchreg`) are tried in turn, relying on the checksum (which covers the prefix) to pick the
/// right one.
impl FromStr for CashAddress<NetworkUnchecked> {
    type Err = ParseError;

    fn from_str(s: &str) -> Result<CashAddress<NetworkUnchecked>, ParseError> {
        check_case(s).map_err(ParseError::Decode)?;
        let inner = match s.split_once(':') {
            Some((prefix, body)) => {
                let known = KnownPrefix::from_prefix_str(prefix)
                    .ok_or_else(|| UnknownPrefixError(prefix.to_owned()))?;
                let (type_bits, data) = decode_body(known.as_str(), body)?;
                interpret(known, type_bits, &data)?
            }
            None => {
                let mut result: Result<CashAddressInner, ParseError> =
                    Err(DecodeError::Checksum.into());
                for known in [KnownPrefix::Mainnet, KnownPrefix::Testnets, KnownPrefix::Regtest] {
                    match decode_body(known.as_str(), s) {
                        Ok((type_bits, data)) => {
                            result = Ok(interpret(known, type_bits, &data)?);
                            break;
                        }
                        // A prefix mismatch surfaces as a checksum failure; try the next one.
                        Err(DecodeError::Checksum) => continue,
                        // Any other error is independent of the prefix.
                        Err(e) => {
                            result = Err(e.into());
                            break;
                        }
                    }
                }
                result?
            }
        };
        Ok(CashAddress(inner, PhantomData))
    }
}

/// Error while encoding a cashaddr with the low-level [`encode`] function.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum EncodeError {
    /// The payload length is not one of the eight sizes defined by the specification.
    InvalidPayloadLength(usize),
    /// The type bits value does not fit in four bits.
    InvalidTypeBits(u8),
    /// The prefix is empty or contains characters other than lowercase ASCII letters.
    InvalidPrefix,
}

internals::impl_from_infallible!(EncodeError);

impl fmt::Display for EncodeError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use EncodeError::*;

        match *self {
            InvalidPayloadLength(len) => write!(
                f,
                "cashaddr payload length {} is not one of 20, 24, 28, 32, 40, 48, 56, 64",
                len
            ),
            InvalidTypeBits(bits) => write!(f, "cashaddr type bits {} exceed four bits", bits),
            InvalidPrefix => write!(f, "cashaddr prefix must be non-empty lowercase ASCII letters"),
        }
    }
}

#[cfg(feature = "std")]
impl std::error::Error for EncodeError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}

/// Error while decoding a cashaddr string.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum DecodeError {
    /// The string mixes lower and upper case characters.
    MixedCase,
    /// The string contains a character outside the cashaddr character set.
    InvalidChar(char),
    /// The checksum does not verify.
    ///
    /// The checksum covers the prefix, so this is also the result of decoding an address
    /// against the wrong prefix.
    Checksum,
    /// The payload or hash length is invalid (either structurally or against the size bits of
    /// the version byte).
    InvalidLength(usize),
    /// The padding of the base32 payload is non-canonical (non-zero or too long).
    InvalidPadding,
    /// The reserved most significant bit of the version byte is set.
    ReservedBitSet,
    /// The string has no `prefix:` part.
    MissingPrefix,
    /// The prefix is empty.
    ///
    /// A prefix with invalid characters is reported as [`InvalidChar`](Self::InvalidChar)
    /// naming the offending character instead.
    InvalidPrefix,
}

internals::impl_from_infallible!(DecodeError);

impl fmt::Display for DecodeError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use DecodeError::*;

        match *self {
            MixedCase => write!(f, "cashaddr strings must not mix lower and upper case"),
            InvalidChar(c) => write!(f, "invalid cashaddr character {:?}", c),
            Checksum => write!(f, "cashaddr checksum verification failed"),
            InvalidLength(len) => write!(f, "invalid cashaddr payload length {}", len),
            InvalidPadding => write!(f, "invalid padding in cashaddr payload"),
            ReservedBitSet => write!(f, "reserved bit of the cashaddr version byte is set"),
            MissingPrefix => write!(f, "cashaddr string has no prefix"),
            InvalidPrefix => write!(f, "cashaddr prefix is empty"),
        }
    }
}

#[cfg(feature = "std")]
impl std::error::Error for DecodeError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}

/// Unknown cashaddr prefix error.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct UnknownPrefixError(pub String);

impl fmt::Display for UnknownPrefixError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "unknown cashaddr prefix: {}", self.0)
    }
}

#[cfg(feature = "std")]
impl std::error::Error for UnknownPrefixError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}

/// The address type bits are valid per the cashaddr specification but do not denote a known
/// Bitcoin Cash address type.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct UnsupportedTypeError {
    /// The unsupported type bits.
    pub type_bits: u8,
}

impl fmt::Display for UnsupportedTypeError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "unsupported cashaddr address type bits: {}", self.type_bits)
    }
}

#[cfg(feature = "std")]
impl std::error::Error for UnsupportedTypeError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}

/// The hash length is valid per the cashaddr specification but not for the denoted address
/// type (P2PKH hashes are 20 bytes; P2SH hashes are 20 or 32 bytes).
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct UnsupportedHashLengthError {
    /// The type bits of the address.
    pub type_bits: u8,
    /// The unsupported hash length in bytes.
    pub length: usize,
}

impl fmt::Display for UnsupportedHashLengthError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "unsupported hash length {} for cashaddr address type bits {}",
            self.length, self.type_bits
        )
    }
}

#[cfg(feature = "std")]
impl std::error::Error for UnsupportedHashLengthError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}

/// Address's network differs from required one.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NetworkValidationError {
    /// Network that was required.
    required: Network,
    /// The address itself.
    address: CashAddress<NetworkUnchecked>,
}

impl fmt::Display for NetworkValidationError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "cashaddr address ")?;
        fmt::Display::fmt(&self.address.0, f)?;
        write!(f, " is not valid on {}", self.required)
    }
}

#[cfg(feature = "std")]
impl std::error::Error for NetworkValidationError {}

/// Cashaddr address parsing error.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParseError {
    /// Low-level decoding error.
    Decode(DecodeError),
    /// The prefix is not one of the known Bitcoin Cash prefixes.
    UnknownPrefix(UnknownPrefixError),
    /// The address type bits do not denote a known address type.
    UnsupportedType(UnsupportedTypeError),
    /// The hash length is not valid for the address type.
    UnsupportedHashLength(UnsupportedHashLengthError),
    /// Address's network differs from required one.
    NetworkValidation(NetworkValidationError),
}

internals::impl_from_infallible!(ParseError);

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use ParseError::*;

        match self {
            Decode(e) => write_err!(f, "cashaddr decoding error"; e),
            UnknownPrefix(e) => write_err!(f, "cashaddr unknown prefix"; e),
            UnsupportedType(e) => write_err!(f, "cashaddr unsupported type"; e),
            UnsupportedHashLength(e) => write_err!(f, "cashaddr unsupported hash length"; e),
            NetworkValidation(e) => write_err!(f, "cashaddr network validation error"; e),
        }
    }
}

#[cfg(feature = "std")]
impl std::error::Error for ParseError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        use ParseError::*;

        match self {
            Decode(e) => Some(e),
            UnknownPrefix(e) => Some(e),
            UnsupportedType(e) => Some(e),
            UnsupportedHashLength(e) => Some(e),
            NetworkValidation(e) => Some(e),
        }
    }
}

impl From<DecodeError> for ParseError {
    fn from(e: DecodeError) -> Self { Self::Decode(e) }
}

impl From<UnknownPrefixError> for ParseError {
    fn from(e: UnknownPrefixError) -> Self { Self::UnknownPrefix(e) }
}

impl From<UnsupportedTypeError> for ParseError {
    fn from(e: UnsupportedTypeError) -> Self { Self::UnsupportedType(e) }
}

impl From<UnsupportedHashLengthError> for ParseError {
    fn from(e: UnsupportedHashLengthError) -> Self { Self::UnsupportedHashLength(e) }
}

impl From<NetworkValidationError> for ParseError {
    fn from(e: NetworkValidationError) -> Self { Self::NetworkValidation(e) }
}

#[cfg(test)]
mod tests {
    use hex::FromHex;

    use super::*;

    fn parse(s: &str) -> CashAddress { s.parse::<CashAddress<_>>().unwrap().assume_checked() }

    /// Builds a cashaddr string from raw 5-bit values, computing a valid checksum. Used to
    /// craft structurally invalid payloads that still pass the checksum.
    fn build_raw(prefix: &str, values: &[u8]) -> String {
        let mut chk = polymod_prefix(1, prefix);
        for &v in values {
            chk = polymod_step(chk, v);
        }
        for _ in 0..8 {
            chk = polymod_step(chk, 0);
        }
        let chk = chk ^ 1;
        let mut s = String::from(prefix);
        s.push(':');
        for &v in values {
            s.push(char::from(CHARSET[usize::from(v)]));
        }
        for i in (0..8).rev() {
            s.push(char::from(CHARSET[((chk >> (5 * i)) & 0x1f) as usize]));
        }
        s
    }

    /// The "Larger Test Vectors" table of the cashaddr specification.
    const SPEC_VECTORS: &[(&str, u8, &str)] = &[
        ("bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2", 0, "F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9"),
        ("bchtest:pr6m7j9njldwwzlg9v7v53unlr4jkmx6eyvwc0uz5t", 1, "F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9"),
        ("pref:pr6m7j9njldwwzlg9v7v53unlr4jkmx6ey65nvtks5", 1, "F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9"),
        ("prefix:0r6m7j9njldwwzlg9v7v53unlr4jkmx6ey3qnjwsrf", 15, "F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9"),
        ("bitcoincash:q9adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2ws4mr9g0", 0, "7ADBF6C17084BC86C1706827B41A56F5CA32865925E946EA"),
        ("bchtest:p9adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2u94tsynr", 1, "7ADBF6C17084BC86C1706827B41A56F5CA32865925E946EA"),
        ("pref:p9adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2khlwwk5v", 1, "7ADBF6C17084BC86C1706827B41A56F5CA32865925E946EA"),
        ("prefix:09adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2p29kc2lp", 15, "7ADBF6C17084BC86C1706827B41A56F5CA32865925E946EA"),
        ("bitcoincash:qgagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkcw59jxxuz", 0, "3A84F9CF51AAE98A3BB3A78BF16A6183790B18719126325BFC0C075B"),
        ("bchtest:pgagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkcvs7md7wt", 1, "3A84F9CF51AAE98A3BB3A78BF16A6183790B18719126325BFC0C075B"),
        ("pref:pgagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkcrsr6gzkn", 1, "3A84F9CF51AAE98A3BB3A78BF16A6183790B18719126325BFC0C075B"),
        ("prefix:0gagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkc5djw8s9g", 15, "3A84F9CF51AAE98A3BB3A78BF16A6183790B18719126325BFC0C075B"),
        ("bitcoincash:qvch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxq5nlegake", 0, "3173EF6623C6B48FFD1A3DCC0CC6489B0A07BB47A37F47CFEF4FE69DE825C060"),
        ("bchtest:pvch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxq7fqng6m6", 1, "3173EF6623C6B48FFD1A3DCC0CC6489B0A07BB47A37F47CFEF4FE69DE825C060"),
        ("pref:pvch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxq4k9m7qf9", 1, "3173EF6623C6B48FFD1A3DCC0CC6489B0A07BB47A37F47CFEF4FE69DE825C060"),
        ("prefix:0vch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxqsh6jgp6w", 15, "3173EF6623C6B48FFD1A3DCC0CC6489B0A07BB47A37F47CFEF4FE69DE825C060"),
        ("bitcoincash:qnq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklv39gr3uvz", 0, "C07138323E00FA4FC122D3B85B9628EA810B3F381706385E289B0B25631197D194B5C238BEB136FB"),
        ("bchtest:pnq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklvmgm6ynej", 1, "C07138323E00FA4FC122D3B85B9628EA810B3F381706385E289B0B25631197D194B5C238BEB136FB"),
        ("pref:pnq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklv0vx5z0w3", 1, "C07138323E00FA4FC122D3B85B9628EA810B3F381706385E289B0B25631197D194B5C238BEB136FB"),
        ("prefix:0nq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklvwsvctzqy", 15, "C07138323E00FA4FC122D3B85B9628EA810B3F381706385E289B0B25631197D194B5C238BEB136FB"),
        ("bitcoincash:qh3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqex2w82sl", 0, "E361CA9A7F99107C17A622E047E3745D3E19CF804ED63C5C40C6BA763696B98241223D8CE62AD48D863F4CB18C930E4C"),
        ("bchtest:ph3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqnzf7mt6x", 1, "E361CA9A7F99107C17A622E047E3745D3E19CF804ED63C5C40C6BA763696B98241223D8CE62AD48D863F4CB18C930E4C"),
        ("pref:ph3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqjntdfcwg", 1, "E361CA9A7F99107C17A622E047E3745D3E19CF804ED63C5C40C6BA763696B98241223D8CE62AD48D863F4CB18C930E4C"),
        ("prefix:0h3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqakcssnmn", 15, "E361CA9A7F99107C17A622E047E3745D3E19CF804ED63C5C40C6BA763696B98241223D8CE62AD48D863F4CB18C930E4C"),
        ("bitcoincash:qmvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqscw8jd03f", 0, "D9FA7C4C6EF56DC4FF423BAAE6D495DBFF663D034A72D1DC7D52CBFE7D1E6858F9D523AC0A7A5C34077638E4DD1A701BD017842789982041"),
        ("bchtest:pmvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqs6kgdsg2g", 1, "D9FA7C4C6EF56DC4FF423BAAE6D495DBFF663D034A72D1DC7D52CBFE7D1E6858F9D523AC0A7A5C34077638E4DD1A701BD017842789982041"),
        ("pref:pmvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqsammyqffl", 1, "D9FA7C4C6EF56DC4FF423BAAE6D495DBFF663D034A72D1DC7D52CBFE7D1E6858F9D523AC0A7A5C34077638E4DD1A701BD017842789982041"),
        ("prefix:0mvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqsgjrqpnw8", 15, "D9FA7C4C6EF56DC4FF423BAAE6D495DBFF663D034A72D1DC7D52CBFE7D1E6858F9D523AC0A7A5C34077638E4DD1A701BD017842789982041"),
        ("bitcoincash:qlg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96mtky5sv5w", 0, "D0F346310D5513D9E01E299978624BA883E6BDA8F4C60883C10F28C2967E67EC77ECC7EEEAEAFC6DA89FAD72D11AC961E164678B868AEEEC5F2C1DA08884175B"),
        ("bchtest:plg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96mc773cwez", 1, "D0F346310D5513D9E01E299978624BA883E6BDA8F4C60883C10F28C2967E67EC77ECC7EEEAEAFC6DA89FAD72D11AC961E164678B868AEEEC5F2C1DA08884175B"),
        ("pref:plg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96mg7pj3lh8", 1, "D0F346310D5513D9E01E299978624BA883E6BDA8F4C60883C10F28C2967E67EC77ECC7EEEAEAFC6DA89FAD72D11AC961E164678B868AEEEC5F2C1DA08884175B"),
        ("prefix:0lg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96ms92w6845", 15, "D0F346310D5513D9E01E299978624BA883E6BDA8F4C60883C10F28C2967E67EC77ECC7EEEAEAFC6DA89FAD72D11AC961E164678B868AEEEC5F2C1DA08884175B"),
    ];

    /// Token-aware test vector pairs from the CashTokens specification: the same payload
    /// encoded without and with token awareness.
    const CASHTOKENS_PAIRS: &[(&str, &str, &str)] = &[
        (
            "bitcoincash:qr7fzmep8g7h7ymfxy74lgc0v950j3r2959lhtxxsl",
            "bitcoincash:zr7fzmep8g7h7ymfxy74lgc0v950j3r295z4y4gq0v",
            "fc916f213a3d7f1369313d5fa30f6168f9446a2d",
        ),
        (
            "bchtest:qr7fzmep8g7h7ymfxy74lgc0v950j3r295pdnvy3hr",
            "bchtest:zr7fzmep8g7h7ymfxy74lgc0v950j3r295x8qj2hgs",
            "fc916f213a3d7f1369313d5fa30f6168f9446a2d",
        ),
        (
            "bchreg:qr7fzmep8g7h7ymfxy74lgc0v950j3r295m39d8z59",
            "bchreg:zr7fzmep8g7h7ymfxy74lgc0v950j3r295umknfytk",
            "fc916f213a3d7f1369313d5fa30f6168f9446a2d",
        ),
        (
            "bitcoincash:qpagr634w55t4wp56ftxx53xukhqgl24yse53qxdge",
            "bitcoincash:zpagr634w55t4wp56ftxx53xukhqgl24ys77z7gth2",
            "7a81ea357528bab834d256635226e5ae047d5524",
        ),
        (
            "bitcoincash:qq9l9e2dgkx0hp43qm3c3h252e9euugrfc6vlt3r9e",
            "bitcoincash:zq9l9e2dgkx0hp43qm3c3h252e9euugrfcaxv4l962",
            "0bf2e54d458cfb86b106e388dd54564b9e71034e",
        ),
        (
            "bitcoincash:qre24q38ghy6k3pegpyvtxahu8q8hqmxmqqn28z85p",
            "bitcoincash:zre24q38ghy6k3pegpyvtxahu8q8hqmxmq8eeevptj",
            "f2aa822745c9ab44394048c59bb7e1c07b8366d8",
        ),
        (
            "bitcoincash:qz7xc0vl85nck65ffrsx5wvewjznp9lflgktxc5878",
            "bitcoincash:zz7xc0vl85nck65ffrsx5wvewjznp9lflg3p4x6pp5",
            "bc6c3d9f3d278b6a8948e06a399974853097e9fa",
        ),
        (
            "bitcoincash:ppawqn2h74a4t50phuza84kdp3794pq3ccvm92p8sh",
            "bitcoincash:rpawqn2h74a4t50phuza84kdp3794pq3cct3k50p0y",
            "7ae04d57f57b55d1e1bf05d3d6cd0c7c5a8411c6",
        ),
        (
            "bitcoincash:pqv53dwyatxse2xh7nnlqhyr6ryjgfdtagkd4vc388",
            "bitcoincash:rqv53dwyatxse2xh7nnlqhyr6ryjgfdtag38xjkhc5",
            "1948b5c4eacd0ca8d7f4e7f05c83d0c92425abea",
        ),
        (
            "bitcoincash:prseh0a4aejjcewhc665wjqhppgwrz2lw5txgn666a",
            "bitcoincash:rrseh0a4aejjcewhc665wjqhppgwrz2lw5vvmd5u9w",
            "e19bbfb5ee652c65d7c6b54748170850e1895f75",
        ),
        (
            "bitcoincash:pzltaslh7xnrsxeqm7qtvh0v53n3gfk0v5wwf6d7j4",
            "bitcoincash:rzltaslh7xnrsxeqm7qtvh0v53n3gfk0v5fy6yrcdx",
            "bebec3f7f1a6381b20df80b65deca4671426cf65",
        ),
        (
            "bitcoincash:pvqqqqqqqqqqqqqqqqqqqqqqzg69v7ysqqqqqqqqqqqqqqqqqqqqqpkp7fqn0",
            "bitcoincash:rvqqqqqqqqqqqqqqqqqqqqqqzg69v7ysqqqqqqqqqqqqqqqqqqqqqn9alsp2y",
            "0000000000000000000000000000123456789000000000000000000000000000",
        ),
        (
            "bitcoincash:pdzyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3jh2p5nn",
            "bitcoincash:rdzyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygrpttc42c",
            "4444444444444444444444444444444444444444444444444444444444444444",
        ),
        (
            "bitcoincash:pwyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsh3sujgcr",
            "bitcoincash:rwyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9zvatfpg",
            "8888888888888888888888888888888888888888888888888888888888888888",
        ),
        (
            "bitcoincash:p0xvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvcm6gz4t77",
            "bitcoincash:r0xvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvcff5rv284",
            "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
        ),
        (
            "bitcoincash:p0llllllllllllllllllllllllllllllllllllllllllllllllll7x3vthu35",
            "bitcoincash:r0llllllllllllllllllllllllllllllllllllllllllllllllll75zs2wagl",
            "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
        ),
    ];

    /// The legacy/cashaddr translation table of the cashaddr specification.
    const LEGACY_PAIRS: &[(&str, &str)] = &[
        (
            "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu",
            "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a",
        ),
        (
            "1KXrWXciRDZUpQwQmuM1DbwsKDLYAYsVLR",
            "bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy",
        ),
        (
            "16w1D5WRVKJuZUsSRzdLp9w3YGcgoxDXb",
            "bitcoincash:qqq3728yw0y47sqn6l2na30mcw6zm78dzqre909m2r",
        ),
        (
            "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC",
            "bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq",
        ),
        (
            "3LDsS579y7sruadqu11beEJoTjdFiFCdX4",
            "bitcoincash:pr95sy3j9xwd2ap32xkykttr4cvcu7as4yc93ky28e",
        ),
        (
            "31nwvkZwyPdgzjBJZXfDmSWsC4ZLKpYyUw",
            "bitcoincash:pqq3728yw0y47sqn6l2na30mcw6zm78dzq5ucqzc37",
        ),
    ];

    #[test]
    fn spec_checksum_vectors() {
        // These strings from the specification have valid checksums but deliberately invalid
        // payloads; only the checksum is verified here.
        for addr in [
            "prefix:x64nx6hz",
            "p:gpf8m4h7",
            "bitcoincash:qpzry9x8gf2tvdw0s3jn54khce6mua7lcw20ayyn",
            "bchtest:testnetaddress4d6njnut",
            "bchreg:555555555555555555555555555555555555555555555udxmlmrz",
        ] {
            let (prefix, body) = addr.split_once(':').unwrap();
            let mut chk = polymod_prefix(1, prefix);
            for c in body.chars() {
                chk = polymod_step(chk, char_value(c).unwrap());
            }
            assert_eq!(chk, 1, "checksum failed for {}", addr);
        }
    }

    #[test]
    fn spec_larger_test_vectors() {
        for &(addr, type_bits, hex) in SPEC_VECTORS {
            let data = Vec::<u8>::from_hex(hex).unwrap();
            let (prefix, _) = addr.split_once(':').unwrap();
            assert_eq!(encode(prefix, type_bits, &data).unwrap(), addr);

            let decoded = decode(addr).unwrap();
            assert_eq!(decoded.prefix, prefix);
            assert_eq!(decoded.type_bits, type_bits);
            assert_eq!(decoded.data, data);
        }
    }

    #[test]
    fn decode_with_prefix_roundtrip() {
        for &(addr, type_bits, hex) in SPEC_VECTORS {
            let data = Vec::<u8>::from_hex(hex).unwrap();
            let (prefix, body) = addr.split_once(':').unwrap();
            let decoded = decode_with_prefix(prefix, body).unwrap();
            assert_eq!(decoded.type_bits, type_bits);
            assert_eq!(decoded.data, data);
        }
        // The checksum covers the prefix, so a wrong prefix fails.
        let (_, body) =
            "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2".split_once(':').unwrap();
        assert_eq!(decode_with_prefix("bchtest", body), Err(DecodeError::Checksum));
    }

    #[test]
    fn cashtokens_vectors() {
        for &(plain, token, hex) in CASHTOKENS_PAIRS {
            let data = Vec::<u8>::from_hex(hex).unwrap();
            let plain_addr = parse(plain);
            let token_addr = parse(token);

            assert!(!plain_addr.is_token_aware());
            assert!(token_addr.is_token_aware());
            assert_eq!(plain_addr.to_string(), plain);
            assert_eq!(token_addr.to_string(), token);

            // Toggling token awareness maps between the pair.
            assert_eq!(plain_addr.clone().with_token_awareness(true), token_addr);
            assert_eq!(token_addr.clone().with_token_awareness(false), plain_addr);

            // Token awareness does not change the script.
            assert_eq!(plain_addr.script_pubkey(), token_addr.script_pubkey());

            let payload: &[u8] = match plain_addr.address_type() {
                AddressType::P2pkh => plain_addr.pubkey_hash().unwrap().to_byte_array().to_vec(),
                AddressType::P2sh => plain_addr.script_hash().unwrap().to_byte_array().to_vec(),
                AddressType::P2sh32 => plain_addr.script_hash32().unwrap().to_byte_array().to_vec(),
                other => panic!("unexpected address type {}", other),
            }
            .leak();
            assert_eq!(payload, &data[..]);
        }
    }

    #[test]
    fn parse_without_prefix() {
        for (with_prefix, expected_prefix) in [
            ("bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2", KnownPrefix::Mainnet),
            ("bchtest:pr6m7j9njldwwzlg9v7v53unlr4jkmx6eyvwc0uz5t", KnownPrefix::Testnets),
            ("bchreg:qr7fzmep8g7h7ymfxy74lgc0v950j3r295m39d8z59", KnownPrefix::Regtest),
        ] {
            let (_, body) = with_prefix.split_once(':').unwrap();
            let addr = parse(body);
            assert_eq!(addr.prefix(), expected_prefix);
            assert_eq!(addr.to_string(), with_prefix);
            assert_eq!(addr, parse(with_prefix));
        }
    }

    #[test]
    fn uppercase() {
        let lower = "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2";
        let upper = "BITCOINCASH:QR6M7J9NJLDWWZLG9V7V53UNLR4JKMX6EYLEP8EKG2";
        let addr = parse(upper);
        assert_eq!(addr, parse(lower));
        assert_eq!(addr.to_string(), lower);
        assert_eq!(format!("{:#}", addr), upper);
    }

    #[test]
    fn network_validation() {
        use crate::network::Network::*;

        let addr: CashAddress<NetworkUnchecked> =
            "bchtest:pr6m7j9njldwwzlg9v7v53unlr4jkmx6eyvwc0uz5t".parse().unwrap();
        for network in [Testnet, Testnet4, Scalenet, Chipnet] {
            assert!(addr.is_valid_for_network(network));
        }
        assert!(!addr.is_valid_for_network(Bitcoin));
        assert!(!addr.is_valid_for_network(Regtest));
        assert!(matches!(addr.require_network(Bitcoin), Err(ParseError::NetworkValidation(_))));

        let addr: CashAddress<NetworkUnchecked> =
            "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2".parse().unwrap();
        assert!(addr.is_valid_for_network(Bitcoin));
        assert!(addr.require_network(Bitcoin).is_ok());
    }

    #[test]
    fn legacy_conversion() {
        use core::str::FromStr;

        for &(legacy, cashaddr) in LEGACY_PAIRS {
            let legacy_addr = Address::from_str(legacy).unwrap().assume_checked();
            assert_eq!(legacy_addr.to_cashaddr().unwrap().to_string(), cashaddr);

            let cash_addr = parse(cashaddr);
            assert_eq!(cash_addr.to_legacy().unwrap().to_string(), legacy);
            assert_eq!(cash_addr.script_pubkey(), legacy_addr.script_pubkey());
        }

        // P2SH32 has no legacy representation.
        let p2sh32 =
            parse("bitcoincash:p0llllllllllllllllllllllllllllllllllllllllllllllllll7x3vthu35");
        assert_eq!(p2sh32.to_legacy(), None);

        // Segwit addresses have no cashaddr representation.
        let segwit = Address::from_str("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4")
            .unwrap()
            .assume_checked();
        assert_eq!(segwit.to_cashaddr(), None);
    }

    #[test]
    fn script_roundtrip() {
        let redeem = ScriptBuf::from_bytes(vec![0x51]); // OP_TRUE

        let p2pkh = parse("bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2");
        let spk = p2pkh.script_pubkey();
        assert!(spk.is_p2pkh());
        assert!(p2pkh.matches_script_pubkey(&spk));
        assert_eq!(CashAddress::from_script(&spk, KnownPrefix::Mainnet).unwrap(), p2pkh);

        let p2sh = CashAddress::p2sh(&redeem, Network::Bitcoin).unwrap();
        assert_eq!(p2sh.script_hash().unwrap(), redeem.script_hash());
        let spk = p2sh.script_pubkey();
        assert!(spk.is_p2sh());
        assert!(p2sh.matches_script_pubkey(&spk));
        assert_eq!(CashAddress::from_script(&spk, KnownPrefix::Mainnet).unwrap(), p2sh);

        let p2sh32 = CashAddress::p2sh32(&redeem, Network::Bitcoin).unwrap();
        assert_eq!(p2sh32.address_type(), AddressType::P2sh32);
        assert_eq!(p2sh32.script_hash32().unwrap(), redeem.script_hash32());
        let spk = p2sh32.script_pubkey();
        assert_eq!(spk, redeem.to_p2sh32());
        assert!(spk.is_p2sh32());
        assert_eq!(spk.len(), 35);
        assert_eq!(spk.as_bytes()[0], 0xaa); // OP_HASH256
        assert_eq!(spk.as_bytes()[1], 0x20); // OP_PUSHBYTES_32
        assert_eq!(spk.as_bytes()[34], 0x87); // OP_EQUAL
        assert!(p2sh32.matches_script_pubkey(&spk));
        assert_eq!(CashAddress::from_script(&spk, KnownPrefix::Mainnet).unwrap(), p2sh32);

        // A known P2SH32 payload encodes to the expected string.
        let hash = ScriptHash32::from_byte_array([0xff; 32]);
        let addr = CashAddress::p2sh32_from_hash(hash, Network::Bitcoin);
        assert_eq!(
            addr.to_string(),
            "bitcoincash:p0llllllllllllllllllllllllllllllllllllllllllllllllll7x3vthu35"
        );

        let excessive = ScriptBuf::from_bytes(vec![0x51; MAX_SCRIPT_ELEMENT_SIZE + 1]);
        assert!(CashAddress::p2sh(&excessive, Network::Bitcoin).is_err());
        assert!(CashAddress::p2sh32(&excessive, Network::Bitcoin).is_err());
    }

    #[test]
    fn parse_errors() {
        fn parse_err(s: &str) -> ParseError { s.parse::<CashAddress<_>>().unwrap_err() }

        // Mixed case, in the payload or across prefix and payload.
        assert_eq!(
            parse_err("bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkMX6eylep8ekg2"),
            ParseError::Decode(DecodeError::MixedCase)
        );
        assert_eq!(
            parse_err("Bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2"),
            ParseError::Decode(DecodeError::MixedCase)
        );

        // Character outside the character set.
        assert_eq!(
            parse_err("bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekb2"),
            ParseError::Decode(DecodeError::InvalidChar('b'))
        );

        // Corrupted checksum, with and without prefix.
        assert_eq!(
            parse_err("bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg3"),
            ParseError::Decode(DecodeError::Checksum)
        );
        assert_eq!(
            parse_err("qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg3"),
            ParseError::Decode(DecodeError::Checksum)
        );

        // Unknown prefix.
        assert!(matches!(
            parse_err("pref:pr6m7j9njldwwzlg9v7v53unlr4jkmx6ey65nvtks5"),
            ParseError::UnknownPrefix(_)
        ));

        // Valid encoding of a type not defined for Bitcoin Cash.
        let unknown_type = encode("bitcoincash", 15, &[0x42; 20]).unwrap();
        assert!(matches!(
            parse_err(&unknown_type),
            ParseError::UnsupportedType(UnsupportedTypeError { type_bits: 15, .. })
        ));

        // Valid encoding of a hash length not defined for the address type.
        let bad_length = encode("bitcoincash", 0, &[0x42; 24]).unwrap();
        assert!(matches!(
            parse_err(&bad_length),
            ParseError::UnsupportedHashLength(UnsupportedHashLengthError {
                type_bits: 0,
                length: 24,
                ..
            })
        ));
        // A 32-byte hash is only defined for P2SH, not P2PKH.
        let bad_length = encode("bitcoincash", 0, &[0x42; 32]).unwrap();
        assert!(matches!(
            parse_err(&bad_length),
            ParseError::UnsupportedHashLength(UnsupportedHashLengthError {
                type_bits: 0,
                length: 32,
                ..
            })
        ));

        // Too short to contain a payload.
        assert_eq!(
            parse_err("bitcoincash:qqqqq"),
            ParseError::Decode(DecodeError::InvalidLength(5))
        );
    }

    #[test]
    fn decode_errors() {
        // Prefix required by the low-level decode.
        assert_eq!(
            decode("qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2"),
            Err(DecodeError::MissingPrefix)
        );

        // Empty prefix.
        assert_eq!(
            decode(":qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2"),
            Err(DecodeError::InvalidPrefix)
        );
        assert_eq!(
            decode_with_prefix("", "qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2"),
            Err(DecodeError::InvalidPrefix)
        );
        // Invalid prefix characters name the offending character.
        assert_eq!(
            decode("b1tcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2"),
            Err(DecodeError::InvalidChar('1'))
        );
        assert_eq!(
            decode_with_prefix("BITCOINCASH", "qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2"),
            Err(DecodeError::InvalidChar('B'))
        );

        // Non-zero padding bits.
        let mut values = [0u8; 34]; // version byte 0x00 plus 20 zero bytes is 34 groups
        values[33] = 1; // the final two bits are padding and must be zero
        assert_eq!(decode(&build_raw("bitcoincash", &values)), Err(DecodeError::InvalidPadding));

        // A whole extra group means more than four padding bits.
        let values = [0u8; 35];
        assert_eq!(decode(&build_raw("bitcoincash", &values)), Err(DecodeError::InvalidPadding));

        // Reserved most significant bit of the version byte.
        let mut s = String::new();
        encode_to_fmt(&mut s, "bitcoincash", 0x80, &[0; 20], false).unwrap();
        assert_eq!(decode(&s), Err(DecodeError::ReservedBitSet));

        // Size bits inconsistent with the actual hash length.
        let mut s = String::new();
        encode_to_fmt(&mut s, "bitcoincash", 0x03, &[0; 20], false).unwrap();
        assert_eq!(decode(&s), Err(DecodeError::InvalidLength(20)));
    }

    #[test]
    fn encode_errors() {
        assert_eq!(encode("bitcoincash", 0, &[0; 21]), Err(EncodeError::InvalidPayloadLength(21)));
        assert_eq!(encode("bitcoincash", 16, &[0; 20]), Err(EncodeError::InvalidTypeBits(16)));
        assert_eq!(encode("Bitcoincash", 0, &[0; 20]), Err(EncodeError::InvalidPrefix));
        assert_eq!(encode("bch1", 0, &[0; 20]), Err(EncodeError::InvalidPrefix));
        assert_eq!(encode("", 0, &[0; 20]), Err(EncodeError::InvalidPrefix));
    }

    #[test]
    fn address_type_p2sh32() {
        use core::str::FromStr;

        assert_eq!(AddressType::from_str("p2sh32").unwrap(), AddressType::P2sh32);
        assert_eq!(AddressType::P2sh32.to_string(), "p2sh32");
    }

    #[cfg(feature = "serde")]
    #[test]
    fn serde_roundtrip() {
        let addr = parse("bitcoincash:zr7fzmep8g7h7ymfxy74lgc0v950j3r295z4y4gq0v");
        let json = serde_json::to_string(&addr).unwrap();
        assert_eq!(json, "\"bitcoincash:zr7fzmep8g7h7ymfxy74lgc0v950j3r295z4y4gq0v\"");
        let back: CashAddress<NetworkUnchecked> = serde_json::from_str(&json).unwrap();
        assert_eq!(back.assume_checked(), addr);
    }
}