bech32: back port improvements from decred/dcrd@9b88dd0
This commit brings a host of improvements to the bech32 package. The public interface of the package remains unchanged. Summary of changes: * Improved error handling using dedicated error types. Programmatically detect if the errors produced are the expected ones. * Improve test coverage to test more corner cases. Added test vectors from Bitcoin Core. * Add a benchmark for a full encode/decode cycle of a bech32 string. * Add a new function DecodeNoLimit, for decoding large bech32 encoded strings. It does NOT validate against the BIP-173 maximum length allowed for bech32 strings. * Automatically convert the HRP to lowercase in Encode function. * Improve performance of encode/decode functions by using strings.Builder. * Improve memory allocation in ConvertBits function. * Updated documentation. Credits: @matheusd Closes #152 and #168.
This commit is contained in:
parent
4031bdc69d
commit
f281d151bb
3 changed files with 586 additions and 161 deletions
357
bech32/bech32.go
357
bech32/bech32.go
|
@ -1,54 +1,196 @@
|
||||||
// Copyright (c) 2017 The btcsuite developers
|
// Copyright (c) 2017 The btcsuite developers
|
||||||
|
// Copyright (c) 2019 The Decred developers
|
||||||
// Use of this source code is governed by an ISC
|
// Use of this source code is governed by an ISC
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package bech32
|
package bech32
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"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"
|
const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
|
||||||
|
|
||||||
|
// gen encodes the generator polynomial for the bech32 BCH checksum.
|
||||||
var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
|
var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
|
||||||
|
|
||||||
// Decode decodes a bech32 encoded string, returning the human-readable
|
// toBytes converts each character in the string 'chars' to the value of the
|
||||||
// part and the data part excluding the checksum.
|
// index of the correspoding character in 'charset'.
|
||||||
func Decode(bech string) (string, []byte, error) {
|
func toBytes(chars string) ([]byte, error) {
|
||||||
// The maximum allowed length for a bech32 string is 90. It must also
|
decoded := make([]byte, 0, len(chars))
|
||||||
// be at least 8 characters, since it needs a non-empty HRP, a
|
for i := 0; i < len(chars); i++ {
|
||||||
// separator, and a 6 character checksum.
|
index := strings.IndexByte(charset, chars[i])
|
||||||
if len(bech) < 8 || len(bech) > 90 {
|
if index < 0 {
|
||||||
return "", nil, fmt.Errorf("invalid bech32 string length %d",
|
return nil, ErrNonCharsetChar(chars[i])
|
||||||
len(bech))
|
}
|
||||||
|
decoded = append(decoded, byte(index))
|
||||||
}
|
}
|
||||||
// Only ASCII characters between 33 and 126 are allowed.
|
return decoded, nil
|
||||||
for i := 0; i < len(bech); i++ {
|
}
|
||||||
if bech[i] < 33 || bech[i] > 126 {
|
|
||||||
return "", nil, fmt.Errorf("invalid character in "+
|
// bech32Polymod calculates the BCH checksum for a given hrp, values and
|
||||||
"string: '%c'", bech[i])
|
// 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(hrp string, values, checksum []byte) int {
|
||||||
|
chk := 1
|
||||||
|
|
||||||
|
// Account for the high bits of the HRP in the checksum.
|
||||||
|
for i := 0; i < len(hrp); i++ {
|
||||||
|
b := chk >> 25
|
||||||
|
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]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The characters must be either all lowercase or all uppercase.
|
// Account for the separator (0) between high and low bits of the HRP.
|
||||||
lower := strings.ToLower(bech)
|
// x^0 == x, so we eliminate the redundant xor used in the other rounds.
|
||||||
upper := strings.ToUpper(bech)
|
b := chk >> 25
|
||||||
if bech != lower && bech != upper {
|
chk = (chk & 0x1ffffff) << 5
|
||||||
return "", nil, fmt.Errorf("string not all lowercase or all " +
|
for i := 0; i < 5; i++ {
|
||||||
"uppercase")
|
if (b>>uint(i))&1 == 1 {
|
||||||
|
chk ^= gen[i]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll work with the lowercase string from now on.
|
// Account for the low bits of the HRP.
|
||||||
bech = lower
|
for i := 0; i < len(hrp); i++ {
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 writeBech32Checksum(hrp string, data []byte, bldr *strings.Builder) {
|
||||||
|
polymod := bech32Polymod(hrp, data, nil) ^ 1
|
||||||
|
for i := 0; i < 6; i++ {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
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
|
||||||
|
// 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 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))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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. 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{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// 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
|
// first character of the string (no human-readable part) or one of the
|
||||||
// last 6 characters of the string (since checksum cannot contain '1'),
|
// 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')
|
one := strings.LastIndexByte(bech, '1')
|
||||||
if one < 1 || one+7 > len(bech) {
|
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'.
|
// The human-readable part is everything before the last '1'.
|
||||||
|
@ -59,85 +201,94 @@ func Decode(bech string) (string, []byte, error) {
|
||||||
// 'charset'.
|
// 'charset'.
|
||||||
decoded, err := toBytes(data)
|
decoded, err := toBytes(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, fmt.Errorf("failed converting data to bytes: "+
|
return "", nil, err
|
||||||
"%v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify if the checksum (stored inside decoded[:]) is valid, given the
|
||||||
|
// previously decoded hrp.
|
||||||
if !bech32VerifyChecksum(hrp, decoded) {
|
if !bech32VerifyChecksum(hrp, decoded) {
|
||||||
moreInfo := ""
|
// Invalid checksum. Calculate what it should have been, so that the
|
||||||
checksum := bech[len(bech)-6:]
|
// error contains this information.
|
||||||
expected, err := toChars(bech32Checksum(hrp,
|
|
||||||
decoded[:len(decoded)-6]))
|
// Extract the payload bytes and actual checksum in the string.
|
||||||
if err == nil {
|
actual := bech[len(bech)-6:]
|
||||||
moreInfo = fmt.Sprintf("Expected %v, got %v.",
|
payload := decoded[:len(decoded)-6]
|
||||||
expected, checksum)
|
|
||||||
|
// 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, fmt.Errorf("checksum failed. " + moreInfo)
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// We exclude the last 6 bytes, which is the checksum.
|
// We exclude the last 6 bytes, which is the checksum.
|
||||||
return hrp, decoded[:len(decoded)-6], nil
|
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, ErrInvalidLength(len(bech))
|
||||||
|
}
|
||||||
|
|
||||||
|
return DecodeNoLimit(bech)
|
||||||
|
}
|
||||||
|
|
||||||
// Encode encodes a byte slice into a bech32 string with the
|
// Encode encodes a byte slice into a bech32 string with the
|
||||||
// human-readable part hrb. Note that the bytes must each encode 5 bits
|
// human-readable part hrb. Note that the bytes must each encode 5 bits
|
||||||
// (base32).
|
// (base32).
|
||||||
func Encode(hrp string, data []byte) (string, error) {
|
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
|
// The resulting bech32 string is the concatenation of the hrp, the
|
||||||
// separator 1, data and checksum. Everything after the separator is
|
// separator 1, data and the 6-byte checksum.
|
||||||
// represented using the specified charset.
|
var bldr strings.Builder
|
||||||
dataChars, err := toChars(combined)
|
bldr.Grow(len(hrp) + 1 + len(data) + 6)
|
||||||
if err != nil {
|
bldr.WriteString(hrp)
|
||||||
return "", fmt.Errorf("unable to convert data bytes to chars: "+
|
bldr.WriteString("1")
|
||||||
"%v", err)
|
|
||||||
}
|
|
||||||
return hrp + "1" + dataChars, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// toBytes converts each character in the string 'chars' to the value of the
|
// Write the data part, using the bech32 charset.
|
||||||
// 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 {
|
for _, b := range data {
|
||||||
if int(b) >= len(charset) {
|
if int(b) >= len(charset) {
|
||||||
return "", fmt.Errorf("invalid data byte: %v", b)
|
return "", ErrInvalidDataByte(b)
|
||||||
}
|
}
|
||||||
result = append(result, charset[b])
|
bldr.WriteByte(charset[b])
|
||||||
}
|
}
|
||||||
return string(result), 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,
|
// ConvertBits converts a byte slice where each byte is encoding fromBits bits,
|
||||||
// to a byte slice where each byte is encoding toBits bits.
|
// to a byte slice where each byte is encoding toBits bits.
|
||||||
func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error) {
|
func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error) {
|
||||||
if fromBits < 1 || fromBits > 8 || toBits < 1 || toBits > 8 {
|
if fromBits < 1 || fromBits > 8 || toBits < 1 || toBits > 8 {
|
||||||
return nil, fmt.Errorf("only bit groups between 1 and 8 allowed")
|
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.
|
// 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
|
// Keep track of the next byte we create and how many bits we have
|
||||||
// added to it out of the toBits goal.
|
// added to it out of the toBits goal.
|
||||||
nextByte := byte(0)
|
nextByte := byte(0)
|
||||||
filledBits := uint8(0)
|
filledBits := uint8(0)
|
||||||
|
|
||||||
|
@ -170,7 +321,7 @@ func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error)
|
||||||
filledBits += toExtract
|
filledBits += toExtract
|
||||||
|
|
||||||
// If the nextByte is completely filled, we add it to
|
// If the nextByte is completely filled, we add it to
|
||||||
// our regrouped bytes and start on the next byte.
|
// our regrouped bytes and start on the next byte.
|
||||||
if filledBits == toBits {
|
if filledBits == toBits {
|
||||||
regrouped = append(regrouped, nextByte)
|
regrouped = append(regrouped, nextByte)
|
||||||
filledBits = 0
|
filledBits = 0
|
||||||
|
@ -189,64 +340,8 @@ func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error)
|
||||||
|
|
||||||
// Any incomplete group must be <= 4 bits, and all zeroes.
|
// Any incomplete group must be <= 4 bits, and all zeroes.
|
||||||
if filledBits > 0 && (filledBits > 4 || nextByte != 0) {
|
if filledBits > 0 && (filledBits > 4 || nextByte != 0) {
|
||||||
return nil, fmt.Errorf("invalid incomplete group")
|
return nil, ErrInvalidIncompleteGroup{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return regrouped, nil
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,54 +1,72 @@
|
||||||
// Copyright (c) 2017 The btcsuite developers
|
// Copyright (c) 2017-2020 The btcsuite developers
|
||||||
|
// Copyright (c) 2019 The Decred developers
|
||||||
// Use of this source code is governed by an ISC
|
// Use of this source code is governed by an ISC
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package bech32_test
|
package bech32
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/btcsuite/btcutil/bech32"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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) {
|
func TestBech32(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
str string
|
str string
|
||||||
valid bool
|
expectedError error
|
||||||
}{
|
}{
|
||||||
{"A12UEL5L", true},
|
{"A12UEL5L", nil},
|
||||||
{"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", true},
|
{"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", nil},
|
||||||
{"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", true},
|
{"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", nil},
|
||||||
{"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", true},
|
{"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", nil},
|
||||||
{"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", true},
|
{"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", nil},
|
||||||
{"split1checkupstagehandshakeupstreamerranterredcaperred2y9e2w", false}, // invalid checksum
|
{"split1checkupstagehandshakeupstreamerranterredcaperred2y9e2w", ErrInvalidChecksum{"2y9e3w", "2y9e2w"}}, // invalid checksum
|
||||||
{"s lit1checkupstagehandshakeupstreamerranterredcaperredp8hs2p", false}, // invalid character (space) in hrp
|
{"s lit1checkupstagehandshakeupstreamerranterredcaperredp8hs2p", ErrInvalidCharacter(' ')}, // invalid character (space) in hrp
|
||||||
{"spl\x7Ft1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", false}, // invalid character (DEL) in hrp
|
{"spl\x7Ft1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", ErrInvalidCharacter(127)}, // invalid character (DEL) in hrp
|
||||||
{"split1cheo2y9e2w", false}, // invalid character (o) in data part
|
{"split1cheo2y9e2w", ErrNonCharsetChar('o')}, // invalid character (o) in data part
|
||||||
{"split1a2y9w", false}, // too short data part
|
{"split1a2y9w", ErrInvalidSeparatorIndex(5)}, // too short data part
|
||||||
{"1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", false}, // empty hrp
|
{"1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", ErrInvalidSeparatorIndex(0)}, // empty hrp
|
||||||
{"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", false}, // too long
|
{"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", ErrInvalidLength(91)}, // too long
|
||||||
|
|
||||||
|
// Additional test vectors used in bitcoin core
|
||||||
|
{" 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
|
str := test.str
|
||||||
hrp, decoded, err := bech32.Decode(str)
|
hrp, decoded, err := Decode(str)
|
||||||
if !test.valid {
|
if test.expectedError != err {
|
||||||
// Invalid string decoding should result in error.
|
t.Errorf("%d: expected decoding error %v "+
|
||||||
if err == nil {
|
"instead got %v", i, test.expectedError, err)
|
||||||
t.Error("expected decoding to fail for "+
|
|
||||||
"invalid string %v", test.str)
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Valid string decoding should result in no error.
|
|
||||||
if err != nil {
|
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
|
// Check that it encodes to the same string
|
||||||
encoded, err := bech32.Encode(hrp, decoded)
|
encoded, err := Encode(hrp, decoded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("encoding failed: %v", err)
|
t.Errorf("encoding failed: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -61,9 +79,236 @@ func TestBech32(t *testing.T) {
|
||||||
// Flip a bit in the string an make sure it is caught.
|
// Flip a bit in the string an make sure it is caught.
|
||||||
pos := strings.LastIndexAny(str, "1")
|
pos := strings.LastIndexAny(str, "1")
|
||||||
flipped := str[:pos+1] + string((str[pos+1] ^ 1)) + str[pos+2:]
|
flipped := str[:pos+1] + string((str[pos+1] ^ 1)) + str[pos+2:]
|
||||||
_, _, err = bech32.Decode(flipped)
|
_, _, err = Decode(flipped)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("expected decoding to fail")
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
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 := "bc"
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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},
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
85
bech32/error.go
Normal file
85
bech32/error.go
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
// 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 (
|
||||||
|
"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))
|
||||||
|
}
|
Loading…
Reference in a new issue