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	amountFloat16, err := NewFloat16(tx.Amount)
111	if err != nil {
112		return nil, tracerr.Wrap(err)
113	}
114	var b [31]byte
115	toBJJSign := byte(0)
116	if tx.ToBJJ != nil && babyjub.PointCoordSign(tx.ToBJJ.X) {
117		toBJJSign = byte(1)
118	}
119	b[0] = toBJJSign
120	b[1] = byte(tx.Fee)
121	nonceBytes, err := tx.Nonce.Bytes()
122	if err != nil {
123		return nil, tracerr.Wrap(err)
124	}
125	copy(b[2:7], nonceBytes[:])
126	copy(b[7:11], tx.TokenID.Bytes())
127	copy(b[11:13], amountFloat16.Bytes())
128	toIdxBytes, err := tx.ToIdx.Bytes()
129	if err != nil {
130		return nil, tracerr.Wrap(err)
131	}
132	copy(b[13:19], toIdxBytes[:])
133	fromIdxBytes, err := tx.FromIdx.Bytes()
134	if err != nil {
135		return nil, tracerr.Wrap(err)
136	}
137	copy(b[19:25], fromIdxBytes[:])
138	copy(b[25:27], []byte{0, 0}) // TODO this will be generated by the ChainID config parameter
139	copy(b[27:31], SignatureConstantBytes[:])
140
141	bi := new(big.Int).SetBytes(b[:])
142	return bi, nil
143}
144
145// TxCompressedDataV2 spec:
146// [ 1 bits  ] toBJJSign // 1 byte
147// [ 8 bits  ] userFee // 1 byte
148// [ 40 bits ] nonce // 5 bytes
149// [ 32 bits ] tokenID // 4 bytes
150// [ 16 bits ] amountFloat16 // 2 bytes
151// [ 48 bits ] toIdx // 6 bytes
152// [ 48 bits ] fromIdx // 6 bytes
153// Total bits compressed data:  193 bits // 25 bytes in *big.Int representation
154func (tx *PoolL2Tx) TxCompressedDataV2() (*big.Int, error) {
155	if tx.Amount == nil {
156		tx.Amount = big.NewInt(0)
157	}
158	amountFloat16, err := NewFloat16(tx.Amount)
159	if err != nil {
160		return nil, tracerr.Wrap(err)
161	}
162	var b [25]byte
163	toBJJSign := byte(0)
164	if tx.ToBJJ != nil && babyjub.PointCoordSign(tx.ToBJJ.X) {
165		toBJJSign = byte(1)
166	}
167	b[0] = toBJJSign
168	b[1] = byte(tx.Fee)
169	nonceBytes, err := tx.Nonce.Bytes()
170	if err != nil {
171		return nil, tracerr.Wrap(err)
172	}
173	copy(b[2:7], nonceBytes[:])
174	copy(b[7:11], tx.TokenID.Bytes())
175	copy(b[11:13], amountFloat16.Bytes())
176	toIdxBytes, err := tx.ToIdx.Bytes()
177	if err != nil {
178		return nil, tracerr.Wrap(err)
179	}
180	copy(b[13:19], toIdxBytes[:])
181	fromIdxBytes, err := tx.FromIdx.Bytes()
182	if err != nil {
183		return nil, tracerr.Wrap(err)
184	}
185	copy(b[19:25], fromIdxBytes[:])
186
187	bi := new(big.Int).SetBytes(b[:])
188	return bi, nil
189}
190
191// RqTxCompressedDataV2 is like the TxCompressedDataV2 but using the 'Rq'
192// parameters. In a future iteration of the hermez-node, the 'Rq' parameters
193// can be inside a struct, which contains the 'Rq' transaction grouped inside,
194// so then computing the 'RqTxCompressedDataV2' would be just calling
195// 'tx.Rq.TxCompressedDataV2()'.
196// RqTxCompressedDataV2 spec:
197// [ 1 bits  ] rqToBJJSign // 1 byte
198// [ 8 bits  ] rqUserFee // 1 byte
199// [ 40 bits ] rqNonce // 5 bytes
200// [ 32 bits ] rqTokenID // 4 bytes
201// [ 16 bits ] rqAmountFloat16 // 2 bytes
202// [ 48 bits ] rqToIdx // 6 bytes
203// [ 48 bits ] rqFromIdx // 6 bytes
204// Total bits compressed data:  193 bits // 25 bytes in *big.Int representation
205func (tx *PoolL2Tx) RqTxCompressedDataV2() (*big.Int, error) {
206	if tx.RqAmount == nil {
207		tx.RqAmount = big.NewInt(0)
208	}
209	amountFloat16, err := NewFloat16(tx.RqAmount)
210	if err != nil {
211		return nil, tracerr.Wrap(err)
212	}
213	var b [25]byte
214	toBJJSign := byte(0)
215	if tx.RqToBJJ != nil && babyjub.PointCoordSign(tx.RqToBJJ.X) {
216		toBJJSign = byte(1)
217	}
218	b[0] = toBJJSign
219	b[1] = byte(tx.RqFee)
220	nonceBytes, err := tx.RqNonce.Bytes()
221	if err != nil {
222		return nil, tracerr.Wrap(err)
223	}
224	copy(b[2:7], nonceBytes[:])
225	copy(b[7:11], tx.RqTokenID.Bytes())
226	copy(b[11:13], amountFloat16.Bytes())
227	toIdxBytes, err := tx.RqToIdx.Bytes()
228	if err != nil {
229		return nil, tracerr.Wrap(err)
230	}
231	copy(b[13:19], toIdxBytes[:])
232	fromIdxBytes, err := tx.RqFromIdx.Bytes()
233	if err != nil {
234		return nil, tracerr.Wrap(err)
235	}
236	copy(b[19:25], fromIdxBytes[:])
237
238	bi := new(big.Int).SetBytes(b[:])
239	return bi, nil
240}
241
242// HashToSign returns the computed Poseidon hash from the *PoolL2Tx that will be signed by the sender.
243func (tx *PoolL2Tx) HashToSign() (*big.Int, error) {
244	toCompressedData, err := tx.TxCompressedData()
245	if err != nil {
246		return nil, tracerr.Wrap(err)
247	}
248	toEthAddr := EthAddrToBigInt(tx.ToEthAddr)
249	rqToEthAddr := EthAddrToBigInt(tx.RqToEthAddr)
250	toBJJY := big.NewInt(0)
251	if tx.ToBJJ != nil {
252		toBJJY = tx.ToBJJ.Y
253	}
254	rqTxCompressedDataV2, err := tx.RqTxCompressedDataV2()
255	if err != nil {
256		return nil, tracerr.Wrap(err)
257	}
258	rqToBJJY := big.NewInt(0)
259	if tx.RqToBJJ != nil {
260		rqToBJJY = tx.RqToBJJ.Y
261	}
262
263	return poseidon.Hash([]*big.Int{toCompressedData, toEthAddr, toBJJY, rqTxCompressedDataV2, rqToEthAddr, rqToBJJY})
264}
265
266// VerifySignature returns true if the signature verification is correct for the given PublicKey
267func (tx *PoolL2Tx) VerifySignature(pk *babyjub.PublicKey) bool {
268	h, err := tx.HashToSign()
269	if err != nil {
270		return false
271	}
272	s, err := tx.Signature.Decompress()
273	if err != nil {
274		return false
275	}
276	return pk.VerifyPoseidon(h, s)
277}
278
279// L2Tx returns a *L2Tx from the PoolL2Tx
280func (tx PoolL2Tx) L2Tx() L2Tx {
281	return L2Tx{
282		TxID:    tx.TxID,
283		FromIdx: tx.FromIdx,
284		ToIdx:   tx.ToIdx,
285		Amount:  tx.Amount,
286		Fee:     tx.Fee,
287		Nonce:   tx.Nonce,
288		Type:    tx.Type,
289	}
290}
291
292// Tx returns a *Tx from the PoolL2Tx
293func (tx PoolL2Tx) Tx() Tx {
294	return Tx{
295		TxID:    tx.TxID,
296		FromIdx: tx.FromIdx,
297		ToIdx:   tx.ToIdx,
298		Amount:  tx.Amount,
299		TokenID: tx.TokenID,
300		Nonce:   &tx.Nonce,
301		Fee:     &tx.Fee,
302		Type:    tx.Type,
303	}
304}
305
306// PoolL2TxsToL2Txs returns an array of []L2Tx from an array of []PoolL2Tx
307func PoolL2TxsToL2Txs(txs []PoolL2Tx) ([]L2Tx, error) {
308	var r []L2Tx
309	for _, poolTx := range txs {
310		r = append(r, poolTx.L2Tx())
311	}
312	return r, nil
313}
314
315// PoolL2TxState is a struct that represents the status of a L2 transaction
316type PoolL2TxState string
317
318const (
319	// PoolL2TxStatePending represents a valid L2Tx that hasn't started the forging process
320	PoolL2TxStatePending PoolL2TxState = "pend"
321	// PoolL2TxStateForging represents a valid L2Tx that has started the forging process
322	PoolL2TxStateForging PoolL2TxState = "fing"
323	// PoolL2TxStateForged represents a L2Tx that has already been forged
324	PoolL2TxStateForged PoolL2TxState = "fged"
325	// PoolL2TxStateInvalid represents a L2Tx that has been invalidated
326	PoolL2TxStateInvalid PoolL2TxState = "invl"
327)