arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/common/pooll2tx.go
common/pooll2tx.goRaw
  1package common
  2
  3import (
  4	"encoding/binary"
  5	"errors"
  6	"fmt"
  7	"math/big"
  8	"time"
  9
 10	ethCommon "github.com/ethereum/go-ethereum/common"
 11	"github.com/hermeznetwork/tracerr"
 12	"github.com/iden3/go-iden3-crypto/babyjub"
 13	"github.com/iden3/go-iden3-crypto/poseidon"
 14)
 15
 16// EmptyBJJComp contains the 32 byte array of a empty BabyJubJub PublicKey
 17// Compressed. It is a valid point in the BabyJubJub curve, so does not give
 18// errors when being decompressed.
 19var EmptyBJJComp = babyjub.PublicKeyComp([32]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
 20
 21// PoolL2Tx is a struct that represents a L2Tx sent by an account to the
 22// coordinator that is waiting to be forged
 23type PoolL2Tx struct {
 24	// Stored in DB: mandatory fileds
 25
 26	// TxID (12 bytes) for L2Tx is:
 27	// bytes:  |  1   |    6    |   5   |
 28	// values: | type | FromIdx | Nonce |
 29	TxID    TxID `meddler:"tx_id"`
 30	FromIdx Idx  `meddler:"from_idx"`
 31	ToIdx   Idx  `meddler:"to_idx,zeroisnull"`
 32	// AuxToIdx is only used internally at the StateDB to avoid repeated
 33	// computation when processing transactions (from Synchronizer,
 34	// TxSelector, BatchBuilder)
 35	AuxToIdx  Idx                   `meddler:"-"`
 36	ToEthAddr ethCommon.Address     `meddler:"to_eth_addr,zeroisnull"`
 37	ToBJJ     babyjub.PublicKeyComp `meddler:"to_bjj,zeroisnull"`
 38	TokenID   TokenID               `meddler:"token_id"`
 39	Amount    *big.Int              `meddler:"amount,bigint"`
 40	Fee       FeeSelector           `meddler:"fee"`
 41	Nonce     Nonce                 `meddler:"nonce"` // effective 40 bits used
 42	State     PoolL2TxState         `meddler:"state"`
 43	// Info contains information about the status & State of the
 44	// transaction. As for example, if the Tx has not been selected in the
 45	// last batch due not enough Balance at the Sender account, this reason
 46	// would appear at this parameter.
 47	Info      string                `meddler:"info,zeroisnull"`
 48	Signature babyjub.SignatureComp `meddler:"signature"`         // tx signature
 49	Timestamp time.Time             `meddler:"timestamp,utctime"` // time when added to the tx pool
 50	// Stored in DB: optional fileds, may be uninitialized
 51	RqFromIdx         Idx                   `meddler:"rq_from_idx,zeroisnull"`
 52	RqToIdx           Idx                   `meddler:"rq_to_idx,zeroisnull"`
 53	RqToEthAddr       ethCommon.Address     `meddler:"rq_to_eth_addr,zeroisnull"`
 54	RqToBJJ           babyjub.PublicKeyComp `meddler:"rq_to_bjj,zeroisnull"`
 55	RqTokenID         TokenID               `meddler:"rq_token_id,zeroisnull"`
 56	RqAmount          *big.Int              `meddler:"rq_amount,bigintnull"`
 57	RqFee             FeeSelector           `meddler:"rq_fee,zeroisnull"`
 58	RqNonce           Nonce                 `meddler:"rq_nonce,zeroisnull"` // effective 48 bits used
 59	AbsoluteFee       float64               `meddler:"fee_usd,zeroisnull"`
 60	AbsoluteFeeUpdate time.Time             `meddler:"usd_update,utctimez"`
 61	Type              TxType                `meddler:"tx_type"`
 62	// Extra metadata, may be uninitialized
 63	RqTxCompressedData []byte `meddler:"-"` // 253 bits, optional for atomic txs
 64}
 65
 66// NewPoolL2Tx returns the given L2Tx with the TxId & Type parameters calculated
 67// from the L2Tx values
 68func NewPoolL2Tx(tx *PoolL2Tx) (*PoolL2Tx, error) {
 69	txTypeOld := tx.Type
 70	if err := tx.SetType(); err != nil {
 71		return nil, tracerr.Wrap(err)
 72	}
 73	// If original Type doesn't match the correct one, return error
 74	if txTypeOld != "" && txTypeOld != tx.Type {
 75		return nil, tracerr.Wrap(fmt.Errorf("L2Tx.Type: %s, should be: %s",
 76			tx.Type, txTypeOld))
 77	}
 78
 79	txIDOld := tx.TxID
 80	if err := tx.SetID(); err != nil {
 81		return nil, tracerr.Wrap(err)
 82	}
 83	// If original TxID doesn't match the correct one, return error
 84	if txIDOld != (TxID{}) && txIDOld != tx.TxID {
 85		return tx, tracerr.Wrap(fmt.Errorf("PoolL2Tx.TxID: %s, should be: %s",
 86			tx.TxID.String(), txIDOld.String()))
 87	}
 88
 89	return tx, nil
 90}
 91
 92// SetType sets the type of the transaction
 93func (tx *PoolL2Tx) SetType() error {
 94	if tx.ToIdx >= IdxUserThreshold {
 95		tx.Type = TxTypeTransfer
 96	} else if tx.ToIdx == 1 {
 97		tx.Type = TxTypeExit
 98	} else if tx.ToIdx == 0 {
 99		if tx.ToBJJ != EmptyBJJComp && tx.ToEthAddr == FFAddr {
100			tx.Type = TxTypeTransferToBJJ
101		} else if tx.ToEthAddr != FFAddr && tx.ToEthAddr != EmptyAddr {
102			tx.Type = TxTypeTransferToEthAddr
103		}
104	} else {
105		return tracerr.Wrap(errors.New("malformed transaction"))
106	}
107	return nil
108}
109
110// SetID sets the ID of the transaction
111func (tx *PoolL2Tx) SetID() error {
112	txID, err := tx.L2Tx().CalculateTxID()
113	if err != nil {
114		return tracerr.Wrap(err)
115	}
116	tx.TxID = txID
117	return nil
118}
119
120// TxCompressedData spec:
121// [ 1 bits  ] toBJJSign // 1 byte
122// [ 8 bits  ] userFee // 1 byte
123// [ 40 bits ] nonce // 5 bytes
124// [ 32 bits ] tokenID // 4 bytes
125// [ 48 bits ] toIdx // 6 bytes
126// [ 48 bits ] fromIdx // 6 bytes
127// [ 16 bits ] chainId // 2 bytes
128// [ 32 bits ] signatureConstant // 4 bytes
129// Total bits compressed data:  225 bits // 29 bytes in *big.Int representation
130func (tx *PoolL2Tx) TxCompressedData(chainID uint16) (*big.Int, error) {
131	var b [29]byte
132
133	toBJJSign := byte(0)
134	pkSign, _ := babyjub.UnpackSignY(tx.ToBJJ)
135	if pkSign {
136		toBJJSign = byte(1)
137	}
138
139	b[0] = toBJJSign
140	b[1] = byte(tx.Fee)
141	nonceBytes, err := tx.Nonce.Bytes()
142	if err != nil {
143		return nil, tracerr.Wrap(err)
144	}
145	copy(b[2:7], nonceBytes[:])
146	copy(b[7:11], tx.TokenID.Bytes())
147	toIdxBytes, err := tx.ToIdx.Bytes()
148	if err != nil {
149		return nil, tracerr.Wrap(err)
150	}
151	copy(b[11:17], toIdxBytes[:])
152	fromIdxBytes, err := tx.FromIdx.Bytes()
153	if err != nil {
154		return nil, tracerr.Wrap(err)
155	}
156	copy(b[17:23], fromIdxBytes[:])
157	binary.BigEndian.PutUint16(b[23:25], chainID)
158	copy(b[25:29], SignatureConstantBytes[:])
159
160	bi := new(big.Int).SetBytes(b[:])
161	return bi, nil
162}
163
164// TxCompressedDataEmpty calculates the TxCompressedData of an empty
165// transaction
166func TxCompressedDataEmpty(chainID uint16) *big.Int {
167	var b [29]byte
168	binary.BigEndian.PutUint16(b[23:25], chainID)
169	copy(b[25:29], SignatureConstantBytes[:])
170	bi := new(big.Int).SetBytes(b[:])
171	return bi
172}
173
174// TxCompressedDataV2 spec:
175// [ 1 bits  ] toBJJSign // 1 byte
176// [ 8 bits  ] userFee // 1 byte
177// [ 40 bits ] nonce // 5 bytes
178// [ 32 bits ] tokenID // 4 bytes
179// [ 40 bits ] amountFloat40 // 5 bytes
180// [ 48 bits ] toIdx // 6 bytes
181// [ 48 bits ] fromIdx // 6 bytes
182// Total bits compressed data:  217 bits // 28 bytes in *big.Int representation
183func (tx *PoolL2Tx) TxCompressedDataV2() (*big.Int, error) {
184	if tx.Amount == nil {
185		tx.Amount = big.NewInt(0)
186	}
187	amountFloat40, err := NewFloat40(tx.Amount)
188	if err != nil {
189		return nil, tracerr.Wrap(err)
190	}
191	amountFloat40Bytes, err := amountFloat40.Bytes()
192	if err != nil {
193		return nil, tracerr.Wrap(err)
194	}
195
196	var b [28]byte
197	toBJJSign := byte(0)
198	if tx.ToBJJ != EmptyBJJComp {
199		sign, _ := babyjub.UnpackSignY(tx.ToBJJ)
200		if sign {
201			toBJJSign = byte(1)
202		}
203	}
204	b[0] = toBJJSign
205	b[1] = byte(tx.Fee)
206	nonceBytes, err := tx.Nonce.Bytes()
207	if err != nil {
208		return nil, tracerr.Wrap(err)
209	}
210	copy(b[2:7], nonceBytes[:])
211	copy(b[7:11], tx.TokenID.Bytes())
212	copy(b[11:16], amountFloat40Bytes)
213	toIdxBytes, err := tx.ToIdx.Bytes()
214	if err != nil {
215		return nil, tracerr.Wrap(err)
216	}
217	copy(b[16:22], toIdxBytes[:])
218	fromIdxBytes, err := tx.FromIdx.Bytes()
219	if err != nil {
220		return nil, tracerr.Wrap(err)
221	}
222	copy(b[22:28], fromIdxBytes[:])
223
224	bi := new(big.Int).SetBytes(b[:])
225	return bi, nil
226}
227
228// RqTxCompressedDataV2 is like the TxCompressedDataV2 but using the 'Rq'
229// parameters. In a future iteration of the hermez-node, the 'Rq' parameters
230// can be inside a struct, which contains the 'Rq' transaction grouped inside,
231// so then computing the 'RqTxCompressedDataV2' would be just calling
232// 'tx.Rq.TxCompressedDataV2()'.
233// RqTxCompressedDataV2 spec:
234// [ 1 bits  ] rqToBJJSign // 1 byte
235// [ 8 bits  ] rqUserFee // 1 byte
236// [ 40 bits ] rqNonce // 5 bytes
237// [ 32 bits ] rqTokenID // 4 bytes
238// [ 40 bits ] rqAmountFloat40 // 5 bytes
239// [ 48 bits ] rqToIdx // 6 bytes
240// [ 48 bits ] rqFromIdx // 6 bytes
241// Total bits compressed data:  217 bits // 28 bytes in *big.Int representation
242func (tx *PoolL2Tx) RqTxCompressedDataV2() (*big.Int, error) {
243	if tx.RqAmount == nil {
244		tx.RqAmount = big.NewInt(0)
245	}
246	amountFloat40, err := NewFloat40(tx.RqAmount)
247	if err != nil {
248		return nil, tracerr.Wrap(err)
249	}
250	amountFloat40Bytes, err := amountFloat40.Bytes()
251	if err != nil {
252		return nil, tracerr.Wrap(err)
253	}
254
255	var b [28]byte
256	rqToBJJSign := byte(0)
257	if tx.RqToBJJ != EmptyBJJComp {
258		sign, _ := babyjub.UnpackSignY(tx.RqToBJJ)
259		if sign {
260			rqToBJJSign = byte(1)
261		}
262	}
263	b[0] = rqToBJJSign
264	b[1] = byte(tx.RqFee)
265	nonceBytes, err := tx.RqNonce.Bytes()
266	if err != nil {
267		return nil, tracerr.Wrap(err)
268	}
269	copy(b[2:7], nonceBytes[:])
270	copy(b[7:11], tx.RqTokenID.Bytes())
271	copy(b[11:16], amountFloat40Bytes)
272	toIdxBytes, err := tx.RqToIdx.Bytes()
273	if err != nil {
274		return nil, tracerr.Wrap(err)
275	}
276	copy(b[16:22], toIdxBytes[:])
277	fromIdxBytes, err := tx.RqFromIdx.Bytes()
278	if err != nil {
279		return nil, tracerr.Wrap(err)
280	}
281	copy(b[22:28], fromIdxBytes[:])
282
283	bi := new(big.Int).SetBytes(b[:])
284	return bi, nil
285}
286
287// HashToSign returns the computed Poseidon hash from the *PoolL2Tx that will
288// be signed by the sender.
289func (tx *PoolL2Tx) HashToSign(chainID uint16) (*big.Int, error) {
290	toCompressedData, err := tx.TxCompressedData(chainID)
291	if err != nil {
292		return nil, tracerr.Wrap(err)
293	}
294
295	// e1: [5 bytes AmountFloat40 | 20 bytes ToEthAddr]
296	var e1B [25]byte
297	amountFloat40, err := NewFloat40(tx.Amount)
298	if err != nil {
299		return nil, tracerr.Wrap(err)
300	}
301	amountFloat40Bytes, err := amountFloat40.Bytes()
302	if err != nil {
303		return nil, tracerr.Wrap(err)
304	}
305	copy(e1B[0:5], amountFloat40Bytes)
306	toEthAddr := EthAddrToBigInt(tx.ToEthAddr)
307	copy(e1B[5:25], toEthAddr.Bytes())
308	e1 := new(big.Int).SetBytes(e1B[:])
309
310	rqToEthAddr := EthAddrToBigInt(tx.RqToEthAddr)
311
312	_, toBJJY := babyjub.UnpackSignY(tx.ToBJJ)
313
314	rqTxCompressedDataV2, err := tx.RqTxCompressedDataV2()
315	if err != nil {
316		return nil, tracerr.Wrap(err)
317	}
318
319	_, rqToBJJY := babyjub.UnpackSignY(tx.RqToBJJ)
320
321	return poseidon.Hash([]*big.Int{toCompressedData, e1, toBJJY, rqTxCompressedDataV2, rqToEthAddr, rqToBJJY})
322}
323
324// VerifySignature returns true if the signature verification is correct for the given PublicKeyComp
325func (tx *PoolL2Tx) VerifySignature(chainID uint16, pkComp babyjub.PublicKeyComp) bool {
326	h, err := tx.HashToSign(chainID)
327	if err != nil {
328		return false
329	}
330	s, err := tx.Signature.Decompress()
331	if err != nil {
332		return false
333	}
334	pk, err := pkComp.Decompress()
335	if err != nil {
336		return false
337	}
338	return pk.VerifyPoseidon(h, s)
339}
340
341// L2Tx returns a *L2Tx from the PoolL2Tx
342func (tx PoolL2Tx) L2Tx() L2Tx {
343	var toIdx Idx
344	if tx.ToIdx == Idx(0) {
345		toIdx = tx.AuxToIdx
346	} else {
347		toIdx = tx.ToIdx
348	}
349	return L2Tx{
350		TxID:    tx.TxID,
351		FromIdx: tx.FromIdx,
352		ToIdx:   toIdx,
353		TokenID: tx.TokenID,
354		Amount:  tx.Amount,
355		Fee:     tx.Fee,
356		Nonce:   tx.Nonce,
357		Type:    tx.Type,
358	}
359}
360
361// Tx returns a *Tx from the PoolL2Tx
362func (tx PoolL2Tx) Tx() Tx {
363	return Tx{
364		TxID:    tx.TxID,
365		FromIdx: tx.FromIdx,
366		ToIdx:   tx.ToIdx,
367		Amount:  tx.Amount,
368		TokenID: tx.TokenID,
369		Nonce:   &tx.Nonce,
370		Fee:     &tx.Fee,
371		Type:    tx.Type,
372	}
373}
374
375// PoolL2TxsToL2Txs returns an array of []L2Tx from an array of []PoolL2Tx
376func PoolL2TxsToL2Txs(txs []PoolL2Tx) ([]L2Tx, error) {
377	l2Txs := make([]L2Tx, len(txs))
378	for i, poolTx := range txs {
379		l2Txs[i] = poolTx.L2Tx()
380	}
381	return l2Txs, nil
382}
383
384// TxIDsFromPoolL2Txs returns an array of TxID from the []PoolL2Tx
385func TxIDsFromPoolL2Txs(txs []PoolL2Tx) []TxID {
386	txIDs := make([]TxID, len(txs))
387	for i, tx := range txs {
388		txIDs[i] = tx.TxID
389	}
390	return txIDs
391}
392
393// PoolL2TxState is a string that represents the status of a L2 transaction
394type PoolL2TxState string
395
396const (
397	// PoolL2TxStatePending represents a valid L2Tx that hasn't started the
398	// forging process
399	PoolL2TxStatePending PoolL2TxState = "pend"
400	// PoolL2TxStateForging represents a valid L2Tx that has started the
401	// forging process
402	PoolL2TxStateForging PoolL2TxState = "fing"
403	// PoolL2TxStateForged represents a L2Tx that has already been forged
404	PoolL2TxStateForged PoolL2TxState = "fged"
405	// PoolL2TxStateInvalid represents a L2Tx that has been invalidated
406	PoolL2TxStateInvalid PoolL2TxState = "invl"
407)