Skip to content

Commit 8d0108f

Browse files
authored
Merge pull request #3355 from fjl/hexutil-2
Improve hex encoding/decoding
2 parents ba41efa + 91bceb4 commit 8d0108f

File tree

17 files changed

+1125
-566
lines changed

17 files changed

+1125
-566
lines changed

accounts/addrcache.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ func (ac *addrCache) scan() ([]Account, error) {
225225
buf = new(bufio.Reader)
226226
addrs []Account
227227
keyJSON struct {
228-
Address common.Address `json:"address"`
228+
Address string `json:"address"`
229229
}
230230
)
231231
for _, fi := range files {
@@ -241,15 +241,16 @@ func (ac *addrCache) scan() ([]Account, error) {
241241
}
242242
buf.Reset(fd)
243243
// Parse the address.
244-
keyJSON.Address = common.Address{}
244+
keyJSON.Address = ""
245245
err = json.NewDecoder(buf).Decode(&keyJSON)
246+
addr := common.HexToAddress(keyJSON.Address)
246247
switch {
247248
case err != nil:
248249
glog.V(logger.Debug).Infof("can't decode key %s: %v", path, err)
249-
case (keyJSON.Address == common.Address{}):
250+
case (addr == common.Address{}):
250251
glog.V(logger.Debug).Infof("can't decode key %s: missing or zero address", path)
251252
default:
252-
addrs = append(addrs, Account{Address: keyJSON.Address, File: path})
253+
addrs = append(addrs, Account{Address: addr, File: path})
253254
}
254255
fd.Close()
255256
}

common/hexutil/hexutil.go

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
// Copyright 2016 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
/*
18+
Package hexutil implements hex encoding with 0x prefix.
19+
This encoding is used by the Ethereum RPC API to transport binary data in JSON payloads.
20+
21+
Encoding Rules
22+
23+
All hex data must have prefix "0x".
24+
25+
For byte slices, the hex data must be of even length. An empty byte slice
26+
encodes as "0x".
27+
28+
Integers are encoded using the least amount of digits (no leading zero digits). Their
29+
encoding may be of uneven length. The number zero encodes as "0x0".
30+
*/
31+
package hexutil
32+
33+
import (
34+
"encoding/hex"
35+
"errors"
36+
"fmt"
37+
"math/big"
38+
"strconv"
39+
)
40+
41+
const uintBits = 32 << (uint64(^uint(0)) >> 63)
42+
43+
var (
44+
ErrEmptyString = errors.New("empty hex string")
45+
ErrMissingPrefix = errors.New("missing 0x prefix for hex data")
46+
ErrSyntax = errors.New("invalid hex")
47+
ErrEmptyNumber = errors.New("hex number has no digits after 0x")
48+
ErrLeadingZero = errors.New("hex number has leading zero digits after 0x")
49+
ErrOddLength = errors.New("hex string has odd length")
50+
ErrUint64Range = errors.New("hex number does not fit into 64 bits")
51+
ErrUintRange = fmt.Errorf("hex number does not fit into %d bits", uintBits)
52+
)
53+
54+
// Decode decodes a hex string with 0x prefix.
55+
func Decode(input string) ([]byte, error) {
56+
if len(input) == 0 {
57+
return nil, ErrEmptyString
58+
}
59+
if !has0xPrefix(input) {
60+
return nil, ErrMissingPrefix
61+
}
62+
return hex.DecodeString(input[2:])
63+
}
64+
65+
// MustDecode decodes a hex string with 0x prefix. It panics for invalid input.
66+
func MustDecode(input string) []byte {
67+
dec, err := Decode(input)
68+
if err != nil {
69+
panic(err)
70+
}
71+
return dec
72+
}
73+
74+
// Encode encodes b as a hex string with 0x prefix.
75+
func Encode(b []byte) string {
76+
enc := make([]byte, len(b)*2+2)
77+
copy(enc, "0x")
78+
hex.Encode(enc[2:], b)
79+
return string(enc)
80+
}
81+
82+
// DecodeUint64 decodes a hex string with 0x prefix as a quantity.
83+
func DecodeUint64(input string) (uint64, error) {
84+
raw, err := checkNumber(input)
85+
if err != nil {
86+
return 0, err
87+
}
88+
dec, err := strconv.ParseUint(raw, 16, 64)
89+
if err != nil {
90+
err = mapError(err)
91+
}
92+
return dec, err
93+
}
94+
95+
// MustDecodeUint64 decodes a hex string with 0x prefix as a quantity.
96+
// It panics for invalid input.
97+
func MustDecodeUint64(input string) uint64 {
98+
dec, err := DecodeUint64(input)
99+
if err != nil {
100+
panic(err)
101+
}
102+
return dec
103+
}
104+
105+
// EncodeUint64 encodes i as a hex string with 0x prefix.
106+
func EncodeUint64(i uint64) string {
107+
enc := make([]byte, 2, 10)
108+
copy(enc, "0x")
109+
return string(strconv.AppendUint(enc, i, 16))
110+
}
111+
112+
var bigWordNibbles int
113+
114+
func init() {
115+
// This is a weird way to compute the number of nibbles required for big.Word.
116+
// The usual way would be to use constant arithmetic but go vet can't handle that.
117+
b, _ := new(big.Int).SetString("FFFFFFFFFF", 16)
118+
switch len(b.Bits()) {
119+
case 1:
120+
bigWordNibbles = 16
121+
case 2:
122+
bigWordNibbles = 8
123+
default:
124+
panic("weird big.Word size")
125+
}
126+
}
127+
128+
// DecodeBig decodes a hex string with 0x prefix as a quantity.
129+
func DecodeBig(input string) (*big.Int, error) {
130+
raw, err := checkNumber(input)
131+
if err != nil {
132+
return nil, err
133+
}
134+
words := make([]big.Word, len(raw)/bigWordNibbles+1)
135+
end := len(raw)
136+
for i := range words {
137+
start := end - bigWordNibbles
138+
if start < 0 {
139+
start = 0
140+
}
141+
for ri := start; ri < end; ri++ {
142+
nib := decodeNibble(raw[ri])
143+
if nib == badNibble {
144+
return nil, ErrSyntax
145+
}
146+
words[i] *= 16
147+
words[i] += big.Word(nib)
148+
}
149+
end = start
150+
}
151+
dec := new(big.Int).SetBits(words)
152+
return dec, nil
153+
}
154+
155+
// MustDecodeBig decodes a hex string with 0x prefix as a quantity.
156+
// It panics for invalid input.
157+
func MustDecodeBig(input string) *big.Int {
158+
dec, err := DecodeBig(input)
159+
if err != nil {
160+
panic(err)
161+
}
162+
return dec
163+
}
164+
165+
// EncodeBig encodes bigint as a hex string with 0x prefix.
166+
// The sign of the integer is ignored.
167+
func EncodeBig(bigint *big.Int) string {
168+
nbits := bigint.BitLen()
169+
if nbits == 0 {
170+
return "0x0"
171+
}
172+
enc := make([]byte, 2, (nbits/8)*2+2)
173+
copy(enc, "0x")
174+
for i := len(bigint.Bits()) - 1; i >= 0; i-- {
175+
enc = strconv.AppendUint(enc, uint64(bigint.Bits()[i]), 16)
176+
}
177+
return string(enc)
178+
}
179+
180+
func has0xPrefix(input string) bool {
181+
return len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X')
182+
}
183+
184+
func checkNumber(input string) (raw string, err error) {
185+
if len(input) == 0 {
186+
return "", ErrEmptyString
187+
}
188+
if !has0xPrefix(input) {
189+
return "", ErrMissingPrefix
190+
}
191+
input = input[2:]
192+
if len(input) == 0 {
193+
return "", ErrEmptyNumber
194+
}
195+
if len(input) > 1 && input[0] == '0' {
196+
return "", ErrLeadingZero
197+
}
198+
return input, nil
199+
}
200+
201+
const badNibble = ^uint64(0)
202+
203+
func decodeNibble(in byte) uint64 {
204+
switch {
205+
case in >= '0' && in <= '9':
206+
return uint64(in - '0')
207+
case in >= 'A' && in <= 'F':
208+
return uint64(in - 'A' + 10)
209+
case in >= 'a' && in <= 'f':
210+
return uint64(in - 'a' + 10)
211+
default:
212+
return badNibble
213+
}
214+
}
215+
216+
func mapError(err error) error {
217+
if err, ok := err.(*strconv.NumError); ok {
218+
switch err.Err {
219+
case strconv.ErrRange:
220+
return ErrUint64Range
221+
case strconv.ErrSyntax:
222+
return ErrSyntax
223+
}
224+
}
225+
if _, ok := err.(hex.InvalidByteError); ok {
226+
return ErrSyntax
227+
}
228+
if err == hex.ErrLength {
229+
return ErrOddLength
230+
}
231+
return err
232+
}

0 commit comments

Comments
 (0)