Skip to content

Commit

Permalink
Merge pull request #5 from bobanetwork/souradeep/zkdvrf_sol
Browse files Browse the repository at this point in the history
feat: add zkdvrf contract
  • Loading branch information
kitounliu authored Feb 27, 2024
2 parents 1150802 + c5d5766 commit 8ba66fa
Show file tree
Hide file tree
Showing 7 changed files with 2,979 additions and 6 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
.DS_Store
contracts/data
build
cache
artifacts/
target/
39 changes: 39 additions & 0 deletions contracts/IPseudoRand.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Pairing} from "./libs/Pairing.sol";

interface IPseudoRand {

struct PartialEvalProof {
uint z;
uint c;
}

function verifyPartialEvalFast(
Pairing.G1Point memory h,
Pairing.G1Point memory sigma,
PartialEvalProof memory proof,
Pairing.G1Point memory vk
) external returns (bool);

function verifyPartialEval(
bytes memory message,
Pairing.G1Point memory sigma,
PartialEvalProof memory proof,
Pairing.G1Point memory vk
) external returns (bool);

function verifyPseudoRandFast(
Pairing.G1Point memory h,
Pairing.G1Point memory sigma,
Pairing.G2Point memory gpk
) external returns (bool);

function verifyPseudoRand(
bytes memory message,
Pairing.G1Point memory sigma,
Pairing.G2Point memory gpk
) external returns (bool);

}
9 changes: 3 additions & 6 deletions contracts/PseudoRand.sol
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Pairing} from "./libs/Pairing.sol";
import {Hash} from "./libs/Hash.sol";
import {IPseudoRand} from "./IPseudoRand.sol";

contract PseudoRand {
contract PseudoRand is IPseudoRand{
using Pairing for *;
using Hash for *;

bytes public constant DOMAIN = bytes("DVRF pseudorandom generation 2023");
uint public constant R = 21888242871839275222246405745257275088548364400416034343698204186575808495617;

struct PartialEvalProof {
uint z;
uint c;
}

// verify partial eval without computing hash to point
function verifyPartialEvalFast(
Pairing.G1Point memory h,
Expand Down
202 changes: 202 additions & 0 deletions contracts/zkdvrf.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Halo2Verifier} from "./Halo2Verifier-3-5-g2.sol";
import {GlobalPublicParams} from "./GlobalPublicParams.sol";
import {Pairing} from "./libs/Pairing.sol";
import {IPseudoRand} from "./IPseudoRand.sol";

import "@openzeppelin/contracts/utils/Strings.sol";
import '@openzeppelin/contracts/access/Ownable.sol';

contract zkdvrf is Ownable {
using Strings for uint256;

struct dvrfNode {
address nodeAddress;
bool status;
uint256 deposit;
bool statusPP;
uint32 ppIndex;
}

enum Status {
Unregistered,
Nidkg,
NidkgComplete,
Ready
}

uint32 public memberCount;
uint32 public threshold;
// current count of nodes added
uint32 internal currentIndex;
// current count of nodes deposited and registered
uint32 internal registeredCount;

uint256 public currentRoundNum;
uint256 public minNodeDeposit;

uint256[][] public ppList;
// vk list order is also same as the ppList
uint32 public ppListIndex;
address[] public ppListOrder;
Pairing.G1Point[] public vkList;
Pairing.G2Point internal gpkVal;

Status public contractPhase;
address public halo2Verifier;
address public globalPublicParams;
address public pseudoRand;

mapping (uint32 => address) public nodes;
mapping (address => dvrfNode) public addrToNode;
mapping (uint256 => string) public roundInput;
mapping (address => uint256) public lastSubmittedRound;
mapping (uint256 => mapping (uint32 => Pairing.G1Point)) public roundToEval;
mapping (uint256 => uint32) public roundSubmissionCount;
// TODO: add timestamp to round for enhanced queries
mapping (uint256 => bytes) public roundToRandom;

constructor(address halo2VerifierAddress, address globalPublicParamsAddress, address pseudoRandAddress, uint256 minDeposit) Ownable(msg.sender) {
require (halo2VerifierAddress != address(0) && globalPublicParamsAddress != address(0) && pseudoRandAddress != address(0), "Cannot be zero addresses");
memberCount = 5;
threshold = 3;
halo2Verifier = halo2VerifierAddress;
globalPublicParams = globalPublicParamsAddress;
pseudoRand = pseudoRandAddress;
minNodeDeposit = minDeposit;
}


// phase: works until all members added,
// to move to the next phase registeredCount has to be equal to memberCount
function addPermissionedNodes(address nodeAddress) public onlyOwner {
require(currentIndex < memberCount, "All members added");
require(nodeAddress != address(0), "Node cannot be zero address");
require(addrToNode[nodeAddress].nodeAddress == address(0), "Node has already been added");

addrToNode[nodeAddress] = dvrfNode(nodeAddress, false, 0, false, 0);
currentIndex++;
}

// each node registers with deposit and confirms
function registerNode() public payable {
require(msg.sender == addrToNode[msg.sender].nodeAddress, "Unauthorized call");
require(!addrToNode[msg.sender].status, "Node Already registered");
require(msg.value >= minNodeDeposit, "Must provide enough node deposit");

nodes[registeredCount] = msg.sender;
addrToNode[msg.sender].deposit = msg.value;
addrToNode[msg.sender].status = true;
registeredCount++;
}

// owner Start Phase 1
// phase: cant proceed until everyone registered
// can't add nodes after this process
function startNidkg() public onlyOwner {
require(contractPhase == Status.Unregistered, "NIDKG has already been completed");
require(registeredCount == memberCount, "Not all Members are ready");
contractPhase = Status.Nidkg;
}

// each node can submit pp_i, zk_i
// contract validates zk_i here for each submission and then accepts it
function submitPublicParams(uint256[] calldata pp, bytes calldata zkProof) public {
require(contractPhase == Status.Nidkg, "Contract not in NIDKG phase");
require(!addrToNode[msg.sender].statusPP, "Node already submitted");
require(Halo2Verifier(halo2Verifier).verifyProof(zkProof, pp));

addrToNode[msg.sender].statusPP = true;

ppList.push(pp);
// ppListOrder is unutilized but added for public visibility
ppListOrder.push(msg.sender);
// index on the above list
addrToNode[msg.sender].ppIndex = ppListIndex;
ppListIndex++;

if (ppList.length == memberCount) {
contractPhase = Status.NidkgComplete;
}
}

// compute gpk and vk and store on the contract
function computeVk(Pairing.G2Point calldata gpk) public {
require(contractPhase == Status.NidkgComplete, "Partial Parameter submission not complete");
(Pairing.G2Point memory gpkRet, Pairing.G1Point[] memory vk) = GlobalPublicParams(globalPublicParams).createGpp(memberCount, gpk, ppList);
for (uint i = 0; i < vk.length; i++) {
vkList.push(vk[i]);
}
gpkVal = gpkRet;
contractPhase = Status.Ready;
}

// 2nd Phase

// can take an optional input
function generateRandom() public onlyOwner {
require(contractPhase == Status.Ready, "Contract not ready");

if (currentRoundNum != 0) {
require(roundToRandom[currentRoundNum].length != 0, "Earlier round not completed");
}

currentRoundNum++;
uint256 currentTimestamp = block.timestamp;
roundInput[currentRoundNum] = currentTimestamp.toString();
}

// // use Concat string utils to use xInput + block.timestamp
// function generateRandom(string memory xInput) public {
// // check last round completed
// currentRoundNum++;
// roundInput[currentRoundNum] = xInput;
// }

function submitPartialEval(Pairing.G1Point memory pEval, IPseudoRand.PartialEvalProof memory proof) public {
// check valid round
require(roundToRandom[currentRoundNum].length == 0, "Round already computed");
// this will help revert calls if the contract status is not Ready and the first generateRandom() is not called
require (lastSubmittedRound[msg.sender] < currentRoundNum, "Already submitted for round");
bytes memory currentX = bytes(roundInput[currentRoundNum]);
uint32 ppIndex = addrToNode[msg.sender].ppIndex;
Pairing.G1Point memory vkStored = vkList[ppIndex];
require(IPseudoRand(pseudoRand).verifyPartialEval(currentX, pEval, proof, vkStored), "Verification of partial eval failed");
lastSubmittedRound[msg.sender] = currentRoundNum;
roundToEval[currentRoundNum][ppIndex] = pEval;
roundSubmissionCount[currentRoundNum]++;
}

// accept a set of partial evals
// take sigma as a param, basically a point that the operator submits (combination of subset of partial evals)
// take the pseudorandom result
// take the gpk as stored in contract
function generateRandom(Pairing.G1Point memory sigma, bytes memory random) public onlyOwner{
require(roundToRandom[currentRoundNum].length == 0, "Answer for round already exists");
require(roundSubmissionCount[currentRoundNum] >= threshold, "Partial evaluation threshold not reached");
require(IPseudoRand(pseudoRand).verifyPseudoRand(random, sigma, gpkVal), "Incorrect random submitted");
roundToRandom[currentRoundNum] = random;
}

function getLatestRandom() public view returns (bytes memory) {
if (roundToRandom[currentRoundNum].length != 0) {
return roundToRandom[currentRoundNum];
}

if (currentRoundNum == 1) {
revert("Answer does not exist for the round yet");
}

return roundToRandom[currentRoundNum - 1];
}

function getRandomAtRound(uint256 roundNum) public view returns (bytes memory) {
if (roundToRandom[roundNum].length != 0) {
return roundToRandom[roundNum];
}

revert("Answer does not exist for the round yet");
}
}
24 changes: 24 additions & 0 deletions hardhat.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* @type import('hardhat/config').HardhatUserConfig
*/
const altCompilerSettings = {
version: '0.8.24',
settings: {
optimizer: { enabled: true, runs: 200 },
viaIR: true
}
}

module.exports = {
solidity: {
compilers: [{
version: '0.8.24',
settings: {
optimizer: { enabled: true, runs: 200 }
}
}],
overrides: {
'contracts/PseudoRand.sol': altCompilerSettings,
}
}
};
29 changes: 29 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "zkdvrf",
"description": "A distributed verifiable random function (DVRF) is a t-out-of-n threshold scheme that enables a group of n participants to jointly compute a random output. The output should be unique, publicly verifiable, unpredictable, and unbiased.",
"version": "1.0.0",
"main": "hardhat.config.js",
"directories": {
"example": "examples",
"test": "test"
},
"dependencies": {
"@openzeppelin/contracts": "^5.0.1",
"hardhat": "^2.19.5",
"hardhat-deploy": "^0.11.45"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/bobanetwork/zkdvrf.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/bobanetwork/zkdvrf/issues"
},
"homepage": "https://github.com/bobanetwork/zkdvrf#readme"
}
Loading

0 comments on commit 8ba66fa

Please sign in to comment.