Calculate public inputs for ZK proof verificition

This commit is contained in:
Mark Spanbroek 2024-01-22 16:43:03 +01:00 committed by markspanbroek
parent 1b3b258ccc
commit 33614ee218
7 changed files with 116 additions and 28 deletions

View File

@ -8,10 +8,11 @@ import "./Configuration.sol";
import "./Requests.sol"; import "./Requests.sol";
import "./Proofs.sol"; import "./Proofs.sol";
import "./StateRetrieval.sol"; import "./StateRetrieval.sol";
import "./Endian.sol";
import "./Verifier.sol"; import "./Verifier.sol";
import "./Groth16.sol"; import "./Groth16.sol";
contract Marketplace is Proofs, StateRetrieval { contract Marketplace is Proofs, StateRetrieval, Endian {
using EnumerableSet for EnumerableSet.Bytes32Set; using EnumerableSet for EnumerableSet.Bytes32Set;
using Requests for Request; using Requests for Request;
@ -159,8 +160,35 @@ contract Marketplace is Proofs, StateRetrieval {
} }
} }
function submitProof(SlotId id, Groth16Proof calldata proof) public { function _challengeToFieldElement(
_proofReceived(id, proof); 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 { function markProofAsMissing(SlotId slotId, Period period) public {

View File

@ -109,20 +109,12 @@ abstract contract Proofs is Periods {
return isRequired && pointer < _config.downtime; 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"); 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( require(
_verifier.verifyProof( _verifier.verifyProof(
[proof.a.x, proof.a.y], [proof.a.x, proof.a.y],

View File

@ -20,4 +20,16 @@ contract TestMarketplace is Marketplace {
function getSlotCollateral(SlotId slotId) public view returns (uint256) { function getSlotCollateral(SlotId slotId) public view returns (uint256) {
return _slots[slotId].currentCollateral; 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);
}
} }

View File

@ -24,8 +24,12 @@ contract TestProofs is Proofs {
_markProofAsMissing(id, period); _markProofAsMissing(id, period);
} }
function proofReceived(SlotId id, Groth16Proof calldata proof) public { function proofReceived(
_proofReceived(id, proof); SlotId id,
Groth16Proof calldata proof,
uint[3] memory pubSignals
) public {
_proofReceived(id, proof, pubSignals);
} }
function setSlotState(SlotId id, SlotState state) public { function setSlotState(SlotId id, SlotState state) public {

View File

@ -31,6 +31,7 @@ const {
advanceTimeToForNextBlock, advanceTimeToForNextBlock,
currentTime, currentTime,
} = require("./evm") } = require("./evm")
const { arrayify } = require("ethers/lib/utils")
const ACCOUNT_STARTING_BALANCE = 1_000_000_000 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 () { describe("request end", function () {
var requestTime var requestTime
beforeEach(async function () { beforeEach(async function () {

View File

@ -11,7 +11,7 @@ const {
advanceTimeToForNextBlock, advanceTimeToForNextBlock,
} = require("./evm") } = require("./evm")
const { periodic } = require("./time") const { periodic } = require("./time")
const { loadProof } = require("./proof") const { loadProof, loadPublicInput } = require("./proof")
const { SlotState } = require("./requests") const { SlotState } = require("./requests")
const binomialTest = require("@stdlib/stats-binomial-test") const binomialTest = require("@stdlib/stats-binomial-test")
const { exampleProof } = require("./examples") const { exampleProof } = require("./examples")
@ -162,6 +162,7 @@ describe("Proofs", function () {
describe("when proofs are required", function () { describe("when proofs are required", function () {
const proof = loadProof("local") const proof = loadProof("local")
const pubSignals = loadPublicInput("local")
beforeEach(async function () { beforeEach(async function () {
await proofs.setSlotState(slotId, SlotState.Filled) await proofs.setSlotState(slotId, SlotState.Filled)
@ -201,26 +202,33 @@ describe("Proofs", function () {
}) })
it("handles a correct proof", async 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 () { it("fails proof submission when proof is incorrect", async function () {
let invalid = exampleProof() 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 () { 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") .to.emit(proofs, "ProofSubmitted")
.withArgs(slotId) .withArgs(slotId)
}) })
it("fails proof submission when already submitted", async function () { it("fails proof submission when already submitted", async function () {
await advanceTimeToForNextBlock(periodEnd(periodOf(await currentTime()))) await advanceTimeToForNextBlock(periodEnd(periodOf(await currentTime())))
await proofs.proofReceived(slotId, proof) await proofs.proofReceived(slotId, proof, pubSignals)
await expect(proofs.proofReceived(slotId, proof)).to.be.revertedWith( await expect(
"Proof already submitted" proofs.proofReceived(slotId, proof, pubSignals)
) ).to.be.revertedWith("Proof already submitted")
}) })
it("marks a proof as missing", async function () { 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 () { it("does not mark a received proof as missing", async function () {
await waitUntilProofIsRequired(slotId) await waitUntilProofIsRequired(slotId)
let receivedPeriod = periodOf(await currentTime()) let receivedPeriod = periodOf(await currentTime())
await proofs.proofReceived(slotId, proof) await proofs.proofReceived(slotId, proof, pubSignals)
await advanceTimeToForNextBlock(periodEnd(receivedPeriod)) await advanceTimeToForNextBlock(periodEnd(receivedPeriod))
await mine() await mine()
await expect( await expect(

View File

@ -4,6 +4,7 @@ const { BigNumber } = ethers
const BASE_PATH = __dirname + "/../verifier/networks" const BASE_PATH = __dirname + "/../verifier/networks"
const PROOF_FILE_NAME = "example-proof/proof.json" const PROOF_FILE_NAME = "example-proof/proof.json"
const PUBLIC_INPUT_FILE_NAME = "example-proof/public.json"
function G1ToStruct(point) { function G1ToStruct(point) {
return { 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 }