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