Calculate public inputs for ZK proof verificition
This commit is contained in:
parent
1b3b258ccc
commit
33614ee218
|
@ -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 {
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 }
|
||||
|
|
Loading…
Reference in New Issue