├── imgs ├── img ├── img1.png ├── img2.png ├── img3.png ├── img4.jpg ├── img5.png └── graph-example.png ├── .gitignore ├── circuits ├── test_input.json ├── prove_score.circom ├── test_input1.json ├── scoring_algorithm.circom └── new_scoring_circuit.circom ├── sequencer ├── sequencer.js ├── mimc.js └── MiMC_hash.js ├── contracts ├── old_contracts │ ├── contract.sol │ └── sybil.sol └── main.sol ├── algorithm_design ├── efficient_scoring_algorithm.nb └── test_scoring_algorithm.nb └── README.md /imgs/img: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /imgs/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zikc2023/tokamak-sybil-resistance/HEAD/imgs/img1.png -------------------------------------------------------------------------------- /imgs/img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zikc2023/tokamak-sybil-resistance/HEAD/imgs/img2.png -------------------------------------------------------------------------------- /imgs/img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zikc2023/tokamak-sybil-resistance/HEAD/imgs/img3.png -------------------------------------------------------------------------------- /imgs/img4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zikc2023/tokamak-sybil-resistance/HEAD/imgs/img4.jpg -------------------------------------------------------------------------------- /imgs/img5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zikc2023/tokamak-sybil-resistance/HEAD/imgs/img5.png -------------------------------------------------------------------------------- /imgs/graph-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zikc2023/tokamak-sybil-resistance/HEAD/imgs/graph-example.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #scoring_algorithm_js 2 | circuits/scoring_algorithm_js/ 3 | 4 | #circom 5 | *.r1cs 6 | *.sym 7 | 8 | #setup 9 | *.ptau 10 | 11 | #zkey 12 | *.zkey 13 | verification_key.json 14 | verifier.sol 15 | 16 | #proof 17 | proof.json 18 | public.json 19 | *.wtns -------------------------------------------------------------------------------- /circuits/test_input.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "subsets": [ 4 | ["1","1","1","0"], 5 | ["1","0","0","0"], 6 | ["0","1","0","1"], 7 | ["0","0","1","1"] 8 | ], 9 | 10 | "weights":[ 11 | ["0","1","2","0"], 12 | ["1","0","7","3"], 13 | ["2","7","0","2"], 14 | ["0","3","2","0"] 15 | ] 16 | 17 | } 18 | -------------------------------------------------------------------------------- /circuits/prove_score.circom: -------------------------------------------------------------------------------- 1 | include "./mimc.circom"; 2 | 3 | 4 | 5 | template merkleroot(n){ 6 | signal input path[n]; 7 | signal input path_lr[n]; 8 | signal input leaf; 9 | signal output root; 10 | component tmp_root[n]; 11 | tmp_root[0] = MultiMiMC7(2,91); 12 | tmp_root[0].in[0] <== leaf - path_lr[0]* (leaf - path[0]); 13 | tmp_root[0].in[1] <== path[0] - path_lr[0]* (path[0] - leaf); 14 | tmp_root[0].k <== 0; 15 | for (var i = 1; i < n; i++){ 16 | tmp_root[i] = MultiMiMC7(2,91); 17 | tmp_root[i].in[0] <== path[i] - path_lr[i]* (path[i] - tmp_root[i-1].out); 18 | tmp_root[i].in[1] <== tmp_root[i-1].out - path_lr[i]*(tmp_root[i-1].out - path[i]); 19 | tmp_root[i].k <== 0; 20 | 21 | } 22 | 23 | root <== tmp_root[n-1].out; 24 | 25 | } 26 | 27 | 28 | component main = merkleroot(2); 29 | -------------------------------------------------------------------------------- /sequencer/sequencer.js: -------------------------------------------------------------------------------- 1 | const { MerkleTree } = require('merkletreejs') 2 | const SHA256 = require('crypto-js/sha256') 3 | const fs = require('fs'); 4 | let tree_data = require('/Users/mac/Desktop/zksnarks/scoring_circuit_js/input.json'); 5 | let link_data = tree_data['weights'] 6 | let scores = require('/Users/mac/Desktop/zksnarks/public.json'); 7 | module.exports.view_scores = function () { 8 | console.log(tree_data); 9 | }; 10 | 11 | module.exports.create_link_tree = function () { 12 | link_tree_roots = [] 13 | for(var i = 0; i < link_data.length; i++){ 14 | let leaves = link_data[i].map(x => SHA256(x)) 15 | let link_tree = new MerkleTree(leaves, SHA256) 16 | let root = link_tree.getRoot().toString('hex') 17 | link_tree_roots.push(root) 18 | } 19 | let main_tree_leaves = link_tree_roots.map(x => SHA256(x)) 20 | let main_tree = new MerkleTree(main_tree_leaves, SHA256) 21 | let main_root = main_tree.getRoot().toString('hex') 22 | console.log('link_tree root hash:',main_root) 23 | }; 24 | 25 | module.exports.create_scores_tree = function () { 26 | let scores_tree_leaves = scores.map(x => SHA256(x)) 27 | let scores_tree = new MerkleTree(scores_tree_leaves, SHA256) 28 | let scores_root = scores_tree.getRoot().toString('hex') 29 | console.log('scores_tree root hash:',scores_root) 30 | }; 31 | 32 | module.exports.update_link_weight = function (i,j,w) { 33 | let content = tree_data 34 | content['weights'][i][j] = String(w) 35 | content['weights'][j][i] = String(w) 36 | fs.writeFileSync('./scoring_circuit_js/input.json', JSON.stringify(content)); 37 | }; 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /circuits/test_input1.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "subsets": [ 4 | ["1", "0", "0", "0", "0", "0", "0", "1", "1", "1", "1", "1", "1", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"], 5 | ["0", "1", "0", "0", "0", "0", "0", "1", "0", "0", "0", "0", "0", "1", "1", "1", "1", "1", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "1", "1", "1", "1", "1", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"], 6 | ["0", "0", "1", "0", "0", "0", "0", "0", "1", "0", "0", "0", "0", "1", "0", "0", "0", "0", "1", "1", "1", "1", "0", "0", "0", "0", "0", "0", "1", "0", "0", "0", "0", "1", "1", "1", "1", "0", "0", "0", "0", "0", "0", "1", "1", "1", "1", "0", "0", "0", "0", "0", "0", "1", "1", "1", "1", "1", "1", "0", "0", "0", "0"], 7 | ["0", "0", "0", "1", "0", "0", "0", "0", "0", "1", "0", "0", "0", "0", "1", "0", "0", "0", "1", "0", "0", "0", "1", "1", "1", "0", "0", "0", "0", "1", "0", "0", "0", "1", "0", "0", "0", "1", "1", "1", "0", "0", "0", "1", "0", "0", "0", "1", "1", "1", "0", "0", "0", "1", "1", "1", "0", "0", "0", "1", "1", "1", "0"], 8 | ["0", "0", "0", "0", "1", "0", "0", "0", "0", "0", "1", "0", "0", "0", "0", "1", "0", "0", "0", "1", "0", "0", "1", "0", "0", "1", "1", "0", "0", "0", "1", "0", "0", "0", "1", "0", "0", "1", "0", "0", "1", "1", "0", "0", "1", "0", "0", "1", "0", "0", "1", "1", "0", "1", "0", "0", "1", "1", "0", "1", "1", "0", "1"], 9 | ["0", "0", "0", "0", "0", "1", "0", "0", "0", "0", "0", "1", "0", "0", "0", "0", "1", "0", "0", "0", "1", "0", "0", "1", "0", "1", "0", "1", "0", "0", "0", "1", "0", "0", "0", "1", "0", "0", "1", "0", "1", "0", "1", "0", "0", "1", "0", "0", "1", "0", "1", "0", "1", "0", "1", "0", "1", "0", "1", "1", "0", "1", "1"], 10 | ["0", "0", "0", "0", "0", "0", "1", "0", "0", "0", "0", "0", "1", "0", "0", "0", "0", "1", "0", "0", "0", "1", "0", "0", "1", "0", "1", "1", "0", "0", "0", "0", "1", "0", "0", "0", "1", "0", "0", "1", "0", "1", "1", "0", "0", "0", "1", "0", "0", "1", "0", "1", "1", "0", "0", "1", "0", "1", "1", "0", "1", "1", "1"] 11 | ], 12 | 13 | "weights": [ 14 | ["0", "1", "2", "0", "0", "7", "0"], 15 | ["1", "0", "1", "6", "0", "3", "0"], 16 | ["2", "1", "0", "0", "4", "0", "5"], 17 | ["0", "6", "0", "0", "1", "0", "2"], 18 | ["0", "0", "4", "1", "0", "0", "3"], 19 | ["7", "3", "0", "0", "0", "0", "0"], 20 | ["0", "0", "5", "2", "3", "0", "0"] 21 | ] 22 | } -------------------------------------------------------------------------------- /sequencer/mimc.js: -------------------------------------------------------------------------------- 1 | import { Scalar, getCurveFromName } from "ffjavascript"; 2 | import { ethers } from "ethers"; 3 | 4 | const SEED = "mimcsponge"; 5 | const NROUNDS = 220; 6 | 7 | export default async function buildMimcSponge() { 8 | const bn128 = await getCurveFromName("bn128", true); 9 | return new MimcSponge(bn128.Fr); 10 | } 11 | 12 | class MimcSponge { 13 | constructor (F) { 14 | this.F = F; 15 | this.cts = this.getConstants(SEED, NROUNDS); 16 | } 17 | 18 | getIV (seed) { 19 | const F = this.F; 20 | if (typeof seed === "undefined") seed = SEED; 21 | const c = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(seed+"_iv")); 22 | const cn = Scalar.e(c); 23 | const iv = cn.mod(F.p); 24 | return iv; 25 | }; 26 | 27 | getConstants (seed, nRounds) { 28 | const F = this.F; 29 | if (typeof seed === "undefined") seed = SEED; 30 | if (typeof nRounds === "undefined") nRounds = NROUNDS; 31 | const cts = new Array(nRounds); 32 | let c = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(SEED));; 33 | for (let i=1; i mapping (Node => uint256)) stakes; 13 | struct PendingTx { 14 | uint8 optype; 15 | bytes20 txDataHash; 16 | uint64 expiryBlock; 17 | } 18 | 19 | mapping (uint64 => PendingTx) internal transactionQueue; 20 | uint64 public firstUnprocessedTx; 21 | uint64 public totalUnprocessedTxs; 22 | 23 | mapping(bytes32 => bool) public nullifierHashes; 24 | mapping(uint256 => bytes32) public filledSubtrees; 25 | mapping(uint256 => bytes32) public roots; 26 | uint32 public constant ROOT_HISTORY_SIZE = 30; 27 | uint32 public currentRootIndex = 0; 28 | uint32 public nextIndex = 0; 29 | 30 | constructor(uint32 _levels, IHasher _hasher) { 31 | require(_levels > 0, "_levels should be greater than zero"); 32 | require(_levels < 32, "_levels should be less than 32"); 33 | levels = _levels; 34 | hasher = _hasher; 35 | 36 | for (uint32 i = 0; i < _levels; i++) { 37 | filledSubtrees[i] = zeros(i); 38 | } 39 | roots[0] = zeros(_levels - 1); 40 | } 41 | 42 | function hashLeftRight( 43 | IHasher _hasher, 44 | bytes32 _left, 45 | bytes32 _right 46 | ) public pure returns (bytes32) { 47 | require(uint256(_left) < FIELD_SIZE, "_left should be inside the field"); 48 | require(uint256(_right) < FIELD_SIZE, "_right should be inside the field"); 49 | uint256 R = uint256(_left); 50 | uint256 C = 0; 51 | (R, C) = _hasher.MiMCSponge(R, C); 52 | R = addmod(R, uint256(_right), FIELD_SIZE); 53 | (R, C) = _hasher.MiMCSponge(R, C); 54 | return bytes32(R); 55 | } 56 | 57 | 58 | function _insert(bytes32 _leaf) internal returns (uint32 index) { 59 | uint32 _nextIndex = nextIndex; 60 | require( 61 | _nextIndex != uint32(2) ** levels, 62 | "Merkle tree is full. No more leaves can be added" 63 | ); 64 | uint32 currentIndex = _nextIndex; 65 | bytes32 currentLevelHash = _leaf; 66 | bytes32 left; 67 | bytes32 right; 68 | 69 | for (uint32 i = 0; i < levels; i++) { 70 | if (currentIndex % 2 == 0) { 71 | left = currentLevelHash; 72 | right = zeros(i); 73 | filledSubtrees[i] = currentLevelHash; 74 | } else { 75 | left = filledSubtrees[i]; 76 | right = currentLevelHash; 77 | } 78 | currentLevelHash = hashLeftRight(uint256(left), uint256(right)); 79 | currentIndex /= 2; 80 | } 81 | 82 | function ProveBlock(bytes scores, bytes proofData) external { 83 | if (totalUnprocessedTxs < N) { 84 | //reject 85 | } 86 | 87 | Verifier.checkProof(scores, proofData) { 88 | //check proof using verifier contract 89 | } 90 | } 91 | 92 | function forgeBatch { 93 | } 94 | 95 | function addL1Transaction { 96 | } 97 | 98 | function withdrawMerkleProof{ 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /contracts/old_contracts/sybil.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | pragma solidity >=0.8.2 <0.9.0; 4 | 5 | contract Sybil { 6 | uint number_nodes; 7 | mapping (uint => address) nodes; 8 | mapping (address => uint) deposits; 9 | mapping (address => uint) totalStaked; 10 | mapping (address => uint) scores; 11 | mapping (address => mapping (address => uint)) stakes; 12 | 13 | constructor() { 14 | number_nodes = 0; 15 | } 16 | 17 | 18 | function addDeposit() external payable { 19 | deposits[msg.sender] += msg.value; 20 | nodes[number_nodes] = msg.sender; 21 | number_nodes = number_nodes + 1; 22 | } 23 | 24 | function increaseStake(address target, uint amount) public { 25 | if (totalStaked[msg.sender] + amount <= deposits[msg.sender]) { 26 | stakes[msg.sender][target] += amount; 27 | totalStaked[msg.sender] += amount; 28 | } 29 | } 30 | 31 | function decreaseStake(address target, uint amount) public { 32 | stakes[msg.sender][target] -= amount; 33 | totalStaked[msg.sender] -= amount; 34 | } 35 | 36 | 37 | function calculateScores() public { 38 | 39 | } 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | function getDeposit(address node) public view returns (uint) { 48 | return deposits[node]; 49 | } 50 | 51 | function getTotalstaked(address node) public view returns (uint) { 52 | return totalStaked[node]; 53 | } 54 | 55 | function getNumbernodes() public view returns (uint) { 56 | return number_nodes; 57 | } 58 | function getStake(address source, address target) public view returns (uint) { 59 | return stakes[source][target]; 60 | } 61 | 62 | function getNodes() public view returns (address[] memory){ 63 | address[] memory ret = new address[](number_nodes); 64 | for (uint i = 0; i < number_nodes; i++) { 65 | ret[i] = nodes[i]; 66 | } 67 | return ret; 68 | } 69 | 70 | function getTotalstakeds() public view returns (uint[] memory){ 71 | uint[] memory ret = new uint[](number_nodes); 72 | for (uint i = 0; i < number_nodes; i++) { 73 | address add = nodes[i]; 74 | ret[i] = totalStaked[add]; 75 | } 76 | return ret; 77 | } 78 | 79 | function getDeposits() public view returns (uint[] memory){ 80 | uint[] memory ret = new uint[](number_nodes); 81 | for (uint i = 0; i < number_nodes; i++) { 82 | address add = nodes[i]; 83 | ret[i] = deposits[add]; 84 | } 85 | return ret; 86 | } 87 | 88 | function getStakes() public view returns (uint[] memory){ 89 | uint[] memory ret = new uint[](number_nodes*number_nodes); 90 | for (uint i = 0; i < number_nodes; i++) { 91 | for (uint j = 0; j < number_nodes; j++) { 92 | ret[(i*number_nodes)+j]=stakes[nodes[i]][nodes[j]]; 93 | } 94 | } 95 | return ret; 96 | } 97 | 98 | 99 | function getScore(address node) public view returns (uint) { 100 | return scores[node]; 101 | } 102 | 103 | function getScores() public view returns (uint[] memory){ 104 | uint[] memory ret = new uint[](number_nodes); 105 | for (uint i = 0; i < number_nodes; i++) { 106 | address add = nodes[i]; 107 | ret[i] = scores[add]; 108 | } 109 | return ret; 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /circuits/scoring_algorithm.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.0.0; 2 | 3 | /** 4 | * all inputs are scaled up 10^6 since circom only accepts integers as inputs 5 | * @param num_verts - number of vertices in part of graph where scoring algorithm will be run 6 | * @param num_subsets - number of subsets of vertices used in calculating score 7 | * @input weights[num_verts][num_verts] - {Array(Uint180)} - scaled integer representing stake on a link 8 | * @input subsets[num_verts][num_subsets] - {Bool} - Boolean of whether a particular vertex is an element of a particular subset 9 | 10 | **/ 11 | template Num2Bits(n) { 12 | signal input in; 13 | signal output out[n]; 14 | var lc1=0; 15 | 16 | var e2=1; 17 | for (var i = 0; i> i) & 1; 19 | out[i] * (out[i] -1 ) === 0; 20 | lc1 += out[i] * e2; 21 | e2 = e2+e2; 22 | } 23 | lc1 === in; 24 | } 25 | template LessThan(n) { 26 | assert(n <= 252); 27 | signal input in[2]; 28 | signal output out; 29 | 30 | 31 | 32 | component n2b = Num2Bits(n+1); 33 | 34 | n2b.in <== in[0]+ (1<> i) & 1; 19 | out[i] * (out[i] -1 ) === 0; 20 | lc1 += out[i] * e2; 21 | e2 = e2+e2; 22 | } 23 | lc1 === in; 24 | } 25 | template LessThan(n) { 26 | assert(n <= 252); 27 | signal input in[2]; 28 | signal output out; 29 | 30 | 31 | 32 | component n2b = Num2Bits(n+1); 33 | 34 | n2b.in <== in[0]+ (1<=0.8.2 <0.9.0; 4 | 5 | contract Sybil { 6 | // Last account index created inside the rollup 7 | uint48 public lastIdx; 8 | 9 | // Last batch forged 10 | uint32 public lastForgedBatch; 11 | 12 | // Each batch forged will have a correlated 'state root' 13 | mapping(uint32 => uint256) public stateRootMap; 14 | 15 | // Each batch forged will have a correlated 'exit tree' represented by the exit root 16 | mapping(uint32 => uint256) public exitRootsMap; 17 | 18 | // Each batch forged will have a correlated 'l1L2TxDataHash' 19 | mapping(uint32 => bytes32) public l1L2TxsDataHashMap; 20 | 21 | // Mapping of exit nullifiers, only allowing each withdrawal to be made once 22 | // rootId => (Idx => true/false) 23 | mapping(uint32 => mapping(uint48 => bool)) public exitNullifierMap; 24 | 25 | // Map of queues of L1-user-tx transactions, the transactions are stored in bytes32 sequentially 26 | // The coordinator is forced to forge the next queue in the next L1-L2-batch 27 | mapping(uint32 => bytes) public mapL1TxQueue; 28 | 29 | // Ethereum block where the last L1-L2-batch was forged 30 | uint64 public lastL1L2Batch; 31 | 32 | // Queue index that will be forged in the next L1-L2-batch 33 | uint32 public nextL1ToForgeQueue; 34 | 35 | // Queue index wich will be filled with the following L1-User-Tx 36 | uint32 public nextL1FillingQueue; 37 | 38 | // Max ethereum blocks after the last L1-L2-batch, when exceeds the timeout only L1-L2-batch are allowed 39 | uint8 public forgeL1L2BatchTimeout; 40 | 41 | // Event emitted when a L1-user transaction is called and added to the nextL1FillingQueue queue 42 | event L1UserTxEvent( 43 | uint32 indexed queueIndex, 44 | uint8 indexed position, // Position inside the queue where the TX resides 45 | bytes l1UserTx 46 | ); 47 | 48 | // Event emitted every time a batch is forged 49 | event ForgeBatch(uint32 indexed batchNum, uint16 l1UserTxsLen); 50 | 51 | // Event emitted when a withdrawal is done 52 | event WithdrawEvent( 53 | uint48 indexed idx, 54 | uint32 indexed numExitRoot, 55 | bool indexed instantWithdraw 56 | ); 57 | 58 | 59 | 60 | 61 | ////////////// 62 | // Coordinator operations 63 | ///////////// 64 | 65 | /** 66 | * @dev Forge a new batch providing the L2 Transactions, L1Corrdinator transactions and the proof. 67 | * If the proof is succesfully verified, update the current state, adding a new state and exit root. 68 | * In order to optimize the gas consumption the parameters `encodedL1CoordinatorTx`, `l1L2TxsData` and `feeIdxCoordinator` 69 | * are read directly from the calldata using assembly with the instruction `calldatacopy` 70 | * @param newLastIdx New total rollup accounts 71 | * @param newStRoot New state root 72 | * @param newExitRoot New exit root 73 | * @param encodedL1CoordinatorTx Encoded L1-coordinator transactions 74 | * @param l1L2TxsData Encoded l2 data 75 | * @param verifierIdx Verifier index 76 | * @param l1Batch Indicates if this batch will be L2 or L1-L2 77 | * @param proofA zk-snark input 78 | * @param proofB zk-snark input 79 | * @param proofC zk-snark input 80 | * Events: `ForgeBatch` 81 | */ 82 | 83 | function forgeBatch( 84 | uint48 newLastIdx, 85 | uint256 newStRoot, 86 | uint256 newExitRoot, 87 | bytes calldata encodedL1CoordinatorTx, 88 | bytes calldata l1L2TxsData, 89 | bytes calldata feeIdxCoordinator, 90 | uint8 verifierIdx, 91 | bool l1Batch, 92 | uint256[2] calldata proofA, 93 | uint256[2][2] calldata proofB, 94 | uint256[2] calldata proofC 95 | ) external virtual { 96 | // Assure data availability from regular ethereum nodes 97 | // We include this line because it's easier to track the transaction data, as it will never be in an internal TX. 98 | // In general this makes no sense, as callling this function from another smart contract will have to pay the calldata twice. 99 | // But forcing, it avoids having to check. 100 | require( 101 | msg.sender == tx.origin, 102 | "Hermez::forgeBatch: INTENAL_TX_NOT_ALLOWED" 103 | ); 104 | 105 | // ask the auction if this coordinator is allow to forge 106 | require( 107 | hermezAuctionContract.canForge(msg.sender, block.number) == true, 108 | "Hermez::forgeBatch: AUCTION_DENIED" 109 | ); 110 | 111 | if (!l1Batch) { 112 | require( 113 | block.number < (lastL1L2Batch + forgeL1L2BatchTimeout), // No overflow since forgeL1L2BatchTimeout is an uint8 114 | "Hermez::forgeBatch: L1L2BATCH_REQUIRED" 115 | ); 116 | } 117 | 118 | // calculate input 119 | uint256 input = _constructCircuitInput( 120 | newLastIdx, 121 | newStRoot, 122 | newExitRoot, 123 | l1Batch, 124 | verifierIdx 125 | ); 126 | 127 | // verify proof 128 | require( 129 | rollupVerifiers[verifierIdx].verifierInterface.verifyProof( 130 | proofA, 131 | proofB, 132 | proofC, 133 | [input] 134 | ), 135 | "Hermez::forgeBatch: INVALID_PROOF" 136 | ); 137 | 138 | // update state 139 | lastForgedBatch++; 140 | lastIdx = newLastIdx; 141 | stateRootMap[lastForgedBatch] = newStRoot; 142 | exitRootsMap[lastForgedBatch] = newExitRoot; 143 | l1L2TxsDataHashMap[lastForgedBatch] = sha256(l1L2TxsData); 144 | 145 | uint16 l1UserTxsLen; 146 | if (l1Batch) { 147 | // restart the timeout 148 | lastL1L2Batch = uint64(block.number); 149 | // clear current queue 150 | l1UserTxsLen = _clearQueue(); 151 | } 152 | 153 | // auction must be aware that a batch is being forged 154 | hermezAuctionContract.forge(msg.sender); 155 | 156 | emit ForgeBatch(lastForgedBatch, l1UserTxsLen); 157 | } 158 | 159 | 160 | 161 | 162 | } 163 | -------------------------------------------------------------------------------- /algorithm_design/efficient_scoring_algorithm.nb: -------------------------------------------------------------------------------- 1 | << IGraphM` 2 | rg1 = IGWattsStrogatzGame[20, 0.2]; g1 = 3 | CreateGraph[VertexList[rg1], EdgeList[rg1], 4 | RandomInteger[{1, 5}, Length[EdgeList[rg1]]]] 5 | 6 | CreateGraph[verts_, edges_, wts_] := 7 | Graph[Function[v, 8 | Labeled[v, Placed[Style[v, Black, Small], Center]]] /@ verts, 9 | Function[i, 10 | Labeled[edges[[i]][[1]] \[UndirectedEdge] edges[[i]][[2]], 11 | wts[[i]]]] /@ Range[Length[edges]], VertexSize -> 0.5, 12 | VertexStyle -> White, VertexLabelStyle -> 14, EdgeWeight -> wts] 13 | 14 | GenerateRandomGraph[n_, r_, k_] := 15 | Module[{p, edges, done = False, i, wts}, 16 | While[done == False, 17 | done = True; 18 | p = PermutationReplace[Range[n r], RandomPermutation[n r]]; 19 | edges = 20 | Sort /@ Function[e, Mod[e, n] + 1] /@ 21 | Function[i, {p[[2 i - 1]], p[[2 i]]}] /@ Range[Floor[n r/2]]; 22 | For[i = 1, i <= Length[edges], i++, 23 | If[edges[[i]][[1]] == edges[[i]][[2]], done = False, Nothing]]; 24 | If[done == True && Length[DeleteDuplicates[edges]] < Length[edges], 25 | done = False, Nothing]; 26 | ]; 27 | wts = RandomInteger[{1, k}, Length[edges]]; 28 | CreateGraph[Range[n], edges, wts] 29 | ] 30 | 31 | g = GenerateRandomGraph[40, 4, 5] 32 | W = Normal[WeightedAdjacencyMatrix[g]] 33 | 34 | 35 | RunDiffusionProcess[W_, v_, epsilon_, p_] := 36 | Module[{mass, rank, queue, node, val, rem, prev, s}, 37 | mass = ConstantArray[0, Length[W]]; 38 | rank = ConstantArray[0, Length[W]]; 39 | mass[[v]] = 1; 40 | queue = 41 | Select[Range[Length[W]], mass[[#]] >= epsilon Total[W[[v]]] &]; 42 | s = 0; 43 | While[s < 1000 && Length[queue] > 0, 44 | node = queue[[1]]; 45 | val = mass[[node]]; 46 | rank[[node]] += p val; 47 | mass[[node]] = 0.5 (1 - p) val; 48 | mass += 0.5 (1 - p) val W[[node]]/Total[W[[node]]]; 49 | queue = 50 | Select[Range[Length[W]], mass[[#]] >= epsilon Total[W[[v]]] &]; 51 | s += 1; 52 | ]; 53 | Print[s]; 54 | SortBy[Range[Length[W]], -rank[[#]] &] 55 | ] 56 | 57 | 58 | SubsetIndicator[A_] := 59 | Total[Function[i, UnitVector[Length[W], i]] /@ A] 60 | 61 | 62 | Score[epsilon_, p_] := Module[{result}, 63 | result = RunDiffusionProcess[W, 1, epsilon, p]; 64 | Min[Function[ 65 | k, (SubsetIndicator[result[[1 ;; k]]] . W . 66 | SubsetIndicator[result[[k + 1 ;; Length[W]]]])/k] /@ 67 | Range[Floor[Length[W]/2]]] 68 | ] 69 | 70 | 71 | 72 | (* scoring metrics *) 73 | 74 | invdist[g_, s_, t_, A_] := Module[{sg, vsg, d}, 75 | sg = Graph[VertexList[g], A]; 76 | 1/GraphDistance[sg, s, t] 77 | ] 78 | 79 | between[g_, s_, t_, A_] := Module[{sps, spsA, d}, 80 | d = GraphDistance[g, s, t]; 81 | sps = FindPath[g, s, t, {d}, All]; 82 | spsA = Select[sps, IntersectingQ[A, pathedges[#]] &]; 83 | If[Length[sps] == 0, 0, Length[spsA]/Length[sps]] 84 | ] 85 | 86 | betweens[g_, s_, t_, A_] := Module[{d , sps, spsA}, 87 | d = GraphDistance[g, s, t]; 88 | sps = FindPath[g, s, t, {d}, All]; 89 | spsA = Select[sps, SubsetQ[A, pathedges[#]] &]; 90 | If[Length[sps] == 0, 0, Length[spsA]/Length[sps]] 91 | ] 92 | 93 | 94 | 95 | algoy[graph_, verts_, k_] := 96 | Module[{v, sg, alpha, i, tau, j, test = Range[0, 7, 0.1], 97 | res}, \ 98 | alpha = ConstantArray[0, Length[verts]]; 99 | v = verts[[1]]; 100 | alpha[[v]] = 1; 101 | sg = Subgraph[graph, {v}]; 102 | Print[HighlightGraph[wg[graph, alpha], sg, 103 | GraphHighlightStyle -> "Thick"]]; 104 | For[i = 2, i <= Length[verts], i++, 105 | v = verts[[i]]; 106 | sg = Subgraph[graph, verts[[Range[i]]]]; 107 | res = 0; 108 | For[j = 1, j <= Length[test], j++, 109 | tau = alpha; 110 | tau[[v]] = test[[j]]; 111 | res = If[L[sg, tau] <= k, test[[j]], res]; 112 | Print[L[sg, tau]]; 113 | ]; 114 | alpha[[v]] = res; 115 | Print[ 116 | HighlightGraph[wg[graph, alpha], sg, 117 | GraphHighlightStyle -> "Thick"]]; 118 | ]; 119 | ] 120 | 121 | 122 | (* how phi evolves given a sequence of edges, phi_0[v] = 1 if v=1 and \ 123 | 0 otherwise *) 124 | evolvei[seq_] := 125 | Module[{verts = Range[5], edges = {}, g, phi, G, G0, i}, 126 | G0 = Graph[verts, 127 | Function[e, e[[1]] \[UndirectedEdge] e[[2]]] /@ seq, 128 | VertexSize -> Medium, VertexLabels -> Placed["Name", Center]]; 129 | phi = Function[v, If[v == 1, 1, 0]] /@ verts; 130 | For[i = 1, i <= Length[seq], i++, 131 | AppendTo[edges, seq[[i]] ]; 132 | g = Graph[verts, 133 | Function[e, e[[1]] \[UndirectedEdge] e[[2]]] /@ edges, 134 | VertexSize -> Medium, VertexLabels -> Placed["Name", Center]]; 135 | phi = thetag[g, phi]; 136 | G = Graph[G0, VertexWeight -> N[phi, 3], 137 | VertexLabels -> Placed["VertexWeight", Center], 138 | VertexLabelStyle -> Directive[Red, Bold, 15], Options[G0]]; 139 | Print[ 140 | HighlightGraph[G, 141 | Function[e, e[[1]] \[UndirectedEdge] e[[2]]] /@ edges, 142 | GraphHighlightStyle -> "Thick"]] 143 | ]; 144 | ] 145 | evolve[graph_, phi0_, seq_] := Module[{verts, edges, g, phi, G, G0, i}, 146 | verts = VertexList[graph]; 147 | edges = Function[e, {e[[1]], e[[2]]}] /@ EdgeList[graph]; 148 | G0 = Graph[verts, 149 | Function[e, e[[1]] \[UndirectedEdge] e[[2]]] /@ Union[edges, seq], 150 | VertexSize -> Medium, VertexLabels -> Placed["Name", Center]]; 151 | phi = phi0; 152 | Print[Graph[G0, VertexWeight -> N[phi, 3], 153 | VertexLabels -> Placed["VertexWeight", Center], 154 | VertexLabelStyle -> Directive[Red, Bold, 15], Options[G0]]]; 155 | For[i = 1, i <= Length[seq], i++, 156 | AppendTo[edges, seq[[i]] ]; 157 | g = Graph[verts, 158 | Function[e, e[[1]] \[UndirectedEdge] e[[2]]] /@ edges, 159 | VertexSize -> Medium, VertexLabels -> Placed["Name", Center]]; 160 | phi = thetag[g, phi]; 161 | G = Graph[G0, VertexWeight -> N[phi, 3], 162 | VertexLabels -> Placed["VertexWeight", Center], 163 | VertexLabelStyle -> Directive[Red, Bold, 15], Options[G0]]; 164 | Print[ 165 | HighlightGraph[G, 166 | Function[e, e[[1]] \[UndirectedEdge] e[[2]]] /@ edges, 167 | GraphHighlightStyle -> "Thick"]] 168 | ]; 169 | ] 170 | 171 | 172 | (* choose scoring metric *) 173 | thetag[g_, phi_] := Module[{val, sh, verts, edges}, 174 | verts = VertexList[g]; 175 | edges = Function[e, {e[[1]], e[[2]]}] /@ EdgeList[g]; 176 | val[A_] := 177 | Total[Function[p, 178 | Sqrt[phi[[p[[1]]]] phi[[p[[2]]]]] between[g, p[[1]], p[[2]], 179 | A]] /@ Subsets[verts, {2}]]; 180 | sh[e_] := 181 | Expand[Simplify[ 182 | Total[Function[ 183 | A, (val[Union[A, {e}]] - val[A])/ 184 | Binomial[Length[edges] - 1, Length[A]]] /@ 185 | Subsets[Complement[edges, {e}]]]/Length[edges]]]; 186 | nu[g, sh /@ edges] 187 | ] 188 | 189 | 190 | (*find fixed points of theta with cords < B using step size d*) 191 | 192 | findfps[g_, B_, d_] := Module[{verts, vol, grid, cands}, 193 | verts = VertexList[g]; 194 | grid = 195 | Flatten[Table @@ 196 | Prepend[Function[x, {x, d, B, d}] /@ xvec[g], xvec[g]], 197 | Length[verts] - 1]; 198 | cands = Function[alpha, theta[g, alpha]] /@ grid; 199 | DeleteDuplicates[Select[cands, theta[g, #] == # &]] 200 | ] 201 | findfps1[g_, B_, d_] := 202 | DeleteDuplicates[ 203 | Select[Function[alpha, theta1[g, alpha]] /@ 204 | Flatten[Table @@ 205 | Prepend[Function[x, {x, d, B, d}] /@ xvec[g], xvec[g]], 206 | Length[VertexList[g]] - 1], theta1[g, #] == # &]] 207 | findfps2[g_, B_, d_] := Module[{verts, vol, grid, cands}, 208 | verts = VertexList[g]; 209 | grid = 210 | Flatten[Table @@ 211 | Prepend[Function[x, {x, d, B, d}] /@ xvec[g], xvec[g]], 212 | Length[verts] - 1]; 213 | cands = Function[alpha, theta1[g, alpha]] /@ grid; 214 | DeleteDuplicates[Select[cands, theta1[g, #] == # &]] 215 | ] 216 | 217 | dev[g_, alpha_, delta_] := Module[{verts, vol, sets}, 218 | verts = VertexList[g]; 219 | vol = Total[alpha]; 220 | sets = 221 | Select[Subsets[verts], 222 | Length[#] > 0 && Total[alpha[[#]]] <= vol/2 &]; 223 | Max @@ 224 | Function[S, 225 | delta^Length[S] (Min @@ 226 | Function[A, 227 | If[SubsetQ[A, S], bdry[g, A]^2 - Total[alpha[[A]]], 228 | Infinity]] /@ sets)] /@ sets 229 | ] 230 | 231 | 232 | 233 | des[g_, x0_] := 234 | Module[{stepsize, steps, verts, sets, x, i, B, grad, res = {x0}}, 235 | stepsize = 0.1; 236 | steps = 100; 237 | verts = VertexList[g]; 238 | sets = Select[Subsets[verts], 1 <= Length[#] <= Length[verts] - 1 &]; 239 | x = x0 + proj[g, RandomReal[{-0.1, 0.1}, Length[verts]]]; 240 | For[i = 1, i <= steps, i++, 241 | B = SortBy[ 242 | Function[ 243 | A, {A, Total[x[[A]]] (1 - Total[x[[A]]])/bdry[g, A]^3}] /@ 244 | sets, Last][[-1]][[1]]; 245 | grad = 246 | Function[v, 247 | If[MemberQ[B, v], 1 - Total[x[[B]]], Total[x[[B]]]]/ 248 | bdry[g, B]] /@ verts; 249 | x = x - stepsize proj[g, grad]; 250 | AppendTo[res, x]; 251 | ]; 252 | Round[res, 0.001] 253 | ] 254 | 255 | 256 | pts[g_] := 257 | Module[{verts, x, sets, bdrys, vecs, inp, pp, i, v, res = {}, 258 | reg = {}}, 259 | verts = VertexList[g]; 260 | x = xvec[g]; 261 | sets = Subsets[verts, {1, Length[x] - 1}]; 262 | bdrys = Function[A, bdry[g, A]^2] /@ sets; 263 | vecs = Function[A, Total[x[[A]]]] /@ sets; 264 | inp = And @@ Function[xi, xi >= 0] /@ x && 265 | And @@ Function[A, 266 | Total[x[[A]]] > Total[x[[Complement[verts, A]]]] || 267 | Total[x[[A]]] <= bdry[g, A]^2] /@ sets; 268 | pp = DeleteDuplicates[ 269 | Flatten[Function[eqs, Solve[And @@ eqs, x]] /@ 270 | Subsets[Function[A, bdry[g, A]^2 == Total[x[[A]]]] /@ 271 | sets, {Length[x]}], 1]]; 272 | For[i = 1, i <= Length[pp], i++, v = Simplify[inp /. pp[[i]]]; 273 | If[BooleanQ[v] == True , 274 | If[v == True, AppendTo[res, x /. pp[[i]]], Nothing], 275 | AppendTo[reg, {pp[[i]], v}]]]; 276 | {res, reg} 277 | ] 278 | isor[g_] := Module[{verts}, 279 | verts = VertexList[g]; 280 | ListPlot[ 281 | Function[A, {Length[A], bdry[g, A]}] /@ 282 | Table[RandomSample[verts, RandomInteger[{1, Length[verts]}]], 283 | 300], PlotRange -> Full] 284 | ] 285 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tokamak Sybil Resistence 2 | ## Overview 3 | The goal is to create a register for Ethereuem addresses, where each address on the register is assigned a ‘score’ in a sybil-resistent way. This means that although someone can create an unlimited number of Ethereum addresses and add them to the register, the total ‘score’ of the addresses will still be limited. These scores can then be used as a measure of uniqueness. Applications that require protection against sybil attacks can use this register. 4 | 5 | The project uses a mechanism similar to a web-of-trust. Adding an address to the register requires a deposit (which can be retrieved by removing address from the register). Then any two registered address can vouch for each other by both ‘staking’ some of their deposit on each other. This gives the data of a weighted graph, where the vertices are the addresses and the edges are the pairs of addressses that have vouched for each other (the weight of an edge is the amount that was staked by the pair on each other). 6 | 7 | From this weighted graph, a score is calculated for each address. The score is a measure of how well connected a particular address is and is robust against manipulation and collusion. The algorithm to compute the score is computationally expensive so it is not possible to run the scoring algorithm in the L1 contract. Instead, the contract requires the updated scores to be submitted to it along with a proof that the score updates are correct. The contract will verify this proof and then update the state. To incentivize people to compute the scores for the contract, it will award a small amount of TON for a correct proof. 8 | 9 | Applications can integrate with this project to obtain sybil-resistence, for example for an airdrop or for a voting application. Users can also use this register in a privacy preserving way by creating a proof that they own an address in the register with a score above a certain threshold. 10 | 11 | ## Scoring algorithm 12 | The way the scoring algorithm works is by starting with a set (denoted by a calligraphic A) of subsets of the nodes (in practice, these will be the subsets that are smaller than a certain threshold). Then to compute the score for a node, we look at each of these subsets that contains the node and compute the sum of the weights of the links that are leaving this subset and divide it by the number of elements in the subset. The score is then defined to be minimum of these values. More precisely, the scoring function **_f_** is defined by:![eq1](https://github.com/tokamak-network/proof-of-uniqueness/blob/main/imgs/img1.png?raw=true) 13 | 14 | where 15 | 16 | ![graph](https://github.com/tokamak-network/proof-of-uniqueness/blob/main/imgs/img3.png?raw=true) 17 | 18 | and 19 | 20 | ![graph](https://github.com/tokamak-network/proof-of-uniqueness/blob/main/imgs/img2.png?raw=true) 21 | 22 | 23 | ### Example: 24 | 25 | ![graph](https://github.com/tokamak-network/proof-of-uniqueness/blob/main/imgs/graph-example.png?raw=true) 26 | 27 | 28 | In this example, let us suppose the set of subsets we are using is all subsets with three or less nodes. Then, to compute the score for node 5, we look at all subsets with three or less elements, that contain node 5: 29 | 30 | ```{5},{0,5},{1,5},{2,5},{3,5},{4,5},{5,6},{0,1,5},{0,2,5},{0,3,5},{0,4,5},{0,6,5},{1,2,5},{1,3,5},{1,4,5},{1,5,6},{2,3,5},{2,4,5},{2,5,6},{3,4,5},{3,5,6},{4,5,6}``` 31 | 32 | Then for each of these subsets, we compute the sum of the weights of the links leaving the subset. For example, for the subset ```{1,3,5}``` the sum of the links leaving it is ```7+1+1+1+2=12```. Then we divide this by the number of elements in the subset we get ```12/3=4```. If we do this for each subset we get a list of values, and then if we take the minimum of this list of values we get the score for node 5. 33 | 34 | ### Scoring circuit 35 | This algorithm is implemented in the file [scoring_algorithm.circom](https://github.com/tokamak-network/proof-of-uniqueness/blob/main/scoring_algorithm.circom). It is implemented as a circom circuit so that the scores can be computed off chain and the proof sent to convince the L1 contract that the scores have been computed correctly. 36 | 37 | 38 | ## Usage 39 | To test this code first install circom and snarkjs: https://docs.circom.io/getting-started/installation/. 40 | 41 | Our circuit can use both the Groth16 and Plonk zk-SNARK schemes. Groth16 has a smaller proof size compared to Plonk, but it requires a specific setup for each individual circuit. On the other hand, Plonk has a larger proof size but only requires a universal setup that can be used across different circuits without needing a new setup for each one. 42 | 43 | Since we use Circom to write our circuits, the constraints are expressed in R1CS format. However, Plonk uses its own constraint format, which results in an increased number of constraints when converting the same circuit into Plonk’s format. 44 | 45 | The following example will use a pre-prepared universal setup file. By downloading the setup file provided by [the snarkjs repo](https://github.com/iden3/snarkjs?tab=readme-ov-file#7-prepare-phase-2), you can skip the setup process. In our case, we need a setup with a power of at least 15. Note that as the power increases, the file size grows exponentially, so be cautious when downloading larger setup files. 46 | 47 | Our example uses a universal setup file provided by snarkjs with a power of 15(`powersOfTau28_hez_final_15.ptau`). 48 | If you want to start the setup by own, refer to [this section](https://github.com/iden3/snarkjs?tab=readme-ov-file#1-start-a-new-powers-of-tau-ceremony) in snarkjs. 49 | 50 | The overall flow is as follows: **circuit compile -> setup -> proof -> verify** 51 | 52 | 53 | ### 1. Cicuit compile 54 | 55 | ```bash 56 | cd circuits 57 | circom scoring_algorithm.circom --r1cs --wasm 58 | ``` 59 | The circom command takes one input (the circuit to compile, in our case circuit.circom) and three options: 60 | - r1cs: generates circuit.r1cs (the r1cs constraint system of the circuit in binary format). 61 | - wasm: generates circuit.wasm (the wasm code to generate the witness). 62 | 63 | Next, you need to create the input for this circuit. A test input is currently provided: `test_input1.json`. 64 | 65 | Once the input is ready, you can use the Javascript/wasm program generated in the previous step to create a witness (values of all the wires) for our input: 66 | ```bash 67 | scoring_algorithm_js$ node generate_witness.js scoring_algorithm.wasm ../test_input1.json ../witness.wtns 68 | ``` 69 | You can verify that the witness was generated correctly with the following command: 70 | ```bash 71 | snarkjs wtns check scoring_algorithm.r1cs witness.wtns 72 | ``` 73 | 74 | ### 2. Setup 75 | 76 | #### Plonk 77 | ```bash 78 | snarkjs plonk setup scoring_algorithm.r1cs powersOfTau28_hez_final_15.ptau circuit_final.zkey 79 | ``` 80 | 81 | 82 | #### groth16 83 | As mentioned earlier, Groth16 requires an additional setup step for each individual circuit. If you want to proceed with Groth16, follow the snarkjs [groth16 setup documentation](https://github.com/iden3/snarkjs?tab=readme-ov-file#groth16). 84 | 85 | 86 | ### 3. Proof 87 | 88 | #### Export the verification key 89 | ```bash 90 | snarkjs zkey export verificationkey circuit_final.zkey verification_key.json 91 | ``` 92 | 93 | ### Create the proof 94 | 95 | #### Plonk 96 | ```bash 97 | snarkjs plonk prove circuit_final.zkey witness.wtns proof.json public.json 98 | ``` 99 | 100 | 101 | #### Groth16 102 | ```bash 103 | snarkjs groth16 prove circuit_final.zkey witness.wtns proof.json public.json 104 | ``` 105 | Note: The circuit_final.zkey for Groth16 is different from that for Plonk and requires the additional Groth16 setup mentioned above. 106 | 107 | ### 4. Verify 108 | 109 | #### Plonk 110 | ```bash 111 | snarkjs plonk verify verification_key.json public.json proof.json 112 | ``` 113 | 114 | 115 | #### Groth16 116 | ```bash 117 | snarkjs groth16 verify verification_key.json public.json proof.json 118 | ``` 119 | 120 | 121 | 122 | ### Extra: Turn the verifier into a smart contract 123 | ```bash 124 | snarkjs zkey export solidityverifier circuit_final.zkey verifier.sol 125 | ``` 126 | 127 | 128 | 129 | 130 | ## Contract design 131 | 132 | The contract will keep a record of the id, owner, deposit and score for each node, as well as how much each node has staked on each other node. This data will be organized into a Merkle tree with a subtree for each leaf. For this tree will use the MiMC hash function. 133 | 134 | 135 | ![graph](https://github.com/tokamak-network/proof-of-uniqueness/blob/main/imgs/img4.jpg?raw=true) 136 | 137 | Each leaf in the main accounts tree stores the ETH address associated with the account, how much TON that account has deposited into the rollup, the “uniqueness” score for that account, and the root hash of a subtree of link data. The link subtree stores the amount that has been staked by that account on a particular other account in the system, (using the account’s id in the account tree). For example if the first account has staked 0.5 TON on the account with id 3, then the third leaf of the subtree for the first leaf of the account tree will store the value 0.5. 138 | 139 | The amounts staked on a particular account are used in the scoring algorithm to compute it’s uniqueness score. To prevent this from being manipulatable, we need to ensure that the total amount that an account can stake on other accounts is limited by how much TON they have deposited into the rollup. Hence we make the rule that a valid instance of the state tree must satisfy 140 | 141 | ![graph](https://github.com/tokamak-network/proof-of-uniqueness/blob/main/imgs/img5.png?raw=true) 142 | 143 | for each leaf of the account tree. 144 | 145 | The contract will accept transactions that update this tree, and store them in a queue. Then anyone can create a batch of these transactions, compute the update of the state tree and provide a proof of correctness. At any time, there is only one possibility for the next block, namely the next N txns in the queue (if the size of the queue is less than N, then the contract wont accept a proposed block). This means we dont need any leader selection process, its just the first valid proof from any user which creates the next block. 146 | 147 | ## Ownership proofs 148 | Given the current state root, a user can make a proof of ownership of a leaf with score above a certain threshold. A different proof circuit is needed depending on the threshold. Also, any contract that wishes to intergrate with this system and check ownership proofs would need its own verifier contract and also a mapping to record nullifiers to ensure each user can only prove once. 149 | -------------------------------------------------------------------------------- /algorithm_design/test_scoring_algorithm.nb: -------------------------------------------------------------------------------- 1 | xvec[g_] := 2 | Function[l, ToExpression[StringJoin["x", ToString[l]]]] /@ VertexList[g]; 3 | 4 | bdry[g_, A_] := 5 | Length[Select[Function[e, {e[[1]], e[[2]]}] /@ EdgeList[g], 6 | Length[Intersection[A, #]] == 1 &]] 7 | 8 | gg[verts_, edges_] := 9 | Graph[Function[v, 10 | Labeled[v, Placed[Style[v, Black, Small], Center]]] /@ verts, 11 | Function[e, e[[1]] \[UndirectedEdge] e[[2]]] /@ edges, 12 | VertexSize -> 0.5, VertexStyle -> White, VertexLabelStyle -> 14] 13 | 14 | 15 | randreg[n_, r_] := Module[{p, edges, done = False, i}, 16 | While[done == False, 17 | done = True; 18 | p = PermutationReplace[Range[n r], RandomPermutation[n r]]; 19 | edges = 20 | Sort /@ Function[e, Mod[e, n] + 1] /@ 21 | Function[i, {p[[2 i - 1]], p[[2 i]]}] /@ Range[Floor[n r/2]]; 22 | For[i = 1, i <= Length[edges], i++, 23 | If[edges[[i]][[1]] == edges[[i]][[2]], done = False, Nothing]]; 24 | If[done == True && Length[DeleteDuplicates[edges]] < Length[edges], 25 | done = False, Nothing]; 26 | ]; 27 | gg[Range[n], edges] 28 | ] 29 | 30 | cheeger[g_] := Module[{verts, sets, conds, x}, 31 | x = xvec[g]; 32 | verts = VertexList[g]; 33 | sets = Subsets[verts, {1, Length[verts]/2}]; 34 | Min @@ Function[A, bdry[g, A]/Length[A]] /@ sets 35 | ] 36 | 37 | int[A_] := 38 | Module[{res}, res = {}; 39 | For[i = 1, i <= m + n, i++, 40 | If[Length[Intersection[A, edges[[i]]]] == 2, res = Append[res, i], 41 | res = res]]; res] 42 | 43 | bdry[A_] := 44 | Module[{res}, res = {}; 45 | For[i = 1, i <= m + n, i++, 46 | If[Length[Intersection[A, edges[[i]]]] == 1, res = Append[res, i], 47 | res = res]]; res] 48 | 49 | ext[v_, A_, y_] := Module[{bd, gp}, 50 | bd = Select[bdry[A], MemberQ[edges[[#]], v] &]; 51 | Total[y[[bd]]] 52 | ] 53 | 54 | vol[A_, y_] := Module[{res}, res = {}; 55 | res = Total[ 56 | Function[v, 57 | Total[y[[Select[Range[n + m], MemberQ[edges[[#]], v] &]]]]^ 58 | sig] /@ A]; 59 | res 60 | ] 61 | 62 | 63 | (* scoring metrics *) 64 | invdist[g_, s_, t_, A_] := Module[{sg, vsg, d}, 65 | sg = Graph[VertexList[g], A]; 66 | 1/GraphDistance[sg, s, t] 67 | ] 68 | between[g_, s_, t_, A_] := Module[{sps, spsA, d}, 69 | d = GraphDistance[g, s, t]; 70 | sps = FindPath[g, s, t, {d}, All]; 71 | spsA = Select[sps, IntersectingQ[A, pathedges[#]] &]; 72 | If[Length[sps] == 0, 0, Length[spsA]/Length[sps]] 73 | ] 74 | betweens[g_, s_, t_, A_] := Module[{d , sps, spsA}, 75 | d = GraphDistance[g, s, t]; 76 | sps = FindPath[g, s, t, {d}, All]; 77 | spsA = Select[sps, SubsetQ[A, pathedges[#]] &]; 78 | If[Length[sps] == 0, 0, Length[spsA]/Length[sps]] 79 | ] 80 | 81 | (* choose scoring metric *) 82 | thetag[g_, phi_] := Module[{val, sh, verts, edges}, 83 | verts = VertexList[g]; 84 | edges = Function[e, {e[[1]], e[[2]]}] /@ EdgeList[g]; 85 | val[A_] := 86 | Total[Function[p, 87 | Sqrt[phi[[p[[1]]]] phi[[p[[2]]]]] between[g, p[[1]], p[[2]], 88 | A]] /@ Subsets[verts, {2}]]; 89 | sh[e_] := 90 | Expand[Simplify[ 91 | Total[Function[ 92 | A, (val[Union[A, {e}]] - val[A])/ 93 | Binomial[Length[edges] - 1, Length[A]]] /@ 94 | Subsets[Complement[edges, {e}]]]/Length[edges]]]; 95 | nu[g, sh /@ edges] 96 | ] 97 | 98 | (* how phi evolves given a sequence of edges, phi_0[v] = 1 if v=1 and \ 99 | 0 otherwise *) 100 | evolvei[seq_] := 101 | Module[{verts = Range[5], edges = {}, g, phi, G, G0, i}, 102 | G0 = Graph[verts, 103 | Function[e, e[[1]] \[UndirectedEdge] e[[2]]] /@ seq, 104 | VertexSize -> Medium, VertexLabels -> Placed["Name", Center]]; 105 | phi = Function[v, If[v == 1, 1, 0]] /@ verts; 106 | For[i = 1, i <= Length[seq], i++, 107 | AppendTo[edges, seq[[i]] ]; 108 | g = Graph[verts, 109 | Function[e, e[[1]] \[UndirectedEdge] e[[2]]] /@ edges, 110 | VertexSize -> Medium, VertexLabels -> Placed["Name", Center]]; 111 | phi = thetag[g, phi]; 112 | G = Graph[G0, VertexWeight -> N[phi, 3], 113 | VertexLabels -> Placed["VertexWeight", Center], 114 | VertexLabelStyle -> Directive[Red, Bold, 15], Options[G0]]; 115 | Print[ 116 | HighlightGraph[G, 117 | Function[e, e[[1]] \[UndirectedEdge] e[[2]]] /@ edges, 118 | GraphHighlightStyle -> "Thick"]] 119 | ]; 120 | ] 121 | evolve[graph_, phi0_, seq_] := Module[{verts, edges, g, phi, G, G0, i}, 122 | verts = VertexList[graph]; 123 | edges = Function[e, {e[[1]], e[[2]]}] /@ EdgeList[graph]; 124 | G0 = Graph[verts, 125 | Function[e, e[[1]] \[UndirectedEdge] e[[2]]] /@ Union[edges, seq], 126 | VertexSize -> Medium, VertexLabels -> Placed["Name", Center]]; 127 | phi = phi0; 128 | Print[Graph[G0, VertexWeight -> N[phi, 3], 129 | VertexLabels -> Placed["VertexWeight", Center], 130 | VertexLabelStyle -> Directive[Red, Bold, 15], Options[G0]]]; 131 | For[i = 1, i <= Length[seq], i++, 132 | AppendTo[edges, seq[[i]] ]; 133 | g = Graph[verts, 134 | Function[e, e[[1]] \[UndirectedEdge] e[[2]]] /@ edges, 135 | VertexSize -> Medium, VertexLabels -> Placed["Name", Center]]; 136 | phi = thetag[g, phi]; 137 | G = Graph[G0, VertexWeight -> N[phi, 3], 138 | VertexLabels -> Placed["VertexWeight", Center], 139 | VertexLabelStyle -> Directive[Red, Bold, 15], Options[G0]]; 140 | Print[ 141 | HighlightGraph[G, 142 | Function[e, e[[1]] \[UndirectedEdge] e[[2]]] /@ edges, 143 | GraphHighlightStyle -> "Thick"]] 144 | ]; 145 | ] 146 | 147 | 148 | (*findfps of theta with cords < B using step size d*) 149 | 150 | findfps1[g_, B_, d_] := 151 | Select[Function[alpha, theta1[g, alpha]] /@ 152 | Flatten[Table @@ 153 | Prepend[Function[x, {x, d, B, d}] /@ xvec[g], xvec[g]], 154 | Length[VertexList[g]] - 1], theta1[g, #] == # &] 155 | 156 | dev2[g_, alpha_, delta_] := Module[{vol, verts, sets}, 157 | verts = VertexList[g]; 158 | vol = Total[alpha]; 159 | sets = Select[Subsets[verts], Total[alpha[[#]]] <= vol/2 &]; 160 | Max @@ (Function[S, Abs[1 - L1[g, S, alpha]] delta^Length[S]] /@ 161 | sets) 162 | ] 163 | 164 | dev1[g_, alpha_, delta_] := Module[{vol, verts, sets}, 165 | verts = VertexList[g]; 166 | vol = Total[alpha]; 167 | sets = Select[Subsets[verts], Total[alpha[[#]]] <= vol/2 &]; 168 | Max @@ (Function[S, Abs[1 - 1/L1[g, S, alpha]] delta^Length[S]] /@ 169 | sets) 170 | ] 171 | 172 | 173 | F[g_, alpha_] := Module[{verts, vol, S}, 174 | verts = VertexList[g]; 175 | vol = Total[alpha]; 176 | S = Select[Subsets[verts], Total[alpha[[#]]] <= vol/2 &]; 177 | Max @@ 178 | Function[v, 179 | Abs[alpha[[v]] - 180 | Min @@ (Function[A, 181 | If[MemberQ[A, v], bdry[g, A]/Length[A], Infinity]] /@ 182 | S)]] /@ verts 183 | ] 184 | 185 | algo1[graph_] := 186 | Module[{verts = VertexList[graph], sg = Subgraph[graph, {1, 2}], 187 | alpha = {1, 1}, i}, 188 | Print[HighlightGraph[wg[graph, alpha], sg, 189 | GraphHighlightStyle -> "Thick"]]; 190 | For[i = 3, i <= Length[verts], i++, 191 | sg = Subgraph[graph, Range[i]]; 192 | alpha = theta[sg, Append[alpha, 0]]; 193 | Print[ 194 | HighlightGraph[wg[graph, alpha], sg, 195 | GraphHighlightStyle -> "Thick"]]; 196 | ]; 197 | ] 198 | 199 | algo2[graph_] := 200 | Module[{verts = VertexList[graph], sg = Subgraph[graph, {1, 2}], 201 | alpha = {1, 1}, i}, 202 | Print[HighlightGraph[wg[graph, alpha], sg, 203 | GraphHighlightStyle -> "Thick"]]; 204 | For[i = 3, i <= Length[verts], i++, 205 | sg = Subgraph[graph, Range[i]]; 206 | alpha = theta[sg, theta[sg, Append[alpha, 0]]]; 207 | Print[ 208 | HighlightGraph[wg[graph, alpha], sg, 209 | GraphHighlightStyle -> "Thick"]]; 210 | ]; 211 | ] 212 | 213 | theta[g_, alpha_] := Module[{verts, vol, sets}, 214 | verts = VertexList[g]; 215 | vol = Total[alpha]; 216 | sets = Select[Subsets[verts], Total[alpha[[#]]] <= vol/2 &]; 217 | Function[v, 218 | alpha[[v]] + 219 | Min @@ (Function[A, 220 | If[MemberQ[A, v], (bdry[g, A] - Total[alpha[[A]]])/Length[A], 221 | Infinity]] /@ sets)] /@ verts 222 | ] 223 | 224 | theta1[g_, alpha_] := Module[{verts, vol, sets}, 225 | verts = VertexList[g]; 226 | vol = Total[alpha]; 227 | sets = Select[Subsets[verts], Total[alpha[[#]]] <= vol/2 &]; 228 | Function[v, 229 | Min @@ (Function[A, 230 | If[MemberQ[A, v], bdry[g, A]/Length[A], Infinity]] /@ sets)] /@ 231 | verts 232 | ] 233 | 234 | theta2[g_, alpha_] := 235 | Function[v, 236 | K1[g, Complement[VertexList[NeighborhoodGraph[g, v]], {v}], 237 | alpha]] /@ VertexList[g] 238 | thetak[g_, alpha_, n_] := 239 | Module[{i, res = alpha}, 240 | For[i = 1, i <= n, i++, res = theta[g, res]; Print[res]]] 241 | 242 | 243 | 244 | (* graphs operations *) 245 | gg[verts_, edges_] := 246 | Graph[Function[v, 247 | Labeled[v, Placed[Style[v, Black, Small], Center]]] /@ verts, 248 | Function[e, e[[1]] \[UndirectedEdge] e[[2]]] /@ edges, 249 | VertexSize -> 0.5, VertexStyle -> White, VertexLabelStyle -> 14] 250 | wg[graph_, alpha_] := Module[{phi, verts, edges, G, G0, i}, 251 | verts = VertexList[graph]; 252 | phi = PadRight[alpha, Length[verts]]; 253 | edges = Function[e, {e[[1]], e[[2]]}] /@ EdgeList[graph]; 254 | Graph[Function[v, 255 | Labeled[v, 256 | Placed[{Style[v, Black, Small], 257 | Style[N[phi[[v]], 3], Red, Bold]}, {Center, Above}]]] /@ 258 | verts, Function[e, e[[1]] \[UndirectedEdge] e[[2]]] /@ edges, 259 | VertexSize -> 0.5, VertexStyle -> White, VertexLabelStyle -> 14] 260 | ] 261 | per[g_, order_] := Module[{verts, edges, p, newedges}, 262 | verts = VertexList[g]; 263 | edges = Function[e, {e[[1]], e[[2]]}] /@ EdgeList[g]; 264 | p = FindPermutation[order, verts]; 265 | newedges = Function[e, PermutationReplace[e, p]] /@ edges; 266 | Graph[order, 267 | Function[e, e[[1]] \[UndirectedEdge] e[[2]]] /@ newedges, 268 | VertexSize -> Medium, VertexLabels -> Placed["Name", Center]] 269 | ] 270 | 271 | randreg[n_, r_] := Module[{p, edges, done = False, i}, 272 | While[done == False, 273 | done = True; 274 | p = PermutationReplace[Range[n r], RandomPermutation[n r]]; 275 | edges = 276 | Sort /@ Function[e, Mod[e, n] + 1] /@ 277 | Function[i, {p[[2 i - 1]], p[[2 i]]}] /@ Range[Floor[n r/2]]; 278 | For[i = 1, i <= Length[edges], i++, 279 | If[edges[[i]][[1]] == edges[[i]][[2]], done = False, Nothing]]; 280 | If[done == True && Length[DeleteDuplicates[edges]] < Length[edges], 281 | done = False, Nothing]; 282 | ]; 283 | gg[Range[n], edges] 284 | ] 285 | --------------------------------------------------------------------------------