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