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