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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
20
21// PoolL2Tx is a struct that represents a L2Tx sent by an account to the
22// coordinator that is waiting to be forged
23type PoolL2Tx struct {
24 // Stored in DB: mandatory fileds
25
26 // TxID (12 bytes) for L2Tx is:
27 // bytes: | 1 | 6 | 5 |
28 // values: | type | FromIdx | Nonce |
29 TxID TxID `meddler:"tx_id"`
30 FromIdx Idx `meddler:"from_idx"`
31 ToIdx Idx `meddler:"to_idx,zeroisnull"`
32 // AuxToIdx is only used internally at the StateDB to avoid repeated
33 // computation when processing transactions (from Synchronizer,
34 // TxSelector, BatchBuilder)
35 AuxToIdx Idx `meddler:"-"`
36 ToEthAddr ethCommon.Address `meddler:"to_eth_addr,zeroisnull"`
37 ToBJJ babyjub.PublicKeyComp `meddler:"to_bjj,zeroisnull"`
38 TokenID TokenID `meddler:"token_id"`
39 Amount *big.Int `meddler:"amount,bigint"`
40 Fee FeeSelector `meddler:"fee"`
41 Nonce Nonce `meddler:"nonce"` // effective 40 bits used
42 State PoolL2TxState `meddler:"state"`
43 // Info contains information about the status & State of the
44 // transaction. As for example, if the Tx has not been selected in the
45 // last batch due not enough Balance at the Sender account, this reason
46 // would appear at this parameter.
47 Info string `meddler:"info,zeroisnull"`
48 Signature babyjub.SignatureComp `meddler:"signature"` // tx signature
49 Timestamp time.Time `meddler:"timestamp,utctime"` // time when added to the tx pool
50 // Stored in DB: optional fileds, may be uninitialized
51 RqFromIdx Idx `meddler:"rq_from_idx,zeroisnull"`
52 RqToIdx Idx `meddler:"rq_to_idx,zeroisnull"`
53 RqToEthAddr ethCommon.Address `meddler:"rq_to_eth_addr,zeroisnull"`
54 RqToBJJ babyjub.PublicKeyComp `meddler:"rq_to_bjj,zeroisnull"`
55 RqTokenID TokenID `meddler:"rq_token_id,zeroisnull"`
56 RqAmount *big.Int `meddler:"rq_amount,bigintnull"`
57 RqFee FeeSelector `meddler:"rq_fee,zeroisnull"`
58 RqNonce Nonce `meddler:"rq_nonce,zeroisnull"` // effective 48 bits used
59 AbsoluteFee float64 `meddler:"fee_usd,zeroisnull"`
60 AbsoluteFeeUpdate time.Time `meddler:"usd_update,utctimez"`
61 Type TxType `meddler:"tx_type"`
62 // Extra metadata, may be uninitialized
63 RqTxCompressedData []byte `meddler:"-"` // 253 bits, optional for atomic txs
64}
65
66// NewPoolL2Tx returns the given L2Tx with the TxId & Type parameters calculated
67// from the L2Tx values
68func NewPoolL2Tx(tx *PoolL2Tx) (*PoolL2Tx, error) {
69 txTypeOld := tx.Type
70 if err := tx.SetType(); err != nil {
71 return nil, tracerr.Wrap(err)
72 }
73 // If original Type doesn't match the correct one, return error
74 if txTypeOld != "" && txTypeOld != tx.Type {
75 return nil, tracerr.Wrap(fmt.Errorf("L2Tx.Type: %s, should be: %s",
76 txTypeOld, tx.Type))
77 }
78
79 txIDOld := tx.TxID
80 if err := tx.SetID(); err != nil {
81 return nil, tracerr.Wrap(err)
82 }
83 // If original TxID doesn't match the correct one, return error
84 if txIDOld != (TxID{}) && txIDOld != tx.TxID {
85 return tx, tracerr.Wrap(fmt.Errorf("PoolL2Tx.TxID: %s, should be: %s",
86 txIDOld.String(), tx.TxID.String()))
87 }
88
89 return tx, nil
90}
91
92// SetType sets the type of the transaction
93func (tx *PoolL2Tx) SetType() error {
94 if tx.ToIdx >= IdxUserThreshold {
95 tx.Type = TxTypeTransfer
96 } else if tx.ToIdx == 1 {
97 tx.Type = TxTypeExit
98 } else if tx.ToIdx == 0 {
99 if tx.ToBJJ != EmptyBJJComp && tx.ToEthAddr == FFAddr {
100 tx.Type = TxTypeTransferToBJJ
101 } else if tx.ToEthAddr != FFAddr && tx.ToEthAddr != EmptyAddr {
102 tx.Type = TxTypeTransferToEthAddr
103 }
104 } else {
105 return tracerr.Wrap(errors.New("malformed transaction"))
106 }
107 return nil
108}
109
110// SetID sets the ID of the transaction
111func (tx *PoolL2Tx) SetID() error {
112 txID, err := tx.L2Tx().CalculateTxID()
113 if err != nil {
114 return tracerr.Wrap(err)
115 }
116 tx.TxID = txID
117 return nil
118}
119
120// TxCompressedData spec:
121// [ 1 bits ] toBJJSign // 1 byte
122// [ 8 bits ] userFee // 1 byte
123// [ 40 bits ] nonce // 5 bytes
124// [ 32 bits ] tokenID // 4 bytes
125// [ 48 bits ] toIdx // 6 bytes
126// [ 48 bits ] fromIdx // 6 bytes
127// [ 16 bits ] chainId // 2 bytes
128// [ 32 bits ] signatureConstant // 4 bytes
129// Total bits compressed data: 225 bits // 29 bytes in *big.Int representation
130func (tx *PoolL2Tx) TxCompressedData(chainID uint16) (*big.Int, error) {
131 var b [29]byte
132
133 toBJJSign := byte(0)
134 pkSign, _ := babyjub.UnpackSignY(tx.ToBJJ)
135 if pkSign {
136 toBJJSign = byte(1)
137 }
138
139 b[0] = toBJJSign
140 b[1] = byte(tx.Fee)
141 nonceBytes, err := tx.Nonce.Bytes()
142 if err != nil {
143 return nil, tracerr.Wrap(err)
144 }
145 copy(b[2:7], nonceBytes[:])
146 copy(b[7:11], tx.TokenID.Bytes())
147 toIdxBytes, err := tx.ToIdx.Bytes()
148 if err != nil {
149 return nil, tracerr.Wrap(err)
150 }
151 copy(b[11:17], toIdxBytes[:])
152 fromIdxBytes, err := tx.FromIdx.Bytes()
153 if err != nil {
154 return nil, tracerr.Wrap(err)
155 }
156 copy(b[17:23], fromIdxBytes[:])
157 binary.BigEndian.PutUint16(b[23:25], chainID)
158 copy(b[25:29], SignatureConstantBytes[:])
159
160 bi := new(big.Int).SetBytes(b[:])
161 return bi, nil
162}
163
164// TxCompressedDataEmpty calculates the TxCompressedData of an empty
165// transaction
166func TxCompressedDataEmpty(chainID uint16) *big.Int {
167 var b [29]byte
168 binary.BigEndian.PutUint16(b[23:25], chainID)
169 copy(b[25:29], SignatureConstantBytes[:])
170 bi := new(big.Int).SetBytes(b[:])
171 return bi
172}
173
174// TxCompressedDataV2 spec:
175// [ 1 bits ] toBJJSign // 1 byte
176// [ 8 bits ] userFee // 1 byte
177// [ 40 bits ] nonce // 5 bytes
178// [ 32 bits ] tokenID // 4 bytes
179// [ 40 bits ] amountFloat40 // 5 bytes
180// [ 48 bits ] toIdx // 6 bytes
181// [ 48 bits ] fromIdx // 6 bytes
182// Total bits compressed data: 217 bits // 28 bytes in *big.Int representation
183func (tx *PoolL2Tx) TxCompressedDataV2() (*big.Int, error) {
184 if tx.Amount == nil {
185 tx.Amount = big.NewInt(0)
186 }
187 amountFloat40, err := NewFloat40(tx.Amount)
188 if err != nil {
189 return nil, tracerr.Wrap(err)
190 }
191 amountFloat40Bytes, err := amountFloat40.Bytes()
192 if err != nil {
193 return nil, tracerr.Wrap(err)
194 }
195
196 var b [28]byte
197 toBJJSign := byte(0)
198 if tx.ToBJJ != EmptyBJJComp {
199 sign, _ := babyjub.UnpackSignY(tx.ToBJJ)
200 if sign {
201 toBJJSign = byte(1)
202 }
203 }
204 b[0] = toBJJSign
205 b[1] = byte(tx.Fee)
206 nonceBytes, err := tx.Nonce.Bytes()
207 if err != nil {
208 return nil, tracerr.Wrap(err)
209 }
210 copy(b[2:7], nonceBytes[:])
211 copy(b[7:11], tx.TokenID.Bytes())
212 copy(b[11:16], amountFloat40Bytes)
213 toIdxBytes, err := tx.ToIdx.Bytes()
214 if err != nil {
215 return nil, tracerr.Wrap(err)
216 }
217 copy(b[16:22], toIdxBytes[:])
218 fromIdxBytes, err := tx.FromIdx.Bytes()
219 if err != nil {
220 return nil, tracerr.Wrap(err)
221 }
222 copy(b[22:28], fromIdxBytes[:])
223
224 bi := new(big.Int).SetBytes(b[:])
225 return bi, nil
226}
227
228// RqTxCompressedDataV2 is like the TxCompressedDataV2 but using the 'Rq'
229// parameters. In a future iteration of the hermez-node, the 'Rq' parameters
230// can be inside a struct, which contains the 'Rq' transaction grouped inside,
231// so then computing the 'RqTxCompressedDataV2' would be just calling
232// 'tx.Rq.TxCompressedDataV2()'.
233// RqTxCompressedDataV2 spec:
234// [ 1 bits ] rqToBJJSign // 1 byte
235// [ 8 bits ] rqUserFee // 1 byte
236// [ 40 bits ] rqNonce // 5 bytes
237// [ 32 bits ] rqTokenID // 4 bytes
238// [ 40 bits ] rqAmountFloat40 // 5 bytes
239// [ 48 bits ] rqToIdx // 6 bytes
240// [ 48 bits ] rqFromIdx // 6 bytes
241// Total bits compressed data: 217 bits // 28 bytes in *big.Int representation
242func (tx *PoolL2Tx) RqTxCompressedDataV2() (*big.Int, error) {
243 if tx.RqAmount == nil {
244 tx.RqAmount = big.NewInt(0)
245 }
246 amountFloat40, err := NewFloat40(tx.RqAmount)
247 if err != nil {
248 return nil, tracerr.Wrap(err)
249 }
250 amountFloat40Bytes, err := amountFloat40.Bytes()
251 if err != nil {
252 return nil, tracerr.Wrap(err)
253 }
254
255 var b [28]byte
256 rqToBJJSign := byte(0)
257 if tx.RqToBJJ != EmptyBJJComp {
258 sign, _ := babyjub.UnpackSignY(tx.RqToBJJ)
259 if sign {
260 rqToBJJSign = byte(1)
261 }
262 }
263 b[0] = rqToBJJSign
264 b[1] = byte(tx.RqFee)
265 nonceBytes, err := tx.RqNonce.Bytes()
266 if err != nil {
267 return nil, tracerr.Wrap(err)
268 }
269 copy(b[2:7], nonceBytes[:])
270 copy(b[7:11], tx.RqTokenID.Bytes())
271 copy(b[11:16], amountFloat40Bytes)
272 toIdxBytes, err := tx.RqToIdx.Bytes()
273 if err != nil {
274 return nil, tracerr.Wrap(err)
275 }
276 copy(b[16:22], toIdxBytes[:])
277 fromIdxBytes, err := tx.RqFromIdx.Bytes()
278 if err != nil {
279 return nil, tracerr.Wrap(err)
280 }
281 copy(b[22:28], fromIdxBytes[:])
282
283 bi := new(big.Int).SetBytes(b[:])
284 return bi, nil
285}
286
287// HashToSign returns the computed Poseidon hash from the *PoolL2Tx that will
288// be signed by the sender.
289func (tx *PoolL2Tx) HashToSign(chainID uint16) (*big.Int, error) {
290 toCompressedData, err := tx.TxCompressedData(chainID)
291 if err != nil {
292 return nil, tracerr.Wrap(err)
293 }
294
295 // e1: [5 bytes AmountFloat40 | 20 bytes ToEthAddr]
296 var e1B [25]byte
297 amountFloat40, err := NewFloat40(tx.Amount)
298 if err != nil {
299 return nil, tracerr.Wrap(err)
300 }
301 amountFloat40Bytes, err := amountFloat40.Bytes()
302 if err != nil {
303 return nil, tracerr.Wrap(err)
304 }
305 copy(e1B[0:5], amountFloat40Bytes)
306 toEthAddr := EthAddrToBigInt(tx.ToEthAddr)
307 copy(e1B[5:25], toEthAddr.Bytes())
308 e1 := new(big.Int).SetBytes(e1B[:])
309
310 rqToEthAddr := EthAddrToBigInt(tx.RqToEthAddr)
311
312 _, toBJJY := babyjub.UnpackSignY(tx.ToBJJ)
313
314 rqTxCompressedDataV2, err := tx.RqTxCompressedDataV2()
315 if err != nil {
316 return nil, tracerr.Wrap(err)
317 }
318
319 _, rqToBJJY := babyjub.UnpackSignY(tx.RqToBJJ)
320
321 return poseidon.Hash([]*big.Int{toCompressedData, e1, toBJJY, rqTxCompressedDataV2, 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)