const { toBN, keccak256, fromWei, toWei, randomHex } = require('web3-utils') const pedersenHash = (data) => circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(data))[0] const toFixedHex = (number, length = 32) => '0x' + bigInt(number).toString(16).padStart(length * 2, '0'); const websnarkUtils = require('websnark/src/utils') const buildGroth16 = require('websnark/src/groth16') const stringifyBigInts = require('websnark/tools/stringifybigint').stringifyBigInts const snarkjs = require('snarkjs') const bigInt = snarkjs.bigInt const MerkleTree = require('fixed-merkle-tree') const crypto = require('crypto') const circomlib = require('circomlib') const Web3 = require('web3'); const rbigint = (nbytes) => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes)) const levels = 20; const fs = require('fs') const subtle = crypto.subtle; let web3, storage, depositAccount, groth16, circuit, proving_key; global.cryptoController = { storageABI: require('../deps/Storage.json').abi, shifterABI: require('../deps/XFTanon.json').abi, tokenABI: require('../deps/Token.json').abi, helpers: { randomHex: randomHex, toFixedHex: toFixedHex, toBN: toBN, keccak256: keccak256, fromWei: fromWei, toWei: toWei, pedersenHash: pedersenHash, rbigint: rbigint, stringifyBigInts: stringifyBigInts, isAddress: (address) => web3.utils.isAddress(address) }, account: { balance: 0, tokenBalances: {} }, loadFactories: async () => { web3 = new Web3(new Web3.providers.HttpProvider(config.rpc), null, { transactionConfirmationBlocks: 1 }) storage = await new web3.eth.Contract(cryptoController.storageABI, config.storageContract); depositAccount = web3.eth.accounts.privateKeyToAccount(config.walletKey); web3.eth.accounts.wallet.add(depositAccount); groth16 = await buildGroth16() circuit = require('../deps/withdraw.json') proving_key = fs.readFileSync('./deps/withdraw_proving_key.bin').buffer cryptoController.sender = depositAccount.address let relayerConfig = await (await fetch(config.relayUrl + "/config")).json() cryptoController.relayer = {}; cryptoController.relayer.refund = toBN(relayerConfig.refund); cryptoController.relayer.fee = toBN(relayerConfig.fee); cryptoController.relayer.address = relayerConfig.address; cryptoController.relayer.shifters = relayerConfig.shifters; cryptoController.shifterList = await cryptoController.getShifters(); cryptoController.shifters = {}; cryptoController.tokens = {}; cryptoController.shifterTokens = {}; if (!cryptoController.xft) { cryptoController.xftAddress = config.xftContract; cryptoController.xft = await new web3.eth.Contract(cryptoController.tokenABI, cryptoController.xftAddress); cryptoController.account.balance = await cryptoController.xft.methods.balanceOf(cryptoController.sender).call() } for (let i = 0; i < cryptoController.shifterList.length; i++) { let shifter = cryptoController.shifterList[i]; cryptoController.shifters[shifter] = await new web3.eth.Contract(cryptoController.shifterABI, shifter); let shifterToken = await cryptoController.shifters[shifter].methods.token().call(); cryptoController.tokens[shifterToken] = await new web3.eth.Contract(cryptoController.tokenABI, shifterToken); cryptoController.shifterTokens[shifter] = shifterToken; let newBalance = await cryptoController.tokens[shifterToken].methods.balanceOf(cryptoController.sender).call(); cryptoController.account.tokenBalances[shifterToken] = cryptoController.account.tokenBalances[shifterToken] + newBalance; } }, getOrSetPasswordLocally: () => { if (!config.storagePassword) { let password = randomHex(32); console.log(`No password found. Creating a new one. This will be the only time it is shown. \n\n>> ${password} <<`); configController.setConfig('storagePassword', password); }; return config.storagePassword; }, parseSaveNote: (saveNote) => { const nullifier = BigInt(`0x${saveNote.slice(2, 64)}`) const secret = BigInt(`0x${saveNote.slice(64, 126)}`) const preimage = Buffer.concat([nullifier.leInt2Buff(31), secret.leInt2Buff(31)]) let hexNote = `0x${preimage.toString('hex')}` const buffNote = Buffer.from(hexNote.slice(2), 'hex') const commitment = pedersenHash(buffNote) const CUT_LENGTH = 31 const nullifierBuff = buffNote.slice(0, CUT_LENGTH) const nullifierHash = BigInt(pedersenHash(nullifierBuff)) return { secret, nullifier, commitment, nullifierBuff, nullifierHash, commitmentHex: toFixedHex(commitment), nullifierHex: toFixedHex(nullifierHash) } }, refBalance: async (shifterAddress) => { if (shifterAddress) { let shifterToken = cryptoController.shifterTokens[shifterAddress] let newBalance = await cryptoController.tokens[shifterToken].methods.balanceOf(cryptoController.sender).call(); cryptoController.account.tokenBalances[cryptoController.shifterTokens[shifterAddress]._address] = newBalance; cryptoController.account.balance = await cryptoController.xft.methods.balanceOf(cryptoController.sender).call(); } else { for (let i = 0; i < cryptoController.tokens.length; i++) { let token = cryptoController.tokens[i]; let newBalance = await token.methods.balanceOf(cryptoController.sender).call(); cryptoController.account.tokenBalances[token._address] = newBalance; } } }, xcrypt: async (data, password, shifter, address, nonce) => { if (!nonce && nonce != 0) nonce = (await storage.methods.getDepositsLength(shifter, address, keccak256(password)).call({ from: address })); const iv = (await subtle.digest('SHA-256', new Uint8Array(Buffer.from((password + nonce))))).slice(0, 16); const dataBuffer = new Uint8Array(Buffer.from(data.slice(2), 'hex')); const keyBuffer = new Uint8Array(Buffer.from(password.slice(2), 'hex')); const key = await subtle.importKey('raw', keyBuffer, { name: 'AES-GCM' }, false, ['encrypt']); try { let encrypted = await subtle.encrypt({ name: 'AES-GCM', iv }, key, dataBuffer); return "0x" + Buffer.from(encrypted).toString('hex').slice(0, 124); } catch (err) { console.log(err) throw new Error("The data provided couldn't be encrypted or decrypted, please check the inputs"); } }, generateProof: async (shifter, recipient, note) => { const deposit = cryptoController.parseSaveNote(note); let { fee, refund, address } = cryptoController.relayer; let relayer = address; let leafIndex; const leaves = await shifter.methods.commitmentList().call() leaves.forEach((e, i) => { const index = toBN(i).toNumber(); if (toBN(e).eq(toBN(toFixedHex(deposit.commitment)))) { leafIndex = index; } leaves[i] = toBN(e).toString(10); }); let nullHash = deposit.nullifierHash; tree = new MerkleTree(levels, leaves) const { pathElements, pathIndices } = tree.path(leafIndex) const input = stringifyBigInts({ root: tree.root(), nullifierHash: nullHash, relayer, recipient, fee, refund, nullifier: deposit.nullifier, secret: deposit.secret, pathElements: pathElements, pathIndices: pathIndices, }) const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key) const { proof } = websnarkUtils.toSolidityInput(proofData) const withdrawArgs = [ toFixedHex(input.root), toFixedHex(input.nullifierHash), toFixedHex(input.recipient, 20), toFixedHex(input.relayer, 20), toFixedHex(input.fee), toFixedHex(input.refund), ] return { proof, withdrawArgs } }, getShifters: async () => { const storageShifters = await storage.methods.getAllShifters().call(); const relayerShifters = cryptoController.relayer.shifters; return storageShifters.filter(shifter => relayerShifters.includes(shifter)); } } global.helpers = cryptoController.helpers;