diff --git a/address/address_v1.go b/address/address_v1.go index 72b5763..db9274b 100644 --- a/address/address_v1.go +++ b/address/address_v1.go @@ -12,7 +12,7 @@ import ( "github.com/pkg/errors" - "github.com/iotexproject/iotex-address/address/bech32" + bech32 "github.com/btcsuite/btcutil/bech32" ) // _v1 is a singleton and defines V1 address metadata diff --git a/address/bech32/bech32.go b/address/bech32/bech32.go deleted file mode 100644 index 84be237..0000000 --- a/address/bech32/bech32.go +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright (c) 2018 IoTeX -// This is an alpha (internal) release and is not suitable for production. This source code is provided 'as is' and no -// warranties are given as to title or non-infringement, merchantability or fitness for purpose and, to the extent -// permitted by law, all liability for your use of the code is disclaimed. This source code is governed by Apache -// License 2.0 that can be found in the LICENSE file. - -// Package bech32 includes a Bech32 string which is at most 90 characters long and consists of: -// The human-readable part, which is intended to convey the type of data, or -// anything else that is relevant to the reader. This part MUST contain 1 to -// 83 US-ASCII characters, with each character having a value in the range -// [33-126]. HRP validity may be further restricted by specific applications. -// -// The separator, which is always "1". In case "1" is allowed inside the -// human-readable part, the last one in the string is the separator. -// -// The data part, which is at least 6 characters long and only consists of -// alphanumeric characters excluding "1", "b", "i", and "o". -// -package bech32 - -import ( - "fmt" - "strings" - - "github.com/pkg/errors" -) - -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, errors.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, errors.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, errors.New("string not all lowercase or all uppercase") - } - - 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, errors.New("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, errors.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, errors.Errorf("checksum failed: %s.", 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 "", errors.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 corresponding 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, errors.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 "", errors.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, errors.New("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 <<= (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 <<= 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 <<= (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, errors.New("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/address/bech32/bech32_test.go b/address/bech32/bech32_test.go deleted file mode 100644 index 0ad9561..0000000 --- a/address/bech32/bech32_test.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2018 IoTeX -// This is an alpha (internal) release and is not suitable for production. This source code is provided 'as is' and no -// warranties are given as to title or non-infringement, merchantability or fitness for purpose and, to the extent -// permitted by law, all liability for your use of the code is disclaimed. This source code is governed by Apache -// License 2.0 that can be found in the LICENSE file. - -package bech32 - -import ( - "strings" - "testing" -) - -func TestBech32(t *testing.T) { - tests := []struct { - str string - valid bool - }{ - // Try some test vectors from https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#Bech32 - {"A12UEL5L", true}, - {"a12uel5l", true}, - {"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", true}, - {"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", true}, - {"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", true}, - {"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", true}, - {"Split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", false}, // mix of lower upper - {"split1checkupstagehandshakeupstreamerranterredcaperred2y9e2w", false}, // invalid checksum - {"s lit1checkupstagehandshakeupstreamerranterredcaperredp8hs2p", false}, // invalid character (space) in hrp - {"spl" + string([]byte{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 - {"li1dgmt3", false}, // Too short checksum - {"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", false}, // overall max length exceeded - } - - for _, test := range tests { - str := test.str - hrp, decoded, err := Decode(str) - if !test.valid { - // Invalid string decoding should result in error. - if err == nil { - t.Errorf("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 := 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 = Decode(flipped) - if err == nil { - t.Error("expected decoding to fail") - } - } -} diff --git a/go.mod b/go.mod index f9f5a8d..21d99ae 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/iotexproject/iotex-address go 1.14 require ( + github.com/btcsuite/btcutil v1.0.2 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.4.0 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 diff --git a/go.sum b/go.sum index b4c1a6f..dcf3250 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,26 @@ +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -7,16 +28,23 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=