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