├── .gitignore ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── contracts ├── Migrations.sol ├── g2Ops.sol ├── ecOps.sol └── dkg.sol ├── test ├── testsData │ ├── constants.js │ ├── happyFlowData.js │ ├── corruptedData.js │ └── corruptedData2.js ├── dkg │ ├── timeout.js │ ├── utils.js │ ├── general.js │ ├── complaint.js │ └── happyFlow.js ├── testHappyFlow.js ├── testTimeouts.js └── testComplaints.js ├── truffle.js ├── truffle-config.js ├── package.json ├── LICENSE ├── simulation ├── scripts │ └── common.sh ├── run.sh ├── README.md ├── src │ ├── bglswrapper.js │ └── app.js └── bglsmain.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | 3 | node_modules 4 | .* 5 | simulation/simulation 6 | simulation/commit_data.json 7 | *.log -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var ecOpsLib = artifacts.require("./ecOps.sol"); 2 | var dkg = artifacts.require("./dkg.sol"); 3 | 4 | const n = 2; 5 | const t = 1; 6 | const deposit = 10 * Math.pow(10,18); 7 | 8 | module.exports = function(deployer) { 9 | deployer.deploy(ecOpsLib); 10 | deployer.link(ecOpsLib, dkg); 11 | deployer.deploy(dkg, t,n, deposit); 12 | }; 13 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/testsData/constants.js: -------------------------------------------------------------------------------- 1 | const phase = { 2 | enrollment: 0, 3 | commit: 1, 4 | postCommit: 2, 5 | endSuccess: 3, 6 | endFail: 4 7 | }; 8 | 9 | const EXCEPTION_PREFIX = "VM Exception while processing transaction: "; 10 | const errTypes = { 11 | revert : "revert", 12 | outOfGas : "out of gas", 13 | invalidJump : "invalid JUMP", 14 | invalidOpcode : "invalid opcode", 15 | stackOverflow : "stack overflow", 16 | stackUnderflow : "stack underflow", 17 | staticStateChange : "static state change" 18 | }; 19 | 20 | 21 | module.exports = { 22 | phase, 23 | EXCEPTION_PREFIX, 24 | errTypes 25 | }; -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | /* 2 | * NB: since truffle-hdwallet-provider 0.0.5 you must wrap HDWallet providers in a 3 | * function when declaring them. Failure to do so will cause commands to hang. ex: 4 | * ``` 5 | * mainnet: { 6 | * provider: function() { 7 | * return new HDWalletProvider(mnemonic, 'https://mainnet.infura.io/') 8 | * }, 9 | * network_id: '1', 10 | * gas: 4500000, 11 | * gasPrice: 10000000000, 12 | * }, 13 | */ 14 | 15 | module.exports = { 16 | // See 17 | // to customize your Truffle configuration! 18 | networks: { 19 | development: { 20 | host: "127.0.0.1", 21 | port: 7545, 22 | network_id: "*" 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * NB: since truffle-hdwallet-provider 0.0.5 you must wrap HDWallet providers in a 3 | * function when declaring them. Failure to do so will cause commands to hang. ex: 4 | * ``` 5 | * mainnet: { 6 | * provider: function() { 7 | * return new HDWalletProvider(mnemonic, 'https://mainnet.infura.io/') 8 | * }, 9 | * network_id: '1', 10 | * gas: 4500000, 11 | * gasPrice: 10000000000, 12 | * }, 13 | */ 14 | 15 | module.exports = { 16 | // See 17 | // to customize your Truffle configuration! 18 | networks: { 19 | development: { 20 | host: "127.0.0.1", 21 | port: 7545, 22 | network_id: "*" 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dkg-on-evm", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/orbs-network/dkg-on-evm.git" 15 | }, 16 | "author": "Orbs", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/orbs-network/dkg-on-evm/issues" 20 | }, 21 | "homepage": "https://github.com/orbs-network/dkg-on-evm#readme", 22 | "dependencies": { 23 | "async": "^2.6.1", 24 | "readline-sync": "^1.4.9", 25 | "truffle": "^4.1.13", 26 | "winston": "^3.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/dkg/timeout.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Close the DKG contract when enrollment phase timeout has expired. 4 | * @param {*} instance 5 | * @param {string} callerAccount the account that will call the tx that 6 | * closes the contract. 7 | */ 8 | async function endEnrollment(instance, callerAccount) { 9 | let arg = callerAccount ? {from: callerAccount} : {}; 10 | let res = await instance.joinTimedOut(arg); 11 | return res.receipt.gasUsed; 12 | } 13 | 14 | 15 | /** 16 | * Close the DKG contract when commit phase timeout has expired. 17 | * @param {*} instance 18 | * @param {string} callerAccount the account that will call the tx that 19 | * closes the contract. 20 | */ 21 | async function endCommit(instance, callerAccount) { 22 | let arg = callerAccount ? {from: callerAccount} : {}; 23 | let res = await instance.commitTimedOut(arg); 24 | return res.receipt.gasUsed; 25 | } 26 | 27 | module.exports = { 28 | endEnrollment, 29 | endCommit 30 | }; -------------------------------------------------------------------------------- /test/dkg/utils.js: -------------------------------------------------------------------------------- 1 | const async = require("async"); 2 | 3 | 4 | 5 | /** 6 | * Returns true iff the elements of 2 arrays are equal. 7 | * @param {object[]} p1 8 | * @param {object[]} p2 9 | */ 10 | function isEqualPoints(p1, p2) { 11 | if (p1.length == p2.length) 12 | { 13 | for(var i = 0; i < p1.length; i++) { 14 | if(p1[i] != p2[i]) { 15 | return false; 16 | } 17 | } 18 | return true; 19 | } 20 | return false; 21 | } 22 | 23 | /** 24 | * Mines numOfBlockToMine at once. 25 | * @param {number} numOfBlockToMine 26 | */ 27 | function forceMineBlocks(numOfBlockToMine) { 28 | var mineArr = []; 29 | for (var i = 0; i < numOfBlockToMine; i++) { 30 | mineArr.push(async.apply(web3.currentProvider.sendAsync, { 31 | jsonrpc: "2.0", 32 | method: "evm_mine", 33 | id: 12345 34 | })); 35 | } 36 | return new Promise( (resolve, reject) => { 37 | async.parallel(mineArr, (err) => { resolve() }); 38 | }); 39 | } 40 | 41 | 42 | module.exports = { 43 | isEqualPoints, 44 | forceMineBlocks 45 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Orbs Network 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /simulation/scripts/common.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | balance=10000000000000000000000000000 4 | accounts="" 5 | 6 | acc=( \ 7 | 0x11c98b8fa69354b26b5db98148a5bc4ef2ebae8187f651b82409f6cefc9bb0b8 \ 8 | 0xc5db67b3865454f6d129ec83204e845a2822d9fe5338ff46fe4c126859e1357e \ 9 | 0x6ac1a8c98fa298af3884406fbd9468dca5200898f065e1283fc85dff95646c25 \ 10 | 0xbd9aebf18275f8074c53036818e8583b242f9bdfb7c0e79088007cb39a96e097 \ 11 | 0x8b727508230fda8e0ec96b7c9e51c89ff0e41ba30fad221c2f0fe942158571b1 \ 12 | 0x514111937962a290ba6afa3dd0044e0720148b46cd2dbc8045e811f8157b6b1a \ 13 | 0x52f21c3eedc184eb13fcd5ec8e45e6741d97bca85a8703d733fab9c19f5e8518 \ 14 | 0xbca3035e18b3f87a38fa34fcc2561a023fe1f9b93354c04c772f37497ef08f3e \ 15 | 0x2d8676754eb3d184f3e9428c5d52eacdf1d507593ba50c3ef2a59e1a3a46b578 \ 16 | 0xabf8c2dd52f5b14ea437325854048e5daadbca80f99f9d6f8e97ab5e05d4f0ab \ 17 | ) 18 | 19 | # Prepare a ganache accounts parameter string like --account="0x11c..,1000" --account="0xc5d...,1000" .... 20 | for a in ${acc[@]}; do 21 | accounts=$accounts" --account=${a},${balance}" 22 | done 23 | 24 | # Helper funcs. 25 | 26 | # Test if ganache is running on port $1. 27 | # Result is in $? 28 | ganache_running() { 29 | nc -z localhost $1 30 | } 31 | 32 | # Kills ganache process with its PID in $ganache_pid. 33 | cleanup() { 34 | echo "cleaning up" 35 | # Kill the ganache instance that we started (if we started one). 36 | if [ -n "$ganache_pid" ]; then 37 | kill -9 $ganache_pid 38 | fi 39 | } 40 | 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DKG on EVM 2 | Distributed key generation on Ethereum virtual machine for BLS threshold signature using the elliptic curve BN256. 3 | 4 | Compatible with [BGLS](https://github.com/orbs-network/bgls) library. 5 | 6 | ## Building from source 7 | 8 | ### Prerequisites 9 | * Node.js (tested on v10.6.0) 10 | * [Truffle](https://truffleframework.com/) 11 | > install: `npm install -g truffle` 12 | * [Ganache](https://truffleframework.com/ganache) 13 | * make sure at least 3 accounts are created. 14 | 15 | ### Installation 16 | ``` 17 | git clone https://github.com/orbs-network/dkg-on-evm.git 18 | cd dkg-on-evm 19 | npm install 20 | ``` 21 | 22 | ## Tests 23 | 24 | ### Run tests 25 | * Full test run: 26 | > `truffle test` 27 | * Specific test run: 28 | > `truffle test ./test/.js` 29 | 30 | ### Test files 31 | * testHappyFlow.js - tests a successful flow. 32 | * testComplaints.js - tests justified/unjustified complaints. 33 | * testTimeouts.js - tests timeouts occurnce in enrollment and commitment phases. 34 | 35 | ### Test issues 36 | * The tests may take rather long time. Right now there are two options to make the test go faster without damaging the quality of the tests. First, make sure the timeouts, that are defined inside the DKG contract, are small. Second, make sure that the not too many account are created in the Ganache client (the minimum number of accounts for the tests to work properly is 3 accounts). 37 | 38 | ## Simulation 39 | In [simulation](https://github.com/orbs-network/dkg-on-evm/tree/master/simulation) directory. 40 | 41 | ## License 42 | MIT -------------------------------------------------------------------------------- /test/testHappyFlow.js: -------------------------------------------------------------------------------- 1 | const dkg = artifacts.require("./dkg.sol"); 2 | const general = require('./dkg/general.js'); 3 | const happyFlow = require('./dkg/happyFlow.js'); 4 | const happyFlowData = require('./testsData/happyFlowData.js'); 5 | 6 | 7 | 8 | contract('DKG happy-flow', async (accounts) => { 9 | accounts.shift(); // the first account will only deploy the contract 10 | 11 | it("Post-Deploy check", async() => { 12 | let instance = await dkg.deployed(); 13 | await happyFlow.postDeploy(instance); 14 | }); 15 | 16 | it("Join", async() => { 17 | let instance = await dkg.deployed(); 18 | await happyFlow.join(instance, accounts, happyFlowData.pks); 19 | }); 20 | 21 | it("Commit", async () => { 22 | let instance = await dkg.deployed(); 23 | await happyFlow.commit(instance, accounts, 24 | happyFlowData.pubCommitG1, happyFlowData.pubCommitG2, happyFlowData.prvCommitEnc); 25 | }); 26 | 27 | it("Post-Commit", async () => { 28 | const callerIndex = 0; 29 | let instance = await dkg.deployed(); 30 | 31 | let deposit = await instance.depositWei.call(); 32 | let n = await instance.n.call(); 33 | let numOfParticipants = n.toNumber(); 34 | 35 | let funcPostCommit = async () => { 36 | return await happyFlow.postCommit(instance, accounts[callerIndex]); 37 | } 38 | let res = await general.getAccountsBalancesDiffAfterFunc(instance, accounts, funcPostCommit); 39 | 40 | let gasPrice = dkg.class_defaults.gasPrice; 41 | res.balancesAfter[callerIndex] += res.gasUsed*gasPrice; 42 | 43 | let ethReceived = deposit.toNumber(); 44 | for(var i = 0; i < numOfParticipants; i++) { 45 | assert.equal(Math.round(res.balancesAfter[i]/gasPrice), Math.round(ethReceived/gasPrice), 46 | "Participant deposit should return"); 47 | } 48 | }); 49 | 50 | it("Check DKG ended successfully", async () => { 51 | let instance = await dkg.deployed(); 52 | await happyFlow.contractEndSuccess(instance); 53 | }); 54 | }); 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /contracts/g2Ops.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.0; 2 | 3 | library g2Ops { 4 | 5 | uint256 public constant p = 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD47; 6 | 7 | 8 | 9 | // Return true iff p1 equals to p2 (points on the elliptic curve) 10 | function isEqualPoints(uint256[2] p1, uint256[2] p2) public pure 11 | returns(bool isEqual) 12 | { 13 | return (p1[0] == p2[0] && p1[1] == p2[1]); 14 | } 15 | 16 | 17 | function addGfp2(uint256[2] f1, uint256[2] f2) public pure 18 | returns(uint256[2] f) 19 | { 20 | f[0] = addmod(f1[0], f2[0], p); 21 | f[1] = addmod(f1[1], f2[1], p); 22 | } 23 | 24 | 25 | function mulGfp2(uint256[2] f1, uint256[2] f2) public pure 26 | returns(uint256[2] f) 27 | { 28 | uint256 t_x = mulmod(f1[0], f2[1], p); 29 | uint256 t = mulmod(f2[1], f1[0], p); 30 | f[0] = addmod(t_x, t, p); 31 | 32 | uint256 t_y = mulmod(f1[1], f2[1], p); 33 | t = mulmod(f1[0], f2[0], p); 34 | f[1] = addmod(t_y, p-t, p); 35 | } 36 | 37 | 38 | function squareGfp2(uint256[2] f1) public pure 39 | returns(uint256[2] f) 40 | { 41 | uint256 t_x = addmod(f1[1], p-f1[0], p); 42 | uint256 t_y = addmod(f1[0], f1[1], p); 43 | f[1] = mulmod(t_x, t_y, p); 44 | t_x = mulmod(f1[0], f1[1], p); 45 | f[1] = addmod(t_x, t_x, p); 46 | } 47 | 48 | 49 | function isOnC2Curve(uint256[4] p1) public pure 50 | returns(bool) 51 | { 52 | // if (p1[0] == 0 && p1[1] == 0 && p1[2] == 0 && p1[3] == 0) { 53 | // return true; 54 | // } 55 | uint256[2] memory twistB = [ 56 | 0x38e7ecccd1dcff6765f0b37d93ce0d3ed749d0dd22ac00aa0141b9ce4a688d4d, 57 | 0x3bf938e377b802a8020b1b273633535d26b7edf0497552602514c6324384a86d 58 | ]; 59 | 60 | uint256[2] memory px = [p1[0], p1[1]]; 61 | uint256[2] memory py = [p1[2], p1[3]]; 62 | uint256[2] memory y2 = squareGfp2(py); 63 | uint256[2] memory x3 = addGfp2(mulGfp2(px, squareGfp2(px)), twistB); 64 | 65 | return isEqualPoints(x3, y2); 66 | } 67 | } -------------------------------------------------------------------------------- /simulation/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ### Entry point for the BGLS demo ### 4 | # If a Ganache instance is already running on GANACHE_PORT, the app will use it and not try to start a new instance, nor will it kill it when it's done. 5 | # So you may run this script multiple times on an existing Ganache instance. 6 | # Note though, that after 3 consecutive runs, your accounts might run out of ether (in the complaint flow where the accused client does not get its deposit back) so you will need to restart Ganache. 7 | 8 | 9 | # Import common variables. 10 | . scripts/common.sh 11 | 12 | GANACHE_PORT=7545 13 | 14 | CLIENT_COUNT=5 # 22 15 | THRESHOLD=2 # 14 16 | DEPOSIT_WEI=25000000000000000000 17 | KEEP_GANACHE_ALIVE=false 18 | DATA_FILE=$(pwd)/commit_data.json 19 | 20 | ### The happy flow is the default. Putting a nonzero value in all 3 indexes below will trigger the complaint flow. 21 | ### Uncomment to enable the complaint flow. 22 | 23 | #COMPLAINER_INDEX=1 # 1-based, the client that complains about client ACCUSED_INDEX 24 | #MALICIOUS_INDEX=2 # 1-based, the client that actually tainted its data 25 | #ACCUSED_INDEX=2 # 1-based, the client that is accused by client COMPLAINER_INDEX of tainting its data 26 | 27 | 28 | 29 | 30 | 31 | while getopts ":n:t:pk" opt; do 32 | case $opt in 33 | n) 34 | NODE_COUNT=$OPTARG 35 | ;; 36 | t) 37 | THRESHOLD=$OPTARG 38 | ;; 39 | p) 40 | GANACHE_PORT=$OPTARG 41 | ;; 42 | k) 43 | KEEP_GANACHE_ALIVE=true 44 | ;; 45 | c) 46 | COMPLAINER_INDEX=$OPTARG 47 | ;; 48 | m) 49 | MALICIOUS_INDEX=$OPTARG 50 | ;; 51 | a) ACCUSED_INDEX=$OPTARG 52 | ;; 53 | \?) 54 | echo "Invalid option: -$OPTARG" >&2 55 | ;; 56 | esac 57 | done 58 | 59 | if [[ "$KEEP_GANACHE_ALIVE" = "false" ]] ; then 60 | echo "Will stop Ganache instance when exiting" 61 | trap cleanup EXIT 62 | fi 63 | 64 | if ganache_running $GANACHE_PORT; then 65 | echo "Ganache instance already running on port $GANACHE_PORT, using it." 66 | else 67 | echo "Starting ganache instance" 68 | ../node_modules/.bin/ganache-cli --accounts ${CLIENT_COUNT} --deterministic --mnemonic "${MNEMONIC}" -p "$GANACHE_PORT" > ganache.log & 69 | ganache_pid=$! 70 | echo "Started ganache with pid $ganache_pid" 71 | # account_setup 72 | fi 73 | 74 | cmd="../node_modules/.bin/truffle exec src/app.js -n ${CLIENT_COUNT} -t ${THRESHOLD} -d ${DEPOSIT_WEI} -j ${DATA_FILE} -c ${COMPLAINER_INDEX} -m ${MALICIOUS_INDEX} -a ${ACCUSED_INDEX}" 75 | echo "Running command: ${cmd}" 76 | ${cmd} 77 | 78 | rc=$? 79 | echo "Finished with rc=$rc" 80 | if [[ $rc -ne 0 ]] ; then 81 | echo "Error enrolling and committing clients. Exiting." 82 | if [[ $ganache_pid -ne 0 ]] ; then 83 | echo "Ganache instance is still running, pid $ganache_pid" 84 | fi 85 | exit 1 86 | fi 87 | 88 | #./bls-bn-curve -func=SignAndVerify ${THRESHOLD} ${CLIENT_COUNT} ${DATA_FILE} 89 | 90 | 91 | account_setup() { 92 | echo "Starting account setup" 93 | 94 | } 95 | 96 | -------------------------------------------------------------------------------- /simulation/README.md: -------------------------------------------------------------------------------- 1 | # DKG - simulation 2 | 3 | Demo client of [BGLS package](https://github.com/orbs-network/bgls). It demonstrates 2 flows: 4 | * Happy flow - in which clients enroll with the DKG contract and then commit, and no client is trying to deceive any other client. 5 | * Complaint flow - in which a client (malicious) intentionally sends incorrect commitments to another client (complainer). 6 | The complainer then sends a `complaint()` transaction against a specific client (accused). 7 | The malicious and accused clients need not be the same client. In case the complaint is justified, the accused/malicious client gas its deposit slashed and distributed among the other clients. 8 | In case the complaint is unjustified, the complainer's deposit is slashed and distributed among the other clients. 9 | 10 | As this is a demo, some code parts are very rudimentary. There is nearly no error checking, and in cases where the app cannot continue, the Go code just throws a panic(). 11 | 12 | ## Installation 13 | 14 | * Go to the directory under which you want to clone this repo 15 | * `git clone git@github.com:orbs-network/dkg-on-evm.git` 16 | * `cd dkg-on-evm` 17 | * `yarn install` 18 | 19 | ## Prerequisites 20 | * node 21 | * truffle/ganache suite 22 | * [BGLS](https://github.com/orbs-network/bgls) 23 | > create the executable in the simulation directory by the command `go build` 24 | 25 | ## Running examples 26 | 27 | There are 2 examples. To run them, first `cd src`, then: 28 | * To run the BLS example, run: `node bls-example.js` 29 | * To run the DKG example, run: `node dkg-example.js` 30 | 31 | ## Docs 32 | 33 | The demo code is written in JavaScript and Go. 34 | The JavaScript part contains the web3 interface with the smart contract: 35 | * Compilation 36 | * Deployment 37 | * Sending transactions 38 | * Calling methods 39 | * Registering/unregistering/listening to events 40 | * Collecting info about gas used 41 | * 42 | 43 | The Go part interfaces with the [BGLS package](https://github.com/orbs-network/bgls) 44 | * Calls BGLS methods 45 | * Demonstrates technical aspects of Go: JSON serialization (including custom structs), using of Big Integers - serialization, conversions to/from strings, with decimal/hex notations 46 | 47 | Both JS and Go code show how parts of the code can be made interactive (just as waiting for user to press Space, enter input) in case you want to use this in a live demo, as we have done internally. 48 | 49 | The JS code is the main part of the app, and it invokes the Go executable's various functions. Data passes between JS and Go either by command line arguments, having JS collect Go's stdout output, or writing data to a disk file. 50 | No single method was suitable for all use cases and having several methods in place also serve as a technical demo of how this can be accomplished. 51 | 52 | While it is possible to run Ganache internally by the demo app (it can start Ganache, do its work, and then stop Ganache), it is far more informative to manually load 53 | a Ganache instance with its UI, and observe the progress of transactions and the accounts' balance - especially in complaint flows where one account has its deposit slashed (forfeited) and the other accounts split it between them. 54 | 55 | ## Files 56 | * bglsmain.go - The Go code - it is compiled to an independent executable 57 | * src/app.js - entrypoint of the demo app, including the flow logic 58 | * src/bglswrapper.js - helper functions including the calls to the Go process 59 | * contracts/dkgEnc.sol - The DKG contract we use 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /test/dkg/general.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {object} Diff 3 | * @property {number[]} balancesAfter balances diff 4 | * @property {number} gasUsed gas used in the execution 5 | */ 6 | 7 | const dkg = artifacts.require('./dkg.sol'); 8 | const util = require('util'); 9 | const dkgUtils = require('./utils.js'); 10 | const constants = require('../testsData/constants.js'); 11 | 12 | /** 13 | * Asserts the instance's phase is phaseNum. 14 | * @param {number} phaseNum 15 | * @param {*} instance 16 | */ 17 | async function verifyPhase(phaseNum, instance) { 18 | let curPhase = await instance.curPhase.call(); 19 | assert.equal(phaseNum, curPhase.toNumber(), 20 | util.format("should be in phase number %s but in phase %s", phaseNum, curPhase.toNumber())); 21 | } 22 | 23 | /** 24 | * Returns the diff of the ether balance in the inputted accounts between 25 | * after and before the execution of the function func. 26 | * @param {*} instance 27 | * @param {string[]} accounts 28 | * @param {*} func async function to execute 29 | * @returns {Diff} 30 | */ 31 | async function getAccountsBalancesDiffAfterFunc(instance, accounts, func) { 32 | let n = await instance.n.call(); 33 | let numOfParticipants = n.toNumber(); 34 | 35 | let balancesBefore = []; 36 | for (var i = 0; i < numOfParticipants; i++) { 37 | let balance = await web3.eth.getBalance(accounts[i]); 38 | balancesBefore.push(balance.toNumber()); 39 | } 40 | 41 | let gasUsed = await func(); 42 | 43 | let balancesAfter = []; 44 | for (var i = 0; i < numOfParticipants; i++) { 45 | let balance = await web3.eth.getBalance(accounts[i]); 46 | balancesAfter.push(balance.toNumber() - balancesBefore[i]); 47 | } 48 | return { 49 | balancesAfter, 50 | gasUsed 51 | }; 52 | } 53 | 54 | 55 | /** 56 | * Assets contract balance is expectedBalance 57 | * @param {number} expectedBalance 58 | */ 59 | async function verifyContractBalance(expectedBalance) { 60 | let contractBalance = await web3.eth.getBalance(dkg.address); 61 | assert.equal( 62 | expectedBalance, contractBalance.toNumber(), 63 | util.format( 64 | "contract balance is %s while expected balance is %s", 65 | contractBalance.toNumber(), expectedBalance 66 | )); 67 | } 68 | 69 | 70 | /** 71 | * Mines empty numOfBlockToMine blocks 72 | * @param {number} numOfBlockToMine 73 | */ 74 | async function mineEmptyBlocks(numOfBlockToMine) { 75 | let latestBlock = await web3.eth.getBlock("latest"); 76 | let startBlockNum = latestBlock.number; 77 | await dkgUtils.forceMineBlocks(numOfBlockToMine); 78 | latestBlock = await web3.eth.getBlock("latest"); 79 | assert.equal(latestBlock.number-numOfBlockToMine, startBlockNum, "Not enough empty blocks were mined"); 80 | } 81 | 82 | /** 83 | * Assets an error has accour during the execution of the promise. 84 | * @param {*} promise 85 | * @param {string} errorType 86 | */ 87 | async function assertError(promise, errorType) { 88 | try { 89 | await promise; 90 | throw null; 91 | } 92 | catch (error) { 93 | assert(error, "Expected an error but did not get one"); 94 | assert(error.message.startsWith(constants.EXCEPTION_PREFIX + errorType), 95 | util.format("Expected an error starting with %s but got %s instead", 96 | constants.EXCEPTION_PREFIX + errorType, error.message)); 97 | } 98 | } 99 | 100 | 101 | 102 | module.exports = { 103 | verifyPhase, 104 | verifyContractBalance, 105 | getAccountsBalancesDiffAfterFunc, 106 | mineEmptyBlocks, 107 | assertError 108 | } -------------------------------------------------------------------------------- /contracts/ecOps.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.0; 2 | 3 | library ecOps { 4 | //////////////////////////////////////////////////////// 5 | // EC operations - precompiled contracts for bn256 only! 6 | //////////////////////////////////////////////////////// 7 | 8 | // The curve y^2 = x^3 + a*x + b (x,y in modulo n field) 9 | uint256 public constant b = 3; 10 | uint256 public constant p = 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD47; 11 | uint256 public constant q = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; 12 | 13 | 14 | 15 | function ecmul(uint256[2] p0, uint256 scalar) public view 16 | returns(uint256[2] p1) 17 | { 18 | uint256[3] memory input; 19 | input[0] = p0[0]; 20 | input[1] = p0[1]; 21 | input[2] = scalar; 22 | 23 | assembly{ 24 | // call ecmul precompile 25 | if iszero(call(not(0), 0x07, 0, input, 0x60, p1, 0x40)) { 26 | revert(0, 0) 27 | } 28 | } 29 | } 30 | 31 | 32 | function ecadd(uint256[2] p0, uint256[2] p1) public view 33 | returns(uint256[2] p2) 34 | { 35 | uint256[4] memory input; 36 | input[0] = p0[0]; 37 | input[1] = p0[1]; 38 | input[2] = p1[0]; 39 | input[3] = p1[1]; 40 | 41 | assembly{ 42 | // call ecadd precompile 43 | if iszero(call(not(0), 0x06, 0, input, 0x80, p2, 0x40)) { 44 | revert(0, 0) 45 | } 46 | } 47 | } 48 | 49 | 50 | function pairingCheck(uint256[2] x, uint256[4] w, uint256[2] y, uint256[4] z) 51 | internal 52 | returns (bool) 53 | { 54 | //returns e(a,x) == e(b,y) 55 | uint256[12] memory input = [ 56 | x[0], x[1], w[0], w[1], w[2], w[3], 57 | y[0], p - y[1], z[0], z[1], z[2], z[3] 58 | ]; 59 | uint[1] memory result; 60 | 61 | assembly { 62 | if iszero(call(not(0), 0x08, 0, input, 0x180, result, 0x20)) { 63 | revert(0, 0) 64 | } 65 | } 66 | return result[0]==1; 67 | } 68 | 69 | 70 | // Return true iff p1 equals to p2 (points on the elliptic curve) 71 | function isEqualPoints(uint256[2] p1, uint256[2] p2) public pure 72 | returns(bool isEqual) 73 | { 74 | return (p1[0] == p2[0] && p1[1] == p2[1]); 75 | } 76 | 77 | 78 | // Returns true iff p1 is in G1 group 79 | function isInG1(uint256[2] p1) public pure 80 | returns(bool) 81 | { 82 | if (p1[0] == 0 && p1[1] == 0) { 83 | return true; 84 | } 85 | 86 | uint256 x3 = mulmod(p1[0], p1[0], p); 87 | x3 = mulmod(x3, p1[0], p); 88 | x3 = addmod(x3, b, p); 89 | uint256 y2 = mulmod(p1[1], p1[1], p); 90 | 91 | return x3 == y2; 92 | } 93 | 94 | 95 | // TODO: make it more gas efficient by implementing the check by yourself 96 | // Returns true iff p1 is in G2. 97 | function isInG2(uint256[4] p1) public view 98 | returns(bool) 99 | { 100 | uint256[12] memory input = [ 101 | 1, 2, p1[0], p1[1], p1[2], p1[3], 102 | 1, p - 2, p1[0], p1[1], p1[2], p1[3] 103 | ]; 104 | uint[1] memory result; 105 | bool isIn = true; 106 | 107 | assembly { 108 | if iszero(call(not(0), 0x08, 0, input, 0x180, result, 0x20)) { 109 | isIn := 0 110 | } 111 | } 112 | return isIn; 113 | } 114 | } -------------------------------------------------------------------------------- /test/dkg/complaint.js: -------------------------------------------------------------------------------- 1 | const general = require('./general.js'); 2 | const constants = require('../testsData/constants.js'); 3 | const happyFlow = require('./happyFlow'); 4 | 5 | 6 | /** 7 | * Asserts contract ended with failure. Verifies no ether is left 8 | * in the contract. 9 | * @param {*} instance 10 | */ 11 | async function contractEndFail(instance) { 12 | await general.verifyPhase(constants.phase.endFail, instance); 13 | await general.verifyContractBalance(0); 14 | await general.assertError(happyFlow.postCommitTimedOut(instance), constants.errTypes.revert); 15 | } 16 | 17 | 18 | /** 19 | * Send a complaint on private commitment. 20 | * @param {*} instance 21 | * @param {string[]} accounts 22 | * @param {number} complainerIndex 23 | * @param {number} accusedIndex 24 | * @param {number} complainerSk 25 | */ 26 | async function complainPrvCommitment(instance, accounts, complainerIndex, accusedIndex, complainerSk) { 27 | await general.verifyPhase(constants.phase.postCommit, instance); 28 | let n = await instance.n.call(); 29 | let numOfParticipants = n.toNumber(); 30 | let indices = _.range(1, numOfParticipants+1); 31 | 32 | let res = await instance.complaintPrivateCommit( 33 | indices[complainerIndex], 34 | indices[accusedIndex], 35 | complainerSk, 36 | {from: accounts[complainerIndex]} 37 | ); 38 | return res.receipt.gasUsed; 39 | } 40 | 41 | 42 | /** 43 | * Send a complaint on public commitment. 44 | * @param {*} instance 45 | * @param {string[]} accounts 46 | * @param {number} complainerIndex 47 | * @param {number} accusedIndex 48 | * @param {number} pubCommitIndex 49 | */ 50 | async function complainPubCommitment(instance, accounts, complainerIndex, accusedIndex, pubCommitIndex) { 51 | await general.verifyPhase(constants.phase.postCommit, instance); 52 | let n = await instance.n.call(); 53 | let numOfParticipants = n.toNumber(); 54 | let indices = _.range(1, numOfParticipants+1); 55 | let res = await instance.complaintPublicCommit( 56 | indices[complainerIndex], 57 | indices[accusedIndex], 58 | pubCommitIndex, 59 | {from: accounts[complainerIndex]} 60 | ); 61 | return res.receipt.gasUsed; 62 | } 63 | 64 | 65 | /** 66 | * Send a complaint on a committed point not in G1. 67 | * @param {*} instance 68 | * @param {string[]} accounts 69 | * @param {number} complainerIndex 70 | * @param {number} accusedIndex 71 | * @param {number} pubCommitIndex 72 | */ 73 | async function complainNotInG1(instance, accounts, complainerIndex, accusedIndex, pubCommitIndex) { 74 | await general.verifyPhase(constants.phase.postCommit, instance); 75 | let n = await instance.n.call(); 76 | let numOfParticipants = n.toNumber(); 77 | let indices = _.range(1, numOfParticipants+1); 78 | let res = await instance.complaintNotInG1( 79 | indices[complainerIndex], 80 | indices[accusedIndex], 81 | pubCommitIndex, 82 | {from: accounts[complainerIndex]} 83 | ); 84 | return res.receipt.gasUsed; 85 | } 86 | 87 | 88 | /** 89 | * Send a complaint on a committed point not in G2. 90 | * @param {*} instance 91 | * @param {string[]} accounts 92 | * @param {number} complainerIndex 93 | * @param {number} accusedIndex 94 | * @param {number} pubCommitIndex 95 | */ 96 | async function complainNotInG2(instance, accounts, complainerIndex, accusedIndex, pubCommitIndex) { 97 | await general.verifyPhase(constants.phase.postCommit, instance); 98 | let n = await instance.n.call(); 99 | let numOfParticipants = n.toNumber(); 100 | let indices = _.range(1, numOfParticipants+1); 101 | let res = await instance.complaintNotInG2( 102 | indices[complainerIndex], 103 | indices[accusedIndex], 104 | pubCommitIndex, 105 | {from: accounts[complainerIndex]} 106 | ); 107 | return res.receipt.gasUsed; 108 | } 109 | 110 | 111 | module.exports = { 112 | contractEndFail, 113 | complainPrvCommitment, 114 | complainPubCommitment, 115 | complainNotInG1, 116 | complainNotInG2 117 | }; -------------------------------------------------------------------------------- /simulation/src/bglswrapper.js: -------------------------------------------------------------------------------- 1 | const {execSync} = require('child_process'); 2 | const CWD = __dirname; 3 | const EXEC_PATH = `${CWD}/../simulation`; 4 | // const OUTPUT_PATH = `${CWD}/../commit_data.json`; 5 | const {createLogger, format, transports} = require('winston'); 6 | 7 | 8 | const SHOW_DEBUG = false; 9 | 10 | const logger = createLogger({ 11 | format: format.json(), 12 | transports: [ 13 | new transports.Console(({ 14 | format: format.simple(), 15 | level: SHOW_DEBUG ? 'debug' : 'info' 16 | })) 17 | // new winston.transports.File({ filename: 'combined.log' }) 18 | ] 19 | }); 20 | 21 | 22 | function runExternal(cmd) { 23 | logger.info(`*** Calling external command ${cmd}`); 24 | return execSync(cmd, {cwd: CWD}, {stdio: [0, 1, 2]}); 25 | } 26 | 27 | // id is 1-based 28 | function GetCommitDataForSingleParticipant(id, threshold, clientCount, sk, pks) { 29 | const pksStr = JSON.stringify(pks); 30 | const cmd = `${EXEC_PATH} -func=GetCommitDataForSingleParticipant ${id} ${threshold} ${clientCount} ${sk} ${pksStr}`; 31 | const resStr = runExternal(cmd); 32 | const res = JSON.parse(resStr); 33 | return res; 34 | } 35 | 36 | function getPKsStr(clientsData, clientsCount) { 37 | const pks = []; 38 | for(let i=0; i { 12 | it("Post-Deploy check", async() => { 13 | let instance = await dkg.deployed(); 14 | await happyFlow.postDeploy(instance); 15 | }); 16 | 17 | it("Join only some of the participants", async() => { 18 | let instance = await dkg.deployed(); 19 | let numOfParticipants = 1; 20 | await happyFlow.join(instance, accounts, happyFlowData.pks, numOfParticipants); 21 | }); 22 | 23 | it("Try to close the contract before timeout", async() => { 24 | let instance = await dkg.deployed(); 25 | const callerIndex = 0; 26 | const joinTimeout = await instance.joinTimeout.call(); 27 | if(joinTimeout > 0) { 28 | let lesThanTimeout = Math.floor(Math.random() * joinTimeout); 29 | await general.mineEmptyBlocks(lesThanTimeout); 30 | await general.assertError(timeout.endEnrollment( 31 | instance, accounts[callerIndex]), constants.errTypes.revert); 32 | } 33 | }); 34 | 35 | it("Wait for enrollment timeout to pass", async() => { 36 | let instance = await dkg.deployed(); 37 | let timeout = await instance.joinTimeout.call(); 38 | await general.mineEmptyBlocks(timeout); 39 | }); 40 | 41 | it("Close the contract", async() => { 42 | let instance = await dkg.deployed(); 43 | 44 | let deposit = await instance.depositWei.call(); 45 | let n = await instance.curN.call(); 46 | let numOfParticipants = n.toNumber(); 47 | const callerIndex = 0; 48 | 49 | let funcEndEnrollment = async () => { 50 | return timeout.endEnrollment(instance, accounts[callerIndex]); 51 | } 52 | let res = await general.getAccountsBalancesDiffAfterFunc(instance, accounts, funcEndEnrollment); 53 | 54 | let gasPrice = dkg.class_defaults.gasPrice; 55 | res.balancesAfter[callerIndex] += res.gasUsed*gasPrice; 56 | 57 | let ethReceived = deposit.toNumber(); 58 | for(var i = 0; i < numOfParticipants; i++) { 59 | assert.equal(Math.round(res.balancesAfter[i]/gasPrice), Math.round(ethReceived/gasPrice), 60 | "Participant deposit should return"); 61 | } 62 | }); 63 | 64 | it("Check DKG ended with failure", async () => { 65 | let instance = await dkg.deployed(); 66 | await complaint.contractEndFail(instance); 67 | }); 68 | }); 69 | 70 | 71 | contract('DKG commit timeout', async (accounts) => { 72 | it("Post-Deploy check", async() => { 73 | let instance = await dkg.deployed(); 74 | await happyFlow.postDeploy(instance); 75 | }); 76 | 77 | it("Join", async() => { 78 | let instance = await dkg.deployed(); 79 | await happyFlow.join(instance, accounts, happyFlowData.pks); 80 | }); 81 | 82 | it("Commit only some of the participants", async () => { 83 | let instance = await dkg.deployed(); 84 | let numOfParticipants = 1; 85 | await happyFlow.commit(instance, accounts, 86 | happyFlowData.pubCommitG1, happyFlowData.pubCommitG2, happyFlowData.prvCommitEnc, numOfParticipants); 87 | }); 88 | 89 | it("Try to close the contract before timeout", async() => { 90 | let instance = await dkg.deployed(); 91 | const callerIndex = 0; 92 | const commitTimeout = await instance.commitTimeout.call(); 93 | if(commitTimeout > 0) { 94 | let lesThanTimeout = Math.floor(Math.random() * commitTimeout); 95 | await general.mineEmptyBlocks(lesThanTimeout); 96 | await general.assertError(timeout.endCommit( 97 | instance, accounts[callerIndex]), constants.errTypes.revert); 98 | } 99 | }); 100 | 101 | it("Wait for commit timeout to pass", async() => { 102 | let instance = await dkg.deployed(); 103 | let timeout = await instance.commitTimeout.call(); 104 | await general.mineEmptyBlocks(timeout); 105 | }); 106 | 107 | it("Close the contract", async() => { 108 | let instance = await dkg.deployed(); 109 | 110 | let totalDeposit = await web3.eth.getBalance(dkg.address); 111 | const numOfParticipantsCommitted = 1; 112 | const callerIndex = 0; 113 | 114 | let funcEndCommit = async () => { 115 | return timeout.endCommit(instance, accounts[callerIndex]); 116 | } 117 | let res = await general.getAccountsBalancesDiffAfterFunc(instance, accounts, funcEndCommit); 118 | 119 | let gasPrice = dkg.class_defaults.gasPrice; 120 | res.balancesAfter[callerIndex] += res.gasUsed*gasPrice; 121 | 122 | let ethReceived = totalDeposit.toNumber()/numOfParticipantsCommitted; 123 | for(var i = 0; i < numOfParticipantsCommitted; i++) { 124 | assert.equal(Math.round(res.balancesAfter[i]/gasPrice), Math.round(ethReceived/gasPrice), 125 | "Participant deposit should return"); 126 | } 127 | }); 128 | 129 | it("Check DKG ended with failure", async () => { 130 | let instance = await dkg.deployed(); 131 | await complaint.contractEndFail(instance); 132 | }); 133 | }); -------------------------------------------------------------------------------- /test/dkg/happyFlow.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {number[2]} G1 3 | * 4 | * @typedef {number[4]} G2 5 | */ 6 | 7 | 8 | const general = require('./general.js'); 9 | const util = require('util'); 10 | const dkgUtils = require('./utils.js'); 11 | const constants = require('../testsData/constants.js'); 12 | 13 | 14 | /** 15 | * Asserts the contract after deployment. 16 | * @param {*} instance 17 | */ 18 | async function postDeploy(instance) { 19 | let n = await instance.n.call(); 20 | let t = await instance.t.call(); 21 | assert(n > t, "the number of participants should be bigger than the threshold"); 22 | } 23 | 24 | /** 25 | * Joins the maximum amount of participants in the enrollment phase. 26 | * Asserts all deposited the right amount. 27 | * @param {*} instance 28 | * @param {string[]} accounts 29 | * @param {G1[]} pks of each of the participants 30 | * @param {number} numOfParticipants 31 | */ 32 | async function join(instance, accounts, pks, numOfParticipants) { 33 | 34 | let n = await instance.n.call(); 35 | numOfParticipants = numOfParticipants ? numOfParticipants : n.toNumber(); 36 | 37 | let indices = _.range(1, numOfParticipants+1); 38 | let deposit = await instance.depositWei.call(); 39 | 40 | await general.verifyPhase(constants.phase.enrollment, instance); 41 | 42 | for (var i = 0; i < numOfParticipants; i++) { 43 | let args = [ 44 | pks[i], 45 | {from: accounts[i], value: deposit} 46 | ]; 47 | 48 | let index = await instance.join.call(args[0], args[1]); 49 | await instance.join(args[0], args[1]); 50 | assert.equal(indices[i], index.toNumber(), "error in index returned for participant"); 51 | 52 | let pkEnc = await instance.getParticipantPkEnc.call(index); 53 | assert(dkgUtils.isEqualPoints(pkEnc.map(x => x.toNumber()), pks[i]), "PK for encryption error") 54 | } 55 | 56 | let expectedBalance = deposit*numOfParticipants; 57 | await general.verifyContractBalance(expectedBalance); 58 | } 59 | 60 | /** 61 | * Commits all the participant by the given data. 62 | * Asserts all participant are committed successfully. 63 | * 64 | * @param {*} instance 65 | * @param {string[]} accounts 66 | * @param {G1[][]} pubCommitG1Data 67 | * @param {G2[][]} pubCommitG2Data 68 | * @param {number[][]} prvCommitEncData 69 | * @param {number} numOfParticipants 70 | */ 71 | async function commit(instance, accounts, pubCommitG1Data, pubCommitG2Data, prvCommitEncData, numOfParticipants) { 72 | let n = await instance.n.call(); 73 | numOfParticipants = numOfParticipants ? numOfParticipants : n.toNumber(); 74 | let indices = _.range(1, n.toNumber()+1); 75 | let t = await instance.t.call(); 76 | let threshold = t.toNumber(); 77 | 78 | await general.verifyPhase(constants.phase.commit, instance); 79 | 80 | for (var i = 0; i < numOfParticipants; i++) { 81 | let args = [ 82 | indices[i], 83 | _.flatMap(pubCommitG1Data[i]), 84 | _.flatMap(pubCommitG2Data[i]), 85 | prvCommitEncData[i], 86 | {from: accounts[i]} 87 | ]; 88 | await instance.commit(args[0], args[1], args[2], args[3], args[4]); 89 | 90 | for (var j = 0; j < threshold+1; j++) { 91 | let pubCommitG1 = await instance.getParticipantPubCommitG1.call(indices[i], j); 92 | assert(dkgUtils.isEqualPoints(pubCommitG1.map(x => x.toNumber()), pubCommitG1Data[i][j]), 93 | util.format("error in public commitment G1 of %s to coefficient %s", indices[i], j)); 94 | 95 | let pubCommitG2 = await instance.getParticipantPubCommitG2.call(indices[i], j); 96 | assert(dkgUtils.isEqualPoints(pubCommitG2.map(x => x.toNumber()), pubCommitG2Data[i][j]), 97 | util.format("error in public commitment G2 of %s to coefficient %s", indices[i], j)); 98 | 99 | let prvCommit = await instance.getParticipantPrvCommit.call(indices[i], indices[j]); 100 | assert.equal(prvCommit.toNumber(), prvCommitEncData[i][j]), 101 | util.format("error in private encrypted commitment of %s to %s", indices[i], indices[j]); 102 | } 103 | 104 | let isCommitted = await instance.getParticipantIsCommitted.call(indices[i]); 105 | assert(isCommitted, 106 | util.format("error participant %s is not marked as committed", indices[i])); 107 | } 108 | } 109 | 110 | /** 111 | * Closes the contract after the commit phase . 112 | * @param {*} instance 113 | * @param {string} callerAccount the account that will call the tx that 114 | * closes the contract. 115 | */ 116 | async function postCommit(instance, callerAccount) { 117 | await general.verifyPhase(constants.phase.postCommit, instance); 118 | return await postCommitTimedOut(instance, callerAccount); 119 | } 120 | 121 | 122 | /** 123 | * Closes the contract after all are committed (mines blocks automatically so 124 | * the timeout expires). 125 | * @param {*} instance 126 | * @param {string} callerAccount the account that will call the tx that 127 | * closes the contract. 128 | * @returns {number} gas used 129 | */ 130 | async function postCommitTimedOut(instance, callerAccount) { 131 | let timeout = await instance.postCommitTimeout.call(); 132 | await general.mineEmptyBlocks(timeout.toNumber()); 133 | let arg = callerAccount ? {from: callerAccount} : {}; 134 | let res = await instance.postCommitTimedOut(arg); 135 | return res.receipt.gasUsed; 136 | } 137 | 138 | /** 139 | * Asserts contract was closed successfully and that no ether 140 | * is left in the contract. 141 | * @param {*} instance 142 | */ 143 | async function contractEndSuccess(instance) { 144 | await general.verifyPhase(constants.phase.endSuccess, instance); 145 | await general.verifyContractBalance(0); 146 | } 147 | 148 | 149 | 150 | module.exports = { 151 | postDeploy, 152 | join, 153 | commit, 154 | postCommit, 155 | postCommitTimedOut, 156 | contractEndSuccess 157 | }; -------------------------------------------------------------------------------- /test/testComplaints.js: -------------------------------------------------------------------------------- 1 | const dkg = artifacts.require("./dkg.sol"); 2 | const general = require('./dkg/general.js'); 3 | const happyFlow = require('./dkg/happyFlow.js'); 4 | const complaint = require('./dkg/complaint.js'); 5 | const happyFlowData = require('./testsData/happyFlowData.js'); 6 | const corruptedData = require('./testsData/corruptedData.js'); 7 | const corruptedData2 = require('./testsData/corruptedData2.js'); 8 | 9 | 10 | 11 | contract('DKG unjustified complaint - private commitment', async (accounts) => { 12 | 13 | it("Post-Deploy check", async() => { 14 | let instance = await dkg.deployed(); 15 | await happyFlow.postDeploy(instance); 16 | }); 17 | 18 | it("Join", async() => { 19 | let instance = await dkg.deployed(); 20 | await happyFlow.join(instance, accounts, happyFlowData.pks); 21 | }); 22 | 23 | it("Commit", async () => { 24 | let instance = await dkg.deployed(); 25 | await happyFlow.commit(instance, accounts, 26 | happyFlowData.pubCommitG1, happyFlowData.pubCommitG2, happyFlowData.prvCommitEnc); 27 | }); 28 | 29 | it("Unjustified Complaint: private commitment", async () => { 30 | const complainer = happyFlowData.flow.complainer; 31 | const accused = happyFlowData.flow.accused; 32 | let instance = await dkg.deployed(); 33 | let deposit = await instance.depositWei.call(); 34 | let n = await instance.n.call(); 35 | let numOfParticipants = n.toNumber(); 36 | 37 | let funcComplain = async () => { 38 | return await complaint.complainPrvCommitment(instance, accounts, complainer, accused, happyFlowData.sks[complainer]); 39 | } 40 | let res = await general.getAccountsBalancesDiffAfterFunc(instance, accounts, funcComplain); 41 | let gasPrice = dkg.class_defaults.gasPrice; 42 | let slashedGas = -res.balancesAfter[complainer]/gasPrice; 43 | assert.equal(Math.round(slashedGas), res.gasUsed, "Slashed participant pays only the gas"); 44 | 45 | let ethReceived = deposit.toNumber() * numOfParticipants / (numOfParticipants-1); 46 | for(var i = 0; i < numOfParticipants; i++) { 47 | if(i != complainer) 48 | { 49 | assert.equal(Math.round(res.balancesAfter[i]/gasPrice), Math.round(ethReceived/gasPrice), 50 | "Participant reward from slashing error"); 51 | } 52 | } 53 | }); 54 | 55 | it("Check DKG ended with failure", async () => { 56 | let instance = await dkg.deployed(); 57 | await complaint.contractEndFail(instance); 58 | }); 59 | }); 60 | 61 | 62 | contract('DKG unjustified complaint - public commitment', async (accounts) => { 63 | 64 | it("Post-Deploy check", async() => { 65 | let instance = await dkg.deployed(); 66 | await happyFlow.postDeploy(instance); 67 | }); 68 | 69 | it("Join", async() => { 70 | let instance = await dkg.deployed(); 71 | await happyFlow.join(instance, accounts, happyFlowData.pks); 72 | }); 73 | 74 | it("Commit", async () => { 75 | let instance = await dkg.deployed(); 76 | await happyFlow.commit(instance, accounts, 77 | happyFlowData.pubCommitG1, happyFlowData.pubCommitG2, happyFlowData.prvCommitEnc); 78 | }); 79 | 80 | it("Unjustified Complaint: public commitment", async () => { 81 | const complainer = happyFlowData.flow.complainer; 82 | const accused = happyFlowData.flow.accused; 83 | let instance = await dkg.deployed(); 84 | let deposit = await instance.depositWei.call(); 85 | let n = await instance.n.call(); 86 | let numOfParticipants = n.toNumber(); 87 | let t = await instance.t.call(); 88 | let threshold = t.toNumber(); 89 | 90 | let funcComplain = async () => { 91 | let pubCommitInd = Math.floor((Math.random() * (threshold+1))); 92 | return await complaint.complainPubCommitment(instance, accounts, complainer, accused, pubCommitInd); 93 | } 94 | let res = await general.getAccountsBalancesDiffAfterFunc(instance, accounts, funcComplain); 95 | let gasPrice = dkg.class_defaults.gasPrice; 96 | let slashedGas = -res.balancesAfter[complainer]/gasPrice; 97 | assert.equal(Math.round(slashedGas), res.gasUsed, "Slashed participant pays only the gas"); 98 | 99 | let ethReceived = deposit.toNumber() * numOfParticipants / (numOfParticipants-1); 100 | for(var i = 0; i < numOfParticipants; i++) { 101 | if(i != complainer) 102 | { 103 | assert.equal(Math.round(res.balancesAfter[i]/gasPrice), Math.round(ethReceived/gasPrice), 104 | "Participant reward from slashing error"); 105 | } 106 | } 107 | }); 108 | 109 | it("Check DKG ended with failure", async () => { 110 | let instance = await dkg.deployed(); 111 | await complaint.contractEndFail(instance); 112 | }); 113 | }); 114 | 115 | 116 | contract('DKG justified complaint - private commitment', async (accounts) => { 117 | 118 | it("Post-Deploy check", async() => { 119 | let instance = await dkg.deployed(); 120 | await happyFlow.postDeploy(instance); 121 | }); 122 | 123 | it("Join", async() => { 124 | let instance = await dkg.deployed(); 125 | await happyFlow.join(instance, accounts, corruptedData.pks); 126 | }); 127 | 128 | it("Commit", async () => { 129 | let instance = await dkg.deployed(); 130 | await happyFlow.commit(instance, accounts, 131 | corruptedData.pubCommitG1, corruptedData.pubCommitG2, corruptedData.prvCommitEnc); 132 | }); 133 | 134 | it("Justified Complaint: private commitment", async () => { 135 | const complainer = corruptedData.flow.complainer; 136 | const accused = corruptedData.flow.accused; 137 | let instance = await dkg.deployed(); 138 | let deposit = await instance.depositWei.call(); 139 | let n = await instance.n.call(); 140 | let numOfParticipants = n.toNumber(); 141 | 142 | let funcComplain = async () => { 143 | return await complaint.complainPrvCommitment(instance, accounts, complainer, accused, corruptedData.sks[complainer]); 144 | } 145 | let res = await general.getAccountsBalancesDiffAfterFunc(instance, accounts, funcComplain); 146 | let gasPrice = dkg.class_defaults.gasPrice; 147 | res.balancesAfter[complainer] += res.gasUsed * gasPrice; 148 | 149 | let ethReceived = deposit.toNumber() * numOfParticipants / (numOfParticipants-1); 150 | for(var i = 0; i < numOfParticipants; i++) { 151 | if(i != accused) 152 | { 153 | assert.equal(Math.round(res.balancesAfter[i]/gasPrice), Math.round(ethReceived/gasPrice), 154 | "Participant reward from slashing error"); 155 | } 156 | else 157 | { 158 | assert.equal(res.balancesAfter[i], 0, 159 | "Slashed participant balance should remain unchanged"); 160 | } 161 | } 162 | }); 163 | 164 | it("Check DKG ended with failure", async () => { 165 | let instance = await dkg.deployed(); 166 | await complaint.contractEndFail(instance); 167 | }); 168 | }); 169 | 170 | 171 | contract('DKG justified complaint - public commitment', async (accounts) => { 172 | 173 | it("Post-Deploy check", async() => { 174 | let instance = await dkg.deployed(); 175 | await happyFlow.postDeploy(instance); 176 | }); 177 | 178 | it("Join", async() => { 179 | let instance = await dkg.deployed(); 180 | await happyFlow.join(instance, accounts, corruptedData.pks); 181 | }); 182 | 183 | it("Commit", async () => { 184 | let instance = await dkg.deployed(); 185 | await happyFlow.commit(instance, accounts, 186 | corruptedData.pubCommitG1, corruptedData.pubCommitG2, corruptedData.prvCommitEnc); 187 | }); 188 | 189 | it("Justified Complaint: public commitment", async () => { 190 | const complainer = corruptedData.flow.complainer; 191 | const accused = corruptedData.flow.accused; 192 | const complainPubCommitIndex = 1; 193 | let instance = await dkg.deployed(); 194 | let deposit = await instance.depositWei.call(); 195 | let n = await instance.n.call(); 196 | let numOfParticipants = n.toNumber(); 197 | 198 | let funcComplain = async () => { 199 | return await complaint.complainPubCommitment(instance, accounts, complainer, accused, complainPubCommitIndex); 200 | } 201 | let res = await general.getAccountsBalancesDiffAfterFunc(instance, accounts, funcComplain); 202 | let gasPrice = dkg.class_defaults.gasPrice; 203 | res.balancesAfter[complainer] += res.gasUsed * gasPrice; 204 | 205 | let ethReceived = deposit.toNumber() * numOfParticipants / (numOfParticipants-1); 206 | for(var i = 0; i < numOfParticipants; i++) { 207 | if(i != accused) 208 | { 209 | assert.equal(Math.round(res.balancesAfter[i]/gasPrice), Math.round(ethReceived/gasPrice), 210 | "Participant reward from slashing error"); 211 | } 212 | else 213 | { 214 | assert.equal(res.balancesAfter[i], 0, 215 | "Slashed participant balance should remain unchanged"); 216 | } 217 | } 218 | }); 219 | 220 | it("Check DKG ended with failure", async () => { 221 | let instance = await dkg.deployed(); 222 | await complaint.contractEndFail(instance); 223 | }); 224 | }); 225 | 226 | 227 | contract('DKG unjustified complaint - public commitment G1 not on the curve', async (accounts) => { 228 | it("Post-Deploy check", async() => { 229 | let instance = await dkg.deployed(); 230 | await happyFlow.postDeploy(instance); 231 | }); 232 | 233 | it("Join", async() => { 234 | let instance = await dkg.deployed(); 235 | await happyFlow.join(instance, accounts, happyFlowData.pks); 236 | }); 237 | 238 | it("Commit", async () => { 239 | let instance = await dkg.deployed(); 240 | await happyFlow.commit(instance, accounts, 241 | happyFlowData.pubCommitG1, happyFlowData.pubCommitG2, happyFlowData.prvCommitEnc); 242 | }); 243 | 244 | it("Unjustified Complaint: public commitment G1 IS(!) on the curve", async () => { 245 | const complainer = happyFlowData.flow.complainer; 246 | const accused = happyFlowData.flow.accused; 247 | let instance = await dkg.deployed(); 248 | let n = await instance.n.call(); 249 | let numOfParticipants = n.toNumber(); 250 | let t = await instance.t.call(); 251 | let threshold = t.toNumber(); 252 | 253 | let funcComplain = async () => { 254 | let pubCommitInd = Math.floor((Math.random() * (threshold+1))); 255 | return await complaint.complainNotInG1(instance, accounts, complainer, accused, pubCommitInd); 256 | } 257 | let res = await general.getAccountsBalancesDiffAfterFunc(instance, accounts, funcComplain); 258 | let gasPrice = dkg.class_defaults.gasPrice; 259 | res.balancesAfter[complainer] += gasPrice * res.gasUsed; 260 | for(var i = 0; i < numOfParticipants; i++) { 261 | assert.equal(Math.round(res.balancesAfter[i]/gasPrice), 0, "No reward nor slashing should occur"); 262 | } 263 | }); 264 | 265 | it("Post-Commit", async () => { 266 | const callerIndex = 0; 267 | let instance = await dkg.deployed(); 268 | 269 | let deposit = await instance.depositWei.call(); 270 | let n = await instance.n.call(); 271 | let numOfParticipants = n.toNumber(); 272 | 273 | let funcPostCommit = async () => { 274 | return await happyFlow.postCommit(instance, accounts[callerIndex]); 275 | } 276 | let res = await general.getAccountsBalancesDiffAfterFunc(instance, accounts, funcPostCommit); 277 | 278 | let gasPrice = dkg.class_defaults.gasPrice; 279 | res.balancesAfter[callerIndex] += res.gasUsed*gasPrice; 280 | 281 | let ethReceived = deposit.toNumber(); 282 | for(var i = 0; i < numOfParticipants; i++) { 283 | assert.equal(Math.round(res.balancesAfter[i]/gasPrice), Math.round(ethReceived/gasPrice), 284 | "Participant deposit should return"); 285 | } 286 | }); 287 | 288 | it("Check DKG ended successfully", async () => { 289 | let instance = await dkg.deployed(); 290 | await happyFlow.contractEndSuccess(instance); 291 | }); 292 | }); 293 | 294 | contract('DKG justified complaint - public commitment G1 not on the curve', async (accounts) => { 295 | it("Post-Deploy check", async() => { 296 | let instance = await dkg.deployed(); 297 | await happyFlow.postDeploy(instance); 298 | }); 299 | 300 | it("Join", async() => { 301 | let instance = await dkg.deployed(); 302 | await happyFlow.join(instance, accounts, corruptedData2.pks); 303 | }); 304 | 305 | it("Commit", async () => { 306 | let instance = await dkg.deployed(); 307 | await happyFlow.commit(instance, accounts, 308 | corruptedData2.pubCommitG1, corruptedData2.pubCommitG2, corruptedData2.prvCommitEnc); 309 | }); 310 | 311 | it("Justified Complaint: public commitment G1 is NOT on the curve", async () => { 312 | const complainer = corruptedData2.flow.complainer; 313 | const accused = corruptedData2.flow.accused; 314 | const pubCommitInd = corruptedData2.flow.pubCommitCorruptInd; 315 | let instance = await dkg.deployed(); 316 | let deposit = await instance.depositWei.call(); 317 | let n = await instance.n.call(); 318 | let numOfParticipants = n.toNumber(); 319 | 320 | let funcComplain = async () => { 321 | return await complaint.complainNotInG1(instance, accounts, complainer, accused, pubCommitInd); 322 | } 323 | let res = await general.getAccountsBalancesDiffAfterFunc(instance, accounts, funcComplain); 324 | let gasPrice = dkg.class_defaults.gasPrice; 325 | res.balancesAfter[complainer] += gasPrice * res.gasUsed; 326 | let ethReceived = deposit.toNumber() * numOfParticipants / (numOfParticipants-1); 327 | for(var i = 0; i < numOfParticipants; i++) { 328 | if(i != accused) 329 | { 330 | assert.equal(Math.round(res.balancesAfter[i]/gasPrice), Math.round(ethReceived/gasPrice), 331 | "Participant reward from slashing error"); 332 | } 333 | else 334 | { 335 | assert.equal(res.balancesAfter[i], 0, 336 | "Slashed participant balance should remain unchanged"); 337 | } 338 | } 339 | }); 340 | 341 | it("Check DKG ended with failure", async () => { 342 | let instance = await dkg.deployed(); 343 | await complaint.contractEndFail(instance); 344 | }); 345 | }); 346 | 347 | contract('DKG unjustified complaint - public commitment G2 not on the curve', async (accounts) => { 348 | it("Post-Deploy check", async() => { 349 | let instance = await dkg.deployed(); 350 | await happyFlow.postDeploy(instance); 351 | }); 352 | 353 | it("Join", async() => { 354 | let instance = await dkg.deployed(); 355 | await happyFlow.join(instance, accounts, happyFlowData.pks); 356 | }); 357 | 358 | it("Commit", async () => { 359 | let instance = await dkg.deployed(); 360 | await happyFlow.commit(instance, accounts, 361 | happyFlowData.pubCommitG1, happyFlowData.pubCommitG2, happyFlowData.prvCommitEnc); 362 | }); 363 | 364 | it("Unjustified Complaint: public commitment G2 IS(!) on the curve", async () => { 365 | const complainer = happyFlowData.flow.complainer; 366 | const accused = happyFlowData.flow.accused; 367 | let instance = await dkg.deployed(); 368 | let n = await instance.n.call(); 369 | let numOfParticipants = n.toNumber(); 370 | let t = await instance.t.call(); 371 | let threshold = t.toNumber(); 372 | 373 | let funcComplain = async () => { 374 | let pubCommitInd = Math.floor((Math.random() * (threshold+1))); 375 | return await complaint.complainNotInG2(instance, accounts, complainer, accused, pubCommitInd); 376 | } 377 | let res = await general.getAccountsBalancesDiffAfterFunc(instance, accounts, funcComplain); 378 | let gasPrice = dkg.class_defaults.gasPrice; 379 | res.balancesAfter[complainer] += gasPrice * res.gasUsed; 380 | for(var i = 0; i < numOfParticipants; i++) { 381 | assert.equal(Math.round(res.balancesAfter[i]/gasPrice), 0, "No reward nor slashing should occur"); 382 | } 383 | }); 384 | 385 | it("Post-Commit", async () => { 386 | const callerIndex = 0; 387 | let instance = await dkg.deployed(); 388 | 389 | let deposit = await instance.depositWei.call(); 390 | let n = await instance.n.call(); 391 | let numOfParticipants = n.toNumber(); 392 | 393 | let funcPostCommit = async () => { 394 | return await happyFlow.postCommit(instance, accounts[callerIndex]); 395 | } 396 | let res = await general.getAccountsBalancesDiffAfterFunc(instance, accounts, funcPostCommit); 397 | 398 | let gasPrice = dkg.class_defaults.gasPrice; 399 | res.balancesAfter[callerIndex] += res.gasUsed*gasPrice; 400 | 401 | let ethReceived = deposit.toNumber(); 402 | for(var i = 0; i < numOfParticipants; i++) { 403 | assert.equal(Math.round(res.balancesAfter[i]/gasPrice), Math.round(ethReceived/gasPrice), 404 | "Participant deposit should return"); 405 | } 406 | }); 407 | 408 | it("Check DKG ended successfully", async () => { 409 | let instance = await dkg.deployed(); 410 | await happyFlow.contractEndSuccess(instance); 411 | }); 412 | }); 413 | 414 | contract('DKG justified complaint - public commitment G2 not on the curve', async (accounts) => { 415 | it("Post-Deploy check", async() => { 416 | let instance = await dkg.deployed(); 417 | await happyFlow.postDeploy(instance); 418 | }); 419 | 420 | it("Join", async() => { 421 | let instance = await dkg.deployed(); 422 | await happyFlow.join(instance, accounts, corruptedData2.pks); 423 | }); 424 | 425 | it("Commit", async () => { 426 | let instance = await dkg.deployed(); 427 | await happyFlow.commit(instance, accounts, 428 | corruptedData2.pubCommitG1, corruptedData2.pubCommitG2, corruptedData2.prvCommitEnc); 429 | }); 430 | 431 | it("Justified Complaint: public commitment G2 is NOT on the curve", async () => { 432 | const complainer = corruptedData2.flow.complainer; 433 | const accused = corruptedData2.flow.accused; 434 | const pubCommitInd = corruptedData2.flow.pubCommitCorruptInd; 435 | let instance = await dkg.deployed(); 436 | let deposit = await instance.depositWei.call(); 437 | let n = await instance.n.call(); 438 | let numOfParticipants = n.toNumber(); 439 | 440 | let funcComplain = async () => { 441 | return await complaint.complainNotInG2(instance, accounts, complainer, accused, pubCommitInd); 442 | } 443 | let res = await general.getAccountsBalancesDiffAfterFunc(instance, accounts, funcComplain); 444 | let gasPrice = dkg.class_defaults.gasPrice; 445 | res.balancesAfter[complainer] += gasPrice * res.gasUsed; 446 | let ethReceived = deposit.toNumber() * numOfParticipants / (numOfParticipants-1); 447 | for(var i = 0; i < numOfParticipants; i++) { 448 | if(i != accused) 449 | { 450 | assert.equal(Math.round(res.balancesAfter[i]/gasPrice), Math.round(ethReceived/gasPrice), 451 | "Participant reward from slashing error"); 452 | } 453 | else 454 | { 455 | assert.equal(res.balancesAfter[i], 0, 456 | "Slashed participant balance should remain unchanged"); 457 | } 458 | } 459 | }); 460 | 461 | it("Check DKG ended with failure", async () => { 462 | let instance = await dkg.deployed(); 463 | await complaint.contractEndFail(instance); 464 | }); 465 | }); -------------------------------------------------------------------------------- /simulation/bglsmain.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "io/ioutil" 9 | "math/big" 10 | "os" 11 | "strconv" 12 | "strings" 13 | 14 | "github.com/orbs-network/bgls/bgls" 15 | . "github.com/orbs-network/bgls/curves" 16 | "github.com/orbs-network/bgls/dkg" 17 | ) 18 | 19 | // Usage examples: 20 | // ./dkgmain -func=cgen 21 | 22 | var cmd string 23 | 24 | const INTERACTIVE = false 25 | const BigintAsStrBase = 16 // All bigints as strings are in hex 26 | 27 | const G1_COORDS_LEN = 2 28 | const G2_COORDS_LEN = 4 29 | 30 | type KeyPair struct { 31 | SK string `json:"sk"` 32 | PK []string `json:"pk"` 33 | } 34 | 35 | type DataForCommit struct { 36 | Coefficients []*big.Int 37 | PubCommitG1 []Point 38 | PubCommitG2 []Point 39 | PrvCommit []*big.Int 40 | PrvCommitEnc []*big.Int 41 | SK *big.Int 42 | PK Point 43 | } 44 | 45 | type JsonDataForCommit struct { 46 | Coefficients []string 47 | PubCommitG1 []string 48 | PubCommitG2 []string 49 | PrvCommit []string 50 | PrvCommitEnc []string 51 | SK string 52 | PK []string 53 | } 54 | 55 | // Conversions between array of numbers and G1/G2 points: 56 | //func (g1Point *altbn128Point1) ToAffineCoords() []*big.Int 57 | // func (g1Point *altbn128Point2) ToAffineCoords() []*big.Int 58 | // func (curve *altbn128) MakeG2Point(coords []*big.Int, check bool) (Point, bool) 59 | 60 | // Data for commitment: 61 | // Generate t+1 random coefficients from (mod p) field for the polynomial 62 | // Generate public commitments 63 | // Generate private commitments 64 | 65 | // index is 1-based 66 | func GetCommitDataForSingleParticipant(curve CurveSystem, index int, t int, n int, mySK *big.Int, pks []Point) (*DataForCommit, error) { 67 | data := DataForCommit{ 68 | Coefficients: make([]*big.Int, t+1), 69 | PubCommitG1: make([]Point, t+1), 70 | PubCommitG2: make([]Point, t+1), 71 | PrvCommit: make([]*big.Int, n), 72 | PrvCommitEnc: make([]*big.Int, n), 73 | } 74 | 75 | for i := 0; i < t+1; i++ { 76 | var err error 77 | data.Coefficients[i], data.PubCommitG1[i], data.PubCommitG2[i], err = dkg.CoefficientGen(curve) 78 | if err != nil { 79 | return nil, err 80 | } 81 | verifyResult := dkg.VerifyPublicCommitment(curve, data.PubCommitG1[i], data.PubCommitG2[i]) 82 | if !verifyResult { 83 | return nil, fmt.Errorf("VerifyPublicCommitment() failed for (participant=%v i=%v)", index, i) 84 | } 85 | //fmt.Printf("PASSED VerifyPublicCommitment() (index=%v i=%v)\n", index, i) 86 | } 87 | 88 | j := big.NewInt(1) 89 | for i := 0; i < n; i++ { 90 | //if i == index-1 { 91 | // data.PrvCommit[i] = big.NewInt(0) // Don't calculate private commitment from me to myself 92 | //} else 93 | //{ 94 | plainPrvCommit := dkg.GetPrivateCommitment(curve, j, data.Coefficients) 95 | //fmt.Printf("Calling Encrypt() with sk=%v pks[%v]=%v\n", mySK, i, pks[i].ToAffineCoords(), ) 96 | data.PrvCommit[i] = plainPrvCommit 97 | data.PrvCommitEnc[i] = dkg.Encrypt(curve, mySK, pks[i], plainPrvCommit) 98 | //fmt.Printf("Encrypt() result: %v\n", data.PrvCommit[i]) 99 | //} 100 | j.Add(j, big.NewInt(1)) 101 | } 102 | 103 | return &data, nil 104 | } 105 | 106 | func SignAndVerify(curve CurveSystem, threshold int, n int, data []*DataForCommit) (bool, error) { 107 | 108 | // == Calculate SK, Pks and group PK == 109 | // Should be happen only once, after DKG flow is done, and not for every SignAndVerify() 110 | 111 | fmt.Println() 112 | fmt.Printf("Starting SignAndVerify with threshold=%v n=%v\n", threshold, n) 113 | 114 | fmt.Println("Calculating SK, PK and Commitments - this is done just once, before signing & verifying messages.") 115 | 116 | // == Verify phase == 117 | 118 | commitPrvAllDec := make([][]*big.Int, n) 119 | 120 | // First decrypt 121 | for committedParticipant := 0; committedParticipant < n; committedParticipant++ { 122 | pk := data[committedParticipant].PK // this is the encrypted pk 123 | fmt.Printf("PK: %v\n", pk) 124 | commitPrvDec := make([]*big.Int, n) 125 | for participant := 0; participant < n; participant++ { 126 | if committedParticipant != participant { 127 | sk := data[participant].SK 128 | fmt.Printf("SK[%v]: %v\n", participant, sk) 129 | enc := big.NewInt(0).Set(data[committedParticipant].PrvCommitEnc[participant]) // PrvCommit is encrypted 130 | fmt.Printf("Enc prv commit[%v][%v]: %v\n", committedParticipant, participant, enc) 131 | commitPrvDec[participant] = dkg.Decrypt(curve, sk, pk, enc) 132 | fmt.Printf("Dec prv commit[%v]: %v\n", participant, commitPrvDec[participant]) 133 | if commitPrvDec[participant].Cmp(data[committedParticipant].PrvCommit[participant]) != 0 { 134 | panic("commitment is not the same after decryption") 135 | } 136 | } else { 137 | commitPrvDec[participant] = data[committedParticipant].PrvCommit[participant] // personal data 138 | } 139 | } 140 | commitPrvAllDec[committedParticipant] = commitPrvDec 141 | } 142 | fmt.Println("PASSED First Decrypt") 143 | 144 | j := big.NewInt(1) 145 | for participant := 0; participant < n; participant++ { 146 | for commitParticipant := 0; commitParticipant < n; commitParticipant++ { 147 | if participant != commitParticipant { 148 | prv := commitPrvAllDec[commitParticipant][participant] 149 | pub := data[commitParticipant].PubCommitG1 150 | if !dkg.VerifyPrivateCommitment(curve, j, prv, pub) { 151 | panic("private commit doesnt match public commit") 152 | } 153 | } 154 | } 155 | j.Add(j, big.NewInt(1)) 156 | } 157 | fmt.Println("PASSED VerifyPrivateCommitment") 158 | 159 | // END OF DKG 160 | 161 | // == Calculate SK, Pks and group PK == 162 | skAll := make([]*big.Int, n) 163 | pkAll := make([][]Point, n) 164 | pubCommitG2Zero := make([]Point, n) 165 | pubCommitG2All := make([][]Point, n) 166 | for participant := 0; participant < n; participant++ { 167 | pubCommitG2All[participant] = data[participant].PubCommitG2 168 | fmt.Printf("pubCommitG2All[%v]: %v\n", participant, pubCommitG2All[participant]) 169 | } 170 | 171 | for participant := 0; participant < n; participant++ { 172 | pkAll[participant] = dkg.GetAllPublicKey(curve, threshold, pubCommitG2All) 173 | pubCommitG2Zero[participant] = pubCommitG2All[participant][0] 174 | prvCommit := make([]*big.Int, n) 175 | for commitParticipant := 0; commitParticipant < n; commitParticipant++ { 176 | prvCommit[commitParticipant] = commitPrvAllDec[commitParticipant][participant] 177 | } 178 | skAll[participant] = dkg.GetSecretKey(prvCommit) 179 | } 180 | 181 | //Verify pkAll are the same for all 182 | fmt.Println("Public Key shares - same values are calculated by each client") 183 | for participant := 0; participant < n; participant++ { 184 | pks := pkAll[participant] 185 | for otherParticipant := 0; otherParticipant < n; otherParticipant++ { 186 | if !pks[participant].Equals(pkAll[otherParticipant][participant]) { 187 | panic("pk for the same participant is different among other paricipants") 188 | } 189 | } 190 | } 191 | 192 | fmt.Println("PASSED Verification that same PKs are shared between all participants") 193 | 194 | fmt.Println("Completed one-time calculation of SK, PK and Commitments") 195 | // fmt.Println("** SECRET KEYS [DEBUG ONLY] **") 196 | // for _, sk := range skAll { 197 | // fmt.Printf("** SK: %x\n", sk) 198 | // } 199 | fmt.Println() 200 | 201 | groupPk := dkg.GetGroupPublicKey(curve, pubCommitG2Zero) 202 | fmt.Printf("Group PK: %v\n", pointToHexCoords(groupPk)) 203 | 204 | coefsZero := make([]*big.Int, n) 205 | for participant := 0; participant < n; participant++ { 206 | coefsZero[participant] = data[participant].Coefficients[0] 207 | } 208 | groupSk := dkg.GetPrivateCommitment(curve, big.NewInt(1), coefsZero) 209 | if !groupPk.Equals(bgls.LoadPublicKey(curve, groupSk)) { 210 | panic("groupPK doesnt match to groupSK") 211 | } 212 | 213 | // == Sign and reconstruct == 214 | 215 | var msg string 216 | if INTERACTIVE { 217 | msg = readFromStdin("*** Enter message: ") 218 | } else { 219 | msg = "Hello Orbs" 220 | } 221 | 222 | fmt.Println() 223 | fmt.Printf("Message for signature verification: %v\n", msg) 224 | msgBytes := []byte(msg) 225 | fmt.Printf("Message bytes: %v\n", msgBytes) 226 | sigs := make([]Point, n) 227 | 228 | // For each participant, generate signature with its SK 229 | for participant := 0; participant < n; participant++ { 230 | sigs[participant] = bgls.Sign(curve, skAll[participant], msgBytes) 231 | 232 | if !bgls.VerifySingleSignature(curve, sigs[participant], pkAll[0][participant], msgBytes) { 233 | return false, fmt.Errorf("signature invalid") 234 | } 235 | fmt.Printf("PASSED VerifySingleSignature() sig share for client ID #%v: %v\n", participant+1, pointToHexCoords(sigs[participant])) 236 | } 237 | 238 | // Generates indices [0..n) 239 | indices := make([]*big.Int, n) 240 | index := big.NewInt(0) 241 | for participant := 0; participant < n; participant++ { 242 | index.Add(index, big.NewInt(1)) 243 | indices[participant] = big.NewInt(0).Set(index) 244 | } 245 | 246 | // These are 1-based (not 0-based) 247 | subIndices := [][]int{ 248 | //{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, 249 | //{1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, 250 | //{1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 20, 18, 16, 14}, 251 | {3, 4, 5}, 252 | {2, 4, 5}, 253 | {1, 3, 5}, 254 | } 255 | 256 | for i := 0; i < len(subIndices); i++ { 257 | fmt.Println() 258 | fmt.Printf("=====> verifySigOnSubset() subIndices #%v <=====\n", subIndices[i]) 259 | readFromStdin("") 260 | _, err := verifySigOnSubset(curve, indices, sigs, groupPk, msgBytes, subIndices[i]) 261 | if err != nil { 262 | fmt.Printf("Error in subgroup %v: %v", subIndices[i], err) 263 | return false, err 264 | } 265 | fmt.Printf("PASSED verifySigOnSubset() subIndices #%v\n", subIndices[i]) 266 | fmt.Printf("Verify signature completed successfully for subgroup %v\n", subIndices[i]) 267 | fmt.Println("======================================================") 268 | } 269 | 270 | fmt.Println() 271 | 272 | return true, nil 273 | } 274 | 275 | func verifySigOnSubset(curve CurveSystem, indices []*big.Int, sigs []Point, groupPk Point, msgBytes []byte, subIndices []int) (bool, error) { 276 | 277 | subSigs := make([]Point, len(subIndices)) 278 | subIndicesBigInt := make([]*big.Int, len(subIndices)) 279 | 280 | for i, idx := range subIndices { 281 | subSigs[i] = sigs[idx-1] 282 | subIndicesBigInt[i] = big.NewInt(int64(idx)) 283 | //subIndices[i] = indices[idx] 284 | } 285 | 286 | fmt.Printf("Sending to SignatureReconstruction(): indices=%v\n", subIndices) 287 | //for i, subSig := range subSigs { 288 | //fmt.Printf("Signature Share %v: %v\n", subIndicesBigInt[i], pointToHexCoords(subSig)) 289 | //} 290 | groupSig1, err := dkg.SignatureReconstruction( 291 | curve, subSigs, subIndicesBigInt) 292 | if err != nil { 293 | return false, fmt.Errorf("group signature reconstruction failed") 294 | } 295 | 296 | fmt.Printf("* Created group signature: %v *\n", pointToHexCoords(groupSig1)) 297 | 298 | if !bgls.VerifySingleSignature(curve, groupSig1, groupPk, msgBytes) { 299 | return false, fmt.Errorf("group signature invalid") 300 | } 301 | fmt.Printf("* PASSED VerifySingleSignature for subgroup signature: %v\n", pointToHexCoords(groupSig1)) 302 | fmt.Printf("Group PK: %v\n", pointToHexCoords(groupPk)) 303 | 304 | return true, nil 305 | } 306 | 307 | func main() { 308 | Init() 309 | curve := Altbn128 310 | //fmt.Println(cmd) 311 | //fmt.Println(flag.Args()) 312 | switch cmd { 313 | 314 | case "VerifyPrivateCommitment": 315 | // func VerifyPrivateCommitment(curve CurveSystem, myIndex *big.Int, prvCommit *big.Int, pubCommitG1 []Point) bool { 316 | complainerIndex := toInt(flag.Arg(0)) // 1-based 317 | accusedIndex := toInt(flag.Arg(1)) // 1-based 318 | 319 | //myIndex, _ := strconv.Atoi(flag.Args()[0]) // 2 1-based 320 | //prvCommit, _ := new(big.Int).SetString(flag.Arg(1), 0) 321 | //pubCommitG1 := strToG1s(curve, flag.Arg(2)) 322 | // prvCommit is prvCommitAll[0][1] - this is what client 0 has commited to client 1 323 | // pubCommitG1 [0] - this is all of client 0 public commitments over G1 324 | myIndex := big.NewInt(int64(complainerIndex)) 325 | dataFile := flag.Arg(2) 326 | jsonData := readDataFile(dataFile, curve) 327 | data := make([]*DataForCommit, len(jsonData)) 328 | for i := 0; i < len(jsonData); i++ { 329 | data[i] = jsonData[i].toData(curve) 330 | } 331 | prvCommit := data[accusedIndex-1].PrvCommit[complainerIndex-1] 332 | pubCommitG1 := data[accusedIndex-1].PubCommitG1 333 | 334 | fmt.Printf("Calling VerifyPrivateCommitment(): myIndex: %v prvCommit: %v pubCommitG1: %v\n", myIndex, bigIntToHexStr(prvCommit), pointsToStr(pubCommitG1)) 335 | passedVerification := dkg.VerifyPrivateCommitment(curve, myIndex, prvCommit, pubCommitG1) 336 | res := fmt.Sprintf("%v\n", boolToStr(passedVerification)) 337 | fmt.Println(res) 338 | 339 | case "GetCommitDataForSingleParticipant": 340 | myIndex, _ := strconv.Atoi(flag.Args()[0]) 341 | threshold := toInt(flag.Arg(1)) 342 | n := toInt(flag.Arg(2)) 343 | sk, _ := new(big.Int).SetString(flag.Arg(3), 0) 344 | pks := strToG1s(curve, flag.Arg(4)) 345 | 346 | dataForCommit, err := GetCommitDataForSingleParticipant(curve, myIndex, threshold, n, sk, pks) 347 | if err != nil { 348 | panic(err) 349 | } 350 | 351 | // TODO Add marshalling for point - maybe add new type like in JsonAllDataForCommit 352 | 353 | json, err := json.Marshal(dataForCommit) 354 | if err != nil { 355 | fmt.Println("Error: ", err) 356 | } 357 | fmt.Printf("%v\n", string(json)) 358 | 359 | case "SignAndVerify": 360 | //fmt.Println("--- SignAndVerify ---") 361 | threshold := toInt(flag.Arg(0)) 362 | n := toInt(flag.Arg(1)) 363 | dataFile := flag.Arg(2) 364 | jsonData := readDataFile(dataFile, curve) 365 | data := make([]*DataForCommit, len(jsonData)) 366 | for i := 0; i < len(jsonData); i++ { 367 | data[i] = jsonData[i].toData(curve) 368 | fmt.Printf("data[%v]: PubCommitG2[0]: %v\n", i, data[i].PubCommitG2[0].ToAffineCoords()) 369 | } 370 | 371 | for i := 0; i < len(jsonData); i++ { 372 | fmt.Printf("data[%v]: PubCommitG2[0]: %v\n", i, data[i].PubCommitG2[0].ToAffineCoords()) 373 | } 374 | isOk, err := SignAndVerify(curve, threshold, n, data) 375 | if err != nil { 376 | res := fmt.Sprintf("Error in SignAndVerify(): %v", err) 377 | fmt.Println(res) 378 | } 379 | fmt.Printf("SignAndVerify() ok? %v\n", isOk) 380 | 381 | case "GenerateKeyPair": 382 | sk, pk, _, _ := dkg.CoefficientGen(curve) 383 | keyPair := KeyPair{bigIntToHexStr(sk), pointToStrArray(pk)} 384 | //keyPairJson, _ := keyPair.Marshal() 385 | //fmt.Println(keyPair) 386 | json, err := json.Marshal(keyPair) 387 | if err != nil { 388 | fmt.Println("Error: ", err) 389 | } 390 | fmt.Printf("%v\n", string(json)) 391 | 392 | } 393 | 394 | } 395 | 396 | // Gets array of the form p0[0], p0[1], p1[0], p1[1], p2[0], p2[1], etc. 397 | // Each pair is a G1 point so an array of Points is returned. 398 | // This is not 399 | func strToG1s(curve CurveSystem, pointStr string) []Point { 400 | //fmt.Printf("pointStr=%v\n", pointStr) 401 | pointStrCoords := strings.Split(pointStr, ",") 402 | points := make([]Point, len(pointStrCoords)/2) 403 | for i := 0; i < len(pointStrCoords); i += 2 { 404 | //fmt.Printf("Reading pointsStrCoords i=%v of %v", i, len(pointStrCoords)) 405 | coord0, ok := new(big.Int).SetString(pointStrCoords[i], 0) 406 | if !ok { 407 | panic(fmt.Errorf("failed parsing coord0 to big.Int: %v (big.Int value: %v)", pointStrCoords[i], coord0)) 408 | } 409 | coord1, ok := new(big.Int).SetString(pointStrCoords[i+1], 0) 410 | if !ok { 411 | panic(fmt.Errorf("failed parsing coord1 to big.Int: %v (big.Int value: %v)", pointStrCoords[i], coord1)) 412 | } 413 | 414 | bigintCoords := []*big.Int{coord0, coord1} 415 | //fmt.Printf("strToG1: coord0=%v coord1=%v\n", coord0, coord1) 416 | point, _ := curve.MakeG1Point(bigintCoords, true) 417 | points[i/2] = point 418 | } 419 | return points 420 | } 421 | 422 | func strToG1(curve CurveSystem, pointStr string) Point { 423 | pointStrCoords := strings.Split(pointStr, ",") 424 | bigintCoords := make([]*big.Int, len(pointStrCoords)) 425 | for i := 0; i < len(pointStr); i++ { 426 | bigintCoords[i], _ = new(big.Int).SetString(pointStrCoords[i], 0) 427 | } 428 | point, _ := curve.MakeG1Point(bigintCoords, true) 429 | return point 430 | } 431 | 432 | func readDataFile(dataFile string, curve CurveSystem) []*JsonDataForCommit { 433 | var inBuf []byte 434 | var resJson []*JsonDataForCommit 435 | var err error 436 | inBuf, err = ioutil.ReadFile(dataFile) 437 | //err = readGob("./data.gob", data) 438 | if err != nil { 439 | panic(err) 440 | } 441 | 442 | if err := json.Unmarshal(inBuf, &resJson); err != nil { 443 | panic(err) 444 | } 445 | return resJson 446 | } 447 | 448 | func (keyPair KeyPair) Marshal() ([]byte, error) { 449 | 450 | return json.Marshal(keyPair) 451 | } 452 | 453 | func pointToStrArray(point Point) []string { 454 | coords := point.ToAffineCoords() 455 | coordsStr := make([]string, len(coords)) 456 | for k := 0; k < len(coords); k++ { 457 | coordsStr[k] = toHexBigInt(coords[k]) 458 | } 459 | return coordsStr 460 | 461 | } 462 | func toHexBigInt(n *big.Int) string { 463 | return fmt.Sprintf("0x%x", n) // or %X or upper case 464 | } 465 | 466 | func toInt(s string) int { 467 | i, _ := strconv.Atoi(s) 468 | return i 469 | } 470 | 471 | func toBigInt(s string) *big.Int { 472 | bigInt := new(big.Int) 473 | bigInt, ok := bigInt.SetString(s, 0) 474 | if !ok { 475 | panic(fmt.Errorf("toBigInt() failed on string %v", s)) 476 | } 477 | return bigInt 478 | } 479 | 480 | func boolToStr(boolRes bool) string { 481 | return fmt.Sprintf("%v", boolRes) 482 | } 483 | 484 | func bigIntToHexStr(bigInt *big.Int) string { 485 | return fmt.Sprintf("0x%x", bigInt) 486 | } 487 | 488 | func bigIntArrayToHexStrArray(bigInts []*big.Int) []string { 489 | 490 | arr := make([]string, len(bigInts)) 491 | for i := 0; i < len(bigInts); i++ { 492 | arr[i] = bigIntToHexStr(bigInts[i]) 493 | } 494 | return arr 495 | } 496 | 497 | func pointToHexCoords(p Point) string { 498 | 499 | return strings.Join(pointToHexCoordsArray(p), ",") 500 | } 501 | 502 | func pointToHexCoordsArray(p Point) []string { 503 | 504 | coords := p.ToAffineCoords() 505 | res := make([]string, len(coords)) 506 | for i, coord := range coords { 507 | res[i] = toHexBigInt(coord) 508 | } 509 | return res 510 | } 511 | 512 | func pointsToStr(points []Point) string { 513 | return strings.Join(pointsToStrArray(points), ",") 514 | } 515 | 516 | func pointsToStrArray(points []Point) []string { 517 | pointStrs := make([]string, 0) 518 | for i := 0; i < len(points); i++ { 519 | pointStrs = append(pointStrs, pointToHexCoordsArray(points[i])...) 520 | } 521 | return pointStrs 522 | } 523 | 524 | func Init() { 525 | 526 | flag.StringVar(&cmd, "func", "", "Name of function") 527 | flag.Parse() 528 | 529 | } 530 | 531 | func readFromStdin(caption string) string { 532 | reader := bufio.NewReader(os.Stdin) 533 | fmt.Println() 534 | fmt.Print(caption) 535 | text, _ := reader.ReadString('\n') 536 | return text 537 | } 538 | 539 | func (jd *JsonDataForCommit) toData(curve CurveSystem) *DataForCommit { 540 | 541 | res := new(DataForCommit) 542 | res.Coefficients = make([]*big.Int, len(jd.Coefficients)) 543 | res.PubCommitG1 = make([]Point, len(jd.PubCommitG1)/G1_COORDS_LEN) 544 | res.PubCommitG2 = make([]Point, len(jd.PubCommitG2)/G2_COORDS_LEN) 545 | res.PrvCommit = make([]*big.Int, len(jd.PrvCommit)) 546 | res.PrvCommitEnc = make([]*big.Int, len(jd.PrvCommitEnc)) 547 | 548 | for i := 0; i < len(jd.Coefficients); i++ { 549 | res.Coefficients[i] = toBigInt(jd.Coefficients[i]) 550 | } 551 | 552 | res.SK = toBigInt(jd.SK) 553 | 554 | coords := make([]*big.Int, G1_COORDS_LEN) 555 | for k := 0; k < G1_COORDS_LEN; k++ { 556 | coords[k] = toBigInt(jd.PK[k]) 557 | } 558 | pk, _ := curve.MakeG1Point(coords, true) 559 | res.PK = pk 560 | 561 | // PubCommitG1 is a string array of flattened x,y points in this format: x0,y0,x1,y1,x2,y2,.. 562 | for i := 0; i < len(jd.PubCommitG1); i += G1_COORDS_LEN { 563 | coords := make([]*big.Int, G1_COORDS_LEN) 564 | for k := 0; k < G1_COORDS_LEN; k++ { 565 | coords[k] = toBigInt(jd.PubCommitG1[i+k]) 566 | } 567 | var isOk bool 568 | res.PubCommitG1[i/G1_COORDS_LEN], isOk = curve.MakeG1Point(coords, true) 569 | //fmt.Printf("G1 Point: %v\n", res.PubCommitG1[i/G1_COORDS_LEN].ToAffineCoords()) 570 | if !isOk { 571 | panic(fmt.Errorf("failed to make G1 point")) 572 | } 573 | } 574 | 575 | // PubCommitG2 is a string array of flattened x,y,z,t points in this format: x0,y0,z0,t0,x1,y1,z1,t1,x2,y2,z2,t2,.. 576 | for i := 0; i < len(jd.PubCommitG2); i += G2_COORDS_LEN { 577 | coords := make([]*big.Int, G2_COORDS_LEN) 578 | for k := 0; k < G2_COORDS_LEN; k++ { 579 | coords[k] = toBigInt(jd.PubCommitG2[i+k]) 580 | } 581 | var isOk bool 582 | //fmt.Printf("Make G2 point from coords: %v\n", coords) 583 | res.PubCommitG2[i/G2_COORDS_LEN], isOk = curve.MakeG2Point(coords, true) 584 | //fmt.Printf("G2 Point: %v\n", res.PubCommitG2[i/G2_COORDS_LEN].ToAffineCoords()) 585 | if !isOk { 586 | panic(fmt.Errorf("failed to make G2 point")) 587 | } 588 | } 589 | 590 | for i := 0; i < len(jd.PrvCommit); i++ { 591 | res.PrvCommit[i] = toBigInt(jd.PrvCommit[i]) 592 | } 593 | for i := 0; i < len(jd.PrvCommitEnc); i++ { 594 | res.PrvCommitEnc[i] = toBigInt(jd.PrvCommitEnc[i]) 595 | } 596 | 597 | return res 598 | 599 | } 600 | 601 | func (data DataForCommit) MarshalJSON() ([]byte, error) { 602 | 603 | res := new(JsonDataForCommit) 604 | 605 | res.Coefficients = bigIntArrayToHexStrArray(data.Coefficients) 606 | res.PubCommitG1 = pointsToStrArray(data.PubCommitG1) 607 | res.PubCommitG2 = pointsToStrArray(data.PubCommitG2) 608 | //fmt.Printf("G1=%v G2=%v\n", res.PubCommitG1, res.PubCommitG2) 609 | res.PrvCommit = bigIntArrayToHexStrArray(data.PrvCommit) 610 | res.PrvCommitEnc = bigIntArrayToHexStrArray(data.PrvCommitEnc) 611 | 612 | return json.Marshal(res) 613 | } 614 | -------------------------------------------------------------------------------- /contracts/dkg.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.0; 2 | 3 | import "./ecOps.sol"; 4 | 5 | contract dkg { 6 | 7 | /** 8 | * DKG phases (optimistic case): 9 | * 10 | * 0) Deploy the contract with a threshold (t), number of participants (n) 11 | * and deposit in Wei. 12 | * 13 | * 1) Enrollment - Each of the participants sends a deposit and a public key 14 | * address (for encryption purpose) that he owns (i.e., has the corresponding 15 | * secret key). At the end of the enrollment each participant receives an 16 | * unique index (1,...,n) that would be used with his identity for the rest 17 | * of the DKG protocol and the threshold signature scheme. 18 | * 19 | * (not on the contract) - each participant generates t+1 random sampled 20 | * coefficients from the (mod q) field. 21 | * 22 | * 2) Each of the participants sends its public commitments (the generator 23 | * exponentiated with t+1 coefficients) and encrypted private commitments 24 | * for all of the other particpants. 25 | * 26 | * After all have validly committed each of the participant can take the 27 | * committed data and locally compute the followings: 28 | * a) its own secret key and public key; 29 | * b) the public keys for the rest of the participants; 30 | * c) the group's public key (no one would know the corresponding secret 31 | * key); 32 | * With the above data each participant is able to sign, verify signature- 33 | * shares and reconstruct from t+1 signatures the group signature. 34 | * 35 | * 36 | * 37 | * In case one (or more) of the participants deviates from the instructions 38 | * of the protocol, we enable the other participants to file a complaint to 39 | * the contract. Such a complaint would terminate the DKG protocol with a 40 | * failure. Once a complaint is filed, a code is run to check the validity of 41 | * the complaint. When done the code decides which participant's deposit to 42 | * slash and divide it among the other participants ("code is law"). 43 | * 44 | * Each participant can send a complaint tx about one of the followings: 45 | * a) 2 distinct participants offered the same public commitment 46 | (one is enough). (TODO) 47 | * b) Some participant offered invalid commitment (invalid is: 48 | * duplicated, insufficient, unmatching commitments G1 to G2) 49 | * c) Umatched private and public commitments. 50 | * d) Time out. 51 | * 52 | */ 53 | 54 | 55 | /** 56 | * Important note: at this point this contract purpose is as a 57 | * POC only, therefore its security is unreliable. 58 | */ 59 | 60 | 61 | 62 | struct Participant { 63 | address ethPk; // Ethereum pk 64 | uint256[2] encPk; // pk for encryption 65 | mapping (uint16 => uint256[2]) publicCommitmentsG1; // coefficient index to commitment 66 | mapping (uint16 => uint256[4]) publicCommitmentsG2; 67 | // TODO: should be encrypted (and possibly off chain). 68 | mapping (uint16 => uint256) encPrivateCommitments; // node's index to its commitment 69 | bool isCommitted; 70 | } 71 | 72 | enum Phase { Enrollment, Commit, PostCommit, EndSuccess, EndFail } // start from 0 73 | 74 | 75 | event PhaseChange( 76 | Phase phase 77 | ); 78 | event NewCommit( 79 | uint16 committerIndex, 80 | uint256[] pubCommitG1, 81 | uint256[] pubCommitG2, 82 | uint256[] prvCommit 83 | ); 84 | event ParticipantJoined( 85 | uint16 index 86 | ); 87 | 88 | 89 | Phase public curPhase; 90 | 91 | 92 | //uint256 public constant a = 0; 93 | //uint256 public constant b = 3; 94 | 95 | // G1 generator (on the curve) 96 | uint256[2] public g1 = [ 97 | 0x0000000000000000000000000000000000000000000000000000000000000001, 98 | 0x0000000000000000000000000000000000000000000000000000000000000002 99 | ]; 100 | // G2 generator (on the curve) 101 | uint256[4] public g2 = [ 102 | 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2, 103 | 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed, 104 | 0x90689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b, 105 | 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa 106 | ]; 107 | 108 | uint256 public depositWei; 109 | 110 | 111 | uint16 public t; // threshold 112 | uint16 public n; // numer of participants; 113 | uint16 public curN; // current num of participants 114 | uint16 public curNumCommittedLeft; // current num of participants that haven't committed 115 | 116 | uint256 public phaseStart; 117 | uint256 public constant joinTimeout = 12; 118 | uint256 public constant commitTimeout = 11; 119 | uint256 public constant postCommitTimeout = 5; 120 | 121 | 122 | // mapping from node's index to a participant 123 | mapping (uint16 => Participant) public participants; 124 | 125 | 126 | constructor(uint16 threshold, uint16 numParticipants, uint deposit) public 127 | { 128 | t = threshold; 129 | n = numParticipants; 130 | curNumCommittedLeft = numParticipants; 131 | depositWei = deposit; 132 | curPhase = Phase.Enrollment; 133 | 134 | require(n > t && t > 0, "wrong input"); 135 | 136 | 137 | phaseStart = block.number; 138 | } 139 | 140 | 141 | 142 | modifier checkDeposit() { 143 | require(msg.value == depositWei, "wrong deposit"); 144 | _; 145 | } 146 | modifier checkAuthorizedSender(uint16 index) { 147 | require(participants[index].ethPk == msg.sender, "not authorized sender"); 148 | _; 149 | } 150 | modifier beFalse(bool term) { 151 | require(!term); 152 | _; 153 | } 154 | modifier inPhase(Phase phase) { 155 | require(curPhase == phase, "wrong phase"); 156 | _; 157 | } 158 | modifier notInPhase(Phase phase) { 159 | require(curPhase != phase, "wrong phase"); 160 | _; 161 | } 162 | 163 | 164 | // Join the DKG (enrollment - phase 1). 165 | // A point on G1 that represents this participant's pk for encryption have 166 | // to be published. The publisher have to know the secret that generates 167 | // this point. 168 | function join(uint256[2] encPk) 169 | checkDeposit() 170 | inPhase(Phase.Enrollment) 171 | external payable 172 | returns(uint16 index) 173 | { 174 | // TODO: check pk 175 | 176 | uint16 cn = curN; 177 | address sender = msg.sender; 178 | 179 | // Check the pk isn't registered already 180 | for(uint16 i = 1; i <= cn; i++) { 181 | require(participants[i].ethPk != sender, "already joined"); 182 | } 183 | 184 | cn++; 185 | participants[cn] = Participant({ethPk: sender, encPk: encPk, isCommitted: false}); 186 | 187 | curN = cn; 188 | if(cn == 1) { 189 | phaseStart = block.number; 190 | } 191 | 192 | // Abort if capacity on participants was reached 193 | if(cn == n) { 194 | curPhase = Phase.Commit; 195 | emit PhaseChange(Phase.Commit); 196 | } 197 | 198 | emit ParticipantJoined(cn); 199 | return cn; 200 | } 201 | 202 | 203 | // Send commitments (phase 2). 204 | // 205 | // pubCommitG1 is composed of t+1 commitments to local randomly sampled 206 | // coefficients. Each commitment should be on the G1 curve (affine 207 | // coordinates) and therefore it has 2 coordinates. Thus, the input array 208 | // is of size (2t+2) and the i'th commitment will be in indices (2i) and 209 | // (2i+1). 210 | // 211 | // pubCommitG2 is composed of t+1 commitments to same sampled coefficients 212 | // from pubCommitG1. Each commitment should be on the G2 curve (affine 213 | // coordinates) and therefore it has 4 coordinates. Thus, the input array 214 | // is of size (4t+4) and the i'th commitment will be in indices (4i),(4i+1), 215 | // (4i+2),(4i+3). 216 | // 217 | // prCommit is an array of size n, where the first index matches the 218 | // first participant (participant index 1) and so forth. The commitment 219 | // is a calculation on the localy generated polynomial in the particpant's 220 | // index. This calculation is encrypted by the recevier pk for encryption. 221 | // The senderIndex private commitment is ignored and can be anything 222 | // (but can't be skipped). 223 | // 224 | // Note that this function does not verifies the committed data, it 225 | // should be done outside of this contract scope. In case of an 226 | // invalid committed data use complaints. 227 | function commit(uint16 senderIndex, uint256[] pubCommitG1, 228 | uint256[] pubCommitG2, uint256[] encPrCommit) 229 | inPhase(Phase.Commit) 230 | checkAuthorizedSender(senderIndex) 231 | beFalse(participants[senderIndex].isCommitted) 232 | external 233 | { 234 | // TODO: phase timeout, make prCommit encrypted, verify sender 235 | // index matches the sender's address. 236 | 237 | assignCommitments(senderIndex, pubCommitG1, pubCommitG2, encPrCommit); 238 | 239 | uint16 committedNumLeft = curNumCommittedLeft - 1; 240 | curNumCommittedLeft = committedNumLeft; 241 | 242 | if(committedNumLeft == 0) { 243 | curPhase = Phase.PostCommit; 244 | phaseStart = block.number; 245 | emit PhaseChange(Phase.PostCommit); 246 | } 247 | } 248 | 249 | 250 | // Assigns the commitments to the sender with index of senderIndex. 251 | function assignCommitments(uint16 senderIndex, uint256[] pubCommitG1, 252 | uint256[] pubCommitG2, uint256[] prCommit) 253 | internal 254 | { 255 | // TODO: consider merging the following loops to save gas 256 | uint16 nParticipants = n; 257 | 258 | uint16 threshold = t; 259 | 260 | // Verify input size 261 | require(pubCommitG1.length == (threshold*2 + 2) 262 | && pubCommitG2.length == (threshold*4 + 4) 263 | && prCommit.length == nParticipants, 264 | "input size invalid"); 265 | 266 | // Assign public commitments from G1 and G2 267 | for(uint16 i = 0; i < (threshold+1); i++) { 268 | participants[senderIndex].publicCommitmentsG1[i] = [pubCommitG1[2*i], pubCommitG1[2*i+1]]; 269 | participants[senderIndex].publicCommitmentsG2[i] = [ 270 | pubCommitG2[4*i], pubCommitG2[4*i+1], pubCommitG2[4*i+2], pubCommitG2[4*i+3] 271 | ]; 272 | } 273 | 274 | // Assign private commitments 275 | for(i = 1; i <= nParticipants; i++) { 276 | if(senderIndex != i) { 277 | participants[senderIndex].encPrivateCommitments[i] = prCommit[i-1]; 278 | } 279 | } 280 | 281 | participants[senderIndex].isCommitted = true; 282 | emit NewCommit(senderIndex, pubCommitG1, pubCommitG2, prCommit); 283 | } 284 | 285 | 286 | // Call this when in Phase.PostCommit for more than postCommitTimeout 287 | // blocks and no comlaint has to be made. 288 | function postCommitTimedOut() 289 | inPhase(Phase.PostCommit) 290 | external 291 | { 292 | 293 | uint curBlockNum = block.number; 294 | 295 | require(curBlockNum > (phaseStart+postCommitTimeout), "hasn't reached timeout yet"); 296 | curPhase = Phase.EndSuccess; 297 | emit PhaseChange(Phase.EndSuccess); 298 | slash(0); 299 | } 300 | 301 | // Call this when in Phase.Enrollment for more than joinTimeout 302 | // blocks and not enough members have joined. 303 | function joinTimedOut() 304 | inPhase(Phase.Enrollment) 305 | external 306 | { 307 | uint curBlockNum = block.number; 308 | 309 | require(curBlockNum > (phaseStart+joinTimeout), "hasn't reached timeout yet"); 310 | curPhase = Phase.EndFail; 311 | emit PhaseChange(Phase.EndFail); 312 | slash(0); 313 | } 314 | 315 | // Call this when in Phase.Commit for more than commitTimeout 316 | // blocks and not enough members have committed. 317 | function commitTimedOut() 318 | inPhase(Phase.Commit) 319 | external 320 | { 321 | uint curBlockNum = block.number; 322 | 323 | require(curBlockNum > (phaseStart+commitTimeout), "hasn't reached timeout yet"); 324 | curPhase = Phase.EndFail; 325 | emit PhaseChange(Phase.EndFail); 326 | slashUncommitted(); 327 | } 328 | 329 | // Returns the group PK. 330 | // This can only be performed after the DKG has ended. This 331 | // means only when the current phase is Phase.End . 332 | function getGroupPK() 333 | inPhase(Phase.EndSuccess) 334 | public view returns(uint256[2] groupPK) 335 | { 336 | 337 | uint16 nParticipants = n; 338 | groupPK = participants[1].publicCommitmentsG1[0]; 339 | 340 | for(uint16 i = 2; i <= nParticipants; i++) { 341 | groupPK = ecOps.ecadd(groupPK, participants[i].publicCommitmentsG1[0]); 342 | } 343 | } 344 | 345 | 346 | 347 | //////////////// 348 | // Complaints // 349 | //////////////// 350 | 351 | 352 | // A complaint on some public commit. If for some reason this 353 | // function fails it will slash the complainer deposit! (unless some 354 | // unauthorized address made the transaction or the wrong phase). 355 | // 356 | // The complaint should be called when the public commitments coming 357 | // from the G1 group does not match to the ones from G2 group (using pairing). 358 | function complaintPublicCommit(uint16 complainerIndex, uint16 accusedIndex, 359 | uint16 pubCommitIndex) 360 | checkAuthorizedSender(complainerIndex) 361 | notInPhase(Phase.EndFail) 362 | notInPhase(Phase.EndSuccess) 363 | public 364 | { 365 | curPhase = Phase.EndFail; 366 | emit PhaseChange(Phase.EndFail); 367 | 368 | Participant storage accused = participants[accusedIndex]; 369 | if(!accused.isCommitted) { 370 | slash(complainerIndex); 371 | return; 372 | } 373 | 374 | 375 | if (ecOps.pairingCheck(accused.publicCommitmentsG1[pubCommitIndex], 376 | g2, g1, accused.publicCommitmentsG2[pubCommitIndex])) { 377 | 378 | slash(complainerIndex); 379 | } 380 | else { 381 | slash(accusedIndex); 382 | } 383 | 384 | } 385 | 386 | // A complaint on some private commitment. If for some reason this 387 | // function fails it will slash the complainer deposit! (unless some 388 | // unauthorized address made the transaction or the wrong phase). 389 | // 390 | // The complaint should be called when some private commitment does 391 | // not match to the public commitment. 392 | // The complainer has to publish the secret key from which its pk 393 | // for encryption is derived. 394 | function complaintPrivateCommit(uint16 complainerIndex, 395 | uint16 accusedIndex, 396 | uint256 complainerSk) 397 | checkAuthorizedSender(complainerIndex) 398 | notInPhase(Phase.EndFail) 399 | notInPhase(Phase.EndSuccess) 400 | public 401 | { 402 | // TODO: a check for edge cases has to be 403 | // done (e.g., when no one has yet committed) 404 | 405 | curPhase = Phase.EndFail; 406 | emit PhaseChange(Phase.EndFail); 407 | 408 | 409 | Participant storage accused = participants[accusedIndex]; 410 | if(!accused.isCommitted) { 411 | slash(complainerIndex); 412 | return; 413 | } 414 | 415 | 416 | if(!ecOps.isEqualPoints(participants[complainerIndex].encPk, ecOps.ecmul(g1, complainerSk))) { 417 | slash(complainerIndex); 418 | return; 419 | } 420 | 421 | uint256 prvCommit = uint256(decrypt(accused.encPk, complainerSk, bytes32(accused.encPrivateCommitments[complainerIndex]))); 422 | 423 | if (isPrvMatchPubCommit(complainerIndex, prvCommit, accused)) { 424 | slash(complainerIndex); 425 | } 426 | else { 427 | slash(accusedIndex); 428 | } 429 | } 430 | 431 | 432 | function isPrvMatchPubCommit(uint16 complainerIndex, uint256 prvCommit, Participant storage accused) 433 | view 434 | internal 435 | returns (bool isMatch) 436 | { 437 | 438 | uint256[2] memory temp; 439 | uint256[2] memory RHS; 440 | uint256[2] memory LHS = ecOps.ecmul(g1, prvCommit); 441 | 442 | 443 | for(uint16 i = 0; i < t+1; i++) { 444 | temp = ecOps.ecmul(accused.publicCommitmentsG1[i], complainerIndex**i); 445 | if(i == 0) { 446 | RHS = temp; 447 | } 448 | else { 449 | RHS = ecOps.ecadd(RHS, temp); 450 | } 451 | } 452 | 453 | return ecOps.isEqualPoints(LHS, RHS); 454 | } 455 | 456 | 457 | // A complaint that the accused participant has committed to a non-G1 458 | // curve point. 459 | function complaintNotInG1(uint16 complainerIndex, uint16 accusedIndex, uint16 coefIndex) 460 | checkAuthorizedSender(complainerIndex) 461 | notInPhase(Phase.EndFail) 462 | notInPhase(Phase.EndSuccess) 463 | public 464 | { 465 | Participant storage accused = participants[accusedIndex]; 466 | if(accused.isCommitted) { 467 | if(!ecOps.isInG1(accused.publicCommitmentsG1[coefIndex])) { 468 | curPhase = Phase.EndFail; 469 | emit PhaseChange(Phase.EndFail); 470 | slash(accusedIndex); 471 | } 472 | } 473 | } 474 | 475 | 476 | // A complaint that the accused participant has committed to a non-G2 477 | // curve point. 478 | function complaintNotInG2(uint16 complainerIndex, uint16 accusedIndex, uint16 coefIndex) 479 | checkAuthorizedSender(complainerIndex) 480 | notInPhase(Phase.EndFail) 481 | notInPhase(Phase.EndSuccess) 482 | public 483 | { 484 | Participant storage accused = participants[accusedIndex]; 485 | if(accused.isCommitted) { 486 | if(!ecOps.isInG2(accused.publicCommitmentsG2[coefIndex])) { 487 | curPhase = Phase.EndFail; 488 | emit PhaseChange(Phase.EndFail); 489 | slash(accusedIndex); 490 | } 491 | } 492 | } 493 | 494 | 495 | // Divides the deposited balance in the contract between 496 | // the enrolled participants except for the participant 497 | // with the slashedIndex. Send slashedIndex = 0 in order 498 | // to divide it between all the participants (no slashing). 499 | function slash(uint16 slashedIndex) private { 500 | 501 | uint16 nParticipants = curN; 502 | uint256 amount; 503 | if (slashedIndex == 0) { 504 | amount = address(this).balance/nParticipants; 505 | } 506 | else { 507 | amount = address(this).balance/(nParticipants-1); 508 | } 509 | 510 | for (uint16 i = 1; i < (nParticipants+1); i++) { 511 | if (i != slashedIndex) { 512 | require(participants[i].ethPk.send(amount)); 513 | } 514 | } 515 | } 516 | 517 | 518 | // Divides the deposited balance in the contract between 519 | // all the committed paricipants. 520 | function slashUncommitted() private { 521 | 522 | uint16 nParticipants = curN; 523 | uint16 committedNum = nParticipants - curNumCommittedLeft; 524 | uint256 amount = address(this).balance/committedNum; 525 | 526 | for (uint16 i = 1; i < (nParticipants+1); i++) { 527 | Participant memory part = participants[i]; 528 | 529 | if (part.isCommitted) { 530 | require(part.ethPk.send(amount)); 531 | } 532 | } 533 | } 534 | 535 | 536 | function decrypt(uint256[2] encrypterPk, uint256 decrypterSk, bytes32 encData) 537 | internal view 538 | returns(bytes32 decryptedData) 539 | { 540 | bytes32 secret = keccak256(abi.encodePacked(ecOps.ecmul(encrypterPk, decrypterSk))); 541 | return encData^secret; 542 | } 543 | 544 | 545 | //////////////////////////////////////////////////////////////////////////// 546 | 547 | function getParticipantPkEnc(uint16 participantIndex) 548 | view 549 | external 550 | returns(uint256[2] encPk) 551 | { 552 | return participants[participantIndex].encPk; 553 | } 554 | 555 | function getParticipantPubCommitG1(uint16 participantIndex, uint16 coefIndex) 556 | view 557 | external 558 | returns(uint256[2] publicCommitmentsG1) 559 | { 560 | return participants[participantIndex].publicCommitmentsG1[coefIndex]; 561 | } 562 | 563 | function getParticipantPubCommitG2(uint16 participantIndex, uint16 coefIndex) 564 | view 565 | external 566 | returns(uint256[4] publicCommitmentsG2) 567 | { 568 | return participants[participantIndex].publicCommitmentsG2[coefIndex]; 569 | } 570 | 571 | function getParticipantPrvCommit(uint16 participantIndex, uint16 committedToIndex) 572 | view 573 | external 574 | returns(uint256 encPrivateCommitments) 575 | { 576 | return participants[participantIndex].encPrivateCommitments[committedToIndex]; 577 | } 578 | 579 | function getParticipantIsCommitted(uint16 participantIndex) 580 | view 581 | external 582 | returns(bool isCommitted) 583 | { 584 | return participants[participantIndex].isCommitted; 585 | } 586 | 587 | 588 | } 589 | 590 | 591 | /** 592 | Test parameters: 593 | 594 | n=2 595 | t=1 596 | 597 | coefficients: 598 | a0) 54379457673493 599 | a1) 23950433293405 600 | 601 | b0) 453845345602931234235 602 | b1) 976507650679506234134 603 | 604 | 605 | public commitments: 606 | a0) 607 | 1368041971066725411361239018179403078339688804905262551154469895335979601856 608 | 1618821492510491564023544834479645350362276877645830361512548330678288690656 609 | a1) 610 | 2631817276443378672842587294280308402376367224288772184477397977654447923479 611 | 10839063031804877909681801875549944362615153185887194276974645822919437293936 612 | 613 | 614 | 615 | 616 | b0) 617 | 13557179362105442634413454166511695479402464592547795407903173557675549038583 618 | 14036788543633373773860064791695546493243519155298095713201690292908488603901 619 | b1) 620 | 1410561832783565967033505993299263011778160659525151177822679323808323926727 621 | 13048336431799703732972170157397576895462246813007390422018149570212241640252 622 | 623 | 624 | 625 | 626 | 627 | sks for decryption: 628 | a)9163450 (0x8bd2ba) 629 | b)197435619 (0xbc4a0e3) 630 | 631 | corresponding pks: 632 | a) 633 | 8010568142108063162131985722241290538226183630284938005062991216917635380726 634 | 19057704389966790842586953303181803223520621589676400762135428513026389437262 635 | b) 636 | 20560911457302142627844632840089789900964525435158247870992611991075785849245 637 | 6050521612570208504883690818928114121824160212045800551636642965355310285042 638 | 639 | private commitments: 640 | fa(2) 641 | 102280324260303 642 | 643 | fb(1) 644 | 1430352996282437468369 645 | 646 | private commitmente encrypted: 647 | fa(2) 648 | 0x492cb4e02f3d22db552acd7d0d37ac3813a17bb0f62bbf314443cb5d4dece465 649 | 650 | fb(1) 651 | 0x492cb4e02f3d22db552acd7d0d37ac3813a17bb0f62bbf7cce6131901dd9c57b 652 | 653 | Group PK: 654 | 5837875810486042432133468170632608166601746087751836698563291230806091472694, 655 | 20890074343725101418521449931290202203738610819454311822513791170653396069990 656 | 657 | ## Join 658 | ["0x11b5d2263b698dd637fb356ea748350b072265cf1acfaf374201f8e99c5bb5f6","0x2a2247476997f4e72285cc8adc57bb0350a105d8f109e523836fc7611d8deb4e"] 659 | ["0x2d75104069619e845ea0f055105e3adb22f07fe1206c093880b9fee9942cb99d","0xd60794fcd581fed59e19e802dcc263a5d53f6a04ddebba96a77745474f700f2"] 660 | 661 | ## Commit 662 | 663 | 1, 664 | ["0x30648c8ef4e8e38d2db668db8a4cab5513343aad935530559090e8a51354fc0", 665 | "0x39438725e6ce47a9b49d4a0b2d90e1cee07d3d7e9a44adb9c0a3cf84078ade0", 666 | "0x5d18e484aeddc886ba162e2fa4bf8bcc125d32230a3fbea6e39ef74de3d6117", 667 | "0x17f6b138a7105622c493ac45d228e9c858544c47227f27a548942c2f01d59970"], 668 | [1,2,3,4,5,6,7,8], 669 | ["0x0000000000000000000000000000000000000000000000000000000000000000", 670 | "0x492cb4e02f3d22db552acd7d0d37ac3813a17bb0f62bbf314443cb5d4dece465"] 671 | 672 | 2, 673 | ["0x1df91772c249f1b2a7e539242ed9eb60e1475f159a376614e91de79c644097f7", 674 | "0x1f088a7004f9c9035af5f4686a5494f576415da8de528c40a67702c5399338fd", 675 | "0x31e598642c78a683eedf66cf7cd4a35a3dd5b5fd8ea947a1c53ab867154fac7", 676 | "0x1cd918c17d9ea92a1a3efb8a999d577d06058a1b205e99769bdc06b6686c8b3c"], 677 | [1,2,3,4,5,6,7,8], 678 | ["0x492cb4e02f3d22db552acd7d0d37ac3813a17bb0f62bbf7cce6131901dd9c57b", 679 | "0x0000000000000000000000000000000000000000000000000000000000000000"] 680 | 681 | 682 | */ -------------------------------------------------------------------------------- /simulation/src/app.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const _ = require('lodash'); 4 | const bgls = require('../src/bglswrapper.js'); 5 | const logger = bgls.logger; 6 | const solc = require('solc'); 7 | const linker = require('solc/linker'); 8 | const argv = require('minimist')(process.argv.slice(2)); 9 | const readlineSync = require('readline-sync'); 10 | const async = require('async'); 11 | 12 | // Default values 13 | let CONTRACT_ADDRESS = '0xF7d58983Dbe1c84E03a789A8A2274118CC29b5da'; 14 | let CLIENT_COUNT; 15 | // const DEPOSIT_WEI = 25000000000000000000; // 25 ether 16 | let DEPOSIT_WEI = 25000000000000000000; // 1e18 * 25 17 | let THRESHOLD = 14; 18 | let OUTPUT_PATH = "../commit_data.json"; 19 | let INTERACTIVE = false; 20 | let COMPLAINER_INDEX = 0; // 1-based 21 | let MALICIOUS_INDEX = 0; // 1-based 22 | let ACCUSED_INDEX = 0; // 1-based 23 | 24 | // Constants 25 | let MIN_BLOCKS_MINED_TILL_COMMIT_IS_APPROVED = 11; 26 | const ECOPS_CONTRACT_NAME = path.join(__dirname, '../../contracts/ecOps.sol'); 27 | const CONTRACT_PATH = path.join(__dirname, '../../contracts/dkg.sol'); 28 | const CONTRACT_NAME = 'dkg'; 29 | const CLIENTS = []; //require('../data/accounts'); 30 | 31 | let dkgContract; 32 | 33 | let dataPerParticipant = []; 34 | const gasUsed = {join: 0, commit: 0, postCommitTimedOut: 0}; 35 | 36 | const enrollBlockToClient = {}; 37 | const commitBlockToClient = {}; 38 | 39 | // Entry point when calling directly with "node" command, not through run.sh. 40 | // Useful for step-by-step debugging 41 | // Example debugging command: 42 | // node --inspect-brk src/app.js -n 22 -t 14 -d 25000000000000000000 -j ${HOME}/dev/orbs/go/src/github.com/orbs-network/bls-bn-curve/commit_data.json 43 | if (require.main === module) { 44 | main(); 45 | } 46 | 47 | // Entry point called by "truffle exec" in run.sh 48 | module.exports = async function (callback) { 49 | 50 | await main(); 51 | callback(); 52 | }; 53 | 54 | // For each client, generate SK and PK 55 | function generateKeys(cb) { 56 | web3.eth.getAccounts((err, res) => { 57 | let accounts = res; 58 | CLIENT_COUNT = accounts.length; 59 | for (let i = 0; i < CLIENT_COUNT; i++) { 60 | const res = bgls.GenerateKeyPair(); 61 | const json = JSON.parse(res); 62 | var clientData = { 63 | address: accounts[i], 64 | sk: json.sk, 65 | pk: json.pk, 66 | gasUsed: 0 67 | }; 68 | CLIENTS.push(clientData); 69 | 70 | logger.info(`i: ${i} SK: ${CLIENTS[i].sk} PK0: ${CLIENTS[i].pk[0]} PK1: ${CLIENTS[i].pk[1]}`); 71 | } 72 | cb(err); 73 | }); 74 | } 75 | 76 | async function main() { 77 | try { 78 | processCommandLineArgs(argv); 79 | logger.info('=====> Starting main flow <====='); 80 | 81 | generateKeys(async (err) => { 82 | await deployManual(); 83 | let timeout = await dkgContract.commitTimeout.call(); 84 | MIN_BLOCKS_MINED_TILL_COMMIT_IS_APPROVED = timeout + 1; 85 | createPhaseChangeListener(); 86 | await enrollAllClients(); 87 | 88 | if (COMPLAINER_INDEX > 0 && MALICIOUS_INDEX > 0 && ACCUSED_INDEX > 0) { 89 | logger.info(`Client ${COMPLAINER_INDEX} is complaining about client ${ACCUSED_INDEX}, and the actual culprit is client ${MALICIOUS_INDEX}`); 90 | dataPerParticipant = getCommitDataWithErrors(COMPLAINER_INDEX, MALICIOUS_INDEX); 91 | fs.writeFileSync(OUTPUT_PATH, JSON.stringify(dataPerParticipant)); 92 | // logger.info(`Data (partially tainted): ${JSON.stringify(dataPerParticipant)}`); 93 | await commitAllClients(dataPerParticipant); 94 | verifyPrivateCommit(COMPLAINER_INDEX, ACCUSED_INDEX); 95 | await sendComplaint(COMPLAINER_INDEX, ACCUSED_INDEX); 96 | } else { 97 | logger.info('No one is complaining so running the contract to completion'); 98 | dataPerParticipant = getCommitData(); 99 | fs.writeFileSync(OUTPUT_PATH, JSON.stringify(dataPerParticipant)); 100 | // logger.debug(`Data: ${JSON.stringify(dataPerParticipant)}`); 101 | await commitAllClients(dataPerParticipant); 102 | await postCommitTimedOut(CLIENTS[0]); 103 | signAndVerify(); 104 | } 105 | }); 106 | } catch (e) { 107 | console.log(e); 108 | process.exit(2); 109 | } 110 | } 111 | 112 | function processCommandLineArgs(myArgs) { 113 | CLIENT_COUNT = myArgs.n; 114 | THRESHOLD = myArgs.t; 115 | DEPOSIT_WEI = myArgs.d; 116 | OUTPUT_PATH = myArgs.j; 117 | if(Number.isInteger(myArgs.c)) 118 | { 119 | COMPLAINER_INDEX = myArgs.c; 120 | MALICIOUS_INDEX = myArgs.m; 121 | ACCUSED_INDEX = myArgs.a; 122 | } 123 | } 124 | 125 | function populateParticipants() { 126 | // TODO impl me 127 | } 128 | 129 | function createPhaseChangeListener() { 130 | const events = dkgContract.PhaseChange(); 131 | events.watch((error, result) => { 132 | const phase = result.args.phase; // enum Phase { Enrollment, Commit, PostCommit, EndSuccess, EndFail } // start from 0 133 | logger.info(`@@@PhaseChange@@@ fired: new phase: ${phase} result: ${JSON.stringify(result)} block: ${result.blockNumber}`); 134 | 135 | switch (phase) { 136 | case 1: 137 | populateParticipants(); 138 | break; 139 | } 140 | 141 | // logger.info(`watch enroll: ${JSON.stringify(result)}`); 142 | }); 143 | } 144 | 145 | 146 | async function deployManual() { 147 | let ecOpsSource = fs.readFileSync(ECOPS_CONTRACT_NAME, 'utf8'); 148 | let dkgSource = fs.readFileSync(CONTRACT_PATH, 'utf8'); 149 | logger.info(`Compiling contract ${CONTRACT_PATH}`); 150 | 151 | const ecOpsSources = { 152 | sources: { 153 | 'ecOps.sol': ecOpsSource 154 | } 155 | }; 156 | 157 | let ecOpsCompiledContract = solc.compile(ecOpsSources, 1); 158 | const ecOpsContractName = "ecOps.sol:ecOps"; 159 | const ecops_contract = ecOpsCompiledContract.contracts[ecOpsContractName]; 160 | let ecOpsAbi = ecops_contract.interface; 161 | let ecOpsByteCode = '0x' + ecops_contract.bytecode; 162 | let ECOPSContract = web3.eth.contract(JSON.parse(ecOpsAbi)); 163 | 164 | try { 165 | await new Promise((resolve, reject) => { 166 | 167 | ECOPSContract.new({ 168 | from: CLIENTS[0].address, 169 | data: ecOpsByteCode, 170 | gas: 1000000000, 171 | }, (err, contractInstance) => { 172 | if (err) { 173 | console.log(`Error returned from compile: ${err} ${JSON.stringify(err)}`); 174 | } 175 | if(!contractInstance) { 176 | return reject(new Error(`Sorry, cannot continue as contract was not deployed.`)); 177 | } 178 | // if (!err) { 179 | // NOTE: The callback will fire twice! 180 | // Once the contract has the transactionHash property set and once its deployed on an address. 181 | 182 | // e.g. check tx hash on the first call (transaction send) 183 | if (!contractInstance.address) { 184 | logger.debug(`First callback call: txHash: ${contractInstance.transactionHash}`); // The hash of the transaction, which deploys the contract 185 | 186 | // check address on the second call (contract deployed) 187 | } else { 188 | logger.debug(`Second callback call: address: ${contractInstance.address}`); // the contract address 189 | ECOPSContract = contractInstance; 190 | resolve(ECOPSContract); 191 | } 192 | }); 193 | }); 194 | } catch (e) { 195 | console.log(); 196 | return Promise.reject(`Caught error: ${e} ${JSON.stringify(e)}`); 197 | } 198 | 199 | const input = { 200 | sources: { 201 | 'dkg.sol': dkgSource, 202 | 'ecOps.sol': ecOpsSource 203 | }, 204 | settings: { 205 | libraries: { 206 | "dkg.sol": { 207 | "ecOps": ECOPSContract.address 208 | } 209 | } 210 | } 211 | }; 212 | 213 | let dkgCompiledContract = solc.compile(input, 1); 214 | // let ecOpsCompiledContract = solc.compile(ecOpsSource, 1); 215 | const dkgContractName = "dkg.sol:dkg"; 216 | // const ecOpsContractName = ":ecOps"; 217 | const dkg_contract = dkgCompiledContract.contracts[dkgContractName]; 218 | // const ecops_contract = ecOpsCompiledContract.contracts[ecOpsContractName]; 219 | // console.log(JSON.stringify(ecOpsCompiledContract.contracts)); 220 | let dkgAbi = dkg_contract.interface; 221 | // let ecOpsAbi = ecops_contract.interface; 222 | 223 | let dkgByteCode = linker.linkBytecode(dkg_contract.bytecode, {"ecOps.sol:ecOps": ECOPSContract.address}); 224 | 225 | dkgByteCode = '0x' + dkgByteCode; 226 | // let ecOpsByteCode = ecops_contract.bytecode; 227 | // let gasEstimate = web3.eth.estimateGas({data: dkgByteCode}); 228 | // console.log("gasEstimate: " + gasEstimate); 229 | 230 | let DKGContract = web3.eth.contract(JSON.parse(dkgAbi)); 231 | // let ECOPSContract = web3.eth.contract(JSON.parse(ecOpsAbi)); 232 | logger.info(`Deploying contract ${CONTRACT_NAME} Params: t: ${THRESHOLD} n: ${CLIENT_COUNT} deposit_wei: ${DEPOSIT_WEI}`); 233 | 234 | // dkgByteCode = linker.linkBytecode(dkgByteCode, { 'ecOps': ecOpsByteCode}); 235 | 236 | try { 237 | await new Promise((resolve, reject) => { 238 | 239 | DKGContract.new(THRESHOLD, CLIENT_COUNT, DEPOSIT_WEI, { 240 | from: CLIENTS[0].address, 241 | data: dkgByteCode, 242 | gas: 1000000000, 243 | }, (err, contractInstance) => { 244 | if (err) { 245 | console.log(`Error returned from compile: ${err} ${JSON.stringify(err)}`); 246 | } 247 | if(!contractInstance) { 248 | return reject(new Error(`Sorry, cannot continue as contract was not deployed.`)); 249 | } 250 | // if (!err) { 251 | // NOTE: The callback will fire twice! 252 | // Once the contract has the transactionHash property set and once its deployed on an address. 253 | 254 | // e.g. check tx hash on the first call (transaction send) 255 | if (!contractInstance.address) { 256 | logger.debug(`First callback call: txHash: ${contractInstance.transactionHash}`); // The hash of the transaction, which deploys the contract 257 | 258 | // check address on the second call (contract deployed) 259 | } else { 260 | logger.debug(`Second callback call: address: ${contractInstance.address}`); // the contract address 261 | dkgContract = contractInstance; 262 | resolve(dkgContract); 263 | } 264 | }); 265 | }); 266 | } catch (e) { 267 | console.log(); 268 | return Promise.reject(`Caught error: ${e} ${JSON.stringify(e)}`); 269 | } 270 | 271 | logger.info(`Deployed DKG contract on address ${dkgContract.address}, txHash: ${dkgContract.transactionHash}`); 272 | logger.info(`----------------------------------------------------`); 273 | CONTRACT_ADDRESS = dkgContract.address; 274 | await printValuesFromContract(); 275 | 276 | } 277 | 278 | async function enrollAllClients() { 279 | logger.info('=====> Starting enroll phase <====='); 280 | let i = 0; 281 | 282 | // Send the transactions 283 | for (let i = 0; i < CLIENT_COUNT; i++) { 284 | if (i < 2) { 285 | pause(); 286 | } 287 | await enroll(CLIENTS[i], i); 288 | } 289 | 290 | logger.info(`***** Total gas used for enrollment: ${gasUsed.join} *****`); 291 | const balanceWei = web3.eth.getBalance(dkgContract.address); 292 | logger.info(`Contract balance: ${balanceWei} wei.`); 293 | 294 | 295 | console.log(''); 296 | pause(); 297 | } 298 | 299 | 300 | async function enroll(client, i) { 301 | logger.info(`Sending transaction to the contract's join() method with client address: ${toShortHex(client.address)} i: ${i}`); 302 | 303 | 304 | await new Promise((resolve, reject) => { 305 | 306 | const events = dkgContract.ParticipantJoined({address: client.address}); 307 | logger.info(`Start watching @ParticipantJoined@: ${client.address}`); 308 | const clientAddr = client.address; 309 | events.watch((error, result) => { 310 | events.stopWatching(); 311 | logger.info(`@ParticipantJoined@ fired: client: ${clientAddr} ID: ${result.args.index} block: ${result.blockNumber}`); 312 | // logger.info(`watch enroll: ${JSON.stringify(result)}`); 313 | const currentClient = enrollBlockToClient[result.blockHash]; 314 | if (currentClient) { 315 | // logger.info(`Get txHash ${result.transactionHash} --> clientAddress ${client.address} --> ${result.args.index}`); 316 | currentClient.id = result.args.index; 317 | logger.info(`@ParticipantJoined@ Set ID ${currentClient.id} to client ${clientAddr} blockNumber ${result.blockNumber}`); 318 | resolve(result); 319 | } else { 320 | logger.info(`!!! @ParticipantJoined@ Client not found for blockHash ${result.blockHash}`); 321 | reject(result.blockHash); 322 | } 323 | }); 324 | 325 | const pk0 = web3.toBigNumber(client.pk[0]); 326 | const pk1 = web3.toBigNumber(client.pk[1]); 327 | 328 | logger.info(`join() params: pk0: ${client.pk[0]} pk1: ${client.pk[1]}`); 329 | dkgContract.join([pk0, pk1], // bigint[2] 330 | { 331 | from: client.address, 332 | value: DEPOSIT_WEI, 333 | gas: 3000000, 334 | }, (err, result) => { 335 | if (err) { 336 | reject(err); 337 | } else { 338 | const receipt = web3.eth.getTransactionReceipt(result); 339 | // logger.info(`Join result: client: ${client.address} ${JSON.stringify(result)}`); 340 | logger.info(`Join result: client: ${client.address} blockNumber: ${receipt.blockNumber} blockHash: ${receipt.blockHash}`); 341 | // logger.info(`Join receipt: result: ${result} blockNumber: ${receipt.blockNumber} blockHash: ${receipt.blockHash}`); 342 | gasUsed.join += receipt.gasUsed; 343 | client.gasUsed += receipt.gasUsed; 344 | enrollBlockToClient[receipt.blockHash] = client; 345 | 346 | } 347 | }); 348 | }); 349 | } 350 | 351 | async function commitAllClients(data) { 352 | logger.info(` =====> Starting commit phase <=====`); 353 | // const {CoefficientsAll, PubCommitG1All, PubCommitG2All, PrvCommitAll} = json; 354 | logger.info("Notice the difference in gas costs between join() and commit()"); 355 | 356 | for (let i = 0; i < CLIENT_COUNT; i++) { 357 | if (i < 2) { 358 | pause(); 359 | } 360 | await commit(CLIENTS[i], i, data[i].Coefficients, data[i].PubCommitG1, data[i].PubCommitG2, data[i].PrvCommit); 361 | const isCommitted = await dkgContract.getParticipantIsCommitted(CLIENTS[i].id); 362 | const curN = await dkgContract.curN.call(); 363 | const curPhase = await dkgContract.curPhase.call(); 364 | logger.info(`isCommitted(${CLIENTS[i].id}): ${isCommitted} curN: ${curN} curPhase: ${curPhase}`); 365 | } 366 | logger.info(`***** Total gas used for commit(): ${gasUsed.commit} *****`); 367 | const balanceWei = web3.eth.getBalance(dkgContract.address); 368 | logger.info(`Contract balance: ${balanceWei} wei`); 369 | console.log(''); 370 | pause(); 371 | } 372 | 373 | 374 | async function commit(client, i, coeffs, commitG1, commitG2, commitPrv) { 375 | 376 | if (!client.id) { 377 | throw new Error(`Missing client ID for client #${i} ${client.address}. Client ID is the result of join(). Did join() finished correctly?`); 378 | } 379 | 380 | const prv = commitPrv.map(numstr => web3.toHex(numstr)); 381 | // 382 | // logger.info(`===> Commit(Index: ${client.id}) <===`); 383 | logger.debug(`G1: ${JSON.stringify(commitG1)}`); 384 | logger.debug(`G2: ${JSON.stringify(commitG2)}`); 385 | logger.debug(`Prv: ${JSON.stringify(prv)}`); 386 | 387 | await new Promise((resolve, reject) => { 388 | 389 | const events = dkgContract.NewCommit({address: client.address}); 390 | logger.info(`Start watching @NewCommit@: ${client.address}`); 391 | const clientAddr = client.address; 392 | events.watch((error, result) => { 393 | events.stopWatching(); 394 | logger.info(`@NewCommit@ fired: client: ${clientAddr}`); 395 | const c = commitBlockToClient[result.blockHash]; 396 | if (c) { 397 | logger.debug(`@NewCommit@ Result: ${JSON.stringify(result)}`); 398 | // logger.info(`@NewCommit@ Client ID ${client.id} blockHash: ${result.blockHash}`); 399 | // committed++; 400 | logger.info(`@NewCommit@ Client ID #${c.id} ${c.address} committed successfully.`); 401 | c.committed = true; 402 | resolve(c.id); 403 | } else { 404 | logger.info(`@NewCommit@ Client not found for blockHash: ${result.blockHash}`); 405 | reject(result.blockHash); 406 | } 407 | }); 408 | 409 | dkgContract.commit(client.id, commitG1, commitG2, prv, { 410 | from: client.address, 411 | gas: 3000000 412 | }, (err, result) => { 413 | if (err) { 414 | reject(err); 415 | } else { 416 | // console.log("commit result: ", JSON.stringify(result)); 417 | const receipt = web3.eth.getTransactionReceipt(result); 418 | commitBlockToClient[receipt.blockHash] = client; 419 | // console.log(`Commit receipt: ${JSON.stringify(receipt)}`); 420 | gasUsed.commit += receipt.gasUsed; 421 | client.gasUsed += receipt.gasUsed; 422 | logger.info(`Client ID #${client.id} of ${CLIENT_COUNT} *** Gas used: ${receipt.gasUsed}. *** Block ${receipt.blockNumber}`); 423 | logger.debug(`Commit(): Client ID #${client.id} ${client.address} committed successfully. Result: ${JSON.stringify(receipt)}`); 424 | } 425 | }); 426 | }); 427 | 428 | } 429 | 430 | function verifyPrivateCommit(complainerIndex, accusedIndex) { 431 | 432 | logger.info(`verifyPrivateCommit(): Now client ID #${complainerIndex} (complainer) is verifying the private commitment of client ID #${accusedIndex} (accused)`); 433 | logger.info(`The private commitment of client ID #${accusedIndex} was intentionally tainted.`); 434 | 435 | 436 | const verifyResult = bgls.VerifyPrivateCommitment(complainerIndex, accusedIndex, OUTPUT_PATH); 437 | logger.info(`Verification passed? ${verifyResult}`); 438 | return verifyResult; 439 | } 440 | 441 | 442 | function getCommitData() { 443 | 444 | const data = bgls.GetCommitDataForAllParticipants(THRESHOLD, CLIENTS, CLIENT_COUNT); 445 | // const data = require(OUTPUT_PATH); 446 | // printDataPerClient(allCommitDataJson); 447 | logger.info('Finished generating commitments data.'); 448 | for (let i = 0; i < CLIENT_COUNT; i++) { 449 | data[i].PK = CLIENTS[i].pk; 450 | data[i].SK = CLIENTS[i].sk; 451 | } 452 | 453 | // pause(); 454 | return data; 455 | } 456 | 457 | function getCommitDataWithErrors(complainerIndex, maliciousIndex) { 458 | 459 | const data = getCommitData(); 460 | 461 | // Actual data is 0-based so -1 the input values which are 1-based 462 | data[maliciousIndex - 1].PrvCommitEnc[complainerIndex - 1] = "0x00000000000000001234567812345678123456781234567812345678FFFFFFFF"; // Taint the data 463 | logger.info(`Tainted private commitment of maliciousIndex=${maliciousIndex} to complainerIndex=${complainerIndex}.`); 464 | 465 | return data; 466 | 467 | } 468 | 469 | async function sendComplaint(complainerIndex, accusedIndex) { 470 | 471 | pause(); 472 | const complainerSK = CLIENTS[complainerIndex - 1].sk; 473 | const complainerID = CLIENTS[complainerIndex - 1].id; 474 | const complainerAddress = CLIENTS[complainerIndex - 1].address; 475 | const accusedID = CLIENTS[accusedIndex - 1].id; 476 | const curPhase = await dkgContract.curPhase.call(); 477 | const accusedEncPk = await dkgContract.getParticipantPkEnc.call(accusedID); 478 | const encPrvCommit = await dkgContract.getParticipantPrvCommit.call(accusedID, complainerID); 479 | const pubCommitG1_0 = await dkgContract.getParticipantPubCommitG1.call(accusedID, 0); 480 | // const pubCommitG1_1 = await dkgContract.getParticipantPubCommitG1.call(accusedID, 1); 481 | const pubCommitG1_t = await dkgContract.getParticipantPubCommitG1.call(accusedID, THRESHOLD); 482 | 483 | // const g0_res = await dkgContract.ecmul.call(pubCommitG1_0, 2); 484 | // const g1_res = await dkgContract.ecmul.call(pubCommitG1_1, 2); 485 | // const gt_res = await dkgContract.ecmul.call(pubCommitG1_t, 2); 486 | 487 | 488 | logger.info(`sendComplaint(): Now client ID #${complainerID} THRESHOLD=${THRESHOLD} (addr: ${complainerAddress}) is sending a complaint on client ID #${accusedID}. Phase: ${curPhase} SK: ${complainerSK}`); 489 | logger.debug(`sendComplaint(): pubCommitG1_0=${pubCommitG1_0[0].toNumber()},${pubCommitG1_0[1].toNumber()} pubCommitG1_t=${pubCommitG1_t[0].toNumber()},${pubCommitG1_t[1].toNumber()}`); 490 | logger.debug(`sendComplaint(): decrypt(accusedEncPk=${accusedEncPk},complainerSk=${complainerSK},encPrvCommit=${encPrvCommit}`); 491 | // logger.debug(`sendComplaint(): g0_res=${g0_res} g1_res=${g1_res} gt_res=${gt_res}`); 492 | // logger.info("==========="); 493 | // logger.info(await dkgContract.getParticipantPkEnc.call(complainerID)); 494 | // const decryptRes = await dkgContract.decrypt.call(accusedEncPk, complainerSK, encPrvCommit); 495 | // logger.debug(`decrypt() res=${decryptRes}`); 496 | 497 | let res = null; 498 | try { 499 | res = await dkgContract.complaintPrivateCommit(complainerID, accusedID, complainerSK, { 500 | from: complainerAddress, 501 | gas: 3000000 502 | }); 503 | } finally { 504 | logger.info(`sendComplaint(): res: ${JSON.stringify(res)}`); 505 | } 506 | 507 | logger.info(`Complaint sent. If the complaint was justified, the deposit of the accused client was split between the other clients, who also had their deposits returned.`); 508 | logger.info(`If the complaint was not justified, the deposit of the complaining client was split between the other clients, who also had their deposits returned.`); 509 | logger.info(`In either case, the contract is closed.`); 510 | 511 | 512 | } 513 | 514 | 515 | function signAndVerify() { 516 | pause(); 517 | bgls.SignAndVerify(THRESHOLD, CLIENT_COUNT, OUTPUT_PATH); 518 | } 519 | 520 | async function postCommitTimedOut(client) { 521 | 522 | // Separate to execution cost (function of opcodes) and transaction cost (execution cost + fixed cost per tx) 523 | 524 | logger.info(`We will now mine ${MIN_BLOCKS_MINED_TILL_COMMIT_IS_APPROVED} blocks to simulate that no one complained for some time after all commits were executed, therefore it is safe to finalize the commit() phase`); 525 | 526 | pause(); 527 | await mineNBlocks(MIN_BLOCKS_MINED_TILL_COMMIT_IS_APPROVED); 528 | logger.info(`No one complained, so calling postCommitTimedOut() to finalize commit phase. `); 529 | logger.info(`Take note of the present balance of accounts and compare to after calling postCommitTimedOut().`); 530 | pause(); 531 | const res = await new Promise((resolve, reject) => { 532 | dkgContract.postCommitTimedOut({ 533 | from: client.address, 534 | gas: 300000 535 | }, (err, result) => { 536 | if (err) { 537 | reject(err); 538 | } else { 539 | console.log("postCommitTimedOut result: ", JSON.stringify(result)); 540 | const receipt = web3.eth.getTransactionReceipt(result); 541 | // console.log(`Commit receipt: ${JSON.stringify(receipt)}`); 542 | gasUsed.postCommitTimedOut += receipt.gasUsed; 543 | client.gasUsed += receipt.gasUsed; 544 | logger.info(`postCommitTimedOut(): finished successfully. *** Gas used: ${receipt.gasUsed}. *** Block ${receipt.blockNumber}`); 545 | logger.debug(`Commit(): Client ID #${client.id} ${client.address} committed successfully. Result: ${JSON.stringify(receipt)}`); 546 | const balanceWei = web3.eth.getBalance(dkgContract.address); 547 | logger.info(`Contract balance: ${balanceWei} wei`); 548 | logger.info('Now take note again of accounts balance, now that deposits have been refunded.'); 549 | console.log(''); 550 | logger.info(`***** Total gas used: ${getTotalGasUsed()} *****`); 551 | console.log(''); 552 | 553 | for (let i = 0; i < CLIENT_COUNT; i++) { 554 | logger.info(`Total gas used by client ${CLIENTS[i].id}: ${CLIENTS[i].gasUsed}`); 555 | } 556 | resolve(result); 557 | } 558 | }); 559 | }); 560 | } 561 | 562 | function getTotalGasUsed() { 563 | return gasUsed.join + gasUsed.commit + gasUsed.postCommitTimedOut; 564 | } 565 | 566 | const mineOneBlock = async () => { 567 | 568 | // TODO replace this print with just the block number 569 | 570 | // logger.info(JSON.stringify(web3.eth)); 571 | 572 | await web3.currentProvider.send({ 573 | jsonrpc: '2.0', 574 | method: 'evm_mine', 575 | params: [], 576 | id: 200, 577 | }); 578 | }; 579 | 580 | const mineNBlocks = async n => { 581 | forceMineBlocks(n, err => console.log(`err: ${err} ${JSON.stringify(err)}`)) 582 | // for (let i = 0; i < n; i++) { 583 | // await mineOneBlock() 584 | // } 585 | }; 586 | 587 | function forceMineBlocks(numOfBlockToMine, cb) { 588 | const mineArr = []; 589 | for (let i = 0; i < numOfBlockToMine; i++) { 590 | mineArr.push(async.apply(web3.currentProvider.sendAsync, { 591 | jsonrpc: "2.0", 592 | method: "evm_mine", 593 | id: 12345 594 | })); 595 | } 596 | 597 | 598 | async.parallel(mineArr, (err) => { 599 | cb(err); 600 | }); 601 | } 602 | 603 | async function printValuesFromContract() { 604 | const valuesFromContract = {}; 605 | valuesFromContract.n = await dkgContract.n.call(); 606 | valuesFromContract.t = await dkgContract.t.call(); 607 | // valuesFromContract.p = await dkgContract.ecOps.p.call(); 608 | // valuesFromContract.q = await dkgContract.ecOps.q.call(); 609 | 610 | logger.info("Contract properties:"); 611 | logger.info(` > n: ${valuesFromContract.n.toString()}`); 612 | logger.info(` > t: ${valuesFromContract.t.toString()}`); 613 | // logger.info(` > p: ${valuesFromContract.p.toString()}`); 614 | // logger.info(` > q: ${valuesFromContract.q.toString()}`); 615 | } 616 | 617 | function toShortHex(hexStr) { 618 | return hexStr.substr(0, 6) + ".." + hexStr.substr(hexStr.length - 4); 619 | } 620 | 621 | function printDataPerClient(data) { 622 | 623 | // TODO Fix text and contents here 624 | 625 | CLIENTS.forEach((client, i) => { 626 | pause(); 627 | console.log(''); 628 | logger.info(`===> Data for client ID #${client.id} ${client.address} <===`); 629 | logger.info(`===================================================`); 630 | for (let j = 0; j < data.CoefficientsAll[i].length; j++) { 631 | logger.info(`Client ID #${i + 1}: Coefficient ${j}: ${data.CoefficientsAll[i][j]}`); 632 | } 633 | for (let j = 0; j < data.PubCommitG1All[i].length; j++) { 634 | logger.info(`Client ID #${i + 1}: Commitment on G1 for coefficient ${j}: ${data.PubCommitG1All[i][j]}`); 635 | } 636 | 637 | for (let j = 0; j < data.PubCommitG2All[i].length; j++) { 638 | logger.info(`Client ID #${i + 1}: Commitment on G2 for coefficient ${j}: ${data.PubCommitG2All[i][j]}`); 639 | } 640 | 641 | for (let j = 0; j < data.PrvCommitAll[i].length; j++) { 642 | logger.info(`Client ID #${i + 1}: f_${i + 1}(${j + 1}) = ${toShortHex(data.PrvCommitAll[i][j])}`); 643 | } 644 | 645 | }); 646 | 647 | 648 | logger.info("") 649 | } 650 | 651 | function pause() { 652 | if (INTERACTIVE) { 653 | readlineSync.keyInPause(); 654 | } 655 | } 656 | 657 | --------------------------------------------------------------------------------