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.LoadAmount (deposit)
37 RqToIdx Idx `meddler:"rq_to_idx,zeroisnull"` // FromIdx is used by L1Tx/Deposit to indicate the Idx receiver of the L1Tx.LoadAmount (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 // sigconstant
111 sc, ok := new(big.Int).SetString("3322668559", 10)
112 if !ok {
113 return nil, tracerr.Wrap(fmt.Errorf("error parsing SignatureConstant"))
114 }
115
116 amountFloat16, err := NewFloat16(tx.Amount)
117 if err != nil {
118 return nil, tracerr.Wrap(err)
119 }
120 var b [31]byte
121 toBJJSign := byte(0)
122 if tx.ToBJJ != nil && babyjub.PointCoordSign(tx.ToBJJ.X) {
123 toBJJSign = byte(1)
124 }
125 b[0] = toBJJSign
126 b[1] = byte(tx.Fee)
127 nonceBytes, err := tx.Nonce.Bytes()
128 if err != nil {
129 return nil, tracerr.Wrap(err)
130 }
131 copy(b[2:7], nonceBytes[:])
132 copy(b[7:11], tx.TokenID.Bytes())
133 copy(b[11:13], amountFloat16.Bytes())
134 toIdxBytes, err := tx.ToIdx.Bytes()
135 if err != nil {
136 return nil, tracerr.Wrap(err)
137 }
138 copy(b[13:19], toIdxBytes[:])
139 fromIdxBytes, err := tx.FromIdx.Bytes()
140 if err != nil {
141 return nil, tracerr.Wrap(err)
142 }
143 copy(b[19:25], fromIdxBytes[:])
144 copy(b[25:27], []byte{0, 1}) // TODO this will be generated by the ChainID config parameter
145 copy(b[27:31], sc.Bytes())
146
147 bi := new(big.Int).SetBytes(b[:])
148 return bi, nil
149}
150
151// TxCompressedDataV2 spec:
152// [ 1 bits ] toBJJSign // 1 byte
153// [ 8 bits ] userFee // 1 byte
154// [ 40 bits ] nonce // 5 bytes
155// [ 32 bits ] tokenID // 4 bytes
156// [ 16 bits ] amountFloat16 // 2 bytes
157// [ 48 bits ] toIdx // 6 bytes
158// [ 48 bits ] fromIdx // 6 bytes
159// Total bits compressed data: 193 bits // 25 bytes in *big.Int representation
160func (tx *PoolL2Tx) TxCompressedDataV2() (*big.Int, error) {
161 if tx.Amount == nil {
162 tx.Amount = big.NewInt(0)
163 }
164 amountFloat16, err := NewFloat16(tx.Amount)
165 if err != nil {
166 return nil, tracerr.Wrap(err)
167 }
168 var b [25]byte
169 toBJJSign := byte(0)
170 if tx.ToBJJ != nil && babyjub.PointCoordSign(tx.ToBJJ.X) {
171 toBJJSign = byte(1)
172 }
173 b[0] = toBJJSign
174 b[1] = byte(tx.Fee)
175 nonceBytes, err := tx.Nonce.Bytes()
176 if err != nil {
177 return nil, tracerr.Wrap(err)
178 }
179 copy(b[2:7], nonceBytes[:])
180 copy(b[7:11], tx.TokenID.Bytes())
181 copy(b[11:13], amountFloat16.Bytes())
182 toIdxBytes, err := tx.ToIdx.Bytes()
183 if err != nil {
184 return nil, tracerr.Wrap(err)
185 }
186 copy(b[13:19], toIdxBytes[:])
187 fromIdxBytes, err := tx.FromIdx.Bytes()
188 if err != nil {
189 return nil, tracerr.Wrap(err)
190 }
191 copy(b[19:25], fromIdxBytes[:])
192
193 bi := new(big.Int).SetBytes(b[:])
194 return bi, nil
195}
196
197// RqTxCompressedDataV2 is like the TxCompressedDataV2 but using the 'Rq'
198// parameters. In a future iteration of the hermez-node, the 'Rq' parameters
199// can be inside a struct, which contains the 'Rq' transaction grouped inside,
200// so then computing the 'RqTxCompressedDataV2' would be just calling
201// 'tx.Rq.TxCompressedDataV2()'.
202// RqTxCompressedDataV2 spec:
203// [ 1 bits ] rqToBJJSign // 1 byte
204// [ 8 bits ] rqUserFee // 1 byte
205// [ 40 bits ] rqNonce // 5 bytes
206// [ 32 bits ] rqTokenID // 4 bytes
207// [ 16 bits ] rqAmountFloat16 // 2 bytes
208// [ 48 bits ] rqToIdx // 6 bytes
209// [ 48 bits ] rqFromIdx // 6 bytes
210// Total bits compressed data: 193 bits // 25 bytes in *big.Int representation
211func (tx *PoolL2Tx) RqTxCompressedDataV2() (*big.Int, error) {
212 if tx.RqAmount == nil {
213 tx.RqAmount = big.NewInt(0)
214 }
215 amountFloat16, err := NewFloat16(tx.RqAmount)
216 if err != nil {
217 return nil, tracerr.Wrap(err)
218 }
219 var b [25]byte
220 toBJJSign := byte(0)
221 if tx.RqToBJJ != nil && babyjub.PointCoordSign(tx.RqToBJJ.X) {
222 toBJJSign = byte(1)
223 }
224 b[0] = toBJJSign
225 b[1] = byte(tx.RqFee)
226 nonceBytes, err := tx.RqNonce.Bytes()
227 if err != nil {
228 return nil, tracerr.Wrap(err)
229 }
230 copy(b[2:7], nonceBytes[:])
231 copy(b[7:11], tx.RqTokenID.Bytes())
232 copy(b[11:13], amountFloat16.Bytes())
233 toIdxBytes, err := tx.RqToIdx.Bytes()
234 if err != nil {
235 return nil, tracerr.Wrap(err)
236 }
237 copy(b[13:19], toIdxBytes[:])
238 fromIdxBytes, err := tx.RqFromIdx.Bytes()
239 if err != nil {
240 return nil, tracerr.Wrap(err)
241 }
242 copy(b[19:25], fromIdxBytes[:])
243
244 bi := new(big.Int).SetBytes(b[:])
245 return bi, nil
246}
247
248// HashToSign returns the computed Poseidon hash from the *PoolL2Tx that will be signed by the sender.
249func (tx *PoolL2Tx) HashToSign() (*big.Int, error) {
250 toCompressedData, err := tx.TxCompressedData()
251 if err != nil {
252 return nil, tracerr.Wrap(err)
253 }
254 toEthAddr := EthAddrToBigInt(tx.ToEthAddr)
255 rqToEthAddr := EthAddrToBigInt(tx.RqToEthAddr)
256 toBJJY := big.NewInt(0)
257 if tx.ToBJJ != nil {
258 toBJJY = tx.ToBJJ.Y
259 }
260 rqTxCompressedDataV2, err := tx.RqTxCompressedDataV2()
261 if err != nil {
262 return nil, tracerr.Wrap(err)
263 }
264 rqToBJJY := big.NewInt(0)
265 if tx.RqToBJJ != nil {
266 rqToBJJY = tx.RqToBJJ.Y
267 }
268
269 return poseidon.Hash([]*big.Int{toCompressedData, toEthAddr, toBJJY, rqTxCompressedDataV2, rqToEthAddr, rqToBJJY})
270}
271
272// VerifySignature returns true if the signature verification is correct for the given PublicKey
273func (tx *PoolL2Tx) VerifySignature(pk *babyjub.PublicKey) bool {
274 h, err := tx.HashToSign()
275 if err != nil {
276 return false
277 }
278 s, err := tx.Signature.Decompress()
279 if err != nil {
280 return false
281 }
282 return pk.VerifyPoseidon(h, s)
283}
284
285// L2Tx returns a *L2Tx from the PoolL2Tx
286func (tx PoolL2Tx) L2Tx() L2Tx {
287 return L2Tx{
288 TxID: tx.TxID,
289 FromIdx: tx.FromIdx,
290 ToIdx: tx.ToIdx,
291 Amount: tx.Amount,
292 Fee: tx.Fee,
293 Nonce: tx.Nonce,
294 Type: tx.Type,
295 }
296}
297
298// Tx returns a *Tx from the PoolL2Tx
299func (tx PoolL2Tx) Tx() Tx {
300 return Tx{
301 TxID: tx.TxID,
302 FromIdx: tx.FromIdx,
303 ToIdx: tx.ToIdx,
304 Amount: tx.Amount,
305 TokenID: tx.TokenID,
306 Nonce: &tx.Nonce,
307 Fee: &tx.Fee,
308 Type: tx.Type,
309 }
310}
311
312// PoolL2TxsToL2Txs returns an array of []L2Tx from an array of []PoolL2Tx
313func PoolL2TxsToL2Txs(txs []PoolL2Tx) ([]L2Tx, error) {
314 var r []L2Tx
315 for _, poolTx := range txs {
316 r = append(r, poolTx.L2Tx())
317 }
318 return r, nil
319}
320
321// PoolL2TxState is a struct that represents the status of a L2 transaction
322type PoolL2TxState string
323
324const (
325 // PoolL2TxStatePending represents a valid L2Tx that hasn't started the forging process
326 PoolL2TxStatePending PoolL2TxState = "pend"
327 // PoolL2TxStateForging represents a valid L2Tx that has started the forging process
328 PoolL2TxStateForging PoolL2TxState = "fing"
329 // PoolL2TxStateForged represents a L2Tx that has already been forged
330 PoolL2TxStateForged PoolL2TxState = "fged"
331 // PoolL2TxStateInvalid represents a L2Tx that has been invalidated
332 PoolL2TxStateInvalid PoolL2TxState = "invl"
333)