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