From 1c1c1788c000aa75da36a9c756cc561f78cb94a6 Mon Sep 17 00:00:00 2001 From: Matheus Degiovani Date: Thu, 7 Mar 2019 11:29:12 -0300 Subject: [PATCH 01/15] bech32: Initial Version This is a port from the github.com/btcsuite/btcutil/bech32 package. --- bech32/README.md | 29 +++++ bech32/bech32.go | 252 +++++++++++++++++++++++++++++++++++++++++ bech32/bech32_test.go | 70 ++++++++++++ bech32/doc.go | 15 +++ bech32/example_test.go | 50 ++++++++ bech32/go.mod | 1 + 6 files changed, 417 insertions(+) create mode 100644 bech32/README.md create mode 100644 bech32/bech32.go create mode 100644 bech32/bech32_test.go create mode 100644 bech32/doc.go create mode 100644 bech32/example_test.go create mode 100644 bech32/go.mod diff --git a/bech32/README.md b/bech32/README.md new file mode 100644 index 0000000000..04b3c59a51 --- /dev/null +++ b/bech32/README.md @@ -0,0 +1,29 @@ +bech32 +========== + +[![Build Status](http://img.shields.io/travis/decred/dcrd/bech32.svg)](https://travis-ci.org/decred/dcrd/bech32) +[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](http://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/decred/dcrd/bech32) + +Package bech32 provides a Go implementation of the bech32 format specified in +[BIP 173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki). + +Test vectors from BIP 173 are added to ensure compatibility with the BIP. + +## Installation and Updating + +```bash +$ go get -u github.com/decred/dcrd/bech32 +``` + +## Examples + +* [Bech32 decode Example](http://godoc.org/github.com/decred/dcrd/bech32#example-Bech32Decode) + Demonstrates how to decode a bech32 encoded string. +* [Bech32 encode Example](http://godoc.org/github.com/decred/dcrd/bech32#example-BechEncode) + Demonstrates how to encode data into a bech32 string. + +## License + +Package bech32 is licensed under the [copyfree](http://copyfree.org) ISC +License. diff --git a/bech32/bech32.go b/bech32/bech32.go new file mode 100644 index 0000000000..8bda0a1d21 --- /dev/null +++ b/bech32/bech32.go @@ -0,0 +1,252 @@ +// Copyright (c) 2017 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package bech32 + +import ( + "fmt" + "strings" +) + +const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" + +var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3} + +// Decode decodes a bech32 encoded string, returning the human-readable +// part and the data part excluding the checksum. +func Decode(bech string) (string, []byte, error) { + // The maximum allowed length for a bech32 string is 90. It must also + // be at least 8 characters, since it needs a non-empty HRP, a + // separator, and a 6 character checksum. + if len(bech) < 8 || len(bech) > 90 { + return "", nil, fmt.Errorf("invalid bech32 string length %d", + len(bech)) + } + // Only ASCII characters between 33 and 126 are allowed. + for i := 0; i < len(bech); i++ { + if bech[i] < 33 || bech[i] > 126 { + return "", nil, fmt.Errorf("invalid character in "+ + "string: '%c'", bech[i]) + } + } + + // The characters must be either all lowercase or all uppercase. + lower := strings.ToLower(bech) + upper := strings.ToUpper(bech) + if bech != lower && bech != upper { + return "", nil, fmt.Errorf("string not all lowercase or all " + + "uppercase") + } + + // We'll work with the lowercase string from now on. + bech = lower + + // The string is invalid if the last '1' is non-existent, it is the + // first character of the string (no human-readable part) or one of the + // last 6 characters of the string (since checksum cannot contain '1'), + // or if the string is more than 90 characters in total. + one := strings.LastIndexByte(bech, '1') + if one < 1 || one+7 > len(bech) { + return "", nil, fmt.Errorf("invalid index of 1") + } + + // The human-readable part is everything before the last '1'. + hrp := bech[:one] + data := bech[one+1:] + + // Each character corresponds to the byte with value of the index in + // 'charset'. + decoded, err := toBytes(data) + if err != nil { + return "", nil, fmt.Errorf("failed converting data to bytes: "+ + "%v", err) + } + + if !bech32VerifyChecksum(hrp, decoded) { + moreInfo := "" + checksum := bech[len(bech)-6:] + expected, err := toChars(bech32Checksum(hrp, + decoded[:len(decoded)-6])) + if err == nil { + moreInfo = fmt.Sprintf("Expected %v, got %v.", + expected, checksum) + } + return "", nil, fmt.Errorf("checksum failed. " + moreInfo) + } + + // We exclude the last 6 bytes, which is the checksum. + return hrp, decoded[:len(decoded)-6], nil +} + +// Encode encodes a byte slice into a bech32 string with the +// human-readable part hrb. Note that the bytes must each encode 5 bits +// (base32). +func Encode(hrp string, data []byte) (string, error) { + // Calculate the checksum of the data and append it at the end. + checksum := bech32Checksum(hrp, data) + combined := append(data, checksum...) + + // The resulting bech32 string is the concatenation of the hrp, the + // separator 1, data and checksum. Everything after the separator is + // represented using the specified charset. + dataChars, err := toChars(combined) + if err != nil { + return "", fmt.Errorf("unable to convert data bytes to chars: "+ + "%v", err) + } + return hrp + "1" + dataChars, nil +} + +// toBytes converts each character in the string 'chars' to the value of the +// index of the correspoding character in 'charset'. +func toBytes(chars string) ([]byte, error) { + decoded := make([]byte, 0, len(chars)) + for i := 0; i < len(chars); i++ { + index := strings.IndexByte(charset, chars[i]) + if index < 0 { + return nil, fmt.Errorf("invalid character not part of "+ + "charset: %v", chars[i]) + } + decoded = append(decoded, byte(index)) + } + return decoded, nil +} + +// toChars converts the byte slice 'data' to a string where each byte in 'data' +// encodes the index of a character in 'charset'. +func toChars(data []byte) (string, error) { + result := make([]byte, 0, len(data)) + for _, b := range data { + if int(b) >= len(charset) { + return "", fmt.Errorf("invalid data byte: %v", b) + } + result = append(result, charset[b]) + } + return string(result), nil +} + +// ConvertBits converts a byte slice where each byte is encoding fromBits bits, +// to a byte slice where each byte is encoding toBits bits. +func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error) { + if fromBits < 1 || fromBits > 8 || toBits < 1 || toBits > 8 { + return nil, fmt.Errorf("only bit groups between 1 and 8 allowed") + } + + // The final bytes, each byte encoding toBits bits. + var regrouped []byte + + // Keep track of the next byte we create and how many bits we have + // added to it out of the toBits goal. + nextByte := byte(0) + filledBits := uint8(0) + + for _, b := range data { + + // Discard unused bits. + b = b << (8 - fromBits) + + // How many bits remaining to extract from the input data. + remFromBits := fromBits + for remFromBits > 0 { + // How many bits remaining to be added to the next byte. + remToBits := toBits - filledBits + + // The number of bytes to next extract is the minimum of + // remFromBits and remToBits. + toExtract := remFromBits + if remToBits < toExtract { + toExtract = remToBits + } + + // Add the next bits to nextByte, shifting the already + // added bits to the left. + nextByte = (nextByte << toExtract) | (b >> (8 - toExtract)) + + // Discard the bits we just extracted and get ready for + // next iteration. + b = b << toExtract + remFromBits -= toExtract + filledBits += toExtract + + // If the nextByte is completely filled, we add it to + // our regrouped bytes and start on the next byte. + if filledBits == toBits { + regrouped = append(regrouped, nextByte) + filledBits = 0 + nextByte = 0 + } + } + } + + // We pad any unfinished group if specified. + if pad && filledBits > 0 { + nextByte = nextByte << (toBits - filledBits) + regrouped = append(regrouped, nextByte) + filledBits = 0 + nextByte = 0 + } + + // Any incomplete group must be <= 4 bits, and all zeroes. + if filledBits > 0 && (filledBits > 4 || nextByte != 0) { + return nil, fmt.Errorf("invalid incomplete group") + } + + return regrouped, nil +} + +// For more details on the checksum calculation, please refer to BIP 173. +func bech32Checksum(hrp string, data []byte) []byte { + // Convert the bytes to list of integers, as this is needed for the + // checksum calculation. + integers := make([]int, len(data)) + for i, b := range data { + integers[i] = int(b) + } + values := append(bech32HrpExpand(hrp), integers...) + values = append(values, []int{0, 0, 0, 0, 0, 0}...) + polymod := bech32Polymod(values) ^ 1 + var res []byte + for i := 0; i < 6; i++ { + res = append(res, byte((polymod>>uint(5*(5-i)))&31)) + } + return res +} + +// For more details on the polymod calculation, please refer to BIP 173. +func bech32Polymod(values []int) int { + chk := 1 + for _, v := range values { + b := chk >> 25 + chk = (chk&0x1ffffff)<<5 ^ v + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + return chk +} + +// For more details on HRP expansion, please refer to BIP 173. +func bech32HrpExpand(hrp string) []int { + v := make([]int, 0, len(hrp)*2+1) + for i := 0; i < len(hrp); i++ { + v = append(v, int(hrp[i]>>5)) + } + v = append(v, 0) + for i := 0; i < len(hrp); i++ { + v = append(v, int(hrp[i]&31)) + } + return v +} + +// For more details on the checksum verification, please refer to BIP 173. +func bech32VerifyChecksum(hrp string, data []byte) bool { + integers := make([]int, len(data)) + for i, b := range data { + integers[i] = int(b) + } + concat := append(bech32HrpExpand(hrp), integers...) + return bech32Polymod(concat) == 1 +} diff --git a/bech32/bech32_test.go b/bech32/bech32_test.go new file mode 100644 index 0000000000..704744968e --- /dev/null +++ b/bech32/bech32_test.go @@ -0,0 +1,70 @@ +// Copyright (c) 2017 The btcsuite developers +// Copyright (c) 2019 The decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package bech32_test + +import ( + "strings" + "testing" + + "github.com/decred/dcrd/bech32" +) + +func TestBech32(t *testing.T) { + tests := []struct { + str string + valid bool + }{ + {"A12UEL5L", true}, + {"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", true}, + {"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", true}, + {"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", true}, + {"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", true}, + {"split1checkupstagehandshakeupstreamerranterredcaperred2y9e2w", false}, // invalid checksum + {"s lit1checkupstagehandshakeupstreamerranterredcaperredp8hs2p", false}, // invalid character (space) in hrp + {"spl" + string(127) + "t1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", false}, // invalid character (DEL) in hrp + {"split1cheo2y9e2w", false}, // invalid character (o) in data part + {"split1a2y9w", false}, // too short data part + {"1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", false}, // empty hrp + {"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", false}, // too long + } + + for _, test := range tests { + str := test.str + hrp, decoded, err := bech32.Decode(str) + if !test.valid { + // Invalid string decoding should result in error. + if err == nil { + t.Error("expected decoding to fail for "+ + "invalid string %v", test.str) + } + continue + } + + // Valid string decoding should result in no error. + if err != nil { + t.Errorf("expected string to be valid bech32: %v", err) + } + + // Check that it encodes to the same string + encoded, err := bech32.Encode(hrp, decoded) + if err != nil { + t.Errorf("encoding failed: %v", err) + } + + if encoded != strings.ToLower(str) { + t.Errorf("expected data to encode to %v, but got %v", + str, encoded) + } + + // Flip a bit in the string an make sure it is caught. + pos := strings.LastIndexAny(str, "1") + flipped := str[:pos+1] + string((str[pos+1] ^ 1)) + str[pos+2:] + _, _, err = bech32.Decode(flipped) + if err == nil { + t.Error("expected decoding to fail") + } + } +} diff --git a/bech32/doc.go b/bech32/doc.go new file mode 100644 index 0000000000..2d64fbe0bf --- /dev/null +++ b/bech32/doc.go @@ -0,0 +1,15 @@ +// Copyright (c) 2017 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +/* +Package bech32 provides a Go implementation of the bech32 format specified in +BIP 173. + +Bech32 strings consist of a human-readable part (hrp), followed by the +separator 1, then a checksummed data part encoded using the 32 characters +"qpzry9x8gf2tvdw0s3jn54khce6mua7l". + +More info: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki +*/ +package bech32 diff --git a/bech32/example_test.go b/bech32/example_test.go new file mode 100644 index 0000000000..5438914f95 --- /dev/null +++ b/bech32/example_test.go @@ -0,0 +1,50 @@ +// Copyright (c) 2017 The btcsuite developers +// Copyright (c) 2019 The decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package bech32_test + +import ( + "encoding/hex" + "fmt" + + "github.com/decred/dcrd/bech32" +) + +// This example demonstrates how to decode a bech32 encoded string. +func ExampleDecode() { + encoded := "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx" + hrp, decoded, err := bech32.Decode(encoded) + if err != nil { + fmt.Println("Error:", err) + } + + // Show the decoded data. + fmt.Println("Decoded human-readable part:", hrp) + fmt.Println("Decoded Data:", hex.EncodeToString(decoded)) + + // Output: + // Decoded human-readable part: bc + // Decoded Data: 010e140f070d1a001912060b0d081504140311021d030c1d03040f1814060e1e160e140f070d1a001912060b0d081504140311021d030c1d03040f1814060e1e16 +} + +// This example demonstrates how to encode data into a bech32 string. +func ExampleEncode() { + data := []byte("Test data") + // Convert test data to base32: + conv, err := bech32.ConvertBits(data, 8, 5, true) + if err != nil { + fmt.Println("Error:", err) + } + encoded, err := bech32.Encode("customHrp!11111q", conv) + if err != nil { + fmt.Println("Error:", err) + } + + // Show the encoded data. + fmt.Println("Encoded Data:", encoded) + + // Output: + // Encoded Data: customHrp!11111q123jhxapqv3shgcgumastr +} diff --git a/bech32/go.mod b/bech32/go.mod new file mode 100644 index 0000000000..3047702d6e --- /dev/null +++ b/bech32/go.mod @@ -0,0 +1 @@ +module github.com/decred/dcrd/bech32 From d6c2ff7435022cd183141a35260b2a9aca2c98b6 Mon Sep 17 00:00:00 2001 From: Matheus Degiovani Date: Fri, 15 Mar 2019 16:38:28 -0300 Subject: [PATCH 02/15] *squash* Additional tests and extra docs --- bech32/bech32.go | 28 +++++++++++++++++++++++----- bech32/bech32_test.go | 16 ++++++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/bech32/bech32.go b/bech32/bech32.go index 8bda0a1d21..c22036f96d 100644 --- a/bech32/bech32.go +++ b/bech32/bech32.go @@ -13,9 +13,14 @@ const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3} -// Decode decodes a bech32 encoded string, returning the human-readable -// part and the data part excluding the checksum. -func Decode(bech string) (string, []byte, error) { +// DecodeNoLimit decodes a bech32 encoded string, returning the human-readable +// part and the data part excluding the checksum. This function does NOT +// validate against the BIP-173 maximum length allowed for bech32 strings and +// is meant for use in custom applications (such as lightning network payment +// requests), NOT on-chain addresses. +// +// Note that the returned data is 5-bit (base32) encoded. +func DecodeNoLimit(bech string) (string, []byte, error) { // The maximum allowed length for a bech32 string is 90. It must also // be at least 8 characters, since it needs a non-empty HRP, a // separator, and a 6 character checksum. @@ -44,8 +49,7 @@ func Decode(bech string) (string, []byte, error) { // The string is invalid if the last '1' is non-existent, it is the // first character of the string (no human-readable part) or one of the - // last 6 characters of the string (since checksum cannot contain '1'), - // or if the string is more than 90 characters in total. + // last 6 characters of the string (since checksum cannot contain '1'). one := strings.LastIndexByte(bech, '1') if one < 1 || one+7 > len(bech) { return "", nil, fmt.Errorf("invalid index of 1") @@ -79,6 +83,20 @@ func Decode(bech string) (string, []byte, error) { return hrp, decoded[:len(decoded)-6], nil } +// Decode decodes a bech32 encoded string, returning the human-readable part and +// the data part excluding the checksum. +// +// Note that the returned data is 5-bit (base32) encoded. +func Decode(bech string) (string, []byte, error) { + // The maximum allowed length for a bech32 string is 90. + if len(bech) > 90 { + return "", nil, fmt.Errorf("invalid bech32 string length %d", + len(bech)) + } + + return DecodeNoLimit(bech) +} + // Encode encodes a byte slice into a bech32 string with the // human-readable part hrb. Note that the bytes must each encode 5 bits // (base32). diff --git a/bech32/bech32_test.go b/bech32/bech32_test.go index 704744968e..7d13744c83 100644 --- a/bech32/bech32_test.go +++ b/bech32/bech32_test.go @@ -29,6 +29,22 @@ func TestBech32(t *testing.T) { {"split1a2y9w", false}, // too short data part {"1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", false}, // empty hrp {"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", false}, // too long + + // Additional test vectors used in bitcoin core + {" 1nwldj5", false}, + {"\x7f" + "1axkwrx", false}, + {"\x801eym55h", false}, + {"an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx", false}, + {"pzry9x0s0muk", false}, + {"1pzry9x0s0muk", false}, + {"x1b4n0q5v", false}, + {"li1dgmt3", false}, + {"de1lg7wt\xff", false}, + {"A1G7SGD8", false}, + {"10a06t8", false}, + {"1qzzfhee", false}, + {"a12UEL5L", false}, + {"A12uEL5L", false}, } for _, test := range tests { From 70e5702fa12cc7eb593b8b5eb641aef239c9d731 Mon Sep 17 00:00:00 2001 From: Matheus Degiovani Date: Fri, 15 Mar 2019 19:31:53 -0300 Subject: [PATCH 03/15] *squash* Improve errors --- bech32/bech32.go | 41 +++++++--------- bech32/bech32_test.go | 110 +++++++++++++++++++++++++----------------- bech32/error.go | 81 +++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+), 67 deletions(-) create mode 100644 bech32/error.go diff --git a/bech32/bech32.go b/bech32/bech32.go index c22036f96d..347c59f83c 100644 --- a/bech32/bech32.go +++ b/bech32/bech32.go @@ -5,7 +5,6 @@ package bech32 import ( - "fmt" "strings" ) @@ -24,15 +23,13 @@ func DecodeNoLimit(bech string) (string, []byte, error) { // The maximum allowed length for a bech32 string is 90. It must also // be at least 8 characters, since it needs a non-empty HRP, a // separator, and a 6 character checksum. - if len(bech) < 8 || len(bech) > 90 { - return "", nil, fmt.Errorf("invalid bech32 string length %d", - len(bech)) + if len(bech) < 8 { + return "", nil, ErrInvalidLength(len(bech)) } // Only ASCII characters between 33 and 126 are allowed. for i := 0; i < len(bech); i++ { if bech[i] < 33 || bech[i] > 126 { - return "", nil, fmt.Errorf("invalid character in "+ - "string: '%c'", bech[i]) + return "", nil, ErrInvalidCharacter(bech[i]) } } @@ -40,8 +37,7 @@ func DecodeNoLimit(bech string) (string, []byte, error) { lower := strings.ToLower(bech) upper := strings.ToUpper(bech) if bech != lower && bech != upper { - return "", nil, fmt.Errorf("string not all lowercase or all " + - "uppercase") + return "", nil, ErrMixedCase{} } // We'll work with the lowercase string from now on. @@ -52,7 +48,7 @@ func DecodeNoLimit(bech string) (string, []byte, error) { // last 6 characters of the string (since checksum cannot contain '1'). one := strings.LastIndexByte(bech, '1') if one < 1 || one+7 > len(bech) { - return "", nil, fmt.Errorf("invalid index of 1") + return "", nil, ErrInvalidSeparatorIndex(one) } // The human-readable part is everything before the last '1'. @@ -63,20 +59,20 @@ func DecodeNoLimit(bech string) (string, []byte, error) { // 'charset'. decoded, err := toBytes(data) if err != nil { - return "", nil, fmt.Errorf("failed converting data to bytes: "+ - "%v", err) + return "", nil, err } if !bech32VerifyChecksum(hrp, decoded) { - moreInfo := "" checksum := bech[len(bech)-6:] expected, err := toChars(bech32Checksum(hrp, decoded[:len(decoded)-6])) if err == nil { - moreInfo = fmt.Sprintf("Expected %v, got %v.", - expected, checksum) + err = ErrInvalidChecksum{ + Expected: expected, + Actual: checksum, + } } - return "", nil, fmt.Errorf("checksum failed. " + moreInfo) + return "", nil, err } // We exclude the last 6 bytes, which is the checksum. @@ -90,8 +86,7 @@ func DecodeNoLimit(bech string) (string, []byte, error) { func Decode(bech string) (string, []byte, error) { // The maximum allowed length for a bech32 string is 90. if len(bech) > 90 { - return "", nil, fmt.Errorf("invalid bech32 string length %d", - len(bech)) + return "", nil, ErrInvalidLength(len(bech)) } return DecodeNoLimit(bech) @@ -110,8 +105,7 @@ func Encode(hrp string, data []byte) (string, error) { // represented using the specified charset. dataChars, err := toChars(combined) if err != nil { - return "", fmt.Errorf("unable to convert data bytes to chars: "+ - "%v", err) + return "", err } return hrp + "1" + dataChars, nil } @@ -123,8 +117,7 @@ func toBytes(chars string) ([]byte, error) { for i := 0; i < len(chars); i++ { index := strings.IndexByte(charset, chars[i]) if index < 0 { - return nil, fmt.Errorf("invalid character not part of "+ - "charset: %v", chars[i]) + return nil, ErrNonCharsetChar(chars[i]) } decoded = append(decoded, byte(index)) } @@ -137,7 +130,7 @@ func toChars(data []byte) (string, error) { result := make([]byte, 0, len(data)) for _, b := range data { if int(b) >= len(charset) { - return "", fmt.Errorf("invalid data byte: %v", b) + return "", ErrInvalidDataByte(b) } result = append(result, charset[b]) } @@ -148,7 +141,7 @@ func toChars(data []byte) (string, error) { // to a byte slice where each byte is encoding toBits bits. func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error) { if fromBits < 1 || fromBits > 8 || toBits < 1 || toBits > 8 { - return nil, fmt.Errorf("only bit groups between 1 and 8 allowed") + return nil, ErrInvalidBitGroups{} } // The final bytes, each byte encoding toBits bits. @@ -207,7 +200,7 @@ func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error) // Any incomplete group must be <= 4 bits, and all zeroes. if filledBits > 0 && (filledBits > 4 || nextByte != 0) { - return nil, fmt.Errorf("invalid incomplete group") + return nil, ErrInvalidIncompleteGroup{} } return regrouped, nil diff --git a/bech32/bech32_test.go b/bech32/bech32_test.go index 7d13744c83..0fc05574e3 100644 --- a/bech32/bech32_test.go +++ b/bech32/bech32_test.go @@ -3,69 +3,65 @@ // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. -package bech32_test +package bech32 import ( + "fmt" "strings" "testing" - - "github.com/decred/dcrd/bech32" ) func TestBech32(t *testing.T) { tests := []struct { - str string - valid bool + str string + expectedError error }{ - {"A12UEL5L", true}, - {"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", true}, - {"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", true}, - {"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", true}, - {"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", true}, - {"split1checkupstagehandshakeupstreamerranterredcaperred2y9e2w", false}, // invalid checksum - {"s lit1checkupstagehandshakeupstreamerranterredcaperredp8hs2p", false}, // invalid character (space) in hrp - {"spl" + string(127) + "t1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", false}, // invalid character (DEL) in hrp - {"split1cheo2y9e2w", false}, // invalid character (o) in data part - {"split1a2y9w", false}, // too short data part - {"1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", false}, // empty hrp - {"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", false}, // too long + {"A12UEL5L", nil}, + {"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", nil}, + {"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", nil}, + {"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", nil}, + {"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", nil}, + {"split1checkupstagehandshakeupstreamerranterredcaperred2y9e2w", ErrInvalidChecksum{"2y9e3w", "2y9e2w"}}, // invalid checksum + {"s lit1checkupstagehandshakeupstreamerranterredcaperredp8hs2p", ErrInvalidCharacter(' ')}, // invalid character (space) in hrp + {"spl" + string(127) + "t1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", ErrInvalidCharacter(127)}, // invalid character (DEL) in hrp + {"split1cheo2y9e2w", ErrNonCharsetChar('o')}, // invalid character (o) in data part + {"split1a2y9w", ErrInvalidSeparatorIndex(5)}, // too short data part + {"1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", ErrInvalidSeparatorIndex(0)}, // empty hrp + {"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", ErrInvalidLength(91)}, // too long // Additional test vectors used in bitcoin core - {" 1nwldj5", false}, - {"\x7f" + "1axkwrx", false}, - {"\x801eym55h", false}, - {"an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx", false}, - {"pzry9x0s0muk", false}, - {"1pzry9x0s0muk", false}, - {"x1b4n0q5v", false}, - {"li1dgmt3", false}, - {"de1lg7wt\xff", false}, - {"A1G7SGD8", false}, - {"10a06t8", false}, - {"1qzzfhee", false}, - {"a12UEL5L", false}, - {"A12uEL5L", false}, + {" 1nwldj5", ErrInvalidCharacter(' ')}, + {"\x7f" + "1axkwrx", ErrInvalidCharacter(0x7f)}, + {"\x801eym55h", ErrInvalidCharacter(0x80)}, + {"an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx", ErrInvalidLength(91)}, + {"pzry9x0s0muk", ErrInvalidSeparatorIndex(-1)}, + {"1pzry9x0s0muk", ErrInvalidSeparatorIndex(0)}, + {"x1b4n0q5v", ErrNonCharsetChar(98)}, + {"li1dgmt3", ErrInvalidSeparatorIndex(2)}, + {"de1lg7wt\xff", ErrInvalidCharacter(0xff)}, + {"A1G7SGD8", ErrInvalidChecksum{"2uel5l", "g7sgd8"}}, + {"10a06t8", ErrInvalidLength(7)}, + {"1qzzfhee", ErrInvalidSeparatorIndex(0)}, + {"a12UEL5L", ErrMixedCase{}}, + {"A12uEL5L", ErrMixedCase{}}, } - for _, test := range tests { + for i, test := range tests { str := test.str - hrp, decoded, err := bech32.Decode(str) - if !test.valid { - // Invalid string decoding should result in error. - if err == nil { - t.Error("expected decoding to fail for "+ - "invalid string %v", test.str) - } + hrp, decoded, err := Decode(str) + if test.expectedError != err { + t.Errorf("%d: expected decoding error %v "+ + "instead got %v", i, test.expectedError, err) continue } - // Valid string decoding should result in no error. if err != nil { - t.Errorf("expected string to be valid bech32: %v", err) + // End test case here if a decoding error was expected. + continue } // Check that it encodes to the same string - encoded, err := bech32.Encode(hrp, decoded) + encoded, err := Encode(hrp, decoded) if err != nil { t.Errorf("encoding failed: %v", err) } @@ -78,9 +74,37 @@ func TestBech32(t *testing.T) { // Flip a bit in the string an make sure it is caught. pos := strings.LastIndexAny(str, "1") flipped := str[:pos+1] + string((str[pos+1] ^ 1)) + str[pos+2:] - _, _, err = bech32.Decode(flipped) + _, _, err = Decode(flipped) if err == nil { t.Error("expected decoding to fail") } } } + +// TestCanDecodeUnlimtedBech32 tests whether decoding a large bech32 string works +// when using the DecodeNoLimit version +func TestCanDecodeUnlimtedBech32(t *testing.T) { + input := "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq5kx0yd" + + // Sanity check that an input of this length errors on regular Decode() + _, _, err := Decode(input) + if err == nil { + t.Fatalf("Test vector not appropriate") + } + + // Try and decode it. + hrp, data, err := DecodeNoLimit(input) + if err != nil { + t.Fatalf("Expected decoding of large string to work. Got error: %v", err) + } + + // Verify data for correctness. + if hrp != "1" { + t.Fatalf("Unexpected hrp: %v", hrp) + } + decodedHex := fmt.Sprintf("%x", data) + expected := "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000" + if decodedHex != expected { + t.Fatalf("Unexpected decoded data: %s", decodedHex) + } +} diff --git a/bech32/error.go b/bech32/error.go new file mode 100644 index 0000000000..0c660bb059 --- /dev/null +++ b/bech32/error.go @@ -0,0 +1,81 @@ +package bech32 + +import ( + "fmt" +) + +// ErrMixedCase is returned when the bech32 string has both lower and uppercase +// characters. +type ErrMixedCase struct{} + +func (e ErrMixedCase) Error() string { + return "string not all lowercase or all uppercase" +} + +// ErrInvalidBitGroups is returned when conversion is attempted between byte +// slices using bit-per-element of unsupported value. +type ErrInvalidBitGroups struct{} + +func (e ErrInvalidBitGroups) Error() string { + return "only bit groups between 1 and 8 allowed" +} + +// ErrInvalidIncompleteGroup is returned when then byte slice used as input has +// data of wrong length. +type ErrInvalidIncompleteGroup struct{} + +func (e ErrInvalidIncompleteGroup) Error() string { + return "invalid incomplete group" +} + +// ErrInvalidLength is returned when the bech32 string has an invalid length +// given the BIP-173 defined restrictions. +type ErrInvalidLength int + +func (e ErrInvalidLength) Error() string { + return fmt.Sprintf("invalid bech32 string length %d", int(e)) +} + +// ErrInvalidCharacter is returned when the bech32 string has a character +// outside the range of the supported charset. +type ErrInvalidCharacter rune + +func (e ErrInvalidCharacter) Error() string { + return fmt.Sprintf("invalid character in string: '%c'", rune(e)) +} + +// ErrInvalidSeparatorIndex is returned when the separator character '1' is +// in an invalid position in the bech32 string. +type ErrInvalidSeparatorIndex int + +func (e ErrInvalidSeparatorIndex) Error() string { + return fmt.Sprintf("invalid separator index %d", int(e)) +} + +// ErrNonCharsetChar is returned when a character outside of the specific +// bech32 charset is used in the string. +type ErrNonCharsetChar rune + +func (e ErrNonCharsetChar) Error() string { + return fmt.Sprintf("invalid character not part of charset: %v", int(e)) +} + +// ErrInvalidChecksum is returned when the extracted checksum of the string +// is different than what was expected. +type ErrInvalidChecksum struct { + Expected string + Actual string +} + +func (e ErrInvalidChecksum) Error() string { + return fmt.Sprintf("invalid checksum (expected %v got %v)", + e.Expected, e.Actual) +} + +// ErrInvalidDataByte is returned when a byte outside the range required for +// conversion into a string was found. +type ErrInvalidDataByte byte + +func (e ErrInvalidDataByte) Error() string { + return fmt.Sprintf("invalid data byte: %v", byte(e)) +} From 92fe60c4a7223ccda186ecde9545a7de6f506071 Mon Sep 17 00:00:00 2001 From: Matheus Degiovani Date: Mon, 18 Mar 2019 09:58:06 -0300 Subject: [PATCH 04/15] *squash* improve comments --- bech32/bech32.go | 19 +++++++++++++++++++ bech32/bech32_test.go | 5 ++++- bech32/error.go | 4 ++++ bech32/example_test.go | 2 +- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/bech32/bech32.go b/bech32/bech32.go index 347c59f83c..ca6a2cda1a 100644 --- a/bech32/bech32.go +++ b/bech32/bech32.go @@ -1,4 +1,5 @@ // Copyright (c) 2017 The btcsuite developers +// Copyright (c) 2019 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -8,8 +9,12 @@ import ( "strings" ) +// charset is the set of characters used in the data section of bech32 strings. +// Note that this is ordered, such that for a given charset[i], i is the binary +// value of the character. const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" +// gen encodes the generator polynomial for the bech32 BCH checksum. var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3} // DecodeNoLimit decodes a bech32 encoded string, returning the human-readable @@ -206,6 +211,10 @@ func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error) return regrouped, nil } +// bech32Checksum calculates the checksum data expected for a string that will +// have the given hrp and payload data. The payload data MUST be encoded as a +// base 32 (5 bits per element) byte slice. +// // For more details on the checksum calculation, please refer to BIP 173. func bech32Checksum(hrp string, data []byte) []byte { // Convert the bytes to list of integers, as this is needed for the @@ -224,6 +233,9 @@ func bech32Checksum(hrp string, data []byte) []byte { return res } +// bech32Polymod calculates the BCH checksum for a given list of 32 bit encoded +// (5 bits per element) int slice. +// // For more details on the polymod calculation, please refer to BIP 173. func bech32Polymod(values []int) int { chk := 1 @@ -239,6 +251,9 @@ func bech32Polymod(values []int) int { return chk } +// bech32HrpExpand converts the given hrp into an int slice according to the +// rules for bech32 strings, so that it can be used in calculating the checksum. +// // For more details on HRP expansion, please refer to BIP 173. func bech32HrpExpand(hrp string) []int { v := make([]int, 0, len(hrp)*2+1) @@ -252,6 +267,10 @@ func bech32HrpExpand(hrp string) []int { return v } +// bech32VerifyChecksum verifies whether the bech32 string specified by the +// provided hrp and payload data (encoded as 5 bits per element byte slice) has +// the correct checksum suffix. +// // For more details on the checksum verification, please refer to BIP 173. func bech32VerifyChecksum(hrp string, data []byte) bool { integers := make([]int, len(data)) diff --git a/bech32/bech32_test.go b/bech32/bech32_test.go index 0fc05574e3..80a699add9 100644 --- a/bech32/bech32_test.go +++ b/bech32/bech32_test.go @@ -1,5 +1,5 @@ // Copyright (c) 2017 The btcsuite developers -// Copyright (c) 2019 The decred developers +// Copyright (c) 2019 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -11,6 +11,9 @@ import ( "testing" ) +// TestBech32 tests whether decoding and re-encoding the valid BIP-173 test +// vectors works and if decoding invalid test vectors fails for the correct +// reason. func TestBech32(t *testing.T) { tests := []struct { str string diff --git a/bech32/error.go b/bech32/error.go index 0c660bb059..c987b6e115 100644 --- a/bech32/error.go +++ b/bech32/error.go @@ -1,3 +1,7 @@ +// Copyright (c) 2019 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + package bech32 import ( diff --git a/bech32/example_test.go b/bech32/example_test.go index 5438914f95..4d88d6f012 100644 --- a/bech32/example_test.go +++ b/bech32/example_test.go @@ -1,5 +1,5 @@ // Copyright (c) 2017 The btcsuite developers -// Copyright (c) 2019 The decred developers +// Copyright (c) 2019 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. From c515a7514e1f9acfce0f717bdfcef0ddda76f85a Mon Sep 17 00:00:00 2001 From: Matheus Degiovani Date: Mon, 18 Mar 2019 09:58:45 -0300 Subject: [PATCH 05/15] *squash* change hrp in example --- bech32/example_test.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/bech32/example_test.go b/bech32/example_test.go index 4d88d6f012..cfc9bfd66f 100644 --- a/bech32/example_test.go +++ b/bech32/example_test.go @@ -14,19 +14,28 @@ import ( // This example demonstrates how to decode a bech32 encoded string. func ExampleDecode() { - encoded := "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx" + encoded := "dcr1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kf0q4gj" hrp, decoded, err := bech32.Decode(encoded) if err != nil { fmt.Println("Error:", err) } + // Convert the decoded data from 5 bits-per-element into 8-bits-per-element + // payload. + decoded8bits, err := bech32.ConvertBits(decoded, 5, 8, true) + if err != nil { + fmt.Println("Error ConvertBits:", err) + } + // Show the decoded data. fmt.Println("Decoded human-readable part:", hrp) fmt.Println("Decoded Data:", hex.EncodeToString(decoded)) + fmt.Println("Decoded 8bpe Data:", hex.EncodeToString(decoded8bits)) // Output: - // Decoded human-readable part: bc + // Decoded human-readable part: dcr // Decoded Data: 010e140f070d1a001912060b0d081504140311021d030c1d03040f1814060e1e160e140f070d1a001912060b0d081504140311021d030c1d03040f1814060e1e16 + // Decoded 8bpe Data: 0ba8f3b740cc8cb6a2a4a0e22e8d9d191f8a19deb3a8f3b740cc8cb6a2a4a0e22e8d9d191f8a19deb0 } // This example demonstrates how to encode data into a bech32 string. From 9dcbe159bcab963ff230f08fe68b42ceaa7c6310 Mon Sep 17 00:00:00 2001 From: Matheus Degiovani Date: Mon, 18 Mar 2019 10:05:19 -0300 Subject: [PATCH 06/15] *squash* reorder functions --- bech32/bech32.go | 194 +++++++++++++++++++++++------------------------ 1 file changed, 97 insertions(+), 97 deletions(-) diff --git a/bech32/bech32.go b/bech32/bech32.go index ca6a2cda1a..520e4b790c 100644 --- a/bech32/bech32.go +++ b/bech32/bech32.go @@ -17,6 +17,103 @@ const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" // gen encodes the generator polynomial for the bech32 BCH checksum. var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3} +// toBytes converts each character in the string 'chars' to the value of the +// index of the correspoding character in 'charset'. +func toBytes(chars string) ([]byte, error) { + decoded := make([]byte, 0, len(chars)) + for i := 0; i < len(chars); i++ { + index := strings.IndexByte(charset, chars[i]) + if index < 0 { + return nil, ErrNonCharsetChar(chars[i]) + } + decoded = append(decoded, byte(index)) + } + return decoded, nil +} + +// toChars converts the byte slice 'data' to a string where each byte in 'data' +// encodes the index of a character in 'charset'. +func toChars(data []byte) (string, error) { + result := make([]byte, 0, len(data)) + for _, b := range data { + if int(b) >= len(charset) { + return "", ErrInvalidDataByte(b) + } + result = append(result, charset[b]) + } + return string(result), nil +} + +// bech32Polymod calculates the BCH checksum for a given list of 32 bit encoded +// (5 bits per element) int slice. +// +// For more details on the polymod calculation, please refer to BIP 173. +func bech32Polymod(values []int) int { + chk := 1 + for _, v := range values { + b := chk >> 25 + chk = (chk&0x1ffffff)<<5 ^ v + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + return chk +} + +// bech32HrpExpand converts the given hrp into an int slice according to the +// rules for bech32 strings, so that it can be used in calculating the checksum. +// +// For more details on HRP expansion, please refer to BIP 173. +func bech32HrpExpand(hrp string) []int { + v := make([]int, 0, len(hrp)*2+1) + for i := 0; i < len(hrp); i++ { + v = append(v, int(hrp[i]>>5)) + } + v = append(v, 0) + for i := 0; i < len(hrp); i++ { + v = append(v, int(hrp[i]&31)) + } + return v +} + +// bech32Checksum calculates the checksum data expected for a string that will +// have the given hrp and payload data. The payload data MUST be encoded as a +// base 32 (5 bits per element) byte slice. +// +// For more details on the checksum calculation, please refer to BIP 173. +func bech32Checksum(hrp string, data []byte) []byte { + // Convert the bytes to list of integers, as this is needed for the + // checksum calculation. + integers := make([]int, len(data)) + for i, b := range data { + integers[i] = int(b) + } + values := append(bech32HrpExpand(hrp), integers...) + values = append(values, []int{0, 0, 0, 0, 0, 0}...) + polymod := bech32Polymod(values) ^ 1 + var res []byte + for i := 0; i < 6; i++ { + res = append(res, byte((polymod>>uint(5*(5-i)))&31)) + } + return res +} + +// bech32VerifyChecksum verifies whether the bech32 string specified by the +// provided hrp and payload data (encoded as 5 bits per element byte slice) has +// the correct checksum suffix. +// +// For more details on the checksum verification, please refer to BIP 173. +func bech32VerifyChecksum(hrp string, data []byte) bool { + integers := make([]int, len(data)) + for i, b := range data { + integers[i] = int(b) + } + concat := append(bech32HrpExpand(hrp), integers...) + return bech32Polymod(concat) == 1 +} + // DecodeNoLimit decodes a bech32 encoded string, returning the human-readable // part and the data part excluding the checksum. This function does NOT // validate against the BIP-173 maximum length allowed for bech32 strings and @@ -115,33 +212,6 @@ func Encode(hrp string, data []byte) (string, error) { return hrp + "1" + dataChars, nil } -// toBytes converts each character in the string 'chars' to the value of the -// index of the correspoding character in 'charset'. -func toBytes(chars string) ([]byte, error) { - decoded := make([]byte, 0, len(chars)) - for i := 0; i < len(chars); i++ { - index := strings.IndexByte(charset, chars[i]) - if index < 0 { - return nil, ErrNonCharsetChar(chars[i]) - } - decoded = append(decoded, byte(index)) - } - return decoded, nil -} - -// toChars converts the byte slice 'data' to a string where each byte in 'data' -// encodes the index of a character in 'charset'. -func toChars(data []byte) (string, error) { - result := make([]byte, 0, len(data)) - for _, b := range data { - if int(b) >= len(charset) { - return "", ErrInvalidDataByte(b) - } - result = append(result, charset[b]) - } - return string(result), nil -} - // ConvertBits converts a byte slice where each byte is encoding fromBits bits, // to a byte slice where each byte is encoding toBits bits. func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error) { @@ -210,73 +280,3 @@ func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error) return regrouped, nil } - -// bech32Checksum calculates the checksum data expected for a string that will -// have the given hrp and payload data. The payload data MUST be encoded as a -// base 32 (5 bits per element) byte slice. -// -// For more details on the checksum calculation, please refer to BIP 173. -func bech32Checksum(hrp string, data []byte) []byte { - // Convert the bytes to list of integers, as this is needed for the - // checksum calculation. - integers := make([]int, len(data)) - for i, b := range data { - integers[i] = int(b) - } - values := append(bech32HrpExpand(hrp), integers...) - values = append(values, []int{0, 0, 0, 0, 0, 0}...) - polymod := bech32Polymod(values) ^ 1 - var res []byte - for i := 0; i < 6; i++ { - res = append(res, byte((polymod>>uint(5*(5-i)))&31)) - } - return res -} - -// bech32Polymod calculates the BCH checksum for a given list of 32 bit encoded -// (5 bits per element) int slice. -// -// For more details on the polymod calculation, please refer to BIP 173. -func bech32Polymod(values []int) int { - chk := 1 - for _, v := range values { - b := chk >> 25 - chk = (chk&0x1ffffff)<<5 ^ v - for i := 0; i < 5; i++ { - if (b>>uint(i))&1 == 1 { - chk ^= gen[i] - } - } - } - return chk -} - -// bech32HrpExpand converts the given hrp into an int slice according to the -// rules for bech32 strings, so that it can be used in calculating the checksum. -// -// For more details on HRP expansion, please refer to BIP 173. -func bech32HrpExpand(hrp string) []int { - v := make([]int, 0, len(hrp)*2+1) - for i := 0; i < len(hrp); i++ { - v = append(v, int(hrp[i]>>5)) - } - v = append(v, 0) - for i := 0; i < len(hrp); i++ { - v = append(v, int(hrp[i]&31)) - } - return v -} - -// bech32VerifyChecksum verifies whether the bech32 string specified by the -// provided hrp and payload data (encoded as 5 bits per element byte slice) has -// the correct checksum suffix. -// -// For more details on the checksum verification, please refer to BIP 173. -func bech32VerifyChecksum(hrp string, data []byte) bool { - integers := make([]int, len(data)) - for i, b := range data { - integers[i] = int(b) - } - concat := append(bech32HrpExpand(hrp), integers...) - return bech32Polymod(concat) == 1 -} From 470e8c1927f4ceb4e33e8b4220064d474c3f989b Mon Sep 17 00:00:00 2001 From: Matheus Degiovani Date: Mon, 18 Mar 2019 12:49:31 -0300 Subject: [PATCH 07/15] *squash* Add benchmark for encode/decode --- bech32/bech32_test.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/bech32/bech32_test.go b/bech32/bech32_test.go index 80a699add9..a5d19c04cf 100644 --- a/bech32/bech32_test.go +++ b/bech32/bech32_test.go @@ -6,6 +6,7 @@ package bech32 import ( + "encoding/hex" "fmt" "strings" "testing" @@ -111,3 +112,39 @@ func TestCanDecodeUnlimtedBech32(t *testing.T) { t.Fatalf("Unexpected decoded data: %s", decodedHex) } } + +func BenchmarkEncodeDecodeCycle(b *testing.B) { + + // Use a fixed, 49-byte raw data for testing. + inputData, err := hex.DecodeString("cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed1") + if err != nil { + b.Fatalf("failed to initialize input data: %v", err) + } + + // Convert this into a 79-byte, base 32 byte slice. + base32Input, err := ConvertBits(inputData, 8, 5, true) + if err != nil { + b.Fatalf("failed to convert input to 32 bits-per-element: %v", err) + } + + // Use a fixed hrp for the tests. This should generate an encoded bech32 + // string of size 90 (the maximum allowed by BIP-173). + hrp := "dcr" + + // Begin the benchmark. Given that we test one roundtrip per iteration + // (that is, one Encode() and one Decode() operation), we expect at most + // 2 allocations per reported test op. + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + str, err := Encode(hrp, base32Input) + if err != nil { + b.Fatalf("failed to encode input: %v", err) + } + + _, _, err = Decode(str) + if err != nil { + b.Fatalf("failed to decode string: %v", err) + } + } +} From 6392a18037633227bd462eb5b730bf86d33dec1a Mon Sep 17 00:00:00 2001 From: Matheus Degiovani Date: Mon, 18 Mar 2019 13:08:15 -0300 Subject: [PATCH 08/15] *squash* Improve alloc count of bech32 encode/decode --- bech32/bech32.go | 213 ++++++++++++++++++++++++++++++----------------- 1 file changed, 135 insertions(+), 78 deletions(-) diff --git a/bech32/bech32.go b/bech32/bech32.go index 520e4b790c..b530e393be 100644 --- a/bech32/bech32.go +++ b/bech32/bech32.go @@ -31,87 +31,120 @@ func toBytes(chars string) ([]byte, error) { return decoded, nil } -// toChars converts the byte slice 'data' to a string where each byte in 'data' -// encodes the index of a character in 'charset'. -func toChars(data []byte) (string, error) { - result := make([]byte, 0, len(data)) - for _, b := range data { - if int(b) >= len(charset) { - return "", ErrInvalidDataByte(b) - } - result = append(result, charset[b]) - } - return string(result), nil -} - -// bech32Polymod calculates the BCH checksum for a given list of 32 bit encoded -// (5 bits per element) int slice. +// bech32Polymod calculates the BCH checksum for a given hrp, values and +// checksum data. Checksum is optional, and if nil a 0 checksum is assumed. +// +// Values and checksum (if provided) MUST be encoded as 5 bits per element (base +// 32), otherwise the results are undefined. // // For more details on the polymod calculation, please refer to BIP 173. -func bech32Polymod(values []int) int { +func bech32Polymod(hrp string, values, checksum []byte) int { chk := 1 - for _, v := range values { + + // Account for the high bits of the HRP in the checksum. + for i := 0; i < len(hrp); i++ { b := chk >> 25 - chk = (chk&0x1ffffff)<<5 ^ v + hiBits := int(hrp[i]) >> 5 + chk = (chk&0x1ffffff)<<5 ^ hiBits for i := 0; i < 5; i++ { if (b>>uint(i))&1 == 1 { chk ^= gen[i] } } } - return chk -} -// bech32HrpExpand converts the given hrp into an int slice according to the -// rules for bech32 strings, so that it can be used in calculating the checksum. -// -// For more details on HRP expansion, please refer to BIP 173. -func bech32HrpExpand(hrp string) []int { - v := make([]int, 0, len(hrp)*2+1) - for i := 0; i < len(hrp); i++ { - v = append(v, int(hrp[i]>>5)) + // Account for the separator (0) between high and low bits of the HRP. + b := chk >> 25 + chk = (chk&0x1ffffff)<<5 ^ 0 + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } } - v = append(v, 0) + + // Account for the low bits of the HRP. for i := 0; i < len(hrp); i++ { - v = append(v, int(hrp[i]&31)) + b := chk >> 25 + loBits := int(hrp[i]) & 31 + chk = (chk&0x1ffffff)<<5 ^ loBits + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + + // Account for the values. + for _, v := range values { + b := chk >> 25 + chk = (chk&0x1ffffff)<<5 ^ int(v) + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } } - return v + + if checksum == nil { + // A nil checksum is used during encoding, so assume all bytes are zero. + for v := 0; v < 6; v++ { + b := chk >> 25 + chk = (chk&0x1ffffff)<<5 ^ 0 + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + } else { + // Checksum is provided during decoding, so use it. + for _, v := range checksum { + b := chk >> 25 + chk = (chk&0x1ffffff)<<5 ^ int(v) + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + } + + return chk } -// bech32Checksum calculates the checksum data expected for a string that will -// have the given hrp and payload data. The payload data MUST be encoded as a -// base 32 (5 bits per element) byte slice. +// writeBech32Checksum calculates the checksum data expected for a string that +// will have the given hrp and payload data and writes it to the provided string +// builder. +// +// The payload data MUST be encoded as a base 32 (5 bits per element) byte slice +// and the hrp MUST only use the allowed character set (ascii chars between 33 +// and 126), otherwise the results are undefined. // // For more details on the checksum calculation, please refer to BIP 173. -func bech32Checksum(hrp string, data []byte) []byte { - // Convert the bytes to list of integers, as this is needed for the - // checksum calculation. - integers := make([]int, len(data)) - for i, b := range data { - integers[i] = int(b) - } - values := append(bech32HrpExpand(hrp), integers...) - values = append(values, []int{0, 0, 0, 0, 0, 0}...) - polymod := bech32Polymod(values) ^ 1 - var res []byte +func writeBech32Checksum(hrp string, data []byte, bldr *strings.Builder) { + polymod := bech32Polymod(hrp, data, nil) ^ 1 for i := 0; i < 6; i++ { - res = append(res, byte((polymod>>uint(5*(5-i)))&31)) + b := byte((polymod >> uint(5*(5-i))) & 31) + + // This can't fail, given we explicitly cap the previous b byte by the + // first 31 bits. + c := charset[b] + bldr.WriteByte(c) } - return res } // bech32VerifyChecksum verifies whether the bech32 string specified by the // provided hrp and payload data (encoded as 5 bits per element byte slice) has // the correct checksum suffix. // +// Data MUST have more than 6 elements, otherwise this function panics. +// // For more details on the checksum verification, please refer to BIP 173. func bech32VerifyChecksum(hrp string, data []byte) bool { - integers := make([]int, len(data)) - for i, b := range data { - integers[i] = int(b) - } - concat := append(bech32HrpExpand(hrp), integers...) - return bech32Polymod(concat) == 1 + checksum := data[len(data)-6:] + values := data[:len(data)-6] + polymod := bech32Polymod(hrp, values, checksum) + return polymod == 1 } // DecodeNoLimit decodes a bech32 encoded string, returning the human-readable @@ -128,22 +161,28 @@ func DecodeNoLimit(bech string) (string, []byte, error) { if len(bech) < 8 { return "", nil, ErrInvalidLength(len(bech)) } + // Only ASCII characters between 33 and 126 are allowed. + var hasLower, hasUpper bool for i := 0; i < len(bech); i++ { if bech[i] < 33 || bech[i] > 126 { return "", nil, ErrInvalidCharacter(bech[i]) } - } - // The characters must be either all lowercase or all uppercase. - lower := strings.ToLower(bech) - upper := strings.ToUpper(bech) - if bech != lower && bech != upper { - return "", nil, ErrMixedCase{} + // The characters must be either all lowercase or all uppercase. Testing + // directly with ascii codes is safe here, given the previous test. + hasLower = hasLower || (bech[i] >= 97 && bech[i] <= 122) + hasUpper = hasUpper || (bech[i] >= 65 && bech[i] <= 90) + if hasLower && hasUpper { + return "", nil, ErrMixedCase{} + } } - // We'll work with the lowercase string from now on. - bech = lower + // Bech32 standard uses only the lowercase for of strings for checksum + // calculation. + if hasUpper { + bech = strings.ToLower(bech) + } // The string is invalid if the last '1' is non-existent, it is the // first character of the string (no human-readable part) or one of the @@ -164,15 +203,25 @@ func DecodeNoLimit(bech string) (string, []byte, error) { return "", nil, err } + // Verify if the checksum (stored inside decoded[:]) is valid, given the + // previously decoded hrp. if !bech32VerifyChecksum(hrp, decoded) { - checksum := bech[len(bech)-6:] - expected, err := toChars(bech32Checksum(hrp, - decoded[:len(decoded)-6])) - if err == nil { - err = ErrInvalidChecksum{ - Expected: expected, - Actual: checksum, - } + // Invalid checksum. Calculate what it should have been, so that the + // error contains this information. + + // Extract the payload bytes and actual checksum in the string. + actual := bech[len(bech)-6:] + payload := decoded[:len(decoded)-6] + + // Calculate the expected checksum, given the hrp and payload data. + var expectedBldr strings.Builder + expectedBldr.Grow(6) + writeBech32Checksum(hrp, payload, &expectedBldr) + expected := expectedBldr.String() + + err = ErrInvalidChecksum{ + Expected: expected, + Actual: actual, } return "", nil, err } @@ -198,18 +247,26 @@ func Decode(bech string) (string, []byte, error) { // human-readable part hrb. Note that the bytes must each encode 5 bits // (base32). func Encode(hrp string, data []byte) (string, error) { - // Calculate the checksum of the data and append it at the end. - checksum := bech32Checksum(hrp, data) - combined := append(data, checksum...) // The resulting bech32 string is the concatenation of the hrp, the - // separator 1, data and checksum. Everything after the separator is - // represented using the specified charset. - dataChars, err := toChars(combined) - if err != nil { - return "", err + // separator 1, data and the 6-byte checksum. + var bldr strings.Builder + bldr.Grow(len(hrp) + 1 + len(data) + 6) + bldr.WriteString(hrp) + bldr.WriteString("1") + + // Write the data part, using the bech32 charset. + for _, b := range data { + if int(b) >= len(charset) { + return "", ErrInvalidDataByte(b) + } + bldr.WriteByte(charset[b]) } - return hrp + "1" + dataChars, nil + + // Calculate and write the checksum of the data. + writeBech32Checksum(hrp, data, &bldr) + + return bldr.String(), nil } // ConvertBits converts a byte slice where each byte is encoding fromBits bits, From ab1e99f0815c35026c6b0652266d9456ca56bead Mon Sep 17 00:00:00 2001 From: Matheus Degiovani Date: Mon, 18 Mar 2019 13:17:06 -0300 Subject: [PATCH 09/15] *squash* add test doc --- bech32/bech32_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bech32/bech32_test.go b/bech32/bech32_test.go index a5d19c04cf..bf9701af41 100644 --- a/bech32/bech32_test.go +++ b/bech32/bech32_test.go @@ -113,6 +113,9 @@ func TestCanDecodeUnlimtedBech32(t *testing.T) { } } +// BenchmarkEncodeDecodeCycle performs a benchmark for a full encode/decode +// cycle of a bech32 string. It also reports the allocation count, which we +// expect to be 2 for a fully optimized cycle. func BenchmarkEncodeDecodeCycle(b *testing.B) { // Use a fixed, 49-byte raw data for testing. From 211692e5bd2d1723f97932a16bf2de57d0d83803 Mon Sep 17 00:00:00 2001 From: Matheus Degiovani Date: Mon, 18 Mar 2019 18:06:28 -0300 Subject: [PATCH 10/15] *squash* add tests and benchmark for convertBits() --- bech32/bech32_test.go | 162 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/bech32/bech32_test.go b/bech32/bech32_test.go index bf9701af41..24b84f75c7 100644 --- a/bech32/bech32_test.go +++ b/bech32/bech32_test.go @@ -6,6 +6,7 @@ package bech32 import ( + "bytes" "encoding/hex" "fmt" "strings" @@ -151,3 +152,164 @@ func BenchmarkEncodeDecodeCycle(b *testing.B) { } } } + +// TestConvertBits tests whether base conversion works using TestConvertBits(). +func TestConvertBits(t *testing.T) { + tests := []struct { + input string + output string + fromBits uint8 + toBits uint8 + pad bool + }{ + // Trivial empty conversions. + {"", "", 8, 5, false}, + {"", "", 8, 5, true}, + {"", "", 5, 8, false}, + {"", "", 5, 8, true}, + + // Conversions of 0 value with/without padding. + {"00", "00", 8, 5, false}, + {"00", "0000", 8, 5, true}, + {"0000", "00", 5, 8, false}, + {"0000", "0000", 5, 8, true}, + {"0000", "0000", 5, 8, true}, + + // Testing when conversion ends exactly at the byte edge. This makes + // both padded and unpadded versions the same. + {"0000000000", "0000000000000000", 8, 5, false}, + {"0000000000", "0000000000000000", 8, 5, true}, + {"0000000000000000", "0000000000", 5, 8, false}, + {"0000000000000000", "0000000000", 5, 8, true}, + + // Conversions of full byte sequences. + {"ffffff", "1f1f1f1f1e", 8, 5, true}, + {"1f1f1f1f1e", "ffffff", 5, 8, false}, + {"1f1f1f1f1e", "ffffff00", 5, 8, true}, + + // Sample random conversions. + {"c9ca", "190705", 8, 5, false}, + {"c9ca", "19070500", 8, 5, true}, + {"19070500", "c9ca", 5, 8, false}, + {"19070500", "c9ca00", 5, 8, true}, + + // Test cases tested on TestConvertBitsFailures with their corresponding + // fixes. + {"ff", "1f1c", 8, 5, true}, + {"1f1c10", "ff20", 5, 8, true}, + + // Large conversions. + { + "cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed1", + "190f13030c170e1b1916141a13040a14040b011f01040e01071e0607160b1906070e06130801131b1a0416020e110008081c1f1a0e19040703120e1d0a06181b160d0407070c1a07070d11131d1408", + 8, 5, true, + }, + { + "190f13030c170e1b1916141a13040a14040b011f01040e01071e0607160b1906070e06130801131b1a0416020e110008081c1f1a0e19040703120e1d0a06181b160d0407070c1a07070d11131d1408", + "cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed100", + 5, 8, true, + }, + } + + for i, tc := range tests { + input, err := hex.DecodeString(tc.input) + if err != nil { + t.Fatalf("invalid test input data: %v", err) + } + + expected, err := hex.DecodeString(tc.output) + if err != nil { + t.Fatalf("invalid test output data: %v", err) + } + + actual, err := ConvertBits(input, tc.fromBits, tc.toBits, tc.pad) + if err != nil { + t.Fatalf("test case %d failed: %v", i, err) + } + + if !bytes.Equal(actual, expected) { + t.Fatalf("test case %d has wrong output; expected=%x actual=%x", + i, expected, actual) + } + } +} + +// TestConvertBitsFailures tests for the expected conversion failures of +// ConvertBits(). +func TestConvertBitsFailures(t *testing.T) { + tests := []struct { + input string + fromBits uint8 + toBits uint8 + pad bool + err error + }{ + // Not enough output bytes when not using padding. + {"ff", 8, 5, false, ErrInvalidIncompleteGroup{}}, + {"1f1c10", 5, 8, false, ErrInvalidIncompleteGroup{}}, + + // Unsupported bit conversions. + {"", 0, 5, false, ErrInvalidBitGroups{}}, + {"", 10, 5, false, ErrInvalidBitGroups{}}, + {"", 5, 0, false, ErrInvalidBitGroups{}}, + {"", 5, 10, false, ErrInvalidBitGroups{}}, + } + + for i, tc := range tests { + input, err := hex.DecodeString(tc.input) + if err != nil { + t.Fatalf("invalid test input data: %v", err) + } + + _, err = ConvertBits(input, tc.fromBits, tc.toBits, tc.pad) + if err != tc.err { + t.Fatalf("test case %d failure: expected '%v' got '%v'", i, + tc.err, err) + } + } + +} + +// BenchmarkConvertBitsDown benchmarks the speed and memory allocation behavior +// of ConvertBits when converting from a higher base into a lower base (e.g. 8 +// => 5). +// +// Only a single allocation is expected, which is used for the output array. +func BenchmarkConvertBitsDown(b *testing.B) { + // Use a fixed, 49-byte raw data for testing. + inputData, err := hex.DecodeString("cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed1") + if err != nil { + b.Fatalf("failed to initialize input data: %v", err) + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ConvertBits(inputData, 8, 5, true) + if err != nil { + b.Fatalf("error converting bits: %v", err) + } + } +} + +// BenchmarkConvertBitsDown benchmarks the speed and memory allocation behavior +// of ConvertBits when converting from a lower base into a higher base (e.g. 5 +// => 8). +// +// Only a single allocation is expected, which is used for the output array. +func BenchmarkConvertBitsUp(b *testing.B) { + // Use a fixed, 79-byte raw data for testing. + inputData, err := hex.DecodeString("190f13030c170e1b1916141a13040a14040b011f01040e01071e0607160b1906070e06130801131b1a0416020e110008081c1f1a0e19040703120e1d0a06181b160d0407070c1a07070d11131d1408") + if err != nil { + b.Fatalf("failed to initialize input data: %v", err) + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ConvertBits(inputData, 8, 5, true) + if err != nil { + b.Fatalf("error converting bits: %v", err) + } + } +} From 052a9eb05a85f41973e9656c4baf2b9049cf310a Mon Sep 17 00:00:00 2001 From: Matheus Degiovani Date: Mon, 18 Mar 2019 18:07:19 -0300 Subject: [PATCH 11/15] *squash* size output array of convertbits only once --- bech32/bech32.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bech32/bech32.go b/bech32/bech32.go index b530e393be..53ab358dbe 100644 --- a/bech32/bech32.go +++ b/bech32/bech32.go @@ -276,8 +276,15 @@ func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error) return nil, ErrInvalidBitGroups{} } + // Determine the maximum size the resulting array can have after base + // conversion, so that we can size it a single time. This might be off + // by a byte depending on whether padding is used or not and if the input + // data is a multiple of both fromBits and toBits, but we ignore that and + // just size it to the maximum possible. + maxSize := len(data)*int(fromBits)/int(toBits) + 1 + // The final bytes, each byte encoding toBits bits. - var regrouped []byte + regrouped := make([]byte, 0, maxSize) // Keep track of the next byte we create and how many bits we have // added to it out of the toBits goal. From b788760c2c6ec93bf630343bdd013b8ea4f4fece Mon Sep 17 00:00:00 2001 From: Matheus Degiovani Date: Mon, 18 Mar 2019 18:08:07 -0300 Subject: [PATCH 12/15] *squash* fix go.mod --- bech32/go.mod | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bech32/go.mod b/bech32/go.mod index 3047702d6e..063be39d0b 100644 --- a/bech32/go.mod +++ b/bech32/go.mod @@ -1 +1,3 @@ module github.com/decred/dcrd/bech32 + +go 1.11 From 52f3aabc5711c4b1c8ff8448210e969814a236fb Mon Sep 17 00:00:00 2001 From: Matheus Degiovani Date: Mon, 18 Mar 2019 18:22:37 -0300 Subject: [PATCH 13/15] *squash* xor nit --- bech32/bech32.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bech32/bech32.go b/bech32/bech32.go index 53ab358dbe..d40891d028 100644 --- a/bech32/bech32.go +++ b/bech32/bech32.go @@ -87,9 +87,10 @@ func bech32Polymod(hrp string, values, checksum []byte) int { if checksum == nil { // A nil checksum is used during encoding, so assume all bytes are zero. + // x^0 == x, so we eliminate the redundant xor used in the other rounds. for v := 0; v < 6; v++ { b := chk >> 25 - chk = (chk&0x1ffffff)<<5 ^ 0 + chk = (chk & 0x1ffffff) << 5 for i := 0; i < 5; i++ { if (b>>uint(i))&1 == 1 { chk ^= gen[i] From ea8860ed055f9aaa8e2083f377a1534306b80daf Mon Sep 17 00:00:00 2001 From: Matheus Degiovani Date: Mon, 18 Mar 2019 18:30:29 -0300 Subject: [PATCH 14/15] *squash* last review nits --- bech32/bech32.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bech32/bech32.go b/bech32/bech32.go index d40891d028..9a0c28f783 100644 --- a/bech32/bech32.go +++ b/bech32/bech32.go @@ -54,8 +54,9 @@ func bech32Polymod(hrp string, values, checksum []byte) int { } // Account for the separator (0) between high and low bits of the HRP. + // x^0 == x, so we eliminate the redundant xor used in the other rounds. b := chk >> 25 - chk = (chk&0x1ffffff)<<5 ^ 0 + chk = (chk & 0x1ffffff) << 5 for i := 0; i < 5; i++ { if (b>>uint(i))&1 == 1 { chk ^= gen[i] @@ -156,9 +157,8 @@ func bech32VerifyChecksum(hrp string, data []byte) bool { // // Note that the returned data is 5-bit (base32) encoded. func DecodeNoLimit(bech string) (string, []byte, error) { - // The maximum allowed length for a bech32 string is 90. It must also - // be at least 8 characters, since it needs a non-empty HRP, a - // separator, and a 6 character checksum. + // The minimum allowed size of a bech32 string is 8 characters, since it + // needs a non-empty HRP, a separator, and a 6 character checksum. if len(bech) < 8 { return "", nil, ErrInvalidLength(len(bech)) } From afc76a6988266a747049350d1d277721dfecb0f3 Mon Sep 17 00:00:00 2001 From: Matheus Degiovani Date: Tue, 19 Mar 2019 07:23:09 -0300 Subject: [PATCH 15/15] *squash* fix dupe test --- bech32/bech32_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/bech32/bech32_test.go b/bech32/bech32_test.go index 24b84f75c7..46ac7583ab 100644 --- a/bech32/bech32_test.go +++ b/bech32/bech32_test.go @@ -173,7 +173,6 @@ func TestConvertBits(t *testing.T) { {"00", "0000", 8, 5, true}, {"0000", "00", 5, 8, false}, {"0000", "0000", 5, 8, true}, - {"0000", "0000", 5, 8, true}, // Testing when conversion ends exactly at the byte edge. This makes // both padded and unpadded versions the same.