hermez-node/apitypes/apitypes.go
apitypes/apitypes.goRaw
1package apitypes
2
3import (
4 "database/sql/driver"
5 "encoding/base64"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "math/big"
10 "strings"
11
12 ethCommon "github.com/ethereum/go-ethereum/common"
13 "github.com/hermeznetwork/hermez-node/common"
14 "github.com/iden3/go-iden3-crypto/babyjub"
15)
16
17// BigIntStr is used to scan/value *big.Int directly into strings from/to sql DBs.
18// It assumes that *big.Int are inserted/fetched to/from the DB using the BigIntMeddler meddler
19// defined at github.com/hermeznetwork/hermez-node/db
20type BigIntStr string
21
22// NewBigIntStr creates a *BigIntStr from a *big.Int.
23// If the provided bigInt is nil the returned *BigIntStr will also be nil
24func NewBigIntStr(bigInt *big.Int) *BigIntStr {
25 if bigInt == nil {
26 return nil
27 }
28 bigIntStr := BigIntStr(bigInt.String())
29 return &bigIntStr
30}
31
32// Scan implements Scanner for database/sql
33func (b *BigIntStr) Scan(src interface{}) error {
34 // decode base64 src
35 var decoded []byte
36 var err error
37 if srcStr, ok := src.(string); ok {
38 // src is a string
39 decoded, err = base64.StdEncoding.DecodeString(srcStr)
40 } else if srcBytes, ok := src.([]byte); ok {
41 // src is []byte
42 decoded, err = base64.StdEncoding.DecodeString(string(srcBytes))
43 } else {
44 // unexpected src
45 return fmt.Errorf("can't scan %T into apitypes.BigIntStr", src)
46 }
47 if err != nil {
48 return err
49 }
50 // decoded bytes to *big.Int
51 bigInt := &big.Int{}
52 bigInt = bigInt.SetBytes(decoded)
53 // *big.Int to BigIntStr
54 bigIntStr := NewBigIntStr(bigInt)
55 if bigIntStr == nil {
56 return nil
57 }
58 *b = *bigIntStr
59 return nil
60}
61
62// Value implements valuer for database/sql
63func (b BigIntStr) Value() (driver.Value, error) {
64 // string to *big.Int
65 bigInt := &big.Int{}
66 bigInt, ok := bigInt.SetString(string(b), 10)
67 if !ok || bigInt == nil {
68 return nil, errors.New("invalid representation of a *big.Int")
69 }
70 // *big.Int to base64
71 return base64.StdEncoding.EncodeToString(bigInt.Bytes()), nil
72}
73
74// CollectedFees is used to retrieve common.batch.CollectedFee from the DB
75type CollectedFees map[common.TokenID]BigIntStr
76
77// UnmarshalJSON unmarshals a json representation of map[common.TokenID]*big.Int
78func (c *CollectedFees) UnmarshalJSON(text []byte) error {
79 bigIntMap := make(map[common.TokenID]*big.Int)
80 if err := json.Unmarshal(text, &bigIntMap); err != nil {
81 return err
82 }
83 bStrMap := make(map[common.TokenID]BigIntStr)
84 for k, v := range bigIntMap {
85 bStr := NewBigIntStr(v)
86 bStrMap[k] = *bStr
87 }
88 *c = CollectedFees(bStrMap)
89 return nil
90}
91
92// 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.
93// It assumes that Ethereum Address are inserted/fetched to/from the DB using the default Scan/Value interface
94type HezEthAddr string
95
96// NewHezEthAddr creates a HezEthAddr from an Ethereum addr
97func NewHezEthAddr(addr ethCommon.Address) HezEthAddr {
98 return HezEthAddr("hez:" + addr.String())
99}
100
101// ToEthAddr returns an Ethereum Address created from HezEthAddr
102func (a HezEthAddr) ToEthAddr() (ethCommon.Address, error) {
103 addrStr := strings.TrimPrefix(string(a), "hez:")
104 var addr ethCommon.Address
105 return addr, addr.UnmarshalText([]byte(addrStr))
106}
107
108// Scan implements Scanner for database/sql
109func (a *HezEthAddr) Scan(src interface{}) error {
110 ethAddr := ðCommon.Address{}
111 if err := ethAddr.Scan(src); err != nil {
112 return err
113 }
114 if ethAddr == nil {
115 return nil
116 }
117 *a = NewHezEthAddr(*ethAddr)
118 return nil
119}
120
121// Value implements valuer for database/sql
122func (a HezEthAddr) Value() (driver.Value, error) {
123 ethAddr, err := a.ToEthAddr()
124 if err != nil {
125 return nil, err
126 }
127 return ethAddr.Value()
128}
129
130// 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.
131// It assumes that *babyjub.PublicKey are inserted/fetched to/from the DB using the default Scan/Value interface
132type HezBJJ string
133
134// NewHezBJJ creates a HezBJJ from a *babyjub.PublicKey.
135// Calling this method with a nil bjj causes panic
136func NewHezBJJ(bjj *babyjub.PublicKey) HezBJJ {
137 pkComp := [32]byte(bjj.Compress())
138 sum := pkComp[0]
139 for i := 1; i < len(pkComp); i++ {
140 sum += pkComp[i]
141 }
142 bjjSum := append(pkComp[:], sum)
143 return HezBJJ("hez:" + base64.RawURLEncoding.EncodeToString(bjjSum))
144}
145
146// ToBJJ returns a *babyjub.PublicKey created from HezBJJ
147func (b HezBJJ) ToBJJ() (*babyjub.PublicKey, error) {
148 const decodedLen = 33
149 const encodedLen = 44
150 formatErr := errors.New("invalid BJJ format. Must follow this regex: ^hez:[A-Za-z0-9_-]{44}$")
151 encoded := strings.TrimPrefix(string(b), "hez:")
152 if len(encoded) != encodedLen {
153 return nil, formatErr
154 }
155 decoded, err := base64.RawURLEncoding.DecodeString(encoded)
156 if err != nil {
157 return nil, formatErr
158 }
159 if len(decoded) != decodedLen {
160 return nil, formatErr
161 }
162 bjjBytes := [decodedLen - 1]byte{}
163 copy(bjjBytes[:decodedLen-1], decoded[:decodedLen-1])
164 sum := bjjBytes[0]
165 for i := 1; i < len(bjjBytes); i++ {
166 sum += bjjBytes[i]
167 }
168 if decoded[decodedLen-1] != sum {
169 return nil, errors.New("checksum verification failed")
170 }
171 bjjComp := babyjub.PublicKeyComp(bjjBytes)
172 return bjjComp.Decompress()
173}
174
175// Scan implements Scanner for database/sql
176func (b *HezBJJ) Scan(src interface{}) error {
177 bjj := &babyjub.PublicKey{}
178 if err := bjj.Scan(src); err != nil {
179 return err
180 }
181 if bjj == nil {
182 return nil
183 }
184 *b = NewHezBJJ(bjj)
185 return nil
186}
187
188// Value implements valuer for database/sql
189func (b HezBJJ) Value() (driver.Value, error) {
190 bjj, err := b.ToBJJ()
191 if err != nil {
192 return nil, err
193 }
194 return bjj.Value()
195}