hermez-node/common/l1tx.go
common/l1tx.goRaw
1package common
2
3import (
4 "encoding/binary"
5 "fmt"
6 "math/big"
7
8 ethCommon "github.com/ethereum/go-ethereum/common"
9 "github.com/ethereum/go-ethereum/crypto"
10 "github.com/iden3/go-iden3-crypto/babyjub"
11)
12
13const (
14 // L1TxBytesLen is the length of the byte array that represents the L1Tx
15 L1TxBytesLen = 72
16 // L1CoordinatorTxBytesLen is the length of the byte array that represents the L1CoordinatorTx
17 L1CoordinatorTxBytesLen = 101
18)
19
20// L1Tx is a struct that represents a L1 tx
21type L1Tx struct {
22 // Stored in DB: mandatory fileds
23
24 // TxID (12 bytes) for L1Tx is:
25 // bytes: | 1 | 8 | 2 | 1 |
26 // values: | type | ToForgeL1TxsNum | Position | 0 (padding) |
27 // where type:
28 // - L1UserTx: 0
29 // - L1CoordinatorTx: 1
30 TxID TxID
31 ToForgeL1TxsNum int64 // toForgeL1TxsNum in which the tx was forged / will be forged
32 Position int
33 UserOrigin bool // true if the tx was originated by a user, false if it was aoriginated by a coordinator. Note that this differ from the spec for implementation simplification purpposes
34 FromIdx Idx // FromIdx is used by L1Tx/Deposit to indicate the Idx receiver of the L1Tx.LoadAmount (deposit)
35 FromEthAddr ethCommon.Address
36 FromBJJ *babyjub.PublicKey
37 ToIdx Idx // ToIdx is ignored in L1Tx/Deposit, but used in the L1Tx/DepositAndTransfer
38 TokenID TokenID
39 Amount *big.Int
40 LoadAmount *big.Int
41 EthBlockNum int64 // Ethereum Block Number in which this L1Tx was added to the queue
42 Type TxType
43 BatchNum *BatchNum
44 USD *float64
45 LoadAmountUSD *float64
46}
47
48// NewL1Tx returns the given L1Tx with the TxId & Type parameters calculated
49// from the L1Tx values
50func NewL1Tx(l1Tx *L1Tx) (*L1Tx, error) {
51 // calculate TxType
52 var txType TxType
53 if l1Tx.FromIdx == Idx(0) {
54 if l1Tx.ToIdx == Idx(0) {
55 txType = TxTypeCreateAccountDeposit
56 } else if l1Tx.ToIdx >= IdxUserThreshold {
57 txType = TxTypeCreateAccountDepositTransfer
58 } else {
59 return l1Tx, fmt.Errorf("Can not determine type of L1Tx, invalid ToIdx value: %d", l1Tx.ToIdx)
60 }
61 } else if l1Tx.FromIdx >= IdxUserThreshold {
62 if l1Tx.ToIdx == Idx(0) {
63 txType = TxTypeDeposit
64 } else if l1Tx.ToIdx == Idx(1) {
65 txType = TxTypeExit
66 } else if l1Tx.ToIdx >= IdxUserThreshold {
67 if l1Tx.LoadAmount.Int64() == int64(0) {
68 txType = TxTypeForceTransfer
69 } else {
70 txType = TxTypeDepositTransfer
71 }
72 } else {
73 return l1Tx, fmt.Errorf("Can not determine type of L1Tx, invalid ToIdx value: %d", l1Tx.ToIdx)
74 }
75 } else {
76 return l1Tx, fmt.Errorf("Can not determine type of L1Tx, invalid FromIdx value: %d", l1Tx.FromIdx)
77 }
78
79 if l1Tx.Type != "" && l1Tx.Type != txType {
80 return l1Tx, fmt.Errorf("L1Tx.Type: %s, should be: %s", l1Tx.Type, txType)
81 }
82 l1Tx.Type = txType
83
84 var txid [TxIDLen]byte
85 if !l1Tx.UserOrigin {
86 txid[0] = TxIDPrefixL1CoordTx
87 }
88 var toForgeL1TxsNumBytes [8]byte
89 binary.BigEndian.PutUint64(toForgeL1TxsNumBytes[:], uint64(l1Tx.ToForgeL1TxsNum))
90 copy(txid[1:9], toForgeL1TxsNumBytes[:])
91
92 var positionBytes [2]byte
93 binary.BigEndian.PutUint16(positionBytes[:], uint16(l1Tx.Position))
94 copy(txid[9:11], positionBytes[:])
95 l1Tx.TxID = TxID(txid)
96
97 return l1Tx, nil
98}
99
100// Tx returns a *Tx from the L1Tx
101func (tx *L1Tx) Tx() *Tx {
102 f := new(big.Float).SetInt(tx.Amount)
103 amountFloat, _ := f.Float64()
104 genericTx := &Tx{
105 IsL1: true,
106 TxID: tx.TxID,
107 Type: tx.Type,
108 Position: tx.Position,
109 FromIdx: tx.FromIdx,
110 ToIdx: tx.ToIdx,
111 Amount: tx.Amount,
112 AmountFloat: amountFloat,
113 TokenID: tx.TokenID,
114 ToForgeL1TxsNum: tx.ToForgeL1TxsNum,
115 UserOrigin: tx.UserOrigin,
116 FromEthAddr: tx.FromEthAddr,
117 FromBJJ: tx.FromBJJ,
118 LoadAmount: tx.LoadAmount,
119 EthBlockNum: tx.EthBlockNum,
120 USD: tx.USD,
121 LoadAmountUSD: tx.LoadAmountUSD,
122 }
123 if tx.LoadAmount != nil {
124 lf := new(big.Float).SetInt(tx.LoadAmount)
125 loadAmountFloat, _ := lf.Float64()
126 genericTx.LoadAmountFloat = &loadAmountFloat
127 }
128 return genericTx
129}
130
131// Bytes encodes a L1Tx into []byte
132func (tx *L1Tx) Bytes() ([]byte, error) {
133 var b [L1TxBytesLen]byte
134 copy(b[0:20], tx.FromEthAddr.Bytes())
135 pkComp := tx.FromBJJ.Compress()
136 copy(b[20:52], pkComp[:])
137 fromIdxBytes, err := tx.FromIdx.Bytes()
138 if err != nil {
139 return nil, err
140 }
141 copy(b[52:58], fromIdxBytes[:])
142 loadAmountFloat16, err := NewFloat16(tx.LoadAmount)
143 if err != nil {
144 return nil, err
145 }
146 copy(b[58:60], loadAmountFloat16.Bytes())
147 amountFloat16, err := NewFloat16(tx.Amount)
148 if err != nil {
149 return nil, err
150 }
151 copy(b[60:62], amountFloat16.Bytes())
152 copy(b[62:66], tx.TokenID.Bytes())
153 toIdxBytes, err := tx.ToIdx.Bytes()
154 if err != nil {
155 return nil, err
156 }
157 copy(b[66:72], toIdxBytes[:])
158 return b[:], nil
159}
160
161// BytesCoordinatorTx encodes a L1CoordinatorTx into []byte
162func (tx *L1Tx) BytesCoordinatorTx(compressedSignatureBytes []byte) ([]byte, error) {
163 var b [L1CoordinatorTxBytesLen]byte
164 v := compressedSignatureBytes[64]
165 s := compressedSignatureBytes[32:64]
166 r := compressedSignatureBytes[0:32]
167 b[0] = v
168 copy(b[1:33], s)
169 copy(b[33:65], r)
170 pkComp := tx.FromBJJ.Compress()
171 copy(b[65:97], pkComp[:])
172 copy(b[97:101], tx.TokenID.Bytes())
173 return b[:], nil
174}
175
176// L1TxFromBytes decodes a L1Tx from []byte
177func L1TxFromBytes(b []byte) (*L1Tx, error) {
178 if len(b) != L1TxBytesLen {
179 return nil, fmt.Errorf("Can not parse L1Tx bytes, expected length %d, current: %d", 68, len(b))
180 }
181
182 tx := &L1Tx{}
183 var err error
184 tx.FromEthAddr = ethCommon.BytesToAddress(b[0:20])
185 pkCompB := b[20:52]
186 var pkComp babyjub.PublicKeyComp
187 copy(pkComp[:], pkCompB)
188 tx.FromBJJ, err = pkComp.Decompress()
189 if err != nil {
190 return nil, err
191 }
192 tx.FromIdx, err = IdxFromBytes(b[52:58])
193 if err != nil {
194 return nil, err
195 }
196 tx.LoadAmount = Float16FromBytes(b[58:60]).BigInt()
197 tx.Amount = Float16FromBytes(b[60:62]).BigInt()
198 tx.TokenID, err = TokenIDFromBytes(b[62:66])
199 if err != nil {
200 return nil, err
201 }
202 tx.ToIdx, err = IdxFromBytes(b[66:72])
203 if err != nil {
204 return nil, err
205 }
206
207 return tx, nil
208}
209
210// L1TxFromCoordinatorBytes decodes a L1Tx from []byte
211func L1TxFromCoordinatorBytes(b []byte) (*L1Tx, error) {
212 if len(b) != L1CoordinatorTxBytesLen {
213 return nil, fmt.Errorf("Can not parse L1CoordinatorTx bytes, expected length %d, current: %d", 101, len(b))
214 }
215
216 bytesMessage1 := []byte("\x19Ethereum Signed Message:\n98")
217 bytesMessage2 := []byte("I authorize this babyjubjub key for hermez rollup account creation")
218
219 tx := &L1Tx{}
220 var err error
221 // Ethereum adds 27 to v
222 v := b[0] - byte(27) //nolint:gomnd
223 s := b[1:33]
224 r := b[33:65]
225
226 pkCompB := b[65:97]
227 var pkComp babyjub.PublicKeyComp
228 copy(pkComp[:], pkCompB)
229 tx.FromBJJ, err = pkComp.Decompress()
230 if err != nil {
231 return nil, err
232 }
233 tx.TokenID, err = TokenIDFromBytes(b[97:101])
234 if err != nil {
235 return nil, err
236 }
237
238 var data []byte
239 data = append(data, bytesMessage1...)
240 data = append(data, bytesMessage2...)
241 data = append(data, pkCompB...)
242 var signature []byte
243 signature = append(signature, r[:]...)
244 signature = append(signature, s[:]...)
245 signature = append(signature, v)
246 hash := crypto.Keccak256(data)
247 pubKeyBytes, err := crypto.Ecrecover(hash, signature)
248 if err != nil {
249 return nil, err
250 }
251 pubKey, err := crypto.UnmarshalPubkey(pubKeyBytes)
252 if err != nil {
253 return nil, err
254 }
255 tx.FromEthAddr = crypto.PubkeyToAddress(*pubKey)
256 return tx, nil
257}