"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BatchBuilder = void 0; const rover_1 = require("./rover"); class BatchBuilder extends rover_1.Prover { constructor() { super(...arguments); this.zeroValue = "0x016a430aa58685aba1311244a973a3bc358859da86784be51094368e8fb6f720"; this.zeroHex = "0x0000000000000000000000000000000000000000000000000000000000000000"; this.withdrawals = []; this.depositTotal = "0"; this.contractInputs = []; this.enqueueQueue = []; this.status = { txs_hash: "", tx_ids: [], proof: "", aggregation_object: [] }; this.fetchQueue = async () => { const merkleRoot = await this.momiji.contract.merkleRoot(); const filter = this.momiji.contract.filters.TransactionSent(null, merkleRoot, null); const txEvents = await this.momiji.contract.queryFilter(filter); const txContractInputs = txEvents.map(e => this.resultToTransaction(this.toResult(e))); this.clearQueue(); this.enqueueLocally(txContractInputs); return txContractInputs; }; this.addToStatus = (update) => { this.status.tx_ids.push(update.id); this.status.txs_hash = this.keccak256('0x' + this.status.tx_ids.map(tx => tx.slice(2)).join('')); this.status.proof = update.proof; this.status.aggregation_object = update.aggregation_object; }; this.publishBatch = async (data) => { if (!this.hasSigner()) throw new Error(`batchBuilder.publishBatch: Signer is undefined, but is required.`); if (Array.isArray(data) && !this.status.proof) { data = data; console.log(`batchBuilder.publishBatch: No batch passed and no batch being generated, generating new proofs.`); this.publishBatch(await this.proveValidBatch(undefined, data)); } let publishTx = await this.momiji.contract.publish(data); console.log(`batchBuilder.publishBatch: Batch published: ${publishTx.hash}.`); console.log(publishTx); }; this.createFinalPublisherInputs = async (batch) => { let new_root = await this.calculateNewStateRoot(batch.transactions.map(tx => tx.tx_id), this.stateRoot.value, (await this.pedersen(this.toFixedHex(0, true)))); let _batch = [ this.config.transactionVkHash, this.stateRoot.value, new_root, await this.pedersen(this.toFixedHex(0, true)), ...(batch.transactions.map(tx => [ tx.current_root, tx.deposit, tx.tx_id, ...tx.withdrawals, ...tx.commitment_out, ...tx.recipients, ...tx.nullifier_hashes, ...tx.proof, tx.pi_contract_hash ])).flat() ]; return { proof: this.status.proof, aggregation_object: this.status.aggregation_object, batch_calldata: _batch, }; }; this.createBatchPublicInputs = async (transactions) => { if (transactions === undefined) transactions = this.mempool.queue; if (!Array.isArray(transactions)) transactions = [transactions]; let _transactions = transactions; return { key_hash: this.config.transactionVkHash, old_root: this.stateRoot.value, new_root: await this.calculateNewStateRoot(_transactions.map(tx => tx.tx_id), this.stateRoot.value, (await this.pedersen(this.toFixedHex(0, true)))), oracle: await this.pedersen(this.toFixedHex(0, true)), tx_ids: new Array(16).fill(this.zeroValue).map((_, idx, _arr) => { let thisTx = (_transactions[idx] !== undefined) ? _transactions[idx].tx_id : _arr[idx]; return thisTx; }) }; }; this.addProvenTransaction = async (transaction, batch) => { if (!batch) batch = await this.initBatchCircuitInputs(); if (batch.tx_ids.filter(i => i == this.zeroValue).length >= 16) { console.log(`batchBuilder.addTransaction: Batch is full. Tossing tx.`); return batch; } if (!batch.tx_ids.includes(transaction.tx_id)) { batch.tx_ids[batch.tx_ids.indexOf(this.zeroValue)] = transaction.tx_id; } batch.oracle = await this.pedersen(this.toFixedHex(0, true)); batch.new_root = await this.calculateNewStateRoot(batch.tx_ids, batch.old_root, batch.oracle); batch.pi_contract_hash = this.keccak256([ "0x", batch.key_hash.slice(2), batch.old_root.slice(2), batch.new_root.slice(2), batch.oracle.slice(2), ...batch.tx_ids.map(tx => tx.slice(2)) ].join('')); batch.proof = transaction.proof; batch.public_inputs = transaction.proof.slice(0, 1); const recursiveProof = await this.momiji.recursion.final.generateFinalProof(batch); const recursiveProofVerified = await this.momiji.recursion.final.verifyFinalProof(recursiveProof); if (!recursiveProofVerified) { console.log(`batchBuilder.addTransaction: Recursive proof failed verification on tx_id ${transaction.tx_id}. Tossing tx.`); } else { const proofHex = '0x' + Buffer.from(recursiveProof.proof).toString('hex'); let publicInputsHex = []; if (recursiveProof.publicInputs instanceof Array) { publicInputsHex = recursiveProof.publicInputs.map(pi => `0x` + Buffer.from(pi).toString('hex')); } const batchStatusUpdate = { id: transaction.tx_id, proof: proofHex, aggregation_object: publicInputsHex.slice(1) }; this.addToStatus(batchStatusUpdate); batch.input_aggregation_object = batchStatusUpdate.aggregation_object; } return batch; }; this.calculateNewStateRoot = async (txs, old_root, oracle) => { let txRoot = await this.MerkleTree(this.config.tree.txDepth, txs).then(result => result.root()); let batch = await this.pedersen([txRoot, oracle]); let new_root = await this.pedersen([batch, old_root]); return new_root; }; this.proveBatch = () => { }; this.proveValidBatch = async (batch, transactions) => { if (!Array.isArray(transactions)) transactions = [transactions]; if (!transactions) transactions = this.mempool.queue; let _transactions = transactions; let _batch = batch || await this.createBatchPublicInputs(transactions); let batchCircuitInputs = await this.initBatchCircuitInputs(_batch); let aggregate = await new Array(_transactions.length) .reduce(async (acc, _, i) => await this.addProvenTransaction(_transactions[i], acc), batchCircuitInputs); let finals = { ...aggregate, proof: this.status.proof, aggregation_object: this.status.aggregation_object, transactions: transactions }; console.log(`batchBuilder.proveValidBatch: Done proving batch.`); return finals; }; this.initBatchCircuitInputs = async (pi) => { if (!pi) pi = (await this.createBatchPublicInputs()); return { ...pi, pi_contract_hash: this.keccak256([ "0x", pi.key_hash.slice(2), pi.old_root.slice(2), pi.new_root.slice(2), pi.oracle.slice(2), ...pi.tx_ids.map(tx => tx.slice(2)) ].join('')), verification_key: this.config.transactionVk, proof: new Array(94).fill(this.toFixedHex(0, true)), public_inputs: new Array(1).fill(this.toFixedHex(0, true)), input_aggregation_object: new Array(16).fill(this.toFixedHex(0, true)) }; }; } } exports.BatchBuilder = BatchBuilder;