hermez-node/common/pooll2tx.go
common/pooll2tx.goRaw
1package common
2
3import (
4 "errors"
5 "fmt"
6 "math/big"
7 "time"
8
9 ethCommon "github.com/ethereum/go-ethereum/common"
10 "github.com/hermeznetwork/tracerr"
11 "github.com/iden3/go-iden3-crypto/babyjub"
12 "github.com/iden3/go-iden3-crypto/poseidon"
13)
14
15// PoolL2Tx is a struct that represents a L2Tx sent by an account to the coordinator hat is waiting to be forged
16type PoolL2Tx struct {
17 // Stored in DB: mandatory fileds
18
19 // TxID (12 bytes) for L2Tx is:
20 // bytes: | 1 | 6 | 5 |
21 // values: | type | FromIdx | Nonce |
22 TxID TxID `meddler:"tx_id"`
23 FromIdx Idx `meddler:"from_idx"`
24 ToIdx Idx `meddler:"to_idx,zeroisnull"`
25 AuxToIdx Idx `meddler:"-"` // AuxToIdx is only used internally at the StateDB to avoid repeated computation when processing transactions (from Synchronizer, TxSelector, BatchBuilder)
26 ToEthAddr ethCommon.Address `meddler:"to_eth_addr,zeroisnull"`
27 ToBJJ *babyjub.PublicKey `meddler:"to_bjj"`
28 TokenID TokenID `meddler:"token_id"`
29 Amount *big.Int `meddler:"amount,bigint"` // TODO: change to float16
30 Fee FeeSelector `meddler:"fee"`
31 Nonce Nonce `meddler:"nonce"` // effective 40 bits used
32 State PoolL2TxState `meddler:"state"`
33 Signature babyjub.SignatureComp `meddler:"signature"` // tx signature
34 Timestamp time.Time `meddler:"timestamp,utctime"` // time when added to the tx pool
35 // Stored in DB: optional fileds, may be uninitialized
36 RqFromIdx Idx `meddler:"rq_from_idx,zeroisnull"` // FromIdx is used by L1Tx/Deposit to indicate the Idx receiver of the L1Tx.DepositAmount (deposit)
37 RqToIdx Idx `meddler:"rq_to_idx,zeroisnull"` // FromIdx is used by L1Tx/Deposit to indicate the Idx receiver of the L1Tx.DepositAmount (deposit)
38 RqToEthAddr ethCommon.Address `meddler:"rq_to_eth_addr,zeroisnull"`
39 RqToBJJ *babyjub.PublicKey `meddler:"rq_to_bjj"` // TODO: stop using json, use scanner/valuer
40 RqTokenID TokenID `meddler:"rq_token_id,zeroisnull"`
41 RqAmount *big.Int `meddler:"rq_amount,bigintnull"` // TODO: change to float16
42 RqFee FeeSelector `meddler:"rq_fee,zeroisnull"`
43 RqNonce Nonce `meddler:"rq_nonce,zeroisnull"` // effective 48 bits used
44 AbsoluteFee float64 `meddler:"fee_usd,zeroisnull"`
45 AbsoluteFeeUpdate time.Time `meddler:"usd_update,utctimez"`
46 Type TxType `meddler:"tx_type"`
47 // Extra metadata, may be uninitialized
48 RqTxCompressedData []byte `meddler:"-"` // 253 bits, optional for atomic txs
49}
50
51// NewPoolL2Tx returns the given L2Tx with the TxId & Type parameters calculated
52// from the L2Tx values
53func NewPoolL2Tx(poolL2Tx *PoolL2Tx) (*PoolL2Tx, error) {
54 // calculate TxType
55 var txType TxType
56 if poolL2Tx.ToIdx >= IdxUserThreshold {
57 txType = TxTypeTransfer
58 } else if poolL2Tx.ToIdx == 1 {
59 txType = TxTypeExit
60 } else if poolL2Tx.ToIdx == 0 {
61 if poolL2Tx.ToBJJ != nil && poolL2Tx.ToEthAddr == FFAddr {
62 txType = TxTypeTransferToBJJ
63 } else if poolL2Tx.ToEthAddr != FFAddr && poolL2Tx.ToEthAddr != EmptyAddr {
64 txType = TxTypeTransferToEthAddr
65 }
66 } else {
67 return nil, tracerr.Wrap(errors.New("malformed transaction"))
68 }
69
70 // if TxType!=poolL2Tx.TxType return error
71 if poolL2Tx.Type != "" && poolL2Tx.Type != txType {
72 return poolL2Tx, tracerr.Wrap(fmt.Errorf("type: %s, should be: %s", poolL2Tx.Type, txType))
73 }
74 poolL2Tx.Type = txType
75
76 var txid [TxIDLen]byte
77 txid[0] = TxIDPrefixL2Tx
78 fromIdxBytes, err := poolL2Tx.FromIdx.Bytes()
79 if err != nil {
80 return poolL2Tx, tracerr.Wrap(err)
81 }
82 copy(txid[1:7], fromIdxBytes[:])
83 nonceBytes, err := poolL2Tx.Nonce.Bytes()
84 if err != nil {
85 return poolL2Tx, tracerr.Wrap(err)
86 }
87 copy(txid[7:12], nonceBytes[:])
88 txID := TxID(txid)
89
90 // if TxID!=poolL2Tx.TxID return error
91 if poolL2Tx.TxID != (TxID{}) && poolL2Tx.TxID != txID {
92 return poolL2Tx, tracerr.Wrap(fmt.Errorf("id: %s, should be: %s", poolL2Tx.TxID.String(), txID.String()))
93 }
94 poolL2Tx.TxID = txID
95 return poolL2Tx, nil
96}
97
98// TxCompressedData spec:
99// [ 1 bits ] toBJJSign // 1 byte
100// [ 8 bits ] userFee // 1 byte
101// [ 40 bits ] nonce // 5 bytes
102// [ 32 bits ] tokenID // 4 bytes
103// [ 16 bits ] amountFloat16 // 2 bytes
104// [ 48 bits ] toIdx // 6 bytes
105// [ 48 bits ] fromIdx // 6 bytes
106// [ 16 bits ] chainId // 2 bytes
107// [ 32 bits ] signatureConstant // 4 bytes
108// Total bits compressed data: 241 bits // 31 bytes in *big.Int representation
109func (tx *PoolL2Tx) TxCompressedData() (*big.Int, error) {
110 amountFloat16, err := NewFloat16(tx.Amount)
111 if err != nil {
112 return nil, tracerr.Wrap(err)
113 }
114 var b [31]byte
115 toBJJSign := byte(0)
116 if tx.ToBJJ != nil && babyjub.PointCoordSign(tx.ToBJJ.X) {
117 toBJJSign = byte(1)
118 }
119 b[0] = toBJJSign
120 b[1] = byte(tx.Fee)
121 nonceBytes, err := tx.Nonce.Bytes()
122 if err != nil {
123 return nil, tracerr.Wrap(err)
124 }
125 copy(b[2:7], nonceBytes[:])
126 copy(b[7:11], tx.TokenID.Bytes())
127 copy(b[11:13], amountFloat16.Bytes())
128 toIdxBytes, err := tx.ToIdx.Bytes()
129 if err != nil {
130 return nil, tracerr.Wrap(err)
131 }
132 copy(b[13:19], toIdxBytes[:])
133 fromIdxBytes, err := tx.FromIdx.Bytes()
134 if err != nil {
135 return nil, tracerr.Wrap(err)
136 }
137 copy(b[19:25], fromIdxBytes[:])
138 copy(b[25:27], []byte{0, 0}) // TODO this will be generated by the ChainID config parameter
139 copy(b[27:31], SignatureConstantBytes[:])
140
141 bi := new(big.Int).SetBytes(b[:])
142 return bi, nil
143}
144
145// TxCompressedDataV2 spec:
146// [ 1 bits ] toBJJSign // 1 byte
147// [ 8 bits ] userFee // 1 byte
148// [ 40 bits ] nonce // 5 bytes
149// [ 32 bits ] tokenID // 4 bytes
150// [ 16 bits ] amountFloat16 // 2 bytes
151// [ 48 bits ] toIdx // 6 bytes
152// [ 48 bits ] fromIdx // 6 bytes
153// Total bits compressed data: 193 bits // 25 bytes in *big.Int representation
154func (tx *PoolL2Tx) TxCompressedDataV2() (*big.Int, error) {
155 if tx.Amount == nil {
156 tx.Amount = big.NewInt(0)
157 }
158 amountFloat16, err := NewFloat16(tx.Amount)
159 if err != nil {
160 return nil, tracerr.Wrap(err)
161 }
162 var b [25]byte
163 toBJJSign := byte(0)
164 if tx.ToBJJ != nil && babyjub.PointCoordSign(tx.ToBJJ.X) {
165 toBJJSign = byte(1)
166 }
167 b[0] = toBJJSign
168 b[1] = byte(tx.Fee)
169 nonceBytes, err := tx.Nonce.Bytes()
170 if err != nil {
171 return nil, tracerr.Wrap(err)
172 }
173 copy(b[2:7], nonceBytes[:])
174 copy(b[7:11], tx.TokenID.Bytes())
175 copy(b[11:13], amountFloat16.Bytes())
176 toIdxBytes, err := tx.ToIdx.Bytes()
177 if err != nil {
178 return nil, tracerr.Wrap(err)
179 }
180 copy(b[13:19], toIdxBytes[:])
181 fromIdxBytes, err := tx.FromIdx.Bytes()
182 if err != nil {
183 return nil, tracerr.Wrap(err)
184 }
185 copy(b[19:25], fromIdxBytes[:])
186
187 bi := new(big.Int).SetBytes(b[:])
188 return bi, nil
189}
190
191// RqTxCompressedDataV2 is like the TxCompressedDataV2 but using the 'Rq'
192// parameters. In a future iteration of the hermez-node, the 'Rq' parameters
193// can be inside a struct, which contains the 'Rq' transaction grouped inside,
194// so then computing the 'RqTxCompressedDataV2' would be just calling
195// 'tx.Rq.TxCompressedDataV2()'.
196// RqTxCompressedDataV2 spec:
197// [ 1 bits ] rqToBJJSign // 1 byte
198// [ 8 bits ] rqUserFee // 1 byte
199// [ 40 bits ] rqNonce // 5 bytes
200// [ 32 bits ] rqTokenID // 4 bytes
201// [ 16 bits ] rqAmountFloat16 // 2 bytes
202// [ 48 bits ] rqToIdx // 6 bytes
203// [ 48 bits ] rqFromIdx // 6 bytes
204// Total bits compressed data: 193 bits // 25 bytes in *big.Int representation
205func (tx *PoolL2Tx) RqTxCompressedDataV2() (*big.Int, error) {
206 if tx.RqAmount == nil {
207 tx.RqAmount = big.NewInt(0)
208 }
209 amountFloat16, err := NewFloat16(tx.RqAmount)
210 if err != nil {
211 return nil, tracerr.Wrap(err)
212 }
213 var b [25]byte
214 toBJJSign := byte(0)
215 if tx.RqToBJJ != nil && babyjub.PointCoordSign(tx.RqToBJJ.X) {
216 toBJJSign = byte(1)
217 }
218 b[0] = toBJJSign
219 b[1] = byte(tx.RqFee)
220 nonceBytes, err := tx.RqNonce.Bytes()
221 if err != nil {
222 return nil, tracerr.Wrap(err)
223 }
224 copy(b[2:7], nonceBytes[:])
225 copy(b[7:11], tx.RqTokenID.Bytes())
226 copy(b[11:13], amountFloat16.Bytes())
227 toIdxBytes, err := tx.RqToIdx.Bytes()
228 if err != nil {
229 return nil, tracerr.Wrap(err)
230 }
231 copy(b[13:19], toIdxBytes[:])
232 fromIdxBytes, err := tx.RqFromIdx.Bytes()
233 if err != nil {
234 return nil, tracerr.Wrap(err)
235 }
236 copy(b[19:25], fromIdxBytes[:])
237
238 bi := new(big.Int).SetBytes(b[:])
239 return bi, nil
240}
241
242// HashToSign returns the computed Poseidon hash from the *PoolL2Tx that will be signed by the sender.
243func (tx *PoolL2Tx) HashToSign() (*big.Int, error) {
244 toCompressedData, err := tx.TxCompressedData()
245 if err != nil {
246 return nil, tracerr.Wrap(err)
247 }
248 toEthAddr := EthAddrToBigInt(tx.ToEthAddr)
249 rqToEthAddr := EthAddrToBigInt(tx.RqToEthAddr)
250 toBJJY := big.NewInt(0)
251 if tx.ToBJJ != nil {
252 toBJJY = tx.ToBJJ.Y
253 }
254 rqTxCompressedDataV2, err := tx.RqTxCompressedDataV2()
255 if err != nil {
256 return nil, tracerr.Wrap(err)
257 }
258 rqToBJJY := big.NewInt(0)
259 if tx.RqToBJJ != nil {
260 rqToBJJY = tx.RqToBJJ.Y
261 }
262
263 return poseidon.Hash([]*big.Int{toCompressedData, toEthAddr, toBJJY, rqTxCompressedDataV2, rqToEthAddr, rqToBJJY})
264}
265
266// VerifySignature returns true if the signature verification is correct for the given PublicKey
267func (tx *PoolL2Tx) VerifySignature(pk *babyjub.PublicKey) bool {
268 h, err := tx.HashToSign()
269 if err != nil {
270 return false
271 }
272 s, err := tx.Signature.Decompress()
273 if err != nil {
274 return false
275 }
276 return pk.VerifyPoseidon(h, s)
277}
278
279// L2Tx returns a *L2Tx from the PoolL2Tx
280func (tx PoolL2Tx) L2Tx() L2Tx {
281 return L2Tx{
282 TxID: tx.TxID,
283 FromIdx: tx.FromIdx,
284 ToIdx: tx.ToIdx,
285 Amount: tx.Amount,
286 Fee: tx.Fee,
287 Nonce: tx.Nonce,
288 Type: tx.Type,
289 }
290}
291
292// Tx returns a *Tx from the PoolL2Tx
293func (tx PoolL2Tx) Tx() Tx {
294 return Tx{
295 TxID: tx.TxID,
296 FromIdx: tx.FromIdx,
297 ToIdx: tx.ToIdx,
298 Amount: tx.Amount,
299 TokenID: tx.TokenID,
300 Nonce: &tx.Nonce,
301 Fee: &tx.Fee,
302 Type: tx.Type,
303 }
304}
305
306// PoolL2TxsToL2Txs returns an array of []L2Tx from an array of []PoolL2Tx
307func PoolL2TxsToL2Txs(txs []PoolL2Tx) ([]L2Tx, error) {
308 var r []L2Tx
309 for _, poolTx := range txs {
310 r = append(r, poolTx.L2Tx())
311 }
312 return r, nil
313}
314
315// PoolL2TxState is a struct that represents the status of a L2 transaction
316type PoolL2TxState string
317
318const (
319 // PoolL2TxStatePending represents a valid L2Tx that hasn't started the forging process
320 PoolL2TxStatePending PoolL2TxState = "pend"
321 // PoolL2TxStateForging represents a valid L2Tx that has started the forging process
322 PoolL2TxStateForging PoolL2TxState = "fing"
323 // PoolL2TxStateForged represents a L2Tx that has already been forged
324 PoolL2TxStateForged PoolL2TxState = "fged"
325 // PoolL2TxStateInvalid represents a L2Tx that has been invalidated
326 PoolL2TxStateInvalid PoolL2TxState = "invl"
327)