Skip to content

Commit a68fe7c

Browse files
authored
Merge pull request #19 from bobanetwork/precompute
optimisation using hash precomputation
2 parents e534165 + 2231a21 commit a68fe7c

File tree

5 files changed

+697
-0
lines changed

5 files changed

+697
-0
lines changed

contracts/IPseudoRand.sol

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ interface IPseudoRand {
2121
bytes32 value;
2222
}
2323

24+
function hashToG1(
25+
bytes memory message
26+
) external returns (Pairing.G1Point memory);
27+
2428
function verifyPartialEvalFast(
2529
Pairing.G1Point memory h,
2630
Pairing.G1Point memory sigma,

contracts/PseudoRand.sol

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ contract PseudoRand is IPseudoRand{
1212
bytes public constant DOMAIN = bytes("DVRF pseudorandom generation 2023");
1313
uint public constant R = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
1414

15+
function hashToG1(bytes memory message) public view returns (Pairing.G1Point memory) {
16+
Pairing.G1Point memory h = Hash.hashToG1(DOMAIN, message);
17+
return h;
18+
}
19+
1520
// verify partial eval without computing hash to point
1621
function verifyPartialEvalFast(
1722
Pairing.G1Point memory h,

contracts/zkdvrf_pre.sol

+286
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import {Halo2Verifier} from "./Halo2Verifier.sol";
5+
import {GlobalPublicParams} from "./GlobalPublicParams.sol";
6+
import {Pairing} from "./libs/Pairing.sol";
7+
import {IPseudoRand} from "./IPseudoRand.sol";
8+
import {Grumpkin} from "./libs/Grumpkin.sol";
9+
10+
import "@openzeppelin/contracts/utils/Strings.sol";
11+
import '@openzeppelin/contracts/access/Ownable.sol';
12+
13+
// zkdvrf with precomputation for hash2curve
14+
contract zkdvrf_pre is Ownable {
15+
using Strings for uint256;
16+
using Grumpkin for *;
17+
18+
event RegistrationCompleted(uint32 count);
19+
event NidkgStarted();
20+
event NidkgCompleted(uint32 count);
21+
event GlobalPublicParamsCreated();
22+
event RandomInitiated(uint roundNum, string input);
23+
event RandomThresholdReached(uint roundNum, string input);
24+
event RandomReady(uint roundNum, string input);
25+
26+
struct dvrfNode {
27+
address nodeAddress;
28+
bool status;
29+
uint256 deposit;
30+
bool statusPP;
31+
uint32 pkIndex;
32+
}
33+
34+
enum Status {
35+
Unregistered,
36+
Nidkg,
37+
NidkgComplete,
38+
Ready
39+
}
40+
41+
string public constant INPUT_PREFIX = "zkRand-v1-2024:";
42+
43+
uint32 public memberCount;
44+
uint32 public threshold;
45+
uint32 public ppLength;
46+
// current count of members added
47+
uint32 internal currentIndex;
48+
// current count of members deposited and registered
49+
uint32 internal registeredCount;
50+
uint32 internal ppSubmissionCount;
51+
52+
uint256 public currentRoundNum;
53+
uint256 public minNodeDeposit;
54+
55+
uint32 public pkListIndex;
56+
Grumpkin.Point[] public pkList;
57+
address[] public pkListOrder;
58+
59+
uint256[][] public ppList;
60+
// address[] public ppListOrder;
61+
62+
// The order in vkList is the same as pkList
63+
Pairing.G1Point[] public vkList;
64+
Pairing.G2Point internal gpkVal;
65+
66+
Status public contractPhase;
67+
address public halo2Verifier;
68+
address public halo2VerifyingKey;
69+
address public globalPublicParams;
70+
address public pseudoRand;
71+
72+
mapping (uint32 => address) public nodes;
73+
mapping (address => dvrfNode) public addrToNode;
74+
mapping (uint256 => string) public roundInput;
75+
mapping (uint256 => Pairing.G1Point) public roundHash;
76+
mapping (address => uint256) public lastSubmittedRound;
77+
mapping (uint256 => mapping (uint32 => IPseudoRand.PartialEval)) public roundToEval;
78+
mapping (uint256 => uint32) public roundSubmissionCount;
79+
mapping (uint256 => IPseudoRand.PseudoRandom) public roundToRandom;
80+
81+
82+
constructor(uint32 thresholdValue, uint32 numberValue, address halo2VerifierAddress, address halo2VerifyingKeyAddress, address globalPublicParamsAddress, address pseudoRandAddress, uint256 minDeposit) Ownable(msg.sender) {
83+
require (halo2VerifierAddress != address(0) && globalPublicParamsAddress != address(0) && pseudoRandAddress != address(0), "Cannot be zero addresses");
84+
memberCount = numberValue;
85+
threshold = thresholdValue;
86+
ppLength = 7 * memberCount + 14;
87+
halo2Verifier = halo2VerifierAddress;
88+
halo2VerifyingKey = halo2VerifyingKeyAddress;
89+
globalPublicParams = globalPublicParamsAddress;
90+
pseudoRand = pseudoRandAddress;
91+
minNodeDeposit = minDeposit;
92+
}
93+
94+
95+
// works until all members added,
96+
// to move to the next phase registeredCount has to be equal to memberCount
97+
function addPermissionedNodes(address nodeAddress) public onlyOwner {
98+
require(currentIndex < memberCount, "All members added");
99+
require(nodeAddress != address(0), "Node cannot be zero address");
100+
require(addrToNode[nodeAddress].nodeAddress == address(0), "Node has already been added");
101+
102+
addrToNode[nodeAddress] = dvrfNode(nodeAddress, false, 0, false, 0);
103+
currentIndex++;
104+
}
105+
106+
// each member registers with deposit and confirms
107+
function registerNode(Grumpkin.Point memory pubKey) public payable {
108+
require(contractPhase == Status.Unregistered, "Registration has already been completed");
109+
require(msg.sender == addrToNode[msg.sender].nodeAddress, "Unauthorized call");
110+
require(!addrToNode[msg.sender].status, "Node Already registered");
111+
require(msg.value >= minNodeDeposit, "Must provide enough node deposit");
112+
require(Grumpkin.isOnCurve(pubKey), "Invalid Public Key submitted");
113+
114+
nodes[registeredCount] = msg.sender;
115+
addrToNode[msg.sender].deposit = msg.value;
116+
addrToNode[msg.sender].status = true;
117+
addrToNode[msg.sender].pkIndex = pkListIndex;
118+
pkList.push(pubKey);
119+
// pkListOrder is unutilized but added for public visibility
120+
pkListOrder.push(msg.sender);
121+
pkListIndex++;
122+
registeredCount++;
123+
124+
// all the permitted nodes have registered
125+
if (registeredCount == memberCount) {
126+
emit RegistrationCompleted(registeredCount);
127+
}
128+
}
129+
130+
// owner starts nidkg protocol
131+
// can't add members after this process
132+
function startNidkg() public onlyOwner {
133+
require(contractPhase == Status.Unregistered, "NIDKG has already been completed");
134+
require(registeredCount == memberCount, "Not all Members are ready");
135+
contractPhase = Status.Nidkg;
136+
137+
emit NidkgStarted();
138+
}
139+
140+
// each member can submit pp_i, zk_i
141+
// contract validates zk_i here for each submission and then accepts it
142+
function submitPublicParams(uint256[] calldata pp, bytes calldata zkProof) public {
143+
require(msg.sender == addrToNode[msg.sender].nodeAddress, "Unauthorized call");
144+
require(contractPhase == Status.Nidkg, "Contract not in NIDKG phase");
145+
require(!addrToNode[msg.sender].statusPP, "Node already submitted");
146+
require(checkPublicParams(pp), "Invalid public parameters");
147+
require(Halo2Verifier(halo2Verifier).verifyProof(halo2VerifyingKey, zkProof, pp), "SNARK proof verification failed");
148+
149+
addrToNode[msg.sender].statusPP = true;
150+
151+
ppList.push(pp);
152+
// ppListOrder is unutilized but added for public visibility
153+
// ppListOrder.push(msg.sender);
154+
ppSubmissionCount++;
155+
156+
if (ppSubmissionCount == memberCount) {
157+
contractPhase = Status.NidkgComplete;
158+
emit NidkgCompleted(ppSubmissionCount);
159+
}
160+
}
161+
162+
// compute gpk and vk and store on the contract
163+
function computeVk(Pairing.G2Point calldata gpk) public {
164+
require(contractPhase == Status.NidkgComplete, "Partial Parameter submission not complete");
165+
(Pairing.G2Point memory gpkRet, Pairing.G1Point[] memory vk) = GlobalPublicParams(globalPublicParams).createGpp(memberCount, gpk, ppList);
166+
for (uint i = 0; i < vk.length; i++) {
167+
vkList.push(vk[i]);
168+
}
169+
gpkVal = gpkRet;
170+
contractPhase = Status.Ready;
171+
172+
emit GlobalPublicParamsCreated();
173+
}
174+
175+
// initiate public inputs for generating randoms
176+
function initiateRandom() public onlyOwner {
177+
require(contractPhase == Status.Ready, "Contract not ready");
178+
179+
if (currentRoundNum != 0) {
180+
require(roundToRandom[currentRoundNum].value != bytes32(0), "Earlier round not completed");
181+
}
182+
183+
currentRoundNum++;
184+
bytes memory input = abi.encodePacked(INPUT_PREFIX, currentRoundNum.toString());
185+
roundInput[currentRoundNum] = string(input);
186+
roundHash[currentRoundNum] = IPseudoRand(pseudoRand).hashToG1(input);
187+
188+
emit RandomInitiated(currentRoundNum, roundInput[currentRoundNum]);
189+
}
190+
191+
// each member can submit their partial evaluation.
192+
// this function can be taken offchain. The onchain storage and verification can help determine which node to reward or punish.
193+
function submitPartialEval(IPseudoRand.PartialEval memory pEval) public {
194+
require(msg.sender == addrToNode[msg.sender].nodeAddress, "Unauthorized call");
195+
// check valid round
196+
require(roundToRandom[currentRoundNum].value == bytes32(0), "Round already computed");
197+
// this will help revert calls if the contract status is not Ready and the first initiateRandom() is not called
198+
require (lastSubmittedRound[msg.sender] < currentRoundNum, "Already submitted for round");
199+
uint32 pkIndex = addrToNode[msg.sender].pkIndex;
200+
require(pEval.indexPlus == pkIndex + 1);
201+
Pairing.G1Point memory vkStored = vkList[pkIndex];
202+
require(IPseudoRand(pseudoRand).verifyPartialEvalFast(roundHash[currentRoundNum], pEval.value, pEval.proof, vkStored), "Verification of partial eval failed");
203+
lastSubmittedRound[msg.sender] = currentRoundNum;
204+
roundToEval[currentRoundNum][pkIndex] = pEval;
205+
roundSubmissionCount[currentRoundNum]++;
206+
207+
if (roundSubmissionCount[currentRoundNum] == threshold) {
208+
emit RandomThresholdReached(currentRoundNum, roundInput[currentRoundNum]);
209+
}
210+
}
211+
212+
// submit the final pseudorandom value which is computed by combining t partial evaluations offchain
213+
function submitRandom(IPseudoRand.PseudoRandom memory pseudo) public onlyOwner {
214+
require(roundToRandom[currentRoundNum].value == bytes32(0), "Answer for round already exists");
215+
require(roundSubmissionCount[currentRoundNum] >= threshold, "Partial evaluation threshold not reached");
216+
require(IPseudoRand(pseudoRand).verifyPseudoRandFast(roundHash[currentRoundNum], pseudo.proof, gpkVal), "Incorrect random submitted");
217+
bytes32 value = keccak256(abi.encodePacked(pseudo.proof.x, pseudo.proof.y));
218+
require(pseudo.value == value, "Incorrect pseudorandom value");
219+
roundToRandom[currentRoundNum] = pseudo;
220+
221+
emit RandomReady(currentRoundNum, roundInput[currentRoundNum]);
222+
}
223+
224+
function getLatestRandom() public view returns (IPseudoRand.PseudoRandom memory pseudo) {
225+
if (roundToRandom[currentRoundNum].value != bytes32(0)) {
226+
return roundToRandom[currentRoundNum];
227+
}
228+
229+
if (currentRoundNum == 1) {
230+
revert("Answer does not exist for the round yet");
231+
}
232+
233+
return roundToRandom[currentRoundNum - 1];
234+
}
235+
236+
function getRandomAtRound(uint256 roundNum) public view returns (IPseudoRand.PseudoRandom memory pseudo) {
237+
if (roundToRandom[roundNum].value != bytes32(0)) {
238+
return roundToRandom[roundNum];
239+
}
240+
241+
revert("Answer does not exist for the round yet");
242+
}
243+
244+
function checkPublicParams(uint256[] calldata pp) public view returns (bool) {
245+
require(pkList.length == memberCount, "Not enough member public keys");
246+
247+
require(pp.length == ppLength, "Wrong size of public parameters");
248+
if (pp.length != ppLength) {
249+
return false;
250+
}
251+
252+
// check if the last 2n elements in pp are public keys
253+
uint j = pp.length - 2 * memberCount;
254+
for (uint i = 0; i < memberCount; i++) {
255+
require(pp[j] == pkList[i].x, "Wrong public key x");
256+
require(pp[j+1] == pkList[i].y, "Wrong public key y");
257+
if (pp[j] != pkList[i].x || pp[j+1] != pkList[i].y) {
258+
return false;
259+
}
260+
j = j+2;
261+
}
262+
263+
return true;
264+
}
265+
266+
function getIndexPlus(address nodeAdress) public view returns (uint32) {
267+
uint32 pkIndex = addrToNode[nodeAdress].pkIndex;
268+
return pkIndex + 1;
269+
}
270+
271+
function getPkList() public view returns (Grumpkin.Point[] memory) {
272+
return pkList;
273+
}
274+
275+
function getPpList() public view returns (uint256[][] memory) {
276+
return ppList;
277+
}
278+
279+
function getGpk() public view returns (Pairing.G2Point memory) {
280+
return gpkVal;
281+
}
282+
283+
function getVkList() public view returns (Pairing.G1Point[] memory) {
284+
return vkList;
285+
}
286+
}

hardhat.config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ module.exports = {
3838
}
3939
}],
4040
overrides: {
41+
'contracts/GlobalPublicParams.sol': altCompilerSettings,
4142
'contracts/PseudoRand.sol': altCompilerSettings,
4243
},
4344
gasReporter: {

0 commit comments

Comments
 (0)