diff --git a/batchbuilder/batchbuilder.go b/batchbuilder/batchbuilder.go index 0f864e5..35c5ad3 100644 --- a/batchbuilder/batchbuilder.go +++ b/batchbuilder/batchbuilder.go @@ -51,8 +51,8 @@ func (bb *BatchBuilder) Reset(batchNum common.BatchNum, fromSynchronizer bool) e } // BuildBatch takes the transactions and returns the common.ZKInputs of the next batch -func (bb *BatchBuilder) BuildBatch(configBatch *ConfigBatch, l1usertxs, l1coordinatortxs []*common.L1Tx, l2txs []*common.L2Tx, tokenIDs []common.TokenID) (*common.ZKInputs, error) { - zkInputs, _, err := bb.localStateDB.ProcessTxs(false, l1usertxs, l1coordinatortxs, l2txs) +func (bb *BatchBuilder) BuildBatch(configBatch *ConfigBatch, l1usertxs, l1coordinatortxs []*common.L1Tx, pooll2txs []*common.PoolL2Tx, tokenIDs []common.TokenID) (*common.ZKInputs, error) { + zkInputs, _, err := bb.localStateDB.ProcessTxs(false, true, l1usertxs, l1coordinatortxs, pooll2txs) if err != nil { return nil, err } diff --git a/common/l2tx.go b/common/l2tx.go index 44a0283..3d49694 100644 --- a/common/l2tx.go +++ b/common/l2tx.go @@ -30,3 +30,28 @@ func (tx *L2Tx) Tx() *Tx { Type: tx.Type, } } + +// PoolL2Tx returns the data structure of PoolL2Tx with the parameters of a +// L2Tx filled +func (tx *L2Tx) PoolL2Tx() *PoolL2Tx { + return &PoolL2Tx{ + TxID: tx.TxID, + BatchNum: tx.BatchNum, + FromIdx: tx.FromIdx, + ToIdx: tx.ToIdx, + Amount: tx.Amount, + Fee: tx.Fee, + Nonce: tx.Nonce, + Type: tx.Type, + } +} + +// L2TxsToPoolL2Txs returns an array of []*PoolL2Tx from an array of []*L2Tx, +// where the PoolL2Tx only have the parameters of a L2Tx filled. +func L2TxsToPoolL2Txs(txs []*L2Tx) []*PoolL2Tx { + var r []*PoolL2Tx + for _, tx := range txs { + r = append(r, tx.PoolL2Tx()) + } + return r +} diff --git a/common/pooll2tx.go b/common/pooll2tx.go index 92009b6..04844b2 100644 --- a/common/pooll2tx.go +++ b/common/pooll2tx.go @@ -27,6 +27,11 @@ func (n Nonce) Bytes() ([5]byte, error) { return b, nil } +// BigInt returns the *big.Int representation of the Nonce value +func (n Nonce) BigInt() *big.Int { + return big.NewInt(int64(n)) +} + // NonceFromBytes returns Nonce from a [5]byte func NonceFromBytes(b [5]byte) Nonce { var nonceBytes [8]byte @@ -76,7 +81,7 @@ type PoolL2Tx struct { // [ 32 bits ] tokenID // 4 bytes: [20:24] // [ 40 bits ] nonce // 5 bytes: [24:29] // [ 8 bits ] userFee // 1 byte: [29:30] -// [ 1 bits ] toBjjSign // 1 byte: [30:31] +// [ 1 bits ] toBJJSign // 1 byte: [30:31] // Total bits compressed data: 241 bits // 31 bytes in *big.Int representation func (tx *PoolL2Tx) TxCompressedData() (*big.Int, error) { // sigconstant @@ -102,11 +107,11 @@ func (tx *PoolL2Tx) TxCompressedData() (*big.Int, error) { } copy(b[24:29], nonceBytes[:]) b[29] = byte(tx.Fee) - toBjjSign := byte(0) + toBJJSign := byte(0) if babyjub.PointCoordSign(tx.ToBJJ.X) { - toBjjSign = byte(1) + toBJJSign = byte(1) } - b[30] = toBjjSign + b[30] = toBJJSign bi := new(big.Int).SetBytes(SwapEndianness(b[:])) return bi, nil @@ -119,7 +124,7 @@ func (tx *PoolL2Tx) TxCompressedData() (*big.Int, error) { // [ 32 bits ] tokenID // 4 bytes: [14:18] // [ 40 bits ] nonce // 5 bytes: [18:23] // [ 8 bits ] userFee // 1 byte: [23:24] -// [ 1 bits ] toBjjSign // 1 byte: [24:25] +// [ 1 bits ] toBJJSign // 1 byte: [24:25] // Total bits compressed data: 193 bits // 25 bytes in *big.Int representation func (tx *PoolL2Tx) TxCompressedDataV2() (*big.Int, error) { amountFloat16, err := utils.NewFloat16(tx.Amount) @@ -137,11 +142,11 @@ func (tx *PoolL2Tx) TxCompressedDataV2() (*big.Int, error) { } copy(b[18:23], nonceBytes[:]) b[23] = byte(tx.Fee) - toBjjSign := byte(0) + toBJJSign := byte(0) if babyjub.PointCoordSign(tx.ToBJJ.X) { - toBjjSign = byte(1) + toBJJSign = byte(1) } - b[24] = toBjjSign + b[24] = toBJJSign bi := new(big.Int).SetBytes(SwapEndianness(b[:])) return bi, nil @@ -154,13 +159,13 @@ func (tx *PoolL2Tx) HashToSign() (*big.Int, error) { return nil, err } toEthAddr := EthAddrToBigInt(tx.ToEthAddr) - toBjjAy := tx.ToBJJ.Y + toBJJAy := tx.ToBJJ.Y rqTxCompressedDataV2, err := tx.TxCompressedDataV2() if err != nil { return nil, err } - return poseidon.Hash([]*big.Int{toCompressedData, toEthAddr, toBjjAy, rqTxCompressedDataV2, EthAddrToBigInt(tx.RqToEthAddr), tx.RqToBJJ.Y}) + return poseidon.Hash([]*big.Int{toCompressedData, toEthAddr, toBJJAy, rqTxCompressedDataV2, EthAddrToBigInt(tx.RqToEthAddr), tx.RqToBJJ.Y}) } // VerifySignature returns true if the signature verification is correct for the given PublicKey diff --git a/common/token.go b/common/token.go index 22a60da..1888698 100644 --- a/common/token.go +++ b/common/token.go @@ -2,6 +2,7 @@ package common import ( "encoding/binary" + "math/big" "time" ethCommon "github.com/ethereum/go-ethereum/common" @@ -34,3 +35,8 @@ func (t TokenID) Bytes() []byte { binary.LittleEndian.PutUint32(tokenIDBytes[:], uint32(t)) return tokenIDBytes[:] } + +// BigInt returns the *big.Int representation of the TokenID +func (t TokenID) BigInt() *big.Int { + return big.NewInt(int64(t)) +} diff --git a/common/zk.go b/common/zk.go index d7d6892..4becfbd 100644 --- a/common/zk.go +++ b/common/zk.go @@ -1,6 +1,6 @@ // Package common contains all the common data structures used at the // hermez-node, zk.go contains the zkSnark inputs used to generate the proof -//nolint:deadcode,structcheck, unused +//nolint:deadcode,structcheck,unused package common import "math/big" @@ -20,127 +20,267 @@ type maxFeeTx uint32 // ZKInputs represents the inputs that will be used to generate the zkSNARK proof type ZKInputs struct { + // + // General + // + // inputs for final `hashGlobalInputs` - // oldLastIdx is the last index assigned to an account - oldLastIdx *big.Int // uint64 (max nLevels bits) - // oldStateRoot is the current state merkle tree root - oldStateRoot *big.Int // Hash - // globalChainID is the blockchain ID (0 for Ethereum mainnet). This value can be get from the smart contract. - globalChainID *big.Int // uint16 - // feeIdxs is an array of merkle tree indexes where the coordinator will receive the accumulated fees - feeIdxs []*big.Int // uint64 (max nLevels bits), len: [maxFeeTx] + // OldLastIdx is the last index assigned to an account + OldLastIdx *big.Int // uint64 (max nLevels bits) + // OldStateRoot is the current state merkle tree root + OldStateRoot *big.Int // Hash + // GlobalChainID is the blockchain ID (0 for Ethereum mainnet). This value can be get from the smart contract. + GlobalChainID *big.Int // uint16 + // FeeIdxs is an array of merkle tree indexes where the coordinator will receive the accumulated fees + FeeIdxs []*big.Int // uint64 (max nLevels bits), len: [maxFeeTx] // accumulate fees - // feePlanTokens contains all the tokenIDs for which the fees are being accumulated - feePlanTokens []*big.Int // uint32 (max 32 bits), len: [maxFeeTx] + // FeePlanTokens contains all the tokenIDs for which the fees are being accumulated + FeePlanTokens []*big.Int // uint32 (max 32 bits), len: [maxFeeTx] - // Intermediary States to parallelize witness computation - // decode-tx - // imOnChain indicates if tx is L1 (true) or L2 (false) - imOnChain []*big.Int // bool, len: [nTx - 1] - // imOutIdx current index account for each Tx - imOutIdx []*big.Int // uint64 (max nLevels bits), len: [nTx - 1] - // rollup-tx - // imStateRoot root at the moment of the Tx, the state root value once the Tx is processed into the state tree - imStateRoot []*big.Int // Hash, len: [nTx - 1] - // imExitTree root at the moment of the Tx the value once the Tx is processed into the exit tree - imExitRoot []*big.Int // Hash, len: [nTx - 1] - // imAccFeeOut accumulated fees once the Tx is processed - imAccFeeOut [][]*big.Int // big.Int, len: [nTx - 1][maxFeeTx] - // fee-tx - // imStateRootFee root at the moment of the Tx, the state root value once the Tx is processed into the state tree - imStateRootFee []*big.Int // Hash, len: [maxFeeTx - 1] - // imInitStateRootFee state root once all L1-L2 tx are processed (before computing the fees-tx) - imInitStateRootFee *big.Int // Hash - // imFinalAccFee final accumulated fees (before computing the fees-tx) - imFinalAccFee []*big.Int // big.Int, len: [maxFeeTx - 1] + // + // Txs (L1&L2) + // // transaction L1-L2 - // txCompressedData - txCompressedData []*big.Int // big.Int (max 251 bits), len: [nTx] - // txCompressedDataV2 - txCompressedDataV2 []*big.Int // big.Int (max 193 bits), len: [nTx] - // fromIdx - fromIdx []*big.Int // uint64 (max nLevels bits), len: [nTx] - // auxFromIdx is the Idx of the new created account which is consequence of a L1CreateAccountTx - auxFromIdx []*big.Int // uint64 (max nLevels bits), len: [nTx] - - // toIdx - toIdx []*big.Int // uint64 (max nLevels bits), len: [nTx] - // auxToIdx is the Idx of the Tx that has 'toIdx==0', is the coordinator who will find which Idx corresponds to the 'toBjjAy' or 'toEthAddr' - auxToIdx []*big.Int // uint64 (max nLevels bits), len: [nTx] - // toBjjAy - toBjjAy []*big.Int // big.Int, len: [nTx] - // toEthAddr - toEthAddr []*big.Int // ethCommon.Address, len: [nTx] - - // onChain determines if is L1 (1/true) or L2 (0/false) - onChain []*big.Int // bool, len: [nTx] - // newAccount boolean (0/1) flag to set L1 tx creates a new account - newAccount []*big.Int // bool, len: [nTx] - // rqOffset relative transaction position to be linked. Used to perform atomic transactions. - rqOffset []*big.Int // uint8 (max 3 bits), len: [nTx] + // TxCompressedData + TxCompressedData []*big.Int // big.Int (max 251 bits), len: [nTx] + // TxCompressedDataV2, only used in L2Txs, in L1Txs is set to 0 + TxCompressedDataV2 []*big.Int // big.Int (max 193 bits), len: [nTx] + + // FromIdx + FromIdx []*big.Int // uint64 (max nLevels bits), len: [nTx] + // AuxFromIdx is the Idx of the new created account which is consequence of a L1CreateAccountTx + AuxFromIdx []*big.Int // uint64 (max nLevels bits), len: [nTx] + + // ToIdx + ToIdx []*big.Int // uint64 (max nLevels bits), len: [nTx] + // AuxToIdx is the Idx of the Tx that has 'toIdx==0', is the coordinator who will find which Idx corresponds to the 'toBJJAy' or 'toEthAddr' + AuxToIdx []*big.Int // uint64 (max nLevels bits), len: [nTx] + // ToBJJAy + ToBJJAy []*big.Int // big.Int, len: [nTx] + // ToEthAddr + ToEthAddr []*big.Int // ethCommon.Address, len: [nTx] + + // OnChain determines if is L1 (1/true) or L2 (0/false) + OnChain []*big.Int // bool, len: [nTx] + + // + // Txs/L1Txs + // + // NewAccount boolean (0/1) flag set 'true' when L1 tx creates a new account (fromIdx==0) + NewAccount []*big.Int // bool, len: [nTx] + // LoadAmountF encoded as float16 + LoadAmountF []*big.Int // uint16, len: [nTx] + // FromEthAddr + FromEthAddr []*big.Int // ethCommon.Address, len: [nTx] + // FromBJJCompressed boolean encoded where each value is a *big.Int + FromBJJCompressed [][256]*big.Int // bool array, len: [nTx][256] + + // + // Txs/L2Txs + // + + // RqOffset relative transaction position to be linked. Used to perform atomic transactions. + RqOffset []*big.Int // uint8 (max 3 bits), len: [nTx] // transaction L2 request data - // rqTxCompressedDataV2 - rqTxCompressedDataV2 []*big.Int // big.Int (max 251 bits), len: [nTx] - // rqToEthAddr - rqToEthAddr []*big.Int // ethCommon.Address, len: [nTx] - // rqToBjjAy - rqToBjjAy []*big.Int // big.Int, len: [nTx] + // RqTxCompressedDataV2 + RqTxCompressedDataV2 []*big.Int // big.Int (max 251 bits), len: [nTx] + // RqToEthAddr + RqToEthAddr []*big.Int // ethCommon.Address, len: [nTx] + // RqToBJJAy + RqToBJJAy []*big.Int // big.Int, len: [nTx] // transaction L2 signature - // s - s []*big.Int // big.Int, len: [nTx] - // r8x - r8x []*big.Int // big.Int, len: [nTx] - // r8y - r8y []*big.Int // big.Int, len: [nTx] - - // transaction L1 - // loadAmountF encoded as float16 - loadAmountF []*big.Int // uint16, len: [nTx] - // fromEthAddr - fromEthAddr []*big.Int // ethCommon.Address, len: [nTx] - // fromBjjCompressed boolean encoded where each value is a *big.Int - fromBjjCompressed [][]*big.Int // bool array, len: [nTx][256] + // S + S []*big.Int // big.Int, len: [nTx] + // R8x + R8x []*big.Int // big.Int, len: [nTx] + // R8y + R8y []*big.Int // big.Int, len: [nTx] + + // + // State MerkleTree Leafs transitions + // // state 1, value of the sender (from) account leaf - tokenID1 []*big.Int // uint32, len: [nTx] - nonce1 []*big.Int // uint64 (max 40 bits), len: [nTx] - sign1 []*big.Int // bool, len: [nTx] - balance1 []*big.Int // big.Int (max 192 bits), len: [nTx] - ay1 []*big.Int // big.Int, len: [nTx] - ethAddr1 []*big.Int // ethCommon.Address, len: [nTx] - siblings1 [][]*big.Int // big.Int, len: [nTx][nLevels + 1] + TokenID1 []*big.Int // uint32, len: [nTx] + Nonce1 []*big.Int // uint64 (max 40 bits), len: [nTx] + Sign1 []*big.Int // bool, len: [nTx] + Ay1 []*big.Int // big.Int, len: [nTx] + Balance1 []*big.Int // big.Int (max 192 bits), len: [nTx] + EthAddr1 []*big.Int // ethCommon.Address, len: [nTx] + Siblings1 [][]*big.Int // big.Int, len: [nTx][nLevels + 1] // Required for inserts and deletes, values of the CircomProcessorProof (smt insert proof) - isOld0_1 []*big.Int // bool, len: [nTx] - oldKey1 []*big.Int // uint64 (max 40 bits), len: [nTx] - oldValue1 []*big.Int // Hash, len: [nTx] + IsOld0_1 []*big.Int // bool, len: [nTx] + OldKey1 []*big.Int // uint64 (max 40 bits), len: [nTx] + OldValue1 []*big.Int // Hash, len: [nTx] // state 2, value of the receiver (to) account leaf - tokenID2 []*big.Int // uint32, len: [nTx] - nonce2 []*big.Int // uint64 (max 40 bits), len: [nTx] - sign2 []*big.Int // bool, len: [nTx] - balance2 []*big.Int // big.Int (max 192 bits), len: [nTx] - ay2 []*big.Int // big.Int, len: [nTx] - ethAddr2 []*big.Int // ethCommon.Address, len: [nTx] - siblings2 [][]*big.Int // big.Int, len: [nTx][nLevels + 1] + // if Tx is an Exit, state 2 is used for the Exit Merkle Proof + TokenID2 []*big.Int // uint32, len: [nTx] + Nonce2 []*big.Int // uint64 (max 40 bits), len: [nTx] + Sign2 []*big.Int // bool, len: [nTx] + Ay2 []*big.Int // big.Int, len: [nTx] + Balance2 []*big.Int // big.Int (max 192 bits), len: [nTx] + EthAddr2 []*big.Int // ethCommon.Address, len: [nTx] + Siblings2 [][]*big.Int // big.Int, len: [nTx][nLevels + 1] // newExit determines if an exit transaction has to create a new leaf in the exit tree - newExit []*big.Int // bool, len: [nTx] + NewExit []*big.Int // bool, len: [nTx] // Required for inserts and deletes, values of the CircomProcessorProof (smt insert proof) - isOld0_2 []*big.Int // bool, len: [nTx] - oldKey2 []*big.Int // uint64 (max 40 bits), len: [nTx] - oldValue2 []*big.Int // Hash, len: [nTx] + IsOld0_2 []*big.Int // bool, len: [nTx] + OldKey2 []*big.Int // uint64 (max 40 bits), len: [nTx] + OldValue2 []*big.Int // Hash, len: [nTx] // state 3, value of the account leaf receiver of the Fees // fee tx // State fees - tokenID3 []*big.Int // uint32, len: [maxFeeTx] - nonce3 []*big.Int // uint64 (max 40 bits), len: [maxFeeTx] - sign3 []*big.Int // bool, len: [maxFeeTx] - balance3 []*big.Int // big.Int (max 192 bits), len: [maxFeeTx] - ay3 []*big.Int // big.Int, len: [maxFeeTx] - ethAddr3 []*big.Int // ethCommon.Address, len: [maxFeeTx] - siblings3 [][]*big.Int // Hash, len: [maxFeeTx][nLevels + 1] + TokenID3 []*big.Int // uint32, len: [maxFeeTx] + Nonce3 []*big.Int // uint64 (max 40 bits), len: [maxFeeTx] + Sign3 []*big.Int // bool, len: [maxFeeTx] + Ay3 []*big.Int // big.Int, len: [maxFeeTx] + Balance3 []*big.Int // big.Int (max 192 bits), len: [maxFeeTx] + EthAddr3 []*big.Int // ethCommon.Address, len: [maxFeeTx] + Siblings3 [][]*big.Int // Hash, len: [maxFeeTx][nLevels + 1] + + // + // Intermediate States + // + + // Intermediate States to parallelize witness computation + // decode-tx + // ISOnChain indicates if tx is L1 (true) or L2 (false) + ISOnChain []*big.Int // bool, len: [nTx - 1] + // ISOutIdx current index account for each Tx + ISOutIdx []*big.Int // uint64 (max nLevels bits), len: [nTx - 1] + // rollup-tx + // ISStateRoot root at the moment of the Tx, the state root value once the Tx is processed into the state tree + ISStateRoot []*big.Int // Hash, len: [nTx - 1] + // ISExitTree root at the moment of the Tx the value once the Tx is processed into the exit tree + ISExitRoot []*big.Int // Hash, len: [nTx - 1] + // ISAccFeeOut accumulated fees once the Tx is processed + ISAccFeeOut [][]*big.Int // big.Int, len: [nTx - 1][maxFeeTx] + // fee-tx + // ISStateRootFee root at the moment of the Tx, the state root value once the Tx is processed into the state tree + ISStateRootFee []*big.Int // Hash, len: [maxFeeTx - 1] + // ISInitStateRootFee state root once all L1-L2 tx are processed (before computing the fees-tx) + ISInitStateRootFee *big.Int // Hash + // ISFinalAccFee final accumulated fees (before computing the fees-tx) + ISFinalAccFee []*big.Int // big.Int, len: [maxFeeTx - 1] +} + +// NewZKInputs returns a pointer to an initialized struct of ZKInputs +func NewZKInputs(nTx, maxFeeTx, nLevels int) *ZKInputs { + zki := &ZKInputs{} + + // General + zki.OldLastIdx = big.NewInt(0) + zki.OldStateRoot = big.NewInt(0) + zki.GlobalChainID = big.NewInt(0) + zki.FeeIdxs = newSlice(maxFeeTx) + zki.FeePlanTokens = newSlice(maxFeeTx) + + // Txs + zki.TxCompressedData = newSlice(nTx) + zki.TxCompressedDataV2 = newSlice(nTx) + zki.FromIdx = newSlice(nTx) + zki.AuxFromIdx = newSlice(nTx) + zki.ToIdx = newSlice(nTx) + zki.AuxToIdx = newSlice(nTx) + zki.ToBJJAy = newSlice(nTx) + zki.ToEthAddr = newSlice(nTx) + zki.OnChain = newSlice(nTx) + zki.NewAccount = newSlice(nTx) + + // L1 + zki.LoadAmountF = newSlice(nTx) + zki.FromEthAddr = newSlice(nTx) + zki.FromBJJCompressed = make([][256]*big.Int, nTx) + for i := 0; i < len(zki.FromBJJCompressed); i++ { + // zki.FromBJJCompressed[i] = newSlice(256) + for j := 0; j < 256; j++ { + zki.FromBJJCompressed[i][j] = big.NewInt(0) + } + } + + // L2 + zki.RqOffset = newSlice(nTx) + zki.RqTxCompressedDataV2 = newSlice(nTx) + zki.RqToEthAddr = newSlice(nTx) + zki.RqToBJJAy = newSlice(nTx) + zki.S = newSlice(nTx) + zki.R8x = newSlice(nTx) + zki.R8y = newSlice(nTx) + + // State MerkleTree Leafs transitions + zki.TokenID1 = newSlice(nTx) + zki.Nonce1 = newSlice(nTx) + zki.Sign1 = newSlice(nTx) + zki.Ay1 = newSlice(nTx) + zki.Balance1 = newSlice(nTx) + zki.EthAddr1 = newSlice(nTx) + zki.Siblings1 = make([][]*big.Int, nTx) + for i := 0; i < len(zki.Siblings1); i++ { + zki.Siblings1[i] = newSlice(nLevels + 1) + } + zki.IsOld0_1 = newSlice(nTx) + zki.OldKey1 = newSlice(nTx) + zki.OldValue1 = newSlice(nTx) + + zki.TokenID2 = newSlice(nTx) + zki.Nonce2 = newSlice(nTx) + zki.Sign2 = newSlice(nTx) + zki.Ay2 = newSlice(nTx) + zki.Balance2 = newSlice(nTx) + zki.EthAddr2 = newSlice(nTx) + zki.Siblings2 = make([][]*big.Int, nTx) + for i := 0; i < len(zki.Siblings2); i++ { + zki.Siblings2[i] = newSlice(nLevels + 1) + } + zki.NewExit = newSlice(nTx) + zki.IsOld0_2 = newSlice(nTx) + zki.OldKey2 = newSlice(nTx) + zki.OldValue2 = newSlice(nTx) + + zki.TokenID3 = newSlice(maxFeeTx) + zki.Nonce3 = newSlice(maxFeeTx) + zki.Sign3 = newSlice(maxFeeTx) + zki.Ay3 = newSlice(maxFeeTx) + zki.Balance3 = newSlice(maxFeeTx) + zki.EthAddr3 = newSlice(maxFeeTx) + zki.Siblings3 = make([][]*big.Int, maxFeeTx) + for i := 0; i < len(zki.Siblings3); i++ { + zki.Siblings3[i] = newSlice(nLevels + 1) + } + + // Intermediate States + zki.ISOnChain = newSlice(nTx - 1) + zki.ISOutIdx = newSlice(nTx - 1) + zki.ISStateRoot = newSlice(nTx - 1) + zki.ISExitRoot = newSlice(nTx - 1) + zki.ISAccFeeOut = make([][]*big.Int, nTx-1) + for i := 0; i < len(zki.ISAccFeeOut); i++ { + zki.ISAccFeeOut[i] = newSlice(maxFeeTx) + } + zki.ISStateRootFee = newSlice(maxFeeTx - 1) + zki.ISInitStateRootFee = big.NewInt(0) + zki.ISFinalAccFee = newSlice(maxFeeTx - 1) + + return zki +} + +// newSlice returns a []*big.Int slice of length n with values initialized at +// 0. +// Is used to initialize all *big.Ints of the ZKInputs data structure, so when +// the transactions are processed and the ZKInputs filled, there is no need to +// set all the elements, and if a transaction does not use a parameter, can be +// leaved as it is in the ZKInputs, as will be 0, so later when using the +// ZKInputs to generate the zkSnark proof there is no 'nil'/'null' values. +func newSlice(n int) []*big.Int { + s := make([]*big.Int, n) + for i := 0; i < len(s); i++ { + s[i] = big.NewInt(0) + } + return s } diff --git a/common/zk_test.go b/common/zk_test.go new file mode 100644 index 0000000..8767838 --- /dev/null +++ b/common/zk_test.go @@ -0,0 +1,15 @@ +package common + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestZKInputs(t *testing.T) { + zki := NewZKInputs(100, 24, 32) + _, err := json.Marshal(zki) + require.Nil(t, err) + // fmt.Println(string(s)) +} diff --git a/coordinator/coordinator.go b/coordinator/coordinator.go index d0d9e2a..a24a777 100644 --- a/coordinator/coordinator.go +++ b/coordinator/coordinator.go @@ -198,8 +198,7 @@ func (c *Coordinator) forge(serverProofInfo *ServerProofInfo) (*BatchInfo, error configBatch := &batchbuilder.ConfigBatch{ ForgerAddress: c.config.ForgerAddress, } - l2Txs := common.PoolL2TxsToL2Txs(poolL2Txs) - zkInputs, err := c.batchBuilder.BuildBatch(configBatch, l1UserTxsExtra, l1OperatorTxs, l2Txs, nil) // TODO []common.TokenID --> feesInfo + zkInputs, err := c.batchBuilder.BuildBatch(configBatch, l1UserTxsExtra, l1OperatorTxs, poolL2Txs, nil) // TODO []common.TokenID --> feesInfo if err != nil { return nil, err } diff --git a/db/statedb/statedb.go b/db/statedb/statedb.go index 5d69494..008e8b0 100644 --- a/db/statedb/statedb.go +++ b/db/statedb/statedb.go @@ -43,6 +43,8 @@ type StateDB struct { mt *merkletree.MerkleTree // idx holds the current Idx that the BatchBuilder is using idx common.Idx + zki *common.ZKInputs + i int // i is the current transaction index in the ZKInputs generation (zki) } // NewStateDB creates a new StateDB, allowing to use an in-memory or in-disk diff --git a/db/statedb/txprocessors.go b/db/statedb/txprocessors.go index 280942c..368c444 100644 --- a/db/statedb/txprocessors.go +++ b/db/statedb/txprocessors.go @@ -1,27 +1,66 @@ package statedb import ( + "bytes" + "errors" + "fmt" "math/big" + ethCommon "github.com/ethereum/go-ethereum/common" "github.com/hermeznetwork/hermez-node/common" + "github.com/hermeznetwork/hermez-node/log" + "github.com/iden3/go-iden3-crypto/babyjub" "github.com/iden3/go-iden3-crypto/poseidon" "github.com/iden3/go-merkletree" "github.com/iden3/go-merkletree/db" "github.com/iden3/go-merkletree/db/memory" ) -// keyidx is used as key in the db to store the current Idx -var keyidx = []byte("idx") +var ( + // keyidx is used as key in the db to store the current Idx + keyidx = []byte("idx") + + ffAddr = ethCommon.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff") +) + +func (s *StateDB) resetZKInputs() { + s.zki = nil + s.i = 0 +} + +type processedExit struct { + exit bool + newExit bool + idx common.Idx + acc common.Account +} // ProcessTxs process the given L1Txs & L2Txs applying the needed updates to // the StateDB depending on the transaction Type. Returns the common.ZKInputs // to generate the SnarkProof later used by the BatchBuilder, and if // cmpExitTree is set to true, returns common.ExitTreeLeaf that is later used // by the Synchronizer to update the HistoryDB. -func (s *StateDB) ProcessTxs(cmpExitTree bool, l1usertxs, l1coordinatortxs []*common.L1Tx, l2txs []*common.L2Tx) (*common.ZKInputs, []*common.ExitInfo, error) { +func (s *StateDB) ProcessTxs(cmpExitTree, cmpZKInputs bool, l1usertxs, l1coordinatortxs []*common.L1Tx, l2txs []*common.PoolL2Tx) (*common.ZKInputs, []*common.ExitInfo, error) { var err error var exitTree *merkletree.MerkleTree - exits := make(map[common.Idx]common.Account) + + if s.zki != nil { + return nil, nil, errors.New("Expected StateDB.zki==nil, something went wrong and it's not empty") + } + defer s.resetZKInputs() + + nTx := len(l1usertxs) + len(l1coordinatortxs) + len(l2txs) + if nTx == 0 { + // TODO return ZKInputs of batch without txs + return nil, nil, nil + } + exits := make([]processedExit, nTx) + + if cmpZKInputs { + s.zki = common.NewZKInputs(nTx, 24, 32) // TODO this values will be parameters of the function, taken from config file/coordinator call + s.zki.OldLastIdx = (s.idx - 1).BigInt() + s.zki.OldStateRoot = s.mt.Root().BigInt() + } // TBD if ExitTree is only in memory or stored in disk, for the moment // only needed in memory @@ -30,42 +69,76 @@ func (s *StateDB) ProcessTxs(cmpExitTree bool, l1usertxs, l1coordinatortxs []*co return nil, nil, err } - for _, tx := range l1coordinatortxs { - exitIdx, exitAccount, err := s.processL1Tx(exitTree, tx) + // assumption: l1usertx are sorted by L1Tx.Position + for _, tx := range l1usertxs { + exitIdx, exitAccount, newExit, err := s.processL1Tx(exitTree, tx) if err != nil { return nil, nil, err } if exitIdx != nil && cmpExitTree { - exits[*exitIdx] = *exitAccount + exits[s.i] = processedExit{ + exit: true, + newExit: newExit, + idx: *exitIdx, + acc: *exitAccount, + } + } + if s.zki != nil { + s.i++ } } - for _, tx := range l1usertxs { - exitIdx, exitAccount, err := s.processL1Tx(exitTree, tx) + for _, tx := range l1coordinatortxs { + exitIdx, exitAccount, newExit, err := s.processL1Tx(exitTree, tx) if err != nil { return nil, nil, err } + if exitIdx != nil { + log.Error("Unexpected Exit in L1CoordinatorTx") + } if exitIdx != nil && cmpExitTree { - exits[*exitIdx] = *exitAccount + exits[s.i] = processedExit{ + exit: true, + newExit: newExit, + idx: *exitIdx, + acc: *exitAccount, + } + } + if s.zki != nil { + s.i++ } } for _, tx := range l2txs { - exitIdx, exitAccount, err := s.processL2Tx(exitTree, tx) + exitIdx, exitAccount, newExit, err := s.processL2Tx(exitTree, tx) if err != nil { return nil, nil, err } if exitIdx != nil && cmpExitTree { - exits[*exitIdx] = *exitAccount + exits[s.i] = processedExit{ + exit: true, + newExit: newExit, + idx: *exitIdx, + acc: *exitAccount, + } + } + if s.zki != nil { + s.i++ } } - if !cmpExitTree { + if !cmpExitTree && !cmpZKInputs { return nil, nil, nil } - // once all txs processed (exitTree root frozen), for each leaf + // once all txs processed (exitTree root frozen), for each Exit, // generate common.ExitInfo data var exitInfos []*common.ExitInfo - for exitIdx, exitAccount := range exits { + for i := 0; i < nTx; i++ { + if !exits[i].exit { + continue + } + exitIdx := exits[i].idx + exitAccount := exits[i].acc + // 0. generate MerkleProof p, err := exitTree.GenerateCircomVerifierProof(exitIdx.BigInt(), nil) if err != nil { @@ -92,88 +165,220 @@ func (s *StateDB) ProcessTxs(cmpExitTree bool, l1usertxs, l1coordinatortxs []*co Balance: exitAccount.Balance, } exitInfos = append(exitInfos, ei) + + if s.zki != nil { + s.zki.TokenID2[i] = exitAccount.TokenID.BigInt() + s.zki.Nonce2[i] = exitAccount.Nonce.BigInt() + if babyjub.PointCoordSign(exitAccount.PublicKey.X) { + s.zki.Sign2[i] = big.NewInt(1) + } + s.zki.Ay2[i] = exitAccount.PublicKey.Y + s.zki.Balance2[i] = exitAccount.Balance + s.zki.EthAddr2[i] = common.EthAddrToBigInt(exitAccount.EthAddr) + s.zki.Siblings2[i] = p.Siblings + if exits[i].newExit { + s.zki.NewExit[i] = big.NewInt(1) + } + if p.IsOld0 { + s.zki.IsOld0_2[i] = big.NewInt(1) + } + s.zki.OldKey2[i] = p.OldKey.BigInt() + s.zki.OldValue2[i] = p.OldValue.BigInt() + } + } + if !cmpZKInputs { + return nil, exitInfos, nil + } + + // compute last ZKInputs parameters + s.zki.GlobalChainID = big.NewInt(0) // TODO, 0: ethereum, this will be get from config file + // zki.FeeIdxs = ? // TODO, this will be get from the config file + tokenIDs, err := s.getTokenIDsBigInt(l1usertxs, l1coordinatortxs, l2txs) + if err != nil { + return nil, nil, err } + s.zki.FeePlanTokens = tokenIDs + + // s.zki.ISInitStateRootFee = s.mt.Root().BigInt() + + // TODO once the Node Config sets the Accounts where to send the Fees + // compute fees & update ZKInputs // return exitInfos, so Synchronizer will be able to store it into // HistoryDB for the concrete BatchNum - return nil, exitInfos, nil + return s.zki, exitInfos, nil +} + +// getTokenIDsBigInt returns the list of TokenIDs in *big.Int format +func (s *StateDB) getTokenIDsBigInt(l1usertxs, l1coordinatortxs []*common.L1Tx, l2txs []*common.PoolL2Tx) ([]*big.Int, error) { + tokenIDs := make(map[common.TokenID]bool) + for i := 0; i < len(l1usertxs); i++ { + tokenIDs[l1usertxs[i].TokenID] = true + } + for i := 0; i < len(l1coordinatortxs); i++ { + tokenIDs[l1coordinatortxs[i].TokenID] = true + } + for i := 0; i < len(l2txs); i++ { + // as L2Tx does not have parameter TokenID, get it from the + // AccountsDB (in the StateDB) + acc, err := s.GetAccount(l2txs[i].ToIdx) + if err != nil { + return nil, err + } + tokenIDs[acc.TokenID] = true + } + var tBI []*big.Int + for t := range tokenIDs { + tBI = append(tBI, t.BigInt()) + } + return tBI, nil } // processL1Tx process the given L1Tx applying the needed updates to the -// StateDB depending on the transaction Type. -func (s *StateDB) processL1Tx(exitTree *merkletree.MerkleTree, tx *common.L1Tx) (*common.Idx, *common.Account, error) { +// StateDB depending on the transaction Type. It returns the 3 parameters +// related to the Exit (in case of): Idx, ExitAccount, boolean determining if +// the Exit created a new Leaf in the ExitTree. +func (s *StateDB) processL1Tx(exitTree *merkletree.MerkleTree, tx *common.L1Tx) (*common.Idx, *common.Account, bool, error) { + // ZKInputs + if s.zki != nil { + // Txs + // s.zki.TxCompressedData[s.i] = tx.TxCompressedData() // uncomment once L1Tx.TxCompressedData is ready + s.zki.FromIdx[s.i] = tx.FromIdx.BigInt() + s.zki.ToIdx[s.i] = tx.ToIdx.BigInt() + s.zki.OnChain[s.i] = big.NewInt(1) + + // L1Txs + s.zki.LoadAmountF[s.i] = tx.LoadAmount + s.zki.FromEthAddr[s.i] = common.EthAddrToBigInt(tx.FromEthAddr) + if tx.FromBJJ != nil { + s.zki.FromBJJCompressed[s.i] = BJJCompressedTo256BigInts(tx.FromBJJ.Compress()) + } + + // Intermediate States + s.zki.ISOnChain[s.i] = big.NewInt(1) + } + switch tx.Type { case common.TxTypeForceTransfer, common.TxTypeTransfer: // go to the MT account of sender and receiver, and update balance // & nonce err := s.applyTransfer(tx.Tx()) if err != nil { - return nil, nil, err + return nil, nil, false, err } case common.TxTypeCreateAccountDeposit: // add new account to the MT, update balance of the MT account err := s.applyCreateAccount(tx) if err != nil { - return nil, nil, err + return nil, nil, false, err + } + + if s.zki != nil { + s.zki.AuxFromIdx[s.i] = s.idx.BigInt() // last s.idx is the one used for creating the new account + s.zki.NewAccount[s.i] = big.NewInt(1) } case common.TxTypeDeposit: // update balance of the MT account err := s.applyDeposit(tx, false) if err != nil { - return nil, nil, err + return nil, nil, false, err } case common.TxTypeDepositTransfer: // update balance in MT account, update balance & nonce of sender // & receiver err := s.applyDeposit(tx, true) if err != nil { - return nil, nil, err + return nil, nil, false, err } case common.TxTypeCreateAccountDepositTransfer: // add new account to the merkletree, update balance in MT account, // update balance & nonce of sender & receiver - err := s.applyCreateAccount(tx) + err := s.applyCreateAccountDepositTransfer(tx) if err != nil { - return nil, nil, err + return nil, nil, false, err } - err = s.applyTransfer(tx.Tx()) - if err != nil { - return nil, nil, err + + if s.zki != nil { + s.zki.AuxFromIdx[s.i] = s.idx.BigInt() // last s.idx is the one used for creating the new account + s.zki.NewAccount[s.i] = big.NewInt(1) } case common.TxTypeExit: // execute exit flow - exitAccount, err := s.applyExit(exitTree, tx.Tx()) + exitAccount, newExit, err := s.applyExit(exitTree, tx.Tx()) if err != nil { - return nil, nil, err + return nil, nil, false, err } - return &tx.FromIdx, exitAccount, nil + return &tx.FromIdx, exitAccount, newExit, nil default: } - return nil, nil, nil + return nil, nil, false, nil } -// processL2Tx process the given L2Tx applying the needed updates to -// the StateDB depending on the transaction Type. -func (s *StateDB) processL2Tx(exitTree *merkletree.MerkleTree, tx *common.L2Tx) (*common.Idx, *common.Account, error) { +// processL2Tx process the given L2Tx applying the needed updates to the +// StateDB depending on the transaction Type. It returns the 3 parameters +// related to the Exit (in case of): Idx, ExitAccount, boolean determining if +// the Exit created a new Leaf in the ExitTree. +func (s *StateDB) processL2Tx(exitTree *merkletree.MerkleTree, tx *common.PoolL2Tx) (*common.Idx, *common.Account, bool, error) { + // ZKInputs + if s.zki != nil { + // Txs + // s.zki.TxCompressedData[s.i] = tx.TxCompressedData() // uncomment once L1Tx.TxCompressedData is ready + // s.zki.TxCompressedDataV2[s.i] = tx.TxCompressedDataV2() // uncomment once L2Tx.TxCompressedDataV2 is ready + s.zki.FromIdx[s.i] = tx.FromIdx.BigInt() + s.zki.ToIdx[s.i] = tx.ToIdx.BigInt() + + // fill AuxToIdx if needed + if tx.ToIdx == common.Idx(0) { + // Idx not set in the Tx, get it from DB through ToEthAddr or ToBJJ + var idx common.Idx + if !bytes.Equal(tx.ToEthAddr.Bytes(), ffAddr.Bytes()) { + idx = s.getIdxByEthAddr(tx.ToEthAddr) + if idx == common.Idx(0) { + return nil, nil, false, fmt.Errorf("Idx can not be found for given tx.FromEthAddr") + } + } else { + idx = s.getIdxByBJJ(tx.ToBJJ) + if idx == common.Idx(0) { + return nil, nil, false, fmt.Errorf("Idx can not be found for given tx.FromBJJ") + } + } + s.zki.AuxToIdx[s.i] = idx.BigInt() + } + s.zki.ToBJJAy[s.i] = tx.ToBJJ.Y + s.zki.ToEthAddr[s.i] = common.EthAddrToBigInt(tx.ToEthAddr) + + s.zki.OnChain[s.i] = big.NewInt(0) + s.zki.NewAccount[s.i] = big.NewInt(0) + + // L2Txs + // s.zki.RqOffset[s.i] = // TODO Rq once TxSelector is ready + // s.zki.RqTxCompressedDataV2[s.i] = // TODO + // s.zki.RqToEthAddr[s.i] = common.EthAddrToBigInt(tx.RqToEthAddr) // TODO + // s.zki.RqToBJJAy[s.i] = tx.ToBJJ.Y // TODO + s.zki.S[s.i] = tx.Signature.S + s.zki.R8x[s.i] = tx.Signature.R8.X + s.zki.R8y[s.i] = tx.Signature.R8.Y + } + switch tx.Type { case common.TxTypeTransfer: // go to the MT account of sender and receiver, and update // balance & nonce err := s.applyTransfer(tx.Tx()) if err != nil { - return nil, nil, err + return nil, nil, false, err } case common.TxTypeExit: // execute exit flow - exitAccount, err := s.applyExit(exitTree, tx.Tx()) + exitAccount, newExit, err := s.applyExit(exitTree, tx.Tx()) if err != nil { - return nil, nil, err + return nil, nil, false, err } - return &tx.FromIdx, exitAccount, nil + return &tx.FromIdx, exitAccount, newExit, nil default: } - return nil, nil, nil + return nil, nil, false, nil } // applyCreateAccount creates a new account in the account of the depositer, it @@ -187,10 +392,26 @@ func (s *StateDB) applyCreateAccount(tx *common.L1Tx) error { EthAddr: tx.FromEthAddr, } - _, err := s.CreateAccount(common.Idx(s.idx+1), account) + p, err := s.CreateAccount(common.Idx(s.idx+1), account) if err != nil { return err } + if s.zki != nil { + s.zki.TokenID1[s.i] = tx.TokenID.BigInt() + s.zki.Nonce1[s.i] = big.NewInt(0) + if babyjub.PointCoordSign(tx.FromBJJ.X) { + s.zki.Sign1[s.i] = big.NewInt(1) + } + s.zki.Ay1[s.i] = tx.FromBJJ.Y + s.zki.Balance1[s.i] = tx.LoadAmount + s.zki.EthAddr1[s.i] = common.EthAddrToBigInt(tx.FromEthAddr) + s.zki.Siblings1[s.i] = siblingsToZKInputFormat(p.Siblings) + if p.IsOld0 { + s.zki.IsOld0_1[s.i] = big.NewInt(1) + } + s.zki.OldKey1[s.i] = p.OldKey.BigInt() + s.zki.OldValue1[s.i] = p.OldValue.BigInt() + } s.idx = s.idx + 1 return s.setIdx(s.idx) @@ -208,8 +429,9 @@ func (s *StateDB) applyDeposit(tx *common.L1Tx, transfer bool) error { accSender.Balance = new(big.Int).Add(accSender.Balance, tx.LoadAmount) // in case that the tx is a L1Tx>DepositTransfer + var accReceiver *common.Account if transfer { - accReceiver, err := s.GetAccount(tx.ToIdx) + accReceiver, err = s.GetAccount(tx.ToIdx) if err != nil { return err } @@ -217,17 +439,46 @@ func (s *StateDB) applyDeposit(tx *common.L1Tx, transfer bool) error { accSender.Balance = new(big.Int).Sub(accSender.Balance, tx.Amount) // add amount to the receiver accReceiver.Balance = new(big.Int).Add(accReceiver.Balance, tx.Amount) - // update receiver account in localStateDB - _, err = s.UpdateAccount(tx.ToIdx, accReceiver) - if err != nil { - return err - } } // update sender account in localStateDB - _, err = s.UpdateAccount(tx.FromIdx, accSender) + p, err := s.UpdateAccount(tx.FromIdx, accSender) if err != nil { return err } + if s.zki != nil { + s.zki.TokenID1[s.i] = accSender.TokenID.BigInt() + s.zki.Nonce1[s.i] = accSender.Nonce.BigInt() + if babyjub.PointCoordSign(accSender.PublicKey.X) { + s.zki.Sign1[s.i] = big.NewInt(1) + } + s.zki.Ay1[s.i] = accSender.PublicKey.Y + s.zki.Balance1[s.i] = accSender.Balance + s.zki.EthAddr1[s.i] = common.EthAddrToBigInt(accSender.EthAddr) + s.zki.Siblings1[s.i] = siblingsToZKInputFormat(p.Siblings) + // IsOld0_1, OldKey1, OldValue1 not needed as this is not an insert + } + + // this is done after updating Sender Account (depositer) + if transfer { + // update receiver account in localStateDB + p, err := s.UpdateAccount(tx.ToIdx, accReceiver) + if err != nil { + return err + } + if s.zki != nil { + s.zki.TokenID2[s.i] = accReceiver.TokenID.BigInt() + s.zki.Nonce2[s.i] = accReceiver.Nonce.BigInt() + if babyjub.PointCoordSign(accReceiver.PublicKey.X) { + s.zki.Sign2[s.i] = big.NewInt(1) + } + s.zki.Ay2[s.i] = accReceiver.PublicKey.Y + s.zki.Balance2[s.i] = accReceiver.Balance + s.zki.EthAddr2[s.i] = common.EthAddrToBigInt(accReceiver.EthAddr) + s.zki.Siblings2[s.i] = siblingsToZKInputFormat(p.Siblings) + // IsOld0_2, OldKey2, OldValue2 not needed as this is not an insert + } + } + return nil } @@ -252,31 +503,130 @@ func (s *StateDB) applyTransfer(tx *common.Tx) error { // add amount to the receiver accReceiver.Balance = new(big.Int).Add(accReceiver.Balance, tx.Amount) - // update receiver account in localStateDB - _, err = s.UpdateAccount(tx.ToIdx, accReceiver) + // update sender account in localStateDB + pSender, err := s.UpdateAccount(tx.FromIdx, accSender) if err != nil { return err } - // update sender account in localStateDB - _, err = s.UpdateAccount(tx.FromIdx, accSender) + if s.zki != nil { + s.zki.TokenID1[s.i] = accSender.TokenID.BigInt() + s.zki.Nonce1[s.i] = accSender.Nonce.BigInt() + if babyjub.PointCoordSign(accSender.PublicKey.X) { + s.zki.Sign1[s.i] = big.NewInt(1) + } + s.zki.Ay1[s.i] = accSender.PublicKey.Y + s.zki.Balance1[s.i] = accSender.Balance + s.zki.EthAddr1[s.i] = common.EthAddrToBigInt(accSender.EthAddr) + s.zki.Siblings1[s.i] = siblingsToZKInputFormat(pSender.Siblings) + } + + // update receiver account in localStateDB + pReceiver, err := s.UpdateAccount(tx.ToIdx, accReceiver) if err != nil { return err } + if s.zki != nil { + s.zki.TokenID2[s.i] = accReceiver.TokenID.BigInt() + s.zki.Nonce2[s.i] = accReceiver.Nonce.BigInt() + if babyjub.PointCoordSign(accReceiver.PublicKey.X) { + s.zki.Sign2[s.i] = big.NewInt(1) + } + s.zki.Ay2[s.i] = accReceiver.PublicKey.Y + s.zki.Balance2[s.i] = accReceiver.Balance + s.zki.EthAddr2[s.i] = common.EthAddrToBigInt(accReceiver.EthAddr) + s.zki.Siblings2[s.i] = siblingsToZKInputFormat(pReceiver.Siblings) + } return nil } -func (s *StateDB) applyExit(exitTree *merkletree.MerkleTree, tx *common.Tx) (*common.Account, error) { +// applyCreateAccountDepositTransfer, in a single tx, creates a new account, +// makes a deposit, and performs a transfer to another account +func (s *StateDB) applyCreateAccountDepositTransfer(tx *common.L1Tx) error { + accSender := &common.Account{ + TokenID: tx.TokenID, + Nonce: 0, + Balance: tx.LoadAmount, + PublicKey: tx.FromBJJ, + EthAddr: tx.FromEthAddr, + } + accSender.Balance = new(big.Int).Add(accSender.Balance, tx.LoadAmount) + accReceiver, err := s.GetAccount(tx.ToIdx) + if err != nil { + return err + } + // subtract amount to the sender + accSender.Balance = new(big.Int).Sub(accSender.Balance, tx.Amount) + // add amount to the receiver + accReceiver.Balance = new(big.Int).Add(accReceiver.Balance, tx.Amount) + + // create Account of the Sender + p, err := s.CreateAccount(common.Idx(s.idx+1), accSender) + if err != nil { + return err + } + if s.zki != nil { + s.zki.TokenID1[s.i] = tx.TokenID.BigInt() + s.zki.Nonce1[s.i] = big.NewInt(0) + if babyjub.PointCoordSign(tx.FromBJJ.X) { + s.zki.Sign1[s.i] = big.NewInt(1) + } + s.zki.Ay1[s.i] = tx.FromBJJ.Y + s.zki.Balance1[s.i] = tx.LoadAmount + s.zki.EthAddr1[s.i] = common.EthAddrToBigInt(tx.FromEthAddr) + s.zki.Siblings1[s.i] = siblingsToZKInputFormat(p.Siblings) + if p.IsOld0 { + s.zki.IsOld0_1[s.i] = big.NewInt(1) + } + s.zki.OldKey1[s.i] = p.OldKey.BigInt() + s.zki.OldValue1[s.i] = p.OldValue.BigInt() + } + + // update receiver account in localStateDB + p, err = s.UpdateAccount(tx.ToIdx, accReceiver) + if err != nil { + return err + } + if s.zki != nil { + s.zki.TokenID2[s.i] = accReceiver.TokenID.BigInt() + s.zki.Nonce2[s.i] = accReceiver.Nonce.BigInt() + if babyjub.PointCoordSign(accReceiver.PublicKey.X) { + s.zki.Sign2[s.i] = big.NewInt(1) + } + s.zki.Ay2[s.i] = accReceiver.PublicKey.Y + s.zki.Balance2[s.i] = accReceiver.Balance + s.zki.EthAddr2[s.i] = common.EthAddrToBigInt(accReceiver.EthAddr) + s.zki.Siblings2[s.i] = siblingsToZKInputFormat(p.Siblings) + } + + s.idx = s.idx + 1 + return s.setIdx(s.idx) +} + +// It returns the ExitAccount and a boolean determining if the Exit created a +// new Leaf in the ExitTree. +func (s *StateDB) applyExit(exitTree *merkletree.MerkleTree, tx *common.Tx) (*common.Account, bool, error) { // 0. subtract tx.Amount from current Account in StateMT // add the tx.Amount into the Account (tx.FromIdx) in the ExitMT acc, err := s.GetAccount(tx.FromIdx) if err != nil { - return nil, err + return nil, false, err } acc.Balance = new(big.Int).Sub(acc.Balance, tx.Amount) - _, err = s.UpdateAccount(tx.FromIdx, acc) + p, err := s.UpdateAccount(tx.FromIdx, acc) if err != nil { - return nil, err + return nil, false, err + } + if s.zki != nil { + s.zki.TokenID1[s.i] = acc.TokenID.BigInt() + s.zki.Nonce1[s.i] = acc.Nonce.BigInt() + if babyjub.PointCoordSign(acc.PublicKey.X) { + s.zki.Sign1[s.i] = big.NewInt(1) + } + s.zki.Ay1[s.i] = acc.PublicKey.Y + s.zki.Balance1[s.i] = acc.Balance + s.zki.EthAddr1[s.i] = common.EthAddrToBigInt(acc.EthAddr) + s.zki.Siblings1[s.i] = siblingsToZKInputFormat(p.Siblings) } exitAccount, err := getAccountInTreeDB(exitTree.DB(), tx.FromIdx) @@ -291,16 +641,16 @@ func (s *StateDB) applyExit(exitTree *merkletree.MerkleTree, tx *common.Tx) (*co EthAddr: acc.EthAddr, } _, err = createAccountInTreeDB(exitTree.DB(), exitTree, tx.FromIdx, exitAccount) - return exitAccount, err + return exitAccount, true, err } else if err != nil { - return exitAccount, err + return exitAccount, false, err } // 1b. if idx already exist in exitTree: // update account, where account.Balance += exitAmount exitAccount.Balance = new(big.Int).Add(exitAccount.Balance, tx.Amount) _, err = updateAccountInTreeDB(exitTree.DB(), exitTree, tx.FromIdx, exitAccount) - return exitAccount, err + return exitAccount, false, err } // getIdx returns the stored Idx from the localStateDB, which is the last Idx diff --git a/db/statedb/txprocessors_test.go b/db/statedb/txprocessors_test.go index 5a9dd06..3e95d92 100644 --- a/db/statedb/txprocessors_test.go +++ b/db/statedb/txprocessors_test.go @@ -1,6 +1,8 @@ package statedb import ( + "encoding/json" + "fmt" "io/ioutil" "strings" "testing" @@ -11,6 +13,8 @@ import ( "github.com/stretchr/testify/require" ) +var debug = false + func TestProcessTxs(t *testing.T) { dir, err := ioutil.TempDir("", "tmpdb") require.Nil(t, err) @@ -30,9 +34,9 @@ func TestProcessTxs(t *testing.T) { // iterate for each batch for i := 0; i < len(l1Txs); i++ { - l2Txs := common.PoolL2TxsToL2Txs(poolL2Txs[i]) + // l2Txs := common.PoolL2TxsToL2Txs(poolL2Txs[i]) - _, _, err := sdb.ProcessTxs(true, l1Txs[i], coordinatorL1Txs[i], l2Txs) + _, _, err := sdb.ProcessTxs(true, true, l1Txs[i], coordinatorL1Txs[i], poolL2Txs[i]) require.Nil(t, err) } @@ -65,8 +69,8 @@ func TestProcessTxsBatchByBatch(t *testing.T) { assert.Equal(t, 7, len(poolL2Txs[2])) // use first batch - l2txs := common.PoolL2TxsToL2Txs(poolL2Txs[0]) - _, exitInfos, err := sdb.ProcessTxs(true, l1Txs[0], coordinatorL1Txs[0], l2txs) + // l2txs := common.PoolL2TxsToL2Txs(poolL2Txs[0]) + _, exitInfos, err := sdb.ProcessTxs(true, true, l1Txs[0], coordinatorL1Txs[0], poolL2Txs[0]) require.Nil(t, err) assert.Equal(t, 0, len(exitInfos)) acc, err := sdb.GetAccount(common.Idx(1)) @@ -74,8 +78,8 @@ func TestProcessTxsBatchByBatch(t *testing.T) { assert.Equal(t, "28", acc.Balance.String()) // use second batch - l2txs = common.PoolL2TxsToL2Txs(poolL2Txs[1]) - _, exitInfos, err = sdb.ProcessTxs(true, l1Txs[1], coordinatorL1Txs[1], l2txs) + // l2txs = common.PoolL2TxsToL2Txs(poolL2Txs[1]) + _, exitInfos, err = sdb.ProcessTxs(true, true, l1Txs[1], coordinatorL1Txs[1], poolL2Txs[1]) require.Nil(t, err) assert.Equal(t, 5, len(exitInfos)) acc, err = sdb.GetAccount(common.Idx(1)) @@ -83,11 +87,38 @@ func TestProcessTxsBatchByBatch(t *testing.T) { assert.Equal(t, "48", acc.Balance.String()) // use third batch - l2txs = common.PoolL2TxsToL2Txs(poolL2Txs[2]) - _, exitInfos, err = sdb.ProcessTxs(true, l1Txs[2], coordinatorL1Txs[2], l2txs) + // l2txs = common.PoolL2TxsToL2Txs(poolL2Txs[2]) + _, exitInfos, err = sdb.ProcessTxs(true, true, l1Txs[2], coordinatorL1Txs[2], poolL2Txs[2]) require.Nil(t, err) assert.Equal(t, 1, len(exitInfos)) acc, err = sdb.GetAccount(common.Idx(1)) assert.Nil(t, err) assert.Equal(t, "23", acc.Balance.String()) } + +func TestZKInputsGeneration(t *testing.T) { + dir, err := ioutil.TempDir("", "tmpdb") + require.Nil(t, err) + + sdb, err := NewStateDB(dir, true, 32) + assert.Nil(t, err) + + // generate test transactions from test.SetTest0 code + parser := test.NewParser(strings.NewReader(test.SetTest0)) + instructions, err := parser.Parse() + assert.Nil(t, err) + + l1Txs, coordinatorL1Txs, poolL2Txs := test.GenerateTestTxs(t, instructions) + assert.Equal(t, 29, len(l1Txs[0])) + assert.Equal(t, 0, len(coordinatorL1Txs[0])) + assert.Equal(t, 21, len(poolL2Txs[0])) + + zki, _, err := sdb.ProcessTxs(false, true, l1Txs[0], coordinatorL1Txs[0], poolL2Txs[0]) + require.Nil(t, err) + + s, err := json.Marshal(zki) + require.Nil(t, err) + if debug { + fmt.Println(string(s)) + } +} diff --git a/db/statedb/utils.go b/db/statedb/utils.go new file mode 100644 index 0000000..7ba3d74 --- /dev/null +++ b/db/statedb/utils.go @@ -0,0 +1,45 @@ +package statedb + +import ( + "math/big" + + ethCommon "github.com/ethereum/go-ethereum/common" + "github.com/hermeznetwork/hermez-node/common" + "github.com/iden3/go-iden3-crypto/babyjub" + "github.com/iden3/go-merkletree" +) + +// TODO +func (s *StateDB) getIdxByEthAddr(addr ethCommon.Address) common.Idx { + return common.Idx(0) +} + +// TODO +func (s *StateDB) getIdxByBJJ(pk *babyjub.PublicKey) common.Idx { + return common.Idx(0) +} + +func siblingsToZKInputFormat(s []*merkletree.Hash) []*big.Int { + b := make([]*big.Int, len(s)) + for i := 0; i < len(s); i++ { + b[i] = s[i].BigInt() + } + return b +} + +// BJJCompressedTo256BigInts returns a [256]*big.Int array with the bit +// representation of the babyjub.PublicKeyComp +func BJJCompressedTo256BigInts(pkComp babyjub.PublicKeyComp) [256]*big.Int { + var r [256]*big.Int + b := pkComp[:] + + for i := 0; i < 256; i++ { + if b[i/8]&(1<<(i%8)) == 0 { + r[i] = big.NewInt(0) + } else { + r[i] = big.NewInt(1) + } + } + + return r +} diff --git a/db/statedb/utils_test.go b/db/statedb/utils_test.go new file mode 100644 index 0000000..c4453ca --- /dev/null +++ b/db/statedb/utils_test.go @@ -0,0 +1,39 @@ +package statedb + +import ( + "math/big" + "testing" + + "github.com/iden3/go-iden3-crypto/babyjub" + "github.com/stretchr/testify/assert" +) + +func TestBJJCompressedTo256BigInt(t *testing.T) { + var pkComp babyjub.PublicKeyComp + r := BJJCompressedTo256BigInts(pkComp) + zero := big.NewInt(0) + for i := 0; i < 256; i++ { + assert.Equal(t, zero, r[i]) + } + + pkComp[0] = 3 + r = BJJCompressedTo256BigInts(pkComp) + one := big.NewInt(1) + for i := 0; i < 256; i++ { + if i != 0 && i != 1 { + assert.Equal(t, zero, r[i]) + } else { + assert.Equal(t, one, r[i]) + } + } + + pkComp[31] = 4 + r = BJJCompressedTo256BigInts(pkComp) + for i := 0; i < 256; i++ { + if i != 0 && i != 1 && i != 250 { + assert.Equal(t, zero, r[i]) + } else { + assert.Equal(t, one, r[i]) + } + } +}