From f9142383cfa835a0dbb527af0c8dceb7628cae41 Mon Sep 17 00:00:00 2001 From: Ilyas Ridhuan Date: Wed, 13 Jul 2022 11:00:38 +0100 Subject: [PATCH 01/15] feat: simplify circuits and tx public data --- common-files/classes/transaction.mjs | 22 +- config/default.js | 2 +- nightfall-client/src/classes/commitment.mjs | 5 +- .../src/services/commitment-storage.mjs | 57 ++++- nightfall-client/src/services/deposit.mjs | 21 +- nightfall-client/src/services/transfer.mjs | 88 ++++--- nightfall-client/src/services/withdraw.mjs | 120 +++++++--- .../src/utils/compute-witness.mjs | 119 ++++++++++ .../common/casts/u8_array_to_field.zok | 9 +- .../Stubs/commitments_stub.zok | 17 ++ .../generic_circuit/Stubs/encryption_stub.zok | 18 ++ .../generic_circuit/Stubs/nullifiers_stub.zok | 20 ++ .../Verifiers/verify_commitments.zok | 36 +++ .../Verifiers/verify_encryption.zok | 39 ++++ .../Verifiers/verify_nullifiers.zok | 61 +++++ .../Verifiers/verify_structure.zok | 67 ++++++ .../common/merkle-tree/path-check.zok | 3 + .../circuits/common/utils/calculations.zok | 61 +++++ .../circuits/common/utils/structures.zok | 32 +++ nightfall-deployer/circuits/deposit.zok | 68 +++--- nightfall-deployer/circuits/deposit_stub.zok | 55 +++-- .../circuits/double_transfer.zok | 160 ------------- .../circuits/double_transfer_stub.zok | 55 ----- .../circuits/single_transfer.zok | 122 ---------- .../circuits/single_transfer_stub.zok | 53 ----- nightfall-deployer/circuits/transfer.zok | 98 ++++++++ nightfall-deployer/circuits/withdraw.zok | 117 +++++----- nightfall-deployer/contracts/Challenges.sol | 2 +- .../contracts/ChallengesUtil.sol | 4 +- nightfall-deployer/contracts/Shield.sol | 3 +- nightfall-deployer/contracts/Structures.sol | 2 +- nightfall-deployer/contracts/Utils.sol | 4 +- nightfall-optimist/src/routes/proposer.mjs | 3 +- .../src/services/transaction-checker.mjs | 214 ++++-------------- test/e2e/tokens/erc20.test.mjs | 31 ++- wallet/src/hooks/User/index.jsx | 10 +- zokrates-worker/src/index.mjs | 8 +- zokrates-worker/src/services/generateKeys.mjs | 2 +- 38 files changed, 1007 insertions(+), 801 deletions(-) create mode 100644 nightfall-client/src/utils/compute-witness.mjs create mode 100644 nightfall-deployer/circuits/common/generic_circuit/Stubs/commitments_stub.zok create mode 100644 nightfall-deployer/circuits/common/generic_circuit/Stubs/encryption_stub.zok create mode 100644 nightfall-deployer/circuits/common/generic_circuit/Stubs/nullifiers_stub.zok create mode 100644 nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok create mode 100644 nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_encryption.zok create mode 100644 nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok create mode 100644 nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_structure.zok create mode 100644 nightfall-deployer/circuits/common/utils/calculations.zok create mode 100644 nightfall-deployer/circuits/common/utils/structures.zok delete mode 100644 nightfall-deployer/circuits/double_transfer.zok delete mode 100644 nightfall-deployer/circuits/double_transfer_stub.zok delete mode 100644 nightfall-deployer/circuits/single_transfer.zok delete mode 100644 nightfall-deployer/circuits/single_transfer_stub.zok create mode 100644 nightfall-deployer/circuits/transfer.zok diff --git a/common-files/classes/transaction.mjs b/common-files/classes/transaction.mjs index a5376e303..842b593a5 100644 --- a/common-files/classes/transaction.mjs +++ b/common-files/classes/transaction.mjs @@ -14,6 +14,13 @@ const { generalise } = gen; const TOKEN_TYPES = { ERC20: 0, ERC721: 1, ERC1155: 2 }; const { TRANSACTION_TYPES } = constants; +const arrayEquality = (as, bs) => { + if (as.length === bs.length) { + return as.every(a => bs.includes(a)); + } + return false; +}; + // function to compute the keccak hash of a transaction function keccak(preimage) { const web3 = Web3.connection(); @@ -30,7 +37,7 @@ function keccak(preimage) { compressedSecrets, } = preimage; let { proof } = preimage; - proof = compressProof(proof); + proof = arrayEquality(proof, [0, 0, 0, 0, 0, 0, 0, 0]) ? [0, 0, 0, 0] : compressProof(proof); const transaction = [ value, historicRootBlockNumberL2, @@ -57,7 +64,7 @@ class Transaction { // them undefined work?) constructor({ fee, - historicRootBlockNumberL2, + historicRootBlockNumberL2: _historicRoot, transactionType, tokenType, tokenId, @@ -69,11 +76,13 @@ class Transaction { compressedSecrets: _compressedSecrets, // this must be array of objects that are compressed from Secrets class proof, // this must be a proof object, as computed by zokrates worker }) { - if (proof === undefined) throw new Error('Proof cannot be undefined'); - const flatProof = Object.values(proof).flat(Infinity); let commitments; let nullifiers; let compressedSecrets; + let flatProof; + let historicRootBlockNumberL2; + if (proof === undefined) flatProof = [0, 0, 0, 0, 0, 0, 0, 0]; + else flatProof = Object.values(proof).flat(Infinity); if (_commitments === undefined) commitments = [{ hash: 0 }, { hash: 0 }]; else if (_commitments.length === 1) commitments = [..._commitments, { hash: 0 }]; else commitments = _commitments; @@ -82,6 +91,9 @@ class Transaction { else nullifiers = _nullifiers; if (_compressedSecrets === undefined) compressedSecrets = [0, 0]; else compressedSecrets = _compressedSecrets; + if (_historicRoot === undefined) historicRootBlockNumberL2 = [0, 0]; + else if (_historicRoot.length === 1) historicRootBlockNumberL2 = [..._historicRoot, 0]; + else historicRootBlockNumberL2 = _historicRoot; if ((transactionType === 0 || transactionType === 3) && TOKEN_TYPES[tokenType] === undefined) throw new Error('Unrecognized token type'); @@ -143,7 +155,7 @@ class Transaction { commitments, nullifiers, compressedSecrets, - proof: compressProof(proof), + proof: arrayEquality(proof, [0, 0, 0, 0, 0, 0, 0, 0]) ? [0, 0, 0, 0] : compressProof(proof), }; } } diff --git a/config/default.js b/config/default.js index 8afeba74f..801dd1991 100644 --- a/config/default.js +++ b/config/default.js @@ -103,7 +103,7 @@ module.exports = { TRANSACTIONS_PER_BLOCK: Number(process.env.TRANSACTIONS_PER_BLOCK) || 2, RETRIES: Number(process.env.AUTOSTART_RETRIES) || 50, USE_STUBS: process.env.USE_STUBS === 'true', - VK_IDS: { deposit: 0, single_transfer: 1, double_transfer: 2, withdraw: 3 }, // used as an enum to mirror the Shield contracts enum for vk types. The keys of this object must correspond to a 'folderpath' (the .zok file without the '.zok' bit) + VK_IDS: { deposit: 0, transfer: 1, withdraw: 2 }, // withdraw: 3, withdraw_change: 4 }, // used as an enum to mirror the Shield contracts enum for vk types. The keys of this object must correspond to a 'folderpath' (the .zok file without the '.zok' bit) MAX_PUBLIC_VALUES: { ERCADDRESS: 2n ** 161n - 1n, COMMITMENT: 2n ** 249n - 1n, diff --git a/nightfall-client/src/classes/commitment.mjs b/nightfall-client/src/classes/commitment.mjs index b508f824b..176571b06 100644 --- a/nightfall-client/src/classes/commitment.mjs +++ b/nightfall-client/src/classes/commitment.mjs @@ -30,7 +30,10 @@ class Commitment { // the compressedPkd is not part of the pre-image but it's used widely in the rest of // the code, so we hold it in the commitment object (but not as part of the preimage) this.preimage = generalise(items); - this.compressedZkpPublicKey = ZkpKeys.compressZkpPublicKey(this.preimage.zkpPublicKey); + this.compressedZkpPublicKey = + this.preimage.zkpPublicKey[0] === 0 + ? [0, 0] + : ZkpKeys.compressZkpPublicKey(this.preimage.zkpPublicKey); // we encode the top four bytes of the tokenId into the empty bytes at the top of the erc address. // this is consistent to what we do in the ZKP circuits const [top4Bytes, remainder] = this.preimage.tokenId.limbs(224, 2).map(l => BigInt(l)); diff --git a/nightfall-client/src/services/commitment-storage.mjs b/nightfall-client/src/services/commitment-storage.mjs index 73fac278b..fce334c24 100644 --- a/nightfall-client/src/services/commitment-storage.mjs +++ b/nightfall-client/src/services/commitment-storage.mjs @@ -82,7 +82,10 @@ export async function countNullifiers(nullifiers) { // function to get count of transaction hashes of withdraw type. Used to decide if we should store sibling path of transaction hash to be used later for finalising or instant withdrawal export async function countWithdrawTransactionHashes(transactionHashes) { const connection = await mongo.connection(MONGO_URL); - const query = { transactionHash: { $in: transactionHashes }, nullifierTransactionType: '3' }; + const query = { + transactionHash: { $in: transactionHashes }, + nullifierTransactionType: '2', + }; const db = connection.db(COMMITMENTS_DB); return db.collection(COMMITMENTS_COLLECTION).countDocuments(query); } @@ -90,7 +93,7 @@ export async function countWithdrawTransactionHashes(transactionHashes) { // function to get if the transaction hash belongs to a withdraw transaction export async function isTransactionHashWithdraw(transactionHash) { const connection = await mongo.connection(MONGO_URL); - const query = { transactionHash, nullifierTransactionType: '3' }; + const query = { transactionHash, nullifierTransactionType: '2' }; const db = connection.db(COMMITMENTS_DB); return db.collection(COMMITMENTS_COLLECTION).countDocuments(query); } @@ -511,7 +514,7 @@ export async function getWithdrawCommitments() { const db = connection.db(COMMITMENTS_DB); const query = { isNullified: true, - nullifierTransactionType: '3', + nullifierTransactionType: '2', isNullifiedOnChain: { $gte: 0 }, }; // Get associated nullifiers of commitments that have been spent on-chain and are used for withdrawals. @@ -617,12 +620,30 @@ async function findUsableCommitments(compressedZkpPublicKey, ercAddress, tokenId await markPending(singleCommitment); return [singleCommitment]; } - // If we get here it means that we have not been able to find a single commitment that matches the required value - if (onlyOne || commitments.length < 2) return null; // sometimes we require just one commitment + // If we only want one or there is only 1 commitment - then we should try a single transfer with change + if (onlyOne || commitments.length === 1) { + const valuesGreaterThanTarget = commitments.filter(c => c.preimage.value.bigInt > value.bigInt); // Do intermediary step since reduce has ugly exception + if (valuesGreaterThanTarget.length === 0) return null; + const singleCommitmentWithChange = valuesGreaterThanTarget.reduce((prev, curr) => + prev.preimage.value.bigInt < curr.preimage.value.bigInt ? prev : curr, + ); + return [singleCommitmentWithChange]; + } + // If we get here it means that we have not been able to find a single commitment that satisfies our requirements (onlyOne) + if (commitments.length < 2) return null; // sometimes we require just one commitment - /* if not, maybe we can do a two-commitment transfer. The current strategy aims to prioritise smaller commitments while also - minimising the creation of low value commitments (dust) + /* if not, maybe we can do more flexible single or double commitment transfers. The current strategy aims to prioritise reducing the complexity of + the commitment set. I.e. Minimise the size of the commitment set by using smaller commitments while also minimising the creation of + low value commitments (dust). + + Transaction type in order of priority. (1) Double transfer without change, (2) Double Transfer with change, (3) Single Transfer with change. + + Double Transfer Without Change: + 1) Sort all commitments by value + 2) Find candidate pairs of commitments that equal the transfer sum. + 3) Select candidate that uses the smallest commitment as one of the input. + Double Transfer With Change: 1) Sort all commitments by value 2) Split commitments into two sets based of if their values are less than or greater than the target value. LT & GT respectively. 3) If the sum of the two largest values in set LT is LESS than the target value: @@ -636,6 +657,9 @@ async function findUsableCommitments(compressedZkpPublicKey, ercAddress, tokenId iii) If the sum of the commitments at the pointers is greater than the target value, we move pointer rhs to the left. iv) Otherwise, we move pointer lhs to the right. v) The selected commitments are the pair that minimise the change difference. The best case in this scenario is a change difference of -1. + + Single Transfer With Change: + 1) If this is the only commitment and it is greater than the transfer sum. */ // sorting will help with making the search easier @@ -643,6 +667,21 @@ async function findUsableCommitments(compressedZkpPublicKey, ercAddress, tokenId Number(a.preimage.value.bigInt - b.preimage.value.bigInt), ); + // Find two commitments that matches the transfer value exactly. Double Transfer With No Change. + let lhs = 0; + let rhs = sortedCommits.length - 1; + /** THIS WILL BE ENABLED LATED + while (lhs < rhs) { + const tempSum = sortedCommits[lhs].bigInt + sortedCommits[rhs].bigInt; + // The first valid solution will include the smallest usable commitment in the set. + if (tempSum === value.bigInt) break; + else if (tempSum > value.bigInt) rhs--; + else lhs++; + } + if (lhs < rhs) return [sortedCommits[lhs], sortedCommits[rhs]]; + */ + + // Find two commitments are greater than the target. Double Transfer With Change // get all commitments less than the target value const commitsLessThanTargetValue = sortedCommits.filter( s => s.preimage.value.bigInt < value.bigInt, @@ -660,8 +699,8 @@ async function findUsableCommitments(compressedZkpPublicKey, ercAddress, tokenId } // If we are here than we can use our commitments less than the target value to sum to greater than the target value - let lhs = 0; - let rhs = commitsLessThanTargetValue.length - 1; + lhs = 0; + rhs = commitsLessThanTargetValue.length - 1; let changeDiff = -Infinity; let commitmentsToUse = null; while (lhs < rhs) { diff --git a/nightfall-client/src/services/deposit.mjs b/nightfall-client/src/services/deposit.mjs index 91d1e6be7..1acd85097 100644 --- a/nightfall-client/src/services/deposit.mjs +++ b/nightfall-client/src/services/deposit.mjs @@ -16,6 +16,7 @@ import constants from 'common-files/constants/index.mjs'; import { Commitment, Transaction } from '../classes/index.mjs'; import { storeCommitment } from './commitment-storage.mjs'; import { ZkpKeys } from './keys.mjs'; +import { computeWitness } from '../utils/compute-witness.mjs'; const { ZOKRATES_WORKER_HOST, PROVING_SCHEME, BACKEND, PROTOCOL, USE_STUBS, BN128_GROUP_ORDER } = config; @@ -33,14 +34,18 @@ async function deposit(items) { const commitment = new Commitment({ ercAddress, tokenId, value, zkpPublicKey, salt }); logger.debug(`Hash of new commitment is ${commitment.hash.hex()}`); // now we can compute a Witness so that we can generate the proof - const witness = [ - ercAddress.field(BN128_GROUP_ORDER), - tokenId.limbs(32, 8), - value.field(BN128_GROUP_ORDER), - ...zkpPublicKey.all.field(BN128_GROUP_ORDER), - salt.field(BN128_GROUP_ORDER), - commitment.hash.field(BN128_GROUP_ORDER), - ].flat(Infinity); + const publicData = Transaction.buildSolidityStruct( + new Transaction({ + fee, + transactionType: 0, + tokenType: items.tokenType, + tokenId, + value, + ercAddress, + commitments: [commitment], + }), + ); + const witness = computeWitness(publicData, [], { salt, recipientPublicKeys: [zkpPublicKey] }); logger.debug(`witness input is ${witness.join(' ')}`); // call a zokrates worker to generate the proof let folderpath = 'deposit'; diff --git a/nightfall-client/src/services/transfer.mjs b/nightfall-client/src/services/transfer.mjs index 48853e5ff..9bc0a4acb 100644 --- a/nightfall-client/src/services/transfer.mjs +++ b/nightfall-client/src/services/transfer.mjs @@ -24,6 +24,7 @@ import { import getProposersUrl from './peers.mjs'; import { ZkpKeys } from './keys.mjs'; import { encrypt, genEphemeralKeys, packSecrets } from './kem-dem.mjs'; +import { computeWitness } from '../utils/compute-witness.mjs'; const { BN128_GROUP_ORDER, ZOKRATES_WORKER_HOST, PROVING_SCHEME, BACKEND, PROTOCOL, USE_STUBS } = config; @@ -76,14 +77,13 @@ async function transfer(transferParams) { const salts = await Promise.all(values.map(async () => randValueLT(BN128_GROUP_ORDER))); // Generate new commitments, already truncated to u32[7] - const newCommitments = recipientCompressedZkpPublicKeys.map( - (rcp, i) => + const newCommitments = values.map( + (value, i) => new Commitment({ ercAddress, tokenId, - value: values[i], + value, zkpPublicKey: recipientZkpPublicKeys[i], - // compressedZkpPublicKey: rcp, salt: salts[i].bigInt, }), ); @@ -100,7 +100,6 @@ async function transfer(transferParams) { // Compress the public key as it will be put on-chain const compressedEPub = edwardsCompress(ePublic); - const binaryEPub = generalise(compressedEPub).binary.padStart(256, '0'); // Commitment Tree Information const commitmentTreeInfo = await Promise.all(oldCommitments.map(c => getSiblingInfo(c))); @@ -111,7 +110,7 @@ async function transfer(transferParams) { const leafIndices = commitmentTreeInfo.map(l => l.leafIndex); const blockNumberL2s = commitmentTreeInfo.map(l => l.isOnChain); const roots = commitmentTreeInfo.map(l => l.root); - console.log( + logger.info( 'Constructing transfer transaction with blockNumberL2s', blockNumberL2s, 'and roots', @@ -130,53 +129,44 @@ async function transfer(transferParams) { } // now we have everything we need to create a Witness and compute a proof - const witness = [ - oldCommitments.map(commitment => commitment.preimage.ercAddress.field(BN128_GROUP_ORDER)), - oldCommitments.map(commitment => [ - commitment.preimage.tokenId.limbs(32, 8), - commitment.preimage.value.limbs(8, 31), - commitment.preimage.salt.field(BN128_GROUP_ORDER), - commitment.hash.field(BN128_GROUP_ORDER), - rootKey.field(BN128_GROUP_ORDER), - ]), - newCommitments.map(commitment => [ - [ - commitment.preimage.zkpPublicKey[0].field(BN128_GROUP_ORDER), - commitment.preimage.zkpPublicKey[1].field(BN128_GROUP_ORDER), - ], - commitment.preimage.value.limbs(8, 31), - commitment.preimage.salt.field(BN128_GROUP_ORDER), - ]), - newCommitments.map(commitment => commitment.hash.field(BN128_GROUP_ORDER)), - nullifiers.map(nullifier => nullifier.hash.field(BN128_GROUP_ORDER)), - localSiblingPaths.map(siblingPath => siblingPath[0].field(BN128_GROUP_ORDER)), - localSiblingPaths.map(siblingPath => - siblingPath.slice(1).map(node => node.field(BN128_GROUP_ORDER)), - ), - leafIndices, - generalise(ePrivate).limbs(32, 8), - [binaryEPub[0], new GN(binaryEPub.slice(1), 'binary').field(BN128_GROUP_ORDER)], - compressedSecrets.map(c => generalise(c).field(BN128_GROUP_ORDER, false)), - ].flat(Infinity); + const transaction = Transaction.buildSolidityStruct( + new Transaction({ + fee, + historicRootBlockNumberL2: blockNumberL2s, + transactionType: 1, + ercAddress: compressedSecrets[0], // this is the encrypted ercAddress + tokenId: compressedSecrets[1], // this is the encrypted tokenID + recipientAddress: compressedEPub, + commitments: newCommitments, + nullifiers, + compressedSecrets: compressedSecrets.slice(2), // these are the [value, salt] + }), + ); + + const privateData = { + rootKey: [rootKey, rootKey], + oldCommitmentPreimage: oldCommitments.map(o => { + return { value: o.preimage.value, salt: o.preimage.salt }; + }), + paths: localSiblingPaths.map(siblingPath => siblingPath.slice(1)), + orders: leafIndices, + newCommitmentPreimage: newCommitments.map(o => { + return { value: o.preimage.value, salt: o.preimage.salt }; + }), + recipientPublicKeys: newCommitments.map(o => o.preimage.zkpPublicKey), + ercAddress, + tokenId, + ephemeralKey: ePrivate, + }; + const witness = computeWitness(transaction, roots, privateData); logger.debug(`witness input is ${witness.join(' ')}`); // call a zokrates worker to generate the proof - // This is (so far) the only place where we need to get specific about the - // circuit - let folderpath; - let transactionType; - if (oldCommitments.length === 1) { - folderpath = 'single_transfer'; - transactionType = 1; - blockNumberL2s.push(0); // We need top pad block numbers if we do a single transfer - } else if (oldCommitments.length === 2) { - folderpath = 'double_transfer'; - transactionType = 2; - } else throw new Error('Unsupported number of commitments'); - if (USE_STUBS) folderpath = `${folderpath}_stub`; + let folderpath = 'transfer'; + if (USE_STUBS) folderpath = 'transfer_stub'; const res = await axios.post(`${PROTOCOL}${ZOKRATES_WORKER_HOST}/generate-proof`, { folderpath, - inputs: await witness, + inputs: witness, provingScheme: PROVING_SCHEME, backend: BACKEND, }); @@ -187,7 +177,7 @@ async function transfer(transferParams) { const optimisticTransferTransaction = new Transaction({ fee, historicRootBlockNumberL2: blockNumberL2s, - transactionType, + transactionType: 1, ercAddress: compressedSecrets[0], // this is the encrypted ercAddress tokenId: compressedSecrets[1], // this is the encrypted tokenID recipientAddress: compressedEPub, diff --git a/nightfall-client/src/services/withdraw.mjs b/nightfall-client/src/services/withdraw.mjs index 60c75e464..2e982a8b3 100644 --- a/nightfall-client/src/services/withdraw.mjs +++ b/nightfall-client/src/services/withdraw.mjs @@ -11,7 +11,8 @@ import gen from 'general-number'; import { getContractInstance } from 'common-files/utils/contract.mjs'; import logger from 'common-files/utils/logger.mjs'; import constants from 'common-files/constants/index.mjs'; -import { Nullifier, Transaction } from '../classes/index.mjs'; +import { randValueLT } from 'common-files/utils/crypto/crypto-random.mjs'; +import { Commitment, Nullifier, Transaction } from '../classes/index.mjs'; import { findUsableCommitmentsMutex, markNullified, @@ -20,6 +21,7 @@ import { } from './commitment-storage.mjs'; import getProposersUrl from './peers.mjs'; import { ZkpKeys } from './keys.mjs'; +import { computeWitness } from '../utils/compute-witness.mjs'; const { BN128_GROUP_ORDER, ZOKRATES_WORKER_HOST, PROVING_SCHEME, BACKEND, PROTOCOL, USE_STUBS } = config; @@ -27,6 +29,7 @@ const { SHIELD_CONTRACT_NAME } = constants; const { generalise } = gen; const NEXT_N_PROPOSERS = 3; +const MAX_WITHDRAW = 5192296858534827628530496329220096n; // 2n**112n async function withdraw(withdrawParams) { logger.info('Creating a withdraw transaction'); @@ -37,43 +40,81 @@ async function withdraw(withdrawParams) { // the first thing we need to do is to find and input commitment which // will enable us to conduct our withdraw. Let's rummage in the db... - const [oldCommitment] = (await findUsableCommitmentsMutex( - compressedZkpPublicKey, - ercAddress, - tokenId, - value, - true, - )) || [null]; - if (oldCommitment) logger.debug(`Found commitment ${JSON.stringify(oldCommitment, null, 2)}`); + const oldCommitments = + (await findUsableCommitmentsMutex(compressedZkpPublicKey, ercAddress, tokenId, value, true)) || + null; + if (oldCommitments) logger.debug(`Found commitment ${JSON.stringify(oldCommitments, null, 2)}`); else throw new Error('No suitable commitments were found'); // caller to handle - need to get the user to make some commitments or wait until they've been posted to the blockchain and Timber knows about them // Having found 1 commitment, which is a suitable input to the // proof, the next step is to compute its nullifier; - const nullifier = new Nullifier(oldCommitment, nullifierKey); + const nullifiers = oldCommitments.map( + oldCommitment => new Nullifier(oldCommitment, nullifierKey), + ); + // we may need to return change to the recipient + const totalInputCommitmentValue = oldCommitments.reduce( + (acc, curr) => curr.preimage.value.bigInt + acc, + 0n, + ); + const withdrawValue = value.bigInt > MAX_WITHDRAW ? MAX_WITHDRAW : value.bigInt; + const change = totalInputCommitmentValue - withdrawValue; + // if so, add an output commitment to do that // and the Merkle path from the commitment to the root - const commitmentTreeInfo = await getSiblingInfo(oldCommitment); - const siblingPath = generalise( - [commitmentTreeInfo.root].concat( - commitmentTreeInfo.siblingPath.path.map(p => p.value).reverse(), - ), + // Commitment Tree Information + const commitmentTreeInfo = await Promise.all(oldCommitments.map(c => getSiblingInfo(c))); + const localSiblingPaths = commitmentTreeInfo.map(l => { + const path = l.siblingPath.path.map(p => p.value); + return generalise([l.root].concat(path.reverse())); + }); + const leafIndices = commitmentTreeInfo.map(l => l.leafIndex); + const blockNumberL2s = commitmentTreeInfo.map(l => l.isOnChain); + logger.silly(`SiblingPath was: ${JSON.stringify(localSiblingPaths)}`); + + let newCommitment = null; + if (change !== 0n) { + const salt = await randValueLT(BN128_GROUP_ORDER); + newCommitment = new Commitment({ + ercAddress, + tokenId, + value: change, + zkpPublicKey: ZkpKeys.decompressZkpPublicKey(compressedZkpPublicKey), + compressedZkpPublicKey, + salt, + }); + } + const publicData = Transaction.buildSolidityStruct( + new Transaction({ + fee, + historicRootBlockNumberL2: blockNumberL2s, + commitments: newCommitment ? [newCommitment] : [{ hash: 0 }, { hash: 0 }], + transactionType: 2, + tokenType: items.tokenType, + tokenId, + value, + ercAddress, + recipientAddress, + nullifiers, + }), ); - logger.silly(`SiblingPath was: ${JSON.stringify(siblingPath)}`); + const privateObj = { + rootKey, + oldCommitmentPreimage: oldCommitments.map(o => { + return { value: o.preimage.value, salt: o.preimage.salt }; + }), + paths: localSiblingPaths.map(siblingPath => siblingPath.slice(1)), + orders: leafIndices, + }; - const { leafIndex, isOnChain } = commitmentTreeInfo; + if (newCommitment) + privateObj.newCommitmentPreimage = { + value: newCommitment.preimage.value, + salt: newCommitment.preimage.salt, + }; - // now we have everything we need to create a Witness and compute a proof - const witness = [ - oldCommitment.preimage.ercAddress.field(BN128_GROUP_ORDER), - oldCommitment.preimage.tokenId.limbs(32, 8), - oldCommitment.preimage.value.field(BN128_GROUP_ORDER), - oldCommitment.preimage.salt.field(BN128_GROUP_ORDER), - oldCommitment.hash.field(BN128_GROUP_ORDER), - rootKey.field(BN128_GROUP_ORDER), - nullifier.hash.field(BN128_GROUP_ORDER), - recipientAddress.field(BN128_GROUP_ORDER), - siblingPath[0].field(BN128_GROUP_ORDER), - siblingPath.slice(1).map(node => node.field(BN128_GROUP_ORDER)), // siblingPAth[32] is a sha hash and will overflow a field but it's ok to take the mod here - hence the 'false' flag - leafIndex, - ].flat(Infinity); + const witness = computeWitness( + publicData, + localSiblingPaths.map(siblingPath => siblingPath[0]), + privateObj, + ); logger.debug(`witness input is ${witness.join(' ')}`); // call a zokrates worker to generate the proof @@ -91,14 +132,15 @@ async function withdraw(withdrawParams) { const shieldContractInstance = await getContractInstance(SHIELD_CONTRACT_NAME); const optimisticWithdrawTransaction = new Transaction({ fee, - historicRootBlockNumberL2: [isOnChain, 0], - transactionType: 3, + historicRootBlockNumberL2: blockNumberL2s, + commitments: newCommitment ? [newCommitment] : [{ hash: 0 }, { hash: 0 }], + transactionType: 2, tokenType: items.tokenType, tokenId, value, ercAddress, recipientAddress, - nullifiers: [nullifier], + nullifiers, proof, }); try { @@ -118,7 +160,9 @@ async function withdraw(withdrawParams) { }), ); // on successful computation of the transaction mark the old commitments as nullified - await markNullified(oldCommitment, optimisticWithdrawTransaction); + await Promise.all( + oldCommitments.map(commitment => markNullified(commitment, optimisticWithdrawTransaction)), + ); const th = optimisticWithdrawTransaction.transactionHash; delete optimisticWithdrawTransaction.transactionHash; optimisticWithdrawTransaction.transactionHash = th; @@ -128,10 +172,12 @@ async function withdraw(withdrawParams) { .submitTransaction(Transaction.buildSolidityStruct(optimisticWithdrawTransaction)) .encodeABI(); // on successful computation of the transaction mark the old commitments as nullified - await markNullified(oldCommitment, optimisticWithdrawTransaction); + await Promise.all( + oldCommitments.map(commitment => markNullified(commitment, optimisticWithdrawTransaction)), + ); return { rawTransaction, transaction: optimisticWithdrawTransaction }; } catch (err) { - await clearPending(oldCommitment); + await Promise.all(oldCommitments.map(commitment => clearPending(commitment))); throw new Error(err); // let the caller handle the error } } diff --git a/nightfall-client/src/utils/compute-witness.mjs b/nightfall-client/src/utils/compute-witness.mjs new file mode 100644 index 000000000..5596f7548 --- /dev/null +++ b/nightfall-client/src/utils/compute-witness.mjs @@ -0,0 +1,119 @@ +import config from 'config'; +import gen from 'general-number'; + +const { generalise } = gen; +const { BN128_GROUP_ORDER } = config; + +const NULL_COMMITMENT = { + value: 0, + salt: 0, +}; +const padArray = (arr, padWith, n) => { + if (!Array.isArray(arr)) + return generalise([arr, ...Array.from({ length: n - 1 }, () => padWith)]); + if (arr.length < n) { + const nullPadding = Array.from({ length: n - arr.length }, () => padWith); + return generalise(arr.concat(nullPadding)); + } + return generalise(arr); +}; + +const computeWitnessPublic = (tx, rootArray) => { + const transaction = generalise(tx); + const publicWitness = [ + transaction.value.field(BN128_GROUP_ORDER), + transaction.historicRootBlockNumberL2.map(h => h.field(BN128_GROUP_ORDER)), + transaction.transactionType.field(BN128_GROUP_ORDER), + transaction.tokenType.field(BN128_GROUP_ORDER), + transaction.tokenId.limbs(32, 8), + transaction.ercAddress.field(BN128_GROUP_ORDER), + transaction.recipientAddress.limbs(32, 8), + transaction.commitments.map(c => c.field(BN128_GROUP_ORDER)), + transaction.nullifiers.map(n => n.field(BN128_GROUP_ORDER)), + transaction.compressedSecrets.map(cs => cs.field(BN128_GROUP_ORDER)), + ]; + if (rootArray.length !== 0) { + const roots = padArray(generalise(rootArray), 0, 2); + publicWitness.push(roots.map(r => r.field(BN128_GROUP_ORDER))); + } + return publicWitness.flat(Infinity); +}; + +const computeWitnessPrivateTransfer = privateObj => { + const { + rootKey, + oldCommitmentPreimage, + paths, + orders, + newCommitmentPreimage, + recipientPublicKeys, + ercAddress, + tokenId, + ephemeralKey, + } = generalise(privateObj); + const paddedOldCommitmentPreimage = padArray(oldCommitmentPreimage, NULL_COMMITMENT, 2); + const paddedNewCommitmentPreimage = padArray(newCommitmentPreimage, NULL_COMMITMENT, 2); + const paddedPaths = padArray(paths, new Array(32).fill(0), 2); + const paddedOrders = padArray(orders, 0, 2); + const paddedRootKeys = padArray(rootKey, 0, 2); + const paddedRecipientPublicKeys = padArray(recipientPublicKeys, [0, 0], 2); + return [ + paddedOldCommitmentPreimage.map(r => r.value.limbs(8, 31)), + paddedOldCommitmentPreimage.map(r => r.salt.field(BN128_GROUP_ORDER)), + paddedRootKeys.map(r => r.field(BN128_GROUP_ORDER)), + paddedPaths.map(ps => ps.map(p => p.field(BN128_GROUP_ORDER))), + paddedOrders.map(m => m.field(BN128_GROUP_ORDER)), + paddedNewCommitmentPreimage.map(r => r.value.limbs(8, 31)), + paddedNewCommitmentPreimage.map(r => r.salt.field(BN128_GROUP_ORDER)), + paddedRecipientPublicKeys.map(rcp => [ + rcp[0].field(BN128_GROUP_ORDER), + rcp[1].field(BN128_GROUP_ORDER), + ]), + ephemeralKey.limbs(32, 8), + ercAddress.field(BN128_GROUP_ORDER), + tokenId.limbs(32, 8), + ].flat(Infinity); +}; + +const computeWitnessPrivateDeposit = privateObj => { + const { salt, recipientPublicKeys } = generalise(privateObj); + return [ + salt.field(BN128_GROUP_ORDER), + recipientPublicKeys.map(rcp => [ + rcp[0].field(BN128_GROUP_ORDER), + rcp[1].field(BN128_GROUP_ORDER), + ]), + ].flat(Infinity); +}; + +const computeWitnessPrivateWithdraw = privateObj => { + const { rootKey, oldCommitmentPreimage, paths, orders } = generalise(privateObj); + const paddedOldCommitmentPreimage = padArray(oldCommitmentPreimage, NULL_COMMITMENT, 2); + const paddedPaths = padArray(paths, new Array(32).fill(0), 2); + const paddedOrders = padArray(orders, 0, 2); + const paddedRootKeys = padArray(rootKey, 0, 2); + const paddedNewCommitmentPreimage = + generalise(privateObj.newCommitmentPreimage) || generalise(NULL_COMMITMENT); + return [ + paddedOldCommitmentPreimage.map(r => r.value.limbs(8, 31)), + paddedOldCommitmentPreimage.map(r => r.salt.field(BN128_GROUP_ORDER)), + paddedRootKeys.map(r => r.field(BN128_GROUP_ORDER)), + paddedPaths.map(ps => ps.map(p => p.field(BN128_GROUP_ORDER))), + paddedOrders.map(m => m.field(BN128_GROUP_ORDER)), + paddedNewCommitmentPreimage.value.limbs(8, 31), + paddedNewCommitmentPreimage.salt.field(BN128_GROUP_ORDER), + ].flat(Infinity); +}; + +// eslint-disable-next-line import/prefer-default-export +export const computeWitness = (txObject, rootArray, privateObj) => { + const publicWitness = computeWitnessPublic(txObject, rootArray); + switch (Number(txObject.transactionType)) { + case 0: + return [...publicWitness, ...computeWitnessPrivateDeposit(privateObj)]; + case 1: + return [...publicWitness, ...computeWitnessPrivateTransfer(privateObj)]; + default: + return [...publicWitness, ...computeWitnessPrivateWithdraw(privateObj)]; + } +}; diff --git a/nightfall-deployer/circuits/common/casts/u8_array_to_field.zok b/nightfall-deployer/circuits/common/casts/u8_array_to_field.zok index 5813774c4..b162f3765 100644 --- a/nightfall-deployer/circuits/common/casts/u8_array_to_field.zok +++ b/nightfall-deployer/circuits/common/casts/u8_array_to_field.zok @@ -1,6 +1,6 @@ from "EMBED" import u8_to_bits -def main(u8[N] i) -> (field): +def convert(u8[N] i) -> (field): field res = 0 for u32 k in 0..N do for u32 j in 0..8 do @@ -10,3 +10,10 @@ def main(u8[N] i) -> (field): endfor endfor return res + +def main(u8[M][N] input) -> (field[M]): + field[M] res = [0; M] + for u32 i in 0..M do + res[i] = convert(input[i]) + endfor + return res diff --git a/nightfall-deployer/circuits/common/generic_circuit/Stubs/commitments_stub.zok b/nightfall-deployer/circuits/common/generic_circuit/Stubs/commitments_stub.zok new file mode 100644 index 000000000..eecee571c --- /dev/null +++ b/nightfall-deployer/circuits/common/generic_circuit/Stubs/commitments_stub.zok @@ -0,0 +1,17 @@ +from "../../utils/structures.zok" import Point +from "../../utils/calculations.zok" import sum + +def main(\ + private field[NumCommitments] newCommitmentValues,\ + private field[NumCommitments] newCommitmentSalts,\ + private Point[NumCommitments] recipientPublicKey\ +)-> (bool): + + for u32 i in 0..NumCommitments do + field s = newCommitmentValues[i] * sum([\ + newCommitmentSalts[i],...recipientPublicKey[i]\ + ]) + assert(s == s) + endfor + + return true diff --git a/nightfall-deployer/circuits/common/generic_circuit/Stubs/encryption_stub.zok b/nightfall-deployer/circuits/common/generic_circuit/Stubs/encryption_stub.zok new file mode 100644 index 000000000..5f3a3526e --- /dev/null +++ b/nightfall-deployer/circuits/common/generic_circuit/Stubs/encryption_stub.zok @@ -0,0 +1,18 @@ +from "../../utils/calculations.zok" import sum +from "utils/pack/u32/pack256.zok" import main as u32_8_to_field + +def main(\ + private u32[Transfer][8] ephemeralKey,\ + private field[Transfer] ercAddressTransfer,\ + private u32[Transfer][8] idTransfer\ +)-> (bool): + + for u32 i in 0..Transfer do + field s = ercAddressTransfer[i] * sum([\ + u32_8_to_field(ephemeralKey[i]),\ + u32_8_to_field(idTransfer[i])\ + ]) + assert(s == s) + endfor + + return true diff --git a/nightfall-deployer/circuits/common/generic_circuit/Stubs/nullifiers_stub.zok b/nightfall-deployer/circuits/common/generic_circuit/Stubs/nullifiers_stub.zok new file mode 100644 index 000000000..d506ec913 --- /dev/null +++ b/nightfall-deployer/circuits/common/generic_circuit/Stubs/nullifiers_stub.zok @@ -0,0 +1,20 @@ +from "../../utils/calculations.zok" import sum + +def main(\ + field[NumNullifiers] nullifierRoots,\ + private field[NumNullifiers] oldCommitmentValues,\ + private field[NumNullifiers] oldCommitmentSalts,\ + private field[NumNullifiers] rootKey,\ + private field[NumNullifiers][32] paths,\ + private field[NumNullifiers] orders\ +)-> (bool): + + for u32 i in 0..NumNullifiers do + field s = nullifierRoots[i] * sum([\ + rootKey[i], oldCommitmentValues[i],\ + oldCommitmentSalts[i],...paths[i], orders[i]\ + ]) + assert(s == s) + endfor + + return true \ No newline at end of file diff --git a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok new file mode 100644 index 000000000..6228655ca --- /dev/null +++ b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok @@ -0,0 +1,36 @@ +from "hashes/poseidon/poseidon.zok" import main as poseidon +from "../../utils/structures.zok" import Point + +/* +* Verify that all commitments are correct +*/ +def main(\ + field packedErcAddress,\ + field idRemainder,\ + field[2] commitmentHashes,\ + field[2] changeZkpPublicKey,\ + private field[NumCommitments] newCommitmentValues,\ + private field[NumCommitments] newCommitmentSalts,\ + private Point[NumCommitments] recipientPublicKey\ +) -> bool: + //Check that all the nullifiers are valid. If NumNullifiers equals zero this loop will be ignored + for u32 i in 0..NumCommitments do + + //Calculate the commmitment hash from the newCommitment parameters + field commitment = if newCommitmentValues[i] == 0 then 0 else \ + poseidon([\ + packedErcAddress,\ + idRemainder,\ + newCommitmentValues[i],\ + ...recipientPublicKey[i],\ + newCommitmentSalts[i]\ + ])\ + fi + + //Check that the calculated commitment matches with the one contained in the transaction + assert(commitment == commitmentHashes[i]) + + //If there is change, we need to check that the recipient public keys of the last commitment + //matches with the change public keys + endfor + return true \ No newline at end of file diff --git a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_encryption.zok b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_encryption.zok new file mode 100644 index 000000000..82d2e4283 --- /dev/null +++ b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_encryption.zok @@ -0,0 +1,39 @@ +from "ecc/edwardsCompress" import main as edwardsCompress +from "utils/pack/bool/nonStrictUnpack256.zok" import main as field_to_bool_256 +from "utils/casts/u32_8_to_bool_256.zok" import main as u32_8_to_bool_256 +from "../../casts/u32_array_to_field.zok" import main as u32_array_to_field +from "../../encryption/kem-dem.zok" import main as kemDem, EncryptedMsgs +from "../../utils/structures.zok" import Point + +/* +* Verify that the secrets are encrypted properly in the Transfer +*/ +def main(\ + field ercAddress,\ + u32[8] tokenId,\ + field[2] compressedSecrets,\ + field packedErcAddress,\ + field idRemainder,\ + private field[NumCommitments] newCommitmentValues,\ + private field[NumCommitments] newCommitmentSalts,\ + private Point[NumCommitments] recipientPublicKey,\ + u32[8] recipientAddress,\ + private u32[8] ephemeralKey\ +) -> bool: + + field[4] cipherText = [ercAddress,u32_array_to_field(tokenId),compressedSecrets[0],compressedSecrets[1]] + bool[256] bitEphemeralKey = u32_8_to_bool_256(ephemeralKey) + + field[4] plainTexts = [\ + packedErcAddress,\ + idRemainder,\ + newCommitmentValues[0],\ + newCommitmentSalts[0]\ + ] + + EncryptedMsgs<4> enc = kemDem(bitEphemeralKey, [recipientPublicKey[0][0], recipientPublicKey[0][1]], plainTexts) + assert(cipherText == enc.cipherText) + + bool[256] compressedPubKeyOutput = edwardsCompress(enc.ephemeralPublicKey) + assert(compressedPubKeyOutput == u32_8_to_bool_256(recipientAddress)) + return true \ No newline at end of file diff --git a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok new file mode 100644 index 000000000..788485ac4 --- /dev/null +++ b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok @@ -0,0 +1,61 @@ +from "ecc/babyjubjubParams" import BabyJubJubParams, main as curveParams +from "ecc/edwardsScalarMult" import main as scalarMult +from "utils/pack/bool/nonStrictUnpack256.zok" import main as field_to_bool_256 +from "hashes/poseidon/poseidon.zok" import main as poseidon +from "../../utils/structures.zok" import Point, PRIVATE_KEY_DOMAIN, NULLIFIER_KEY_DOMAIN +from "../../merkle-tree/path-check.zok" import main as pathCheck + +/* +* Verify that all the nullifiers are correct +*/ +def main(\ + field packedErcAddress,\ + field idRemainder,\ + field[NumNullifiers] nullifierHashes,\ + field[NumNullifiers] roots,\ + private field[NumNullifiers] oldCommitmentValues,\ + private field[NumNullifiers] oldCommitmentSalts,\ + private field[NumNullifiers] rootKey,\ + private field[NumNullifiers][32] paths,\ + private field[NumNullifiers] orders\ +) -> (Point): + // Get Curve Params + BabyJubJubParams context = curveParams() + Point g = [context.Gu, context.Gv] + + //If the transaction contains change, the receiver of that change MUST be the same user that + //created the transaction. In this field[2] we will store the zkpPublicKey of the sender in case + //we need it to check the change. + Point firstInputZkpPublicKeys = [0,0] + + //Check that all the nullifiers are valid. If NumNullifiers equals zero this loop will be ignored + for u32 i in 0..NumNullifiers do + // Calculation of zkpPrivateKey and nullifierKey from rootKey + field zkpPrivateKeys = poseidon([rootKey[i], PRIVATE_KEY_DOMAIN]) + field nullifierKeys = poseidon([rootKey[i], NULLIFIER_KEY_DOMAIN]) + + // Calculate zkpPublicKey + Point zkpPublicKeys = scalarMult(field_to_bool_256(zkpPrivateKeys), g, context) + + //Calculate the nullifier hash from the oldCommitment parameters + field calculatedOldCommitmentHash = poseidon([\ + packedErcAddress,\ + idRemainder,\ + oldCommitmentValues[i],\ + ...zkpPublicKeys,\ + oldCommitmentSalts[i]\ + ]) + field nullifier = if(oldCommitmentValues[i] == 0) then 0 else poseidon([nullifierKeys, calculatedOldCommitmentHash]) fi + + //Check that the calculated nullifier matches with the one contained in the transaction + assert(nullifier == nullifierHashes[i]) + + //Check that the nullifier is contained in the tree + bool pathValidity = if(oldCommitmentValues[i] == 0) then true else pathCheck([roots[i], ...paths[i]], orders[i], calculatedOldCommitmentHash) fi + assert(pathValidity) + + //Set the changeZkpPublicKeys if i = 0. Otherwise just set the same value + firstInputZkpPublicKeys = i == 0 ? zkpPublicKeys : firstInputZkpPublicKeys + endfor + + return firstInputZkpPublicKeys \ No newline at end of file diff --git a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_structure.zok b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_structure.zok new file mode 100644 index 000000000..b536518cd --- /dev/null +++ b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_structure.zok @@ -0,0 +1,67 @@ +from "../../casts/u32_array_to_field.zok" import main as u32_array_to_field +from "utils/pack/u32/nonStrictUnpack256.zok" import main as field_to_u32_8 + +/* +* Given a Public tx, check that the structure and all the public parameters are valid +*/ +def main(\ + field value,\ + field transactionType,\ + field tokenType,\ + u32[8] tokenId,\ + field ercAddress,\ + u32[8] recipientAddress,\ + field[2] commitments,\ + field[2] nullifiers,\ + field[2] historicRootBlockNumberL2,\ + field[2] compressedSecrets\ +) -> (bool): + + //Check that transaction type matches + // assert(TxType == transactionType) + + //Deposits must have at least a commitment, transfers must at least have one commitment and one nullifier + //and withdrawal must have at least a nullifier + assert((TxType == 0 && NumCommitments != 0)\ + || (TxType == 1 && NumCommitments != 0 && NumNullifiers != 0)\ + || (TxType == 2 && NumNullifiers != 0)) + + //ErcAddress cannot be zero. In transfer will contain the encrypted version of the ercAddress belonging to the ciphertext + assert(ercAddress != 0) + + //Withdrawals will have a recipientAddress and also transfers, since we are using it as a way to send the public ephemeral key + assert((TxType == 0 && recipientAddress == field_to_u32_8(0))\ + || (TxType != 0 && recipientAddress != field_to_u32_8(0))) + + field id = u32_array_to_field(tokenId) + + //Transfers will have value equal to zero and id different than zero because it will contain the encrypted version + //of the id beloning to the CipherText + //For deposits and withdrawals, check that combination id and value matches the token type + //ERC20 -> Value > 0 and Id == 0 + //ERC721 -> Value == 0 + //ERC1155 -> Value > 0 + assert((TxType == 1 && value == 0)\ + || (TxType != 1 &&\ + (tokenType == 0 && value != 0 && id == 0)\ + || (tokenType == 1 && value == 0)\ + || (tokenType == 2 && value != 0))) + + assert((TxType == 0 && commitments[0] != 0 && commitments[1] == 0) ||\ + (TxType == 1 && commitments[0] != 0 && commitments[0] != commitments[1]) ||\ + (TxType == 2 && commitments[1] == 0)) + + //Check nullifiers + assert(\ + (TxType == 0 && nullifiers[0] == 0 && nullifiers[1] == 0) || \ + ((TxType == 1 || TxType == 2) &&\ + (nullifiers[0] != 0 && nullifiers[0] != nullifiers[1])\ + )\ + ) + + //For transfers, compressedSecrets needs to have at least one element different than zero + //For deposits and withdrawals, compressedSecrets will be zero + assert((TxType == 1 && (compressedSecrets[0] != 0 || compressedSecrets[1] != 0))\ + || (TxType != 1 && compressedSecrets[0] == 0 && compressedSecrets[1] == 0)) + + return true \ No newline at end of file diff --git a/nightfall-deployer/circuits/common/merkle-tree/path-check.zok b/nightfall-deployer/circuits/common/merkle-tree/path-check.zok index 93fca12f7..75b8fc251 100644 --- a/nightfall-deployer/circuits/common/merkle-tree/path-check.zok +++ b/nightfall-deployer/circuits/common/merkle-tree/path-check.zok @@ -1,6 +1,9 @@ from "hashes/poseidon/poseidon.zok" import main as poseidon import "utils/pack/bool/unpack128.zok" as unpack128 +from "hashes/poseidon/poseidon.zok" import main as poseidon + + def orderFields(bool order, field pathNode, field siblingNode)->(field[2]): field right = if order then pathNode else siblingNode fi field left = if order then siblingNode else pathNode fi diff --git a/nightfall-deployer/circuits/common/utils/calculations.zok b/nightfall-deployer/circuits/common/utils/calculations.zok new file mode 100644 index 000000000..d15376034 --- /dev/null +++ b/nightfall-deployer/circuits/common/utils/calculations.zok @@ -0,0 +1,61 @@ + +from "ecc/edwardsCompress" import main as edwardsCompress +from "hashes/poseidon/poseidon.zok" import main as poseidon +from "utils/casts/u32_8_to_bool_256.zok" import main as u32_8_to_bool_256 +from "utils/pack/bool/nonStrictUnpack256.zok" import main as field_to_bool_256 +from "utils/pack/u32/pack256.zok" import main as u32_8_to_field +from "./structures.zok" import CommitmentPreimage, Point + +def sum(field[N] a) -> field: + field res = 0 + for u32 i in 0..N do + res = res + a[i] + endfor + return res + +def calculateCommitmentHashesN(\ + field packedErcAddress,\ + field idRemainder,\ + field[N] value,\ + Point[N] zkpPublicKeyRecipient,\ + field[N] salt\ + ) -> field[N]: + field[N] output = [0; N] + for u32 i in 0..N do + output[i] = poseidon([\ + packedErcAddress,\ + idRemainder,\ + value[i],\ + zkpPublicKeyRecipient[i][0],\ + zkpPublicKeyRecipient[i][1],\ + salt[i]\ + ]) + endfor + return output + +def calculateCommitmentHash(\ + field packedErcAddress,\ + field idRemainder,\ + field[1] value,\ + Point[1] zkpPublicKeyRecipient,\ + field[1] salt\ + ) -> field: + field[1] output = calculateCommitmentHashesN(packedErcAddress,idRemainder, value, zkpPublicKeyRecipient, salt) + return output[0] + + +def calculateNullifier(\ + field nullifierKey,\ + field commitmentHashes\ +) -> field: + return poseidon([nullifierKey,commitmentHashes]) + +def calculateNullifiersN(\ + field[N] nullifierKey,\ + field[N] commitmentHashes\ +) -> field[N]: + field[N] output = [0; N] + for u32 i in 0..N do + output[i] = calculateNullifier(nullifierKey[i], commitmentHashes[i]) + endfor + return output diff --git a/nightfall-deployer/circuits/common/utils/structures.zok b/nightfall-deployer/circuits/common/utils/structures.zok new file mode 100644 index 000000000..9c773fa37 --- /dev/null +++ b/nightfall-deployer/circuits/common/utils/structures.zok @@ -0,0 +1,32 @@ +type Point = field[2] + +struct CommitmentPreimage { + u8[N][31] value + field[N] salt +} + + +const field PRIVATE_KEY_DOMAIN = 2708019456231621178814538244712057499818649907582893776052749473028258908910 +const field NULLIFIER_KEY_DOMAIN = 7805187439118198468809896822299973897593108379494079213870562208229492109015 + +// 2 ^ 160 +const field SHIFT = 1461501637330902918203684832716283019655932542976 + +struct CompressedPoint { + bool[N] parity + field[N] ordinate +} + +struct PublicTransaction { + field value + field[2] historicRootBlockNumberL2 + field transactionType + field tokenType + u32[8] tokenId + field ercAddress + u32[8] recipientAddress + field[2] commitments + field[2] nullifiers + field[2] compressedSecrets +} + diff --git a/nightfall-deployer/circuits/deposit.zok b/nightfall-deployer/circuits/deposit.zok index 3057e57dd..5f8204373 100644 --- a/nightfall-deployer/circuits/deposit.zok +++ b/nightfall-deployer/circuits/deposit.zok @@ -1,40 +1,48 @@ -from "hashes/poseidon/poseidon.zok" import main as poseidon +from "./common/utils/structures.zok" import CommitmentPreimage,Point, PublicTransaction, SHIFT +from "./common/generic_circuit/Verifiers/verify_commitments.zok" import main as verify_commitments +from "./common/generic_circuit/Verifiers/verify_structure.zok" import main as verify_structure from "./common/casts/u32_array_to_field.zok" import main as u32_array_to_field -// Inputs for main: -// - ercContractAddress (public) is the ERCx contract address -// - value (public) is the 256 bit value (fungible) or identifier (non-fungible) -// - publicKey (private) is the public key of the newCommitment derived by hashing the Secret Key Sk of the newCommitment. IT IS KEPT PRIVATE!! -// - salt (private) is the salt for the newCommitment -// - newCommitment (public) is the newCommitment - -type Point = field[2] - -// 2 ^ 160 -const field SHIFT = 1461501637330902918203684832716283019655932542976 +const u32 txType = 0 +const u32 numCommitments = 1 +const u32 numNullifiers = 0 +const u32 transfer = 0 def main(\ - field ercContractAddress,\ - u32[8] id,\ - field value,\ - private Point compressedZkpPublicKey,\ + PublicTransaction tx,\ private field salt,\ - field newCommitment\ -)->(): + private Point[numCommitments] recipientPublicKey\ + ) -> (): - // pack the top four bytes of the token id into the ercAddress field (address only + //Verify public transaction structure + assert(verify_structure::(\ + tx.value,\ + tx.transactionType,\ + tx.tokenType,\ + tx.tokenId,\ + tx.ercAddress,\ + tx.recipientAddress,\ + tx.commitments,\ + tx.nullifiers,\ + tx.historicRootBlockNumberL2,\ + tx.compressedSecrets\ + )) + + // pack the top four bytes of the token id into the ercAddress field (address only // uses 160 bits and the Shield contract prevents creation of something with more than 160 bits) - field idTop4Bytes = u32_array_to_field([id[0]]) - field idRemainder = u32_array_to_field(id[1..8]) - field packedErcAddress = ercContractAddress + idTop4Bytes * SHIFT + field idRemainder = u32_array_to_field(tx.tokenId[1..8]) + field packedErcAddress = tx.ercAddress + u32_array_to_field([tx.tokenId[0]]) * SHIFT - field newCommitmentCheck = poseidon([ + //Verify new Commmitments + Point firstInputZkpPublicKeys = [0,0] + assert(verify_commitments::(\ packedErcAddress,\ idRemainder,\ - value,\ - ...compressedZkpPublicKey,\ - salt\ - ]) - assert(newCommitmentCheck == newCommitment) - - return + tx.commitments,\ + firstInputZkpPublicKeys,\ + [tx.value],\ + [salt],\ + recipientPublicKey\ + )) + + return diff --git a/nightfall-deployer/circuits/deposit_stub.zok b/nightfall-deployer/circuits/deposit_stub.zok index f0910889c..290a5e5c7 100644 --- a/nightfall-deployer/circuits/deposit_stub.zok +++ b/nightfall-deployer/circuits/deposit_stub.zok @@ -1,28 +1,35 @@ -// Inputs for main: -// - ercContractAddress (public) is the ERCx contract address -// - value (public) is the 256 bit value (fungible) or identifier (non-fungible) -// - compressedZkpPublicKey (private) is the public key of the newCommitment derived by hashing the Secret Key Sk of the newCommitment. IT IS KEPT PRIVATE!! -// - salt (private) is the salt for the newCommitment -// - newCommitment (public) is the newCommitment +from "./common/utils/structures.zok" import CommitmentPreimage,Point, PublicTransaction +from "./common/generic_circuit/Stubs/commitments_stub.zok" import main as commitment_stub +from "./common/generic_circuit/Stubs/nullifiers_stub.zok" import main as nullifiers_stub +from "./common/generic_circuit/Stubs/encryption_stub.zok" import main as encryption_stub +from "./common/generic_circuit/Verifiers/verify_structure.zok" import main as verify_structure -type Point = field[2] - -struct TokenId { - field top4Bytes - field remainder -} +const u32 txType = 0 +const u32 numCommitments = 1 +const u32 numNullifiers = 0 +const u32 transfer = 0 def main(\ - field ercContractAddress,\ - u32[8] id,\ - field value,\ - private Point compressedZkpPublicKey,\ - private field salt,\ - field newCommitment\ -)->(): + PublicTransaction tx,\ + private CommitmentPreimage newCommitment,\ + private Point[numCommitments] recipientPublicKey\ + ) -> (): + + //Verify public transaction structure + assert(verify_structure::<0, 2, 2>(\ + tx.value,\ + tx.transactionType,\ + tx.tokenType,\ + tx.tokenId,\ + tx.ercAddress,\ + tx.recipientAddress,\ + tx.commitments,\ + tx.nullifiers,\ + tx.historicRootBlockNumberL2,\ + tx.compressedSecrets\ + )) - field u = compressedZkpPublicKey[0] * compressedZkpPublicKey[1] + ercContractAddress + value + newCommitment * salt - u32 v = id[0] * id[1] + id[2] + id[3] + id[4] + id[5] + id[6] * id[7] - assert(u==u) - assert (v==v) - return + assert(commitment_stub::(\ + newCommitment.value, newCommitment.salt, recipientPublicKey)) + + return diff --git a/nightfall-deployer/circuits/double_transfer.zok b/nightfall-deployer/circuits/double_transfer.zok deleted file mode 100644 index 607a49dbf..000000000 --- a/nightfall-deployer/circuits/double_transfer.zok +++ /dev/null @@ -1,160 +0,0 @@ -from "ecc/babyjubjubParams" import BabyJubJubParams -from "ecc/babyjubjubParams" import main as curveParams -from "ecc/edwardsScalarMult" import main as scalarMult -from "ecc/edwardsCompress" import main as edwardsCompress -from "./common/encryption/kem-dem.zok" import main as kemDem, EncryptedMsgs - -from "utils/casts/u32_8_to_bool_256.zok" import main as u32_8_to_bool_256 -from "utils/pack/bool/nonStrictUnpack256.zok" import main as field_to_bool_256 -from "./common/casts/u8_array_to_field.zok" import main as u8_array_to_field -from "./common/casts/u32_array_to_field.zok" import main as u32_array_to_field - -from "hashes/poseidon/poseidon.zok" import main as poseidon -from "./common/merkle-tree/path-check.zok" import main as pathCheck - -type Point = field[2] - -// 2 ^ 160 -const field SHIFT = 1461501637330902918203684832716283019655932542976 - -struct OldCommitmentPreimage { - u32[8] id - u8[31] value - field salt - field hash - field rootKey -} - -struct NewCommitmentPreimage { - Point zkpPublicKeyRecipient - u8[31] value - field salt -} - -struct CompressedPoint { - bool parity - field ordinate -} - -def main(\ - private field[2] ercAddress,\ - private OldCommitmentPreimage[2] oldCommitment,\ - private NewCommitmentPreimage[2] newCommitment,\ - field[2] newCommitmentHash,\ - field[2] nullifier,\ - field[2] root,\ - private field[2][32] path,\ - private field[2] order,\ - private u32[8] ephemeralKey,\ - CompressedPoint compressedEphemeralPublicKey,\ - field[4] cipherText\ -)->(): - - BabyJubJubParams context = curveParams() - field[2] g = [context.Gu, context.Gv] - - // The domain numbers are derived thusly: - // keccak256('zkpPrivateKey') % BN128_GROUP_ORDER 2708019456231621178814538244712057499818649907582893776052749473028258908910 - // keccak256('nullifierKey') % BN128_GROUP_ORDER 7805187439118198468809896822299973897593108379494079213870562208229492109015 - - // Calculation of zkpPrivateKey and nullifierKey from rootKey - field[2] zkpPrivateKey = [\ - poseidon([oldCommitment[0].rootKey, 2708019456231621178814538244712057499818649907582893776052749473028258908910]),\ - poseidon([oldCommitment[1].rootKey, 2708019456231621178814538244712057499818649907582893776052749473028258908910])\ - ] - field[2] nullifierKey = [\ - poseidon([oldCommitment[0].rootKey, 7805187439118198468809896822299973897593108379494079213870562208229492109015]),\ - poseidon([oldCommitment[1].rootKey, 7805187439118198468809896822299973897593108379494079213870562208229492109015])\ - ] - bool[2][256] zkpPrivateKeyBool = [field_to_bool_256(zkpPrivateKey[0]), field_to_bool_256(zkpPrivateKey[1])] - field[2][2] zkpPublicKey = [scalarMult(zkpPrivateKeyBool[0], g, context), scalarMult(zkpPrivateKeyBool[1], g, context)] - - // constrain new commitment 1 to be 'change' - assert(newCommitment[1].zkpPublicKeyRecipient == zkpPublicKey[0]) - - //save values as fields (we know they can't overflow, so this is safe) - field[2] valueOld = [u8_array_to_field(oldCommitment[0].value), u8_array_to_field(oldCommitment[1].value)] - field[2] valueNew = [u8_array_to_field(newCommitment[0].value), u8_array_to_field(newCommitment[1].value)] - - // check the summation is correct - assert(valueOld[0] + valueOld[1] == valueNew[0] + valueNew[1]) - // check the two old commitments relate to the same ERC contract - assert(ercAddress[0] == ercAddress[1]) - // and are of the same type (they might not be for ERC1155) - assert(oldCommitment[0].id == oldCommitment[1].id) - // commitments can never be equal - assert(newCommitmentHash[0] != newCommitmentHash[1]) - // nullifiers can never be equal - assert(nullifier[0] != nullifier[1]) - - // check the nullifiers are valid - for u32 i in 0..2 do - field nullifierCheck = poseidon([\ - nullifierKey[i],\ - oldCommitment[i].hash\ - ]) - assert(nullifierCheck == nullifier[i]) - endfor - - // check the new commitment for recipient is valid - // firstly we need to pack the top four bytes of the TokenId into the address - // these are all static values so we only need do it once. - field idTop4Bytes = u32_array_to_field([oldCommitment[0].id[0]]) - field idRemainder = u32_array_to_field(oldCommitment[0].id[1..8]) - field packedErcAddress = ercAddress[0] + idTop4Bytes * SHIFT - field newCommitmentCheck = poseidon([\ - packedErcAddress,\ - idRemainder,\ - valueNew[0],\ - ...newCommitment[0].zkpPublicKeyRecipient,\ - newCommitment[0].salt\ - ]) - assert(newCommitmentCheck == newCommitmentHash[0]) - - // check the new commitment for sender is valid - newCommitmentCheck = poseidon([\ - packedErcAddress,\ - idRemainder,\ - valueNew[1],\ - ...zkpPublicKey[0],\ - newCommitment[1].salt\ - ]) - assert(newCommitmentCheck == newCommitmentHash[1]) - - // check the old commitments are valid - for u32 i in 0..2 do - field oldCommitmentCheck = poseidon([\ - packedErcAddress,\ - idRemainder,\ - valueOld[i],\ - ...zkpPublicKey[i],\ - oldCommitment[i].salt\ - ]) - assert(oldCommitmentCheck == oldCommitment[i].hash) - endfor - - // check that the old commitments are in the merkle tree - for u32 i in 0..2 do - field hash = oldCommitment[i].hash - assert(pathCheck([root[i], ...path[i]], order[i], hash)) - endfor - - // KEM-DEM Encryption - bool[256] bitEphemeralKey = u32_8_to_bool_256(ephemeralKey) - - field[4] plainTexts = [\ - packedErcAddress,\ - idRemainder,\ - valueNew[0],\ - newCommitment[0].salt\ - ] - EncryptedMsgs<4> enc = kemDem(bitEphemeralKey, newCommitment[0].zkpPublicKeyRecipient, plainTexts) - assert(cipherText == enc.cipherText) - - bool[256] compressedPubKeyOutput = edwardsCompress(enc.ephemeralPublicKey) - bool parity = compressedEphemeralPublicKey.parity - bool[256] ordinate = field_to_bool_256(compressedEphemeralPublicKey.ordinate) - bool[256] compressedCheck256 = [ parity, ...ordinate[1..256] ] - assert(compressedPubKeyOutput == compressedCheck256) - - return diff --git a/nightfall-deployer/circuits/double_transfer_stub.zok b/nightfall-deployer/circuits/double_transfer_stub.zok deleted file mode 100644 index 2564603e9..000000000 --- a/nightfall-deployer/circuits/double_transfer_stub.zok +++ /dev/null @@ -1,55 +0,0 @@ -type Point = field[2] - -struct OldCommitmentPreimage { - u32[8] id - u8[31] value - field salt - field hash - field rootKey -} - -struct NewCommitmentPreimage { - Point zkpPublicKeyRecipient - u8[31] value - field salt -} - -struct CompressedPoint { - bool parity - field ordinate -} - -def main(\ - private field[2] ercAddress,\ - private OldCommitmentPreimage[2] oldCommitment,\ - private NewCommitmentPreimage[2] newCommitment,\ - field[2] newCommitmentHash,\ - field[2] nullifier,\ - field[2] root,\ - private field[2][32] path,\ - private field[2] order,\ - private u32[8] ephemeralKey,\ - CompressedPoint compressedEphemeralPublicKey,\ - field[4] cipherText\ -)->(): - - field u = 0 - u32 v = 0 - u8 w = 0 - for u32 i in 0..2 do - u = ercAddress[i] + oldCommitment[i].salt + oldCommitment[i].hash + newCommitment[i].salt + newCommitment[i].zkpPublicKeyRecipient[0] + newCommitment[i].zkpPublicKeyRecipient[1] + nullifier[i] + root[i] + order[i] + oldCommitment[i].rootKey - for u32 j in 0..32 do - u = u * path[i][j] - endfor - v = oldCommitment[i].id[0] * oldCommitment[i].id[1] + oldCommitment[i].id[2] + oldCommitment[i].id[3] + oldCommitment[i].id[4] + oldCommitment[i].id[5] + oldCommitment[i].id[6] * oldCommitment[i].id[7] - for u32 j in 0..31 do - w = w + newCommitment[i].value[j] - endfor - endfor - - - assert(u == u) - assert(v == v) - assert(w == w) - - return diff --git a/nightfall-deployer/circuits/single_transfer.zok b/nightfall-deployer/circuits/single_transfer.zok deleted file mode 100644 index f797fcf66..000000000 --- a/nightfall-deployer/circuits/single_transfer.zok +++ /dev/null @@ -1,122 +0,0 @@ -from "ecc/babyjubjubParams" import BabyJubJubParams -from "ecc/babyjubjubParams" import main as curveParams -from "ecc/edwardsScalarMult" import main as scalarMult -from "ecc/edwardsCompress" import main as edwardsCompress -from "hashes/poseidon/poseidon.zok" import main as poseidon -from "./common/encryption/kem-dem" import main as kemDem, EncryptedMsgs - -from "utils/casts/u32_8_to_bool_256.zok" import main as u32_8_to_bool_256 -from "utils/pack/bool/nonStrictUnpack256.zok" import main as field_to_bool_256 - -from "./common/merkle-tree/path-check.zok" import main as pathCheck -from "./common/casts/u8_array_to_field.zok" import main as u8_array_to_field -from "./common/casts/u32_array_to_field.zok" import main as u32_array_to_field - -type Point = field[2] - -// 2 ^ 160 -const field SHIFT = 1461501637330902918203684832716283019655932542976 - -struct OldCommitmentPreimage { - u32[8] id - u8[31] value // needed to prevent overflow attacks in transfers - field salt - field hash - field rootKey -} - -struct NewCommitmentPreimage { - Point zkpPublicKeyRecipient - u8[31] value - field salt -} - -struct CompressedPoint { - bool parity - field ordinate -} - -def main(\ - private field ercAddress,\ - private OldCommitmentPreimage oldCommitment,\ - private NewCommitmentPreimage newCommitment,\ - field newCommitmentHash,\ - field nullifier,\ - field root,\ - private field[32] path,\ - private field order,\ - private u32[8] ephemeralKey,\ - CompressedPoint compressedEphemeralPublicKey,\ - field[4] cipherText\ -)->(): - - BabyJubJubParams context = curveParams() - field[2] g = [context.Gu, context.Gv] - - // Calculation of zkpPrivateKey and nullifierKey from rootKey - field zkpPrivateKey = poseidon([oldCommitment.rootKey, 2708019456231621178814538244712057499818649907582893776052749473028258908910]) - field nullifierKey = poseidon([oldCommitment.rootKey, 7805187439118198468809896822299973897593108379494079213870562208229492109015]) - bool[256] zkpPrivateKeyBool = field_to_bool_256(zkpPrivateKey) - field[2] zkpPublicKey = scalarMult(zkpPrivateKeyBool, g, context) - - // check the nullifier is valid - field nullifierCheck = poseidon([nullifierKey, oldCommitment.hash]) - assert (nullifierCheck == nullifier) - - // check the new commitment is valid - // we effectively throw away the value of the new commitment by insisting - // that it is equal to the old commitment value for a single-token transfer - // This is a little inefficient but makes the witness computation in node - // independent of how many commitments are being transformed. - assert(newCommitment.value == oldCommitment.value) - field value = u8_array_to_field(oldCommitment.value) - // pack the top four bytes of the token id into the ercAddress field (address only - // uses 160 bits and the Shield contract prevents creation of something with more than 160 bits) - field idTop4Bytes = u32_array_to_field([oldCommitment.id[0]]) - field idRemainder = u32_array_to_field(oldCommitment.id[1..8]) - field packedErcAddress = ercAddress + idTop4Bytes * SHIFT - - field newCommitmentCheck = poseidon([\ - packedErcAddress,\ - idRemainder,\ - value,\ - ...newCommitment.zkpPublicKeyRecipient,\ - newCommitment.salt\ - ]) - assert (newCommitmentCheck == newCommitmentHash) - - // check the old commitment is valid - field oldCommitmentCheck = poseidon([\ - packedErcAddress,\ - idRemainder,\ - value,\ - ...zkpPublicKey,\ - oldCommitment.salt\ - ]) - assert(oldCommitmentCheck == oldCommitment.hash) - - // check that the old commitment is in the merkle tree (path[0] should be the root) - field hash = oldCommitment.hash - bool x = pathCheck([root, ...path], order, hash) - assert(x) - - // KEM-DEM Encryption - bool[256] bitEphemeralKey = u32_8_to_bool_256(ephemeralKey) - - field[4] plainTexts = [\ - packedErcAddress,\ - idRemainder,\ - value,\ - newCommitment.salt\ - ] - - EncryptedMsgs<4> enc = kemDem(bitEphemeralKey, newCommitment.zkpPublicKeyRecipient, plainTexts) - assert(cipherText == enc.cipherText) - - bool[256] compressedPubKeyOutput = edwardsCompress(enc.ephemeralPublicKey) - bool parity = compressedEphemeralPublicKey.parity - bool[256] ordinate = field_to_bool_256(compressedEphemeralPublicKey.ordinate) - bool[256] compressedCheck256 = [ parity, ...ordinate[1..256] ] - assert(compressedPubKeyOutput == compressedCheck256) - - return diff --git a/nightfall-deployer/circuits/single_transfer_stub.zok b/nightfall-deployer/circuits/single_transfer_stub.zok deleted file mode 100644 index 802094695..000000000 --- a/nightfall-deployer/circuits/single_transfer_stub.zok +++ /dev/null @@ -1,53 +0,0 @@ -type Point = field[2] - -struct OldCommitmentPreimage { - u32[8] id - u8[31] value // needed to prevent overflow attacks in transfers - field salt - field hash - field rootKey -} - -struct NewCommitmentPreimage { - Point zkpPublicKeyRecipient - u8[31] value - field salt -} - -struct CompressedPoint { - bool parity - field ordinate -} - -def main(\ - private field ercAddress,\ - private OldCommitmentPreimage oldCommitment,\ - private NewCommitmentPreimage newCommitment,\ - field newCommitmentHash,\ - field nullifier,\ - field root,\ - private field[32] path,\ - private field order,\ - private u32[8] ephemeralKey,\ - CompressedPoint compressedEphemeralPublicKey,\ - field[4] cipherText\ -)->(): - - field u = ercAddress + oldCommitment.salt + oldCommitment.hash + newCommitment.salt + newCommitment.zkpPublicKeyRecipient[0] + newCommitment.zkpPublicKeyRecipient[1] + nullifier + root + order + oldCommitment.rootKey - - for u32 i in 0..32 do - u = u * path[i] - endfor - - u32 v = oldCommitment.id[0] * oldCommitment.id[1] + oldCommitment.id[2] + oldCommitment.id[3] + oldCommitment.id[4] + oldCommitment.id[5] + oldCommitment.id[6] * oldCommitment.id[7] - - u8 w = 0 - for u32 i in 0..31 do - w = w + newCommitment.value[i] - endfor - - assert(u == u) - assert(v == v) - assert(w == w) - - return diff --git a/nightfall-deployer/circuits/transfer.zok b/nightfall-deployer/circuits/transfer.zok new file mode 100644 index 000000000..ee8163de4 --- /dev/null +++ b/nightfall-deployer/circuits/transfer.zok @@ -0,0 +1,98 @@ +from "./common/utils/structures.zok" import CommitmentPreimage,Point, PublicTransaction, SHIFT +from "./common/generic_circuit/Verifiers/verify_commitments.zok" import main as verify_commitments +from "./common/generic_circuit/Verifiers/verify_structure.zok" import main as verify_structure +from "./common/generic_circuit/Verifiers/verify_encryption.zok" import main as verify_encryption +from "./common/generic_circuit/Verifiers/verify_nullifiers.zok" import main as verify_nullifiers +from "./common/casts/u32_array_to_field.zok" import main as u32_array_to_field +from "./common/utils/calculations.zok" import sum +from "./common/casts/u8_array_to_field.zok" import main as u8_array_to_field + +const u32 txType = 1 +const u32 numCommitments = 2 +const u32 numNullifiers = 2 +const u32 transfer = 1 + + +def main(\ + PublicTransaction tx,\ + field[numNullifiers] roots,\ + private CommitmentPreimage oldCommitment,\ + private field[numNullifiers] rootKey,\ + private field[numNullifiers][32] paths,\ + private field[numNullifiers] orders,\ + private CommitmentPreimage newCommitment,\ + private Point[numCommitments] recipientPublicKey,\ + private u32[8] ephemeralKey,\ + private field ercAddressTransfer,\ + private u32[8] tokenId\ +)-> (): + //Verify public transaction structure + assert(verify_structure::(\ + tx.value,\ + tx.transactionType,\ + tx.tokenType,\ + tx.tokenId,\ + tx.ercAddress,\ + tx.recipientAddress,\ + tx.commitments,\ + tx.nullifiers,\ + tx.historicRootBlockNumberL2,\ + tx.compressedSecrets\ + )) + + //Check that values match + assert(\ + sum(u8_array_to_field(oldCommitment.value)) ==\ + sum(u8_array_to_field(newCommitment.value))\ + ) + + // pack the top four bytes of the token id into the ercAddress field (address only + // uses 160 bits and the Shield contract prevents creation of something with more than 160 bits) + field idRemainder = u32_array_to_field(tokenId[1..8]) + field packedErcAddress = ercAddressTransfer + u32_array_to_field([tokenId[0]]) * SHIFT + + //Verify nullifiers + Point firstInputZkpPublicKeys = [0,0] + firstInputZkpPublicKeys = verify_nullifiers::(\ + packedErcAddress,\ + idRemainder,\ + tx.nullifiers,\ + roots,\ + u8_array_to_field(oldCommitment.value),\ + oldCommitment.salt,\ + rootKey,\ + paths,\ + orders\ + ) + + assert(\ + u8_array_to_field([newCommitment.value[numCommitments - 1]])[0] == 0 || \ + firstInputZkpPublicKeys == recipientPublicKey[numCommitments - 1]\ + ) + + //Verify new Commmitments + assert(verify_commitments::(\ + packedErcAddress,\ + idRemainder,\ + tx.commitments,\ + firstInputZkpPublicKeys,\ + u8_array_to_field(newCommitment.value),\ + newCommitment.salt,\ + recipientPublicKey\ + )) + + //Verify Kem Dem encryption + assert(verify_encryption::(\ + tx.ercAddress,\ + tx.tokenId,\ + tx.compressedSecrets,\ + packedErcAddress,\ + idRemainder,\ + u8_array_to_field(newCommitment.value),\ + newCommitment.salt,\ + recipientPublicKey,\ + tx.recipientAddress,\ + ephemeralKey\ + )) + + return \ No newline at end of file diff --git a/nightfall-deployer/circuits/withdraw.zok b/nightfall-deployer/circuits/withdraw.zok index a2f842b35..261cf5f23 100644 --- a/nightfall-deployer/circuits/withdraw.zok +++ b/nightfall-deployer/circuits/withdraw.zok @@ -1,70 +1,75 @@ -from "ecc/babyjubjubParams" import BabyJubJubParams -from "ecc/babyjubjubParams" import main as curveParams -from "ecc/edwardsCompress" import main as edwardsCompress -from "ecc/edwardsScalarMult" import main as scalarMult - -from "utils/pack/bool/nonStrictUnpack256.zok" import main as field_to_bool_256 -from "./common/casts/u8_array_to_field.zok" import main as u8_array_to_field +from "./common/utils/structures.zok" import CommitmentPreimage,Point, PublicTransaction, SHIFT +from "./common/generic_circuit/Verifiers/verify_commitments.zok" import main as verify_commitments +from "./common/generic_circuit/Verifiers/verify_structure.zok" import main as verify_structure +from "./common/generic_circuit/Verifiers/verify_encryption.zok" import main as verify_encryption +from "./common/generic_circuit/Verifiers/verify_nullifiers.zok" import main as verify_nullifiers from "./common/casts/u32_array_to_field.zok" import main as u32_array_to_field +from "./common/utils/calculations.zok" import sum +from "./common/casts/u8_array_to_field.zok" import main as u8_array_to_field -from "hashes/poseidon/poseidon.zok" import main as poseidon -from "./common/merkle-tree/path-check.zok" import main as pathCheck - -// 2 ^ 160 -const field SHIFT = 1461501637330902918203684832716283019655932542976 - -struct OldCommitmentPreimage { - field salt - field hash - field rootKey -} +const u32 txType = 2 +const u32 numCommitments = 1 +const u32 numNullifiers = 2 +const u32 transfer = 1 def main(\ - field ercAddress,\ - u32[8] id,\ - field value,\ - private OldCommitmentPreimage oldCommitment,\ - field nullifier,\ - field recipientAddress,\ - field root,\ - private field[32] path,\ - private field order\ -)->(): + PublicTransaction tx,\ + field[numNullifiers] roots,\ + private CommitmentPreimage oldCommitment,\ + private field[numNullifiers] rootKey,\ + private field[numNullifiers][32] paths,\ + private field[numNullifiers] orders,\ + private CommitmentPreimage newCommitment\ + )-> (): - BabyJubJubParams context = curveParams() - field[2] g = [context.Gu, context.Gv] + //Verify public transaction structure + assert(verify_structure::(\ + tx.value,\ + tx.transactionType,\ + tx.tokenType,\ + tx.tokenId,\ + tx.ercAddress,\ + tx.recipientAddress,\ + tx.commitments,\ + tx.nullifiers,\ + tx.historicRootBlockNumberL2,\ + tx.compressedSecrets\ + )) - // Calculation of zkpPrivateKey and nullifierKey from rootKey - field zkpPrivateKey = poseidon([oldCommitment.rootKey, 2708019456231621178814538244712057499818649907582893776052749473028258908910]) - field nullifierKey = poseidon([oldCommitment.rootKey, 7805187439118198468809896822299973897593108379494079213870562208229492109015]) - bool[256] zkpPrivateKeyBool = field_to_bool_256(zkpPrivateKey) - field[2] zkpPublicKey = scalarMult(zkpPrivateKeyBool, g, context) - - // check the nullifier is valid - field nullifierCheck = poseidon([\ - nullifierKey,\ - oldCommitment.hash\ - ]) - assert(nullifierCheck == nullifier) + //Check that values match + assert(\ + sum(u8_array_to_field(oldCommitment.value)) ==\ + sum(u8_array_to_field(newCommitment.value)) + tx.value\ + ) // pack the top four bytes of the token id into the ercAddress field (address only // uses 160 bits and the Shield contract prevents creation of something with more than 160 bits) - field idTop4Bytes = u32_array_to_field([id[0]]) - field idRemainder = u32_array_to_field(id[1..8]) - field packedErcAddress = ercAddress + idTop4Bytes * SHIFT + field idRemainder = u32_array_to_field(tx.tokenId[1..8]) + field packedErcAddress = tx.ercAddress + u32_array_to_field([tx.tokenId[0]]) * SHIFT - // check the old commitment is valid - field oldCommitmentCheck = poseidon([\ + //Verify nullifiers + Point firstInputZkpPublicKeys = [0,0] + firstInputZkpPublicKeys = verify_nullifiers::(\ packedErcAddress,\ idRemainder,\ - value,\ - ...zkpPublicKey,\ - oldCommitment.salt\ - ]) - assert(oldCommitmentCheck == oldCommitment.hash) - - // check that the old commitment is in the merkle tree - field hash = oldCommitment.hash - assert(pathCheck([root, ...path], order, hash)) + tx.nullifiers,\ + roots,\ + u8_array_to_field(oldCommitment.value),\ + oldCommitment.salt,\ + rootKey,\ + paths,\ + orders\ + ) + + //Verify new Commmitments + assert(verify_commitments::(\ + packedErcAddress,\ + idRemainder,\ + tx.commitments,\ + firstInputZkpPublicKeys,\ + u8_array_to_field(newCommitment.value),\ + newCommitment.salt,\ + [firstInputZkpPublicKeys]\ + )) return diff --git a/nightfall-deployer/contracts/Challenges.sol b/nightfall-deployer/contracts/Challenges.sol index 69f6e1302..ce2ec1357 100644 --- a/nightfall-deployer/contracts/Challenges.sol +++ b/nightfall-deployer/contracts/Challenges.sol @@ -291,7 +291,7 @@ contract Challenges is Stateful, Key_Registry, Config { state.areBlockAndTransactionsReal(blockL2, transactions); if ( transactions[transactionIndex].transactionType == - Structures.TransactionTypes.DOUBLE_TRANSFER + Structures.TransactionTypes.TRANSFER ) { require( state.getNumberOfL2Blocks() < diff --git a/nightfall-deployer/contracts/ChallengesUtil.sol b/nightfall-deployer/contracts/ChallengesUtil.sol index a27b29041..dc0014067 100644 --- a/nightfall-deployer/contracts/ChallengesUtil.sol +++ b/nightfall-deployer/contracts/ChallengesUtil.sol @@ -58,9 +58,7 @@ library ChallengesUtil { if (transaction.transactionType == Structures.TransactionTypes.DEPOSIT) libChallengeTransactionTypeDeposit(transaction); // TODO add these checks back after PR for out of gas - else if (transaction.transactionType == Structures.TransactionTypes.SINGLE_TRANSFER) - libChallengeTransactionTypeSingleTransfer(transaction); - else if (transaction.transactionType == Structures.TransactionTypes.DOUBLE_TRANSFER) + else if (transaction.transactionType == Structures.TransactionTypes.TRANSFER) libChallengeTransactionTypeDoubleTransfer(transaction); // if(transaction.transactionType == TransactionTypes.WITHDRAW) else libChallengeTransactionTypeWithdraw(transaction); } diff --git a/nightfall-deployer/contracts/Shield.sol b/nightfall-deployer/contracts/Shield.sol index d9f6cdcb7..93d69caeb 100644 --- a/nightfall-deployer/contracts/Shield.sol +++ b/nightfall-deployer/contracts/Shield.sol @@ -216,7 +216,8 @@ contract Shield is Stateful, Config, Key_Registry, ReentrancyGuardUpgradeable, P bytes32[6] memory siblingPath ) external payable nonReentrant { // The transaction is a withdrawal transaction - require(t.transactionType == TransactionTypes.WITHDRAW, 'Can only advance withdrawals'); + require(t.transactionType == TransactionTypes.WITHDRAW, + 'Can only advance withdrawals'); // check this block is a real one, in the queue, not something made up. state.areBlockAndTransactionReal(b, t, index, siblingPath); diff --git a/nightfall-deployer/contracts/Structures.sol b/nightfall-deployer/contracts/Structures.sol index 8f8d6167c..4b9c8f934 100644 --- a/nightfall-deployer/contracts/Structures.sol +++ b/nightfall-deployer/contracts/Structures.sol @@ -6,7 +6,7 @@ Basic data structures for an optimistic rollup pragma solidity ^0.8.0; contract Structures { - enum TransactionTypes {DEPOSIT, SINGLE_TRANSFER, DOUBLE_TRANSFER, WITHDRAW} + enum TransactionTypes {DEPOSIT, TRANSFER, WITHDRAW} enum TokenType {ERC20, ERC721, ERC1155} diff --git a/nightfall-deployer/contracts/Utils.sol b/nightfall-deployer/contracts/Utils.sol index c0195fc43..d443cd3fd 100644 --- a/nightfall-deployer/contracts/Utils.sol +++ b/nightfall-deployer/contracts/Utils.sol @@ -107,9 +107,7 @@ library Utils { // uint256[] memory inputs = new uint256[](countPublicInputs(ts)); if (ts.transactionType == Structures.TransactionTypes.DEPOSIT) { inputs = getDepositInputs(ts); - } else if (ts.transactionType == Structures.TransactionTypes.SINGLE_TRANSFER) { - inputs = getSingleTransferInputs(ts, roots); - } else if (ts.transactionType == Structures.TransactionTypes.DOUBLE_TRANSFER) { + } else if (ts.transactionType == Structures.TransactionTypes.TRANSFER) { inputs = getDoubleTransferInputs(ts, roots); } else { inputs = getWithdrawInputs(ts, roots); diff --git a/nightfall-optimist/src/routes/proposer.mjs b/nightfall-optimist/src/routes/proposer.mjs index 0ced53bc8..5fe86fde6 100644 --- a/nightfall-optimist/src/routes/proposer.mjs +++ b/nightfall-optimist/src/routes/proposer.mjs @@ -366,7 +366,8 @@ router.post('/offchain-transaction', async (req, res) => { switch (Number(transactionType)) { case 1: case 2: - case 3: { + case 3: + case 4: { // When comparing this with getTransactionSubmittedCalldata, // note we dont need to decompressProof as proofs are only compressed if they go on-chain. // let's not directly call transactionSubmittedEventHandler, instead, we'll queue it diff --git a/nightfall-optimist/src/services/transaction-checker.mjs b/nightfall-optimist/src/services/transaction-checker.mjs index 58b14a11f..eb5d1f828 100644 --- a/nightfall-optimist/src/services/transaction-checker.mjs +++ b/nightfall-optimist/src/services/transaction-checker.mjs @@ -14,9 +14,9 @@ import { waitForContract } from '../event-handlers/subscribe.mjs'; import { getBlockByBlockNumberL2 } from './database.mjs'; import verify from './verify.mjs'; -const { generalise, GN } = gen; -const { PROVING_SCHEME, BACKEND, CURVE, BN128_GROUP_ORDER, MAX_PUBLIC_VALUES } = config; const { ZERO, CHALLENGES_CONTRACT_NAME } = constants; +const { generalise } = gen; +const { PROVING_SCHEME, BACKEND, CURVE, BN128_GROUP_ORDER, MAX_PUBLIC_VALUES } = config; function isOverflow(value, check) { const bigValue = value.bigInt; @@ -35,110 +35,24 @@ async function checkTransactionHash(transaction) { throw new TransactionError('The transaction hash did not match the transaction data', 0); } } -// next that the fields provided are consistent with the transaction type -async function checkTransactionType(transaction) { - switch (Number(transaction.transactionType)) { - // Assuming nullifiers and commitments can't be valid ZEROs. - // But points can such as compressedSecrets, Proofs - case 0: // deposit - if ( - (Number(transaction.tokenType) !== 0 && - transaction.tokenId === ZERO && - BigInt(transaction.value) === 0n) || - transaction.ercAddress === ZERO || - transaction.recipientAddress !== ZERO || - transaction.commitments[0] === ZERO || - transaction.commitments[1] !== ZERO || - transaction.commitments.length !== 2 || - transaction.nullifiers.some(n => n !== ZERO) || - transaction.compressedSecrets.some(cs => cs !== ZERO) || - transaction.compressedSecrets.length !== 2 || - transaction.proof.every(p => p === ZERO) || - // This extra check is unique to deposits - Number(transaction.historicRootBlockNumberL2[0]) !== 0 || - Number(transaction.historicRootBlockNumberL2[1]) !== 0 - ) - throw new TransactionError( - 'The data provided was inconsistent with a transaction type of DEPOSIT', - 1, - ); - break; - case 1: // single token transaction - if ( - BigInt(transaction.value) !== 0n || - transaction.commitments[0] === ZERO || - transaction.commitments[1] !== ZERO || - transaction.commitments.length !== 2 || - transaction.nullifiers[0] === ZERO || - transaction.nullifiers[1] !== ZERO || - transaction.nullifiers.length !== 2 || - transaction.compressedSecrets.every(cs => cs === ZERO) || - transaction.compressedSecrets.length !== 2 || - transaction.proof.every(p => p === ZERO) - ) - throw new TransactionError( - 'The data provided was inconsistent with a transaction type of SINGLE_TRANSFER', - 1, - ); - break; - case 2: // double token transaction - if ( - BigInt(transaction.value) !== 0n || - transaction.commitments.some(c => c === ZERO) || - transaction.commitments.length !== 2 || - transaction.nullifiers.some(n => n === ZERO) || - transaction.nullifiers.length !== 2 || - transaction.nullifiers[0] === transaction.nullifiers[1] || - transaction.compressedSecrets.every(cs => cs === ZERO) || - transaction.compressedSecrets.length !== 2 || - transaction.proof.every(p => p === ZERO) - ) - throw new TransactionError( - 'The data provided was inconsistent with a transaction type of DOUBLE_TRANSFER', - 1, - ); - break; - case 3: // withdraw transaction - if ( - (Number(transaction.tokenType) !== 0 && - transaction.tokenId === ZERO && - BigInt(transaction.value) === 0n) || - transaction.ercAddress === ZERO || - transaction.recipientAddress === ZERO || - transaction.commitments.some(c => c !== ZERO) || - transaction.nullifiers[0] === ZERO || - transaction.nullifiers[1] !== ZERO || - transaction.nullifiers.length !== 2 || - transaction.compressedSecrets.some(cs => cs !== ZERO) || - transaction.proof.every(p => p === ZERO) - ) - throw new TransactionError( - 'The data provided was inconsistent with a transaction type of WITHDRAW', - 1, - ); - break; - default: - throw new TransactionError('Unknown transaction type', 2); - } -} async function checkHistoricRoot(transaction) { // Deposit transaction have a historic root of 0 // the validity is tested in checkTransactionType - if (Number(transaction.transactionType) === 1 || Number(transaction.transactionType) === 3) { - const historicRootFirst = await getBlockByBlockNumberL2( - transaction.historicRootBlockNumberL2[0], - ); - if (historicRootFirst === null) - throw new TransactionError('The historic root in the transaction does not exist', 3); - } - if (Number(transaction.transactionType) === 2) { + if (Number(transaction.transactionType) === 1) { const [historicRootFirst, historicRootSecond] = await Promise.all( transaction.historicRootBlockNumberL2.map(h => getBlockByBlockNumberL2(h)), ); if (historicRootFirst === null || historicRootSecond === null) throw new TransactionError('The historic root in the transaction does not exist', 3); } + if (Number(transaction.transactionType) === 1 || Number(transaction.transactionType) === 2) { + const historicRootFirst = await getBlockByBlockNumberL2( + transaction.historicRootBlockNumberL2[0], + ); + if (historicRootFirst === null) + throw new TransactionError('The historic root in the transaction does not exist', 3); + } } async function verifyProof(transaction) { @@ -150,28 +64,30 @@ async function verifyProof(transaction) { // to verify a proof, we make use of a zokrates-worker, which has an offchain // verifier capability let inputs; - const historicRootFirst = (await getBlockByBlockNumberL2( - transaction.historicRootBlockNumberL2[0], - )) ?? { root: ZERO }; - const historicRootSecond = (await getBlockByBlockNumberL2( - transaction.historicRootBlockNumberL2[1], - )) ?? { root: ZERO }; - - const bin = new GN(transaction.recipientAddress).binary.padStart(256, '0'); - const parity = bin[0]; - const ordinate = bin.slice(1); - const binaryEPub = [parity, new GN(ordinate, 'binary').field(BN128_GROUP_ORDER, false)]; + const historicRootFirst = + transaction.nullifiers[0] === ZERO + ? { root: ZERO } + : (await getBlockByBlockNumberL2(transaction.historicRootBlockNumberL2[0])) ?? { root: ZERO }; + const historicRootSecond = + transaction.nullifiers[1] === ZERO + ? { root: ZERO } + : (await getBlockByBlockNumberL2(transaction.historicRootBlockNumberL2[1])) ?? { root: ZERO }; + const publicInputs = [ + transaction.value, + transaction.historicRootBlockNumberL2, + transaction.transactionType, + transaction.tokenType, + generalise(transaction.tokenId).limbs(32, 8), + transaction.ercAddress, + generalise(transaction.recipientAddress).limbs(32, 8), + transaction.commitments, + transaction.nullifiers, + transaction.compressedSecrets, + ].flat(Infinity); switch (Number(transaction.transactionType)) { case 0: // deposit transaction - inputs = generalise( - [ - transaction.ercAddress, - generalise(transaction.tokenId).limbs(32, 8), - transaction.value, - transaction.commitments[0], - ].flat(Infinity), - ); + inputs = generalise(publicInputs).all.hex(32); if ( isOverflow(transaction.ercAddress, MAX_PUBLIC_VALUES.ERCADDRESS) || isOverflow(transaction.commitments[0], MAX_PUBLIC_VALUES.COMMITMENTS) @@ -179,38 +95,24 @@ async function verifyProof(transaction) { throw new TransactionError('Truncated value overflow in public input', 4); break; case 1: // single transfer transaction - inputs = generalise( - [ - transaction.commitments[0], - transaction.nullifiers[0], - historicRootFirst.root, - binaryEPub, - transaction.ercAddress, - transaction.tokenId, - ...transaction.compressedSecrets, - ].flat(Infinity), - ); + inputs = generalise([ + ...publicInputs, + historicRootFirst.root, + historicRootSecond.root, + ]).all.hex(32); // check for truncation overflow attacks if ( - isOverflow(transaction.commitments[0], MAX_PUBLIC_VALUES.COMMITMENTS) || - isOverflow(transaction.nullifiers[0], MAX_PUBLIC_VALUES.NULLIFIER) || - isOverflow(historicRootFirst.root, BN128_GROUP_ORDER) + isOverflow(historicRootFirst.root, BN128_GROUP_ORDER) || + isOverflow(historicRootSecond.root, BN128_GROUP_ORDER) ) throw new TransactionError('Overflow in public input', 4); break; - case 2: // double transfer transaction - inputs = generalise( - [ - transaction.commitments, // not truncating here as we already ensured hash < group order - transaction.nullifiers, - historicRootFirst.root, - historicRootSecond.root, - binaryEPub, - transaction.ercAddress, - transaction.tokenId, - ...transaction.compressedSecrets, - ].flat(Infinity), - ); + case 2: // withdraw transaction + inputs = generalise([ + ...publicInputs, + historicRootFirst.root, + historicRootSecond.root, + ]).all.hex(32); // check for truncation overflow attacks for (let i = 0; i < transaction.nullifiers.length; i++) { if (isOverflow(transaction.nullifiers[i], MAX_PUBLIC_VALUES.NULLIFIER)) @@ -220,32 +122,9 @@ async function verifyProof(transaction) { if (isOverflow(transaction.commitments[i], MAX_PUBLIC_VALUES.COMMITMENT)) throw new TransactionError('Overflow in public input', 4); } - if ( - isOverflow(historicRootFirst.root, BN128_GROUP_ORDER) || - isOverflow(historicRootSecond.root, BN128_GROUP_ORDER) - ) + if (isOverflow(historicRootFirst.root, BN128_GROUP_ORDER)) throw new TransactionError('Overflow in public input', 4); break; - case 3: // withdraw transaction - inputs = generalise( - [ - transaction.ercAddress, - generalise(transaction.tokenId).limbs(32, 8), - transaction.value, - transaction.nullifiers[0], - transaction.recipientAddress, - historicRootFirst.root, - ].flat(Infinity), - ); - // check for truncation overflow attacks - if ( - isOverflow(transaction.ercAddress, MAX_PUBLIC_VALUES.ERCADDRESS) || - isOverflow(transaction.recipientAddress, MAX_PUBLIC_VALUES.ERCADDRESS) || - isOverflow(transaction.nullifiers[0], MAX_PUBLIC_VALUES.NULLIFIER) || - isOverflow(historicRootFirst.root, BN128_GROUP_ORDER) - ) - throw new TransactionError('Truncated value overflow in public input', 4); - break; default: throw new TransactionError('Unknown transaction type', 2); } @@ -258,7 +137,7 @@ async function verifyProof(transaction) { provingScheme: PROVING_SCHEME, backend: BACKEND, curve: CURVE, - inputs: inputs.all.hex(32), + inputs, }); if (!res) throw new TransactionError('The proof did not verify', 4); } @@ -266,7 +145,6 @@ async function verifyProof(transaction) { async function checkTransaction(transaction) { return Promise.all([ checkTransactionHash(transaction), - checkTransactionType(transaction), checkHistoricRoot(transaction), verifyProof(transaction), ]); diff --git a/test/e2e/tokens/erc20.test.mjs b/test/e2e/tokens/erc20.test.mjs index 226e828c2..4db6663a9 100644 --- a/test/e2e/tokens/erc20.test.mjs +++ b/test/e2e/tokens/erc20.test.mjs @@ -271,7 +271,7 @@ describe('ERC20 tests', () => { false, erc20Address, tokenType, - transferValue, + transferValue / 2, tokenId, nf3Users[0].ethereumAddress, ); @@ -289,7 +289,7 @@ describe('ERC20 tests', () => { false, erc20Address, tokenType, - transferValue, + Math.floor(transferValue / 2), tokenId, nf3Users[0].ethereumAddress, ); @@ -318,7 +318,7 @@ describe('ERC20 tests', () => { false, erc20Address, tokenType, - transferValue, + Math.floor(transferValue / 2), tokenId, nf3Users[0].ethereumAddress, ); @@ -349,6 +349,23 @@ describe('ERC20 tests', () => { this.skip(); } }); + + it('should withdraw from L2 with some change', async function () { + const beforeBalance = (await nf3Users[0].getLayer2Balances())[erc20Address]?.[0].balance; + const rec = await nf3Users[0].withdraw( + false, + erc20Address, + tokenType, + Math.floor(transferValue / 2), + tokenId, + nf3Users[0].ethereumAddress, + ); + expectTransaction(rec); + + logger.debug(` Gas used was ${Number(rec.gasUsed)}`); + const afterBalance = (await nf3Users[0].getLayer2Balances())[erc20Address]?.[0].balance; + expect(afterBalance).to.be.lessThan(beforeBalance); + }); }); describe('Instant withdrawals from L2', () => { @@ -361,7 +378,7 @@ describe('ERC20 tests', () => { nf3LiquidityProvider.ethereumAddress, nf3LiquidityProvider.shieldContractAddress, tokenType, - transferValue, + Math.floor(transferValue / 2), web3Client.getWeb3(), !!nf3LiquidityProvider.ethereumSigningKey, ); @@ -392,7 +409,7 @@ describe('ERC20 tests', () => { false, erc20Address, tokenType, - transferValue, + Math.floor(transferValue / 2), tokenId, nf3Users[0].ethereumAddress, fee, @@ -423,7 +440,7 @@ describe('ERC20 tests', () => { false, erc20Address, tokenType, - transferValue, + Math.floor(transferValue / 2), tokenId, nf3Users[0].ethereumAddress, fee, @@ -537,7 +554,6 @@ describe('ERC20 tests', () => { await new Promise(resolve => setTimeout(resolve, 30000)); } - // console.log('withdrawing', trnsferValue * 6); const rec = await nf3Users[0].withdraw( false, erc20Address, @@ -547,7 +563,6 @@ describe('ERC20 tests', () => { nf3Users[0].ethereumAddress, fee, ); - await new Promise(resolve => setTimeout(resolve, 15000)); expectTransaction(rec); diff --git a/wallet/src/hooks/User/index.jsx b/wallet/src/hooks/User/index.jsx index 887e4a7c5..dd3c784db 100644 --- a/wallet/src/hooks/User/index.jsx +++ b/wallet/src/hooks/User/index.jsx @@ -143,8 +143,14 @@ export const UserProvider = ({ children }) => { useInterval( async () => { const circuitName = USE_STUBS - ? ['deposit_stub', 'single_transfer_stub', 'double_transfer_stub', 'withdraw_stub'] - : ['deposit', 'single_transfer', 'double_transfer', 'withdraw']; + ? [ + 'deposit_stub', + 'single_transfer_stub', + 'double_transfer_stub', + 'withdraw_stub', + 'withdraw_change_stub', + ] + : ['deposit', 'single_transfer', 'double_transfer', 'withdraw', 'withdraw_change']; const circuitCheck = await Promise.all(circuitName.map(c => checkIndexDBForCircuit(c))); console.log('Circuit Check', circuitCheck); diff --git a/zokrates-worker/src/index.mjs b/zokrates-worker/src/index.mjs index 2850e40a8..0ea26d66f 100644 --- a/zokrates-worker/src/index.mjs +++ b/zokrates-worker/src/index.mjs @@ -33,7 +33,13 @@ const checkCircuitsOutput = async () => { : `${DEFAULT_CIRCUIT_FILES_URL}/${env}`; const url = `${baseUrl}/proving_files/hash.txt`; const outputPath = `./output`; - const circuits = ['deposit', 'double_transfer', 'single_transfer', 'withdraw']; + const circuits = [ + 'deposit', + 'double_transfer', + 'single_transfer', + 'withdraw', + 'withdraw_change', + ]; const res = await axios.get(url); // get all circuit files const files = res.data.split('\n'); diff --git a/zokrates-worker/src/services/generateKeys.mjs b/zokrates-worker/src/services/generateKeys.mjs index 2471eabab..e30aab10e 100644 --- a/zokrates-worker/src/services/generateKeys.mjs +++ b/zokrates-worker/src/services/generateKeys.mjs @@ -24,7 +24,7 @@ export default async function generateKeys({ filepath, curve = 'bn128' }) { await compile( `${circuitsPath}/${filepath}`, `${outputPath}/${circuitDir}`, - `${circuitName}_out`, + `${circuitName}`, curve, ); From d39e592530deacdf8cae503a9f379f3e9f10722e Mon Sep 17 00:00:00 2001 From: Ilyas Ridhuan Date: Thu, 28 Jul 2022 15:28:41 +0100 Subject: [PATCH 02/15] fix: rebase conflicts --- nightfall-deployer/circuits/common/merkle-tree/path-check.zok | 2 -- 1 file changed, 2 deletions(-) diff --git a/nightfall-deployer/circuits/common/merkle-tree/path-check.zok b/nightfall-deployer/circuits/common/merkle-tree/path-check.zok index 75b8fc251..102fd746e 100644 --- a/nightfall-deployer/circuits/common/merkle-tree/path-check.zok +++ b/nightfall-deployer/circuits/common/merkle-tree/path-check.zok @@ -1,8 +1,6 @@ from "hashes/poseidon/poseidon.zok" import main as poseidon import "utils/pack/bool/unpack128.zok" as unpack128 -from "hashes/poseidon/poseidon.zok" import main as poseidon - def orderFields(bool order, field pathNode, field siblingNode)->(field[2]): field right = if order then pathNode else siblingNode fi From 097123bbb35cfcc3b12baac104ab919eddbaba28 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Fri, 29 Jul 2022 10:57:09 +0200 Subject: [PATCH 03/15] chore: merging issue818 changes --- common-files/classes/transaction.mjs | 2 +- nightfall-client/src/services/deposit.mjs | 6 +- nightfall-client/src/services/withdraw.mjs | 38 ++-- .../src/utils/compute-witness.mjs | 99 ++++----- .../generic_circuit/Stubs/encryption_stub.zok | 20 +- .../Verifiers/verify_commitments.zok | 34 ++- .../Verifiers/verify_encryption.zok | 16 +- .../Verifiers/verify_nullifiers.zok | 9 +- .../Verifiers/verify_structure.zok | 16 +- .../common/hashes/mimc/mimc-constants.zok | 94 -------- .../common/hashes/mimc/mimc-encryption.zok | 12 - .../common/hashes/mimc/mimc-hash-1.zok | 8 - .../common/hashes/mimc/mimc-hash-2.zok | 10 - .../common/hashes/mimc/mimc-hash-5.zok | 10 - .../common/hashes/mimc/mimc-hash-N.zok | 10 - .../circuits/common/utils/structures.zok | 17 ++ nightfall-deployer/circuits/deposit.zok | 39 ++-- nightfall-deployer/circuits/deposit_stub.zok | 39 +--- nightfall-deployer/circuits/transfer.zok | 104 +++------ nightfall-deployer/circuits/transfer_stub.zok | 27 +++ nightfall-deployer/circuits/withdraw.zok | 82 +++---- nightfall-deployer/circuits/withdraw_stub.zok | 44 ++-- nightfall-deployer/contracts/Challenges.sol | 173 ++++----------- .../contracts/ChallengesUtil.sol | 145 ++---------- .../contracts/MerkleTree_Stateless.sol | 125 ----------- nightfall-deployer/contracts/State.sol | 11 +- nightfall-deployer/contracts/Structures.sol | 2 +- nightfall-deployer/contracts/Utils.sol | 118 +++------- nightfall-deployer/contracts/Verifier.sol | 206 +++++++++--------- .../src/services/challenges.mjs | 123 ++++------- .../src/services/transaction-checker.mjs | 109 ++++----- 31 files changed, 564 insertions(+), 1184 deletions(-) delete mode 100644 nightfall-deployer/circuits/common/hashes/mimc/mimc-constants.zok delete mode 100644 nightfall-deployer/circuits/common/hashes/mimc/mimc-encryption.zok delete mode 100644 nightfall-deployer/circuits/common/hashes/mimc/mimc-hash-1.zok delete mode 100644 nightfall-deployer/circuits/common/hashes/mimc/mimc-hash-2.zok delete mode 100644 nightfall-deployer/circuits/common/hashes/mimc/mimc-hash-5.zok delete mode 100644 nightfall-deployer/circuits/common/hashes/mimc/mimc-hash-N.zok create mode 100644 nightfall-deployer/circuits/transfer_stub.zok diff --git a/common-files/classes/transaction.mjs b/common-files/classes/transaction.mjs index 842b593a5..638940cab 100644 --- a/common-files/classes/transaction.mjs +++ b/common-files/classes/transaction.mjs @@ -95,7 +95,7 @@ class Transaction { else if (_historicRoot.length === 1) historicRootBlockNumberL2 = [..._historicRoot, 0]; else historicRootBlockNumberL2 = _historicRoot; - if ((transactionType === 0 || transactionType === 3) && TOKEN_TYPES[tokenType] === undefined) + if ((transactionType === 0 || transactionType === 2) && TOKEN_TYPES[tokenType] === undefined) throw new Error('Unrecognized token type'); // convert everything to hex(32) for interfacing with web3 const preimage = generalise({ diff --git a/nightfall-client/src/services/deposit.mjs b/nightfall-client/src/services/deposit.mjs index 1acd85097..35a6ca618 100644 --- a/nightfall-client/src/services/deposit.mjs +++ b/nightfall-client/src/services/deposit.mjs @@ -45,7 +45,11 @@ async function deposit(items) { commitments: [commitment], }), ); - const witness = computeWitness(publicData, [], { salt, recipientPublicKeys: [zkpPublicKey] }); + + const privateData = { salt, recipientPublicKeys: [zkpPublicKey] }; + const roots = []; + + const witness = computeWitness(publicData, roots, privateData); logger.debug(`witness input is ${witness.join(' ')}`); // call a zokrates worker to generate the proof let folderpath = 'deposit'; diff --git a/nightfall-client/src/services/withdraw.mjs b/nightfall-client/src/services/withdraw.mjs index 2e982a8b3..7ccafca6e 100644 --- a/nightfall-client/src/services/withdraw.mjs +++ b/nightfall-client/src/services/withdraw.mjs @@ -69,23 +69,25 @@ async function withdraw(withdrawParams) { const blockNumberL2s = commitmentTreeInfo.map(l => l.isOnChain); logger.silly(`SiblingPath was: ${JSON.stringify(localSiblingPaths)}`); - let newCommitment = null; + const newCommitment = []; if (change !== 0n) { const salt = await randValueLT(BN128_GROUP_ORDER); - newCommitment = new Commitment({ - ercAddress, - tokenId, - value: change, - zkpPublicKey: ZkpKeys.decompressZkpPublicKey(compressedZkpPublicKey), - compressedZkpPublicKey, - salt, - }); + newCommitment.push( + new Commitment({ + ercAddress, + tokenId, + value: change, + zkpPublicKey: ZkpKeys.decompressZkpPublicKey(compressedZkpPublicKey), + compressedZkpPublicKey, + salt, + }), + ); } const publicData = Transaction.buildSolidityStruct( new Transaction({ fee, historicRootBlockNumberL2: blockNumberL2s, - commitments: newCommitment ? [newCommitment] : [{ hash: 0 }, { hash: 0 }], + commitments: newCommitment.length > 0 ? newCommitment : [{ hash: 0 }, { hash: 0 }], transactionType: 2, tokenType: items.tokenType, tokenId, @@ -95,25 +97,23 @@ async function withdraw(withdrawParams) { nullifiers, }), ); - const privateObj = { + const privateData = { rootKey, oldCommitmentPreimage: oldCommitments.map(o => { return { value: o.preimage.value, salt: o.preimage.salt }; }), paths: localSiblingPaths.map(siblingPath => siblingPath.slice(1)), orders: leafIndices, + newCommitmentPreimage: newCommitment.map(o => { + return { value: o.preimage.value, salt: o.preimage.salt }; + }), + recipientPublicKeys: newCommitment.map(o => o.preimage.zkpPublicKey), }; - if (newCommitment) - privateObj.newCommitmentPreimage = { - value: newCommitment.preimage.value, - salt: newCommitment.preimage.salt, - }; - const witness = computeWitness( publicData, localSiblingPaths.map(siblingPath => siblingPath[0]), - privateObj, + privateData, ); logger.debug(`witness input is ${witness.join(' ')}`); @@ -133,7 +133,7 @@ async function withdraw(withdrawParams) { const optimisticWithdrawTransaction = new Transaction({ fee, historicRootBlockNumberL2: blockNumberL2s, - commitments: newCommitment ? [newCommitment] : [{ hash: 0 }, { hash: 0 }], + commitments: newCommitment.length > 0 ? newCommitment : [{ hash: 0 }, { hash: 0 }], transactionType: 2, tokenType: items.tokenType, tokenId, diff --git a/nightfall-client/src/utils/compute-witness.mjs b/nightfall-client/src/utils/compute-witness.mjs index 5596f7548..0ccbd43b9 100644 --- a/nightfall-client/src/utils/compute-witness.mjs +++ b/nightfall-client/src/utils/compute-witness.mjs @@ -18,8 +18,9 @@ const padArray = (arr, padWith, n) => { return generalise(arr); }; -const computeWitnessPublic = (tx, rootArray) => { +const computeWitnessPublic = (tx, rootsNullifiers) => { const transaction = generalise(tx); + const roots = padArray(generalise(rootsNullifiers), 0, 2); const publicWitness = [ transaction.value.field(BN128_GROUP_ORDER), transaction.historicRootBlockNumberL2.map(h => h.field(BN128_GROUP_ORDER)), @@ -31,52 +32,52 @@ const computeWitnessPublic = (tx, rootArray) => { transaction.commitments.map(c => c.field(BN128_GROUP_ORDER)), transaction.nullifiers.map(n => n.field(BN128_GROUP_ORDER)), transaction.compressedSecrets.map(cs => cs.field(BN128_GROUP_ORDER)), - ]; - if (rootArray.length !== 0) { - const roots = padArray(generalise(rootArray), 0, 2); - publicWitness.push(roots.map(r => r.field(BN128_GROUP_ORDER))); - } - return publicWitness.flat(Infinity); + roots.map(r => r.field(BN128_GROUP_ORDER)), + ].flat(Infinity); + return publicWitness; +}; + +const computeWitnessEncryption = privateData => { + const { ephemeralKey, ercAddress, tokenId } = generalise(privateData); + return [ + ephemeralKey.limbs(32, 8), + ercAddress.field(BN128_GROUP_ORDER), + tokenId.limbs(32, 8), + ].flat(Infinity); }; -const computeWitnessPrivateTransfer = privateObj => { - const { - rootKey, - oldCommitmentPreimage, - paths, - orders, - newCommitmentPreimage, - recipientPublicKeys, - ercAddress, - tokenId, - ephemeralKey, - } = generalise(privateObj); +const computeWitnessNullifiers = privateData => { + const { oldCommitmentPreimage, paths, orders, rootKey } = generalise(privateData); const paddedOldCommitmentPreimage = padArray(oldCommitmentPreimage, NULL_COMMITMENT, 2); - const paddedNewCommitmentPreimage = padArray(newCommitmentPreimage, NULL_COMMITMENT, 2); const paddedPaths = padArray(paths, new Array(32).fill(0), 2); const paddedOrders = padArray(orders, 0, 2); const paddedRootKeys = padArray(rootKey, 0, 2); - const paddedRecipientPublicKeys = padArray(recipientPublicKeys, [0, 0], 2); + return [ - paddedOldCommitmentPreimage.map(r => r.value.limbs(8, 31)), - paddedOldCommitmentPreimage.map(r => r.salt.field(BN128_GROUP_ORDER)), + paddedOldCommitmentPreimage.map(commitment => commitment.value.limbs(8, 31)), + paddedOldCommitmentPreimage.map(commitment => commitment.salt.field(BN128_GROUP_ORDER)), paddedRootKeys.map(r => r.field(BN128_GROUP_ORDER)), paddedPaths.map(ps => ps.map(p => p.field(BN128_GROUP_ORDER))), paddedOrders.map(m => m.field(BN128_GROUP_ORDER)), - paddedNewCommitmentPreimage.map(r => r.value.limbs(8, 31)), - paddedNewCommitmentPreimage.map(r => r.salt.field(BN128_GROUP_ORDER)), + ].flat(Infinity); +}; + +const computeWitnessCommitments = privateData => { + const { newCommitmentPreimage, recipientPublicKeys } = generalise(privateData); + const paddedNewCommitmentPreimage = padArray(newCommitmentPreimage, NULL_COMMITMENT, 2); + const paddedRecipientPublicKeys = padArray(recipientPublicKeys, [0, 0], 2); + return [ + paddedNewCommitmentPreimage.map(commitment => commitment.value.limbs(8, 31)), + paddedNewCommitmentPreimage.map(commitment => commitment.salt.field(BN128_GROUP_ORDER)), paddedRecipientPublicKeys.map(rcp => [ rcp[0].field(BN128_GROUP_ORDER), rcp[1].field(BN128_GROUP_ORDER), ]), - ephemeralKey.limbs(32, 8), - ercAddress.field(BN128_GROUP_ORDER), - tokenId.limbs(32, 8), ].flat(Infinity); }; -const computeWitnessPrivateDeposit = privateObj => { - const { salt, recipientPublicKeys } = generalise(privateObj); +const computeWitnessPrivateDeposit = privateData => { + const { salt, recipientPublicKeys } = generalise(privateData); return [ salt.field(BN128_GROUP_ORDER), recipientPublicKeys.map(rcp => [ @@ -86,34 +87,24 @@ const computeWitnessPrivateDeposit = privateObj => { ].flat(Infinity); }; -const computeWitnessPrivateWithdraw = privateObj => { - const { rootKey, oldCommitmentPreimage, paths, orders } = generalise(privateObj); - const paddedOldCommitmentPreimage = padArray(oldCommitmentPreimage, NULL_COMMITMENT, 2); - const paddedPaths = padArray(paths, new Array(32).fill(0), 2); - const paddedOrders = padArray(orders, 0, 2); - const paddedRootKeys = padArray(rootKey, 0, 2); - const paddedNewCommitmentPreimage = - generalise(privateObj.newCommitmentPreimage) || generalise(NULL_COMMITMENT); - return [ - paddedOldCommitmentPreimage.map(r => r.value.limbs(8, 31)), - paddedOldCommitmentPreimage.map(r => r.salt.field(BN128_GROUP_ORDER)), - paddedRootKeys.map(r => r.field(BN128_GROUP_ORDER)), - paddedPaths.map(ps => ps.map(p => p.field(BN128_GROUP_ORDER))), - paddedOrders.map(m => m.field(BN128_GROUP_ORDER)), - paddedNewCommitmentPreimage.value.limbs(8, 31), - paddedNewCommitmentPreimage.salt.field(BN128_GROUP_ORDER), - ].flat(Infinity); -}; - // eslint-disable-next-line import/prefer-default-export -export const computeWitness = (txObject, rootArray, privateObj) => { - const publicWitness = computeWitnessPublic(txObject, rootArray); +export const computeWitness = (txObject, rootsNullifiers, privateData) => { + const publicWitness = computeWitnessPublic(txObject, rootsNullifiers); switch (Number(txObject.transactionType)) { case 0: - return [...publicWitness, ...computeWitnessPrivateDeposit(privateObj)]; + return [...publicWitness, ...computeWitnessPrivateDeposit(privateData)]; case 1: - return [...publicWitness, ...computeWitnessPrivateTransfer(privateObj)]; + return [ + ...publicWitness, + ...computeWitnessNullifiers(privateData), + ...computeWitnessCommitments(privateData), + ...computeWitnessEncryption(privateData), + ]; default: - return [...publicWitness, ...computeWitnessPrivateWithdraw(privateObj)]; + return [ + ...publicWitness, + ...computeWitnessNullifiers(privateData), + ...computeWitnessCommitments(privateData), + ]; } }; diff --git a/nightfall-deployer/circuits/common/generic_circuit/Stubs/encryption_stub.zok b/nightfall-deployer/circuits/common/generic_circuit/Stubs/encryption_stub.zok index 5f3a3526e..87d0103be 100644 --- a/nightfall-deployer/circuits/common/generic_circuit/Stubs/encryption_stub.zok +++ b/nightfall-deployer/circuits/common/generic_circuit/Stubs/encryption_stub.zok @@ -1,18 +1,16 @@ from "../../utils/calculations.zok" import sum from "utils/pack/u32/pack256.zok" import main as u32_8_to_field -def main(\ - private u32[Transfer][8] ephemeralKey,\ - private field[Transfer] ercAddressTransfer,\ - private u32[Transfer][8] idTransfer\ +def main(\ + private u32[8] ephemeralKey,\ + private field ercAddressTransfer,\ + private u32[8] idTransfer\ )-> (bool): - for u32 i in 0..Transfer do - field s = ercAddressTransfer[i] * sum([\ - u32_8_to_field(ephemeralKey[i]),\ - u32_8_to_field(idTransfer[i])\ - ]) - assert(s == s) - endfor + field s = ercAddressTransfer * sum([\ + u32_8_to_field(ephemeralKey),\ + u32_8_to_field(idTransfer)\ + ]) + assert(s == s) return true diff --git a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok index 6228655ca..532d15a8a 100644 --- a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok +++ b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok @@ -4,33 +4,29 @@ from "../../utils/structures.zok" import Point /* * Verify that all commitments are correct */ -def main(\ +def main(\ field packedErcAddress,\ field idRemainder,\ - field[2] commitmentHashes,\ - field[2] changeZkpPublicKey,\ + field[NumCommitments] commitmentHashes,\ private field[NumCommitments] newCommitmentValues,\ private field[NumCommitments] newCommitmentSalts,\ private Point[NumCommitments] recipientPublicKey\ ) -> bool: - //Check that all the nullifiers are valid. If NumNullifiers equals zero this loop will be ignored + + //Check that all the commitments are valid. If NumCommitments equals zero this loop will be ignored for u32 i in 0..NumCommitments do //Calculate the commmitment hash from the newCommitment parameters - field commitment = if newCommitmentValues[i] == 0 then 0 else \ - poseidon([\ - packedErcAddress,\ - idRemainder,\ - newCommitmentValues[i],\ - ...recipientPublicKey[i],\ - newCommitmentSalts[i]\ - ])\ - fi + field commitment = poseidon([\ + packedErcAddress,\ + idRemainder,\ + newCommitmentValues[i],\ + ...recipientPublicKey[i],\ + newCommitmentSalts[i]\ + ]) //Check that the calculated commitment matches with the one contained in the transaction - assert(commitment == commitmentHashes[i]) - - //If there is change, we need to check that the recipient public keys of the last commitment - //matches with the change public keys - endfor - return true \ No newline at end of file + assert(newCommitmentValues[i] == 0 || commitment == commitmentHashes[i]) + endfor + + return true diff --git a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_encryption.zok b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_encryption.zok index 82d2e4283..6a4861a9f 100644 --- a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_encryption.zok +++ b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_encryption.zok @@ -8,15 +8,15 @@ from "../../utils/structures.zok" import Point /* * Verify that the secrets are encrypted properly in the Transfer */ -def main(\ +def main(\ field ercAddress,\ u32[8] tokenId,\ field[2] compressedSecrets,\ field packedErcAddress,\ field idRemainder,\ - private field[NumCommitments] newCommitmentValues,\ - private field[NumCommitments] newCommitmentSalts,\ - private Point[NumCommitments] recipientPublicKey,\ + private field newCommitmentValues,\ + private field newCommitmentSalts,\ + private Point recipientPublicKey,\ u32[8] recipientAddress,\ private u32[8] ephemeralKey\ ) -> bool: @@ -27,13 +27,13 @@ def main(\ field[4] plainTexts = [\ packedErcAddress,\ idRemainder,\ - newCommitmentValues[0],\ - newCommitmentSalts[0]\ + newCommitmentValues,\ + newCommitmentSalts\ ] - EncryptedMsgs<4> enc = kemDem(bitEphemeralKey, [recipientPublicKey[0][0], recipientPublicKey[0][1]], plainTexts) + EncryptedMsgs<4> enc = kemDem(bitEphemeralKey, [recipientPublicKey[0], recipientPublicKey[1]], plainTexts) assert(cipherText == enc.cipherText) bool[256] compressedPubKeyOutput = edwardsCompress(enc.ephemeralPublicKey) assert(compressedPubKeyOutput == u32_8_to_bool_256(recipientAddress)) - return true \ No newline at end of file + return true diff --git a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok index 788485ac4..d593b8387 100644 --- a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok +++ b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok @@ -45,17 +45,16 @@ def main(\ ...zkpPublicKeys,\ oldCommitmentSalts[i]\ ]) - field nullifier = if(oldCommitmentValues[i] == 0) then 0 else poseidon([nullifierKeys, calculatedOldCommitmentHash]) fi + field nullifier = poseidon([nullifierKeys, calculatedOldCommitmentHash]) //Check that the calculated nullifier matches with the one contained in the transaction - assert(nullifier == nullifierHashes[i]) + assert(oldCommitmentValues[i] == 0 || nullifier == nullifierHashes[i]) //Check that the nullifier is contained in the tree - bool pathValidity = if(oldCommitmentValues[i] == 0) then true else pathCheck([roots[i], ...paths[i]], orders[i], calculatedOldCommitmentHash) fi - assert(pathValidity) + assert(oldCommitmentValues[i] == 0 || pathCheck([roots[i], ...paths[i]], orders[i], calculatedOldCommitmentHash)) //Set the changeZkpPublicKeys if i = 0. Otherwise just set the same value firstInputZkpPublicKeys = i == 0 ? zkpPublicKeys : firstInputZkpPublicKeys endfor - return firstInputZkpPublicKeys \ No newline at end of file + return firstInputZkpPublicKeys diff --git a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_structure.zok b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_structure.zok index b536518cd..8977c2956 100644 --- a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_structure.zok +++ b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_structure.zok @@ -1,10 +1,11 @@ from "../../casts/u32_array_to_field.zok" import main as u32_array_to_field +from "utils/casts/u32_to_field.zok" import main as u32_to_field from "utils/pack/u32/nonStrictUnpack256.zok" import main as field_to_u32_8 /* * Given a Public tx, check that the structure and all the public parameters are valid */ -def main(\ +def main(\ field value,\ field transactionType,\ field tokenType,\ @@ -17,14 +18,8 @@ def main(\ field[2] compressedSecrets\ ) -> (bool): - //Check that transaction type matches - // assert(TxType == transactionType) - - //Deposits must have at least a commitment, transfers must at least have one commitment and one nullifier - //and withdrawal must have at least a nullifier - assert((TxType == 0 && NumCommitments != 0)\ - || (TxType == 1 && NumCommitments != 0 && NumNullifiers != 0)\ - || (TxType == 2 && NumNullifiers != 0)) + //Check that transaction type matches + assert(u32_to_field(TxType) == transactionType) //ErcAddress cannot be zero. In transfer will contain the encrypted version of the ercAddress belonging to the ciphertext assert(ercAddress != 0) @@ -47,6 +42,7 @@ def main(\ || (tokenType == 1 && value == 0)\ || (tokenType == 2 && value != 0))) + //Check commitments assert((TxType == 0 && commitments[0] != 0 && commitments[1] == 0) ||\ (TxType == 1 && commitments[0] != 0 && commitments[0] != commitments[1]) ||\ (TxType == 2 && commitments[1] == 0)) @@ -64,4 +60,4 @@ def main(\ assert((TxType == 1 && (compressedSecrets[0] != 0 || compressedSecrets[1] != 0))\ || (TxType != 1 && compressedSecrets[0] == 0 && compressedSecrets[1] == 0)) - return true \ No newline at end of file + return true diff --git a/nightfall-deployer/circuits/common/hashes/mimc/mimc-constants.zok b/nightfall-deployer/circuits/common/hashes/mimc/mimc-constants.zok deleted file mode 100644 index 799dfb3c6..000000000 --- a/nightfall-deployer/circuits/common/hashes/mimc/mimc-constants.zok +++ /dev/null @@ -1,94 +0,0 @@ -def main()->(field[91]): - return [ - 20888961410941983456478427210666206549300505294776164667214940546594746570981, - 15265126113435022738560151911929040668591755459209400716467504685752745317193, - 8334177627492981984476504167502758309043212251641796197711684499645635709656, - 1374324219480165500871639364801692115397519265181803854177629327624133579404, - 11442588683664344394633565859260176446561886575962616332903193988751292992472, - 2558901189096558760448896669327086721003508630712968559048179091037845349145, - 11189978595292752354820141775598510151189959177917284797737745690127318076389, - 3262966573163560839685415914157855077211340576201936620532175028036746741754, - 17029914891543225301403832095880481731551830725367286980611178737703889171730, - 4614037031668406927330683909387957156531244689520944789503628527855167665518, - 19647356996769918391113967168615123299113119185942498194367262335168397100658, - 5040699236106090655289931820723926657076483236860546282406111821875672148900, - 2632385916954580941368956176626336146806721642583847728103570779270161510514, - 17691411851977575435597871505860208507285462834710151833948561098560743654671, - 11482807709115676646560379017491661435505951727793345550942389701970904563183, - 8360838254132998143349158726141014535383109403565779450210746881879715734773, - 12663821244032248511491386323242575231591777785787269938928497649288048289525, - 3067001377342968891237590775929219083706800062321980129409398033259904188058, - 8536471869378957766675292398190944925664113548202769136103887479787957959589, - 19825444354178182240559170937204690272111734703605805530888940813160705385792, - 16703465144013840124940690347975638755097486902749048533167980887413919317592, - 13061236261277650370863439564453267964462486225679643020432589226741411380501, - 10864774797625152707517901967943775867717907803542223029967000416969007792571, - 10035653564014594269791753415727486340557376923045841607746250017541686319774, - 3446968588058668564420958894889124905706353937375068998436129414772610003289, - 4653317306466493184743870159523234588955994456998076243468148492375236846006, - 8486711143589723036499933521576871883500223198263343024003617825616410932026, - 250710584458582618659378487568129931785810765264752039738223488321597070280, - 2104159799604932521291371026105311735948154964200596636974609406977292675173, - 16313562605837709339799839901240652934758303521543693857533755376563489378839, - 6032365105133504724925793806318578936233045029919447519826248813478479197288, - 14025118133847866722315446277964222215118620050302054655768867040006542798474, - 7400123822125662712777833064081316757896757785777291653271747396958201309118, - 1744432620323851751204287974553233986555641872755053103823939564833813704825, - 8316378125659383262515151597439205374263247719876250938893842106722210729522, - 6739722627047123650704294650168547689199576889424317598327664349670094847386, - 21211457866117465531949733809706514799713333930924902519246949506964470524162, - 13718112532745211817410303291774369209520657938741992779396229864894885156527, - 5264534817993325015357427094323255342713527811596856940387954546330728068658, - 18884137497114307927425084003812022333609937761793387700010402412840002189451, - 5148596049900083984813839872929010525572543381981952060869301611018636120248, - 19799686398774806587970184652860783461860993790013219899147141137827718662674, - 19240878651604412704364448729659032944342952609050243268894572835672205984837, - 10546185249390392695582524554167530669949955276893453512788278945742408153192, - 5507959600969845538113649209272736011390582494851145043668969080335346810411, - 18177751737739153338153217698774510185696788019377850245260475034576050820091, - 19603444733183990109492724100282114612026332366576932662794133334264283907557, - 10548274686824425401349248282213580046351514091431715597441736281987273193140, - 1823201861560942974198127384034483127920205835821334101215923769688644479957, - 11867589662193422187545516240823411225342068709600734253659804646934346124945, - 18718569356736340558616379408444812528964066420519677106145092918482774343613, - 10530777752259630125564678480897857853807637120039176813174150229243735996839, - 20486583726592018813337145844457018474256372770211860618687961310422228379031, - 12690713110714036569415168795200156516217175005650145422920562694422306200486, - 17386427286863519095301372413760745749282643730629659997153085139065756667205, - 2216432659854733047132347621569505613620980842043977268828076165669557467682, - 6309765381643925252238633914530877025934201680691496500372265330505506717193, - 20806323192073945401862788605803131761175139076694468214027227878952047793390, - 4037040458505567977365391535756875199663510397600316887746139396052445718861, - 19948974083684238245321361840704327952464170097132407924861169241740046562673, - 845322671528508199439318170916419179535949348988022948153107378280175750024, - 16222384601744433420585982239113457177459602187868460608565289920306145389382, - 10232118865851112229330353999139005145127746617219324244541194256766741433339, - 6699067738555349409504843460654299019000594109597429103342076743347235369120, - 6220784880752427143725783746407285094967584864656399181815603544365010379208, - 6129250029437675212264306655559561251995722990149771051304736001195288083309, - 10773245783118750721454994239248013870822765715268323522295722350908043393604, - 4490242021765793917495398271905043433053432245571325177153467194570741607167, - 19596995117319480189066041930051006586888908165330319666010398892494684778526, - 837850695495734270707668553360118467905109360511302468085569220634750561083, - 11803922811376367215191737026157445294481406304781326649717082177394185903907, - 10201298324909697255105265958780781450978049256931478989759448189112393506592, - 13564695482314888817576351063608519127702411536552857463682060761575100923924, - 9262808208636973454201420823766139682381973240743541030659775288508921362724, - 173271062536305557219323722062711383294158572562695717740068656098441040230, - 18120430890549410286417591505529104700901943324772175772035648111937818237369, - 20484495168135072493552514219686101965206843697794133766912991150184337935627, - 19155651295705203459475805213866664350848604323501251939850063308319753686505, - 11971299749478202793661982361798418342615500543489781306376058267926437157297, - 18285310723116790056148596536349375622245669010373674803854111592441823052978, - 7069216248902547653615508023941692395371990416048967468982099270925308100727, - 6465151453746412132599596984628739550147379072443683076388208843341824127379, - 16143532858389170960690347742477978826830511669766530042104134302796355145785, - 19362583304414853660976404410208489566967618125972377176980367224623492419647, - 1702213613534733786921602839210290505213503664731919006932367875629005980493, - 10781825404476535814285389902565833897646945212027592373510689209734812292327, - 4212716923652881254737947578600828255798948993302968210248673545442808456151, - 7594017890037021425366623750593200398174488805473151513558919864633711506220, - 18979889247746272055963929241596362599320706910852082477600815822482192194401, - 13602139229813231349386885113156901793661719180900395818909719758150455500533, - 13952667105157556595308191233585255581771936717523666104281454907150877850313 -] diff --git a/nightfall-deployer/circuits/common/hashes/mimc/mimc-encryption.zok b/nightfall-deployer/circuits/common/hashes/mimc/mimc-encryption.zok deleted file mode 100644 index 367d5490e..000000000 --- a/nightfall-deployer/circuits/common/hashes/mimc/mimc-encryption.zok +++ /dev/null @@ -1,12 +0,0 @@ -// the mimc encryption function, used as the basis of hashing rounds -// encryption function - -import "./mimc-constants.zok" as constants - -def main(field x, field k)->(field): - field[91] c = constants() - for u32 i in 0..91 do - field t = x + c[i] + k - x = t**7 // t^7 because 7th power is bijective in this field - endfor - return x + k diff --git a/nightfall-deployer/circuits/common/hashes/mimc/mimc-hash-1.zok b/nightfall-deployer/circuits/common/hashes/mimc/mimc-hash-1.zok deleted file mode 100644 index 073ac39c3..000000000 --- a/nightfall-deployer/circuits/common/hashes/mimc/mimc-hash-1.zok +++ /dev/null @@ -1,8 +0,0 @@ -// MiMC hashing function for five input fields - -import "./mimc-encryption.zok" as mimcpe7 - -def main(field a)->(field): - field r = 0 - r = a + mimcpe7(a, r) - return r diff --git a/nightfall-deployer/circuits/common/hashes/mimc/mimc-hash-2.zok b/nightfall-deployer/circuits/common/hashes/mimc/mimc-hash-2.zok deleted file mode 100644 index b4da97670..000000000 --- a/nightfall-deployer/circuits/common/hashes/mimc/mimc-hash-2.zok +++ /dev/null @@ -1,10 +0,0 @@ -// MiMC hashing function for five input fields - -import "./mimc-encryption.zok" as mimcpe7 - -def main(field[2] a)->(field): - field r = 0 - for u32 i in 0..2 do - r = r + a[i] + mimcpe7(a[i], r) - endfor - return r diff --git a/nightfall-deployer/circuits/common/hashes/mimc/mimc-hash-5.zok b/nightfall-deployer/circuits/common/hashes/mimc/mimc-hash-5.zok deleted file mode 100644 index e5be18a71..000000000 --- a/nightfall-deployer/circuits/common/hashes/mimc/mimc-hash-5.zok +++ /dev/null @@ -1,10 +0,0 @@ -// MiMC hashing function for five input fields - -import "./mimc-encryption.zok" as mimcpe7 - -def main(field[5] a)->(field): - field r = 0 - for u32 i in 0..5 do - r = r + a[i] + mimcpe7(a[i], r) - endfor - return r diff --git a/nightfall-deployer/circuits/common/hashes/mimc/mimc-hash-N.zok b/nightfall-deployer/circuits/common/hashes/mimc/mimc-hash-N.zok deleted file mode 100644 index e95bf3bfa..000000000 --- a/nightfall-deployer/circuits/common/hashes/mimc/mimc-hash-N.zok +++ /dev/null @@ -1,10 +0,0 @@ -// MiMC hashing function for N input fields - -import "./mimc-encryption.zok" as mimcpe7 - -def main(field[N] a)->(field): - field r = 0 - for u32 i in 0..N do - r = r + a[i] + mimcpe7(a[i], r) - endfor - return r diff --git a/nightfall-deployer/circuits/common/utils/structures.zok b/nightfall-deployer/circuits/common/utils/structures.zok index 9c773fa37..761641991 100644 --- a/nightfall-deployer/circuits/common/utils/structures.zok +++ b/nightfall-deployer/circuits/common/utils/structures.zok @@ -30,3 +30,20 @@ struct PublicTransaction { field[2] compressedSecrets } +struct Nullifiers { + CommitmentPreimage oldCommitments + field[N] rootKey + field[N][32] paths + field[N] orders +} + +struct Commitments { + CommitmentPreimage newCommitments + Point[N] recipientPublicKey +} + +struct Transfer { + u32[8] ephemeralKey + field ercAddressTransfer + u32[8] idTransfer +} diff --git a/nightfall-deployer/circuits/deposit.zok b/nightfall-deployer/circuits/deposit.zok index 5f8204373..22c69b9b1 100644 --- a/nightfall-deployer/circuits/deposit.zok +++ b/nightfall-deployer/circuits/deposit.zok @@ -1,21 +1,20 @@ -from "./common/utils/structures.zok" import CommitmentPreimage,Point, PublicTransaction, SHIFT -from "./common/generic_circuit/Verifiers/verify_commitments.zok" import main as verify_commitments -from "./common/generic_circuit/Verifiers/verify_structure.zok" import main as verify_structure -from "./common/casts/u32_array_to_field.zok" import main as u32_array_to_field -const u32 txType = 0 -const u32 numCommitments = 1 -const u32 numNullifiers = 0 -const u32 transfer = 0 +from "./common/utils/structures.zok" import Point, PublicTransaction, Nullifiers, Commitments, Transfer, SHIFT +from "./common/utils/calculations.zok" import sum +from "./common/casts/u32_array_to_field.zok" import main as u32_array_to_field +from "./common/casts/u8_array_to_field.zok" import main as u8_array_to_field +from "./common/generic_circuit/Verifiers/verify_structure.zok" import main as verify_structure +from "./common/generic_circuit/Verifiers/verify_commitments.zok" import main as verify_commitments def main(\ PublicTransaction tx,\ - private field salt,\ - private Point[numCommitments] recipientPublicKey\ - ) -> (): - + field[2] nullifierRoots,\ + private field salt,\ + private Point recipientPublicKey\ +)-> (): + //Verify public transaction structure - assert(verify_structure::(\ + assert(verify_structure::<0>(\ tx.value,\ tx.transactionType,\ tx.tokenType,\ @@ -34,15 +33,7 @@ def main(\ field packedErcAddress = tx.ercAddress + u32_array_to_field([tx.tokenId[0]]) * SHIFT //Verify new Commmitments - Point firstInputZkpPublicKeys = [0,0] - assert(verify_commitments::(\ - packedErcAddress,\ - idRemainder,\ - tx.commitments,\ - firstInputZkpPublicKeys,\ - [tx.value],\ - [salt],\ - recipientPublicKey\ - )) - + assert(verify_commitments::<1>(packedErcAddress, idRemainder, [tx.commitments[0]],\ + [tx.value], [salt], [recipientPublicKey])) + return diff --git a/nightfall-deployer/circuits/deposit_stub.zok b/nightfall-deployer/circuits/deposit_stub.zok index 290a5e5c7..4b5a4f0be 100644 --- a/nightfall-deployer/circuits/deposit_stub.zok +++ b/nightfall-deployer/circuits/deposit_stub.zok @@ -1,35 +1,16 @@ -from "./common/utils/structures.zok" import CommitmentPreimage,Point, PublicTransaction -from "./common/generic_circuit/Stubs/commitments_stub.zok" import main as commitment_stub -from "./common/generic_circuit/Stubs/nullifiers_stub.zok" import main as nullifiers_stub -from "./common/generic_circuit/Stubs/encryption_stub.zok" import main as encryption_stub -from "./common/generic_circuit/Verifiers/verify_structure.zok" import main as verify_structure -const u32 txType = 0 -const u32 numCommitments = 1 -const u32 numNullifiers = 0 -const u32 transfer = 0 +from "./common/utils/structures.zok" import PublicTransaction, Commitments, Nullifiers +from "./common/generic_circuit/Stubs/commitments_stub.zok" import main as commitment_stub +from "./common/generic_circuit/Stubs/nullifiers_stub.zok" import main as nullifier_stub +from "./common/casts/u8_array_to_field.zok" import main as u8_array_to_field def main(\ PublicTransaction tx,\ - private CommitmentPreimage newCommitment,\ - private Point[numCommitments] recipientPublicKey\ - ) -> (): - - //Verify public transaction structure - assert(verify_structure::<0, 2, 2>(\ - tx.value,\ - tx.transactionType,\ - tx.tokenType,\ - tx.tokenId,\ - tx.ercAddress,\ - tx.recipientAddress,\ - tx.commitments,\ - tx.nullifiers,\ - tx.historicRootBlockNumberL2,\ - tx.compressedSecrets\ - )) - - assert(commitment_stub::(\ - newCommitment.value, newCommitment.salt, recipientPublicKey)) + field[2] nullifierRoots,\ + private field salt,\ + private field[2] recipientPublicKey\ +)-> (): + assert(commitment_stub::<1>([tx.value], [salt], [recipientPublicKey])) + return diff --git a/nightfall-deployer/circuits/transfer.zok b/nightfall-deployer/circuits/transfer.zok index ee8163de4..03e3177ed 100644 --- a/nightfall-deployer/circuits/transfer.zok +++ b/nightfall-deployer/circuits/transfer.zok @@ -1,33 +1,24 @@ -from "./common/utils/structures.zok" import CommitmentPreimage,Point, PublicTransaction, SHIFT -from "./common/generic_circuit/Verifiers/verify_commitments.zok" import main as verify_commitments + +from "./common/utils/structures.zok" import Point, PublicTransaction, Nullifiers, Commitments, Transfer, SHIFT +from "./common/utils/calculations.zok" import sum +from "./common/casts/u32_array_to_field.zok" import main as u32_array_to_field +from "./common/casts/u8_array_to_field.zok" import main as u8_array_to_field from "./common/generic_circuit/Verifiers/verify_structure.zok" import main as verify_structure from "./common/generic_circuit/Verifiers/verify_encryption.zok" import main as verify_encryption from "./common/generic_circuit/Verifiers/verify_nullifiers.zok" import main as verify_nullifiers -from "./common/casts/u32_array_to_field.zok" import main as u32_array_to_field -from "./common/utils/calculations.zok" import sum -from "./common/casts/u8_array_to_field.zok" import main as u8_array_to_field - -const u32 txType = 1 -const u32 numCommitments = 2 -const u32 numNullifiers = 2 -const u32 transfer = 1 - +from "./common/generic_circuit/Verifiers/verify_commitments.zok" import main as verify_commitments def main(\ PublicTransaction tx,\ - field[numNullifiers] roots,\ - private CommitmentPreimage oldCommitment,\ - private field[numNullifiers] rootKey,\ - private field[numNullifiers][32] paths,\ - private field[numNullifiers] orders,\ - private CommitmentPreimage newCommitment,\ - private Point[numCommitments] recipientPublicKey,\ - private u32[8] ephemeralKey,\ - private field ercAddressTransfer,\ - private u32[8] tokenId\ + field[2] nullifierRoots,\ + private Nullifiers<2> nullifiers,\ + private Commitments<2> commitments,\ + private Transfer transfer\ )-> (): + + //Verify public transaction structure - assert(verify_structure::(\ + assert(verify_structure::<1>(\ tx.value,\ tx.transactionType,\ tx.tokenType,\ @@ -40,59 +31,32 @@ def main(\ tx.compressedSecrets\ )) + field[2] nullifiersValue = u8_array_to_field(nullifiers.oldCommitments.value) + field[2] commitmentsValue = u8_array_to_field(commitments.newCommitments.value) + //Check that values match - assert(\ - sum(u8_array_to_field(oldCommitment.value)) ==\ - sum(u8_array_to_field(newCommitment.value))\ - ) + assert(sum(nullifiersValue) == sum(commitmentsValue)) // pack the top four bytes of the token id into the ercAddress field (address only // uses 160 bits and the Shield contract prevents creation of something with more than 160 bits) - field idRemainder = u32_array_to_field(tokenId[1..8]) - field packedErcAddress = ercAddressTransfer + u32_array_to_field([tokenId[0]]) * SHIFT + field idRemainder = u32_array_to_field(transfer.idTransfer[1..8]) + field packedErcAddress = transfer.ercAddressTransfer + u32_array_to_field([transfer.idTransfer[0]]) * SHIFT //Verify nullifiers - Point firstInputZkpPublicKeys = [0,0] - firstInputZkpPublicKeys = verify_nullifiers::(\ - packedErcAddress,\ - idRemainder,\ - tx.nullifiers,\ - roots,\ - u8_array_to_field(oldCommitment.value),\ - oldCommitment.salt,\ - rootKey,\ - paths,\ - orders\ - ) - - assert(\ - u8_array_to_field([newCommitment.value[numCommitments - 1]])[0] == 0 || \ - firstInputZkpPublicKeys == recipientPublicKey[numCommitments - 1]\ - ) - - //Verify new Commmitments - assert(verify_commitments::(\ - packedErcAddress,\ - idRemainder,\ - tx.commitments,\ - firstInputZkpPublicKeys,\ - u8_array_to_field(newCommitment.value),\ - newCommitment.salt,\ - recipientPublicKey\ - )) - + Point firstInputZkpPublicKeys = verify_nullifiers::<2>(packedErcAddress, idRemainder,\ + tx.nullifiers, nullifierRoots, nullifiersValue, nullifiers.oldCommitments.salt,\ + nullifiers.rootKey, nullifiers.paths, nullifiers.orders) + + //Verify new Commmitments + assert(verify_commitments::<2>(packedErcAddress, idRemainder, tx.commitments,\ + commitmentsValue, commitments.newCommitments.salt, commitments.recipientPublicKey)) + + //Verify Change + assert(commitmentsValue[1] == 0 || firstInputZkpPublicKeys == commitments.recipientPublicKey[1]) + //Verify Kem Dem encryption - assert(verify_encryption::(\ - tx.ercAddress,\ - tx.tokenId,\ - tx.compressedSecrets,\ - packedErcAddress,\ - idRemainder,\ - u8_array_to_field(newCommitment.value),\ - newCommitment.salt,\ - recipientPublicKey,\ - tx.recipientAddress,\ - ephemeralKey\ - )) + assert(verify_encryption(tx.ercAddress,tx.tokenId, tx.compressedSecrets,\ + packedErcAddress,idRemainder,commitmentsValue[0],commitments.newCommitments.salt[0],\ + commitments.recipientPublicKey[0],tx.recipientAddress, transfer.ephemeralKey)) - return \ No newline at end of file + return diff --git a/nightfall-deployer/circuits/transfer_stub.zok b/nightfall-deployer/circuits/transfer_stub.zok new file mode 100644 index 000000000..e5fc2e681 --- /dev/null +++ b/nightfall-deployer/circuits/transfer_stub.zok @@ -0,0 +1,27 @@ +from "./common/utils/structures.zok" import Point, PublicTransaction, Nullifiers, Commitments, Transfer, SHIFT +from "./common/generic_circuit/Stubs/nullifiers_stub.zok" import main as nullifier_stub +from "./common/generic_circuit/Stubs/commitments_stub.zok" import main as commitment_stub +from "./common/generic_circuit/Stubs/encryption_stub.zok" import main as encryption_stub +from "./common/casts/u8_array_to_field.zok" import main as u8_array_to_field + + +def main(\ + PublicTransaction tx,\ + field[2] nullifierRoots,\ + private Nullifiers<2> nullifiers,\ + private Commitments<2> commitments,\ + private Transfer transfer\ +)-> (): + + field[2] nullifiersValue = u8_array_to_field(nullifiers.oldCommitments.value) + field[2] commitmentsValue = u8_array_to_field(commitments.newCommitments.value) + + assert(nullifier_stub::<2>(\ + nullifierRoots, nullifiersValue, nullifiers.oldCommitments.salt,\ + nullifiers.rootKey, nullifiers.paths, nullifiers.orders)) + + assert(commitment_stub::<2>(\ + commitmentsValue, commitments.newCommitments.salt, commitments.recipientPublicKey)) + + assert(encryption_stub(transfer.ephemeralKey, transfer.ercAddressTransfer, transfer.idTransfer)) + return diff --git a/nightfall-deployer/circuits/withdraw.zok b/nightfall-deployer/circuits/withdraw.zok index 261cf5f23..78f077a80 100644 --- a/nightfall-deployer/circuits/withdraw.zok +++ b/nightfall-deployer/circuits/withdraw.zok @@ -1,29 +1,21 @@ -from "./common/utils/structures.zok" import CommitmentPreimage,Point, PublicTransaction, SHIFT -from "./common/generic_circuit/Verifiers/verify_commitments.zok" import main as verify_commitments +from "./common/utils/structures.zok" import Point, PublicTransaction, Nullifiers, Commitments, Transfer, SHIFT +from "./common/utils/calculations.zok" import sum +from "./common/casts/u32_array_to_field.zok" import main as u32_array_to_field +from "./common/casts/u8_array_to_field.zok" import main as u8_array_to_field from "./common/generic_circuit/Verifiers/verify_structure.zok" import main as verify_structure from "./common/generic_circuit/Verifiers/verify_encryption.zok" import main as verify_encryption from "./common/generic_circuit/Verifiers/verify_nullifiers.zok" import main as verify_nullifiers -from "./common/casts/u32_array_to_field.zok" import main as u32_array_to_field -from "./common/utils/calculations.zok" import sum -from "./common/casts/u8_array_to_field.zok" import main as u8_array_to_field - -const u32 txType = 2 -const u32 numCommitments = 1 -const u32 numNullifiers = 2 -const u32 transfer = 1 +from "./common/generic_circuit/Verifiers/verify_commitments.zok" import main as verify_commitments def main(\ PublicTransaction tx,\ - field[numNullifiers] roots,\ - private CommitmentPreimage oldCommitment,\ - private field[numNullifiers] rootKey,\ - private field[numNullifiers][32] paths,\ - private field[numNullifiers] orders,\ - private CommitmentPreimage newCommitment\ - )-> (): - - //Verify public transaction structure - assert(verify_structure::(\ + field[2] nullifierRoots,\ + private Nullifiers<2> nullifiers,\ + private Commitments<2> commitments\ +)-> (): + + //Verify public transaction structure + assert(verify_structure::<2>(\ tx.value,\ tx.transactionType,\ tx.tokenType,\ @@ -36,40 +28,28 @@ def main(\ tx.compressedSecrets\ )) - //Check that values match - assert(\ - sum(u8_array_to_field(oldCommitment.value)) ==\ - sum(u8_array_to_field(newCommitment.value)) + tx.value\ - ) + field[2] nullifiersValue = u8_array_to_field(nullifiers.oldCommitments.value) + field[2] commitmentsValue = u8_array_to_field(commitments.newCommitments.value) + + //Check that values match + assert(sum(nullifiersValue) == sum(commitmentsValue) + tx.value) - // pack the top four bytes of the token id into the ercAddress field (address only + // pack the top four bytes of the token id into the ercAddress field (address only // uses 160 bits and the Shield contract prevents creation of something with more than 160 bits) field idRemainder = u32_array_to_field(tx.tokenId[1..8]) field packedErcAddress = tx.ercAddress + u32_array_to_field([tx.tokenId[0]]) * SHIFT - //Verify nullifiers - Point firstInputZkpPublicKeys = [0,0] - firstInputZkpPublicKeys = verify_nullifiers::(\ - packedErcAddress,\ - idRemainder,\ - tx.nullifiers,\ - roots,\ - u8_array_to_field(oldCommitment.value),\ - oldCommitment.salt,\ - rootKey,\ - paths,\ - orders\ - ) - - //Verify new Commmitments - assert(verify_commitments::(\ - packedErcAddress,\ - idRemainder,\ - tx.commitments,\ - firstInputZkpPublicKeys,\ - u8_array_to_field(newCommitment.value),\ - newCommitment.salt,\ - [firstInputZkpPublicKeys]\ - )) + //Verify nullifiers + Point firstInputZkpPublicKeys = verify_nullifiers::<2>(packedErcAddress, idRemainder,\ + tx.nullifiers, nullifierRoots, nullifiersValue, nullifiers.oldCommitments.salt,\ + nullifiers.rootKey, nullifiers.paths, nullifiers.orders) + + //Verify new Commmitments + assert(verify_commitments::<2>(packedErcAddress, idRemainder, tx.commitments,\ + commitmentsValue, commitments.newCommitments.salt, commitments.recipientPublicKey)) + + //Verify Change + assert(commitmentsValue[0] == 0 || firstInputZkpPublicKeys == commitments.recipientPublicKey[0]) + + return - return diff --git a/nightfall-deployer/circuits/withdraw_stub.zok b/nightfall-deployer/circuits/withdraw_stub.zok index b6b1801d3..10aaf2224 100644 --- a/nightfall-deployer/circuits/withdraw_stub.zok +++ b/nightfall-deployer/circuits/withdraw_stub.zok @@ -1,26 +1,24 @@ -struct OldCommitmentPreimage { - field salt - field hash - field rootKey -} +from "./common/utils/structures.zok" import Point, PublicTransaction, Nullifiers, Commitments, Transfer, SHIFT +from "./common/generic_circuit/Stubs/nullifiers_stub.zok" import main as nullifier_stub +from "./common/generic_circuit/Stubs/commitments_stub.zok" import main as commitment_stub +from "./common/casts/u8_array_to_field.zok" import main as u8_array_to_field + def main(\ - field ercAddress,\ - u32[8] id,\ - field value,\ - private OldCommitmentPreimage oldCommitment,\ - field nullifier,\ - field recipientAddress,\ - field root,\ - private field[32] path,\ - private field order\ -)->(): + PublicTransaction tx,\ + field[2] nullifierRoots,\ + private Nullifiers<2> nullifiers,\ + private Commitments<2> commitments\ +)-> (): + + field[2] nullifiersValue = u8_array_to_field(nullifiers.oldCommitments.value) + field[2] commitmentsValue = u8_array_to_field(commitments.newCommitments.value) + + assert(nullifier_stub::<2>(\ + nullifierRoots, nullifiersValue, nullifiers.oldCommitments.salt,\ + nullifiers.rootKey, nullifiers.paths, nullifiers.orders)) + + assert(commitment_stub::<2>(\ + commitmentsValue, commitments.newCommitments.salt, commitments.recipientPublicKey)) - field u = ercAddress + value + oldCommitment.salt + oldCommitment.hash + oldCommitment.rootKey + nullifier + recipientAddress + root + order - u32 v = id[0] + id[1] + id[2] + id[3] + id[4] + id[5] + id[6] * id[7] - for u32 i in 0..32 do - u = u * path[i] - endfor - assert(u==u) - assert (v==v) - return + return diff --git a/nightfall-deployer/contracts/Challenges.sol b/nightfall-deployer/contracts/Challenges.sol index ce2ec1357..bc8b077d4 100644 --- a/nightfall-deployer/contracts/Challenges.sol +++ b/nightfall-deployer/contracts/Challenges.sol @@ -34,8 +34,7 @@ contract Challenges is Stateful, Key_Registry, Config { Block memory priorBlockL2, // the block immediately prior to this one Transaction[] memory priorBlockTransactions, // the transactions in the prior block Block memory blockL2, - Transaction[] memory transactions, - bytes32 salt + Transaction[] memory transactions ) external onlyBootChallenger { checkCommit(msg.data); // check if the block hash is correct and the block hash exists for the block and prior block. Also if the transactions are part of these block @@ -46,13 +45,6 @@ contract Challenges is Stateful, Key_Registry, Config { priorBlockTransactions, blockL2.leafCount ); - // Now, we have an incorrect leafCount, but Timber relies on the leafCount - // emitted by the rollback event to revert its commitment database, so we - // need to correct the leafCount before we call challengeAccepted(...). - // We'll do that by counting forwards from the prior block. - blockL2.leafCount = - priorBlockL2.leafCount + - uint48(Utils.countCommitments(priorBlockTransactions)); challengeAccepted(blockL2); } @@ -68,8 +60,7 @@ contract Challenges is Stateful, Key_Registry, Config { Transaction[] memory priorBlockTransactions, // the transactions in the prior block bytes32[33] calldata frontierPriorBlock, // frontier path before prior block is added. The same frontier used in calculating root when prior block is added Block memory blockL2, - Transaction[] memory transactions, - bytes32 salt + Transaction[] memory transactions ) external onlyBootChallenger { checkCommit(msg.data); // check if the block hash is correct and the block hash exists for the block and prior block @@ -97,8 +88,7 @@ contract Challenges is Stateful, Key_Registry, Config { Transaction[] memory transactions1, Transaction[] memory transactions2, uint256 transactionIndex1, - uint256 transactionIndex2, - bytes32 salt + uint256 transactionIndex2 ) external onlyBootChallenger { checkCommit(msg.data); // first, check we have real, in-train, contiguous blocks @@ -121,120 +111,55 @@ contract Challenges is Stateful, Key_Registry, Config { } } - function challengeTransactionType( - Block memory blockL2, - Transaction[] memory transactions, - uint256 transactionIndex, - bytes32 salt - ) external onlyBootChallenger { - checkCommit(msg.data); - state.areBlockAndTransactionsReal(blockL2, transactions); - ChallengesUtil.libChallengeTransactionType(transactions[transactionIndex]); - // Delete the latest block of the two - challengeAccepted(blockL2); - } - - // signature for deposit: function challengeProofVerification( Block memory blockL2, Transaction[] calldata transactions, uint256 transactionIndex, - uint256[8] memory uncompressedProof, - bytes32 salt + Block[2] calldata blockL2ContainingHistoricRoot, + Transaction[][2] memory transactionsOfblockL2ContainingHistoricRoot, + uint256[8] memory uncompressedProof ) external onlyBootChallenger { checkCommit(msg.data); state.areBlockAndTransactionsReal(blockL2, transactions); - // first check the transaction and block do not overflow - ChallengesUtil.libCheckOverflows(blockL2, transactions[transactionIndex]); - // now we need to check that the proof is correct - ChallengesUtil.libCheckCompressedProof( - transactions[transactionIndex].proof, - uncompressedProof - ); - ChallengesUtil.libChallengeProofVerification( - transactions[transactionIndex], - [uint256(0), uint256(0)], - uncompressedProof, - vks[transactions[transactionIndex].transactionType] - ); - challengeAccepted(blockL2); - } - // signature for single transfer/withdraw: - function challengeProofVerification( - Block memory blockL2, - Transaction[] calldata transactions, - uint256 transactionIndex, - Block memory blockL2ContainingHistoricRoot, - Transaction[] memory transactionsOfblockL2ContainingHistoricRoot, - uint256[8] memory uncompressedProof, - bytes32 salt - ) external onlyBootChallenger { - checkCommit(msg.data); - state.areBlockAndTransactionsReal(blockL2, transactions); - state.areBlockAndTransactionsReal( - blockL2ContainingHistoricRoot, - transactionsOfblockL2ContainingHistoricRoot - ); - // check the historic root is in the block provided. - require( - transactions[transactionIndex].historicRootBlockNumberL2[0] == - blockL2ContainingHistoricRoot.blockNumberL2 - ); - // first check the transaction and block do not overflow - ChallengesUtil.libCheckOverflows(blockL2, transactions[transactionIndex]); - // now we need to check that the proof is correct - ChallengesUtil.libCheckCompressedProof( - transactions[transactionIndex].proof, - uncompressedProof - ); - ChallengesUtil.libChallengeProofVerification( - transactions[transactionIndex], - [uint256(blockL2ContainingHistoricRoot.root), uint256(0)], - uncompressedProof, - vks[transactions[transactionIndex].transactionType] - ); - challengeAccepted(blockL2); - } + uint256[2] memory roots; - // signature for double transfer: - function challengeProofVerification( - Block memory blockL2, - Transaction[] calldata transactions, - uint256 transactionIndex, - Block[2] calldata blockL2ContainingHistoricRoot, - Transaction[] memory transactionsOfblockL2ContainingHistoricRoot, - Transaction[] memory transactionsOfblockL2ContainingHistoricRoot2, - uint256[8] memory uncompressedProof, - bytes32 salt - ) external onlyBootChallenger { - checkCommit(msg.data); - state.areBlockAndTransactionsReal(blockL2, transactions); - state.areBlockAndTransactionsReal( - blockL2ContainingHistoricRoot[0], - transactionsOfblockL2ContainingHistoricRoot - ); - state.areBlockAndTransactionsReal( - blockL2ContainingHistoricRoot[1], - transactionsOfblockL2ContainingHistoricRoot2 - ); - // check the historic roots are in the blocks provided. - require( - transactions[transactionIndex].historicRootBlockNumberL2[0] == - blockL2ContainingHistoricRoot[0].blockNumberL2 && + if (uint256(transactions[transactionIndex].nullifiers[0]) != 0) { + state.areBlockAndTransactionsReal( + blockL2ContainingHistoricRoot[0], + transactionsOfblockL2ContainingHistoricRoot[0] + ); + require( + transactions[transactionIndex].historicRootBlockNumberL2[0] == + blockL2ContainingHistoricRoot[0].blockNumberL2, + 'Incorrect historic root block' + ); + roots[0] = uint256(blockL2ContainingHistoricRoot[0].root); + } else { + roots[0] = uint256(0); + } + + if (uint256(transactions[transactionIndex].nullifiers[1]) != 0) { + state.areBlockAndTransactionsReal( + blockL2ContainingHistoricRoot[1], + transactionsOfblockL2ContainingHistoricRoot[1] + ); + require( transactions[transactionIndex].historicRootBlockNumberL2[1] == - blockL2ContainingHistoricRoot[1].blockNumberL2, - 'Incorrect historic root block' - ); + blockL2ContainingHistoricRoot[1].blockNumberL2, + 'Incorrect historic root block' + ); + roots[1] = uint256(blockL2ContainingHistoricRoot[1].root); + } else { + roots[1] = uint256(0); + } + // first check the transaction and block do not overflow ChallengesUtil.libCheckOverflows(blockL2, transactions[transactionIndex]); // now we need to check that the proof is correct ChallengesUtil.libChallengeProofVerification( transactions[transactionIndex], - [ - uint256(blockL2ContainingHistoricRoot[0].root), - uint256(blockL2ContainingHistoricRoot[1].root) - ], + roots, uncompressedProof, vks[transactions[transactionIndex].transactionType] ); @@ -254,18 +179,18 @@ contract Challenges is Stateful, Key_Registry, Config { Block memory block2, Transaction[] memory txs2, uint256 transactionIndex2, - uint256 nullifierIndex2, - bytes32 salt + uint256 nullifierIndex2 ) external onlyBootChallenger { checkCommit(msg.data); + state.areBlockAndTransactionsReal(block1, txs1); + state.areBlockAndTransactionsReal(block2, txs2); + ChallengesUtil.libChallengeNullifier( txs1[transactionIndex1], nullifierIndex1, txs2[transactionIndex2], nullifierIndex2 ); - state.areBlockAndTransactionsReal(block1, txs1); - state.areBlockAndTransactionsReal(block2, txs2); // The blocks are different and we prune the later block of the two // as we have a block number, it's easy to see which is the latest. @@ -277,21 +202,20 @@ contract Challenges is Stateful, Key_Registry, Config { } /* - This checks if the historic root blockNumberL2 provided is greater than the numbe of blocks on-chain. + This checks if the historic root blockNumberL2 provided is greater than the number of blocks on-chain. If the root stored in the block is itself invalid, that is challengeable by challengeNewRootCorrect. the indices for the same nullifier in two **different** transactions contained in two blocks (note it should also be ok for the blocks to be the same) */ function challengeHistoricRoot( Block memory blockL2, Transaction[] memory transactions, - uint256 transactionIndex, - bytes32 salt + uint256 transactionIndex ) external onlyBootChallenger { checkCommit(msg.data); state.areBlockAndTransactionsReal(blockL2, transactions); if ( - transactions[transactionIndex].transactionType == - Structures.TransactionTypes.TRANSFER + uint256(transactions[transactionIndex].nullifiers[0]) != 0 && + uint256(transactions[transactionIndex].nullifiers[1]) != 0 ) { require( state.getNumberOfL2Blocks() < @@ -300,9 +224,7 @@ contract Challenges is Stateful, Key_Registry, Config { uint256(transactions[transactionIndex].historicRootBlockNumberL2[1]), 'Historic root exists' ); - } else if ( - transactions[transactionIndex].transactionType == Structures.TransactionTypes.DEPOSIT - ) { + } else if (uint256(transactions[transactionIndex].nullifiers[0]) == 0) { require( uint256(transactions[transactionIndex].historicRootBlockNumberL2[0]) != 0 || uint256(transactions[transactionIndex].historicRootBlockNumberL2[1]) != 0, @@ -331,7 +253,7 @@ contract Challenges is Stateful, Key_Registry, Config { // State.sol because Timber gets confused if its events come from two // different contracts (it uses the contract name as part of the db // connection - we need to change that). - state.emitRollback(badBlock.blockNumberL2, badBlock.leafCount); + state.emitRollback(badBlock.blockNumberL2); // we need to remove the block that has been successfully // challenged from the linked list of blocks and all of the subsequent // blocks @@ -363,7 +285,6 @@ contract Challenges is Stateful, Key_Registry, Config { // within the challenge function using this function: function checkCommit(bytes calldata messageData) private { bytes32 hash = keccak256(messageData); - // salt = 0; // not really required as salt is in msg.data but stops the unused variable compiler warning. Bit of a waste of gas though. require(committers[hash] == msg.sender, 'Commitment hash is invalid'); delete committers[hash]; } diff --git a/nightfall-deployer/contracts/ChallengesUtil.sol b/nightfall-deployer/contracts/ChallengesUtil.sol index dc0014067..e64d0f2c9 100644 --- a/nightfall-deployer/contracts/ChallengesUtil.sol +++ b/nightfall-deployer/contracts/ChallengesUtil.sol @@ -32,143 +32,23 @@ library ChallengesUtil { Structures.Transaction[] memory transactions ) public pure { // next check the sibling path is valid and get the Frontier - bool valid; + bytes32 root; bytes32[33] memory _frontier; - (valid, _frontier) = MerkleTree_Stateless.checkPath( + (root, _frontier, ) = MerkleTree_Stateless.insertLeaves( Utils.filterCommitments(priorBlockTransactions), frontierPriorBlock, - priorBlockL2.leafCount, - priorBlockL2.root + priorBlockL2.leafCount ); - require(valid, 'The sibling path is invalid'); + require(root == priorBlockL2.root, 'The sibling path is invalid'); uint256 commitmentIndex = priorBlockL2.leafCount + Utils.filterCommitments(priorBlockTransactions).length; // At last, we can check if the root itself is correct! - (bytes32 root, , ) = - MerkleTree_Stateless.insertLeaves( - Utils.filterCommitments(transactions), - _frontier, - commitmentIndex - ); - require(root != blockL2.root, 'The root is actually fine'); - } - - // the transaction type is challenged to not be valid - function libChallengeTransactionType(Structures.Transaction memory transaction) public pure { - if (transaction.transactionType == Structures.TransactionTypes.DEPOSIT) - libChallengeTransactionTypeDeposit(transaction); - // TODO add these checks back after PR for out of gas - else if (transaction.transactionType == Structures.TransactionTypes.TRANSFER) - libChallengeTransactionTypeDoubleTransfer(transaction); // if(transaction.transactionType == TransactionTypes.WITHDRAW) - else libChallengeTransactionTypeWithdraw(transaction); - } - - // the transaction type deposit is challenged to not be valid - function libChallengeTransactionTypeDeposit(Structures.Transaction memory transaction) - public - pure - { - uint256 nZeroProof; - for (uint256 i = 0; i < transaction.proof.length; i++) { - if (transaction.proof[i] == 0) nZeroProof++; - } - uint256 nZeroCompressedSecrets; - for (uint256 i = 0; i < transaction.compressedSecrets.length; i++) { - if (transaction.compressedSecrets[i] == 0) nZeroCompressedSecrets++; - } - require( - (transaction.tokenId == ZERO && transaction.value == 0) || - transaction.ercAddress == ZERO || - transaction.recipientAddress != ZERO || - transaction.commitments[0] == ZERO || - transaction.commitments[1] != ZERO || - transaction.nullifiers[0] != ZERO || - transaction.nullifiers[1] != ZERO || - nZeroCompressedSecrets != 2 || - nZeroProof == 4 || // We assume that 3 out of the 4 proof elements can be a valid ZERO. Deals with exception cases - transaction.historicRootBlockNumberL2[0] != 0 || - transaction.historicRootBlockNumberL2[1] != 0, - 'This deposit transaction type is valid' - ); - } - - // the transaction type single transfer is challenged to not be valid - function libChallengeTransactionTypeSingleTransfer(Structures.Transaction memory transaction) - public - pure - { - uint256 nZeroCompressedSecrets; - for (uint256 i = 0; i < transaction.compressedSecrets.length; i++) { - if (transaction.compressedSecrets[i] == 0) nZeroCompressedSecrets++; - } - uint256 nZeroProof; - for (uint256 i = 0; i < transaction.proof.length; i++) { - if (transaction.proof[i] == 0) nZeroProof++; - } - require( - transaction.value != 0 || - transaction.commitments[0] == ZERO || - transaction.commitments[1] != ZERO || - transaction.nullifiers[0] == ZERO || - transaction.nullifiers[1] != ZERO || - nZeroCompressedSecrets == 2 || // We assume that 1 out of the 2 compressed secrets elements can be a valid ZERO. Deals with exception cases - nZeroProof == 4 || // We assume that 3 out of the 4 proof elements can be a valid ZERO. Deals with exception cases - transaction.historicRootBlockNumberL2[1] != 0, // If this is a single, the second historicBlockNumber needs to be zero - 'This single transfer transaction type is valid' - ); - } - - // the transaction type double transfer is challenged to not be valid - function libChallengeTransactionTypeDoubleTransfer(Structures.Transaction memory transaction) - public - pure - { - uint256 nZeroCompressedSecrets; - for (uint256 i = 0; i < transaction.compressedSecrets.length; i++) { - if (transaction.compressedSecrets[i] == 0) nZeroCompressedSecrets++; - } - uint256 nZeroProof; - for (uint256 i = 0; i < transaction.proof.length; i++) { - if (transaction.proof[i] == 0) nZeroProof++; - } - require( - transaction.value != 0 || - transaction.commitments[0] == ZERO || - transaction.commitments[1] == ZERO || - transaction.nullifiers[0] == ZERO || - transaction.nullifiers[1] == ZERO || - nZeroCompressedSecrets == 2 || // We assume that 1 out of the 2 compressed secrets elements can be a valid ZERO. Deals with exception cases - nZeroProof == 4, // We assume that 3 out of the 4 proof elements can be a valid ZERO. Deals with exception cases - 'This double transfer transaction type is valid' - ); - } - - // the transaction type withdraw is challenged to not be valid - function libChallengeTransactionTypeWithdraw(Structures.Transaction memory transaction) - public - pure - { - uint256 nZeroProof; - for (uint256 i = 0; i < transaction.proof.length; i++) { - if (transaction.proof[i] == 0) nZeroProof++; - } - uint256 nZeroCompressedSecrets; - for (uint256 i = 0; i < transaction.compressedSecrets.length; i++) { - if (transaction.compressedSecrets[i] == 0) nZeroCompressedSecrets++; - } - require( - (transaction.tokenId == ZERO && transaction.value == 0) || - transaction.ercAddress == ZERO || - transaction.recipientAddress == ZERO || - transaction.commitments[0] != ZERO || - transaction.commitments[1] != ZERO || - transaction.nullifiers[0] == ZERO || - transaction.nullifiers[1] != ZERO || - nZeroCompressedSecrets != 2 || - nZeroProof == 4 || // We assume that 3 out of the 4 proof elements can be a valid ZERO. Deals with exception cases - transaction.historicRootBlockNumberL2[1] != 0, // A withdraw has a similar constraint as a single transfer - 'This withdraw transaction type is valid' + (root, , ) = MerkleTree_Stateless.insertLeaves( + Utils.filterCommitments(transactions), + _frontier, + commitmentIndex ); + require(root != blockL2.root, 'The root is actually fine'); } function libChallengeProofVerification( @@ -183,7 +63,7 @@ library ChallengesUtil { for (uint256 i = 0; i < proof.length; i++) { proof1[i] = proof[i]; } - uint256[] memory publicInputs = Utils.getPublicInputs(transaction, roots); + uint256[30] memory publicInputs = Utils.getPublicInputs(transaction, roots); require(!Verifier.verify(proof1, publicInputs, vk), 'This proof appears to be valid'); } @@ -205,7 +85,10 @@ library ChallengesUtil { ) public pure { require(uint256(transaction.ercAddress) <= MAX20, 'ERC address out of range'); require( - uint256(transaction.recipientAddress) <= MAX20, + (transaction.transactionType != Structures.TransactionTypes.TRANSFER && + uint256(transaction.recipientAddress) <= MAX20) || + (transaction.transactionType == Structures.TransactionTypes.TRANSFER && + uint256(transaction.recipientAddress) <= MAX31), 'Recipient ERC address out of range' ); require(uint256(transaction.commitments[0]) <= MAX31, 'Commitment 0 out of range'); diff --git a/nightfall-deployer/contracts/MerkleTree_Stateless.sol b/nightfall-deployer/contracts/MerkleTree_Stateless.sol index 0942d7321..f6e5f1dfb 100644 --- a/nightfall-deployer/contracts/MerkleTree_Stateless.sol +++ b/nightfall-deployer/contracts/MerkleTree_Stateless.sol @@ -190,129 +190,4 @@ library MerkleTree_Stateless { _leafCount += numberOfLeaves; // the incrememnting of leafCount costs us 20k for the first leaf, and 5k thereafter return (root, _frontier, _leafCount); //the root of the tree } - - function checkPath( - bytes32[33] memory siblingPath, - uint256 leafIndex, - bytes32 node - ) - public - pure - returns ( - /* bytes32 root */ - bool, - bytes32[33] memory - ) - { - bytes32[33] memory _frontier; - /* if (siblingPath[0] != root) return (false, _frontier); // check root of sibling path is actually the prior block root */ - // This is an incomplete check. Root parameter can be manipulated to pass the following check because this is not grounded to any data - uint256 nodeValue = uint256(node); - for (uint256 i = 32; i > 0; i--) { - _frontier[i] = bytes32(nodeValue); - if (leafIndex % 2 == 0) - nodeValue = Poseidon.poseidon(nodeValue, uint256(siblingPath[i])); - else nodeValue = Poseidon.poseidon(uint256(siblingPath[i]), nodeValue); - leafIndex >> 1; - } - _frontier[0] = node; - return (siblingPath[0] == node, _frontier); - } - - function checkPath( - bytes32[] memory leafValues, - bytes32[33] memory _frontier, - uint256 _leafCount, - bytes32 _root - ) public pure returns (bool, bytes32[33] memory) { - uint256 numberOfLeaves = leafValues.length; - - // check that space exists in the tree: - require(treeWidth > _leafCount, 'There is no space left in the tree.'); - if (numberOfLeaves > treeWidth - _leafCount) { - uint256 numberOfExcessLeaves = numberOfLeaves - (treeWidth - _leafCount); - // remove the excess leaves, because we only want to emit those we've added as an event: - for (uint256 xs = 0; xs < numberOfExcessLeaves; xs++) { - /* - CAUTION!!! This attempts to succinctly achieve leafValues.pop() on a **memory** dynamic array. Not thoroughly tested! - Credit: https://ethereum.stackexchange.com/a/51897/45916 - */ - - assembly { - mstore(leafValues, sub(mload(leafValues), 1)) - } - } - numberOfLeaves = treeWidth - _leafCount; - } - - uint256 slot; - uint256 nodeIndex; - uint256 prevNodeIndex; - uint256 nodeValue; - - uint256 output; // the output of the hash - - // consider each new leaf in turn, from left to right: - for (uint256 leafIndex = _leafCount; leafIndex < _leafCount + numberOfLeaves; leafIndex++) { - nodeValue = uint256(leafValues[leafIndex - _leafCount]); - nodeIndex = leafIndex + treeWidth - 1; // convert the leafIndex to a nodeIndex - - slot = getFrontierSlot(leafIndex); // determine at which level we will next need to store a nodeValue - - if (slot == 0) { - _frontier[slot] = bytes32(nodeValue); // update Frontier - continue; - } - - // hash up to the level whose nodeValue we'll store in the frontier slot: - for (uint256 level = 1; level <= slot; level++) { - if (nodeIndex % 2 == 0) { - // even nodeIndex - output = Poseidon.poseidon(uint256(_frontier[level - 1]), nodeValue); // poseidon hash of concatenation of each node - - nodeValue = output; // the parentValue, but will become the nodeValue of the next level - prevNodeIndex = nodeIndex; - nodeIndex = (nodeIndex - 1) / 2; // move one row up the tree - // emit Output(input, output, prevNodeIndex, nodeIndex); // for debugging only - } else { - // odd nodeIndex - output = Poseidon.poseidon(nodeValue, 0); // poseidon hash of concatenation of each node - - nodeValue = output; // the parentValue, but will become the nodeValue of the next level - prevNodeIndex = nodeIndex; - nodeIndex = nodeIndex / 2; // the parentIndex, but will become the nodeIndex of the next level - // emit Output(input, output, prevNodeIndex, nodeIndex); // for debugging only - } - } - _frontier[slot] = bytes32(nodeValue); // update frontier - } - - // So far we've added all leaves, and hashed up to a particular level of the tree. We now need to continue hashing from that level until the root: - for (uint256 level = slot + 1; level <= treeHeight; level++) { - if (nodeIndex % 2 == 0) { - // even nodeIndex - output = Poseidon.poseidon(uint256(_frontier[level - 1]), nodeValue); // poseidon hash of concatenation of each node - - nodeValue = output; // the parentValue, but will become the nodeValue of the next level - prevNodeIndex = nodeIndex; - nodeIndex = (nodeIndex - 1) / 2; // the parentIndex, but will become the nodeIndex of the next level - // emit Output(input, output, prevNodeIndex, nodeIndex); // for debugging only - } else { - // odd nodeIndex - output = Poseidon.poseidon(nodeValue, 0); // poseidon hash of concatenation of each node - - nodeValue = output; // the parentValue, but will become the nodeValue of the next level - prevNodeIndex = nodeIndex; - nodeIndex = nodeIndex / 2; // the parentIndex, but will become the nodeIndex of the next level - // emit Output(input, output, prevNodeIndex, nodeIndex); // for debugging only - } - } - - /* root = nodeValue; */ - - //emit NewLeaves(_leafCount, leafValues, root); // this event is what the merkle-tree microservice's filter will listen for. - - /* return (root, _frontier, _leafCount); //the root of the tree */ - return (_root == bytes32(nodeValue), _frontier); - } } diff --git a/nightfall-deployer/contracts/State.sol b/nightfall-deployer/contracts/State.sol index d63780a92..1ea97a9b2 100644 --- a/nightfall-deployer/contracts/State.sol +++ b/nightfall-deployer/contracts/State.sol @@ -160,15 +160,8 @@ contract State is Initializable, ReentrancyGuardUpgradeable, Pausable, Config { // it's uinque, although technically not needed (Optimist consumes the // block number and Timber the leaf count). It's helpful when testing to make // sure we have the correct event. - function emitRollback(uint256 blockNumberL2ToRollbackTo, uint256 leafCountToRollbackTo) - public - onlyRegistered - { - emit Rollback( - blockHashes[blockNumberL2ToRollbackTo].blockHash, - blockNumberL2ToRollbackTo, - leafCountToRollbackTo - ); + function emitRollback(uint256 blockNumberL2ToRollbackTo) public onlyRegistered { + emit Rollback(blockNumberL2ToRollbackTo); } function setProposer(address addr, LinkedAddress memory proposer) public onlyRegistered { diff --git a/nightfall-deployer/contracts/Structures.sol b/nightfall-deployer/contracts/Structures.sol index 4b9c8f934..4a511cf16 100644 --- a/nightfall-deployer/contracts/Structures.sol +++ b/nightfall-deployer/contracts/Structures.sol @@ -10,7 +10,7 @@ contract Structures { enum TokenType {ERC20, ERC721, ERC1155} - event Rollback(bytes32 indexed blockHash, uint256 blockNumberL2, uint256 leafCount); + event Rollback(uint256 blockNumberL2); event BlockProposed(); diff --git a/nightfall-deployer/contracts/Utils.sol b/nightfall-deployer/contracts/Utils.sol index d443cd3fd..e35b5bbfd 100644 --- a/nightfall-deployer/contracts/Utils.sol +++ b/nightfall-deployer/contracts/Utils.sol @@ -25,7 +25,7 @@ library Utils { bytes32[] memory transactionHashes = new bytes32[](ts.length); for (uint256 i = 0; i < ts.length; i++) { - transactionHashes[i ] = hashTransaction(ts[i]); + transactionHashes[i] = hashTransaction(ts[i]); } transactionHashesRoot = calculateMerkleRoot(transactionHashes); return transactionHashesRoot; @@ -102,96 +102,51 @@ library Utils { function getPublicInputs(Structures.Transaction calldata ts, uint256[2] memory roots) internal pure - returns (uint256[] memory inputs) + returns (uint256[30] memory inputs) { - // uint256[] memory inputs = new uint256[](countPublicInputs(ts)); - if (ts.transactionType == Structures.TransactionTypes.DEPOSIT) { - inputs = getDepositInputs(ts); - } else if (ts.transactionType == Structures.TransactionTypes.TRANSFER) { - inputs = getDoubleTransferInputs(ts, roots); - } else { - inputs = getWithdrawInputs(ts, roots); - } - } - - function getDepositInputs(Structures.Transaction calldata ts) - internal - pure - returns (uint256[] memory) - { - uint256[] memory inputs = new uint256[](4); - inputs[0] = uint256(ts.ercAddress); - inputs[1] = uint256(ts.tokenId); - inputs[2] = ts.value; - inputs[3] = uint256(ts.commitments[0]); - return inputs; - } - - function getSingleTransferInputs(Structures.Transaction calldata ts, uint256[2] memory roots) - internal - pure - returns (uint256[] memory) - { - uint256[] memory inputs = new uint256[](12); - inputs[0] = uint256(ts.ercAddress); - inputs[1] = uint256(ts.commitments[0]); - inputs[2] = uint256(ts.nullifiers[0]); - inputs[3] = roots[0]; - for (uint256 i = 4; i < 12; i++) { - inputs[i] = uint256(ts.compressedSecrets[i - 4]); - } - return inputs; - } - - function getWithdrawInputs(Structures.Transaction calldata ts, uint256[2] memory roots) - internal - pure - returns (uint256[] memory) - { - uint256[] memory inputs = new uint256[](6); - inputs[0] = uint256(ts.ercAddress); - inputs[1] = uint256(ts.tokenId); - inputs[2] = ts.value; - inputs[3] = uint256(ts.nullifiers[0]); - inputs[4] = uint256(ts.recipientAddress); - inputs[5] = roots[0]; - return inputs; - } - - function getDoubleTransferInputs(Structures.Transaction calldata ts, uint256[2] memory roots) - internal - pure - returns (uint256[] memory) - { - uint256[] memory inputs = new uint256[](16); - inputs[0] = uint256(ts.ercAddress); - inputs[1] = uint256(ts.ercAddress); - inputs[2] = uint256(ts.commitments[0]); - inputs[3] = uint256(ts.commitments[1]); - inputs[4] = uint256(ts.nullifiers[0]); - inputs[5] = uint256(ts.nullifiers[1]); - inputs[6] = roots[0]; - inputs[7] = roots[1]; - for (uint256 i = 8; i < 16; i++) { - inputs[i] = uint256(ts.compressedSecrets[i - 8]); - } - return inputs; + inputs[0] = uint256(ts.value); + inputs[1] = uint256(ts.historicRootBlockNumberL2[0]); + inputs[2] = uint256(ts.historicRootBlockNumberL2[1]); + inputs[3] = uint256(ts.transactionType); + inputs[4] = uint256(ts.tokenType); + inputs[5] = uint32(uint256(ts.tokenId) >> 224); + inputs[6] = uint32(uint256(ts.tokenId) >> 192); + inputs[7] = uint32(uint256(ts.tokenId) >> 160); + inputs[8] = uint32(uint256(ts.tokenId) >> 128); + inputs[9] = uint32(uint256(ts.tokenId) >> 96); + inputs[10] = uint32(uint256(ts.tokenId) >> 64); + inputs[11] = uint32(uint256(ts.tokenId) >> 32); + inputs[12] = uint32(uint256(ts.tokenId)); + inputs[13] = uint256(ts.ercAddress); + inputs[14] = uint32(uint256(ts.recipientAddress) >> 224); + inputs[15] = uint32(uint256(ts.recipientAddress) >> 192); + inputs[16] = uint32(uint256(ts.recipientAddress) >> 160); + inputs[17] = uint32(uint256(ts.recipientAddress) >> 128); + inputs[18] = uint32(uint256(ts.recipientAddress) >> 96); + inputs[19] = uint32(uint256(ts.recipientAddress) >> 64); + inputs[20] = uint32(uint256(ts.recipientAddress) >> 32); + inputs[21] = uint32(uint256(ts.recipientAddress)); + inputs[22] = uint256(ts.commitments[0]); + inputs[23] = uint256(ts.commitments[1]); + inputs[24] = uint256(ts.nullifiers[0]); + inputs[25] = uint256(ts.nullifiers[1]); + inputs[26] = uint256(ts.compressedSecrets[0]); + inputs[27] = uint256(ts.compressedSecrets[1]); + inputs[28] = roots[0]; + inputs[29] = roots[1]; } function calculateMerkleRoot(bytes32[] memory leaves) public pure returns (bytes32 result) { assembly { let length := mload(leaves) - let leavesPos := add(leaves,0x20) + let leavesPos := add(leaves, 0x20) let transactionHashesPos := mload(0x40) for { let i := 0 } lt(i, length) { i := add(i, 1) } { - mstore( - add(transactionHashesPos, mul(0x20, i)), - mload(add(leavesPos, mul(0x20, i))) - ) + mstore(add(transactionHashesPos, mul(0x20, i)), mload(add(leavesPos, mul(0x20, i)))) } for { let i := 5 @@ -209,10 +164,7 @@ library Utils { result := 0 } // returns bool if eq(and(iszero(left), iszero(right)), 0) { - result := keccak256( - add(transactionHashesPos, mul(mul(0x20, j), 2)), - 0x40 - ) + result := keccak256(add(transactionHashesPos, mul(mul(0x20, j), 2)), 0x40) } // returns bool mstore(add(transactionHashesPos, mul(0x20, j)), result) } diff --git a/nightfall-deployer/contracts/Verifier.sol b/nightfall-deployer/contracts/Verifier.sol index 2eda1c64d..88c6c4ed6 100644 --- a/nightfall-deployer/contracts/Verifier.sol +++ b/nightfall-deployer/contracts/Verifier.sol @@ -27,121 +27,125 @@ Harry R pragma solidity ^0.8.0; -import "./Ownable.sol"; -import "./Pairing.sol"; +import './Ownable.sol'; +import './Pairing.sol'; library Verifier { + using Pairing for *; - using Pairing for *; + uint256 constant BN128_GROUP_ORDER = + 21888242871839275222246405745257275088548364400416034343698204186575808495617; - uint256 constant BN128_GROUP_ORDER = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + struct Proof_G16 { + Pairing.G1Point A; + Pairing.G2Point B; + Pairing.G1Point C; + } - struct Proof_G16 { - Pairing.G1Point A; - Pairing.G2Point B; - Pairing.G1Point C; - } - - struct Verification_Key_G16 { - Pairing.G1Point alpha; + struct Verification_Key_G16 { + Pairing.G1Point alpha; Pairing.G2Point beta; Pairing.G2Point gamma; Pairing.G2Point delta; Pairing.G1Point[] gamma_abc; - } - - function verify(uint256[] memory _proof, uint256[] memory _publicInputs, uint256[] memory _vk) public returns (bool result) { - if (verificationCalculation(_proof, _publicInputs, _vk) == 0) { - result = true; - } else { - result = false; - } - } - - function verificationCalculation(uint256[] memory _proof, uint256[] memory _publicInputs, uint256[] memory _vk) public returns (uint) { - - Proof_G16 memory proof; - Pairing.G1Point memory vk_dot_inputs; - Verification_Key_G16 memory vk; - - vk_dot_inputs = Pairing.G1Point(0, 0); //initialise - - proof.A = Pairing.G1Point(_proof[0], _proof[1]); - proof.B = Pairing.G2Point([_proof[2], _proof[3]], [_proof[4], _proof[5]]); - proof.C = Pairing.G1Point(_proof[6], _proof[7]); - - vk.alpha = Pairing.G1Point(_vk[0],_vk[1]); - vk.beta = Pairing.G2Point([_vk[2],_vk[3]],[_vk[4],_vk[5]]); - vk.gamma = Pairing.G2Point([_vk[6],_vk[7]],[_vk[8],_vk[9]]); - vk.delta = Pairing.G2Point([_vk[10],_vk[11]],[_vk[12],_vk[13]]); - - if (_vk.length > 14) { - vk.gamma_abc = new Pairing.G1Point[]((_vk.length - 14)/2); // num public inputs + 1 - for (uint i = 14; i < _vk.length; i+=2) { - vk.gamma_abc[(i-14)/2] = Pairing.G1Point( - _vk[i], _vk[i+1] - ); + } + + function verify( + uint256[] memory _proof, + uint256[30] memory _publicInputs, + uint256[] memory _vk + ) public returns (bool result) { + if (verificationCalculation(_proof, _publicInputs, _vk) == 0) { + result = true; + } else { + result = false; + } + } + + function verificationCalculation( + uint256[] memory _proof, + uint256[30] memory _publicInputs, + uint256[] memory _vk + ) public returns (uint256) { + Proof_G16 memory proof; + Pairing.G1Point memory vk_dot_inputs; + Verification_Key_G16 memory vk; + + vk_dot_inputs = Pairing.G1Point(0, 0); //initialise + + proof.A = Pairing.G1Point(_proof[0], _proof[1]); + proof.B = Pairing.G2Point([_proof[2], _proof[3]], [_proof[4], _proof[5]]); + proof.C = Pairing.G1Point(_proof[6], _proof[7]); + + vk.alpha = Pairing.G1Point(_vk[0], _vk[1]); + vk.beta = Pairing.G2Point([_vk[2], _vk[3]], [_vk[4], _vk[5]]); + vk.gamma = Pairing.G2Point([_vk[6], _vk[7]], [_vk[8], _vk[9]]); + vk.delta = Pairing.G2Point([_vk[10], _vk[11]], [_vk[12], _vk[13]]); + + if (_vk.length > 14) { + vk.gamma_abc = new Pairing.G1Point[]((_vk.length - 14) / 2); // num public inputs + 1 + for (uint256 i = 14; i < _vk.length; i += 2) { + vk.gamma_abc[(i - 14) / 2] = Pairing.G1Point(_vk[i], _vk[i + 1]); + } } - } - - - /* require(vk.gamma.abc.length == 2, "Length of vk.gamma.abc is incorrect!"); */ - // Replacing for the above require statement so that the proof verification returns false. Removing require statements to ensure a wrong proof verification challenge's require statement correctly works - if (vk.gamma_abc.length != _publicInputs.length + 1) { - return 1; - } - + /* require(vk.gamma.abc.length == 2, "Length of vk.gamma.abc is incorrect!"); */ + // Replacing for the above require statement so that the proof verification returns false. Removing require statements to ensure a wrong proof verification challenge's require statement correctly works + if (vk.gamma_abc.length != _publicInputs.length + 1) { + return 1; + } + { + Pairing.G1Point memory sm_qpih; + // The following success variables replace require statements with corresponding functions called. Removing require statements to ensure a wrong proof verification challenge's require statement correctly works + bool success_sm_qpih; + bool success_vkdi_sm_qpih; + for (uint256 i = 0; i < _publicInputs.length; i++) { + // check for overflow attacks + if (_publicInputs[i] >= BN128_GROUP_ORDER) return 2; + (sm_qpih, success_sm_qpih) = Pairing.scalar_mul( + vk.gamma_abc[i + 1], + _publicInputs[i] + ); + (vk_dot_inputs, success_vkdi_sm_qpih) = Pairing.addition(vk_dot_inputs, sm_qpih); + if (!success_sm_qpih || !success_vkdi_sm_qpih) { + return 2; + } + } + } + { + // The following success variables replace require statements with corresponding functions called. Removing require statements to ensure a wrong proof verification challenge's require statement correctly works + bool success_vkdi_q; + (vk_dot_inputs, success_vkdi_q) = Pairing.addition(vk_dot_inputs, vk.gamma_abc[0]); + if (!success_vkdi_q) { + return 3; + } + } - { - Pairing.G1Point memory sm_qpih; - // The following success variables replace require statements with corresponding functions called. Removing require statements to ensure a wrong proof verification challenge's require statement correctly works - bool success_sm_qpih; - bool success_vkdi_sm_qpih; - for (uint i = 0; i < _publicInputs.length; i++) { - // check for overflow attacks - if (_publicInputs[i] >= BN128_GROUP_ORDER) return 2; - (sm_qpih, success_sm_qpih) = Pairing.scalar_mul(vk.gamma_abc[i+1], _publicInputs[i]); - (vk_dot_inputs, success_vkdi_sm_qpih) = Pairing.addition( - vk_dot_inputs, - sm_qpih + /** + * e(A*G^{alpha}, B*H^{beta}) = e(G^{alpha}, H^{beta}) * e(G^{psi}, H^{gamma}) + * * e(C, H) + * where psi = \sum_{i=0}^l input_i pvk.query[i] + */ + { + // The following success variables replace require statements with corresponding functions called. Removing require statements to ensure a wrong proof verification challenge's require statement correctly works + bool success_pp4_out_not_0; + bool success_pp4_pairing; + (success_pp4_out_not_0, success_pp4_pairing) = Pairing.pairingProd4( + proof.A, + proof.B, + Pairing.negate(vk_dot_inputs), + vk.gamma, + Pairing.negate(proof.C), + vk.delta, + Pairing.negate(vk.alpha), + vk.beta ); - if (!success_sm_qpih || !success_vkdi_sm_qpih) { - return 2; - } - } - } - - { - // The following success variables replace require statements with corresponding functions called. Removing require statements to ensure a wrong proof verification challenge's require statement correctly works - bool success_vkdi_q; - (vk_dot_inputs, success_vkdi_q) = Pairing.addition(vk_dot_inputs, vk.gamma_abc[0]); - if (!success_vkdi_q) { - return 3; - } - } - - - /** - * e(A*G^{alpha}, B*H^{beta}) = e(G^{alpha}, H^{beta}) * e(G^{psi}, H^{gamma}) - * * e(C, H) - * where psi = \sum_{i=0}^l input_i pvk.query[i] - */ - { - // The following success variables replace require statements with corresponding functions called. Removing require statements to ensure a wrong proof verification challenge's require statement correctly works - bool success_pp4_out_not_0; - bool success_pp4_pairing; - (success_pp4_out_not_0, success_pp4_pairing) = Pairing.pairingProd4( - proof.A, proof.B, - Pairing.negate(vk_dot_inputs), vk.gamma, - Pairing.negate(proof.C), vk.delta, - Pairing.negate(vk.alpha), vk.beta); - if (!success_pp4_out_not_0 || !success_pp4_pairing) { - return 5; + if (!success_pp4_out_not_0 || !success_pp4_pairing) { + return 5; + } } - } - return 0; - } + return 0; + } } diff --git a/nightfall-optimist/src/services/challenges.mjs b/nightfall-optimist/src/services/challenges.mjs index 6d7f0e809..545f20784 100644 --- a/nightfall-optimist/src/services/challenges.mjs +++ b/nightfall-optimist/src/services/challenges.mjs @@ -1,6 +1,5 @@ import WebSocket from 'ws'; import config from 'config'; -import { rand } from 'common-files/utils/crypto/crypto-random.mjs'; import logger from 'common-files/utils/logger.mjs'; import Web3 from 'common-files/utils/web3.mjs'; import { getContractInstance } from 'common-files/utils/contract.mjs'; @@ -83,7 +82,6 @@ export async function createChallenge(block, transactions, err) { let txDataToSign; if (makeChallenges) { const challengeContractInstance = await getContractInstance(CHALLENGES_CONTRACT_NAME); - const salt = (await rand(32)).hex(32); switch (err.code) { // Challenge wrong root case 0: { @@ -118,7 +116,6 @@ export async function createChallenge(block, transactions, err) { frontierToValidatePreviousBlock, Block.buildSolidityStruct(block), transactions.map(t => Transaction.buildSolidityStruct(t)), - salt, ) .encodeABI(); break; @@ -147,27 +144,10 @@ export async function createChallenge(block, transactions, err) { transactions2.map(t => Transaction.buildSolidityStruct(t)), transactionIndex1, // index of duplicate transaction in block transactionIndex2, - salt, ) .encodeABI(); break; } - // invalid transaction type - case 2: { - const { transactionHashIndex: transactionIndex } = err.metadata; - // Create a challenge - txDataToSign = await challengeContractInstance.methods - .challengeTransactionType( - Block.buildSolidityStruct(block), - transactions.map(t => Transaction.buildSolidityStruct(t)), - transactionIndex, - salt, - ) - .encodeABI(); - logger.debug('returning raw transaction'); - logger.silly(`raw transaction is ${JSON.stringify(txDataToSign, null, 2)}`); - break; - } // historic root is incorrect case 3: { const { transactionHashIndex: transactionIndex } = err.metadata; @@ -177,7 +157,6 @@ export async function createChallenge(block, transactions, err) { Block.buildSolidityStruct(block), transactions.map(t => Transaction.buildSolidityStruct(t)), transactionIndex, - salt, ) .encodeABI(); break; @@ -187,63 +166,57 @@ export async function createChallenge(block, transactions, err) { const { transactionHashIndex: transactionIndex } = err.metadata; // Create a challenge const uncompressedProof = transactions[transactionIndex].proof; - if (transactions[transactionIndex].transactionType === '0') { - txDataToSign = await challengeContractInstance.methods - .challengeProofVerification( - Block.buildSolidityStruct(block), - transactions.map(t => Transaction.buildSolidityStruct(t)), - transactionIndex, - uncompressedProof, - salt, - ) - .encodeABI(); - } else if (transactions[transactionIndex].transactionType === '2') { - // Create a specific challenge for a double_transfer - const [historicInput1, historicInput2] = await Promise.all( - transactions[transactionIndex].historicRootBlockNumberL2.map(async b => { - const historicBlock = await getBlockByBlockNumberL2(b); - const historicTxs = await getTransactionsByTransactionHashes(block.transactionHashes); + const [historicInput1, historicInput2] = await Promise.all( + transactions[transactionIndex].historicRootBlockNumberL2.map(async b => { + if (b === 0) { return { - historicBlock, - historicTxs, + historicBlock: {}, + historicTxs: [], }; - }), - ); - txDataToSign = await challengeContractInstance.methods - .challengeProofVerification( - Block.buildSolidityStruct(block), - transactions.map(t => Transaction.buildSolidityStruct(t)), - transactionIndex, - Block.buildSolidityStruct(historicInput1.historicBlock), - Block.buildSolidityStruct(historicInput2.historicBlock), + } + const historicBlock = await getBlockByBlockNumberL2(b); + const historicTxs = await getTransactionsByTransactionHashes(block.transactionHashes); + return { + historicBlock: Block.buildSolidityStruct(historicBlock), + historicTxs, + }; + }), + ); + + const [historicInputFee1, historicInputFee2] = await Promise.all( + transactions[transactionIndex].historicRootBlockNumberL2Fee.map(async b => { + if (b === 0) { + return { + historicBlock: {}, + historicTxs: [], + }; + } + const historicBlock = await getBlockByBlockNumberL2(b); + const historicTxs = await getTransactionsByTransactionHashes(block.transactionHashes); + return { + historicBlock: Block.buildSolidityStruct(historicBlock), + historicTxs, + }; + }), + ); + txDataToSign = await challengeContractInstance.methods + .challengeProofVerification( + Block.buildSolidityStruct(block), + transactions.map(t => Transaction.buildSolidityStruct(t)), + transactionIndex, + [historicInput1.historicBlock, historicInput2.historicBlock], + [ historicInput1.historicTxs.map(t => Transaction.buildSolidityStruct(t)), historicInput2.historicTxs.map(t => Transaction.buildSolidityStruct(t)), - uncompressedProof, - salt, - ) - .encodeABI(); - } else { - const blockL2ContainingHistoricRoot = await getBlockByBlockNumberL2( - transactions[transactionIndex].historicRootBlockNumberL2[0], // TODO - ); - const transactionsOfblockL2ContainingHistoricRoot = - await getTransactionsByTransactionHashes( - blockL2ContainingHistoricRoot.transactionHashes, - ); - txDataToSign = await challengeContractInstance.methods - .challengeProofVerification( - Block.buildSolidityStruct(block), - transactions.map(t => Transaction.buildSolidityStruct(t)), - transactionIndex, - Block.buildSolidityStruct(blockL2ContainingHistoricRoot), - transactionsOfblockL2ContainingHistoricRoot.map(t => - Transaction.buildSolidityStruct(t), - ), - uncompressedProof, - salt, - ) - .encodeABI(); - } + ], + [historicInputFee1.historicBlock, historicInputFee2.historicBlock], + [ + historicInputFee1.historicTxs.map(t => Transaction.buildSolidityStruct(t)), + historicInputFee2.historicTxs.map(t => Transaction.buildSolidityStruct(t)), + ], + uncompressedProof, + ) + .encodeABI(); break; } // Challenge Duplicate Nullfier @@ -284,7 +257,6 @@ export async function createChallenge(block, transactions, err) { oldBlockTransactions.map(t => Transaction.buildSolidityStruct(t)), oldTxIdx, oldNullifierIdx, - salt, ) .encodeABI(); } @@ -302,7 +274,6 @@ export async function createChallenge(block, transactions, err) { priorBlockTransactions.map(t => Transaction.buildSolidityStruct(t)), // the transactions in the prior block Block.buildSolidityStruct(block), transactions.map(t => Transaction.buildSolidityStruct(t)), - salt, ) .encodeABI(); break; diff --git a/nightfall-optimist/src/services/transaction-checker.mjs b/nightfall-optimist/src/services/transaction-checker.mjs index eb5d1f828..22aada490 100644 --- a/nightfall-optimist/src/services/transaction-checker.mjs +++ b/nightfall-optimist/src/services/transaction-checker.mjs @@ -39,20 +39,20 @@ async function checkTransactionHash(transaction) { async function checkHistoricRoot(transaction) { // Deposit transaction have a historic root of 0 // the validity is tested in checkTransactionType - if (Number(transaction.transactionType) === 1) { - const [historicRootFirst, historicRootSecond] = await Promise.all( - transaction.historicRootBlockNumberL2.map(h => getBlockByBlockNumberL2(h)), - ); - if (historicRootFirst === null || historicRootSecond === null) - throw new TransactionError('The historic root in the transaction does not exist', 3); - } - if (Number(transaction.transactionType) === 1 || Number(transaction.transactionType) === 2) { + if (Number(transaction.nullifiers[0]) !== 0) { const historicRootFirst = await getBlockByBlockNumberL2( transaction.historicRootBlockNumberL2[0], ); if (historicRootFirst === null) throw new TransactionError('The historic root in the transaction does not exist', 3); } + if (Number(transaction.nullifiers[0]) !== 0) { + const historicRootSecond = await getBlockByBlockNumberL2( + transaction.historicRootBlockNumberL2[1], + ); + if (historicRootSecond === null) + throw new TransactionError('The historic root in the transaction does not exist', 3); + } } async function verifyProof(transaction) { @@ -63,7 +63,6 @@ async function verifyProof(transaction) { .call(); // to verify a proof, we make use of a zokrates-worker, which has an offchain // verifier capability - let inputs; const historicRootFirst = transaction.nullifiers[0] === ZERO ? { root: ZERO } @@ -73,60 +72,44 @@ async function verifyProof(transaction) { ? { root: ZERO } : (await getBlockByBlockNumberL2(transaction.historicRootBlockNumberL2[1])) ?? { root: ZERO }; - const publicInputs = [ - transaction.value, - transaction.historicRootBlockNumberL2, - transaction.transactionType, - transaction.tokenType, - generalise(transaction.tokenId).limbs(32, 8), - transaction.ercAddress, - generalise(transaction.recipientAddress).limbs(32, 8), - transaction.commitments, - transaction.nullifiers, - transaction.compressedSecrets, - ].flat(Infinity); - switch (Number(transaction.transactionType)) { - case 0: // deposit transaction - inputs = generalise(publicInputs).all.hex(32); - if ( - isOverflow(transaction.ercAddress, MAX_PUBLIC_VALUES.ERCADDRESS) || - isOverflow(transaction.commitments[0], MAX_PUBLIC_VALUES.COMMITMENTS) - ) - throw new TransactionError('Truncated value overflow in public input', 4); - break; - case 1: // single transfer transaction - inputs = generalise([ - ...publicInputs, - historicRootFirst.root, - historicRootSecond.root, - ]).all.hex(32); - // check for truncation overflow attacks - if ( - isOverflow(historicRootFirst.root, BN128_GROUP_ORDER) || - isOverflow(historicRootSecond.root, BN128_GROUP_ORDER) - ) - throw new TransactionError('Overflow in public input', 4); - break; - case 2: // withdraw transaction - inputs = generalise([ - ...publicInputs, - historicRootFirst.root, - historicRootSecond.root, - ]).all.hex(32); - // check for truncation overflow attacks - for (let i = 0; i < transaction.nullifiers.length; i++) { - if (isOverflow(transaction.nullifiers[i], MAX_PUBLIC_VALUES.NULLIFIER)) - throw new TransactionError('Overflow in public input', 4); - } - for (let i = 0; i < transaction.commitments.length; i++) { - if (isOverflow(transaction.commitments[i], MAX_PUBLIC_VALUES.COMMITMENT)) - throw new TransactionError('Overflow in public input', 4); - } - if (isOverflow(historicRootFirst.root, BN128_GROUP_ORDER)) - throw new TransactionError('Overflow in public input', 4); - break; - default: - throw new TransactionError('Unknown transaction type', 2); + if (![0, 1, 2].includes(Number(transaction.transactionType))) { + throw new TransactionError('Unknown transaction type', 2); + } + + const inputs = generalise( + [ + transaction.value, + transaction.historicRootBlockNumberL2, + transaction.transactionType, + transaction.tokenType, + generalise(transaction.tokenId).limbs(32, 8), + transaction.ercAddress, + generalise(transaction.recipientAddress).limbs(32, 8), + transaction.commitments, + transaction.nullifiers, + transaction.compressedSecrets, + historicRootFirst.root, + historicRootSecond.root, + ].flat(Infinity), + ).all.hex(32); + + if ( + isOverflow(transaction.ercAddress, MAX_PUBLIC_VALUES.ERCADDRESS) || + isOverflow(historicRootFirst.root, BN128_GROUP_ORDER) || + isOverflow(historicRootSecond.root, BN128_GROUP_ORDER) || + (transaction.transactionType === 2 && + isOverflow(transaction.recipientAddress, MAX_PUBLIC_VALUES.ERCADDRESS)) + ) { + throw new TransactionError('Overflow in public input', 4); + } + + for (let i = 0; i < transaction.nullifiers.length; i++) { + if (isOverflow(transaction.nullifiers[i], MAX_PUBLIC_VALUES.NULLIFIER)) + throw new TransactionError('Overflow in public input', 4); + } + for (let i = 0; i < transaction.commitments.length; i++) { + if (isOverflow(transaction.commitments[i], MAX_PUBLIC_VALUES.COMMITMENT)) + throw new TransactionError('Overflow in public input', 4); } // check for modular overflow attacks // if (inputs.filter(input => input.bigInt >= BN128_GROUP_ORDER).length > 0) From e0d2056e80168d8227ffe37236979e749b3b1d58 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Mon, 1 Aug 2022 10:33:04 +0200 Subject: [PATCH 04/15] chore: merging issue818 changes for checking purposes --- .../src/services/commitment-storage.mjs | 9 +- nightfall-client/src/services/transfer.mjs | 11 +- nightfall-client/src/services/withdraw.mjs | 40 +-- .../src/utils/compute-witness.mjs | 44 ++-- .../Verifiers/verify_commitments.zok | 18 +- .../Verifiers/verify_nullifiers.zok | 7 +- nightfall-deployer/circuits/deposit.zok | 1 - nightfall-deployer/circuits/deposit_stub.zok | 1 - nightfall-deployer/circuits/transfer.zok | 4 +- nightfall-deployer/circuits/transfer_stub.zok | 4 +- nightfall-deployer/circuits/withdraw.zok | 4 +- nightfall-deployer/circuits/withdraw_stub.zok | 4 +- nightfall-deployer/contracts/Challenges.sol | 6 +- .../contracts/ChallengesUtil.sol | 2 +- nightfall-deployer/contracts/Shield.sol | 8 +- nightfall-deployer/contracts/State.sol | 10 +- nightfall-deployer/contracts/Utils.sol | 9 +- nightfall-deployer/contracts/Verifier.sol | 4 +- .../src/services/transaction-checker.mjs | 7 +- package.json | 1 + test/e2e/circuits.test.mjs | 234 ++++++++++++++++++ wallet/src/hooks/User/index.jsx | 10 +- 22 files changed, 343 insertions(+), 95 deletions(-) create mode 100644 test/e2e/circuits.test.mjs diff --git a/nightfall-client/src/services/commitment-storage.mjs b/nightfall-client/src/services/commitment-storage.mjs index fce334c24..cd36e45d7 100644 --- a/nightfall-client/src/services/commitment-storage.mjs +++ b/nightfall-client/src/services/commitment-storage.mjs @@ -711,7 +711,7 @@ async function findUsableCommitments(compressedZkpPublicKey, ercAddress, tokenId // This value will always be negative, // this is equivalent to tempSum - value.bigInt - commitsLessThanTargetValue[lhs].preimage.value.bigInt const tempChangeDiff = commitsLessThanTargetValue[rhs].preimage.value.bigInt - value.bigInt; - if (tempSum > value.bigInt) { + if (tempSum >= value.bigInt) { if (tempChangeDiff > changeDiff) { // We have a set of commitments that has a lower negative change in our outputs. changeDiff = tempChangeDiff; @@ -724,11 +724,10 @@ async function findUsableCommitments(compressedZkpPublicKey, ercAddress, tokenId logger.info( `Found commitments suitable for two-token transfer: ${JSON.stringify(commitmentsToUse)}`, ); - } else { - return null; + await Promise.all(commitmentsToUse.map(commitment => markPending(commitment))); + return commitmentsToUse; } - await Promise.all(commitmentsToUse.map(commitment => markPending(commitment))); - return commitmentsToUse; + return null; } // mutex for the above function to ensure it only runs with a concurrency of one diff --git a/nightfall-client/src/services/transfer.mjs b/nightfall-client/src/services/transfer.mjs index 9bc0a4acb..1ccc26bca 100644 --- a/nightfall-client/src/services/transfer.mjs +++ b/nightfall-client/src/services/transfer.mjs @@ -117,15 +117,12 @@ async function transfer(transferParams) { roots, ); - // time for a quick sanity check. We expect the number of old commitments, - // new commitments and nullifiers to be equal. - if (nullifiers.length !== oldCommitments.length || nullifiers.length !== newCommitments.length) { + // time for a quick sanity check. We expect the number of old commitments and nullifiers to be equal. + if (nullifiers.length !== oldCommitments.length) { logger.error( - `number of old commitments: ${oldCommitments.length}, number of new commitments: ${newCommitments.length}, number of nullifiers: ${nullifiers.length}`, - ); - throw new Error( - 'Commitment or nullifier numbers are mismatched. There should be equal numbers of each', + `number of old commitments: ${oldCommitments.length}, number of nullifiers: ${nullifiers.length}`, ); + throw new Error('Number of nullifiers and old commitments are mismatched'); } // now we have everything we need to create a Witness and compute a proof diff --git a/nightfall-client/src/services/withdraw.mjs b/nightfall-client/src/services/withdraw.mjs index 7ccafca6e..758582383 100644 --- a/nightfall-client/src/services/withdraw.mjs +++ b/nightfall-client/src/services/withdraw.mjs @@ -18,6 +18,7 @@ import { markNullified, clearPending, getSiblingInfo, + storeCommitment, } from './commitment-storage.mjs'; import getProposersUrl from './peers.mjs'; import { ZkpKeys } from './keys.mjs'; @@ -26,7 +27,7 @@ import { computeWitness } from '../utils/compute-witness.mjs'; const { BN128_GROUP_ORDER, ZOKRATES_WORKER_HOST, PROVING_SCHEME, BACKEND, PROTOCOL, USE_STUBS } = config; const { SHIELD_CONTRACT_NAME } = constants; -const { generalise } = gen; +const { generalise, GN } = gen; const NEXT_N_PROPOSERS = 3; const MAX_WITHDRAW = 5192296858534827628530496329220096n; // 2n**112n @@ -36,14 +37,17 @@ async function withdraw(withdrawParams) { // let's extract the input items const { offchain = false, ...items } = withdrawParams; const { ercAddress, tokenId, value, recipientAddress, rootKey, fee } = generalise(items); - const { compressedZkpPublicKey, nullifierKey } = new ZkpKeys(rootKey); + const { compressedZkpPublicKey, nullifierKey, zkpPublicKey } = new ZkpKeys(rootKey); // the first thing we need to do is to find and input commitment which // will enable us to conduct our withdraw. Let's rummage in the db... - const oldCommitments = - (await findUsableCommitmentsMutex(compressedZkpPublicKey, ercAddress, tokenId, value, true)) || - null; - if (oldCommitments) logger.debug(`Found commitment ${JSON.stringify(oldCommitments, null, 2)}`); + const oldCommitments = await findUsableCommitmentsMutex( + compressedZkpPublicKey, + ercAddress, + tokenId, + value, + ); + if (oldCommitments) logger.debug(`Found commitments ${JSON.stringify(oldCommitments, null, 2)}`); else throw new Error('No suitable commitments were found'); // caller to handle - need to get the user to make some commitments or wait until they've been posted to the blockchain and Timber knows about them // Having found 1 commitment, which is a suitable input to the // proof, the next step is to compute its nullifier; @@ -57,7 +61,6 @@ async function withdraw(withdrawParams) { ); const withdrawValue = value.bigInt > MAX_WITHDRAW ? MAX_WITHDRAW : value.bigInt; const change = totalInputCommitmentValue - withdrawValue; - // if so, add an output commitment to do that // and the Merkle path from the commitment to the root // Commitment Tree Information const commitmentTreeInfo = await Promise.all(oldCommitments.map(c => getSiblingInfo(c))); @@ -70,16 +73,15 @@ async function withdraw(withdrawParams) { logger.silly(`SiblingPath was: ${JSON.stringify(localSiblingPaths)}`); const newCommitment = []; + const salt = await randValueLT(BN128_GROUP_ORDER); if (change !== 0n) { - const salt = await randValueLT(BN128_GROUP_ORDER); newCommitment.push( new Commitment({ ercAddress, tokenId, - value: change, - zkpPublicKey: ZkpKeys.decompressZkpPublicKey(compressedZkpPublicKey), - compressedZkpPublicKey, - salt, + value: new GN(change), + zkpPublicKey, + salt: salt.bigInt, }), ); } @@ -98,7 +100,7 @@ async function withdraw(withdrawParams) { }), ); const privateData = { - rootKey, + rootKey: [rootKey, rootKey], oldCommitmentPreimage: oldCommitments.map(o => { return { value: o.preimage.value, salt: o.preimage.salt }; }), @@ -159,6 +161,10 @@ async function withdraw(withdrawParams) { ); }), ); + // we store the change commitment + if (change !== 0n) { + await storeCommitment(newCommitment[0], nullifierKey); + } // on successful computation of the transaction mark the old commitments as nullified await Promise.all( oldCommitments.map(commitment => markNullified(commitment, optimisticWithdrawTransaction)), @@ -166,16 +172,20 @@ async function withdraw(withdrawParams) { const th = optimisticWithdrawTransaction.transactionHash; delete optimisticWithdrawTransaction.transactionHash; optimisticWithdrawTransaction.transactionHash = th; - return { transaction: optimisticWithdrawTransaction }; + return { transaction: optimisticWithdrawTransaction, salts: [salt.hex(32)] }; } const rawTransaction = await shieldContractInstance.methods .submitTransaction(Transaction.buildSolidityStruct(optimisticWithdrawTransaction)) .encodeABI(); + // we store the change commitment + if (change !== 0n) { + await storeCommitment(newCommitment[0], nullifierKey); + } // on successful computation of the transaction mark the old commitments as nullified await Promise.all( oldCommitments.map(commitment => markNullified(commitment, optimisticWithdrawTransaction)), ); - return { rawTransaction, transaction: optimisticWithdrawTransaction }; + return { rawTransaction, transaction: optimisticWithdrawTransaction, salts: [salt.hex(32)] }; } catch (err) { await Promise.all(oldCommitments.map(commitment => clearPending(commitment))); throw new Error(err); // let the caller handle the error diff --git a/nightfall-client/src/utils/compute-witness.mjs b/nightfall-client/src/utils/compute-witness.mjs index 0ccbd43b9..050f6eaa4 100644 --- a/nightfall-client/src/utils/compute-witness.mjs +++ b/nightfall-client/src/utils/compute-witness.mjs @@ -18,10 +18,10 @@ const padArray = (arr, padWith, n) => { return generalise(arr); }; -const computeWitnessPublic = (tx, rootsNullifiers) => { +const computePublicInputs = (tx, roots) => { const transaction = generalise(tx); - const roots = padArray(generalise(rootsNullifiers), 0, 2); - const publicWitness = [ + const rootsOldCommitments = padArray(generalise(roots), 0, 2); + let publicWitness = [ transaction.value.field(BN128_GROUP_ORDER), transaction.historicRootBlockNumberL2.map(h => h.field(BN128_GROUP_ORDER)), transaction.transactionType.field(BN128_GROUP_ORDER), @@ -32,12 +32,16 @@ const computeWitnessPublic = (tx, rootsNullifiers) => { transaction.commitments.map(c => c.field(BN128_GROUP_ORDER)), transaction.nullifiers.map(n => n.field(BN128_GROUP_ORDER)), transaction.compressedSecrets.map(cs => cs.field(BN128_GROUP_ORDER)), - roots.map(r => r.field(BN128_GROUP_ORDER)), - ].flat(Infinity); - return publicWitness; + ]; + + if (Number(tx.transactionType) !== 0) { + publicWitness = [...publicWitness, rootsOldCommitments.map(r => r.field(BN128_GROUP_ORDER))]; + } + + return publicWitness.flat(Infinity); }; -const computeWitnessEncryption = privateData => { +const computePrivateInputsEncryption = privateData => { const { ephemeralKey, ercAddress, tokenId } = generalise(privateData); return [ ephemeralKey.limbs(32, 8), @@ -46,7 +50,7 @@ const computeWitnessEncryption = privateData => { ].flat(Infinity); }; -const computeWitnessNullifiers = privateData => { +const computePrivateInputsNullifiers = privateData => { const { oldCommitmentPreimage, paths, orders, rootKey } = generalise(privateData); const paddedOldCommitmentPreimage = padArray(oldCommitmentPreimage, NULL_COMMITMENT, 2); const paddedPaths = padArray(paths, new Array(32).fill(0), 2); @@ -62,7 +66,7 @@ const computeWitnessNullifiers = privateData => { ].flat(Infinity); }; -const computeWitnessCommitments = privateData => { +const computePrivateInputsCommitments = privateData => { const { newCommitmentPreimage, recipientPublicKeys } = generalise(privateData); const paddedNewCommitmentPreimage = padArray(newCommitmentPreimage, NULL_COMMITMENT, 2); const paddedRecipientPublicKeys = padArray(recipientPublicKeys, [0, 0], 2); @@ -76,7 +80,7 @@ const computeWitnessCommitments = privateData => { ].flat(Infinity); }; -const computeWitnessPrivateDeposit = privateData => { +const computePrivateInputsDeposit = privateData => { const { salt, recipientPublicKeys } = generalise(privateData); return [ salt.field(BN128_GROUP_ORDER), @@ -88,23 +92,23 @@ const computeWitnessPrivateDeposit = privateData => { }; // eslint-disable-next-line import/prefer-default-export -export const computeWitness = (txObject, rootsNullifiers, privateData) => { - const publicWitness = computeWitnessPublic(txObject, rootsNullifiers); +export const computeWitness = (txObject, roots, privateData) => { + const publicInputs = computePublicInputs(txObject, roots); switch (Number(txObject.transactionType)) { case 0: - return [...publicWitness, ...computeWitnessPrivateDeposit(privateData)]; + return [...publicInputs, ...computePrivateInputsDeposit(privateData)]; case 1: return [ - ...publicWitness, - ...computeWitnessNullifiers(privateData), - ...computeWitnessCommitments(privateData), - ...computeWitnessEncryption(privateData), + ...publicInputs, + ...computePrivateInputsNullifiers(privateData), + ...computePrivateInputsCommitments(privateData), + ...computePrivateInputsEncryption(privateData), ]; default: return [ - ...publicWitness, - ...computeWitnessNullifiers(privateData), - ...computeWitnessCommitments(privateData), + ...publicInputs, + ...computePrivateInputsNullifiers(privateData), + ...computePrivateInputsCommitments(privateData), ]; } }; diff --git a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok index 532d15a8a..8d835d1a4 100644 --- a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok +++ b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok @@ -17,16 +17,18 @@ def main(\ for u32 i in 0..NumCommitments do //Calculate the commmitment hash from the newCommitment parameters - field commitment = poseidon([\ - packedErcAddress,\ - idRemainder,\ - newCommitmentValues[i],\ - ...recipientPublicKey[i],\ - newCommitmentSalts[i]\ - ]) + field commitment = if newCommitmentValues[i] == 0 then 0 else \ + poseidon([\ + packedErcAddress,\ + idRemainder,\ + newCommitmentValues[i],\ + ...recipientPublicKey[i],\ + newCommitmentSalts[i]\ + ])\ + fi //Check that the calculated commitment matches with the one contained in the transaction - assert(newCommitmentValues[i] == 0 || commitment == commitmentHashes[i]) + assert(commitment == commitmentHashes[i]) endfor return true diff --git a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok index d593b8387..c4e925a59 100644 --- a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok +++ b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok @@ -45,13 +45,14 @@ def main(\ ...zkpPublicKeys,\ oldCommitmentSalts[i]\ ]) - field nullifier = poseidon([nullifierKeys, calculatedOldCommitmentHash]) + field nullifier = if(oldCommitmentValues[i] == 0) then 0 else poseidon([nullifierKeys, calculatedOldCommitmentHash]) fi //Check that the calculated nullifier matches with the one contained in the transaction - assert(oldCommitmentValues[i] == 0 || nullifier == nullifierHashes[i]) + assert(nullifier == nullifierHashes[i]) //Check that the nullifier is contained in the tree - assert(oldCommitmentValues[i] == 0 || pathCheck([roots[i], ...paths[i]], orders[i], calculatedOldCommitmentHash)) + bool pathValidity = if(oldCommitmentValues[i] == 0) then true else pathCheck([roots[i], ...paths[i]], orders[i], calculatedOldCommitmentHash) fi + assert(pathValidity) //Set the changeZkpPublicKeys if i = 0. Otherwise just set the same value firstInputZkpPublicKeys = i == 0 ? zkpPublicKeys : firstInputZkpPublicKeys diff --git a/nightfall-deployer/circuits/deposit.zok b/nightfall-deployer/circuits/deposit.zok index 22c69b9b1..79ea66e05 100644 --- a/nightfall-deployer/circuits/deposit.zok +++ b/nightfall-deployer/circuits/deposit.zok @@ -8,7 +8,6 @@ from "./common/generic_circuit/Verifiers/verify_commitments.zok" import main as def main(\ PublicTransaction tx,\ - field[2] nullifierRoots,\ private field salt,\ private Point recipientPublicKey\ )-> (): diff --git a/nightfall-deployer/circuits/deposit_stub.zok b/nightfall-deployer/circuits/deposit_stub.zok index 4b5a4f0be..aac979a4a 100644 --- a/nightfall-deployer/circuits/deposit_stub.zok +++ b/nightfall-deployer/circuits/deposit_stub.zok @@ -6,7 +6,6 @@ from "./common/casts/u8_array_to_field.zok" import main as u8_array_to_field def main(\ PublicTransaction tx,\ - field[2] nullifierRoots,\ private field salt,\ private field[2] recipientPublicKey\ )-> (): diff --git a/nightfall-deployer/circuits/transfer.zok b/nightfall-deployer/circuits/transfer.zok index 03e3177ed..9fcb80ae5 100644 --- a/nightfall-deployer/circuits/transfer.zok +++ b/nightfall-deployer/circuits/transfer.zok @@ -10,7 +10,7 @@ from "./common/generic_circuit/Verifiers/verify_commitments.zok" import main as def main(\ PublicTransaction tx,\ - field[2] nullifierRoots,\ + field[2] roots,\ private Nullifiers<2> nullifiers,\ private Commitments<2> commitments,\ private Transfer transfer\ @@ -44,7 +44,7 @@ def main(\ //Verify nullifiers Point firstInputZkpPublicKeys = verify_nullifiers::<2>(packedErcAddress, idRemainder,\ - tx.nullifiers, nullifierRoots, nullifiersValue, nullifiers.oldCommitments.salt,\ + tx.nullifiers, roots, nullifiersValue, nullifiers.oldCommitments.salt,\ nullifiers.rootKey, nullifiers.paths, nullifiers.orders) //Verify new Commmitments diff --git a/nightfall-deployer/circuits/transfer_stub.zok b/nightfall-deployer/circuits/transfer_stub.zok index e5fc2e681..ea9aa480a 100644 --- a/nightfall-deployer/circuits/transfer_stub.zok +++ b/nightfall-deployer/circuits/transfer_stub.zok @@ -7,7 +7,7 @@ from "./common/casts/u8_array_to_field.zok" import main as u8_array_to_field def main(\ PublicTransaction tx,\ - field[2] nullifierRoots,\ + field[2] roots,\ private Nullifiers<2> nullifiers,\ private Commitments<2> commitments,\ private Transfer transfer\ @@ -17,7 +17,7 @@ def main(\ field[2] commitmentsValue = u8_array_to_field(commitments.newCommitments.value) assert(nullifier_stub::<2>(\ - nullifierRoots, nullifiersValue, nullifiers.oldCommitments.salt,\ + roots, nullifiersValue, nullifiers.oldCommitments.salt,\ nullifiers.rootKey, nullifiers.paths, nullifiers.orders)) assert(commitment_stub::<2>(\ diff --git a/nightfall-deployer/circuits/withdraw.zok b/nightfall-deployer/circuits/withdraw.zok index 78f077a80..a29d814f9 100644 --- a/nightfall-deployer/circuits/withdraw.zok +++ b/nightfall-deployer/circuits/withdraw.zok @@ -9,7 +9,7 @@ from "./common/generic_circuit/Verifiers/verify_commitments.zok" import main as def main(\ PublicTransaction tx,\ - field[2] nullifierRoots,\ + field[2] roots,\ private Nullifiers<2> nullifiers,\ private Commitments<2> commitments\ )-> (): @@ -41,7 +41,7 @@ def main(\ //Verify nullifiers Point firstInputZkpPublicKeys = verify_nullifiers::<2>(packedErcAddress, idRemainder,\ - tx.nullifiers, nullifierRoots, nullifiersValue, nullifiers.oldCommitments.salt,\ + tx.nullifiers, roots, nullifiersValue, nullifiers.oldCommitments.salt,\ nullifiers.rootKey, nullifiers.paths, nullifiers.orders) //Verify new Commmitments diff --git a/nightfall-deployer/circuits/withdraw_stub.zok b/nightfall-deployer/circuits/withdraw_stub.zok index 10aaf2224..e1ee5db98 100644 --- a/nightfall-deployer/circuits/withdraw_stub.zok +++ b/nightfall-deployer/circuits/withdraw_stub.zok @@ -6,7 +6,7 @@ from "./common/casts/u8_array_to_field.zok" import main as u8_array_to_field def main(\ PublicTransaction tx,\ - field[2] nullifierRoots,\ + field[2] roots,\ private Nullifiers<2> nullifiers,\ private Commitments<2> commitments\ )-> (): @@ -15,7 +15,7 @@ def main(\ field[2] commitmentsValue = u8_array_to_field(commitments.newCommitments.value) assert(nullifier_stub::<2>(\ - nullifierRoots, nullifiersValue, nullifiers.oldCommitments.salt,\ + roots, nullifiersValue, nullifiers.oldCommitments.salt,\ nullifiers.rootKey, nullifiers.paths, nullifiers.orders)) assert(commitment_stub::<2>(\ diff --git a/nightfall-deployer/contracts/Challenges.sol b/nightfall-deployer/contracts/Challenges.sol index bc8b077d4..6d284a55b 100644 --- a/nightfall-deployer/contracts/Challenges.sol +++ b/nightfall-deployer/contracts/Challenges.sol @@ -226,14 +226,14 @@ contract Challenges is Stateful, Key_Registry, Config { ); } else if (uint256(transactions[transactionIndex].nullifiers[0]) == 0) { require( - uint256(transactions[transactionIndex].historicRootBlockNumberL2[0]) != 0 || + state.getNumberOfL2Blocks() < + uint256(transactions[transactionIndex].historicRootBlockNumberL2[0]) || uint256(transactions[transactionIndex].historicRootBlockNumberL2[1]) != 0, 'Historic root exists' ); } else { require( - state.getNumberOfL2Blocks() < - uint256(transactions[transactionIndex].historicRootBlockNumberL2[0]) || + uint256(transactions[transactionIndex].historicRootBlockNumberL2[0]) != 0 || uint256(transactions[transactionIndex].historicRootBlockNumberL2[1]) != 0, 'Historic root exists' ); diff --git a/nightfall-deployer/contracts/ChallengesUtil.sol b/nightfall-deployer/contracts/ChallengesUtil.sol index e64d0f2c9..99151402c 100644 --- a/nightfall-deployer/contracts/ChallengesUtil.sol +++ b/nightfall-deployer/contracts/ChallengesUtil.sol @@ -63,7 +63,7 @@ library ChallengesUtil { for (uint256 i = 0; i < proof.length; i++) { proof1[i] = proof[i]; } - uint256[30] memory publicInputs = Utils.getPublicInputs(transaction, roots); + uint256[] memory publicInputs = Utils.getPublicInputs(transaction, roots); require(!Verifier.verify(proof1, publicInputs, vk), 'This proof appears to be valid'); } diff --git a/nightfall-deployer/contracts/Shield.sol b/nightfall-deployer/contracts/Shield.sol index 93d69caeb..cf883c81d 100644 --- a/nightfall-deployer/contracts/Shield.sol +++ b/nightfall-deployer/contracts/Shield.sol @@ -216,8 +216,7 @@ contract Shield is Stateful, Config, Key_Registry, ReentrancyGuardUpgradeable, P bytes32[6] memory siblingPath ) external payable nonReentrant { // The transaction is a withdrawal transaction - require(t.transactionType == TransactionTypes.WITHDRAW, - 'Can only advance withdrawals'); + require(t.transactionType == TransactionTypes.WITHDRAW, 'Can only advance withdrawals'); // check this block is a real one, in the queue, not something made up. state.areBlockAndTransactionReal(b, t, index, siblingPath); @@ -275,7 +274,10 @@ contract Shield is Stateful, Config, Key_Registry, ReentrancyGuardUpgradeable, P function payIn(Transaction memory t) internal { // check the address fits in 160 bits. This is so we can't overflow the circuit uint256 addrNum = uint256(t.ercAddress); - require (addrNum < 0x010000000000000000000000000000000000000000, 'The given address is more than 160 bits'); + require( + addrNum < 0x010000000000000000000000000000000000000000, + 'The given address is more than 160 bits' + ); address addr = address(uint160(addrNum)); if (t.tokenType == TokenType.ERC20) { diff --git a/nightfall-deployer/contracts/State.sol b/nightfall-deployer/contracts/State.sol index 1ea97a9b2..b82304f47 100644 --- a/nightfall-deployer/contracts/State.sol +++ b/nightfall-deployer/contracts/State.sol @@ -27,10 +27,10 @@ contract State is Initializable, ReentrancyGuardUpgradeable, Pausable, Config { address public challengesAddress; address public shieldAddress; - function initialize() public override(Pausable, Config){ - Pausable.initialize(); - Config.initialize(); - ReentrancyGuardUpgradeable.__ReentrancyGuard_init(); + function initialize() public override(Pausable, Config) { + Pausable.initialize(); + Config.initialize(); + ReentrancyGuardUpgradeable.__ReentrancyGuard_init(); } function initialize( @@ -42,7 +42,7 @@ contract State is Initializable, ReentrancyGuardUpgradeable, Pausable, Config { challengesAddress = _challengesAddress; shieldAddress = _shieldAddress; initialize(); - } + } modifier onlyRegistered { require( diff --git a/nightfall-deployer/contracts/Utils.sol b/nightfall-deployer/contracts/Utils.sol index e35b5bbfd..55db2a491 100644 --- a/nightfall-deployer/contracts/Utils.sol +++ b/nightfall-deployer/contracts/Utils.sol @@ -102,7 +102,7 @@ library Utils { function getPublicInputs(Structures.Transaction calldata ts, uint256[2] memory roots) internal pure - returns (uint256[30] memory inputs) + returns (uint256[] memory inputs) { inputs[0] = uint256(ts.value); inputs[1] = uint256(ts.historicRootBlockNumberL2[0]); @@ -132,8 +132,11 @@ library Utils { inputs[25] = uint256(ts.nullifiers[1]); inputs[26] = uint256(ts.compressedSecrets[0]); inputs[27] = uint256(ts.compressedSecrets[1]); - inputs[28] = roots[0]; - inputs[29] = roots[1]; + + if (uint256(ts.transactionType) != 0) { + inputs[28] = uint256(roots[0]); + inputs[29] = uint256(roots[1]); + } } function calculateMerkleRoot(bytes32[] memory leaves) public pure returns (bytes32 result) { diff --git a/nightfall-deployer/contracts/Verifier.sol b/nightfall-deployer/contracts/Verifier.sol index 88c6c4ed6..5a0c6255a 100644 --- a/nightfall-deployer/contracts/Verifier.sol +++ b/nightfall-deployer/contracts/Verifier.sol @@ -52,7 +52,7 @@ library Verifier { function verify( uint256[] memory _proof, - uint256[30] memory _publicInputs, + uint256[] memory _publicInputs, uint256[] memory _vk ) public returns (bool result) { if (verificationCalculation(_proof, _publicInputs, _vk) == 0) { @@ -64,7 +64,7 @@ library Verifier { function verificationCalculation( uint256[] memory _proof, - uint256[30] memory _publicInputs, + uint256[] memory _publicInputs, uint256[] memory _vk ) public returns (uint256) { Proof_G16 memory proof; diff --git a/nightfall-optimist/src/services/transaction-checker.mjs b/nightfall-optimist/src/services/transaction-checker.mjs index 22aada490..98c9d8fdf 100644 --- a/nightfall-optimist/src/services/transaction-checker.mjs +++ b/nightfall-optimist/src/services/transaction-checker.mjs @@ -88,11 +88,14 @@ async function verifyProof(transaction) { transaction.commitments, transaction.nullifiers, transaction.compressedSecrets, - historicRootFirst.root, - historicRootSecond.root, ].flat(Infinity), ).all.hex(32); + if (Number(transaction.transactionType) !== 0) { + inputs.push(generalise(historicRootFirst.root).hex(32)); + inputs.push(generalise(historicRootSecond.root).hex(32)); + } + if ( isOverflow(transaction.ercAddress, MAX_PUBLIC_VALUES.ERCADDRESS) || isOverflow(historicRootFirst.root, BN128_GROUP_ORDER) || diff --git a/package.json b/package.json index 68a44b76c..662a4e037 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "neg-test-ropsten": "mocha --timeout 0 --bail --exit test/neg-http.mjs", "test-e2e-protocol": "LOG_LEVEL=error mocha --timeout 0 --bail --exit test/e2e/protocol/*.test.mjs ", "test-gas": "mocha --timeout 0 --bail --exit test/e2e/gas.test.mjs ", + "test-circuits": "LOG_LEVEL=error mocha --timeout 0 --bail --exit test/e2e/circuits.test.mjs ", "test-e2e-tokens": "LOG_LEVEL=error mocha --timeout 0 --bail --exit test/e2e/tokens/*.test.mjs ", "test-erc20-tokens": "LOG_LEVEL=error mocha --timeout 0 --bail --exit test/e2e/tokens/erc20.test.mjs ", "test-erc721-tokens": "LOG_LEVEL=error mocha --timeout 0 --bail --exit test/e2e/tokens/erc721.test.mjs ", diff --git a/test/e2e/circuits.test.mjs b/test/e2e/circuits.test.mjs new file mode 100644 index 000000000..54b2b38e7 --- /dev/null +++ b/test/e2e/circuits.test.mjs @@ -0,0 +1,234 @@ +/* eslint-disable no-await-in-loop */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import chaiAsPromised from 'chai-as-promised'; +import config from 'config'; +import logger from 'common-files/utils/logger.mjs'; +import Nf3 from '../../cli/lib/nf3.mjs'; +import { expectTransaction, depositNTransactions, Web3Client } from '../utils.mjs'; + +// so we can use require with mjs file +const { expect } = chai; +chai.use(chaiHttp); +chai.use(chaiAsPromised); + +const environment = config.ENVIRONMENTS[process.env.ENVIRONMENT] || config.ENVIRONMENTS.localhost; + +const { + fee, + txPerBlock, + tokenConfigs: { tokenType, tokenId }, + mnemonics, + signingKeys, +} = config.TEST_OPTIONS; + +const nf3Users = [new Nf3(signingKeys.user1, environment)]; +const nf3Proposer = new Nf3(signingKeys.proposer1, environment); + +const web3Client = new Web3Client(); + +let erc20Address; +let stateAddress; +let eventLogs = []; + +/* + This function tries to zero the number of unprocessed transactions in the optimist node + that nf3 is connected to. We call it extensively on the tests, as we want to query stuff from the + L2 layer, which is dependent on a block being made. We also need 0 unprocessed transactions by the end + of the tests, otherwise the optimist will become out of sync with the L2 block count on-chain. +*/ +describe('General Circuit Test', () => { + before(async () => { + await nf3Proposer.init(mnemonics.proposer); + // we must set the URL from the point of view of the client container + await nf3Proposer.registerProposer('http://optimist1'); + + // Proposer listening for incoming events + const newGasBlockEmitter = await nf3Proposer.startProposer(); + newGasBlockEmitter.on('gascost', async gasUsed => { + logger.debug( + `Block proposal gas cost was ${gasUsed}, cost per transaction was ${gasUsed / txPerBlock}`, + ); + }); + + await nf3Users[0].init(mnemonics.user1); + erc20Address = await nf3Users[0].getContractAddress('ERC20Mock'); + + stateAddress = await nf3Users[0].stateContractAddress; + web3Client.subscribeTo('logs', eventLogs, { address: stateAddress }); + + await nf3Users[0].makeBlockNow(); + }); + + it('Test that all circuits are working', async () => { + async function getBalance() { + return (await nf3Users[0].getLayer2Balances())[erc20Address]?.[0].balance || 0; + } + + logger.debug(`Sending 1 deposit of 10...`); + await depositNTransactions(nf3Users[0], 1, erc20Address, tokenType, 10, tokenId, fee); + await nf3Users[0].makeBlockNow(); + + // Wait until we see the right number of blocks appear + eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + + // Deposit checks here + + logger.debug(`Sending single transfer with no change...`); + const singleTransferNoChange = await nf3Users[0].transfer( + false, + erc20Address, + tokenType, + 10, + tokenId, + nf3Users[0].zkpKeys.compressedZkpPublicKey, + fee, + ); + expectTransaction(singleTransferNoChange); + + await nf3Users[0].makeBlockNow(); + + eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + + // Single Transfer No Change checks here + + logger.debug(`Sending single transfer with change...`); + const singleTransferChange = await nf3Users[0].transfer( + false, + erc20Address, + tokenType, + 5, + tokenId, + nf3Users[0].zkpKeys.compressedZkpPublicKey, + fee, + ); + expectTransaction(singleTransferChange); + + await nf3Users[0].makeBlockNow(); + + eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + + logger.debug(`Sending withdrawal with no change...`); + const withdrawalNoChange = await nf3Users[0].withdraw( + false, + erc20Address, + tokenType, + 5, + tokenId, + nf3Users[0].ethereumAddress, + ); + + expectTransaction(withdrawalNoChange); + + await nf3Users[0].makeBlockNow(); + + eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + // Withdrawal No Change checks here + + logger.debug(`Sending withdrawal with change...`); + const withdrawalChange = await nf3Users[0].withdraw( + false, + erc20Address, + tokenType, + 2, + tokenId, + nf3Users[0].ethereumAddress, + ); + + expectTransaction(withdrawalChange); + + await nf3Users[0].makeBlockNow(); + + eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + // Withdrawal Change checks here + + logger.debug(`Sending deposit of 8...`); + await depositNTransactions(nf3Users[0], 1, erc20Address, tokenType, 8, tokenId, fee); + await nf3Users[0].makeBlockNow(); + eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + + logger.debug(`Sending double transfer with change...`); + const doubleTransferChange = await nf3Users[0].transfer( + false, + erc20Address, + tokenType, + 9, + tokenId, + nf3Users[0].zkpKeys.compressedZkpPublicKey, + fee, + ); + + expectTransaction(doubleTransferChange); + + await nf3Users[0].makeBlockNow(); + + eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + // Double transfer Change checks here + + logger.debug(`Sending double transfer with no change...`); + const doubleTransferNoChange = await nf3Users[0].transfer( + false, + erc20Address, + tokenType, + 11, + tokenId, + nf3Users[0].zkpKeys.compressedZkpPublicKey, + fee, + ); + + expectTransaction(doubleTransferNoChange); + + await nf3Users[0].makeBlockNow(); + + eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + // Double transfer No Change checks here + + logger.debug(`Sending deposit of 4...`); + await depositNTransactions(nf3Users[0], 1, erc20Address, tokenType, 4, tokenId, fee); + await nf3Users[0].makeBlockNow(); + eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + + logger.debug(`Sending double withdrawal with change...`); + const doubleWithdrawalChange = await nf3Users[0].withdraw( + false, + erc20Address, + tokenType, + 12, + tokenId, + nf3Users[0].ethereumAddress, + ); + + expectTransaction(doubleWithdrawalChange); + + await nf3Users[0].makeBlockNow(); + + eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + // Double Withdrawal Change checks here + + logger.debug(`Sending deposit of 2...`); + await depositNTransactions(nf3Users[0], 1, erc20Address, tokenType, 2, tokenId, fee); + await nf3Users[0].makeBlockNow(); + + eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + + logger.debug(`Sending double Withdrawal with no change...`); + const doubleWithdrawalNoChange = await nf3Users[0].withdraw( + false, + erc20Address, + tokenType, + 5, + tokenId, + nf3Users[0].ethereumAddress, + ); + + expectTransaction(doubleWithdrawalNoChange); + + await nf3Users[0].makeBlockNow(); + + eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); + // Double Withdrawal No Change + + const finalBalance = await getBalance(); + expect(finalBalance).to.be.equal(0); + }); +}); diff --git a/wallet/src/hooks/User/index.jsx b/wallet/src/hooks/User/index.jsx index dd3c784db..4f18b6671 100644 --- a/wallet/src/hooks/User/index.jsx +++ b/wallet/src/hooks/User/index.jsx @@ -143,14 +143,8 @@ export const UserProvider = ({ children }) => { useInterval( async () => { const circuitName = USE_STUBS - ? [ - 'deposit_stub', - 'single_transfer_stub', - 'double_transfer_stub', - 'withdraw_stub', - 'withdraw_change_stub', - ] - : ['deposit', 'single_transfer', 'double_transfer', 'withdraw', 'withdraw_change']; + ? ['deposit_stub', 'transfer_stub', 'withdraw_stub'] + : ['deposit', 'transfer', 'withdraw']; const circuitCheck = await Promise.all(circuitName.map(c => checkIndexDBForCircuit(c))); console.log('Circuit Check', circuitCheck); From 508840471b6030d98b3748b570c4ebb5bf8226b8 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Mon, 1 Aug 2022 12:51:22 +0200 Subject: [PATCH 05/15] fix: review changes applied --- nightfall-client/src/routes/transfer.mjs | 4 +-- nightfall-client/src/services/transfer.mjs | 2 -- nightfall-client/src/services/withdraw.mjs | 4 +-- nightfall-deployer/contracts/Challenges.sol | 18 ++++++---- .../contracts/ChallengesUtil.sol | 7 +--- .../src/services/challenges.mjs | 33 ++++++------------- .../src/services/transaction-checker.mjs | 6 +--- 7 files changed, 28 insertions(+), 46 deletions(-) diff --git a/nightfall-client/src/routes/transfer.mjs b/nightfall-client/src/routes/transfer.mjs index ed8a9530f..f8e83f0ed 100644 --- a/nightfall-client/src/routes/transfer.mjs +++ b/nightfall-client/src/routes/transfer.mjs @@ -10,10 +10,10 @@ const router = express.Router(); router.post('/', async (req, res, next) => { logger.debug(`transfer endpoint received POST ${JSON.stringify(req.body, null, 2)}`); try { - const { rawTransaction: txDataToSign, transaction, salts } = await transfer(req.body); + const { rawTransaction: txDataToSign, transaction } = await transfer(req.body); logger.debug('returning raw transaction'); logger.silly(` raw transaction is ${JSON.stringify(txDataToSign, null, 2)}`); - res.json({ txDataToSign, transaction, salts }); + res.json({ txDataToSign, transaction }); } catch (err) { logger.error(err); if (err.message.includes('No suitable commitments')) { diff --git a/nightfall-client/src/services/transfer.mjs b/nightfall-client/src/services/transfer.mjs index 1ccc26bca..cf146a04b 100644 --- a/nightfall-client/src/services/transfer.mjs +++ b/nightfall-client/src/services/transfer.mjs @@ -221,7 +221,6 @@ async function transfer(transferParams) { ); return { transaction: optimisticTransferTransaction, - salts: salts.map(salt => salt.hex(32)), }; } const rawTransaction = await shieldContractInstance.methods @@ -240,7 +239,6 @@ async function transfer(transferParams) { return { rawTransaction, transaction: optimisticTransferTransaction, - salts: salts.map(salt => salt.hex(32)), }; } catch (err) { await Promise.all(oldCommitments.map(commitment => clearPending(commitment))); diff --git a/nightfall-client/src/services/withdraw.mjs b/nightfall-client/src/services/withdraw.mjs index 758582383..6ec8a343a 100644 --- a/nightfall-client/src/services/withdraw.mjs +++ b/nightfall-client/src/services/withdraw.mjs @@ -172,7 +172,7 @@ async function withdraw(withdrawParams) { const th = optimisticWithdrawTransaction.transactionHash; delete optimisticWithdrawTransaction.transactionHash; optimisticWithdrawTransaction.transactionHash = th; - return { transaction: optimisticWithdrawTransaction, salts: [salt.hex(32)] }; + return { transaction: optimisticWithdrawTransaction }; } const rawTransaction = await shieldContractInstance.methods .submitTransaction(Transaction.buildSolidityStruct(optimisticWithdrawTransaction)) @@ -185,7 +185,7 @@ async function withdraw(withdrawParams) { await Promise.all( oldCommitments.map(commitment => markNullified(commitment, optimisticWithdrawTransaction)), ); - return { rawTransaction, transaction: optimisticWithdrawTransaction, salts: [salt.hex(32)] }; + return { rawTransaction, transaction: optimisticWithdrawTransaction }; } catch (err) { await Promise.all(oldCommitments.map(commitment => clearPending(commitment))); throw new Error(err); // let the caller handle the error diff --git a/nightfall-deployer/contracts/Challenges.sol b/nightfall-deployer/contracts/Challenges.sol index 6d284a55b..7e4692706 100644 --- a/nightfall-deployer/contracts/Challenges.sol +++ b/nightfall-deployer/contracts/Challenges.sol @@ -34,7 +34,8 @@ contract Challenges is Stateful, Key_Registry, Config { Block memory priorBlockL2, // the block immediately prior to this one Transaction[] memory priorBlockTransactions, // the transactions in the prior block Block memory blockL2, - Transaction[] memory transactions + Transaction[] memory transactions, + bytes32 salt ) external onlyBootChallenger { checkCommit(msg.data); // check if the block hash is correct and the block hash exists for the block and prior block. Also if the transactions are part of these block @@ -60,7 +61,8 @@ contract Challenges is Stateful, Key_Registry, Config { Transaction[] memory priorBlockTransactions, // the transactions in the prior block bytes32[33] calldata frontierPriorBlock, // frontier path before prior block is added. The same frontier used in calculating root when prior block is added Block memory blockL2, - Transaction[] memory transactions + Transaction[] memory transactions, + bytes32 salt ) external onlyBootChallenger { checkCommit(msg.data); // check if the block hash is correct and the block hash exists for the block and prior block @@ -88,7 +90,8 @@ contract Challenges is Stateful, Key_Registry, Config { Transaction[] memory transactions1, Transaction[] memory transactions2, uint256 transactionIndex1, - uint256 transactionIndex2 + uint256 transactionIndex2, + bytes32 salt ) external onlyBootChallenger { checkCommit(msg.data); // first, check we have real, in-train, contiguous blocks @@ -117,7 +120,8 @@ contract Challenges is Stateful, Key_Registry, Config { uint256 transactionIndex, Block[2] calldata blockL2ContainingHistoricRoot, Transaction[][2] memory transactionsOfblockL2ContainingHistoricRoot, - uint256[8] memory uncompressedProof + uint256[8] memory uncompressedProof, + bytes32 salt ) external onlyBootChallenger { checkCommit(msg.data); state.areBlockAndTransactionsReal(blockL2, transactions); @@ -179,7 +183,8 @@ contract Challenges is Stateful, Key_Registry, Config { Block memory block2, Transaction[] memory txs2, uint256 transactionIndex2, - uint256 nullifierIndex2 + uint256 nullifierIndex2, + bytes32 salt ) external onlyBootChallenger { checkCommit(msg.data); state.areBlockAndTransactionsReal(block1, txs1); @@ -209,7 +214,8 @@ contract Challenges is Stateful, Key_Registry, Config { function challengeHistoricRoot( Block memory blockL2, Transaction[] memory transactions, - uint256 transactionIndex + uint256 transactionIndex, + bytes32 salt ) external onlyBootChallenger { checkCommit(msg.data); state.areBlockAndTransactionsReal(blockL2, transactions); diff --git a/nightfall-deployer/contracts/ChallengesUtil.sol b/nightfall-deployer/contracts/ChallengesUtil.sol index 99151402c..e2708689f 100644 --- a/nightfall-deployer/contracts/ChallengesUtil.sol +++ b/nightfall-deployer/contracts/ChallengesUtil.sol @@ -87,14 +87,9 @@ library ChallengesUtil { require( (transaction.transactionType != Structures.TransactionTypes.TRANSFER && uint256(transaction.recipientAddress) <= MAX20) || - (transaction.transactionType == Structures.TransactionTypes.TRANSFER && - uint256(transaction.recipientAddress) <= MAX31), + (transaction.transactionType == Structures.TransactionTypes.TRANSFER), 'Recipient ERC address out of range' ); - require(uint256(transaction.commitments[0]) <= MAX31, 'Commitment 0 out of range'); - require(uint256(transaction.commitments[1]) <= MAX31, 'Commitment 1 out of range'); - require(uint256(transaction.nullifiers[0]) <= MAX31, 'Nullifier 0 out of range'); - require(uint256(transaction.nullifiers[1]) <= MAX31, 'Nullifier 1 out of range'); require(uint256(blockL2.root) < BN128_GROUP_ORDER, 'root out of range'); } diff --git a/nightfall-optimist/src/services/challenges.mjs b/nightfall-optimist/src/services/challenges.mjs index 545f20784..5bbe0fe3a 100644 --- a/nightfall-optimist/src/services/challenges.mjs +++ b/nightfall-optimist/src/services/challenges.mjs @@ -4,6 +4,7 @@ import logger from 'common-files/utils/logger.mjs'; import Web3 from 'common-files/utils/web3.mjs'; import { getContractInstance } from 'common-files/utils/contract.mjs'; import constants from 'common-files/constants/index.mjs'; +import { rand } from 'common-files/utils/crypto/crypto-random.mjs'; import { getBlockByBlockHash, getBlockByTransactionHash, @@ -82,6 +83,7 @@ export async function createChallenge(block, transactions, err) { let txDataToSign; if (makeChallenges) { const challengeContractInstance = await getContractInstance(CHALLENGES_CONTRACT_NAME); + const salt = (await rand(32)).hex(32); switch (err.code) { // Challenge wrong root case 0: { @@ -116,6 +118,7 @@ export async function createChallenge(block, transactions, err) { frontierToValidatePreviousBlock, Block.buildSolidityStruct(block), transactions.map(t => Transaction.buildSolidityStruct(t)), + salt, ) .encodeABI(); break; @@ -144,6 +147,7 @@ export async function createChallenge(block, transactions, err) { transactions2.map(t => Transaction.buildSolidityStruct(t)), transactionIndex1, // index of duplicate transaction in block transactionIndex2, + salt, ) .encodeABI(); break; @@ -157,6 +161,7 @@ export async function createChallenge(block, transactions, err) { Block.buildSolidityStruct(block), transactions.map(t => Transaction.buildSolidityStruct(t)), transactionIndex, + salt, ) .encodeABI(); break; @@ -167,8 +172,8 @@ export async function createChallenge(block, transactions, err) { // Create a challenge const uncompressedProof = transactions[transactionIndex].proof; const [historicInput1, historicInput2] = await Promise.all( - transactions[transactionIndex].historicRootBlockNumberL2.map(async b => { - if (b === 0) { + transactions[transactionIndex].historicRootBlockNumberL2.map(async (b, i) => { + if (transactions[transactionIndex].nullifiers[i] === 0) { return { historicBlock: {}, historicTxs: [], @@ -183,22 +188,6 @@ export async function createChallenge(block, transactions, err) { }), ); - const [historicInputFee1, historicInputFee2] = await Promise.all( - transactions[transactionIndex].historicRootBlockNumberL2Fee.map(async b => { - if (b === 0) { - return { - historicBlock: {}, - historicTxs: [], - }; - } - const historicBlock = await getBlockByBlockNumberL2(b); - const historicTxs = await getTransactionsByTransactionHashes(block.transactionHashes); - return { - historicBlock: Block.buildSolidityStruct(historicBlock), - historicTxs, - }; - }), - ); txDataToSign = await challengeContractInstance.methods .challengeProofVerification( Block.buildSolidityStruct(block), @@ -209,12 +198,8 @@ export async function createChallenge(block, transactions, err) { historicInput1.historicTxs.map(t => Transaction.buildSolidityStruct(t)), historicInput2.historicTxs.map(t => Transaction.buildSolidityStruct(t)), ], - [historicInputFee1.historicBlock, historicInputFee2.historicBlock], - [ - historicInputFee1.historicTxs.map(t => Transaction.buildSolidityStruct(t)), - historicInputFee2.historicTxs.map(t => Transaction.buildSolidityStruct(t)), - ], uncompressedProof, + salt, ) .encodeABI(); break; @@ -257,6 +242,7 @@ export async function createChallenge(block, transactions, err) { oldBlockTransactions.map(t => Transaction.buildSolidityStruct(t)), oldTxIdx, oldNullifierIdx, + salt, ) .encodeABI(); } @@ -274,6 +260,7 @@ export async function createChallenge(block, transactions, err) { priorBlockTransactions.map(t => Transaction.buildSolidityStruct(t)), // the transactions in the prior block Block.buildSolidityStruct(block), transactions.map(t => Transaction.buildSolidityStruct(t)), + salt, ) .encodeABI(); break; diff --git a/nightfall-optimist/src/services/transaction-checker.mjs b/nightfall-optimist/src/services/transaction-checker.mjs index 98c9d8fdf..eeeb9e27a 100644 --- a/nightfall-optimist/src/services/transaction-checker.mjs +++ b/nightfall-optimist/src/services/transaction-checker.mjs @@ -46,7 +46,7 @@ async function checkHistoricRoot(transaction) { if (historicRootFirst === null) throw new TransactionError('The historic root in the transaction does not exist', 3); } - if (Number(transaction.nullifiers[0]) !== 0) { + if (Number(transaction.nullifiers[1]) !== 0) { const historicRootSecond = await getBlockByBlockNumberL2( transaction.historicRootBlockNumberL2[1], ); @@ -72,10 +72,6 @@ async function verifyProof(transaction) { ? { root: ZERO } : (await getBlockByBlockNumberL2(transaction.historicRootBlockNumberL2[1])) ?? { root: ZERO }; - if (![0, 1, 2].includes(Number(transaction.transactionType))) { - throw new TransactionError('Unknown transaction type', 2); - } - const inputs = generalise( [ transaction.value, From acbbf2d058fcce52937577b4ddbf7757f07af355 Mon Sep 17 00:00:00 2001 From: Ilyas Ridhuan Date: Mon, 1 Aug 2022 14:36:37 +0100 Subject: [PATCH 06/15] chore: cleanup --- .../circuits/common/merkle-tree/path-check.zok | 1 - nightfall-optimist/src/routes/proposer.mjs | 4 +--- zokrates-worker/src/index.mjs | 8 +------- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/nightfall-deployer/circuits/common/merkle-tree/path-check.zok b/nightfall-deployer/circuits/common/merkle-tree/path-check.zok index 102fd746e..93fca12f7 100644 --- a/nightfall-deployer/circuits/common/merkle-tree/path-check.zok +++ b/nightfall-deployer/circuits/common/merkle-tree/path-check.zok @@ -1,7 +1,6 @@ from "hashes/poseidon/poseidon.zok" import main as poseidon import "utils/pack/bool/unpack128.zok" as unpack128 - def orderFields(bool order, field pathNode, field siblingNode)->(field[2]): field right = if order then pathNode else siblingNode fi field left = if order then siblingNode else pathNode fi diff --git a/nightfall-optimist/src/routes/proposer.mjs b/nightfall-optimist/src/routes/proposer.mjs index 5fe86fde6..b087ab2ef 100644 --- a/nightfall-optimist/src/routes/proposer.mjs +++ b/nightfall-optimist/src/routes/proposer.mjs @@ -365,9 +365,7 @@ router.post('/offchain-transaction', async (req, res) => { try { switch (Number(transactionType)) { case 1: - case 2: - case 3: - case 4: { + case 2: { // When comparing this with getTransactionSubmittedCalldata, // note we dont need to decompressProof as proofs are only compressed if they go on-chain. // let's not directly call transactionSubmittedEventHandler, instead, we'll queue it diff --git a/zokrates-worker/src/index.mjs b/zokrates-worker/src/index.mjs index 0ea26d66f..12a244e5f 100644 --- a/zokrates-worker/src/index.mjs +++ b/zokrates-worker/src/index.mjs @@ -33,13 +33,7 @@ const checkCircuitsOutput = async () => { : `${DEFAULT_CIRCUIT_FILES_URL}/${env}`; const url = `${baseUrl}/proving_files/hash.txt`; const outputPath = `./output`; - const circuits = [ - 'deposit', - 'double_transfer', - 'single_transfer', - 'withdraw', - 'withdraw_change', - ]; + const circuits = ['deposit', 'transfer', 'withdraw']; const res = await axios.get(url); // get all circuit files const files = res.data.split('\n'); From b3f6b16d653cc9e6322ec87c734bbd9dbbacbf74 Mon Sep 17 00:00:00 2001 From: Ilyas Ridhuan Date: Tue, 2 Aug 2022 22:44:58 +0100 Subject: [PATCH 07/15] fix: erc721 checks --- .../Verifiers/verify_commitments.zok | 27 ++++++++++----- .../Verifiers/verify_nullifiers.zok | 34 +++++++++++-------- nightfall-deployer/circuits/deposit.zok | 2 +- nightfall-deployer/circuits/transfer.zok | 4 +-- nightfall-deployer/circuits/withdraw.zok | 10 +++--- 5 files changed, 46 insertions(+), 31 deletions(-) diff --git a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok index 8d835d1a4..7cf7fdb9b 100644 --- a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok +++ b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok @@ -4,27 +4,36 @@ from "../../utils/structures.zok" import Point /* * Verify that all commitments are correct */ -def main(\ +def main(\ field packedErcAddress,\ field idRemainder,\ - field[NumCommitments] commitmentHashes,\ - private field[NumCommitments] newCommitmentValues,\ - private field[NumCommitments] newCommitmentSalts,\ - private Point[NumCommitments] recipientPublicKey\ + field[MaxCommitments] commitmentHashes,\ + private field[MaxCommitments] newCommitmentValues,\ + private field[MaxCommitments] newCommitmentSalts,\ + private Point[MaxCommitments] recipientPublicKey\ ) -> bool: //Check that all the commitments are valid. If NumCommitments equals zero this loop will be ignored - for u32 i in 0..NumCommitments do + for u32 i in 0..MaxCommitments do - //Calculate the commmitment hash from the newCommitment parameters - field commitment = if newCommitmentValues[i] == 0 then 0 else \ + /* + Calculate the commmitment hash from the newCommitment parameters + We can check if commitment is supposed to be null or non-null by the following + 1) Note that degenerate case for each circuit type (deposit, transfer, withdraw) also matches the possible uses involving erc721 + 2) Thus, we first perform checks based on this degenerate case (characterised by MinCommitments). + 3) We use (i + 1) as the loop is zero-indexed by MinCommitments is a count of minimum allowable commitments. + 4) Therefore we check (with a poseidon hash) any input when the incremented loop index matches this conditional. + 5) Finally, we check the "optional extra commitments" that are only allowable to erc20/1155 (characterised by value > 0) + */ + + field commitment = if (newCommitmentValues[i] != 0 || (i+1) == MinCommitments) then\ poseidon([\ packedErcAddress,\ idRemainder,\ newCommitmentValues[i],\ ...recipientPublicKey[i],\ newCommitmentSalts[i]\ - ])\ + ]) else 0\ fi //Check that the calculated commitment matches with the one contained in the transaction diff --git a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok index c4e925a59..d79164f64 100644 --- a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok +++ b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok @@ -8,16 +8,16 @@ from "../../merkle-tree/path-check.zok" import main as pathCheck /* * Verify that all the nullifiers are correct */ -def main(\ +def main(\ field packedErcAddress,\ field idRemainder,\ - field[NumNullifiers] nullifierHashes,\ - field[NumNullifiers] roots,\ - private field[NumNullifiers] oldCommitmentValues,\ - private field[NumNullifiers] oldCommitmentSalts,\ - private field[NumNullifiers] rootKey,\ - private field[NumNullifiers][32] paths,\ - private field[NumNullifiers] orders\ + field[MaxNullifiers] nullifierHashes,\ + field[MaxNullifiers] roots,\ + private field[MaxNullifiers] oldCommitmentValues,\ + private field[MaxNullifiers] oldCommitmentSalts,\ + private field[MaxNullifiers] rootKey,\ + private field[MaxNullifiers][32] paths,\ + private field[MaxNullifiers] orders\ ) -> (Point): // Get Curve Params BabyJubJubParams context = curveParams() @@ -29,7 +29,7 @@ def main(\ Point firstInputZkpPublicKeys = [0,0] //Check that all the nullifiers are valid. If NumNullifiers equals zero this loop will be ignored - for u32 i in 0..NumNullifiers do + for u32 i in 0..MaxNullifiers do // Calculation of zkpPrivateKey and nullifierKey from rootKey field zkpPrivateKeys = poseidon([rootKey[i], PRIVATE_KEY_DOMAIN]) field nullifierKeys = poseidon([rootKey[i], NULLIFIER_KEY_DOMAIN]) @@ -37,7 +37,15 @@ def main(\ // Calculate zkpPublicKey Point zkpPublicKeys = scalarMult(field_to_bool_256(zkpPrivateKeys), g, context) - //Calculate the nullifier hash from the oldCommitment parameters + /* + Calculate the nullifier hash from the oldCommitment parameters + We can check if nullifier is supposed to be null or non-null by the following + 1) Note that degenerate case for each circuit type (deposit, transfer, withdraw) also matches the possible uses involving erc721 + 2) Thus, we first perform checks based on this degenerate case (characterised by MinNullifiers). + 3) We use (i + 1) as the loop is zero-indexed by MinNullifiers is a count of minimum allowable nullifiers. + 4) Therefore we check (with a poseidon hash/path check) any input when the incremented loop index matches this conditional. + 5) Finally, we check the "optional extra nullifiers" that are only allowable to erc20/1155 (characterised by value > 0) + */ field calculatedOldCommitmentHash = poseidon([\ packedErcAddress,\ idRemainder,\ @@ -45,17 +53,15 @@ def main(\ ...zkpPublicKeys,\ oldCommitmentSalts[i]\ ]) - field nullifier = if(oldCommitmentValues[i] == 0) then 0 else poseidon([nullifierKeys, calculatedOldCommitmentHash]) fi + field nullifier = if(oldCommitmentValues[i] != 0 || (i+1) != MinNullifiers) then poseidon([nullifierKeys, calculatedOldCommitmentHash]) else 0 fi //Check that the calculated nullifier matches with the one contained in the transaction assert(nullifier == nullifierHashes[i]) //Check that the nullifier is contained in the tree - bool pathValidity = if(oldCommitmentValues[i] == 0) then true else pathCheck([roots[i], ...paths[i]], orders[i], calculatedOldCommitmentHash) fi + bool pathValidity = if(oldCommitmentValues[i] != 0 || (i+1) != MinNullifiers) then pathCheck([roots[i], ...paths[i]], orders[i], calculatedOldCommitmentHash) else true fi assert(pathValidity) - //Set the changeZkpPublicKeys if i = 0. Otherwise just set the same value - firstInputZkpPublicKeys = i == 0 ? zkpPublicKeys : firstInputZkpPublicKeys endfor return firstInputZkpPublicKeys diff --git a/nightfall-deployer/circuits/deposit.zok b/nightfall-deployer/circuits/deposit.zok index 79ea66e05..c23ab0b6b 100644 --- a/nightfall-deployer/circuits/deposit.zok +++ b/nightfall-deployer/circuits/deposit.zok @@ -32,7 +32,7 @@ def main(\ field packedErcAddress = tx.ercAddress + u32_array_to_field([tx.tokenId[0]]) * SHIFT //Verify new Commmitments - assert(verify_commitments::<1>(packedErcAddress, idRemainder, [tx.commitments[0]],\ + assert(verify_commitments::<1,1>(packedErcAddress, idRemainder, [tx.commitments[0]],\ [tx.value], [salt], [recipientPublicKey])) return diff --git a/nightfall-deployer/circuits/transfer.zok b/nightfall-deployer/circuits/transfer.zok index 9fcb80ae5..5df8f04be 100644 --- a/nightfall-deployer/circuits/transfer.zok +++ b/nightfall-deployer/circuits/transfer.zok @@ -43,12 +43,12 @@ def main(\ field packedErcAddress = transfer.ercAddressTransfer + u32_array_to_field([transfer.idTransfer[0]]) * SHIFT //Verify nullifiers - Point firstInputZkpPublicKeys = verify_nullifiers::<2>(packedErcAddress, idRemainder,\ + Point firstInputZkpPublicKeys = verify_nullifiers::<1,2>(packedErcAddress, idRemainder,\ tx.nullifiers, roots, nullifiersValue, nullifiers.oldCommitments.salt,\ nullifiers.rootKey, nullifiers.paths, nullifiers.orders) //Verify new Commmitments - assert(verify_commitments::<2>(packedErcAddress, idRemainder, tx.commitments,\ + assert(verify_commitments::<1,2>(packedErcAddress, idRemainder, tx.commitments,\ commitmentsValue, commitments.newCommitments.salt, commitments.recipientPublicKey)) //Verify Change diff --git a/nightfall-deployer/circuits/withdraw.zok b/nightfall-deployer/circuits/withdraw.zok index a29d814f9..cb7d8ea4f 100644 --- a/nightfall-deployer/circuits/withdraw.zok +++ b/nightfall-deployer/circuits/withdraw.zok @@ -11,7 +11,7 @@ def main(\ PublicTransaction tx,\ field[2] roots,\ private Nullifiers<2> nullifiers,\ - private Commitments<2> commitments\ + private Commitments<1> commitments\ )-> (): //Verify public transaction structure @@ -29,23 +29,23 @@ def main(\ )) field[2] nullifiersValue = u8_array_to_field(nullifiers.oldCommitments.value) - field[2] commitmentsValue = u8_array_to_field(commitments.newCommitments.value) + field[1] commitmentsValue = u8_array_to_field(commitments.newCommitments.value) //Check that values match assert(sum(nullifiersValue) == sum(commitmentsValue) + tx.value) // pack the top four bytes of the token id into the ercAddress field (address only // uses 160 bits and the Shield contract prevents creation of something with more than 160 bits) - field idRemainder = u32_array_to_field(tx.tokenId[1..8]) + field idRemainder = u32_array_to_field(tx.tokenId[1..8]) field packedErcAddress = tx.ercAddress + u32_array_to_field([tx.tokenId[0]]) * SHIFT //Verify nullifiers - Point firstInputZkpPublicKeys = verify_nullifiers::<2>(packedErcAddress, idRemainder,\ + Point firstInputZkpPublicKeys = verify_nullifiers::<1,2>(packedErcAddress, idRemainder,\ tx.nullifiers, roots, nullifiersValue, nullifiers.oldCommitments.salt,\ nullifiers.rootKey, nullifiers.paths, nullifiers.orders) //Verify new Commmitments - assert(verify_commitments::<2>(packedErcAddress, idRemainder, tx.commitments,\ + assert(verify_commitments::<0,1>(packedErcAddress, idRemainder, [tx.commitments[0]],\ commitmentsValue, commitments.newCommitments.salt, commitments.recipientPublicKey)) //Verify Change From 81dbc0389426060963b0d396f273531965a24b1f Mon Sep 17 00:00:00 2001 From: Ilyas Ridhuan Date: Wed, 3 Aug 2022 08:30:41 +0100 Subject: [PATCH 08/15] fix: rebase and fixes --- nightfall-client/src/utils/compute-witness.mjs | 13 ++++++++----- .../Verifiers/verify_commitments.zok | 2 +- .../generic_circuit/Verifiers/verify_nullifiers.zok | 8 +++++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/nightfall-client/src/utils/compute-witness.mjs b/nightfall-client/src/utils/compute-witness.mjs index 050f6eaa4..a2336b287 100644 --- a/nightfall-client/src/utils/compute-witness.mjs +++ b/nightfall-client/src/utils/compute-witness.mjs @@ -66,10 +66,10 @@ const computePrivateInputsNullifiers = privateData => { ].flat(Infinity); }; -const computePrivateInputsCommitments = privateData => { +const computePrivateInputsCommitments = (privateData, padTo) => { const { newCommitmentPreimage, recipientPublicKeys } = generalise(privateData); - const paddedNewCommitmentPreimage = padArray(newCommitmentPreimage, NULL_COMMITMENT, 2); - const paddedRecipientPublicKeys = padArray(recipientPublicKeys, [0, 0], 2); + const paddedNewCommitmentPreimage = padArray(newCommitmentPreimage, NULL_COMMITMENT, padTo); + const paddedRecipientPublicKeys = padArray(recipientPublicKeys, [0, 0], padTo); return [ paddedNewCommitmentPreimage.map(commitment => commitment.value.limbs(8, 31)), paddedNewCommitmentPreimage.map(commitment => commitment.salt.field(BN128_GROUP_ORDER)), @@ -96,19 +96,22 @@ export const computeWitness = (txObject, roots, privateData) => { const publicInputs = computePublicInputs(txObject, roots); switch (Number(txObject.transactionType)) { case 0: + // Deposit return [...publicInputs, ...computePrivateInputsDeposit(privateData)]; case 1: + // Transfer return [ ...publicInputs, ...computePrivateInputsNullifiers(privateData), - ...computePrivateInputsCommitments(privateData), + ...computePrivateInputsCommitments(privateData, 2), ...computePrivateInputsEncryption(privateData), ]; default: + // Withdraw return [ ...publicInputs, ...computePrivateInputsNullifiers(privateData), - ...computePrivateInputsCommitments(privateData), + ...computePrivateInputsCommitments(privateData, 1), ]; } }; diff --git a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok index 7cf7fdb9b..fcffee3db 100644 --- a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok +++ b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_commitments.zok @@ -21,7 +21,7 @@ def main(\ We can check if commitment is supposed to be null or non-null by the following 1) Note that degenerate case for each circuit type (deposit, transfer, withdraw) also matches the possible uses involving erc721 2) Thus, we first perform checks based on this degenerate case (characterised by MinCommitments). - 3) We use (i + 1) as the loop is zero-indexed by MinCommitments is a count of minimum allowable commitments. + 3) We use (i + 1) as the loop is zero-indexed while MinCommitments is a count of minimum allowable commitments. 4) Therefore we check (with a poseidon hash) any input when the incremented loop index matches this conditional. 5) Finally, we check the "optional extra commitments" that are only allowable to erc20/1155 (characterised by value > 0) */ diff --git a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok index d79164f64..69dd2123d 100644 --- a/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok +++ b/nightfall-deployer/circuits/common/generic_circuit/Verifiers/verify_nullifiers.zok @@ -42,7 +42,7 @@ def main(\ We can check if nullifier is supposed to be null or non-null by the following 1) Note that degenerate case for each circuit type (deposit, transfer, withdraw) also matches the possible uses involving erc721 2) Thus, we first perform checks based on this degenerate case (characterised by MinNullifiers). - 3) We use (i + 1) as the loop is zero-indexed by MinNullifiers is a count of minimum allowable nullifiers. + 3) We use (i + 1) as the loop is zero-indexed while MinNullifiers is a count of minimum allowable nullifiers. 4) Therefore we check (with a poseidon hash/path check) any input when the incremented loop index matches this conditional. 5) Finally, we check the "optional extra nullifiers" that are only allowable to erc20/1155 (characterised by value > 0) */ @@ -53,15 +53,17 @@ def main(\ ...zkpPublicKeys,\ oldCommitmentSalts[i]\ ]) - field nullifier = if(oldCommitmentValues[i] != 0 || (i+1) != MinNullifiers) then poseidon([nullifierKeys, calculatedOldCommitmentHash]) else 0 fi + field nullifier = if(oldCommitmentValues[i] != 0 || (i+1) == MinNullifiers) then poseidon([nullifierKeys, calculatedOldCommitmentHash]) else 0 fi //Check that the calculated nullifier matches with the one contained in the transaction assert(nullifier == nullifierHashes[i]) //Check that the nullifier is contained in the tree - bool pathValidity = if(oldCommitmentValues[i] != 0 || (i+1) != MinNullifiers) then pathCheck([roots[i], ...paths[i]], orders[i], calculatedOldCommitmentHash) else true fi + bool pathValidity = if(oldCommitmentValues[i] != 0 || (i+1) == MinNullifiers) then pathCheck([roots[i], ...paths[i]], orders[i], calculatedOldCommitmentHash) else true fi assert(pathValidity) + //Set the changeZkpPublicKeys if i = 0. Otherwise just set the same value + firstInputZkpPublicKeys = i == 0 ? zkpPublicKeys : firstInputZkpPublicKeys endfor return firstInputZkpPublicKeys From 4af1344147f37afbd5f20dc7a3dbe269113d390c Mon Sep 17 00:00:00 2001 From: Ilyas Ridhuan Date: Sat, 6 Aug 2022 16:42:08 +0100 Subject: [PATCH 09/15] fix: log failing test --- test/e2e/gas.test.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/e2e/gas.test.mjs b/test/e2e/gas.test.mjs index 92dd837be..57ea4a3c0 100644 --- a/test/e2e/gas.test.mjs +++ b/test/e2e/gas.test.mjs @@ -209,6 +209,8 @@ describe('Gas test', () => { await emptyL2(nf3Users[0]); await web3Client.timeJump(3600 * 24 * 10); // jump in time by 10 days const commitments = await nf3Users[0].getPendingWithdraws(); + console.log('Withdraw Commitments', commitments); + console.log('CompressedZKP', commitments[nf3Users[0].zkpKeys.compressedZkpPublicKey]); expect( commitments[nf3Users[0].zkpKeys.compressedZkpPublicKey][erc20Address].length, ).to.be.greaterThan(0); From acbab400d835ce4fbf4855ebfd00fae7d7001631 Mon Sep 17 00:00:00 2001 From: Ilyas Ridhuan Date: Sat, 6 Aug 2022 20:08:33 +0100 Subject: [PATCH 10/15] fix: log failing test --- test/e2e/gas.test.mjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/e2e/gas.test.mjs b/test/e2e/gas.test.mjs index 57ea4a3c0..b9d6afee6 100644 --- a/test/e2e/gas.test.mjs +++ b/test/e2e/gas.test.mjs @@ -10,6 +10,7 @@ import { withdrawNTransactions, Web3Client, expectTransaction, + waitForTimeout, } from '../utils.mjs'; // so we can use require with mjs file @@ -204,6 +205,7 @@ describe('Gas test', () => { it('should withdraw from L2, checking for L1 balance (only with time-jump client)', async function () { const nodeInfo = await web3Client.getInfo(); if (nodeInfo.includes('TestRPC')) { + waitForTimeout(5000); const startBalance = await web3Client.getBalance(nf3Users[0].ethereumAddress); const withdrawal = await nf3Users[0].getLatestWithdrawHash(); await emptyL2(nf3Users[0]); @@ -211,6 +213,7 @@ describe('Gas test', () => { const commitments = await nf3Users[0].getPendingWithdraws(); console.log('Withdraw Commitments', commitments); console.log('CompressedZKP', commitments[nf3Users[0].zkpKeys.compressedZkpPublicKey]); + console.log('L2 Commitments', await nf3Users[0].getLayer2Commitments()); expect( commitments[nf3Users[0].zkpKeys.compressedZkpPublicKey][erc20Address].length, ).to.be.greaterThan(0); From 9d86b67f59420e628c5ed0ed389c863b70d15ada Mon Sep 17 00:00:00 2001 From: Ilyas Ridhuan Date: Sun, 7 Aug 2022 11:38:17 +0100 Subject: [PATCH 11/15] fix: log failing test --- test/e2e/gas.test.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/gas.test.mjs b/test/e2e/gas.test.mjs index b9d6afee6..f9638dad2 100644 --- a/test/e2e/gas.test.mjs +++ b/test/e2e/gas.test.mjs @@ -205,15 +205,15 @@ describe('Gas test', () => { it('should withdraw from L2, checking for L1 balance (only with time-jump client)', async function () { const nodeInfo = await web3Client.getInfo(); if (nodeInfo.includes('TestRPC')) { - waitForTimeout(5000); const startBalance = await web3Client.getBalance(nf3Users[0].ethereumAddress); const withdrawal = await nf3Users[0].getLatestWithdrawHash(); + console.log('Withdrawal Hash', withdrawal); await emptyL2(nf3Users[0]); await web3Client.timeJump(3600 * 24 * 10); // jump in time by 10 days const commitments = await nf3Users[0].getPendingWithdraws(); console.log('Withdraw Commitments', commitments); console.log('CompressedZKP', commitments[nf3Users[0].zkpKeys.compressedZkpPublicKey]); - console.log('L2 Commitments', await nf3Users[0].getLayer2Commitments()); + console.log('L2 Commitments', JSON.stringify(await nf3Users[0].getLayer2Commitments())); expect( commitments[nf3Users[0].zkpKeys.compressedZkpPublicKey][erc20Address].length, ).to.be.greaterThan(0); From cc72de31d394d38a5c3350d741dcb7cc4c9ba16b Mon Sep 17 00:00:00 2001 From: Ilyas Ridhuan Date: Sun, 7 Aug 2022 13:40:09 +0100 Subject: [PATCH 12/15] fix: log failing test --- nightfall-client/src/event-handlers/block-proposed.mjs | 7 +++---- test/e2e/gas.test.mjs | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/nightfall-client/src/event-handlers/block-proposed.mjs b/nightfall-client/src/event-handlers/block-proposed.mjs index 3b0e1250a..388e916ab 100644 --- a/nightfall-client/src/event-handlers/block-proposed.mjs +++ b/nightfall-client/src/event-handlers/block-proposed.mjs @@ -76,12 +76,10 @@ async function blockProposedEventHandler(data, syncing) { } else if (transaction.transactionType === '0' && countOfNonZeroCommitments >= 1) { // case when deposit transaction created by user saveTxToDb = true; - } else if (transaction.transactionType === '3' && countOfNonZeroNullifiers >= 1) { - // case when withdraw transaction created by user - saveTxToDb = true; } - if (saveTxToDb) + if (saveTxToDb) { + logger.info('Saving Tx', transaction.transactionHash); await saveTransaction({ transactionHashL1, blockNumber: data.blockNumber, @@ -92,6 +90,7 @@ async function blockProposedEventHandler(data, syncing) { if (!syncing || !err.message.includes('replay existing transaction')) throw err; logger.warn('Attempted to replay existing transaction. This is expected while syncing'); }); + } return Promise.all([ saveTxToDb, diff --git a/test/e2e/gas.test.mjs b/test/e2e/gas.test.mjs index f9638dad2..881343184 100644 --- a/test/e2e/gas.test.mjs +++ b/test/e2e/gas.test.mjs @@ -205,6 +205,7 @@ describe('Gas test', () => { it('should withdraw from L2, checking for L1 balance (only with time-jump client)', async function () { const nodeInfo = await web3Client.getInfo(); if (nodeInfo.includes('TestRPC')) { + waitForTimeout(10000); const startBalance = await web3Client.getBalance(nf3Users[0].ethereumAddress); const withdrawal = await nf3Users[0].getLatestWithdrawHash(); console.log('Withdrawal Hash', withdrawal); @@ -213,7 +214,6 @@ describe('Gas test', () => { const commitments = await nf3Users[0].getPendingWithdraws(); console.log('Withdraw Commitments', commitments); console.log('CompressedZKP', commitments[nf3Users[0].zkpKeys.compressedZkpPublicKey]); - console.log('L2 Commitments', JSON.stringify(await nf3Users[0].getLayer2Commitments())); expect( commitments[nf3Users[0].zkpKeys.compressedZkpPublicKey][erc20Address].length, ).to.be.greaterThan(0); From 7eb624484f5881ba0dc6c2e75927357f927d6252 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Mon, 8 Aug 2022 10:14:49 +0200 Subject: [PATCH 13/15] fix: gas test --- test/e2e/gas.test.mjs | 44 +++++-------------------------------------- 1 file changed, 5 insertions(+), 39 deletions(-) diff --git a/test/e2e/gas.test.mjs b/test/e2e/gas.test.mjs index 881343184..e3a5977fe 100644 --- a/test/e2e/gas.test.mjs +++ b/test/e2e/gas.test.mjs @@ -43,40 +43,6 @@ let eventLogs = []; const averageL1GasCost = receipts => receipts.map(receipt => receipt.gasUsed).reduce((acc, el) => acc + el) / receipts.length; -/* - This function tries to zero the number of unprocessed transactions in the optimist node - that nf3 is connected to. We call it extensively on the tests, as we want to query stuff from the - L2 layer, which is dependent on a block being made. We also need 0 unprocessed transactions by the end - of the tests, otherwise the optimist will become out of sync with the L2 block count on-chain. -*/ -const emptyL2 = async nf3Instance => { - let count = await nf3Instance.unprocessedTransactionCount(); - while (count !== 0) { - if (count % txPerBlock) { - const tx = (count % txPerBlock) - 1; - for (let i = 0; i < tx; i++) { - eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); - } - } else { - const tx = txPerBlock - count; - - await depositNTransactions( - nf3Instance, - tx, - erc20Address, - tokenType, - transferValue, - tokenId, - fee, - ); - - eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); - - count = await nf3Instance.unprocessedTransactionCount(); - } - } -}; - describe('Gas test', () => { let gasCost = 0; before(async () => { @@ -195,6 +161,7 @@ describe('Gas test', () => { nf3Users[0].ethereumAddress, fee, ); + await nf3Users[0].makeBlockNow(); eventLogs = await web3Client.waitForEvent(eventLogs, ['blockProposed']); expect(gasCost).to.be.lessThan(expectedGasCostPerTx); console.log('Withdraw L1 average gas used, if on-chain, was', averageL1GasCost(receipts)); @@ -208,12 +175,10 @@ describe('Gas test', () => { waitForTimeout(10000); const startBalance = await web3Client.getBalance(nf3Users[0].ethereumAddress); const withdrawal = await nf3Users[0].getLatestWithdrawHash(); - console.log('Withdrawal Hash', withdrawal); - await emptyL2(nf3Users[0]); + await nf3Users[0].makeBlockNow(); + await web3Client.waitForEvent(eventLogs, ['blockProposed']); await web3Client.timeJump(3600 * 24 * 10); // jump in time by 10 days const commitments = await nf3Users[0].getPendingWithdraws(); - console.log('Withdraw Commitments', commitments); - console.log('CompressedZKP', commitments[nf3Users[0].zkpKeys.compressedZkpPublicKey]); expect( commitments[nf3Users[0].zkpKeys.compressedZkpPublicKey][erc20Address].length, ).to.be.greaterThan(0); @@ -235,7 +200,8 @@ describe('Gas test', () => { }); after(async () => { - await emptyL2(nf3Users[0]); + await nf3Users[0].makeBlockNow(); + await web3Client.waitForEvent(eventLogs, ['blockProposed']); await nf3Proposer1.deregisterProposer(); await nf3Proposer1.close(); await nf3Users[0].close(); From 27537cf274a4f6bd24c52afa9edf803bdf4a3a6b Mon Sep 17 00:00:00 2001 From: Ilyas Ridhuan Date: Mon, 8 Aug 2022 11:26:28 +0100 Subject: [PATCH 14/15] fix: path issues with compile --- zokrates-worker/src/services/generateKeys.mjs | 2 +- zokrates-worker/src/zokrates-lib/compile.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zokrates-worker/src/services/generateKeys.mjs b/zokrates-worker/src/services/generateKeys.mjs index e30aab10e..2471eabab 100644 --- a/zokrates-worker/src/services/generateKeys.mjs +++ b/zokrates-worker/src/services/generateKeys.mjs @@ -24,7 +24,7 @@ export default async function generateKeys({ filepath, curve = 'bn128' }) { await compile( `${circuitsPath}/${filepath}`, `${outputPath}/${circuitDir}`, - `${circuitName}`, + `${circuitName}_out`, curve, ); diff --git a/zokrates-worker/src/zokrates-lib/compile.mjs b/zokrates-worker/src/zokrates-lib/compile.mjs index 6d9f3b31e..bafd6bb88 100644 --- a/zokrates-worker/src/zokrates-lib/compile.mjs +++ b/zokrates-worker/src/zokrates-lib/compile.mjs @@ -38,7 +38,7 @@ export default async function compile( '-o', `${parsedOutputPath}${parsedOutputName}`, '-s', - `${parsedOutputPath}abi.json`, + `${parsedOutputPath}_abi.json`, '--curve', curve, ], From 29cc1b0dd1b8b3b824a4e7833bab05a7fb3a968b Mon Sep 17 00:00:00 2001 From: Ilyas Ridhuan Date: Mon, 8 Aug 2022 13:15:47 +0100 Subject: [PATCH 15/15] test: remove last makeBlockNow --- test/e2e/gas.test.mjs | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/e2e/gas.test.mjs b/test/e2e/gas.test.mjs index e3a5977fe..a4cbec193 100644 --- a/test/e2e/gas.test.mjs +++ b/test/e2e/gas.test.mjs @@ -200,8 +200,6 @@ describe('Gas test', () => { }); after(async () => { - await nf3Users[0].makeBlockNow(); - await web3Client.waitForEvent(eventLogs, ['blockProposed']); await nf3Proposer1.deregisterProposer(); await nf3Proposer1.close(); await nf3Users[0].close();