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 "./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 {

View File

@ -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],

View File

@ -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);
}
}

View File

@ -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 {

View File

@ -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 () {

View File

@ -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(

View File

@ -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 }