bech32_test.go raw
1 // Copyright (c) 2017-2020 The btcsuite developers
2 // Copyright (c) 2019 The Decred developers
3 // Use of this source code is governed by an ISC
4 // license that can be found in the LICENSE file.
5
6 package bech32
7
8 import (
9 "bytes"
10 "encoding/hex"
11 "errors"
12 "fmt"
13 "strings"
14 "testing"
15
16 "next.orly.dev/pkg/nostr/utils"
17 )
18
19 // TestBech32 tests whether decoding and re-encoding the valid BIP-173 test
20 // vectors works and if decoding invalid test vectors fails for the correct
21 // reason.
22 func TestBech32(t *testing.T) {
23 tests := []struct {
24 str string
25 expectedError error
26 }{
27 {"A12UEL5L", nil},
28 {"a12uel5l", nil},
29 {
30 "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs",
31 nil,
32 },
33 {"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", nil},
34 {
35 "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j",
36 nil,
37 },
38 {"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", nil},
39 {
40 "split1checkupstagehandshakeupstreamerranterredcaperred2y9e2w",
41 ErrInvalidChecksum{
42 "2y9e3w", "lc445v",
43 "2y9e2w",
44 },
45 }, // invalid checksum
46 {
47 "s lit1checkupstagehandshakeupstreamerranterredcaperredp8hs2p",
48 ErrInvalidCharacter(' '),
49 }, // invalid character (space) in hrp
50 {
51 "spl\x7Ft1checkupstagehandshakeupstreamerranterredcaperred2y9e3w",
52 ErrInvalidCharacter(127),
53 }, // invalid character (DEL) in hrp
54 {
55 "split1cheo2y9e2w",
56 ErrNonCharsetChar('o'),
57 }, // invalid character (o) in data part
58 {"split1a2y9w", ErrInvalidSeparatorIndex(5)}, // too short data part
59 {
60 "1checkupstagehandshakeupstreamerranterredcaperred2y9e3w",
61 ErrInvalidSeparatorIndex(0),
62 }, // empty hrp
63 {
64 "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j",
65 ErrInvalidLength(91),
66 }, // too long
67 // Additional test vectors used in bitcoin core
68 {" 1nwldj5", ErrInvalidCharacter(' ')},
69 {"\x7f" + "1axkwrx", ErrInvalidCharacter(0x7f)},
70 {"\x801eym55h", ErrInvalidCharacter(0x80)},
71 {
72 "an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx",
73 ErrInvalidLength(91),
74 },
75 {"pzry9x0s0muk", ErrInvalidSeparatorIndex(-1)},
76 {"1pzry9x0s0muk", ErrInvalidSeparatorIndex(0)},
77 {"x1b4n0q5v", ErrNonCharsetChar(98)},
78 {"li1dgmt3", ErrInvalidSeparatorIndex(2)},
79 {"de1lg7wt\xff", ErrInvalidCharacter(0xff)},
80 {"A1G7SGD8", ErrInvalidChecksum{"2uel5l", "lqfn3a", "g7sgd8"}},
81 {"10a06t8", ErrInvalidLength(7)},
82 {"1qzzfhee", ErrInvalidSeparatorIndex(0)},
83 {"a12UEL5L", ErrMixedCase{}},
84 {"A12uEL5L", ErrMixedCase{}},
85 }
86 for i, test := range tests {
87 str := []byte(test.str)
88 hrp, decoded, err := Decode([]byte(str))
89 if !errors.Is(err, test.expectedError) {
90 t.Errorf(
91 "%d: expected decoding error %v "+
92 "instead got %v", i, test.expectedError, err,
93 )
94 continue
95 }
96 if err != nil {
97 // End test case here if a decoding error was expected.
98 continue
99 }
100 // Check that it encodes to the same string
101 encoded, err := Encode(hrp, decoded)
102 if err != nil {
103 t.Errorf("encoding failed: %v", err)
104 }
105 if !utils.FastEqual(encoded, bytes.ToLower([]byte(str))) {
106 t.Errorf(
107 "expected data to encode to %v, but got %v",
108 str, encoded,
109 )
110 }
111 // Flip a bit in the string an make sure it is caught.
112 pos := bytes.LastIndexAny(str, "1")
113 flipped := []byte(string(str[:pos+1]) + string(str[pos+1]^1) + string(str[pos+2:]))
114 _, _, err = Decode(flipped)
115 if err == nil {
116 t.Error("expected decoding to fail")
117 }
118 }
119 }
120
121 // TestBech32M tests that the following set of strings, based on the test
122 // vectors in BIP-350 are either valid or invalid using the new bech32m
123 // checksum algo. Some of these strings are similar to the set of above test
124 // vectors, but end up with different checksums.
125 func TestBech32M(t *testing.T) {
126 tests := []struct {
127 str string
128 expectedError error
129 }{
130 {"A1LQFN3A", nil},
131 {"a1lqfn3a", nil},
132 {
133 "an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6",
134 nil,
135 },
136 {"abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx", nil},
137 {
138 "11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8",
139 nil,
140 },
141 {"split1checkupstagehandshakeupstreamerranterredcaperredlc445v", nil},
142 {"?1v759aa", nil},
143 // Additional test vectors used in bitcoin core
144 {"\x201xj0phk", ErrInvalidCharacter('\x20')},
145 {"\x7f1g6xzxy", ErrInvalidCharacter('\x7f')},
146 {"\x801vctc34", ErrInvalidCharacter('\x80')},
147 {
148 "an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4",
149 ErrInvalidLength(91),
150 },
151 {"qyrz8wqd2c9m", ErrInvalidSeparatorIndex(-1)},
152 {"1qyrz8wqd2c9m", ErrInvalidSeparatorIndex(0)},
153 {"y1b0jsk6g", ErrNonCharsetChar(98)},
154 {"lt1igcx5c0", ErrNonCharsetChar(105)},
155 {"in1muywd", ErrInvalidSeparatorIndex(2)},
156 {"mm1crxm3i", ErrNonCharsetChar(105)},
157 {"au1s5cgom", ErrNonCharsetChar(111)},
158 {"M1VUXWEZ", ErrInvalidChecksum{"mzl49c", "w70eq6", "vuxwez"}},
159 {"16plkw9", ErrInvalidLength(7)},
160 {"1p2gdwpf", ErrInvalidSeparatorIndex(0)},
161
162 {" 1nwldj5", ErrInvalidCharacter(' ')},
163 {"\x7f" + "1axkwrx", ErrInvalidCharacter(0x7f)},
164 {"\x801eym55h", ErrInvalidCharacter(0x80)},
165 }
166 for i, test := range tests {
167 str := []byte(test.str)
168 hrp, decoded, err := Decode(str)
169 if test.expectedError != err {
170 t.Errorf(
171 "%d: (%v) expected decoding error %v "+
172 "instead got %v", i, str, test.expectedError,
173 err,
174 )
175 continue
176 }
177 if err != nil {
178 // End test case here if a decoding error was expected.
179 continue
180 }
181 // Check that it encodes to the same string, using bech32 m.
182 encoded, err := EncodeM(hrp, decoded)
183 if err != nil {
184 t.Errorf("encoding failed: %v", err)
185 }
186
187 if !utils.FastEqual(encoded, bytes.ToLower(str)) {
188 t.Errorf(
189 "expected data to encode to %v, but got %v",
190 str, encoded,
191 )
192 }
193 // Flip a bit in the string an make sure it is caught.
194 pos := bytes.LastIndexAny(str, "1")
195 flipped := []byte(string(str[:pos+1]) + string(str[pos+1]^1) + string(str[pos+2:]))
196 _, _, err = Decode(flipped)
197 if err == nil {
198 t.Error("expected decoding to fail")
199 }
200 }
201 }
202
203 // TestBech32DecodeGeneric tests that given a bech32 string, or a bech32m
204 // string, the proper checksum version is returned so that callers can perform
205 // segwit addr validation.
206 func TestBech32DecodeGeneric(t *testing.T) {
207 tests := []struct {
208 str string
209 version Version
210 }{
211 {"A1LQFN3A", VersionM},
212 {"a1lqfn3a", VersionM},
213 {
214 "an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6",
215 VersionM,
216 },
217 {"abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx", VersionM},
218 {
219 "11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8",
220 VersionM,
221 },
222 {
223 "split1checkupstagehandshakeupstreamerranterredcaperredlc445v",
224 VersionM,
225 },
226 {"?1v759aa", VersionM},
227 {"A12UEL5L", Version0},
228 {"a12uel5l", Version0},
229 {
230 "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs",
231 Version0,
232 },
233 {"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", Version0},
234 {
235 "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j",
236 Version0,
237 },
238 {
239 "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w",
240 Version0,
241 },
242 {"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", Version0},
243 {
244 "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
245 Version0,
246 },
247 {
248 "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
249 VersionM,
250 },
251 {"BC1SW50QGDZ25J", VersionM},
252 {"bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs", VersionM},
253 {
254 "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
255 Version0,
256 },
257 {
258 "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c",
259 VersionM,
260 },
261 {
262 "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0",
263 VersionM,
264 },
265 }
266 for i, test := range tests {
267 _, _, version, err := DecodeGeneric([]byte(test.str))
268 if err != nil {
269 t.Errorf(
270 "%d: (%v) unexpected error during "+
271 "decoding: %v", i, test.str, err,
272 )
273 continue
274 }
275 if version != test.version {
276 t.Errorf(
277 "(%v): invalid version: expected %v, got %v",
278 test.str, test.version, version,
279 )
280 }
281 }
282 }
283
284 // TestMixedCaseEncode ensures mixed case HRPs are converted to lowercase as
285 // expected when encoding and that decoding the produced encoding when converted
286 // to all uppercase produces the lowercase HRP and original data.
287 func TestMixedCaseEncode(t *testing.T) {
288 tests := []struct {
289 name string
290 hrp string
291 data string
292 encoded string
293 }{
294 {
295 name: "all uppercase HRP with no data",
296 hrp: "A",
297 data: "",
298 encoded: "a12uel5l",
299 }, {
300 name: "all uppercase HRP with data",
301 hrp: "UPPERCASE",
302 data: "787878",
303 encoded: "uppercase10pu8sss7kmp",
304 }, {
305 name: "mixed case HRP even offsets uppercase",
306 hrp: "AbCdEf",
307 data: "00443214c74254b635cf84653a56d7c675be77df",
308 encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
309 }, {
310 name: "mixed case HRP odd offsets uppercase ",
311 hrp: "aBcDeF",
312 data: "00443214c74254b635cf84653a56d7c675be77df",
313 encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
314 }, {
315 name: "all lowercase HRP",
316 hrp: "abcdef",
317 data: "00443214c74254b635cf84653a56d7c675be77df",
318 encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
319 },
320 }
321 for _, test := range tests {
322 // Convert the text hex to bytes, convert those bytes from base256 to
323 // base32, then ensure the encoded result with the HRP provided in the
324 // test data is as expected.
325 data, err := hex.DecodeString(test.data)
326 if err != nil {
327 t.Errorf("%q: invalid hex %q: %v", test.name, test.data, err)
328 continue
329 }
330 convertedData, err := ConvertBits(data, 8, 5, true)
331 if err != nil {
332 t.Errorf(
333 "%q: unexpected convert bits error: %v", test.name,
334 err,
335 )
336 continue
337 }
338 gotEncoded, err := Encode([]byte(test.hrp), convertedData)
339 if err != nil {
340 t.Errorf("%q: unexpected encode error: %v", test.name, err)
341 continue
342 }
343 if !utils.FastEqual(gotEncoded, []byte(test.encoded)) {
344 t.Errorf(
345 "%q: mismatched encoding -- got %q, want %q", test.name,
346 gotEncoded, test.encoded,
347 )
348 continue
349 }
350 // Ensure the decoding the expected lowercase encoding converted to all
351 // uppercase produces the lowercase HRP and original data.
352 gotHRP, gotData, err := Decode(bytes.ToUpper([]byte(test.encoded)))
353 if err != nil {
354 t.Errorf("%q: unexpected decode error: %v", test.name, err)
355 continue
356 }
357 wantHRP := strings.ToLower(test.hrp)
358 if !utils.FastEqual(gotHRP, []byte(wantHRP)) {
359 t.Errorf(
360 "%q: mismatched decoded HRP -- got %q, want %q", test.name,
361 gotHRP, wantHRP,
362 )
363 continue
364 }
365 convertedGotData, err := ConvertBits(gotData, 5, 8, false)
366 if err != nil {
367 t.Errorf(
368 "%q: unexpected convert bits error: %v", test.name,
369 err,
370 )
371 continue
372 }
373 if !utils.FastEqual(convertedGotData, data) {
374 t.Errorf(
375 "%q: mismatched data -- got %x, want %x", test.name,
376 convertedGotData, data,
377 )
378 continue
379 }
380 }
381 }
382
383 // TestCanDecodeUnlimtedBech32 tests whether decoding a large bech32 string works
384 // when using the DecodeNoLimit version
385 func TestCanDecodeUnlimtedBech32(t *testing.T) {
386 input := "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq5kx0yd"
387 // Sanity check that an input of this length errors on regular Decode()
388 _, _, err := Decode([]byte(input))
389 if err == nil {
390 t.Fatalf("Test vector not appropriate")
391 }
392 // Try and decode it.
393 hrp, data, err := DecodeNoLimit([]byte(input))
394 if err != nil {
395 t.Fatalf(
396 "Expected decoding of large string to work. Got error: %v",
397 err,
398 )
399 }
400 // Verify data for correctness.
401 if !utils.FastEqual(hrp, []byte("1")) {
402 t.Fatalf("Unexpected hrp: %v", hrp)
403 }
404 decodedHex := fmt.Sprintf("%x", data)
405 expected := "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000"
406 if decodedHex != expected {
407 t.Fatalf("Unexpected decoded data: %s", decodedHex)
408 }
409 }
410
411 // TestBech32Base256 ensures decoding and encoding various bech32, HRPs, and
412 // data produces the expected results when using EncodeFromBase256 and
413 // DecodeToBase256. It includes tests for proper handling of case
414 // manipulations.
415 func TestBech32Base256(t *testing.T) {
416 tests := []struct {
417 name string // test name
418 encoded string // bech32 string to decode
419 hrp string // expected human-readable part
420 data string // expected hex-encoded data
421 err error // expected error
422 }{
423 {
424 name: "all uppercase, no data",
425 encoded: "A12UEL5L",
426 hrp: "a",
427 data: "",
428 }, {
429 name: "long hrp with separator and excluded chars, no data",
430 encoded: "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs",
431 hrp: "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio",
432 data: "",
433 }, {
434 name: "6 char hrp with data with leading zero",
435 encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
436 hrp: "abcdef",
437 data: "00443214c74254b635cf84653a56d7c675be77df",
438 }, {
439 name: "hrp same as separator and max length encoded string",
440 encoded: "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j",
441 hrp: "1",
442 data: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
443 }, {
444 name: "5 char hrp with data chosen to produce human-readable data part",
445 encoded: "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w",
446 hrp: "split",
447 data: "c5f38b70305f519bf66d85fb6cf03058f3dde463ecd7918f2dc743918f2d",
448 }, {
449 name: "same as previous but with checksum invalidated",
450 encoded: "split1checkupstagehandshakeupstreamerranterredcaperred2y9e2w",
451 err: ErrInvalidChecksum{"2y9e3w", "lc445v", "2y9e2w"},
452 }, {
453 name: "hrp with invalid character (space)",
454 encoded: "s lit1checkupstagehandshakeupstreamerranterredcaperredp8hs2p",
455 err: ErrInvalidCharacter(' '),
456 }, {
457 name: "hrp with invalid character (DEL)",
458 encoded: "spl\x7ft1checkupstagehandshakeupstreamerranterredcaperred2y9e3w",
459 err: ErrInvalidCharacter(127),
460 }, {
461 name: "data part with invalid character (o)",
462 encoded: "split1cheo2y9e2w",
463 err: ErrNonCharsetChar('o'),
464 }, {
465 name: "data part too short",
466 encoded: "split1a2y9w",
467 err: ErrInvalidSeparatorIndex(5),
468 }, {
469 name: "empty hrp",
470 encoded: "1checkupstagehandshakeupstreamerranterredcaperred2y9e3w",
471 err: ErrInvalidSeparatorIndex(0),
472 }, {
473 name: "no separator",
474 encoded: "pzry9x0s0muk",
475 err: ErrInvalidSeparatorIndex(-1),
476 }, {
477 name: "too long by one char",
478 encoded: "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j",
479 err: ErrInvalidLength(91),
480 }, {
481 name: "invalid due to mixed case in hrp",
482 encoded: "aBcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
483 err: ErrMixedCase{},
484 }, {
485 name: "invalid due to mixed case in data part",
486 encoded: "abcdef1Qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
487 err: ErrMixedCase{},
488 },
489 }
490 for _, test := range tests {
491 // Ensure the decode either produces an error or not as expected.
492 str := test.encoded
493 gotHRP, gotData, err := DecodeToBase256([]byte(str))
494 if test.err != err {
495 t.Errorf(
496 "%q: unexpected decode error -- got %v, want %v",
497 test.name, err, test.err,
498 )
499 continue
500 }
501 if err != nil {
502 // End test case here if a decoding error was expected.
503 continue
504 }
505 // Ensure the expected HRP and original data are as expected.
506 if !utils.FastEqual(gotHRP, []byte(test.hrp)) {
507 t.Errorf(
508 "%q: mismatched decoded HRP -- got %q, want %q", test.name,
509 gotHRP, test.hrp,
510 )
511 continue
512 }
513 data, err := hex.DecodeString(test.data)
514 if err != nil {
515 t.Errorf("%q: invalid hex %q: %v", test.name, test.data, err)
516 continue
517 }
518 if !utils.FastEqual(gotData, data) {
519 t.Errorf(
520 "%q: mismatched data -- got %x, want %x", test.name,
521 gotData, data,
522 )
523 continue
524 }
525 // Encode the same data with the HRP converted to all uppercase and
526 // ensure the result is the lowercase version of the original encoded
527 // bech32 string.
528 gotEncoded, err := EncodeFromBase256(
529 bytes.ToUpper([]byte(test.hrp)), data,
530 )
531 if err != nil {
532 t.Errorf(
533 "%q: unexpected uppercase HRP encode error: %v", test.name,
534 err,
535 )
536 }
537 wantEncoded := bytes.ToLower([]byte(str))
538 if !utils.FastEqual(gotEncoded, wantEncoded) {
539 t.Errorf(
540 "%q: mismatched encoding -- got %q, want %q", test.name,
541 gotEncoded, wantEncoded,
542 )
543 }
544 // Encode the same data with the HRP converted to all lowercase and
545 // ensure the result is the lowercase version of the original encoded
546 // bech32 string.
547 gotEncoded, err = EncodeFromBase256(
548 bytes.ToLower([]byte(test.hrp)), data,
549 )
550 if err != nil {
551 t.Errorf(
552 "%q: unexpected lowercase HRP encode error: %v", test.name,
553 err,
554 )
555 }
556 if !utils.FastEqual(gotEncoded, wantEncoded) {
557 t.Errorf(
558 "%q: mismatched encoding -- got %q, want %q", test.name,
559 gotEncoded, wantEncoded,
560 )
561 }
562 // Encode the same data with the HRP converted to mixed upper and
563 // lowercase and ensure the result is the lowercase version of the
564 // original encoded bech32 string.
565 var mixedHRPBuilder bytes.Buffer
566 for i, r := range test.hrp {
567 if i%2 == 0 {
568 mixedHRPBuilder.WriteString(strings.ToUpper(string(r)))
569 continue
570 }
571 mixedHRPBuilder.WriteRune(r)
572 }
573 gotEncoded, err = EncodeFromBase256(mixedHRPBuilder.Bytes(), data)
574 if err != nil {
575 t.Errorf(
576 "%q: unexpected lowercase HRP encode error: %v", test.name,
577 err,
578 )
579 }
580 if !utils.FastEqual(gotEncoded, wantEncoded) {
581 t.Errorf(
582 "%q: mismatched encoding -- got %q, want %q", test.name,
583 gotEncoded, wantEncoded,
584 )
585 }
586 // Ensure a bit flip in the string is caught.
587 pos := strings.LastIndexAny(test.encoded, "1")
588 flipped := str[:pos+1] + string(str[pos+1]^1) + str[pos+2:]
589 _, _, err = DecodeToBase256([]byte(flipped))
590 if err == nil {
591 t.Error("expected decoding to fail")
592 }
593 }
594 }
595
596 // BenchmarkEncodeDecodeCycle performs a benchmark for a full encode/decode
597 // cycle of a bech32 string. It also reports the allocation count, which we
598 // expect to be 2 for a fully optimized cycle.
599 func BenchmarkEncodeDecodeCycle(b *testing.B) {
600 // Use a fixed, 49-byte raw data for testing.
601 inputData, err := hex.DecodeString("cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed1")
602 if err != nil {
603 b.Fatalf("failed to initialize input data: %v", err)
604 }
605 // Convert this into a 79-byte, base 32 byte slice.
606 base32Input, err := ConvertBits(inputData, 8, 5, true)
607 if err != nil {
608 b.Fatalf("failed to convert input to 32 bits-per-element: %v", err)
609 }
610 // Use a fixed hrp for the tests. This should generate an encoded bech32
611 // string of size 90 (the maximum allowed by BIP-173).
612 hrp := "bc"
613 // Begin the benchmark. Given that we test one roundtrip per iteration
614 // (that is, one Encode() and one Decode() operation), we expect at most
615 // 2 allocations per reported test op.
616 b.ReportAllocs()
617 b.ResetTimer()
618 for i := 0; i < b.N; i++ {
619 str, err := Encode([]byte(hrp), base32Input)
620 if err != nil {
621 b.Fatalf("failed to encode input: %v", err)
622 }
623 _, _, err = Decode(str)
624 if err != nil {
625 b.Fatalf("failed to decode string: %v", err)
626 }
627 }
628 }
629
630 // TestConvertBits tests whether base conversion works using TestConvertBits().
631 func TestConvertBits(t *testing.T) {
632 tests := []struct {
633 input string
634 output string
635 fromBits uint8
636 toBits uint8
637 pad bool
638 }{
639 // Trivial empty conversions.
640 {"", "", 8, 5, false},
641 {"", "", 8, 5, true},
642 {"", "", 5, 8, false},
643 {"", "", 5, 8, true},
644 // Conversions of 0 value with/without padding.
645 {"00", "00", 8, 5, false},
646 {"00", "0000", 8, 5, true},
647 {"0000", "00", 5, 8, false},
648 {"0000", "0000", 5, 8, true},
649 // Testing when conversion ends exactly at the byte edge. This makes
650 // both padded and unpadded versions the same.
651 {"0000000000", "0000000000000000", 8, 5, false},
652 {"0000000000", "0000000000000000", 8, 5, true},
653 {"0000000000000000", "0000000000", 5, 8, false},
654 {"0000000000000000", "0000000000", 5, 8, true},
655 // Conversions of full byte sequences.
656 {"ffffff", "1f1f1f1f1e", 8, 5, true},
657 {"1f1f1f1f1e", "ffffff", 5, 8, false},
658 {"1f1f1f1f1e", "ffffff00", 5, 8, true},
659 // Sample random conversions.
660 {"c9ca", "190705", 8, 5, false},
661 {"c9ca", "19070500", 8, 5, true},
662 {"19070500", "c9ca", 5, 8, false},
663 {"19070500", "c9ca00", 5, 8, true},
664 // Test cases tested on TestConvertBitsFailures with their corresponding
665 // fixes.
666 {"ff", "1f1c", 8, 5, true},
667 {"1f1c10", "ff20", 5, 8, true},
668 // Large conversions.
669 {
670 "cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed1",
671 "190f13030c170e1b1916141a13040a14040b011f01040e01071e0607160b1906070e06130801131b1a0416020e110008081c1f1a0e19040703120e1d0a06181b160d0407070c1a07070d11131d1408",
672 8, 5, true,
673 },
674 {
675 "190f13030c170e1b1916141a13040a14040b011f01040e01071e0607160b1906070e06130801131b1a0416020e110008081c1f1a0e19040703120e1d0a06181b160d0407070c1a07070d11131d1408",
676 "cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed100",
677 5, 8, true,
678 },
679 }
680 for i, tc := range tests {
681 input, err := hex.DecodeString(tc.input)
682 if err != nil {
683 t.Fatalf("invalid test input data: %v", err)
684 }
685 expected, err := hex.DecodeString(tc.output)
686 if err != nil {
687 t.Fatalf("invalid test output data: %v", err)
688 }
689 actual, err := ConvertBits(input, tc.fromBits, tc.toBits, tc.pad)
690 if err != nil {
691 t.Fatalf("test case %d failed: %v", i, err)
692 }
693 if !utils.FastEqual(actual, expected) {
694 t.Fatalf(
695 "test case %d has wrong output; expected=%x actual=%x",
696 i, expected, actual,
697 )
698 }
699 }
700 }
701
702 // TestConvertBitsFailures tests for the expected conversion failures of
703 // ConvertBits().
704 func TestConvertBitsFailures(t *testing.T) {
705 tests := []struct {
706 input string
707 fromBits uint8
708 toBits uint8
709 pad bool
710 err error
711 }{
712 // Not enough output bytes when not using padding.
713 {"ff", 8, 5, false, ErrInvalidIncompleteGroup{}},
714 {"1f1c10", 5, 8, false, ErrInvalidIncompleteGroup{}},
715 // Unsupported bit conversions.
716 {"", 0, 5, false, ErrInvalidBitGroups{}},
717 {"", 10, 5, false, ErrInvalidBitGroups{}},
718 {"", 5, 0, false, ErrInvalidBitGroups{}},
719 {"", 5, 10, false, ErrInvalidBitGroups{}},
720 }
721 for i, tc := range tests {
722 input, err := hex.DecodeString(tc.input)
723 if err != nil {
724 t.Fatalf("invalid test input data: %v", err)
725 }
726 _, err = ConvertBits(input, tc.fromBits, tc.toBits, tc.pad)
727 if err != tc.err {
728 t.Fatalf(
729 "test case %d failure: expected '%v' got '%v'", i,
730 tc.err, err,
731 )
732 }
733 }
734 }
735
736 // BenchmarkConvertBitsDown benchmarks the speed and memory allocation behavior
737 // of ConvertBits when converting from a higher base into a lower base (e.g. 8
738 // => 5).
739 //
740 // Only a single allocation is expected, which is used for the output array.
741 func BenchmarkConvertBitsDown(b *testing.B) {
742 // Use a fixed, 49-byte raw data for testing.
743 inputData, err := hex.DecodeString("cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed1")
744 if err != nil {
745 b.Fatalf("failed to initialize input data: %v", err)
746 }
747 b.ReportAllocs()
748 b.ResetTimer()
749 for i := 0; i < b.N; i++ {
750 _, err := ConvertBits(inputData, 8, 5, true)
751 if err != nil {
752 b.Fatalf("error converting bits: %v", err)
753 }
754 }
755 }
756
757 // BenchmarkConvertBitsDown benchmarks the speed and memory allocation behavior
758 // of ConvertBits when converting from a lower base into a higher base (e.g. 5
759 // => 8).
760 //
761 // Only a single allocation is expected, which is used for the output array.
762 func BenchmarkConvertBitsUp(b *testing.B) {
763 // Use a fixed, 79-byte raw data for testing.
764 inputData, err := hex.DecodeString("190f13030c170e1b1916141a13040a14040b011f01040e01071e0607160b1906070e06130801131b1a0416020e110008081c1f1a0e19040703120e1d0a06181b160d0407070c1a07070d11131d1408")
765 if err != nil {
766 b.Fatalf("failed to initialize input data: %v", err)
767 }
768 b.ReportAllocs()
769 b.ResetTimer()
770 for i := 0; i < b.N; i++ {
771 _, err := ConvertBits(inputData, 8, 5, true)
772 if err != nil {
773 b.Fatalf("error converting bits: %v", err)
774 }
775 }
776 }
777