hermez-node/common/pooll2tx.go
common/pooll2tx.goRaw
1package common
2
3import (
4 "encoding/binary"
5 "errors"
6 "fmt"
7 "math/big"
8 "time"
9
10 ethCommon "github.com/ethereum/go-ethereum/common"
11 "github.com/hermeznetwork/tracerr"
12 "github.com/iden3/go-iden3-crypto/babyjub"
13 "github.com/iden3/go-iden3-crypto/poseidon"
14)
15
16// EmptyBJJComp contains the 32 byte array of a empty BabyJubJub PublicKey
17// Compressed. It is a valid point in the BabyJubJub curve, so does not give
18// errors when being decompressed.
19var EmptyBJJComp = babyjub.PublicKeyComp([32]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
20 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
21
22// PoolL2Tx is a struct that represents a L2Tx sent by an account to the
23// coordinator that is waiting to be forged
24type PoolL2Tx struct {
25 // Stored in DB: mandatory fileds
26
27 // TxID (12 bytes) for L2Tx is:
28 // bytes: | 1 | 6 | 5 |
29 // values: | type | FromIdx | Nonce |
30 TxID TxID `meddler:"tx_id"`
31 FromIdx Idx `meddler:"from_idx"`
32 ToIdx Idx `meddler:"to_idx,zeroisnull"`
33 // AuxToIdx is only used internally at the StateDB to avoid repeated
34 // computation when processing transactions (from Synchronizer,
35 // TxSelector, BatchBuilder)
36 AuxToIdx Idx `meddler:"-"`
37 ToEthAddr ethCommon.Address `meddler:"to_eth_addr,zeroisnull"`
38 ToBJJ babyjub.PublicKeyComp `meddler:"to_bjj,zeroisnull"`
39 TokenID TokenID `meddler:"token_id"`
40 Amount *big.Int `meddler:"amount,bigint"`
41 Fee FeeSelector `meddler:"fee"`
42 Nonce Nonce `meddler:"nonce"` // effective 40 bits used
43 State PoolL2TxState `meddler:"state"`
44 // Info contains information about the status & State of the
45 // transaction. As for example, if the Tx has not been selected in the
46 // last batch due not enough Balance at the Sender account, this reason
47 // would appear at this parameter.
48 Info string `meddler:"info,zeroisnull"`
49 Signature babyjub.SignatureComp `meddler:"signature"` // tx signature
50 Timestamp time.Time `meddler:"timestamp,utctime"` // time when added to the tx pool
51 // Stored in DB: optional fileds, may be uninitialized
52 RqFromIdx Idx `meddler:"rq_from_idx,zeroisnull"`
53 RqToIdx Idx `meddler:"rq_to_idx,zeroisnull"`
54 RqToEthAddr ethCommon.Address `meddler:"rq_to_eth_addr,zeroisnull"`
55 RqToBJJ babyjub.PublicKeyComp `meddler:"rq_to_bjj,zeroisnull"`
56 RqTokenID TokenID `meddler:"rq_token_id,zeroisnull"`
57 RqAmount *big.Int `meddler:"rq_amount,bigintnull"`
58 RqFee FeeSelector `meddler:"rq_fee,zeroisnull"`
59 RqNonce Nonce `meddler:"rq_nonce,zeroisnull"` // effective 48 bits used
60 AbsoluteFee float64 `meddler:"fee_usd,zeroisnull"`
61 AbsoluteFeeUpdate time.Time `meddler:"usd_update,utctimez"`
62 Type TxType `meddler:"tx_type"`
63 // Extra metadata, may be uninitialized
64 RqTxCompressedData []byte `meddler:"-"` // 253 bits, optional for atomic txs
65}
66
67// NewPoolL2Tx returns the given L2Tx with the TxId & Type parameters calculated
68// from the L2Tx values
69func NewPoolL2Tx(tx *PoolL2Tx) (*PoolL2Tx, error) {
70 txTypeOld := tx.Type
71 if err := tx.SetType(); err != nil {
72 return nil, tracerr.Wrap(err)
73 }
74 // If original Type doesn't match the correct one, return error
75 if txTypeOld != "" && txTypeOld != tx.Type {
76 return nil, tracerr.Wrap(fmt.Errorf("L2Tx.Type: %s, should be: %s",
77 txTypeOld, tx.Type))
78 }
79
80 txIDOld := tx.TxID
81 if err := tx.SetID(); err != nil {
82 return nil, tracerr.Wrap(err)
83 }
84 // If original TxID doesn't match the correct one, return error
85 if txIDOld != (TxID{}) && txIDOld != tx.TxID {
86 return tx, tracerr.Wrap(fmt.Errorf("PoolL2Tx.TxID: %s, should be: %s",
87 txIDOld.String(), tx.TxID.String()))
88 }
89
90 return tx, nil
91}
92
93// SetType sets the type of the transaction
94func (tx *PoolL2Tx) SetType() error {
95 if tx.ToIdx >= IdxUserThreshold {
96 tx.Type = TxTypeTransfer
97 } else if tx.ToIdx == 1 {
98 tx.Type = TxTypeExit
99 } else if tx.ToIdx == 0 {
100 if tx.ToBJJ != EmptyBJJComp && tx.ToEthAddr == FFAddr {
101 tx.Type = TxTypeTransferToBJJ
102 } else if tx.ToEthAddr != FFAddr && tx.ToEthAddr != EmptyAddr {
103 tx.Type = TxTypeTransferToEthAddr
104 } else {
105 return tracerr.Wrap(errors.New("malformed transaction"))
106 }
107 } else {
108 return tracerr.Wrap(errors.New("malformed transaction"))
109 }
110 return nil
111}
112
113// SetID sets the ID of the transaction
114func (tx *PoolL2Tx) SetID() error {
115 txID, err := tx.L2Tx().CalculateTxID()
116 if err != nil {
117 return tracerr.Wrap(err)
118 }
119 tx.TxID = txID
120 return nil
121}
122
123// TxCompressedData spec:
124// [ 1 bits ] toBJJSign // 1 byte
125// [ 8 bits ] userFee // 1 byte
126// [ 40 bits ] nonce // 5 bytes
127// [ 32 bits ] tokenID // 4 bytes
128// [ 48 bits ] toIdx // 6 bytes
129// [ 48 bits ] fromIdx // 6 bytes
130// [ 16 bits ] chainId // 2 bytes
131// [ 32 bits ] signatureConstant // 4 bytes
132// Total bits compressed data: 225 bits // 29 bytes in *big.Int representation
133func (tx *PoolL2Tx) TxCompressedData(chainID uint16) (*big.Int, error) {
134 var b [29]byte
135
136 toBJJSign := byte(0)
137 pkSign, _ := babyjub.UnpackSignY(tx.ToBJJ)
138 if pkSign {
139 toBJJSign = byte(1)
140 }
141
142 b[0] = toBJJSign
143 b[1] = byte(tx.Fee)
144 nonceBytes, err := tx.Nonce.Bytes()
145 if err != nil {
146 return nil, tracerr.Wrap(err)
147 }
148 copy(b[2:7], nonceBytes[:])
149 copy(b[7:11], tx.TokenID.Bytes())
150 toIdxBytes, err := tx.ToIdx.Bytes()
151 if err != nil {
152 return nil, tracerr.Wrap(err)
153 }
154 copy(b[11:17], toIdxBytes[:])
155 fromIdxBytes, err := tx.FromIdx.Bytes()
156 if err != nil {
157 return nil, tracerr.Wrap(err)
158 }
159 copy(b[17:23], fromIdxBytes[:])
160 binary.BigEndian.PutUint16(b[23:25], chainID)
161 copy(b[25:29], SignatureConstantBytes[:])
162
163 bi := new(big.Int).SetBytes(b[:])
164 return bi, nil
165}
166
167// TxCompressedDataEmpty calculates the TxCompressedData of an empty
168// transaction
169func TxCompressedDataEmpty(chainID uint16) *big.Int {
170 var b [29]byte
171 binary.BigEndian.PutUint16(b[23:25], chainID)
172 copy(b[25:29], SignatureConstantBytes[:])
173 bi := new(big.Int).SetBytes(b[:])
174 return bi
175}
176
177// TxCompressedDataV2 spec:
178// [ 1 bits ] toBJJSign // 1 byte
179// [ 8 bits ] userFee // 1 byte
180// [ 40 bits ] nonce // 5 bytes
181// [ 32 bits ] tokenID // 4 bytes
182// [ 40 bits ] amountFloat40 // 5 bytes
183// [ 48 bits ] toIdx // 6 bytes
184// [ 48 bits ] fromIdx // 6 bytes
185// Total bits compressed data: 217 bits // 28 bytes in *big.Int representation
186func (tx *PoolL2Tx) TxCompressedDataV2() (*big.Int, error) {
187 if tx.Amount == nil {
188 tx.Amount = big.NewInt(0)
189 }
190 amountFloat40, err := NewFloat40(tx.Amount)
191 if err != nil {
192 return nil, tracerr.Wrap(err)
193 }
194 amountFloat40Bytes, err := amountFloat40.Bytes()
195 if err != nil {
196 return nil, tracerr.Wrap(err)
197 }
198
199 var b [28]byte
200 toBJJSign := byte(0)
201 if tx.ToBJJ != EmptyBJJComp {
202 sign, _ := babyjub.UnpackSignY(tx.ToBJJ)
203 if sign {
204 toBJJSign = byte(1)
205 }
206 }
207 b[0] = toBJJSign
208 b[1] = byte(tx.Fee)
209 nonceBytes, err := tx.Nonce.Bytes()
210 if err != nil {
211 return nil, tracerr.Wrap(err)
212 }
213 copy(b[2:7], nonceBytes[:])
214 copy(b[7:11], tx.TokenID.Bytes())
215 copy(b[11:16], amountFloat40Bytes)
216 toIdxBytes, err := tx.ToIdx.Bytes()
217 if err != nil {
218 return nil, tracerr.Wrap(err)
219 }
220 copy(b[16:22], toIdxBytes[:])
221 fromIdxBytes, err := tx.FromIdx.Bytes()
222 if err != nil {
223 return nil, tracerr.Wrap(err)
224 }
225 copy(b[22:28], fromIdxBytes[:])
226
227 bi := new(big.Int).SetBytes(b[:])
228 return bi, nil
229}
230
231// RqTxCompressedDataV2 is like the TxCompressedDataV2 but using the 'Rq'
232// parameters. In a future iteration of the hermez-node, the 'Rq' parameters
233// can be inside a struct, which contains the 'Rq' transaction grouped inside,
234// so then computing the 'RqTxCompressedDataV2' would be just calling
235// 'tx.Rq.TxCompressedDataV2()'.
236// RqTxCompressedDataV2 spec:
237// [ 1 bits ] rqToBJJSign // 1 byte
238// [ 8 bits ] rqUserFee // 1 byte
239// [ 40 bits ] rqNonce // 5 bytes
240// [ 32 bits ] rqTokenID // 4 bytes
241// [ 40 bits ] rqAmountFloat40 // 5 bytes
242// [ 48 bits ] rqToIdx // 6 bytes
243// [ 48 bits ] rqFromIdx // 6 bytes
244// Total bits compressed data: 217 bits // 28 bytes in *big.Int representation
245func (tx *PoolL2Tx) RqTxCompressedDataV2() (*big.Int, error) {
246 if tx.RqAmount == nil {
247 tx.RqAmount = big.NewInt(0)
248 }
249 amountFloat40, err := NewFloat40(tx.RqAmount)
250 if err != nil {
251 return nil, tracerr.Wrap(err)
252 }
253 amountFloat40Bytes, err := amountFloat40.Bytes()
254 if err != nil {
255 return nil, tracerr.Wrap(err)
256 }
257
258 var b [28]byte
259 rqToBJJSign := byte(0)
260 if tx.RqToBJJ != EmptyBJJComp {
261 sign, _ := babyjub.UnpackSignY(tx.RqToBJJ)
262 if sign {
263 rqToBJJSign = byte(1)
264 }
265 }
266 b[0] = rqToBJJSign
267 b[1] = byte(tx.RqFee)
268 nonceBytes, err := tx.RqNonce.Bytes()
269 if err != nil {
270 return nil, tracerr.Wrap(err)
271 }
272 copy(b[2:7], nonceBytes[:])
273 copy(b[7:11], tx.RqTokenID.Bytes())
274 copy(b[11:16], amountFloat40Bytes)
275 toIdxBytes, err := tx.RqToIdx.Bytes()
276 if err != nil {
277 return nil, tracerr.Wrap(err)
278 }
279 copy(b[16:22], toIdxBytes[:])
280 fromIdxBytes, err := tx.RqFromIdx.Bytes()
281 if err != nil {
282 return nil, tracerr.Wrap(err)
283 }
284 copy(b[22:28], fromIdxBytes[:])
285
286 bi := new(big.Int).SetBytes(b[:])
287 return bi, nil
288}
289
290// HashToSign returns the computed Poseidon hash from the *PoolL2Tx that will
291// be signed by the sender.
292func (tx *PoolL2Tx) HashToSign(chainID uint16) (*big.Int, error) {
293 toCompressedData, err := tx.TxCompressedData(chainID)
294 if err != nil {
295 return nil, tracerr.Wrap(err)
296 }
297
298 // e1: [5 bytes AmountFloat40 | 20 bytes ToEthAddr]
299 var e1B [25]byte
300 amountFloat40, err := NewFloat40(tx.Amount)
301 if err != nil {
302 return nil, tracerr.Wrap(err)
303 }
304 amountFloat40Bytes, err := amountFloat40.Bytes()
305 if err != nil {
306 return nil, tracerr.Wrap(err)
307 }
308 copy(e1B[0:5], amountFloat40Bytes)
309 copy(e1B[5:25], tx.ToEthAddr[:])
310 e1 := new(big.Int).SetBytes(e1B[:])
311 rqToEthAddr := EthAddrToBigInt(tx.RqToEthAddr)
312
313 _, toBJJY := babyjub.UnpackSignY(tx.ToBJJ)
314
315 rqTxCompressedDataV2, err := tx.RqTxCompressedDataV2()
316 if err != nil {
317 return nil, tracerr.Wrap(err)
318 }
319
320 _, rqToBJJY := babyjub.UnpackSignY(tx.RqToBJJ)
321
322 return poseidon.Hash([]*big.Int{toCompressedData, e1, toBJJY, rqTxCompressedDataV2,
323 rqToEthAddr, rqToBJJY})
324}
325
326// VerifySignature returns true if the signature verification is correct for the given PublicKeyComp
327func (tx *PoolL2Tx) VerifySignature(chainID uint16, pkComp babyjub.PublicKeyComp) bool {
328 h, err := tx.HashToSign(chainID)
329 if err != nil {
330 return false
331 }
332 s, err := tx.Signature.Decompress()
333 if err != nil {
334 return false
335 }
336 pk, err := pkComp.Decompress()
337 if err != nil {
338 return false
339 }
340 return pk.VerifyPoseidon(h, s)
341}
342
343// L2Tx returns a *L2Tx from the PoolL2Tx
344func (tx PoolL2Tx) L2Tx() L2Tx {
345 var toIdx Idx
346 if tx.ToIdx == Idx(0) {
347 toIdx = tx.AuxToIdx
348 } else {
349 toIdx = tx.ToIdx
350 }
351 return L2Tx{
352 TxID: tx.TxID,
353 FromIdx: tx.FromIdx,
354 ToIdx: toIdx,
355 TokenID: tx.TokenID,
356 Amount: tx.Amount,
357 Fee: tx.Fee,
358 Nonce: tx.Nonce,
359 Type: tx.Type,
360 }
361}
362
363// Tx returns a *Tx from the PoolL2Tx
364func (tx PoolL2Tx) Tx() Tx {
365 return Tx{
366 TxID: tx.TxID,
367 FromIdx: tx.FromIdx,
368 ToIdx: tx.ToIdx,
369 Amount: tx.Amount,
370 TokenID: tx.TokenID,
371 Nonce: &tx.Nonce,
372 Fee: &tx.Fee,
373 Type: tx.Type,
374 }
375}
376
377// PoolL2TxsToL2Txs returns an array of []L2Tx from an array of []PoolL2Tx
378func PoolL2TxsToL2Txs(txs []PoolL2Tx) ([]L2Tx, error) {
379 l2Txs := make([]L2Tx, len(txs))
380 for i, poolTx := range txs {
381 l2Txs[i] = poolTx.L2Tx()
382 }
383 return l2Txs, nil
384}
385
386// TxIDsFromPoolL2Txs returns an array of TxID from the []PoolL2Tx
387func TxIDsFromPoolL2Txs(txs []PoolL2Tx) []TxID {
388 txIDs := make([]TxID, len(txs))
389 for i, tx := range txs {
390 txIDs[i] = tx.TxID
391 }
392 return txIDs
393}
394
395// PoolL2TxState is a string that represents the status of a L2 transaction
396type PoolL2TxState string
397
398const (
399 // PoolL2TxStatePending represents a valid L2Tx that hasn't started the
400 // forging process
401 PoolL2TxStatePending PoolL2TxState = "pend"
402 // PoolL2TxStateForging represents a valid L2Tx that has started the
403 // forging process
404 PoolL2TxStateForging PoolL2TxState = "fing"
405 // PoolL2TxStateForged represents a L2Tx that has already been forged
406 PoolL2TxStateForged PoolL2TxState = "fged"
407 // PoolL2TxStateInvalid represents a L2Tx that has been invalidated
408 PoolL2TxStateInvalid PoolL2TxState = "invl"
409)