diff --git a/config/default.js b/config/default.js index 12c71f2e8..ae9826ff6 100644 --- a/config/default.js +++ b/config/default.js @@ -86,7 +86,11 @@ module.exports = { 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) TIMBER_HEIGHT: 32, - + MAX_PUBLIC_VALUES: { + ERCADDRESS: 2n ** 161n - 1n, + COMMITMENT: 2n ** 249n - 1n, + NULLIFIER: 2n ** 249n - 1n, + }, // the various parameters needed to describe the Babyjubjub curve that we use for El-Gamal // BABYJUBJUB // Montgomery EC form is y^2 = x^3 + Ax^2 + Bx @@ -112,7 +116,8 @@ module.exports = { }, MAX_QUEUE: 5, MPC: { - RADIX_FILES_URL: 'https://nightfallv3-proving-files.s3.eu-west-1.amazonaws.com/radix', + MPC_PARAMS_URL: + 'https://nightfallv3-proving-files.s3.eu-west-1.amazonaws.com/phase2/mpc_params', }, ENVIRONMENTS: { mainnet: { diff --git a/doc/mpc.md b/doc/mpc.md index 27fda3213..076479971 100644 --- a/doc/mpc.md +++ b/doc/mpc.md @@ -9,35 +9,3 @@ The MPC envolves two phases: it ensures that if at least one participant of the ceremony is honest, then the result is forcibly trustablke. - Phase 2, which is circuit-specific and needs to be generated upon deployment. - -## Radix files - -The radix files are the MPC params that are calculated from the latest response file from PPOT. -These radix files have specific depths depending on the number of constraints for each circuit, and -can take a while to compute. They're also quite big in size so we shouldn't commit them to git. -Instead, we store them in a publicly accessible S3 bucket, and the `zokrates-worker` fetches them -synchonously if they're not present in the docker volume. - -Does this mean you have to trust these radix files? No! Because you can start the process on your -own, and create your own radix files. You don't need to trust anybody. - -## How can I not trust people - -You are able to bring your own keys to the table. For that, you need to get the latest response from -the [PPOT ceremony](https://github.com/weijiekoh/perpetualpowersoftau) and download the -`new_challenge` file, rename it to `challenge` and run the little `mpc.sh` script you'll find -[here](https://github.com/EYBlockchain/nightfall_3/blob/master/zokrates-worker/src/mpc.sh). This -script will spit out a bunch of radix files like `phase1radix2m2`, `phase1radix2m3`... -`phase1radix2m(n)` where `n` should be the depth of the circuit we're using. - -This means number of constraints, which you can get by running `zokrates compile` to compile the -circuits, and then `zokrates inspect` on the compiled circuits, which should spit out the number of -constraints. You should pick a value of `n` for which `2^n` is bigger than the number of -constraints. For example, at the time of writing, the compiled `deposit` circuit has `84766` -constraints so we need to pick up the `phase1radix2m17` as `2^16 < 84766 < 2^17`. - -You should rename these radix files to the name of the circuits, and host them somewhere. IPFS, S3 -buckets, your own webserver, whatever you want. - -Lastly, don't forget to modify the env variable `RADIX_FILES_URL` to point to the URL where you'll -get the radix files. diff --git a/nightfall-client/src/classes/commitment.mjs b/nightfall-client/src/classes/commitment.mjs index ccea45fed..993ef4008 100644 --- a/nightfall-client/src/classes/commitment.mjs +++ b/nightfall-client/src/classes/commitment.mjs @@ -31,13 +31,17 @@ class Commitment { compressedPkd, salt, }); - this.hash = sha256([ - this.preimage.ercAddress, - this.preimage.tokenId, - this.preimage.value, - this.preimage.compressedPkd, - this.preimage.salt, - ]); + // we truncate the hash down to 31 bytes but store it in a 32 byte variable + // this is consistent to what we do in the ZKP circuits + this.hash = generalise( + sha256([ + this.preimage.ercAddress, + this.preimage.tokenId, + this.preimage.value, + this.preimage.compressedPkd, + this.preimage.salt, + ]).hex(32, 31), + ); } // sometimes (e.g. going over http) the general-number class is inconvenient diff --git a/nightfall-client/src/classes/nullifier.mjs b/nightfall-client/src/classes/nullifier.mjs index fa0db1ab7..a1c6a26b9 100644 --- a/nightfall-client/src/classes/nullifier.mjs +++ b/nightfall-client/src/classes/nullifier.mjs @@ -16,7 +16,9 @@ class Nullifier { nsk, commitment: commitment.hash, }); - this.hash = sha256([this.preimage.nsk, this.preimage.commitment]); + // we truncate the hash down to 31 bytes but store it in a 32 byte variable + // this is consistent to what we do in the ZKP circuits + this.hash = generalise(sha256([this.preimage.nsk, this.preimage.commitment]).hex(32, 31)); } } diff --git a/nightfall-client/src/services/transfer.mjs b/nightfall-client/src/services/transfer.mjs index 8961acb8f..37f0cab1a 100644 --- a/nightfall-client/src/services/transfer.mjs +++ b/nightfall-client/src/services/transfer.mjs @@ -108,7 +108,6 @@ async function transfer(transferParams) { // compress the secrets to save gas const compressedSecrets = Secrets.compressSecrets(secrets); - const commitmentTreeInfo = await Promise.all(oldCommitments.map(c => getSiblingInfo(c))); const localSiblingPaths = commitmentTreeInfo.map(l => { const path = l.siblingPath.path.map(p => p.value); @@ -166,7 +165,13 @@ async function transfer(transferParams) { secrets.cipherText.flat().map(text => text.field(BN128_GROUP_ORDER)), ...secrets.squareRootsElligator2.map(sqroot => sqroot.field(BN128_GROUP_ORDER)), ], - compressedSecrets.map(text => generalise(text.hex(32, 31)).field(BN128_GROUP_ORDER)), + compressedSecrets.map(text => { + const bin = text.binary.padStart(256, '0'); + const parity = bin[0]; + const ordinate = bin.slice(1); + const fields = [parity, new GN(ordinate, 'binary').field(BN128_GROUP_ORDER)]; + return fields; + }), ].flat(Infinity); logger.debug(`witness input is ${witness.join(' ')}`); diff --git a/nightfall-deployer/circuits/double_transfer.zok b/nightfall-deployer/circuits/double_transfer.zok index a3c42b29e..80b669295 100644 --- a/nightfall-deployer/circuits/double_transfer.zok +++ b/nightfall-deployer/circuits/double_transfer.zok @@ -42,6 +42,11 @@ struct Secrets { field sqrtMessage4 } +struct CompressedPoint { + bool parity + field ordinate +} + def main(\ private field[2] fErcAddress,\ private OldCommitmentPreimage[2] oldCommitment,\ @@ -53,7 +58,7 @@ def main(\ private field[2][32] path,\ private field[2] order,\ private Secrets secrets,\ - field[8] compressedCipherText\ + CompressedPoint[8] compressedCipherText\ )->(): BabyJubJubParams context = curveParams() @@ -155,7 +160,6 @@ def main(\ assert(u32_to_bool_32(sha[0])[8..32] == u32_to_bool_32(newCommitmentHash[1][0])[8..32]) // check the old commitments are valid - // old commitments are private inputs, so they are u32[8] and not truncated for u32 i in 0..2 do sha = sha256of1280([\ ...ercAddress[i],\ @@ -164,7 +168,11 @@ def main(\ ...pkdU32,\ ...oldCommitment[i].salt\ ]) - assert(sha == oldCommitment[i].hash) + // assert(sha == oldCommitment[i].hash) + // last 224 bits: + assert(sha[1..8] == oldCommitment[i].hash[1..8]) + // first 24 bits: + assert(u32_to_bool_32(sha[0])[8..32] == u32_to_bool_32(oldCommitment[i].hash[0])[8..32]) endfor // And the encryption of the transaction (extend the value up to 256 bits) @@ -178,10 +186,10 @@ def main(\ // there is likely a compiler bug with zokrates 0.6.4 which makes using spreads (e.g. [8..256]) inside a function (e.g. assert()) very slow u32 j = 2*i bool[256] compressed256 = edwardsCompress([secrets.cipherText[j], secrets.cipherText[j+1]]) - bool[256] compressedCheck256 = field_to_bool_256(compressedCipherText[i]) - bool[248] compressed = compressed256[8..256] - bool[248] compressedCheck = compressedCheck256[8..256] - assert(compressed == compressedCheck) + bool parity = compressedCipherText[i].parity + bool[256] ordinate = field_to_bool_256(compressedCipherText[i].ordinate) + bool[256] compressedCheck256 = [ parity, ...ordinate[1..256] ] + assert(compressed256 == compressedCheck256) endfor // check that the old commitments are in the merkle tree diff --git a/nightfall-deployer/circuits/double_transfer_stub.zok b/nightfall-deployer/circuits/double_transfer_stub.zok index fb9b00128..94d4af937 100644 --- a/nightfall-deployer/circuits/double_transfer_stub.zok +++ b/nightfall-deployer/circuits/double_transfer_stub.zok @@ -25,6 +25,11 @@ struct Secrets { field sqrtMessage4 } +struct CompressedPoint { + bool parity + field ordinate +} + def main(\ private field[2] fErcAddress,\ private OldCommitmentPreimage[2] oldCommitment,\ @@ -36,11 +41,12 @@ def main(\ private field[2][32] path,\ private field[2] order,\ private Secrets secrets,\ - field[8] compressedCipherText\ + CompressedPoint[8] compressedCipherText\ )->(): field u = 0 u32 v = 0x00000000 + bool b = true for u32 j in 0..2 do u = u + fErcAddress[j] + fNewCommitmentHash[j] + fNullifier[j] - root[j] for u32 i in 0..8 do @@ -56,7 +62,8 @@ def main(\ u32 w = 0x00000000 for u32 i in 0..8 do - u = u + compressedCipherText[i] + u = u + compressedCipherText[i].ordinate + b = b && compressedCipherText[i].parity w = w + secrets.ephemeralKey1[i] +\ secrets.ephemeralKey2[i] +\ secrets.ephemeralKey3[i] +\ @@ -83,5 +90,6 @@ def main(\ assert(v == v) assert(u == u) assert(w == w) + assert(b == b) return diff --git a/nightfall-deployer/circuits/single_transfer.zok b/nightfall-deployer/circuits/single_transfer.zok index 39bc244a8..8df3561e9 100644 --- a/nightfall-deployer/circuits/single_transfer.zok +++ b/nightfall-deployer/circuits/single_transfer.zok @@ -42,6 +42,11 @@ struct Secrets { field sqrtMessage4 } +struct CompressedPoint { + bool parity + field ordinate +} + def main(\ private field fErcAddress,\ private OldCommitmentPreimage oldCommitment,\ @@ -53,7 +58,7 @@ def main(\ private field[32] path,\ private field order,\ private Secrets secrets,\ - field[8] compressedCipherText\ + CompressedPoint[8] compressedCipherText\ )->(): BabyJubJubParams context = curveParams() @@ -110,7 +115,6 @@ def main(\ assert(u32_to_bool_32(sha[0])[8..32] == u32_to_bool_32(newCommitmentHash[0])[8..32]) // check the old commitment is valid - // old commitments are private inputs, so they are u32[8] and not truncated sha = sha256of1280([\ ...ercAddress,\ ...oldCommitment.id,\ @@ -118,7 +122,11 @@ def main(\ ...pkdU32,\ ...oldCommitment.salt\ ]) - assert(sha == oldCommitment.hash) + // assert(sha == oldCommitment.hash) + // last 224 bits: + assert(sha[1..8] == oldCommitment.hash[1..8]) + // first 24 bits: + assert(u32_to_bool_32(sha[0])[8..32] == u32_to_bool_32(oldCommitment.hash[0])[8..32]) // And the encryption of the transaction (extend the value up to 256 bits) assert(secrets.cipherText == enc4(ercAddress, oldCommitment.id, oldCommitment.value, newCommitment.salt, newCommitment.pkdRecipient, secrets.ephemeralKey1, secrets.ephemeralKey2, secrets.ephemeralKey3, secrets.ephemeralKey4, secrets.sqrtMessage1, secrets.sqrtMessage2, secrets.sqrtMessage3, secrets.sqrtMessage4)) @@ -130,10 +138,10 @@ def main(\ // there is likely a compiler bug with zokrates 0.6.4 which makes using spreads (e.g. [8..256]) inside a function (e.g. assert()) very slow u32 j = 2*i bool[256] compressed256 = edwardsCompress([secrets.cipherText[j], secrets.cipherText[j+1]]) - bool[256] compressedCheck256 = field_to_bool_256(compressedCipherText[i]) - bool[248] compressed = compressed256[8..256] - bool[248] compressedCheck = compressedCheck256[8..256] - assert(compressed == compressedCheck) + bool parity = compressedCipherText[i].parity + bool[256] ordinate = field_to_bool_256(compressedCipherText[i].ordinate) + bool[256] compressedCheck256 = [ parity, ...ordinate[1..256] ] + assert(compressed256 == compressedCheck256) endfor // check that the old commitment is in the merkle tree (path[0] should be the root) diff --git a/nightfall-deployer/circuits/single_transfer_stub.zok b/nightfall-deployer/circuits/single_transfer_stub.zok index 5f620dda9..b9404f4ee 100644 --- a/nightfall-deployer/circuits/single_transfer_stub.zok +++ b/nightfall-deployer/circuits/single_transfer_stub.zok @@ -24,6 +24,11 @@ struct Secrets { field sqrtMessage4 } +struct CompressedPoint { + bool parity + field ordinate +} + def main(\ private field fErcAddress,\ private OldCommitmentPreimage oldCommitment,\ @@ -35,11 +40,12 @@ def main(\ private field[32] path,\ private field order,\ private Secrets secrets,\ - field[8] compressedCipherText\ + CompressedPoint[8] compressedCipherText\ )->(): field u = fErcAddress + fNewCommitmentHash + fNullifier - root u32 v = 0x00000000 + bool b = true for u32 i in 0..8 do v = v + oldCommitment.id[i] +\ oldCommitment.value[i] +\ @@ -52,7 +58,8 @@ def main(\ u32 w = 0x00000000 for u32 i in 0..8 do - u = u + compressedCipherText[i] + u = u + compressedCipherText[i].ordinate + b = b && compressedCipherText[i].parity w = w + secrets.ephemeralKey1[i] +\ secrets.ephemeralKey2[i] +\ secrets.ephemeralKey3[i] +\ @@ -71,5 +78,6 @@ def main(\ assert(v == v) assert(u == u) assert(w == w) + assert(b == b) return diff --git a/nightfall-deployer/circuits/withdraw.zok b/nightfall-deployer/circuits/withdraw.zok index f809c16a0..785bad35f 100644 --- a/nightfall-deployer/circuits/withdraw.zok +++ b/nightfall-deployer/circuits/withdraw.zok @@ -65,14 +65,17 @@ def main(\ assert(u32_to_bool_32(sha[0])[8..32] == u32_to_bool_32(nullifier[0])[8..32]) // check the old commitment is valid - // old commitments are private inputs, so they are u32[8] and not truncated - assert(oldCommitment.hash == sha256of1280([\ + sha = sha256of1280([\ ...ercAddress,\ ...id,\ ...value,\ ...pkdU32,\ ...oldCommitment.salt\ - ])) + ]) + // last 224 bits: + assert(sha[1..8] == oldCommitment.hash[1..8]) + // first 24 bits: + assert(u32_to_bool_32(sha[0])[8..32] == u32_to_bool_32(oldCommitment.hash[0])[8..32]) // check that the old commitment is in the merkle tree field mimcHash = u32_8_to_field(oldCommitment.hash) diff --git a/nightfall-deployer/contracts/Challenges.sol b/nightfall-deployer/contracts/Challenges.sol index 3642f161f..113c43f40 100644 --- a/nightfall-deployer/contracts/Challenges.sol +++ b/nightfall-deployer/contracts/Challenges.sol @@ -151,6 +151,11 @@ contract Challenges is Stateful, Key_Registry, Config { ) external onlyBootChallenger { checkCommit(msg.data); state.isBlockReal(blockL2, transactions, 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, @@ -189,6 +194,11 @@ contract Challenges is Stateful, Key_Registry, Config { transactions[transactionIndex].historicRootBlockNumberL2[0] == blockNumberL2ContainingHistoricRoot ); + // 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, @@ -236,6 +246,11 @@ contract Challenges is Stateful, Key_Registry, Config { blockNumberL2ContainingHistoricRoot[1], 'Incorrect historic root block' ); + // 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], diff --git a/nightfall-deployer/contracts/ChallengesUtil.sol b/nightfall-deployer/contracts/ChallengesUtil.sol index ac13fc05f..88bf83861 100644 --- a/nightfall-deployer/contracts/ChallengesUtil.sol +++ b/nightfall-deployer/contracts/ChallengesUtil.sol @@ -10,6 +10,9 @@ import './Structures.sol'; library ChallengesUtil { bytes32 public constant ZERO = 0x0000000000000000000000000000000000000000000000000000000000000000; + uint256 public constant MAX31 = 2 ** 249 - 1; + uint256 public constant MAX20 = 2 ** 161 - 1; + uint256 public constant BN128_GROUP_ORDER = 21888242871839275222246405745257275088548364400416034343698204186575808495617; function libChallengeLeafCountCorrect( Structures.Block memory priorBlockL2, @@ -203,6 +206,19 @@ library ChallengesUtil { ); } + function libCheckOverflows( + Structures.Block calldata blockL2, + Structures.Transaction calldata transaction + ) public pure { + require(uint256(transaction.ercAddress) <= MAX20, 'ERC address out of range'); + require(uint256(transaction.recipientAddress) <= MAX20, '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'); + } + function libChallengeNullifier( Structures.Transaction memory tx1, uint256 nullifierIndex1, diff --git a/nightfall-deployer/contracts/State.sol b/nightfall-deployer/contracts/State.sol index c1f0cf4c2..5ba505a5e 100644 --- a/nightfall-deployer/contracts/State.sol +++ b/nightfall-deployer/contracts/State.sol @@ -80,6 +80,8 @@ contract State is Structures, Initializable, ReentrancyGuardUpgradeable, Config ); // this will fail if a tx is re-mined out of order due to a chain reorg. require(BLOCK_STAKE <= msg.value, 'The stake payment is incorrect'); require(b.proposer == msg.sender, 'The proposer address is not the sender'); + // set the maximum tx/block to prevent unchallengably large blocks + require (t.length < 33, 'The block has too many transactions'); // We need to set the blockHash on chain here, because there is no way to // convince a challenge function of the (in)correctness by an offchain // computation; the on-chain code doesn't save the pre-image of the hash so diff --git a/nightfall-deployer/contracts/Verifier.sol b/nightfall-deployer/contracts/Verifier.sol index 6ef6c9924..2eda1c64d 100644 --- a/nightfall-deployer/contracts/Verifier.sol +++ b/nightfall-deployer/contracts/Verifier.sol @@ -34,6 +34,8 @@ library Verifier { using Pairing for *; + uint256 constant BN128_GROUP_ORDER = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + struct Proof_G16 { Pairing.G1Point A; Pairing.G2Point B; @@ -98,7 +100,9 @@ library Verifier { // 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++) { + 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, diff --git a/nightfall-optimist/src/services/transaction-checker.mjs b/nightfall-optimist/src/services/transaction-checker.mjs index cb31ef154..0f4f3d17e 100644 --- a/nightfall-optimist/src/services/transaction-checker.mjs +++ b/nightfall-optimist/src/services/transaction-checker.mjs @@ -13,8 +13,22 @@ import { waitForContract } from '../event-handlers/subscribe.mjs'; import { getBlockByBlockNumberL2 } from './database.mjs'; import verify from './verify.mjs'; -const { generalise } = gen; -const { PROVING_SCHEME, BACKEND, CURVE, ZERO, CHALLENGES_CONTRACT_NAME } = config; +const { generalise, GN } = gen; +const { + PROVING_SCHEME, + BACKEND, + CURVE, + ZERO, + CHALLENGES_CONTRACT_NAME, + BN128_GROUP_ORDER, + MAX_PUBLIC_VALUES, +} = config; + +function isOverflow(value, check) { + const bigValue = value.bigInt; + if (bigValue < 0 || bigValue >= check) return true; + return false; +} // first, let's check the hash. That's nice and easy: // NB as we actually now comput the hash on receipt of the transaction this @@ -162,22 +176,37 @@ async function verifyProof(transaction) { transaction.ercAddress, transaction.tokenId, transaction.value, - transaction.commitments[0], // not truncating here as we already ensured hash < group order + transaction.commitments[0], ].flat(Infinity), ); + 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( [ - // transaction.ercAddress, - transaction.commitments[0], // not truncating here as we already ensured hash < group order - generalise(transaction.nullifiers[0]).hex(32, 31), + transaction.commitments[0], + transaction.nullifiers[0], historicRootFirst.root, - ...transaction.compressedSecrets.map(compressedSecret => - generalise(compressedSecret).hex(32, 31), - ), + // expand the compressed secrets slightly to extract the parity as a separate field + ...transaction.compressedSecrets.map(text => { + const bin = new GN(text).binary.padStart(256, '0'); + const parity = bin[0]; + const ordinate = bin.slice(1); + return [parity, new GN(ordinate, 'binary').field(BN128_GROUP_ORDER, false)]; + }), ].flat(Infinity), ); + // 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) + ) + throw new TransactionError('Overflow in public input', 4); break; case 2: // double transfer transaction inputs = generalise( @@ -185,14 +214,32 @@ async function verifyProof(transaction) { // transaction.ercAddress, // this is correct; ercAddress appears twice // transaction.ercAddress, // in a double-transfer public input hash transaction.commitments, // not truncating here as we already ensured hash < group order - transaction.nullifiers.map(nullifier => generalise(nullifier).hex(32, 31)), + transaction.nullifiers, historicRootFirst.root, historicRootSecond.root, - ...transaction.compressedSecrets.map(compressedSecret => - generalise(compressedSecret).hex(32, 31), - ), + // expand the compressed secrets slightly to extract the parity as a separate field + ...transaction.compressedSecrets.map(text => { + const bin = new GN(text).binary.padStart(256, '0'); + const parity = bin[0]; + const ordinate = bin.slice(1); + return [parity, new GN(ordinate, 'binary').field(BN128_GROUP_ORDER, false)]; + }), ].flat(Infinity), ); + // 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) || + isOverflow(historicRootSecond.root, BN128_GROUP_ORDER) + ) + throw new TransactionError('Overflow in public input', 4); break; case 3: // withdraw transaction inputs = generalise( @@ -200,15 +247,26 @@ async function verifyProof(transaction) { transaction.ercAddress, transaction.tokenId, transaction.value, - generalise(transaction.nullifiers[0]).hex(32, 31), + 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); } + // check for modular overflow attacks + // if (inputs.filter(input => input.bigInt >= BN128_GROUP_ORDER).length > 0) + // throw new TransactionError('Modular overflow in public input', 4); const res = await verify({ vk: new VerificationKey(vkArray), proof: new Proof(transaction.proof), diff --git a/wallet/src/nightfall-browser/classes/commitment.js b/wallet/src/nightfall-browser/classes/commitment.js index a6c59eaa8..dca53a15d 100644 --- a/wallet/src/nightfall-browser/classes/commitment.js +++ b/wallet/src/nightfall-browser/classes/commitment.js @@ -33,13 +33,13 @@ class Commitment { compressedPkd, salt, }); - this.hash = sha256([ + this.hash = generalise(sha256([ this.preimage.ercAddress, this.preimage.tokenId, this.preimage.value, this.preimage.compressedPkd, this.preimage.salt, - ]); + ]).hex(32, 31)); } // sometimes (e.g. going over http) the general-number class is inconvenient diff --git a/wallet/src/nightfall-browser/classes/nullifier.js b/wallet/src/nightfall-browser/classes/nullifier.js index 12e79abaf..b6f4c1131 100644 --- a/wallet/src/nightfall-browser/classes/nullifier.js +++ b/wallet/src/nightfall-browser/classes/nullifier.js @@ -18,7 +18,7 @@ class Nullifier { nsk, commitment: commitment.hash, }); - this.hash = sha256([this.preimage.nsk, this.preimage.commitment]); + this.hash = generalise(sha256([this.preimage.nsk, this.preimage.commitment]).hex(32, 31)); } } diff --git a/zokrates-worker/.gitignore b/zokrates-worker/.gitignore index ee0988d90..a41cc9126 100644 --- a/zokrates-worker/.gitignore +++ b/zokrates-worker/.gitignore @@ -81,4 +81,4 @@ circuits/* !circuits/test/ # for now, ignore mpc parameters -radix +mpc_params diff --git a/zokrates-worker/package.json b/zokrates-worker/package.json index d42394370..35fb8f21e 100644 --- a/zokrates-worker/package.json +++ b/zokrates-worker/package.json @@ -7,7 +7,7 @@ "private": true, "scripts": { "start": "node ./src/index.mjs", - "dev": "nodemon ./src/index.mjs --ignore '**/*.params' --ignore '**/abi.json' --ignore '**/radix/**' --ignore '**/*_out'", + "dev": "nodemon ./src/index.mjs --ignore '**/*.params' --ignore '**/abi.json' --ignore '**/mpc_params/**' --ignore '**/*_out'", "setup": "./bin/setup.sh", "test": "mocha" }, diff --git a/zokrates-worker/src/index.mjs b/zokrates-worker/src/index.mjs index e1556f48b..c55fcc0b1 100644 --- a/zokrates-worker/src/index.mjs +++ b/zokrates-worker/src/index.mjs @@ -7,35 +7,26 @@ import queues from './queues/index.mjs'; import logger from './utils/logger.mjs'; const { - MPC: { RADIX_FILES_URL }, + MPC: { MPC_PARAMS_URL }, } = config; const main = async () => { try { - if (!fs.existsSync('./src/radix')) fs.mkdirSync('./src/radix'); + if (!fs.existsSync('./mpc_params')) fs.mkdirSync('./mpc_params'); - const radixPromises = []; + const mpcPromises = []; - for (const circuit of [ - 'deposit', - 'double_transfer', - 'single_transfer', - 'withdraw', - 'deposit_stub', - 'withdraw_stub', - 'double_transfer_stub', - 'single_transfer_stub', - ]) { - if (!fs.existsSync(`./src/radix/${circuit}`)) { - radixPromises.push( + for (const circuit of ['deposit', 'double_transfer', 'single_transfer', 'withdraw']) { + if (!fs.existsSync(`./mpc_params/${circuit}`)) { + mpcPromises.push( new Promise((resolve, reject) => { axios - .get(`${RADIX_FILES_URL}/${circuit}`, { + .get(`${MPC_PARAMS_URL}/${circuit}`, { responseType: 'stream', }) .then(response => { resolve(); - response.data.pipe(fs.createWriteStream(`./src/radix/${circuit}`)); + response.data.pipe(fs.createWriteStream(`./mpc_params/${circuit}`)); }) .catch(error => { reject(); @@ -47,7 +38,7 @@ const main = async () => { } } - await Promise.all(radixPromises); + await Promise.all(mpcPromises); // 1 means enable it // 0 mean keep it disabled diff --git a/zokrates-worker/src/mpc.sh b/zokrates-worker/src/mpc.sh deleted file mode 100644 index e5f07594b..000000000 --- a/zokrates-worker/src/mpc.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -rm response* -rm transcript -rm phase1radix* -rm tmp_* - -set -e - -SIZE=28 -BATCH=256 - -yes | cargo run --release --bin compute_constrained challenge response1 $SIZE $BATCH -cargo run --release --bin verify_transform_constrained challenge response1 challenge2 $SIZE $BATCH - -cargo run --release --bin beacon_constrained challenge2 response2 $SIZE $BATCH 0000000000000000000a558a61ddc8ee4e488d647a747fe4dcc362fe2026c620 10 -cargo run --release --bin verify_transform_constrained challenge2 response2 challenge3 $SIZE $BATCH - -cargo run --release --bin prepare_phase2 response2 $SIZE $BATCH diff --git a/zokrates-worker/src/services/generateKeys.mjs b/zokrates-worker/src/services/generateKeys.mjs index 3e9cc5807..2471eabab 100644 --- a/zokrates-worker/src/services/generateKeys.mjs +++ b/zokrates-worker/src/services/generateKeys.mjs @@ -1,15 +1,6 @@ import fs from 'fs'; import path from 'path'; -import crypto from 'crypto'; -import { - compile, - extractVk, - ceremony, - beacon, - exportKeys, - contribution, - setup, -} from '../zokrates-lib/index.mjs'; +import { compile, extractVk, exportKeys, setup } from '../zokrates-lib/index.mjs'; import logger from '../utils/logger.mjs'; export default async function generateKeys({ filepath, curve = 'bn128' }) { @@ -38,19 +29,6 @@ export default async function generateKeys({ filepath, curve = 'bn128' }) { ); if (process.env.MPC) { - logger.info('MPC Ceremony...'); - await ceremony(`${outputPath}/${circuitDir}`, `${circuitName}`, { verbose: true }); - - // magic number is the max int for randomInt - let randomness = crypto.randomInt(281474976710655); - logger.info('Contributing...'); - await contribution(randomness); - - randomness = crypto.randomInt(281474976710655); - const hash = crypto.createHash('sha256').update(randomness.toString()).digest('hex'); - logger.info('MPC Beacon...'); - await beacon(hash, { verbose: true }); - logger.info('Export keys...'); await exportKeys(`${outputPath}/${circuitDir}`, `${circuitName}`); } else { diff --git a/zokrates-worker/src/zokrates-lib/beacon.mjs b/zokrates-worker/src/zokrates-lib/beacon.mjs deleted file mode 100644 index a1503e059..000000000 --- a/zokrates-worker/src/zokrates-lib/beacon.mjs +++ /dev/null @@ -1,70 +0,0 @@ -import childProcess from 'child_process'; - -const { spawn } = childProcess; - -/** - * Compiles code found at `codePath` and outputs verifying and proving keys. - * - * TODO: Needs to check that files and whatnot all exist. - * - * @example - * // Will generate keys at ft-mint-vk.key and ft-mint-pk.key - * setup('./code/ft-mint/ft-mint', './', 'gm17', 'ft-mint-vk', 'ft-mint-pk'); - * - * @param {String} codePath - Path of code file to compile - * @param {String} outputPath - Directory to output, defaults to current directory - * @param {String} provingScheme - Available options are g16, pghr13, gm17 - * @param {String} backEnd - Available options are 'libsnark', 'bellman', 'ark' - * @param {String} vkName - name of verification key file, defaults to verification.key - * @param {String} pkName - name of proving key file, defaults to proving.key - */ -export default async function beacon(random, options = {}) { - const { maxReturn = 10000000, verbose = false } = options; - - return new Promise((resolve, reject) => { - const zokrates = spawn( - '/app/zokrates', - [ - 'mpc', - 'beacon', - '-i', - 'contribution.params', - '-o', - 'final.params', - '-h', - random, - '-n', - '10', - ], - { - env: { - ZOKRATES_STDLIB: process.env.ZOKRATES_STDLIB, - }, - }, - ); - - let output = ''; - - zokrates.stdout.on('data', data => { - if (verbose) { - output += data.toString('utf8'); - // If the entire output gets too large, just send ...[truncated]. - if (output.length > maxReturn) output = '...[truncated]'; - } - }); - - zokrates.stderr.on('data', err => { - reject(new Error(`Setup failed: ${err}`)); - }); - - zokrates.on('close', () => { - // ZoKrates sometimes outputs error through stdout instead of stderr, - // so we need to catch those errors manually. - if (output.includes('panicked')) { - reject(new Error(output.slice(output.indexOf('panicked')))); - } - if (verbose) resolve(output); - else resolve(); - }); - }); -} diff --git a/zokrates-worker/src/zokrates-lib/ceremony.mjs b/zokrates-worker/src/zokrates-lib/ceremony.mjs deleted file mode 100644 index ad6413393..000000000 --- a/zokrates-worker/src/zokrates-lib/ceremony.mjs +++ /dev/null @@ -1,74 +0,0 @@ -import childProcess from 'child_process'; -import fs from 'fs'; - -const { spawn } = childProcess; - -/** - * Compiles code found at `codePath` and outputs verifying and proving keys. - * - * TODO: Needs to check that files and whatnot all exist. - * - * @example - * // Will generate keys at ft-mint-vk.key and ft-mint-pk.key - * setup('./code/ft-mint/ft-mint', './', 'gm17', 'ft-mint-vk', 'ft-mint-pk'); - * - * @param {String} codePath - Path of code file to compile - * @param {String} outputPath - Directory to output, defaults to current directory - * @param {String} provingScheme - Available options are g16, pghr13, gm17 - * @param {String} backEnd - Available options are 'libsnark', 'bellman', 'ark' - * @param {String} vkName - name of verification key file, defaults to verification.key - * @param {String} pkName - name of proving key file, defaults to proving.key - */ -export default async function ceremony(path, circuitName, options = {}) { - const { maxReturn = 10000000, verbose = false } = options; - - const fullPath = `${path}/${circuitName}_out`; - if (!fs.existsSync(fullPath)) { - throw new Error('Setup input file(s) not found'); - } - - if (fullPath.endsWith('.zok')) { - throw new Error( - 'Setup cannot take the .zok version, use the compiled version with no extension.', - ); - } - - // TODO use mpcParams out of the Powers of Tau, this is just for testing - return new Promise((resolve, reject) => { - const zokrates = spawn( - '/app/zokrates', - ['mpc', 'init', '-i', fullPath, '-o', 'mpc.params', '-r', `./src/radix/${circuitName}`], - { - // stdio: ['ignore', 'pipe', 'pipe'], - env: { - ZOKRATES_STDLIB: process.env.ZOKRATES_STDLIB, - }, - }, - ); - - let output = ''; - - zokrates.stdout.on('data', data => { - if (verbose) { - output += data.toString('utf8'); - // If the entire output gets too large, just send ...[truncated]. - if (output.length > maxReturn) output = '...[truncated]'; - } - }); - - zokrates.stderr.on('data', err => { - console.log(err); - reject(new Error(`Setup failed: ${err}`)); - }); - - zokrates.on('close', () => { - // ZoKrates sometimes outputs error through stdout instead of stderr, - // so we need to catch those errors manually. - if (output.includes('panicked')) { - reject(new Error(output.slice(output.indexOf('panicked')))); - } - if (verbose) resolve(output); - else resolve(); - }); - }); -} diff --git a/zokrates-worker/src/zokrates-lib/contribution.mjs b/zokrates-worker/src/zokrates-lib/contribution.mjs deleted file mode 100644 index 4141219b5..000000000 --- a/zokrates-worker/src/zokrates-lib/contribution.mjs +++ /dev/null @@ -1,59 +0,0 @@ -import childProcess from 'child_process'; - -const { spawn } = childProcess; - -/** - * Compiles code found at `codePath` and outputs verifying and proving keys. - * - * TODO: Needs to check that files and whatnot all exist. - * - * @example - * // Will generate keys at ft-mint-vk.key and ft-mint-pk.key - * setup('./code/ft-mint/ft-mint', './', 'gm17', 'ft-mint-vk', 'ft-mint-pk'); - * - * @param {String} codePath - Path of code file to compile - * @param {String} outputPath - Directory to output, defaults to current directory - * @param {String} provingScheme - Available options are g16, pghr13, gm17 - * @param {String} backEnd - Available options are 'libsnark', 'bellman', 'ark' - * @param {String} vkName - name of verification key file, defaults to verification.key - * @param {String} pkName - name of proving key file, defaults to proving.key - */ -export default async function ceremony(random, options = {}) { - const { maxReturn = 10000000, verbose = false } = options; - - return new Promise((resolve, reject) => { - const zokrates = spawn( - '/app/zokrates', - ['mpc', 'contribute', '-i', 'mpc.params', '-o', 'contribution.params', '-e', random], - { - env: { - ZOKRATES_STDLIB: process.env.ZOKRATES_STDLIB, - }, - }, - ); - - let output = ''; - - zokrates.stdout.on('data', data => { - if (verbose) { - output += data.toString('utf8'); - // If the entire output gets too large, just send ...[truncated]. - if (output.length > maxReturn) output = '...[truncated]'; - } - }); - - zokrates.stderr.on('data', err => { - reject(new Error(`Setup failed: ${err}`)); - }); - - zokrates.on('close', () => { - // ZoKrates sometimes outputs error through stdout instead of stderr, - // so we need to catch those errors manually. - if (output.includes('panicked')) { - reject(new Error(output.slice(output.indexOf('panicked')))); - } - if (verbose) resolve(output); - else resolve(); - }); - }); -} diff --git a/zokrates-worker/src/zokrates-lib/exportKeys.mjs b/zokrates-worker/src/zokrates-lib/exportKeys.mjs index 4ec4a1cf5..f69675aca 100644 --- a/zokrates-worker/src/zokrates-lib/exportKeys.mjs +++ b/zokrates-worker/src/zokrates-lib/exportKeys.mjs @@ -26,7 +26,7 @@ export default async function exportKeys(path, circuitName, options = {}) { 'mpc', 'export', '-i', - 'final.params', + `./mpc_params/${circuitName}`, '-v', `${path}/${circuitName}_vk.key`, '-p', @@ -38,7 +38,7 @@ export default async function exportKeys(path, circuitName, options = {}) { 'mpc', 'export', '-i', - 'final.params', + `./mpc_params/${circuitName}`, '-v', `${path}/${circuitName}_vk.key`, '-p', diff --git a/zokrates-worker/src/zokrates-lib/index.mjs b/zokrates-worker/src/zokrates-lib/index.mjs index 4afb0caff..1ceef7ef9 100644 --- a/zokrates-worker/src/zokrates-lib/index.mjs +++ b/zokrates-worker/src/zokrates-lib/index.mjs @@ -3,21 +3,7 @@ import computeWitness from './compute-witness.mjs'; import extractVk from './extract-vk.mjs'; import generateProof from './generate-proof.mjs'; import verify from './verify.mjs'; -import ceremony from './ceremony.mjs'; -import contribution from './contribution.mjs'; -import beacon from './beacon.mjs'; import exportKeys from './exportKeys.mjs'; import setup from './setup.mjs'; -export { - compile, - computeWitness, - extractVk, - generateProof, - verify, - ceremony, - contribution, - beacon, - exportKeys, - setup, -}; +export { compile, computeWitness, extractVk, generateProof, verify, exportKeys, setup };