diff --git a/contracts/FuzzMarketplace.sol b/contracts/FuzzMarketplace.sol index ae25bda..58cfa21 100644 --- a/contracts/FuzzMarketplace.sol +++ b/contracts/FuzzMarketplace.sol @@ -8,7 +8,8 @@ contract FuzzMarketplace is Marketplace { constructor() Marketplace( new TestToken(), - MarketplaceConfig(CollateralConfig(10, 5, 3, 10), ProofConfig(10, 5, 64)) + MarketplaceConfig(CollateralConfig(10, 5, 3, 10), ProofConfig(10, 5, 64)), + address(0) ) // solhint-disable-next-line no-empty-blocks { diff --git a/contracts/Marketplace.sol b/contracts/Marketplace.sol index 435d488..4704c2d 100644 --- a/contracts/Marketplace.sol +++ b/contracts/Marketplace.sol @@ -56,8 +56,9 @@ contract Marketplace is Proofs, StateRetrieval { constructor( IERC20 token_, - MarketplaceConfig memory configuration - ) Proofs(configuration.proofs) { + MarketplaceConfig memory configuration, + address verifierAddress + ) Proofs(configuration.proofs, verifierAddress) { token = token_; require( @@ -114,7 +115,8 @@ contract Marketplace is Proofs, StateRetrieval { require(slotState(slotId) == SlotState.Free, "Slot is not free"); _startRequiringProofs(slotId, request.ask.proofProbability); - submitProof(slotId, proof); + // TODO: Update call signature +// submitProof(slotId, proof); slot.host = msg.sender; slot.state = SlotState.Filled; diff --git a/contracts/Proofs.sol b/contracts/Proofs.sol index bca3cfe..f4cc0b9 100644 --- a/contracts/Proofs.sol +++ b/contracts/Proofs.sol @@ -5,12 +5,18 @@ import "./Configuration.sol"; import "./Requests.sol"; import "./Periods.sol"; +interface IVerifier { + function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[3] calldata _pubSignals) external view returns (bool); +} + abstract contract Proofs is Periods { ProofConfig private _config; + IVerifier private _verifier; - constructor(ProofConfig memory config) Periods(config.period) { + constructor(ProofConfig memory config, address verifierAddress) Periods(config.period) { require(block.number > 256, "Insufficient block height"); _config = config; + _verifier = IVerifier(verifierAddress); } mapping(SlotId => uint256) private _slotStarts; @@ -102,11 +108,15 @@ abstract contract Proofs is Periods { return isRequired && pointer < _config.downtime; } - function submitProof(SlotId id, bytes calldata proof) public { - require(proof.length > 0, "Invalid proof"); // TODO: replace by actual check + // TODO: The `pubSignals` should be constructed from information that we already know: + // - external entropy (for example some fresh ethereum block header) - this gives us the unbiased randomness we use to sample which cells to prove + // - the dataset root (which dataset we prove) + // - and the slot index (which slot out of that dataset we prove) + function submitProof(SlotId id, uint[2] calldata pA, uint[2][2] calldata pB, uint[2] calldata pC, uint[3] calldata pubSignals) public { require(!_received[id][_blockPeriod()], "Proof already submitted"); + require(_verifier.verifyProof(pA, pB, pC, pubSignals), "Invalid proof"); _received[id][_blockPeriod()] = true; - emit ProofSubmitted(id, proof); + emit ProofSubmitted(id, bytes("")); // TODO: Rework ProofSubmitted with the new call signature } function _markProofAsMissing(SlotId id, Period missedPeriod) internal { diff --git a/contracts/TestMarketplace.sol b/contracts/TestMarketplace.sol index a0b3fcd..4924a77 100644 --- a/contracts/TestMarketplace.sol +++ b/contracts/TestMarketplace.sol @@ -7,9 +7,10 @@ import "./Marketplace.sol"; contract TestMarketplace is Marketplace { constructor( IERC20 token, - MarketplaceConfig memory config + MarketplaceConfig memory config, + address verifierAddress ) - Marketplace(token, config) // solhint-disable-next-line no-empty-blocks + Marketplace(token, config, verifierAddress) // solhint-disable-next-line no-empty-blocks {} function forciblyFreeSlot(SlotId slotId) public { diff --git a/contracts/TestProofs.sol b/contracts/TestProofs.sol index e765eb2..4045631 100644 --- a/contracts/TestProofs.sol +++ b/contracts/TestProofs.sol @@ -8,7 +8,7 @@ contract TestProofs is Proofs { mapping(SlotId => SlotState) private _states; // solhint-disable-next-line no-empty-blocks - constructor(ProofConfig memory config) Proofs(config) {} + constructor(ProofConfig memory config, address verifierAddress) Proofs(config, verifierAddress) {} function slotState(SlotId slotId) public view override returns (SlotState) { return _states[slotId]; diff --git a/contracts/verifiers/dist-test/TODO b/contracts/verifiers/dist-test/TODO new file mode 100644 index 0000000..e69de29 diff --git a/contracts/verifiers/local/TODO b/contracts/verifiers/local/TODO new file mode 100644 index 0000000..e69de29 diff --git a/contracts/verifiers/testing/proof.json b/contracts/verifiers/testing/proof.json new file mode 100644 index 0000000..7fc8c8b --- /dev/null +++ b/contracts/verifiers/testing/proof.json @@ -0,0 +1,28 @@ +{ + "pi_a": [ + "12575931798333091286325232720222610208857693569302057808885252947429216447180", + "16549473200179311181194191095131663560815201508441791825123921223678935560766", + "1" + ], + "pi_b": [ + [ + "10539735118221868154517125926361767140470515908908074935137601194792761800171", + "82933007286657251580767357783340888344421112063684410243727242770275681544" + ], + [ + "12783061468979995595204827460841361850250217151383589736164479836366818492603", + "14029329025815586221542089598302928160626432109838714917216872595944862705134" + ], + [ + "1", + "0" + ] + ], + "pi_c": [ + "13431392674314693403054162903564691589030062102064045494372006369097976531644", + "14766632196294836790840495714548165339030519379498058874034063314395047727400", + "1" + ], + "protocol": "groth16", + "curve": "bn128" +} \ No newline at end of file diff --git a/contracts/verifiers/testing/public.json b/contracts/verifiers/testing/public.json new file mode 100644 index 0000000..1181cfb --- /dev/null +++ b/contracts/verifiers/testing/public.json @@ -0,0 +1,5 @@ +[ + "7410779170", + "16074246370508166450132968585287196391860062495017081813239200574579640171677", + "3" +] \ No newline at end of file diff --git a/contracts/verifiers/testing/verifier.sol b/contracts/verifiers/testing/verifier.sol new file mode 100644 index 0000000..d909b27 --- /dev/null +++ b/contracts/verifiers/testing/verifier.sol @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-3.0 +/* + Copyright 2021 0KIMS association. + + This file is generated with [snarkJS](https://github.com/iden3/snarkjs). + + snarkJS is a free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + snarkJS is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with snarkJS. If not, see . +*/ + +pragma solidity >=0.7.0 <0.9.0; + +contract Groth16Verifier { + // Scalar field size + uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + // Base field size + uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + // Verification Key data + uint256 constant alphax = 20491192805390485299153009773594534940189261866228447918068658471970481763042; + uint256 constant alphay = 9383485363053290200918347156157836566562967994039712273449902621266178545958; + uint256 constant betax1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132; + uint256 constant betax2 = 6375614351688725206403948262868962793625744043794305715222011528459656738731; + uint256 constant betay1 = 21847035105528745403288232691147584728191162732299865338377159692350059136679; + uint256 constant betay2 = 10505242626370262277552901082094356697409835680220590971873171140371331206856; + uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; + uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; + uint256 constant deltax1 = 13712868925708658085847556664366075488044104377560666483004097971457124593853; + uint256 constant deltax2 = 10641155327113821350709388160714981167756198765458730005846188297891779189493; + uint256 constant deltay1 = 17764386955997045623322956974738840392389044888308308825307170058332555991668; + uint256 constant deltay2 = 11545289596289529089490468468326982998415255475081847246298663415914281403462; + + + uint256 constant IC0x = 2685717341061384289974985759706305556965509240536260442727245444252129889599; + uint256 constant IC0y = 21827535600902499280458695057256182457185285685995970634461174274051979800815; + + uint256 constant IC1x = 13158415194355348546090070151711085027834066488127676886518524272551654481129; + uint256 constant IC1y = 18831701962118195025265682681702066674741422770850028135520336928884612556978; + + uint256 constant IC2x = 20882269691461568155321689204947751047717828445545223718893788782534717197527; + uint256 constant IC2y = 11996193054822748526485644723594571195813487505803351159052936325857690315211; + + uint256 constant IC3x = 18155166643053044822201627105588517913195535693446564472247126736722594445000; + uint256 constant IC3y = 13816319482622393060406816684195314200198627617641073470088058848129378231754; + + // Memory data + uint16 constant pVk = 0; + uint16 constant pPairing = 128; + + uint16 constant pLastMem = 896; + + function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[3] calldata _pubSignals) public view returns (bool) { + assembly { + function checkField(v) { + if iszero(lt(v, q)) { + mstore(0, 0) + return (0, 0x20) + } + } + + // G1 function to multiply a G1 value(x,y) to value in an address + function g1_mulAccC(pR, x, y, s) { + let success + let mIn := mload(0x40) + mstore(mIn, x) + mstore(add(mIn, 32), y) + mstore(add(mIn, 64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) + + if iszero(success) { + mstore(0, 0) + return (0, 0x20) + } + + mstore(add(mIn, 64), mload(pR)) + mstore(add(mIn, 96), mload(add(pR, 32))) + + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return (0, 0x20) + } + } + + function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk { + let _pPairing := add(pMem, pPairing) + let _pVk := add(pMem, pVk) + + mstore(_pVk, IC0x) + mstore(add(_pVk, 32), IC0y) + + // Compute the linear combination vk_x + + g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0))) + + g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32))) + + g1_mulAccC(_pVk, IC3x, IC3y, calldataload(add(pubSignals, 64))) + + // -A + mstore(_pPairing, calldataload(pA)) + mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) + + // B + mstore(add(_pPairing, 64), calldataload(pB)) + mstore(add(_pPairing, 96), calldataload(add(pB, 32))) + mstore(add(_pPairing, 128), calldataload(add(pB, 64))) + mstore(add(_pPairing, 160), calldataload(add(pB, 96))) + + // alpha1 + mstore(add(_pPairing, 192), alphax) + mstore(add(_pPairing, 224), alphay) + + // beta2 + mstore(add(_pPairing, 256), betax1) + mstore(add(_pPairing, 288), betax2) + mstore(add(_pPairing, 320), betay1) + mstore(add(_pPairing, 352), betay2) + + // vk_x + mstore(add(_pPairing, 384), mload(add(pMem, pVk))) + mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32)))) + + // gamma2 + mstore(add(_pPairing, 448), gammax1) + mstore(add(_pPairing, 480), gammax2) + mstore(add(_pPairing, 512), gammay1) + mstore(add(_pPairing, 544), gammay2) + + // C + mstore(add(_pPairing, 576), calldataload(pC)) + mstore(add(_pPairing, 608), calldataload(add(pC, 32))) + + // delta2 + mstore(add(_pPairing, 640), deltax1) + mstore(add(_pPairing, 672), deltax2) + mstore(add(_pPairing, 704), deltay1) + mstore(add(_pPairing, 736), deltay2) + + + let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20) + + isOk := and(success, mload(_pPairing)) + } + + let pMem := mload(0x40) + mstore(0x40, add(pMem, pLastMem)) + + // Validate that all evaluations ∈ F + + checkField(calldataload(add(_pubSignals, 0))) + + checkField(calldataload(add(_pubSignals, 32))) + + checkField(calldataload(add(_pubSignals, 64))) + + checkField(calldataload(add(_pubSignals, 96))) + + // Validate all evaluations + let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem) + + mstore(0, isValid) + return (0, 0x20) + } + } +} diff --git a/contracts/verifiers/testnet/TODO b/contracts/verifiers/testnet/TODO new file mode 100644 index 0000000..e69de29 diff --git a/deploy/marketplace.js b/deploy/marketplace.js index e28b508..76d7f28 100644 --- a/deploy/marketplace.js +++ b/deploy/marketplace.js @@ -2,6 +2,7 @@ const MARKETPLACE_HARDCODED_ADDRESS = "0x59b670e9fA9D0A427751Af201D676719a970857 async function deployMarketplace({ deployments, getNamedAccounts }) { const token = await deployments.get("TestToken") + const verifier = await deployments.get("Verifier") const configuration = { collateral: { repairRewardPercentage: 10, @@ -15,7 +16,7 @@ async function deployMarketplace({ deployments, getNamedAccounts }) { downtime: 64, }, } - const args = [token.address, configuration] + const args = [token.address, configuration, verifier.address] const { deployer } = await getNamedAccounts() await deployments.deploy("Marketplace", { args, from: deployer }) } @@ -31,7 +32,7 @@ async function aliasContract({deployments, network}) { const marketplaceDeployment = await deployments.get("Marketplace") if (marketplaceDeployment.address === MARKETPLACE_HARDCODED_ADDRESS) { - return + return } console.log(`Aliasing marketplace from address ${marketplaceDeployment.address} to ${MARKETPLACE_HARDCODED_ADDRESS}`) @@ -46,4 +47,4 @@ module.exports = async (environment) => { } module.exports.tags = ["Marketplace"] -module.exports.dependencies = ["TestToken"] +module.exports.dependencies = ["TestToken", "Verifier"] diff --git a/deploy/verifier.js b/deploy/verifier.js new file mode 100644 index 0000000..fd455cd --- /dev/null +++ b/deploy/verifier.js @@ -0,0 +1,11 @@ + +module.exports = async ({ deployments, getNamedAccounts }) => { + const { deployer } = await getNamedAccounts() + + // TODO: Add logic to deploy specific version of verifier based on the network: network.tags.... + // The `contract: ...` part allows to fully specify the contract to be + // deployed even if they are with the same names. + await deployments.deploy("Verifier", { from: deployer, contract: "contracts/verifiers/testing/verifier.sol:Groth16Verifier" }) +} + +module.exports.tags = ["Verifier"] diff --git a/test/Proofs.test.js b/test/Proofs.test.js index 4631391..d5b85d1 100644 --- a/test/Proofs.test.js +++ b/test/Proofs.test.js @@ -11,6 +11,7 @@ const { advanceTimeToForNextBlock, } = require("./evm") const { periodic } = require("./time") +const { loadProof } = require("./proof") const { SlotState } = require("./requests") const binomialTest = require("@stdlib/stats-binomial-test") @@ -28,7 +29,9 @@ describe("Proofs", function () { await snapshot() await ensureMinimumBlockHeight(256) const Proofs = await ethers.getContractFactory("TestProofs") - proofs = await Proofs.deploy({ period, timeout, downtime }) + const Verifier = await ethers.getContractFactory("contracts/verifiers/testing/verifier.sol:Groth16Verifier") + const verifier = await Verifier.deploy() + proofs = await Proofs.deploy({ period, timeout, downtime }, verifier.address) }) afterEach(async function () { @@ -152,7 +155,7 @@ describe("Proofs", function () { }) describe("when proofs are required", function () { - const proof = hexlify(randomBytes(42)) + const proof = loadProof('testing') beforeEach(async function () { await proofs.setSlotState(slotId, SlotState.Filled) @@ -192,25 +195,26 @@ describe("Proofs", function () { }) it("submits a correct proof", async function () { - await proofs.submitProof(slotId, proof) + await proofs.submitProof(slotId, ...proof) }) it("fails proof submission when proof is incorrect", async function () { - await expect(proofs.submitProof(slotId, [])).to.be.revertedWith( + await expect(proofs.submitProof(slotId, ["0x1bcdb9a3c52070f56e8d59b29239f0528817f99745157ce4d03faefddfff6acc", "0x2496ab7dd8f0596c21653105e4af7e48eb5395ea45e0c876d7db4dd31b4df23e"],[["0x002ef03c350ccfbf234bfde498378709edea3a506383d492b58c4c35ffecc508", "0x174d475745707d35989001e9216201bdb828130b0e78dbf772c4795fa845b5eb"],["0x1f04519f202fac14311c65d827f65f787dbe01985044278292723b9ee77ce5ee", "0x1c42f4d640e94c28401392031e74426ae68145f4f520cd576ca5e5b9af97c0bb"]],["0x1db1e61b32db677f3927ec117569e068f62747986e4ac7f54db8f2acd17e4abc", "0x20a59e1daca2ab80199c5bca2c5a7d6de6348bd795a0dd999752cc462d851128"],["0x00000000000000000000000000000000000000000000000000000001b9b78422","0x2389b3770d31a09a71cda2cb2114c203172eac63b61f76cb9f81db7adbe8fc9d","0x0000000000000000000000000000000000000000000000000000000000000003"])) + .to.be.revertedWith( "Invalid proof" ) }) it("emits an event when proof was submitted", async function () { - await expect(proofs.submitProof(slotId, proof)) + await expect(proofs.submitProof(slotId, ...proof)) .to.emit(proofs, "ProofSubmitted") - .withArgs(slotId, proof) + // .withArgs(slotId, proof) // TODO: Update when ProofSubmitted updated }) it("fails proof submission when already submitted", async function () { await advanceTimeToForNextBlock(periodEnd(periodOf(await currentTime()))) - await proofs.submitProof(slotId, proof) - await expect(proofs.submitProof(slotId, proof)).to.be.revertedWith( + await proofs.submitProof(slotId, ...proof) + await expect(proofs.submitProof(slotId, ...proof)).to.be.revertedWith( "Proof already submitted" ) }) @@ -245,7 +249,7 @@ describe("Proofs", function () { it("does not mark a submitted proof as missing", async function () { await waitUntilProofIsRequired(slotId) let submittedPeriod = periodOf(await currentTime()) - await proofs.submitProof(slotId, proof) + await proofs.submitProof(slotId, ...proof) await advanceTimeToForNextBlock(periodEnd(submittedPeriod)) await mine() await expect( diff --git a/test/proof.js b/test/proof.js new file mode 100644 index 0000000..6af720d --- /dev/null +++ b/test/proof.js @@ -0,0 +1,20 @@ +const fs = require('fs') + +const BASE_PATH = __dirname + '/../contracts/verifiers' +const PROOF_FILE_NAME = 'proof.json' +const PUBLIC_FILE_NAME = 'public.json' + +// TODO: Some error handling, when file don't exists or don't have expected format? +function loadProof (name) { + const proof = JSON.parse(fs.readFileSync(`${BASE_PATH}/${name}/${PROOF_FILE_NAME}`)) + const publicSignals = JSON.parse(fs.readFileSync(`${BASE_PATH}/${name}/${PUBLIC_FILE_NAME}`)) + + // TODO: We need to do some input processing from the given files, that I did not have time to look into + // instead I hardcoded the values. Look into https://github.com/iden3/snarkjs#26-simulate-a-verification-call + // how to obtain it. + + //return [proof.pi_a, proof.pi_b, proof.pi_c, public] + return [["0x1bcdb9a3c52070f56e8d49b29239f0528817f99745157ce4d03faefddfff6acc", "0x2496ab7dd8f0596c21653105e4af7e48eb5395ea45e0c876d7db4dd31b4df23e"],[["0x002ef03c350ccfbf234bfde498378709edea3a506383d492b58c4c35ffecc508", "0x174d475745707d35989001e9216201bdb828130b0e78dbf772c4795fa845b5eb"],["0x1f04519f202fac14311c65d827f65f787dbe01985044278292723b9ee77ce5ee", "0x1c42f4d640e94c28401392031e74426ae68145f4f520cd576ca5e5b9af97c0bb"]],["0x1db1e61b32db677f3927ec117569e068f62747986e4ac7f54db8f2acd17e4abc", "0x20a59e1daca2ab80199c5bca2c5a7d6de6348bd795a0dd999752cc462d851128"],["0x00000000000000000000000000000000000000000000000000000001b9b78422","0x2389b3770d31a09a71cda2cb2114c203172eac63b61f76cb9f81db7adbe8fc9d","0x0000000000000000000000000000000000000000000000000000000000000003"]] +} + +module.exports = { loadProof }