You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

281 lines
8.9 KiB

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