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