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