├── smart-contracts ├── packages │ ├── smart-contracts │ │ ├── migrations │ │ │ └── .gitkeep │ │ ├── .soliumignore │ │ ├── .gitignore │ │ ├── etc │ │ │ └── filter_json.sh │ │ ├── .DS_Store │ │ ├── .solhint.json │ │ ├── .soliumrc.json │ │ ├── index.js │ │ ├── package.json │ │ ├── build │ │ │ └── contracts │ │ │ │ └── Ownable.json │ │ └── contracts │ │ │ ├── Ownable.sol │ │ │ ├── LockableTransactionAuthorizer.sol │ │ │ └── VotersRegistry.sol │ ├── crypto-lib │ │ ├── .gitignore │ │ ├── .DS_Store │ │ ├── index.js │ │ ├── package.json │ │ └── src │ │ │ ├── utils.js │ │ │ ├── elGamalPartitioner.js │ │ │ └── elGamal.js │ ├── .DS_Store │ ├── voting-lib │ │ ├── .DS_Store │ │ ├── src │ │ │ ├── .DS_Store │ │ │ ├── providers.js │ │ │ ├── contracts │ │ │ │ ├── index.js │ │ │ │ ├── ownable.js │ │ │ │ ├── lockableTransactionAuthorizer.js │ │ │ │ └── votersRegistry.js │ │ │ ├── accounts.js │ │ │ └── helpers.js │ │ ├── index.js │ │ └── package.json │ └── common-deps │ │ └── package.json ├── .DS_Store ├── package.json ├── lerna.json └── readme.md ├── voting-form └── src │ ├── forms │ ├── forms │ │ └── mgik │ │ │ ├── mgd-view │ │ │ ├── manualTxt.tpl │ │ │ ├── error_exception.tpl │ │ │ ├── error_user.tpl │ │ │ └── show.tpl │ │ │ ├── mgd-golosovanie │ │ │ ├── manualTxt.tpl │ │ │ ├── error_exception.tpl │ │ │ ├── error_user.tpl │ │ │ └── templates.tpl │ │ │ └── mgd2019 │ │ │ ├── manualTxt.tpl │ │ │ ├── send.tpl │ │ │ ├── templates.tpl │ │ │ ├── error_exception.tpl │ │ │ ├── show_revocation.tpl │ │ │ ├── error_profile.tpl │ │ │ ├── template_browsers.tpl │ │ │ ├── std_person_document.tpl │ │ │ ├── std_fias.tpl │ │ │ └── show.tpl │ └── common │ │ ├── htdocs │ │ └── forms │ │ │ ├── img │ │ │ └── forms │ │ │ │ └── mgik │ │ │ │ ├── map.png │ │ │ │ ├── mgd_view.jpg │ │ │ │ ├── deputies │ │ │ │ ├── 01_01.jpg │ │ │ │ ├── 01_02.jpg │ │ │ │ ├── 01_03.jpg │ │ │ │ ├── 01_04.jpg │ │ │ │ ├── 01_05.jpg │ │ │ │ ├── 10_01.jpg │ │ │ │ ├── 10_02.jpg │ │ │ │ ├── 10_03.jpg │ │ │ │ ├── 10_04.jpg │ │ │ │ ├── 10_05.jpg │ │ │ │ ├── 30_01.jpg │ │ │ │ ├── 30_02.jpg │ │ │ │ ├── 30_03.jpg │ │ │ │ ├── 30_04.jpg │ │ │ │ ├── 30_05.jpg │ │ │ │ └── 30_06.jpg │ │ │ │ ├── mgd2019 │ │ │ │ └── remote_voting.jpg │ │ │ │ └── mgd-golosovanie │ │ │ │ ├── elections.png │ │ │ │ └── browser_icons.png │ │ │ ├── mgik │ │ │ ├── bulletin_example_1.pdf │ │ │ ├── bulletin_example_10.pdf │ │ │ └── bulletin_example_30.pdf │ │ │ └── js_v3 │ │ │ └── mgik │ │ │ ├── mgd-golosovanie.js │ │ │ ├── mgd-view.js │ │ │ ├── mgd2019.js │ │ │ ├── landing.js │ │ │ └── check.browser.js │ │ └── data │ │ ├── lib │ │ └── Form │ │ │ └── mgik │ │ │ ├── mgd │ │ │ ├── LogicException.php │ │ │ ├── TaskRevocationRequest.php │ │ │ ├── MgdAjaxHandler.php │ │ │ ├── MgdAjaxService.php │ │ │ ├── ReceiveStatusWorker.php │ │ │ ├── TaskRequest.php │ │ │ └── TaskRegistrationRequest.php │ │ │ ├── mgik.class.php │ │ │ ├── mgd2019.class.php │ │ │ ├── mgd-view.class.php │ │ │ └── mgd-golosovanie.class.php │ │ └── config │ │ └── Mgik.php │ ├── ballot │ ├── common │ │ ├── module_tpl │ │ │ └── election │ │ │ │ └── default │ │ │ │ ├── error.tpl │ │ │ │ ├── success.tpl │ │ │ │ ├── _header.tpl │ │ │ │ └── show.tpl │ │ └── module │ │ │ └── election │ │ │ ├── LogicException.php │ │ │ ├── TaskVoteRequest.php │ │ │ ├── AjaxService.php │ │ │ ├── update_dit_voting_settings_cron_task.php │ │ │ ├── Settings.php │ │ │ ├── TaskRequest.php │ │ │ ├── UpdateDitVotingSettingsCommand.php │ │ │ └── m_election.php │ ├── config │ │ └── Mgik.php │ └── static │ │ └── js │ │ └── forms │ │ ├── mgik │ │ ├── LeavingPageCheckerInit.js │ │ └── LeavingPageChecker.js │ │ └── main.min.js │ └── crypt │ └── registr │ ├── baseWsInclude.php │ └── v1 │ └── Oauth.php ├── Описание методов наблюдателя БЧ.pdf ├── encryption-keys ├── keys │ ├── private-key.json │ └── public-key.json ├── readme.md └── data │ ├── original-datums.csv │ └── encrypted-datums.csv └── README.md /smart-contracts/packages/smart-contracts/migrations/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /smart-contracts/packages/smart-contracts/.soliumignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /smart-contracts/packages/crypto-lib/.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | .nyc_output/ 3 | -------------------------------------------------------------------------------- /smart-contracts/packages/smart-contracts/.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | coverage.json 3 | coverageEnv 4 | -------------------------------------------------------------------------------- /smart-contracts/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/smart-contracts/.DS_Store -------------------------------------------------------------------------------- /voting-form/src/forms/forms/mgik/mgd-view/manualTxt.tpl: -------------------------------------------------------------------------------- 1 |

Проверка голоса на выборах депутатов Московской городской Думы седьмого созыва

-------------------------------------------------------------------------------- /smart-contracts/packages/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/smart-contracts/packages/.DS_Store -------------------------------------------------------------------------------- /Описание методов наблюдателя БЧ.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/Описание методов наблюдателя БЧ.pdf -------------------------------------------------------------------------------- /smart-contracts/packages/crypto-lib/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/smart-contracts/packages/crypto-lib/.DS_Store -------------------------------------------------------------------------------- /smart-contracts/packages/voting-lib/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/smart-contracts/packages/voting-lib/.DS_Store -------------------------------------------------------------------------------- /smart-contracts/packages/smart-contracts/etc/filter_json.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | jq '{abi: .abi, bytecode: .bytecode}' "$1" > tmpfile && mv tmpfile "$1" 6 | -------------------------------------------------------------------------------- /voting-form/src/forms/forms/mgik/mgd-golosovanie/manualTxt.tpl: -------------------------------------------------------------------------------- 1 |

Дистанционное электронное голосование на выборах депутатов Московской городской Думы седьмого созыва

-------------------------------------------------------------------------------- /smart-contracts/packages/smart-contracts/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/smart-contracts/packages/smart-contracts/.DS_Store -------------------------------------------------------------------------------- /smart-contracts/packages/voting-lib/src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/smart-contracts/packages/voting-lib/src/.DS_Store -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/map.png -------------------------------------------------------------------------------- /voting-form/src/forms/forms/mgik/mgd2019/manualTxt.tpl: -------------------------------------------------------------------------------- 1 |

Включение в список избирателей для электронного дистанционного голосования на выборах депутатов Московской городской Думы седьмого созыва

2 | -------------------------------------------------------------------------------- /smart-contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "devDependencies": { 5 | "lerna": "3.13.4" 6 | }, 7 | "workspaces": [ 8 | "packages/*" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/mgd_view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/mgd_view.jpg -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/mgik/bulletin_example_1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/mgik/bulletin_example_1.pdf -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/mgik/bulletin_example_10.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/mgik/bulletin_example_10.pdf -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/mgik/bulletin_example_30.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/mgik/bulletin_example_30.pdf -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/01_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/01_01.jpg -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/01_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/01_02.jpg -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/01_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/01_03.jpg -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/01_04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/01_04.jpg -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/01_05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/01_05.jpg -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/10_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/10_01.jpg -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/10_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/10_02.jpg -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/10_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/10_03.jpg -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/10_04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/10_04.jpg -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/10_05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/10_05.jpg -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/30_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/30_01.jpg -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/30_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/30_02.jpg -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/30_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/30_03.jpg -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/30_04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/30_04.jpg -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/30_05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/30_05.jpg -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/30_06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/deputies/30_06.jpg -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/mgd2019/remote_voting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/mgd2019/remote_voting.jpg -------------------------------------------------------------------------------- /smart-contracts/packages/smart-contracts/.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:default", 3 | "plugins": [], 4 | "rules": { 5 | "avoid-throw": false, 6 | "avoid-suicide": "error", 7 | "avoid-sha3": "warn" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/mgd-golosovanie/elections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/mgd-golosovanie/elections.png -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/img/forms/mgik/mgd-golosovanie/browser_icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moscow-technologies/blockchain-voting_2019/HEAD/voting-form/src/forms/common/htdocs/forms/img/forms/mgik/mgd-golosovanie/browser_icons.png -------------------------------------------------------------------------------- /smart-contracts/lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": { 3 | "init": { 4 | "exact": true 5 | } 6 | }, 7 | "npmClient": "yarn", 8 | "packages": [ 9 | "packages/*" 10 | ], 11 | "useWorkspaces": true, 12 | "version": "0.0.0" 13 | } 14 | -------------------------------------------------------------------------------- /smart-contracts/packages/crypto-lib/index.js: -------------------------------------------------------------------------------- 1 | const ElGamal = require('./src/elGamal'); 2 | const ElGamalPartitioner = require('./src/elGamalPartitioner'); 3 | const utils = require('./src/utils'); 4 | 5 | module.exports = { 6 | ElGamal, 7 | ElGamalPartitioner, 8 | utils, 9 | }; 10 | -------------------------------------------------------------------------------- /voting-form/src/forms/forms/mgik/mgd2019/send.tpl: -------------------------------------------------------------------------------- 1 | {extends file="$base_template_path/send.tpl"} 2 | {block name="add_content"} 3 | 4 | 5 | 6 | {/block} -------------------------------------------------------------------------------- /smart-contracts/packages/smart-contracts/.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solium:all", 3 | "plugins": ["security"], 4 | "rules": { 5 | "quotes": ["error", "double"], 6 | "indentation": ["error", 4], 7 | "linebreak-style": ["error", "unix"], 8 | "max-len": ["off"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /encryption-keys/keys/private-key.json: -------------------------------------------------------------------------------- 1 | { 2 | "privateKeys": [ 3 | "3100148267073415041020358795635949266658955872724328721648384134941615159536", 4 | "29241408289934333462681359776043110921654408715862349961870126787759319354207", 5 | "6789992603951884978848988061326488516017142690826312380266968519080696552347" 6 | ] 7 | } -------------------------------------------------------------------------------- /smart-contracts/packages/voting-lib/index.js: -------------------------------------------------------------------------------- 1 | const accounts = require('./src/accounts'); 2 | const providers = require('./src/providers'); 3 | const contracts = require('./src/contracts'); 4 | const helpers = require('./src/helpers'); 5 | 6 | module.exports = { 7 | accounts, 8 | providers, 9 | contracts, 10 | helpers, 11 | }; 12 | -------------------------------------------------------------------------------- /smart-contracts/packages/voting-lib/src/providers.js: -------------------------------------------------------------------------------- 1 | const { 2 | providers: { JsonRpcProvider, IpcProvider }, 3 | } = require('ethers'); 4 | 5 | const newIpcProvider = path => new IpcProvider(path); 6 | 7 | const newJsonRpcProvider = path => new JsonRpcProvider(path); 8 | 9 | module.exports = { 10 | newIpcProvider, 11 | newJsonRpcProvider, 12 | }; 13 | -------------------------------------------------------------------------------- /voting-form/src/ballot/common/module_tpl/election/default/error.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | {include file="$template_path/_header.tpl"} 4 | 5 | 6 |
7 |

Голос не может быть подан.

8 |

{$error_message}

9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /smart-contracts/packages/voting-lib/src/contracts/index.js: -------------------------------------------------------------------------------- 1 | const LockableTransactionAuthorizer = require('./lockableTransactionAuthorizer'); 2 | 3 | const VotersRegistry = require('./votersRegistry'); 4 | const BallotsRegistry = require('./ballotsRegistry'); 5 | 6 | const Ownable = require('./ownable'); 7 | 8 | module.exports = { 9 | LockableTransactionAuthorizer, 10 | 11 | VotersRegistry, 12 | BallotsRegistry, 13 | 14 | Ownable, 15 | }; 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blockchain-voting 2 | В данном репозитории для ознакомления представлен код системы электронного голосования, который будет использоваться на выборах в Московскую городскую Думу. 3 | 4 | ## Цели и задачи репозитория 5 | Репозиторий создан для ознакомления с исходным кодом системы. 6 | Несмотря на то, что направленность системы - электронные выборы, просим оставлять комментарии и открывать проблемы по техническоим вопросам. Все комментарии относящиеся к политическим высказываниям, будут удаляться. 7 | -------------------------------------------------------------------------------- /smart-contracts/packages/smart-contracts/index.js: -------------------------------------------------------------------------------- 1 | const LockableTransactionAuthorizer = require('./build/contracts/LockableTransactionAuthorizer.json'); 2 | 3 | const VotersRegistry = require('./build/contracts/VotersRegistry.json'); 4 | const BallotsRegistry = require('./build/contracts/BallotsRegistry.json'); 5 | 6 | const Ownable = require('./build/contracts/Ownable.json'); 7 | 8 | module.exports = { 9 | LockableTransactionAuthorizer, 10 | 11 | VotersRegistry, 12 | BallotsRegistry, 13 | 14 | Ownable, 15 | }; 16 | -------------------------------------------------------------------------------- /smart-contracts/packages/voting-lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voting-lib", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "DIT Voting Smart-Contracts wrapper classes", 6 | "main": "index.js", 7 | "scripts": { 8 | "lint": "eslint src/ samples/ *.js" 9 | }, 10 | "dependencies": { 11 | "ethers": "4.0.27", 12 | "smart-contracts": "0.0.0", 13 | "crypto-lib": "0.0.0", 14 | "jsbn": "1.1.0" 15 | }, 16 | "devDependencies": { 17 | "common-deps": "0.0.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /voting-form/src/forms/common/data/lib/Form/mgik/mgd/LogicException.php: -------------------------------------------------------------------------------- 1 | data = (array) $data; 15 | 16 | parent::__construct($message, $code, $previous); 17 | } 18 | 19 | public function getData() 20 | { 21 | return $this->data; 22 | } 23 | } -------------------------------------------------------------------------------- /smart-contracts/packages/crypto-lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crypto-lib", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "DIT Voting Crypto classes", 6 | "main": "index.js", 7 | "scripts": { 8 | "lint": "eslint test/*.js src/*.js samples/*.js *.js", 9 | "test": "mocha", 10 | "coverage": "nyc --reporter html mocha" 11 | }, 12 | "dependencies": { 13 | "ethers": "4.0.27", 14 | "jsbn": "1.1.0", 15 | "node-rsa": "^1.0.5", 16 | "randombytes": "2.1.0", 17 | "secrets.js-grempe": "1.1.0" 18 | }, 19 | "devDependencies": { 20 | "common-deps": "0.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /voting-form/src/ballot/common/module/election/LogicException.php: -------------------------------------------------------------------------------- 1 | data = (array) $data; 15 | 16 | parent::__construct($message, $code, $previous); 17 | } 18 | 19 | public function getData() 20 | { 21 | return array_merge($this->data,array('sess-id'=>session_id(),'referer'=>$_SERVER['HTTP_REFERER'])); 22 | } 23 | } -------------------------------------------------------------------------------- /voting-form/src/forms/forms/mgik/mgd2019/templates.tpl: -------------------------------------------------------------------------------- 1 | 11 | 12 | {include file="$base_template_path/mgik/mgd2019/template_browsers.tpl"} -------------------------------------------------------------------------------- /smart-contracts/readme.md: -------------------------------------------------------------------------------- 1 | # Smart contracts & javascript bindings 2 | Contents of ./packages folder are: 3 | 4 | - `common-deps` - just dependencies common for packages 5 | - `crypto-lib` - library implementing encryption system based on El-Gamal 6 | - `smart-contracts` - smart contracts code & compiled abi & binaries 7 | - `voting-lib` - javascript bindings for smart contacts based on ethers.js 8 | 9 | ## Building 10 | First run `npm install` to install lerna, then run `./node_modules/.bin/lerna bootstrap` 11 | to install packages dependencies. 12 | 13 | For compiling contracts run `npm run compile` in ./packages/smart-contracts folder. 14 | 15 | For running tests you should have parity running with json-rpc interface enabled on port 8545. 16 | -------------------------------------------------------------------------------- /smart-contracts/packages/smart-contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-contracts", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "DIT Voting Smart-Contracts", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "truffle test", 9 | "cover": "solidity-coverage", 10 | "compile": "truffle compile && find ./build -name '*.json' -exec ./etc/filter_json.sh {} \\;", 11 | "lint": "eslint test/**/*.js *.js && solhint contracts/*.sol && solium -d contracts/" 12 | }, 13 | "devDependencies": { 14 | "common-deps": "0.0.0", 15 | "crypto-lib": "0.0.0", 16 | "jsbn": "1.1.0", 17 | "solhint": "2.0.0", 18 | "solidity-coverage": "0.5.11", 19 | "solium": "1.2.4", 20 | "truffle": "5.0.17", 21 | "truffle-assertions": "0.8.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /encryption-keys/readme.md: -------------------------------------------------------------------------------- 1 | # Тестирование криптографической стойкости 2 | 3 | Данные для тестирования находятся в следующих директориях 4 | 5 | - `data`- содержит в себе случайно сгенерированный набор данных для применения ключей шифрования 6 | - `keys` - содержит ключи шифрования. 7 | 8 | 9 | 10 | ## Зашифрованные данные 11 | Каждый день, в начале дня, в папке `data` будет размещен файл с зашифрованными данными, а в папке `keys` будет размещен публичный ключ. 12 | 13 | Задача проверки криптографической стойкости сводится к необходимости провести расшифровку данных за время, равное времени проведения выборов - 12 часов. 14 | 15 | 16 | 17 | ## Расшифрованные данные 18 | Спустя 12 часов, в конце дня, в папке `data` будет размещен файл с исходными данными, а в папке `keys` будет размещен закрытый ключ. 19 | -------------------------------------------------------------------------------- /voting-form/src/forms/common/data/lib/Form/mgik/mgd/TaskRevocationRequest.php: -------------------------------------------------------------------------------- 1 | config['queue']['revocation']; 16 | } 17 | 18 | /** 19 | * @return array 20 | */ 21 | public function asArray() 22 | { 23 | $result = []; 24 | 25 | $result['message_type'] = '4'; 26 | $result['rejection'] = [ 27 | 'reg_num' => $this->app['REG_NUM'], 28 | 'sso' => $this->client['SUDIR_ID'], 29 | 30 | ]; 31 | 32 | return $result; 33 | } 34 | } -------------------------------------------------------------------------------- /voting-form/src/ballot/common/module/election/TaskVoteRequest.php: -------------------------------------------------------------------------------- 1 | config['queue']['vote']; 16 | } 17 | 18 | /** 19 | * @return array 20 | */ 21 | public function asArray() 22 | { 23 | return $this->fields; 24 | } 25 | 26 | /** 27 | * @return array 28 | */ 29 | public function asTaskData() 30 | { 31 | return [ 32 | 'eno' => $this->app['REG_NUM'] ?? null, 33 | 'queue' => $this->queueName(), 34 | 'json' => $this->asArray(), 35 | ]; 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /voting-form/src/forms/forms/mgik/mgd2019/error_exception.tpl: -------------------------------------------------------------------------------- 1 | {extends file="$base_template_path/error.tpl"} 2 | 3 | {block name="message_header"}Заявление не может быть подано{/block} 4 | 5 | {block name="message_add_text"} 6 | 7 | 8 | 9 | 15 | 16 |
17 | 18 | Перейти в личный кабинет 19 | 20 |
21 | 22 | {/block} -------------------------------------------------------------------------------- /voting-form/src/forms/forms/mgik/mgd-view/error_exception.tpl: -------------------------------------------------------------------------------- 1 | {extends file="$base_template_path/error.tpl"} 2 | 3 | {block name="message_header"}Доступ к форме проверки голоса запрещен{/block} 4 | 5 | {block name="message_add_text"} 6 | 7 | 8 | 9 | 15 | 16 |
17 | 18 | Перейти в личный кабинет 19 | 20 |
21 | 22 | {/block} -------------------------------------------------------------------------------- /voting-form/src/forms/forms/mgik/mgd-golosovanie/error_exception.tpl: -------------------------------------------------------------------------------- 1 | {extends file="$base_template_path/error.tpl"} 2 | 3 | {block name="message_header"}Доступ к дистанционному электронному голосованию запрещен{/block} 4 | 5 | {block name="message_add_text"} 6 | 7 | 8 | 9 | 15 | 16 |
17 | 18 | Перейти в личный кабинет 19 | 20 |
21 | 22 | {/block} -------------------------------------------------------------------------------- /voting-form/src/forms/forms/mgik/mgd-view/error_user.tpl: -------------------------------------------------------------------------------- 1 | {extends file="$base_template_path/error.tpl"} 2 | 3 | {block name="message_header"}Доступ к форме проверки голоса запрещен{/block} 4 | 5 | {block name="message_add_text" } 6 | 7 | 8 | 9 |

Уважаемый пользователь!

10 | 11 |

Доступ к форме проверки голоса запрещен, так как Вы не участвовали в дистанционном электронном голосовании на выборах Депутатов Московской городской Думы седьмого созыва

12 | 13 |
14 | 15 | Перейти в личный кабинет 16 | 17 |
18 | 19 | {/block} -------------------------------------------------------------------------------- /smart-contracts/packages/common-deps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "common-deps", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "DIT Voting Project common development deps", 6 | "devDependencies": { 7 | "@babel/core": "7.4.4", 8 | "@babel/preset-env": "7.4.4", 9 | "@babel/runtime": "7.4.4", 10 | "chai": "4.2.0", 11 | "chai-as-promised": "7.1.1", 12 | "choma": "1.2.1", 13 | "eslint": "5.16.0", 14 | "eslint-config-airbnb-base": "13.1.0", 15 | "eslint-plugin-import": "2.17.2", 16 | "mocha": "6.1.4", 17 | "nyc": "14.1.1", 18 | "rollup-plugin-babel": "4.3.2", 19 | "rollup-plugin-commonjs": "9.3.4", 20 | "rollup-plugin-istanbul": "2.0.1", 21 | "rollup-plugin-json": "4.0.0", 22 | "rollup-plugin-node-builtins": "2.1.2", 23 | "rollup-plugin-node-globals": "1.4.0", 24 | "rollup-plugin-node-resolve": "4.2.4", 25 | "rollup-plugin-terser": "4.0.4" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /encryption-keys/data/original-datums.csv: -------------------------------------------------------------------------------- 1 | 13112283206282341589939261161118909456523672818901389266279234979380668261341 2 | 9537266403378652029582253820421820507484849062574530707250565053173398215861 3 | 11068513650385046131379595998206447307025905031384686600463684674867135305179 4 | 8654421830119967172472226791428607421453434554363478910491134642343369776339 5 | 7570583526979178564777162401494767735157632987628046863419790412655369697581 6 | 6658136887333442040078197472430938843396508839855976290823159148125519218683 7 | 10718218053391635422300822262751666630598757178427546964517445539221469279235 8 | 11822287086228852504511790919996319583538429317834402402418592701636689380089 9 | 13102589922455022904473200407911868416062098992747929998728624371771659558165 10 | 12351592095745046315027495613051886574426753686984173961205723539017493804515 11 | 13059504539689535114993475564237630965339527784561332758000681387973907459229 12 | 10352928640994684251296554622674682747455489631687433374774573655642201763799 -------------------------------------------------------------------------------- /encryption-keys/keys/public-key.json: -------------------------------------------------------------------------------- 1 | { 2 | "modulo": "100627590814506256180379036786188261966005912425008608027910859704550882961591418803872072305745904601913015245097812875886798212712694662445323678201384359740027439588690880234391145675099291004487668846511981135309331094869021425403957856145722681330313515482620918593602329299394441379077427748866822254003", 3 | "generator": "20938162663634717592050150871604811965450185147804653686210341370048875526465651925988096081498977531693711058194333885660484655132497051711289316232574822094981061190270796634217228273638531730721024423591535339502392703158974687088258365877782810885461860894240185695001397437423927797525752628688868571010", 4 | "publicKey": "69869939556699578412481990448414288405398435179055114398103563207158851026044172687600238140984542441733811519355635274478280224618944886326303030593806942128698581582995535506792363556379422300596532434755856820167980500565477482309964287055106318673831750120449183092965628809034498878410377075732012129326" 5 | } 6 | -------------------------------------------------------------------------------- /smart-contracts/packages/voting-lib/src/contracts/ownable.js: -------------------------------------------------------------------------------- 1 | const ethers = require('ethers'); 2 | 3 | const { 4 | Ownable: { abi }, 5 | } = require('smart-contracts'); 6 | 7 | class Ownable { 8 | constructor(contract) { 9 | this.contract = contract; 10 | } 11 | 12 | // Fields 13 | get address() { 14 | return this.contract.address; 15 | } 16 | 17 | // Getters 18 | async getOwner() { 19 | return this.contract.getOwner(); 20 | } 21 | 22 | async isOwner() { 23 | return this.contract.isOwner(); 24 | } 25 | 26 | // "Setters" 27 | async transferOwnership(address) { 28 | const tx = await this.contract.transferOwnership(address); 29 | 30 | return tx.wait(); 31 | } 32 | 33 | // Statics 34 | static async at(address, signerAccount) { 35 | const deployedContract = new ethers.Contract(address, abi, signerAccount); 36 | 37 | await deployedContract.deployed(); 38 | 39 | return new Ownable(deployedContract); 40 | } 41 | 42 | static async deploy() { 43 | throw new Error('Can not deploy Ownable by itself!'); 44 | } 45 | } 46 | 47 | module.exports = Ownable; 48 | -------------------------------------------------------------------------------- /voting-form/src/forms/common/data/lib/Form/mgik/mgik.class.php: -------------------------------------------------------------------------------- 1 | logData['error'] = 1; 22 | 23 | if ($exception instanceof LogicException) { 24 | $data = $exception->getData(); 25 | $message = $data['errorMessage'] ?? $exception->getMessage(); 26 | $this->logTrait($message, array_merge($this->logData, $data)); 27 | } else { 28 | $this->logData['errorMessage'] = $exception->getMessage(); 29 | $this->logData['errorTrace'] = $exception->getTraceAsString(); 30 | $this->logTrait('Неизвестная ошибка', $this->logData); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /voting-form/src/ballot/common/module/election/AjaxService.php: -------------------------------------------------------------------------------- 1 | action = $action; 20 | $method = "action" . ucfirst($action); 21 | 22 | if (in_array($action, $this->allowedActions) && method_exists($this, $method)) { 23 | return call_user_func([$this, $method]); 24 | } 25 | } 26 | 27 | protected function actionHit() 28 | { 29 | $hit = $_REQUEST['hit'] ?? null; 30 | $tp = $_REQUEST['type'] ?? null; 31 | $value = $_REQUEST['value'] ?? null; 32 | $guid = preg_replace('/.*election\/([^\/]+)$/','$1',$_SERVER['HTTP_REFERER']); 33 | if ($hit) { 34 | $log = GrayLogger::create('MgicHitCounter'); 35 | $log->info($hit, ['data' => $value,'action'=>$tp,'errorMessage'=>$hit,'guid'=> $guid ?? '']); 36 | } 37 | 38 | exit; 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /voting-form/src/forms/forms/mgik/mgd-golosovanie/error_user.tpl: -------------------------------------------------------------------------------- 1 | {extends file="$base_template_path/error.tpl"} 2 | 3 | {block name="message_header"}Доступ к дистанционному электронному голосованию запрещен{/block} 4 | 5 | {block name="message_add_text" } 6 | 7 | 8 | 9 |

Уважаемый пользователь!

10 | 11 |

Доступ к дистанционному электронного голосованию запрещен по одной из следующих причин:

12 | 13 | 17 | 18 |

Если Вы не включены в список для дистанционного электронного голосования, Вам необходимо посетить избирательный участок, за которым Вы закреплены.

19 | 20 |
21 | 22 | Перейти в личный кабинет 23 | 24 |
25 | 26 | {/block} -------------------------------------------------------------------------------- /voting-form/src/forms/common/data/lib/Form/mgik/mgd/MgdAjaxHandler.php: -------------------------------------------------------------------------------- 1 | action = 'action' . ucfirst($action); 22 | $this->form = $form; 23 | 24 | $result = ['result' => false]; 25 | 26 | if (in_array($action, $this->allowActions) && method_exists($this, $this->action)) { 27 | $result = call_user_func([$this, $this->action]); 28 | } 29 | 30 | return json_encode($result); 31 | } 32 | 33 | private function actionLog() 34 | { 35 | $log = $_REQUEST['log'] ?? null; 36 | 37 | if (! $log) { 38 | return ['result' => false]; 39 | } 40 | 41 | $message = $log['error'] ?? 'Неизвестная ошибка'; 42 | 43 | $this->form->logData['error'] = 1; 44 | $this->form->logData['errorMessage'] = $log; 45 | $this->form->logTrait($message, $this->form->logData); 46 | 47 | return ['result' => true]; 48 | } 49 | } -------------------------------------------------------------------------------- /voting-form/src/ballot/common/module/election/update_dit_voting_settings_cron_task.php: -------------------------------------------------------------------------------- 1 | 'UpdateDitVotingSettings', 15 | 'lockTriesInterval' => 1, 16 | 'keyLifetime' => 600, 17 | 'timeToWait' => 0 18 | ]); 19 | 20 | if (isset($argv[1]) && $argv[1] === 'release') { 21 | $mutex->release(); 22 | } 23 | 24 | try { 25 | if (!$mutex->lock()) { 26 | $logger->debug('Процесс уже запущен'); 27 | exit('Процесс уже запущен'); 28 | } 29 | 30 | $command = new UpdateDitVotingSettingsCommand($logger); 31 | $command->handle(); 32 | 33 | } catch (LogicException $e) { 34 | $data = $e->getData(); 35 | $message = $data['errorMessage'] ?? $e->getMessage(); 36 | 37 | $logger->error($message, array_merge($data, [ 38 | 'error' => 1 39 | ])); 40 | 41 | echo "$message\n"; 42 | print_r($data); 43 | 44 | } catch (\Throwable $e) { 45 | $logger->error($e->getMessage(), [ 46 | 'error' => 1, 47 | 'errorMessage' => $e->getMessage(), 48 | 'errorTrace' => $e->getTrace(), 49 | ]); 50 | 51 | echo $e->getMessage() . "\n"; 52 | echo $e->getTraceAsString(); 53 | } 54 | 55 | $mutex->release(); -------------------------------------------------------------------------------- /voting-form/src/forms/forms/mgik/mgd-golosovanie/templates.tpl: -------------------------------------------------------------------------------- 1 | 17 | 18 | 30 | 31 | {include file="$base_template_path/mgik/mgd2019/template_browsers.tpl"} -------------------------------------------------------------------------------- /smart-contracts/packages/smart-contracts/build/contracts/Ownable.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "inputs": [], 5 | "payable": false, 6 | "stateMutability": "nonpayable", 7 | "type": "constructor" 8 | }, 9 | { 10 | "anonymous": false, 11 | "inputs": [ 12 | { 13 | "indexed": true, 14 | "name": "previousOwner", 15 | "type": "address" 16 | }, 17 | { 18 | "indexed": true, 19 | "name": "newOwner", 20 | "type": "address" 21 | } 22 | ], 23 | "name": "OwnershipTransferred", 24 | "type": "event" 25 | }, 26 | { 27 | "constant": true, 28 | "inputs": [], 29 | "name": "getOwner", 30 | "outputs": [ 31 | { 32 | "name": "", 33 | "type": "address" 34 | } 35 | ], 36 | "payable": false, 37 | "stateMutability": "view", 38 | "type": "function" 39 | }, 40 | { 41 | "constant": true, 42 | "inputs": [], 43 | "name": "isOwner", 44 | "outputs": [ 45 | { 46 | "name": "", 47 | "type": "bool" 48 | } 49 | ], 50 | "payable": false, 51 | "stateMutability": "view", 52 | "type": "function" 53 | }, 54 | { 55 | "constant": false, 56 | "inputs": [ 57 | { 58 | "name": "newOwner", 59 | "type": "address" 60 | } 61 | ], 62 | "name": "transferOwnership", 63 | "outputs": [], 64 | "payable": false, 65 | "stateMutability": "nonpayable", 66 | "type": "function" 67 | } 68 | ], 69 | "bytecode": "0x" 70 | } 71 | -------------------------------------------------------------------------------- /voting-form/src/ballot/common/module_tpl/election/default/success.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | {include file="$template_path/_header.tpl"} 4 | 5 | 6 |
7 |
8 | 9 |
10 |

Спасибо, ваш голос учтен!

11 | 12 | {if $tx && $isShowTxResult} 13 |

Ниже представлен ваш зашифрованный голос.

14 |
{$tx}
15 | 16 |

17 | Вы можете сохранить свой зашифрованный голос и при желании расшифровать его после подведения итогов голосования.
18 | Сервис для расшифровки будет доступен по ссылке. 19 |

20 | 21 | Скопировать 22 | {/if} 23 |
24 | 25 |
26 |
27 | 28 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /voting-form/src/ballot/config/Mgik.php: -------------------------------------------------------------------------------- 1 | !empty(cfgEnv('SITE_DEBUG', false)) ? true : false, 5 | 'districts' => [1, 10, 30], 6 | 'service' => [ 7 | 'election' => [ 8 | 'url' => [ 9 | 'get' => "http://example-host.ru", 10 | 'check' => "http://example-host.ru", 11 | 'check_sign' => "http://example-host.ru", 12 | 'send' => "http://example-host.ru", 13 | 'crypt' => "http://example-host.ru", 14 | 'decrypt' => "http://example-host.ru", 15 | ] 16 | ], 17 | 'blockchain' => [ 18 | 'login' => 'example-login', 19 | 'password' => 'example-password', 20 | 'host' => 'http://example-host.ru', 21 | 'url' => [ 22 | 'authenticate' => "http://example-host.ru", 23 | 'addresses' => 'http://example-host.ru', 24 | 'keys' => 'http://example-host.ru', 25 | ] 26 | ], 27 | 'system'=>'example-system', 28 | 'token'=>'example-token' 29 | ], 30 | 'amqp' => [ 31 | 'host' => 'http://example-host.ru', 32 | 'vhost' => 'example-vhost', 33 | 'port' => 'example-post', 34 | 'login' => 'example-login', 35 | 'password' => 'example-password', 36 | 'queue' => [ 37 | 'registration' => 'example-queue-1', 38 | 'revocation' => 'example-queue-1', 39 | 'status' => 'example-queue-2', 40 | 'vote' => 'example-queue-3', 41 | ] 42 | ], 43 | 'ballot_template' => cfgEnv('BALLOT_TEMPLATE', 'show'), 44 | 'show_tx_result' => cfgEnv('SHOW_TX_RESULT', false), 45 | ]; -------------------------------------------------------------------------------- /smart-contracts/packages/smart-contracts/contracts/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.4; 2 | 3 | 4 | /** 5 | * @title Ownable 6 | * @dev The Ownable contract has an owner address, and provides basic authorization control 7 | * functions, this simplifies the implementation of "user permissions". 8 | */ 9 | contract Ownable { 10 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 11 | 12 | address private currentOwner; 13 | 14 | /** 15 | * @dev The Ownable constructor sets the original `owner` of the contract to the sender 16 | * account. 17 | */ 18 | constructor() internal { 19 | currentOwner = msg.sender; 20 | emit OwnershipTransferred(address(0), currentOwner); 21 | } 22 | 23 | /** 24 | * @return the address of the owner. 25 | */ 26 | function getOwner() public view returns (address) { 27 | return currentOwner; 28 | } 29 | 30 | /** 31 | * @dev Throws if called by any account other than the owner. 32 | */ 33 | modifier onlyOwner() { 34 | require(isOwner(), "Only Owner can do this!"); 35 | _; 36 | } 37 | 38 | /** 39 | * @return true if `msg.sender` is the owner of the contract. 40 | */ 41 | function isOwner() public view returns (bool) { 42 | return msg.sender == currentOwner; 43 | } 44 | 45 | /** 46 | * @dev Allows the current owner to transfer control of the contract to a newOwner. 47 | * @param newOwner The address to transfer ownership to. 48 | */ 49 | function transferOwnership(address newOwner) public onlyOwner { 50 | require(newOwner != address(0), "Can not transfer to Zero!"); 51 | emit OwnershipTransferred(currentOwner, newOwner); 52 | currentOwner = newOwner; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /voting-form/src/ballot/static/js/forms/mgik/LeavingPageCheckerInit.js: -------------------------------------------------------------------------------- 1 | var allowLeaving = (function() { 2 | var leavingPageAllowed = false; 3 | 4 | var message = 'Если Вы покинете страницу, Вы не сможете проголосовать.'; 5 | var messageTwoLines = 'Если Вы покинете страницу,
Вы не сможете проголосовать.'; 6 | 7 | var allowLeaving = function() { 8 | leavingPageAllowed = true; 9 | } 10 | 11 | var checkLeavingAllowed = function() { 12 | return leavingPageAllowed; 13 | } 14 | 15 | function showMessage() { 16 | $('.leavingMessage').show(); 17 | } 18 | 19 | function hideMessage() { 20 | $('.leavingMessage').fadeOut('fast'); 21 | } 22 | 23 | function isMessageVisible() { 24 | return $('.leavingMessage').is(':visible'); 25 | } 26 | 27 | function resizeMessageBox() { 28 | var twoLinesWidth = 600; 29 | if ($(window).width() > 600) { 30 | $('.leavingMessageInner').html(message); 31 | } else { 32 | $('.leavingMessageInner').html(messageTwoLines); 33 | } 34 | var actualWidth = $('.leavingMessage').width(); 35 | var leftOffset = -(actualWidth / 2); 36 | $('.leavingMessage').css({'margin-left': leftOffset}); 37 | } 38 | 39 | var leavingPageChecker = new LeavingPageChecker( 40 | { 41 | showMessage: showMessage, 42 | hideMessage: hideMessage, 43 | isMessageVisible: isMessageVisible, 44 | resizeMessageBox: resizeMessageBox, 45 | checkLeavingAllowed: checkLeavingAllowed, 46 | }, 47 | message, 48 | { 49 | showOnKeydown: true, 50 | showOnMouseout: false, 51 | } 52 | ); 53 | 54 | $(document).ready(function() { 55 | leavingPageChecker.run(); 56 | }); 57 | 58 | return allowLeaving; 59 | })(); -------------------------------------------------------------------------------- /voting-form/src/forms/common/data/lib/Form/mgik/mgd2019.class.php: -------------------------------------------------------------------------------- 1 | 'link', 31 | 'data' => [ 32 | 'url' => "{$this->mainHost}/ru/application/{$this->org_id}/mgd-golosovanie/", 33 | 'text' => 'Проголосовать', 34 | 'btn_class' => self::ELK_BTN_GREEN_CLASS 35 | ], 36 | ]; 37 | } 38 | 39 | if ($statusCode == '1077.1') { 40 | $appInfo['ACTIONS'][] = [ 41 | 'type' => 'link', 42 | 'data' => [ 43 | 'url' => "{$this->mainHost}/ru/service/mgd-election", 44 | 'text' => 'Перейти к тестовому голосованию', 45 | 'btn_class' => self::ELK_BTN_GREEN_CLASS 46 | ], 47 | ]; 48 | } 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /smart-contracts/packages/voting-lib/src/accounts.js: -------------------------------------------------------------------------------- 1 | const { Wallet } = require('ethers'); 2 | 3 | class Account extends Wallet { 4 | constructor(privateKey, provider) { 5 | super(privateKey, provider); 6 | 7 | this.nonce = null; 8 | this.txSemaphore = null; 9 | 10 | if (provider) { 11 | this.txSemaphore = this.getTransactionCount() 12 | .then((txCount) => { 13 | this.nonce = txCount; 14 | }); 15 | } 16 | } 17 | 18 | /** 19 | * Creates account with random generated keypair 20 | * @param {Object} [provider] - ethers connection provider 21 | */ 22 | static createRandom(provider) { 23 | // eslint-disable-next-line prefer-destructuring 24 | const privateKey = Wallet.createRandom().signingKey.privateKey; 25 | return new Account(privateKey, provider); 26 | } 27 | 28 | sendTransaction(transaction) { 29 | if (!this.provider) { 30 | throw new Error('missing provider'); 31 | } 32 | 33 | const tx = this.txSemaphore 34 | .then(() => { 35 | // eslint-disable-next-line no-param-reassign 36 | transaction.nonce = this.nonce; 37 | return super.sendTransaction(transaction); 38 | }) 39 | .then((txResponse) => { 40 | this.nonce++; // eslint-disable-line no-plusplus 41 | return txResponse; 42 | }); 43 | 44 | this.txSemaphore = tx.catch(() => {}); 45 | 46 | return tx; 47 | } 48 | } 49 | 50 | const newRandomAccount = () => Account.createRandom(); 51 | 52 | const newRandomAccountWithProvider = provider => Account.createRandom(provider); 53 | 54 | const accountFromPrivateKey = privateKey => new Account(privateKey); 55 | 56 | // eslint-disable-next-line max-len 57 | const accountFromPrivateKeyWithProvider = (privateKey, provider) => new Account(privateKey, provider); 58 | 59 | 60 | module.exports = { 61 | newRandomAccount, 62 | newRandomAccountWithProvider, 63 | accountFromPrivateKey, 64 | accountFromPrivateKeyWithProvider, 65 | }; 66 | -------------------------------------------------------------------------------- /voting-form/src/ballot/common/module_tpl/election/default/_header.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {$title} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/js_v3/mgik/mgd-golosovanie.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | 3 | $.checkBrowser({target: '#form_element'}); 4 | 5 | var confirmPopupContent = OPR.templater('vote_start_confirm', []); 6 | var sendingPopupContent = OPR.templater('sending_popup', []); 7 | 8 | var formController = new FormController(0, { 9 | skipAgreement: true, 10 | useSendingPopup: false, 11 | useHitcounter: true 12 | }); 13 | 14 | ELK.ready(function () { 15 | ELK.fill($('#form_element')); 16 | }); 17 | 18 | $('.js-get-code').on('click', function() { 19 | var $button = $(this); 20 | var $target = $('.needConfirm'); 21 | 22 | $button.fadeOut('fast'); 23 | 24 | $target.data({ 25 | oldValue: '', 26 | confirmText: 'Телефон успешно подтвержден.', 27 | confirmCallback: function () { 28 | $('.vote-block').fadeIn('fast'); 29 | } 30 | }); 31 | 32 | $target.trigger('keyup.delay'); 33 | 34 | return false; 35 | }); 36 | 37 | $('.js-show-vote-popup').on('click', function() { 38 | var $isAgree = $('#is_agree'); 39 | 40 | if (! $isAgree.valid()) { 41 | return false; 42 | } 43 | 44 | messagebox('Приступить к голосованию', confirmPopupContent, null); 45 | 46 | return false; 47 | }); 48 | 49 | $(document).on('click', '.js-start-vote', function() { 50 | messagebox('Подождите, пожалуйста', sendingPopupContent, null, null, true, function () { 51 | formController.advanceNext(); 52 | 53 | setTimeout(function() { 54 | var $popup = $('.popup'); 55 | 56 | $('.popup_loader', $popup).hide(); 57 | $('.popup_error', $popup).show(); 58 | }, 360000); 59 | }); 60 | 61 | return false; 62 | }); 63 | 64 | $(document).on('click', '.js-cancel-vote', function() { 65 | $('.popup_messagebox_shadow').fadeOut('fast'); 66 | $('.popup_messagebox').fadeOut('fast'); 67 | 68 | return false; 69 | }); 70 | 71 | }); 72 | -------------------------------------------------------------------------------- /smart-contracts/packages/crypto-lib/src/utils.js: -------------------------------------------------------------------------------- 1 | // Based on MIT licensed work of Kristóf Poduszló in 2016 https://github.com/kripod/elgamal.js 2 | const randomBytes = require('randombytes'); 3 | const { BigInteger: BigInt } = require('jsbn'); 4 | 5 | const KEY_BIT_LENGTH = 1024; 6 | const CRYPTO_MAX_INT = BigInt.ONE.shiftLeft(KEY_BIT_LENGTH + 1).subtract(BigInt.ONE); 7 | 8 | // Helper functions 9 | 10 | /** 11 | * @returns {String} 12 | */ 13 | const randomBytesHex = bits => randomBytes(Math.ceil(bits / 8)).toString('hex'); 14 | 15 | /** 16 | * @returns {BigInt} 17 | */ 18 | const trimBigInt = (bigInt, bits) => { 19 | const trimLength = bigInt.bitLength() - bits; 20 | return trimLength > 0 ? bigInt.shiftRight(trimLength) : bigInt; 21 | }; 22 | 23 | // exported functions 24 | 25 | /** 26 | * @returns {Promise} 27 | */ 28 | const getRandomBigInt = async (min, max) => { 29 | if (min.compareTo(BigInt.ZERO) <= 0) { 30 | throw new Error('Min value can not be less or equal 0!'); 31 | } 32 | 33 | if (min.compareTo(max) >= 0) { 34 | throw new Error('Min value can not be more or equal Max value!'); 35 | } 36 | 37 | if (max.compareTo(CRYPTO_MAX_INT) > 0) { 38 | console.log(`max: ${max.toString()}`); 39 | console.log(`crypto: ${CRYPTO_MAX_INT.toString()}`); 40 | console.log(`compare: ${max.compareTo(CRYPTO_MAX_INT)}`); 41 | throw new Error('Can not generate BigInt bigger than crypto max value!'); 42 | } 43 | 44 | const rangeBitLength = max 45 | .subtract(min) 46 | .subtract(BigInt.ONE) 47 | .bitLength(); 48 | 49 | let randomBigInt; 50 | do { 51 | randomBigInt = new BigInt(randomBytesHex(rangeBitLength), 16).add(min); 52 | } while (randomBigInt.compareTo(max) >= 0); 53 | 54 | return randomBigInt; 55 | }; 56 | 57 | const randomValidBigInt = () => getRandomBigInt(BigInt.ONE, CRYPTO_MAX_INT); 58 | 59 | const checkEveryFieldIsSame = (objects, fieldName) => { 60 | const isSame = objects.map(obj => obj[fieldName]).every((val, i, arr) => val === arr[0]); 61 | 62 | if (!isSame) { 63 | throw new Error(`Not all objects have the same ${fieldName}!`); 64 | } 65 | 66 | return isSame; 67 | }; 68 | 69 | module.exports = { 70 | CRYPTO_MAX_INT, 71 | KEY_BIT_LENGTH, 72 | 73 | getRandomBigInt, 74 | randomValidBigInt, 75 | checkEveryFieldIsSame, 76 | trimBigInt, 77 | }; 78 | -------------------------------------------------------------------------------- /voting-form/src/forms/common/data/lib/Form/mgik/mgd-view.class.php: -------------------------------------------------------------------------------- 1 | config = PoolConfig::me()->conf('Mgik'); 30 | $this->service = new Service(); 31 | } 32 | 33 | protected function show() 34 | { 35 | $this->smarty->assign('hide_cutalog_button', true); 36 | 37 | try { 38 | $this->service->setLogger($this->getLogger(), array_merge($this->logData, $this->getExtLogData())); 39 | 40 | $userData = $this->service->hasBallot($this->client['SUDIR_ID']); 41 | 42 | if (! $userData) { 43 | return $this->getTplPath('error_user.tpl'); 44 | } 45 | 46 | return parent::show(); 47 | 48 | } catch (LogicException $e) { 49 | $this->logExceptionError($e); 50 | $this->smarty->assign('message', $e->getMessage()); 51 | 52 | return $this->getTplPath('error_exception.tpl'); 53 | 54 | } catch (\Exception $e) { 55 | $this->logExceptionError($e); 56 | $this->smarty->assign('message', 'Неизвестная ошибка.'); 57 | 58 | return $this->getTplPath('error_exception.tpl'); 59 | } 60 | } 61 | 62 | protected function send() 63 | { 64 | header("Location: http://" . CFG_HOST_NAME . "/ru/app/{$this->org_id}/{$this->form_id}"); 65 | die(); 66 | } 67 | } -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/js_v3/mgik/mgd-view.js: -------------------------------------------------------------------------------- 1 | var controller = false; 2 | 3 | $(function () { 4 | var $input = $('[name="tx"]'); 5 | var $btn = $('.js-btn-submit'); 6 | var $loader = $('.js-loader'); 7 | var $result = $('.js-result'); 8 | 9 | var initStep1 = function (step, data) { 10 | $input.prop('disabled', false); 11 | $btn.fadeIn('fast').css('display', 'inline-block'); 12 | 13 | return true; 14 | }; 15 | 16 | var initStep2 = function (step, data) { 17 | return $.ajax({ 18 | url: cfgMainHost + '/common/ajax/index.php?ajaxModule=MgdService&ajaxAction=view', 19 | type: 'post', 20 | data: { 21 | tx: $input.val() 22 | }, 23 | beforeSend: function () { 24 | $loader.fadeIn('fast'); 25 | $input.prop('disabled', true); 26 | $btn.fadeOut('fast'); 27 | }, 28 | success: function (data) { 29 | if (data.error) { 30 | return error(data); 31 | } 32 | 33 | return success(data); 34 | }, 35 | error: function (data) { 36 | return error(data); 37 | } 38 | }); 39 | }; 40 | 41 | var error = function (data) { 42 | var template = OPR.templater('result', { 43 | error: data.errorMessage || 'Произошла непредвиденная ошибка. Попробуйте повторить запрос позднее.' 44 | }); 45 | 46 | return result('error', template); 47 | }; 48 | 49 | var success = function (data) { 50 | var template = OPR.templater('result', { 51 | error: false, 52 | deputy: data 53 | }); 54 | 55 | return result('success', template); 56 | }; 57 | 58 | var result = function (status, template) { 59 | $loader.fadeOut('fast'); 60 | $result.html(template).fadeIn('fast'); 61 | 62 | return status === 'success'; 63 | }; 64 | 65 | var controller = new FormController(0); 66 | 67 | controller.addAfterStepHandler({ step: 1, action: initStep1 }); 68 | controller.addBeforeStepHandler({ step: 2, action: initStep2 }); 69 | 70 | $btn.on('click', function () { 71 | controller.advanceNext(); 72 | 73 | return false; 74 | }); 75 | 76 | }); -------------------------------------------------------------------------------- /smart-contracts/packages/voting-lib/src/contracts/lockableTransactionAuthorizer.js: -------------------------------------------------------------------------------- 1 | const ethers = require('ethers'); 2 | 3 | const { 4 | LockableTransactionAuthorizer: { abi, bytecode }, 5 | } = require('smart-contracts'); 6 | 7 | const { txReceiptParseLogs } = require('../helpers'); 8 | 9 | class LockableTransactionAuthorizer { 10 | constructor(contract) { 11 | this.contract = contract; 12 | } 13 | 14 | // Fields 15 | get address() { 16 | return this.contract.address; 17 | } 18 | 19 | // Getters 20 | allowedTxTypes(sender, to, value) { 21 | return this.contract.allowedTxTypes(sender, to, value); 22 | } 23 | 24 | contractName() { 25 | return this.contract.contractName(); 26 | } 27 | 28 | contractNameHash() { 29 | return this.contract.contractNameHash(); 30 | } 31 | 32 | contractVersion() { 33 | return this.contract.contractVersion(); 34 | } 35 | 36 | isOwner() { 37 | return this.contract.isOwner(); 38 | } 39 | 40 | isLocked() { 41 | return this.contract.isLocked(); 42 | } 43 | 44 | getLockReason() { 45 | return this.contract.getLockReason(); 46 | } 47 | 48 | getCreator() { 49 | return this.contract.getCreator(); 50 | } 51 | 52 | /** 53 | * Returns parsed events from trabsaction receipt 54 | * @param {Object} txReceipt - transaction receipt. Tx should be sent to this contract 55 | * @return {Object[]} - array of events in this transaction 56 | */ 57 | // eslint-disable-next-line class-methods-use-this 58 | getEventsFromTransaction(txReceipt) { 59 | return txReceiptParseLogs(txReceipt, { abi, bytecode }); 60 | } 61 | 62 | // "Setters" 63 | async lock(reason) { 64 | const tx = await this.contract.lock(reason); 65 | 66 | return tx.wait(); 67 | } 68 | 69 | // Statics 70 | static async at(address, signerAccount) { 71 | const deployedContract = new ethers.Contract(address, abi, signerAccount); 72 | 73 | await deployedContract.deployed(); 74 | 75 | return new LockableTransactionAuthorizer(deployedContract); 76 | } 77 | 78 | static async deploy(signerAccount) { 79 | const factory = new ethers.ContractFactory(abi, bytecode, signerAccount); 80 | 81 | const contract = await factory.deploy(); 82 | 83 | await contract.deployed(); 84 | 85 | return new LockableTransactionAuthorizer(contract); 86 | } 87 | } 88 | 89 | module.exports = LockableTransactionAuthorizer; 90 | -------------------------------------------------------------------------------- /voting-form/src/forms/forms/mgik/mgd2019/show_revocation.tpl: -------------------------------------------------------------------------------- 1 | {include file="$base_template_path/std_head_service.tpl" faq="#" special=false} 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | Отзыв заявления 15 | 16 |
17 |
18 | Этим действием Вы подтверждаете, что хотите отозвать свое заявление на включение вас в список 19 | избирателей для дистанционного электронного голосования на выборах депутатов Московской городской 20 | Думы. После отзыва заявления вернуть его в работу будет невозможно. Оформление нового заявления на 21 | включение в список избирателей для дистанционного электронного голосования на выборах депутатов 22 | Московской городской Думы будет возможно через сутки. 23 |
24 | 25 | {include file="$base_template_path/std_mos/std_checkbox.tpl" 26 | label='Подтверждаю, что я ознакомился с условиями отзыва и хочу отозвать свое заявление' 27 | id="cb1" name="fields[internal.revocation]" 28 | value='1' 29 | container_class="revocation_check" 30 | required=true 31 | } 32 | 33 | {include file="$base_template_path/revocation_required_fields.tpl"} 34 |
35 | 36 |
37 | 38 | {include file="$base_template_path/std_blocks/std_form_controls.tpl"} 39 | 40 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /voting-form/src/forms/common/data/lib/Form/mgik/mgd/MgdAjaxService.php: -------------------------------------------------------------------------------- 1 | action = $action; 33 | $method = "action" . ucfirst($action); 34 | 35 | $user = User::get_current_client(); 36 | if (! $user) { 37 | return $this->error('Вы не авторизованы. Пожалуйста, перезайдите.'); 38 | } 39 | 40 | if (in_array($action, $this->allowedActions) && method_exists($this, $method)) { 41 | $this->config = PoolConfig::me()->conf('Mgik'); 42 | 43 | return call_user_func([$this, $method]); 44 | } 45 | 46 | return $this->error('Метод не доступен. Попробуйте повторить запрос позднее.'); 47 | } 48 | 49 | protected function actionView() 50 | { 51 | $tx = $_REQUEST['tx'] ?? null; 52 | if (! $tx) { 53 | return $this->error('Укажите обязательный параметр "Секретный ключ"'); 54 | } 55 | 56 | try { 57 | $service = new Service; 58 | $result = $service->decryptTx($tx); 59 | $deputies = $service->getDistrictDeputies($result['districtId']); 60 | $deputy = $deputies[$result['deputyId']] ?? null; 61 | 62 | if (! $deputy) { 63 | return $this->error(); 64 | } 65 | 66 | $this->result = $deputy; 67 | 68 | return json_encode($this->result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); 69 | } catch (LogicException $e) { 70 | $this->result['logData'] = $e->getData(); 71 | 72 | return $this->error($e->getMessage()); 73 | } 74 | } 75 | 76 | private function error(string $message = '') 77 | { 78 | $this->result['error'] = 1; 79 | $this->result['errorMessage'] = $message ? $message : 'Произошла непредвиденная ошибка. Попробуйте повторить запрос позднее.'; 80 | $this->result['headerSended'] = true; 81 | 82 | return json_encode($this->result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /voting-form/src/forms/common/data/config/Mgik.php: -------------------------------------------------------------------------------- 1 | true, 5 | 'registration' => [ 6 | 'start_date' => 20190724000000, 7 | 'period' => 24, 8 | 'statuses' => [ 9 | 'allow' => [101099, 102099, 1080, 1090], 10 | 'disallow' => [1080, 1090] 11 | ], 12 | 'profile_validators' => [ 13 | 'age' => true, 14 | 'isConfirmed' => true, 15 | 'gender' => true, 16 | 'passport' => true, 17 | 'regAddress' => true, 18 | 'district' => true, 19 | ] 20 | ], 21 | 'allowed_districts' => [ 22 | 1005 => "муниципальный округ Крюково", 23 | 1009 => "муниципальный округ Матушкино", 24 | 1010 => "муниципальное образование Савелки", 25 | 1011 => "муниципальный округ Силино", 26 | 315 => "муниципальный округ Северный", 27 | 306 => "муниципальный округ Лианозово", 28 | 304 => "муниципальный округ Бибирево", 29 | 620 => "муниципальный округ Чертаново Центральное", 30 | 621 => "муниципальный округ Чертаново Южное", 31 | ], 32 | 'districts' => [1, 10, 30], 33 | 'service' => [ 34 | 'mdm' => [ 35 | 'token' => 'example-token', 36 | 'url' => [ 37 | 'check' => "http://example-host.ru", 38 | 'get' => "http://example-host.ru", 39 | 'flush' => "http://example-host.ru", 40 | ], 41 | ], 42 | 'deputies' => [ 43 | 'url' => "http://example-host.ru" 44 | ], 45 | 'election' => [ 46 | 'host' => 'http://example-host.ru', 47 | 'cert' => 'example-cert', 48 | 'secret' => 'example-secret', 49 | 'systems' => [ 50 | 'example-system' => 'example-secret' 51 | ], 52 | 'url' => [ 53 | 'get' => "http://example-host.ru", 54 | ] 55 | ], 56 | 'decrypt' => [ 57 | 'login' => 'example-login', 58 | 'password' => 'example-password', 59 | 'token' => 'example-token', 60 | 'url' => [ 61 | 'decrypt' => 'http://example-host.ru', 62 | ] 63 | ], 64 | ], 65 | 'amqp' => [ 66 | 'host' => 'http://example-host.ru', 67 | 'vhost' => 'example-host', 68 | 'port' => 'example-post', 69 | 'login' => 'example-login', 70 | 'password' => 'example-password', 71 | 'workers' => 3, 72 | 'waitTime' => 0, 73 | 'lifeTime' => 60 * 3, 74 | 'queue' => [ 75 | 'registration' => 'example-queue-1', 76 | 'revocation' => 'example-queue-1', 77 | 'status' => 'example-queue-2', 78 | 'vote' => 'example-queue-3', 79 | ], 80 | ], 81 | ]; -------------------------------------------------------------------------------- /voting-form/src/forms/forms/mgik/mgd-view/show.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {include file="$base_template_path/std_head_service.tpl" faq="{$CFG_MAIN_HOST}/ru/faq/?subject=$faq" download_app_file_enabled = false has_additional_legals=false} 5 | 6 |
7 | 8 |
9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | Транзакция зашифрованного голоса 19 | 20 |

Укажите транзакцию зашифрованного голоса, которую Вы получили после голосования:

21 | 22 | {include file="$base_template_path/std_blocks/std_textarea.tpl" 23 | label=false 24 | name="tx" 25 | required=true 26 | class="field--tx" 27 | } 28 | 29 |
30 |
31 | Проверить 32 |
33 |
34 |
35 | 36 | 44 | 45 | {include file="$base_template_path/std_blocks/std_form_controls.tpl"} 46 | 47 |
48 | 49 |
50 | 51 |
52 | 53 |
54 | 55 |
56 | 57 | -------------------------------------------------------------------------------- /voting-form/src/forms/forms/mgik/mgd2019/error_profile.tpl: -------------------------------------------------------------------------------- 1 | {extends file="$base_template_path/error.tpl"} 2 | 3 | {block name="message_header"}Заявление не может быть подано.{/block} 4 | 5 | {block name="message_add_text"} 6 | 7 | 8 | 9 | {assign var=messages value=[ 10 | 'age' => 'На день голосования вам будет 18 лет', 11 | 'isConfirmed' => 'Вы подтвердили свою учетную запись в МФЦ', 12 | 'gender' => 'Указан пол в Личном кабинете', 13 | 'passport' => 'Данные вашего паспорта проверены в Личном кабинете', 14 | 'regAddress' => 'Ваш адрес регистрации проверен в Личном кабинете', 15 | 'district' => 'Вы зарегистрированы в одном из избирательных округов, для которых доступен формат дистанционного электронного голосования: № 1 — Крюково, Матушкино, Савелки, Силино, Старое Крюково, № 10 — Северный, Лианозово, Бибирево, № 30 — Чертаново Центральное, Чертаново Южное' 16 | ]} 17 | 18 |

Уважаемый пользователь!

19 | 20 | {if $profile_validators.district === true} 21 |

Участие в пилотном дистанционном голосовании могут принять только жители районов указанных ниже:

22 | 32 | {/if} 33 | 34 |

Для подачи заявления на включение в список избирателей для электронного дистанционого голосования на выборах депутатов Московской городской Думы, необходимо, чтобы в вашем Личном кабинете были подтверждены следующие данные:

35 | 36 | {if $profile_errors} 37 | 51 | {/if} 52 | 53 |

Убедитесь, что все необходимые требования выполнены.

54 | 55 |

Если в вашем Личном кабинете отсутствуют необходимые сведения, вам необходимо перейти в Личный кабинет, указать недостающие сведения и дождаться их проверки.

56 | 57 |
58 | 59 | Перейти в личный кабинет 60 | 61 |
62 | 63 | {/block} -------------------------------------------------------------------------------- /voting-form/src/forms/forms/mgik/mgd2019/template_browsers.tpl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /smart-contracts/packages/voting-lib/src/helpers.js: -------------------------------------------------------------------------------- 1 | const ethers = require('ethers'); 2 | const { BigInteger: BigInt } = require('jsbn'); 3 | 4 | /** 5 | * 6 | * @param {Object} txReceipt - transaction recipt 7 | * @param {any} txReceipt.logs - transaction logs object 8 | * @param {Object} contract - contract definition 9 | * @param {Object} contract.abi - contract definition 10 | * @param {Object} contract.bytecode - contract bytecode 11 | * @returns {Array.<{ 12 | * transactionLogIndex: Number, 13 | * transactionIndex: Number, 14 | * blockNumber: Number, 15 | * transactionHash: String, 16 | * address: String, 17 | * topics: Array, 18 | * data: String, 19 | * logIndex: Number, 20 | * blockHash: 21 | * String, 22 | * args: any, 23 | * event: String, 24 | * eventSignature: String}>} - array of events 25 | */ 26 | const txReceiptParseLogs = (txReceipt, contract) => { 27 | if (!(txReceipt && txReceipt.logs)) { 28 | throw new Error('Must pass a proper Transaction Receipt with definition with Logs!'); 29 | } 30 | 31 | if (!(contract && contract.abi && contract.bytecode)) { 32 | throw new Error('Must pass a proper contract definition with ABI and Bytecode!'); 33 | } 34 | 35 | const { abi, bytecode } = contract; 36 | 37 | const factory = new ethers.ContractFactory(abi, bytecode); 38 | 39 | // Copy of Ethers.js tx.wait(), minus added 40 | // functions removeListener, getBlock, getTransaction, getTransactionReceipt 41 | return txReceipt.logs.map((log) => { 42 | const event = Object.assign({}, log); 43 | 44 | const parsed = factory.interface.parseLog(log); 45 | if (parsed) { 46 | event.args = parsed.values; 47 | event.decode = parsed.decode; 48 | event.event = parsed.name; 49 | event.eventSignature = parsed.signature; 50 | } 51 | 52 | return event; 53 | }); 54 | }; 55 | 56 | /** 57 | * Parses raw transaction & returns its properties 58 | * See https://docs.ethers.io/ethers.js/html/api-utils.html#transactions 59 | * @param {String} rawTx - raw transaction data 60 | * @return {Object} - transaction properties (from, to, data, hash, etc.) 61 | */ 62 | const parseRawTransaction = rawTx => ethers.utils.parseTransaction(rawTx); 63 | 64 | /** 65 | * Strips 0x from hex sequence if present 66 | * @param {string} hex - hex sequence 67 | * @return {string} - hex sequence without 0x 68 | */ 69 | const cleanHex = (hex) => { // eslint-disable-line arrow-body-style 70 | return (typeof hex === 'string' && hex.slice(0, 2) === '0x') ? hex.slice(2) : hex; // eslint-disable-line no-extra-parens 71 | }; 72 | 73 | /** 74 | * Converts BigInteger number to bytes representaion 75 | * @param {Object} bigint - BigInteger 76 | * @return {string} - bytes representaion of number 77 | */ 78 | const bigIntToBytes = (bigint) => { 79 | const hex = cleanHex(bigint.toString(16)); 80 | // eslint-disable-next-line prefer-template 81 | return '0x' + (((hex.length % 64) !== 0) ? '0'.repeat(64 - (hex.length % 64)) : '') + hex; 82 | }; 83 | 84 | /** 85 | * Converts bytes representaion to BigInteger 86 | * @param {string} - bytes representaion of number 87 | * @return {Object} bigint - BigInteger 88 | */ 89 | const bytesToBigInt = (bytes) => { // eslint-disable-line arrow-body-style 90 | return new BigInt(cleanHex(bytes), 16); 91 | }; 92 | 93 | module.exports = { 94 | txReceiptParseLogs, 95 | parseRawTransaction, 96 | bigIntToBytes, 97 | bytesToBigInt, 98 | }; 99 | -------------------------------------------------------------------------------- /voting-form/src/ballot/common/module/election/Settings.php: -------------------------------------------------------------------------------- 1 | init(); 23 | 24 | return $this->settings[$name] ?? ''; 25 | } 26 | 27 | /** 28 | * @return array 29 | * @throws \Exception 30 | */ 31 | public function getAll() 32 | { 33 | $this->init(); 34 | 35 | return $this->settings; 36 | } 37 | 38 | /** 39 | * @return bool 40 | * @throws \Exception 41 | */ 42 | public function init() 43 | { 44 | if ($this->settings) { 45 | return true; 46 | } 47 | 48 | $this->settings = MemoryCache::get(static::CACHE_KEY); 49 | 50 | if (! $this->settings) { 51 | $this->load(); 52 | } 53 | 54 | return (bool) $this->settings; 55 | } 56 | 57 | /** 58 | * @return array 59 | * @throws \Exception 60 | */ 61 | public function load() 62 | { 63 | $this->settings = []; 64 | $this->setCache(); 65 | 66 | $result = db::sql_select("SELECT * FROM p_settings"); 67 | 68 | if (! $result) { 69 | return $this->settings; 70 | } 71 | 72 | foreach ($result as $row) { 73 | $this->settings[$row['name']] = $row['value']; 74 | } 75 | 76 | $this->setCache(); 77 | 78 | return $this->settings; 79 | } 80 | 81 | /** 82 | * @return mixed 83 | */ 84 | public function setCache() 85 | { 86 | return MemoryCache::set(static::CACHE_KEY, $this->settings, Cache::EXPIRES_MAXIMUM); 87 | } 88 | 89 | /** 90 | * @return void 91 | * @throws \Exception 92 | */ 93 | public function resetCache() 94 | { 95 | $this->load(); 96 | } 97 | 98 | /** 99 | * @param string $name 100 | * @param string $value 101 | * @return mixed 102 | * @throws \Exception 103 | */ 104 | public function set(string $name, string $value) 105 | { 106 | $data = [ 107 | 'name' => $name, 108 | 'value' => $value, 109 | ]; 110 | 111 | $result = db::update_record('p_settings', $data, [], ['name' => $name]); 112 | 113 | if (! $result) { 114 | $result = db::insert_record('p_settings', $data); 115 | } 116 | 117 | if (! $result) { 118 | throw new \Exception('Не удалось сохранить значение а базу данных'); 119 | } 120 | 121 | $this->settings[$name] = $value; 122 | 123 | return $this->setCache(); 124 | } 125 | 126 | /** 127 | * @param array $settings 128 | * @throws \Exception 129 | */ 130 | public function setArray(array $settings) 131 | { 132 | try { 133 | db::beginTransaction(); 134 | foreach ($settings as $name => $value) { 135 | $this->set($name, $value); 136 | } 137 | db::commit(); 138 | } catch (\Exception $e) { 139 | db::rollBack(); 140 | throw $e; 141 | } 142 | } 143 | 144 | } -------------------------------------------------------------------------------- /voting-form/src/forms/common/data/lib/Form/mgik/mgd/ReceiveStatusWorker.php: -------------------------------------------------------------------------------- 1 | setPid(posix_getpid()); 23 | } 24 | 25 | /** 26 | * @param array $message 27 | * @throws \Exception 28 | */ 29 | public function brokerCallback($message) 30 | { 31 | try { 32 | $data = $this->createUpdateStatusData($message); 33 | $this->log('Формирование запроса', ['data' => $data], 'info'); 34 | 35 | $result = update_status($data, true); 36 | $this->log('Обновление статуса', ['result' => $result], 'info'); 37 | 38 | $removeMessage = true; 39 | 40 | if (strpos($result, 'ORA') !== false) { 41 | $removeMessage = false; 42 | } elseif (strpos($result, 'Status date is old') !== false) { 43 | $removeMessage = true; 44 | } elseif ($result !== 'OK') { 45 | $moveResult = $this->broker->send('0MDM-2.BK', $message['body'], [ 46 | 'persistent' => 'true', 47 | 'result' => $result, 48 | ]); 49 | 50 | if (!$moveResult) { 51 | $removeMessage = false; 52 | } 53 | 54 | $result .= "\nПеренос в .BK очередь: " . (int)$moveResult; 55 | } 56 | 57 | if ($removeMessage) { 58 | $this->getBroker()->ack($message); 59 | $result .= "\nСообщение удалено из очереди"; 60 | } 61 | 62 | $this->log('Изменение статуса заявления', [ 63 | 'eno' => $data->reg_num ?? null, 64 | 'sso_id' => $data->sso ?? null, 65 | 'data' => $data, 66 | 'result' => $result, 67 | ], 'info'); 68 | 69 | } catch (\Throwable $e) { 70 | $this->error('Критическая ошибка', [ 71 | 'message' => $e->getMessage(), 72 | 'data' => $e->getTrace() 73 | ]); 74 | 75 | throw new \Exception($e->getMessage()); 76 | } 77 | } 78 | 79 | /** 80 | * @param array $message 81 | * @return object 82 | */ 83 | private function createUpdateStatusData($message) 84 | { 85 | $body = json_decode($message['body']); 86 | $data = $body->result ?? null; 87 | 88 | $result = [ 89 | 'reg_num' => $data->reg_num ?? '', 90 | 'status_info' => [ 91 | 'status_code' => $data->status_info->status_code ?? '', 92 | 'status_title' => $data->status_info->status_title ?? '', 93 | 'status_date' => $data->status_info->status_date ?? '', 94 | ], 95 | 'extra_info' => [ 96 | 'append' => false, 97 | 'value' => $data->extra_info->value ?? '', 98 | ], 99 | ]; 100 | 101 | return json_decode(json_encode($result), false); 102 | } 103 | } -------------------------------------------------------------------------------- /smart-contracts/packages/smart-contracts/contracts/LockableTransactionAuthorizer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.4; 2 | 3 | 4 | contract LockableTransactionAuthorizer { 5 | /// Allowed transaction types mask 6 | uint32 constant NONE = 0; 7 | uint32 constant BASIC = 0x01; 8 | uint32 constant CALL = 0x02; 9 | uint32 constant CREATE = 0x04; 10 | uint32 constant PRIVATE = 0x08; 11 | uint32 constant ALL = BASIC | CALL | CREATE | PRIVATE; 12 | 13 | event Locked(address requester, string reason); 14 | 15 | bool private locked = false; 16 | string private lockReason; 17 | address private owner; 18 | address private creator; 19 | 20 | constructor() public { 21 | creator = msg.sender; 22 | 23 | // FOR GENESIS BLOCK THIS ADDRESS MUST BE REPLACED WITH PROPER VALUE 24 | // this is "admin" from tests 25 | address hardcodedOwner = address(0xD0D8E2C98C1e759b82a4705e973b9542C677183d); 26 | if (creator == address(0)) { 27 | owner = hardcodedOwner; 28 | } else { 29 | owner = creator; 30 | } 31 | } 32 | 33 | modifier onlyOwner() { 34 | require(isOwner(), "Only Owner can do this!"); 35 | _; 36 | } 37 | 38 | /* 39 | * Allowed transaction types 40 | * 41 | * Returns: 42 | * - uint32 - set of allowed transactions for #'sender' depending on tx #'to' address 43 | * and value in wei. 44 | * - bool - if true is returned the same permissions will be applied from the same #'sender' 45 | * without calling this contract again. 46 | * 47 | * In case of contract creation #'to' address equals to zero-address 48 | * 49 | * Result is represented as set of flags: 50 | * - 0x01 - basic transaction (e.g. ether transferring to user wallet) 51 | * - 0x02 - contract call 52 | * - 0x04 - contract creation 53 | * - 0x08 - private transaction 54 | * 55 | * @param sender Transaction sender address 56 | * @param to Transaction recepient address 57 | * @param value Value in wei for transaction 58 | * 59 | */ 60 | function allowedTxTypes(address sender, address to, uint256 value) public view returns (uint32, bool) { 61 | if (isLocked()) { 62 | return (BASIC | CALL, false); 63 | } 64 | 65 | return (ALL, false); 66 | } 67 | 68 | /// Contract name 69 | function contractName() public pure returns (string memory) { 70 | return "TX_PERMISSION_CONTRACT"; 71 | } 72 | 73 | /// Contract name hash 74 | function contractNameHash() public pure returns (bytes32) { 75 | return keccak256(abi.encodePacked(contractName())); 76 | } 77 | 78 | /// Contract version 79 | function contractVersion() public pure returns (uint256) { 80 | return 2; 81 | } 82 | 83 | function isOwner() public view returns (bool) { 84 | return msg.sender == owner; 85 | } 86 | 87 | function lock(string memory reason) public onlyOwner { 88 | require(!isLocked(), "Can not lock again!"); 89 | 90 | locked = true; 91 | lockReason = reason; 92 | 93 | emit Locked(msg.sender, getLockReason()); 94 | } 95 | 96 | function isLocked() public view returns (bool) { 97 | return locked; 98 | } 99 | 100 | function getLockReason() public view returns (string memory) { 101 | return lockReason; 102 | } 103 | 104 | function getCreator() public view returns (address) { 105 | return creator; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/js_v3/mgik/mgd2019.js: -------------------------------------------------------------------------------- 1 | $.validator.addMethod('dictrict', function (value, element, params) { 2 | if (! params) { 3 | return true; 4 | } 5 | 6 | var allowedDistricts = params.split(';'); 7 | var currentDistrict = $('.DistrictInput').val().replace(/\s/g, ''); 8 | 9 | $.validator.messages.dictrict = 'Для указанного района не доступен формат электронного дистанционного голосования'; 10 | 11 | if (! currentDistrict || allowedDistricts.indexOf(currentDistrict) === -1) { 12 | return false; 13 | } 14 | 15 | return true; 16 | }); 17 | 18 | $(document).ready(function () { 19 | 20 | $.checkBrowser({target: '#form_element'}); 21 | 22 | var isOkButtonWasPressed = false; 23 | var confirmPopupContent = OPR.templater('address_change_confirm', []); 24 | var elkUnad = window.lkProfile.unad, 25 | elkUnom = window.lkProfile.unom, 26 | elkFlat = window.lkProfile.flat; 27 | 28 | var formController = new FormController(0, { 29 | skipAgreement: true, 30 | useSendingPopup: true, 31 | useHitcounter: true, 32 | finishButtonText: 'Отправить заявление' 33 | }); 34 | 35 | formController.addValidator({ 36 | step: 1, 37 | next: function() { 38 | if (isAddressWasChanged() && ! isOkButtonWasPressed) { 39 | messagebox('Изменение адреса регистрации', confirmPopupContent, null, null); 40 | 41 | return false; 42 | } else { 43 | return true; 44 | } 45 | } 46 | }); 47 | 48 | ELK.ready(function () { 49 | ELK.fill($('#form_element')); 50 | }); 51 | 52 | $(document).on('click', '.js-change-address', function() { 53 | var $form = $('#kladr_1'); 54 | var regAddress = { 55 | REG_ADDRESS: { 56 | CORPUSNO: $('.CorpusInput', $form).val(), 57 | DISTRICTLABEL: $('.DistrictInput', $form).val(), 58 | FLAT: $('.FlatInput', $form).val(), 59 | HOUSENO: $('.HouseInput', $form).val(), 60 | STREET: $('.StreetInput', $form).val(), 61 | STREETNAME: $('.StreetInput', $form).val(), 62 | STROENIENO: $('.StroenieInput', $form).val(), 63 | UNAD: $('.UnadInput', $form).val(), 64 | UNOM: $('.UnomInput', $form).val(), 65 | } 66 | }; 67 | 68 | ELK.saveUserProfileData({ 69 | block: 'REG_ADDRESS', 70 | data: regAddress, 71 | done: function (data) {}, 72 | error: function (data) {}, 73 | }); 74 | 75 | isOkButtonWasPressed = true; 76 | formController.advanceNext(); 77 | 78 | return false; 79 | }); 80 | 81 | $(document).on('click', '.js-change-address-cancel', function() { 82 | $('.popup_messagebox_shadow').fadeOut('fast'); 83 | $('.popup_messagebox').fadeOut('fast'); 84 | 85 | return false; 86 | }); 87 | 88 | /** 89 | * @returns {boolean} 90 | */ 91 | function isAddressWasChanged() { 92 | var formUnom = $('.UnomInput').val(); 93 | var formUnad = $('.UnadInput').val(); 94 | var formFlat = $('.FlatInput').val(); 95 | 96 | if (formUnad) { 97 | if (formUnad !== elkUnad || formUnom !== elkUnom || formFlat !== elkFlat) { 98 | return true; 99 | } 100 | } else { 101 | if (formUnom !== elkUnom || formFlat !== elkFlat) { 102 | return true; 103 | } 104 | } 105 | 106 | return false; 107 | } 108 | 109 | }); 110 | -------------------------------------------------------------------------------- /voting-form/src/forms/forms/mgik/mgd2019/std_person_document.tpl: -------------------------------------------------------------------------------- 1 | {if !isset($autocomplete)}{$autocomplete = false}{/if} 2 | {if !isset($child)}{$child = false}{/if} 3 | {if !$defaultDocType}{$defaultDocType=1}{/if} 4 | {if !$contact}{$contact = "declarant"}{/if} 5 | {if !$docTypeList}{$docTypeList = "passportOnly"}{/if} 6 | {if $docTypeList == 'full' && !isset($document_types)} 7 | {array vars="array( 8 | '1' => 'Паспорт гражданина РФ', 9 | '2' => 'Военный билет', 10 | '3' => 'Удостоверение личности офицера', 11 | '4' => 'Справка об освобождении из мест лишения свободы', 12 | '5' => 'Паспорт иностранного образца', 13 | '6' => 'Паспорт моряка', 14 | '7' => 'Вид на жительство в РФ', 15 | '8' => 'Удостоверение беженцев РФ', 16 | '9' => 'Временное удостоверение личности гражданина РФ', 17 | '10' => 'Разрешение на временное проживание', 18 | '11' => 'Документ, удостоверяющий личность лица без гражданства', 19 | '12' => 'Документ, удостоверяющий личность иностранного гражданина', 20 | '13' => 'Паспорт гражданина СССР', 21 | '14' => 'Свидетельство о регистрации ходатайства иммигранта о признании его беженцем', 22 | '15' => 'Удостоверение личности моряка', 23 | '16' => 'Заграничный паспорт', 24 | '17' => 'Свидетельство о рождении', 25 | '18' => 'Свидетельство о рождении иностранного образца', 26 | '19' => 'Удостоверение личности военнослужащего' 27 | )" 28 | assign="document_types"} 29 | {/if} 30 | {if !$wrapper}{$wrapper = "fieldset"}{/if} 31 | 32 | <{$wrapper} {if $autocomplete}data-autocomplete{/if} class="{if $wrapper=="fieldset"}form-block {/if}person-document {$contact}_passport_data{if isset($container_class)} {$container_class}{/if}"{if isset($container_id)} id="{$container_id}{$postfix}"{/if}> 33 | {if isset($title)&&$title} 34 | {$title} 35 | {/if} 36 | {if $portalid} 37 | {if $contact=='declarant'||$contact=='trustee'||$contact=='account'} 38 | 39 | {else} 40 | )/":"$1"|regex_replace:"/.*?\.([0-9]+)/":"$1"}.contact.new_portalid]" value="{$portalid}"> 41 | {/if} 42 | {/if} 43 | {if $send_index}{/if} 44 | {block name="add_blocks"}{/block} 45 | {if $docTypeList == 'full'} 46 | {include file="$base_template_path/std_blocks/std_select.tpl" label="Тип документа" required=true class="valid doc-type-select save_passport_rf" container_class="valid {$doc_type_select_class}" items=$document_types name="field[{$contact}.new_doctype{$postfix}]" value=$defaultDocType no_empty=true} 47 |
48 | {include file="$base_template_path/mgik/mgd2019/std_person_passport.tpl" child=$child contact="{$contact}" no_ovdcode={$no_ovdcode} no_birthday={$no_birthday} no_photo={$no_photo} one_photo={$one_photo} free_photo={$free_photo} autocomplete=$autocomplete} 49 |
50 | {else} 51 | {include file="$base_template_path/std_blocks/std_label.tpl" container_class='doc-type-select-label' required=true label="Тип документа" name="field[{$contact}.new_doctype{$postfix}]" value="1" text="Паспорт гражданина РФ"} 52 |
53 | {include file="$base_template_path/mgik/mgd2019/std_person_passport.tpl" 54 | child=$child no_place_validator="main_ru" 55 | contact="{$contact}" 56 | no_ovdcode={$no_ovdcode} 57 | no_birthday={$no_birthday} 58 | no_photo={$no_photo} 59 | one_photo={$one_photo} 60 | free_photo={$free_photo} 61 | autocomplete=$autocomplete 62 | disabled=$disabled 63 | document_container_class=$document_container_class 64 | } 65 |
66 | {/if} 67 | 68 | -------------------------------------------------------------------------------- /smart-contracts/packages/voting-lib/src/contracts/votersRegistry.js: -------------------------------------------------------------------------------- 1 | const ethers = require('ethers'); 2 | 3 | const { 4 | VotersRegistry: { abi, bytecode }, 5 | } = require('smart-contracts'); 6 | 7 | const { txReceiptParseLogs } = require('../helpers'); 8 | 9 | const Ownable = require('./ownable.js'); 10 | 11 | class VotersRegistry extends Ownable { 12 | constructor(contract) { 13 | super(); 14 | 15 | this.contract = contract; 16 | } 17 | 18 | // Fields 19 | get address() { 20 | return this.contract.address; 21 | } 22 | 23 | // Getters 24 | async isRegistrationStopped() { 25 | return this.contract.isRegistrationStopped(); 26 | } 27 | 28 | async getVotersCount() { 29 | return this.contract.getVotersCount(); 30 | } 31 | 32 | /** 33 | * Returns map with issued ballots amount by each voting 34 | * @return {Promise{Object}} - promise resolved with map votingId => issuedBallotsCount 35 | */ 36 | async getIssuedBallotsByVotingsCount() { 37 | return this.contract.getIssuedBallotsByVotingsCount() 38 | .then(([votingsIds, ballotsCount]) => { // eslint-disable-line arrow-body-style 39 | return votingsIds.reduce((votingsMap, votingId, index) => { 40 | // eslint-disable-next-line no-param-reassign 41 | votingsMap[votingId.toNumber()] = ballotsCount[index].toNumber(); 42 | return votingsMap; 43 | }, {}); 44 | }); 45 | } 46 | 47 | async isAnyBallotIssued(voterId) { 48 | return this.contract.isAnyBallotIssued(voterId); 49 | } 50 | 51 | async getParticipationFor(voterId) { 52 | const [isParticipating, paricipationReceivedBlock] = await this.contract.getParticipationFor( 53 | voterId, 54 | ); 55 | 56 | return { isParticipating, paricipationReceivedBlock }; 57 | } 58 | 59 | async getRevocationFor(voterId) { 60 | const [isParticipationRevoked, revocationReceivedBlock] = await this.contract.getRevocationFor( 61 | voterId, 62 | ); 63 | 64 | return { isParticipationRevoked, revocationReceivedBlock }; 65 | } 66 | 67 | async getBallotFor(voterId, votingId) { 68 | const [isBallotIssued, ballotIssuedBlock] = await this.contract.getBallotFor(voterId, votingId); 69 | 70 | return { isBallotIssued, ballotIssuedBlock }; 71 | } 72 | 73 | /** 74 | * Returns parsed events from transaction receipt 75 | * @param {Object} txReceipt - transaction receipt. Tx should be sent to this contract 76 | * @return {Object[]} - array of events in this transaction 77 | */ 78 | // eslint-disable-next-line class-methods-use-this 79 | getEventsFromTransaction(txReceipt) { 80 | return txReceiptParseLogs(txReceipt, { abi, bytecode }); 81 | } 82 | 83 | // "Setters" 84 | async stopRegistration() { 85 | const tx = await this.contract.stopRegistration(); 86 | 87 | return tx.wait(); 88 | } 89 | 90 | async addVoter(voterId) { 91 | const tx = await this.contract.addVoter(voterId); 92 | 93 | return tx.wait(); 94 | } 95 | 96 | async addVotersBatch(votersIds) { 97 | const tx = await this.contract.addVotersBatch(votersIds); 98 | 99 | return tx.wait(); 100 | } 101 | 102 | async revokeParticipation(voterId) { 103 | const tx = await this.contract.revokeParticipation(voterId); 104 | 105 | return tx.wait(); 106 | } 107 | 108 | async issueBallotFor(voterId, votingId) { 109 | const tx = await this.contract.issueBallotFor(voterId, votingId); 110 | 111 | return tx.wait(); 112 | } 113 | 114 | // Statics 115 | static async at(address, signerAccount) { 116 | const deployedContract = new ethers.Contract(address, abi, signerAccount); 117 | 118 | await deployedContract.deployed(); 119 | 120 | return new VotersRegistry(deployedContract); 121 | } 122 | 123 | static async deploy(signerAccount) { 124 | const factory = new ethers.ContractFactory(abi, bytecode, signerAccount); 125 | 126 | const contract = await factory.deploy(); 127 | 128 | await contract.deployed(); 129 | 130 | return new VotersRegistry(contract); 131 | } 132 | } 133 | 134 | module.exports = VotersRegistry; 135 | -------------------------------------------------------------------------------- /smart-contracts/packages/crypto-lib/src/elGamalPartitioner.js: -------------------------------------------------------------------------------- 1 | const Shamir = require('secrets.js-grempe'); 2 | const RSA = require('node-rsa'); 3 | 4 | const ElGamal = require('./elGamal'); 5 | const { checkEveryFieldIsSame } = require('./utils'); 6 | 7 | class ElGamalPartitioner { 8 | static partition( 9 | elGamalInstance, 10 | partsCount = 5, 11 | partsThreshold = 3, 12 | padLength = 1024, 13 | rsaKeyLength = 4096, 14 | ) { 15 | if (!(elGamalInstance instanceof ElGamal)) { 16 | throw new Error('Only instances of ElGamal can be partitioned!'); 17 | } 18 | 19 | if (partsCount < 2) { 20 | throw new Error('Parts Count can not be less than 2!'); 21 | } 22 | 23 | if (partsCount > 255) { 24 | throw new Error('Parts Count can not be more than 255!'); 25 | } 26 | 27 | if (partsThreshold > partsCount) { 28 | throw new Error('Parts Threshold can not be more than Parts Count!'); 29 | } 30 | 31 | if (partsThreshold < 1) { 32 | throw new Error('Parts Threshold can not less than 1!'); 33 | } 34 | 35 | if (padLength > 1024) { 36 | throw new Error('Pad Length can not be more than 1024!'); 37 | } 38 | 39 | const rsaKey = new RSA({ key: rsaKeyLength }); 40 | const signaturePublicKey = Buffer.from(rsaKey.exportKey('public')).toString('base64'); 41 | 42 | // Cryptosystem fields to recreate it later 43 | const modulePAsString = elGamalInstance.moduleP.toString(); 44 | const generatorGAsString = elGamalInstance.generatorG.toString(); 45 | const publicKeyAsString = elGamalInstance.publicKey.toString(); 46 | 47 | // Data to be partitioned 48 | const privateKeyAsString = elGamalInstance.privateKey.toString(); 49 | const privateKeyHash = rsaKey.sign(privateKeyAsString, 'base64', 'base64').toString(); 50 | 51 | /** 52 | * @type Array 53 | */ 54 | const parts = Shamir.share(privateKeyAsString, partsCount, partsThreshold, padLength); 55 | 56 | return parts.map(privateKeyPart => ({ 57 | partsCount, 58 | partsThreshold, 59 | moduleP: modulePAsString, 60 | generatorG: generatorGAsString, 61 | publicKey: publicKeyAsString, 62 | privateKeyPart, 63 | privateKeyPartHash: rsaKey.sign(privateKeyPart, 'base64', 'base64').toString(), 64 | privateKeyHash, 65 | signaturePublicKey, 66 | })); 67 | } 68 | 69 | static combine(hashedParts) { 70 | if (hashedParts.length === 0) { 71 | throw new Error('Hashed Parts is Empty!'); 72 | } 73 | 74 | if (hashedParts.length > hashedParts[0].partsCount) { 75 | throw new Error('Too many Hashed Parts!'); 76 | } 77 | 78 | if (hashedParts.length < hashedParts[0].partsThreshold) { 79 | throw new Error('Not enough Hashed Parts!'); 80 | } 81 | 82 | checkEveryFieldIsSame(hashedParts, 'partsCount'); 83 | checkEveryFieldIsSame(hashedParts, 'partsThreshold'); 84 | checkEveryFieldIsSame(hashedParts, 'moduleP'); 85 | checkEveryFieldIsSame(hashedParts, 'generatorG'); 86 | checkEveryFieldIsSame(hashedParts, 'publicKey'); 87 | checkEveryFieldIsSame(hashedParts, 'privateKeyHash'); 88 | checkEveryFieldIsSame(hashedParts, 'signaturePublicKey'); 89 | 90 | const rsaKey = new RSA(); 91 | const signaturePublicKey = Buffer.from(hashedParts[0].signaturePublicKey, 'base64'); 92 | 93 | rsaKey.importKey(signaturePublicKey, 'public'); 94 | 95 | const parts = hashedParts.map((partObj) => { 96 | // eslint-disable-next-line max-len 97 | if (!rsaKey.verify(partObj.privateKeyPart, partObj.privateKeyPartHash, 'base64', 'base64')) { 98 | throw new Error(`Hashed Part ${partObj.privateKeyPart} hash is wrong!`); 99 | } 100 | 101 | return partObj.privateKeyPart; 102 | }); 103 | 104 | const rebuiltPrivateKey = Shamir.combine(parts); 105 | // eslint-disable-next-line max-len 106 | const isValidPrivateKeyHash = hashedParts.every(part => rsaKey.verify(rebuiltPrivateKey, part.privateKeyHash, 'base64', 'base64')); 107 | 108 | if (!isValidPrivateKeyHash) { 109 | throw new Error('One of Hashed Parts has wrong Private Key hash!'); 110 | } 111 | 112 | const { moduleP, generatorG, publicKey } = hashedParts[0]; 113 | 114 | return ElGamal.buildWithPrivateKey(moduleP, generatorG, publicKey, rebuiltPrivateKey); 115 | } 116 | } 117 | 118 | module.exports = ElGamalPartitioner; 119 | -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/js_v3/mgik/landing.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | var $candidatesBox = $('.js-candidates-box'); 3 | var $districtsBox = $('.js-districts-box', $candidatesBox); 4 | var $districtsList = $('.js-districts-list', $districtsBox); 5 | var $deputiesBox = $('.js-deputies-box', $candidatesBox); 6 | var $deputiesList = $('.js-deputies-list', $deputiesBox); 7 | var $districtName = $('.js-district-name', $deputiesBox); 8 | var $loader = $('.js-loader'); 9 | 10 | var isShowDistrictName = true; 11 | var isShowQuestion = false; 12 | var questions = {}; 13 | 14 | var districts = {}; 15 | var deputies = {}; 16 | var currentDistrict; 17 | 18 | var formatDeputies = function (data) { 19 | $.each(data, function (districtId, items) { 20 | var districtDeputies = []; 21 | 22 | $.each(items, function (id, item) { 23 | var data = item.split('|'); 24 | 25 | if (data.length === 1) { 26 | districts[districtId] = { 27 | id: districtId, 28 | name: data[0] 29 | }; 30 | 31 | if (! currentDistrict) { 32 | currentDistrict = districtId; 33 | } 34 | 35 | } else { 36 | districtDeputies.push({ 37 | id: data[0], 38 | lastName: data[1], 39 | firstName: data[2], 40 | middleName: data[3], 41 | photo: data[4], 42 | desc: data[5], 43 | descFull: data[6], 44 | income: data[7], 45 | }); 46 | } 47 | }); 48 | 49 | deputies[districtId] = districtDeputies; 50 | }); 51 | }; 52 | 53 | var renderDistricts = function () { 54 | var html = ''; 55 | 56 | $.each(districts, function (index, item) { 57 | var classes = 'js-district-link' + (currentDistrict === item.id ? ' active' : ''); 58 | 59 | html = html + '
  • ' + item.name + '
  • '; 60 | }); 61 | 62 | return html; 63 | }; 64 | 65 | var renderDeputies = function (districtId) { 66 | var html = ''; 67 | 68 | $.each(deputies[districtId], function (index, item) { 69 | html = html + OPR.templater('deputy', item); 70 | }); 71 | 72 | return html; 73 | }; 74 | 75 | var updateDistricts = function () { 76 | $loader.hide(); 77 | 78 | if (Object.keys(districts).length === 1) { 79 | $districtsBox.hide(); 80 | $deputiesBox.removeClass('col-md-9').addClass('col-md-12'); 81 | } 82 | 83 | $districtsList.html(renderDistricts()); 84 | }; 85 | 86 | var updateDeputies = function (districtId) { 87 | if (currentDistrict == districtId) { 88 | return false; 89 | } 90 | 91 | if (districtId) { 92 | currentDistrict = districtId; 93 | } 94 | 95 | if (isShowDistrictName) { 96 | $districtName.text(districts[currentDistrict].name); 97 | } 98 | 99 | if (isShowQuestion && questions) { 100 | $districtName.text(questions[currentDistrict]); 101 | } 102 | 103 | $deputiesList.hide() 104 | .html(renderDeputies(currentDistrict)) 105 | .fadeIn('fast'); 106 | }; 107 | 108 | $.checkBrowser({ 109 | target: '.js-browser-check', 110 | loader: '.js-browser-loader', 111 | successTemplate: 'browser_success_template' 112 | }); 113 | 114 | $.ajax({ 115 | url: window.mgikDeputiesUrl, 116 | type: 'get', 117 | success: function (data) { 118 | if (data.error !== 0 || ! data.result) { 119 | return false; 120 | } 121 | 122 | formatDeputies(data.result); 123 | 124 | updateDistricts(); 125 | updateDeputies(); 126 | $candidatesBox.fadeIn('fast'); 127 | }, 128 | error: function () { 129 | 130 | } 131 | }); 132 | 133 | $candidatesBox.on('click', '.js-district-link', function () { 134 | var $this = $(this); 135 | var districtId = $this.data('id'); 136 | 137 | $('.js-district-link').removeClass('active'); 138 | $this.toggleClass('active'); 139 | 140 | updateDeputies(districtId); 141 | 142 | return false; 143 | }); 144 | 145 | }); -------------------------------------------------------------------------------- /voting-form/src/ballot/common/module/election/TaskRequest.php: -------------------------------------------------------------------------------- 1 | client = $client; 36 | $this->fields = $fields; 37 | $this->app = $app; 38 | 39 | $this->config = PoolConfig::me()->conf('Mgik')->get('amqp'); 40 | } 41 | 42 | /** 43 | * @return string 44 | */ 45 | abstract public function queueName(); 46 | 47 | /** 48 | * @return array 49 | */ 50 | abstract public function asArray(); 51 | 52 | /** 53 | * @return string 54 | */ 55 | public function asJson() 56 | { 57 | return json_encode($this->asArray(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); 58 | } 59 | 60 | /** 61 | * @return array 62 | */ 63 | public function asTaskData() 64 | { 65 | return [ 66 | 'eno' => $this->app['REG_NUM'] ?? null, 67 | 'queue' => $this->queueName(), 68 | 'json' => $this->asJson(), 69 | ]; 70 | } 71 | 72 | /** 73 | * Добавляет задачу в TaskManager 74 | * @return void 75 | */ 76 | public function addQueueTask() 77 | { 78 | // $config = PoolConfig::me()->conf('Mgik')->get('amqp'); 79 | // $broker = new BrokerAmqp($config['host'], $config['port'], $config['login'], $config['password'],null, $config['vhost']); 80 | // $data = $this->asTaskData(); 81 | // $result = $broker->send( 82 | // $data['queue'], 83 | // $data['json'], 84 | // ['persistent' => 'true'] 85 | // ); 86 | 87 | $data = $this->asTaskData(); 88 | 89 | $result = TaskManager::queueTask( 90 | $this->client['PGU_USER_ID'], 91 | $this->plugin, 92 | $data, 93 | [ 94 | 'execute_now' => true, 95 | 'store_in_buffer' => false, 96 | 'app_id' => $this->app['APP_ID'] ?? null, 97 | ] 98 | ); 99 | 100 | $logger = GrayLogger::create('MgicTaskManager'); 101 | 102 | $log = [ 103 | 'result' => $result, 104 | 'action' => $this->plugin, 105 | 'jsonRequest' => json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 106 | 'user_id' => $this->client['PGU_USER_ID'] ?? null, 107 | 'app_id' => $this->app['APP_ID'] ?? null, 108 | 'eno' => $this->app['REG_NUM'] ?? null, 109 | 'data' => $this->asJson(), 110 | ]; 111 | 112 | if (! empty($result) && $result != 'OK') { 113 | $logger->error('Не удалось отправить заявку', $log); 114 | } else { 115 | $logger->info('Заявка отправлена', $log); 116 | } 117 | } 118 | 119 | /** 120 | * @param string $date Дата 121 | * @param string $format Формат даты. По умолчанию 'd.m.Y' 122 | * @return string Дата в формате 'Y-m-d H:i:s.000' 123 | */ 124 | protected function formatAsDate($date, $format = 'd.m.Y') 125 | { 126 | $dt = \DateTime::createFromFormat($format, $date); 127 | 128 | if (! $dt) { 129 | return ''; 130 | } 131 | 132 | if (str_replace(['H', 'i', 's'], '', $format) === $format) { 133 | $dt->setTime(0, 0, 0); 134 | } 135 | 136 | return $dt->format('Y-m-d H:i:s.000'); 137 | } 138 | 139 | /** 140 | * @param $string 141 | * @return string 142 | */ 143 | protected function formatAsNumber($string) 144 | { 145 | return preg_replace('/[^0-9]/', '', $string); 146 | } 147 | 148 | /** 149 | * @param string $serial_number 150 | * @return string 151 | */ 152 | protected function formatAsPassportSerial($serial_number) 153 | { 154 | return substr($this->formatAsNumber($serial_number), 0, 4); 155 | } 156 | 157 | /** 158 | * @param string $serial_number 159 | * @return string 160 | */ 161 | protected function formatAsPassportNumber($serial_number) 162 | { 163 | return substr($this->formatAsNumber($serial_number), 4, 6); 164 | } 165 | 166 | } -------------------------------------------------------------------------------- /voting-form/src/forms/common/data/lib/Form/mgik/mgd-golosovanie.class.php: -------------------------------------------------------------------------------- 1 | _config = PoolConfig::me()->conf('Mgik'); 35 | $this->_service = new Service(); 36 | 37 | $this->registerAjaxModule('mgd', new MgdAjaxHandler()); 38 | } 39 | 40 | protected function beforeShow() 41 | { 42 | $this->_service->setLogger($this->getLogger(), array_merge($this->logData, $this->getExtLogData())); 43 | parent::beforeShow(); 44 | } 45 | 46 | protected function show() 47 | { 48 | $this->smarty->assign('hide_cutalog_button', true); 49 | 50 | if (isset($_REQUEST['action']) && $_REQUEST['action'] === 'send') { 51 | return parent::show(); 52 | } 53 | 54 | try { 55 | $userData = $this->_service->checkBallot($this->client['SUDIR_ID']); 56 | 57 | if (! $userData) { 58 | return $this->getTplPath('error_user.tpl'); 59 | } 60 | 61 | $this->smarty->assign('security_js', $this->_config->get('service/security')); 62 | $this->smarty->assign('district', $userData['district']); 63 | 64 | return parent::show(); 65 | 66 | } catch (LogicException $e) { 67 | $this->logExceptionError($e); 68 | $this->smarty->assign('message', $e->getMessage()); 69 | 70 | return $this->getTplPath('error_exception.tpl'); 71 | 72 | } catch (\Exception $e) { 73 | $this->logExceptionError($e); 74 | $this->smarty->assign('message', 'Неизвестная ошибка.'); 75 | 76 | return $this->getTplPath('error_exception.tpl'); 77 | } 78 | } 79 | 80 | protected function send() 81 | { 82 | $this->smarty->assign('hide_cutalog_button', true); 83 | 84 | try { 85 | $this->isPhoneConfirmed(); 86 | 87 | $userData = $this->_service->checkBallot($this->client['SUDIR_ID']); 88 | if (! $userData) { 89 | return $this->getTplPath('error_user.tpl'); 90 | } 91 | 92 | $guid = $this->_service->getGuid($userData); 93 | $host = $this->_config->get('service/election/host'); 94 | 95 | $userData = $this->_service->getBallot($this->client['SUDIR_ID']); 96 | if (! $userData) { 97 | return $this->getTplPath('error_user.tpl'); 98 | } 99 | 100 | header("Location: $host/election/check/$guid"); 101 | die(); 102 | 103 | } catch (LogicException $e) { 104 | $this->logExceptionError($e); 105 | $this->smarty->assign('message', $e->getMessage()); 106 | 107 | return $this->getTplPath('error_exception.tpl'); 108 | 109 | } catch (\Exception $e) { 110 | $this->logExceptionError($e); 111 | $this->smarty->assign('message', 'Неизвестная ошибка.'); 112 | 113 | return $this->getTplPath('error_exception.tpl'); 114 | } 115 | } 116 | 117 | /** 118 | * @return bool 119 | * @throws LogicException 120 | */ 121 | protected function isPhoneConfirmed() 122 | { 123 | $key = "sms|confirmed|" . $this->client['SUDIR_ID']; 124 | $result = MemoryCache::get($key); 125 | 126 | if ($result !== true) { 127 | throw new LogicException('Телефон не был подтвержден', [ 128 | 'errorMessage' => 'В кеше отсутствует запись о подтверждении телефона' 129 | ]); 130 | } 131 | 132 | MemoryCache::delete($key); 133 | 134 | return $result; 135 | } 136 | 137 | } -------------------------------------------------------------------------------- /voting-form/src/forms/common/data/lib/Form/mgik/mgd/TaskRequest.php: -------------------------------------------------------------------------------- 1 | app = $app; 37 | $this->client = $client; 38 | $this->fields = $fields; 39 | 40 | $this->config = PoolConfig::me()->conf('Mgik')->get('amqp'); 41 | } 42 | 43 | /** 44 | * @param array $profile 45 | */ 46 | public function setProfile(array $profile = []) 47 | { 48 | $this->profile = $profile; 49 | } 50 | 51 | /** 52 | * @return string 53 | */ 54 | abstract public function queueName(); 55 | 56 | /** 57 | * @return array 58 | */ 59 | abstract public function asArray(); 60 | 61 | /** 62 | * @return string 63 | */ 64 | public function asJson() 65 | { 66 | return json_encode($this->asArray(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); 67 | } 68 | 69 | /** 70 | * @return array 71 | */ 72 | public function asTaskData() 73 | { 74 | return [ 75 | 'eno' => $this->app['REG_NUM'] ?? null, 76 | 'queue' => $this->queueName(), 77 | 'json' => $this->asJson(), 78 | ]; 79 | } 80 | 81 | /** 82 | * Добавляет задачу в TaskManager 83 | * @return void 84 | */ 85 | public function addQueueTask() 86 | { 87 | $result = TaskManager::queueTask( 88 | $this->client['PGU_USER_ID'], 89 | $this->plugin, 90 | $this->asTaskData(), 91 | [ 92 | 'execute_now' => true, 93 | 'store_in_buffer' => false, 94 | 'app_id' => $this->app['APP_ID'] ?? null, 95 | ] 96 | ); 97 | 98 | if (! empty($result) && $result != 'OK') { 99 | $logger = LoggerPool::create('MgicTaskManager ['. CFG_HOST_NAME .']', 'graylog'); 100 | $logger->error('Не удалось отправить заявку', [ 101 | 'result' => $result, 102 | 'action' => $this->plugin, 103 | 'user_id' => $this->client['PGU_USER_ID'], 104 | 'app_id' => $this->app['APP_ID'] ?? null, 105 | 'eno' => $this->app['REG_NUM'] ?? null, 106 | 'data' => $this->asJson(), 107 | ]); 108 | } 109 | } 110 | 111 | /** 112 | * @param string $name 113 | * @param mixed $default 114 | * @return mixed 115 | */ 116 | protected function field($name, $default = null) 117 | { 118 | $value = $this->fields[$name] ?? null; 119 | 120 | return $value ? $value : $default; 121 | } 122 | 123 | /** 124 | * @param string $date Дата 125 | * @param string $format Формат даты. По умолчанию 'd.m.Y' 126 | * @return string|null Дата в формате 'Y-m-d H:i:s.000' 127 | */ 128 | protected function formatAsDate($date = '', $format = 'd.m.Y') 129 | { 130 | $dt = \DateTime::createFromFormat($format, $date); 131 | 132 | if (! $dt) { 133 | return null; 134 | } 135 | 136 | if (str_replace(['H', 'i', 's'], '', $format) === $format) { 137 | $dt->setTime(0, 0, 0); 138 | } 139 | 140 | return $dt->format('Y-m-d H:i:s.000'); 141 | } 142 | 143 | /** 144 | * @param $string 145 | * @return string|null 146 | */ 147 | protected function formatAsNumber($string = '') 148 | { 149 | $result = preg_replace('/[^0-9]/', '', $string); 150 | 151 | return $result ? $result : null; 152 | } 153 | 154 | /** 155 | * @param string $serial_number 156 | * @return string|null 157 | */ 158 | protected function formatAsPassportSerial($serial_number = '') 159 | { 160 | $result = substr($this->formatAsNumber($serial_number), 0, 4); 161 | 162 | return $result ? $result : null; 163 | } 164 | 165 | /** 166 | * @param string $serial_number 167 | * @return string|null 168 | */ 169 | protected function formatAsPassportNumber($serial_number = '') 170 | { 171 | $result = substr($this->formatAsNumber($serial_number), 4, 6); 172 | 173 | return $result ? $result : null; 174 | } 175 | 176 | } -------------------------------------------------------------------------------- /voting-form/src/ballot/common/module/election/UpdateDitVotingSettingsCommand.php: -------------------------------------------------------------------------------- 1 | logger = $logger; 33 | $this->config = PoolConfig::me()->conf('Mgik'); 34 | $this->service = new Service(); 35 | $this->settings = new Settings(); 36 | } 37 | 38 | /** 39 | * @throws LogicException|\Exception 40 | */ 41 | public function handle() 42 | { 43 | $this->logger->info('Начало работы'); 44 | 45 | $token = $this->service->getBlockchainAuthenticate(); 46 | $this->logger->info('Успешное получение токена', ['data' => $token]); 47 | 48 | $ballotsRegistryAddress = $this->service->getBlockchainRegistryAddress($token); 49 | $this->logger->info('Успешное получение адреса', ['data' => $ballotsRegistryAddress]); 50 | 51 | $encryptionKeys = $this->service->getBlockchainEncryptionKeys($token); 52 | $this->logger->info('Успешное получение ключей', ['data' => $encryptionKeys]); 53 | 54 | $this->settings->resetCache(); 55 | $this->createLogData($ballotsRegistryAddress, $encryptionKeys); 56 | 57 | if (! $this->isChanged($ballotsRegistryAddress, $encryptionKeys)) { 58 | $this->logger->info('Адрес и ключи совпадают (изменение не требуется)', ['data' => $this->logData]); 59 | $this->logger->info('Успешное завершение работы'); 60 | 61 | return false; 62 | } 63 | 64 | $this->settings->setArray([ 65 | 'ballotsRegistryAddress' => $ballotsRegistryAddress, 66 | 'modulo' => $encryptionKeys->modulo, 67 | 'generator' => $encryptionKeys->generator, 68 | 'publicKey' => $encryptionKeys->publicKey, 69 | ]); 70 | 71 | $this->logger->info('Адрес и ключи успешно обновлены', ['data' => $this->logData]); 72 | $this->logger->info('Успешное завершение работы'); 73 | 74 | return true; 75 | } 76 | 77 | /** 78 | * @param string $ballotsRegistryAddress 79 | * @param object $encryptionKeys 80 | * @throws \Exception 81 | */ 82 | private function createLogData(string $ballotsRegistryAddress, $encryptionKeys) 83 | { 84 | $this->logData = [ 85 | 'new' => [ 86 | 'ballotsRegistryAddress' => $ballotsRegistryAddress, 87 | 'modulo' => $encryptionKeys->modulo, 88 | 'generator' => $encryptionKeys->generator, 89 | 'publicKey' => $encryptionKeys->publicKey, 90 | ], 91 | 'old' => [ 92 | 'ballotsRegistryAddress' => $this->settings->get('ballotsRegistryAddress'), 93 | 'modulo' => $this->settings->get('modulo'), 94 | 'generator' => $this->settings->get('generator'), 95 | 'publicKey' => $this->settings->get('publicKey'), 96 | ] 97 | ]; 98 | } 99 | 100 | /** 101 | * @param string $ballotsRegistryAddress 102 | * @param object $encryptionKeys 103 | * @return bool 104 | * @throws \Exception 105 | */ 106 | private function isChanged(string $ballotsRegistryAddress, $encryptionKeys) 107 | { 108 | $currentRegistryAddress = $this->settings->get('ballotsRegistryAddress'); 109 | $currentModulo = $this->settings->get('modulo'); 110 | $currentGenerator = $this->settings->get('generator'); 111 | $currentPublicKey = $this->settings->get('publicKey'); 112 | 113 | $isChanged = false; 114 | $messages = []; 115 | 116 | if (! ($currentRegistryAddress && $currentModulo && $currentGenerator && $currentPublicKey)) { 117 | $messages[] = 'Настойки адреса или ключей отсутствуют в базе дынных'; 118 | $isChanged = true; 119 | } 120 | 121 | if ($currentRegistryAddress != $ballotsRegistryAddress) { 122 | $messages[] = 'Новый адрес отличается от старого'; 123 | $isChanged = true; 124 | } 125 | 126 | if ($currentModulo != $encryptionKeys->modulo || 127 | $currentGenerator != $encryptionKeys->generator || 128 | $currentPublicKey != $encryptionKeys->publicKey) 129 | { 130 | $messages[] = 'Новые ключи отличаются от старых'; 131 | $isChanged = true; 132 | } 133 | 134 | if ($isChanged) { 135 | $this->logger->info('Получен новый адрес или ключи (требуется изменение)', [ 136 | 'data' => $this->logData, 137 | 'info' => implode("\n", $messages) 138 | ]); 139 | } 140 | 141 | return $isChanged; 142 | } 143 | 144 | } -------------------------------------------------------------------------------- /voting-form/src/forms/common/data/lib/Form/mgik/mgd/TaskRegistrationRequest.php: -------------------------------------------------------------------------------- 1 | config['queue']['registration']; 16 | } 17 | 18 | /** 19 | * @return array 20 | */ 21 | public function asArray() 22 | { 23 | $result = []; 24 | $result['message_type'] = '1'; 25 | $result['person_data'] = [ 26 | 'birth_dt' => $this->formatAsDate($this->field('declarant.birthdate')), 27 | 'last_name' => $this->field('declarant.lastname'), 28 | 'given_name_one' => $this->field('declarant.firstname'), 29 | 'given_name_two' => $this->field('declarant.middlename'), 30 | 'gender_tp_code' => $this->field('declarant.gendercode'), 31 | 'SSO' => $this->client['SUDIR_ID'] ?? null, 32 | 'is_confirmed_offline' => true, 33 | 'mp_phone_mobile' => $this->formatAsNumber($this->field('declarant.mobilephone')), 34 | ]; 35 | 36 | if ($this->hasPassport()) { 37 | $address = $this->field('declarant.address1_postofficebox', ''); 38 | $flat = $this->field('declarant.address1_line3', ''); 39 | 40 | $result['person_data']['reg_address'] = [ 41 | 'addr_line_one' => "$address Квартира $flat", 42 | 'unom' => $this->field('declarant.address1_unom'), 43 | 'residence_num' => $this->field('declarant.address1_line3'), 44 | 'confirmed_mfc' => $this->hasConfirmedMfc(), 45 | ]; 46 | } 47 | 48 | if ($this->hasPassport()) { 49 | $result['person_data']['documents'][] = [ 50 | 'id_tp_cd' => '1000016', 51 | 'identification_series' => $this->formatAsPassportSerial($this->field('declarant.serial_number')), 52 | 'ref_num' => $this->formatAsPassportNumber($this->field('declarant.serial_number')), 53 | 'start_dt' => $this->formatAsDate($this->field('declarant.new_passport_date2')), 54 | 'identification_issuer' => $this->field('declarant.new_passport_place2'), 55 | 'identification_issuer_code' => $this->formatAsNumber($this->field('declarant.new_divisioncode2')), 56 | 'identification_passport_birth_place' => $this->field('declarant.new_birthplace'), 57 | ]; 58 | } 59 | 60 | $result['person_data']['documents'][] = [ 61 | 'id_tp_cd' => '2000000', 62 | 'ref_num' => $this->app['REG_NUM'] ?? null, 63 | 'start_dt' => $this->formatAsDate($this->app['APP_DATE'] ?? '', 'YmdHis'), 64 | ]; 65 | 66 | $result['person_data']['documents'][] = [ 67 | 'id_tp_cd' => '1000015', 68 | 'ref_num' => $this->formatAsNumber($this->field('declarant.new_snils')), 69 | 70 | ]; 71 | 72 | return $result; 73 | } 74 | 75 | /** 76 | * @return bool 77 | */ 78 | private function hasPassport() 79 | { 80 | $serialNumber = $this->fields['declarant.serial_number'] ?? false; 81 | 82 | return (bool) $serialNumber; 83 | } 84 | 85 | /** 86 | * @return bool 87 | */ 88 | private function hasConfirmedMfc() 89 | { 90 | if (! $this->isAddressWasChanged() && ($this->isConfirmedMfc() || $this->isConfirmedHpsm())) { 91 | return true; 92 | } 93 | 94 | return false; 95 | } 96 | 97 | /** 98 | * @return bool 99 | */ 100 | private function isConfirmedMfc() 101 | { 102 | $confirmedMfc = $this->profile['REG_ADDRESS']['CONFIRMED_MFC'] ?? null; 103 | 104 | return $confirmedMfc === 'true'; 105 | } 106 | 107 | /** 108 | * @return bool 109 | */ 110 | private function isConfirmedHpsm() 111 | { 112 | $district = $this->profile['REG_ADDRESS']['DISTRICTID'] ?? null; 113 | $validationDate = $this->profile['REG_ADDRESS']['VALIDATION_DATE'] ?? null; 114 | $validationStatus = $this->profile['REG_ADDRESS']['VALIDATION_STATUS'] ?? null; 115 | 116 | if ($district && $validationDate && $validationStatus === '1') { 117 | return true; 118 | } 119 | 120 | return false; 121 | } 122 | 123 | /** 124 | * @return bool 125 | */ 126 | private function isAddressWasChanged() 127 | { 128 | $formUnad = $this->field('declarant.address1_unad'); 129 | $formUnom = $this->field('declarant.address1_unom'); 130 | $formFlat = $this->field('declarant.address1_line3'); 131 | 132 | $elkUnad = $this->profile['REG_ADDRESS']['UNAD'] ?? null; 133 | $elkUnom = $this->profile['REG_ADDRESS']['UNOM'] ?? null; 134 | $elkFlat = $this->profile['REG_ADDRESS']['FLAT'] ?? null; 135 | 136 | if ($formUnad) { 137 | if ($formUnad != $elkUnad || $formUnom != $elkUnom || $formFlat != $elkFlat) { 138 | return true; 139 | } 140 | } else { 141 | if ($formUnom != $elkUnom || $formFlat != $elkFlat) { 142 | return true; 143 | } 144 | } 145 | 146 | return false; 147 | } 148 | } -------------------------------------------------------------------------------- /voting-form/src/forms/forms/mgik/mgd2019/std_fias.tpl: -------------------------------------------------------------------------------- 1 | {if !$contact}{assign var=contact value="declarant"}{/if} 2 | {if !$addressType}{assign var=addressType value="1"}{/if} 3 | {if !$filter}{assign var=filter value="none"}{/if} 4 | {if !$type}{assign var=type value="B"}{/if} 5 | {* 6 | filter бывает вида 7 | none -все отображать default 8 | moscowFull - москва+ новая москва 9 | moscowNew - новая москва только 10 | moscow - старая москва + зеленоград 11 | *} 12 | {* 13 | type бывает вида, требует обязательного заполнения уровня справочника 14 | B - дома - default 15 | S - улицы 16 | *} 17 | {if !$limitRegionsTo}{$limitRegionsTo=[]}{/if} 18 | {if !$skipInit} 19 | {literal} 20 | 32 | 33 | {/if} 34 | 35 |
    36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
    55 |
    56 |
    57 |
    58 | {include file="$base_template_path/std_blocks/std_text.tpl" 59 | label="{if isset($label)}{$label}{else}Адрес дома{/if}" 60 | class="fiasInput" 61 | text="" 62 | id="{$id}_fiasInput" 63 | value=false container_class="" 64 | validator="notValidClass|Введен неверный адрес{if $type!='S'} с точностью до дома{/if} dictrict|{$allowed_districts}" 65 | maxlength="255" name="field[{$contact}.{$new}address{$addressType}_postofficebox]" 66 | required=true 67 | placeholder="Например, Дмитровская, 15к1" 68 | hint="Минимум 3 символа. Пример адреса: Ленинский проспект 4, строение 5" 69 | } 70 |
    71 |
    72 | {if !$skipFlat} 73 | {include file="$base_template_path/std_blocks/std_text.tpl" label="Квартира" class="FlatInput fiasField" text="" id="{$id}_Flat" value=false container_class="" maxlength="15" name="field[{$contact}.{$new}address{$addressType}_line3]" required=(!empty($validateFlat)) placeholder="Введите квартиру/офис"} 74 | {/if} 75 |
    76 |
    77 |
    78 |
    79 | 80 | {* в регламенте нет такого поля*} 81 | 82 | {if $includeOkato} 83 | 84 | {/if} 85 |
    -------------------------------------------------------------------------------- /voting-form/src/ballot/common/module_tpl/election/default/show.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | {include file="$template_path/_header.tpl" title="Выборы депутатов Московской городской думы седьмого созыва"} 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | {if !empty($security)} 15 | 16 | {/if} 17 | 18 |
    19 | 20 |
    21 | 22 |
    23 | 24 |
    25 | 26 |
    27 |
    28 |

    Избирательный бюллетень

    29 | 30 |

    31 | ДИСТАНЦИОННОГО ЭЛЕКТРОННОГО ГОЛОСОВАНИЯ НА ВЫБОРАХ
    32 | ДЕПУТАТОВ МОСКОВСКОЙ ГОРОДСКОЙ ДУМЫ СЕДЬМОГО СОЗЫВА 33 |

    34 | 35 |
    36 | 8 сентября 2019 года 37 |
    38 | 39 |

    ОДНОМАНДАТНЫЙ ИЗБИРАТЕЛЬНЫЙ ОКРУГ №{$district}

    40 |
    41 |
    42 | 43 |
    44 |
    45 |

    Разъяснение о порядке голосования

    46 |
    47 | Время, выделенное на голосование 15 минут. По истечении выделенного времени проголосовать будет невозможно.
    48 | Поставьте отметку в квадрате справа от фамилии только одного зарегистрированного кандидата, в пользу которого сделан выбор. 49 | Для подтверждения сделанного выбора и окончания процесса голосования необходимо нажать кнопку "Проголосовать" 50 |
    51 |
    52 |
    53 | 54 |
    55 |
    56 | {foreach from=$deputies key=key item=deputy} 57 |
    58 |
    59 | 60 |
    61 | 65 | 66 |
    67 | 68 |
    69 |
    70 |
    {$deputy.last_name}
    {$deputy.first_name} {$deputy.middle_name}
    71 |
    72 |
    73 | {$deputy.description} 74 |
    75 |
    76 | 77 |
    78 |
    79 | {/foreach} 80 |
    81 |
    82 | 83 |
    84 | 85 | 86 | 87 | 88 |
    89 | 90 |
    91 | 92 |
    93 |

    94 |
    95 | 96 |
    97 | 98 |
    99 | 100 | 106 | 107 |
    108 |
    109 | Если Вы покинете страницу, Вы не сможете проголосовать. 110 |
    111 |
    112 | 113 | 114 | -------------------------------------------------------------------------------- /voting-form/src/forms/common/htdocs/forms/js_v3/mgik/check.browser.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | var options = {}; 4 | var errors = []; 5 | var parser = new UAParser(); 6 | var browser = parser.getBrowser(); 7 | 8 | var isModernBrowser = false; 9 | var isEncryptionWorks = false; 10 | 11 | var checkVersion = function () { 12 | var browserName = browser.name.toLowerCase(); 13 | var browserVersion = browser.major; 14 | var result = false; 15 | 16 | $.each(options.allow, function (allowName, allowVersion) { 17 | if (browserName === allowName && browserVersion >= allowVersion) { 18 | result = true; 19 | 20 | return false; 21 | } 22 | }); 23 | 24 | return result; 25 | }; 26 | 27 | var checkCrypt = function () { 28 | try { 29 | var entropy = Math.floor( 1234 * (Math.floor(new Date() / 1000)) ); 30 | var encryptor = new window.ditVoting.ElGamal(options.modulo, options.generator, options.publicKey); 31 | var signer = new window.ditVoting.TransactionSigner(); 32 | 33 | return signer.initContract(options.ballotRegistryAddress).then(function () { 34 | return signer.getSignedTransaction(options.votingId, options.dataToEncrypt, entropy, encryptor); 35 | }).then(function (rawTX) { 36 | var address = signer.getAccountAddress(); 37 | var hash = encryptor.getKeyVerificationHash(); 38 | 39 | if (rawTX && address && hash) { 40 | return true; 41 | } 42 | 43 | return false; 44 | 45 | }).catch(function (error) { 46 | return false; 47 | }); 48 | 49 | } catch(e) { 50 | return false; 51 | } 52 | }; 53 | 54 | var result = function (status) { 55 | var $loader = $(options.loader); 56 | var $target = $(options.target); 57 | var template; 58 | 59 | if ($loader) { 60 | $loader.hide(); 61 | } 62 | 63 | if (isModernBrowser && isEncryptionWorks) { 64 | if (options.successTemplate) { 65 | template = OPR.templater(options.successTemplate, {}); 66 | } 67 | } else { 68 | if (options.errorTemplate) { 69 | template = OPR.templater(options.errorTemplate, { 70 | isModernBrowser: isModernBrowser, 71 | isEncryptionWorks: isEncryptionWorks 72 | }); 73 | } 74 | 75 | logError(); 76 | } 77 | 78 | if (template) { 79 | $target.html(template); 80 | } 81 | 82 | $target.fadeIn('fast'); 83 | 84 | return isModernBrowser && isEncryptionWorks; 85 | }; 86 | 87 | var logError = function() { 88 | var error = []; 89 | 90 | if (! isModernBrowser) { 91 | errors.push('Версия браузера устарела'); 92 | } 93 | 94 | if (! isEncryptionWorks) { 95 | errors.push('Ошибка шифрования в браузере'); 96 | } 97 | 98 | $.ajax({ 99 | url: cfgMainHost + '/ru/app/mgik/mgd2019/?ajaxModule=mgd&ajaxAction=log', 100 | method: 'post', 101 | data: { 102 | log: { 103 | error: errors.join(' и '), 104 | isModernBrowser: isModernBrowser, 105 | isEncryptionWorks: isEncryptionWorks, 106 | browser: browser, 107 | allow: options.allow 108 | } 109 | } 110 | }); 111 | }; 112 | 113 | $.checkBrowser = function (opt) { 114 | options = $.extend({}, $.checkBrowser.defaults, opt); 115 | 116 | isModernBrowser = checkVersion(); 117 | 118 | try { 119 | return checkCrypt().then(function (checkCryptResult) { 120 | isEncryptionWorks = checkCryptResult; 121 | 122 | return result(); 123 | }); 124 | } catch (e) { 125 | return result(); 126 | } 127 | }; 128 | 129 | $.checkBrowser.defaults = { 130 | loader: '.js-loader', 131 | errorTemplate: 'browser_errors_template', 132 | successTemplate: '', 133 | allow: { 134 | 'chrome': 37, 135 | 'yandex': 18, 136 | 'firefox': 34, 137 | 'opera': 24, 138 | 'edge': 12, 139 | 'safari': 11, 140 | 'mobile safari': 11 141 | }, 142 | votingId: 1, 143 | dataToEncrypt: 42, 144 | ballotRegistryAddress: '0xEc125529358FAF12Db8d9011c436FDd366C3c57F', 145 | modulo: '146570125332794430542818431487733773806445617910805576681149223214515926515262344449800205240321052888756776899073115676322199498416392708781288575864811600918242614466003206600150496684000188399730466527726818579732721375724514648843247384550519643901843281193538127050405451310812425466119354395937435723963', 146 | generator: '51485467714157181686957437925033977273931460391056172036554775203931631465270206203402107913383811463538076053469067553943003916925222036013186843031240314832311012753998386766013230324015500372022104138209548710911021144186418000301820195612465143572987234804341944496905743918171709905612170964166439147779', 147 | publicKey: '89561115595849849871212801772140896801544244852380268144766865424391551858413980304317773345761511001991772833045048640658973810891326201766126501621920988674652049039313774711073092021084184281639063209808216438926777433371839132484031959511138202274700402983229844754677626217079288260543070252921289635039' 148 | }; 149 | 150 | })(jQuery); -------------------------------------------------------------------------------- /smart-contracts/packages/crypto-lib/src/elGamal.js: -------------------------------------------------------------------------------- 1 | // Based on MIT licensed work of Kristóf Poduszló in 2016 https://github.com/kripod/elgamal.js 2 | const ethers = require('ethers'); 3 | const { BigInteger: BigInt } = require('jsbn'); 4 | 5 | const { 6 | KEY_BIT_LENGTH, CRYPTO_MAX_INT, getRandomBigInt, trimBigInt, 7 | } = require('./utils'); 8 | 9 | 10 | class ElGamal { 11 | constructor(moduleP, generatorG, publicKey) { 12 | this.moduleP = new BigInt(moduleP.toString()); 13 | if (this.moduleP.compareTo(CRYPTO_MAX_INT) >= 0) { 14 | this.moduleP = null; 15 | throw new Error(`Basis Module P can not be bigger than ${KEY_BIT_LENGTH}-bit value!`); 16 | } 17 | 18 | this.Q = this.moduleP.subtract(BigInt.ONE).shiftRight(1); 19 | 20 | this.generatorG = new BigInt(generatorG.toString()); 21 | if (this.generatorG.compareTo(CRYPTO_MAX_INT) >= 0) { 22 | this.generatorG = null; 23 | throw new Error(`Basis Generator G can not be bigger than ${KEY_BIT_LENGTH}-bit value!`); 24 | } 25 | 26 | this.publicKey = new BigInt(publicKey.toString()); 27 | if (this.publicKey.compareTo(CRYPTO_MAX_INT) >= 0) { 28 | this.publicKey = null; 29 | throw new Error(`Public Key Key can not be bigger than ${KEY_BIT_LENGTH}-bit value!`); 30 | } 31 | 32 | this.privateKey = null; 33 | } 34 | 35 | static buildWithPrivateKey(moduleP, generatorG, publicKey, privateKey) { 36 | const elGamal = new ElGamal(moduleP, generatorG, publicKey); 37 | 38 | elGamal.setPrivateKey(privateKey); 39 | 40 | return elGamal; 41 | } 42 | 43 | setPrivateKey(privateKey) { 44 | this.privateKey = new BigInt(privateKey.toString()); 45 | 46 | if (this.privateKey.compareTo(CRYPTO_MAX_INT) >= 0) { 47 | this.privateKey = null; 48 | throw new Error('Private Key can not be bigger than max value!'); 49 | } 50 | } 51 | 52 | toString() { 53 | return `ElGamal(moduleP: ${this.getModuleP()}, generatorG: ${this.getGeneratorG()}, publicKey: ${this.getPublicKey()}, privateKey: ${this.getPrivateKey()})`; 54 | } 55 | 56 | getModuleP() { 57 | return this.moduleP.toString(); 58 | } 59 | 60 | getGeneratorG() { 61 | return this.generatorG.toString(); 62 | } 63 | 64 | getPrivateKey() { 65 | return this.privateKey ? this.privateKey.toString() : null; 66 | } 67 | 68 | getPublicKey() { 69 | return this.publicKey.toString(); 70 | } 71 | 72 | /** 73 | * Retuns key verification hash which allows to encsure that all cryptosystem 74 | * params are set correctly 75 | * @return {String} - sha256 hash of moduleP|generatorG|publicKey 76 | */ 77 | getKeyVerificationHash() { 78 | return ethers.utils.id([ 79 | this.moduleP.toString(), 80 | this.generatorG.toString(), 81 | this.publicKey.toString(), 82 | ].join('|')); 83 | } 84 | 85 | /** 86 | * @returns {BigInt} 87 | */ 88 | getDecryptor(encryptedData) { 89 | if (!('a' in encryptedData)) { 90 | throw new Error('Can not get decryptor — "A" is not present!'); 91 | } 92 | 93 | return new BigInt(encryptedData.a.toString()) 94 | .modInverse(this.moduleP) 95 | .modPow(this.privateKey, this.moduleP); 96 | } 97 | 98 | /** 99 | * @returns {Promise<{a: string, b: string}>} 100 | */ 101 | async encrypt(data, entropy) { 102 | if (!data) { 103 | throw new Error('Data must present!'); 104 | } 105 | 106 | if (!entropy) { 107 | throw new Error('Entropy must present!'); 108 | } 109 | 110 | const dataAsBI = new BigInt(data.toString()); 111 | const entropyAsBI = new BigInt(entropy.toString()); 112 | 113 | if (dataAsBI.compareTo(this.Q) >= 0) { 114 | throw new Error('Data to encrypt can not be bigger or equal that (P-1)/2!'); 115 | } 116 | 117 | if (entropyAsBI.compareTo(BigInt.ONE) <= 0) { 118 | throw new Error('Entropy for Session Key must be Integer bigger than 1!'); 119 | } 120 | 121 | if (entropyAsBI.compareTo(this.moduleP) >= 0) { 122 | throw new Error('Entropy for Session Key can not be bigger or equal Basis Module P!'); 123 | } 124 | 125 | const randomBigInt = await getRandomBigInt(BigInt.ONE, this.moduleP.subtract(BigInt.ONE)); 126 | const xoredRandomBigInt = randomBigInt.xor(entropyAsBI); 127 | const sessionKey = trimBigInt(xoredRandomBigInt, this.moduleP.bitLength() - 1); 128 | const squaredData = dataAsBI.modPow(new BigInt('2'), this.moduleP); 129 | 130 | const a = this.generatorG.modPow(sessionKey, this.moduleP).toString(); 131 | const b = this.publicKey 132 | .modPow(sessionKey, this.moduleP) 133 | .multiply(squaredData) 134 | .remainder(this.moduleP) 135 | .toString(); 136 | 137 | // ElGamal Ciphertext, usually named (c_1, c_2) 138 | return { a, b }; 139 | } 140 | 141 | /** 142 | * @returns {Promise} 143 | */ 144 | async decrypt(encryptedData) { 145 | const decryptor = this.getDecryptor(encryptedData); 146 | 147 | const squared = new BigInt(encryptedData.b.toString()) 148 | .multiply(decryptor) 149 | .remainder(this.moduleP); 150 | 151 | const criterion = squared.modPow(this.moduleP.subtract(BigInt.ONE).divide(new BigInt('2')), this.moduleP); 152 | 153 | if (criterion.compareTo(BigInt.ONE) !== 0) { 154 | throw new Error('Data is incorrect: encrypted value should be quadratic residue moduleP'); 155 | } 156 | 157 | const decryptedValue = squared.modPow(this.moduleP.add(BigInt.ONE).divide(new BigInt('4')), this.moduleP); 158 | const decryptedValueNegative = this.moduleP.subtract(decryptedValue); 159 | 160 | return (decryptedValueNegative.compareTo(decryptedValue) > 0) 161 | ? decryptedValue.toString() 162 | : decryptedValueNegative.toString(); 163 | } 164 | } 165 | 166 | module.exports = ElGamal; 167 | -------------------------------------------------------------------------------- /encryption-keys/data/encrypted-datums.csv: -------------------------------------------------------------------------------- 1 | {"a": "75099741862293337531913733578741055229895167258658943332787931057889085050149433013052211838246723191183261495751391867930496509189170681746031976631559389712350695170884979037735794365580915240553727112919529724772221137045922111180935095192296850593213678234203753065714842216737185267000383166010966498835", "b": "86911001506497462251782638567319361833688978813664946437333829354738909404439744819279292632834869872334063264665050250274346790605838816897062326305286058138295055984777741255550170498945067604675549635835663141274356550963994173797345489306417174072514309856175754908122436241421564859178326320313204945649"} 2 | {"a": "95824950976077871393271608456400224810008188825094595844491698067459286165032477556902164076038772344043752519168932076637298439691343405714140763625300790016027963662236919449758740025544481832128759261615670362693034248074171226006426627686545675282062951622866521867388787207698744823969885743939170450419", "b": "32994578715846315625334282465389128113015193084444994471583135772127926449518921614274535705667662989798641851705206164031247974270107307075201610948340405359817499941661787769955180551913736127546566546769123076444375224889357541488942667685714188203805416972085863674686599803137288027861639262227344813980"} 3 | {"a": "46510280958790435137208037953357054790434767832826324903875390199244386097270520007562930361187779657954353733436409660126673361812074160563124961709027615656884745456015561541014905429888047764670225256472018075697311650312648691392934220506980150036574500008810622758252700470414451486845642545892807709695", "b": "25605451399106620676652873102021964641362454624148409311459772958496440670168435783159085451840777727945938309791516168198109662557095679208141307781970980669472368996913795738392317034953045148344118833747706532287151838997509598299206147956479381022563215978764100195629663712388182647511089787862332483202"} 4 | {"a": "97437177441018387334738197718848897699004417114853802243301250736350742449515672800565474094838503932237818098945550091164251313049761115869964132029263635254180959312938599418779496788202026524971859362542779028616116260761356890653837768595184562286899995030124516749141631507679424264083206014423293087018", "b": "30936197551567269685847042352240834287171756541862382295858852516666762117558059797298790230072852868807326748919890077410226333308005503687425634694123708900938179463238979856207845679644295864478950135707610820877962547470703268773776147336174270678101221755152924933175072952910690305403946708512011344065"} 5 | {"a": "81368337084711122750429588594364516561597303566248065635068710843337019449344658139318002210166164097354278533230601794351833279295829610002267240618488953142573709917196363111532191681801489652987753855546713941210417744749574155682886549234323884065318246697789017267837349989938710552314724998321360731414", "b": "90189227659365697355063500941760536836478537551461759945631823319091683131305399470434162229845802705261525937564575554855990187402432293242268496056123926044272963767175613487057669605358427303185798116851898339053864084929055706240055307151918736952456608210700937953363208336695605308414504363789714782355"} 6 | {"a": "82310609889892602271777878996800738419550389816953454735996543120346988283567116230180839095876699649730641498575713319662247523101526612486284552681331106918378090481676880406443931412301431122159827133246859757032277068139806858363821696620052751129509526689430205845097476198013071153038274810800983276899", "b": "91764714915834445310265717136195446845915020510854708634828807741642908650888052340165093420099138094287959197229266138475390790559978167881879917052624500221133644203420782690236378637668193427162338885285759230413278401533846260888398253877915981254520562872698617685705979612448346470413913994244174120780"} 7 | {"a": "100084611446219212139389624656385375668504058817891590998770562734872638895872118120300221740908126888655894447914513841677196319941661742511671780592227743015427459388380999621763274824960903436621138337721111006543762359130466421855865108751252758482607446764245820676291749224581628227753629655921659478899", "b": "53180133541691920877303393106622876213880557470163604793597655634027675133606851167683767583003388786519619556331918441255876200575005249456402393227799616594227461148863031287440218730437548530377230727786729956805232142613661312171461386140429576621530845469410809123204273518058446975266361694186911940244"} 8 | {"a": "99699819244210257352421405467820773642825697689353472458599673957587783858546927041615478553217866598023361860500476811181705257953798941365445761399117081344954029754847464114590681599166789429832855752387569842489562898748449342641891132404555073560271254809391949519209754294327503952168344318805918747694", "b": "96389110287648758509344773386657594488132549702589565012028823522666392603231743268712895346901901178272542352519420374191818168267810455905932937115562363365747923634081141969330929808282300805577394037992878891461243697630183068655120651685499248763092459000930306871431366198968873609555301941599393034947"} 9 | {"a": "99388736148196229457544300776886225194110043965920678345925492888350997021412872311349101759399554709550233076706687773820702396463125206030165331829737735260760163980060602268825814186901024731843066066668811578962921389104561639148412036030431714262212105966737530740338056359912006326989362701446608405479", "b": "6886851896871840196194756588328695767849685951608120864539139405151743060154089569868014396600078685718742310976349636761884312463762214119090170143678141116307892372626892480783711873063933988540884639378939546853097965701800706584840528069727689283919454214761611987409749455736753344803639667081357573332"} 10 | {"a": "51413967434996119322363063988792559953646603559941294365617813646232953722335914244058839912145921050282732427125215315616994672026603173000395444978066484240107665269930228260827607772350594885310306241748957943212544799964564832933451245916679657248779132679355896017290426031260511156099392808991624552429", "b": "59049331935932409191703521981449178033897833739363938803374780496048381081678526491160095374593863860325992671827318552218040035459630165455424123146739280023651401037057755563599858583753397421886557753387424403345003133365685878245562130520111649077186632157205095851334912141011894784614717824328145876601"} 11 | -------------------------------------------------------------------------------- /voting-form/src/ballot/static/js/forms/mgik/LeavingPageChecker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * When user leaves the page, LeavingPageChecker shows standart confirm message 3 | * and addititional shows custom message. 4 | */ 5 | 6 | /** 7 | * Class constructor. 8 | * 9 | * @param object callbacksList Assoc array of 4 callbacks: 10 | * callbacksList.showMessage 11 | * callbacksList.hideMessage 12 | * callbacksList.isMessageVisible 13 | * callbacksList.resizeMessageBox 14 | * callbacksList.checkLeavingAllowed 15 | * @param string message Standart browser confirm message. 16 | * @param object params Assoc array of 2 paramethers: 17 | * params.showOnKeydown 18 | * params.showOnMouseout 19 | */ 20 | function LeavingPageChecker(callbacksList, message, params) { 21 | 22 | if (callbacksList && typeof callbacksList.showMessage === 'function') { 23 | this.outerShowMessage = callbacksList.showMessage; 24 | } else { 25 | this.outerShowMessage = function() { 26 | return false; 27 | } 28 | } 29 | 30 | if (callbacksList && typeof callbacksList.hideMessage === 'function') { 31 | this.outerHideMessage = callbacksList.hideMessage; 32 | } else { 33 | this.outerHideMessage = function() { 34 | return false; 35 | } 36 | } 37 | 38 | if (callbacksList && typeof callbacksList.resizeMessageBox === 'function') { 39 | this.outerResizeMessageBox = callbacksList.resizeMessageBox; 40 | } else { 41 | this.outerResizeMessageBox = null; 42 | } 43 | 44 | if (callbacksList && typeof callbacksList.isMessageVisible === 'function') { 45 | this.outerIsMessageVisible = callbacksList.isMessageVisible; 46 | } else { 47 | this.outerIsMessageVisible = function() { 48 | return true; 49 | } 50 | } 51 | 52 | if (callbacksList && typeof callbacksList.checkLeavingAllowed === 'function') { 53 | this.outerCheckLeavingAllowed = callbacksList.checkLeavingAllowed; 54 | } else { 55 | this.outerCheckLeavingAllowed = function() { 56 | return false; 57 | } 58 | } 59 | 60 | this.message = message; 61 | 62 | this.params = {}; 63 | if (params && typeof params.showOnKeydown !== 'undefined') { 64 | this.params.showOnKeydown = params.showOnKeydown; 65 | } else { 66 | this.params.showOnKeydown = true; 67 | } 68 | if (params && typeof params.showOnMouseout !== 'undefined') { 69 | this.params.showOnMouseout = params.showOnMouseout; 70 | } else { 71 | this.params.showOnMouseout = true; 72 | } 73 | 74 | this.hideTime = 0; 75 | this.showMessageTimeout = 3000; 76 | this.reasonCtrl = false; 77 | this.reasonAlt = false; 78 | this.reasonMouse = false; 79 | this.reasonTimer = 0; 80 | 81 | } 82 | 83 | LeavingPageChecker.prototype.addEvent = function(obj, evt, fn) { 84 | if (obj.addEventListener) { 85 | obj.addEventListener(evt, fn, false); 86 | } 87 | else if (obj.attachEvent) { 88 | obj.attachEvent("on" + evt, fn); 89 | } 90 | } 91 | 92 | LeavingPageChecker.prototype.noReason = function() { 93 | return ! this.reasonCtrl && ! this.reasonAlt && ! this.reasonMouse && (this.reasonTimer < +(new Date())); 94 | } 95 | 96 | LeavingPageChecker.prototype.showMessage = function() { 97 | this.outerShowMessage(); 98 | this.hideTime = +(new Date) + this.showMessageTimeout; 99 | } 100 | 101 | LeavingPageChecker.prototype.hideMessage = function() { 102 | var self = this; 103 | 104 | if (this.outerIsMessageVisible()) { 105 | if (this.hideTime < +(new Date) || this.noReason()) { 106 | this.outerHideMessage(); 107 | } 108 | } 109 | setTimeout(function(){self.hideMessage();}, 100); 110 | } 111 | 112 | LeavingPageChecker.prototype.run = function() { 113 | 114 | self = this; 115 | 116 | if (self.params.showOnMouseout) { 117 | self.addEvent(document, "mouseout", function(e) { 118 | e = e ? e : window.event; 119 | var from = e.relatedTarget || e.toElement; 120 | if (! from || from.nodeName == "HTML") { 121 | self.reasonMouse = true; 122 | self.showMessage(); 123 | } 124 | }); 125 | 126 | self.addEvent(document.body, "mouseenter", function(e) { 127 | self.reasonMouse = false; 128 | }); 129 | } 130 | 131 | if (self.params.showOnKeydown) { 132 | self.addEvent(document, "keydown", function(e) { 133 | if (e.key === 'Alt' || e.key === 'Control' || e.key === 'F5' ) { 134 | if (e.key === 'Alt') { 135 | self.reasonAlt = true; 136 | } else if (e.key === 'Control') { 137 | self.reasonCtrl = true; 138 | } else if (e.key === 'F5') { 139 | self.reasonTimer = +(new Date()) + self.showMessageTimeout; 140 | } 141 | self.showMessage(); 142 | } 143 | }); 144 | 145 | self.addEvent(document, "keyup", function(e) { 146 | if (e.key === 'Alt' || e.key === 'Control') { 147 | if (e.key === 'Alt') { 148 | self.reasonAlt = false; 149 | } else if (e.key === 'Control') { 150 | self.reasonCtrl = false; 151 | } 152 | } 153 | }); 154 | } 155 | 156 | self.addEvent(window, "beforeunload", function (e) { 157 | if (self.outerCheckLeavingAllowed()) 158 | return null; 159 | 160 | (e || window.event).returnValue = self.message; 161 | return self.message; 162 | }); 163 | 164 | if (self.outerResizeMessageBox) { 165 | self.addEvent(window, "resize", function (e) { 166 | self.outerResizeMessageBox(); 167 | }); 168 | this.outerResizeMessageBox(); 169 | } 170 | 171 | setTimeout(function(){self.hideMessage();}, 100); 172 | } 173 | -------------------------------------------------------------------------------- /smart-contracts/packages/smart-contracts/contracts/VotersRegistry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.4; 2 | 3 | import "./Ownable.sol"; 4 | 5 | 6 | contract VotersRegistry is Ownable { 7 | struct BallotInfo { 8 | bool isBallotIssued; 9 | uint256 ballotIssuedBlock; 10 | } 11 | 12 | struct Voter { 13 | bool isParticipating; 14 | bool isParticipationRevoked; 15 | uint256 paricipationReceivedBlock; 16 | uint256 revocationReceivedBlock; 17 | uint256 firstBallotIssuedBlock; 18 | // votingId => BallotInfo 19 | mapping(uint256 => BallotInfo) issuedBallots; 20 | } 21 | 22 | event VoterParticipating(uint256 voterId); 23 | event VoterRevokedParticipating(uint256 voterId); 24 | event BallotIssued(uint256 voterId, uint256 votingId); 25 | event RegistrationStopped(); 26 | 27 | mapping(uint256 => Voter) private voters; 28 | uint256 private votersCount; 29 | mapping(uint256 => bool) private votingsMap; 30 | uint256[] private votingsList; 31 | mapping(uint256 => uint256) private ballotsIssuedByVoting; 32 | 33 | bool public isRegistrationStopped = false; 34 | 35 | function stopRegistration() external onlyOwner { 36 | require( 37 | isRegistrationStopped == false, 38 | "Can not stop registration twice!"); 39 | 40 | isRegistrationStopped = true; 41 | 42 | emit RegistrationStopped(); 43 | } 44 | 45 | function revokeParticipation(uint256 voterId) external onlyOwner { 46 | require( 47 | voterId != 0, 48 | "Voter Id can not be zero!"); 49 | 50 | require( 51 | voters[voterId].paricipationReceivedBlock != 0, 52 | "Voter must participate!"); 53 | 54 | require( 55 | voters[voterId].revocationReceivedBlock == 0, 56 | "Voter must not revoke earlier!"); 57 | 58 | require( 59 | voters[voterId].firstBallotIssuedBlock == 0, 60 | "Voter can not revoke after Ballot Issue!"); 61 | 62 | voters[voterId].isParticipating = false; 63 | voters[voterId].isParticipationRevoked = true; 64 | voters[voterId].revocationReceivedBlock = block.number; 65 | 66 | votersCount--; 67 | 68 | emit VoterRevokedParticipating(voterId); 69 | } 70 | 71 | function issueBallotFor(uint256 voterId, uint256 votingId) external onlyOwner { 72 | require( 73 | voterId != 0, 74 | "Voter Id can not be zero!"); 75 | 76 | require( 77 | votingId != 0, 78 | "Voting Id can not be zero!"); 79 | 80 | require( 81 | voters[voterId].paricipationReceivedBlock != 0, 82 | "Voter must participate!"); 83 | 84 | require( 85 | voters[voterId].revocationReceivedBlock == 0, 86 | "Voter must not revoke earlier!"); 87 | 88 | require( 89 | voters[voterId].issuedBallots[votingId].ballotIssuedBlock == 0, 90 | "Voter Issue Ballot twice!"); 91 | 92 | voters[voterId].firstBallotIssuedBlock = block.number; 93 | 94 | voters[voterId].issuedBallots[votingId].isBallotIssued = true; 95 | voters[voterId].issuedBallots[votingId].ballotIssuedBlock = block.number; 96 | 97 | if (!votingsMap[votingId]) { 98 | votingsMap[votingId] = true; 99 | votingsList.push(votingId); 100 | } 101 | 102 | ballotsIssuedByVoting[votingId]++; 103 | 104 | emit BallotIssued(voterId, votingId); 105 | } 106 | 107 | function isAnyBallotIssued(uint256 voterId) external view returns (bool) { 108 | return voters[voterId].firstBallotIssuedBlock > 0; 109 | } 110 | 111 | function getVotersCount() external view returns (uint256) { 112 | return votersCount; 113 | } 114 | 115 | function getIssuedBallotsByVotingsCount() external view returns (uint256[] memory, uint256[] memory) { 116 | uint256 votingsAmount = votingsList.length; 117 | uint256[] memory ballotsIssuedCount = new uint256[](votingsAmount); 118 | 119 | for (uint i = 0; i < votingsAmount; i++) { 120 | ballotsIssuedCount[i] = ballotsIssuedByVoting[votingsList[i]]; 121 | } 122 | 123 | return (votingsList, ballotsIssuedCount); 124 | } 125 | 126 | function getParticipationFor(uint256 voterId) external view returns (bool, uint256) { 127 | bool isParticipating = voters[voterId].isParticipating; 128 | uint256 paricipationReceivedBlock = voters[voterId].paricipationReceivedBlock; 129 | 130 | return (isParticipating, paricipationReceivedBlock); 131 | } 132 | 133 | function getRevocationFor(uint256 voterId) external view returns (bool, uint256) { 134 | bool isParticipationRevoked = voters[voterId].isParticipationRevoked; 135 | uint256 revocationReceivedBlock = voters[voterId].revocationReceivedBlock; 136 | 137 | return (isParticipationRevoked, revocationReceivedBlock); 138 | } 139 | 140 | function getBallotFor(uint256 voterId, uint256 votingId) external view returns (bool, uint256) { 141 | bool isBallotIssued = voters[voterId].issuedBallots[votingId].isBallotIssued; 142 | uint256 ballotIssuedBlock = voters[voterId].issuedBallots[votingId].ballotIssuedBlock; 143 | 144 | return (isBallotIssued, ballotIssuedBlock); 145 | } 146 | 147 | function addVoter(uint256 voterId) public onlyOwner { 148 | require( 149 | isRegistrationStopped == false, 150 | "Can not add Voter when registration is closed!"); 151 | 152 | require( 153 | voterId != 0, 154 | "Voter Id can not be zero!"); 155 | 156 | require( 157 | voters[voterId].paricipationReceivedBlock == 0, 158 | "Voter must not participate earlier!"); 159 | 160 | voters[voterId].isParticipating = true; 161 | voters[voterId].paricipationReceivedBlock = block.number; 162 | 163 | votersCount++; 164 | 165 | emit VoterParticipating(voterId); 166 | } 167 | 168 | function addVotersBatch(uint256[] memory votersIds) public onlyOwner { 169 | uint idsAmount = votersIds.length; 170 | 171 | for (uint i = 0; i < idsAmount; i++) { 172 | addVoter(votersIds[i]); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /voting-form/src/forms/forms/mgik/mgd2019/show.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {include file="$base_template_path/std_head_service.tpl" faq="{$CFG_MAIN_HOST}/ru/faq/?subject=$faq" download_app_file_enabled = false has_additional_legals=false} 12 | 13 |
    14 | 15 |
    16 | 17 |
    18 |

    Проверка браузера...

    19 | 20 |
    21 | 22 | 112 | 113 | {include file="$base_template_path/mgik/mgd2019/templates.tpl"} -------------------------------------------------------------------------------- /voting-form/src/ballot/common/module/election/m_election.php: -------------------------------------------------------------------------------- 1 | _config = PoolConfig::me()->conf('Mgik'); 43 | $this->_service = new Service(); 44 | $this->_settings = new Settings(); 45 | 46 | $this->tpl = new smarty_ee_module($this); 47 | $this->tpl->assign('template_path', params::$params['common_data_server'] . 'module_tpl/election/default'); 48 | 49 | try { 50 | $this->_action = $_GET['action'] ?? self::DEFAULT_ACTION; 51 | if (! in_array($this->_action, $this->_actions)) { 52 | $this->_action = self::DEFAULT_ACTION; 53 | } 54 | 55 | $method = "action" . ucfirst($this->_action); 56 | if (method_exists($this, $method)) { 57 | return call_user_func([$this, $method]); 58 | } 59 | 60 | return $this->body; 61 | 62 | } catch (LogicException $e) { 63 | $log = GrayLogger::create('MgicBallotService'); 64 | $log->error($e->getData()['error'] ?? $e->getMessage(), $e->getData()); 65 | 66 | $this->tpl->assign('error_message', $e->getMessage()); 67 | $this->body = $this->tpl->fetch($this->tpl_dir . 'error.tpl'); 68 | 69 | } catch (\Throwable $e) { 70 | $this->tpl->assign('error_message', Service::DEFAULT_ERROR_MESSAGE); 71 | $this->body = $this->tpl->fetch($this->tpl_dir . 'error.tpl'); 72 | } 73 | 74 | return $this->body; 75 | } 76 | 77 | /** 78 | * @throws LogicException 79 | */ 80 | private function actionDefault() 81 | { 82 | $guid = $_REQUEST['guid'] ?? null; 83 | $userData = $this->_service->checkGuid($guid); 84 | 85 | $this->tpl->assign('guid', $guid); 86 | $this->tpl->assign('district', $userData['district']); 87 | 88 | $this->tpl->assign('deputies', $this->_service->getDistrictDeputies($userData['district'])); 89 | $this->tpl->assign('dit_voting', json_encode([ 90 | 'ballotsRegistryAddress' => $this->_settings->get('ballotsRegistryAddress'), 91 | 'modulo' => $this->_settings->get('modulo'), 92 | 'generator' => $this->_settings->get('generator'), 93 | 'publicKey' => $this->_settings->get('publicKey'), 94 | ], JSON_UNESCAPED_UNICODE)); 95 | 96 | $template = $this->_config->get('ballot_template'); 97 | $this->tpl->assign('security', $this->_config->get('security')); 98 | if (! file_exists("{$this->tpl_dir}{$template}.tpl")) { 99 | $template = 'show'; 100 | } 101 | 102 | return $this->tpl->fetch("{$this->tpl_dir}{$template}.tpl"); 103 | } 104 | 105 | /** 106 | * @throws LogicException 107 | */ 108 | private function actionCheck() 109 | { 110 | $guid = $_REQUEST['guid'] ?? null; 111 | $url = $this->_service->checkSign($guid); 112 | 113 | header("Location: $url"); 114 | die(); 115 | } 116 | 117 | private function actionError() 118 | { 119 | $errors = [ 120 | 1 => 'Время истекло.', 121 | 2 => 'Бюллетень уже был отправлен или время истекло.', 122 | ]; 123 | 124 | $code = $_GET['code'] ?? null; 125 | $message = $errors[$code] ?? null; 126 | 127 | if ($code && $message) { 128 | $this->tpl->assign('error_message', $message); 129 | } 130 | 131 | return $this->tpl->fetch($this->tpl_dir . "error.tpl"); 132 | } 133 | 134 | private function actionSuccess() 135 | { 136 | $sessionId = @session_id(); 137 | if ($sessionId) { 138 | $tx = MemoryCache::get("tx|$sessionId"); 139 | $this->tpl->assign('tx', $tx); 140 | } 141 | 142 | $this->tpl->assign('isShowTxResult', $this->_config->get('show_tx_result')); 143 | $this->tpl->assign('mpguUrl', lib::getMpguUrl()); 144 | 145 | return $this->tpl->fetch($this->tpl_dir . "success.tpl"); 146 | } 147 | 148 | /** 149 | * @throws LogicException 150 | */ 151 | private function actionVote() 152 | { 153 | $registryAddress = $_POST['registryAddress'] ?? null; 154 | $guid = $_POST['guid'] ?? null; 155 | $voterAddress = $_POST['voterAddress'] ?? null; 156 | $keyVerificationHash = $_POST['keyVerificationHash'] ?? null; 157 | $votingId = $_POST['votingId'] ?? null; 158 | $tx = $_POST['tx'] ?? null; 159 | 160 | if (! ($guid && $voterAddress && $keyVerificationHash && $votingId && $tx)) { 161 | return $this->sendAjaxResponse('error'); 162 | } 163 | 164 | $data = [ 165 | 'voterAddress' => $voterAddress, 166 | 'keyVerificationHash' => $keyVerificationHash, 167 | 'votingId' => $votingId, 168 | 'tx' => $tx, 169 | ]; 170 | 171 | $vote = $this->_service->sendGuid($guid, $data); 172 | 173 | if (! $vote) { 174 | return $this->sendAjaxResponse(['status' => 'error', 'code' => 2]); 175 | } 176 | 177 | $sessionId = @session_id(); 178 | if ($sessionId) { 179 | MemoryCache::set("tx|$sessionId", $tx); 180 | } 181 | 182 | $request = new TaskVoteRequest(['PGU_USER_ID' => 0], $vote); 183 | $request->addQueueTask(); 184 | 185 | return $this->sendAjaxResponse('success'); 186 | } 187 | 188 | /** 189 | * @param string|array $data 190 | */ 191 | private function sendAjaxResponse($data) 192 | { 193 | if (is_string($data)) { 194 | $data = ['status' => $data]; 195 | } 196 | 197 | header("Content-Type: application/json; charset=utf-8"); 198 | die(json_encode($data,JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE)); 199 | } 200 | 201 | } -------------------------------------------------------------------------------- /voting-form/src/crypt/registr/baseWsInclude.php: -------------------------------------------------------------------------------- 1 | info('swagger', array( 18 | 'errorMessage' => 'Запрос swagger', 19 | 'vers' => $version)); 20 | $file = 'swagger.yml'; 21 | if (file_exists($file)) { 22 | 23 | // сбрасываем буфер вывода PHP, чтобы избежать переполнения памяти выделенной под скрипт 24 | // если этого не сделать файл будет читаться в память полностью! 25 | if (ob_get_level()) { 26 | ob_end_clean(); 27 | } 28 | // заставляем браузер показать окно сохранения файла 29 | header('Content-Description: File Transfer'); 30 | header('Content-Type: application/octet-stream'); 31 | header("Content-Disposition: attachment; filename={$module}_v{$version}_".basename($file)); 32 | header('Content-Transfer-Encoding: binary'); 33 | header('Expires: 0'); 34 | header('Cache-Control: must-revalidate'); 35 | header('Pragma: public'); 36 | header('Content-Length: '.filesize($file)); 37 | // читаем файл и отправляем его пользователю 38 | readfile($file); 39 | exit(); 40 | } 41 | } 42 | 43 | class ws 44 | { 45 | public $errorsMap = array( 46 | 1 => array( 47 | 'Не поддерживаемый метод', 48 | 401) 49 | ); 50 | protected $logger; 51 | protected $module; 52 | protected $version; 53 | protected $postdata; 54 | protected $logData = array(); // массив доп данных для логов 55 | function __construct($client = null) 56 | { 57 | preg_match('|api\/([^\/]+)\/v([0-9\.]+)|', $_SERVER['REQUEST_URI'], $found); 58 | if (!empty($found)) { 59 | $this->module = $found[1]; 60 | $this->version = $found[2]; 61 | } 62 | else { 63 | $this->module = 'api'; 64 | } 65 | if (!empty($client)) { 66 | $this->client = $client; 67 | } 68 | $this->logger = GrayLogger::create('MGD_'.$this->module); 69 | 70 | if ($this->module == 'api'){ 71 | $this->returnError(1,'Что то не так с настройками nginx, нет модуля'); 72 | } 73 | } 74 | 75 | public function returnError($code,$debugMessage=null) 76 | { 77 | 78 | if (isset($this->errorsMap[$code])) { 79 | $message = (string) $this->errorsMap[$code][0]; 80 | if (isset($this->errorsMap[$code][1])) { 81 | $httpcode = (int) $this->errorsMap[$code][1]; 82 | } else { 83 | $httpcode = 400; 84 | } 85 | } else { 86 | $message = 'Произошла непредвиденная ошибка'; 87 | $httpcode = 500; 88 | } 89 | 90 | switch ($httpcode) { 91 | case 400: 92 | $string = "400 Bad Request"; 93 | break; 94 | case 403: 95 | $string = "403 Forbidden"; 96 | break; 97 | case 401: 98 | $string = "401 Unauthorized"; 99 | break; 100 | case 404: 101 | $string = "404 Not Found"; 102 | break; 103 | case 405: 104 | $string = "405 Method Not Allowed"; 105 | break; 106 | case 204: 107 | $string = "204 No Content"; 108 | break; 109 | case 501: 110 | $string = "501 Not Implemented"; 111 | break; 112 | case 500: 113 | default: 114 | $string = "500 Internal Server Error"; 115 | 116 | break; 117 | } 118 | header("HTTP/1.0 {$string}"); 119 | header("Content-Type: application/json; charset=utf-8"); 120 | $mainLogData = array( 121 | 'errorMessage' => $message, 122 | 'errorText' => $debugMessage, 123 | 'errorCode' => $code, 124 | 'version'=>$this->version, 125 | 'action'=>$_REQUEST['action'], 126 | 'jsonRequest' => json_encode($this->postdata, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_SLASHES), 127 | 'corpId' => !empty($this->client['legal']['CORP_ID']) ? $this->client['legal']['CORP_ID'] : $_SERVER['HTTP_LEGALCORPID'], 128 | 'legalId' => !empty($this->client['legal']['ID']) ? $this->client['legal']['ID'] : null, 129 | 'ssoId' => !empty($this->client['user']['USER_ID']) ? $this->client['user']['USER_ID'] : null, 130 | 'system'=>$_SERVER['HTTP_SYSTEM'], 131 | 'token'=>$_SERVER['HTTP_SYSTEM_TOKEN'], 132 | 'sess-id'=> session_id() 133 | ); 134 | if (!empty($this->logData)) { 135 | $mainLogData = array_merge($mainLogData,$this->logData); 136 | } 137 | $this->logger->error($message, $mainLogData); 138 | echo json_encode(array( 139 | 'error' => 1, 140 | 'errorMessage' => $message, 141 | 'code' => $code, 142 | 'debugMessage'=>$this->conf->get('debug',false)?$debugMessage:'' 143 | ), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); 144 | exit; 145 | } 146 | 147 | public function sendOk($response) 148 | { 149 | $response = array( 150 | 'error' => 0, 151 | 'data' => $response); 152 | $data = json_encode($response, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); 153 | $mainLogData = array( 154 | 'jsonResult' => $data, 155 | 'version'=>$this->version, 156 | 'action'=>$_REQUEST['action'], 157 | 'jsonRequest' => json_encode($this->postdata, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 158 | 'corpid' => !empty($this->client['legal']['CORP_ID']) ? $this->client['legal']['CORP_ID'] : $_SERVER['HTTP_LEGALCORPID'], 159 | 'legalid' => !empty($this->client['legal']['ID']) ? $this->client['legal']['ID'] : null, 160 | 'ssoid' => !empty($this->client['user']['USER_ID']) ? $this->client['user']['USER_ID'] : null, 161 | 'system'=>$_SERVER['HTTP_SYSTEM'], 162 | 'sess-id'=> session_id() 163 | //'token'=>$_SERVER['HTTP_SYSTEMTOKEN'] 164 | ); 165 | if (!empty($this->logData)) { 166 | $mainLogData = array_merge($mainLogData,$this->logData); 167 | } 168 | $this->logger->info('Ок', $mainLogData); 169 | header("Content-Type: application/json; charset=utf-8"); 170 | echo $data; 171 | exit(); 172 | } 173 | 174 | public function redirect404($code) 175 | { 176 | header('HTTP/1.0 404 Not Found'); 177 | exit; 178 | } 179 | 180 | public function redirect500($code) 181 | { 182 | header('HTTP/1.0 500 Server Error'); 183 | exit; 184 | } 185 | 186 | public function redirect401($code) 187 | { 188 | header('HTTP/1.0 401 Not Authorized'); 189 | exit; 190 | } 191 | } -------------------------------------------------------------------------------- /voting-form/src/crypt/registr/v1/Oauth.php: -------------------------------------------------------------------------------- 1 | config = $config; 36 | } else { 37 | $this->config = PoolConfig::me()->get('Wsregistr'); 38 | } 39 | $this->logger = GrayLogger::create('MGD_cryptoPro'); 40 | $this->host = $this->config->get('crypt/host'); 41 | $this->consumerKey = $this->config->get('crypt/client'); 42 | $this->login = $this->config->get('crypt/login'); 43 | $this->password = $this->config->get('crypt/password'); 44 | $this->consumerSecret = $this->config->get('crypt/secret'); 45 | $this->backUrl = $this->config->get('crypt/backUrl'); 46 | $this->timeout = $this->config->get('crypt/timeout', 5); 47 | 48 | $this->initToken(); 49 | } 50 | 51 | //функция авторизации токена 52 | protected function initToken() 53 | { 54 | $data = MemoryCache::get('cryptApi'); 55 | if (empty($data) || $data['validTo'] <= time()) { 56 | //получим токен или //нужно рефреш сделать 57 | $data = $this->getToken(); 58 | if (is_array($data)) { 59 | MemoryCache::set('cryptApi', $data, $data['expires_in']); 60 | } 61 | } 62 | $this->access_token = $data['access_token']; 63 | 64 | if (!$this->hasToken()) { 65 | throw new Exception('Нет токена доступа к сервису шифрования', 401); 66 | } 67 | } 68 | 69 | /** 70 | * @return self 71 | */ 72 | public static function me(FileConfig $config = null) 73 | { 74 | if (self::$instance === null) { 75 | self::$instance = new self($config); 76 | } 77 | 78 | return self::$instance; 79 | } 80 | 81 | //функция получения токена 82 | protected function getToken($refresh_token = false, $is_recursive = false) 83 | { 84 | $url = $this->config->get('crypt/oauth'); 85 | $ch = curl_init($url); 86 | # Setup request to send json via POST. 87 | 88 | $params = array( 89 | "grant_type" => 'password', 90 | "username" => $this->getLogin(), 91 | "client_id" => $this->getConsumerKey(), 92 | "resource" => $this->config->get('crypt/resource'), 93 | "password" => $this->getPassword(), 94 | ); 95 | 96 | # Return response instead of printing. 97 | curl_setopt($ch, CURLOPT_POST, 1); 98 | curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params)); 99 | $headers = array( 100 | 'Content-Type: application/x-www-form-urlencoded', 101 | 'Authorization: Basic '.base64_encode($this->getConsumerKey().':'.$this->getConsumerSecret()) 102 | ); 103 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 104 | 105 | # Return response instead of printing. 106 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 107 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); 108 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); 109 | curl_setopt($ch, CURLOPT_TIMEOUT, $this->getTimeout()); 110 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->getTimeout()); 111 | curl_setopt($ch, CURLINFO_HEADER_OUT, true); 112 | # Send request. 113 | $resultJson = curl_exec($ch); 114 | $result = json_decode($resultJson,true); 115 | $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); 116 | 117 | $logData = array( 118 | 'errorCode'=>0, 119 | 'errorMessage'=>'', 120 | 'jsonRequest'=>json_encode($params,JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE), 121 | 'jsonResponse'=>$resultJson, 122 | 'headers'=>$headers, 123 | 'url'=>$url 124 | ); 125 | if (!curl_errno($ch)) { 126 | switch ($http_code) { 127 | case 200: 128 | $result['validTo'] = time() + $result['expires_in']; 129 | $this->logger->info('Токен получен',$logData); 130 | break; 131 | default: 132 | $logData['errorCode'] = $http_code; 133 | $logData['errorMessage'] = $result['error_description']; 134 | $this->logger->error($result['error_description'],$logData); 135 | throw new Exception($result['error_description'],$http_code); 136 | break; 137 | 138 | } 139 | } 140 | if ($ch) { 141 | curl_close($ch); 142 | } 143 | 144 | return $result; 145 | } 146 | 147 | public function hasToken() { 148 | return !empty($this->access_token); 149 | } 150 | 151 | 152 | 153 | 154 | public function requestJson($url, $curlOptions = array()) 155 | { 156 | 157 | //TODO реализовать ограничение по районам 158 | $ch = curl_init($url); 159 | 160 | $curlOptions[CURLOPT_HTTPHEADER] [] = 'Authorization: Bearer '.$this->access_token; 161 | 162 | if (!isset($curlOptions[CURLOPT_TIMEOUT])) { 163 | $curlOptions[CURLOPT_TIMEOUT] = $this->getTimeout(); 164 | } 165 | if (!isset($curlOptions[CURLOPT_CONNECTTIMEOUT])) { 166 | $curlOptions[CURLOPT_CONNECTTIMEOUT] = $this->getTimeout(); 167 | } 168 | 169 | foreach ($curlOptions as $name => $option) { 170 | curl_setopt($ch, $name, $option); 171 | } 172 | 173 | # Return response instead of printing. 174 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 175 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); 176 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); 177 | $response = curl_exec($ch); 178 | 179 | $error = ''; 180 | $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); 181 | if ($http_code !== 200) { 182 | $error = curl_error($ch); 183 | } 184 | curl_close($ch); 185 | 186 | return array( 187 | $response, 188 | $http_code, 189 | $error); 190 | } 191 | 192 | /** 193 | * @return int 194 | */ 195 | public function getTimeout() 196 | { 197 | return $this->timeout; 198 | } 199 | 200 | /** 201 | * @return bool 202 | */ 203 | public function useTokenAutorization() 204 | { 205 | return $this->useTokenAutorization; 206 | } 207 | 208 | /** 209 | * @return string 210 | */ 211 | public function getLogin() 212 | { 213 | return $this->login; 214 | } 215 | 216 | /** 217 | * @return string 218 | */ 219 | public function getPassword() 220 | { 221 | return $this->password; 222 | } 223 | 224 | /** 225 | * @return string 226 | */ 227 | public function getConsumerKey() 228 | { 229 | return $this->consumerKey; 230 | } 231 | 232 | /** 233 | * @return string 234 | */ 235 | public function getConsumerSecret() 236 | { 237 | return $this->consumerSecret; 238 | } 239 | 240 | } -------------------------------------------------------------------------------- /voting-form/src/ballot/static/js/forms/main.min.js: -------------------------------------------------------------------------------- 1 | // console.log('hello mos.ru!!!'); 2 | 3 | $(function () { 4 | var field_over = false; 5 | 6 | $('.hint').hover(function () { 7 | $(this).addClass('hint--visible'); 8 | field_over = true; 9 | }, function () { 10 | field_over = false 11 | setTimeout(() => { 12 | if (!field_over) { 13 | $(this).removeClass('hint--visible'); 14 | } 15 | }, 100); 16 | } 17 | ); 18 | 19 | $('.field__input').focus(function () { 20 | var $field = $(this).closest('.field'); 21 | $field.addClass('field--focused'); 22 | }); 23 | 24 | $('.field__input').blur(function () { 25 | var $field = $(this).closest('.field'); 26 | 27 | $field.removeClass('field--focused'); 28 | $(this).prop('readonly', 'readonly'); 29 | }); 30 | 31 | $('.field__input').on('keyup change', function () { 32 | var $field = $(this).closest('.field'); 33 | 34 | if ($.trim($(this).val()) === '') { 35 | $field.removeClass('field--filled'); 36 | } else { 37 | $field.addClass('field--filled'); 38 | } 39 | }); 40 | 41 | $('.field__input').on('click', function () { 42 | $(this).prop('readonly', ''); 43 | $(this).focus(); 44 | }); 45 | 46 | $('.field__clear').click(function () { 47 | var $field = $(this).closest('.field'); 48 | var $input = $field.find('.field__input'); 49 | $field.removeClass('field--filled'); 50 | $input.val(''); 51 | }); 52 | 53 | $('.AboutService__toggle-btn').click(function (e) { 54 | var $toggle = $(this).closest('.AboutService__toggle'); 55 | var $el = $toggle.closest('.AboutService'); 56 | var $content = $el.find('.MosCollapse__content'); 57 | var $contentHeight = $content.find('.MosCollapse__contentBox').outerHeight(); 58 | var $icon = $toggle.find('.AboutService__toggle-icon'); 59 | var $iconInfoContent = ''; 60 | var $iconCloseContent = '' 61 | 62 | if ($toggle.hasClass('active')) { 63 | $el.removeClass('active'); 64 | $toggle.removeClass('active'); 65 | $icon 66 | .removeClass('TooltipClose') 67 | .addClass('TooltipInfo') 68 | .html($iconInfoContent); 69 | $content.css('height', 0); 70 | 71 | setTimeout(() => { 72 | $content 73 | .removeClass('MosCollapse__content_active') 74 | .addClass('MosCollapse__content_inactive'); 75 | }, 350); 76 | } else { 77 | $el.addClass('active'); 78 | $toggle.addClass('active'); 79 | $icon 80 | .removeClass('TooltipInfo') 81 | .addClass('TooltipClose') 82 | .html($iconCloseContent); 83 | $content.css('height', $contentHeight + 'px'); 84 | 85 | setTimeout(() => { 86 | $content 87 | .removeClass('MosCollapse__content_inactive') 88 | .addClass('MosCollapse__content_active'); 89 | }, 350); 90 | } 91 | }); 92 | 93 | $('.field__input').on('autocompletechange', function (event, ui) { 94 | $(event.currentTarget).trigger('change'); 95 | }); 96 | 97 | $('.js-datepicker').datepicker({ 98 | changeMonth: true, 99 | changeYear: true, 100 | }); 101 | 102 | 103 | 104 | 105 | initSelects(); 106 | }); 107 | 108 | function initSelects() { 109 | function setSelectFieldLabel(select) { 110 | var field = select.closest('div.field'); 111 | var labelText = select.attr('data-label'); 112 | var labelTpl = ''; 115 | 116 | if (field.find('.field__label').length === 0) { 117 | field.append(labelTpl); 118 | } 119 | } 120 | 121 | $('.js-select').selectpicker({ 122 | styleBase: 'form-control field__input', 123 | style: '', 124 | dropupAuto: false, 125 | noneSelectedText: '', 126 | noneResultsText: 'Не найдено {0}' 127 | }); 128 | 129 | $('.js-select').on('loaded.bs.select', function (e, clickedIndex, isSelected, previousValue) { 130 | setSelectFieldLabel($(e.target)); 131 | }); 132 | 133 | $('.js-select').on('show.bs.select', function (e, clickedIndex, isSelected, previousValue) { 134 | var selectEl = $(e.target); 135 | var fieldName = selectEl.attr('name'); 136 | var field = selectEl.closest('div.field'); 137 | var attrMultiple = selectEl.attr('multiple'); 138 | var attrLiveSearch = selectEl.attr('data-live-search'); 139 | 140 | field.addClass('field--focused'); 141 | 142 | if (typeof attrLiveSearch !== 'undefined' && attrLiveSearch === 'true' && field.find('.field__clear').length === 0) { 143 | var clearTpl = $('
    '); 144 | field.append(clearTpl); 145 | field.find(clearTpl).click(function (e) { 146 | selectEl.val('default'); 147 | selectEl.selectpicker('refresh'); 148 | }); 149 | } 150 | 151 | if (typeof attrMultiple !== 'undefined') { 152 | field.find('.dropdown-menu.inner .dropdown-item').each(function (index, element) { 153 | var item = $(element); 154 | var id = fieldName + '_' + index; 155 | var text = item.attr('data-text') || item.find('.text').text(); 156 | var checkboxTpl = '
    ' + 157 | '' + 158 | '' + 159 | '
    '; 160 | 161 | item.attr('data-text', text); 162 | item.html(checkboxTpl); 163 | if (item.hasClass('selected')) { 164 | item.find('input[type="checkbox"]').prop('checked', true); 165 | } 166 | }); 167 | } 168 | }); 169 | 170 | 171 | $('.js-select').on('hide.bs.select', function (e, clickedIndex, isSelected, previousValue) { 172 | var selectEl = $(e.target); 173 | var field = selectEl.closest('div.field'); 174 | 175 | if (selectEl.val() === '') { 176 | field.removeClass('field--filled'); 177 | } 178 | 179 | field.removeClass('field--focused'); 180 | }); 181 | 182 | $('.js-select').on('changed.bs.select', function (e, clickedIndex, isSelected, previousValue) { 183 | var selectEl = $(e.target); 184 | var field = selectEl.closest('div.field'); 185 | 186 | if (selectEl.val() !== '') { 187 | field.addClass('field--filled'); 188 | } 189 | 190 | if (clickedIndex) { 191 | field.find('.dropdown-menu.inner li:nth-child(' + (clickedIndex) + ') input[type="checkbox"]').prop('checked', isSelected); 192 | } 193 | }); 194 | 195 | $('.js-select').on('shown.bs.select', function (e, clickedIndex, isSelected, previousValue) { 196 | var selectEl = $(e.target); 197 | var field = selectEl.closest('div.field'); 198 | 199 | field.find('div.inner').scroll(function () { 200 | if ($(this).scrollTop() === 0) { 201 | field.removeClass('is-dropdown-list-scrolled'); 202 | } else { 203 | field.addClass('is-dropdown-list-scrolled'); 204 | } 205 | }); 206 | 207 | // Подстановка значений при навигации через клавиатуру 208 | field.find('.bs-searchbox .form-control').on('keyup.bs.dropdown.data-api', function (e) { 209 | 210 | if (e.keyCode == 38 || e.keyCode == 40) { 211 | field.find('.form-control').val(field.find('li.active .text').text()) 212 | field.addClass('field--filled'); 213 | } 214 | }); 215 | }); 216 | 217 | $(document).on('click', '.field-select .field__clear', function (e) { 218 | var field = $(e.target).closest('.field') 219 | var selectEl = field.find('select'); 220 | selectEl.val('default'); 221 | selectEl.selectpicker('refresh'); 222 | field.removeClass('field--filled'); 223 | }); 224 | } --------------------------------------------------------------------------------