bech32: Ensure HRP is lowercase when encoding.
BIP173 specifically calls out that encoders must always output an all lowercase bech32 string and that the lowercase form is used when determining a character's value for calculating the checksum. Currently, the implementation does not respect either of those requirements. This modifies the Encode function to convert the provided HRP to lowercase to ensure the requirements are satisfied and adds tests accordingly.
This commit is contained in:
parent
f281d151bb
commit
36377a3c8c
3 changed files with 102 additions and 11 deletions
|
@ -150,12 +150,13 @@ func bech32VerifyChecksum(hrp string, data []byte) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeNoLimit decodes a bech32 encoded string, returning the human-readable
|
// DecodeNoLimit decodes a bech32 encoded string, returning the human-readable
|
||||||
// part and the data part excluding the checksum. This function does NOT
|
// part and the data part excluding the checksum. This function does NOT
|
||||||
// validate against the BIP-173 maximum length allowed for bech32 strings and
|
// validate against the BIP-173 maximum length allowed for bech32 strings and
|
||||||
// is meant for use in custom applications (such as lightning network payment
|
// is meant for use in custom applications (such as lightning network payment
|
||||||
// requests), NOT on-chain addresses.
|
// requests), NOT on-chain addresses.
|
||||||
//
|
//
|
||||||
// Note that the returned data is 5-bit (base32) encoded.
|
// Note that the returned data is 5-bit (base32) encoded and the human-readable
|
||||||
|
// part will be lowercase.
|
||||||
func DecodeNoLimit(bech string) (string, []byte, error) {
|
func DecodeNoLimit(bech string) (string, []byte, error) {
|
||||||
// The minimum allowed size of a bech32 string is 8 characters, since it
|
// 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.
|
// needs a non-empty HRP, a separator, and a 6 character checksum.
|
||||||
|
@ -234,7 +235,8 @@ func DecodeNoLimit(bech string) (string, []byte, error) {
|
||||||
// Decode decodes a bech32 encoded string, returning the human-readable part and
|
// Decode decodes a bech32 encoded string, returning the human-readable part and
|
||||||
// the data part excluding the checksum.
|
// the data part excluding the checksum.
|
||||||
//
|
//
|
||||||
// Note that the returned data is 5-bit (base32) encoded.
|
// Note that the returned data is 5-bit (base32) encoded and the human-readable
|
||||||
|
// part will be lowercase.
|
||||||
func Decode(bech string) (string, []byte, error) {
|
func Decode(bech string) (string, []byte, error) {
|
||||||
// The maximum allowed length for a bech32 string is 90.
|
// The maximum allowed length for a bech32 string is 90.
|
||||||
if len(bech) > 90 {
|
if len(bech) > 90 {
|
||||||
|
@ -244,13 +246,14 @@ func Decode(bech string) (string, []byte, error) {
|
||||||
return DecodeNoLimit(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 given
|
||||||
// human-readable part hrb. Note that the bytes must each encode 5 bits
|
// human-readable part (HRP). The HRP will be converted to lowercase if needed
|
||||||
// (base32).
|
// since mixed cased encodings are not permitted and lowercase is used for
|
||||||
|
// checksum purposes. Note that the bytes must each encode 5 bits (base32).
|
||||||
func Encode(hrp string, data []byte) (string, error) {
|
func Encode(hrp string, data []byte) (string, error) {
|
||||||
|
// The resulting bech32 string is the concatenation of the lowercase hrp,
|
||||||
// The resulting bech32 string is the concatenation of the hrp, the
|
// the separator 1, data and the 6-byte checksum.
|
||||||
// separator 1, data and the 6-byte checksum.
|
hrp = strings.ToLower(hrp)
|
||||||
var bldr strings.Builder
|
var bldr strings.Builder
|
||||||
bldr.Grow(len(hrp) + 1 + len(data) + 6)
|
bldr.Grow(len(hrp) + 1 + len(data) + 6)
|
||||||
bldr.WriteString(hrp)
|
bldr.WriteString(hrp)
|
||||||
|
|
|
@ -86,6 +86,95 @@ func TestBech32(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestMixedCaseEncode ensures mixed case HRPs are converted to lowercase as
|
||||||
|
// expected when encoding and that decoding the produced encoding when converted
|
||||||
|
// to all uppercase produces the lowercase HRP and original data.
|
||||||
|
func TestMixedCaseEncode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
hrp string
|
||||||
|
data string
|
||||||
|
encoded string
|
||||||
|
}{{
|
||||||
|
name: "all uppercase HRP with no data",
|
||||||
|
hrp: "A",
|
||||||
|
data: "",
|
||||||
|
encoded: "a12uel5l",
|
||||||
|
}, {
|
||||||
|
name: "all uppercase HRP with data",
|
||||||
|
hrp: "UPPERCASE",
|
||||||
|
data: "787878",
|
||||||
|
encoded: "uppercase10pu8sss7kmp",
|
||||||
|
}, {
|
||||||
|
name: "mixed case HRP even offsets uppercase",
|
||||||
|
hrp: "AbCdEf",
|
||||||
|
data: "00443214c74254b635cf84653a56d7c675be77df",
|
||||||
|
encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
|
||||||
|
}, {
|
||||||
|
name: "mixed case HRP odd offsets uppercase ",
|
||||||
|
hrp: "aBcDeF",
|
||||||
|
data: "00443214c74254b635cf84653a56d7c675be77df",
|
||||||
|
encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
|
||||||
|
}, {
|
||||||
|
name: "all lowercase HRP",
|
||||||
|
hrp: "abcdef",
|
||||||
|
data: "00443214c74254b635cf84653a56d7c675be77df",
|
||||||
|
encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
// Convert the text hex to bytes, convert those bytes from base256 to
|
||||||
|
// base32, then ensure the encoded result with the HRP provided in the
|
||||||
|
// test data is as expected.
|
||||||
|
data, err := hex.DecodeString(test.data)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%q: invalid hex %q: %v", test.name, test.data, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
convertedData, err := ConvertBits(data, 8, 5, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%q: unexpected convert bits error: %v", test.name,
|
||||||
|
err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gotEncoded, err := Encode(test.hrp, convertedData)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%q: unexpected encode error: %v", test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if gotEncoded != test.encoded {
|
||||||
|
t.Errorf("%q: mismatched encoding -- got %q, want %q", test.name,
|
||||||
|
gotEncoded, test.encoded)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the decoding the expected lowercase encoding converted to all
|
||||||
|
// uppercase produces the lowercase HRP and original data.
|
||||||
|
gotHRP, gotData, err := Decode(strings.ToUpper(test.encoded))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%q: unexpected decode error: %v", test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wantHRP := strings.ToLower(test.hrp)
|
||||||
|
if gotHRP != wantHRP {
|
||||||
|
t.Errorf("%q: mismatched decoded HRP -- got %q, want %q", test.name,
|
||||||
|
gotHRP, wantHRP)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
convertedGotData, err := ConvertBits(gotData, 5, 8, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%q: unexpected convert bits error: %v", test.name,
|
||||||
|
err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(convertedGotData, data) {
|
||||||
|
t.Errorf("%q: mismatched data -- got %x, want %x", test.name,
|
||||||
|
convertedGotData, data)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestCanDecodeUnlimtedBech32 tests whether decoding a large bech32 string works
|
// TestCanDecodeUnlimtedBech32 tests whether decoding a large bech32 string works
|
||||||
// when using the DecodeNoLimit version
|
// when using the DecodeNoLimit version
|
||||||
func TestCanDecodeUnlimtedBech32(t *testing.T) {
|
func TestCanDecodeUnlimtedBech32(t *testing.T) {
|
||||||
|
@ -118,7 +207,6 @@ func TestCanDecodeUnlimtedBech32(t *testing.T) {
|
||||||
// cycle of a bech32 string. It also reports the allocation count, which we
|
// cycle of a bech32 string. It also reports the allocation count, which we
|
||||||
// expect to be 2 for a fully optimized cycle.
|
// expect to be 2 for a fully optimized cycle.
|
||||||
func BenchmarkEncodeDecodeCycle(b *testing.B) {
|
func BenchmarkEncodeDecodeCycle(b *testing.B) {
|
||||||
|
|
||||||
// Use a fixed, 49-byte raw data for testing.
|
// Use a fixed, 49-byte raw data for testing.
|
||||||
inputData, err := hex.DecodeString("cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed1")
|
inputData, err := hex.DecodeString("cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed1")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -45,5 +45,5 @@ func ExampleEncode() {
|
||||||
fmt.Println("Encoded Data:", encoded)
|
fmt.Println("Encoded Data:", encoded)
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// Encoded Data: customHrp!11111q123jhxapqv3shgcgumastr
|
// Encoded Data: customhrp!11111q123jhxapqv3shgcgkxpuhe
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue