2954f40eac
- use Text instead of fmt.Sprintf - reduced allocs from 6 to 2 - improved speed
242 lines
5.9 KiB
Go
242 lines
5.9 KiB
Go
// Copyright 2016 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
/*
|
|
Package hexutil implements hex encoding with 0x prefix.
|
|
This encoding is used by the Ethereum RPC API to transport binary data in JSON payloads.
|
|
|
|
Encoding Rules
|
|
|
|
All hex data must have prefix "0x".
|
|
|
|
For byte slices, the hex data must be of even length. An empty byte slice
|
|
encodes as "0x".
|
|
|
|
Integers are encoded using the least amount of digits (no leading zero digits). Their
|
|
encoding may be of uneven length. The number zero encodes as "0x0".
|
|
*/
|
|
package hexutil
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math/big"
|
|
"strconv"
|
|
)
|
|
|
|
const uintBits = 32 << (uint64(^uint(0)) >> 63)
|
|
|
|
// Errors
|
|
var (
|
|
ErrEmptyString = &decError{"empty hex string"}
|
|
ErrSyntax = &decError{"invalid hex string"}
|
|
ErrMissingPrefix = &decError{"hex string without 0x prefix"}
|
|
ErrOddLength = &decError{"hex string of odd length"}
|
|
ErrEmptyNumber = &decError{"hex string \"0x\""}
|
|
ErrLeadingZero = &decError{"hex number with leading zero digits"}
|
|
ErrUint64Range = &decError{"hex number > 64 bits"}
|
|
ErrUintRange = &decError{fmt.Sprintf("hex number > %d bits", uintBits)}
|
|
ErrBig256Range = &decError{"hex number > 256 bits"}
|
|
)
|
|
|
|
type decError struct{ msg string }
|
|
|
|
func (err decError) Error() string { return err.msg }
|
|
|
|
// Decode decodes a hex string with 0x prefix.
|
|
func Decode(input string) ([]byte, error) {
|
|
if len(input) == 0 {
|
|
return nil, ErrEmptyString
|
|
}
|
|
if !has0xPrefix(input) {
|
|
return nil, ErrMissingPrefix
|
|
}
|
|
b, err := hex.DecodeString(input[2:])
|
|
if err != nil {
|
|
err = mapError(err)
|
|
}
|
|
return b, err
|
|
}
|
|
|
|
// MustDecode decodes a hex string with 0x prefix. It panics for invalid input.
|
|
func MustDecode(input string) []byte {
|
|
dec, err := Decode(input)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return dec
|
|
}
|
|
|
|
// Encode encodes b as a hex string with 0x prefix.
|
|
func Encode(b []byte) string {
|
|
enc := make([]byte, len(b)*2+2)
|
|
copy(enc, "0x")
|
|
hex.Encode(enc[2:], b)
|
|
return string(enc)
|
|
}
|
|
|
|
// DecodeUint64 decodes a hex string with 0x prefix as a quantity.
|
|
func DecodeUint64(input string) (uint64, error) {
|
|
raw, err := checkNumber(input)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
dec, err := strconv.ParseUint(raw, 16, 64)
|
|
if err != nil {
|
|
err = mapError(err)
|
|
}
|
|
return dec, err
|
|
}
|
|
|
|
// MustDecodeUint64 decodes a hex string with 0x prefix as a quantity.
|
|
// It panics for invalid input.
|
|
func MustDecodeUint64(input string) uint64 {
|
|
dec, err := DecodeUint64(input)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return dec
|
|
}
|
|
|
|
// EncodeUint64 encodes i as a hex string with 0x prefix.
|
|
func EncodeUint64(i uint64) string {
|
|
enc := make([]byte, 2, 10)
|
|
copy(enc, "0x")
|
|
return string(strconv.AppendUint(enc, i, 16))
|
|
}
|
|
|
|
var bigWordNibbles int
|
|
|
|
func init() {
|
|
// This is a weird way to compute the number of nibbles required for big.Word.
|
|
// The usual way would be to use constant arithmetic but go vet can't handle that.
|
|
b, _ := new(big.Int).SetString("FFFFFFFFFF", 16)
|
|
switch len(b.Bits()) {
|
|
case 1:
|
|
bigWordNibbles = 16
|
|
case 2:
|
|
bigWordNibbles = 8
|
|
default:
|
|
panic("weird big.Word size")
|
|
}
|
|
}
|
|
|
|
// DecodeBig decodes a hex string with 0x prefix as a quantity.
|
|
// Numbers larger than 256 bits are not accepted.
|
|
func DecodeBig(input string) (*big.Int, error) {
|
|
raw, err := checkNumber(input)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(raw) > 64 {
|
|
return nil, ErrBig256Range
|
|
}
|
|
words := make([]big.Word, len(raw)/bigWordNibbles+1)
|
|
end := len(raw)
|
|
for i := range words {
|
|
start := end - bigWordNibbles
|
|
if start < 0 {
|
|
start = 0
|
|
}
|
|
for ri := start; ri < end; ri++ {
|
|
nib := decodeNibble(raw[ri])
|
|
if nib == badNibble {
|
|
return nil, ErrSyntax
|
|
}
|
|
words[i] *= 16
|
|
words[i] += big.Word(nib)
|
|
}
|
|
end = start
|
|
}
|
|
dec := new(big.Int).SetBits(words)
|
|
return dec, nil
|
|
}
|
|
|
|
// MustDecodeBig decodes a hex string with 0x prefix as a quantity.
|
|
// It panics for invalid input.
|
|
func MustDecodeBig(input string) *big.Int {
|
|
dec, err := DecodeBig(input)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return dec
|
|
}
|
|
|
|
// EncodeBig encodes bigint as a hex string with 0x prefix.
|
|
func EncodeBig(bigint *big.Int) string {
|
|
if sign := bigint.Sign(); sign == 0 {
|
|
return "0x0"
|
|
} else if sign > 0 {
|
|
return "0x" + bigint.Text(16)
|
|
} else {
|
|
return "-0x" + bigint.Text(16)[1:]
|
|
}
|
|
}
|
|
|
|
func has0xPrefix(input string) bool {
|
|
return len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X')
|
|
}
|
|
|
|
func checkNumber(input string) (raw string, err error) {
|
|
if len(input) == 0 {
|
|
return "", ErrEmptyString
|
|
}
|
|
if !has0xPrefix(input) {
|
|
return "", ErrMissingPrefix
|
|
}
|
|
input = input[2:]
|
|
if len(input) == 0 {
|
|
return "", ErrEmptyNumber
|
|
}
|
|
if len(input) > 1 && input[0] == '0' {
|
|
return "", ErrLeadingZero
|
|
}
|
|
return input, nil
|
|
}
|
|
|
|
const badNibble = ^uint64(0)
|
|
|
|
func decodeNibble(in byte) uint64 {
|
|
switch {
|
|
case in >= '0' && in <= '9':
|
|
return uint64(in - '0')
|
|
case in >= 'A' && in <= 'F':
|
|
return uint64(in - 'A' + 10)
|
|
case in >= 'a' && in <= 'f':
|
|
return uint64(in - 'a' + 10)
|
|
default:
|
|
return badNibble
|
|
}
|
|
}
|
|
|
|
func mapError(err error) error {
|
|
if err, ok := err.(*strconv.NumError); ok {
|
|
switch err.Err {
|
|
case strconv.ErrRange:
|
|
return ErrUint64Range
|
|
case strconv.ErrSyntax:
|
|
return ErrSyntax
|
|
}
|
|
}
|
|
if _, ok := err.(hex.InvalidByteError); ok {
|
|
return ErrSyntax
|
|
}
|
|
if err == hex.ErrLength {
|
|
return ErrOddLength
|
|
}
|
|
return err
|
|
}
|