1
- import Codec.Binary.Bech32 (DecodeError (.. ), EncodeError (.. ), bech32Decode , bech32Encode ,
1
+ import Codec.Binary.Bech32 (DecodeError (.. ), EncodeError (.. ), Bech32Type (.. ),
2
+ bech32Decode , bech32Encode , bech32Spec ,
2
3
segwitDecode , segwitEncode , word5 )
3
4
import Control.Monad (forM_ )
4
5
import Data.Bits (xor )
5
6
import qualified Data.ByteString as BS
6
7
import qualified Data.ByteString.Base16 as B16
7
8
import qualified Data.ByteString.Char8 as BSC
8
9
import Data.Char (toLower )
9
- import Data.Either (isLeft )
10
10
import Data.Maybe (isJust , isNothing )
11
11
import Data.Word (Word8 )
12
12
import Test.Tasty
@@ -15,38 +15,68 @@ import Test.Tasty.HUnit
15
15
main :: IO ()
16
16
main = defaultMain tests
17
17
18
- validChecksums :: [BS. ByteString ]
19
- validChecksums = map BSC. pack
20
- [ " A12UEL5L"
21
- , " an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs"
22
- , " abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw"
23
- , " 11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j"
24
- , " split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w"
25
- ]
18
+ validChecksums :: [(Bech32Type , BS. ByteString )]
19
+ validChecksums = [(b32type, BSC. pack string)
20
+ | (b32type, string) <-
21
+ [ (Bech32 , " A12UEL5L" )
22
+ , (Bech32 , " a12uel5l" )
23
+ , (Bech32 , " an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs" )
24
+ , (Bech32 , " abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw" )
25
+ , (Bech32 , " 11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j" )
26
+ , (Bech32 , " split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w" )
27
+ , (Bech32m , " A1LQFN3A" )
28
+ , (Bech32m , " a1lqfn3a" )
29
+ , (Bech32m , " an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6" )
30
+ , (Bech32m , " abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx" )
31
+ , (Bech32m , " 11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8" )
32
+ , (Bech32m , " split1checkupstagehandshakeupstreamerranterredcaperredlc445v" )
33
+ ] ]
26
34
27
- invalidChecksums :: [BS. ByteString ]
28
- invalidChecksums = map BSC. pack
29
- [ " 1nwldj5"
30
- , " \DEL 1axkwrx"
31
- , " an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx"
32
- , " pzry9x0s0muk"
33
- , " 1pzry9x0s0muk"
34
- , " x1b4n0q5v"
35
- , " li1dgmt3"
36
- , " de1lg7wt\xFF "
37
- ]
35
+ invalidChecksums :: [(Bech32Type , BS. ByteString )]
36
+ invalidChecksums = [(b32type, BSC. pack string)
37
+ | (b32type, string) <-
38
+ [ (Bech32 , " 1nwldj5" )
39
+ , (Bech32 , " \DEL 1axkwrx" )
40
+ , (Bech32 , " an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx" )
41
+ , (Bech32 , " pzry9x0s0muk" )
42
+ , (Bech32 , " 1pzry9x0s0muk" )
43
+ , (Bech32 , " x1b4n0q5v" )
44
+ , (Bech32 , " li1dgmt3" )
45
+ , (Bech32 , " de1lg7wt\xFF " )
46
+ , (Bech32 , " A1G7SGD8" )
47
+ , (Bech32 , " 10a06t8" )
48
+ , (Bech32 , " 1qzzfhee" )
49
+ , (Bech32m , " 1xj0phk" )
50
+ , (Bech32m , " \x79 " ++ " 1g6xzxy" )
51
+ , (Bech32m , " \x80 " ++ " 1vctc34" )
52
+ , (Bech32m , " an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4" )
53
+ , (Bech32m , " qyrz8wqd2c9m" )
54
+ , (Bech32m , " 1qyrz8wqd2c9m" )
55
+ , (Bech32m , " y1b0jsk6g" )
56
+ , (Bech32m , " lt1igcx5c0" )
57
+ , (Bech32m , " in1muywd" )
58
+ , (Bech32m , " mm1crxm3i" )
59
+ , (Bech32m , " au1s5cgom" )
60
+ , (Bech32m , " M1VUXWEZ" )
61
+ , (Bech32m , " 16plkw9" )
62
+ , (Bech32m , " 1p2gdwpf" )
63
+ ] ]
38
64
39
65
validAddresses :: [(BS. ByteString , BS. ByteString )]
40
66
validAddresses = map mapTuple
41
67
[ (" BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4" , " 0014751e76e8199196d454941c45d1b3a323f1433bd6" )
42
68
, (" tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7"
43
69
," 00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262" )
44
- , (" bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx "
70
+ , (" bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y "
45
71
," 5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6" )
46
- , (" BC1SW50QA3JX3S " , " 6002751e" )
47
- , (" bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj " , " 5210751e76e8199196d454941c45d1b3a323" )
72
+ , (" BC1SW50QGDZ25J " , " 6002751e" )
73
+ , (" bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs " , " 5210751e76e8199196d454941c45d1b3a323" )
48
74
, (" tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy"
49
75
," 0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433" )
76
+ , (" tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c"
77
+ ," 5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433" )
78
+ , (" bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0"
79
+ ," 512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" )
50
80
]
51
81
where
52
82
mapTuple (a, b) = (BSC. pack a, BSC. pack b)
@@ -62,6 +92,20 @@ invalidAddresses = map BSC.pack
62
92
, " tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7"
63
93
, " bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du"
64
94
, " tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv"
95
+ , " tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut"
96
+ , " bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd"
97
+ , " tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf"
98
+ , " BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL"
99
+ , " bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh"
100
+ , " tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47"
101
+ , " bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4"
102
+ , " BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R"
103
+ , " bc1pw5dgrnzv"
104
+ , " bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav"
105
+ , " BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P"
106
+ , " tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq"
107
+ , " bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf"
108
+ , " tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j"
65
109
, " bc1gmk9yu"
66
110
]
67
111
@@ -74,21 +118,23 @@ segwitScriptPubkey witver witprog = BS.pack $ witver' : (fromIntegral $ length w
74
118
75
119
tests :: TestTree
76
120
tests = testGroup " Tests"
77
- [ testCase " Checksums" $ forM_ validChecksums $ \ checksum -> do
121
+ [ testCase " Checksums" $ forM_ validChecksums $ \ (b32type, checksum) -> do
122
+ let spec = bech32Spec b32type
78
123
case bech32Decode checksum of
79
124
Left err -> assertFailure (show checksum ++ " , " ++ show err)
80
- Right (resultHRP, resultData) -> do
125
+ Right (residue, resultHRP, resultData) -> do
126
+ assertEqual (show checksum ++ " spec" ) spec residue
81
127
-- test that a corrupted checksum fails decoding.
82
128
let (hrp, rest) = BSC. breakEnd (== ' 1' ) checksum
83
129
Just (first, rest') = BS. uncons rest
84
130
checksumCorrupted = (hrp `BS.snoc` (first `xor` 1 )) `BS.append` rest'
85
- assertBool (show checksum ++ " corrupted" ) $ isLeft (bech32Decode checksumCorrupted)
131
+ assertBool (show checksum ++ " corrupted" ) $ isCorrupted spec (bech32Decode checksumCorrupted)
86
132
-- test that re-encoding the decoded checksum results in the same checksum.
87
- let checksumEncoded = bech32Encode resultHRP resultData
133
+ let checksumEncoded = bech32Encode spec resultHRP resultData
88
134
expectedChecksum = Right $ BSC. map toLower checksum
89
135
assertEqual (show checksum ++ " re-encode" ) expectedChecksum checksumEncoded
90
136
, testCase " Invalid checksums" $ forM_ invalidChecksums $
91
- \ checksum -> assertBool (show checksum) (isLeft $ bech32Decode checksum)
137
+ \ (b32type, checksum) -> assertBool (show checksum) $ isCorrupted (bech32Spec b32type) ( bech32Decode checksum)
92
138
, testCase " Addresses" $ forM_ validAddresses $ \ (address, hexscript) -> do
93
139
let address' = BSC. map toLower address
94
140
hrp = BSC. take 2 address'
@@ -102,7 +148,7 @@ tests = testGroup "Tests"
102
148
assertBool (show address) (isNothing $ segwitDecode (BSC. pack " tb" ) address)
103
149
, testCase " More Encoding/Decoding Cases" $ do
104
150
assertBool " length > 90" $ isError ResultStringLengthExceeded $
105
- bech32Encode (BSC. pack " bc" ) (replicate 82 (word5 (1 :: Word8 )))
151
+ bech32Encode 1 (BSC. pack " bc" ) (replicate 82 (word5 (1 :: Word8 )))
106
152
assertBool " segwit version bounds" $ isNothing $
107
153
segwitEncode (BSC. pack " bc" ) 17 []
108
154
assertBool " segwit prog len version 0" $ isNothing $
@@ -111,13 +157,17 @@ tests = testGroup "Tests"
111
157
segwitEncode (BSC. pack " bc" ) 1 (replicate 30 1 )
112
158
assertBool " segwit prog len version != 0" $ isNothing $
113
159
segwitEncode (BSC. pack " bc" ) 1 (replicate 41 1 )
114
- assertBool " empty HRP encode" $ isError InvalidHumanReadablePart $ bech32Encode (BSC. pack " " ) []
160
+ assertBool " empty HRP encode" $ isError InvalidHumanReadablePart $ bech32Encode 1 (BSC. pack " " ) []
115
161
assertBool " empty HRP decode" $ isError InvalidHRP $ bech32Decode (BSC. pack " 10a06t8" )
116
162
assertEqual " hrp lowercased"
117
163
(Right $ BSC. pack " hrp1g9xj8m" )
118
- (bech32Encode (BSC. pack " HRP" ) [] )
164
+ (bech32Encode 1 (BSC. pack " HRP" ) [] )
119
165
]
120
166
121
167
isError :: Eq a => a -> Either a b -> Bool
122
168
isError e' (Left e) = e == e'
123
169
isError _ _ = False
170
+
171
+ isCorrupted :: Word -> Either x (Word , y , z ) -> Bool
172
+ isCorrupted _ (Left _) = True
173
+ isCorrupted spec (Right (resultSpec, _, _)) = spec /= resultSpec
0 commit comments