-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
perf: Fork bech32 from btcsuite/btcutil (#10082)
- Loading branch information
Showing
5 changed files
with
1,067 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,380 @@ | ||
// 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. | ||
|
||
// source: https://github.com/btcsuite/btcutil/blob/e2ba6805/bech32/bech32.go | ||
|
||
package bech32 | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/enigmampc/btcutil/bech32" | ||
"strings" | ||
) | ||
|
||
// ConvertAndEncode converts from a base64 encoded byte string to base32 encoded byte string and then to bech32. | ||
func ConvertAndEncode(hrp string, data []byte) (string, error) { | ||
converted, err := bech32.ConvertBits(data, 8, 5, true) | ||
if err != nil { | ||
return "", fmt.Errorf("encoding bech32 failed: %w", err) | ||
// 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} | ||
|
||
// 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 | ||
} | ||
|
||
// 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(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] | ||
} | ||
} | ||
} | ||
|
||
// 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 | ||
for i := 0; i < 5; i++ { | ||
if (b>>uint(i))&1 == 1 { | ||
chk ^= gen[i] | ||
} | ||
} | ||
|
||
// Account for the low bits of the HRP. | ||
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) | ||
} | ||
} | ||
|
||
return bech32.Encode(hrp, converted) | ||
// 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 | ||
} | ||
|
||
// DecodeAndConvert decodes a bech32 encoded string and converts to base64 encoded bytes. | ||
func DecodeAndConvert(bech string) (string, []byte, error) { | ||
hrp, data, err := bech32.Decode(bech, 1023) | ||
// 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 and the human-readable | ||
// part will be lowercase. | ||
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 | ||
// first character of the string (no human-readable part) or one of the | ||
// 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, ErrInvalidSeparatorIndex(one) | ||
} | ||
|
||
// 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("decoding bech32 failed: %w", err) | ||
return "", nil, err | ||
} | ||
|
||
converted, err := bech32.ConvertBits(data, 5, 8, false) | ||
// Verify if the checksum (stored inside decoded[:]) is valid, given the | ||
// previously decoded hrp. | ||
if !bech32VerifyChecksum(hrp, decoded) { | ||
// 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 | ||
} | ||
|
||
// We exclude the last 6 bytes, which is the checksum. | ||
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 and the human-readable | ||
// part will be lowercase. | ||
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 given | ||
// human-readable part (HRP). The HRP will be converted to lowercase if needed | ||
// 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) { | ||
// The resulting bech32 string is the concatenation of the lowercase hrp, | ||
// the separator 1, data and the 6-byte checksum. | ||
hrp = strings.ToLower(hrp) | ||
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]) | ||
} | ||
|
||
// 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, | ||
// 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, 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. | ||
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. | ||
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, ErrInvalidIncompleteGroup{} | ||
} | ||
|
||
return regrouped, nil | ||
} | ||
|
||
// EncodeFromBase256 converts a base256-encoded byte slice into a base32-encoded | ||
// byte slice and then encodes it into a bech32 string with the given | ||
// human-readable part (HRP). The HRP will be converted to lowercase if needed | ||
// since mixed cased encodings are not permitted and lowercase is used for | ||
// checksum purposes. | ||
func EncodeFromBase256(hrp string, data []byte) (string, error) { | ||
converted, err := ConvertBits(data, 8, 5, true) | ||
if err != nil { | ||
return "", nil, fmt.Errorf("decoding bech32 failed: %w", err) | ||
return "", err | ||
} | ||
return Encode(hrp, converted) | ||
} | ||
|
||
// DecodeToBase256 decodes a bech32-encoded string into its associated | ||
// human-readable part (HRP) and base32-encoded data, converts that data to a | ||
// base256-encoded byte slice and returns it along with the lowercase HRP. | ||
func DecodeToBase256(bech string) (string, []byte, error) { | ||
hrp, data, err := Decode(bech) | ||
if err != nil { | ||
return "", nil, err | ||
} | ||
converted, err := ConvertBits(data, 5, 8, false) | ||
if err != nil { | ||
return "", nil, err | ||
} | ||
return hrp, converted, nil | ||
} |
Oops, something went wrong.