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