diff --git a/.eslintrc.js b/.eslintrc.js index 32e2c671..ce75c5cf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -24,7 +24,6 @@ module.exports = { "semi": [ "error", "always" - ], - "mocha/no-exclusive-tests": "error" + ] } }; diff --git a/circuits/balancesupdater.circom b/circuits/balancesupdater.circom index b3d59770..767c9fc5 100644 --- a/circuits/balancesupdater.circom +++ b/circuits/balancesupdater.circom @@ -8,7 +8,7 @@ In case of an offChan TX the system does not allow it. - It also checks overflow of < 2^126 + It also checks overflow of < 2^192 */ include "../node_modules/circomlib/circuits/bitify.circom"; @@ -19,9 +19,10 @@ template BalancesUpdater() { signal input oldStAmountRecieiver; signal input amount; signal input loadAmount; - signal input maxFee; - signal input minFee; + signal input userFee; + signal input operatorsFee; signal input onChain; + signal input nop; signal input countersIn; signal input countersBase; @@ -30,41 +31,53 @@ template BalancesUpdater() { signal output countersOut; signal output update2; - signal limitsOk; - signal feeOk; - signal txOk; + signal feeApplies; // 1 If fee applies (offChain) 0 if not applies (onChain) + signal appliedFee; // amount of fee that needs to be discounted - component n2bSender = Num2Bits(127); - component n2bReceiver = Num2Bits(127); + signal limitsOk; // 1 if from is >0 and <2**192 + signal feeOk; // If userFee > operatorFee + signal txOk; // If both are ok. + + signal effectiveAmount1; + signal effectiveAmount2; + signal effectiveLoadAmount; + + component n2bSender = Num2Bits(193); component feeGE = GreaterEqThan(128); - component amountIsZero = IsZero(); + component effectiveAmountIsZero = IsZero(); + + feeApplies <== (1-onChain)*(1-nop); // Fee applies only on onChainTx and is not a NOP - amountIsZero.in <== amount; + appliedFee <== operatorsFee*feeApplies; - // Only apply fee if amount >0 - signal applyFee; - applyFee <== minFee*(1- amountIsZero.out); + effectiveLoadAmount <== loadAmount*onChain; + effectiveAmount1 <== amount*(1-nop); - n2bSender.in <== (1<<126) + oldStAmountSender + loadAmount - (amount + applyFee); - n2bReceiver.in <== oldStAmountRecieiver + amount; + // Check limits and fees on limits + n2bSender.in <== (1<<192) + oldStAmountSender + effectiveLoadAmount - effectiveAmount1 - appliedFee; - feeGE.in[0] <== maxFee; - feeGE.in[1] <== applyFee; + // Fee offered by the user must be greater that the operators demanded + feeGE.in[0] <== userFee; + feeGE.in[1] <== operatorsFee; feeOk <== feeGE.out; - limitsOk <== (n2bSender.out[126])*(1-n2bReceiver.out[126]); + limitsOk <== n2bSender.out[192]; txOk <== feeOk * limitsOk; // if not onChain and not txOk => error (1-txOk)*(1-onChain) === 0; + effectiveAmount2 <== txOk*effectiveAmount1; + + effectiveAmountIsZero.in <== effectiveAmount2; + // if !txOk then return 0; - newStAmountSender <== oldStAmountSender + loadAmount - (applyFee + amount)*txOk; - newStAmountReceiver <== oldStAmountRecieiver + amount*txOk + newStAmountSender <== oldStAmountSender + effectiveLoadAmount - effectiveAmount2 - appliedFee; + newStAmountReceiver <== oldStAmountRecieiver + effectiveAmount2 // Counters - countersOut <== countersIn + countersBase*(1- amountIsZero.out); - update2 <== txOk * (1 - amountIsZero.out); + countersOut <== countersIn + countersBase*feeApplies; + update2 <== 1-effectiveAmountIsZero.out; } diff --git a/circuits/decodetx.circom b/circuits/decodetx.circom index b631972f..bb2ecee4 100644 --- a/circuits/decodetx.circom +++ b/circuits/decodetx.circom @@ -15,7 +15,7 @@ template DecodeTx(nLevels) { signal input previousOnChain; signal input oldOnChainHash; signal input txData; - signal input rqTxHash; + signal input rqTxData; signal input loadAmount; signal input ethAddr; signal input ax; @@ -24,21 +24,21 @@ template DecodeTx(nLevels) { signal output fromIdx; // 64 0..63 signal output toIdx; // 64 64..127 signal output amount; // 16 128..143 - signal output coin; // 16 144..159 - signal output nonce; // 48 160..207 - signal output maxFee; // 16 208..223 - signal output rqOffset; // 4 224..227 - signal output onChain // 1 228 - signal output newAccount // 1 229 - - signal output dataAvailabilityBits[nLevels*2+16+16]; + signal output coin; // 32 144..175 + signal output nonce; // 48 176..223 + signal output userFee; // 16 224..239 + signal output rqOffset; // 3 240..242 + signal output onChain // 1 243 + signal output newAccount // 1 244 + + signal output dataAvailabilityBits[nLevels*2+16]; signal output offChainHash; // For the signature signal output newOnChainHash; // For the chained onchain var i; var IDEN3_ROLLUP_TX = 1625792389453394788515067275302403776356063435417596283072371667635754651289; // blake2b("IDEN3_ROLLUP_TX") % r - component n2bData = Num2Bits(230); + component n2bData = Num2Bits(245); n2bData.in <== txData; // from @@ -77,8 +77,8 @@ template DecodeTx(nLevels) { // coin //////// - component b2nCoin = Bits2Num(16); - for (i=0; i<16; i++) { + component b2nCoin = Bits2Num(32); + for (i=0; i<32; i++) { b2nCoin.in[i] <== n2bData.out[144 + i]; } b2nCoin.out ==> coin; @@ -87,47 +87,44 @@ template DecodeTx(nLevels) { //////// component b2nNonce = Bits2Num(48); for (i=0; i<48; i++) { - b2nNonce.in[i] <== n2bData.out[160 + i]; + b2nNonce.in[i] <== n2bData.out[176 + i]; } b2nNonce.out ==> nonce; -// maxFee +// userFee //////// - component dfMaxFee = DecodeFloatBin(); + component dfUserFee = DecodeFloatBin(); for (i=0; i<16; i++) { - dfMaxFee.in[i] <== n2bData.out[208 + i]; + dfUserFee.in[i] <== n2bData.out[224 + i]; } - dfMaxFee.out ==> maxFee; + dfUserFee.out ==> userFee; // rqOffset //////// - component b2nRqOffset = Bits2Num(4); - for (i=0; i<4; i++) { - b2nRqOffset.in[i] <== n2bData.out[224 + i]; + component b2nRqOffset = Bits2Num(3); + for (i=0; i<3; i++) { + b2nRqOffset.in[i] <== n2bData.out[240 + i]; } b2nRqOffset.out ==> rqOffset; // onChain //////// - onChain <== n2bData.out[228]; + onChain <== n2bData.out[243]; // newAccount //////// - newAccount <== n2bData.out[229]; + newAccount <== n2bData.out[244]; // Data Availability bits //////// for (i=0; i offChainHash; diff --git a/circuits/feeSelector.circom b/circuits/feeSelector.circom index 75f5319a..cd7c2bae 100644 --- a/circuits/feeSelector.circom +++ b/circuits/feeSelector.circom @@ -118,7 +118,7 @@ template FeeSelector() { signal input coin; signal input feePlanCoin[16]; signal input feePlanFee[16]; - signal output minFee; + signal output operatorsFee; signal output countersBase; component stp[16]; @@ -139,6 +139,6 @@ template FeeSelector() { stp[i].feePlanFee <== feePlanFee[i]; } - minFee <== stp[15].currentFeeOut; + operatorsFee <== stp[15].currentFeeOut; countersBase <== stp[15].counterBaseOut * stp[15].isSelectedOut; } diff --git a/circuits/requiredtxverifier.circom b/circuits/requiredtxverifier.circom index 3e390e12..ded1ab82 100644 --- a/circuits/requiredtxverifier.circom +++ b/circuits/requiredtxverifier.circom @@ -11,26 +11,26 @@ RqTxVerifier └──┬─┬─┬──────┘ │ │ │ ╲ │ │ │ - TxHash -1 ╲ │ │ │ + TxData -1 ╲ │ │ │ ──────────▶ ╲▼ │ │ - TxHash -2 ╲ │ │ + TxData -2 ╲ │ │ ──────────▶ ╲▼ │ - TxHash -3 ╲ │ + TxData -3 ╲ │ ──────────▶ ╲▼ - TxHash -4 ╲ + TxData -4 ╲ ──────────▶ ╲ - TxHash +3 Mux3 ──────────┐ + TxData +3 Mux3 ──────────┐ ──────────▶ ╱ │ - TxHash +2 ╱ │ ┌────────┐ + TxData +2 ╱ │ ┌────────┐ ──────────▶ ╱ │ │ │ - TxHash +1 ╱ └─────────▶│ │ + TxData +1 ╱ └─────────▶│ │ ──────────▶ ╱ │ === │ 0 ╱ │ │ ──────────▶ ╱ ┌────────────────────▶│ │ ╱ │ └────────┘ ╱ │ │ - rqHash │ + rqTxData │ ────────────────────┘ */ @@ -39,21 +39,21 @@ include "../node_modules/circomlib/circuits/bitify.circom"; include "../node_modules/circomlib/circuits/mux3.circom"; template RequiredTxVerifier() { - signal input pastTxHash[4]; - signal input futureTxHash[3]; - signal input rqTxHash; + signal input pastTxData[4]; + signal input futureTxData[3]; + signal input rqTxData; signal input rqTxOffset; component mux = Mux3(); mux.c[0] <== 0; - mux.c[1] <== futureTxHash[0]; - mux.c[2] <== futureTxHash[1]; - mux.c[3] <== futureTxHash[2]; - mux.c[4] <== pastTxHash[3]; - mux.c[5] <== pastTxHash[2]; - mux.c[6] <== pastTxHash[1]; - mux.c[7] <== pastTxHash[0]; + mux.c[1] <== futureTxData[0]; + mux.c[2] <== futureTxData[1]; + mux.c[3] <== futureTxData[2]; + mux.c[4] <== pastTxData[3]; + mux.c[5] <== pastTxData[2]; + mux.c[6] <== pastTxData[1]; + mux.c[7] <== pastTxData[0]; component n2b = Num2Bits(3); @@ -62,5 +62,5 @@ template RequiredTxVerifier() { n2b.out[1] ==> mux.s[1]; n2b.out[2] ==> mux.s[2]; - mux.out === rqTxHash; + mux.out === rqTxData; } diff --git a/circuits/rollup.circom b/circuits/rollup.circom index bbb6bb2e..c0a53f28 100644 --- a/circuits/rollup.circom +++ b/circuits/rollup.circom @@ -18,7 +18,7 @@ template Rollup(nTx, nLevels) { signal output countersOut; signal private input txData[nTx]; - signal private input rqTxHash[nTx]; + signal private input rqTxData[nTx]; signal private input s[nTx]; signal private input r8x[nTx]; signal private input r8y[nTx]; @@ -68,7 +68,7 @@ template Rollup(nTx, nLevels) { } var nDataAvailabilityBitsPerTx; - nDataAvailabilityBitsPerTx = (nLevels*2+16+16) + nDataAvailabilityBitsPerTx = (nLevels*2+16) component offChainHasher = Sha256(nDataAvailabilityBitsPerTx*nTx); component decodeTx[nTx] @@ -86,12 +86,12 @@ template Rollup(nTx, nLevels) { decodeTx[i].previousOnChain <== decodeTx[i-1].onChain; } decodeTx[i].txData <== txData[i]; - decodeTx[i].rqTxHash <== rqTxHash[i]; + decodeTx[i].rqTxData <== rqTxData[i]; decodeTx[i].loadAmount <== loadAmount[i]; decodeTx[i].ethAddr <== ethAddr[i]; decodeTx[i].ax <== ax[i]; decodeTx[i].ay <== ay[i]; - for (j=0; j countersOut; @@ -264,8 +286,8 @@ template RollupTx(nLevels) { for (i=0; i key2; - verifySignEnabled <== (1-onChain)*isFromIdx + verifySignEnabled <== (1-onChain)*isFromIdx; + nop <== fromIdxIsZero.out; } diff --git a/circuits/statepacker.circom b/circuits/statepacker.circom index 978e3361..ac7228c0 100644 --- a/circuits/statepacker.circom +++ b/circuits/statepacker.circom @@ -12,13 +12,14 @@ template StatePacker() { signal output out; signal data; - data <== amount + coin * (1<<128) + nonce * (1<<142); - component hash = Poseidon(4, 6, 8, 57); + data <== coin + nonce * (1<<32); + component hash = Poseidon(5, 6, 8, 57); hash.inputs[0] <== data; - hash.inputs[1] <== ax; - hash.inputs[2] <== ay; - hash.inputs[3] <== ethAddr; + hash.inputs[1] <== amount; + hash.inputs[2] <== ax; + hash.inputs[3] <== ay; + hash.inputs[4] <== ethAddr; hash.out ==> out; } diff --git a/js/blockbuilder.js b/js/blockbuilder.js index 3cce7c70..685504fb 100644 --- a/js/blockbuilder.js +++ b/js/blockbuilder.js @@ -1,5 +1,5 @@ -const SMT = require("circomlib").SMT.SMT; +const SMT = require("circomlib").SMT; const SMTTmpDb = require("./smttmpdb"); const utils = require("./utils"); const assert = require("assert"); @@ -8,19 +8,26 @@ const bigInt = require("snarkjs").bigInt; const poseidon = require("circomlib").poseidon; module.exports = class BlockBuilder { - constructor(currentTree, maxNTx, nLevels) { + constructor(db, blockNumber, root, maxNTx, nLevels) { assert((nLevels % 8) == 0); + this.blockNumber = blockNumber; this.maxNTx = maxNTx || 4; this.nLevels = nLevels; this.offChainTxs = []; this.onChainTxs = []; - this.stateTree = new SMT(new SMTTmpDb(currentTree.db), currentTree.root); + this.dbState = new SMTTmpDb(db); + this.stateTree = new SMT(this.dbState, root); + this.dbExit = new SMTTmpDb(db); + this.exitTree = new SMT(this.dbExit, bigInt(0)); + this.feePlan = []; + this.counters = []; + this.input = { - oldStRoot: currentTree.root, + oldStRoot: this.stateTree.root, feePlanCoins: 0, feePlanFees: 0, txData: [], - rqTxHash: [], + rqTxData: [], s: [], r8x: [], r8y: [], @@ -59,12 +66,12 @@ module.exports = class BlockBuilder { amount: 0, coin: 0, nonce: 0, - maxFee: 0, + userFee: 0, rqOffset: 0, inChain: 0, newAccount: 0 }); - this.input.rqTxHash[i]= 0; + this.input.rqTxData[i]= 0; this.input.s[i]= 0; this.input.r8x[i]= 0; this.input.r8y[i]= 0; @@ -87,7 +94,7 @@ module.exports = class BlockBuilder { this.input.oldKey1[i]= 0; this.input.oldValue1[i]= 0; - // State 1 + // State 2 this.input.ax2[i]= 0; this.input.ay2[i]= 0; this.input.amount2[i]= 0; @@ -102,42 +109,125 @@ module.exports = class BlockBuilder { this.input.oldValue2[i]= 0; } - async _addOnChainTx(tx) { + getOperatorFee(coin) { + for (let i=0; i { + for (let i=0; i= ( (idx < 15) ? (1<<13) : (1<<16) ) ) { + throw new Error("Maximum TXs per coin in a block reached"); + } + this.counters[idx]++; } async build() { if (this.builded) throw new Error("Block already builded"); for (let i=0; i> (i*8))&0xFF); + bytes.push((n >> ((size-1-i)*8))&0xFF); } } for (let i=0; i= this.maxNTx) { throw Error("Too many TX per block"); } - this.onChainTxs.push(tx); + if (tx.onChain) { + this.onChainTxs.push(tx); + } else { + this.offChainTxs.push(tx); + } + } + + addCoin(coin, fee) { + const feeF = utils.fix2float(fee); + if (feeF == 0) return; + if (this.feePlan.length >= 16) { + throw new Error("Maximum 16 coins per block"); + } + if ((this.feePlan.length == 15)&&(coin >= 1<<13)) { + throw new Error("Coin 16 muns be less than 2^13"); + } + this.feePlan.push([coin, feeF]); + this.counters.push(0); } diff --git a/js/rollupaccount.js b/js/rollupaccount.js index 3a107aca..85ede890 100644 --- a/js/rollupaccount.js +++ b/js/rollupaccount.js @@ -3,12 +3,20 @@ const ec = new EC("secp256k1"); const keccak256 = require("js-sha3").keccak256; const crypto = require("crypto"); const babyJub = require("circomlib").babyJub; +const eddsa = require("circomlib").eddsa; const bigInt = require("snarkjs").bigInt; +const utils = require("./utils"); +const poseidon = require("circomlib").poseidon; module.exports = class RollupAccount { constructor(privateKey) { if (privateKey) { - this.privateKey = privateKey; + if (typeof(privateKey) != "string") { + this.privateKey = bigInt(privateKey).toString(16); + } else { + this.privateKey = privateKey; + } + while (this.privateKey.length < 64) this.privateKey = "0" + this.privateKey; } else { this.privateKey = crypto.randomBytes(32).toString("hex"); } @@ -40,9 +48,26 @@ module.exports = class RollupAccount { // Derive a private key wit a hash this.rollupPrvKey = keccak256("ROLLUP" + this.privateKey); - const bjPubKey = babyJub.mulPointEscalar(babyJub.Base8, bigInt("0x" + this.rollupPrvKey)); + const bjPubKey = eddsa.prv2pub(this.rollupPrvKey); this.ax = bjPubKey[0].toString(16); this.ay = bjPubKey[1].toString(16); } + + signTx(tx) { + const IDEN3_ROLLUP_TX = bigInt("1625792389453394788515067275302403776356063435417596283072371667635754651289"); + const data = utils.buildTxData(tx); + const hash = poseidon.createHash(6, 8, 57); + + const h = hash([ + IDEN3_ROLLUP_TX, + data, + tx.rqData || 0 + ]); + + const signature = eddsa.signPoseidon(this.rollupPrvKey, h); + tx.r8x = signature.R8[0]; + tx.r8y = signature.R8[1]; + tx.s = signature.S; + } }; diff --git a/js/rollupdb.js b/js/rollupdb.js new file mode 100644 index 00000000..7dcb3419 --- /dev/null +++ b/js/rollupdb.js @@ -0,0 +1,50 @@ +const BlockBuilder = require("./blockbuilder"); +const bigInt = require("snarkjs").bigInt; + +class RollupDB { + + constructor(db, lastBlock, stateRoot) { + this.db = db; + this.lastBlock = lastBlock; + this.stateRoot = stateRoot; + } + + async buildBlock(maxNTx, nLevels) { + return new BlockBuilder(this.db, this.lastBlock+1, this.stateRoot, maxNTx, nLevels); + } + + async consolidate(bb) { + if (bb.blockNumber != this.lastBlock+1) { + throw new Error("Updating the wrong block"); + } + if (!bb.builded) { + await bb.build(); + } + const insertsState = Object.keys(bb.dbState.inserts).map(function(key) { + return [bigInt(key), bb.dbState.inserts[key]]; + }); + const insertsExit = Object.keys(bb.dbExit.inserts).map(function(key) { + return [bigInt(key), bb.dbExit.inserts[key]]; + }); + await this.db.multiIns([ + ...insertsState, + ...insertsExit, + [ bb.blockNumber, [bb.stateTree.root, bb.exitTree.root]], + [ 0, this.lastBlock] + ]); + this.lastBlock = this.blockNumber; + this.stateRoot = bb.stateTree.root; + } +} + +module.exports = async function(db) { + const master = await db.get(0); + if (!master) { + return new RollupDB(db, 0, bigInt(0)); + } + const block = await db.get(master[0]); + if (!block) { + throw new Error("Database corrupted"); + } + return new RollupDB(db, master[0], block[0]); +}; diff --git a/js/utils.js b/js/utils.js index 80eb787c..e85c25d1 100644 --- a/js/utils.js +++ b/js/utils.js @@ -73,31 +73,46 @@ function buildTxData(tx) { res = res.add( bigInt(tx.toIdx || 0).shl(64)); res = res.add( bigInt(fix2float(tx.amount || 0)).shl(128)); res = res.add( bigInt(tx.coin || 0).shl(144)); - res = res.add( bigInt(tx.nonce || 0).shl(160)); - res = res.add( bigInt(fix2float(tx.maxFee || 0)).shl(208)); - res = res.add( bigInt(tx.rqOffset || 0).shl(224)); - res = res.add( bigInt(tx.inChain ? 1 : 0).shl(228)); - res = res.add( bigInt(tx.newAccount ? 1 : 0).shl(229)); + res = res.add( bigInt(tx.nonce || 0).shl(176)); + res = res.add( bigInt(fix2float(tx.userFee || 0)).shl(224)); + res = res.add( bigInt(tx.rqOffset || 0).shl(240)); + res = res.add( bigInt(tx.onChain ? 1 : 0).shl(243)); + res = res.add( bigInt(tx.newAccount ? 1 : 0).shl(244)); return res; } -function hashState(st) { - const hash = poseidon.createHash(6, 8, 57); - - const data = bigInt(st.amount).add( bigInt(st.coin).shl(128) ).add( bigInt(st.nonce).shl(142) ); - - const res = hash([ +function state2array(st) { + const data = bigInt(st.coin).add( bigInt(st.nonce).shl(32) ); + return [ data, + bigInt(st.amount), bigInt("0x" + st.ax), bigInt("0x" + st.ay), bigInt(st.ethAddress), - ]); + ]; +} - return res; +function array2state(a) { + return { + coin: bigInt(a[0]).and(bigInt(1).shl(32).sub(bigInt(1))).toJSNumber(), + nonce: bigInt(a[0]).shr(32).and(bigInt(1).shl(32).sub(bigInt(1))).toJSNumber(), + amount: bigInt(a[1]), + ax: bigInt(a[2]).toString(16), + ay: bigInt(a[3]).toString(16), + ethAddress: "0x" + bigInt(a[4]).toString(16), + }; +} + +function hashState(st) { + const hash = poseidon.createHash(6, 8, 57); + + return hash(state2array(st)); } module.exports.buildTxData = buildTxData; module.exports.fix2float = fix2float; module.exports.float2fix = float2fix; module.exports.hashState = hashState; +module.exports.state2array = state2array; +module.exports.array2state = array2state; diff --git a/test/circuits/input.json b/test/circuits/input.json index 5f4e3135..80e79e82 100644 --- a/test/circuits/input.json +++ b/test/circuits/input.json @@ -1 +1,240 @@ -{"oldStRoot":"0","feePlanCoins":0,"feePlanFees":0,"txData":["0","0","0","1294077440023230710144016724176942272334582932281947479092973291962369"],"rqTxHash":[0,0,0,0],"s":[0,0,0,0],"r8x":[0,0,0,0],"r8y":[0,0,0,0],"loadAmount":[0,0,0,1000],"ethAddr":[0,0,0,"1116390867936048925210605243246199936148774961419"],"ax":[0,0,0,"2536444495813302334051725399203529611284083085956012556783209998548806902399"],"ay":[0,0,0,"14129099889874404948746412698085340218026543006739588516267964927105356860264"],"ax1":[0,0,0,4660],"ay1":[0,0,0,4660],"amount1":[0,0,0,4660],"nonce1":[0,0,0,4660],"ethAddr1":[0,0,0,"1116390867936048925210605243246199936148774961419"],"siblings1":[[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"]],"isOld0_1":[0,0,0,1],"oldKey1":[0,0,0,0],"oldValue1":[0,0,0,0],"ax2":[0,0,0,0],"ay2":[0,0,0,0],"amount2":[0,0,0,0],"nonce2":[0,0,0,0],"ethAddr2":[0,0,0,0],"siblings2":[[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]],"isOld0_2":[0,0,0,0],"oldKey2":[0,0,0,0],"oldValue2":[0,0,0,0]} +{ + "oldStRoot": "0", + "feePlanCoins": 0, + "feePlanFees": 0, + "txData": [ + "0", + "0", + "1294077440023230710144016724176942272334582932281947479092973291962369", + "431359146674410236714672241732596457699132774224023767129425975443457" + ], + "rqTxData": [ + 0, + 0, + 0, + 0 + ], + "s": [ + 0, + 0, + 0, + 0 + ], + "r8x": [ + 0, + 0, + 0, + 0 + ], + "r8y": [ + 0, + 0, + 0, + 0 + ], + "loadAmount": [ + 0, + 0, + 1000, + 0 + ], + "ethAddr": [ + 0, + 0, + "564285745605685379469803228198653436451829107432", + "564285745605685379469803228198653436451829107432" + ], + "ax": [ + 0, + 0, + "19978187010499430245869538435991859273974821401831892378333645864391780110865", + "19978187010499430245869538435991859273974821401831892378333645864391780110865" + ], + "ay": [ + 0, + 0, + "8221948295571505153368741349618427496143137236526643398342942827546824861670", + "8221948295571505153368741349618427496143137236526643398342942827546824861670" + ], + "ax1": [ + 0, + 0, + 4660, + "19978187010499430245869538435991859273974821401831892378333645864391780110865" + ], + "ay1": [ + 0, + 0, + 4660, + "8221948295571505153368741349618427496143137236526643398342942827546824861670" + ], + "amount1": [ + 0, + 0, + 4660, + "1000" + ], + "nonce1": [ + 0, + 0, + 4660, + 0 + ], + "ethAddr1": [ + 0, + 0, + "564285745605685379469803228198653436451829107432", + "564285745605685379469803228198653436451829107432" + ], + "siblings1": [ + [ + 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" + ] + ], + "isOld0_1": [ + 0, + 0, + 1, + 0 + ], + "oldKey1": [ + 0, + 0, + 0, + 4660 + ], + "oldValue1": [ + 0, + 0, + 0, + 4660 + ], + "ax2": [ + 0, + 0, + 0, + 4660 + ], + "ay2": [ + 0, + 0, + 0, + 4660 + ], + "amount2": [ + 0, + 0, + 0, + 4660 + ], + "nonce2": [ + 0, + 0, + 0, + 4660 + ], + "ethAddr2": [ + 0, + 0, + 0, + "564285745605685379469803228198653436451829107432" + ], + "siblings2": [ + [ + 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" + ] + ], + "isOld0_2": [ + 0, + 0, + 0, + 1 + ], + "oldKey2": [ + 0, + 0, + 0, + 0 + ], + "oldValue2": [ + 0, + 0, + 0, + 0 + ] +} + diff --git a/test/rollup_circuit.js b/test/rollup_circuit.js index 9f011b01..a92e972c 100644 --- a/test/rollup_circuit.js +++ b/test/rollup_circuit.js @@ -2,21 +2,28 @@ const chai = require("chai"); const path = require("path"); const snarkjs = require("snarkjs"); const compiler = require("circom"); -const utils = require("../js/utils"); const bigInt = require("snarkjs").bigInt; const SMT = require("circomlib").SMT; -const BlockBuilder = require("../js/blockbuilder"); +const SMTMemDB = require("circomlib").SMTMemDB; const fs = require("fs"); const RollupAccount = require("../js/rollupaccount"); +const RollupDB = require("../js/rollupdb"); const assert = chai.assert; function checkBlock(circuit, w, bb) { const newStateRoot = w[circuit.getSignalIdx("main.newStRoot")]; - assert(newStateRoot.equals(bb.getNewStateRoot())); +/* console.log(newStateRoot.toString()); + const v=bb.getNewStateRoot(); + console.log(v); +*/ assert(newStateRoot.equals(bb.getNewStateRoot())); + const newExitRoot = w[circuit.getSignalIdx("main.newExitRoot")]; - assert(newExitRoot.equals(bb.getNewExitRoot())); +/* console.log(newExitRoot.toString()); + const v2=bb.getNewExitRoot(); + console.log(v2); +*/ assert(newExitRoot.equals(bb.getNewExitRoot())); const onChainHash = w[circuit.getSignalIdx("main.onChainHash")]; assert(onChainHash.equals(bb.getOnChainHash())); @@ -34,47 +41,234 @@ describe("Rollup run 4 null TX", function () { this.timeout(100000); before( async() => { - // const cirDef = await compiler(path.join(__dirname, "circuits", "rollup_test.circom"), {reduceConstraints:false}); - const cirDef = JSON.parse(fs.readFileSync(path.join(__dirname, "circuits", "circuit.json"), "utf8")); + const cirDef = await compiler(path.join(__dirname, "circuits", "rollup_test.circom"), {reduceConstraints:false}); + // const cirDef = JSON.parse(fs.readFileSync(path.join(__dirname, "circuits", "circuit.json"), "utf8")); circuit = new snarkjs.Circuit(cirDef); console.log("NConstrains Rollup: " + circuit.nConstraints); }); - it("Should create 4 empty TXs", async () => { // Start a new state - const state = await SMT.newMemEmptyTrie(); - const bb = new BlockBuilder(state, 4, 8); + const db = new SMTMemDB(); + const rollupDB = await RollupDB(db); + const bb = await rollupDB.buildBlock(4, 8); + + await bb.build(); + const input = bb.getInput(); + + const w = circuit.calculateWitness(input, {logTrigger:false, logOutput: false, logSet: false}); + + checkBlock(circuit, w, bb); + }); + it("Should create 1 deposit onchain TXs", async () => { + + // Start a new state + const db = new SMTMemDB(); + const rollupDB = await RollupDB(db); + const bb = await rollupDB.buildBlock(4, 8); + + const account1 = new RollupAccount(1); + + bb.addTx({ + fromIdx: 1, + loadAmount: 1000, + coin: 0, + ax: account1.ax, + ay: account1.ay, + ethAddress: account1.ethAddress, + onChain: true + }); + + await bb.build(); + const input = bb.getInput(); + + const w = circuit.calculateWitness(input, {logTrigger:false, logOutput: false, logSet: false}); + + checkBlock(circuit, w, bb); + }); + it("Should create 1 deposit onchain TXs and 1 exit onchain TX", async () => { + + // Start a new state + const db = new SMTMemDB(); + const rollupDB = await RollupDB(db); + const bb = await rollupDB.buildBlock(4, 8); + + const account1 = new RollupAccount(1); + + bb.addTx({ + fromIdx: 1, + loadAmount: 1000, + coin: 0, + ax: account1.ax, + ay: account1.ay, + ethAddress: account1.ethAddress, + onChain: true + }); + + bb.addTx({ + fromIdx: 1, + toIdx: 0, + loadAmount: 0, + coin: 0, + ax: account1.ax, + ay: account1.ay, + ethAddress: account1.ethAddress, + amount: 1000, + onChain: true + }); await bb.build(); const input = bb.getInput(); +// console.log(JSON.stringify(snarkjs.stringifyBigInts(input), null, 1)); + const w = circuit.calculateWitness(input, {logTrigger:false, logOutput: false, logSet: false}); checkBlock(circuit, w, bb); }); - it("Should create 1 onChain TXs", async () => { + it("Should create 2 deposits and then a normal offchain transfer", async () => { // Start a new state - const state = await SMT.newMemEmptyTrie(); - const bb = new BlockBuilder(state, 4, 8); + const db = new SMTMemDB(); + const rollupDB = await RollupDB(db); + const bb = await rollupDB.buildBlock(4, 8); - const account1 = new RollupAccount(); + const account1 = new RollupAccount(1); + const account2 = new RollupAccount(2); - bb.createAccount({ + bb.addTx({ fromIdx: 1, loadAmount: 1000, coin: 0, ax: account1.ax, ay: account1.ay, - ethAddress: account1.ethAddress + ethAddress: account1.ethAddress, + onChain: true + }); + + bb.addTx({ + fromIdx: 2, + loadAmount: 2000, + coin: 0, + ax: account2.ax, + ay: account2.ay, + ethAddress: account2.ethAddress, + onChain: true }); await bb.build(); const input = bb.getInput(); const w = circuit.calculateWitness(input, {logTrigger:false, logOutput: false, logSet: false}); + checkBlock(circuit, w, bb); + + await rollupDB.consolidate(bb); + + const bb2 = await rollupDB.buildBlock(4, 8); + + const tx = { + fromIdx: 1, + toIdx: 2, + coin: 0, + amount: 50, + nonce: 0, + userFee: 10 + }; + account1.signTx(tx); + bb2.addTx(tx); + + bb2.addCoin(0, 5); + + await bb2.build(); + const input2 = bb2.getInput(); + + const w2 = circuit.calculateWitness(input2, {logTrigger:false, logOutput: false, logSet: false}); + checkBlock(circuit, w2, bb2); + }); + it("Should check big amounts", async () => { + + // Start a new state + const db = new SMTMemDB(); + const rollupDB = await RollupDB(db); + const bb = await rollupDB.buildBlock(4, 8); + + const account1 = new RollupAccount(1); + const account2 = new RollupAccount(2); + + bb.addTx({ + fromIdx: 1, + loadAmount: bigInt("1000000000000000000000"), + coin: 0, + ax: account1.ax, + ay: account1.ay, + ethAddress: account1.ethAddress, + onChain: true + }); + + bb.addTx({ + fromIdx: 2, + loadAmount: bigInt("2000000000000000000000"), + coin: 0, + ax: account2.ax, + ay: account2.ay, + ethAddress: account2.ethAddress, + onChain: true + }); + + await bb.build(); + const input = bb.getInput(); + const w = circuit.calculateWitness(input, {logTrigger:false, logOutput: false, logSet: false}); checkBlock(circuit, w, bb); + + await rollupDB.consolidate(bb); + + const bb2 = await rollupDB.buildBlock(4, 8); + + const tx = { + fromIdx: 1, + toIdx: 2, + coin: 0, + amount: bigInt("1000000000000000000"), + nonce: 0, + userFee: bigInt("2000000000000") + }; + account1.signTx(tx); + bb2.addTx(tx); + + bb2.addCoin(0, 5); + + await bb2.build(); + const input2 = bb2.getInput(); + + const w2 = circuit.calculateWitness(input2, {logTrigger:false, logOutput: false, logSet: false}); + checkBlock(circuit, w2, bb2); + }); + it("Should check underflow onchain", async () => { + }); + it("Should check underflow offchain", async () => { }); + it("Should check offchain with loadAmount", async () => { + }); + it("Should check onchain transfer", async () => { + }); + it("Should check onchain deposit existing", async () => { + }); + it("Should check combined deposit transfer", async () => { + }); + it("Should check combined deposit exit", async () => { + }); + it("Should check offchain with invalid fee", async () => { + }); + it("Should check offchain with invalid nonce", async () => { + }); + it("Should check offchain with invalid signature", async () => { + }); + it("Should check offchain with not defined coin", async () => { + }); + it("Should check offchain with double defined coin", async () => { + }); + it("Should check block with invalid order", async () => { + }); + });