hermez-node/apitypes/apitypes.go
apitypes/apitypes.goRaw
1package apitypes
2
3import (
4 "database/sql/driver"
5 "encoding/base64"
6 "encoding/hex"
7 "encoding/json"
8 "errors"
9 "fmt"
10 "math/big"
11 "strconv"
12 "strings"
13
14 ethCommon "github.com/ethereum/go-ethereum/common"
15 "github.com/hermeznetwork/hermez-node/common"
16 "github.com/iden3/go-iden3-crypto/babyjub"
17)
18
19// BigIntStr is used to scan/value *big.Int directly into strings from/to sql DBs.
20// It assumes that *big.Int are inserted/fetched to/from the DB using the BigIntMeddler meddler
21// defined at github.com/hermeznetwork/hermez-node/db
22type BigIntStr string
23
24// NewBigIntStr creates a *BigIntStr from a *big.Int.
25// If the provided bigInt is nil the returned *BigIntStr will also be nil
26func NewBigIntStr(bigInt *big.Int) *BigIntStr {
27 if bigInt == nil {
28 return nil
29 }
30 bigIntStr := BigIntStr(bigInt.String())
31 return &bigIntStr
32}
33
34// Scan implements Scanner for database/sql
35func (b *BigIntStr) Scan(src interface{}) error {
36 // decode base64 src
37 var decoded []byte
38 var err error
39 if srcStr, ok := src.(string); ok {
40 // src is a string
41 decoded, err = base64.StdEncoding.DecodeString(srcStr)
42 } else if srcBytes, ok := src.([]byte); ok {
43 // src is []byte
44 decoded, err = base64.StdEncoding.DecodeString(string(srcBytes))
45 } else {
46 // unexpected src
47 return fmt.Errorf("can't scan %T into apitypes.BigIntStr", src)
48 }
49 if err != nil {
50 return err
51 }
52 // decoded bytes to *big.Int
53 bigInt := &big.Int{}
54 bigInt = bigInt.SetBytes(decoded)
55 // *big.Int to BigIntStr
56 bigIntStr := NewBigIntStr(bigInt)
57 if bigIntStr == nil {
58 return nil
59 }
60 *b = *bigIntStr
61 return nil
62}
63
64// Value implements valuer for database/sql
65func (b BigIntStr) Value() (driver.Value, error) {
66 // string to *big.Int
67 bigInt := &big.Int{}
68 bigInt, ok := bigInt.SetString(string(b), 10)
69 if !ok || bigInt == nil {
70 return nil, errors.New("invalid representation of a *big.Int")
71 }
72 // *big.Int to base64
73 return base64.StdEncoding.EncodeToString(bigInt.Bytes()), nil
74}
75
76// StrBigInt is used to unmarshal BigIntStr directly into an alias of big.Int
77type StrBigInt big.Int
78
79// UnmarshalText unmarshals a StrBigInt
80func (s *StrBigInt) UnmarshalText(text []byte) error {
81 bi, ok := (*big.Int)(s).SetString(string(text), 10)
82 if !ok {
83 return fmt.Errorf("could not unmarshal %s into a StrBigInt", text)
84 }
85 *s = StrBigInt(*bi)
86 return nil
87}
88
89// CollectedFees is used to retrieve common.batch.CollectedFee from the DB
90type CollectedFees map[common.TokenID]BigIntStr
91
92// UnmarshalJSON unmarshals a json representation of map[common.TokenID]*big.Int
93func (c *CollectedFees) UnmarshalJSON(text []byte) error {
94 bigIntMap := make(map[common.TokenID]*big.Int)
95 if err := json.Unmarshal(text, &bigIntMap); err != nil {
96 return err
97 }
98 *c = CollectedFees(make(map[common.TokenID]BigIntStr))
99 for k, v := range bigIntMap {
100 bStr := NewBigIntStr(v)
101 (CollectedFees(*c)[k]) = *bStr
102 }
103 // *c = CollectedFees(bStrMap)
104 return nil
105}
106
107// HezEthAddr is used to scan/value Ethereum Address directly into strings that follow the Ethereum address hez fotmat (^hez:0x[a-fA-F0-9]{40}$) from/to sql DBs.
108// It assumes that Ethereum Address are inserted/fetched to/from the DB using the default Scan/Value interface
109type HezEthAddr string
110
111// NewHezEthAddr creates a HezEthAddr from an Ethereum addr
112func NewHezEthAddr(addr ethCommon.Address) HezEthAddr {
113 return HezEthAddr("hez:" + addr.String())
114}
115
116// ToEthAddr returns an Ethereum Address created from HezEthAddr
117func (a HezEthAddr) ToEthAddr() (ethCommon.Address, error) {
118 addrStr := strings.TrimPrefix(string(a), "hez:")
119 var addr ethCommon.Address
120 return addr, addr.UnmarshalText([]byte(addrStr))
121}
122
123// Scan implements Scanner for database/sql
124func (a *HezEthAddr) Scan(src interface{}) error {
125 ethAddr := ðCommon.Address{}
126 if err := ethAddr.Scan(src); err != nil {
127 return err
128 }
129 if ethAddr == nil {
130 return nil
131 }
132 *a = NewHezEthAddr(*ethAddr)
133 return nil
134}
135
136// Value implements valuer for database/sql
137func (a HezEthAddr) Value() (driver.Value, error) {
138 ethAddr, err := a.ToEthAddr()
139 if err != nil {
140 return nil, err
141 }
142 return ethAddr.Value()
143}
144
145// StrHezEthAddr is used to unmarshal HezEthAddr directly into an alias of ethCommon.Address
146type StrHezEthAddr ethCommon.Address
147
148// UnmarshalText unmarshals a StrHezEthAddr
149func (s *StrHezEthAddr) UnmarshalText(text []byte) error {
150 withoutHez := strings.TrimPrefix(string(text), "hez:")
151 var addr ethCommon.Address
152 if err := addr.UnmarshalText([]byte(withoutHez)); err != nil {
153 return err
154 }
155 *s = StrHezEthAddr(addr)
156 return nil
157}
158
159// HezBJJ is used to scan/value *babyjub.PublicKey directly into strings that follow the BJJ public key hez fotmat (^hez:[A-Za-z0-9_-]{44}$) from/to sql DBs.
160// It assumes that *babyjub.PublicKey are inserted/fetched to/from the DB using the default Scan/Value interface
161type HezBJJ string
162
163// NewHezBJJ creates a HezBJJ from a *babyjub.PublicKey.
164// Calling this method with a nil bjj causes panic
165func NewHezBJJ(bjj *babyjub.PublicKey) HezBJJ {
166 pkComp := [32]byte(bjj.Compress())
167 sum := pkComp[0]
168 for i := 1; i < len(pkComp); i++ {
169 sum += pkComp[i]
170 }
171 bjjSum := append(pkComp[:], sum)
172 return HezBJJ("hez:" + base64.RawURLEncoding.EncodeToString(bjjSum))
173}
174
175func hezStrToBJJ(s string) (*babyjub.PublicKey, error) {
176 const decodedLen = 33
177 const encodedLen = 44
178 formatErr := errors.New("invalid BJJ format. Must follow this regex: ^hez:[A-Za-z0-9_-]{44}$")
179 encoded := strings.TrimPrefix(s, "hez:")
180 if len(encoded) != encodedLen {
181 return nil, formatErr
182 }
183 decoded, err := base64.RawURLEncoding.DecodeString(encoded)
184 if err != nil {
185 return nil, formatErr
186 }
187 if len(decoded) != decodedLen {
188 return nil, formatErr
189 }
190 bjjBytes := [decodedLen - 1]byte{}
191 copy(bjjBytes[:decodedLen-1], decoded[:decodedLen-1])
192 sum := bjjBytes[0]
193 for i := 1; i < len(bjjBytes); i++ {
194 sum += bjjBytes[i]
195 }
196 if decoded[decodedLen-1] != sum {
197 return nil, errors.New("checksum verification failed")
198 }
199 bjjComp := babyjub.PublicKeyComp(bjjBytes)
200 return bjjComp.Decompress()
201}
202
203// ToBJJ returns a *babyjub.PublicKey created from HezBJJ
204func (b HezBJJ) ToBJJ() (*babyjub.PublicKey, error) {
205 return hezStrToBJJ(string(b))
206}
207
208// Scan implements Scanner for database/sql
209func (b *HezBJJ) Scan(src interface{}) error {
210 bjj := &babyjub.PublicKey{}
211 if err := bjj.Scan(src); err != nil {
212 return err
213 }
214 if bjj == nil {
215 return nil
216 }
217 *b = NewHezBJJ(bjj)
218 return nil
219}
220
221// Value implements valuer for database/sql
222func (b HezBJJ) Value() (driver.Value, error) {
223 bjj, err := b.ToBJJ()
224 if err != nil {
225 return nil, err
226 }
227 return bjj.Value()
228}
229
230// StrHezBJJ is used to unmarshal HezBJJ directly into an alias of babyjub.PublicKey
231type StrHezBJJ babyjub.PublicKey
232
233// UnmarshalText unmarshals a StrHezBJJ
234func (s *StrHezBJJ) UnmarshalText(text []byte) error {
235 bjj, err := hezStrToBJJ(string(text))
236 if err != nil {
237 return err
238 }
239 *s = StrHezBJJ(*bjj)
240 return nil
241}
242
243// HezIdx is used to value common.Idx directly into strings that follow the Idx key hez fotmat (hez:tokenSymbol:idx) to sql DBs.
244// Note that this can only be used to insert to DB since there is no way to automaticaly read from the DB since it needs the tokenSymbol
245type HezIdx string
246
247// StrHezIdx is used to unmarshal HezIdx directly into an alias of common.Idx
248type StrHezIdx common.Idx
249
250// UnmarshalText unmarshals a StrHezIdx
251func (s *StrHezIdx) UnmarshalText(text []byte) error {
252 withoutHez := strings.TrimPrefix(string(text), "hez:")
253 splitted := strings.Split(withoutHez, ":")
254 const expectedLen = 2
255 if len(splitted) != expectedLen {
256 return fmt.Errorf("can not unmarshal %s into StrHezIdx", text)
257 }
258 idxInt, err := strconv.Atoi(splitted[1])
259 if err != nil {
260 return err
261 }
262 *s = StrHezIdx(common.Idx(idxInt))
263 return nil
264}
265
266// EthSignature is used to scan/value []byte representing an Ethereum signature directly into strings from/to sql DBs.
267type EthSignature string
268
269// NewEthSignature creates a *EthSignature from []byte
270// If the provided signature is nil the returned *EthSignature will also be nil
271func NewEthSignature(signature []byte) *EthSignature {
272 if signature == nil {
273 return nil
274 }
275 ethSignature := EthSignature("0x" + hex.EncodeToString(signature))
276 return ðSignature
277}
278
279// Scan implements Scanner for database/sql
280func (e *EthSignature) Scan(src interface{}) error {
281 if srcStr, ok := src.(string); ok {
282 // src is a string
283 *e = *(NewEthSignature([]byte(srcStr)))
284 return nil
285 } else if srcBytes, ok := src.([]byte); ok {
286 // src is []byte
287 *e = *(NewEthSignature(srcBytes))
288 return nil
289 } else {
290 // unexpected src
291 return fmt.Errorf("can't scan %T into apitypes.EthSignature", src)
292 }
293}
294
295// Value implements valuer for database/sql
296func (e EthSignature) Value() (driver.Value, error) {
297 without0x := strings.TrimPrefix(string(e), "0x")
298 return hex.DecodeString(without0x)
299}
300
301// UnmarshalText unmarshals a StrEthSignature
302func (e *EthSignature) UnmarshalText(text []byte) error {
303 without0x := strings.TrimPrefix(string(text), "0x")
304 signature, err := hex.DecodeString(without0x)
305 if err != nil {
306 return err
307 }
308 *e = EthSignature([]byte(signature))
309 return nil
310}