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