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 }
105 } else {
106 return tracerr.Wrap(errors.New("malformed transaction"))
107 }
108 return nil
109}
110
111// SetID sets the ID of the transaction
112func (tx *PoolL2Tx) SetID() error {
113 txID, err := tx.L2Tx().CalculateTxID()
114 if err != nil {
115 return tracerr.Wrap(err)
116 }
117 tx.TxID = txID
118 return nil
119}
120
121// TxCompressedData spec:
122// [ 1 bits ] toBJJSign // 1 byte
123// [ 8 bits ] userFee // 1 byte
124// [ 40 bits ] nonce // 5 bytes
125// [ 32 bits ] tokenID // 4 bytes
126// [ 48 bits ] toIdx // 6 bytes
127// [ 48 bits ] fromIdx // 6 bytes
128// [ 16 bits ] chainId // 2 bytes
129// [ 32 bits ] signatureConstant // 4 bytes
130// Total bits compressed data: 225 bits // 29 bytes in *big.Int representation
131func (tx *PoolL2Tx) TxCompressedData(chainID uint16) (*big.Int, error) {
132 var b [29]byte
133
134 toBJJSign := byte(0)
135 pkSign, _ := babyjub.UnpackSignY(tx.ToBJJ)
136 if pkSign {
137 toBJJSign = byte(1)
138 }
139
140 b[0] = toBJJSign
141 b[1] = byte(tx.Fee)
142 nonceBytes, err := tx.Nonce.Bytes()
143 if err != nil {
144 return nil, tracerr.Wrap(err)
145 }
146 copy(b[2:7], nonceBytes[:])
147 copy(b[7:11], tx.TokenID.Bytes())
148 toIdxBytes, err := tx.ToIdx.Bytes()
149 if err != nil {
150 return nil, tracerr.Wrap(err)
151 }
152 copy(b[11:17], toIdxBytes[:])
153 fromIdxBytes, err := tx.FromIdx.Bytes()
154 if err != nil {
155 return nil, tracerr.Wrap(err)
156 }
157 copy(b[17:23], fromIdxBytes[:])
158 binary.BigEndian.PutUint16(b[23:25], chainID)
159 copy(b[25:29], SignatureConstantBytes[:])
160
161 bi := new(big.Int).SetBytes(b[:])
162 return bi, nil
163}
164
165// TxCompressedDataEmpty calculates the TxCompressedData of an empty
166// transaction
167func TxCompressedDataEmpty(chainID uint16) *big.Int {
168 var b [29]byte
169 binary.BigEndian.PutUint16(b[23:25], chainID)
170 copy(b[25:29], SignatureConstantBytes[:])
171 bi := new(big.Int).SetBytes(b[:])
172 return bi
173}
174
175// TxCompressedDataV2 spec:
176// [ 1 bits ] toBJJSign // 1 byte
177// [ 8 bits ] userFee // 1 byte
178// [ 40 bits ] nonce // 5 bytes
179// [ 32 bits ] tokenID // 4 bytes
180// [ 40 bits ] amountFloat40 // 5 bytes
181// [ 48 bits ] toIdx // 6 bytes
182// [ 48 bits ] fromIdx // 6 bytes
183// Total bits compressed data: 217 bits // 28 bytes in *big.Int representation
184func (tx *PoolL2Tx) TxCompressedDataV2() (*big.Int, error) {
185 if tx.Amount == nil {
186 tx.Amount = big.NewInt(0)
187 }
188 amountFloat40, err := NewFloat40(tx.Amount)
189 if err != nil {
190 return nil, tracerr.Wrap(err)
191 }
192 amountFloat40Bytes, err := amountFloat40.Bytes()
193 if err != nil {
194 return nil, tracerr.Wrap(err)
195 }
196
197 var b [28]byte
198 toBJJSign := byte(0)
199 if tx.ToBJJ != EmptyBJJComp {
200 sign, _ := babyjub.UnpackSignY(tx.ToBJJ)
201 if sign {
202 toBJJSign = byte(1)
203 }
204 }
205 b[0] = toBJJSign
206 b[1] = byte(tx.Fee)
207 nonceBytes, err := tx.Nonce.Bytes()
208 if err != nil {
209 return nil, tracerr.Wrap(err)
210 }
211 copy(b[2:7], nonceBytes[:])
212 copy(b[7:11], tx.TokenID.Bytes())
213 copy(b[11:16], amountFloat40Bytes)
214 toIdxBytes, err := tx.ToIdx.Bytes()
215 if err != nil {
216 return nil, tracerr.Wrap(err)
217 }
218 copy(b[16:22], toIdxBytes[:])
219 fromIdxBytes, err := tx.FromIdx.Bytes()
220 if err != nil {
221 return nil, tracerr.Wrap(err)
222 }
223 copy(b[22:28], fromIdxBytes[:])
224
225 bi := new(big.Int).SetBytes(b[:])
226 return bi, nil
227}
228
229// RqTxCompressedDataV2 is like the TxCompressedDataV2 but using the 'Rq'
230// parameters. In a future iteration of the hermez-node, the 'Rq' parameters
231// can be inside a struct, which contains the 'Rq' transaction grouped inside,
232// so then computing the 'RqTxCompressedDataV2' would be just calling
233// 'tx.Rq.TxCompressedDataV2()'.
234// RqTxCompressedDataV2 spec:
235// [ 1 bits ] rqToBJJSign // 1 byte
236// [ 8 bits ] rqUserFee // 1 byte
237// [ 40 bits ] rqNonce // 5 bytes
238// [ 32 bits ] rqTokenID // 4 bytes
239// [ 40 bits ] rqAmountFloat40 // 5 bytes
240// [ 48 bits ] rqToIdx // 6 bytes
241// [ 48 bits ] rqFromIdx // 6 bytes
242// Total bits compressed data: 217 bits // 28 bytes in *big.Int representation
243func (tx *PoolL2Tx) RqTxCompressedDataV2() (*big.Int, error) {
244 if tx.RqAmount == nil {
245 tx.RqAmount = big.NewInt(0)
246 }
247 amountFloat40, err := NewFloat40(tx.RqAmount)
248 if err != nil {
249 return nil, tracerr.Wrap(err)
250 }
251 amountFloat40Bytes, err := amountFloat40.Bytes()
252 if err != nil {
253 return nil, tracerr.Wrap(err)
254 }
255
256 var b [28]byte
257 rqToBJJSign := byte(0)
258 if tx.RqToBJJ != EmptyBJJComp {
259 sign, _ := babyjub.UnpackSignY(tx.RqToBJJ)
260 if sign {
261 rqToBJJSign = byte(1)
262 }
263 }
264 b[0] = rqToBJJSign
265 b[1] = byte(tx.RqFee)
266 nonceBytes, err := tx.RqNonce.Bytes()
267 if err != nil {
268 return nil, tracerr.Wrap(err)
269 }
270 copy(b[2:7], nonceBytes[:])
271 copy(b[7:11], tx.RqTokenID.Bytes())
272 copy(b[11:16], amountFloat40Bytes)
273 toIdxBytes, err := tx.RqToIdx.Bytes()
274 if err != nil {
275 return nil, tracerr.Wrap(err)
276 }
277 copy(b[16:22], toIdxBytes[:])
278 fromIdxBytes, err := tx.RqFromIdx.Bytes()
279 if err != nil {
280 return nil, tracerr.Wrap(err)
281 }
282 copy(b[22:28], fromIdxBytes[:])
283
284 bi := new(big.Int).SetBytes(b[:])
285 return bi, nil
286}
287
288// HashToSign returns the computed Poseidon hash from the *PoolL2Tx that will
289// be signed by the sender.
290func (tx *PoolL2Tx) HashToSign(chainID uint16) (*big.Int, error) {
291 toCompressedData, err := tx.TxCompressedData(chainID)
292 if err != nil {
293 return nil, tracerr.Wrap(err)
294 }
295
296 // e1: [5 bytes AmountFloat40 | 20 bytes ToEthAddr]
297 var e1B [25]byte
298 amountFloat40, err := NewFloat40(tx.Amount)
299 if err != nil {
300 return nil, tracerr.Wrap(err)
301 }
302 amountFloat40Bytes, err := amountFloat40.Bytes()
303 if err != nil {
304 return nil, tracerr.Wrap(err)
305 }
306 copy(e1B[0:5], amountFloat40Bytes)
307 copy(e1B[5:25], tx.ToEthAddr[:])
308 e1 := new(big.Int).SetBytes(e1B[:])
309 rqToEthAddr := EthAddrToBigInt(tx.RqToEthAddr)
310
311 _, toBJJY := babyjub.UnpackSignY(tx.ToBJJ)
312
313 rqTxCompressedDataV2, err := tx.RqTxCompressedDataV2()
314 if err != nil {
315 return nil, tracerr.Wrap(err)
316 }
317
318 _, rqToBJJY := babyjub.UnpackSignY(tx.RqToBJJ)
319
320 return poseidon.Hash([]*big.Int{toCompressedData, e1, toBJJY, rqTxCompressedDataV2,
321 rqToEthAddr, rqToBJJY})
322}
323
324// VerifySignature returns true if the signature verification is correct for the given PublicKeyComp
325func (tx *PoolL2Tx) VerifySignature(chainID uint16, pkComp babyjub.PublicKeyComp) bool {
326 h, err := tx.HashToSign(chainID)
327 if err != nil {
328 return false
329 }
330 s, err := tx.Signature.Decompress()
331 if err != nil {
332 return false
333 }
334 pk, err := pkComp.Decompress()
335 if err != nil {
336 return false
337 }
338 return pk.VerifyPoseidon(h, s)
339}
340
341// L2Tx returns a *L2Tx from the PoolL2Tx
342func (tx PoolL2Tx) L2Tx() L2Tx {
343 var toIdx Idx
344 if tx.ToIdx == Idx(0) {
345 toIdx = tx.AuxToIdx
346 } else {
347 toIdx = tx.ToIdx
348 }
349 return L2Tx{
350 TxID: tx.TxID,
351 FromIdx: tx.FromIdx,
352 ToIdx: toIdx,
353 TokenID: tx.TokenID,
354 Amount: tx.Amount,
355 Fee: tx.Fee,
356 Nonce: tx.Nonce,
357 Type: tx.Type,
358 }
359}
360
361// Tx returns a *Tx from the PoolL2Tx
362func (tx PoolL2Tx) Tx() Tx {
363 return Tx{
364 TxID: tx.TxID,
365 FromIdx: tx.FromIdx,
366 ToIdx: tx.ToIdx,
367 Amount: tx.Amount,
368 TokenID: tx.TokenID,
369 Nonce: &tx.Nonce,
370 Fee: &tx.Fee,
371 Type: tx.Type,
372 }
373}
374
375// PoolL2TxsToL2Txs returns an array of []L2Tx from an array of []PoolL2Tx
376func PoolL2TxsToL2Txs(txs []PoolL2Tx) ([]L2Tx, error) {
377 l2Txs := make([]L2Tx, len(txs))
378 for i, poolTx := range txs {
379 l2Txs[i] = poolTx.L2Tx()
380 }
381 return l2Txs, nil
382}
383
384// TxIDsFromPoolL2Txs returns an array of TxID from the []PoolL2Tx
385func TxIDsFromPoolL2Txs(txs []PoolL2Tx) []TxID {
386 txIDs := make([]TxID, len(txs))
387 for i, tx := range txs {
388 txIDs[i] = tx.TxID
389 }
390 return txIDs
391}
392
393// PoolL2TxState is a string that represents the status of a L2 transaction
394type PoolL2TxState string
395
396const (
397 // PoolL2TxStatePending represents a valid L2Tx that hasn't started the
398 // forging process
399 PoolL2TxStatePending PoolL2TxState = "pend"
400 // PoolL2TxStateForging represents a valid L2Tx that has started the
401 // forging process
402 PoolL2TxStateForging PoolL2TxState = "fing"
403 // PoolL2TxStateForged represents a L2Tx that has already been forged
404 PoolL2TxStateForged PoolL2TxState = "fged"
405 // PoolL2TxStateInvalid represents a L2Tx that has been invalidated
406 PoolL2TxStateInvalid PoolL2TxState = "invl"
407)