diff --git a/contracts/Marketplace.sol b/contracts/Marketplace.sol index 1723bc0..9302929 100644 --- a/contracts/Marketplace.sol +++ b/contracts/Marketplace.sol @@ -8,10 +8,11 @@ import "./Configuration.sol"; import "./Requests.sol"; import "./Proofs.sol"; import "./StateRetrieval.sol"; +import "./Endian.sol"; import "./Verifier.sol"; import "./Groth16.sol"; -contract Marketplace is Proofs, StateRetrieval { +contract Marketplace is Proofs, StateRetrieval, Endian { using EnumerableSet for EnumerableSet.Bytes32Set; using Requests for Request; @@ -159,8 +160,35 @@ contract Marketplace is Proofs, StateRetrieval { } } - function submitProof(SlotId id, Groth16Proof calldata proof) public { - _proofReceived(id, proof); + function _challengeToFieldElement( + bytes32 challenge + ) internal pure returns (uint256) { + // use only 31 bytes of the challenge to ensure that it fits into the field + bytes32 truncated = bytes32(bytes31(challenge)); + // convert from little endian to big endian + bytes32 bigEndian = _byteSwap(truncated); + // convert bytes to integer + return uint256(bigEndian); + } + + function _merkleRootToFieldElement( + bytes32 merkleRoot + ) internal pure returns (uint256) { + // convert from little endian to big endian + bytes32 bigEndian = _byteSwap(merkleRoot); + // convert bytes to integer + return uint256(bigEndian); + } + + function submitProof( + SlotId id, + Groth16Proof calldata proof + ) public requestIsKnown(_slots[id].requestId) { + Slot storage slot = _slots[id]; + Request storage request = _requests[slot.requestId]; + uint256 challenge = _challengeToFieldElement(getChallenge(id)); + uint256 merkleRoot = _merkleRootToFieldElement(request.content.merkleRoot); + _proofReceived(id, proof, [challenge, merkleRoot, slot.slotIndex]); } function markProofAsMissing(SlotId slotId, Period period) public { diff --git a/contracts/Proofs.sol b/contracts/Proofs.sol index f30ae8a..78d1738 100644 --- a/contracts/Proofs.sol +++ b/contracts/Proofs.sol @@ -109,20 +109,12 @@ abstract contract Proofs is Periods { return isRequired && pointer < _config.downtime; } - function _proofReceived(SlotId id, Groth16Proof calldata proof) internal { + function _proofReceived( + SlotId id, + Groth16Proof calldata proof, + uint[3] memory pubSignals + ) internal { require(!_received[id][_blockPeriod()], "Proof already submitted"); - - // 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) - uint256[3] memory pubSignals; - pubSignals[0] = 7538754537; - pubSignals[ - 1 - ] = 16074246370508166450132968585287196391860062495017081813239200574579640171677; - pubSignals[2] = 3; - require( _verifier.verifyProof( [proof.a.x, proof.a.y], diff --git a/contracts/TestMarketplace.sol b/contracts/TestMarketplace.sol index 13d79b4..e5da1b6 100644 --- a/contracts/TestMarketplace.sol +++ b/contracts/TestMarketplace.sol @@ -20,4 +20,16 @@ contract TestMarketplace is Marketplace { function getSlotCollateral(SlotId slotId) public view returns (uint256) { return _slots[slotId].currentCollateral; } + + function challengeToFieldElement( + bytes32 challenge + ) public pure returns (uint256) { + return _challengeToFieldElement(challenge); + } + + function merkleRootToFieldElement( + bytes32 merkleRoot + ) public pure returns (uint256) { + return _merkleRootToFieldElement(merkleRoot); + } } diff --git a/contracts/TestProofs.sol b/contracts/TestProofs.sol index 36676aa..9816708 100644 --- a/contracts/TestProofs.sol +++ b/contracts/TestProofs.sol @@ -24,8 +24,12 @@ contract TestProofs is Proofs { _markProofAsMissing(id, period); } - function proofReceived(SlotId id, Groth16Proof calldata proof) public { - _proofReceived(id, proof); + function proofReceived( + SlotId id, + Groth16Proof calldata proof, + uint[3] memory pubSignals + ) public { + _proofReceived(id, proof, pubSignals); } function setSlotState(SlotId id, SlotState state) public { diff --git a/test/Marketplace.test.js b/test/Marketplace.test.js index 4fb879b..9ef50b7 100644 --- a/test/Marketplace.test.js +++ b/test/Marketplace.test.js @@ -31,6 +31,7 @@ const { advanceTimeToForNextBlock, currentTime, } = require("./evm") +const { arrayify } = require("ethers/lib/utils") const ACCOUNT_STARTING_BALANCE = 1_000_000_000 @@ -313,6 +314,41 @@ describe("Marketplace", function () { }) }) + describe("submitting proofs when slot is filled", function () { + beforeEach(async function () { + switchAccount(client) + await token.approve(marketplace.address, price(request)) + await marketplace.requestStorage(request) + switchAccount(host) + await token.approve(marketplace.address, request.ask.collateral) + await marketplace.fillSlot(slot.request, slot.index, proof) + await advanceTimeForNextBlock(config.proofs.period) + }) + + it("allows proofs to be submitted", async function () { + await marketplace.submitProof(slotId(slot), proof) + }) + + it("converts first 31 bytes of challenge to field element", async function () { + let challenge = arrayify(await marketplace.getChallenge(slotId(slot))) + let truncated = challenge.slice(0, 31) + let littleEndian = new Uint8Array(truncated).reverse() + let expected = BigNumber.from(littleEndian) + expect(await marketplace.challengeToFieldElement(challenge)).to.equal( + expected + ) + }) + + it("converts merkle root to field element", async function () { + let merkleRoot = request.content.merkleRoot + let littleEndian = new Uint8Array(merkleRoot).reverse() + let expected = BigNumber.from(littleEndian) + expect(await marketplace.merkleRootToFieldElement(merkleRoot)).to.equal( + expected + ) + }) + }) + describe("request end", function () { var requestTime beforeEach(async function () { diff --git a/test/Proofs.test.js b/test/Proofs.test.js index 82eeb1d..c8fb3ac 100644 --- a/test/Proofs.test.js +++ b/test/Proofs.test.js @@ -11,7 +11,7 @@ const { advanceTimeToForNextBlock, } = require("./evm") const { periodic } = require("./time") -const { loadProof } = require("./proof") +const { loadProof, loadPublicInput } = require("./proof") const { SlotState } = require("./requests") const binomialTest = require("@stdlib/stats-binomial-test") const { exampleProof } = require("./examples") @@ -162,6 +162,7 @@ describe("Proofs", function () { describe("when proofs are required", function () { const proof = loadProof("local") + const pubSignals = loadPublicInput("local") beforeEach(async function () { await proofs.setSlotState(slotId, SlotState.Filled) @@ -201,26 +202,33 @@ describe("Proofs", function () { }) it("handles a correct proof", async function () { - await proofs.proofReceived(slotId, proof) + await proofs.proofReceived(slotId, proof, pubSignals) }) it("fails proof submission when proof is incorrect", async function () { let invalid = exampleProof() - await expect(proofs.proofReceived(slotId, invalid)).to.be.reverted + await expect(proofs.proofReceived(slotId, invalid, pubSignals)).to.be + .reverted + }) + + it("fails proof submission when public input is incorrect", async function () { + let invalid = [1, 2, 3] + await expect(proofs.proofReceived(slotId, proof, invalid)).to.be + .reverted }) it("emits an event when proof was submitted", async function () { - await expect(proofs.proofReceived(slotId, proof)) + await expect(proofs.proofReceived(slotId, proof, pubSignals)) .to.emit(proofs, "ProofSubmitted") .withArgs(slotId) }) it("fails proof submission when already submitted", async function () { await advanceTimeToForNextBlock(periodEnd(periodOf(await currentTime()))) - await proofs.proofReceived(slotId, proof) - await expect(proofs.proofReceived(slotId, proof)).to.be.revertedWith( - "Proof already submitted" - ) + await proofs.proofReceived(slotId, proof, pubSignals) + await expect( + proofs.proofReceived(slotId, proof, pubSignals) + ).to.be.revertedWith("Proof already submitted") }) it("marks a proof as missing", async function () { @@ -253,7 +261,7 @@ describe("Proofs", function () { it("does not mark a received proof as missing", async function () { await waitUntilProofIsRequired(slotId) let receivedPeriod = periodOf(await currentTime()) - await proofs.proofReceived(slotId, proof) + await proofs.proofReceived(slotId, proof, pubSignals) await advanceTimeToForNextBlock(periodEnd(receivedPeriod)) await mine() await expect( diff --git a/test/proof.js b/test/proof.js index 73b9672..f42865c 100644 --- a/test/proof.js +++ b/test/proof.js @@ -4,6 +4,7 @@ const { BigNumber } = ethers const BASE_PATH = __dirname + "/../verifier/networks" const PROOF_FILE_NAME = "example-proof/proof.json" +const PUBLIC_INPUT_FILE_NAME = "example-proof/public.json" function G1ToStruct(point) { return { @@ -30,4 +31,11 @@ function loadProof(name) { } } -module.exports = { loadProof } +function loadPublicInput(name) { + const input = JSON.parse( + fs.readFileSync(`${BASE_PATH}/${name}/${PUBLIC_INPUT_FILE_NAME}`) + ) + return input +} + +module.exports = { loadProof, loadPublicInput }