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 "./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 {
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 () {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
Loading…
Reference in New Issue