-
Notifications
You must be signed in to change notification settings - Fork 656
/
encoding.go
163 lines (147 loc) · 4.29 KB
/
encoding.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package formatting
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"math"
"strings"
"github.com/ava-labs/avalanchego/utils/hashing"
"github.com/mr-tron/base58/base58"
)
const (
// maximum length byte slice can be encoded as a string
// using the CB58 encoding. Must be longer than the length
// of an ID and longer than the length of a SECP256k1 private key
// TODO: Reduce to a reasonable amount (e.g. 16 * 1024) after we
// give users a chance to export very large keystore users to hex
maxCB58EncodeSize = math.MaxInt32
hexPrefix = "0x"
checksumLen = 4
)
var (
// Maximum length CB58 encoded string that can be decoded to bytes
// This is different than [maxCB58EncodeSize] because each byte can express up
// to 256 but each base 58 digit can express up to 58
// The 10 is because there seems to be a floating point issue where the calculated
// max decode size (using this formula) is slightly smaller than the actual
maxCB58DecodeSize = int(float64(maxCB58EncodeSize)*math.Log2(256)/math.Log2(58)) + 10
errInvalidEncoding = errors.New("invalid encoding")
errMissingChecksum = errors.New("input string is smaller than the checksum size")
errBadChecksum = errors.New("invalid input checksum")
errMissingHexPrefix = errors.New("missing 0x prefix to hex encoding")
)
// Encoding defines how bytes are converted to a string and vice versa
type Encoding uint8
const (
// CB58 specifies the CB58 encoding format
CB58 Encoding = iota
// Hex specifies a hex plus 4 byte checksum encoding format
Hex
)
// String ...
func (enc Encoding) String() string {
switch enc {
case Hex:
return "hex"
case CB58:
return "cb58"
default:
return errInvalidEncoding.Error()
}
}
func (enc Encoding) valid() bool {
switch enc {
case Hex, CB58:
return true
}
return false
}
// MarshalJSON ...
func (enc Encoding) MarshalJSON() ([]byte, error) {
if !enc.valid() {
return nil, errInvalidEncoding
}
return []byte("\"" + enc.String() + "\""), nil
}
// UnmarshalJSON ...
func (enc *Encoding) UnmarshalJSON(b []byte) error {
str := string(b)
if str == "null" {
return nil
}
switch strings.ToLower(str) {
case "\"hex\"":
*enc = Hex
case "\"cb58\"":
*enc = CB58
default:
return errInvalidEncoding
}
return nil
}
// Encode [bytes] to a string using the given encoding format
// [bytes] may be nil, in which case it will be treated the same
// as an empty slice
func Encode(encoding Encoding, bytes []byte) (string, error) {
switch {
case !encoding.valid():
return "", errInvalidEncoding
case encoding == CB58 && len(bytes) > maxCB58EncodeSize:
return "", fmt.Errorf("byte slice length (%d) > maximum for cb58 (%d)", len(bytes), maxCB58EncodeSize)
}
checked := make([]byte, len(bytes)+checksumLen)
copy(checked, bytes)
copy(checked[len(bytes):], hashing.Checksum(bytes, checksumLen))
switch encoding {
case Hex:
return fmt.Sprintf("0x%x", checked), nil
case CB58:
return base58.Encode(checked), nil
default:
return "", errInvalidEncoding
}
}
// Decode [str] to bytes using the given encoding
// If [str] is the empty string, returns a nil byte slice and nil error
func Decode(encoding Encoding, str string) ([]byte, error) {
switch {
case !encoding.valid():
return nil, errInvalidEncoding
case len(str) == 0:
return nil, nil
case encoding == CB58 && len(str) > maxCB58DecodeSize:
return nil, fmt.Errorf("string length (%d) > maximum for cb58 (%d)", len(str), maxCB58DecodeSize)
}
var (
decodedBytes []byte
err error
)
switch encoding {
case Hex:
if !strings.HasPrefix(str, hexPrefix) {
return nil, errMissingHexPrefix
}
decodedBytes, err = hex.DecodeString(str[2:])
case CB58:
decodedBytes, err = base58.Decode(str)
}
if err != nil {
return nil, err
}
if len(decodedBytes) < checksumLen {
return nil, errMissingChecksum
}
// Verify the checksum
rawBytes := decodedBytes[:len(decodedBytes)-checksumLen]
if len(rawBytes) > maxCB58EncodeSize {
return nil, fmt.Errorf("byte slice length (%d) > maximum for cb58 (%d)", len(decodedBytes), maxCB58EncodeSize)
}
checksum := decodedBytes[len(decodedBytes)-checksumLen:]
if !bytes.Equal(checksum, hashing.Checksum(rawBytes, checksumLen)) {
return nil, errBadChecksum
}
return rawBytes, nil
}